Spaces:
Runtime error
Runtime error
Commit
·
731825e
1
Parent(s):
6a809e4
Added comments
Browse files- app.py +30 -21
- tools/answer_excel.py +8 -4
- tools/answer_question.py +9 -3
- tools/answer_question_from_file.py +6 -3
- tools/audio_tool.py +6 -3
- tools/chess_tool.py +11 -5
- tools/code_exec.py +6 -5
- tools/download_file.py +0 -2
- tools/fetch_web_page.py +5 -2
- tools/web_search.py +9 -30
- tools/wikipedia.py +13 -13
- tools/youtube_transcript.py +3 -0
app.py
CHANGED
@@ -1,3 +1,4 @@
|
|
|
|
1 |
import os
|
2 |
import gradio as gr
|
3 |
import requests
|
@@ -41,6 +42,7 @@ from tools.fetch_web_page import FetchWebPageTool
|
|
41 |
load_dotenv(".env", override=True)
|
42 |
BRAVE_API_KEY = os.getenv("BRAVE_API")
|
43 |
|
|
|
44 |
class State(TypedDict):
|
45 |
file_path : str
|
46 |
file: Optional[str]
|
@@ -55,8 +57,10 @@ class State(TypedDict):
|
|
55 |
DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
|
56 |
|
57 |
# --- Basic Agent Definition ---
|
|
|
58 |
class BasicAgent:
|
59 |
-
def __init__(self):
|
|
|
60 |
tools = [CodeGenTool(), PythonExecutionTool(temp_dir="./"), YoutubeTranscriptTool(),
|
61 |
AnswerQuestionFromFileTool(), AnswerQuestionTool(), DownloadFile(),
|
62 |
ReverseString(), WebSearchTool(), WikipediaTool(), AnswerExcelTool(), ChessTool(), AudioTool(), FetchWebPageTool()]
|
@@ -64,12 +68,12 @@ class BasicAgent:
|
|
64 |
#llm = ChatGoogleGenerativeAI(
|
65 |
# model="gemini-2.0-flash",
|
66 |
# temperature=0)
|
67 |
-
llm = ChatOpenAI(model="gpt-4.1-mini", temperature=0)
|
68 |
self.llm_with_tools = llm.bind_tools(tools)
|
69 |
|
70 |
-
builder = StateGraph(State)
|
71 |
|
72 |
-
builder.add_node("assistant", self.assistant)
|
73 |
builder.add_node("tools", ToolNode(tools))
|
74 |
builder.add_node("final_answer", BasicAgent.final_answer)
|
75 |
#builder.add_node("download_file", BasicAgent.download_file_node)
|
@@ -84,8 +88,8 @@ class BasicAgent:
|
|
84 |
#builder.add_edge("parse_img", "assistant")
|
85 |
#builder.add_edge("parse_pdf", "assistant")
|
86 |
#builder.add_edge("parse_audio", "assistant")
|
87 |
-
builder.add_conditional_edges(
|
88 |
-
"assistant",
|
89 |
tools_condition,
|
90 |
path_map={
|
91 |
"tools": "tools",
|
@@ -93,29 +97,31 @@ class BasicAgent:
|
|
93 |
}
|
94 |
)
|
95 |
|
96 |
-
builder.add_edge("tools", "assistant")
|
97 |
builder.add_edge("final_answer", END)
|
98 |
|
99 |
-
self.react_graph = builder.compile()
|
100 |
|
101 |
|
102 |
def __call__(self, question: str, task_id: str, file_name: Optional[str]) -> str:
|
|
|
103 |
print(f"Agent received question (first 50 chars): {question[:50]}...")
|
104 |
|
105 |
-
messages = [HumanMessage(question)]
|
106 |
-
messages = self.react_graph.invoke({"messages": messages, "file_path": file_name, "question": question})
|
107 |
|
108 |
-
with open(f'messages_{task_id}.txt', 'w', encoding='utf-8') as out:
|
109 |
with redirect_stdout(out):
|
110 |
for m in messages['messages']:
|
111 |
m.pretty_print()
|
112 |
|
113 |
-
final_answer = messages["messages"][-1].content.strip()
|
114 |
print(f"Final answer is {final_answer}")
|
115 |
return final_answer
|
116 |
|
117 |
|
118 |
def assistant(self, state: State):
|
|
|
119 |
if state["file_path"]:
|
120 |
file_name = state["file_path"].split(".")[0]
|
121 |
file_extension = state["file_path"].split(".")[1]
|
@@ -123,7 +129,7 @@ class BasicAgent:
|
|
123 |
file_extension = None
|
124 |
file_name = None
|
125 |
|
126 |
-
prompt = f"""
|
127 |
You are a general AI assistant. When I ask you a question:
|
128 |
|
129 |
Share your reasoning process clearly.
|
@@ -158,12 +164,13 @@ class BasicAgent:
|
|
158 |
Do **NOT** include the file extension in the URL and send WITHOUT MODIFICATION.
|
159 |
"""
|
160 |
|
161 |
-
sys_msg = SystemMessage(content=prompt)
|
162 |
|
163 |
-
time.sleep(40)
|
164 |
return {"messages": [self.llm_with_tools.invoke([sys_msg] + state["messages"])]}
|
165 |
|
166 |
def final_answer(state: State):
|
|
|
167 |
system_prompt = f"""
|
168 |
You will be given an answer and a question. You MUST remove EVERYTHING not needed from the answer and answer the question exactly without reporting "FINAL ANSWER".
|
169 |
That is if you are being asked the number of something, you must not return the thought process, but just the number X.
|
@@ -245,7 +252,7 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
|
|
245 |
print(f"An unexpected error occurred fetching questions: {e}")
|
246 |
return f"An unexpected error occurred fetching questions: {e}", None
|
247 |
|
248 |
-
#
|
249 |
results_log = []
|
250 |
answers_payload = []
|
251 |
print(f"Running agent on {len(questions_data)} questions...")
|
@@ -270,12 +277,13 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
|
|
270 |
return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
|
271 |
|
272 |
# 4. Prepare Submission
|
273 |
-
submission_data = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload}
|
274 |
status_update = f"Agent finished. Submitting {len(answers_payload)} answers for user '{username}'..."
|
275 |
print(status_update)
|
276 |
|
277 |
# 5. Submit
|
278 |
print(f"Submitting {len(answers_payload)} answers to: {submit_url}")
|
|
|
279 |
try:
|
280 |
response = requests.post(submit_url, json=submission_data, timeout=60)
|
281 |
response.raise_for_status()
|
@@ -318,12 +326,12 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
|
|
318 |
return status_message, results_df
|
319 |
|
320 |
|
321 |
-
#
|
322 |
with gr.Blocks() as demo:
|
323 |
-
gr.Markdown("# Basic Agent Evaluation Runner")
|
324 |
gr.Markdown(
|
325 |
"""
|
326 |
-
|
327 |
|
328 |
1. Please clone this space, then modify the code to define your agent's logic, the tools, the necessary packages, etc ...
|
329 |
2. Log in to your Hugging Face account using the button below. This uses your HF username for submission.
|
@@ -336,7 +344,7 @@ with gr.Blocks() as demo:
|
|
336 |
"""
|
337 |
)
|
338 |
|
339 |
-
gr.LoginButton()
|
340 |
|
341 |
run_button = gr.Button("Run Evaluation & Submit All Answers")
|
342 |
|
@@ -350,6 +358,7 @@ with gr.Blocks() as demo:
|
|
350 |
)
|
351 |
|
352 |
if __name__ == "__main__":
|
|
|
353 |
print("\n" + "-"*30 + " App Starting " + "-"*30)
|
354 |
# Check for SPACE_HOST and SPACE_ID at startup for information
|
355 |
space_host_startup = os.getenv("SPACE_HOST")
|
|
|
1 |
+
# Importing necessary libraries and modules
|
2 |
import os
|
3 |
import gradio as gr
|
4 |
import requests
|
|
|
42 |
load_dotenv(".env", override=True)
|
43 |
BRAVE_API_KEY = os.getenv("BRAVE_API")
|
44 |
|
45 |
+
# Defining the State class which will hold various parameters related to the agent's state
|
46 |
class State(TypedDict):
|
47 |
file_path : str
|
48 |
file: Optional[str]
|
|
|
57 |
DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
|
58 |
|
59 |
# --- Basic Agent Definition ---
|
60 |
+
# Defining the BasicAgent class which contains the logic for the AI agent
|
61 |
class BasicAgent:
|
62 |
+
def __init__(self):
|
63 |
+
# Initializing the BasicAgent with tools and an LLM (Large Language Model)
|
64 |
tools = [CodeGenTool(), PythonExecutionTool(temp_dir="./"), YoutubeTranscriptTool(),
|
65 |
AnswerQuestionFromFileTool(), AnswerQuestionTool(), DownloadFile(),
|
66 |
ReverseString(), WebSearchTool(), WikipediaTool(), AnswerExcelTool(), ChessTool(), AudioTool(), FetchWebPageTool()]
|
|
|
68 |
#llm = ChatGoogleGenerativeAI(
|
69 |
# model="gemini-2.0-flash",
|
70 |
# temperature=0)
|
71 |
+
llm = ChatOpenAI(model="gpt-4.1-mini", temperature=0) # Configuring the LLM
|
72 |
self.llm_with_tools = llm.bind_tools(tools)
|
73 |
|
74 |
+
builder = StateGraph(State) # Building a state graph for handling agent's state transitions
|
75 |
|
76 |
+
builder.add_node("assistant", self.assistant) # Adding nodes to the state graph
|
77 |
builder.add_node("tools", ToolNode(tools))
|
78 |
builder.add_node("final_answer", BasicAgent.final_answer)
|
79 |
#builder.add_node("download_file", BasicAgent.download_file_node)
|
|
|
88 |
#builder.add_edge("parse_img", "assistant")
|
89 |
#builder.add_edge("parse_pdf", "assistant")
|
90 |
#builder.add_edge("parse_audio", "assistant")
|
91 |
+
builder.add_conditional_edges( # Adding conditional edges to manage state transitions based on tools' availability
|
92 |
+
"assistant", # Starting with the assistant node
|
93 |
tools_condition,
|
94 |
path_map={
|
95 |
"tools": "tools",
|
|
|
97 |
}
|
98 |
)
|
99 |
|
100 |
+
builder.add_edge("tools", "assistant") # Defining edges for state transitions
|
101 |
builder.add_edge("final_answer", END)
|
102 |
|
103 |
+
self.react_graph = builder.compile() # Compiling the state graph into a reactive graph
|
104 |
|
105 |
|
106 |
def __call__(self, question: str, task_id: str, file_name: Optional[str]) -> str:
|
107 |
+
# Handling the agent's main call
|
108 |
print(f"Agent received question (first 50 chars): {question[:50]}...")
|
109 |
|
110 |
+
messages = [HumanMessage(question)] # Creating a list of human messages
|
111 |
+
messages = self.react_graph.invoke({"messages": messages, "file_path": file_name, "question": question}) # Invoking the reactive graph with the current state
|
112 |
|
113 |
+
with open(f'messages_{task_id}.txt', 'w', encoding='utf-8') as out: # Writing the messages to a file
|
114 |
with redirect_stdout(out):
|
115 |
for m in messages['messages']:
|
116 |
m.pretty_print()
|
117 |
|
118 |
+
final_answer = messages["messages"][-1].content.strip() # Extracting the final answer from the messages
|
119 |
print(f"Final answer is {final_answer}")
|
120 |
return final_answer
|
121 |
|
122 |
|
123 |
def assistant(self, state: State):
|
124 |
+
# Defining the assistant node which processes the state
|
125 |
if state["file_path"]:
|
126 |
file_name = state["file_path"].split(".")[0]
|
127 |
file_extension = state["file_path"].split(".")[1]
|
|
|
129 |
file_extension = None
|
130 |
file_name = None
|
131 |
|
132 |
+
prompt = f""" # Constructing the prompt for the language model
|
133 |
You are a general AI assistant. When I ask you a question:
|
134 |
|
135 |
Share your reasoning process clearly.
|
|
|
164 |
Do **NOT** include the file extension in the URL and send WITHOUT MODIFICATION.
|
165 |
"""
|
166 |
|
167 |
+
sys_msg = SystemMessage(content=prompt) # Creating a system message with the prompt
|
168 |
|
169 |
+
time.sleep(40) # Simulating a delay for processing
|
170 |
return {"messages": [self.llm_with_tools.invoke([sys_msg] + state["messages"])]}
|
171 |
|
172 |
def final_answer(state: State):
|
173 |
+
# Defining the final answer node which processes the state and returns an answer
|
174 |
system_prompt = f"""
|
175 |
You will be given an answer and a question. You MUST remove EVERYTHING not needed from the answer and answer the question exactly without reporting "FINAL ANSWER".
|
176 |
That is if you are being asked the number of something, you must not return the thought process, but just the number X.
|
|
|
252 |
print(f"An unexpected error occurred fetching questions: {e}")
|
253 |
return f"An unexpected error occurred fetching questions: {e}", None
|
254 |
|
255 |
+
# Running the agent on the fetched questions
|
256 |
results_log = []
|
257 |
answers_payload = []
|
258 |
print(f"Running agent on {len(questions_data)} questions...")
|
|
|
277 |
return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
|
278 |
|
279 |
# 4. Prepare Submission
|
280 |
+
submission_data = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload} # Preparing the data for submission
|
281 |
status_update = f"Agent finished. Submitting {len(answers_payload)} answers for user '{username}'..."
|
282 |
print(status_update)
|
283 |
|
284 |
# 5. Submit
|
285 |
print(f"Submitting {len(answers_payload)} answers to: {submit_url}")
|
286 |
+
# Submitting the answers
|
287 |
try:
|
288 |
response = requests.post(submit_url, json=submission_data, timeout=60)
|
289 |
response.raise_for_status()
|
|
|
326 |
return status_message, results_df
|
327 |
|
328 |
|
329 |
+
# Building the Gradio interface using Blocks
|
330 |
with gr.Blocks() as demo:
|
331 |
+
gr.Markdown("# Basic Agent Evaluation Runner") # Title of the interface
|
332 |
gr.Markdown(
|
333 |
"""
|
334 |
+
# Instructions for the interface usage
|
335 |
|
336 |
1. Please clone this space, then modify the code to define your agent's logic, the tools, the necessary packages, etc ...
|
337 |
2. Log in to your Hugging Face account using the button below. This uses your HF username for submission.
|
|
|
344 |
"""
|
345 |
)
|
346 |
|
347 |
+
gr.LoginButton() # Login button for Hugging Face account
|
348 |
|
349 |
run_button = gr.Button("Run Evaluation & Submit All Answers")
|
350 |
|
|
|
358 |
)
|
359 |
|
360 |
if __name__ == "__main__":
|
361 |
+
# Launching Gradio interface for the application
|
362 |
print("\n" + "-"*30 + " App Starting " + "-"*30)
|
363 |
# Check for SPACE_HOST and SPACE_ID at startup for information
|
364 |
space_host_startup = os.getenv("SPACE_HOST")
|
tools/answer_excel.py
CHANGED
@@ -1,24 +1,28 @@
|
|
|
|
1 |
from langchain_core.tools.base import BaseTool
|
2 |
from langchain_experimental.agents.agent_toolkits import create_pandas_dataframe_agent
|
3 |
import pandas as pd
|
4 |
from langchain_google_genai import ChatGoogleGenerativeAI
|
5 |
from langchain.agents.agent_types import AgentType
|
6 |
|
|
|
7 |
class AnswerExcelTool(BaseTool):
|
8 |
name : str = "answer_excel_tool"
|
9 |
description: str = "Given the path to a file containing an excel file and a query, this tool tries to get an answer by querying the excel file. Provide the whole question in input. Another agent will later break down the task."
|
10 |
|
11 |
def _run(self, query: str, file_path: str) -> str:
|
12 |
-
|
|
|
13 |
|
14 |
-
llm = ChatGoogleGenerativeAI(model="gemini-2.0-flash", temperature=0)
|
15 |
|
16 |
agent_executor = create_pandas_dataframe_agent(
|
|
|
17 |
llm,
|
18 |
df,
|
19 |
agent_type=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
|
20 |
verbose=True,
|
21 |
-
allow_dangerous_code=True
|
22 |
)
|
23 |
|
24 |
-
return agent_executor(query)
|
|
|
1 |
+
# Importing necessary libraries and modules
|
2 |
from langchain_core.tools.base import BaseTool
|
3 |
from langchain_experimental.agents.agent_toolkits import create_pandas_dataframe_agent
|
4 |
import pandas as pd
|
5 |
from langchain_google_genai import ChatGoogleGenerativeAI
|
6 |
from langchain.agents.agent_types import AgentType
|
7 |
|
8 |
+
# Defining the AnswerExcelTool class which extends BaseTool
|
9 |
class AnswerExcelTool(BaseTool):
|
10 |
name : str = "answer_excel_tool"
|
11 |
description: str = "Given the path to a file containing an excel file and a query, this tool tries to get an answer by querying the excel file. Provide the whole question in input. Another agent will later break down the task."
|
12 |
|
13 |
def _run(self, query: str, file_path: str) -> str:
|
14 |
+
# Method to run the tool, using a query and the file path to an Excel file
|
15 |
+
df = pd.read_excel(file_path) # Reading the Excel file into a DataFrame
|
16 |
|
17 |
+
llm = ChatGoogleGenerativeAI(model="gemini-2.0-flash", temperature=0) # Configuring the LLM
|
18 |
|
19 |
agent_executor = create_pandas_dataframe_agent(
|
20 |
+
# Creating a Pandas DataFrame agent with the LLM and DataFrame
|
21 |
llm,
|
22 |
df,
|
23 |
agent_type=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
|
24 |
verbose=True,
|
25 |
+
allow_dangerous_code=True # IMPORTANT: Understand the risks
|
26 |
)
|
27 |
|
28 |
+
return agent_executor(query) # Executing the query using the agent
|
tools/answer_question.py
CHANGED
@@ -1,3 +1,4 @@
|
|
|
|
1 |
from langchain_google_genai import ChatGoogleGenerativeAI
|
2 |
from pydantic import PrivateAttr
|
3 |
from langchain_core.tools.base import BaseTool
|
@@ -6,6 +7,7 @@ from langchain_openai import ChatOpenAI
|
|
6 |
import time
|
7 |
from openai import OpenAI
|
8 |
|
|
|
9 |
class AnswerQuestionTool(BaseTool):
|
10 |
name : str = "answer_question_tool"
|
11 |
description: str = "Use this tool to answer any elementary question that you can solve without needing access to any external tool. Simply provide the question in input, reporting the whole question including desired output format. You can use this tool for example for vegetable classification."
|
@@ -13,6 +15,7 @@ class AnswerQuestionTool(BaseTool):
|
|
13 |
_system_prompt = PrivateAttr()
|
14 |
|
15 |
def __init__(self):
|
|
|
16 |
super().__init__()
|
17 |
#self._llm = ChatGoogleGenerativeAI(
|
18 |
# model="gemini-2.0-flash",
|
@@ -30,15 +33,18 @@ class AnswerQuestionTool(BaseTool):
|
|
30 |
""")
|
31 |
|
32 |
def _run(self, question: str) -> str:
|
|
|
33 |
human_message = HumanMessage(
|
|
|
34 |
content=[
|
35 |
{"type": "text", "text": question},
|
36 |
]
|
37 |
)
|
38 |
|
39 |
-
time.sleep(5)
|
40 |
-
client = OpenAI()
|
41 |
response = client.responses.create(
|
|
|
42 |
model="o4-mini",
|
43 |
messages = [
|
44 |
{
|
@@ -50,4 +56,4 @@ class AnswerQuestionTool(BaseTool):
|
|
50 |
)
|
51 |
#response = self._llm.invoke([self._system_prompt, human_message])
|
52 |
|
53 |
-
return response
|
|
|
1 |
+
# Importing necessary libraries and modules
|
2 |
from langchain_google_genai import ChatGoogleGenerativeAI
|
3 |
from pydantic import PrivateAttr
|
4 |
from langchain_core.tools.base import BaseTool
|
|
|
7 |
import time
|
8 |
from openai import OpenAI
|
9 |
|
10 |
+
# Defining the AnswerQuestionTool class which extends BaseTool
|
11 |
class AnswerQuestionTool(BaseTool):
|
12 |
name : str = "answer_question_tool"
|
13 |
description: str = "Use this tool to answer any elementary question that you can solve without needing access to any external tool. Simply provide the question in input, reporting the whole question including desired output format. You can use this tool for example for vegetable classification."
|
|
|
15 |
_system_prompt = PrivateAttr()
|
16 |
|
17 |
def __init__(self):
|
18 |
+
# Initializing the AnswerQuestionTool
|
19 |
super().__init__()
|
20 |
#self._llm = ChatGoogleGenerativeAI(
|
21 |
# model="gemini-2.0-flash",
|
|
|
33 |
""")
|
34 |
|
35 |
def _run(self, question: str) -> str:
|
36 |
+
# Method to run the tool and get an answer for the given question
|
37 |
human_message = HumanMessage(
|
38 |
+
# Creating a human message with the question content
|
39 |
content=[
|
40 |
{"type": "text", "text": question},
|
41 |
]
|
42 |
)
|
43 |
|
44 |
+
time.sleep(5) # Adding a delay for rate limits
|
45 |
+
client = OpenAI() # Initializing the OpenAI client
|
46 |
response = client.responses.create(
|
47 |
+
# Creating a response using OpenAI's API
|
48 |
model="o4-mini",
|
49 |
messages = [
|
50 |
{
|
|
|
56 |
)
|
57 |
#response = self._llm.invoke([self._system_prompt, human_message])
|
58 |
|
59 |
+
return response # Returning the response from the OpenAI API
|
tools/answer_question_from_file.py
CHANGED
@@ -1,3 +1,4 @@
|
|
|
|
1 |
from langchain_core.tools.base import BaseTool
|
2 |
from langchain_core.messages import AnyMessage, SystemMessage, HumanMessage
|
3 |
from langchain_google_genai import ChatGoogleGenerativeAI
|
@@ -7,15 +8,16 @@ from dotenv import load_dotenv
|
|
7 |
import whisper
|
8 |
import base64
|
9 |
|
10 |
-
load_dotenv(".env", override=True)
|
11 |
|
12 |
-
AZURE_OPENAI_ENDPOINT = os.getenv("AZURE_OPENAI_ENDPOINT")
|
13 |
AZURE_OPENAI_API_KEY = os.getenv("AZURE_OPENAI_API_KEY")
|
14 |
OPENAI_API_VERSION = os.getenv("OPENAI_API_VERSION_GEN", "2023-12-01-preview") # Default API version
|
15 |
# AZURE_OPENAI_DEPLOYMENT_NAME will be used as the 'model' for API calls
|
16 |
AZURE_OPENAI_DEPLOYMENT_NAME = "gpt-4.1"
|
17 |
|
18 |
|
|
|
19 |
class AnswerQuestionFromFileTool(BaseTool):
|
20 |
name: str = "answer_question_from_file_tool"
|
21 |
description: str = """
|
@@ -29,8 +31,9 @@ class AnswerQuestionFromFileTool(BaseTool):
|
|
29 |
_llm = PrivateAttr()
|
30 |
|
31 |
def __init__(self):
|
|
|
32 |
super().__init__()
|
33 |
-
self._llm = ChatGoogleGenerativeAI(
|
34 |
model="gemini-2.0-flash",
|
35 |
temperature=0)
|
36 |
|
|
|
1 |
+
# Importing necessary libraries and modules
|
2 |
from langchain_core.tools.base import BaseTool
|
3 |
from langchain_core.messages import AnyMessage, SystemMessage, HumanMessage
|
4 |
from langchain_google_genai import ChatGoogleGenerativeAI
|
|
|
8 |
import whisper
|
9 |
import base64
|
10 |
|
11 |
+
load_dotenv(".env", override=True) # Loading environment variables
|
12 |
|
13 |
+
AZURE_OPENAI_ENDPOINT = os.getenv("AZURE_OPENAI_ENDPOINT") # Fetching Azure OpenAI endpoint from environment
|
14 |
AZURE_OPENAI_API_KEY = os.getenv("AZURE_OPENAI_API_KEY")
|
15 |
OPENAI_API_VERSION = os.getenv("OPENAI_API_VERSION_GEN", "2023-12-01-preview") # Default API version
|
16 |
# AZURE_OPENAI_DEPLOYMENT_NAME will be used as the 'model' for API calls
|
17 |
AZURE_OPENAI_DEPLOYMENT_NAME = "gpt-4.1"
|
18 |
|
19 |
|
20 |
+
# Defining the AnswerQuestionFromFileTool class which extends BaseTool
|
21 |
class AnswerQuestionFromFileTool(BaseTool):
|
22 |
name: str = "answer_question_from_file_tool"
|
23 |
description: str = """
|
|
|
31 |
_llm = PrivateAttr()
|
32 |
|
33 |
def __init__(self):
|
34 |
+
# Initializing the AnswerQuestionFromFileTool
|
35 |
super().__init__()
|
36 |
+
self._llm = ChatGoogleGenerativeAI( # Setting up the LLM with specific parameters
|
37 |
model="gemini-2.0-flash",
|
38 |
temperature=0)
|
39 |
|
tools/audio_tool.py
CHANGED
@@ -1,3 +1,4 @@
|
|
|
|
1 |
from langchain_core.tools.base import BaseTool
|
2 |
import whisper
|
3 |
from langchain_google_genai import ChatGoogleGenerativeAI
|
@@ -9,11 +10,13 @@ import torch
|
|
9 |
from langchain_openai import ChatOpenAI
|
10 |
import time
|
11 |
|
|
|
12 |
class AudioTool(BaseTool):
|
13 |
name : str = "answer_question_audio_tool"
|
14 |
description: str = "This tool will reply to a query based on the audio given the path of a locally stored file. This file DOES NOT DOWNLOAD the file from the web. Run the download_file_tool first"
|
15 |
|
16 |
def _run(self, query: str, file_path: str) -> str:
|
|
|
17 |
try:
|
18 |
#pipe = pipeline(
|
19 |
# task="automatic-speech-recognition",
|
@@ -24,7 +27,7 @@ class AudioTool(BaseTool):
|
|
24 |
#)
|
25 |
#result = pipe(str(Path("./") / Path(file_path)), return_timestamps=True)
|
26 |
model = whisper.load_model("base")
|
27 |
-
result = model.transcribe(audio=str(Path("./") / Path(file_path)), language='en')
|
28 |
except Exception as e:
|
29 |
print("Exception", e)
|
30 |
|
@@ -48,9 +51,9 @@ Always prioritize accuracy and strict adherence to the user's stated needs befor
|
|
48 |
time.sleep(5)
|
49 |
llm = ChatOpenAI(model="gpt-4.1-mini", temperature=0)
|
50 |
|
51 |
-
response = llm.invoke([system_message, human_message])
|
52 |
|
53 |
-
return response
|
54 |
|
55 |
|
56 |
|
|
|
1 |
+
# Importing necessary libraries and modules
|
2 |
from langchain_core.tools.base import BaseTool
|
3 |
import whisper
|
4 |
from langchain_google_genai import ChatGoogleGenerativeAI
|
|
|
10 |
from langchain_openai import ChatOpenAI
|
11 |
import time
|
12 |
|
13 |
+
# Defining the AudioTool class which extends BaseTool
|
14 |
class AudioTool(BaseTool):
|
15 |
name : str = "answer_question_audio_tool"
|
16 |
description: str = "This tool will reply to a query based on the audio given the path of a locally stored file. This file DOES NOT DOWNLOAD the file from the web. Run the download_file_tool first"
|
17 |
|
18 |
def _run(self, query: str, file_path: str) -> str:
|
19 |
+
# Method to transcribe the provided audio file and answer the query using LLM
|
20 |
try:
|
21 |
#pipe = pipeline(
|
22 |
# task="automatic-speech-recognition",
|
|
|
27 |
#)
|
28 |
#result = pipe(str(Path("./") / Path(file_path)), return_timestamps=True)
|
29 |
model = whisper.load_model("base")
|
30 |
+
result = model.transcribe(audio=str(Path("./") / Path(file_path)), language='en') # Transcribing the audio using Whisper model
|
31 |
except Exception as e:
|
32 |
print("Exception", e)
|
33 |
|
|
|
51 |
time.sleep(5)
|
52 |
llm = ChatOpenAI(model="gpt-4.1-mini", temperature=0)
|
53 |
|
54 |
+
response = llm.invoke([system_message, human_message]) # Getting the response from the LLM
|
55 |
|
56 |
+
return response # Returning the response from the LLM
|
57 |
|
58 |
|
59 |
|
tools/chess_tool.py
CHANGED
@@ -1,16 +1,21 @@
|
|
|
|
1 |
from langchain_core.tools.base import BaseTool
|
2 |
from chessimg2pos import predict_fen
|
3 |
from stockfish import Stockfish
|
4 |
import chess
|
5 |
|
|
|
6 |
class ChessTool(BaseTool):
|
7 |
name : str = "chess_tool"
|
8 |
description : str = "Given the path of an image, this tool returns the best next move that can be done on the chessboard. You must give ONLY the PATH of the image here! Pass in input b or w as color_turn based on whose turn is it. Use w if unspecified."
|
9 |
|
10 |
def _run(self, img_path: str, color_turn: str) -> str:
|
|
|
11 |
# Get the FEN string
|
12 |
-
fen = predict_fen("./downloaded_files/image.png")
|
13 |
|
|
|
|
|
14 |
if color_turn == "b":
|
15 |
ranks = fen.split('/')
|
16 |
rotated_matrix = []
|
@@ -25,17 +30,18 @@ class ChessTool(BaseTool):
|
|
25 |
fen = f"{final_fen} {color_turn} - - 0 1"
|
26 |
|
27 |
try:
|
|
|
28 |
stockfish = Stockfish(path="C:/Users/FORMAGGA/Documents/personal/stockfish-windows-x86-64-avx2/stockfish/stockfish-windows-x86-64-avx2.exe")
|
29 |
|
30 |
stockfish.set_fen_position(fen)
|
31 |
|
32 |
-
next_move = str(stockfish.get_best_move())
|
33 |
except Exception as e:
|
34 |
print("Exception", e)
|
35 |
raise e
|
36 |
|
37 |
-
piece = stockfish.get_what_is_on_square(next_move[:2])
|
38 |
|
39 |
-
next_move_fen = piece.name + next_move[2:]
|
40 |
|
41 |
-
return next_move_fen
|
|
|
1 |
+
# Importing necessary libraries and modules
|
2 |
from langchain_core.tools.base import BaseTool
|
3 |
from chessimg2pos import predict_fen
|
4 |
from stockfish import Stockfish
|
5 |
import chess
|
6 |
|
7 |
+
# Defining the ChessTool class which extends BaseTool
|
8 |
class ChessTool(BaseTool):
|
9 |
name : str = "chess_tool"
|
10 |
description : str = "Given the path of an image, this tool returns the best next move that can be done on the chessboard. You must give ONLY the PATH of the image here! Pass in input b or w as color_turn based on whose turn is it. Use w if unspecified."
|
11 |
|
12 |
def _run(self, img_path: str, color_turn: str) -> str:
|
13 |
+
# Method to analyze the chessboard image and return the best move
|
14 |
# Get the FEN string
|
15 |
+
fen = predict_fen("./downloaded_files/image.png") # Predicting the FEN string from the chessboard image
|
16 |
|
17 |
+
# The fen predicted is always with a1 at the bottom left.
|
18 |
+
# If it's black turn than the bottom left is h8, you need to reverse the positions retrieved.
|
19 |
if color_turn == "b":
|
20 |
ranks = fen.split('/')
|
21 |
rotated_matrix = []
|
|
|
30 |
fen = f"{final_fen} {color_turn} - - 0 1"
|
31 |
|
32 |
try:
|
33 |
+
# Initializing Stockfish chess engine
|
34 |
stockfish = Stockfish(path="C:/Users/FORMAGGA/Documents/personal/stockfish-windows-x86-64-avx2/stockfish/stockfish-windows-x86-64-avx2.exe")
|
35 |
|
36 |
stockfish.set_fen_position(fen)
|
37 |
|
38 |
+
next_move = str(stockfish.get_best_move()) # Getting the best move from Stockfish
|
39 |
except Exception as e:
|
40 |
print("Exception", e)
|
41 |
raise e
|
42 |
|
43 |
+
piece = stockfish.get_what_is_on_square(next_move[:2]) # Getting the piece on the starting square of the move
|
44 |
|
45 |
+
next_move_fen = piece.name + next_move[2:] # Constructing the FEN representation of the move
|
46 |
|
47 |
+
return next_move_fen # Returning the best move in FEN format
|
tools/code_exec.py
CHANGED
@@ -1,3 +1,4 @@
|
|
|
|
1 |
from langchain_core.tools.base import BaseTool, ToolException
|
2 |
from typing import Optional
|
3 |
import subprocess
|
@@ -5,12 +6,11 @@ import tempfile
|
|
5 |
import os
|
6 |
from pydantic import PrivateAttr
|
7 |
|
|
|
8 |
class PythonExecutionTool(BaseTool):
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
Python subprocess, captures stdout/stderr, and returns the result.
|
13 |
-
"""
|
14 |
|
15 |
name : str = "python_execution"
|
16 |
description : str = (
|
@@ -28,6 +28,7 @@ class PythonExecutionTool(BaseTool):
|
|
28 |
*,
|
29 |
temp_dir: Optional[str] = None
|
30 |
):
|
|
|
31 |
"""
|
32 |
:param python_executable: Path to the Python interpreter to invoke.
|
33 |
:param timeout: Maximum seconds to allow the code to run.
|
|
|
1 |
+
# Importing necessary libraries and modules
|
2 |
from langchain_core.tools.base import BaseTool, ToolException
|
3 |
from typing import Optional
|
4 |
import subprocess
|
|
|
6 |
import os
|
7 |
from pydantic import PrivateAttr
|
8 |
|
9 |
+
# Defining the PythonExecutionTool class which extends BaseTool
|
10 |
class PythonExecutionTool(BaseTool):
|
11 |
+
# A LangChain “tool” that takes a string of Python code,
|
12 |
+
# writes it to a temporary .py file, executes it in a fresh
|
13 |
+
# Python subprocess, captures stdout/stderr, and returns the result.
|
|
|
|
|
14 |
|
15 |
name : str = "python_execution"
|
16 |
description : str = (
|
|
|
28 |
*,
|
29 |
temp_dir: Optional[str] = None
|
30 |
):
|
31 |
+
|
32 |
"""
|
33 |
:param python_executable: Path to the Python interpreter to invoke.
|
34 |
:param timeout: Maximum seconds to allow the code to run.
|
tools/download_file.py
CHANGED
@@ -64,8 +64,6 @@ class DownloadFile(BaseTool):
|
|
64 |
return "The file extension is not valid."
|
65 |
else:
|
66 |
msg = "There was an error downloading the file."
|
67 |
-
file = None
|
68 |
-
b64_file = None
|
69 |
|
70 |
return msg
|
71 |
|
|
|
64 |
return "The file extension is not valid."
|
65 |
else:
|
66 |
msg = "There was an error downloading the file."
|
|
|
|
|
67 |
|
68 |
return msg
|
69 |
|
tools/fetch_web_page.py
CHANGED
@@ -1,12 +1,15 @@
|
|
|
|
1 |
from langchain_core.tools.base import BaseTool
|
2 |
from typing import List
|
3 |
import requests
|
4 |
|
|
|
5 |
class FetchWebPageTool(BaseTool):
|
6 |
name : str = "fetch_web_page_tool"
|
7 |
description: str = "Provided the urls of 1 or more web pages, this tool returns the full content of the web page. This tool needs to be called AFTER calling the web_page_tool. It's important to fetch only pages which are useful to your task!"
|
8 |
|
9 |
def _run(self, urls: List[str]) -> List[str]:
|
10 |
-
|
|
|
11 |
|
12 |
-
return pages
|
|
|
1 |
+
# Importing necessary libraries and modules
|
2 |
from langchain_core.tools.base import BaseTool
|
3 |
from typing import List
|
4 |
import requests
|
5 |
|
6 |
+
# Defining the FetchWebPageTool class which extends BaseTool
|
7 |
class FetchWebPageTool(BaseTool):
|
8 |
name : str = "fetch_web_page_tool"
|
9 |
description: str = "Provided the urls of 1 or more web pages, this tool returns the full content of the web page. This tool needs to be called AFTER calling the web_page_tool. It's important to fetch only pages which are useful to your task!"
|
10 |
|
11 |
def _run(self, urls: List[str]) -> List[str]:
|
12 |
+
# Method to fetch the full content of the provided web pages
|
13 |
+
pages = [requests.get(url).text for url in urls] # Fetching the content of each URL
|
14 |
|
15 |
+
return pages # Returning the fetched content of the web pages
|
tools/web_search.py
CHANGED
@@ -1,3 +1,4 @@
|
|
|
|
1 |
from langchain_core.tools.base import BaseTool
|
2 |
from dotenv import load_dotenv
|
3 |
from langchain_community.utilities import DuckDuckGoSearchAPIWrapper
|
@@ -9,44 +10,22 @@ from langchain_community.document_loaders import WebBaseLoader
|
|
9 |
import json
|
10 |
import requests
|
11 |
|
12 |
-
load_dotenv(".env", override=True)
|
13 |
-
|
14 |
|
|
|
15 |
class WebSearchTool(BaseTool):
|
16 |
name: str = "web_search_tool"
|
17 |
description: str = "Perform a web search and extract concise factual answers. The query should be concise, below 400 characters. Use for online facts not in GAIA/Wikipedia—e.g. sports stats, Olympic participation, published papers, museum specimen locations, competition winners, and other up-to-date info."
|
18 |
-
#_search: BraveSearch = PrivateAttr()
|
19 |
_search: TavilySearch = PrivateAttr()
|
20 |
|
21 |
def __init__(self):
|
|
|
22 |
super().__init__()
|
23 |
-
|
24 |
-
#self._search = DuckDuckGoSearchResults(api_wrapper=wrapper, output_format="json")
|
25 |
-
|
26 |
-
self._search = TavilySearch(max_results=3, topic="general")
|
27 |
-
|
28 |
-
def _run_old(self, query: str) -> str:
|
29 |
-
json_str = self._search.run(query) # list[Document]
|
30 |
-
docs = json.loads(json_str)
|
31 |
-
urls = [doc["link"] for doc in docs]
|
32 |
-
print(urls)
|
33 |
-
pages = [requests.get(url) for url in urls]
|
34 |
-
|
35 |
-
res = "\n\n---\n\n".join(
|
36 |
-
page.text for page in pages
|
37 |
-
)
|
38 |
-
|
39 |
-
try:
|
40 |
-
with open("./web_search.txt", "wt", encoding="utf-8") as f:
|
41 |
-
f.write(str(res))
|
42 |
-
except Exception as e:
|
43 |
-
print(e)
|
44 |
-
|
45 |
-
return res
|
46 |
|
47 |
def _run(self, query: str) -> dict:
|
48 |
-
#
|
49 |
-
search_results = []
|
50 |
-
search_results.append(self._search.run(query))
|
51 |
|
52 |
-
return search_results
|
|
|
1 |
+
# Importing necessary libraries and modules
|
2 |
from langchain_core.tools.base import BaseTool
|
3 |
from dotenv import load_dotenv
|
4 |
from langchain_community.utilities import DuckDuckGoSearchAPIWrapper
|
|
|
10 |
import json
|
11 |
import requests
|
12 |
|
13 |
+
load_dotenv(".env", override=True) # Loading environment variables
|
|
|
14 |
|
15 |
+
# Defining the WebSearchTool class which extends BaseTool
|
16 |
class WebSearchTool(BaseTool):
|
17 |
name: str = "web_search_tool"
|
18 |
description: str = "Perform a web search and extract concise factual answers. The query should be concise, below 400 characters. Use for online facts not in GAIA/Wikipedia—e.g. sports stats, Olympic participation, published papers, museum specimen locations, competition winners, and other up-to-date info."
|
|
|
19 |
_search: TavilySearch = PrivateAttr()
|
20 |
|
21 |
def __init__(self):
|
22 |
+
# Initializing the WebSearchTool
|
23 |
super().__init__()
|
24 |
+
self._search = TavilySearch(max_results=3, topic="general") # Setting up the TavilySearch with specific parameters
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
25 |
|
26 |
def _run(self, query: str) -> dict:
|
27 |
+
# Method to run the web search tool with the given query
|
28 |
+
search_results = [] # Initializing the list for search results
|
29 |
+
search_results.append(self._search.run(query)) # Performing the search and adding the results to the list
|
30 |
|
31 |
+
return search_results # Returning the search results
|
tools/wikipedia.py
CHANGED
@@ -1,3 +1,4 @@
|
|
|
|
1 |
from langchain_community.tools import WikipediaQueryRun
|
2 |
from langchain_community.utilities import WikipediaAPIWrapper
|
3 |
from pydantic import PrivateAttr
|
@@ -7,33 +8,32 @@ import requests
|
|
7 |
from bs4 import BeautifulSoup
|
8 |
import wikipedia
|
9 |
|
10 |
-
|
11 |
class WikipediaTool(BaseTool):
|
12 |
name: str = "wikipedia_tool"
|
13 |
description: str = "Search Wikipedia for a given query, retrieving the corresponding page's HTML content. The query should not contain any noise and ask for something specific."
|
14 |
-
#_wikipedia = PrivateAttr()
|
15 |
|
16 |
def __init__(self):
|
|
|
17 |
super().__init__()
|
18 |
-
#self._wikipedia = WikipediaQueryRun(api_wrapper=WikipediaAPIWrapper(lang="en", doc_content_chars_max=100000, top_k_results=1))
|
19 |
-
|
20 |
|
21 |
def _run(self, query: str):
|
22 |
-
|
|
|
23 |
# Step 1: Get Wikipedia HTML
|
24 |
-
page = wikipedia.page(query)
|
25 |
-
html = page.html()
|
26 |
|
27 |
# Step 2: Parse HTML
|
28 |
-
soup = BeautifulSoup(html, "html.parser")
|
29 |
-
content_div = soup.find("div", class_="mw-parser-output")
|
30 |
# content_div = soup.find("table", class_="wikitable")
|
31 |
if not content_div:
|
32 |
return ""
|
33 |
|
34 |
# Step 3: Find all tags to remove (style, script, sup, infobox, etc.)
|
35 |
-
to_decompose = []
|
36 |
-
for tag in content_div.find_all():
|
37 |
tag_classes = tag.get("class", [])
|
38 |
if (
|
39 |
tag.name in ["style", "script", "sup"]
|
@@ -42,7 +42,7 @@ class WikipediaTool(BaseTool):
|
|
42 |
to_decompose.append(tag)
|
43 |
|
44 |
# Remove them after collecting
|
45 |
-
for tag in to_decompose:
|
46 |
tag.decompose()
|
47 |
|
48 |
-
return str(content_div)
|
|
|
1 |
+
# Importing necessary libraries and modules
|
2 |
from langchain_community.tools import WikipediaQueryRun
|
3 |
from langchain_community.utilities import WikipediaAPIWrapper
|
4 |
from pydantic import PrivateAttr
|
|
|
8 |
from bs4 import BeautifulSoup
|
9 |
import wikipedia
|
10 |
|
11 |
+
# Defining the WikipediaTool class which extends BaseTool
|
12 |
class WikipediaTool(BaseTool):
|
13 |
name: str = "wikipedia_tool"
|
14 |
description: str = "Search Wikipedia for a given query, retrieving the corresponding page's HTML content. The query should not contain any noise and ask for something specific."
|
|
|
15 |
|
16 |
def __init__(self):
|
17 |
+
# Initializing the WikipediaTool
|
18 |
super().__init__()
|
|
|
|
|
19 |
|
20 |
def _run(self, query: str):
|
21 |
+
# Method to run the Wikipedia tool with the given query
|
22 |
+
print(f"wikipedia_search_html called with query='{query}'") # Logging the query
|
23 |
# Step 1: Get Wikipedia HTML
|
24 |
+
page = wikipedia.page(query) # Fetching the Wikipedia page for the query
|
25 |
+
html = page.html() # Extracting the HTML content of the page
|
26 |
|
27 |
# Step 2: Parse HTML
|
28 |
+
soup = BeautifulSoup(html, "html.parser") # Parsing the HTML content
|
29 |
+
content_div = soup.find("div", class_="mw-parser-output") # Finding the content division
|
30 |
# content_div = soup.find("table", class_="wikitable")
|
31 |
if not content_div:
|
32 |
return ""
|
33 |
|
34 |
# Step 3: Find all tags to remove (style, script, sup, infobox, etc.)
|
35 |
+
to_decompose = [] # Collecting tags to be removed
|
36 |
+
for tag in content_div.find_all(): # Looping through all tags in the content division
|
37 |
tag_classes = tag.get("class", [])
|
38 |
if (
|
39 |
tag.name in ["style", "script", "sup"]
|
|
|
42 |
to_decompose.append(tag)
|
43 |
|
44 |
# Remove them after collecting
|
45 |
+
for tag in to_decompose: # Decompose and remove the collected tags
|
46 |
tag.decompose()
|
47 |
|
48 |
+
return str(content_div) # Returning the cleaned content division as string
|
tools/youtube_transcript.py
CHANGED
@@ -15,10 +15,13 @@ class YoutubeTranscriptTool(BaseTool):
|
|
15 |
Returns:
|
16 |
The transcript as a single string.
|
17 |
"""
|
|
|
18 |
re_match = re.search(r"watch\?v=([^&]+)", youtube_link)
|
19 |
if not re_match:
|
20 |
raise ValueError(f"Invalid YouTube URL: {youtube_link}")
|
21 |
video_id = re_match.group(1)
|
|
|
|
|
22 |
ytt_api = YouTubeTranscriptApi()
|
23 |
fetched_transcript = ytt_api.fetch(video_id)
|
24 |
|
|
|
15 |
Returns:
|
16 |
The transcript as a single string.
|
17 |
"""
|
18 |
+
# Get the video ID from the youtube URL
|
19 |
re_match = re.search(r"watch\?v=([^&]+)", youtube_link)
|
20 |
if not re_match:
|
21 |
raise ValueError(f"Invalid YouTube URL: {youtube_link}")
|
22 |
video_id = re_match.group(1)
|
23 |
+
|
24 |
+
# Initialize the transcriptAPI and retrieve the transcript for the given videoID
|
25 |
ytt_api = YouTubeTranscriptApi()
|
26 |
fetched_transcript = ytt_api.fetch(video_id)
|
27 |
|