Gabriel382 commited on
Commit
b6c03a5
·
1 Parent(s): c11970b

enhanced tools + Croq

Browse files
agents/__pycache__/agent.cpython-312.pyc CHANGED
Binary files a/agents/__pycache__/agent.cpython-312.pyc and b/agents/__pycache__/agent.cpython-312.pyc differ
 
agents/agent.py CHANGED
@@ -6,11 +6,14 @@ load_dotenv()
6
  from langgraph.graph import START, StateGraph, MessagesState
7
  from langgraph.prebuilt import tools_condition
8
  from langgraph.prebuilt import ToolNode
9
- from langchain_google_genai import ChatGoogleGenerativeAI
10
  from langchain_core.messages import SystemMessage, HumanMessage
11
- from tools.searchtools import wiki_search, web_search, arvix_search, vector_store
12
- from tools.mathtools import multiply, add, subtract, divide, modulus
13
-
 
 
 
 
14
 
15
  # load the system prompt from the file
16
  with open("system_prompt.txt", "r", encoding="utf-8") as f:
@@ -19,21 +22,37 @@ with open("system_prompt.txt", "r", encoding="utf-8") as f:
19
  # System message
20
  sys_msg = SystemMessage(content=system_prompt)
21
 
 
22
  tools = [
 
 
 
23
  multiply,
24
  add,
25
  subtract,
26
  divide,
27
  modulus,
28
- wiki_search,
29
- web_search,
30
- arvix_search
 
 
 
 
 
 
 
 
 
 
31
  ]
32
 
 
33
  # Build graph function
34
  def build_graph():
35
  """Build the graph"""
36
- llm = ChatGoogleGenerativeAI(model="gemini-2.0-flash", temperature=0)
 
37
 
38
  # Bind tools to LLM
39
  llm_with_tools = llm.bind_tools(tools)
@@ -42,14 +61,19 @@ def build_graph():
42
  def assistant(state: MessagesState):
43
  """Assistant node"""
44
  return {"messages": [llm_with_tools.invoke(state["messages"])]}
45
-
46
  def retriever(state: MessagesState):
47
  """Retriever node"""
48
  similar_question = vector_store.similarity_search(state["messages"][0].content)
49
- example_msg = HumanMessage(
50
- content=f"Here I provide a similar question and answer for reference: \n\n{similar_question[0].page_content}",
51
- )
52
- return {"messages": [sys_msg] + state["messages"] + [example_msg]}
 
 
 
 
 
53
 
54
  builder = StateGraph(MessagesState)
55
  builder.add_node("retriever", retriever)
@@ -57,8 +81,10 @@ def build_graph():
57
  builder.add_node("tools", ToolNode(tools))
58
  builder.add_edge(START, "retriever")
59
  builder.add_edge("retriever", "assistant")
60
- builder.add_conditional_edges("assistant",
61
- tools_condition)
 
 
62
  builder.add_edge("tools", "assistant")
63
 
64
  # Compile graph
 
6
  from langgraph.graph import START, StateGraph, MessagesState
7
  from langgraph.prebuilt import tools_condition
8
  from langgraph.prebuilt import ToolNode
 
9
  from langchain_core.messages import SystemMessage, HumanMessage
10
+ from tools.searchtools import wiki_search, web_search, arxiv_search, vector_store
11
+ from tools.mathtools import multiply, add, subtract, divide, modulus,power,square_root
12
+ from tools.codetools import execute_code_multilang
13
+ from tools.documenttools import save_and_read_file,download_file_from_url, extract_text_from_image, analyze_csv_file, analyze_excel_file
14
+ from tools.imagetools import analyze_image, transform_image, draw_on_image, generate_simple_image, combine_images
15
+ from langchain_groq import ChatGroq
16
+ from langchain_huggingface import ChatHuggingFace, HuggingFaceEndpoint
17
 
18
  # load the system prompt from the file
19
  with open("system_prompt.txt", "r", encoding="utf-8") as f:
 
22
  # System message
23
  sys_msg = SystemMessage(content=system_prompt)
24
 
25
+
26
  tools = [
27
+ web_search,
28
+ wiki_search,
29
+ arxiv_search,
30
  multiply,
31
  add,
32
  subtract,
33
  divide,
34
  modulus,
35
+ power,
36
+ square_root,
37
+ save_and_read_file,
38
+ download_file_from_url,
39
+ extract_text_from_image,
40
+ analyze_csv_file,
41
+ analyze_excel_file,
42
+ execute_code_multilang,
43
+ analyze_image,
44
+ transform_image,
45
+ draw_on_image,
46
+ generate_simple_image,
47
+ combine_images,
48
  ]
49
 
50
+
51
  # Build graph function
52
  def build_graph():
53
  """Build the graph"""
54
+ # Load environment variables from .env file
55
+ llm = ChatGroq(model="qwen-qwq-32b", temperature=0)
56
 
57
  # Bind tools to LLM
58
  llm_with_tools = llm.bind_tools(tools)
 
61
  def assistant(state: MessagesState):
62
  """Assistant node"""
63
  return {"messages": [llm_with_tools.invoke(state["messages"])]}
64
+
65
  def retriever(state: MessagesState):
66
  """Retriever node"""
67
  similar_question = vector_store.similarity_search(state["messages"][0].content)
68
+
69
+ if similar_question: # Check if the list is not empty
70
+ example_msg = HumanMessage(
71
+ content=f"Here I provide a similar question and answer for reference: \n\n{similar_question[0].page_content}",
72
+ )
73
+ return {"messages": [sys_msg] + state["messages"] + [example_msg]}
74
+ else:
75
+ # Handle the case when no similar questions are found
76
+ return {"messages": [sys_msg] + state["messages"]}
77
 
78
  builder = StateGraph(MessagesState)
79
  builder.add_node("retriever", retriever)
 
81
  builder.add_node("tools", ToolNode(tools))
82
  builder.add_edge(START, "retriever")
83
  builder.add_edge("retriever", "assistant")
