Ricardo Teixeira commited on
Commit
84b3767
·
1 Parent(s): 5826ed0

first version

Browse files
Files changed (7) hide show
  1. agent.py +68 -0
  2. app.py +2 -12
  3. code_interpreter.py +348 -0
  4. image_tools.py +310 -0
  5. requirements.txt +18 -1
  6. system_prompt.txt +5 -0
  7. tools.py +285 -0
agent.py CHANGED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import TypedDict, Annotated
2
+ from langgraph.graph.message import add_messages
3
+ from langchain_core.messages import AnyMessage, HumanMessage, SystemMessage
4
+ from langgraph.graph import START, StateGraph, MessagesState
5
+ from langgraph.prebuilt import tools_condition, ToolNode
6
+ from langchain_ollama import ChatOllama
7
+ from langchain_google_genai import ChatGoogleGenerativeAI
8
+ from langchain_groq import ChatGroq
9
+
10
+ from tools import get_tools
11
+
12
+ class Agent():
13
+ def __init__(self):
14
+ self.tools = get_tools()
15
+
16
+ with open('system_prompt.txt', 'r') as f:
17
+ system_prompt = f.read()
18
+ self.sys_msg = SystemMessage(content=system_prompt)
19
+
20
+ print("Agent initialized.")
21
+
22
+ def build_graph(self, provider: str = ''):
23
+ if provider == 'ollama':
24
+ llm = ChatOllama(model="llama3.1:8b", temperature=0)
25
+ elif provider == 'google':
26
+ llm = ChatGoogleGenerativeAI(model="gemini-2.0-flash", temperature=0)
27
+ elif provider == 'groq':
28
+ llm = ChatGroq(model="qwen-qwq-32b", temperature=0)
29
+ else:
30
+ raise ValueError(f'Provider {provider} not supported')
31
+ llm_with_tools = llm.bind_tools(self.tools)
32
+
33
+ def assistant(state: MessagesState):
34
+ """Assistant node"""
35
+ return {"messages": [llm_with_tools.invoke([self.sys_msg] + state["messages"])]}
36
+
37
+ builder = StateGraph(MessagesState)
38
+ builder.add_node("assistant", assistant)
39
+ builder.add_node("tools", ToolNode(self.tools))
40
+ builder.add_edge(START, "assistant")
41
+ builder.add_conditional_edges(
42
+ "assistant",
43
+ tools_condition,
44
+ )
45
+ builder.add_edge("tools", "assistant")
46
+
47
+ # Compile graph
48
+ return builder.compile()
49
+
50
+ def __call__(self, question: str) -> str:
51
+ print(f"Agent received question (first 50 chars): {question[:50]}...")
52
+ # Wrap the question in a HumanMessage from langchain_core
53
+ messages = [HumanMessage(content=question)]
54
+ graph = self.build_graph()
55
+ messages = graph.invoke({"messages": messages})
56
+ answer = messages['messages'][-1].content
57
+ return answer[14:]
58
+
59
+
60
+
61
+ if __name__ == "__main__":
62
+ agent = Agent()
63
+ question = "How many studio albums were published by Mercedes Sosa between 2000 and 2009 (included)? You can use the latest 2022 version of english wikipedia."
64
+ graph = agent.build_graph('ollama')
65
+ messages = [HumanMessage(content=question)]
66
+ messages = graph.invoke({"messages": messages})
67
+ for m in messages["messages"]:
68
+ m.pretty_print()
app.py CHANGED
@@ -3,22 +3,12 @@ import gradio as gr
3
  import requests
4
  import inspect
5
  import pandas as pd
 
6
 
7
  # (Keep Constants as is)
8
  # --- Constants ---
9
  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,
@@ -40,7 +30,7 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
40
 
41
  # 1. Instantiate Agent ( modify this part to create your agent)
42
  try:
43
- agent = BasicAgent()
44
  except Exception as e:
45
  print(f"Error instantiating agent: {e}")
46
  return f"Error initializing agent: {e}", None
 
3
  import requests
4
  import inspect
5
  import pandas as pd
6
+ from agent import Agent
7
 
8
  # (Keep Constants as is)
9
  # --- Constants ---
10
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
11
 
 
 
 
 
 
 
 
 
 
 
 
12
  def run_and_submit_all( profile: gr.OAuthProfile | None):
13
  """
14
  Fetches all questions, runs the BasicAgent on them, submits all answers,
 
30
 
31
  # 1. Instantiate Agent ( modify this part to create your agent)
32
  try:
33
+ agent = Agent()
34
  except Exception as e:
35
  print(f"Error instantiating agent: {e}")
36
  return f"Error initializing agent: {e}", None
code_interpreter.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)
image_tools.py ADDED
@@ -0,0 +1,310 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import io
3
+ import base64
4
+ import uuid
5
+ from PIL import Image
6
+ from typing import List, Dict, Any, Optional
7
+ from PIL import Image, ImageDraw, ImageFont, ImageEnhance, ImageFilter
8
+ import numpy as np
9
+ from langchain_core.tools import tool
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
+ @tool
33
+ def analyze_image(image_base64: str) -> Dict[str, Any]:
34
+ """
35
+ Analyze basic properties of an image (size, mode, color analysis, thumbnail preview).
36
+ Args:
37
+ image_base64 (str): Base64 encoded image string
38
+ Returns:
39
+ Dictionary with analysis result
40
+ """
41
+ try:
42
+ img = decode_image(image_base64)
43
+ width, height = img.size
44
+ mode = img.mode
45
+
46
+ if mode in ("RGB", "RGBA"):
47
+ arr = np.array(img)
48
+ avg_colors = arr.mean(axis=(0, 1))
49
+ dominant = ["Red", "Green", "Blue"][np.argmax(avg_colors[:3])]
50
+ brightness = avg_colors.mean()
51
+ color_analysis = {
52
+ "average_rgb": avg_colors.tolist(),
53
+ "brightness": brightness,
54
+ "dominant_color": dominant,
55
+ }
56
+ else:
57
+ color_analysis = {"note": f"No color analysis for mode {mode}"}
58
+
59
+ thumbnail = img.copy()
60
+ thumbnail.thumbnail((100, 100))
61
+ thumb_path = save_image(thumbnail, "thumbnails")
62
+ thumbnail_base64 = encode_image(thumb_path)
63
+
64
+ return {
65
+ "dimensions": (width, height),
66
+ "mode": mode,
67
+ "color_analysis": color_analysis,
68
+ "thumbnail": thumbnail_base64,
69
+ }
70
+ except Exception as e:
71
+ return {"error": str(e)}
72
+
73
+
74
+ @tool
75
+ def transform_image(
76
+ image_base64: str, operation: str, params: Optional[Dict[str, Any]] = None
77
+ ) -> Dict[str, Any]:
78
+ """
79
+ Apply transformations: resize, rotate, crop, flip, brightness, contrast, blur, sharpen, grayscale.
80
+ Args:
81
+ image_base64 (str): Base64 encoded input image
82
+ operation (str): Transformation operation
83
+ params (Dict[str, Any], optional): Parameters for the operation
84
+ Returns:
85
+ Dictionary with transformed image (base64)
86
+ """
87
+ try:
88
+ img = decode_image(image_base64)
89
+ params = params or {}
90
+
91
+ if operation == "resize":
92
+ img = img.resize(
93
+ (
94
+ params.get("width", img.width // 2),
95
+ params.get("height", img.height // 2),
96
+ )
97
+ )
98
+ elif operation == "rotate":
99
+ img = img.rotate(params.get("angle", 90), expand=True)
100
+ elif operation == "crop":
101
+ img = img.crop(
102
+ (
103
+ params.get("left", 0),
104
+ params.get("top", 0),
105
+ params.get("right", img.width),
106
+ params.get("bottom", img.height),
107
+ )
108
+ )
109
+ elif operation == "flip":
110
+ if params.get("direction", "horizontal") == "horizontal":
111
+ img = img.transpose(Image.FLIP_LEFT_RIGHT)
112
+ else:
113
+ img = img.transpose(Image.FLIP_TOP_BOTTOM)
114
+ elif operation == "adjust_brightness":
115
+ img = ImageEnhance.Brightness(img).enhance(params.get("factor", 1.5))
116
+ elif operation == "adjust_contrast":
117
+ img = ImageEnhance.Contrast(img).enhance(params.get("factor", 1.5))
118
+ elif operation == "blur":
119
+ img = img.filter(ImageFilter.GaussianBlur(params.get("radius", 2)))
120
+ elif operation == "sharpen":
121
+ img = img.filter(ImageFilter.SHARPEN)
122
+ elif operation == "grayscale":
123
+ img = img.convert("L")
124
+ else:
125
+ return {"error": f"Unknown operation: {operation}"}
126
+
127
+ result_path = save_image(img)
128
+ result_base64 = encode_image(result_path)
129
+ return {"transformed_image": result_base64}
130
+
131
+ except Exception as e:
132
+ return {"error": str(e)}
133
+
134
+
135
+ @tool
136
+ def draw_on_image(
137
+ image_base64: str, drawing_type: str, params: Dict[str, Any]
138
+ ) -> Dict[str, Any]:
139
+ """
140
+ Draw shapes (rectangle, circle, line) or text onto an image.
141
+ Args:
142
+ image_base64 (str): Base64 encoded input image
143
+ drawing_type (str): Drawing type
144
+ params (Dict[str, Any]): Drawing parameters
145
+ Returns:
146
+ Dictionary with result image (base64)
147
+ """
148
+ try:
149
+ img = decode_image(image_base64)
150
+ draw = ImageDraw.Draw(img)
151
+ color = params.get("color", "red")
152
+
153
+ if drawing_type == "rectangle":
154
+ draw.rectangle(
155
+ [params["left"], params["top"], params["right"], params["bottom"]],
156
+ outline=color,
157
+ width=params.get("width", 2),
158
+ )
159
+ elif drawing_type == "circle":
160
+ x, y, r = params["x"], params["y"], params["radius"]
161
+ draw.ellipse(
162
+ (x - r, y - r, x + r, y + r),
163
+ outline=color,
164
+ width=params.get("width", 2),
165
+ )
166
+ elif drawing_type == "line":
167
+ draw.line(
168
+ (
169
+ params["start_x"],
170
+ params["start_y"],
171
+ params["end_x"],
172
+ params["end_y"],
173
+ ),
174
+ fill=color,
175
+ width=params.get("width", 2),
176
+ )
177
+ elif drawing_type == "text":
178
+ font_size = params.get("font_size", 20)
179
+ try:
180
+ font = ImageFont.truetype("arial.ttf", font_size)
181
+ except IOError:
182
+ font = ImageFont.load_default()
183
+ draw.text(
184
+ (params["x"], params["y"]),
185
+ params.get("text", "Text"),
186
+ fill=color,
187
+ font=font,
188
+ )
189
+ else:
190
+ return {"error": f"Unknown drawing type: {drawing_type}"}
191
+
192
+ result_path = save_image(img)
193
+ result_base64 = encode_image(result_path)
194
+ return {"result_image": result_base64}
195
+
196
+ except Exception as e:
197
+ return {"error": str(e)}
198
+
199
+
200
+ @tool
201
+ def generate_simple_image(
202
+ image_type: str,
203
+ width: int = 500,
204
+ height: int = 500,
205
+ params: Optional[Dict[str, Any]] = None,
206
+ ) -> Dict[str, Any]:
207
+ """
208
+ Generate a simple image (gradient, noise, pattern, chart).
209
+ Args:
210
+ image_type (str): Type of image
211
+ width (int), height (int)
212
+ params (Dict[str, Any], optional): Specific parameters
213
+ Returns:
214
+ Dictionary with generated image (base64)
215
+ """
216
+ try:
217
+ params = params or {}
218
+
219
+ if image_type == "gradient":
220
+ direction = params.get("direction", "horizontal")
221
+ start_color = params.get("start_color", (255, 0, 0))
222
+ end_color = params.get("end_color", (0, 0, 255))
223
+
224
+ img = Image.new("RGB", (width, height))
225
+ draw = ImageDraw.Draw(img)
226
+
227
+ if direction == "horizontal":
228
+ for x in range(width):
229
+ r = int(
230
+ start_color[0] + (end_color[0] - start_color[0]) * x / width
231
+ )
232
+ g = int(
233
+ start_color[1] + (end_color[1] - start_color[1]) * x / width
234
+ )
235
+ b = int(
236
+ start_color[2] + (end_color[2] - start_color[2]) * x / width
237
+ )
238
+ draw.line([(x, 0), (x, height)], fill=(r, g, b))
239
+ else:
240
+ for y in range(height):
241
+ r = int(
242
+ start_color[0] + (end_color[0] - start_color[0]) * y / height
243
+ )
244
+ g = int(
245
+ start_color[1] + (end_color[1] - start_color[1]) * y / height
246
+ )
247
+ b = int(
248
+ start_color[2] + (end_color[2] - start_color[2]) * y / height
249
+ )
250
+ draw.line([(0, y), (width, y)], fill=(r, g, b))
251
+
252
+ elif image_type == "noise":
253
+ noise_array = np.random.randint(0, 256, (height, width, 3), dtype=np.uint8)
254
+ img = Image.fromarray(noise_array, "RGB")
255
+
256
+ else:
257
+ return {"error": f"Unsupported image_type {image_type}"}
258
+
259
+ result_path = save_image(img)
260
+ result_base64 = encode_image(result_path)
261
+ return {"generated_image": result_base64}
262
+
263
+ except Exception as e:
264
+ return {"error": str(e)}
265
+
266
+
267
+ @tool
268
+ def combine_images(
269
+ images_base64: List[str], operation: str, params: Optional[Dict[str, Any]] = None
270
+ ) -> Dict[str, Any]:
271
+ """
272
+ Combine multiple images (collage, stack, blend).
273
+ Args:
274
+ images_base64 (List[str]): List of base64 images
275
+ operation (str): Combination type
276
+ params (Dict[str, Any], optional)
277
+ Returns:
278
+ Dictionary with combined image (base64)
279
+ """
280
+ try:
281
+ images = [decode_image(b64) for b64 in images_base64]
282
+ params = params or {}
283
+
284
+ if operation == "stack":
285
+ direction = params.get("direction", "horizontal")
286
+ if direction == "horizontal":
287
+ total_width = sum(img.width for img in images)
288
+ max_height = max(img.height for img in images)
289
+ new_img = Image.new("RGB", (total_width, max_height))
290
+ x = 0
291
+ for img in images:
292
+ new_img.paste(img, (x, 0))
293
+ x += img.width
294
+ else:
295
+ max_width = max(img.width for img in images)
296
+ total_height = sum(img.height for img in images)
297
+ new_img = Image.new("RGB", (max_width, total_height))
298
+ y = 0
299
+ for img in images:
300
+ new_img.paste(img, (0, y))
301
+ y += img.height
302
+ else:
303
+ return {"error": f"Unsupported combination operation {operation}"}
304
+
305
+ result_path = save_image(new_img)
306
+ result_base64 = encode_image(result_path)
307
+ return {"combined_image": result_base64}
308
+
309
+ except Exception as e:
310
+ return {"error": str(e)}
requirements.txt CHANGED
@@ -1,2 +1,19 @@
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-ollama
9
+ langchain-tavily
10
+ langchain-chroma
11
+ langgraph
12
+ huggingface_hub
13
+ arxiv
14
+ pymupdf
15
+ wikipedia
16
+ pgvector
17
+ python-dotenv
18
+ pytesseract
19
+ matplotlib
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 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.py ADDED
@@ -0,0 +1,285 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_community.tools import DuckDuckGoSearchRun
2
+ from langchain.tools import Tool
3
+ from langchain_community.document_loaders import WikipediaLoader, ArxivLoader
4
+ from langchain_community.tools.tavily_search import TavilySearchResults
5
+ from langchain_core.tools import tool
6
+ import cmath
7
+ import pandas as pd
8
+ import numpy as np
9
+ from typing import List, Dict, Any, Optional
10
+ import tempfile
11
+ from urllib.parse import urlparse
12
+ import os
13
+ import uuid
14
+ import requests
15
+ from PIL import Image
16
+ import pytesseract
17
+ from code_interpreter import execute_code_multilang
18
+ from image_tools import analyze_image, transform_image, draw_on_image, generate_simple_image, combine_images
19
+
20
+ ########################## Search Tools ##########################
21
+
22
+ @tool
23
+ def wiki_search(query: str) -> str:
24
+ """Search Wikipedia for a query and return maximum 2 results.
25
+ Args:
26
+ query: The search query."""
27
+ search_docs = WikipediaLoader(query=query, load_max_docs=2).load()
28
+ formatted_search_docs = "\n\n---\n\n".join(
29
+ [
30
+ f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>'
31
+ for doc in search_docs
32
+ ]
33
+ )
34
+ return {"wiki_results": formatted_search_docs}
35
+
36
+
37
+ @tool
38
+ def web_search(query: str) -> str:
39
+ """Search Tavily for a query and return maximum 3 results.
40
+ Args:
41
+ query: The search query."""
42
+ search_docs = TavilySearchResults(max_results=3).invoke(query=query)
43
+ formatted_search_docs = "\n\n---\n\n".join(
44
+ [
45
+ f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>'
46
+ for doc in search_docs
47
+ ]
48
+ )
49
+ return {"web_results": formatted_search_docs}
50
+
51
+
52
+ @tool
53
+ def arxiv_search(query: str) -> str:
54
+ """Search Arxiv for a query and return maximum 3 result.
55
+ Args:
56
+ query: The search query."""
57
+ search_docs = ArxivLoader(query=query, load_max_docs=3).load()
58
+ formatted_search_docs = "\n\n---\n\n".join(
59
+ [
60
+ f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content[:1000]}\n</Document>'
61
+ for doc in search_docs
62
+ ]
63
+ )
64
+ return {"arxiv_results": formatted_search_docs}
65
+
66
+ search_tools = [web_search,wiki_search,arxiv_search]
67
+
68
+ ########################## Math tools ##########################
69
+
70
+ @tool
71
+ def add(a:float, b:float) -> float:
72
+ """ Adds two numbers
73
+ Args:
74
+ a: the first number
75
+ b: the second number
76
+ """
77
+
78
+ return a+b
79
+
80
+ @tool
81
+ def subtract(a:float, b:float) -> float:
82
+ """ Subtracts two numbers
83
+ Args:
84
+ a: the first number
85
+ b: the second number
86
+ """
87
+
88
+ return a-b
89
+
90
+ @tool
91
+ def multiply(a:float, b:float) -> float:
92
+ """ Multiplies two numbers
93
+ Args:
94
+ a: the first number
95
+ b: the second number
96
+ """
97
+
98
+ return a*b
99
+
100
+ @tool
101
+ def divide(a:float, b:float) -> float:
102
+ """ Divides two numbers
103
+ Args:
104
+ a: the first number
105
+ b: the second number
106
+ """
107
+
108
+ return a/b
109
+
110
+ @tool
111
+ def modulus(a: int, b: int) -> int:
112
+ """
113
+ Get the modulus of two numbers.
114
+ Args:
115
+ a (int): the first number
116
+ b (int): the second number
117
+ """
118
+ return a % b
119
+
120
+
121
+ @tool
122
+ def power(a: float, b: float) -> float:
123
+ """
124
+ Get the power of two numbers.
125
+ Args:
126
+ a (float): the first number
127
+ b (float): the second number
128
+ """
129
+ return a**b
130
+
131
+
132
+ @tool
133
+ def square_root(a: float) -> float | complex:
134
+ """
135
+ Get the square root of a number.
136
+ Args:
137
+ a (float): the number to get the square root of
138
+ """
139
+ if a >= 0:
140
+ return a**0.5
141
+ return cmath.sqrt(a)
142
+
143
+ math_tools = [add,subtract,multiply,divide,modulus,power,square_root]
144
+
145
+ ########################## Doc tools ##########################
146
+
147
+ @tool
148
+ def save_and_read_file(content: str, filename: Optional[str] = None) -> str:
149
+ """
150
+ Save content to a file and return the path.
151
+ Args:
152
+ content (str): the content to save to the file
153
+ filename (str, optional): the name of the file. If not provided, a random name file will be created.
154
+ """
155
+ temp_dir = tempfile.gettempdir()
156
+ if filename is None:
157
+ temp_file = tempfile.NamedTemporaryFile(delete=False, dir=temp_dir)
158
+ filepath = temp_file.name
159
+ else:
160
+ filepath = os.path.join(temp_dir, filename)
161
+
162
+ with open(filepath, "w") as f:
163
+ f.write(content)
164
+
165
+ return f"File saved to {filepath}. You can read this file to process its contents."
166
+
167
+
168
+ @tool
169
+ def download_file_from_url(url: str, filename: Optional[str] = None) -> str:
170
+ """
171
+ Download a file from a URL and save it to a temporary location.
172
+ Args:
173
+ url (str): the URL of the file to download.
174
+ filename (str, optional): the name of the file. If not provided, a random name file will be created.
175
+ """
176
+ try:
177
+ # Parse URL to get filename if not provided
178
+ if not filename:
179
+ path = urlparse(url).path
180
+ filename = os.path.basename(path)
181
+ if not filename:
182
+ filename = f"downloaded_{uuid.uuid4().hex[:8]}"
183
+
184
+ # Create temporary file
185
+ temp_dir = tempfile.gettempdir()
186
+ filepath = os.path.join(temp_dir, filename)
187
+
188
+ # Download the file
189
+ response = requests.get(url, stream=True)
190
+ response.raise_for_status()
191
+
192
+ # Save the file
193
+ with open(filepath, "wb") as f:
194
+ for chunk in response.iter_content(chunk_size=8192):
195
+ f.write(chunk)
196
+
197
+ return f"File downloaded to {filepath}. You can read this file to process its contents."
198
+ except Exception as e:
199
+ return f"Error downloading file: {str(e)}"
200
+
201
+
202
+ @tool
203
+ def extract_text_from_image(image_path: str) -> str:
204
+ """
205
+ Extract text from an image using OCR library pytesseract (if available).
206
+ Args:
207
+ image_path (str): the path to the image file.
208
+ """
209
+ try:
210
+ # Open the image
211
+ image = Image.open(image_path)
212
+
213
+ # Extract text from the image
214
+ text = pytesseract.image_to_string(image)
215
+
216
+ return f"Extracted text from image:\n\n{text}"
217
+ except Exception as e:
218
+ return f"Error extracting text from image: {str(e)}"
219
+
220
+
221
+ @tool
222
+ def analyze_csv_file(file_path: str, query: str) -> str:
223
+ """
224
+ Analyze a CSV file using pandas and answer a question about it.
225
+ Args:
226
+ file_path (str): the path to the CSV file.
227
+ query (str): Question about the data
228
+ """
229
+ try:
230
+ # Read the CSV file
231
+ df = pd.read_csv(file_path)
232
+
233
+ # Run various analyses based on the query
234
+ result = f"CSV file loaded with {len(df)} rows and {len(df.columns)} columns.\n"
235
+ result += f"Columns: {', '.join(df.columns)}\n\n"
236
+
237
+ # Add summary statistics
238
+ result += "Summary statistics:\n"
239
+ result += str(df.describe())
240
+
241
+ return result
242
+
243
+ except Exception as e:
244
+ return f"Error analyzing CSV file: {str(e)}"
245
+
246
+
247
+ @tool
248
+ def analyze_excel_file(file_path: str, query: str) -> str:
249
+ """
250
+ Analyze an Excel file using pandas and answer a question about it.
251
+ Args:
252
+ file_path (str): the path to the Excel file.
253
+ query (str): Question about the data
254
+ """
255
+ try:
256
+ # Read the Excel file
257
+ df = pd.read_excel(file_path)
258
+
259
+ # Run various analyses based on the query
260
+ result = (
261
+ f"Excel file loaded with {len(df)} rows and {len(df.columns)} columns.\n"
262
+ )
263
+ result += f"Columns: {', '.join(df.columns)}\n\n"
264
+
265
+ # Add summary statistics
266
+ result += "Summary statistics:\n"
267
+ result += str(df.describe())
268
+
269
+ return result
270
+
271
+ except Exception as e:
272
+ return f"Error analyzing Excel file: {str(e)}"
273
+
274
+
275
+ doc_tools = [save_and_read_file,download_file_from_url,extract_text_from_image,analyze_csv_file,analyze_excel_file]
276
+
277
+ ######################### Code tools #########################
278
+
279
+ code_tools = [execute_code_multilang]
280
+
281
+ ######################### Image tools #########################
282
+
283
+ image_tools = [analyze_image, transform_image, draw_on_image, generate_simple_image, combine_images]
284
+
285
+ ######################### Final t