JPM34 commited on
Commit
3b013ba
·
1 Parent(s): 062f377

Created distincts file for langchain and smolagent tools

Browse files
tools_audio.py → tools_langchain_audio.py RENAMED
File without changes
tools_langchain_browser.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from bs4 import BeautifulSoup
2
+
3
+ from langchain_community.document_loaders import WikipediaLoader
4
+ from langchain_community.document_loaders import ArxivLoader
5
+ from langchain_community.tools.tavily_search import TavilySearchResults
6
+
7
+ from langchain_core.tools import tool
8
+ # from smolagents import tool
9
+
10
+ from playwright.sync_api import sync_playwright
11
+
12
+
13
+ @tool
14
+ def wiki_search(query: str) -> str:
15
+ """Search Wikipedia for a query and return maximum 2 results.
16
+ Args:
17
+ query: The search query."""
18
+ search_docs = WikipediaLoader(query=query, load_max_docs=2).load()
19
+ formatted_search_docs = "\n\n---\n\n".join(
20
+ [
21
+ f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>'
22
+ for doc in search_docs
23
+ ]
24
+ )
25
+ return {"wiki_results": formatted_search_docs}
26
+
27
+
28
+ @tool
29
+ def web_search(query: str) -> str:
30
+ """Search Tavily for a query and return maximum 3 results.
31
+ Args:
32
+ query: The search query."""
33
+ search_docs = TavilySearchResults(max_results=3).invoke(query=query)
34
+ formatted_search_docs = "\n\n---\n\n".join(
35
+ [
36
+ f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>'
37
+ for doc in search_docs
38
+ ]
39
+ )
40
+ return {"web_results": formatted_search_docs}
41
+
42
+
43
+ @tool
44
+ def arxiv_search(query: str) -> str:
45
+ """Search Arxiv for a query and return maximum 3 result.
46
+ Args:
47
+ query: The search query."""
48
+ search_docs = ArxivLoader(query=query, load_max_docs=3).load()
49
+ formatted_search_docs = "\n\n---\n\n".join(
50
+ [
51
+ f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content[:1000]}\n</Document>'
52
+ for doc in search_docs
53
+ ]
54
+ )
55
+ return {"arxiv_results": formatted_search_docs}
56
+
57
+
58
+ @tool
59
+ def website_scrape(url: str, question: str) -> str:
60
+ """Scrapes a website and returns the text.
61
+ Args:
62
+ url: the URL to the website to scrape.
63
+ question: the question to answer when searching for the answer in the website.
64
+ Returns:
65
+ str: The text of the website.
66
+ """
67
+
68
+ with sync_playwright() as p:
69
+ browser = p.chromium.launch(headless=True)
70
+ page = browser.new_page()
71
+ page.goto(url)
72
+ html_content = page.content()
73
+ browser.close()
74
+
75
+ soup = BeautifulSoup(html_content, "html.parser")
76
+
77
+ # Extract text from the website
78
+ text = soup.get_text()
79
+
80
+ return text
tools_code.py → tools_langchain_code.py RENAMED
File without changes
tools_doc.py → tools_langchain_doc.py RENAMED
File without changes
tools_img.py → tools_langchain_img.py RENAMED
File without changes
tools_maths.py → tools_langchain_maths.py RENAMED
File without changes
tools_video.py → tools_langchain_video.py RENAMED
File without changes
tools_smolagent_audio.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Audio Transcription Tool
2
+ import os
3
+
4
+ from google import genai
5
+ from google.genai import types
6
+ from smolagents import tool
7
+
8
+
9
+ @tool
10
+ def transcribe_audio(audio_file_path: str, mime_type: str) -> str:
11
+ """Transcribes an audio file using Gemini's audio capabilities.
12
+ Args:
13
+ audio_file_path (str): the path to the audio file to transcribe.
14
+ mime_type (str): the mime type of the audio file.
15
+ Returns:
16
+ str: The transcript of the audio file.
17
+ """
18
+ try:
19
+ # Initialize the model
20
+ client = genai.Client(api_key=os.getenv("GEMINI_KEY"))
21
+ model = "models/gemini-1.5-flash-8b"
22
+
23
+ # Read and encode the audio file
24
+ with open(audio_file_path, "rb") as audio_file:
25
+ audio_data = audio_file.read()
26
+
27
+ # Create the content with audio data
28
+ contents = types.Content(
29
+ parts=[
30
+ types.Part.from_bytes(
31
+ data=audio_data,
32
+ mime_type=mime_type,
33
+ ),
34
+ types.Part(text="Please transcribe this audio file."),
35
+ ]
36
+ )
37
+
38
+ # Generate transcription
39
+ response = client.models.generate_content(
40
+ model=model, contents=contents
41
+ )
42
+ return response.text
43
+ except Exception as e:
44
+ return f"Error transcribing audio: {str(e)}"
tools_browser.py → tools_smolagent_browser.py RENAMED
@@ -4,8 +4,7 @@ from langchain_community.document_loaders import WikipediaLoader
4
  from langchain_community.document_loaders import ArxivLoader
5
  from langchain_community.tools.tavily_search import TavilySearchResults
6
 
7
- from langchain_core.tools import tool
8
- # from smolagents import tool
9
 
10
  from playwright.sync_api import sync_playwright
11
 
@@ -56,7 +55,7 @@ def arxiv_search(query: str) -> str:
56
 
57
 
58
  @tool
59
- def website_scrape(url: str, question: str) -> str:
60
  """Scrapes a website and returns the text.
61
  Args:
62
  url (str): the URL to the website to scrape.
 
4
  from langchain_community.document_loaders import ArxivLoader
5
  from langchain_community.tools.tavily_search import TavilySearchResults
6
 
7
+ from smolagents import tool
 
8
 
9
  from playwright.sync_api import sync_playwright
10
 
 
55
 
56
 
57
  @tool
58
+ def website_scrape(url: str) -> str:
59
  """Scrapes a website and returns the text.
60
  Args:
61
  url (str): the URL to the website to scrape.