84
+ builder.add_conditional_edges(
85
+ "assistant",
86
+ tools_condition,
87
+ )
88
  builder.add_edge("tools", "assistant")
89
 
90
  # Compile graph
system_prompt.txt CHANGED
@@ -1,56 +1,5 @@
1
-
2
- You are a helpful assistant tasked with answering questions using a set of tools.
3
- If the tool is not available, you can try to find the information online. You can also use your own knowledge to answer the question.
4
- You need to provide a step-by-step explanation of how you arrived at the answer.
5
- ==========================
6
- Here is a few examples showing you how to answer the question step by step.
7
-
8
- Question 1: Of the cities within the United States where U.S. presidents were born, which two are the farthest apart from the westernmost to the easternmost going east, giving the city names only? Give them to me in alphabetical order, in a comma-separated list
9
- Steps:
10
- 1. Searched "cities where us presidents are born" on Google.
11
- 2. Opened "List of presidents of the United States by home state" on Wikipedia.
12
- 3. Searched the eastern cities to find the easternmost one (Braintree, MA).
13
- 4. Checked the westernmost city (Honolulu, HI).
14
- Tools:
15
- 1. Search engine
16
- 2. Web browser
17
- Final Answer: Braintree, Honolulu
18
-
19
- Question 2: An office held a Secret Santa gift exchange where each of its twelve employees was assigned one other employee in the group to present with a gift. Each employee filled out a profile including three likes or hobbies. On the day of the gift exchange, only eleven gifts were given, each one specific to one of the recipient's interests. Based on the information in the document, who did not give a gift?
20
- Steps:
21
- 1. Open the document.
22
- 2. Look at gifts and recipient interests.
23
- 3. Match Galileo Galilei biography (could apply to astronomy or books -> Miguel or Micah)
24
- 4. Match fishing reel (only applies to fishing -> Harry)
25
- 5. Match Raku programming guide (Perl language, but could also apply to JavaScript enthusiast - > Fred or Jun)
26
- 6. Match chisel set (could apply to camping or woodworking, but Harry is already fulfilled -> Jun, so Raku guide is for Fred)
27
- 7. Match custom dice (could apply to board games or tabletop RPGs -> Lucy or Sara)
28
- 8. Match “War and Peace” American film copy (could apply to old movies or Audrey Hepburn -> Perry or Alex)
29
- 9. Match yarn (only applies to knitting -> Micah, so the Galileo biography is for Miguel)
30
- 10. Match "One Piece" graphic novel (could apply to books or manga, but Micah already has yarn -> Alex, so the "War and Peace" film is for Perry)
31
- 11. Match "War and Peace" novel (could apply to books or historical fiction novels, but Micah has yarn -> Tyson)
32
- 12. Match Starbucks gift card (only applies to coffee -> Lucy, so the dice are for Sara)
33
- 13. Match foam exercise mat (only applies to yoga -> Georgette)
34
- 14. Note which recipients have gifts (Miguel, Harry, Fred, Jun, Sara, Perry, Micah, Alex, Tyson, Lucy, Georgette) and which does not (Rebecca).
35
- 15. Find who was supposed to give Rebecca a gift (Fred).
36
- Tools:
37
- 1. Word document access
38
- Final Answer: Fred
39
-
40
- Question 3: Eva Draconis has a personal website which can be accessed on her YouTube page. What is the meaning of the only symbol seen in the top banner that has a curved line that isn't a circle or a portion of a circle? Answer without punctuation.
41
- Steps:
42
- 1. By googling Eva Draconis youtube, you can find her channel.
43
- 2. In her about section, she has written her website URL, orionmindproject.com.
44
- 3. Entering this website, you can see a series of symbols at the top, and the text "> see what the symbols mean here" below it.
45
- 4. Reading through the entries, you can see a short description of some of the symbols.
46
- 5. The only symbol with a curved line that isn't a circle or a portion of a circle is the last one.
47
- 6. Note that the symbol supposedly means "War is not here, this is a land of peace."
48
- Tools:
49
- 1. A web browser.
50
- 2. A search engine.
51
- 3. Access to YouTube
52
- 4. Image recognition tools
53
- Final Answer: War is not here this is a land of peace
54
-
55
- ==========================
56
- Now, please answer the following question step by step.
 
1
+ You are a helpful assistant tasked with answering questions using a set of tools.
2
+ Now, I will ask you a question. Report your thoughts, and finish your answer with the following template:
3
+ FINAL ANSWER: [YOUR FINAL ANSWER].
4
+ YOUR FINAL ANSWER should be a number OR as few words as possible OR a comma separated list of numbers and/or strings. If you are asked for a number, don't use comma to write your number neither use units such as $ or percent sign unless specified otherwise. If you are asked for a string, don't use articles, neither abbreviations (e.g. for cities), and write the digits in plain text unless specified otherwise. If you are asked for a comma separated list, Apply the rules above for each element (number or string), ensure there is exactly one space after each comma.
5
+ Your answer should only start with "FINAL ANSWER: ", then follows with the answer.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
tools/__pycache__/codetools.cpython-312.pyc ADDED
Binary file (14.4 kB). View file
 
tools/__pycache__/documenttools.cpython-312.pyc ADDED
Binary file (6.01 kB). View file
 
tools/__pycache__/imagetools.cpython-312.pyc ADDED
Binary file (14.5 kB). View file
 
tools/__pycache__/mathtools.cpython-312.pyc CHANGED
Binary files a/tools/__pycache__/mathtools.cpython-312.pyc and b/tools/__pycache__/mathtools.cpython-312.pyc differ
 
tools/__pycache__/searchtools.cpython-312.pyc CHANGED
Binary files a/tools/__pycache__/searchtools.cpython-312.pyc and b/tools/__pycache__/searchtools.cpython-312.pyc differ
 
