ValentinGuigon commited on
Commit
21f89f6
·
1 Parent(s): 651bf23

"First implementation"

Browse files
README.md CHANGED
@@ -12,4 +12,14 @@ hf_oauth: true
12
  hf_oauth_expiration_minutes: 480
13
  ---
14
 
15
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
12
  hf_oauth_expiration_minutes: 480
13
  ---
14
 
15
+ # GAIA Benchmark Agent (Level 1)
16
+
17
+ This project implements a multimodal agent for the GAIA benchmark challenge (Level 1), using the [`smolagent`](https://github.com/smol-ai/smolagent) framework.
18
+
19
+ ## Setup
20
+
21
+ ```bash
22
+ pip install -r requirements.txt
23
+ ```
24
+
25
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
agents/__init__.py ADDED
File without changes
agents/agent.py ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """LangGraph Agent for GAIA Benchmark"""
2
+ import os
3
+ from dotenv import load_dotenv
4
+
5
+ from langgraph.graph import START, StateGraph, MessagesState
6
+ from langgraph.prebuilt import tools_condition, ToolNode
7
+ from langchain_core.messages import SystemMessage, HumanMessage
8
+ from langchain_groq import ChatGroq
9
+ from tools.CodeToolkit import execute_code_multilang
10
+ from tools.DocumentsToolkit import (
11
+ save_and_read_file,
12
+ download_file_from_url,
13
+ extract_text_from_image,
14
+ analyze_csv_file,
15
+ analyze_excel_file,
16
+ analyze_word_file,
17
+ analyze_pdf_file
18
+ )
19
+ from tools.ImagesToolkit import (
20
+ analyze_image,
21
+ transform_image,
22
+ draw_on_image,
23
+ generate_simple_image,
24
+ combine_images
25
+ )
26
+ from tools.MathsToolkit import (
27
+ multiply, add, subtract, divide, modulus, power, square_root
28
+ )
29
+ from tools.SearchToolkit import wiki_search, web_search, arxiv_search, vector_store
30
+
31
+ # Load environment variables
32
+ load_dotenv()
33
+
34
+ # Load system prompt
35
+ with open("system_prompt.txt", "r", encoding="utf-8") as f:
36
+ system_prompt = f.read()
37
+ sys_msg = SystemMessage(content=system_prompt)
38
+
39
+ # Toolset
40
+ tools = [
41
+ # SearchToolkit
42
+ web_search,
43
+ wiki_search,
44
+ arxiv_search,
45
+
46
+ # MathsToolkit
47
+ multiply,
48
+ add,
49
+ subtract,
50
+ divide,
51
+ modulus,
52
+ power,
53
+ square_root,
54
+
55
+ # DocumentsToolkit
56
+ save_and_read_file,
57
+ download_file_from_url,
58
+ extract_text_from_image,
59
+ analyze_csv_file,
60
+ analyze_excel_file,
61
+ analyze_word_file,
62
+ analyze_pdf_file,
63
+
64
+ # CodeToolkit
65
+ execute_code_multilang,
66
+
67
+ # ImagesToolkit
68
+ analyze_image,
69
+ transform_image,
70
+ draw_on_image,
71
+ generate_simple_image,
72
+ combine_images,
73
+ ]
74
+
75
+ # Build LangGraph workflow
76
+
77
+
78
+ def build_graph():
79
+ llm = ChatGroq(model="qwen-qwq-32b", temperature=0)
80
+ llm_with_tools = llm.bind_tools(tools)
81
+
82
+ def assistant(state: MessagesState):
83
+ return {"messages": [llm_with_tools.invoke(state["messages"])]}
84
+
85
+ def retriever(state: MessagesState):
86
+ similar = vector_store.similarity_search(state["messages"][0].content)
87
+ if similar:
88
+ reference = HumanMessage(
89
+ content=f"Here is a similar Q&A that might help: \n\n{similar[0].page_content}"
90
+ )
91
+ return {"messages": [sys_msg] + state["messages"] + [reference]}
92
+ else:
93
+ return {"messages": [sys_msg] + state["messages"]}
94
+
95
+ builder = StateGraph(MessagesState)
96
+ builder.add_node("retriever", retriever)
97
+ builder.add_node("assistant", assistant)
98
+ builder.add_node("tools", ToolNode(tools))
99
+ builder.add_edge(START, "retriever")
100
+ builder.add_edge("retriever", "assistant")
101
+ builder.add_conditional_edges("assistant", tools_condition)
102
+ builder.add_edge("tools", "assistant")
103
+
104
+ return builder.compile()
app.py CHANGED
@@ -10,25 +10,36 @@ DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
10
 
11
  # --- Basic Agent Definition ---
12
  # ----- THIS IS WERE YOU CAN BUILD WHAT YOU WANT ------
 
 
13
  class BasicAgent:
 
 
14
  def __init__(self):
15
  print("BasicAgent initialized.")
 
 
16
  def __call__(self, question: str) -> str:
17
  print(f"Agent received question (first 50 chars): {question[:50]}...")
18
- fixed_answer = "This is a default answer."
19
- print(f"Agent returning fixed answer: {fixed_answer}")
20
- return fixed_answer
 
 
 
 
21
 
22
- def run_and_submit_all( profile: gr.OAuthProfile | None):
23
  """
24
  Fetches all questions, runs the BasicAgent on them, submits all answers,
25
  and displays the results.
26
  """
27
  # --- Determine HF Space Runtime URL and Repo URL ---
28
- space_id = os.getenv("SPACE_ID") # Get the SPACE_ID for sending link to the code
 
29
 
30
  if profile:
31
- username= f"{profile.username}"
32
  print(f"User logged in: {username}")
33
  else:
34
  print("User not logged in.")
@@ -55,16 +66,16 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
55
  response.raise_for_status()
56
  questions_data = response.json()
57
  if not questions_data:
58
- print("Fetched questions list is empty.")
59
- return "Fetched questions list is empty or invalid format.", None
60
  print(f"Fetched {len(questions_data)} questions.")
61
  except requests.exceptions.RequestException as e:
62
  print(f"Error fetching questions: {e}")
63
  return f"Error fetching questions: {e}", None
64
  except requests.exceptions.JSONDecodeError as e:
65
- print(f"Error decoding JSON response from questions endpoint: {e}")
66
- print(f"Response text: {response.text[:500]}")
67
- return f"Error decoding server response for questions: {e}", None
68
  except Exception as e:
69
  print(f"An unexpected error occurred fetching questions: {e}")
70
  return f"An unexpected error occurred fetching questions: {e}", None
@@ -81,18 +92,22 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
81
  continue
82
  try:
83
  submitted_answer = agent(question_text)
84
- answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
85
- results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
 
 
86
  except Exception as e:
87
- print(f"Error running agent on task {task_id}: {e}")
88
- results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": f"AGENT ERROR: {e}"})
 
89
 
90
  if not answers_payload:
91
  print("Agent did not produce any answers to submit.")
92
  return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
93
 
94
- # 4. Prepare Submission
95
- submission_data = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload}
 
96
  status_update = f"Agent finished. Submitting {len(answers_payload)} answers for user '{username}'..."
97
  print(status_update)
98
 
@@ -162,9 +177,11 @@ with gr.Blocks() as demo:
162
 
163
  run_button = gr.Button("Run Evaluation & Submit All Answers")
164
 
165
- status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False)
 
166
  # Removed max_rows=10 from DataFrame constructor
167
- results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
 
168
 
169
  run_button.click(
170
  fn=run_and_submit_all,
@@ -175,22 +192,24 @@ if __name__ == "__main__":
175
  print("\n" + "-"*30 + " App Starting " + "-"*30)
176
  # Check for SPACE_HOST and SPACE_ID at startup for information
177
  space_host_startup = os.getenv("SPACE_HOST")
178
- space_id_startup = os.getenv("SPACE_ID") # Get SPACE_ID at startup
179
 
180
  if space_host_startup:
181
  print(f"✅ SPACE_HOST found: {space_host_startup}")
182
- print(f" Runtime URL should be: https://{space_host_startup}.hf.space")
 
183
  else:
184
  print("ℹ️ SPACE_HOST environment variable not found (running locally?).")
185
 
186
- if space_id_startup: # Print repo URLs if SPACE_ID is found
187
  print(f"✅ SPACE_ID found: {space_id_startup}")
188
  print(f" Repo URL: https://huggingface.co/spaces/{space_id_startup}")
189
- print(f" Repo Tree URL: https://huggingface.co/spaces/{space_id_startup}/tree/main")
 
190
  else:
191
  print("ℹ️ SPACE_ID environment variable not found (running locally?). Repo URL cannot be determined.")
192
 
193
  print("-"*(60 + len(" App Starting ")) + "\n")
194
 
195
  print("Launching Gradio Interface for Basic Agent Evaluation...")
196
- demo.launch(debug=True, share=False)
 
10
 
11
  # --- Basic Agent Definition ---
12
  # ----- THIS IS WERE YOU CAN BUILD WHAT YOU WANT ------
13
+
14
+
15
  class BasicAgent:
16
+ """A langgraph agent."""
17
+
18
  def __init__(self):
19
  print("BasicAgent initialized.")
20
+ self.graph = build_graph() # build_graph defines a LangGraph workflow graph
21
+
22
  def __call__(self, question: str) -> str:
23
  print(f"Agent received question (first 50 chars): {question[:50]}...")
24
+ # wrap input in LangChain message format
25
+ messages = [HumanMessage(content=question)]
26
+ messages = self.graph.invoke(
27
+ {"messages": messages}) # Run the graph workflow
28
+ answer = messages['messages'][-1].content
29
+ return answer[14:] # Possibly trimming a prefix like "Answer: "
30
+
31
 
32
+ def run_and_submit_all(profile: gr.OAuthProfile | None):
33
  """
34
  Fetches all questions, runs the BasicAgent on them, submits all answers,
35
  and displays the results.
36
  """
37
  # --- Determine HF Space Runtime URL and Repo URL ---
38
+ # Get the SPACE_ID for sending link to the code
39
+ space_id = os.getenv("SPACE_ID")
40
 
41
  if profile:
42
+ username = f"{profile.username}"
43
  print(f"User logged in: {username}")
44
  else:
45
  print("User not logged in.")
 
66
  response.raise_for_status()
67
  questions_data = response.json()
68
  if not questions_data:
69
+ print("Fetched questions list is empty.")
70
+ return "Fetched questions list is empty or invalid format.", None
71
  print(f"Fetched {len(questions_data)} questions.")
72
  except requests.exceptions.RequestException as e:
73
  print(f"Error fetching questions: {e}")
74
  return f"Error fetching questions: {e}", None
75
  except requests.exceptions.JSONDecodeError as e:
76
+ print(f"Error decoding JSON response from questions endpoint: {e}")
77
+ print(f"Response text: {response.text[:500]}")
78
+ return f"Error decoding server response for questions: {e}", None
79
  except Exception as e:
80
  print(f"An unexpected error occurred fetching questions: {e}")
81
  return f"An unexpected error occurred fetching questions: {e}", None
 
92
  continue
93
  try:
94
  submitted_answer = agent(question_text)
95
+ answers_payload.append(
96
+ {"task_id": task_id, "submitted_answer": submitted_answer})
97
+ results_log.append(
98
+ {"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
99
  except Exception as e:
100
+ print(f"Error running agent on task {task_id}: {e}")
101
+ results_log.append(
102
+ {"Task ID": task_id, "Question": question_text, "Submitted Answer": f"AGENT ERROR: {e}"})
103
 
104
  if not answers_payload:
105
  print("Agent did not produce any answers to submit.")
106
  return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
107
 
108
+ # 4. Prepare Submission
109
+ submission_data = {"username": username.strip(
110
+ ), "agent_code": agent_code, "answers": answers_payload}
111
  status_update = f"Agent finished. Submitting {len(answers_payload)} answers for user '{username}'..."
112
  print(status_update)
113
 
 
177
 
178
  run_button = gr.Button("Run Evaluation & Submit All Answers")
179
 
180
+ status_output = gr.Textbox(
181
+ label="Run Status / Submission Result", lines=5, interactive=False)
182
  # Removed max_rows=10 from DataFrame constructor
183
+ results_table = gr.DataFrame(
184
+ label="Questions and Agent Answers", wrap=True)
185
 
186
  run_button.click(
187
  fn=run_and_submit_all,
 
192
  print("\n" + "-"*30 + " App Starting " + "-"*30)
193
  # Check for SPACE_HOST and SPACE_ID at startup for information
194
  space_host_startup = os.getenv("SPACE_HOST")
195
+ space_id_startup = os.getenv("SPACE_ID") # Get SPACE_ID at startup
196
 
197
  if space_host_startup:
198
  print(f"✅ SPACE_HOST found: {space_host_startup}")
199
+ print(
200
+ f" Runtime URL should be: https://{space_host_startup}.hf.space")
201
  else:
202
  print("ℹ️ SPACE_HOST environment variable not found (running locally?).")
203
 
204
+ if space_id_startup: # Print repo URLs if SPACE_ID is found
205
  print(f"✅ SPACE_ID found: {space_id_startup}")
206
  print(f" Repo URL: https://huggingface.co/spaces/{space_id_startup}")
207
+ print(
208
+ f" Repo Tree URL: https://huggingface.co/spaces/{space_id_startup}/tree/main")
209
  else:
210
  print("ℹ️ SPACE_ID environment variable not found (running locally?). Repo URL cannot be determined.")
211
 
212
  print("-"*(60 + len(" App Starting ")) + "\n")
213
 
214
  print("Launching Gradio Interface for Basic Agent Evaluation...")
215
+ demo.launch(debug=True, share=False)
prompts/system_prompt.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
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 above rules depending of whether the element to be put in the list is a number or a string.
5
+ Your answer should only start with "FINAL ANSWER: ", then follows with the answer.
requirements.txt CHANGED
@@ -1,2 +1,20 @@
1
  gradio
2
- requests
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ supabase
14
+ arxiv
15
+ pymupdf
16
+ wikipedia
17
+ pgvector
18
+ python-dotenv
19
+ pytesseract
20
+ matplotlib
tools/CodeToolkit.py ADDED
@@ -0,0 +1,355 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
19
+ class CodeInterpreter:
20
+ def __init__(self, allowed_modules=None, max_execution_time=30, working_directory=None):
21
+ """Initialize the code interpreter with safety measures."""
22
+ self.allowed_modules = allowed_modules or [
23
+ "numpy", "pandas", "matplotlib", "scipy", "sklearn",
24
+ "math", "random", "statistics", "datetime", "collections",
25
+ "itertools", "functools", "operator", "re", "json",
26
+ "sympy", "networkx", "nltk", "PIL", "pytesseract",
27
+ "cmath", "uuid", "tempfile", "requests", "urllib"
28
+ ]
29
+ self.max_execution_time = max_execution_time
30
+ self.working_directory = working_directory or os.path.join(os.getcwd())
31
+ if not os.path.exists(self.working_directory):
32
+ os.makedirs(self.working_directory)
33
+
34
+ self.globals = {
35
+ "__builtins__": __builtins__,
36
+ "np": np,
37
+ "pd": pd,
38
+ "plt": plt,
39
+ "Image": Image,
40
+ }
41
+ self.temp_sqlite_db = os.path.join(
42
+ tempfile.gettempdir(), "code_exec.db")
43
+
44
+ def execute_code(self, code: str, language: str = "python") -> Dict[str, Any]:
45
+ """Execute the provided code in the selected programming language."""
46
+ language = language.lower()
47
+ execution_id = str(uuid.uuid4())
48
+
49
+ result = {
50
+ "execution_id": execution_id,
51
+ "status": "error",
52
+ "stdout": "",
53
+ "stderr": "",
54
+ "result": None,
55
+ "plots": [],
56
+ "dataframes": []
57
+ }
58
+
59
+ try:
60
+ if language == "python":
61
+ return self._execute_python(code, execution_id)
62
+ elif language == "bash":
63
+ return self._execute_bash(code, execution_id)
64
+ elif language == "sql":
65
+ return self._execute_sql(code, execution_id)
66
+ elif language == "c":
67
+ return self._execute_c(code, execution_id)
68
+ elif language == "java":
69
+ return self._execute_java(code, execution_id)
70
+ else:
71
+ result["stderr"] = f"Unsupported language: {language}"
72
+ except Exception as e:
73
+ result["stderr"] = str(e)
74
+
75
+ return result
76
+
77
+ def _execute_python(self, code: str, execution_id: str) -> dict:
78
+ output_buffer = io.StringIO()
79
+ error_buffer = io.StringIO()
80
+ result = {
81
+ "execution_id": execution_id,
82
+ "status": "error",
83
+ "stdout": "",
84
+ "stderr": "",
85
+ "result": None,
86
+ "plots": [],
87
+ "dataframes": []
88
+ }
89
+
90
+ try:
91
+ exec_dir = os.path.join(self.working_directory, execution_id)
92
+ os.makedirs(exec_dir, exist_ok=True)
93
+ plt.switch_backend('Agg')
94
+
95
+ with contextlib.redirect_stdout(output_buffer), contextlib.redirect_stderr(error_buffer):
96
+ exec_result = exec(code, self.globals)
97
+
98
+ if plt.get_fignums():
99
+ for i, fig_num in enumerate(plt.get_fignums()):
100
+ fig = plt.figure(fig_num)
101
+ img_path = os.path.join(exec_dir, f"plot_{i}.png")
102
+ fig.savefig(img_path)
103
+ with open(img_path, "rb") as img_file:
104
+ img_data = base64.b64encode(
105
+ img_file.read()).decode('utf-8')
106
+ result["plots"].append({
107
+ "figure_number": fig_num,
108
+ "data": img_data
109
+ })
110
+
111
+ for var_name, var_value in self.globals.items():
112
+ if isinstance(var_value, pd.DataFrame) and len(var_value) > 0:
113
+ result["dataframes"].append({
114
+ "name": var_name,
115
+ "head": var_value.head().to_dict(),
116
+ "shape": var_value.shape,
117
+ "dtypes": str(var_value.dtypes)
118
+ })
119
+
120
+ result["status"] = "success"
121
+ result["stdout"] = output_buffer.getvalue()
122
+ result["result"] = exec_result
123
+
124
+ except Exception as e:
125
+ result["status"] = "error"
126
+ result["stderr"] = f"{error_buffer.getvalue()}\n{traceback.format_exc()}"
127
+
128
+ return result
129
+
130
+ def _execute_bash(self, code: str, execution_id: str) -> dict:
131
+ try:
132
+ completed = subprocess.run(
133
+ code, shell=True, capture_output=True, text=True, timeout=self.max_execution_time
134
+ )
135
+ return {
136
+ "execution_id": execution_id,
137
+ "status": "success" if completed.returncode == 0 else "error",
138
+ "stdout": completed.stdout,
139
+ "stderr": completed.stderr,
140
+ "result": None,
141
+ "plots": [],
142
+ "dataframes": []
143
+ }
144
+ except subprocess.TimeoutExpired:
145
+ return {
146
+ "execution_id": execution_id,
147
+ "status": "error",
148
+ "stdout": "",
149
+ "stderr": "Execution timed out.",
150
+ "result": None,
151
+ "plots": [],
152
+ "dataframes": []
153
+ }
154
+
155
+ def _execute_sql(self, code: str, execution_id: str) -> dict:
156
+ result = {
157
+ "execution_id": execution_id,
158
+ "status": "error",
159
+ "stdout": "",
160
+ "stderr": "",
161
+ "result": None,
162
+ "plots": [],
163
+ "dataframes": []
164
+ }
165
+ try:
166
+ conn = sqlite3.connect(self.temp_sqlite_db)
167
+ cur = conn.cursor()
168
+ cur.execute(code)
169
+ if code.strip().lower().startswith("select"):
170
+ columns = [description[0] for description in cur.description]
171
+ rows = cur.fetchall()
172
+ df = pd.DataFrame(rows, columns=columns)
173
+ result["dataframes"].append({
174
+ "name": "query_result",
175
+ "head": df.head().to_dict(),
176
+ "shape": df.shape,
177
+ "dtypes": str(df.dtypes)
178
+ })
179
+ else:
180
+ conn.commit()
181
+
182
+ result["status"] = "success"
183
+ result["stdout"] = "Query executed successfully."
184
+
185
+ except Exception as e:
186
+ result["stderr"] = str(e)
187
+ finally:
188
+ conn.close()
189
+
190
+ return result
191
+
192
+ def _execute_c(self, code: str, execution_id: str) -> dict:
193
+ temp_dir = tempfile.mkdtemp()
194
+ source_path = os.path.join(temp_dir, "program.c")
195
+ binary_path = os.path.join(temp_dir, "program")
196
+
197
+ try:
198
+ with open(source_path, "w") as f:
199
+ f.write(code)
200
+
201
+ compile_proc = subprocess.run(
202
+ ["gcc", source_path, "-o", binary_path],
203
+ capture_output=True, text=True, timeout=self.max_execution_time
204
+ )
205
+ if compile_proc.returncode != 0:
206
+ return {
207
+ "execution_id": execution_id,
208
+ "status": "error",
209
+ "stdout": compile_proc.stdout,
210
+ "stderr": compile_proc.stderr,
211
+ "result": None,
212
+ "plots": [],
213
+ "dataframes": []
214
+ }
215
+
216
+ run_proc = subprocess.run(
217
+ [binary_path],
218
+ capture_output=True, text=True, timeout=self.max_execution_time
219
+ )
220
+ return {
221
+ "execution_id": execution_id,
222
+ "status": "success" if run_proc.returncode == 0 else "error",
223
+ "stdout": run_proc.stdout,
224
+ "stderr": run_proc.stderr,
225
+ "result": None,
226
+ "plots": [],
227
+ "dataframes": []
228
+ }
229
+ except Exception as e:
230
+ return {
231
+ "execution_id": execution_id,
232
+ "status": "error",
233
+ "stdout": "",
234
+ "stderr": str(e),
235
+ "result": None,
236
+ "plots": [],
237
+ "dataframes": []
238
+ }
239
+
240
+ def _execute_java(self, code: str, execution_id: str) -> dict:
241
+ temp_dir = tempfile.mkdtemp()
242
+ source_path = os.path.join(temp_dir, "Main.java")
243
+
244
+ try:
245
+ with open(source_path, "w") as f:
246
+ f.write(code)
247
+
248
+ compile_proc = subprocess.run(
249
+ ["javac", source_path],
250
+ capture_output=True, text=True, timeout=self.max_execution_time
251
+ )
252
+ if compile_proc.returncode != 0:
253
+ return {
254
+ "execution_id": execution_id,
255
+ "status": "error",
256
+ "stdout": compile_proc.stdout,
257
+ "stderr": compile_proc.stderr,
258
+ "result": None,
259
+ "plots": [],
260
+ "dataframes": []
261
+ }
262
+
263
+ run_proc = subprocess.run(
264
+ ["java", "-cp", temp_dir, "Main"],
265
+ capture_output=True, text=True, timeout=self.max_execution_time
266
+ )
267
+ return {
268
+ "execution_id": execution_id,
269
+ "status": "success" if run_proc.returncode == 0 else "error",
270
+ "stdout": run_proc.stdout,
271
+ "stderr": run_proc.stderr,
272
+ "result": None,
273
+ "plots": [],
274
+ "dataframes": []
275
+ }
276
+ except Exception as e:
277
+ return {
278
+ "execution_id": execution_id,
279
+ "status": "error",
280
+ "stdout": "",
281
+ "stderr": str(e),
282
+ "result": None,
283
+ "plots": [],
284
+ "dataframes": []
285
+ }
286
+
287
+
288
+ interpreter_instance = CodeInterpreter()
289
+
290
+
291
+ @tool
292
+ def execute_code_multilang(code: str, language: str = "python") -> str:
293
+ """Execute code in multiple languages (Python, Bash, SQL, C, Java) and return results.
294
+ Args:
295
+ code (str): The source code to execute.
296
+ language (str): The language of the code. Supported: "python", "bash", "sql", "c", "java".
297
+ Returns:
298
+ A string summarizing the execution results (stdout, stderr, errors, plots, dataframes if any).
299
+ """
300
+ supported_languages = ["python", "bash", "sql", "c", "java"]
301
+ language = language.lower()
302
+
303
+ if language not in supported_languages:
304
+ return f"❌ Unsupported language: {language}. Supported languages are: {', '.join(supported_languages)}"
305
+
306
+ result = interpreter_instance.execute_code(code, language=language)
307
+
308
+ response = []
309
+
310
+ if result["status"] == "success":
311
+ response.append(
312
+ f"✅ Code executed successfully in **{language.upper()}**")
313
+
314
+ if result.get("stdout"):
315
+ response.append(
316
+ "\n**Standard Output:**\n```\n" +
317
+ result["stdout"].strip() + "\n```"
318
+ )
319
+
320
+ if result.get("stderr"):
321
+ response.append(
322
+ "\n**Standard Error (if any):**\n```\n"
323
+ + result["stderr"].strip()
324
+ + "\n```"
325
+ )
326
+
327
+ if result.get("result") is not None:
328
+ response.append(
329
+ "\n**Execution Result:**\n```\n"
330
+ + str(result["result"]).strip()
331
+ + "\n```"
332
+ )
333
+
334
+ if result.get("dataframes"):
335
+ for df_info in result["dataframes"]:
336
+ response.append(
337
+ f"\n**DataFrame `{df_info['name']}` (Shape: {df_info['shape']})**"
338
+ )
339
+ df_preview = pd.DataFrame(df_info["head"])
340
+ response.append("First 5 rows:\n```\n" +
341
+ str(df_preview) + "\n```")
342
+
343
+ if result.get("plots"):
344
+ response.append(
345
+ f"\n**Generated {len(result['plots'])} plot(s)** (Image data returned separately)"
346
+ )
347
+
348
+ else:
349
+ response.append(f"❌ Code execution failed in **{language.upper()}**")
350
+ if result.get("stderr"):
351
+ response.append(
352
+ "\n**Error Log:**\n```\n" + result["stderr"].strip() + "\n```"
353
+ )
354
+
355
+ return "\n".join(response)
tools/DocumentsToolkit.py ADDED
@@ -0,0 +1,175 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ import docx
12
+ import fitz
13
+
14
+
15
+ @tool
16
+ def save_and_read_file(content: str, filename: Optional[str] = None) -> str:
17
+ """
18
+ Save content to a file and return the path.
19
+ Args:
20
+ content (str): the content to save to the file
21
+ filename (str, optional): the name of the file. If not provided, a random name file will be created.
22
+ """
23
+ temp_dir = tempfile.gettempdir()
24
+ if filename is None:
25
+ temp_file = tempfile.NamedTemporaryFile(delete=False, dir=temp_dir)
26
+ filepath = temp_file.name
27
+ else:
28
+ filepath = os.path.join(temp_dir, filename)
29
+
30
+ with open(filepath, "w") as f:
31
+ f.write(content)
32
+
33
+ return f"File saved to {filepath}. You can read this file to process its contents."
34
+
35
+
36
+ @tool
37
+ def download_file_from_url(url: str, filename: Optional[str] = None) -> str:
38
+ """
39
+ Download a file from a URL and save it to a temporary location.
40
+ Args:
41
+ url (str): the URL of the file to download.
42
+ filename (str, optional): the name of the file. If not provided, a random name file will be created.
43
+ """
44
+ try:
45
+ # Parse URL to get filename if not provided
46
+ if not filename:
47
+ path = urlparse(url).path
48
+ filename = os.path.basename(path)
49
+ if not filename:
50
+ filename = f"downloaded_{uuid.uuid4().hex[:8]}"
51
+
52
+ # Create temporary file
53
+ temp_dir = tempfile.gettempdir()
54
+ filepath = os.path.join(temp_dir, filename)
55
+
56
+ # Download the file
57
+ response = requests.get(url, stream=True)
58
+ response.raise_for_status()
59
+
60
+ # Save the file
61
+ with open(filepath, "wb") as f:
62
+ for chunk in response.iter_content(chunk_size=8192):
63
+ f.write(chunk)
64
+
65
+ return f"File downloaded to {filepath}. You can read this file to process its contents."
66
+ except Exception as e:
67
+ return f"Error downloading file: {str(e)}"
68
+
69
+
70
+ @tool
71
+ def extract_text_from_image(image_path: str) -> str:
72
+ """
73
+ Extract text from an image using OCR library pytesseract (if available).
74
+ Args:
75
+ image_path (str): the path to the image file.
76
+ """
77
+ try:
78
+ # Open the image
79
+ image = Image.open(image_path)
80
+
81
+ # Extract text from the image
82
+ text = pytesseract.image_to_string(image)
83
+
84
+ return f"Extracted text from image:\n\n{text}"
85
+ except Exception as e:
86
+ return f"Error extracting text from image: {str(e)}"
87
+
88
+
89
+ @tool
90
+ def analyze_csv_file(file_path: str, query: str) -> str:
91
+ """
92
+ Analyze a CSV file using pandas and answer a question about it.
93
+ Args:
94
+ file_path (str): the path to the CSV file.
95
+ query (str): Question about the data
96
+ """
97
+ try:
98
+ # Read the CSV file
99
+ df = pd.read_csv(file_path)
100
+
101
+ # Run various analyses based on the query
102
+ result = f"CSV file loaded with {len(df)} rows and {len(df.columns)} columns.\n"
103
+ result += f"Columns: {', '.join(df.columns)}\n\n"
104
+
105
+ # Add summary statistics
106
+ result += "Summary statistics:\n"
107
+ result += str(df.describe())
108
+
109
+ return result
110
+
111
+ except Exception as e:
112
+ return f"Error analyzing CSV file: {str(e)}"
113
+
114
+
115
+ @tool
116
+ def analyze_excel_file(file_path: str, query: str) -> str:
117
+ """
118
+ Analyze an Excel file using pandas and answer a question about it.
119
+ Args:
120
+ file_path (str): the path to the Excel file.
121
+ query (str): Question about the data
122
+ """
123
+ try:
124
+ # Read the Excel file
125
+ df = pd.read_excel(file_path)
126
+
127
+ # Run various analyses based on the query
128
+ result = (
129
+ f"Excel file loaded with {len(df)} rows and {len(df.columns)} columns.\n"
130
+ )
131
+ result += f"Columns: {', '.join(df.columns)}\n\n"
132
+
133
+ # Add summary statistics
134
+ result += "Summary statistics:\n"
135
+ result += str(df.describe())
136
+
137
+ return result
138
+
139
+ except Exception as e:
140
+ return f"Error analyzing Excel file: {str(e)}"
141
+
142
+
143
+ @tool
144
+ def analyze_word_file(file_path: str, query: str) -> str:
145
+ """
146
+ Analyze a Word (.docx) document and summarize its contents.
147
+ Args:
148
+ file_path (str): path to the .docx file.
149
+ query (str): Question or instruction regarding the document content.
150
+ """
151
+ try:
152
+ doc = docx.Document(file_path)
153
+ full_text = "\n".join(
154
+ [para.text for para in doc.paragraphs if para.text.strip()])
155
+ return f"Word document loaded. Length: {len(full_text)} characters.\nPreview:\n{full_text[:1000]}"
156
+ except Exception as e:
157
+ return f"Error analyzing Word document: {str(e)}"
158
+
159
+
160
+ @tool
161
+ def analyze_pdf_file(file_path: str, query: str) -> str:
162
+ """
163
+ Analyze a PDF file and summarize its contents.
164
+ Args:
165
+ file_path (str): path to the PDF file.
166
+ query (str): Question or instruction regarding the document content.
167
+ """
168
+ try:
169
+ doc = fitz.open(file_path)
170
+ text = ""
171
+ for page in doc:
172
+ text += page.get_text()
173
+ return f"PDF loaded. Length: {len(text)} characters.\nPreview:\n{text[:1000]}"
174
+ except Exception as e:
175
+ return f"Error analyzing PDF: {str(e)}"
tools/ImagesToolkit.py ADDED
@@ -0,0 +1,321 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
13
+
14
+ def encode_image(image_path: str) -> str:
15
+ """Convert an image file to base64 string."""
16
+ with open(image_path, "rb") as image_file:
17
+ return base64.b64encode(image_file.read()).decode("utf-8")
18
+
19
+
20
+ def decode_image(base64_string: str) -> Image.Image:
21
+ """Convert a base64 string to a PIL Image."""
22
+ image_data = base64.b64decode(base64_string)
23
+ return Image.open(io.BytesIO(image_data))
24
+
25
+
26
+ def save_image(image: Image.Image, directory: str = "image_outputs") -> str:
27
+ """Save a PIL Image to disk and return the path."""
28
+ os.makedirs(directory, exist_ok=True)
29
+ image_id = str(uuid.uuid4())
30
+ image_path = os.path.join(directory, f"{image_id}.png")
31
+ image.save(image_path)
32
+ return image_path
33
+
34
+
35
+ @tool
36
+ def analyze_image(image_base64: str) -> Dict[str, Any]:
37
+ """
38
+ Analyze basic properties of an image (size, mode, color analysis, thumbnail preview).
39
+ Args:
40
+ image_base64 (str): Base64 encoded image string
41
+ Returns:
42
+ Dictionary with analysis result
43
+ """
44
+ try:
45
+ img = decode_image(image_base64)
46
+ width, height = img.size
47
+ mode = img.mode
48
+
49
+ if mode in ("RGB", "RGBA"):
50
+ arr = np.array(img)
51
+ avg_colors = arr.mean(axis=(0, 1))
52
+ dominant = ["Red", "Green", "Blue"][np.argmax(avg_colors[:3])]
53
+ brightness = avg_colors.mean()
54
+ color_analysis = {
55
+ "average_rgb": avg_colors.tolist(),
56
+ "brightness": brightness,
57
+ "dominant_color": dominant,
58
+ }
59
+ else:
60
+ color_analysis = {"note": f"No color analysis for mode {mode}"}
61
+
62
+ thumbnail = img.copy()
63
+ thumbnail.thumbnail((100, 100))
64
+ thumb_path = save_image(thumbnail, "thumbnails")
65
+ thumbnail_base64 = encode_image(thumb_path)
66
+
67
+ return {
68
+ "dimensions": (width, height),
69
+ "mode": mode,
70
+ "color_analysis": color_analysis,
71
+ "thumbnail": thumbnail_base64,
72
+ }
73
+ except Exception as e:
74
+ return {"error": str(e)}
75
+
76
+
77
+ @tool
78
+ def transform_image(
79
+ image_base64: str, operation: str, params: Optional[Dict[str, Any]] = None
80
+ ) -> Dict[str, Any]:
81
+ """
82
+ Apply transformations: resize, rotate, crop, flip, brightness, contrast, blur, sharpen, grayscale.
83
+ Args:
84
+ image_base64 (str): Base64 encoded input image
85
+ operation (str): Transformation operation
86
+ params (Dict[str, Any], optional): Parameters for the operation
87
+ Returns:
88
+ Dictionary with transformed image (base64)
89
+ """
90
+ try:
91
+ img = decode_image(image_base64)
92
+ params = params or {}
93
+
94
+ if operation == "resize":
95
+ img = img.resize(
96
+ (
97
+ params.get("width", img.width // 2),
98
+ params.get("height", img.height // 2),
99
+ )
100
+ )
101
+ elif operation == "rotate":
102
+ img = img.rotate(params.get("angle", 90), expand=True)
103
+ elif operation == "crop":
104
+ img = img.crop(
105
+ (
106
+ params.get("left", 0),
107
+ params.get("top", 0),
108
+ params.get("right", img.width),
109
+ params.get("bottom", img.height),
110
+ )
111
+ )
112
+ elif operation == "flip":
113
+ if params.get("direction", "horizontal") == "horizontal":
114
+ img = img.transpose(Image.FLIP_LEFT_RIGHT)
115
+ else:
116
+ img = img.transpose(Image.FLIP_TOP_BOTTOM)
117
+ elif operation == "adjust_brightness":
118
+ img = ImageEnhance.Brightness(
119
+ img).enhance(params.get("factor", 1.5))
120
+ elif operation == "adjust_contrast":
121
+ img = ImageEnhance.Contrast(img).enhance(params.get("factor", 1.5))
122
+ elif operation == "blur":
123
+ img = img.filter(ImageFilter.GaussianBlur(params.get("radius", 2)))
124
+ elif operation == "sharpen":
125
+ img = img.filter(ImageFilter.SHARPEN)
126
+ elif operation == "grayscale":
127
+ img = img.convert("L")
128
+ else:
129
+ return {"error": f"Unknown operation: {operation}"}
130
+
131
+ result_path = save_image(img)
132
+ result_base64 = encode_image(result_path)
133
+ return {"transformed_image": result_base64}
134
+
135
+ except Exception as e:
136
+ return {"error": str(e)}
137
+
138
+
139
+ @tool
140
+ def draw_on_image(
141
+ image_base64: str, drawing_type: str, params: Dict[str, Any]
142
+ ) -> Dict[str, Any]:
143
+ """
144
+ Draw shapes (rectangle, circle, line) or text onto an image.
145
+ Args:
146
+ image_base64 (str): Base64 encoded input image
147
+ drawing_type (str): Drawing type
148
+ params (Dict[str, Any]): Drawing parameters
149
+ Returns:
150
+ Dictionary with result image (base64)
151
+ """
152
+ try:
153
+ img = decode_image(image_base64)
154
+ draw = ImageDraw.Draw(img)
155
+ color = params.get("color", "red")
156
+
157
+ if drawing_type == "rectangle":
158
+ draw.rectangle(
159
+ [params["left"], params["top"], params["right"], params["bottom"]],
160
+ outline=color,
161
+ width=params.get("width", 2),
162
+ )
163
+ elif drawing_type == "circle":
164
+ x, y, r = params["x"], params["y"], params["radius"]
165
+ draw.ellipse(
166
+ (x - r, y - r, x + r, y + r),
167
+ outline=color,
168
+ width=params.get("width", 2),
169
+ )
170
+ elif drawing_type == "line":
171
+ draw.line(
172
+ (
173
+ params["start_x"],
174
+ params["start_y"],
175
+ params["end_x"],
176
+ params["end_y"],
177
+ ),
178
+ fill=color,
179
+ width=params.get("width", 2),
180
+ )
181
+ elif drawing_type == "text":
182
+ font_size = params.get("font_size", 20)
183
+ try:
184
+ font = ImageFont.truetype("arial.ttf", font_size)
185
+ except IOError:
186
+ font = ImageFont.load_default()
187
+ draw.text(
188
+ (params["x"], params["y"]),
189
+ params.get("text", "Text"),
190
+ fill=color,
191
+ font=font,
192
+ )
193
+ else:
194
+ return {"error": f"Unknown drawing type: {drawing_type}"}
195
+
196
+ result_path = save_image(img)
197
+ result_base64 = encode_image(result_path)
198
+ return {"result_image": result_base64}
199
+
200
+ except Exception as e:
201
+ return {"error": str(e)}
202
+
203
+
204
+ @tool
205
+ def generate_simple_image(
206
+ image_type: str,
207
+ width: int = 500,
208
+ height: int = 500,
209
+ params: Optional[Dict[str, Any]] = None,
210
+ ) -> Dict[str, Any]:
211
+ """
212
+ Generate a simple image (gradient, noise, pattern, chart).
213
+ Args:
214
+ image_type (str): Type of image
215
+ width (int), height (int)
216
+ params (Dict[str, Any], optional): Specific parameters
217
+ Returns:
218
+ Dictionary with generated image (base64)
219
+ """
220
+ try:
221
+ params = params or {}
222
+
223
+ if image_type == "gradient":
224
+ direction = params.get("direction", "horizontal")
225
+ start_color = params.get("start_color", (255, 0, 0))
226
+ end_color = params.get("end_color", (0, 0, 255))
227
+
228
+ img = Image.new("RGB", (width, height))
229
+ draw = ImageDraw.Draw(img)
230
+
231
+ if direction == "horizontal":
232
+ for x in range(width):
233
+ r = int(
234
+ start_color[0] +
235
+ (end_color[0] - start_color[0]) * x / width
236
+ )
237
+ g = int(
238
+ start_color[1] +
239
+ (end_color[1] - start_color[1]) * x / width
240
+ )
241
+ b = int(
242
+ start_color[2] +
243
+ (end_color[2] - start_color[2]) * x / width
244
+ )
245
+ draw.line([(x, 0), (x, height)], fill=(r, g, b))
246
+ else:
247
+ for y in range(height):
248
+ r = int(
249
+ start_color[0] + (end_color[0] -
250
+ start_color[0]) * y / height
251
+ )
252
+ g = int(
253
+ start_color[1] + (end_color[1] -
254
+ start_color[1]) * y / height
255
+ )
256
+ b = int(
257
+ start_color[2] + (end_color[2] -
258
+ start_color[2]) * y / height
259
+ )
260
+ draw.line([(0, y), (width, y)], fill=(r, g, b))
261
+
262
+ elif image_type == "noise":
263
+ noise_array = np.random.randint(
264
+ 0, 256, (height, width, 3), dtype=np.uint8)
265
+ img = Image.fromarray(noise_array, "RGB")
266
+
267
+ else:
268
+ return {"error": f"Unsupported image_type {image_type}"}
269
+
270
+ result_path = save_image(img)
271
+ result_base64 = encode_image(result_path)
272
+ return {"generated_image": result_base64}
273
+
274
+ except Exception as e:
275
+ return {"error": str(e)}
276
+
277
+
278
+ @tool
279
+ def combine_images(
280
+ images_base64: List[str], operation: str, params: Optional[Dict[str, Any]] = None
281
+ ) -> Dict[str, Any]:
282
+ """
283
+ Combine multiple images (collage, stack, blend).
284
+ Args:
285
+ images_base64 (List[str]): List of base64 images
286
+ operation (str): Combination type
287
+ params (Dict[str, Any], optional)
288
+ Returns:
289
+ Dictionary with combined image (base64)
290
+ """
291
+ try:
292
+ images = [decode_image(b64) for b64 in images_base64]
293
+ params = params or {}
294
+
295
+ if operation == "stack":
296
+ direction = params.get("direction", "horizontal")
297
+ if direction == "horizontal":
298
+ total_width = sum(img.width for img in images)
299
+ max_height = max(img.height for img in images)
300
+ new_img = Image.new("RGB", (total_width, max_height))
301
+ x = 0
302
+ for img in images:
303
+ new_img.paste(img, (x, 0))
304
+ x += img.width
305
+ else:
306
+ max_width = max(img.width for img in images)
307
+ total_height = sum(img.height for img in images)
308
+ new_img = Image.new("RGB", (max_width, total_height))
309
+ y = 0
310
+ for img in images:
311
+ new_img.paste(img, (0, y))
312
+ y += img.height
313
+ else:
314
+ return {"error": f"Unsupported combination operation {operation}"}
315
+
316
+ result_path = save_image(new_img)
317
+ result_base64 = encode_image(result_path)
318
+ return {"combined_image": result_base64}
319
+
320
+ except Exception as e:
321
+ return {"error": str(e)}
tools/MathsToolkit.py ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_core.tools import tool
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/SearchToolkit.py ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_core.tools import tool
2
+ from langchain_community.tools.tavily_search import TavilySearchResults
3
+ from langchain_community.document_loaders import WikipediaLoader
4
+ from langchain_community.document_loaders import ArxivLoader
5
+ import os
6
+ from supabase.client import Client, create_client
7
+ from langchain_huggingface import HuggingFaceEmbeddings
8
+ from langchain_community.vectorstores import SupabaseVectorStore
9
+ from langchain.tools.retriever import create_retriever_tool
10
+
11
+ embeddings = HuggingFaceEmbeddings(
12
+ model_name="sentence-transformers/all-mpnet-base-v2") # dim=768
13
+
14
+ supabase_url = os.environ.get("SUPABASE_URL")
15
+ supabase_key = os.environ.get("SUPABASE_SERVICE_KEY")
16
+ supabase: Client = create_client(supabase_url, supabase_key)
17
+ vector_store = SupabaseVectorStore(
18
+ client=supabase,
19
+ embedding=embeddings,
20
+ table_name="documents",
21
+ query_name="match_documents",
22
+ )
23
+
24
+ question_retrieve_tool = create_retriever_tool(
25
+ vector_store.as_retriever(),
26
+ "Question_Retriever",
27
+ "Find similar questions in the vector database for the given question.",
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:
78
+ """Search the vector database for similar questions and return the first results.
79
+
80
+ Args:
81
+ question: the question human provided."""
82
+ matched_docs = vector_store.similarity_search(question, 3)
83
+ formatted_search_docs = "\n\n---\n\n".join(
84
+ [
85
+ f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content[:1000]}\n</Document>'
86
+ for doc in matched_docs
87
+ ])
88
+ return {"similar_questions": formatted_search_docs}
tools/__init__.py ADDED
File without changes