Viske commited on
Commit
c82a650
·
verified ·
1 Parent(s): 0fd510f

Update tools.py

Browse files
Files changed (1) hide show
  1. tools.py +486 -347
tools.py CHANGED
@@ -4,7 +4,7 @@ import json
4
  import base64
5
  import tempfile
6
  import subprocess
7
- from pathlib import Path
8
 
9
  import requests
10
  import pandas as pd
@@ -13,17 +13,9 @@ from bs4 import BeautifulSoup
13
  from groq import Groq
14
 
15
 
16
- # ============================================================
17
- # CONFIG
18
- # ============================================================
19
 
20
- SCORING_API = (
21
- "https://agents-course-unit4-scoring.hf.space"
22
- )
23
-
24
- GROQ_API_KEY = os.getenv(
25
- "GROQ_API_KEY"
26
- )
27
 
28
  VISION_MODEL = os.getenv(
29
  "GROQ_VISION_MODEL",
@@ -40,237 +32,368 @@ groq_client = Groq(
40
  )
41
 
42
 
43
- # ============================================================
44
- # WEB SEARCH
45
- # ============================================================
 
 
 
 
 
 
 
46
 
47
  def web_search(query):
48
 
49
- try:
50
 
 
51
  from ddgs import DDGS
52
 
53
- except ImportError:
54
 
55
- from duckduckgo_search import DDGS
 
 
 
 
56
 
57
- results = []
58
 
59
- with DDGS() as ddgs:
 
 
 
 
60
 
61
- for item in ddgs.text(
62
- query,
63
- max_results=6
64
- ):
65
 
66
- results.append(
67
- {
68
- "title": item.get(
69
- "title",
70
- ""
71
- ),
72
- "url": item.get(
73
- "href",
74
- ""
75
- ),
76
- "snippet": item.get(
77
- "body",
78
- ""
79
- )
80
- }
81
  )
82
 
83
- return json.dumps(
84
- results,
85
- ensure_ascii=False,
86
- indent=2
87
- )
88
 
 
89
 
90
- # ============================================================
91
- # WEBPAGE
92
- # ============================================================
93
 
94
- def read_webpage(url):
95
 
96
- response = requests.get(
97
- url,
98
- timeout=30,
99
- headers={
100
- "User-Agent":
101
- "Mozilla/5.0 GAIA-Agent"
102
- }
103
- )
104
 
105
- response.raise_for_status()
 
 
 
 
106
 
107
- soup = BeautifulSoup(
108
- response.text,
109
- "html.parser"
110
- )
111
 
112
- for tag in soup(
113
- [
114
- "script",
115
- "style",
116
- "noscript",
117
- "svg",
118
- "nav",
119
- "footer"
120
- ]
121
- ):
122
 
123
- tag.decompose()
124
 
125
- text = soup.get_text(
126
- "\n",
127
- strip=True
128
- )
129
 
130
- # Keep context manageable.
131
- return text[:25000]
 
 
132
 
 
 
 
 
 
 
 
 
133
 
134
- # ============================================================
135
- # DOWNLOAD GAIA FILE
136
- # ============================================================
137
 
138
- def download_file(task_id):
 
139
 
140
- url = (
141
- f"{SCORING_API}/files/{task_id}"
142
- )
143
 
144
- response = requests.get(
145
- url,
146
- timeout=60
147
- )
148
 
149
- response.raise_for_status()
 
 
 
 
 
 
 
 
 
150
 
151
- content_type = response.headers.get(
152
- "content-type",
153
- ""
154
- ).lower()
155
 
156
- disposition = response.headers.get(
157
- "content-disposition",
158
- ""
159
- )
160
 
161
- filename = None
 
 
 
 
 
 
 
 
 
 
 
162
 
163
- match = re.search(
164
- r'filename="?([^"]+)"?',
165
- disposition
166
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
 
168
- if match:
 
 
 
169
 
170
- filename = match.group(1)
 
171
 
172
- if not filename:
173
 
174
- extension_map = {
175
- "audio/mpeg": ".mp3",
176
- "audio/mp3": ".mp3",
177
- "audio/wav": ".wav",
178
- "image/png": ".png",
179
- "image/jpeg": ".jpg",
180
- "application/pdf": ".pdf",
181
- "text/plain": ".txt",
182
- "text/x-python": ".py",
183
- "application/python": ".py",
184
- "text/csv": ".csv",
185
- "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": ".xlsx"
186
- }
187
 
188
- extension = ".bin"
 
189
 
190
- for key, value in extension_map.items():
191
 
192
- if key in content_type:
193
 
194
- extension = value
195
- break
196
 
197
- filename = (
198
- f"{task_id}{extension}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
  )
200
 
201
- filename = re.sub(
202
- r"[^A-Za-z0-9_.-]",
203
- "_",
204
- filename
205
- )
206
 
207
- folder = (
208
- Path(tempfile.gettempdir())
209
- / "gaia_files"
210
- )
211
 
212
- folder.mkdir(
213
- parents=True,
214
- exist_ok=True
215
- )
216
 
217
- path = (
218
- folder
219
- / filename
220
- )
221
 
222
- path.write_bytes(
223
- response.content
224
- )
225
 
226
- print(
227
- f"Downloaded: {path}"
228
- )
229
 
230
- return str(path)
 
 
 
231
 
 
232
 
233
- # ============================================================
234
- # TEXT FILE
235
- # ============================================================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
236
 
237
  def inspect_text(path):
238
 
239
- return Path(path).read_text(
 
 
240
  encoding="utf-8",
241
- errors="ignore"
242
- )[:30000]
243
 
 
244
 
245
- # ============================================================
246
- # EXCEL
247
- # ============================================================
248
 
249
- def inspect_excel(path):
250
 
251
- workbook = pd.ExcelFile(
252
- path
253
- )
254
 
255
- output = []
 
 
256
 
257
- output.append(
258
- "SHEETS:"
259
- )
260
 
261
- output.append(
262
- str(workbook.sheet_names)
 
263
  )
264
 
265
- for sheet in workbook.sheet_names:
266
 
267
- df = pd.read_excel(
268
- path,
269
- sheet_name=sheet
270
- )
271
 
272
  output.append(
273
- f"\n===== {sheet} ====="
274
  )
275
 
276
  output.append(
@@ -281,188 +404,198 @@ def inspect_excel(path):
281
  f"Columns: {list(df.columns)}"
282
  )
283
 
284
- # Avoid flooding the model.
285
  output.append(
286
  df.head(100).to_string(
287
  index=False
288
  )
289
  )
290
 
291
- return "\n".join(
292
- output
293
- )[:35000]
294
 
295
 
296
- # ============================================================
297
- # PDF
298
- # ============================================================
299
 
300
  def inspect_pdf(path):
301
 
302
  from PyPDF2 import PdfReader
303
 
304
- reader = PdfReader(
305
- path
306
- )
307
 
308
  pages = []
309
 
310
  for page in reader.pages:
311
 
312
- text = (
313
- page.extract_text()
314
- or ""
315
- )
316
 
317
  pages.append(text)
318
 
319
- return "\n".join(
320
- pages
321
- )[:35000]
 
 
 
 
 
 
 
 
322
 
 
323
 
324
- # ============================================================
325
- # GENERIC FILE INSPECTION
326
- # ============================================================
327
 
328
- def inspect_file(path):
 
 
 
 
 
 
 
329
 
330
  extension = (
331
- Path(path)
332
- .suffix
333
  .lower()
334
  )
335
 
 
 
 
 
 
 
 
 
 
336
  if extension in [
337
  ".txt",
338
  ".py",
339
- ".md",
340
- ".csv",
341
- ".tsv",
342
  ".json",
343
- ".xml"
 
 
 
344
  ]:
345
-
346
- return inspect_text(
347
- path
348
- )
349
 
350
  if extension in [
351
- ".xlsx",
352
- ".xls"
 
353
  ]:
354
-
355
- return inspect_excel(
356
- path
357
  )
358
 
359
- if extension == ".pdf":
360
-
361
- return inspect_pdf(
362
- path
 
 
 
 
 
363
  )
364
 
365
  return (
366
- f"Unsupported file type: "
367
- f"{extension}"
368
  )
369
 
370
 
371
- # ============================================================
372
- # PYTHON
373
- # ============================================================
374
 
375
  def run_python(code):
376
 
377
- folder = (
378
- Path(tempfile.gettempdir())
379
- / "gaia_python"
380
  )
381
 
382
- folder.mkdir(
383
- parents=True,
384
- exist_ok=True
385
- )
 
 
386
 
387
- script = (
388
- folder
389
- / "script.py"
390
- )
391
 
392
- script.write_text(
393
- code,
394
- encoding="utf-8"
395
- )
 
 
 
 
 
396
 
397
- process = subprocess.run(
398
- [
399
- "python",
400
- str(script)
401
- ],
402
- capture_output=True,
403
- text=True,
404
- timeout=30
405
- )
406
 
407
- output = (
408
- process.stdout
409
- + "\n"
410
- + process.stderr
411
- )
412
 
413
- return output[-20000:]
414
 
 
415
 
416
- # ============================================================
417
- # AUDIO TRANSCRIPTION
418
- # ============================================================
419
 
420
- def transcribe_audio(path):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
421
 
422
  with open(
423
  path,
424
  "rb"
425
- ) as audio:
426
-
427
- result = (
428
- groq_client.audio.transcriptions.create(
429
- file=audio,
430
- model=AUDIO_MODEL,
431
- response_format="text",
432
- temperature=0
433
- )
434
- )
435
-
436
- if hasattr(
437
- result,
438
- "text"
439
- ):
440
 
441
- return result.text
 
 
 
 
442
 
443
- return str(result)
444
 
445
 
446
- # ============================================================
447
- # IMAGE ANALYSIS
448
- # ============================================================
449
 
450
  def analyze_image(
451
- path,
452
  question
453
  ):
454
 
455
- raw = Path(
456
- path
457
- ).read_bytes()
458
-
459
- encoded = base64.b64encode(
460
- raw
461
- ).decode("utf-8")
462
 
463
  extension = (
464
- Path(path)
465
- .suffix
466
  .lower()
467
  )
468
 
@@ -473,104 +606,110 @@ def analyze_image(
473
  ".webp": "image/webp"
474
  }
475
 
476
- mime = mime_map.get(
477
  extension,
478
  "image/png"
479
  )
480
 
481
- data_url = (
482
- f"data:{mime};base64,{encoded}"
483
- )
484
-
485
- response = (
486
- groq_client.chat.completions.create(
487
- model=VISION_MODEL,
488
- messages=[
489
- {
490
- "role": "user",
491
- "content": [
492
- {
493
- "type": "text",
494
- "text": question
495
- },
496
- {
497
- "type": "image_url",
498
- "image_url": {
499
- "url": data_url
500
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
501
  }
502
- ]
503
- }
504
- ],
505
- temperature=0.1,
506
- max_tokens=1500
507
- )
508
  )
509
 
510
- return (
511
- response
512
- .choices[0]
513
- .message
514
- .content
515
- )
516
 
517
 
518
- # ============================================================
519
- # TOOL DISPATCH
520
- # ============================================================
521
 
522
  def execute_tool(
523
- tool_name,
524
- tool_input
 
525
  ):
526
 
527
- if tool_name == "web_search":
528
 
529
  return web_search(
530
- str(tool_input)
531
  )
532
 
533
- if tool_name == "read_webpage":
534
 
535
  return read_webpage(
536
- str(tool_input)
 
 
537
  )
538
 
539
- if tool_name == "inspect_file":
 
 
 
 
 
 
540
 
541
  return inspect_file(
542
- str(tool_input)
543
  )
544
 
545
- if tool_name == "run_python":
546
 
547
  return run_python(
548
- str(tool_input)
549
  )
550
 
551
- if tool_name == "transcribe_audio":
552
 
553
  return transcribe_audio(
554
- str(tool_input)
555
  )
556
 
557
- if tool_name == "analyze_image":
558
-
559
- if not isinstance(
560
- tool_input,
561
- dict
562
- ):
563
-
564
- return (
565
- "analyze_image requires "
566
- "path and question."
567
- )
568
 
569
  return analyze_image(
570
- tool_input["path"],
571
- tool_input["question"]
572
  )
573
 
574
- return (
575
- f"Unknown tool: {tool_name}"
576
- )
 
4
  import base64
5
  import tempfile
6
  import subprocess
7
+ import mimetypes
8
 
9
  import requests
10
  import pandas as pd
 
13
  from groq import Groq
14
 
15
 
16
+ SCORING_API = "https://agents-course-unit4-scoring.hf.space"
 
 
17
 
18
+ GROQ_API_KEY = os.getenv("GROQ_API_KEY")
 
 
 
 
 
 
19
 
20
  VISION_MODEL = os.getenv(
21
  "GROQ_VISION_MODEL",
 
32
  )
33
 
34
 
35
+ # ---------------------------------------------------------
36
+ # Web search state
37
+ # ---------------------------------------------------------
38
+
39
+ LAST_SEARCH_RESULTS = []
40
+
41
+
42
+ # ---------------------------------------------------------
43
+ # Web search
44
+ # ---------------------------------------------------------
45
 
46
  def web_search(query):
47
 
48
+ global LAST_SEARCH_RESULTS
49
 
50
+ try:
51
  from ddgs import DDGS
52
 
53
+ results = []
54
 
55
+ with DDGS() as ddgs:
56
+ search_results = ddgs.text(
57
+ query,
58
+ max_results=6
59
+ )
60
 
61
+ for item in search_results:
62
 
63
+ result = {
64
+ "title": item.get("title", ""),
65
+ "url": item.get("href", ""),
66
+ "snippet": item.get("body", "")
67
+ }
68
 
69
+ results.append(result)
 
 
 
70
 
71
+ LAST_SEARCH_RESULTS = results
72
+
73
+ if not results:
74
+ return "No search results found."
75
+
76
+ output = []
77
+
78
+ for i, item in enumerate(results):
79
+
80
+ output.append(
81
+ f"[{i}]\n"
82
+ f"TITLE: {item['title']}\n"
83
+ f"URL: {item['url']}\n"
84
+ f"SNIPPET: {item['snippet']}"
 
85
  )
86
 
87
+ return "\n\n".join(output)
 
 
 
 
88
 
89
+ except Exception as e:
90
 
91
+ return f"Web search error: {type(e).__name__}: {e}"
 
 
92
 
 
93
 
94
+ # ---------------------------------------------------------
95
+ # Read webpage
96
+ # ---------------------------------------------------------
 
 
 
 
 
97
 
98
+ def read_webpage(
99
+ url=None,
100
+ cursor=None,
101
+ pattern=None
102
+ ):
103
 
104
+ global LAST_SEARCH_RESULTS
 
 
 
105
 
106
+ # -----------------------------------------------------
107
+ # Resolve missing URL using latest search results
108
+ # -----------------------------------------------------
 
 
 
 
 
 
 
109
 
110
+ if not url:
111
 
112
+ if cursor is None:
113
+ cursor = 0
 
 
114
 
115
+ try:
116
+ cursor = int(cursor)
117
+ except Exception:
118
+ cursor = 0
119
 
120
+ if (
121
+ cursor < 0
122
+ or cursor >= len(LAST_SEARCH_RESULTS)
123
+ ):
124
+ return (
125
+ "ERROR: No valid URL was supplied and the requested "
126
+ "search-result cursor does not exist."
127
+ )
128
 
129
+ url = LAST_SEARCH_RESULTS[cursor].get("url", "")
 
 
130
 
131
+ if not url:
132
+ return "ERROR: Could not resolve webpage URL."
133
 
134
+ # -----------------------------------------------------
135
+ # Fetch webpage
136
+ # -----------------------------------------------------
137
 
138
+ try:
 
 
 
139
 
140
+ response = requests.get(
141
+ url,
142
+ timeout=20,
143
+ headers={
144
+ "User-Agent": (
145
+ "Mozilla/5.0 "
146
+ "(compatible; GAIA-Agent/1.0)"
147
+ )
148
+ }
149
+ )
150
 
151
+ response.raise_for_status()
 
 
 
152
 
153
+ soup = BeautifulSoup(
154
+ response.text,
155
+ "html.parser"
156
+ )
157
 
158
+ for tag in soup(
159
+ [
160
+ "script",
161
+ "style",
162
+ "noscript",
163
+ "svg",
164
+ "nav",
165
+ "footer",
166
+ "header"
167
+ ]
168
+ ):
169
+ tag.decompose()
170
 
171
+ text = soup.get_text(
172
+ "\n",
173
+ strip=True
174
+ )
175
+
176
+ # -------------------------------------------------
177
+ # If model supplied a pattern, return context
178
+ # around occurrences rather than the whole page.
179
+ # -------------------------------------------------
180
+
181
+ if pattern:
182
+
183
+ pattern_lower = pattern.lower()
184
+ text_lower = text.lower()
185
+
186
+ positions = []
187
+
188
+ start = 0
189
+
190
+ while True:
191
 
192
+ position = text_lower.find(
193
+ pattern_lower,
194
+ start
195
+ )
196
 
197
+ if position == -1:
198
+ break
199
 
200
+ positions.append(position)
201
 
202
+ start = position + len(pattern_lower)
 
 
 
 
 
 
 
 
 
 
 
 
203
 
204
+ if len(positions) >= 5:
205
+ break
206
 
207
+ if positions:
208
 
209
+ chunks = []
210
 
211
+ for position in positions:
 
212
 
213
+ left = max(
214
+ 0,
215
+ position - 1800
216
+ )
217
+
218
+ right = min(
219
+ len(text),
220
+ position + 3500
221
+ )
222
+
223
+ chunks.append(
224
+ text[left:right]
225
+ )
226
+
227
+ text = "\n\n--- MATCH ---\n\n".join(
228
+ chunks
229
+ )
230
+
231
+ if len(text) > 18000:
232
+ text = text[:18000]
233
+
234
+ return (
235
+ f"URL: {url}\n\n"
236
+ f"CONTENT:\n{text}"
237
  )
238
 
239
+ except Exception as e:
 
 
 
 
240
 
241
+ return (
242
+ f"Webpage read error: "
243
+ f"{type(e).__name__}: {e}"
244
+ )
245
 
 
 
 
 
246
 
247
+ # ---------------------------------------------------------
248
+ # Download GAIA attachment
249
+ # ---------------------------------------------------------
 
250
 
251
+ def download_file(task_id):
 
 
252
 
253
+ try:
254
+
255
+ url = f"{SCORING_API}/files/{task_id}"
256
 
257
+ response = requests.get(
258
+ url,
259
+ timeout=60
260
+ )
261
 
262
+ response.raise_for_status()
263
 
264
+ content_type = response.headers.get(
265
+ "content-type",
266
+ ""
267
+ ).lower()
268
+
269
+ disposition = response.headers.get(
270
+ "content-disposition",
271
+ ""
272
+ )
273
+
274
+ filename = None
275
+
276
+ # -------------------------------------------------
277
+ # Try Content-Disposition
278
+ # -------------------------------------------------
279
+
280
+ match = re.search(
281
+ r'filename="?([^";]+)"?',
282
+ disposition,
283
+ re.IGNORECASE
284
+ )
285
+
286
+ if match:
287
+ filename = match.group(1)
288
+
289
+ # -------------------------------------------------
290
+ # Try URL filename
291
+ # -------------------------------------------------
292
+
293
+ if not filename:
294
+
295
+ url_name = url.rstrip("/").split("/")[-1]
296
+
297
+ if "." in url_name:
298
+ filename = url_name
299
+
300
+ # -------------------------------------------------
301
+ # Content-type fallback
302
+ # -------------------------------------------------
303
+
304
+ if not filename:
305
+
306
+ extension_map = {
307
+ "audio/mpeg": ".mp3",
308
+ "audio/mp3": ".mp3",
309
+ "audio/wav": ".wav",
310
+ "audio/x-wav": ".wav",
311
+ "image/png": ".png",
312
+ "image/jpeg": ".jpg",
313
+ "image/jpg": ".jpg",
314
+ "application/pdf": ".pdf",
315
+ "text/plain": ".txt",
316
+ "text/x-python": ".py",
317
+ "application/json": ".json",
318
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": ".xlsx",
319
+ "application/vnd.ms-excel": ".xls"
320
+ }
321
+
322
+ filename = (
323
+ "gaia_attachment"
324
+ + extension_map.get(
325
+ content_type,
326
+ ".bin"
327
+ )
328
+ )
329
+
330
+ # -------------------------------------------------
331
+ # Save
332
+ # -------------------------------------------------
333
+
334
+ safe_filename = os.path.basename(
335
+ filename
336
+ )
337
+
338
+ path = os.path.join(
339
+ tempfile.gettempdir(),
340
+ safe_filename
341
+ )
342
+
343
+ with open(
344
+ path,
345
+ "wb"
346
+ ) as f:
347
+ f.write(response.content)
348
+
349
+ return path
350
+
351
+ except Exception as e:
352
+
353
+ raise RuntimeError(
354
+ f"Could not download task file: "
355
+ f"{type(e).__name__}: {e}"
356
+ )
357
+
358
+
359
+ # ---------------------------------------------------------
360
+ # Inspect text
361
+ # ---------------------------------------------------------
362
 
363
  def inspect_text(path):
364
 
365
+ with open(
366
+ path,
367
+ "r",
368
  encoding="utf-8",
369
+ errors="replace"
370
+ ) as f:
371
 
372
+ text = f.read()
373
 
374
+ if len(text) > 20000:
375
+ text = text[:20000]
 
376
 
377
+ return text
378
 
 
 
 
379
 
380
+ # ---------------------------------------------------------
381
+ # Inspect Excel
382
+ # ---------------------------------------------------------
383
 
384
+ def inspect_excel(path):
 
 
385
 
386
+ sheets = pd.read_excel(
387
+ path,
388
+ sheet_name=None
389
  )
390
 
391
+ output = []
392
 
393
+ for sheet_name, df in sheets.items():
 
 
 
394
 
395
  output.append(
396
+ f"=== SHEET: {sheet_name} ==="
397
  )
398
 
399
  output.append(
 
404
  f"Columns: {list(df.columns)}"
405
  )
406
 
 
407
  output.append(
408
  df.head(100).to_string(
409
  index=False
410
  )
411
  )
412
 
413
+ return "\n\n".join(output)
 
 
414
 
415
 
416
+ # ---------------------------------------------------------
417
+ # Inspect PDF
418
+ # ---------------------------------------------------------
419
 
420
  def inspect_pdf(path):
421
 
422
  from PyPDF2 import PdfReader
423
 
424
+ reader = PdfReader(path)
 
 
425
 
426
  pages = []
427
 
428
  for page in reader.pages:
429
 
430
+ text = page.extract_text() or ""
 
 
 
431
 
432
  pages.append(text)
433
 
434
+ if len("\n".join(pages)) > 20000:
435
+ break
436
+
437
+ text = "\n".join(pages)
438
+
439
+ return text[:20000]
440
+
441
+
442
+ # ---------------------------------------------------------
443
+ # Inspect Python / source
444
+ # ---------------------------------------------------------
445
 
446
+ def inspect_source(path):
447
 
448
+ return inspect_text(path)
 
 
449
 
450
+
451
+ # ---------------------------------------------------------
452
+ # Generic file inspection
453
+ # ---------------------------------------------------------
454
+
455
+ def inspect_file(task_id):
456
+
457
+ path = download_file(task_id)
458
 
459
  extension = (
460
+ os.path.splitext(path)[1]
 
461
  .lower()
462
  )
463
 
464
+ if extension in [
465
+ ".xlsx",
466
+ ".xls"
467
+ ]:
468
+ return inspect_excel(path)
469
+
470
+ if extension == ".pdf":
471
+ return inspect_pdf(path)
472
+
473
  if extension in [
474
  ".txt",
475
  ".py",
 
 
 
476
  ".json",
477
+ ".csv",
478
+ ".md",
479
+ ".xml",
480
+ ".html"
481
  ]:
482
+ return inspect_source(path)
 
 
 
483
 
484
  if extension in [
485
+ ".png",
486
+ ".jpg",
487
+ ".jpeg"
488
  ]:
489
+ return (
490
+ f"Image file downloaded successfully: {path}\n"
491
+ "Use analyze_image for visual analysis."
492
  )
493
 
494
+ if extension in [
495
+ ".mp3",
496
+ ".wav",
497
+ ".m4a",
498
+ ".ogg"
499
+ ]:
500
+ return (
501
+ f"Audio file downloaded successfully: {path}\n"
502
+ "Use transcribe_audio for speech analysis."
503
  )
504
 
505
  return (
506
+ f"Downloaded file: {path}\n"
507
+ f"Extension: {extension}"
508
  )
509
 
510
 
511
+ # ---------------------------------------------------------
512
+ # Run Python
513
+ # ---------------------------------------------------------
514
 
515
  def run_python(code):
516
 
517
+ path = os.path.join(
518
+ tempfile.gettempdir(),
519
+ "gaia_agent_code.py"
520
  )
521
 
522
+ with open(
523
+ path,
524
+ "w",
525
+ encoding="utf-8"
526
+ ) as f:
527
+ f.write(code)
528
 
529
+ try:
 
 
 
530
 
531
+ result = subprocess.run(
532
+ [
533
+ "python",
534
+ path
535
+ ],
536
+ capture_output=True,
537
+ text=True,
538
+ timeout=30
539
+ )
540
 
541
+ output = (
542
+ result.stdout
543
+ + "\n"
544
+ + result.stderr
545
+ ).strip()
 
 
 
 
546
 
547
+ if len(output) > 12000:
548
+ output = output[:12000]
 
 
 
549
 
550
+ return output
551
 
552
+ except subprocess.TimeoutExpired:
553
 
554
+ return "Python execution timed out after 30 seconds."
 
 
555
 
556
+ except Exception as e:
557
+
558
+ return (
559
+ f"Python execution error: "
560
+ f"{type(e).__name__}: {e}"
561
+ )
562
+
563
+
564
+ # ---------------------------------------------------------
565
+ # Audio transcription
566
+ # ---------------------------------------------------------
567
+
568
+ def transcribe_audio(task_id):
569
+
570
+ path = download_file(task_id)
571
 
572
  with open(
573
  path,
574
  "rb"
575
+ ) as audio_file:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
576
 
577
+ transcription = groq_client.audio.transcriptions.create(
578
+ file=audio_file,
579
+ model=AUDIO_MODEL,
580
+ response_format="text"
581
+ )
582
 
583
+ return str(transcription)
584
 
585
 
586
+ # ---------------------------------------------------------
587
+ # Image analysis
588
+ # ---------------------------------------------------------
589
 
590
  def analyze_image(
591
+ task_id,
592
  question
593
  ):
594
 
595
+ path = download_file(task_id)
 
 
 
 
 
 
596
 
597
  extension = (
598
+ os.path.splitext(path)[1]
 
599
  .lower()
600
  )
601
 
 
606
  ".webp": "image/webp"
607
  }
608
 
609
+ mime_type = mime_map.get(
610
  extension,
611
  "image/png"
612
  )
613
 
614
+ with open(
615
+ path,
616
+ "rb"
617
+ ) as f:
618
+
619
+ encoded = base64.b64encode(
620
+ f.read()
621
+ ).decode("utf-8")
622
+
623
+ response = groq_client.chat.completions.create(
624
+
625
+ model=VISION_MODEL,
626
+
627
+ temperature=0,
628
+
629
+ max_completion_tokens=1500,
630
+
631
+ messages=[
632
+ {
633
+ "role": "user",
634
+ "content": [
635
+ {
636
+ "type": "text",
637
+ "text": (
638
+ "Analyze this image carefully.\n\n"
639
+ f"Question: {question}\n\n"
640
+ "Return the factual answer needed for the question."
641
+ )
642
+ },
643
+ {
644
+ "type": "image_url",
645
+ "image_url": {
646
+ "url": (
647
+ f"data:{mime_type};base64,"
648
+ f"{encoded}"
649
+ )
650
  }
651
+ }
652
+ ]
653
+ }
654
+ ]
 
 
655
  )
656
 
657
+ return response.choices[0].message.content
 
 
 
 
 
658
 
659
 
660
+ # ---------------------------------------------------------
661
+ # Tool dispatcher
662
+ # ---------------------------------------------------------
663
 
664
  def execute_tool(
665
+ name,
666
+ args,
667
+ task_id=None
668
  ):
669
 
670
+ if name == "web_search":
671
 
672
  return web_search(
673
+ args.get("query", "")
674
  )
675
 
676
+ if name == "read_webpage":
677
 
678
  return read_webpage(
679
+ url=args.get("url"),
680
+ cursor=args.get("cursor"),
681
+ pattern=args.get("pattern")
682
  )
683
 
684
+ if name == "download_file":
685
+
686
+ return download_file(
687
+ args.get("task_id") or task_id
688
+ )
689
+
690
+ if name == "inspect_file":
691
 
692
  return inspect_file(
693
+ args.get("task_id") or task_id
694
  )
695
 
696
+ if name == "run_python":
697
 
698
  return run_python(
699
+ args.get("code", "")
700
  )
701
 
702
+ if name == "transcribe_audio":
703
 
704
  return transcribe_audio(
705
+ args.get("task_id") or task_id
706
  )
707
 
708
+ if name == "analyze_image":
 
 
 
 
 
 
 
 
 
 
709
 
710
  return analyze_image(
711
+ task_id=args.get("task_id") or task_id,
712
+ question=args.get("question", "")
713
  )
714
 
715
+ return f"Unknown tool: {name}"