mdicio commited on
Commit
ef7a70c
Β·
1 Parent(s): b69b087

restructuring

Browse files
Files changed (12) hide show
  1. agent.py +47 -62
  2. app.py +4 -5
  3. app_template.py +0 -1
  4. cocolabelmap.py +1 -2
  5. fullreq.txt +309 -0
  6. langtools.py +20 -14
  7. requirements.txt +15 -8
  8. setup_actions.ipynb +30 -20
  9. tools copy.py +55 -61
  10. tools.py +326 -241
  11. tools_beta.py +222 -228
  12. utils.py +11 -10
agent.py CHANGED
@@ -1,46 +1,39 @@
1
- from dotenv import load_dotenv
2
  import os
3
- from typing import Union, List, Dict, Any, Optional, Tuple, Bool
4
 
5
- # Import tools from LangChain
6
- from langchain.agents import get_all_tool_names
7
- from langchain.agents import load_tools
 
 
 
 
8
 
9
  # Import custom tools
10
  from Final_Assignment_Template.tools import (
11
- ReadFileContentTool,
12
- WikipediaSearchTool,
13
- VisitWebpageTool,
14
- TranscribeAudioTool,
15
- TranscibeVideoFileTool,
16
- BraveWebSearchTool,
17
- DescribeImageTool,
18
  ArxivSearchTool,
19
  DownloadFileFromLinkTool,
20
  DuckDuckGoSearchTool,
21
- AddDocumentToVectorStoreTool,
22
  QueryVectorStoreTool,
23
- image_question_answering
 
 
 
 
24
  )
25
 
26
  # Import utility functions
27
- from utils import replace_tool_mentions, extract_final_answer
28
-
29
- # Import SmolaAgents tools
30
- from smolagents.default_tools import (
31
- PythonInterpreterTool,
32
- FinalAnswerTool
33
- )
34
 
35
- # Import models from SmolaAgents
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='cuda',
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='cuda',
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 langchain_core.messages import HumanMessage
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 = 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
- requests
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
- huggingface_hub
13
- chromadb
14
- sentence-transformers
 
15
  python-dotenv
16
- protobuf==3.20.*
 
 
 
 
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(\"gaia-benchmark/GAIA\", name=\"2023_level1\", split=\"validation\", trust_remote_code=True, cache_dir = \"ragdata\")"
 
 
 
 
 
 
269
  ]
270
  },
271
  {
@@ -286,7 +293,9 @@
286
  }
287
  ],
