Spaces:
Sleeping
Sleeping
restructuring
Browse files- agent.py +47 -62
- app.py +4 -5
- app_template.py +0 -1
- cocolabelmap.py +1 -2
- fullreq.txt +309 -0
- langtools.py +20 -14
- requirements.txt +15 -8
- setup_actions.ipynb +30 -20
- tools copy.py +55 -61
- tools.py +326 -241
- tools_beta.py +222 -228
- utils.py +11 -10
agent.py
CHANGED
@@ -1,46 +1,39 @@
|
|
1 |
-
from dotenv import load_dotenv
|
2 |
import os
|
3 |
-
from typing import
|
4 |
|
5 |
-
|
6 |
-
|
7 |
-
|
|
|
|
|
|
|
|
|
8 |
|
9 |
# Import custom tools
|
10 |
from Final_Assignment_Template.tools import (
|
11 |
-
|
12 |
-
WikipediaSearchTool,
|
13 |
-
VisitWebpageTool,
|
14 |
-
TranscribeAudioTool,
|
15 |
-
TranscibeVideoFileTool,
|
16 |
-
BraveWebSearchTool,
|
17 |
-
DescribeImageTool,
|
18 |
ArxivSearchTool,
|
19 |
DownloadFileFromLinkTool,
|
20 |
DuckDuckGoSearchTool,
|
21 |
-
AddDocumentToVectorStoreTool,
|
22 |
QueryVectorStoreTool,
|
23 |
-
|
|
|
|
|
|
|
|
|
24 |
)
|
25 |
|
26 |
# Import utility functions
|
27 |
-
from utils import
|
28 |
-
|
29 |
-
# Import SmolaAgents tools
|
30 |
-
from smolagents.default_tools import (
|
31 |
-
PythonInterpreterTool,
|
32 |
-
FinalAnswerTool
|
33 |
-
)
|
34 |
|
35 |
-
# Import
|
36 |
-
from smolagents import OpenAIServerModel, LiteLLMModel, CodeAgent, HfApiModel
|
37 |
|
38 |
|
39 |
class BoomBot:
|
40 |
def __init__(self, provider="deepinfra"):
|
41 |
"""
|
42 |
Initialize the BoomBot with the specified provider.
|
43 |
-
|
44 |
Args:
|
45 |
provider (str): The model provider to use (e.g., "groq", "qwen", "gemma", "anthropic", "deepinfra", "meta")
|
46 |
"""
|
@@ -48,11 +41,11 @@ class BoomBot:
|
|
48 |
self.provider = provider
|
49 |
self.model = self._initialize_model()
|
50 |
self.agent = self._create_agent()
|
51 |
-
|
52 |
def _initialize_model(self):
|
53 |
"""
|
54 |
Initialize the appropriate model based on the provider.
|
55 |
-
|
56 |
Returns:
|
57 |
The initialized model object
|
58 |
"""
|
@@ -60,10 +53,10 @@ class BoomBot:
|
|
60 |
qwen_model = "ollama_chat/qwen3:8b"
|
61 |
return LiteLLMModel(
|
62 |
model_id=qwen_model,
|
63 |
-
device=
|
64 |
num_ctx=32768,
|
65 |
temperature=0.6,
|
66 |
-
top_p=0.95
|
67 |
)
|
68 |
elif self.provider == "gemma":
|
69 |
gemma_model = "ollama_chat/gemma3:12b-it-qat"
|
@@ -71,18 +64,14 @@ class BoomBot:
|
|
71 |
model_id=gemma_model,
|
72 |
num_ctx=65536,
|
73 |
temperature=1.0,
|
74 |
-
device=
|
75 |
top_k=64,
|
76 |
top_p=0.95,
|
77 |
-
min_p=0.0
|
78 |
)
|
79 |
elif self.provider == "anthropic":
|
80 |
model_id = "anthropic/claude-3-5-sonnet-latest"
|
81 |
-
return LiteLLMModel(
|
82 |
-
model_id=model_id,
|
83 |
-
temperature=0.6,
|
84 |
-
max_tokens=8192
|
85 |
-
)
|
86 |
elif self.provider == "deepinfra":
|
87 |
deepinfra_model = "Qwen/Qwen3-235B-A22B"
|
88 |
return OpenAIServerModel(
|
@@ -91,7 +80,7 @@ class BoomBot:
|
|
91 |
api_key=os.environ["DEEPINFRA_API_KEY"],
|
92 |
flatten_messages_as_text=True,
|
93 |
max_tokens=8192,
|
94 |
-
temperature=0.1
|
95 |
)
|
96 |
elif self.provider == "meta":
|
97 |
meta_model = "meta-llama/Llama-3.3-70B-Instruct-Turbo"
|
@@ -101,23 +90,19 @@ class BoomBot:
|
|
101 |
api_key=os.environ["DEEPINFRA_API_KEY"],
|
102 |
flatten_messages_as_text=True,
|
103 |
max_tokens=8192,
|
104 |
-
temperature=0.7
|
105 |
)
|
106 |
elif self.provider == "groq":
|
107 |
# Default to use groq's claude-3-opus or llama-3
|
108 |
model_id = "claude-3-opus-20240229"
|
109 |
-
return LiteLLMModel(
|
110 |
-
model_id=model_id,
|
111 |
-
temperature=0.7,
|
112 |
-
max_tokens=8192
|
113 |
-
)
|
114 |
else:
|
115 |
raise ValueError(f"Unsupported provider: {self.provider}")
|
116 |
-
|
117 |
def _create_agent(self):
|
118 |
"""
|
119 |
Create and configure the agent with all necessary tools.
|
120 |
-
|
121 |
Returns:
|
122 |
The configured CodeAgent
|
123 |
"""
|
@@ -132,11 +117,11 @@ class BoomBot:
|
|
132 |
arxiv_search = ArxivSearchTool()
|
133 |
add_doc_vectorstore = AddDocumentToVectorStoreTool()
|
134 |
retrieve_doc_vectorstore = QueryVectorStoreTool()
|
135 |
-
|
136 |
# SmolaAgents default tools
|
137 |
python_interpreter = PythonInterpreterTool()
|
138 |
final_answer = FinalAnswerTool()
|
139 |
-
|
140 |
# Combine all tools
|
141 |
agent_tools = [
|
142 |
web_searcher,
|
@@ -150,9 +135,9 @@ class BoomBot:
|
|
150 |
add_doc_vectorstore,
|
151 |
retrieve_doc_vectorstore,
|
152 |
python_interpreter,
|
153 |
-
final_answer
|
154 |
]
|
155 |
-
|
156 |
# Additional imports for the Python interpreter
|
157 |
additional_imports = [
|
158 |
"json",
|
@@ -178,7 +163,7 @@ class BoomBot:
|
|
178 |
"itertools",
|
179 |
"functools",
|
180 |
]
|
181 |
-
|
182 |
# Create the agent
|
183 |
agent = CodeAgent(
|
184 |
tools=agent_tools,
|
@@ -186,19 +171,19 @@ class BoomBot:
|
|
186 |
model=self.model,
|
187 |
add_base_tools=False,
|
188 |
stream_outputs=True,
|
189 |
-
additional_authorized_imports=additional_imports
|
190 |
)
|
191 |
-
|
192 |
# Modify the system prompt
|
193 |
modified_prompt = replace_tool_mentions(agent.system_prompt)
|
194 |
agent.system_prompt = modified_prompt + self._get_system_prompt()
|
195 |
-
|
196 |
return agent
|
197 |
-
|
198 |
def _get_system_prompt(self):
|
199 |
"""
|
200 |
Return the system prompt for the agent.
|
201 |
-
|
202 |
Returns:
|
203 |
str: The system prompt
|
204 |
"""
|
@@ -260,32 +245,32 @@ class BoomBot:
|
|
260 |
- List β comma-separated, one space (e.g., 2, 3, 4)
|
261 |
- Conclude with: FINAL ANSWER: <your_answer>
|
262 |
"""
|
263 |
-
|
264 |
def run(self, question: str, task_id: str, to_download: Bool) -> str:
|
265 |
"""
|
266 |
Run the agent with the given question, task_id, and download flag.
|
267 |
-
|
268 |
Args:
|
269 |
question (str): The question or task for the agent to process
|
270 |
task_id (str): A unique identifier for the task
|
271 |
to_download (Bool): Flag indicating whether to download resources
|
272 |
-
|
273 |
Returns:
|
274 |
str: The agent's response
|
275 |
"""
|
276 |
print(f"BoomBot running with question (first 50 chars): {question[:50]}...")
|
277 |
-
|
278 |
# Configure any task-specific settings based on the parameters
|
279 |
if to_download:
|
280 |
# You could set up specific agent configurations here for download tasks
|
281 |
pass
|
282 |
-
|
283 |
# Run the agent with the given question
|
284 |
result = self.agent.generate_response(question)
|
285 |
-
|
286 |
# Extract the final answer from the result
|
287 |
final_answer = extract_final_answer(result)
|
288 |
-
|
289 |
return final_answer
|
290 |
|
291 |
|
@@ -293,4 +278,4 @@ class BoomBot:
|
|
293 |
# if __name__ == "__main__":
|
294 |
# agent = BasicAgent()
|
295 |
# response = agent("What is the current population of Tokyo?", "population_query", True)
|
296 |
-
# print(f"Response: {response}")
|
|
|
|
|
1 |
import os
|
2 |
+
from typing import Bool
|
3 |
|
4 |
+
from dotenv import load_dotenv
|
5 |
+
|
6 |
+
# Import models from SmolaAgents
|
7 |
+
from smolagents import CodeAgent, LiteLLMModel, OpenAIServerModel
|
8 |
+
|
9 |
+
# Import SmolaAgents tools
|
10 |
+
from smolagents.default_tools import FinalAnswerTool, PythonInterpreterTool
|
11 |
|
12 |
# Import custom tools
|
13 |
from Final_Assignment_Template.tools import (
|
14 |
+
AddDocumentToVectorStoreTool,
|
|
|
|
|
|
|
|
|
|
|
|
|
15 |
ArxivSearchTool,
|
16 |
DownloadFileFromLinkTool,
|
17 |
DuckDuckGoSearchTool,
|
|
|
18 |
QueryVectorStoreTool,
|
19 |
+
ReadFileContentTool,
|
20 |
+
TranscibeVideoFileTool,
|
21 |
+
TranscribeAudioTool,
|
22 |
+
VisitWebpageTool,
|
23 |
+
WikipediaSearchTool,
|
24 |
)
|
25 |
|
26 |
# Import utility functions
|
27 |
+
from utils import extract_final_answer, replace_tool_mentions
|
|
|
|
|
|
|
|
|
|
|
|
|
28 |
|
29 |
+
# Import tools from LangChain
|
|
|
30 |
|
31 |
|
32 |
class BoomBot:
|
33 |
def __init__(self, provider="deepinfra"):
|
34 |
"""
|
35 |
Initialize the BoomBot with the specified provider.
|
36 |
+
|
37 |
Args:
|
38 |
provider (str): The model provider to use (e.g., "groq", "qwen", "gemma", "anthropic", "deepinfra", "meta")
|
39 |
"""
|
|
|
41 |
self.provider = provider
|
42 |
self.model = self._initialize_model()
|
43 |
self.agent = self._create_agent()
|
44 |
+
|
45 |
def _initialize_model(self):
|
46 |
"""
|
47 |
Initialize the appropriate model based on the provider.
|
48 |
+
|
49 |
Returns:
|
50 |
The initialized model object
|
51 |
"""
|
|
|
53 |
qwen_model = "ollama_chat/qwen3:8b"
|
54 |
return LiteLLMModel(
|
55 |
model_id=qwen_model,
|
56 |
+
device="cuda",
|
57 |
num_ctx=32768,
|
58 |
temperature=0.6,
|
59 |
+
top_p=0.95,
|
60 |
)
|
61 |
elif self.provider == "gemma":
|
62 |
gemma_model = "ollama_chat/gemma3:12b-it-qat"
|
|
|
64 |
model_id=gemma_model,
|
65 |
num_ctx=65536,
|
66 |
temperature=1.0,
|
67 |
+
device="cuda",
|
68 |
top_k=64,
|
69 |
top_p=0.95,
|
70 |
+
min_p=0.0,
|
71 |
)
|
72 |
elif self.provider == "anthropic":
|
73 |
model_id = "anthropic/claude-3-5-sonnet-latest"
|
74 |
+
return LiteLLMModel(model_id=model_id, temperature=0.6, max_tokens=8192)
|
|
|
|
|
|
|
|
|
75 |
elif self.provider == "deepinfra":
|
76 |
deepinfra_model = "Qwen/Qwen3-235B-A22B"
|
77 |
return OpenAIServerModel(
|
|
|
80 |
api_key=os.environ["DEEPINFRA_API_KEY"],
|
81 |
flatten_messages_as_text=True,
|
82 |
max_tokens=8192,
|
83 |
+
temperature=0.1,
|
84 |
)
|
85 |
elif self.provider == "meta":
|
86 |
meta_model = "meta-llama/Llama-3.3-70B-Instruct-Turbo"
|
|
|
90 |
api_key=os.environ["DEEPINFRA_API_KEY"],
|
91 |
flatten_messages_as_text=True,
|
92 |
max_tokens=8192,
|
93 |
+
temperature=0.7,
|
94 |
)
|
95 |
elif self.provider == "groq":
|
96 |
# Default to use groq's claude-3-opus or llama-3
|
97 |
model_id = "claude-3-opus-20240229"
|
98 |
+
return LiteLLMModel(model_id=model_id, temperature=0.7, max_tokens=8192)
|
|
|
|
|
|
|
|
|
99 |
else:
|
100 |
raise ValueError(f"Unsupported provider: {self.provider}")
|
101 |
+
|
102 |
def _create_agent(self):
|
103 |
"""
|
104 |
Create and configure the agent with all necessary tools.
|
105 |
+
|
106 |
Returns:
|
107 |
The configured CodeAgent
|
108 |
"""
|
|
|
117 |
arxiv_search = ArxivSearchTool()
|
118 |
add_doc_vectorstore = AddDocumentToVectorStoreTool()
|
119 |
retrieve_doc_vectorstore = QueryVectorStoreTool()
|
120 |
+
|
121 |
# SmolaAgents default tools
|
122 |
python_interpreter = PythonInterpreterTool()
|
123 |
final_answer = FinalAnswerTool()
|
124 |
+
|
125 |
# Combine all tools
|
126 |
agent_tools = [
|
127 |
web_searcher,
|
|
|
135 |
add_doc_vectorstore,
|
136 |
retrieve_doc_vectorstore,
|
137 |
python_interpreter,
|
138 |
+
final_answer,
|
139 |
]
|
140 |
+
|
141 |
# Additional imports for the Python interpreter
|
142 |
additional_imports = [
|
143 |
"json",
|
|
|
163 |
"itertools",
|
164 |
"functools",
|
165 |
]
|
166 |
+
|
167 |
# Create the agent
|
168 |
agent = CodeAgent(
|
169 |
tools=agent_tools,
|
|
|
171 |
model=self.model,
|
172 |
add_base_tools=False,
|
173 |
stream_outputs=True,
|
174 |
+
additional_authorized_imports=additional_imports,
|
175 |
)
|
176 |
+
|
177 |
# Modify the system prompt
|
178 |
modified_prompt = replace_tool_mentions(agent.system_prompt)
|
179 |
agent.system_prompt = modified_prompt + self._get_system_prompt()
|
180 |
+
|
181 |
return agent
|
182 |
+
|
183 |
def _get_system_prompt(self):
|
184 |
"""
|
185 |
Return the system prompt for the agent.
|
186 |
+
|
187 |
Returns:
|
188 |
str: The system prompt
|
189 |
"""
|
|
|
245 |
- List β comma-separated, one space (e.g., 2, 3, 4)
|
246 |
- Conclude with: FINAL ANSWER: <your_answer>
|
247 |
"""
|
248 |
+
|
249 |
def run(self, question: str, task_id: str, to_download: Bool) -> str:
|
250 |
"""
|
251 |
Run the agent with the given question, task_id, and download flag.
|
252 |
+
|
253 |
Args:
|
254 |
question (str): The question or task for the agent to process
|
255 |
task_id (str): A unique identifier for the task
|
256 |
to_download (Bool): Flag indicating whether to download resources
|
257 |
+
|
258 |
Returns:
|
259 |
str: The agent's response
|
260 |
"""
|
261 |
print(f"BoomBot running with question (first 50 chars): {question[:50]}...")
|
262 |
+
|
263 |
# Configure any task-specific settings based on the parameters
|
264 |
if to_download:
|
265 |
# You could set up specific agent configurations here for download tasks
|
266 |
pass
|
267 |
+
|
268 |
# Run the agent with the given question
|
269 |
result = self.agent.generate_response(question)
|
270 |
+
|
271 |
# Extract the final answer from the result
|
272 |
final_answer = extract_final_answer(result)
|
273 |
+
|
274 |
return final_answer
|
275 |
|
276 |
|
|
|
278 |
# if __name__ == "__main__":
|
279 |
# agent = BasicAgent()
|
280 |
# response = agent("What is the current population of Tokyo?", "population_query", True)
|
281 |
+
# print(f"Response: {response}")
|
app.py
CHANGED
@@ -4,8 +4,7 @@ import os
|
|
4 |
import gradio as gr
|
5 |
import pandas as pd
|
6 |
import requests
|
7 |
-
from
|
8 |
-
from traitlets import Bool # type: ignore
|
9 |
|
10 |
from agent import BoomBot
|
11 |
|
@@ -15,16 +14,16 @@ DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
|
|
15 |
|
16 |
|
17 |
# --- Basic Agent Definition --
|
18 |
-
# --- Basic Agent Definition ---
|
19 |
class BasicAgent:
|
20 |
def __init__(self):
|
21 |
print("BasicAgent initialized.")
|
22 |
self.agent = BoomBot(provider="groq")
|
23 |
-
|
24 |
def __call__(self, question: str, task_id: str, to_download: Bool) -> str:
|
25 |
print(f"Agent received question (first 50 chars): {question[:50]}...")
|
26 |
return self.agent.run(question, task_id, to_download)
|
27 |
|
|
|
28 |
def run_and_submit_all(profile: gr.OAuthProfile | None):
|
29 |
"""
|
30 |
Fetches all questions, runs the BasicAgent on them, submits all answers,
|
@@ -93,7 +92,7 @@ def run_and_submit_all(profile: gr.OAuthProfile | None):
|
|
93 |
print(f"Skipping item with missing task_id or question: {item}")
|
94 |
continue
|
95 |
try:
|
96 |
-
submitted_answer = agent(question_text, task_id, to_download
|
97 |
answers_payload.append(
|
98 |
{"task_id": task_id, "submitted_answer": submitted_answer}
|
99 |
)
|
|
|
4 |
import gradio as gr
|
5 |
import pandas as pd
|
6 |
import requests
|
7 |
+
from traitlets import Bool # type: ignore
|
|
|
8 |
|
9 |
from agent import BoomBot
|
10 |
|
|
|
14 |
|
15 |
|
16 |
# --- Basic Agent Definition --
|
|
|
17 |
class BasicAgent:
|
18 |
def __init__(self):
|
19 |
print("BasicAgent initialized.")
|
20 |
self.agent = BoomBot(provider="groq")
|
21 |
+
|
22 |
def __call__(self, question: str, task_id: str, to_download: Bool) -> str:
|
23 |
print(f"Agent received question (first 50 chars): {question[:50]}...")
|
24 |
return self.agent.run(question, task_id, to_download)
|
25 |
|
26 |
+
|
27 |
def run_and_submit_all(profile: gr.OAuthProfile | None):
|
28 |
"""
|
29 |
Fetches all questions, runs the BasicAgent on them, submits all answers,
|
|
|
92 |
print(f"Skipping item with missing task_id or question: {item}")
|
93 |
continue
|
94 |
try:
|
95 |
+
submitted_answer = agent(question_text, task_id, to_download=to_download)
|
96 |
answers_payload.append(
|
97 |
{"task_id": task_id, "submitted_answer": submitted_answer}
|
98 |
)
|
app_template.py
CHANGED
@@ -1,4 +1,3 @@
|
|
1 |
-
import inspect
|
2 |
import os
|
3 |
|
4 |
import gradio as gr
|
|
|
|
|
1 |
import os
|
2 |
|
3 |
import gradio as gr
|
cocolabelmap.py
CHANGED
@@ -181,6 +181,5 @@ LABEL_MAP = {
|
|
181 |
179: "waterdrops",
|
182 |
180: "window",
|
183 |
181: "window",
|
184 |
-
182: "wood"
|
185 |
}
|
186 |
-
|
|
|
181 |
179: "waterdrops",
|
182 |
180: "window",
|
183 |
181: "window",
|
184 |
+
182: "wood",
|
185 |
}
|
|
fullreq.txt
ADDED
@@ -0,0 +1,309 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
accelerate==1.6.0
|
2 |
+
aiofiles==23.2.1
|
3 |
+
aiohappyeyeballs==2.4.4
|
4 |
+
aiohttp==3.11.18
|
5 |
+
aiosignal==1.3.2
|
6 |
+
alabaster==1.0.0
|
7 |
+
altair==5.5.0
|
8 |
+
annotated-types==0.7.0
|
9 |
+
anyio==4.8.0
|
10 |
+
arrow==1.3.0
|
11 |
+
arxiv==2.2.0
|
12 |
+
asgiref==3.8.1
|
13 |
+
astroid==3.3.9
|
14 |
+
asttokens==3.0.0
|
15 |
+
attrs==25.1.0
|
16 |
+
babel==2.17.0
|
17 |
+
backoff==2.2.1
|
18 |
+
bcrypt==4.2.1
|
19 |
+
beautifulsoup4==4.12.3
|
20 |
+
bibtexparser==1.4.3
|
21 |
+
black==25.1.0
|
22 |
+
blinker==1.9.0
|
23 |
+
bs4==0.0.2
|
24 |
+
build==1.2.2.post1
|
25 |
+
cachetools==5.5.1
|
26 |
+
certifi==2025.1.31
|
27 |
+
chardet==5.2.0
|
28 |
+
charset-normalizer==3.4.1
|
29 |
+
chess==1.11.2
|
30 |
+
Chroma==0.2.0
|
31 |
+
chroma-hnswlib==0.7.6
|
32 |
+
chromadb==1.0.9
|
33 |
+
click==8.1.8
|
34 |
+
colorama==0.4.6
|
35 |
+
coloredlogs==15.0.1
|
36 |
+
comm==0.2.2
|
37 |
+
contourpy==1.3.2
|
38 |
+
cssselect==1.3.0
|
39 |
+
cycler==0.12.1
|
40 |
+
dataclasses-json==0.6.7
|
41 |
+
datasets==3.5.1
|
42 |
+
debugpy==1.8.13
|
43 |
+
decorator==5.2.1
|
44 |
+
Deprecated==1.2.18
|
45 |
+
dill==0.3.8
|
46 |
+
diskcache==5.6.3
|
47 |
+
distro==1.9.0
|
48 |
+
docutils==0.21.2
|
49 |
+
duckduckgo_search==8.0.2
|
50 |
+
durationpy==0.9
|
51 |
+
et_xmlfile==2.0.0
|
52 |
+
executing==2.2.0
|
53 |
+
fake-useragent==2.2.0
|
54 |
+
fastapi==0.115.9
|
55 |
+
feedparser==6.0.11
|
56 |
+
ffmpy==0.5.0
|
57 |
+
filelock==3.17.0
|
58 |
+
flake8==7.2.0
|
59 |
+
flatbuffers==25.1.24
|
60 |
+
fonttools==4.58.0
|
61 |
+
free_proxy==1.1.3
|
62 |
+
frozenlist==1.5.0
|
63 |
+
fsspec==2024.12.0
|
64 |
+
gguf==0.16.3
|
65 |
+
git-filter-repo==2.47.0
|
66 |
+
gitdb==4.0.12
|
67 |
+
GitPython==3.1.44
|
68 |
+
google-auth==2.38.0
|
69 |
+
google_search_results==2.4.2
|
70 |
+
googleapis-common-protos==1.66.0
|
71 |
+
gradio==5.14.0
|
72 |
+
gradio_client==1.7.0
|
73 |
+
greenlet==3.1.1
|
74 |
+
grpcio==1.70.0
|
75 |
+
h11==0.14.0
|
76 |
+
hf-xet==1.1.0
|
77 |
+
httpcore==1.0.7
|
78 |
+
httptools==0.6.4
|
79 |
+
httpx==0.27.2
|
80 |
+
httpx-sse==0.4.0
|
81 |
+
huggingface-hub==0.31.1
|
82 |
+
humanfriendly==10.0
|
83 |
+
idna==3.10
|
84 |
+
imagesize==1.4.1
|
85 |
+
impact-factor==1.1.2
|
86 |
+
importlib_metadata==8.5.0
|
87 |
+
importlib_resources==6.5.2
|
88 |
+
iniconfig==2.1.0
|
89 |
+
ipykernel==6.29.5
|
90 |
+
ipython==9.1.0
|
91 |
+
ipython_pygments_lexers==1.1.1
|
92 |
+
ipywidgets==8.1.7
|
93 |
+
isort==6.0.1
|
94 |
+
jedi==0.19.2
|
95 |
+
Jinja2==3.1.5
|
96 |
+
jiter==0.8.2
|
97 |
+
joblib==1.4.2
|
98 |
+
jsonpatch==1.33
|
99 |
+
jsonpointer==3.0.0
|
100 |
+
jsonschema==4.23.0
|
101 |
+
jsonschema-specifications==2024.10.1
|
102 |
+
jupyter_client==8.6.3
|
103 |
+
jupyter_core==5.7.2
|
104 |
+
jupyterlab_widgets==3.0.15
|
105 |
+
kiwisolver==1.4.8
|
106 |
+
kubernetes==32.0.0
|
107 |
+
langchain==0.3.25
|
108 |
+
langchain-chroma==0.2.1
|
109 |
+
langchain-community==0.3.16
|
110 |
+
langchain-core==0.3.59
|
111 |
+
langchain-huggingface==0.1.2
|
112 |
+
langchain-tavily==0.1.6
|
113 |
+
langchain-text-splitters==0.3.8
|
114 |
+
langsmith==0.3.3
|
115 |
+
litellm==1.59.10
|
116 |
+
llama_cpp_python==0.3.9
|
117 |
+
lxml==5.3.0
|
118 |
+
lxml_html_clean==0.4.2
|
119 |
+
markdown-it-py==3.0.0
|
120 |
+
markdownify==0.14.1
|
121 |
+
MarkupSafe==2.1.5
|
122 |
+
marshmallow==3.26.0
|
123 |
+
matplotlib==3.10.3
|
124 |
+
matplotlib-inline==0.1.7
|
125 |
+
matplotlib-venn==1.1.2
|
126 |
+
mccabe==0.7.0
|
127 |
+
mdurl==0.1.2
|
128 |
+
mmh3==5.1.0
|
129 |
+
monotonic==1.6
|
130 |
+
mpmath==1.3.0
|
131 |
+
multidict==6.1.0
|
132 |
+
multiprocess==0.70.16
|
133 |
+
mypy==1.15.0
|
134 |
+
mypy-extensions==1.0.0
|
135 |
+
narwhals==1.24.1
|
136 |
+
nest-asyncio==1.6.0
|
137 |
+
networkx==3.4.2
|
138 |
+
numpy==1.26.4
|
139 |
+
nvidia-cublas-cu12==12.6.4.1
|
140 |
+
nvidia-cuda-cupti-cu12==12.6.80
|
141 |
+
nvidia-cuda-nvrtc-cu12==12.6.77
|
142 |
+
nvidia-cuda-runtime-cu12==12.6.77
|
143 |
+
nvidia-cudnn-cu12==9.5.1.17
|
144 |
+
nvidia-cufft-cu12==11.3.0.4
|
145 |
+
nvidia-cufile-cu12==1.11.1.6
|
146 |
+
nvidia-curand-cu12==10.3.7.77
|
147 |
+
nvidia-cusolver-cu12==11.7.1.2
|
148 |
+
nvidia-cusparse-cu12==12.5.4.2
|
149 |
+
nvidia-cusparselt-cu12==0.6.3
|
150 |
+
nvidia-nccl-cu12==2.26.2
|
151 |
+
nvidia-nvjitlink-cu12==12.6.85
|
152 |
+
nvidia-nvtx-cu12==12.6.77
|
153 |
+
oauthlib==3.2.2
|
154 |
+
ollama==0.4.8
|
155 |
+
onnxruntime==1.20.1
|
156 |
+
openai==1.60.2
|
157 |
+
opencv-python==4.11.0.86
|
158 |
+
openpyxl==3.1.5
|
159 |
+
opentelemetry-api==1.29.0
|
160 |
+
opentelemetry-exporter-otlp-proto-common==1.29.0
|
161 |
+
opentelemetry-exporter-otlp-proto-grpc==1.29.0
|
162 |
+
opentelemetry-instrumentation==0.50b0
|
163 |
+
opentelemetry-instrumentation-asgi==0.50b0
|
164 |
+
opentelemetry-instrumentation-fastapi==0.50b0
|
165 |
+
opentelemetry-proto==1.29.0
|
166 |
+
opentelemetry-sdk==1.29.0
|
167 |
+
opentelemetry-semantic-conventions==0.50b0
|
168 |
+
opentelemetry-util-http==0.50b0
|
169 |
+
orjson==3.10.15
|
170 |
+
outcome==1.3.0.post0
|
171 |
+
overrides==7.7.0
|
172 |
+
packaging==24.2
|
173 |
+
pandas==2.2.3
|
174 |
+
paperscraper==0.3.0
|
175 |
+
parso==0.8.4
|
176 |
+
pathspec==0.12.1
|
177 |
+
pexpect==4.9.0
|
178 |
+
pillow==11.1.0
|
179 |
+
platformdirs==4.3.7
|
180 |
+
pluggy==1.6.0
|
181 |
+
posthog==3.11.0
|
182 |
+
prettytable==3.16.0
|
183 |
+
primp==0.15.0
|
184 |
+
prompt_toolkit==3.0.50
|
185 |
+
propcache==0.2.1
|
186 |
+
protobuf==5.29.3
|
187 |
+
psutil==7.0.0
|
188 |
+
ptyprocess==0.7.0
|
189 |
+
pure_eval==0.2.3
|
190 |
+
pyarrow==19.0.0
|
191 |
+
pyasn1==0.6.1
|
192 |
+
pyasn1_modules==0.4.1
|
193 |
+
pycodestyle==2.13.0
|
194 |
+
pydantic==2.10.6
|
195 |
+
pydantic-settings==2.7.1
|
196 |
+
pydantic_core==2.27.2
|
197 |
+
pydeck==0.9.1
|
198 |
+
pydub==0.25.1
|
199 |
+
pyflakes==3.3.2
|
200 |
+
Pygments==2.19.1
|
201 |
+
pylint==3.3.6
|
202 |
+
pymed_paperscraper==1.0.4
|
203 |
+
PyMuPDF==1.25.5
|
204 |
+
pyparsing==3.2.3
|
205 |
+
pypdf==5.2.0
|
206 |
+
PyPika==0.48.9
|
207 |
+
pyproject_hooks==1.2.0
|
208 |
+
pyreadline3==3.5.4
|
209 |
+
PySocks==1.7.1
|
210 |
+
pytesseract==0.3.13
|
211 |
+
pytest==8.3.5
|
212 |
+
python-dateutil==2.9.0.post0
|
213 |
+
python-dotenv==1.0.1
|
214 |
+
python-multipart==0.0.20
|
215 |
+
pytz==2025.1
|
216 |
+
PyYAML==6.0.2
|
217 |
+
pyzmq==26.4.0
|
218 |
+
rank-bm25==0.2.2
|
219 |
+
RapidFuzz==3.13.0
|
220 |
+
readability-lxml==0.8.4.1
|
221 |
+
referencing==0.36.2
|
222 |
+
regex==2024.11.6
|
223 |
+
requests==2.32.3
|
224 |
+
requests-file==2.1.0
|
225 |
+
requests-oauthlib==2.0.0
|
226 |
+
requests-toolbelt==1.0.0
|
227 |
+
rich==13.9.4
|
228 |
+
roman-numerals-py==3.1.0
|
229 |
+
rpds-py==0.22.3
|
230 |
+
rsa==4.9
|
231 |
+
ruff==0.9.4
|
232 |
+
safehttpx==0.1.6
|
233 |
+
safetensors==0.5.2
|
234 |
+
scholarly==1.7.11
|
235 |
+
scikit-learn==1.6.1
|
236 |
+
scipy==1.15.1
|
237 |
+
seaborn==0.13.2
|
238 |
+
selenium==4.32.0
|
239 |
+
semantic-version==2.10.0
|
240 |
+
semanticscholar==0.10.0
|
241 |
+
sentence-transformers==3.4.1
|
242 |
+
sentencepiece==0.2.0
|
243 |
+
sgmllib3k==1.0.0
|
244 |
+
shellingham==1.5.4
|
245 |
+
simple-loggers==1.0.5
|
246 |
+
six==1.17.0
|
247 |
+
smmap==5.0.2
|
248 |
+
smolagents==1.15.0
|
249 |
+
sniffio==1.3.1
|
250 |
+
snowballstemmer==3.0.1
|
251 |
+
sortedcontainers==2.4.0
|
252 |
+
soupsieve==2.6
|
253 |
+
SpeechRecognition==3.14.3
|
254 |
+
Sphinx==8.2.3
|
255 |
+
sphinx-rtd-theme==3.0.2
|
256 |
+
sphinxcontrib-applehelp==2.0.0
|
257 |
+
sphinxcontrib-devhelp==2.0.0
|
258 |
+
sphinxcontrib-htmlhelp==2.1.0
|
259 |
+
sphinxcontrib-jquery==4.1
|
260 |
+
sphinxcontrib-jsmath==1.0.1
|
261 |
+
sphinxcontrib-qthelp==2.0.0
|
262 |
+
sphinxcontrib-serializinghtml==2.0.0
|
263 |
+
sql-manager==1.0.5
|
264 |
+
SQLAlchemy==2.0.37
|
265 |
+
stack-data==0.6.3
|
266 |
+
starlette==0.45.3
|
267 |
+
streamlit==1.41.1
|
268 |
+
sympy==1.14.0
|
269 |
+
tenacity==9.0.0
|
270 |
+
thefuzz==0.22.1
|
271 |
+
threadpoolctl==3.5.0
|
272 |
+
tiktoken==0.8.0
|
273 |
+
tldextract==5.3.0
|
274 |
+
tokenizers==0.21.0
|
275 |
+
toml==0.10.2
|
276 |
+
tomlkit==0.13.2
|
277 |
+
torch==2.7.0
|
278 |
+
torchvision==0.22.0
|
279 |
+
tornado==6.4.2
|
280 |
+
tqdm==4.67.1
|
281 |
+
traitlets==5.14.3
|
282 |
+
transformers==4.51.3
|
283 |
+
trio==0.30.0
|
284 |
+
trio-websocket==0.12.2
|
285 |
+
triton==3.3.0
|
286 |
+
typer==0.15.1
|
287 |
+
types-python-dateutil==2.9.0.20250516
|
288 |
+
typing-inspect==0.9.0
|
289 |
+
typing_extensions==4.12.2
|
290 |
+
tzdata==2025.1
|
291 |
+
urllib3==2.3.0
|
292 |
+
uvicorn==0.34.0
|
293 |
+
uvloop==0.21.0
|
294 |
+
watchdog==6.0.0
|
295 |
+
watchfiles==1.0.4
|
296 |
+
wcwidth==0.2.13
|
297 |
+
webrequests==1.0.8
|
298 |
+
websocket-client==1.8.0
|
299 |
+
websockets==14.2
|
300 |
+
whisper==1.1.10
|
301 |
+
widgetsnbextension==4.0.14
|
302 |
+
wikipedia==1.4.0
|
303 |
+
Wikipedia-API==0.8.1
|
304 |
+
wrapt==1.17.2
|
305 |
+
wsproto==1.2.0
|
306 |
+
xxhash==3.5.0
|
307 |
+
yarl==1.18.3
|
308 |
+
zipp==3.21.0
|
309 |
+
zstandard==0.23.0
|
langtools.py
CHANGED
@@ -1,13 +1,11 @@
|
|
1 |
-
import os
|
2 |
from dotenv import load_dotenv
|
|
|
3 |
from langchain_community.tools.tavily_search import TavilySearchResults
|
4 |
-
from langchain_community.document_loaders import WikipediaLoader
|
5 |
-
from langchain_community.document_loaders import ArxivLoader
|
6 |
from langchain_core.tools import tool
|
7 |
|
8 |
-
|
9 |
load_dotenv()
|
10 |
|
|
|
11 |
@tool
|
12 |
def multiply(a: int, b: int) -> int:
|
13 |
"""Multiply two numbers.
|
@@ -17,30 +15,33 @@ def multiply(a: int, b: int) -> int:
|
|
17 |
"""
|
18 |
return a * b
|
19 |
|
|
|
20 |
@tool
|
21 |
def add(a: int, b: int) -> int:
|
22 |
"""Add two numbers.
|
23 |
-
|
24 |
Args:
|
25 |
a: first int
|
26 |
b: second int
|
27 |
"""
|
28 |
return a + b
|
29 |
|
|
|
30 |
@tool
|
31 |
def subtract(a: int, b: int) -> int:
|
32 |
"""Subtract two numbers.
|
33 |
-
|
34 |
Args:
|
35 |
a: first int
|
36 |
b: second int
|
37 |
"""
|
38 |
return a - b
|
39 |
|
|
|
40 |
@tool
|
41 |
def divide(a: int, b: int) -> int:
|
42 |
"""Divide two numbers.
|
43 |
-
|
44 |
Args:
|
45 |
a: first int
|
46 |
b: second int
|
@@ -49,20 +50,22 @@ def divide(a: int, b: int) -> int:
|
|
49 |
raise ValueError("Cannot divide by zero.")
|
50 |
return a / b
|
51 |
|
|
|
52 |
@tool
|
53 |
def modulus(a: int, b: int) -> int:
|
54 |
"""Get the modulus of two numbers.
|
55 |
-
|
56 |
Args:
|
57 |
a: first int
|
58 |
b: second int
|
59 |
"""
|
60 |
return a % b
|
61 |
|
|
|
62 |
@tool
|
63 |
def wiki_search(query: str) -> str:
|
64 |
"""Tool to search Wikipedia for a query about a known or historical person or subject and return maximum 2 results.
|
65 |
-
|
66 |
Args:
|
67 |
query: The search query."""
|
68 |
search_docs = WikipediaLoader(query=query, load_max_docs=2).load()
|
@@ -70,13 +73,15 @@ def wiki_search(query: str) -> str:
|
|
70 |
[
|
71 |
f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>'
|
72 |
for doc in search_docs
|
73 |
-
]
|
|
|
74 |
return {"wiki_results": formatted_search_docs}
|
75 |
|
|
|
76 |
@tool
|
77 |
def web_search(query: str) -> str:
|
78 |
"""Search Tavily for a query and return maximum 3 results.
|
79 |
-
|
80 |
Args:
|
81 |
query: The search query."""
|
82 |
search_docs = TavilySearchResults(max_results=3).invoke(query=query)
|
@@ -84,13 +89,15 @@ def web_search(query: str) -> str:
|
|
84 |
[
|
85 |
f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>'
|
86 |
for doc in search_docs
|
87 |
-
]
|
|
|
88 |
return {"web_results": formatted_search_docs}
|
89 |
|
|
|
90 |
@tool
|
91 |
def arvix_search(query: str) -> str:
|
92 |
"""Tool to search Arxiv for a query about a research paper or article and return maximum 3 results.
|
93 |
-
|
94 |
Args:
|
95 |
query: The search query."""
|
96 |
search_docs = ArxivLoader(query=query, load_max_docs=3).load()
|
@@ -101,4 +108,3 @@ def arvix_search(query: str) -> str:
|
|
101 |
# ])
|
102 |
# return {"arvix_results": formatted_search_docs}
|
103 |
return search_docs
|
104 |
-
|
|
|
|
|
1 |
from dotenv import load_dotenv
|
2 |
+
from langchain_community.document_loaders import ArxivLoader, WikipediaLoader
|
3 |
from langchain_community.tools.tavily_search import TavilySearchResults
|
|
|
|
|
4 |
from langchain_core.tools import tool
|
5 |
|
|
|
6 |
load_dotenv()
|
7 |
|
8 |
+
|
9 |
@tool
|
10 |
def multiply(a: int, b: int) -> int:
|
11 |
"""Multiply two numbers.
|
|
|
15 |
"""
|
16 |
return a * b
|
17 |
|
18 |
+
|
19 |
@tool
|
20 |
def add(a: int, b: int) -> int:
|
21 |
"""Add two numbers.
|
22 |
+
|
23 |
Args:
|
24 |
a: first int
|
25 |
b: second int
|
26 |
"""
|
27 |
return a + b
|
28 |
|
29 |
+
|
30 |
@tool
|
31 |
def subtract(a: int, b: int) -> int:
|
32 |
"""Subtract two numbers.
|
33 |
+
|
34 |
Args:
|
35 |
a: first int
|
36 |
b: second int
|
37 |
"""
|
38 |
return a - b
|
39 |
|
40 |
+
|
41 |
@tool
|
42 |
def divide(a: int, b: int) -> int:
|
43 |
"""Divide two numbers.
|
44 |
+
|
45 |
Args:
|
46 |
a: first int
|
47 |
b: second int
|
|
|
50 |
raise ValueError("Cannot divide by zero.")
|
51 |
return a / b
|
52 |
|
53 |
+
|
54 |
@tool
|
55 |
def modulus(a: int, b: int) -> int:
|
56 |
"""Get the modulus of two numbers.
|
57 |
+
|
58 |
Args:
|
59 |
a: first int
|
60 |
b: second int
|
61 |
"""
|
62 |
return a % b
|
63 |
|
64 |
+
|
65 |
@tool
|
66 |
def wiki_search(query: str) -> str:
|
67 |
"""Tool to search Wikipedia for a query about a known or historical person or subject and return maximum 2 results.
|
68 |
+
|
69 |
Args:
|
70 |
query: The search query."""
|
71 |
search_docs = WikipediaLoader(query=query, load_max_docs=2).load()
|
|
|
73 |
[
|
74 |
f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>'
|
75 |
for doc in search_docs
|
76 |
+
]
|
77 |
+
)
|
78 |
return {"wiki_results": formatted_search_docs}
|
79 |
|
80 |
+
|
81 |
@tool
|
82 |
def web_search(query: str) -> str:
|
83 |
"""Search Tavily for a query and return maximum 3 results.
|
84 |
+
|
85 |
Args:
|
86 |
query: The search query."""
|
87 |
search_docs = TavilySearchResults(max_results=3).invoke(query=query)
|
|
|
89 |
[
|
90 |
f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>'
|
91 |
for doc in search_docs
|
92 |
+
]
|
93 |
+
)
|
94 |
return {"web_results": formatted_search_docs}
|
95 |
|
96 |
+
|
97 |
@tool
|
98 |
def arvix_search(query: str) -> str:
|
99 |
"""Tool to search Arxiv for a query about a research paper or article and return maximum 3 results.
|
100 |
+
|
101 |
Args:
|
102 |
query: The search query."""
|
103 |
search_docs = ArxivLoader(query=query, load_max_docs=3).load()
|
|
|
108 |
# ])
|
109 |
# return {"arvix_results": formatted_search_docs}
|
110 |
return search_docs
|
|
requirements.txt
CHANGED
@@ -1,16 +1,23 @@
|
|
|
|
|
|
|
|
1 |
gradio
|
2 |
-
|
3 |
langchain
|
|
|
4 |
langchain-community
|
5 |
langchain-core
|
6 |
-
langchain-google-genai
|
7 |
-
langchain-huggingface
|
8 |
langchain-groq
|
|
|
|
|
9 |
langchain-tavily
|
10 |
-
langchain-chroma
|
11 |
langgraph
|
12 |
-
|
13 |
-
|
14 |
-
|
|
|
15 |
python-dotenv
|
16 |
-
|
|
|
|
|
|
|
|
1 |
+
beautifulsoup4
|
2 |
+
chromadb
|
3 |
+
duckduckgo_search
|
4 |
gradio
|
5 |
+
huggingface_hub
|
6 |
langchain
|
7 |
+
langchain-chroma
|
8 |
langchain-community
|
9 |
langchain-core
|
|
|
|
|
10 |
langchain-groq
|
11 |
+
langchain-huggingface
|
12 |
+
langchain-google-genai
|
13 |
langchain-tavily
|
|
|
14 |
langgraph
|
15 |
+
markdownify
|
16 |
+
pandas
|
17 |
+
protobuf==3.20.*
|
18 |
+
PyMuPDF
|
19 |
python-dotenv
|
20 |
+
requests
|
21 |
+
sentence-transformers
|
22 |
+
smolagents
|
23 |
+
traitlets
|
setup_actions.ipynb
CHANGED
@@ -16,9 +16,10 @@
|
|
16 |
}
|
17 |
],
|
18 |
"source": [
|
19 |
-
"from dotenv import load_dotenv\n",
|
20 |
"import os\n",
|
21 |
"\n",
|
|
|
|
|
22 |
"load_dotenv()\n",
|
23 |
"token = os.getenv(\"HUGGINGFACE_TOKEN\")\n",
|
24 |
"\n",
|
@@ -265,7 +266,13 @@
|
|
265 |
"source": [
|
266 |
"from datasets import load_dataset\n",
|
267 |
"\n",
|
268 |
-
"dataset = load_dataset(\"
|
|
|
|
|
|
|
|
|
|
|
|
|
269 |
]
|
270 |
},
|
271 |
{
|
@@ -286,7 +293,9 @@
|
|
286 |
}
|
287 |
],
|
288 |
"source": [
|
289 |
-
"os.listdir(
|
|
|
|
|
290 |
]
|
291 |
},
|
292 |
{
|
@@ -307,11 +316,17 @@
|
|
307 |
"source": [
|
308 |
"from datasets import load_dataset\n",
|
309 |
"from langchain.embeddings import HuggingFaceEmbeddings\n",
|
310 |
-
"from langchain.vectorstores import Chroma\n",
|
311 |
"from langchain.schema import Document\n",
|
|
|
312 |
"\n",
|
313 |
"# Load the GAIA validation dataset\n",
|
314 |
-
"dataset = load_dataset(\
|
|
|
|
|
|
|
|
|
|
|
|
|
315 |
"# Prepare the embeddings model\n",
|
316 |
"embeddings = HuggingFaceEmbeddings(model_name=\"all-MiniLM-L6-v2\")\n",
|
317 |
"\n",
|
@@ -320,29 +335,24 @@
|
|
320 |
"for entry in dataset:\n",
|
321 |
" question = entry[\"Question\"]\n",
|
322 |
" answer = entry[\"Final answer\"]\n",
|
323 |
-
"
|
324 |
" # Create a document with both the question and the answer as metadata\n",
|
325 |
" metadata = {\n",
|
326 |
" \"task_id\": entry[\"task_id\"],\n",
|
327 |
" \"steps\": entry[\"Annotator Metadata\"][\"Steps\"],\n",
|
328 |
" \"tools\": entry[\"Annotator Metadata\"][\"Tools\"],\n",
|
329 |
-
" \"answer\": answer
|
330 |
" }\n",
|
331 |
-
"
|
332 |
" # Add the question to the list of documents\n",
|
333 |
-
" documents.append(\n",
|
334 |
-
" Document(\n",
|
335 |
-
" page_content=question,\n",
|
336 |
-
" metadata=metadata\n",
|
337 |
-
" )\n",
|
338 |
-
" )\n",
|
339 |
"\n",
|
340 |
"# Insert the documents into Chroma\n",
|
341 |
"vectorstore = Chroma.from_documents(\n",
|
342 |
" documents=documents,\n",
|
343 |
" embedding=embeddings,\n",
|
344 |
" collection_name=\"gaia_validation\",\n",
|
345 |
-
" persist_directory=\"./chroma_store\"
|
346 |
")\n",
|
347 |
"\n",
|
348 |
"# Persist the data for future use\n",
|
@@ -376,19 +386,19 @@
|
|
376 |
"for entry in dataset:\n",
|
377 |
" # Access the tools used (they are stored in the 'Tools' field of 'Annotator Metadata')\n",
|
378 |
" tools = entry[\"Annotator Metadata\"][\"Tools\"]\n",
|
379 |
-
"
|
380 |
" # Split the tools into a list (since they are stored as a string, we split by line breaks)\n",
|
381 |
-
" tools_list = tools.split(
|
382 |
-
"
|
383 |
" # Add each tool to the set (set automatically ensures uniqueness)\n",
|
384 |
" for tool in tools_list:\n",
|
385 |
" unique_tools.add(tool.strip()) # Remove any extra spaces or newlines\n",
|
386 |
"\n",
|
387 |
"# Convert the set of unique tools to a dictionary under the key 'tools'\n",
|
388 |
-
"tools_dict = {
|
389 |
"\n",
|
390 |
"# Print the unique tools to get a sense of what was used\n",
|
391 |
-
"print(tools_dict)
|
392 |
]
|
393 |
},
|
394 |
{
|
|
|
16 |
}
|
17 |
],
|
18 |
"source": [
|
|
|
19 |
"import os\n",
|
20 |
"\n",
|
21 |
+
"from dotenv import load_dotenv\n",
|
22 |
+
"\n",
|
23 |
"load_dotenv()\n",
|
24 |
"token = os.getenv(\"HUGGINGFACE_TOKEN\")\n",
|
25 |
"\n",
|
|
|
266 |
"source": [
|
267 |
"from datasets import load_dataset\n",
|
268 |
"\n",
|
269 |
+
"dataset = load_dataset(\n",
|
270 |
+
" \"gaia-benchmark/GAIA\",\n",
|
271 |
+
" name=\"2023_level1\",\n",
|
272 |
+
" split=\"validation\",\n",
|
273 |
+
" trust_remote_code=True,\n",
|
274 |
+
" cache_dir=\"ragdata\",\n",
|
275 |
+
")"
|
276 |
]
|
277 |
},
|
278 |
{
|
|
|
293 |
}
|
294 |
],
|
295 |
"source": [
|
296 |
+
"os.listdir(\n",
|
297 |
+
" r\"ragdata/gaia-benchmark___gaia/2023_level1/0.0.1/ec492fe4320ee795b1aed6bb46229c5f693226b0f1316347501c24b4baeee005\"\n",
|
298 |
+
")"
|
299 |
]
|
300 |
},
|
301 |
{
|
|
|
316 |
"source": [
|
317 |
"from datasets import load_dataset\n",
|
318 |
"from langchain.embeddings import HuggingFaceEmbeddings\n",
|
|
|
319 |
"from langchain.schema import Document\n",
|
320 |
+
"from langchain.vectorstores import Chroma\n",
|
321 |
"\n",
|
322 |
"# Load the GAIA validation dataset\n",
|
323 |
+
"dataset = load_dataset(\n",
|
324 |
+
" \"gaia-benchmark/GAIA\",\n",
|
325 |
+
" name=\"2023_level1\",\n",
|
326 |
+
" split=\"validation\",\n",
|
327 |
+
" trust_remote_code=True,\n",
|
328 |
+
" cache_dir=\"ragdata\",\n",
|
329 |
+
")\n",
|
330 |
"# Prepare the embeddings model\n",
|
331 |
"embeddings = HuggingFaceEmbeddings(model_name=\"all-MiniLM-L6-v2\")\n",
|
332 |
"\n",
|
|
|
335 |
"for entry in dataset:\n",
|
336 |
" question = entry[\"Question\"]\n",
|
337 |
" answer = entry[\"Final answer\"]\n",
|
338 |
+
"\n",
|
339 |
" # Create a document with both the question and the answer as metadata\n",
|
340 |
" metadata = {\n",
|
341 |
" \"task_id\": entry[\"task_id\"],\n",
|
342 |
" \"steps\": entry[\"Annotator Metadata\"][\"Steps\"],\n",
|
343 |
" \"tools\": entry[\"Annotator Metadata\"][\"Tools\"],\n",
|
344 |
+
" \"answer\": answer,\n",
|
345 |
" }\n",
|
346 |
+
"\n",
|
347 |
" # Add the question to the list of documents\n",
|
348 |
+
" documents.append(Document(page_content=question, metadata=metadata))\n",
|
|
|
|
|
|
|
|
|
|
|
349 |
"\n",
|
350 |
"# Insert the documents into Chroma\n",
|
351 |
"vectorstore = Chroma.from_documents(\n",
|
352 |
" documents=documents,\n",
|
353 |
" embedding=embeddings,\n",
|
354 |
" collection_name=\"gaia_validation\",\n",
|
355 |
+
" persist_directory=\"./chroma_store\",\n",
|
356 |
")\n",
|
357 |
"\n",
|
358 |
"# Persist the data for future use\n",
|
|
|
386 |
"for entry in dataset:\n",
|
387 |
" # Access the tools used (they are stored in the 'Tools' field of 'Annotator Metadata')\n",
|
388 |
" tools = entry[\"Annotator Metadata\"][\"Tools\"]\n",
|
389 |
+
"\n",
|
390 |
" # Split the tools into a list (since they are stored as a string, we split by line breaks)\n",
|
391 |
+
" tools_list = tools.split(\"\\n\")\n",
|
392 |
+
"\n",
|
393 |
" # Add each tool to the set (set automatically ensures uniqueness)\n",
|
394 |
" for tool in tools_list:\n",
|
395 |
" unique_tools.add(tool.strip()) # Remove any extra spaces or newlines\n",
|
396 |
"\n",
|
397 |
"# Convert the set of unique tools to a dictionary under the key 'tools'\n",
|
398 |
+
"tools_dict = {\"tools\": list(unique_tools)}\n",
|
399 |
"\n",
|
400 |
"# Print the unique tools to get a sense of what was used\n",
|
401 |
+
"print(tools_dict)"
|
402 |
]
|
403 |
},
|
404 |
{
|
tools copy.py
CHANGED
@@ -1,18 +1,13 @@
|
|
1 |
import os
|
2 |
import re
|
3 |
import tempfile
|
4 |
-
import
|
5 |
-
|
6 |
-
import pandas as pd
|
7 |
import fitz # PyMuPDF
|
8 |
-
|
9 |
-
from smolagents import Tool
|
10 |
-
from smolagents import Tool
|
11 |
import requests
|
12 |
-
import traceback
|
13 |
-
from langchain_community.retrievers import BM25Retriever
|
14 |
from smolagents import Tool
|
15 |
-
|
16 |
|
17 |
class DownloadFileFromTaskTool(Tool):
|
18 |
name = "download_file_from_task"
|
@@ -20,15 +15,12 @@ class DownloadFileFromTaskTool(Tool):
|
|
20 |
Use this when question requires information from a mentioned file, before reading a file."""
|
21 |
|
22 |
inputs = {
|
23 |
-
"task_id": {
|
24 |
-
"type": "string",
|
25 |
-
"description": "The GAIA task ID (REQUIRED)."
|
26 |
-
},
|
27 |
"filename": {
|
28 |
"type": "string",
|
29 |
"description": "Optional custom filename to save the file as (e.g., 'data.xlsx').",
|
30 |
-
"nullable": True
|
31 |
-
}
|
32 |
}
|
33 |
output_type = "string"
|
34 |
|
@@ -65,15 +57,13 @@ class DownloadFileFromTaskTool(Tool):
|
|
65 |
except Exception as e:
|
66 |
return f"β Error: {e}"
|
67 |
|
|
|
68 |
class ReadFileContentTool(Tool):
|
69 |
name = "read_file_content"
|
70 |
description = """Reads and returns the content of a file. Use after downloading a file using `download_file_from_task`."""
|
71 |
|
72 |
inputs = {
|
73 |
-
"file_path": {
|
74 |
-
"type": "string",
|
75 |
-
"description": "Full path to a file to read."
|
76 |
-
}
|
77 |
}
|
78 |
output_type = "string"
|
79 |
|
@@ -124,15 +114,16 @@ class ReadFileContentTool(Tool):
|
|
124 |
except Exception as e:
|
125 |
return f"β Could not read {file_path}: {e}"
|
126 |
|
|
|
127 |
class GetWikipediaInfoTool(Tool):
|
128 |
name = "get_wikipedia_info"
|
129 |
description = """Fetches a short summary about a topic from Wikipedia.
|
130 |
Use this when a user asks for background information, an explanation, or context on a well-known subject."""
|
131 |
-
|
132 |
inputs = {
|
133 |
"topic": {
|
134 |
"type": "string",
|
135 |
-
"description": "The topic to search for on Wikipedia."
|
136 |
}
|
137 |
}
|
138 |
output_type = "string"
|
@@ -145,10 +136,10 @@ Use this when a user asks for background information, an explanation, or context
|
|
145 |
search_response.raise_for_status()
|
146 |
search_data = search_response.json()
|
147 |
|
148 |
-
if not search_data.get(
|
149 |
return f"No Wikipedia info for '{topic}'."
|
150 |
|
151 |
-
page_id = search_data[
|
152 |
|
153 |
content_url = (
|
154 |
f"https://en.wikipedia.org/w/api.php?action=query&prop=extracts&"
|
@@ -158,7 +149,7 @@ Use this when a user asks for background information, an explanation, or context
|
|
158 |
content_response.raise_for_status()
|
159 |
content_data = content_response.json()
|
160 |
|
161 |
-
extract = content_data[
|
162 |
if len(extract) > 1500:
|
163 |
extract = extract[:1500] + "..."
|
164 |
|
@@ -171,6 +162,7 @@ Use this when a user asks for background information, an explanation, or context
|
|
171 |
traceback.print_exc()
|
172 |
return f"Error wiki: {e}"
|
173 |
|
|
|
174 |
class VisitWebpageTool(Tool):
|
175 |
name = "visit_webpage"
|
176 |
description = """
|
@@ -181,23 +173,24 @@ class VisitWebpageTool(Tool):
|
|
181 |
inputs = {
|
182 |
"url": {
|
183 |
"type": "string",
|
184 |
-
"description": "The full URL of the webpage to visit."
|
185 |
}
|
186 |
}
|
187 |
output_type = "string"
|
188 |
|
189 |
def forward(self, url: str) -> str:
|
190 |
try:
|
|
|
|
|
191 |
import requests
|
192 |
from bs4 import BeautifulSoup
|
193 |
-
import json
|
194 |
|
195 |
response = requests.get(url, timeout=10)
|
196 |
response.raise_for_status()
|
197 |
soup = BeautifulSoup(response.text, "html.parser")
|
198 |
|
199 |
def clean(text):
|
200 |
-
return
|
201 |
|
202 |
def extract_tables(soup):
|
203 |
tables_data = []
|
@@ -254,57 +247,57 @@ class VisitWebpageTool(Tool):
|
|
254 |
|
255 |
except Exception as e:
|
256 |
return f"β Failed to fetch or parse webpage: {str(e)}"
|
257 |
-
|
|
|
258 |
class TranscribeAudioTool(Tool):
|
259 |
name = "transcribe_audio"
|
260 |
-
description =
|
|
|
|
|
261 |
|
262 |
-
inputs = {
|
263 |
-
"file_path": {
|
264 |
-
"type": "string",
|
265 |
-
"description": "Path to an audio file."
|
266 |
-
}
|
267 |
-
}
|
268 |
output_type = "string"
|
269 |
|
270 |
def forward(self, file_path: str) -> str:
|
271 |
try:
|
272 |
-
import speech_recognition as sr
|
273 |
-
from pydub import AudioSegment
|
274 |
import os
|
275 |
import tempfile
|
276 |
-
|
|
|
|
|
|
|
277 |
# Initialize recognizer
|
278 |
recognizer = sr.Recognizer()
|
279 |
-
|
280 |
# Convert to WAV if not already (needed for speech_recognition)
|
281 |
file_ext = os.path.splitext(file_path)[1].lower()
|
282 |
-
|
283 |
-
if file_ext !=
|
284 |
# Create temp WAV file
|
285 |
-
temp_wav = tempfile.NamedTemporaryFile(suffix=
|
286 |
-
|
287 |
# Convert to WAV using pydub
|
288 |
audio = AudioSegment.from_file(file_path)
|
289 |
audio.export(temp_wav, format="wav")
|
290 |
audio_path = temp_wav
|
291 |
else:
|
292 |
audio_path = file_path
|
293 |
-
|
294 |
# Transcribe audio using Google's speech recognition
|
295 |
with sr.AudioFile(audio_path) as source:
|
296 |
audio_data = recognizer.record(source)
|
297 |
transcript = recognizer.recognize_google(audio_data)
|
298 |
-
|
299 |
# Clean up temp file if created
|
300 |
-
if file_ext !=
|
301 |
os.remove(temp_wav)
|
302 |
-
|
303 |
return transcript.strip()
|
304 |
-
|
305 |
except Exception as e:
|
306 |
return f"β Transcription failed: {str(e)}"
|
307 |
|
|
|
308 |
class TranscibeVideoFileTool(Tool):
|
309 |
name = "transcribe_video"
|
310 |
description = """Transcribes speech from a video file. Use this to understand video lectures, tutorials, or visual demos."""
|
@@ -312,41 +305,42 @@ class TranscibeVideoFileTool(Tool):
|
|
312 |
inputs = {
|
313 |
"file_path": {
|
314 |
"type": "string",
|
315 |
-
"description": "Path to the video file (e.g., .mp4, .mov)."
|
316 |
}
|
317 |
}
|
318 |
output_type = "string"
|
319 |
|
320 |
def forward(self, file_path: str) -> str:
|
321 |
try:
|
322 |
-
import moviepy.editor as mp
|
323 |
-
import speech_recognition as sr
|
324 |
import os
|
325 |
import tempfile
|
326 |
-
|
|
|
|
|
|
|
327 |
# Extract audio from video
|
328 |
video = mp.VideoFileClip(file_path)
|
329 |
-
|
330 |
# Create temporary audio file
|
331 |
-
temp_audio = tempfile.NamedTemporaryFile(suffix=
|
332 |
-
|
333 |
# Extract audio to WAV format (required for speech_recognition)
|
334 |
video.audio.write_audiofile(temp_audio, verbose=False, logger=None)
|
335 |
video.close()
|
336 |
-
|
337 |
# Initialize recognizer
|
338 |
recognizer = sr.Recognizer()
|
339 |
-
|
340 |
# Transcribe audio
|
341 |
with sr.AudioFile(temp_audio) as source:
|
342 |
audio_data = recognizer.record(source)
|
343 |
transcript = recognizer.recognize_google(audio_data)
|
344 |
-
|
345 |
# Clean up temp file
|
346 |
if os.path.exists(temp_audio):
|
347 |
os.remove(temp_audio)
|
348 |
-
|
349 |
return transcript.strip()
|
350 |
-
|
351 |
except Exception as e:
|
352 |
-
return f"β Video processing failed: {str(e)}"
|
|
|
1 |
import os
|
2 |
import re
|
3 |
import tempfile
|
4 |
+
import traceback
|
5 |
+
|
|
|
6 |
import fitz # PyMuPDF
|
7 |
+
import pandas as pd
|
|
|
|
|
8 |
import requests
|
|
|
|
|
9 |
from smolagents import Tool
|
10 |
+
|
11 |
|
12 |
class DownloadFileFromTaskTool(Tool):
|
13 |
name = "download_file_from_task"
|
|
|
15 |
Use this when question requires information from a mentioned file, before reading a file."""
|
16 |
|
17 |
inputs = {
|
18 |
+
"task_id": {"type": "string", "description": "The GAIA task ID (REQUIRED)."},
|
|
|
|
|
|
|
19 |
"filename": {
|
20 |
"type": "string",
|
21 |
"description": "Optional custom filename to save the file as (e.g., 'data.xlsx').",
|
22 |
+
"nullable": True,
|
23 |
+
},
|
24 |
}
|
25 |
output_type = "string"
|
26 |
|
|
|
57 |
except Exception as e:
|
58 |
return f"β Error: {e}"
|
59 |
|
60 |
+
|
61 |
class ReadFileContentTool(Tool):
|
62 |
name = "read_file_content"
|
63 |
description = """Reads and returns the content of a file. Use after downloading a file using `download_file_from_task`."""
|
64 |
|
65 |
inputs = {
|
66 |
+
"file_path": {"type": "string", "description": "Full path to a file to read."}
|
|
|
|
|
|
|
67 |
}
|
68 |
output_type = "string"
|
69 |
|
|
|
114 |
except Exception as e:
|
115 |
return f"β Could not read {file_path}: {e}"
|
116 |
|
117 |
+
|
118 |
class GetWikipediaInfoTool(Tool):
|
119 |
name = "get_wikipedia_info"
|
120 |
description = """Fetches a short summary about a topic from Wikipedia.
|
121 |
Use this when a user asks for background information, an explanation, or context on a well-known subject."""
|
122 |
+
|
123 |
inputs = {
|
124 |
"topic": {
|
125 |
"type": "string",
|
126 |
+
"description": "The topic to search for on Wikipedia.",
|
127 |
}
|
128 |
}
|
129 |
output_type = "string"
|
|
|
136 |
search_response.raise_for_status()
|
137 |
search_data = search_response.json()
|
138 |
|
139 |
+
if not search_data.get("query", {}).get("search", []):
|
140 |
return f"No Wikipedia info for '{topic}'."
|
141 |
|
142 |
+
page_id = search_data["query"]["search"][0]["pageid"]
|
143 |
|
144 |
content_url = (
|
145 |
f"https://en.wikipedia.org/w/api.php?action=query&prop=extracts&"
|
|
|
149 |
content_response.raise_for_status()
|
150 |
content_data = content_response.json()
|
151 |
|
152 |
+
extract = content_data["query"]["pages"][str(page_id)]["extract"]
|
153 |
if len(extract) > 1500:
|
154 |
extract = extract[:1500] + "..."
|
155 |
|
|
|
162 |
traceback.print_exc()
|
163 |
return f"Error wiki: {e}"
|
164 |
|
165 |
+
|
166 |
class VisitWebpageTool(Tool):
|
167 |
name = "visit_webpage"
|
168 |
description = """
|
|
|
173 |
inputs = {
|
174 |
"url": {
|
175 |
"type": "string",
|
176 |
+
"description": "The full URL of the webpage to visit.",
|
177 |
}
|
178 |
}
|
179 |
output_type = "string"
|
180 |
|
181 |
def forward(self, url: str) -> str:
|
182 |
try:
|
183 |
+
import json
|
184 |
+
|
185 |
import requests
|
186 |
from bs4 import BeautifulSoup
|
|
|
187 |
|
188 |
response = requests.get(url, timeout=10)
|
189 |
response.raise_for_status()
|
190 |
soup = BeautifulSoup(response.text, "html.parser")
|
191 |
|
192 |
def clean(text):
|
193 |
+
return " ".join(text.strip().split())
|
194 |
|
195 |
def extract_tables(soup):
|
196 |
tables_data = []
|
|
|
247 |
|
248 |
except Exception as e:
|
249 |
return f"β Failed to fetch or parse webpage: {str(e)}"
|
250 |
+
|
251 |
+
|
252 |
class TranscribeAudioTool(Tool):
|
253 |
name = "transcribe_audio"
|
254 |
+
description = (
|
255 |
+
"""Transcribes spoken audio (e.g. voice memos, lectures) into plain text."""
|
256 |
+
)
|
257 |
|
258 |
+
inputs = {"file_path": {"type": "string", "description": "Path to an audio file."}}
|
|
|
|
|
|
|
|
|
|
|
259 |
output_type = "string"
|
260 |
|
261 |
def forward(self, file_path: str) -> str:
|
262 |
try:
|
|
|
|
|
263 |
import os
|
264 |
import tempfile
|
265 |
+
|
266 |
+
import speech_recognition as sr
|
267 |
+
from pydub import AudioSegment
|
268 |
+
|
269 |
# Initialize recognizer
|
270 |
recognizer = sr.Recognizer()
|
271 |
+
|
272 |
# Convert to WAV if not already (needed for speech_recognition)
|
273 |
file_ext = os.path.splitext(file_path)[1].lower()
|
274 |
+
|
275 |
+
if file_ext != ".wav":
|
276 |
# Create temp WAV file
|
277 |
+
temp_wav = tempfile.NamedTemporaryFile(suffix=".wav", delete=False).name
|
278 |
+
|
279 |
# Convert to WAV using pydub
|
280 |
audio = AudioSegment.from_file(file_path)
|
281 |
audio.export(temp_wav, format="wav")
|
282 |
audio_path = temp_wav
|
283 |
else:
|
284 |
audio_path = file_path
|
285 |
+
|
286 |
# Transcribe audio using Google's speech recognition
|
287 |
with sr.AudioFile(audio_path) as source:
|
288 |
audio_data = recognizer.record(source)
|
289 |
transcript = recognizer.recognize_google(audio_data)
|
290 |
+
|
291 |
# Clean up temp file if created
|
292 |
+
if file_ext != ".wav" and os.path.exists(temp_wav):
|
293 |
os.remove(temp_wav)
|
294 |
+
|
295 |
return transcript.strip()
|
296 |
+
|
297 |
except Exception as e:
|
298 |
return f"β Transcription failed: {str(e)}"
|
299 |
|
300 |
+
|
301 |
class TranscibeVideoFileTool(Tool):
|
302 |
name = "transcribe_video"
|
303 |
description = """Transcribes speech from a video file. Use this to understand video lectures, tutorials, or visual demos."""
|
|
|
305 |
inputs = {
|
306 |
"file_path": {
|
307 |
"type": "string",
|
308 |
+
"description": "Path to the video file (e.g., .mp4, .mov).",
|
309 |
}
|
310 |
}
|
311 |
output_type = "string"
|
312 |
|
313 |
def forward(self, file_path: str) -> str:
|
314 |
try:
|
|
|
|
|
315 |
import os
|
316 |
import tempfile
|
317 |
+
|
318 |
+
import moviepy.editor as mp
|
319 |
+
import speech_recognition as sr
|
320 |
+
|
321 |
# Extract audio from video
|
322 |
video = mp.VideoFileClip(file_path)
|
323 |
+
|
324 |
# Create temporary audio file
|
325 |
+
temp_audio = tempfile.NamedTemporaryFile(suffix=".wav", delete=False).name
|
326 |
+
|
327 |
# Extract audio to WAV format (required for speech_recognition)
|
328 |
video.audio.write_audiofile(temp_audio, verbose=False, logger=None)
|
329 |
video.close()
|
330 |
+
|
331 |
# Initialize recognizer
|
332 |
recognizer = sr.Recognizer()
|
333 |
+
|
334 |
# Transcribe audio
|
335 |
with sr.AudioFile(temp_audio) as source:
|
336 |
audio_data = recognizer.record(source)
|
337 |
transcript = recognizer.recognize_google(audio_data)
|
338 |
+
|
339 |
# Clean up temp file
|
340 |
if os.path.exists(temp_audio):
|
341 |
os.remove(temp_audio)
|
342 |
+
|
343 |
return transcript.strip()
|
344 |
+
|
345 |
except Exception as e:
|
346 |
+
return f"β Video processing failed: {str(e)}"
|
tools.py
CHANGED
@@ -1,71 +1,41 @@
|
|
1 |
-
import os
|
2 |
-
import re
|
3 |
-
import tempfile
|
4 |
-
import mimetypes
|
5 |
-
import requests
|
6 |
-
import pandas as pd
|
7 |
-
import fitz # PyMuPDF
|
8 |
-
from urllib.parse import unquote
|
9 |
-
from smolagents import Tool
|
10 |
-
import requests
|
11 |
-
import traceback
|
12 |
-
import math
|
13 |
-
from langchain_community.tools import BraveSearch
|
14 |
-
from typing import List, Dict
|
15 |
-
import json
|
16 |
-
import html
|
17 |
-
import requests, cv2, numpy as np, os
|
18 |
import html
|
19 |
import json
|
20 |
-
import requests
|
21 |
-
from bs4 import BeautifulSoup
|
22 |
-
from langchain_community.document_loaders import ArxivLoader
|
23 |
-
import arxiv
|
24 |
-
from smolagents import tool
|
25 |
-
|
26 |
-
from smolagents.tools import Tool
|
27 |
-
import requests
|
28 |
-
import os
|
29 |
import mimetypes
|
|
|
|
|
|
|
30 |
import traceback
|
|
|
|
|
31 |
from urllib.parse import urlparse
|
32 |
|
33 |
-
import
|
34 |
-
import
|
|
|
|
|
|
|
|
|
35 |
from duckduckgo_search import DDGS
|
36 |
from duckduckgo_search.exceptions import (
|
|
|
37 |
DuckDuckGoSearchException,
|
38 |
RatelimitException,
|
39 |
TimeoutException,
|
40 |
-
ConversationLimitException,
|
41 |
)
|
42 |
-
|
43 |
-
from smolagents.tools import Tool
|
44 |
-
import chromadb
|
45 |
-
from pathlib import Path
|
46 |
-
import traceback
|
47 |
-
import json
|
48 |
-
import os
|
49 |
from langchain.document_loaders import (
|
50 |
-
|
|
|
|
|
|
|
|
|
51 |
)
|
52 |
from langchain.text_splitter import RecursiveCharacterTextSplitter
|
53 |
-
|
54 |
-
import chromadb.utils.embedding_functions as embedding_functions
|
55 |
-
|
56 |
-
import os
|
57 |
-
import pandas as pd
|
58 |
-
import fitz # PyMuPDF
|
59 |
-
from markdownify import markdownify
|
60 |
-
from bs4 import BeautifulSoup
|
61 |
-
import re
|
62 |
-
from smolagents.utils import truncate_content
|
63 |
-
import requests
|
64 |
-
from bs4 import BeautifulSoup
|
65 |
from markdownify import markdownify
|
66 |
-
import
|
67 |
from smolagents.utils import truncate_content
|
68 |
|
|
|
69 |
class ReadFileContentTool(Tool):
|
70 |
name = "read_file_content"
|
71 |
description = """Reads local files in various formats (text, CSV, Excel, PDF, HTML, etc.) and returns their content as readable text. Automatically detects and processes the appropriate file format."""
|
@@ -73,7 +43,7 @@ class ReadFileContentTool(Tool):
|
|
73 |
inputs = {
|
74 |
"file_path": {
|
75 |
"type": "string",
|
76 |
-
"description": "The full path to the file from which the content should be read."
|
77 |
}
|
78 |
}
|
79 |
output_type = "string"
|
@@ -91,17 +61,23 @@ class ReadFileContentTool(Tool):
|
|
91 |
|
92 |
elif ext == ".csv":
|
93 |
df = pd.read_csv(file_path)
|
94 |
-
return truncate_content(
|
|
|
|
|
95 |
|
96 |
elif ext in [".xlsx", ".xls"]:
|
97 |
df = pd.read_excel(file_path)
|
98 |
-
return truncate_content(
|
|
|
|
|
99 |
|
100 |
elif ext == ".pdf":
|
101 |
doc = fitz.open(file_path)
|
102 |
text = "".join([page.get_text() for page in doc])
|
103 |
doc.close()
|
104 |
-
return truncate_content(
|
|
|
|
|
105 |
|
106 |
elif ext == ".json":
|
107 |
with open(file_path, "r", encoding="utf-8") as f:
|
@@ -135,6 +111,7 @@ class ReadFileContentTool(Tool):
|
|
135 |
except Exception as e:
|
136 |
return f"β Could not read {file_path}: {e}"
|
137 |
|
|
|
138 |
class WikipediaSearchTool(Tool):
|
139 |
name = "wikipedia_search"
|
140 |
description = """Searches Wikipedia for a specific topic and returns a concise summary. Useful for background information on subjects, concepts, historical events, or scientific topics."""
|
@@ -142,7 +119,7 @@ class WikipediaSearchTool(Tool):
|
|
142 |
inputs = {
|
143 |
"query": {
|
144 |
"type": "string",
|
145 |
-
"description": "The query or subject to search for on Wikipedia."
|
146 |
}
|
147 |
}
|
148 |
output_type = "string"
|
@@ -155,10 +132,10 @@ class WikipediaSearchTool(Tool):
|
|
155 |
search_response.raise_for_status()
|
156 |
search_data = search_response.json()
|
157 |
|
158 |
-
if not search_data.get(
|
159 |
return f"No Wikipedia info for '{query}'."
|
160 |
|
161 |
-
page_id = search_data[
|
162 |
|
163 |
content_link = (
|
164 |
f"https://en.wikipedia.org/w/api.php?action=query&prop=extracts&"
|
@@ -168,7 +145,7 @@ class WikipediaSearchTool(Tool):
|
|
168 |
content_response.raise_for_status()
|
169 |
content_data = content_response.json()
|
170 |
|
171 |
-
extract = content_data[
|
172 |
if len(extract) > 1500:
|
173 |
extract = extract[:1500] + "..."
|
174 |
|
@@ -181,11 +158,10 @@ class WikipediaSearchTool(Tool):
|
|
181 |
traceback.print_exc()
|
182 |
return f"Error wiki: {e}"
|
183 |
|
|
|
184 |
class VisitWebpageTool(Tool):
|
185 |
name = "visit_webpage"
|
186 |
-
description =
|
187 |
-
"Loads a webpage from a URL and converts its content to markdown format. Use this to browse websites, extract information, or identify downloadable resources from a specific web address."
|
188 |
-
)
|
189 |
inputs = {
|
190 |
"url": {
|
191 |
"type": "string",
|
@@ -201,7 +177,6 @@ class VisitWebpageTool(Tool):
|
|
201 |
import requests
|
202 |
from markdownify import markdownify
|
203 |
from requests.exceptions import RequestException
|
204 |
-
|
205 |
from smolagents.utils import truncate_content
|
206 |
except ImportError as e:
|
207 |
raise ImportError(
|
@@ -220,7 +195,8 @@ class VisitWebpageTool(Tool):
|
|
220 |
return f"Error fetching the webpage: {str(e)}"
|
221 |
except Exception as e:
|
222 |
return f"An unexpected error occurred: {str(e)}"
|
223 |
-
|
|
|
224 |
class TranscribeAudioTool(Tool):
|
225 |
name = "transcribe_audio"
|
226 |
description = """Converts spoken content in audio files to text. Handles various audio formats and produces a transcript of the spoken content for analysis."""
|
@@ -228,54 +204,57 @@ class TranscribeAudioTool(Tool):
|
|
228 |
inputs = {
|
229 |
"file_path": {
|
230 |
"type": "string",
|
231 |
-
"description": "The full path to the audio file that needs to be transcribed."
|
232 |
}
|
233 |
}
|
234 |
output_type = "string"
|
235 |
|
236 |
-
|
237 |
def forward(self, file_path: str) -> str:
|
238 |
try:
|
239 |
-
import speech_recognition as sr
|
240 |
-
from pydub import AudioSegment
|
241 |
import os
|
242 |
import tempfile
|
243 |
-
|
|
|
|
|
|
|
244 |
# Verify file exists
|
245 |
if not os.path.exists(file_path):
|
246 |
-
return
|
247 |
-
|
|
|
|
|
248 |
# Initialize recognizer
|
249 |
recognizer = sr.Recognizer()
|
250 |
-
|
251 |
# Convert to WAV if not already (needed for speech_recognition)
|
252 |
file_ext = os.path.splitext(file_path)[1].lower()
|
253 |
-
|
254 |
-
if file_ext !=
|
255 |
# Create temp WAV file
|
256 |
-
temp_wav = tempfile.NamedTemporaryFile(suffix=
|
257 |
-
|
258 |
# Convert to WAV using pydub
|
259 |
audio = AudioSegment.from_file(file_path)
|
260 |
audio.export(temp_wav, format="wav")
|
261 |
audio_path = temp_wav
|
262 |
else:
|
263 |
audio_path = file_path
|
264 |
-
|
265 |
# Transcribe audio using Google's speech recognition
|
266 |
with sr.AudioFile(audio_path) as source:
|
267 |
audio_data = recognizer.record(source)
|
268 |
transcript = recognizer.recognize_google(audio_data)
|
269 |
-
|
270 |
# Clean up temp file if created
|
271 |
-
if file_ext !=
|
272 |
os.remove(temp_wav)
|
273 |
-
|
274 |
return transcript.strip()
|
275 |
-
|
276 |
except Exception as e:
|
277 |
return f"β Transcription failed: {str(e)}"
|
278 |
|
|
|
279 |
class TranscibeVideoFileTool(Tool):
|
280 |
name = "transcribe_video"
|
281 |
description = """Extracts and transcribes speech from video files. Converts the audio portion of videos into readable text for analysis or reference."""
|
@@ -283,7 +262,7 @@ class TranscibeVideoFileTool(Tool):
|
|
283 |
inputs = {
|
284 |
"file_path": {
|
285 |
"type": "string",
|
286 |
-
"description": "The full path to the video file that needs to be transcribed."
|
287 |
}
|
288 |
}
|
289 |
output_type = "string"
|
@@ -292,40 +271,44 @@ class TranscibeVideoFileTool(Tool):
|
|
292 |
try:
|
293 |
# Verify file exists
|
294 |
if not os.path.exists(file_path):
|
295 |
-
return
|
296 |
-
|
297 |
-
|
298 |
-
|
299 |
import os
|
300 |
import tempfile
|
301 |
-
|
|
|
|
|
|
|
302 |
# Extract audio from video
|
303 |
video = mp.VideoFileClip(file_path)
|
304 |
-
|
305 |
# Create temporary audio file
|
306 |
-
temp_audio = tempfile.NamedTemporaryFile(suffix=
|
307 |
-
|
308 |
# Extract audio to WAV format (required for speech_recognition)
|
309 |
video.audio.write_audiofile(temp_audio, verbose=False, logger=None)
|
310 |
video.close()
|
311 |
-
|
312 |
# Initialize recognizer
|
313 |
recognizer = sr.Recognizer()
|
314 |
-
|
315 |
# Transcribe audio
|
316 |
with sr.AudioFile(temp_audio) as source:
|
317 |
audio_data = recognizer.record(source)
|
318 |
transcript = recognizer.recognize_google(audio_data)
|
319 |
-
|
320 |
# Clean up temp file
|
321 |
if os.path.exists(temp_audio):
|
322 |
os.remove(temp_audio)
|
323 |
-
|
324 |
return transcript.strip()
|
325 |
-
|
326 |
except Exception as e:
|
327 |
return f"β Video processing failed: {str(e)}"
|
328 |
-
|
|
|
329 |
class BraveWebSearchTool(Tool):
|
330 |
name = "web_search"
|
331 |
description = """Performs web searches and returns content from top results. Provides real-time information from across the internet including current events, facts, and website content relevant to your query."""
|
@@ -333,7 +316,7 @@ class BraveWebSearchTool(Tool):
|
|
333 |
inputs = {
|
334 |
"query": {
|
335 |
"type": "string",
|
336 |
-
"description": "A web search query string (e.g., a question or query)."
|
337 |
}
|
338 |
}
|
339 |
output_type = "string"
|
@@ -366,10 +349,14 @@ class BraveWebSearchTool(Tool):
|
|
366 |
def forward(self, query: str) -> str:
|
367 |
try:
|
368 |
results_json = self.tool.run(query)
|
369 |
-
results =
|
|
|
|
|
|
|
|
|
370 |
|
371 |
output_parts = []
|
372 |
-
for i, r in enumerate(results[:self.count], start=1):
|
373 |
title = html.unescape(r.get("title", "").strip())
|
374 |
link = r.get("link", "").strip()
|
375 |
|
@@ -388,6 +375,7 @@ class BraveWebSearchTool(Tool):
|
|
388 |
except Exception as e:
|
389 |
return f"Search failed: {str(e)}"
|
390 |
|
|
|
391 |
class DescribeImageTool(Tool):
|
392 |
name = "describe_image"
|
393 |
description = """Analyzes images and generates detailed text descriptions. Identifies objects, scenes, text, and visual elements within the image to provide context or understanding."""
|
@@ -395,23 +383,27 @@ class DescribeImageTool(Tool):
|
|
395 |
inputs = {
|
396 |
"image_path": {
|
397 |
"type": "string",
|
398 |
-
"description": "The full path to the image file to describe."
|
399 |
}
|
400 |
}
|
401 |
output_type = "string"
|
402 |
|
403 |
def forward(self, image_path: str) -> str:
|
404 |
import os
|
|
|
405 |
from PIL import Image
|
406 |
-
import
|
407 |
-
from transformers import BlipProcessor, BlipForConditionalGeneration
|
408 |
|
409 |
if not os.path.exists(image_path):
|
410 |
return f"β Image file does not exist: {image_path}"
|
411 |
|
412 |
try:
|
413 |
-
processor = BlipProcessor.from_pretrained(
|
414 |
-
|
|
|
|
|
|
|
|
|
415 |
|
416 |
image = Image.open(image_path).convert("RGB")
|
417 |
inputs = processor(images=image, return_tensors="pt")
|
@@ -422,24 +414,37 @@ class DescribeImageTool(Tool):
|
|
422 |
except Exception as e:
|
423 |
return f"β Failed to describe image: {e}"
|
424 |
|
|
|
425 |
class DownloadFileFromLinkTool(Tool):
|
426 |
name = "download_file_from_link"
|
427 |
description = "Downloads files from a URL and saves them locally. Supports various formats including PDFs, documents, images, and data files. Returns the local file path for further processing."
|
428 |
|
429 |
inputs = {
|
430 |
-
"link": {
|
431 |
-
"type": "string",
|
432 |
-
"description": "The URL to download the file from."
|
433 |
-
},
|
434 |
"file_name": {
|
435 |
"type": "string",
|
436 |
"description": "Desired name of the saved file, without extension.",
|
437 |
-
"nullable": True
|
438 |
-
}
|
439 |
}
|
440 |
|
441 |
output_type = "string"
|
442 |
-
SUPPORTED_EXTENSIONS = {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
443 |
|
444 |
def forward(self, link: str, file_name: str = "taskfile") -> str:
|
445 |
print(f"β¬οΈ Downloading file from: {link}")
|
@@ -452,7 +457,9 @@ class DownloadFileFromLinkTool(Tool):
|
|
452 |
return f"β Error: Request failed - {e}"
|
453 |
|
454 |
if response.status_code != 200:
|
455 |
-
return
|
|
|
|
|
456 |
|
457 |
# Step 1: Try extracting extension from provided filename
|
458 |
base_name, provided_ext = os.path.splitext(file_name)
|
@@ -463,7 +470,9 @@ class DownloadFileFromLinkTool(Tool):
|
|
463 |
ext = provided_ext
|
464 |
else:
|
465 |
# Step 3: Try to infer from Content-Type
|
466 |
-
content_type =
|
|
|
|
|
467 |
guessed_ext = mimetypes.guess_extension(content_type or "") or ""
|
468 |
|
469 |
# Step 4: If mimetype returned .bin or nothing useful, try to fallback to URL
|
@@ -489,6 +498,7 @@ class DownloadFileFromLinkTool(Tool):
|
|
489 |
|
490 |
return file_path
|
491 |
|
|
|
492 |
class DuckDuckGoSearchTool(Tool):
|
493 |
name = "web_search"
|
494 |
description = """Performs web searches and returns content from top results. Provides real-time information from across the internet including current events, facts, and website content relevant to your query."""
|
@@ -496,7 +506,7 @@ class DuckDuckGoSearchTool(Tool):
|
|
496 |
inputs = {
|
497 |
"query": {
|
498 |
"type": "string",
|
499 |
-
"description": "The search query to run on DuckDuckGo"
|
500 |
},
|
501 |
}
|
502 |
output_type = "string"
|
@@ -507,7 +517,9 @@ class DuckDuckGoSearchTool(Tool):
|
|
507 |
|
508 |
def forward(self, query: str) -> str:
|
509 |
self._configure()
|
510 |
-
print(
|
|
|
|
|
511 |
|
512 |
top_results = 5
|
513 |
|
@@ -532,7 +544,7 @@ class DuckDuckGoSearchTool(Tool):
|
|
532 |
title = res.get("title", "N/A")
|
533 |
url = res.get("href", "N/A")
|
534 |
snippet = res.get("body", "N/A")
|
535 |
-
|
536 |
output_lines.append(
|
537 |
f"Result {idx}:\n"
|
538 |
f"Title: {title}\n"
|
@@ -545,9 +557,16 @@ class DuckDuckGoSearchTool(Tool):
|
|
545 |
print(f"-> Tool Result (DuckDuckGo): {output[:1500]}...")
|
546 |
return output
|
547 |
|
548 |
-
except (
|
|
|
|
|
|
|
|
|
|
|
549 |
retries += 1
|
550 |
-
print(
|
|
|
|
|
551 |
traceback.print_exc()
|
552 |
time.sleep(retry_sleep)
|
553 |
|
@@ -557,12 +576,22 @@ class DuckDuckGoSearchTool(Tool):
|
|
557 |
return f"Unhandled exception during DuckDuckGo search: {e}"
|
558 |
|
559 |
return f"β Failed to retrieve results after {max_retries} retries."
|
560 |
-
|
|
|
561 |
huggingface_ef = embedding_functions.HuggingFaceEmbeddingFunction(
|
562 |
-
api_key=os.environ["HF_TOKEN"],
|
563 |
-
model_name="sentence-transformers/all-mpnet-base-v2"
|
564 |
)
|
565 |
-
SUPPORTED_EXTENSIONS = [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
566 |
|
567 |
class AddDocumentToVectorStoreTool(Tool):
|
568 |
name = "add_document_to_vector_store"
|
@@ -599,16 +628,21 @@ class AddDocumentToVectorStoreTool(Tool):
|
|
599 |
return f"Unsupported or missing file: {file_path}"
|
600 |
|
601 |
docs = self._load_file(path)
|
602 |
-
text_splitter = RecursiveCharacterTextSplitter(
|
|
|
|
|
603 |
split_docs = text_splitter.split_documents(docs)
|
604 |
|
605 |
-
client = chromadb.Client(
|
606 |
-
|
607 |
-
|
|
|
|
|
608 |
|
609 |
-
collection = client.get_or_create_collection(
|
610 |
-
|
611 |
-
|
|
|
612 |
|
613 |
texts = [doc.page_content for doc in split_docs]
|
614 |
metadatas = [doc.metadata for doc in split_docs]
|
@@ -616,7 +650,7 @@ class AddDocumentToVectorStoreTool(Tool):
|
|
616 |
collection.add(
|
617 |
documents=texts,
|
618 |
metadatas=metadatas,
|
619 |
-
ids=[f"{path.stem}_{i}" for i in range(len(texts))]
|
620 |
)
|
621 |
|
622 |
return f"β
Successfully added {len(texts)} chunks from '{file_path}' to collection '{collection_name}'."
|
@@ -625,7 +659,8 @@ class AddDocumentToVectorStoreTool(Tool):
|
|
625 |
print(f"β Error in add_to_vector_store: {e}")
|
626 |
traceback.print_exc()
|
627 |
return f"Error: {e}"
|
628 |
-
|
|
|
629 |
class QueryVectorStoreTool(Tool):
|
630 |
name = "query_downloaded_documents"
|
631 |
description = "Performs semantic searches across your downloaded documents. Use detailed queries to find specific information, concepts, or answers from your collected resources."
|
@@ -638,8 +673,8 @@ class QueryVectorStoreTool(Tool):
|
|
638 |
"top_k": {
|
639 |
"type": "integer",
|
640 |
"description": "Number of top results to retrieve. Usually between 3 and 30",
|
641 |
-
"nullable": True
|
642 |
-
}
|
643 |
}
|
644 |
output_type = "string"
|
645 |
|
@@ -653,9 +688,11 @@ class QueryVectorStoreTool(Tool):
|
|
653 |
|
654 |
print(f"π Querying vector store '{collection_name}' with: '{query}'")
|
655 |
try:
|
656 |
-
client = chromadb.Client(
|
657 |
-
|
658 |
-
|
|
|
|
|
659 |
collection = client.get_collection(name=collection_name)
|
660 |
|
661 |
results = collection.query(
|
@@ -668,9 +705,7 @@ class QueryVectorStoreTool(Tool):
|
|
668 |
doc = results["documents"][0][i]
|
669 |
metadata = results["metadatas"][0][i]
|
670 |
formatted.append(
|
671 |
-
f"Result {i+1}:\n"
|
672 |
-
f"Content: {doc}\n"
|
673 |
-
f"Metadata: {metadata}\n"
|
674 |
)
|
675 |
|
676 |
return "\n".join(formatted) or "No relevant documents found."
|
@@ -680,15 +715,16 @@ class QueryVectorStoreTool(Tool):
|
|
680 |
traceback.print_exc()
|
681 |
return f"Error querying vector store: {e}"
|
682 |
|
|
|
683 |
@tool
|
684 |
def image_question_answering(image_path: str, prompt: str) -> str:
|
685 |
"""
|
686 |
Analyzes images and answers specific questions about their content. Can identify objects, read text, describe scenes, or interpret visual information based on your questions.
|
687 |
-
|
688 |
Args:
|
689 |
image_path: The path to the image file
|
690 |
prompt: The question to ask about the image
|
691 |
-
|
692 |
Returns:
|
693 |
A string answer generated by the local Ollama model
|
694 |
"""
|
@@ -703,15 +739,15 @@ def image_question_answering(image_path: str, prompt: str) -> str:
|
|
703 |
|
704 |
# Send the image and prompt to Ollama's local model
|
705 |
response = chat(
|
706 |
-
model=
|
707 |
messages=[
|
708 |
{
|
709 |
-
|
710 |
-
|
711 |
-
|
712 |
},
|
713 |
],
|
714 |
-
options={
|
715 |
)
|
716 |
|
717 |
return response.message.content.strip()
|
@@ -719,9 +755,7 @@ def image_question_answering(image_path: str, prompt: str) -> str:
|
|
719 |
|
720 |
class VisitWebpageTool(Tool):
|
721 |
name = "visit_webpage"
|
722 |
-
description =
|
723 |
-
"Loads a webpage from a URL and converts its content to markdown format. Use this to browse websites, extract information, or identify downloadable resources from a specific web address."
|
724 |
-
)
|
725 |
inputs = {
|
726 |
"url": {
|
727 |
"type": "string",
|
@@ -732,53 +766,51 @@ class VisitWebpageTool(Tool):
|
|
732 |
|
733 |
def forward(self, url: str) -> str:
|
734 |
try:
|
735 |
-
import re
|
736 |
from urllib.parse import urlparse
|
737 |
|
738 |
import requests
|
739 |
from bs4 import BeautifulSoup
|
740 |
from markdownify import markdownify
|
741 |
from requests.exceptions import RequestException
|
742 |
-
|
743 |
from smolagents.utils import truncate_content
|
744 |
except ImportError as e:
|
745 |
raise ImportError(
|
746 |
"You must install packages `markdownify`, `requests`, and `beautifulsoup4` to run this tool: for instance run `pip install markdownify requests beautifulsoup4`."
|
747 |
) from e
|
748 |
-
|
749 |
try:
|
750 |
# Get the webpage content
|
751 |
headers = {
|
752 |
-
|
753 |
}
|
754 |
response = requests.get(url, headers=headers, timeout=20)
|
755 |
response.raise_for_status()
|
756 |
-
|
757 |
# Parse the HTML with BeautifulSoup
|
758 |
-
soup = BeautifulSoup(response.text,
|
759 |
-
|
760 |
# Extract domain name for context
|
761 |
domain = urlparse(url).netloc
|
762 |
-
|
763 |
# Remove common clutter elements
|
764 |
self._remove_clutter(soup)
|
765 |
-
|
766 |
# Try to identify and prioritize main content
|
767 |
main_content = self._extract_main_content(soup)
|
768 |
-
|
769 |
if main_content:
|
770 |
# Convert the cleaned HTML to markdown
|
771 |
markdown_content = markdownify(str(main_content)).strip()
|
772 |
else:
|
773 |
# Fallback to full page content if main content extraction fails
|
774 |
markdown_content = markdownify(str(soup)).strip()
|
775 |
-
|
776 |
# Post-process the markdown content
|
777 |
markdown_content = self._clean_markdown(markdown_content)
|
778 |
-
|
779 |
# Add source information
|
780 |
result = f"Content from {domain}:\n\n{markdown_content}"
|
781 |
-
|
782 |
return truncate_content(result, 40000)
|
783 |
|
784 |
except requests.exceptions.Timeout:
|
@@ -787,47 +819,75 @@ class VisitWebpageTool(Tool):
|
|
787 |
return f"Error fetching the webpage: {str(e)}"
|
788 |
except Exception as e:
|
789 |
return f"An unexpected error occurred: {str(e)}"
|
790 |
-
|
791 |
def _remove_clutter(self, soup):
|
792 |
"""Remove common elements that clutter web pages."""
|
793 |
# Common non-content elements to remove
|
794 |
clutter_selectors = [
|
795 |
-
|
796 |
-
|
797 |
-
|
798 |
-
|
799 |
-
|
800 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
801 |
]
|
802 |
-
|
803 |
for selector in clutter_selectors:
|
804 |
for element in soup.select(selector):
|
805 |
element.decompose()
|
806 |
-
|
807 |
# Remove hidden elements
|
808 |
-
for hidden in soup.select(
|
|
|
|
|
809 |
hidden.decompose()
|
810 |
-
|
811 |
def _extract_main_content(self, soup):
|
812 |
"""Try to identify and extract the main content of the page."""
|
813 |
# Priority order for common main content containers
|
814 |
main_content_selectors = [
|
815 |
-
|
816 |
-
'[role="main"]',
|
817 |
-
|
818 |
-
|
819 |
-
|
820 |
-
|
821 |
-
|
822 |
-
|
823 |
-
|
824 |
-
|
825 |
-
|
826 |
-
|
827 |
-
|
828 |
-
|
829 |
]
|
830 |
-
|
831 |
# Try to find the main content container
|
832 |
for selector in main_content_selectors:
|
833 |
main_content = soup.select(selector)
|
@@ -836,9 +896,9 @@ class VisitWebpageTool(Tool):
|
|
836 |
if len(main_content) > 1:
|
837 |
return max(main_content, key=lambda x: len(x.get_text()))
|
838 |
return main_content[0]
|
839 |
-
|
840 |
# If no main content container found, look for the largest text block
|
841 |
-
paragraphs = soup.find_all(
|
842 |
if paragraphs:
|
843 |
# Find the parent that contains the most paragraphs
|
844 |
parents = {}
|
@@ -847,88 +907,106 @@ class VisitWebpageTool(Tool):
|
|
847 |
if p.parent not in parents:
|
848 |
parents[p.parent] = 0
|
849 |
parents[p.parent] += 1
|
850 |
-
|
851 |
if parents:
|
852 |
# Return the parent with the most paragraphs
|
853 |
return max(parents.items(), key=lambda x: x[1])[0]
|
854 |
-
|
855 |
# Return None if we can't identify main content
|
856 |
return None
|
857 |
-
|
858 |
def _clean_markdown(self, content):
|
859 |
"""Clean up the markdown content."""
|
860 |
# Normalize whitespace
|
861 |
-
content = re.sub(r
|
862 |
-
|
863 |
# Remove consecutive duplicate links
|
864 |
-
content = re.sub(r
|
865 |
-
|
866 |
# Remove very short lines that are likely menu items
|
867 |
-
lines = content.split(
|
868 |
filtered_lines = []
|
869 |
-
|
870 |
# Skip consecutive short lines (likely menus)
|
871 |
short_line_threshold = 40 # characters
|
872 |
consecutive_short_lines = 0
|
873 |
max_consecutive_short_lines = 3
|
874 |
-
|
875 |
for line in lines:
|
876 |
stripped_line = line.strip()
|
877 |
-
if len(
|
|
|
|
|
878 |
consecutive_short_lines += 1
|
879 |
if consecutive_short_lines > max_consecutive_short_lines:
|
880 |
continue
|
881 |
else:
|
882 |
consecutive_short_lines = 0
|
883 |
-
|
884 |
filtered_lines.append(line)
|
885 |
-
|
886 |
-
content =
|
887 |
-
|
888 |
# Remove duplicate headers
|
889 |
seen_headers = set()
|
890 |
-
lines = content.split(
|
891 |
filtered_lines = []
|
892 |
-
|
893 |
for line in lines:
|
894 |
-
if line.startswith(
|
895 |
header_text = line.strip()
|
896 |
if header_text in seen_headers:
|
897 |
continue
|
898 |
seen_headers.add(header_text)
|
899 |
filtered_lines.append(line)
|
900 |
-
|
901 |
-
content =
|
902 |
-
|
903 |
# Remove lines containing common footer patterns
|
904 |
footer_patterns = [
|
905 |
-
r
|
906 |
-
r
|
907 |
-
r
|
|
|
|
|
|
|
|
|
|
|
|
|
908 |
]
|
909 |
-
|
910 |
-
footer_pattern =
|
911 |
-
lines = content.split(
|
912 |
filtered_lines = []
|
913 |
-
|
914 |
for line in lines:
|
915 |
if not re.search(footer_pattern, line.lower()):
|
916 |
filtered_lines.append(line)
|
917 |
-
|
918 |
-
content =
|
919 |
-
|
920 |
return content
|
921 |
-
|
922 |
|
923 |
class ArxivSearchTool(Tool):
|
924 |
name = "arxiv_search"
|
925 |
description = """Searches arXiv for academic papers and returns structured information including titles, authors, publication dates, abstracts, and download links."""
|
926 |
|
927 |
-
|
928 |
inputs = {
|
929 |
-
"query":
|
930 |
-
|
931 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
932 |
}
|
933 |
|
934 |
output_type = "string"
|
@@ -962,15 +1040,17 @@ class ArxivSearchTool(Tool):
|
|
962 |
f"Summary : {p['abstract'][:500]}{'...' if len(p['abstract'])>500 else ''}",
|
963 |
f"Entry ID : {p['entry_link']}",
|
964 |
f"Download link: {p['download_link']}",
|
965 |
-
""
|
966 |
]
|
967 |
|
968 |
return "\n".join(output_lines).strip()
|
969 |
-
|
|
|
|
|
970 |
|
971 |
import requests
|
972 |
from bs4 import BeautifulSoup
|
973 |
-
|
974 |
|
975 |
def fetch_and_parse_arxiv(url: str) -> List[Dict[str, str]]:
|
976 |
"""
|
@@ -999,7 +1079,9 @@ def fetch_and_parse_arxiv(url: str) -> List[Dict[str, str]]:
|
|
999 |
|
1000 |
# Abstract
|
1001 |
ab = li.find("span", class_="abstract-full")
|
1002 |
-
abstract =
|
|
|
|
|
1003 |
|
1004 |
# Published date
|
1005 |
d = li.find("p", class_="is-size-7")
|
@@ -1017,24 +1099,27 @@ def fetch_and_parse_arxiv(url: str) -> List[Dict[str, str]]:
|
|
1017 |
doi = a_tag["href"]
|
1018 |
break
|
1019 |
|
1020 |
-
results.append(
|
1021 |
-
|
1022 |
-
|
1023 |
-
|
1024 |
-
|
1025 |
-
|
1026 |
-
|
1027 |
-
|
|
|
|
|
|
|
|
|
1028 |
|
1029 |
return results
|
1030 |
|
|
|
1031 |
from urllib.parse import quote_plus
|
1032 |
|
|
|
1033 |
def build_arxiv_url(
|
1034 |
-
query: str,
|
1035 |
-
from_date: str = None,
|
1036 |
-
to_date: str = None,
|
1037 |
-
size: int = 50
|
1038 |
) -> str:
|
1039 |
"""
|
1040 |
Build an arXiv advanced-search URL matching the exact segment order:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import html
|
2 |
import json
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3 |
import mimetypes
|
4 |
+
import os
|
5 |
+
import re
|
6 |
+
import time
|
7 |
import traceback
|
8 |
+
from pathlib import Path
|
9 |
+
from typing import Dict, List
|
10 |
from urllib.parse import urlparse
|
11 |
|
12 |
+
import chromadb
|
13 |
+
import chromadb.utils.embedding_functions as embedding_functions
|
14 |
+
import fitz # PyMuPDF
|
15 |
+
import pandas as pd
|
16 |
+
import requests
|
17 |
+
from bs4 import BeautifulSoup
|
18 |
from duckduckgo_search import DDGS
|
19 |
from duckduckgo_search.exceptions import (
|
20 |
+
ConversationLimitException,
|
21 |
DuckDuckGoSearchException,
|
22 |
RatelimitException,
|
23 |
TimeoutException,
|
|
|
24 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
25 |
from langchain.document_loaders import (
|
26 |
+
BSHTMLLoader,
|
27 |
+
JSONLoader,
|
28 |
+
PyPDFLoader,
|
29 |
+
TextLoader,
|
30 |
+
UnstructuredFileLoader,
|
31 |
)
|
32 |
from langchain.text_splitter import RecursiveCharacterTextSplitter
|
33 |
+
from langchain_community.tools import BraveSearch
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
34 |
from markdownify import markdownify
|
35 |
+
from smolagents import Tool, tool
|
36 |
from smolagents.utils import truncate_content
|
37 |
|
38 |
+
|
39 |
class ReadFileContentTool(Tool):
|
40 |
name = "read_file_content"
|
41 |
description = """Reads local files in various formats (text, CSV, Excel, PDF, HTML, etc.) and returns their content as readable text. Automatically detects and processes the appropriate file format."""
|
|
|
43 |
inputs = {
|
44 |
"file_path": {
|
45 |
"type": "string",
|
46 |
+
"description": "The full path to the file from which the content should be read.",
|
47 |
}
|
48 |
}
|
49 |
output_type = "string"
|
|
|
61 |
|
62 |
elif ext == ".csv":
|
63 |
df = pd.read_csv(file_path)
|
64 |
+
return truncate_content(
|
65 |
+
f"CSV Content:\n{df.to_string(index=False)}\n\nColumn names: {', '.join(df.columns)}"
|
66 |
+
)
|
67 |
|
68 |
elif ext in [".xlsx", ".xls"]:
|
69 |
df = pd.read_excel(file_path)
|
70 |
+
return truncate_content(
|
71 |
+
f"Excel Content:\n{df.to_string(index=False)}\n\nColumn names: {', '.join(df.columns)}"
|
72 |
+
)
|
73 |
|
74 |
elif ext == ".pdf":
|
75 |
doc = fitz.open(file_path)
|
76 |
text = "".join([page.get_text() for page in doc])
|
77 |
doc.close()
|
78 |
+
return truncate_content(
|
79 |
+
text.strip() or "β οΈ PDF contains no readable text."
|
80 |
+
)
|
81 |
|
82 |
elif ext == ".json":
|
83 |
with open(file_path, "r", encoding="utf-8") as f:
|
|
|
111 |
except Exception as e:
|
112 |
return f"β Could not read {file_path}: {e}"
|
113 |
|
114 |
+
|
115 |
class WikipediaSearchTool(Tool):
|
116 |
name = "wikipedia_search"
|
117 |
description = """Searches Wikipedia for a specific topic and returns a concise summary. Useful for background information on subjects, concepts, historical events, or scientific topics."""
|
|
|
119 |
inputs = {
|
120 |
"query": {
|
121 |
"type": "string",
|
122 |
+
"description": "The query or subject to search for on Wikipedia.",
|
123 |
}
|
124 |
}
|
125 |
output_type = "string"
|
|
|
132 |
search_response.raise_for_status()
|
133 |
search_data = search_response.json()
|
134 |
|
135 |
+
if not search_data.get("query", {}).get("search", []):
|
136 |
return f"No Wikipedia info for '{query}'."
|
137 |
|
138 |
+
page_id = search_data["query"]["search"][0]["pageid"]
|
139 |
|
140 |
content_link = (
|
141 |
f"https://en.wikipedia.org/w/api.php?action=query&prop=extracts&"
|
|
|
145 |
content_response.raise_for_status()
|
146 |
content_data = content_response.json()
|
147 |
|
148 |
+
extract = content_data["query"]["pages"][str(page_id)]["extract"]
|
149 |
if len(extract) > 1500:
|
150 |
extract = extract[:1500] + "..."
|
151 |
|
|
|
158 |
traceback.print_exc()
|
159 |
return f"Error wiki: {e}"
|
160 |
|
161 |
+
|
162 |
class VisitWebpageTool(Tool):
|
163 |
name = "visit_webpage"
|
164 |
+
description = "Loads a webpage from a URL and converts its content to markdown format. Use this to browse websites, extract information, or identify downloadable resources from a specific web address."
|
|
|
|
|
165 |
inputs = {
|
166 |
"url": {
|
167 |
"type": "string",
|
|
|
177 |
import requests
|
178 |
from markdownify import markdownify
|
179 |
from requests.exceptions import RequestException
|
|
|
180 |
from smolagents.utils import truncate_content
|
181 |
except ImportError as e:
|
182 |
raise ImportError(
|
|
|
195 |
return f"Error fetching the webpage: {str(e)}"
|
196 |
except Exception as e:
|
197 |
return f"An unexpected error occurred: {str(e)}"
|
198 |
+
|
199 |
+
|
200 |
class TranscribeAudioTool(Tool):
|
201 |
name = "transcribe_audio"
|
202 |
description = """Converts spoken content in audio files to text. Handles various audio formats and produces a transcript of the spoken content for analysis."""
|
|
|
204 |
inputs = {
|
205 |
"file_path": {
|
206 |
"type": "string",
|
207 |
+
"description": "The full path to the audio file that needs to be transcribed.",
|
208 |
}
|
209 |
}
|
210 |
output_type = "string"
|
211 |
|
|
|
212 |
def forward(self, file_path: str) -> str:
|
213 |
try:
|
|
|
|
|
214 |
import os
|
215 |
import tempfile
|
216 |
+
|
217 |
+
import speech_recognition as sr
|
218 |
+
from pydub import AudioSegment
|
219 |
+
|
220 |
# Verify file exists
|
221 |
if not os.path.exists(file_path):
|
222 |
+
return (
|
223 |
+
f"β Audio file not found at: {file_path}. Download the file first."
|
224 |
+
)
|
225 |
+
|
226 |
# Initialize recognizer
|
227 |
recognizer = sr.Recognizer()
|
228 |
+
|
229 |
# Convert to WAV if not already (needed for speech_recognition)
|
230 |
file_ext = os.path.splitext(file_path)[1].lower()
|
231 |
+
|
232 |
+
if file_ext != ".wav":
|
233 |
# Create temp WAV file
|
234 |
+
temp_wav = tempfile.NamedTemporaryFile(suffix=".wav", delete=False).name
|
235 |
+
|
236 |
# Convert to WAV using pydub
|
237 |
audio = AudioSegment.from_file(file_path)
|
238 |
audio.export(temp_wav, format="wav")
|
239 |
audio_path = temp_wav
|
240 |
else:
|
241 |
audio_path = file_path
|
242 |
+
|
243 |
# Transcribe audio using Google's speech recognition
|
244 |
with sr.AudioFile(audio_path) as source:
|
245 |
audio_data = recognizer.record(source)
|
246 |
transcript = recognizer.recognize_google(audio_data)
|
247 |
+
|
248 |
# Clean up temp file if created
|
249 |
+
if file_ext != ".wav" and os.path.exists(temp_wav):
|
250 |
os.remove(temp_wav)
|
251 |
+
|
252 |
return transcript.strip()
|
253 |
+
|
254 |
except Exception as e:
|
255 |
return f"β Transcription failed: {str(e)}"
|
256 |
|
257 |
+
|
258 |
class TranscibeVideoFileTool(Tool):
|
259 |
name = "transcribe_video"
|
260 |
description = """Extracts and transcribes speech from video files. Converts the audio portion of videos into readable text for analysis or reference."""
|
|
|
262 |
inputs = {
|
263 |
"file_path": {
|
264 |
"type": "string",
|
265 |
+
"description": "The full path to the video file that needs to be transcribed.",
|
266 |
}
|
267 |
}
|
268 |
output_type = "string"
|
|
|
271 |
try:
|
272 |
# Verify file exists
|
273 |
if not os.path.exists(file_path):
|
274 |
+
return (
|
275 |
+
f"β Video file not found at: {file_path}. Download the file first."
|
276 |
+
)
|
277 |
+
|
278 |
import os
|
279 |
import tempfile
|
280 |
+
|
281 |
+
import moviepy.editor as mp
|
282 |
+
import speech_recognition as sr
|
283 |
+
|
284 |
# Extract audio from video
|
285 |
video = mp.VideoFileClip(file_path)
|
286 |
+
|
287 |
# Create temporary audio file
|
288 |
+
temp_audio = tempfile.NamedTemporaryFile(suffix=".wav", delete=False).name
|
289 |
+
|
290 |
# Extract audio to WAV format (required for speech_recognition)
|
291 |
video.audio.write_audiofile(temp_audio, verbose=False, logger=None)
|
292 |
video.close()
|
293 |
+
|
294 |
# Initialize recognizer
|
295 |
recognizer = sr.Recognizer()
|
296 |
+
|
297 |
# Transcribe audio
|
298 |
with sr.AudioFile(temp_audio) as source:
|
299 |
audio_data = recognizer.record(source)
|
300 |
transcript = recognizer.recognize_google(audio_data)
|
301 |
+
|
302 |
# Clean up temp file
|
303 |
if os.path.exists(temp_audio):
|
304 |
os.remove(temp_audio)
|
305 |
+
|
306 |
return transcript.strip()
|
307 |
+
|
308 |
except Exception as e:
|
309 |
return f"β Video processing failed: {str(e)}"
|
310 |
+
|
311 |
+
|
312 |
class BraveWebSearchTool(Tool):
|
313 |
name = "web_search"
|
314 |
description = """Performs web searches and returns content from top results. Provides real-time information from across the internet including current events, facts, and website content relevant to your query."""
|
|
|
316 |
inputs = {
|
317 |
"query": {
|
318 |
"type": "string",
|
319 |
+
"description": "A web search query string (e.g., a question or query).",
|
320 |
}
|
321 |
}
|
322 |
output_type = "string"
|
|
|
349 |
def forward(self, query: str) -> str:
|
350 |
try:
|
351 |
results_json = self.tool.run(query)
|
352 |
+
results = (
|
353 |
+
json.loads(results_json)
|
354 |
+
if isinstance(results_json, str)
|
355 |
+
else results_json
|
356 |
+
)
|
357 |
|
358 |
output_parts = []
|
359 |
+
for i, r in enumerate(results[: self.count], start=1):
|
360 |
title = html.unescape(r.get("title", "").strip())
|
361 |
link = r.get("link", "").strip()
|
362 |
|
|
|
375 |
except Exception as e:
|
376 |
return f"Search failed: {str(e)}"
|
377 |
|
378 |
+
|
379 |
class DescribeImageTool(Tool):
|
380 |
name = "describe_image"
|
381 |
description = """Analyzes images and generates detailed text descriptions. Identifies objects, scenes, text, and visual elements within the image to provide context or understanding."""
|
|
|
383 |
inputs = {
|
384 |
"image_path": {
|
385 |
"type": "string",
|
386 |
+
"description": "The full path to the image file to describe.",
|
387 |
}
|
388 |
}
|
389 |
output_type = "string"
|
390 |
|
391 |
def forward(self, image_path: str) -> str:
|
392 |
import os
|
393 |
+
|
394 |
from PIL import Image
|
395 |
+
from transformers import BlipForConditionalGeneration, BlipProcessor
|
|
|
396 |
|
397 |
if not os.path.exists(image_path):
|
398 |
return f"β Image file does not exist: {image_path}"
|
399 |
|
400 |
try:
|
401 |
+
processor = BlipProcessor.from_pretrained(
|
402 |
+
"Salesforce/blip-image-captioning-base", use_fast=True
|
403 |
+
)
|
404 |
+
model = BlipForConditionalGeneration.from_pretrained(
|
405 |
+
"Salesforce/blip-image-captioning-base"
|
406 |
+
)
|
407 |
|
408 |
image = Image.open(image_path).convert("RGB")
|
409 |
inputs = processor(images=image, return_tensors="pt")
|
|
|
414 |
except Exception as e:
|
415 |
return f"β Failed to describe image: {e}"
|
416 |
|
417 |
+
|
418 |
class DownloadFileFromLinkTool(Tool):
|
419 |
name = "download_file_from_link"
|
420 |
description = "Downloads files from a URL and saves them locally. Supports various formats including PDFs, documents, images, and data files. Returns the local file path for further processing."
|
421 |
|
422 |
inputs = {
|
423 |
+
"link": {"type": "string", "description": "The URL to download the file from."},
|
|
|
|
|
|
|
424 |
"file_name": {
|
425 |
"type": "string",
|
426 |
"description": "Desired name of the saved file, without extension.",
|
427 |
+
"nullable": True,
|
428 |
+
},
|
429 |
}
|
430 |
|
431 |
output_type = "string"
|
432 |
+
SUPPORTED_EXTENSIONS = {
|
433 |
+
".xlsx",
|
434 |
+
".pdf",
|
435 |
+
".txt",
|
436 |
+
".csv",
|
437 |
+
".json",
|
438 |
+
".xml",
|
439 |
+
".html",
|
440 |
+
".jpg",
|
441 |
+
".jpeg",
|
442 |
+
".png",
|
443 |
+
".mp4",
|
444 |
+
".mp3",
|
445 |
+
".wav",
|
446 |
+
".zip",
|
447 |
+
}
|
448 |
|
449 |
def forward(self, link: str, file_name: str = "taskfile") -> str:
|
450 |
print(f"β¬οΈ Downloading file from: {link}")
|
|
|
457 |
return f"β Error: Request failed - {e}"
|
458 |
|
459 |
if response.status_code != 200:
|
460 |
+
return (
|
461 |
+
f"β Error: Unable to fetch file. Status code: {response.status_code}"
|
462 |
+
)
|
463 |
|
464 |
# Step 1: Try extracting extension from provided filename
|
465 |
base_name, provided_ext = os.path.splitext(file_name)
|
|
|
470 |
ext = provided_ext
|
471 |
else:
|
472 |
# Step 3: Try to infer from Content-Type
|
473 |
+
content_type = (
|
474 |
+
response.headers.get("Content-Type", "").split(";")[0].strip()
|
475 |
+
)
|
476 |
guessed_ext = mimetypes.guess_extension(content_type or "") or ""
|
477 |
|
478 |
# Step 4: If mimetype returned .bin or nothing useful, try to fallback to URL
|
|
|
498 |
|
499 |
return file_path
|
500 |
|
501 |
+
|
502 |
class DuckDuckGoSearchTool(Tool):
|
503 |
name = "web_search"
|
504 |
description = """Performs web searches and returns content from top results. Provides real-time information from across the internet including current events, facts, and website content relevant to your query."""
|
|
|
506 |
inputs = {
|
507 |
"query": {
|
508 |
"type": "string",
|
509 |
+
"description": "The search query to run on DuckDuckGo",
|
510 |
},
|
511 |
}
|
512 |
output_type = "string"
|
|
|
517 |
|
518 |
def forward(self, query: str) -> str:
|
519 |
self._configure()
|
520 |
+
print(
|
521 |
+
f"EXECUTING TOOL: duckduckgo_search(query='{query}', top_results={top_results})"
|
522 |
+
)
|
523 |
|
524 |
top_results = 5
|
525 |
|
|
|
544 |
title = res.get("title", "N/A")
|
545 |
url = res.get("href", "N/A")
|
546 |
snippet = res.get("body", "N/A")
|
547 |
+
|
548 |
output_lines.append(
|
549 |
f"Result {idx}:\n"
|
550 |
f"Title: {title}\n"
|
|
|
557 |
print(f"-> Tool Result (DuckDuckGo): {output[:1500]}...")
|
558 |
return output
|
559 |
|
560 |
+
except (
|
561 |
+
DuckDuckGoSearchException,
|
562 |
+
TimeoutException,
|
563 |
+
RatelimitException,
|
564 |
+
ConversationLimitException,
|
565 |
+
) as e:
|
566 |
retries += 1
|
567 |
+
print(
|
568 |
+
f"β οΈ DuckDuckGo Exception (Attempt {retries}/{max_retries}): {type(e).__name__}: {e}"
|
569 |
+
)
|
570 |
traceback.print_exc()
|
571 |
time.sleep(retry_sleep)
|
572 |
|
|
|
576 |
return f"Unhandled exception during DuckDuckGo search: {e}"
|
577 |
|
578 |
return f"β Failed to retrieve results after {max_retries} retries."
|
579 |
+
|
580 |
+
|
581 |
huggingface_ef = embedding_functions.HuggingFaceEmbeddingFunction(
|
582 |
+
api_key=os.environ["HF_TOKEN"], model_name="sentence-transformers/all-mpnet-base-v2"
|
|
|
583 |
)
|
584 |
+
SUPPORTED_EXTENSIONS = [
|
585 |
+
".txt",
|
586 |
+
".md",
|
587 |
+
".py",
|
588 |
+
".pdf",
|
589 |
+
".json",
|
590 |
+
".jsonl",
|
591 |
+
".html",
|
592 |
+
".htm",
|
593 |
+
]
|
594 |
+
|
595 |
|
596 |
class AddDocumentToVectorStoreTool(Tool):
|
597 |
name = "add_document_to_vector_store"
|
|
|
628 |
return f"Unsupported or missing file: {file_path}"
|
629 |
|
630 |
docs = self._load_file(path)
|
631 |
+
text_splitter = RecursiveCharacterTextSplitter(
|
632 |
+
chunk_size=500, chunk_overlap=50
|
633 |
+
)
|
634 |
split_docs = text_splitter.split_documents(docs)
|
635 |
|
636 |
+
client = chromadb.Client(
|
637 |
+
chromadb.config.Settings(
|
638 |
+
persist_directory="./chroma_store",
|
639 |
+
)
|
640 |
+
)
|
641 |
|
642 |
+
collection = client.get_or_create_collection(
|
643 |
+
name=collection_name,
|
644 |
+
configuration={"embedding_function": huggingface_ef},
|
645 |
+
)
|
646 |
|
647 |
texts = [doc.page_content for doc in split_docs]
|
648 |
metadatas = [doc.metadata for doc in split_docs]
|
|
|
650 |
collection.add(
|
651 |
documents=texts,
|
652 |
metadatas=metadatas,
|
653 |
+
ids=[f"{path.stem}_{i}" for i in range(len(texts))],
|
654 |
)
|
655 |
|
656 |
return f"β
Successfully added {len(texts)} chunks from '{file_path}' to collection '{collection_name}'."
|
|
|
659 |
print(f"β Error in add_to_vector_store: {e}")
|
660 |
traceback.print_exc()
|
661 |
return f"Error: {e}"
|
662 |
+
|
663 |
+
|
664 |
class QueryVectorStoreTool(Tool):
|
665 |
name = "query_downloaded_documents"
|
666 |
description = "Performs semantic searches across your downloaded documents. Use detailed queries to find specific information, concepts, or answers from your collected resources."
|
|
|
673 |
"top_k": {
|
674 |
"type": "integer",
|
675 |
"description": "Number of top results to retrieve. Usually between 3 and 30",
|
676 |
+
"nullable": True,
|
677 |
+
},
|
678 |
}
|
679 |
output_type = "string"
|
680 |
|
|
|
688 |
|
689 |
print(f"π Querying vector store '{collection_name}' with: '{query}'")
|
690 |
try:
|
691 |
+
client = chromadb.Client(
|
692 |
+
chromadb.config.Settings(
|
693 |
+
persist_directory="./chroma_store",
|
694 |
+
)
|
695 |
+
)
|
696 |
collection = client.get_collection(name=collection_name)
|
697 |
|
698 |
results = collection.query(
|
|
|
705 |
doc = results["documents"][0][i]
|
706 |
metadata = results["metadatas"][0][i]
|
707 |
formatted.append(
|
708 |
+
f"Result {i+1}:\n" f"Content: {doc}\n" f"Metadata: {metadata}\n"
|
|
|
|
|
709 |
)
|
710 |
|
711 |
return "\n".join(formatted) or "No relevant documents found."
|
|
|
715 |
traceback.print_exc()
|
716 |
return f"Error querying vector store: {e}"
|
717 |
|
718 |
+
|
719 |
@tool
|
720 |
def image_question_answering(image_path: str, prompt: str) -> str:
|
721 |
"""
|
722 |
Analyzes images and answers specific questions about their content. Can identify objects, read text, describe scenes, or interpret visual information based on your questions.
|
723 |
+
|
724 |
Args:
|
725 |
image_path: The path to the image file
|
726 |
prompt: The question to ask about the image
|
727 |
+
|
728 |
Returns:
|
729 |
A string answer generated by the local Ollama model
|
730 |
"""
|
|
|
739 |
|
740 |
# Send the image and prompt to Ollama's local model
|
741 |
response = chat(
|
742 |
+
model="llava", # Assuming your model is named 'lava'
|
743 |
messages=[
|
744 |
{
|
745 |
+
"role": "user",
|
746 |
+
"content": prompt,
|
747 |
+
"images": [path],
|
748 |
},
|
749 |
],
|
750 |
+
options={"temperature": 0.2}, # Slight randomness for naturalness
|
751 |
)
|
752 |
|
753 |
return response.message.content.strip()
|
|
|
755 |
|
756 |
class VisitWebpageTool(Tool):
|
757 |
name = "visit_webpage"
|
758 |
+
description = "Loads a webpage from a URL and converts its content to markdown format. Use this to browse websites, extract information, or identify downloadable resources from a specific web address."
|
|
|
|
|
759 |
inputs = {
|
760 |
"url": {
|
761 |
"type": "string",
|
|
|
766 |
|
767 |
def forward(self, url: str) -> str:
|
768 |
try:
|
|
|
769 |
from urllib.parse import urlparse
|
770 |
|
771 |
import requests
|
772 |
from bs4 import BeautifulSoup
|
773 |
from markdownify import markdownify
|
774 |
from requests.exceptions import RequestException
|
|
|
775 |
from smolagents.utils import truncate_content
|
776 |
except ImportError as e:
|
777 |
raise ImportError(
|
778 |
"You must install packages `markdownify`, `requests`, and `beautifulsoup4` to run this tool: for instance run `pip install markdownify requests beautifulsoup4`."
|
779 |
) from e
|
780 |
+
|
781 |
try:
|
782 |
# Get the webpage content
|
783 |
headers = {
|
784 |
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
|
785 |
}
|
786 |
response = requests.get(url, headers=headers, timeout=20)
|
787 |
response.raise_for_status()
|
788 |
+
|
789 |
# Parse the HTML with BeautifulSoup
|
790 |
+
soup = BeautifulSoup(response.text, "html.parser")
|
791 |
+
|
792 |
# Extract domain name for context
|
793 |
domain = urlparse(url).netloc
|
794 |
+
|
795 |
# Remove common clutter elements
|
796 |
self._remove_clutter(soup)
|
797 |
+
|
798 |
# Try to identify and prioritize main content
|
799 |
main_content = self._extract_main_content(soup)
|
800 |
+
|
801 |
if main_content:
|
802 |
# Convert the cleaned HTML to markdown
|
803 |
markdown_content = markdownify(str(main_content)).strip()
|
804 |
else:
|
805 |
# Fallback to full page content if main content extraction fails
|
806 |
markdown_content = markdownify(str(soup)).strip()
|
807 |
+
|
808 |
# Post-process the markdown content
|
809 |
markdown_content = self._clean_markdown(markdown_content)
|
810 |
+
|
811 |
# Add source information
|
812 |
result = f"Content from {domain}:\n\n{markdown_content}"
|
813 |
+
|
814 |
return truncate_content(result, 40000)
|
815 |
|
816 |
except requests.exceptions.Timeout:
|
|
|
819 |
return f"Error fetching the webpage: {str(e)}"
|
820 |
except Exception as e:
|
821 |
return f"An unexpected error occurred: {str(e)}"
|
822 |
+
|
823 |
def _remove_clutter(self, soup):
|
824 |
"""Remove common elements that clutter web pages."""
|
825 |
# Common non-content elements to remove
|
826 |
clutter_selectors = [
|
827 |
+
"header",
|
828 |
+
"footer",
|
829 |
+
"nav",
|
830 |
+
".nav",
|
831 |
+
".navigation",
|
832 |
+
".menu",
|
833 |
+
".sidebar",
|
834 |
+
".footer",
|
835 |
+
".header",
|
836 |
+
"#footer",
|
837 |
+
"#header",
|
838 |
+
"#nav",
|
839 |
+
"#sidebar",
|
840 |
+
".widget",
|
841 |
+
".cookie",
|
842 |
+
".cookies",
|
843 |
+
".ad",
|
844 |
+
".ads",
|
845 |
+
".advertisement",
|
846 |
+
"script",
|
847 |
+
"style",
|
848 |
+
"noscript",
|
849 |
+
"iframe",
|
850 |
+
".social",
|
851 |
+
".share",
|
852 |
+
".comment",
|
853 |
+
".comments",
|
854 |
+
".subscription",
|
855 |
+
".newsletter",
|
856 |
+
'[role="banner"]',
|
857 |
+
'[role="navigation"]',
|
858 |
+
'[role="complementary"]',
|
859 |
]
|
860 |
+
|
861 |
for selector in clutter_selectors:
|
862 |
for element in soup.select(selector):
|
863 |
element.decompose()
|
864 |
+
|
865 |
# Remove hidden elements
|
866 |
+
for hidden in soup.select(
|
867 |
+
'[style*="display: none"], [style*="display:none"], [style*="visibility: hidden"], [style*="visibility:hidden"], [hidden]'
|
868 |
+
):
|
869 |
hidden.decompose()
|
870 |
+
|
871 |
def _extract_main_content(self, soup):
|
872 |
"""Try to identify and extract the main content of the page."""
|
873 |
# Priority order for common main content containers
|
874 |
main_content_selectors = [
|
875 |
+
"main",
|
876 |
+
'[role="main"]',
|
877 |
+
"article",
|
878 |
+
".content",
|
879 |
+
".main-content",
|
880 |
+
".post-content",
|
881 |
+
"#content",
|
882 |
+
"#main",
|
883 |
+
"#main-content",
|
884 |
+
".article",
|
885 |
+
".post",
|
886 |
+
".entry",
|
887 |
+
".page-content",
|
888 |
+
".entry-content",
|
889 |
]
|
890 |
+
|
891 |
# Try to find the main content container
|
892 |
for selector in main_content_selectors:
|
893 |
main_content = soup.select(selector)
|
|
|
896 |
if len(main_content) > 1:
|
897 |
return max(main_content, key=lambda x: len(x.get_text()))
|
898 |
return main_content[0]
|
899 |
+
|
900 |
# If no main content container found, look for the largest text block
|
901 |
+
paragraphs = soup.find_all("p")
|
902 |
if paragraphs:
|
903 |
# Find the parent that contains the most paragraphs
|
904 |
parents = {}
|
|
|
907 |
if p.parent not in parents:
|
908 |
parents[p.parent] = 0
|
909 |
parents[p.parent] += 1
|
910 |
+
|
911 |
if parents:
|
912 |
# Return the parent with the most paragraphs
|
913 |
return max(parents.items(), key=lambda x: x[1])[0]
|
914 |
+
|
915 |
# Return None if we can't identify main content
|
916 |
return None
|
917 |
+
|
918 |
def _clean_markdown(self, content):
|
919 |
"""Clean up the markdown content."""
|
920 |
# Normalize whitespace
|
921 |
+
content = re.sub(r"\n{3,}", "\n\n", content)
|
922 |
+
|
923 |
# Remove consecutive duplicate links
|
924 |
+
content = re.sub(r"(\[.*?\]\(.*?\))\s*\1+", r"\1", content)
|
925 |
+
|
926 |
# Remove very short lines that are likely menu items
|
927 |
+
lines = content.split("\n")
|
928 |
filtered_lines = []
|
929 |
+
|
930 |
# Skip consecutive short lines (likely menus)
|
931 |
short_line_threshold = 40 # characters
|
932 |
consecutive_short_lines = 0
|
933 |
max_consecutive_short_lines = 3
|
934 |
+
|
935 |
for line in lines:
|
936 |
stripped_line = line.strip()
|
937 |
+
if len(
|
938 |
+
stripped_line
|
939 |
+
) < short_line_threshold and not stripped_line.startswith("#"):
|
940 |
consecutive_short_lines += 1
|
941 |
if consecutive_short_lines > max_consecutive_short_lines:
|
942 |
continue
|
943 |
else:
|
944 |
consecutive_short_lines = 0
|
945 |
+
|
946 |
filtered_lines.append(line)
|
947 |
+
|
948 |
+
content = "\n".join(filtered_lines)
|
949 |
+
|
950 |
# Remove duplicate headers
|
951 |
seen_headers = set()
|
952 |
+
lines = content.split("\n")
|
953 |
filtered_lines = []
|
954 |
+
|
955 |
for line in lines:
|
956 |
+
if line.startswith("#"):
|
957 |
header_text = line.strip()
|
958 |
if header_text in seen_headers:
|
959 |
continue
|
960 |
seen_headers.add(header_text)
|
961 |
filtered_lines.append(line)
|
962 |
+
|
963 |
+
content = "\n".join(filtered_lines)
|
964 |
+
|
965 |
# Remove lines containing common footer patterns
|
966 |
footer_patterns = [
|
967 |
+
r"^copyright",
|
968 |
+
r"^Β©",
|
969 |
+
r"^all rights reserved",
|
970 |
+
r"^terms",
|
971 |
+
r"^privacy policy",
|
972 |
+
r"^contact us",
|
973 |
+
r"^follow us",
|
974 |
+
r"^social media",
|
975 |
+
r"^disclaimer",
|
976 |
]
|
977 |
+
|
978 |
+
footer_pattern = "|".join(footer_patterns)
|
979 |
+
lines = content.split("\n")
|
980 |
filtered_lines = []
|
981 |
+
|
982 |
for line in lines:
|
983 |
if not re.search(footer_pattern, line.lower()):
|
984 |
filtered_lines.append(line)
|
985 |
+
|
986 |
+
content = "\n".join(filtered_lines)
|
987 |
+
|
988 |
return content
|
989 |
+
|
990 |
|
991 |
class ArxivSearchTool(Tool):
|
992 |
name = "arxiv_search"
|
993 |
description = """Searches arXiv for academic papers and returns structured information including titles, authors, publication dates, abstracts, and download links."""
|
994 |
|
|
|
995 |
inputs = {
|
996 |
+
"query": {
|
997 |
+
"type": "string",
|
998 |
+
"description": "A research-related query (e.g., 'AI regulation')",
|
999 |
+
},
|
1000 |
+
"from_date": {
|
1001 |
+
"type": "string",
|
1002 |
+
"description": "Optional search start date in format (YYYY or YYYY-MM or YYYY-MM-DD) (e.g., '2022-06' or '2022' or '2022-04-12')",
|
1003 |
+
"nullable": True,
|
1004 |
+
},
|
1005 |
+
"to_date": {
|
1006 |
+
"type": "string",
|
1007 |
+
"description": "Optional search end date in (YYYY or YYYY-MM or YYYY-MM-DD) (e.g., '2022-06' or '2022' or '2022-04-12')",
|
1008 |
+
"nullable": True,
|
1009 |
+
},
|
1010 |
}
|
1011 |
|
1012 |
output_type = "string"
|
|
|
1040 |
f"Summary : {p['abstract'][:500]}{'...' if len(p['abstract'])>500 else ''}",
|
1041 |
f"Entry ID : {p['entry_link']}",
|
1042 |
f"Download link: {p['download_link']}",
|
1043 |
+
"",
|
1044 |
]
|
1045 |
|
1046 |
return "\n".join(output_lines).strip()
|
1047 |
+
|
1048 |
+
|
1049 |
+
from typing import Dict, List
|
1050 |
|
1051 |
import requests
|
1052 |
from bs4 import BeautifulSoup
|
1053 |
+
|
1054 |
|
1055 |
def fetch_and_parse_arxiv(url: str) -> List[Dict[str, str]]:
|
1056 |
"""
|
|
|
1079 |
|
1080 |
# Abstract
|
1081 |
ab = li.find("span", class_="abstract-full")
|
1082 |
+
abstract = (
|
1083 |
+
ab.get_text(strip=True).replace("Abstract:", "").strip() if ab else ""
|
1084 |
+
)
|
1085 |
|
1086 |
# Published date
|
1087 |
d = li.find("p", class_="is-size-7")
|
|
|
1099 |
doi = a_tag["href"]
|
1100 |
break
|
1101 |
|
1102 |
+
results.append(
|
1103 |
+
{
|
1104 |
+
"title": title,
|
1105 |
+
"authors": authors,
|
1106 |
+
"published": published,
|
1107 |
+
"abstract": abstract,
|
1108 |
+
"entry_link": entry_link,
|
1109 |
+
"download_link": (
|
1110 |
+
entry_link.replace("abs", "pdf") if "abs" in entry_link else "N/A"
|
1111 |
+
),
|
1112 |
+
}
|
1113 |
+
)
|
1114 |
|
1115 |
return results
|
1116 |
|
1117 |
+
|
1118 |
from urllib.parse import quote_plus
|
1119 |
|
1120 |
+
|
1121 |
def build_arxiv_url(
|
1122 |
+
query: str, from_date: str = None, to_date: str = None, size: int = 50
|
|
|
|
|
|
|
1123 |
) -> str:
|
1124 |
"""
|
1125 |
Build an arXiv advanced-search URL matching the exact segment order:
|
tools_beta.py
CHANGED
@@ -1,24 +1,13 @@
|
|
|
|
1 |
import os
|
2 |
import re
|
3 |
-
import
|
4 |
-
import
|
5 |
-
|
6 |
-
import pandas as pd
|
7 |
import fitz # PyMuPDF
|
8 |
-
from urllib.parse import unquote
|
9 |
-
from smolagents import Tool
|
10 |
-
from smolagents import Tool
|
11 |
import requests
|
12 |
-
import traceback
|
13 |
from langchain_community.retrievers import BM25Retriever
|
14 |
from smolagents import Tool
|
15 |
-
import math
|
16 |
-
|
17 |
-
import subprocess
|
18 |
-
import sys
|
19 |
-
import os
|
20 |
-
import re
|
21 |
-
|
22 |
|
23 |
|
24 |
class DetectVisualElementsTool(Tool):
|
@@ -28,204 +17,204 @@ class DetectVisualElementsTool(Tool):
|
|
28 |
inputs = {
|
29 |
"image_path": {
|
30 |
"type": "string",
|
31 |
-
"description": "The full path to the image file to analyze."
|
32 |
}
|
33 |
}
|
34 |
output_type = "string"
|
35 |
|
36 |
def forward(self, image_path: str) -> list:
|
37 |
import os
|
38 |
-
|
39 |
import torch
|
40 |
-
import torchvision.transforms as T
|
41 |
import torchvision.models.detection as models
|
|
|
|
|
42 |
|
43 |
label_map = {
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
-
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
|
198 |
-
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
|
206 |
-
|
207 |
-
|
208 |
-
|
209 |
-
|
210 |
-
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
|
215 |
-
|
216 |
-
|
217 |
-
|
218 |
-
|
219 |
-
|
220 |
-
|
221 |
-
|
222 |
-
|
223 |
-
|
224 |
-
|
225 |
-
|
226 |
-
|
227 |
-
}
|
228 |
-
|
229 |
|
230 |
if not os.path.exists(image_path):
|
231 |
return [f"β Image file does not exist: {image_path}"]
|
@@ -246,7 +235,7 @@ class DetectVisualElementsTool(Tool):
|
|
246 |
if score > 0.8:
|
247 |
print(str(label_id.item()))
|
248 |
labels_list.append(label_map.get(label_id.item()))
|
249 |
-
|
250 |
labels = ",".join(labels_list)
|
251 |
|
252 |
return labels or ["β οΈ No confident visual elements detected."]
|
@@ -263,19 +252,17 @@ class ChessPositionSolverTool(Tool):
|
|
263 |
"url": {
|
264 |
"type": "string",
|
265 |
"description": "Optional. URL pointing to an image of a chessboard position.",
|
266 |
-
"nullable": True
|
267 |
},
|
268 |
"file_path": {
|
269 |
"type": "string",
|
270 |
"description": "Optional. Local file path to an image of a chessboard position.",
|
271 |
-
"nullable": True
|
272 |
-
}
|
273 |
}
|
274 |
|
275 |
output_type = "string"
|
276 |
|
277 |
-
|
278 |
-
|
279 |
def forward(self, url: str = None, file_path: str = None) -> str:
|
280 |
if not url and not file_path:
|
281 |
return "β Please provide either a URL or a local file path to the chessboard image."
|
@@ -303,7 +290,10 @@ class ChessPositionSolverTool(Tool):
|
|
303 |
|
304 |
board = chess.Board(fen)
|
305 |
|
306 |
-
STOCKFISH_PATH = os.getenv(
|
|
|
|
|
|
|
307 |
|
308 |
# Step 3 - Analyze with Stockfish
|
309 |
engine = chess.engine.SimpleEngine.popen_uci(STOCKFISH_PATH)
|
@@ -327,17 +317,19 @@ def patch_pyproject(path):
|
|
327 |
|
328 |
with open(pyproject_path, "w", encoding="utf-8") as f:
|
329 |
for line in lines:
|
330 |
-
if re.match(r
|
331 |
f.write('python = ">=3.8,<3.12"\n')
|
332 |
else:
|
333 |
f.write(line)
|
334 |
|
|
|
335 |
def install_chesscog():
|
336 |
TARGET_DIR = "chesscog"
|
337 |
REPO_URL = "https://github.com/georg-wolflein/chesscog.git"
|
338 |
|
339 |
try:
|
340 |
-
|
|
|
341 |
print("β
chesscog already installed.")
|
342 |
# return
|
343 |
except ImportError:
|
@@ -348,9 +340,12 @@ def install_chesscog():
|
|
348 |
|
349 |
patch_pyproject(TARGET_DIR)
|
350 |
|
351 |
-
subprocess.run(
|
|
|
|
|
352 |
print("β
chesscog installed successfully.")
|
353 |
|
|
|
354 |
class RetrieverTool(Tool):
|
355 |
name = "retriever"
|
356 |
description = "Retrieves the most similar known question to the query."
|
@@ -374,6 +369,7 @@ class RetrieverTool(Tool):
|
|
374 |
else:
|
375 |
return "No similar question found."
|
376 |
|
|
|
377 |
class CalculatorTool(Tool):
|
378 |
name = "calculator"
|
379 |
description = """Performs basic mathematical calculations (e.g., addition, subtraction, multiplication, division, exponentiation, square root).
|
@@ -382,7 +378,7 @@ Use this tool whenever math is required, especially for numeric reasoning."""
|
|
382 |
inputs = {
|
383 |
"expression": {
|
384 |
"type": "string",
|
385 |
-
"description": "A basic math expression, e.g., '5 + 3 * 2', 'sqrt(49)', '2 ** 3'. No variables or natural language."
|
386 |
}
|
387 |
}
|
388 |
output_type = "string"
|
@@ -390,8 +386,7 @@ Use this tool whenever math is required, especially for numeric reasoning."""
|
|
390 |
def forward(self, expression: str) -> str:
|
391 |
try:
|
392 |
allowed_names = {
|
393 |
-
k: v for k, v in math.__dict__.items()
|
394 |
-
if not k.startswith("__")
|
395 |
}
|
396 |
allowed_names.update({"abs": abs, "round": round})
|
397 |
result = eval(expression, {"__builtins__": {}}, allowed_names)
|
@@ -399,6 +394,7 @@ Use this tool whenever math is required, especially for numeric reasoning."""
|
|
399 |
except Exception as e:
|
400 |
return f"Error: Invalid math expression. ({e})"
|
401 |
|
|
|
402 |
class AnalyzeChessImageTool(Tool):
|
403 |
name = "analyze_chess_image"
|
404 |
description = """Extracts the board state from a chessboard image and returns the best move for black (in algebraic notation)."""
|
@@ -406,7 +402,7 @@ class AnalyzeChessImageTool(Tool):
|
|
406 |
inputs = {
|
407 |
"file_path": {
|
408 |
"type": "string",
|
409 |
-
"description": "Path to the image file of the chess board."
|
410 |
}
|
411 |
}
|
412 |
output_type = "string"
|
@@ -431,7 +427,6 @@ class AnalyzeChessImageTool(Tool):
|
|
431 |
return f"β Chess analysis failed: {e}"
|
432 |
|
433 |
|
434 |
-
|
435 |
class ExecutePythonCodeTool(Tool):
|
436 |
name = "execute_python_code"
|
437 |
description = """Executes a provided Python code snippet in a controlled, sandboxed environment.
|
@@ -440,7 +435,7 @@ class ExecutePythonCodeTool(Tool):
|
|
440 |
inputs = {
|
441 |
"code": {
|
442 |
"type": "string",
|
443 |
-
"description": "A valid Python code block that needs to be executed. It should be a string containing executable Python code."
|
444 |
}
|
445 |
}
|
446 |
output_type = "string"
|
@@ -485,8 +480,8 @@ class ExecutePythonCodeTool(Tool):
|
|
485 |
exec(code, restricted_globals, exec_locals)
|
486 |
|
487 |
# If the code produces a result, we return that as output
|
488 |
-
if
|
489 |
-
return str(exec_locals[
|
490 |
else:
|
491 |
return "β The code did not produce a result."
|
492 |
|
@@ -494,7 +489,6 @@ class ExecutePythonCodeTool(Tool):
|
|
494 |
return f"β Error executing code: {str(e)}"
|
495 |
|
496 |
|
497 |
-
|
498 |
class ArxivSearchTool(Tool):
|
499 |
name = "arxiv_search"
|
500 |
description = """Searches arXiv for academic papers and returns structured information including titles, authors, publication dates, and abstracts. Ideal for finding scientific research on specific topics."""
|
@@ -502,7 +496,7 @@ class ArxivSearchTool(Tool):
|
|
502 |
inputs = {
|
503 |
"query": {
|
504 |
"type": "string",
|
505 |
-
"description": "A research-related query string (e.g., 'Superstring Cosmology')"
|
506 |
}
|
507 |
}
|
508 |
output_type = "string"
|
@@ -512,9 +506,7 @@ class ArxivSearchTool(Tool):
|
|
512 |
|
513 |
try:
|
514 |
search_docs = ArxivLoader(
|
515 |
-
query=query,
|
516 |
-
load_max_docs=max_results,
|
517 |
-
load_all_available_meta=True
|
518 |
).load()
|
519 |
except Exception as e:
|
520 |
return f"β Arxiv search failed: {e}"
|
@@ -539,7 +531,9 @@ class ArxivSearchTool(Tool):
|
|
539 |
# output_lines.append(f"Journal Ref : {meta.get('journal_ref', '[N/A]')}")
|
540 |
# output_lines.append(f"Primary Cat. : {meta.get('primary_category', '[N/A]')}")
|
541 |
# output_lines.append(f"Categories : {', '.join(meta.get('categories', [])) or '[N/A]'}")
|
542 |
-
output_lines.append(
|
|
|
|
|
543 |
|
544 |
if content:
|
545 |
preview = content[:30] + ("..." if len(content) > 30 else "")
|
@@ -547,4 +541,4 @@ class ArxivSearchTool(Tool):
|
|
547 |
|
548 |
output_lines.append("") # spacing between results
|
549 |
|
550 |
-
return "\n".join(output_lines).strip()
|
|
|
1 |
+
import math
|
2 |
import os
|
3 |
import re
|
4 |
+
import subprocess
|
5 |
+
import sys
|
6 |
+
|
|
|
7 |
import fitz # PyMuPDF
|
|
|
|
|
|
|
8 |
import requests
|
|
|
9 |
from langchain_community.retrievers import BM25Retriever
|
10 |
from smolagents import Tool
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
11 |
|
12 |
|
13 |
class DetectVisualElementsTool(Tool):
|
|
|
17 |
inputs = {
|
18 |
"image_path": {
|
19 |
"type": "string",
|
20 |
+
"description": "The full path to the image file to analyze.",
|
21 |
}
|
22 |
}
|
23 |
output_type = "string"
|
24 |
|
25 |
def forward(self, image_path: str) -> list:
|
26 |
import os
|
27 |
+
|
28 |
import torch
|
|
|
29 |
import torchvision.models.detection as models
|
30 |
+
import torchvision.transforms as T
|
31 |
+
from PIL import Image
|
32 |
|
33 |
label_map = {
|
34 |
+
0: "unlabeled",
|
35 |
+
1: "person",
|
36 |
+
2: "bicycle",
|
37 |
+
3: "car",
|
38 |
+
4: "motorcycle",
|
39 |
+
5: "airplane",
|
40 |
+
6: "bus",
|
41 |
+
7: "train",
|
42 |
+
8: "truck",
|
43 |
+
9: "boat",
|
44 |
+
10: "traffic",
|
45 |
+
11: "fire",
|
46 |
+
12: "street",
|
47 |
+
13: "stop",
|
48 |
+
14: "parking",
|
49 |
+
15: "bench",
|
50 |
+
16: "bird",
|
51 |
+
17: "cat",
|
52 |
+
18: "dog",
|
53 |
+
19: "horse",
|
54 |
+
20: "sheep",
|
55 |
+
21: "cow",
|
56 |
+
22: "elephant",
|
57 |
+
23: "bear",
|
58 |
+
24: "zebra",
|
59 |
+
25: "giraffe",
|
60 |
+
26: "hat",
|
61 |
+
27: "backpack",
|
62 |
+
28: "umbrella",
|
63 |
+
29: "shoe",
|
64 |
+
30: "eye",
|
65 |
+
31: "handbag",
|
66 |
+
32: "tie",
|
67 |
+
33: "suitcase",
|
68 |
+
34: "frisbee",
|
69 |
+
35: "skis",
|
70 |
+
36: "snowboard",
|
71 |
+
37: "sports",
|
72 |
+
38: "kite",
|
73 |
+
39: "baseball",
|
74 |
+
40: "baseball",
|
75 |
+
41: "skateboard",
|
76 |
+
42: "surfboard",
|
77 |
+
43: "tennis",
|
78 |
+
44: "bottle",
|
79 |
+
45: "plate",
|
80 |
+
46: "wine",
|
81 |
+
47: "cup",
|
82 |
+
48: "fork",
|
83 |
+
49: "knife",
|
84 |
+
50: "spoon",
|
85 |
+
51: "bowl",
|
86 |
+
52: "banana",
|
87 |
+
53: "apple",
|
88 |
+
54: "sandwich",
|
89 |
+
55: "orange",
|
90 |
+
56: "broccoli",
|
91 |
+
57: "carrot",
|
92 |
+
58: "hot",
|
93 |
+
59: "pizza",
|
94 |
+
60: "donut",
|
95 |
+
61: "cake",
|
96 |
+
62: "chair",
|
97 |
+
63: "couch",
|
98 |
+
64: "potted",
|
99 |
+
65: "bed",
|
100 |
+
66: "mirror",
|
101 |
+
67: "dining",
|
102 |
+
68: "window",
|
103 |
+
69: "desk",
|
104 |
+
70: "toilet",
|
105 |
+
71: "door",
|
106 |
+
72: "tv",
|
107 |
+
73: "laptop",
|
108 |
+
74: "mouse",
|
109 |
+
75: "remote",
|
110 |
+
76: "keyboard",
|
111 |
+
77: "cell",
|
112 |
+
78: "microwave",
|
113 |
+
79: "oven",
|
114 |
+
80: "toaster",
|
115 |
+
81: "sink",
|
116 |
+
82: "refrigerator",
|
117 |
+
83: "blender",
|
118 |
+
84: "book",
|
119 |
+
85: "clock",
|
120 |
+
86: "vase",
|
121 |
+
87: "scissors",
|
122 |
+
88: "teddy",
|
123 |
+
89: "hair",
|
124 |
+
90: "toothbrush",
|
125 |
+
91: "hair",
|
126 |
+
92: "banner",
|
127 |
+
93: "blanket",
|
128 |
+
94: "branch",
|
129 |
+
95: "bridge",
|
130 |
+
96: "building",
|
131 |
+
97: "bush",
|
132 |
+
98: "cabinet",
|
133 |
+
99: "cage",
|
134 |
+
100: "cardboard",
|
135 |
+
101: "carpet",
|
136 |
+
102: "ceiling",
|
137 |
+
103: "ceiling",
|
138 |
+
104: "cloth",
|
139 |
+
105: "clothes",
|
140 |
+
106: "clouds",
|
141 |
+
107: "counter",
|
142 |
+
108: "cupboard",
|
143 |
+
109: "curtain",
|
144 |
+
110: "desk",
|
145 |
+
111: "dirt",
|
146 |
+
112: "door",
|
147 |
+
113: "fence",
|
148 |
+
114: "floor",
|
149 |
+
115: "floor",
|
150 |
+
116: "floor",
|
151 |
+
117: "floor",
|
152 |
+
118: "floor",
|
153 |
+
119: "flower",
|
154 |
+
120: "fog",
|
155 |
+
121: "food",
|
156 |
+
122: "fruit",
|
157 |
+
123: "furniture",
|
158 |
+
124: "grass",
|
159 |
+
125: "gravel",
|
160 |
+
126: "ground",
|
161 |
+
127: "hill",
|
162 |
+
128: "house",
|
163 |
+
129: "leaves",
|
164 |
+
130: "light",
|
165 |
+
131: "mat",
|
166 |
+
132: "metal",
|
167 |
+
133: "mirror",
|
168 |
+
134: "moss",
|
169 |
+
135: "mountain",
|
170 |
+
136: "mud",
|
171 |
+
137: "napkin",
|
172 |
+
138: "net",
|
173 |
+
139: "paper",
|
174 |
+
140: "pavement",
|
175 |
+
141: "pillow",
|
176 |
+
142: "plant",
|
177 |
+
143: "plastic",
|
178 |
+
144: "platform",
|
179 |
+
145: "playingfield",
|
180 |
+
146: "railing",
|
181 |
+
147: "railroad",
|
182 |
+
148: "river",
|
183 |
+
149: "road",
|
184 |
+
150: "rock",
|
185 |
+
151: "roof",
|
186 |
+
152: "rug",
|
187 |
+
153: "salad",
|
188 |
+
154: "sand",
|
189 |
+
155: "sea",
|
190 |
+
156: "shelf",
|
191 |
+
157: "sky",
|
192 |
+
158: "skyscraper",
|
193 |
+
159: "snow",
|
194 |
+
160: "solid",
|
195 |
+
161: "stairs",
|
196 |
+
162: "stone",
|
197 |
+
163: "straw",
|
198 |
+
164: "structural",
|
199 |
+
165: "table",
|
200 |
+
166: "tent",
|
201 |
+
167: "textile",
|
202 |
+
168: "towel",
|
203 |
+
169: "tree",
|
204 |
+
170: "vegetable",
|
205 |
+
171: "wall",
|
206 |
+
172: "wall",
|
207 |
+
173: "wall",
|
208 |
+
174: "wall",
|
209 |
+
175: "wall",
|
210 |
+
176: "wall",
|
211 |
+
177: "wall",
|
212 |
+
178: "water",
|
213 |
+
179: "waterdrops",
|
214 |
+
180: "window",
|
215 |
+
181: "window",
|
216 |
+
182: "wood",
|
217 |
+
}
|
|
|
218 |
|
219 |
if not os.path.exists(image_path):
|
220 |
return [f"β Image file does not exist: {image_path}"]
|
|
|
235 |
if score > 0.8:
|
236 |
print(str(label_id.item()))
|
237 |
labels_list.append(label_map.get(label_id.item()))
|
238 |
+
|
239 |
labels = ",".join(labels_list)
|
240 |
|
241 |
return labels or ["β οΈ No confident visual elements detected."]
|
|
|
252 |
"url": {
|
253 |
"type": "string",
|
254 |
"description": "Optional. URL pointing to an image of a chessboard position.",
|
255 |
+
"nullable": True,
|
256 |
},
|
257 |
"file_path": {
|
258 |
"type": "string",
|
259 |
"description": "Optional. Local file path to an image of a chessboard position.",
|
260 |
+
"nullable": True,
|
261 |
+
},
|
262 |
}
|
263 |
|
264 |
output_type = "string"
|
265 |
|
|
|
|
|
266 |
def forward(self, url: str = None, file_path: str = None) -> str:
|
267 |
if not url and not file_path:
|
268 |
return "β Please provide either a URL or a local file path to the chessboard image."
|
|
|
290 |
|
291 |
board = chess.Board(fen)
|
292 |
|
293 |
+
STOCKFISH_PATH = os.getenv(
|
294 |
+
"STOCKFISH_PATH",
|
295 |
+
"/home/boom/Desktop/repos/boombot/engines/stockfish-ubuntu-x86-64-bmi2",
|
296 |
+
) # Ensure Stockfish is available
|
297 |
|
298 |
# Step 3 - Analyze with Stockfish
|
299 |
engine = chess.engine.SimpleEngine.popen_uci(STOCKFISH_PATH)
|
|
|
317 |
|
318 |
with open(pyproject_path, "w", encoding="utf-8") as f:
|
319 |
for line in lines:
|
320 |
+
if re.match(r"\s*python\s*=", line):
|
321 |
f.write('python = ">=3.8,<3.12"\n')
|
322 |
else:
|
323 |
f.write(line)
|
324 |
|
325 |
+
|
326 |
def install_chesscog():
|
327 |
TARGET_DIR = "chesscog"
|
328 |
REPO_URL = "https://github.com/georg-wolflein/chesscog.git"
|
329 |
|
330 |
try:
|
331 |
+
pass
|
332 |
+
|
333 |
print("β
chesscog already installed.")
|
334 |
# return
|
335 |
except ImportError:
|
|
|
340 |
|
341 |
patch_pyproject(TARGET_DIR)
|
342 |
|
343 |
+
subprocess.run(
|
344 |
+
[sys.executable, "-m", "pip", "install", f"./{TARGET_DIR}"], check=True
|
345 |
+
)
|
346 |
print("β
chesscog installed successfully.")
|
347 |
|
348 |
+
|
349 |
class RetrieverTool(Tool):
|
350 |
name = "retriever"
|
351 |
description = "Retrieves the most similar known question to the query."
|
|
|
369 |
else:
|
370 |
return "No similar question found."
|
371 |
|
372 |
+
|
373 |
class CalculatorTool(Tool):
|
374 |
name = "calculator"
|
375 |
description = """Performs basic mathematical calculations (e.g., addition, subtraction, multiplication, division, exponentiation, square root).
|
|
|
378 |
inputs = {
|
379 |
"expression": {
|
380 |
"type": "string",
|
381 |
+
"description": "A basic math expression, e.g., '5 + 3 * 2', 'sqrt(49)', '2 ** 3'. No variables or natural language.",
|
382 |
}
|
383 |
}
|
384 |
output_type = "string"
|
|
|
386 |
def forward(self, expression: str) -> str:
|
387 |
try:
|
388 |
allowed_names = {
|
389 |
+
k: v for k, v in math.__dict__.items() if not k.startswith("__")
|
|
|
390 |
}
|
391 |
allowed_names.update({"abs": abs, "round": round})
|
392 |
result = eval(expression, {"__builtins__": {}}, allowed_names)
|
|
|
394 |
except Exception as e:
|
395 |
return f"Error: Invalid math expression. ({e})"
|
396 |
|
397 |
+
|
398 |
class AnalyzeChessImageTool(Tool):
|
399 |
name = "analyze_chess_image"
|
400 |
description = """Extracts the board state from a chessboard image and returns the best move for black (in algebraic notation)."""
|
|
|
402 |
inputs = {
|
403 |
"file_path": {
|
404 |
"type": "string",
|
405 |
+
"description": "Path to the image file of the chess board.",
|
406 |
}
|
407 |
}
|
408 |
output_type = "string"
|
|
|
427 |
return f"β Chess analysis failed: {e}"
|
428 |
|
429 |
|
|
|
430 |
class ExecutePythonCodeTool(Tool):
|
431 |
name = "execute_python_code"
|
432 |
description = """Executes a provided Python code snippet in a controlled, sandboxed environment.
|
|
|
435 |
inputs = {
|
436 |
"code": {
|
437 |
"type": "string",
|
438 |
+
"description": "A valid Python code block that needs to be executed. It should be a string containing executable Python code.",
|
439 |
}
|
440 |
}
|
441 |
output_type = "string"
|
|
|
480 |
exec(code, restricted_globals, exec_locals)
|
481 |
|
482 |
# If the code produces a result, we return that as output
|
483 |
+
if "result" in exec_locals:
|
484 |
+
return str(exec_locals["result"])
|
485 |
else:
|
486 |
return "β The code did not produce a result."
|
487 |
|
|
|
489 |
return f"β Error executing code: {str(e)}"
|
490 |
|
491 |
|
|
|
492 |
class ArxivSearchTool(Tool):
|
493 |
name = "arxiv_search"
|
494 |
description = """Searches arXiv for academic papers and returns structured information including titles, authors, publication dates, and abstracts. Ideal for finding scientific research on specific topics."""
|
|
|
496 |
inputs = {
|
497 |
"query": {
|
498 |
"type": "string",
|
499 |
+
"description": "A research-related query string (e.g., 'Superstring Cosmology')",
|
500 |
}
|
501 |
}
|
502 |
output_type = "string"
|
|
|
506 |
|
507 |
try:
|
508 |
search_docs = ArxivLoader(
|
509 |
+
query=query, load_max_docs=max_results, load_all_available_meta=True
|
|
|
|
|
510 |
).load()
|
511 |
except Exception as e:
|
512 |
return f"β Arxiv search failed: {e}"
|
|
|
531 |
# output_lines.append(f"Journal Ref : {meta.get('journal_ref', '[N/A]')}")
|
532 |
# output_lines.append(f"Primary Cat. : {meta.get('primary_category', '[N/A]')}")
|
533 |
# output_lines.append(f"Categories : {', '.join(meta.get('categories', [])) or '[N/A]'}")
|
534 |
+
output_lines.append(
|
535 |
+
f"Links : {', '.join(meta.get('links', [])) or '[N/A]'}"
|
536 |
+
)
|
537 |
|
538 |
if content:
|
539 |
preview = content[:30] + ("..." if len(content) > 30 else "")
|
|
|
541 |
|
542 |
output_lines.append("") # spacing between results
|
543 |
|
544 |
+
return "\n".join(output_lines).strip()
|
utils.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1 |
import re
|
2 |
|
|
|
3 |
def extract_final_answer(output: str) -> str:
|
4 |
"""
|
5 |
Extracts the text after 'FINAL ANSWER:' in the model's output.
|
@@ -9,27 +10,27 @@ def extract_final_answer(output: str) -> str:
|
|
9 |
output = str(output)
|
10 |
marker = "FINAL ANSWER:"
|
11 |
lower_output = output.lower()
|
12 |
-
|
13 |
if marker.lower() in lower_output:
|
14 |
# Find actual case version in original output (for safety)
|
15 |
idx = lower_output.rfind(marker.lower())
|
16 |
-
raw_answer = output[idx + len(marker):].strip()
|
17 |
-
|
18 |
# Normalize comma-separated lists: ensure single space after commas
|
19 |
-
cleaned_answer = re.sub(r
|
20 |
return cleaned_answer
|
21 |
-
|
22 |
return output
|
23 |
|
24 |
|
25 |
def replace_tool_mentions(prompt: str) -> str:
|
26 |
# Replace tool mentions in backticks: `search` -> `web_search`, `wiki` -> `wikipedia_search`
|
27 |
-
prompt = re.sub(r
|
28 |
-
prompt = re.sub(r
|
29 |
|
30 |
# Replace function calls: search(...) -> web_search(...), wiki(...) -> wikipedia_search(...)
|
31 |
# This ensures we only catch function calls (not words like arxiv_search)
|
32 |
-
prompt = re.sub(r
|
33 |
-
prompt = re.sub(r
|
34 |
|
35 |
-
return prompt
|
|
|
1 |
import re
|
2 |
|
3 |
+
|
4 |
def extract_final_answer(output: str) -> str:
|
5 |
"""
|
6 |
Extracts the text after 'FINAL ANSWER:' in the model's output.
|
|
|
10 |
output = str(output)
|
11 |
marker = "FINAL ANSWER:"
|
12 |
lower_output = output.lower()
|
13 |
+
|
14 |
if marker.lower() in lower_output:
|
15 |
# Find actual case version in original output (for safety)
|
16 |
idx = lower_output.rfind(marker.lower())
|
17 |
+
raw_answer = output[idx + len(marker) :].strip()
|
18 |
+
|
19 |
# Normalize comma-separated lists: ensure single space after commas
|
20 |
+
cleaned_answer = re.sub(r",\s*", ", ", raw_answer)
|
21 |
return cleaned_answer
|
22 |
+
|
23 |
return output
|
24 |
|
25 |
|
26 |
def replace_tool_mentions(prompt: str) -> str:
|
27 |
# Replace tool mentions in backticks: `search` -> `web_search`, `wiki` -> `wikipedia_search`
|
28 |
+
prompt = re.sub(r"(?<!\w)`search`(?!\w)", "`web_search`", prompt)
|
29 |
+
prompt = re.sub(r"(?<!\w)`wiki`(?!\w)", "`wikipedia_search`", prompt)
|
30 |
|
31 |
# Replace function calls: search(...) -> web_search(...), wiki(...) -> wikipedia_search(...)
|
32 |
# This ensures we only catch function calls (not words like arxiv_search)
|
33 |
+
prompt = re.sub(r"(?<!\w)(?<!_)search\(", "web_search(", prompt)
|
34 |
+
prompt = re.sub(r"(?<!\w)(?<!_)wiki\(", "wikipedia_search(", prompt)
|
35 |
|
36 |
+
return prompt
|