Giustino Esposito commited on
Commit
d5ccf60
·
1 Parent(s): d904730

Refactored code

Browse files
app_for_submission.py CHANGED
@@ -3,7 +3,7 @@ import gradio as gr
3
  import requests
4
  import inspect
5
  import pandas as pd
6
- from app import alfred
7
  from langfuse.callback import CallbackHandler
8
  from typing import Optional
9
  from langchain_core.messages import AnyMessage, HumanMessage, AIMessage
@@ -60,7 +60,7 @@ def run_and_submit_all( profile: Optional[gr.OAuthProfile]):
60
 
61
  # 1. Instantiate Agent ( modify this part to create your agent)
62
  try:
63
- agent = alfred
64
  except Exception as e:
65
  print(f"Error instantiating agent: {e}")
66
  return f"Error initializing agent: {e}", None
@@ -106,7 +106,7 @@ def run_and_submit_all( profile: Optional[gr.OAuthProfile]):
106
  messages = HumanMessage(content=question_text + " Path: files/" + file_name)
107
  else:
108
  messages = HumanMessage(content=question_text)
109
- submitted_answer = alfred.invoke(input={"messages": messages}, config={"callbacks": [langfuse_handler]})
110
  answers_payload.append({
111
  "task_id": task_id,
112
  "submitted_answer": submitted_answer['messages'][-1].content[-1]
 
3
  import requests
4
  import inspect
5
  import pandas as pd
6
+ from graph.graph_builder import graph
7
  from langfuse.callback import CallbackHandler
8
  from typing import Optional
9
  from langchain_core.messages import AnyMessage, HumanMessage, AIMessage
 
60
 
61
  # 1. Instantiate Agent ( modify this part to create your agent)
62
  try:
63
+ agent = graph
64
  except Exception as e:
65
  print(f"Error instantiating agent: {e}")
66
  return f"Error initializing agent: {e}", None
 
106
  messages = HumanMessage(content=question_text + " Path: files/" + file_name)
107
  else:
108
  messages = HumanMessage(content=question_text)
109
+ submitted_answer = graph.invoke(input={"messages": messages}, config={"callbacks": [langfuse_handler]})
110
  answers_payload.append({
111
  "task_id": task_id,
112
  "submitted_answer": submitted_answer['messages'][-1].content[-1]
graph/graph_builder.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langgraph.graph import START, StateGraph
2
+ from langgraph.prebuilt import tools_condition
3
+ from langgraph.prebuilt import ToolNode
4
+ from nodes.core import assistant, tools
5
+ from states.state import AgentState
6
+
7
+ ## The graph
8
+ builder = StateGraph(AgentState)
9
+
10
+ # Define nodes: these do the work
11
+ builder.add_node("assistant", assistant)
12
+ builder.add_node("tools", ToolNode(tools))
13
+
14
+ # Define edges: these determine how the control flow moves
15
+ builder.add_edge(START, "assistant")
16
+ builder.add_conditional_edges(
17
+ "assistant",
18
+ # If the latest message requires a tool, route to tools
19
+ # Otherwise, provide a direct response
20
+ tools_condition,
21
+ )
22
+ builder.add_edge("tools", "assistant")
23
+ graph = builder.compile()
math_tools.py DELETED
@@ -1,44 +0,0 @@
1
- from langchain.tools import Tool
2
- import operator
3
-
4
- def add(a: float, b: float) -> float:
5
- """Adds two numbers."""
6
- return operator.add(a, b)
7
-
8
- def subtract(a: float, b: float) -> float:
9
- """Subtracts the second number from the first."""
10
- return operator.sub(a, b)
11
-
12
- def multiply(a: float, b: float) -> float:
13
- """Multiplies two numbers."""
14
- return operator.mul(a, b)
15
-
16
- def divide(a: float, b: float) -> float:
17
- """Divides the first number by the second. Returns an error message if division by zero."""
18
- if b == 0:
19
- return "Error: Cannot divide by zero."
20
- return operator.truediv(a, b)
21
-
22
- add_tool = Tool(
23
- name="calculator_add",
24
- func=add,
25
- description="Adds two numbers. Input should be two numbers (a, b)."
26
- )
27
-
28
- subtract_tool = Tool(
29
- name="calculator_subtract",
30
- func=subtract,
31
- description="Subtracts the second number from the first. Input should be two numbers (a, b)."
32
- )
33
-
34
- multiply_tool = Tool(
35
- name="calculator_multiply",
36
- func=multiply,
37
- description="Multiplies two numbers. Input should be two numbers (a, b)."
38
- )
39
-
40
- divide_tool = Tool(
41
- name="calculator_divide",
42
- func=divide,
43
- description="Divides the first number by the second. Input should be two numbers (a, b)."
44
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
nodes/core.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from states.state import AgentState
2
+ import os
3
+ # Import the load_dotenv function from the dotenv library
4
+ from dotenv import load_dotenv
5
+ from langchain_google_genai import ChatGoogleGenerativeAI
6
+ from tools.multimodal_tools import extract_text, analyze_image_tool, analyze_audio_tool
7
+ from tools.math_tools import add, subtract, multiply, divide
8
+ from tools.search_tools import search_tool, serpapi_search
9
+ from tools.youtube_tools import extract_youtube_transcript
10
+ from langfuse.callback import CallbackHandler
11
+
12
+ load_dotenv()
13
+
14
+ # Read your API key from the environment variable or set it manually
15
+ api_key = os.getenv("GEMINI_API_KEY")
16
+ langfuse_secret_key = os.getenv("LANGFUSE_SECRET_KEY")
17
+ langfuse_public_key = os.getenv("LANGFUSE_PUBLIC_KEY")
18
+
19
+ # Initialize Langfuse CallbackHandler for LangGraph/Langchain (tracing)
20
+ langfuse_handler = CallbackHandler(
21
+ public_key=langfuse_public_key,
22
+ secret_key=langfuse_secret_key,
23
+ host="http://localhost:3000"
24
+ )
25
+
26
+ chat = ChatGoogleGenerativeAI(
27
+ model= "gemini-2.5-pro-preview-05-06",
28
+ temperature=0,
29
+ max_retries=2,
30
+ google_api_key=api_key,
31
+ thinking_budget= 0
32
+ )
33
+
34
+ tools = [
35
+ extract_text,
36
+ analyze_image_tool,
37
+ analyze_audio_tool,
38
+ extract_youtube_transcript,
39
+ add,
40
+ subtract,
41
+ multiply,
42
+ divide,
43
+ search_tool
44
+ ]
45
+
46
+ chat_with_tools = chat.bind_tools(tools)
47
+
48
+ def assistant(state: AgentState):
49
+ sys_msg = "You are a helpful assistant with access to tools. Understand user requests accurately. Use your tools when needed to answer effectively. Strictly follow all user instructions and constraints." \
50
+ "Pay attention: your output needs to contain only the final answer without any reasoning since it will be strictly evaluated against a dataset which contains only the specific response." \
51
+ "Your final output needs to be just the string or integer containing the answer, not an array or technical stuff."
52
+ return {
53
+ "messages": [chat_with_tools.invoke([sys_msg] + state["messages"])]
54
+ }
states/state.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ from typing import TypedDict, Annotated
2
+ from langchain_core.messages import AnyMessage
3
+ from langgraph.graph.message import add_messages
4
+
5
+
6
+ class AgentState(TypedDict):
7
+ messages: Annotated[list[AnyMessage], add_messages]
tools.py DELETED
@@ -1,69 +0,0 @@
1
- from langchain.tools import Tool
2
- from youtube_transcript_api import YouTubeTranscriptApi, NoTranscriptFound, TranscriptsDisabled
3
- import operator
4
-
5
-
6
- def extract_youtube_transcript(youtube_url: str) -> str:
7
- """
8
- Extracts the transcript from a given YouTube video URL.
9
- Returns the transcript as a single string or an error message if not found.
10
- """
11
- try:
12
- video_id = youtube_url.split("v=")[1].split("&")[0]
13
- transcript_list = YouTubeTranscriptApi.get_transcript(video_id)
14
- transcript = " ".join([item['text'] for item in transcript_list])
15
- return transcript
16
- except NoTranscriptFound:
17
- return "Error: No transcript found for this video. It might be disabled or not available in English."
18
- except TranscriptsDisabled:
19
- return "Error: Transcripts are disabled for this video."
20
- except Exception as e:
21
- return f"Error extracting transcript: {str(e)}"
22
-
23
- youtube_transcript_tool = Tool(
24
- name="youtube_transcript_extractor",
25
- func=extract_youtube_transcript,
26
- description="Extracts the full transcript from a YouTube video given its URL. Input should be a valid YouTube video URL."
27
- )
28
-
29
- def add(a: float, b: float) -> float:
30
- """Adds two numbers."""
31
- return operator.add(a, b)
32
-
33
- def subtract(a: float, b: float) -> float:
34
- """Subtracts the second number from the first."""
35
- return operator.sub(a, b)
36
-
37
- def multiply(a: float, b: float) -> float:
38
- """Multiplies two numbers."""
39
- return operator.mul(a, b)
40
-
41
- def divide(a: float, b: float) -> float:
42
- """Divides the first number by the second. Returns an error message if division by zero."""
43
- if b == 0:
44
- return "Error: Cannot divide by zero."
45
- return operator.truediv(a, b)
46
-
47
- add_tool = Tool(
48
- name="calculator_add",
49
- func=add,
50
- description="Adds two numbers. Input should be two numbers (a, b)."
51
- )
52
-
53
- subtract_tool = Tool(
54
- name="calculator_subtract",
55
- func=subtract,
56
- description="Subtracts the second number from the first. Input should be two numbers (a, b)."
57
- )
58
-
59
- multiply_tool = Tool(
60
- name="calculator_multiply",
61
- func=multiply,
62
- description="Multiplies two numbers. Input should be two numbers (a, b)."
63
- )
64
-
65
- divide_tool = Tool(
66
- name="calculator_divide",
67
- func=divide,
68
- description="Divides the first number by the second. Input should be two numbers (a, b)."
69
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
tools/math_tools.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_core.tools import tool
2
+ import operator
3
+
4
+ @tool("add_tool", parse_docstring=True)
5
+ def add(a: float, b: float) -> float:
6
+ """Adds two numbers.
7
+
8
+ Args:
9
+ a (float): The first number.
10
+ b (float): The second number.
11
+
12
+ Returns:
13
+ float: The sum of a and b.
14
+ """
15
+ return operator.add(a, b)
16
+
17
+ @tool("subtract_tool", parse_docstring=True)
18
+ def subtract(a: float, b: float) -> float:
19
+ """Subtracts the second number from the first.
20
+
21
+ Args:
22
+ a (float): The first number (minuend).
23
+ b (float): The second number (subtrahend).
24
+
25
+ Returns:
26
+ float: The result of subtracting b from a.
27
+ """
28
+ return operator.sub(a, b)
29
+
30
+ @tool("multiply_tool", parse_docstring=True)
31
+ def multiply(a: float, b: float) -> float:
32
+ """Multiplies two numbers.
33
+
34
+ Args:
35
+ a (float): The first number.
36
+ b (float): The second number.
37
+
38
+ Returns:
39
+ float: The product of a and b.
40
+ """
41
+ return operator.mul(a, b)
42
+
43
+ @tool("divide_tool", parse_docstring=True)
44
+ def divide(a: float, b: float) -> float:
45
+ """Divides the first number by the second.
46
+
47
+ Args:
48
+ a (float): The numerator.
49
+ b (float): The denominator.
50
+
51
+ Returns:
52
+ float: The result of dividing a by b.
53
+ Returns an error message string if division by zero occurs.
54
+ """
55
+ if b == 0:
56
+ return "Error: Cannot divide by zero."
57
+ return operator.truediv(a, b)
multimodal_tools.py → tools/multimodal_tools.py RENAMED
@@ -15,10 +15,15 @@ vision_llm = ChatGoogleGenerativeAI(
15
  google_api_key=api_key
16
  )
17
 
 
18
  def extract_text(img_path: str) -> str:
19
- """
20
- Extract text from an image file using a multimodal model.
21
- Input needs to be the path of the image.
 
 
 
 
22
  """
23
  all_text = ""
24
  try:
@@ -64,12 +69,14 @@ def extract_text(img_path: str) -> str:
64
 
65
  @tool("analyze_image_tool", parse_docstring=True)
66
  def analyze_image_tool(user_query: str, img_path: str) -> str:
67
- """
68
- Answer the question reasoning on the image.
69
 
70
  Args:
71
- user_query (str): The question to be answered.
72
- img_path (str): Path to the image file.
 
 
 
73
  """
74
  all_text = ""
75
  try:
@@ -114,12 +121,14 @@ def analyze_image_tool(user_query: str, img_path: str) -> str:
114
 
115
  @tool("analyze_audio_tool", parse_docstring=True)
116
  def analyze_audio_tool(user_query: str, audio_path: str) -> str:
117
- """
118
- Answer the question by reasoning on the provided audio file.
119
 
120
  Args:
121
- user_query (str): The question to be answered.
122
  audio_path (str): Path to the audio file (e.g., .mp3, .wav, .flac, .aac, .ogg).
 
 
 
123
  """
124
  try:
125
  # Determine MIME type from file extension
@@ -165,10 +174,4 @@ def analyze_audio_tool(user_query: str, audio_path: str) -> str:
165
  except Exception as e:
166
  error_msg = f"Error analyzing audio: {str(e)}"
167
  print(error_msg)
168
- return ""
169
-
170
- extract_text_tool = Tool(
171
- name="extract_text_tool",
172
- func=extract_text,
173
- description="Extract text from an image file using a multimodal model."
174
- )
 
15
  google_api_key=api_key
16
  )
17
 
18
+ @tool("extract_text_tool", parse_docstring=True)
19
  def extract_text(img_path: str) -> str:
20
+ """Extract text from an image file using a multimodal model.
21
+
22
+ Args:
23
+ img_path (str): The path to the image file from which to extract text.
24
+
25
+ Returns:
26
+ str: The extracted text from the image, or an empty string if an error occurs.
27
  """
28
  all_text = ""
29
  try:
 
69
 
70
  @tool("analyze_image_tool", parse_docstring=True)
71
  def analyze_image_tool(user_query: str, img_path: str) -> str:
72
+ """Answer the question reasoning on the image.
 
73
 
74
  Args:
75
+ user_query (str): The question to be answered based on the image.
76
+ img_path (str): Path to the image file to be analyzed.
77
+
78
+ Returns:
79
+ str: The answer to the query based on image content, or an empty string if an error occurs.
80
  """
81
  all_text = ""
82
  try:
 
121
 
122
  @tool("analyze_audio_tool", parse_docstring=True)
123
  def analyze_audio_tool(user_query: str, audio_path: str) -> str:
124
+ """Answer the question by reasoning on the provided audio file.
 
125
 
126
  Args:
127
+ user_query (str): The question to be answered based on the audio content.
128
  audio_path (str): Path to the audio file (e.g., .mp3, .wav, .flac, .aac, .ogg).
129
+
130
+ Returns:
131
+ str: The answer to the query based on audio content, or an error message/empty string if an error occurs.
132
  """
133
  try:
134
  # Determine MIME type from file extension
 
174
  except Exception as e:
175
  error_msg = f"Error analyzing audio: {str(e)}"
176
  print(error_msg)
177
+ return ""
 
 
 
 
 
 
serpapi_tools.py → tools/search_tools.py RENAMED
@@ -2,13 +2,22 @@ import os
2
  from langchain.tools import Tool
3
  from serpapi import GoogleSearch
4
  from dotenv import load_dotenv
 
 
5
 
6
  # Carica le variabili d'ambiente se hai la chiave API in un file .env
7
  load_dotenv()
8
 
9
  SERPAPI_API_KEY = os.getenv("SERPAPI_API_KEY")
10
 
11
- def _serpapi_search(query: str, num_results: int = 5, gl: str = "it", hl: str = "it") -> str:
 
 
 
 
 
 
 
12
  """
13
  Esegue una ricerca sul web utilizzando SerpAPI con Google Search e restituisce i risultati formattati.
14
  Questo tool ha un costo elevato, pertanto sono da preferire altri tool se disponibili.
@@ -42,12 +51,4 @@ def _serpapi_search(query: str, num_results: int = 5, gl: str = "it", hl: str =
42
  return f"Nessun risultato trovato per '{query}'."
43
 
44
  formatted_results = "\n\n".join([f"Title: {res.get('title')}\nLink: {res.get('link')}\nSnippet: {res.get('snippet')}" for res in organic_results])
45
- return formatted_results
46
-
47
- serpapi_search_tool = Tool(
48
- name="serpapi_web_search",
49
- func=_serpapi_search,
50
- description="Esegue una ricerca sul web utilizzando SerpAPI (Google Search) per trovare informazioni aggiornate. L'input dovrebbe essere la query di ricerca." \
51
- " Questo tool ha un costo elevato, pertanto sono da preferire altri tool se disponibili. \
52
- Richiamare questo tool soltanto in caso gli altri tool non siano stati soddisfacenti."
53
- )
 
2
  from langchain.tools import Tool
3
  from serpapi import GoogleSearch
4
  from dotenv import load_dotenv
5
+ from langchain_community.tools.tavily_search import TavilySearchResults
6
+ from langchain_core.tools import tool
7
 
8
  # Carica le variabili d'ambiente se hai la chiave API in un file .env
9
  load_dotenv()
10
 
11
  SERPAPI_API_KEY = os.getenv("SERPAPI_API_KEY")
12
 
13
+ search_tool = TavilySearchResults(
14
+ name="tavily_web_search", # Puoi personalizzare il nome se vuoi
15
+ description="Esegue una ricerca web avanzata utilizzando Tavily per informazioni aggiornate e complete. Utile per domande complesse o che richiedono dati recenti. Può essere utile fare più ricerche modificando la query per ottenere risultati migliori.", # Descrizione per l'LLM
16
+ max_results=5
17
+ )
18
+
19
+ @tool("serpapi_search_tool", parse_docstring=True)
20
+ def serpapi_search(query: str, num_results: int = 5, gl: str = "it", hl: str = "it") -> str:
21
  """
22
  Esegue una ricerca sul web utilizzando SerpAPI con Google Search e restituisce i risultati formattati.
23
  Questo tool ha un costo elevato, pertanto sono da preferire altri tool se disponibili.
 
51
  return f"Nessun risultato trovato per '{query}'."
52
 
53
  formatted_results = "\n\n".join([f"Title: {res.get('title')}\nLink: {res.get('link')}\nSnippet: {res.get('snippet')}" for res in organic_results])
54
+ return formatted_results
 
 
 
 
 
 
 
 
youtube_tools.py → tools/youtube_tools.py RENAMED
@@ -1,10 +1,16 @@
1
- from langchain.tools import Tool
2
  from youtube_transcript_api import YouTubeTranscriptApi, NoTranscriptFound, TranscriptsDisabled
3
 
 
4
  def extract_youtube_transcript(youtube_url: str) -> str:
5
- """
6
- Extracts the transcript from a given YouTube video URL.
7
- Returns the transcript as a single string or an error message if not found.
 
 
 
 
 
8
  """
9
  try:
10
  video_id = youtube_url.split("v=")[1].split("&")[0]
@@ -17,9 +23,3 @@ def extract_youtube_transcript(youtube_url: str) -> str:
17
  return "Error: Transcripts are disabled for this video."
18
  except Exception as e:
19
  return f"Error extracting transcript: {str(e)}"
20
-
21
- youtube_transcript_tool = Tool(
22
- name="youtube_transcript_extractor",
23
- func=extract_youtube_transcript,
24
- description="Extracts the full transcript from a YouTube video given its URL. Input should be a valid YouTube video URL."
25
- )
 
1
+ from langchain_core.tools import tool
2
  from youtube_transcript_api import YouTubeTranscriptApi, NoTranscriptFound, TranscriptsDisabled
3
 
4
+ @tool("youtube_transcript_extractor", parse_docstring=True)
5
  def extract_youtube_transcript(youtube_url: str) -> str:
6
+ """Extracts the transcript from a given YouTube video URL.
7
+
8
+ Args:
9
+ youtube_url (str): The URL of the YouTube video.
10
+
11
+ Returns:
12
+ str: The transcript as a single string, or an error message if the transcript
13
+ cannot be found or an error occurs.
14
  """
15
  try:
16
  video_id = youtube_url.split("v=")[1].split("&")[0]
 
23
  return "Error: Transcripts are disabled for this video."
24
  except Exception as e:
25
  return f"Error extracting transcript: {str(e)}"