288
  "source": [
289
- "os.listdir(r\"ragdata/gaia-benchmark___gaia/2023_level1/0.0.1/ec492fe4320ee795b1aed6bb46229c5f693226b0f1316347501c24b4baeee005\")"
 
 
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(\"gaia-benchmark/GAIA\", name=\"2023_level1\", split=\"validation\", trust_remote_code=True, cache_dir = \"ragdata\")\n",
 
 
 
 
 
 
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
- " \n",
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\n",
330
  " }\n",
331
- " \n",
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\"\n",
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
- " \n",
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('\\n')\n",
382
- " \n",
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 = {'tools': list(unique_tools)}\n",
389
  "\n",
390
  "# Print the unique tools to get a sense of what was used\n",
391
- "print(tools_dict)\n"
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 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
- 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
  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('query', {}).get('search', []):
149
  return f"No Wikipedia info for '{topic}'."
150
 
151
- page_id = search_data['query']['search'][0]['pageid']
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['query']['pages'][str(page_id)]['extract']
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 ' '.join(text.strip().split())
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 = """Transcribes spoken audio (e.g. voice memos, lectures) into plain text."""
 
 
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 != '.wav':
284
  # Create temp WAV file
285
- temp_wav = tempfile.NamedTemporaryFile(suffix='.wav', delete=False).name
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 != '.wav' and os.path.exists(temp_wav):
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='.wav', delete=False).name
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 time
34
- import traceback
 
 
 
 
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
- TextLoader, PyPDFLoader, JSONLoader, UnstructuredFileLoader,BSHTMLLoader
 
 
 
 
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 re
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(f"CSV Content:\n{df.to_string(index=False)}\n\nColumn names: {', '.join(df.columns)}")
 
 
95
 
96
  elif ext in [".xlsx", ".xls"]:
97
  df = pd.read_excel(file_path)
98
- return truncate_content(f"Excel Content:\n{df.to_string(index=False)}\n\nColumn names: {', '.join(df.columns)}")
 
 
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(text.strip() or "⚠️ PDF contains no readable text.")
 
 
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('query', {}).get('search', []):
159
  return f"No Wikipedia info for '{query}'."
160
 
161
- page_id = search_data['query']['search'][0]['pageid']
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['query']['pages'][str(page_id)]['extract']
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 f"❌ Audio file not found at: {file_path}. Download the file first."
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 != '.wav':
255
  # Create temp WAV file
256
- temp_wav = tempfile.NamedTemporaryFile(suffix='.wav', delete=False).name
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 != '.wav' and os.path.exists(temp_wav):
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 f"❌ Video file not found at: {file_path}. Download the file first."
296
-
297
- import moviepy.editor as mp
298
- import speech_recognition as sr
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='.wav', delete=False).name
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 = json.loads(results_json) if isinstance(results_json, str) else results_json
 
 
 
 
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 torch
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("Salesforce/blip-image-captioning-base", use_fast = True)
414
- model = BlipForConditionalGeneration.from_pretrained("Salesforce/blip-image-captioning-base")
 
 
 
 
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 = {'.xlsx','.pdf', '.txt', '.csv', '.json', '.xml', '.html', '.jpg', '.jpeg', '.png', '.mp4', '.mp3', '.wav', '.zip'}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 f"❌ Error: Unable to fetch file. Status code: {response.status_code}"
 
 
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 = response.headers.get("Content-Type", "").split(";")[0].strip()
 
 
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(f"EXECUTING TOOL: duckduckgo_search(query='{query}', top_results={top_results})")
 
 
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 (DuckDuckGoSearchException, TimeoutException, RatelimitException, ConversationLimitException) as e:
 
 
 
 
 
549
  retries += 1
550
- print(f"⚠️ DuckDuckGo Exception (Attempt {retries}/{max_retries}): {type(e).__name__}: {e}")
 
 
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 = [".txt", ".md", ".py", ".pdf", ".json", ".jsonl", '.html', '.htm']
 
 
 
 
 
 
 
 
 
 
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(chunk_size=500, chunk_overlap=50)
 
 
603
  split_docs = text_splitter.split_documents(docs)
604
 
605
- client = chromadb.Client(chromadb.config.Settings(
606
- persist_directory="./chroma_store",
607
- ))
 
 
608
 