tools/codetools.py ADDED
@@ -0,0 +1,348 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_core.tools import tool
2
+ import os
3
+ import io
4
+ import sys
5
+ import uuid
6
+ import base64
7
+ import traceback
8
+ import contextlib
9
+ import tempfile
10
+ import subprocess
11
+ import sqlite3
12
+ from typing import Dict, List, Any, Optional, Union
13
+ import numpy as np
14
+ import pandas as pd
15
+ import matplotlib.pyplot as plt
16
+ from PIL import Image
17
+
18
+ class CodeInterpreter:
19
+ def __init__(self, allowed_modules=None, max_execution_time=30, working_directory=None):
20
+ """Initialize the code interpreter with safety measures."""
21
+ self.allowed_modules = allowed_modules or [
22
+ "numpy", "pandas", "matplotlib", "scipy", "sklearn",
23
+ "math", "random", "statistics", "datetime", "collections",
24
+ "itertools", "functools", "operator", "re", "json",
25
+ "sympy", "networkx", "nltk", "PIL", "pytesseract",
26
+ "cmath", "uuid", "tempfile", "requests", "urllib"
27
+ ]
28
+ self.max_execution_time = max_execution_time
29
+ self.working_directory = working_directory or os.path.join(os.getcwd())
30
+ if not os.path.exists(self.working_directory):
31
+ os.makedirs(self.working_directory)
32
+
33
+ self.globals = {
34
+ "__builtins__": __builtins__,
35
+ "np": np,
36
+ "pd": pd,
37
+ "plt": plt,
38
+ "Image": Image,
39
+ }
40
+ self.temp_sqlite_db = os.path.join(tempfile.gettempdir(), "code_exec.db")
41
+
42
+ def execute_code(self, code: str, language: str = "python") -> Dict[str, Any]:
43
+ """Execute the provided code in the selected programming language."""
44
+ language = language.lower()
45
+ execution_id = str(uuid.uuid4())
46
+
47
+ result = {
48
+ "execution_id": execution_id,
49
+ "status": "error",
50
+ "stdout": "",
51
+ "stderr": "",
52
+ "result": None,
53
+ "plots": [],
54
+ "dataframes": []
55
+ }
56
+
57
+ try:
58
+ if language == "python":
59
+ return self._execute_python(code, execution_id)
60
+ elif language == "bash":
61
+ return self._execute_bash(code, execution_id)
62
+ elif language == "sql":
63
+ return self._execute_sql(code, execution_id)
64
+ elif language == "c":
65
+ return self._execute_c(code, execution_id)
66
+ elif language == "java":
67
+ return self._execute_java(code, execution_id)
68
+ else:
69
+ result["stderr"] = f"Unsupported language: {language}"
70
+ except Exception as e:
71
+ result["stderr"] = str(e)
72
+
73
+ return result
74
+
75
+ def _execute_python(self, code: str, execution_id: str) -> dict:
76
+ output_buffer = io.StringIO()
77
+ error_buffer = io.StringIO()
78
+ result = {
79
+ "execution_id": execution_id,
80
+ "status": "error",
81
+ "stdout": "",
82
+ "stderr": "",
83
+ "result": None,
84
+ "plots": [],
85
+ "dataframes": []
86
+ }
87
+
88
+ try:
89
+ exec_dir = os.path.join(self.working_directory, execution_id)
90
+ os.makedirs(exec_dir, exist_ok=True)
91
+ plt.switch_backend('Agg')
92
+
93
+ with contextlib.redirect_stdout(output_buffer), contextlib.redirect_stderr(error_buffer):
94
+ exec_result = exec(code, self.globals)
95
+
96
+ if plt.get_fignums():
97
+ for i, fig_num in enumerate(plt.get_fignums()):
98
+ fig = plt.figure(fig_num)
99
+ img_path = os.path.join(exec_dir, f"plot_{i}.png")
100
+ fig.savefig(img_path)
101
+ with open(img_path, "rb") as img_file:
102
+ img_data = base64.b64encode(img_file.read()).decode('utf-8')
103
+ result["plots"].append({
104
+ "figure_number": fig_num,
105
+ "data": img_data
106
+ })
107
+
108
+ for var_name, var_value in self.globals.items():
109
+ if isinstance(var_value, pd.DataFrame) and len(var_value) > 0:
110
+ result["dataframes"].append({
111
+ "name": var_name,
112
+ "head": var_value.head().to_dict(),
113
+ "shape": var_value.shape,
114
+ "dtypes": str(var_value.dtypes)
115
+ })
116
+
117
+ result["status"] = "success"
118
+ result["stdout"] = output_buffer.getvalue()
119
+ result["result"] = exec_result
120
+
121
+ except Exception as e:
122
+ result["status"] = "error"
123
+ result["stderr"] = f"{error_buffer.getvalue()}\n{traceback.format_exc()}"
124
+
125
+ return result
126
+
127
+ def _execute_bash(self, code: str, execution_id: str) -> dict:
128
+ try:
129
+ completed = subprocess.run(
130
+ code, shell=True, capture_output=True, text=True, timeout=self.max_execution_time
131
+ )
132
+ return {
133
+ "execution_id": execution_id,
134
+ "status": "success" if completed.returncode == 0 else "error",
135
+ "stdout": completed.stdout,
136
+ "stderr": completed.stderr,
137
+ "result": None,
138
+ "plots": [],
139
+ "dataframes": []
140
+ }
141
+ except subprocess.TimeoutExpired:
142
+ return {
143
+ "execution_id": execution_id,
144
+ "status": "error",
145
+ "stdout": "",
146
+ "stderr": "Execution timed out.",
147
+ "result": None,
148
+ "plots": [],
149
+ "dataframes": []
150
+ }
151
+
152
+ def _execute_sql(self, code: str, execution_id: str) -> dict:
153
+ result = {
154
+ "execution_id": execution_id,
155
+ "status": "error",
156
+ "stdout": "",
157
+ "stderr": "",
158
+ "result": None,
159
+ "plots": [],
160
+ "dataframes": []
161
+ }
162
+ try:
163
+ conn = sqlite3.connect(self.temp_sqlite_db)
164
+ cur = conn.cursor()
165
+ cur.execute(code)
166
+ if code.strip().lower().startswith("select"):
167
+ columns = [description[0] for description in cur.description]
168
+ rows = cur.fetchall()
169
+ df = pd.DataFrame(rows, columns=columns)
170
+ result["dataframes"].append({
171
+ "name": "query_result",
172
+ "head": df.head().to_dict(),
173
+ "shape": df.shape,
174
+ "dtypes": str(df.dtypes)
175
+ })
176
+ else:
177
+ conn.commit()
178
+
179
+ result["status"] = "success"
180
+ result["stdout"] = "Query executed successfully."
181
+
182
+ except Exception as e:
183
+ result["stderr"] = str(e)
184
+ finally:
185
+ conn.close()
186
+
187
+ return result
188
+
189
+ def _execute_c(self, code: str, execution_id: str) -> dict:
190
+ temp_dir = tempfile.mkdtemp()
191
+ source_path = os.path.join(temp_dir, "program.c")
192
+ binary_path = os.path.join(temp_dir, "program")
193
+
194
+ try:
195
+ with open(source_path, "w") as f:
196
+ f.write(code)
197
+
198
+ compile_proc = subprocess.run(
199
+ ["gcc", source_path, "-o", binary_path],
200
+ capture_output=True, text=True, timeout=self.max_execution_time
201
+ )
202
+ if compile_proc.returncode != 0:
203
+ return {
204
+ "execution_id": execution_id,
205
+ "status": "error",
206
+ "stdout": compile_proc.stdout,
207
+ "stderr": compile_proc.stderr,
208
+ "result": None,
209
+ "plots": [],
210
+ "dataframes": []
211
+ }
212
+
213
+ run_proc = subprocess.run(
214
+ [binary_path],
215
+ capture_output=True, text=True, timeout=self.max_execution_time
216
+ )
217
+ return {
218
+ "execution_id": execution_id,
219
+ "status": "success" if run_proc.returncode == 0 else "error",
220
+ "stdout": run_proc.stdout,
221
+ "stderr": run_proc.stderr,
222
+ "result": None,
223
+ "plots": [],
224
+ "dataframes": []
225
+ }
226
+ except Exception as e:
227
+ return {
228
+ "execution_id": execution_id,
229
+ "status": "error",
230
+ "stdout": "",
231
+ "stderr": str(e),
232
+ "result": None,
233
+ "plots": [],
234
+ "dataframes": []
235
+ }
236
+
237
+ def _execute_java(self, code: str, execution_id: str) -> dict:
238
+ temp_dir = tempfile.mkdtemp()
239
+ source_path = os.path.join(temp_dir, "Main.java")
240
+
241
+ try:
242
+ with open(source_path, "w") as f:
243
+ f.write(code)
244
+
245
+ compile_proc = subprocess.run(
246
+ ["javac", source_path],
247
+ capture_output=True, text=True, timeout=self.max_execution_time
248
+ )
249
+ if compile_proc.returncode != 0:
250
+ return {
251
+ "execution_id": execution_id,
252
+ "status": "error",
253
+ "stdout": compile_proc.stdout,
254
+ "stderr": compile_proc.stderr,
255
+ "result": None,
256
+ "plots": [],
257
+ "dataframes": []
258
+ }
259
+
260
+ run_proc = subprocess.run(
261
+ ["java", "-cp", temp_dir, "Main"],
262
+ capture_output=True, text=True, timeout=self.max_execution_time
263
+ )
264
+ return {
265
+ "execution_id": execution_id,
266
+ "status": "success" if run_proc.returncode == 0 else "error",
267
+ "stdout": run_proc.stdout,
268
+ "stderr": run_proc.stderr,
269
+ "result": None,
270
+ "plots": [],
271
+ "dataframes": []
272
+ }
273
+ except Exception as e:
274
+ return {
275
+ "execution_id": execution_id,
276
+ "status": "error",
277
+ "stdout": "",
278
+ "stderr": str(e),
279
+ "result": None,
280
+ "plots": [],
281
+ "dataframes": []
282
+ }
283
+
284
+
285
+ interpreter_instance = CodeInterpreter()
286
+
287
+ @tool
288
+ def execute_code_multilang(code: str, language: str = "python") -> str:
289
+ """Execute code in multiple languages (Python, Bash, SQL, C, Java) and return results.
290
+ Args:
291
+ code (str): The source code to execute.
292
+ language (str): The language of the code. Supported: "python", "bash", "sql", "c", "java".
293
+ Returns:
294
+ A string summarizing the execution results (stdout, stderr, errors, plots, dataframes if any).
295
+ """
296
+ supported_languages = ["python", "bash", "sql", "c", "java"]
297
+ language = language.lower()
298
+
299
+ if language not in supported_languages:
300
+ return f"❌ Unsupported language: {language}. Supported languages are: {', '.join(supported_languages)}"
301
+
302
+ result = interpreter_instance.execute_code(code, language=language)
303
+
304
+ response = []
305
+
306
+ if result["status"] == "success":
307
+ response.append(f"✅ Code executed successfully in **{language.upper()}**")
308
+
309
+ if result.get("stdout"):
310
+ response.append(
311
+ "\n**Standard Output:**\n```\n" + result["stdout"].strip() + "\n```"
312
+ )
313
+
314
+ if result.get("stderr"):
315
+ response.append(
316
+ "\n**Standard Error (if any):**\n```\n"
317
+ + result["stderr"].strip()
318
+ + "\n```"
319
+ )
320
+
321
+ if result.get("result") is not None:
322
+ response.append(
323
+ "\n**Execution Result:**\n```\n"
324
+ + str(result["result"]).strip()
325
+ + "\n```"
326
+ )
327
+
328
+ if result.get("dataframes"):
329
+ for df_info in result["dataframes"]:
330
+ response.append(
331
+ f"\n**DataFrame `{df_info['name']}` (Shape: {df_info['shape']})**"
332
+ )
333
+ df_preview = pd.DataFrame(df_info["head"])
334
+ response.append("First 5 rows:\n```\n" + str(df_preview) + "\n```")
335
+
336
+ if result.get("plots"):
337
+ response.append(
338
+ f"\n**Generated {len(result['plots'])} plot(s)** (Image data returned separately)"
339
+ )
340
+
341
+ else:
342
+ response.append(f"❌ Code execution failed in **{language.upper()}**")
343
+ if result.get("stderr"):
344
+ response.append(
345
+ "\n**Error Log:**\n```\n" + result["stderr"].strip() + "\n```"
346
+ )
347
+
348
+ return "\n".join(response)
tools/documenttools.py ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_core.tools import tool
2
+ from typing import List, Dict, Any, Optional
3
+ import tempfile
4
+ from urllib.parse import urlparse
5
+ import os
6
+ import uuid
7
+ import requests
8
+ from PIL import Image
9
+ import pytesseract
10
+ import pandas as pd
11
+
12
+ @tool
13
+ def save_and_read_file(content: str, filename: Optional[str] = None) -> str:
14
+ """
15
+ Save content to a file and return the path.
16
+ Args:
17
+ content (str): the content to save to the file
18
+ filename (str, optional): the name of the file. If not provided, a random name file will be created.
19
+ """
20
+ temp_dir = tempfile.gettempdir()
21
+ if filename is None:
22
+ temp_file = tempfile.NamedTemporaryFile(delete=False, dir=temp_dir)
23
+ filepath = temp_file.name
24
+ else:
25
+ filepath = os.path.join(temp_dir, filename)
26
+
27
+ with open(filepath, "w") as f:
28
+ f.write(content)
29
+
30
+ return f"File saved to {filepath}. You can read this file to process its contents."
31
+
32
+
33
+ @tool
34
+ def download_file_from_url(url: str, filename: Optional[str] = None) -> str:
35
+ """
36
+ Download a file from a URL and save it to a temporary location.
37
+ Args:
38
+ url (str): the URL of the file to download.
39
+ filename (str, optional): the name of the file. If not provided, a random name file will be created.
40
+ """
41
+ try:
42
+ # Parse URL to get filename if not provided
43
+ if not filename:
44
+ path = urlparse(url).path
45
+ filename = os.path.basename(path)
46
+ if not filename:
47
+ filename = f"downloaded_{uuid.uuid4().hex[:8]}"
48
+
49
+ # Create temporary file
50
+ temp_dir = tempfile.gettempdir()
51
+ filepath = os.path.join(temp_dir, filename)
52
+
53
+ # Download the file
54
+ response = requests.get(url, stream=True)
55
+ response.raise_for_status()
56
+
57
+ # Save the file
58
+ with open(filepath, "wb") as f:
59
+ for chunk in response.iter_content(chunk_size=8192):
60
+ f.write(chunk)
61
+
62
+ return f"File downloaded to {filepath}. You can read this file to process its contents."
63
+ except Exception as e:
64
+ return f"Error downloading file: {str(e)}"
65
+
66
+
67
+ @tool
68
+ def extract_text_from_image(image_path: str) -> str:
69
+ """
70
+ Extract text from an image using OCR library pytesseract (if available).
71
+ Args:
72
+ image_path (str): the path to the image file.
73
+ """
74
+ try:
75
+ # Open the image
76
+ image = Image.open(image_path)
77
+
78
+ # Extract text from the image
79
+ text = pytesseract.image_to_string(image)
80
+
81
+ return f"Extracted text from image:\n\n{text}"
82
+ except Exception as e:
83
+ return f"Error extracting text from image: {str(e)}"
84
+
85
+
86
+ @tool
87
+ def analyze_csv_file(file_path: str, query: str) -> str:
88
+ """
89
+ Analyze a CSV file using pandas and answer a question about it.
90
+ Args:
91
+ file_path (str): the path to the CSV file.
92
+ query (str): Question about the data
93
+ """
94
+ try:
95
+ # Read the CSV file
96
+ df = pd.read_csv(file_path)
97
+
98
+ # Run various analyses based on the query
99
+ result = f"CSV file loaded with {len(df)} rows and {len(df.columns)} columns.\n"
100
+ result += f"Columns: {', '.join(df.columns)}\n\n"
101
+
102
+ # Add summary statistics
103
+ result += "Summary statistics:\n"
104
+ result += str(df.describe())
105
+
106
+ return result
107
+
108
+ except Exception as e:
109
+ return f"Error analyzing CSV file: {str(e)}"
110
+
111
+
112
+ @tool
113
+ def analyze_excel_file(file_path: str, query: str) -> str:
114
+ """
115
+ Analyze an Excel file using pandas and answer a question about it.
116
+ Args:
117
+ file_path (str): the path to the Excel file.
118
+ query (str): Question about the data
119
+ """
120
+ try:
121
+ # Read the Excel file
122
+ df = pd.read_excel(file_path)
123
+
124
+ # Run various analyses based on the query
125
+ result = (
126
+ f"Excel file loaded with {len(df)} rows and {len(df.columns)} columns.\n"
127
+ )
128
+ result += f"Columns: {', '.join(df.columns)}\n\n"
129
+
130
+ # Add summary statistics
131
+ result += "Summary statistics:\n"
132
+ result += str(df.describe())
133
+
134
+ return result
135
+
136
+ except Exception as e:
137
+ return f"Error analyzing Excel file: {str(e)}"
tools/imagetools.py ADDED
@@ -0,0 +1,311 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_core.tools import tool
2
+ import os
3
+ import io
4
+ import base64
5
+ import uuid
6
+ from PIL import Image
7
+ from typing import List, Dict, Any, Optional
8
+ import numpy as np
9
+ from PIL import Image, ImageDraw, ImageFont, ImageEnhance, ImageFilter
10
+
11
+ # Helper functions for image processing
12
+ def encode_image(image_path: str) -> str:
13
+ """Convert an image file to base64 string."""
14
+ with open(image_path, "rb") as image_file:
15
+ return base64.b64encode(image_file.read()).decode("utf-8")
16
+
17
+
18
+ def decode_image(base64_string: str) -> Image.Image:
19
+ """Convert a base64 string to a PIL Image."""
20
+ image_data = base64.b64decode(base64_string)
21
+ return Image.open(io.BytesIO(image_data))
22
+
23
+
24
+ def save_image(image: Image.Image, directory: str = "image_outputs") -> str:
25
+ """Save a PIL Image to disk and return the path."""
26
+ os.makedirs(directory, exist_ok=True)
27
+ image_id = str(uuid.uuid4())
28
+ image_path = os.path.join(directory, f"{image_id}.png")
29
+ image.save(image_path)
30
+ return image_path
31
+
32
+
33
+ @tool
34
+ def analyze_image(image_base64: str) -> Dict[str, Any]:
35
+ """
36
+ Analyze basic properties of an image (size, mode, color analysis, thumbnail preview).
37
+ Args:
38
+ image_base64 (str): Base64 encoded image string
39
+ Returns:
40
+ Dictionary with analysis result
41
+ """
42
+ try:
43
+ img = decode_image(image_base64)
44
+ width, height = img.size
45
+ mode = img.mode
46
+
47
+ if mode in ("RGB", "RGBA"):
48
+ arr = np.array(img)
49
+ avg_colors = arr.mean(axis=(0, 1))
50
+ dominant = ["Red", "Green", "Blue"][np.argmax(avg_colors[:3])]
51
+ brightness = avg_colors.mean()
52
+ color_analysis = {
53
+ "average_rgb": avg_colors.tolist(),
54
+ "brightness": brightness,
55
+ "dominant_color": dominant,
56
+ }
57
+ else:
58
+ color_analysis = {"note": f"No color analysis for mode {mode}"}
59
+
60
+ thumbnail = img.copy()
61
+ thumbnail.thumbnail((100, 100))
62
+ thumb_path = save_image(thumbnail, "thumbnails")
63
+ thumbnail_base64 = encode_image(thumb_path)
64
+
65
+ return {
66
+ "dimensions": (width, height),
67
+ "mode": mode,
68
+ "color_analysis": color_analysis,
69
+ "thumbnail": thumbnail_base64,
70
+ }
71
+ except Exception as e:
72
+ return {"error": str(e)}
73
+
74
+
75
+ @tool
76
+ def transform_image(
77
+ image_base64: str, operation: str, params: Optional[Dict[str, Any]] = None
78
+ ) -> Dict[str, Any]:
79
+ """
80
+ Apply transformations: resize, rotate, crop, flip, brightness, contrast, blur, sharpen, grayscale.
81
+ Args:
82
+ image_base64 (str): Base64 encoded input image
83
+ operation (str): Transformation operation
84
+ params (Dict[str, Any], optional): Parameters for the operation
85
+ Returns:
86
+ Dictionary with transformed image (base64)
87
+ """
88
+ try:
89
+ img = decode_image(image_base64)
90
+ params = params or {}
91
+
92
+ if operation == "resize":
93
+ img = img.resize(
94
+ (
95
+ params.get("width", img.width // 2),
96
+ params.get("height", img.height // 2),
97
+ )
98
+ )
99
+ elif operation == "rotate":
100
+ img = img.rotate(params.get("angle", 90), expand=True)
101
+ elif operation == "crop":
102
+ img = img.crop(
103
+ (
104
+ params.get("left", 0),
105
+ params.get("top", 0),
106
+ params.get("right", img.width),
107
+ params.get("bottom", img.height),
108
+ )
109
+ )
110
+ elif operation == "flip":
111
+ if params.get("direction", "horizontal") == "horizontal":
112
+ img = img.transpose(Image.FLIP_LEFT_RIGHT)
113
+ else:
114
+ img = img.transpose(Image.FLIP_TOP_BOTTOM)
115
+ elif operation == "adjust_brightness":
116
+ img = ImageEnhance.Brightness(img).enhance(params.get("factor", 1.5))
117
+ elif operation == "adjust_contrast":
118
+ img = ImageEnhance.Contrast(img).enhance(params.get("factor", 1.5))
119
+ elif operation == "blur":
120
+ img = img.filter(ImageFilter.GaussianBlur(params.get("radius", 2)))
121
+ elif operation == "sharpen":
122
+ img = img.filter(ImageFilter.SHARPEN)
123
+ elif operation == "grayscale":
124
+ img = img.convert("L")
125
+ else:
126
+ return {"error": f"Unknown operation: {operation}"}
127
+
128
+ result_path = save_image(img)
129
+ result_base64 = encode_image(result_path)
130
+ return {"transformed_image": result_base64}
131
+
132
+ except Exception as e:
133
+ return {"error": str(e)}
134
+
135
+
136
+ @tool
137
+ def draw_on_image(
138
+ image_base64: str, drawing_type: str, params: Dict[str, Any]
139
+ ) -> Dict[str, Any]:
140
+ """
141
+ Draw shapes (rectangle, circle, line) or text onto an image.
142
+ Args:
143
+ image_base64 (str): Base64 encoded input image
144
+ drawing_type (str): Drawing type
145
+ params (Dict[str, Any]): Drawing parameters
146
+ Returns:
147
+ Dictionary with result image (base64)
148
+ """
149
+ try:
150
+ img = decode_image(image_base64)
151
+ draw = ImageDraw.Draw(img)
152
+ color = params.get("color", "red")
153
+
154
+ if drawing_type == "rectangle":
155
+ draw.rectangle(
156
+ [params["left"], params["top"], params["right"], params["bottom"]],
157
+ outline=color,
158
+ width=params.get("width", 2),
159
+ )
160
+ elif drawing_type == "circle":
161
+ x, y, r = params["x"], params["y"], params["radius"]
162
+ draw.ellipse(
163
+ (x - r, y - r, x + r, y + r),
164
+ outline=color,
165
+ width=params.get("width", 2),
166
+ )
167
+ elif drawing_type == "line":
168
+ draw.line(
169
+ (
170
+ params["start_x"],
171
+ params["start_y"],
172
+ params["end_x"],
173
+ params["end_y"],
174
+ ),
175
+ fill=color,
176
+ width=params.get("width", 2),
177
+ )
178
+ elif drawing_type == "text":
179
+ font_size = params.get("font_size", 20)
180
+ try:
181
+ font = ImageFont.truetype("arial.ttf", font_size)
182
+ except IOError:
183
+ font = ImageFont.load_default()
184
+ draw.text(
185
+ (params["x"], params["y"]),
186
+ params.get("text", "Text"),
187
+ fill=color,
188
+ font=font,
189
+ )
190
+ else:
191
+ return {"error": f"Unknown drawing type: {drawing_type}"}
192
+
193
+ result_path = save_image(img)
194
+ result_base64 = encode_image(result_path)
195
+ return {"result_image": result_base64}
196
+
197
+ except Exception as e:
198
+ return {"error": str(e)}
199
+
200
+
201
+ @tool
202
+ def generate_simple_image(
203
+ image_type: str,
204
+ width: int = 500,
205
+ height: int = 500,
206
+ params: Optional[Dict[str, Any]] = None,
207
+ ) -> Dict[str, Any]:
208
+ """
209
+ Generate a simple image (gradient, noise, pattern, chart).
210
+ Args:
211
+ image_type (str): Type of image
212
+ width (int), height (int)
213
+ params (Dict[str, Any], optional): Specific parameters
214
+ Returns:
215
+ Dictionary with generated image (base64)
216
+ """
217
+ try:
218
+ params = params or {}
219
+
220
+ if image_type == "gradient":
221
+ direction = params.get("direction", "horizontal")
222
+ start_color = params.get("start_color", (255, 0, 0))
223
+ end_color = params.get("end_color", (0, 0, 255))
224
+
225
+ img = Image.new("RGB", (width, height))
226
+ draw = ImageDraw.Draw(img)
227
+
228
+ if direction == "horizontal":
229
+ for x in range(width):
230
+ r = int(
231
+ start_color[0] + (end_color[0] - start_color[0]) * x / width
232
+ )
233
+ g = int(
234
+ start_color[1] + (end_color[1] - start_color[1]) * x / width
235
+ )
236
+ b = int(
237
+ start_color[2] + (end_color[2] - start_color[2]) * x / width
238
+ )
239
+ draw.line([(x, 0), (x, height)], fill=(r, g, b))
240
+ else:
241
+ for y in range(height):
242
+ r = int(
243
+ start_color[0] + (end_color[0] - start_color[0]) * y / height
244
+ )
245
+ g = int(
246
+ start_color[1] + (end_color[1] - start_color[1]) * y / height
247
+ )
248
+ b = int(
249
+ start_color[2] + (end_color[2] - start_color[2]) * y / height
250
+ )
251
+ draw.line([(0, y), (width, y)], fill=(r, g, b))
252
+
253
+ elif image_type == "noise":
254
+ noise_array = np.random.randint(0, 256, (height, width, 3), dtype=np.uint8)
255
+ img = Image.fromarray(noise_array, "RGB")
256
+
257
+ else:
258
+ return {"error": f"Unsupported image_type {image_type}"}
259
+
260
+ result_path = save_image(img)
261
+ result_base64 = encode_image(result_path)
262
+ return {"generated_image": result_base64}
263
+
264
+ except Exception as e:
265
+ return {"error": str(e)}
266
+
267
+
268
+ @tool
269
+ def combine_images(
270
+ images_base64: List[str], operation: str, params: Optional[Dict[str, Any]] = None
271
+ ) -> Dict[str, Any]:
272
+ """
273
+ Combine multiple images (collage, stack, blend).
274
+ Args:
275
+ images_base64 (List[str]): List of base64 images
276
+ operation (str): Combination type
277
+ params (Dict[str, Any], optional)
278
+ Returns:
279
+ Dictionary with combined image (base64)
280
+ """
281
+ try:
282
+ images = [decode_image(b64) for b64 in images_base64]
283
+ params = params or {}
284
+
285
+ if operation == "stack":
286
+ direction = params.get("direction", "horizontal")
287
+ if direction == "horizontal":
288
+ total_width = sum(img.width for img in images)
289
+ max_height = max(img.height for img in images)
290
+ new_img = Image.new("RGB", (total_width, max_height))
291
+ x = 0
292
+ for img in images:
293
+ new_img.paste(img, (x, 0))
294
+ x += img.width
295
+ else:
296
+ max_width = max(img.width for img in images)
297
+ total_height = sum(img.height for img in images)
298
+ new_img = Image.new("RGB", (max_width, total_height))
299
+ y = 0
300
+ for img in images:
301
+ new_img.paste(img, (0, y))
302
+ y += img.height
303
+ else:
304
+ return {"error": f"Unsupported combination operation {operation}"}
305
+
306
+ result_path = save_image(new_img)
307
+ result_base64 = encode_image(result_path)
308
+ return {"combined_image": result_base64}
309
+
310
+ except Exception as e:
311
+ return {"error": str(e)}
tools/mathtools.py CHANGED
@@ -2,53 +2,80 @@ from langchain_core.tools import tool
2
 
3
 
4
  @tool
5
- def multiply(a: int, b: int) -> int:
6
- """Multiply two numbers.
7
-
8
  Args:
9
- a: first int
10
- b: second int
11
  """
12
  return a * b
13
 
 
14
  @tool
15
- def add(a: int, b: int) -> int:
16
- """Add two numbers.
17
-
18
  Args:
19
- a: first int
20
- b: second int
21
  """
22
  return a + b
23
 
 
24
  @tool
25
- def subtract(a: int, b: int) -> int:
26
- """Subtract two numbers.
27
-
28
  Args:
29
- a: first int
30
- b: second int
31
  """
32
  return a - b
33
 
 
34
  @tool
35
- def divide(a: int, b: int) -> int:
36
- """Divide two numbers.
37
-
38
  Args:
39
- a: first int
40
- b: second int
41
  """
42
  if b == 0:
43
- raise ValueError("Cannot divide by zero.")
44
  return a / b
45
 
 
46
  @tool
47
  def modulus(a: int, b: int) -> int:
48
- """Get the modulus of two numbers.
49
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  Args:
51
- a: first int
52
- b: second int
53
  """
54
- return a % b
 
 
 
2
 
3
 
4
  @tool
5
+ def multiply(a: float, b: float) -> float:
6
+ """
7
+ Multiplies two numbers.
8
  Args:
9
+ a (float): the first number
10
+ b (float): the second number
11
  """
12
  return a * b
13
 
14
+
15
  @tool
16
+ def add(a: float, b: float) -> float:
17
+ """
18
+ Adds two numbers.
19
  Args:
20
+ a (float): the first number
21
+ b (float): the second number
22
  """
23
  return a + b
24
 
25
+
26
  @tool
27
+ def subtract(a: float, b: float) -> int:
28
+ """
29
+ Subtracts two numbers.
30
  Args:
31
+ a (float): the first number
32
+ b (float): the second number
33
  """
34
  return a - b
35
 
36
+
37
  @tool
38
+ def divide(a: float, b: float) -> float:
39
+ """
40
+ Divides two numbers.
41
  Args:
42
+ a (float): the first float number
43
+ b (float): the second float number
44
  """
45
  if b == 0:
46
+ raise ValueError("Cannot divided by zero.")
47
  return a / b
48
 
49
+
50
  @tool
51
  def modulus(a: int, b: int) -> int:
52
+ """
53
+ Get the modulus of two numbers.
54
+ Args:
55
+ a (int): the first number
56
+ b (int): the second number
57
+ """
58
+ return a % b
59
+
60
+
61
+ @tool
62
+ def power(a: float, b: float) -> float:
63
+ """
64
+ Get the power of two numbers.
65
+ Args:
66
+ a (float): the first number
67
+ b (float): the second number
68
+ """
69
+ return a**b
70
+
71
+
72
+ @tool
73
+ def square_root(a: float) -> float | complex:
74
+ """
75
+ Get the square root of a number.
76
  Args:
77
+ a (float): the number to get the square root of
 
78
  """
79
+ if a >= 0:
80
+ return a**0.5
81
+ return cmath.sqrt(a)
tools/searchtools.py CHANGED
@@ -27,44 +27,51 @@ question_retrieve_tool = create_retriever_tool(
27
  )
28
 
29
 
 
30
  @tool
31
- def wiki_search(input: str) -> str:
32
  """Search Wikipedia for a query and return maximum 2 results.
33
-
34
  Args:
35
- input: The search query."""
36
- search_docs = WikipediaLoader(query=input, load_max_docs=2).load()
37
  formatted_search_docs = "\n\n---\n\n".join(
38
  [
39
  f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>'
40
  for doc in search_docs
41
- ])
 
42
  return {"wiki_results": formatted_search_docs}
43
 
 
44
  @tool
45
- def web_search(input: str) -> str:
46
  """Search Tavily for a query and return maximum 3 results.
47
-
48
  Args:
49
- input: The search query."""
50
-
51
- search_tool = TavilySearchResults(tavily_api_key=os.getenv("TAVILY_API_KEY"), max_results=3)
52
- results = search_tool.run(input) # ✅ Use .run() instead of .invoke()
53
- return {"web_results": results}
 
 
 
 
 
54
 
55
  @tool
56
- def arvix_search(input: str) -> str:
57
  """Search Arxiv for a query and return maximum 3 result.
58
-
59
  Args:
60
- input: The search query."""
61
- search_docs = ArxivLoader(query=input, load_max_docs=3).load()
62
  formatted_search_docs = "\n\n---\n\n".join(
63
  [
64
  f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content[:1000]}\n</Document>'
65
  for doc in search_docs
66
- ])
67
- return {"arvix_results": formatted_search_docs}
 
 
68
 
69
  @tool
70
  def similar_question_search(question: str) -> str:
 
27
  )