tools_smolagent_code.py ADDED
@@ -0,0 +1,410 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import base64
2
+ import contextlib
3
+ import io
4
+ import os
5
+ import subprocess
6
+ import sqlite3
7
+ import tempfile
8
+ import traceback
9
+ import uuid
10
+
11
+ from smolagents import tool
12
+ from typing import Dict, Any
13
+
14
+ # Imports for Python Code interpreter
15
+ import numpy as np
16
+ import pandas as pd
17
+ import matplotlib.pyplot as plt
18
+ from PIL import Image
19
+
20
+
21
+ class CodeInterpreter:
22
+ def __init__(
23
+ self,
24
+ allowed_modules=None,
25
+ max_execution_time=30,
26
+ working_directory=None,
27
+ ):
28
+ """Initialize the code interpreter with safety measures."""
29
+ self.allowed_modules = allowed_modules or [
30
+ "numpy",
31
+ "pandas",
32
+ "matplotlib",
33
+ "scipy",
34
+ "sklearn",
35
+ "math",
36
+ "random",
37
+ "statistics",
38
+ "datetime",
39
+ "collections",
40
+ "itertools",
41
+ "functools",
42
+ "operator",
43
+ "re",
44
+ "json",
45
+ "sympy",
46
+ "networkx",
47
+ "nltk",
48
+ "PIL",
49
+ "pytesseract",
50
+ "cmath",
51
+ "uuid",
52
+ "tempfile",
53
+ "requests",
54
+ "urllib",
55
+ ]
56
+ self.max_execution_time = max_execution_time
57
+ self.working_directory = working_directory or os.path.join(os.getcwd())
58
+ if not os.path.exists(self.working_directory):
59
+ os.makedirs(self.working_directory)
60
+
61
+ self.globals = {
62
+ "__builtins__": __builtins__,
63
+ "np": np,
64
+ "pd": pd,
65
+ "plt": plt,
66
+ "Image": Image,
67
+ }
68
+ self.temp_sqlite_db = os.path.join(
69
+ tempfile.gettempdir(), "code_exec.db"
70
+ )
71
+
72
+ def execute_code(
73
+ self, code: str, language: str = "python"
74
+ ) -> Dict[str, Any]:
75
+ """Execute the provided code in the selected programming language."""
76
+ language = language.lower()
77
+ execution_id = str(uuid.uuid4())
78
+
79
+ result = {
80
+ "execution_id": execution_id,
81
+ "status": "error",
82
+ "stdout": "",
83
+ "stderr": "",
84
+ "result": None,
85
+ "plots": [],
86
+ "dataframes": [],
87
+ }
88
+
89
+ try:
90
+ if language == "python":
91
+ return self._execute_python(code, execution_id)
92
+ elif language == "bash":
93
+ return self._execute_bash(code, execution_id)
94
+ elif language == "sql":
95
+ return self._execute_sql(code, execution_id)
96
+ elif language == "c":
97
+ return self._execute_c(code, execution_id)
98
+ elif language == "java":
99
+ return self._execute_java(code, execution_id)
100
+ else:
101
+ result["stderr"] = f"Unsupported language: {language}"
102
+ except Exception as e:
103
+ result["stderr"] = str(e)
104
+
105
+ return result
106
+
107
+ def _execute_python(self, code: str, execution_id: str) -> dict:
108
+ output_buffer = io.StringIO()
109
+ error_buffer = io.StringIO()
110
+ result = {
111
+ "execution_id": execution_id,
112
+ "status": "error",
113
+ "stdout": "",
114
+ "stderr": "",
115
+ "result": None,
116
+ "plots": [],
117
+ "dataframes": [],
118
+ }
119
+
120
+ try:
121
+ exec_dir = os.path.join(self.working_directory, execution_id)
122
+ os.makedirs(exec_dir, exist_ok=True)
123
+ plt.switch_backend("Agg")
124
+
125
+ with (
126
+ contextlib.redirect_stdout(output_buffer),
127
+ contextlib.redirect_stderr(error_buffer),
128
+ ):
129
+ exec_result = exec(code, self.globals)
130
+
131
+ if plt.get_fignums():
132
+ for i, fig_num in enumerate(plt.get_fignums()):
133
+ fig = plt.figure(fig_num)
134
+ img_path = os.path.join(exec_dir, f"plot_{i}.png")
135
+ fig.savefig(img_path)
136
+ with open(img_path, "rb") as img_file:
137
+ img_data = base64.b64encode(
138
+ img_file.read()
139
+ ).decode("utf-8")
140
+ result["plots"].append(
141
+ {"figure_number": fig_num, "data": img_data}
142
+ )
143
+
144
+ for var_name, var_value in self.globals.items():
145
+ if (
146
+ isinstance(var_value, pd.DataFrame)
147
+ and len(var_value) > 0
148
+ ):
149
+ result["dataframes"].append(
150
+ {
151
+ "name": var_name,
152
+ "head": var_value.head().to_dict(),
153
+ "shape": var_value.shape,
154
+ "dtypes": str(var_value.dtypes),
155
+ }
156
+ )
157
+
158
+ result["status"] = "success"
159
+ result["stdout"] = output_buffer.getvalue()
160
+ result["result"] = exec_result
161
+
162
+ except Exception as e:
163
+ result["status"] = "error"
164
+ result["stderr"] = (
165
+ f"{error_buffer.getvalue()}\n{traceback.format_exc()}"
166
+ )
167
+
168
+ return result
169
+
170
+ def _execute_bash(self, code: str, execution_id: str) -> dict:
171
+ try:
172
+ completed = subprocess.run(
173
+ code,
174
+ shell=True,
175
+ capture_output=True,
176
+ text=True,
177
+ timeout=self.max_execution_time,
178
+ )
179
+ return {
180
+ "execution_id": execution_id,
181
+ "status": "success" if completed.returncode == 0 else "error",
182
+ "stdout": completed.stdout,
183
+ "stderr": completed.stderr,
184
+ "result": None,
185
+ "plots": [],
186
+ "dataframes": [],
187
+ }
188
+ except subprocess.TimeoutExpired:
189
+ return {
190
+ "execution_id": execution_id,
191
+ "status": "error",
192
+ "stdout": "",
193
+ "stderr": "Execution timed out.",
194
+ "result": None,
195
+ "plots": [],
196
+ "dataframes": [],
197
+ }
198
+
199
+ def _execute_sql(self, code: str, execution_id: str) -> dict:
200
+ result = {
201
+ "execution_id": execution_id,
202
+ "status": "error",
203
+ "stdout": "",
204
+ "stderr": "",
205
+ "result": None,
206
+ "plots": [],
207
+ "dataframes": [],
208
+ }
209
+ try:
210
+ conn = sqlite3.connect(self.temp_sqlite_db)
211
+ cur = conn.cursor()
212
+ cur.execute(code)
213
+ if code.strip().lower().startswith("select"):
214
+ columns = [description[0] for description in cur.description]
215
+ rows = cur.fetchall()
216
+ df = pd.DataFrame(rows, columns=columns)
217
+ result["dataframes"].append(
218
+ {
219
+ "name": "query_result",
220
+ "head": df.head().to_dict(),
221
+ "shape": df.shape,
222
+ "dtypes": str(df.dtypes),
223
+ }
224
+ )
225
+ else:
226
+ conn.commit()
227
+
228
+ result["status"] = "success"
229
+ result["stdout"] = "Query executed successfully."
230
+
231
+ except Exception as e:
232
+ result["stderr"] = str(e)
233
+ finally:
234
+ conn.close()
235
+
236
+ return result
237
+
238
+ def _execute_c(self, code: str, execution_id: str) -> dict:
239
+ temp_dir = tempfile.mkdtemp()
240
+ source_path = os.path.join(temp_dir, "program.c")
241
+ binary_path = os.path.join(temp_dir, "program")
242
+
243
+ try:
244
+ with open(source_path, "w") as f:
245
+ f.write(code)
246
+
247
+ compile_proc = subprocess.run(
248
+ ["gcc", source_path, "-o", binary_path],
249
+ capture_output=True,
250
+ text=True,
251
+ timeout=self.max_execution_time,
252
+ )
253
+ if compile_proc.returncode != 0:
254
+ return {
255
+ "execution_id": execution_id,
256
+ "status": "error",
257
+ "stdout": compile_proc.stdout,
258
+ "stderr": compile_proc.stderr,
259
+ "result": None,
260
+ "plots": [],
261
+ "dataframes": [],
262
+ }
263
+
264
+ run_proc = subprocess.run(
265
+ [binary_path],
266
+ capture_output=True,
267
+ text=True,
268
+ timeout=self.max_execution_time,
269
+ )
270
+ return {
271
+ "execution_id": execution_id,
272
+ "status": "success" if run_proc.returncode == 0 else "error",
273
+ "stdout": run_proc.stdout,
274
+ "stderr": run_proc.stderr,
275
+ "result": None,
276
+ "plots": [],
277
+ "dataframes": [],
278
+ }
279
+ except Exception as e:
280
+ return {
281
+ "execution_id": execution_id,
282
+ "status": "error",
283
+ "stdout": "",
284
+ "stderr": str(e),
285
+ "result": None,
286
+ "plots": [],
287
+ "dataframes": [],
288
+ }
289
+
290
+ def _execute_java(self, code: str, execution_id: str) -> dict:
291
+ temp_dir = tempfile.mkdtemp()
292
+ source_path = os.path.join(temp_dir, "Main.java")
293
+
294
+ try:
295
+ with open(source_path, "w") as f:
296
+ f.write(code)
297
+
298
+ compile_proc = subprocess.run(
299
+ ["javac", source_path],
300
+ capture_output=True,
301
+ text=True,
302
+ timeout=self.max_execution_time,
303
+ )
304
+ if compile_proc.returncode != 0:
305
+ return {
306
+ "execution_id": execution_id,
307
+ "status": "error",
308
+ "stdout": compile_proc.stdout,
309
+ "stderr": compile_proc.stderr,
310
+ "result": None,
311
+ "plots": [],
312
+ "dataframes": [],
313
+ }
314
+
315
+ run_proc = subprocess.run(
316
+ ["java", "-cp", temp_dir, "Main"],
317
+ capture_output=True,
318
+ text=True,
319
+ timeout=self.max_execution_time,
320
+ )
321
+ return {
322
+ "execution_id": execution_id,
323
+ "status": "success" if run_proc.returncode == 0 else "error",
324
+ "stdout": run_proc.stdout,
325
+ "stderr": run_proc.stderr,
326
+ "result": None,
327
+ "plots": [],
328
+ "dataframes": [],
329
+ }
330
+ except Exception as e:
331
+ return {
332
+ "execution_id": execution_id,
333
+ "status": "error",
334
+ "stdout": "",
335
+ "stderr": str(e),
336
+ "result": None,
337
+ "plots": [],
338
+ "dataframes": [],
339
+ }
340
+
341
+
342
+ @tool
343
+ def execute_code_multilang(code: str, language: str = "python") -> str:
344
+ """Execute code in multiple languages (Python, Bash, SQL, C, Java) and return results.
345
+ Args:
346
+ code (str): The source code to execute.
347
+ language (str): The language of the code. Supported: "python", "bash", "sql", "c", "java".
348
+ Returns:
349
+ A string summarizing the execution results (stdout, stderr, errors, plots, dataframes if any).
350
+ """
351
+ interpreter_instance = CodeInterpreter()
352
+ supported_languages = ["python", "bash", "sql", "c", "java"]
353
+ language = language.lower()
354
+
355
+ if language not in supported_languages:
356
+ return f"❌ Unsupported language: {language}. Supported languages are: {', '.join(supported_languages)}"
357
+
358
+ result = interpreter_instance.execute_code(code, language=language)
359
+
360
+ response = []
361
+
362
+ if result["status"] == "success":
363
+ response.append(
364
+ f"✅ Code executed successfully in **{language.upper()}**"
365
+ )
366
+
367
+ if result.get("stdout"):
368
+ response.append(
369
+ "\n**Standard Output:**\n```\n"
370
+ + result["stdout"].strip()
371
+ + "\n```"
372
+ )
373
+
374
+ if result.get("stderr"):
375
+ response.append(
376
+ "\n**Standard Error (if any):**\n```\n"
377
+ + result["stderr"].strip()
378
+ + "\n```"
379
+ )
380
+
381
+ if result.get("result") is not None:
382
+ response.append(
383
+ "\n**Execution Result:**\n```\n"
384
+ + str(result["result"]).strip()
385
+ + "\n```"
386
+ )
387
+
388
+ if result.get("dataframes"):
389
+ for df_info in result["dataframes"]:
390
+ response.append(
391
+ f"\n**DataFrame `{df_info['name']}` (Shape: {df_info['shape']})**"
392
+ )
393
+ df_preview = pd.DataFrame(df_info["head"])
394
+ response.append(
395
+ "First 5 rows:\n```\n" + str(df_preview) + "\n```"
396
+ )
397
+
398
+ if result.get("plots"):
399
+ response.append(
400
+ f"\n**Generated {len(result['plots'])} plot(s)** (Image data returned separately)"
401
+ )
402
+
403
+ else:
404
+ response.append(f"❌ Code execution failed in **{language.upper()}**")
405
+ if result.get("stderr"):
406
+ response.append(
407
+ "\n**Error Log:**\n```\n" + result["stderr"].strip() + "\n```"
408
+ )
409
+
410
+ return "\n".join(response)
tools_smolagent_doc.py ADDED
@@ -0,0 +1,275 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import pandas as pd
3
+ import re
4
+ import requests
5
+ import tempfile
6
+ import uuid
7
+
8
+ from smolagents import tool
9
+
10
+ from google import genai
11
+ from google.genai import types
12
+
13
+ # from smolagents import tool
14
+ from typing import Optional, Dict, Union
15
+
16
+
17
+ @tool
18
+ def save_and_read_file(content: str, filename: Optional[str] = None) -> str:
19
+ """
20
+ Save content to a temporary file and return the path.
21
+ Useful for processing files from the GAIA API.
22
+
23
+ Args:
24
+ content: The content to save to the file
25
+ filename: Optional filename, will generate a random name if not provided
26
+
27
+ Returns:
28
+ Path to the saved file
29
+ """
30
+ temp_dir = tempfile.gettempdir()
31
+ if filename is None:
32
+ temp_file = tempfile.NamedTemporaryFile(delete=False)
33
+ filepath = temp_file.name
34
+ else:
35
+ filepath = os.path.join(temp_dir, filename)
36
+
37
+ # Write content to the file
38
+ with open(filepath, "w") as f:
39
+ f.write(content)
40
+
41
+ return f"File saved to {filepath}. You can read this file to process its contents."
42
+
43
+
44
+ # File Download Tool
45
+ @tool
46
+ def download_file_from_url(
47
+ url: str, directory: str
48
+ ) -> Dict[str, Union[str, None]]:
49
+ """Downloads a file from a URL and saves it to a directory.
50
+ Args:
51
+ url (str): the URL to download the file from.
52
+ directory (str): the directory to save the file to.
53
+ Returns:
54
+ Dict[str, Union[str, None]]: A dictionary containing the file type and path.
55
+ """
56
+
57
+ try:
58
+ response = requests.get(url, stream=True, timeout=10)
59
+ response.raise_for_status()
60
+
61
+ content_type = response.headers.get("content-type", "").lower()
62
+
63
+ # Try to get filename from headers
64
+ filename = None
65
+ cd = response.headers.get("content-disposition", "")
66
+ match = re.search(r"filename\*=UTF-8\'\'(.+)", cd) or re.search(
67
+ r'filename="?([^"]+)"?', cd
68
+ )
69
+ if match:
70
+ filename = match.group(1)
71
+
72
+ # If not in headers, try URL
73
+ if not filename:
74
+ filename = os.path.basename(url.split("?")[0])
75
+
76
+ # Fallback to generated filename
77
+ if not filename:
78
+ extension = {
79
+ "image/jpeg": ".jpg",
80
+ "image/png": ".png",
81
+ "image/gif": ".gif",
82
+ "audio/wav": ".wav",
83
+ "audio/mpeg": ".mp3",
84
+ "video/mp4": ".mp4",
85
+ "text/plain": ".txt",
86
+ "text/csv": ".csv",
87
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": ".xlsx",
88
+ "application/vnd.ms-excel": ".xls",
89
+ "application/octet-stream": ".bin",
90
+ }.get(content_type, ".bin")
91
+ filename = f"downloaded_{uuid.uuid4().hex[:8]}{extension}"
92
+
93
+ os.makedirs(directory, exist_ok=True)
94
+ file_path = os.path.join(directory, filename)
95
+
96
+ with open(file_path, "wb") as f:
97
+ for chunk in response.iter_content(chunk_size=8192):
98
+ f.write(chunk)
99
+
100
+ # shutil.copy(file_path, os.getcwd())
101
+
102
+ if os.path.exists(file_path) and os.path.getsize(file_path) > 0:
103
+ return {"type": content_type, "path": file_path}
104
+ else:
105
+ return {
106
+ "type": "error",
107
+ "path": None,
108
+ "error": "Failed to save file",
109
+ }
110
+
111
+ except Exception as e:
112
+ return {
113
+ "type": "error",
114
+ "path": None,
115
+ "error": f"Error downloading file: {str(e)}",
116
+ }
117
+
118
+
119
+ @tool
120
+ def extract_text_from_image(image_path: str) -> str:
121
+ """
122
+ Extract text from an image using pytesseract (if available).
123
+
124
+ Args:
125
+ image_path: Path to the image file
126
+
127
+ Returns:
128
+ Extracted text or error message
129
+ """
130
+ try:
131
+ # Try to import pytesseract
132
+ import pytesseract
133
+ from PIL import Image
134
+
135
+ # Open the image
136
+ image = Image.open(image_path)
137
+
138
+ # Extract text
139
+ text = pytesseract.image_to_string(image)
140
+
141
+ return f"Extracted text from image:\n\n{text}"
142
+ except ImportError:
143
+ return "Error: pytesseract is not installed. Please install it with 'pip install pytesseract' and ensure Tesseract OCR is installed on your system."
144
+ except Exception as e:
145
+ return f"Error extracting text from image: {str(e)}"
146
+
147
+
148
+ # CSV Analysis Tool
149
+ @tool
150
+ def analyze_csv_file(file_path: str, query: str) -> str:
151
+ """Analyzes a CSV file and answers questions about its contents using Gemini.
152
+ Args:
153
+ file_path (str): the path to the CSV file to analyze.
154
+ query (str): the question to answer about the CSV file.
155
+ Returns:
156
+ str: The result of the analysis.
157
+ """
158
+ try:
159
+ # Read the CSV file
160
+ df = pd.read_csv(file_path)
161
+
162
+ # Initialize Gemini
163
+ client = genai.Client(api_key=os.getenv("GEMINI_KEY"))
164
+ model = "models/gemini-1.5-flash-8b"
165
+
166
+ # Convert DataFrame to a string representation
167
+ df_str = df.to_string()
168
+
169
+ # Create a prompt for Gemini
170
+ prompt = f"""Analyze this CSV data and provide insights:
171
+ Dimensions: {len(df)} rows × {len(df.columns)} columns
172
+ Data:
173
+ {df_str}
174
+ Please provide:
175
+ 1. A summary of the data structure and content
176
+ 2. Key patterns and insights
177
+ 3. Potential data quality issues
178
+ 4. Suggestions for analysis
179
+ User Query: {query}
180
+ Please format your response in a clear, structured way with sections and bullet points."""
181
+
182
+ # Get analysis from Gemini
183
+ response = client.models.generate_content(
184
+ model=model,
185
+ contents=types.Content(
186
+ parts=[
187
+ types.Part(text=df_str),
188
+ types.Part(text=prompt),
189
+ ]
190
+ ),
191
+ )
192
+
193
+ result = f"CSV file loaded with {len(df)} rows and {len(df.columns)} columns.\n\n"
194
+ result += response.text
195
+
196
+ return result
197
+ except Exception as e:
198
+ return f"Error analyzing CSV file: {str(e)}"
199
+
200
+
201
+ # Excel Analysis Tool
202
+ @tool
203
+ def analyze_excel_file(file_path: str, query: str) -> str:
204
+ """Analyzes an Excel file and answers questions about its contents using Gemini.
205
+ Args:
206
+ file_path (str): the path to the Excel file to analyze.
207
+ query (str): the question to answer about the Excel file.
208
+ Returns:
209
+ str: The result of the analysis.
210
+ """
211
+ try:
212
+ # Read all sheets from the Excel file
213
+ excel_file = pd.ExcelFile(file_path)
214
+ sheet_names = excel_file.sheet_names
215
+
216
+ # Initialize Gemini
217
+ client = genai.Client(api_key=os.getenv("GEMINI_KEY"))
218
+ model = "models/gemini-1.5-flash-8b"
219
+
220
+ result = f"Excel file loaded with {len(sheet_names)} sheets: {', '.join(sheet_names)}\n\n"
221
+
222
+ # Analyze each sheet
223
+ for sheet_name in sheet_names:
224
+ df = pd.read_excel(file_path, sheet_name=sheet_name)
225
+
226
+ # Convert DataFrame to a string representation
227
+ df_str = df.to_string()
228
+
229
+ # Create a prompt for Gemini
230
+ prompt = f"""Analyze this Excel sheet data and provide insights:
231
+ Sheet Name: {sheet_name}
232
+ Dimensions: {len(df)} rows × {len(df.columns)} columns
233
+ Data:
234
+ {df_str}
235
+ Please provide:
236
+ 1. A summary of the data structure and content
237
+ 2. Key patterns and insights
238
+ 3. Potential data quality issues
239
+ 4. Suggestions for analysis
240
+ User Query: {query}
241
+ Please format your response in a clear, structured way with sections and bullet points."""
242
+
243
+ # Get analysis from Gemini
244
+ response = client.models.generate_content(
245
+ model=model,
246
+ contents=types.Content(
247
+ parts=[types.Part(text=df_str), types.Part(text=prompt)]
248
+ ),
249
+ )
250
+
251
+ result += f"=== Sheet: {sheet_name} ===\n"
252
+ result += response.text + "\n"
253
+ result += "=" * 50 + "\n\n"
254
+
255
+ return result
256
+ except Exception as e:
257
+ return f"Error analyzing Excel file: {str(e)}"
258
+
259
+
260
+ @tool
261
+ def read_file(filepath: str) -> str:
262
+ """Reads the content of a text file.
263
+ Args:
264
+ filepath (str): the path to the file to read.
265
+ Returns:
266
+ str: The content of the file.
267
+ """
268
+ try:
269
+ with open(filepath, "r", encoding="utf-8") as file:
270
+ content = file.read()
271
+ return content
272
+ except FileNotFoundError:
273
+ return f"File not found: {filepath}"
274
+ except IOError as e:
275
+ return f"Error reading file: {str(e)}"
tools_smolagent_img.py ADDED
@@ -0,0 +1,332 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import io
3
+ import base64
4
+ import numpy as np
5
+ import uuid
6
+
7
+ from PIL import Image, ImageDraw, ImageFont, ImageEnhance, ImageFilter
8
+ from smolagents import tool
9
+
10
+ # from smolagents import tool
11
+ from typing import Any, Dict, List, Optional
12
+
13
+
14
+ @tool
15
+ def analyze_image(image_base64: str) -> Dict[str, Any]:
16
+ """
17
+ Analyze basic properties of an image (size, mode, color analysis, thumbnail preview).
18
+ Args:
19
+ image_base64 (str): Base64 encoded image string
20
+ Returns:
21
+ Dictionary with analysis result
22
+ """
23
+ try:
24
+ img = decode_image(image_base64)
25
+ width, height = img.size
26
+ mode = img.mode
27
+
28
+ if mode in ("RGB", "RGBA"):
29
+ arr = np.array(img)
30
+ avg_colors = arr.mean(axis=(0, 1))
31
+ dominant = ["Red", "Green", "Blue"][np.argmax(avg_colors[:3])]
32
+ brightness = avg_colors.mean()
33
+ color_analysis = {
34
+ "average_rgb": avg_colors.tolist(),
35
+ "brightness": brightness,
36
+ "dominant_color": dominant,
37
+ }
38
+ else:
39
+ color_analysis = {"note": f"No color analysis for mode {mode}"}
40
+
41
+ thumbnail = img.copy()
42
+ thumbnail.thumbnail((100, 100))
43
+ thumb_path = save_image(thumbnail, "thumbnails")
44
+ thumbnail_base64 = encode_image(thumb_path)
45
+
46
+ return {
47
+ "dimensions": (width, height),
48
+ "mode": mode,
49
+ "color_analysis": color_analysis,
50
+ "thumbnail": thumbnail_base64,
51
+ }
52
+ except Exception as e:
53
+ return {"error": str(e)}
54
+
55
+
56
+ @tool
57
+ def transform_image(
58
+ image_base64: str, operation: str, params: Optional[Dict[str, Any]] = None
59
+ ) -> Dict[str, Any]:
60
+ """
61
+ Apply transformations: resize, rotate, crop, flip, brightness, contrast, blur, sharpen, grayscale.
62
+ Args:
63
+ image_base64 (str): Base64 encoded input image
64
+ operation (str): Transformation operation
65
+ params (Dict[str, Any], optional): Parameters for the operation
66
+ Returns:
67
+ Dictionary with transformed image (base64)
68
+ """
69
+ try:
70
+ img = decode_image(image_base64)
71
+ params = params or {}
72
+
73
+ if operation == "resize":
74
+ img = img.resize(
75
+ (
76
+ params.get("width", img.width // 2),
77
+ params.get("height", img.height // 2),
78
+ )
79
+ )
80
+ elif operation == "rotate":
81
+ img = img.rotate(params.get("angle", 90), expand=True)
82
+ elif operation == "crop":
83
+ img = img.crop(
84
+ (
85
+ params.get("left", 0),
86
+ params.get("top", 0),
87
+ params.get("right", img.width),
88
+ params.get("bottom", img.height),
89
+ )
90
+ )
91
+ elif operation == "flip":
92
+ if params.get("direction", "horizontal") == "horizontal":
93
+ img = img.transpose(Image.FLIP_LEFT_RIGHT)
94
+ else:
95
+ img = img.transpose(Image.FLIP_TOP_BOTTOM)
96
+ elif operation == "adjust_brightness":
97
+ img = ImageEnhance.Brightness(img).enhance(
98
+ params.get("factor", 1.5)
99
+ )
100
+ elif operation == "adjust_contrast":
101
+ img = ImageEnhance.Contrast(img).enhance(params.get("factor", 1.5))
102
+ elif operation == "blur":
103
+ img = img.filter(ImageFilter.GaussianBlur(params.get("radius", 2)))
104
+ elif operation == "sharpen":
105
+ img = img.filter(ImageFilter.SHARPEN)
106
+ elif operation == "grayscale":
107
+ img = img.convert("L")
108
+ else:
109
+ return {"error": f"Unknown operation: {operation}"}
110
+
111
+ result_path = save_image(img)
112
+ result_base64 = encode_image(result_path)
113
+ return {"transformed_image": result_base64}
114
+
115
+ except Exception as e:
116
+ return {"error": str(e)}
117
+
118
+
119
+ @tool
120
+ def draw_on_image(
121
+ image_base64: str, drawing_type: str, params: Dict[str, Any]
122
+ ) -> Dict[str, Any]:
123
+ """
124
+ Draw shapes (rectangle, circle, line) or text onto an image.
125
+ Args:
126
+ image_base64 (str): Base64 encoded input image
127
+ drawing_type (str): Drawing type
128
+ params (Dict[str, Any]): Drawing parameters
129
+ Returns:
130
+ Dictionary with result image (base64)
131
+ """
132
+ try:
133
+ img = decode_image(image_base64)
134
+ draw = ImageDraw.Draw(img)
135
+ color = params.get("color", "red")
136
+
137
+ if drawing_type == "rectangle":
138
+ draw.rectangle(
139
+ [
140
+ params["left"],
141
+ params["top"],
142
+ params["right"],
143
+ params["bottom"],
144
+ ],
145
+ outline=color,
146
+ width=params.get("width", 2),
147
+ )
148
+ elif drawing_type == "circle":
149
+ x, y, r = params["x"], params["y"], params["radius"]
150
+ draw.ellipse(
151
+ (x - r, y - r, x + r, y + r),
152
+ outline=color,
153
+ width=params.get("width", 2),
154
+ )
155
+ elif drawing_type == "line":
156
+ draw.line(
157
+ (
158
+ params["start_x"],
159
+ params["start_y"],
160
+ params["end_x"],
161
+ params["end_y"],
162
+ ),
163
+ fill=color,
164
+ width=params.get("width", 2),
165
+ )
166
+ elif drawing_type == "text":
167
+ font_size = params.get("font_size", 20)
168
+ try:
169
+ font = ImageFont.truetype("arial.ttf", font_size)
170
+ except IOError:
171
+ font = ImageFont.load_default()
172
+ draw.text(
173
+ (params["x"], params["y"]),
174
+ params.get("text", "Text"),
175
+ fill=color,
176
+ font=font,
177
+ )
178
+ else:
179
+ return {"error": f"Unknown drawing type: {drawing_type}"}
180
+
181
+ result_path = save_image(img)
182
+ result_base64 = encode_image(result_path)
183
+ return {"result_image": result_base64}
184
+
185
+ except Exception as e:
186
+ return {"error": str(e)}
187
+
188
+
189
+ @tool
190
+ def generate_simple_image(
191
+ image_type: str,
192
+ width: int = 500,
193
+ height: int = 500,
194
+ params: Optional[Dict[str, Any]] = None,
195
+ ) -> Dict[str, Any]:
196
+ """
197
+ Generate a simple image (gradient, noise, pattern, chart).
198
+ Args:
199
+ image_type (str): Type of image
200
+ width (int): Width of image,
201
+ height (int): Height of image,
202
+ params (Dict[str, Any], optional): Specific parameters
203
+ Returns:
204
+ Dictionary with generated image (base64)
205
+ """
206
+ try:
207
+ params = params or {}
208
+
209
+ if image_type == "gradient":
210
+ direction = params.get("direction", "horizontal")
211
+ start_color = params.get("start_color", (255, 0, 0))
212
+ end_color = params.get("end_color", (0, 0, 255))
213
+
214
+ img = Image.new("RGB", (width, height))
215
+ draw = ImageDraw.Draw(img)
216
+
217
+ if direction == "horizontal":
218
+ for x in range(width):
219
+ r = int(
220
+ start_color[0]
221
+ + (end_color[0] - start_color[0]) * x / width
222
+ )
223
+ g = int(
224
+ start_color[1]
225
+ + (end_color[1] - start_color[1]) * x / width
226
+ )
227
+ b = int(
228
+ start_color[2]
229
+ + (end_color[2] - start_color[2]) * x / width
230
+ )
231
+ draw.line([(x, 0), (x, height)], fill=(r, g, b))
232
+ else:
233
+ for y in range(height):
234
+ r = int(
235
+ start_color[0]
236
+ + (end_color[0] - start_color[0]) * y / height
237
+ )
238
+ g = int(
239
+ start_color[1]
240
+ + (end_color[1] - start_color[1]) * y / height
241
+ )
242
+ b = int(
243
+ start_color[2]
244
+ + (end_color[2] - start_color[2]) * y / height
245
+ )
246
+ draw.line([(0, y), (width, y)], fill=(r, g, b))
247
+
248
+ elif image_type == "noise":
249
+ noise_array = np.random.randint(
250
+ 0, 256, (height, width, 3), dtype=np.uint8
251
+ )
252
+ img = Image.fromarray(noise_array, "RGB")
253
+
254
+ else:
255
+ return {"error": f"Unsupported image_type {image_type}"}
256
+
257
+ result_path = save_image(img)
258
+ result_base64 = encode_image(result_path)
259
+ return {"generated_image": result_base64}
260
+
261
+ except Exception as e:
262
+ return {"error": str(e)}
263
+
264
+
265
+ @tool
266
+ def combine_images(
267
+ images_base64: List[str],
268
+ operation: str,
269
+ 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): Parameters
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)}
311
+
312
+
313
+ # Helper functions for image processing
314
+ def encode_image(image_path: str) -> str:
315
+ """Convert an image file to base64 string."""
316
+ with open(image_path, "rb") as image_file:
317
+ return base64.b64encode(image_file.read()).decode("utf-8")
318
+
319
+
320
+ def decode_image(base64_string: str) -> Image.Image:
321
+ """Convert a base64 string to a PIL Image."""
322
+ image_data = base64.b64decode(base64_string)
323
+ return Image.open(io.BytesIO(image_data))
324
+
325
+
326
+ def save_image(image: Image.Image, directory: str = "image_outputs") -> str:
327
+ """Save a PIL Image to disk and return the path."""
328
+ os.makedirs(directory, exist_ok=True)
329
+ image_id = str(uuid.uuid4())
330
+ image_path = os.path.join(directory, f"{image_id}.png")
331
+ image.save(image_path)
332
+ return image_path
tools_smolagent_maths.py ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cmath
2
+
3
+ from smolagents import tool
4
+
5
+
6
+ @tool
7
+ def multiply(a: int, b: int) -> int:
8
+ """Multiply two numbers.
9
+ Args:
10
+ a: first int
11
+ b: second int
12
+ """
13
+ return a * b
14
+
15
+
16
+ @tool
17
+ def add(a: int, b: int) -> int:
18
+ """Add two numbers.
19
+
20
+ Args:
21
+ a: first int
22
+ b: second int
23
+ """
24
+ return a + b
25
+
26
+
27
+ @tool
28
+ def subtract(a: int, b: int) -> int:
29
+ """Subtract two numbers.
30
+
31
+ Args:
32
+ a: first int
33
+ b: second int
34
+ """
35
+ return a - b
36
+
37
+
38
+ @tool
39
+ def divide(a: int, b: int) -> int:
40
+ """Divide two numbers.
41
+
42
+ Args:
43
+ a: first int
44
+ b: second int
45
+ """
46
+ if b == 0:
47
+ raise ValueError("Cannot divide by zero.")
48
+ return a / b
49
+
50
+
51
+ @tool
52
+ def modulus(a: int, b: int) -> int:
53
+ """Get the modulus of two numbers.
54
+
55
+ Args:
56
+ a: first int
57
+ b: second int
58
+ """
59
+ return a % b
60
+
61
+
62
+ @tool
63
+ def power(a: float, b: float) -> float:
64
+ """
65
+ Get the power of two numbers.
66
+ Args:
67
+ a (float): the first number
68
+ b (float): the second number
69
+ """
70
+ return a**b
71
+
72
+
73
+ # @tool
74
+ # def square_root(a: float) -> float | complex:
75
+ # """
76
+ # Get the square root of a number.
77
+ # Args:
78
+ # a (float): the number to get the square root of
79
+ # """
80
+ # print(a)
81
+ # if a >= 0:
82
+ # return a**0.5
83
+ # return cmath.sqrt(a)
tools_smolagent_video.py ADDED
@@ -0,0 +1,279 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import imageio
2
+ import os
3
+ import re
4
+ import tempfile
5
+ import yt_dlp
6
+
7
+ from datetime import timedelta
8
+ from google import genai
9
+ from google.genai import types
10
+
11
+ from smolagents import tool
12
+ from typing import List, Optional
13
+ from youtube_transcript_api import YouTubeTranscriptApi
14
+
15
+
16
+ # YouTube Video Review Tool
17
+ @tool
18
+ def review_youtube_video(url: str, question: str) -> str:
19
+ """Reviews a YouTube video and answers a specific question about that video.
20
+ Args:
21
+ url (str): the URL to the YouTube video.
22
+ question (str): The question you are asking about the video
23
+ Returns:
24
+ str: The answer to the question
25
+ """
26
+ try:
27
+ client = genai.Client(api_key=os.getenv("GEMINI_KEY"))
28
+ model = "models/gemini-1.5-flash-8b"
29
+
30
+ response = client.models.generate_content(
31
+ model=model,
32
+ contents=types.Content(
33
+ parts=[
34
+ types.Part(file_data=types.FileData(file_uri=url)),
35
+ types.Part(text=question),
36
+ ]
37
+ ),
38
+ )
39
+ return response.text
40
+ except Exception as e:
41
+ return f"Error asking {model} about video: {str(e)}"
42
+
43
+
44
+ @tool
45
+ def use_vision_model(
46
+ question: str, image_paths: List[str], mime_type: str
47
+ ) -> str:
48
+ """Use a Vision Model to answer a question about a set of images.
49
+ Args:
50
+ question (str): The question you are asking about the images.
51
+ image_paths (List[str]): The paths to the images to use for the question.
52
+ mime_type (str): The mime type of the image.
53
+ Returns:
54
+ str: The answer to the question
55
+ """
56
+ try:
57
+ client = genai.Client(api_key=os.getenv("GEMINI_KEY"))
58
+ model = "models/gemini-2.0-flash-001"
59
+
60
+ # Prepare the content parts
61
+ parts = []
62
+ for image_path in image_paths:
63
+ with open(image_path, "rb") as f:
64
+ image_bytes = f.read()
65
+
66
+ response = []
67
+
68
+ for chunk in client.models.generate_content_stream(
69
+ model=model,
70
+ contents=[
71
+ question,
72
+ types.Part.from_bytes(data=image_bytes, mime_type=mime_type),
73
+ ],
74
+ ):
75
+ response.append(chunk.text)
76
+
77
+ return " ".join(response)
78
+
79
+ except Exception as e:
80
+ return f"Error using vision model: {str(e)}"
81
+
82
+
83
+ # YouTube Frames to Images Tool
84
+ @tool
85
+ def video_frames_to_images(
86
+ url: str,
87
+ folder_name: str,
88
+ sample_interval_seconds: int = 5,
89
+ ) -> List[str]:
90
+ """Extracts frames from a video at specified intervals and saves them as images.
91
+ Args:
92
+ url (str): the URL to the video.
93
+ folder_name (str): the name of the folder to save the images to.
94
+ sample_interval_seconds (int): the interval between frames to sample.
95
+ Returns:
96
+ List[str]: A list of paths to the saved image files.
97
+ """
98
+ # Create a subdirectory for the frames
99
+ frames_dir = os.path.join(folder_name, "frames")
100
+ os.makedirs(frames_dir, exist_ok=True)
101
+
102
+ ydl_opts = {
103
+ "format": "bestvideo[height<=1080]+bestaudio/best[height<=1080]/best",
104
+ "outtmpl": os.path.join(folder_name, "video.%(ext)s"),
105
+ "quiet": True,
106
+ "noplaylist": True,
107
+ "merge_output_format": "mp4",
108
+ "force_ipv4": True,
109
+ }
110
+
111
+ try:
112
+ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
113
+ info = ydl.extract_info(url, download=True)
114
+ video_path = next(
115
+ (
116
+ os.path.join(folder_name, f)
117
+ for f in os.listdir(folder_name)
118
+ if f.endswith(".mp4")
119
+ ),
120
+ None,
121
+ )
122
+
123
+ if not video_path:
124
+ raise RuntimeError("Failed to download video as mp4")
125
+
126
+ reader = imageio.get_reader(video_path)
127
+ metadata = reader.get_meta_data()
128
+ fps = metadata.get("fps")
129
+
130
+ if fps is None:
131
+ reader.close()
132
+ raise RuntimeError(
133
+ "Unable to determine FPS from video metadata"
134
+ )
135
+
136
+ frame_interval = int(fps * sample_interval_seconds)
137
+ image_paths: List[str] = []
138
+
139
+ for idx, frame in enumerate(reader):
140
+ if idx % frame_interval == 0:
141
+ # Save frame as image
142
+ image_path = os.path.join(
143
+ frames_dir, f"frame_{idx:06d}.jpg"
144
+ )
145
+ imageio.imwrite(image_path, frame)
146
+ image_paths.append(image_path)
147
+
148
+ reader.close()
149
+ return image_paths
150
+
151
+ except Exception as e:
152
+ raise RuntimeError(f"Error processing video frames: {str(e)}") from e
153
+
154
+
155
+ @tool
156
+ def transcribe_youtube(url: str) -> str:
157
+ """Transcribes a YouTube video using YouTube Transcript API or Gemini as fallback.
158
+ Args:
159
+ url (str): the URL to the YouTube video.
160
+ Returns:
161
+ str: The transcript of the YouTube video.
162
+ """
163
+ try:
164
+ # First try using YouTube Transcript API
165
+ video_id = _extract_video_id(url)
166
+ if not video_id:
167
+ raise ValueError(f"Invalid YouTube URL: {url}")
168
+
169
+ try:
170
+ # Try to get transcript in English
171
+ transcript_chunks = YouTubeTranscriptApi.get_transcript(
172
+ video_id, languages=["en"]
173
+ )
174
+ # Combine all chunks into a single transcript with timestamps
175
+ transcript = ""
176
+ for chunk in transcript_chunks:
177
+ timestamp = str(timedelta(seconds=int(chunk["start"])))
178
+ transcript += f"[{timestamp}] {chunk['text']}\n"
179
+ return transcript
180
+
181
+ except Exception as transcript_error:
182
+ print(
183
+ f"Failed to get transcript using YouTube API: {str(transcript_error)}"
184
+ )
185
+ print("Falling back to Gemini-based transcription...")
186
+
187
+ # Fallback to Gemini-based transcription
188
+ with tempfile.TemporaryDirectory() as tmpdir:
189
+ # Download audio from YouTube
190
+ ydl_opts = {
191
+ "format": "bestaudio/best",
192
+ "outtmpl": os.path.join(tmpdir, "audio.%(ext)s"),
193
+ "quiet": True,
194
+ "noplaylist": True,
195
+ "postprocessors": [
196
+ {
197
+ "key": "FFmpegExtractAudio",
198
+ "preferredcodec": "wav",
199
+ "preferredquality": "192",
200
+ }
201
+ ],
202
+ }
203
+
204
+ try:
205
+ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
206
+ info = ydl.extract_info(url, download=True)
207
+ audio_path = next(
208
+ (
209
+ os.path.join(tmpdir, f)
210
+ for f in os.listdir(tmpdir)
211
+ if f.endswith(".wav")
212
+ ),
213
+ None,
214
+ )
215
+
216
+ if not audio_path:
217
+ raise RuntimeError(
218
+ "Failed to download audio"
219
+ ) from transcript_error
220
+
221
+ # Use Gemini to transcribe the audio
222
+ client = genai.Client(api_key=os.getenv("GEMINI_KEY"))
223
+ model = "models/gemini-1.5-flash-8b"
224
+
225
+ # Read the audio file
226
+ with open(audio_path, "rb") as audio_file:
227
+ audio_data = audio_file.read()
228
+
229
+ # Create the content with audio data
230
+ contents = types.Content(
231
+ parts=[
232
+ types.Part(
233
+ file_data=types.FileData(
234
+ mime_type="audio/wav",
235
+ data=audio_data,
236
+ )
237
+ ),
238
+ types.Part(
239
+ text="Please transcribe this audio file. Include timestamps if possible."
240
+ ),
241
+ ]
242
+ )
243
+
244
+ # Generate transcription
245
+ response = client.models.generate_content(
246
+ model=model, contents=contents
247
+ )
248
+ return response.text
249
+
250
+ except yt_dlp.utils.DownloadError as e:
251
+ raise RuntimeError(
252
+ f"Error downloading YouTube video: {str(e)}"
253
+ ) from transcript_error
254
+ except Exception as e:
255
+ raise RuntimeError(
256
+ f"Error processing YouTube video: {str(e)}"
257
+ ) from transcript_error
258
+
259
+ except Exception as e:
260
+ raise RuntimeError(f"Error in YouTube transcription: {str(e)}") from e
261
+
262
+
263
+ def _extract_video_id(url: str) -> Optional[str]:
264
+ """Extract video ID from YouTube URL.
265
+ Args:
266
+ url (str): the URL to the YouTube video.
267
+ Returns:
268
+ str: The video ID of the YouTube video.
269
+ """
270
+ patterns = [
271
+ r"(?:youtube\.com\/watch\?v=|youtube\.com\/embed\/|youtu\.be\/)([^&\n?#]+)",
272
+ r"(?:youtube\.com\/v\/|youtube\.com\/e\/|youtube\.com\/user\/[^\/]+\/|youtube\.com\/[^\/]+\/|youtube\.com\/embed\/|youtu\.be\/)([^&\n?#]+)",
273
+ ]
274
+
275
+ for pattern in patterns:
276
+ match = re.search(pattern, url)
277
+ if match:
278
+ return match.group(1)
279
+ return None