609
- collection = client.get_or_create_collection(name=collection_name,configuration={
610
- "embedding_function": huggingface_ef
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(chromadb.config.Settings(
657
- persist_directory="./chroma_store",
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='llava', # Assuming your model is named 'lava'
707
  messages=[
708
  {
709
- 'role': 'user',
710
- 'content': prompt,
711
- 'images': [path],
712
  },
713
  ],
714
- options={'temperature': 0.2} # Slight randomness for naturalness
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
- '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'
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, 'html.parser')
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
- 'header', 'footer', 'nav', '.nav', '.navigation', '.menu', '.sidebar',
796
- '.footer', '.header', '#footer', '#header', '#nav', '#sidebar',
797
- '.widget', '.cookie', '.cookies', '.ad', '.ads', '.advertisement',
798
- 'script', 'style', 'noscript', 'iframe', '.social', '.share',
799
- '.comment', '.comments', '.subscription', '.newsletter',
800
- '[role="banner"]', '[role="navigation"]', '[role="complementary"]'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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('[style*="display: none"], [style*="display:none"], [style*="visibility: hidden"], [style*="visibility:hidden"], [hidden]'):
 
 
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
- 'main',
816
- '[role="main"]',
817
- 'article',
818
- '.content',
819
- '.main-content',
820
- '.post-content',
821
- '#content',
822
- '#main',
823
- '#main-content',
824
- '.article',
825
- '.post',
826
- '.entry',
827
- '.page-content',
828
- '.entry-content',
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('p')
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'\n{3,}', '\n\n', content)
862
-
863
  # Remove consecutive duplicate links
864
- content = re.sub(r'(\[.*?\]\(.*?\))\s*\1+', r'\1', content)
865
-
866
  # Remove very short lines that are likely menu items
867
- lines = content.split('\n')
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(stripped_line) < short_line_threshold and not stripped_line.startswith('#'):
 
 
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 = '\n'.join(filtered_lines)
887
-
888
  # Remove duplicate headers
889
  seen_headers = set()
890
- lines = content.split('\n')
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 = '\n'.join(filtered_lines)
902
-
903
  # Remove lines containing common footer patterns
904
  footer_patterns = [
905
- r'^copyright', r'^Β©', r'^all rights reserved',
906
- r'^terms', r'^privacy policy', r'^contact us',
907
- r'^follow us', r'^social media', r'^disclaimer',
 
 
 
 
 
 
908
  ]
909
-
910
- footer_pattern = '|'.join(footer_patterns)
911
- lines = content.split('\n')
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 = '\n'.join(filtered_lines)
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": {"type": "string", "description": "A research-related query (e.g., 'AI regulation')"},
930
- "from_date":{"type": "string", "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')", "nullable": True},
931
- "to_date": {"type": "string", "description": "Optional search end date in (YYYY or YYYY-MM or YYYY-MM-DD) (e.g., '2022-06' or '2022' or '2022-04-12')", "nullable": True},
 
 
 
 
 
 
 
 
 
 
 
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
- from typing import List, Dict
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 = ab.get_text(strip=True).replace("Abstract:", "").strip() if ab else ""
 
 
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
- "title": title,
1022
- "authors": authors,
1023
- "published": published,
1024
- "abstract": abstract,
1025
- "entry_link": entry_link,
1026
- "download_link": entry_link.replace("abs", "pdf") if "abs" in entry_link else "N/A"
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 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
- 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
- from PIL import Image
39
  import torch
40
- import torchvision.transforms as T
41
  import torchvision.models.detection as models
 
 
42
 
43
  label_map = {
44
- 0: "unlabeled",
45
- 1: "person",
46
- 2: "bicycle",
47
- 3: "car",
48
- 4: "motorcycle",
49
- 5: "airplane",
50
- 6: "bus",
51
- 7: "train",
52
- 8: "truck",
53
- 9: "boat",
54
- 10: "traffic",
55
- 11: "fire",
56
- 12: "street",
57
- 13: "stop",
58
- 14: "parking",
59
- 15: "bench",
60
- 16: "bird",
61
- 17: "cat",
62
- 18: "dog",
63
- 19: "horse",
64
- 20: "sheep",
65
- 21: "cow",
66
- 22: "elephant",
67
- 23: "bear",
68
- 24: "zebra",
69
- 25: "giraffe",
70
- 26: "hat",
71
- 27: "backpack",
72
- 28: "umbrella",
73
- 29: "shoe",
74
- 30: "eye",
75
- 31: "handbag",
76
- 32: "tie",
77
- 33: "suitcase",
78
- 34: "frisbee",
79
- 35: "skis",
80
- 36: "snowboard",
81
- 37: "sports",
82
- 38: "kite",
83
- 39: "baseball",
84
- 40: "baseball",
85
- 41: "skateboard",
86
- 42: "surfboard",
87
- 43: "tennis",
88
- 44: "bottle",
89
- 45: "plate",
90
- 46: "wine",
91
- 47: "cup",
92
- 48: "fork",
93
- 49: "knife",
94
- 50: "spoon",
95
- 51: "bowl",
96
- 52: "banana",
97
- 53: "apple",
98
- 54: "sandwich",
99
- 55: "orange",
100
- 56: "broccoli",
101
- 57: "carrot",
102
- 58: "hot",
103
- 59: "pizza",
104
- 60: "donut",
105
- 61: "cake",
106
- 62: "chair",
107
- 63: "couch",
108
- 64: "potted",
109
- 65: "bed",
110
- 66: "mirror",
111
- 67: "dining",
112
- 68: "window",
113
- 69: "desk",
114
- 70: "toilet",
115
- 71: "door",
116
- 72: "tv",
117
- 73: "laptop",
118
- 74: "mouse",
119
- 75: "remote",
120
- 76: "keyboard",
121
- 77: "cell",
122
- 78: "microwave",
123
- 79: "oven",
124
- 80: "toaster",
125
- 81: "sink",
126
- 82: "refrigerator",
127
- 83: "blender",
128
- 84: "book",
129
- 85: "clock",
130
- 86: "vase",
131
- 87: "scissors",
132
- 88: "teddy",
133
- 89: "hair",
134
- 90: "toothbrush",
135
- 91: "hair",
136
- 92: "banner",
137
- 93: "blanket",
138
- 94: "branch",
139
- 95: "bridge",
140
- 96: "building",
141
- 97: "bush",
142
- 98: "cabinet",
143
- 99: "cage",
144
- 100: "cardboard",
145
- 101: "carpet",
146
- 102: "ceiling",
147
- 103: "ceiling",
148
- 104: "cloth",
149
- 105: "clothes",
150
- 106: "clouds",
151
- 107: "counter",
152
- 108: "cupboard",
153
- 109: "curtain",
154
- 110: "desk",
155
- 111: "dirt",
156
- 112: "door",
157
- 113: "fence",
158
- 114: "floor",
159
- 115: "floor",
160
- 116: "floor",
161
- 117: "floor",
162
- 118: "floor",
163
- 119: "flower",
164
- 120: "fog",
165
- 121: "food",
166
- 122: "fruit",
167
- 123: "furniture",
168
- 124: "grass",
169
- 125: "gravel",
170
- 126: "ground",
171
- 127: "hill",
172
- 128: "house",
173
- 129: "leaves",
174
- 130: "light",
175
- 131: "mat",
176
- 132: "metal",
177
- 133: "mirror",
178
- 134: "moss",
179
- 135: "mountain",
180
- 136: "mud",
181
- 137: "napkin",
182
- 138: "net",
183
- 139: "paper",
184
- 140: "pavement",
185
- 141: "pillow",
186
- 142: "plant",
187
- 143: "plastic",
188
- 144: "platform",
189
- 145: "playingfield",
190
- 146: "railing",
191
- 147: "railroad",
192
- 148: "river",
193
- 149: "road",
194
- 150: "rock",
195
- 151: "roof",
196
- 152: "rug",
197
- 153: "salad",
198
- 154: "sand",
199
- 155: "sea",
200
- 156: "shelf",
201
- 157: "sky",
202
- 158: "skyscraper",
203
- 159: "snow",
204
- 160: "solid",
205
- 161: "stairs",
206
- 162: "stone",
207
- 163: "straw",
208
- 164: "structural",
209
- 165: "table",
210
- 166: "tent",
211
- 167: "textile",
212
- 168: "towel",
213
- 169: "tree",
214
- 170: "vegetable",
215
- 171: "wall",
216
- 172: "wall",
217
- 173: "wall",
218
- 174: "wall",
219
- 175: "wall",
220
- 176: "wall",
221
- 177: "wall",
222
- 178: "water",
223
- 179: "waterdrops",
224
- 180: "window",
225
- 181: "window",
226
- 182: "wood"
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("STOCKFISH_PATH", "/home/boom/Desktop/repos/boombot/engines/stockfish-ubuntu-x86-64-bmi2") # Ensure Stockfish is available
 
 
 
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'\s*python\s*=', line):
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
- import chesscog
 
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([sys.executable, "-m", "pip", "install", f"./{TARGET_DIR}"], check=True)
 
 
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 'result' in exec_locals:
489
- return str(exec_locals['result'])
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(f"Links : {', '.join(meta.get('links', [])) or '[N/A]'}")
 
 
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',\s*', ', ', raw_answer)
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'(?<!\w)`search`(?!\w)', '`web_search`', prompt)
28
- prompt = re.sub(r'(?<!\w)`wiki`(?!\w)', '`wikipedia_search`', prompt)
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'(?<!\w)(?<!_)search\(', 'web_search(', prompt)
33
- prompt = re.sub(r'(?<!\w)(?<!_)wiki\(', 'wikipedia_search(', prompt)
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