28
 
29
 
30
+
31
  @tool
32
+ def wiki_search(query: str) -> str:
33
  """Search Wikipedia for a query and return maximum 2 results.
 
34
  Args:
35
+ query: The search query."""
36
+ search_docs = WikipediaLoader(query=query, load_max_docs=2).load()
37
  formatted_search_docs = "\n\n---\n\n".join(
38
  [
39
  f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>'
40
  for doc in search_docs
41
+ ]
42
+ )
43
  return {"wiki_results": formatted_search_docs}
44
 
45
+
46
  @tool
47
+ def web_search(query: str) -> str:
48
  """Search Tavily for a query and return maximum 3 results.
 
49
  Args:
50
+ query: The search query."""
51
+ search_docs = TavilySearchResults(max_results=3).invoke(query=query)
52
+ formatted_search_docs = "\n\n---\n\n".join(
53
+ [
54
+ f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>'
55
+ for doc in search_docs
56
+ ]
57
+ )
58
+ return {"web_results": formatted_search_docs}
59
+
60
 
61
  @tool
62
+ def arxiv_search(query: str) -> str:
63
  """Search Arxiv for a query and return maximum 3 result.
 
64
  Args:
65
+ query: The search query."""
66
+ search_docs = ArxivLoader(query=query, load_max_docs=3).load()
67
  formatted_search_docs = "\n\n---\n\n".join(
68
  [
69
  f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content[:1000]}\n</Document>'
70
  for doc in search_docs
71
+ ]
72
+ )
73
+ return {"arxiv_results": formatted_search_docs}
74
+
75
 
76
  @tool
77
  def similar_question_search(question: str) -> str: