Viske commited on
Commit
c6c032e
·
verified ·
1 Parent(s): 9245762

Update tools.py

Browse files
Files changed (1) hide show
  1. tools.py +305 -216
tools.py CHANGED
@@ -4,12 +4,12 @@ import json
4
  import base64
5
  import tempfile
6
  import subprocess
 
7
 
8
  import requests
9
  import pandas as pd
10
 
11
  from bs4 import BeautifulSoup
12
- from duckduckgo_search import DDGS
13
  from huggingface_hub import InferenceClient
14
 
15
 
@@ -21,20 +21,11 @@ SCORING_API = "https://agents-course-unit4-scoring.hf.space"
21
 
22
  HF_TOKEN = os.getenv("HF_TOKEN")
23
 
24
- TEXT_MODEL = os.getenv(
25
- "TEXT_MODEL",
26
- "Qwen/Qwen2.5-72B-Instruct"
27
- )
28
 
29
- VISION_MODEL = os.getenv(
30
- "VISION_MODEL",
31
- "Qwen/Qwen2.5-VL-72B-Instruct"
32
- )
33
 
34
- ASR_MODEL = os.getenv(
35
- "ASR_MODEL",
36
- "openai/whisper-large-v3"
37
- )
38
 
39
  client = InferenceClient(
40
  token=HF_TOKEN
@@ -46,18 +37,31 @@ client = InferenceClient(
46
  # ============================================================
47
 
48
  def web_search(query):
 
 
 
 
 
 
 
 
 
49
  results = []
50
 
51
  with DDGS() as ddgs:
52
- for result in ddgs.text(
 
53
  query,
54
- max_results=6
55
  ):
56
- results.append({
57
- "title": result.get("title", ""),
58
- "url": result.get("href", ""),
59
- "snippet": result.get("body", "")
60
- })
 
 
 
61
 
62
  return json.dumps(
63
  results,
@@ -71,11 +75,15 @@ def web_search(query):
71
  # ============================================================
72
 
73
  def read_webpage(url):
 
74
  response = requests.get(
75
  url,
76
- timeout=25,
77
  headers={
78
- "User-Agent": "Mozilla/5.0"
 
 
 
79
  }
80
  )
81
 
@@ -86,13 +94,14 @@ def read_webpage(url):
86
  "html.parser"
87
  )
88
 
89
- for tag in soup([
90
- "script",
91
- "style",
92
- "nav",
93
- "footer",
94
- "header"
95
- ]):
 
96
  tag.decompose()
97
 
98
  text = soup.get_text(
@@ -100,15 +109,16 @@ def read_webpage(url):
100
  strip=True
101
  )
102
 
103
- # Avoid sending enormous webpages to the model.
104
- return text[:40000]
105
 
106
 
107
  # ============================================================
108
  # DOWNLOAD GAIA ATTACHMENT
109
  # ============================================================
110
 
111
- def download_file(task_id):
 
112
  url = f"{SCORING_API}/files/{task_id}"
113
 
114
  response = requests.get(
@@ -123,148 +133,193 @@ def download_file(task_id):
123
  ""
124
  ).lower()
125
 
126
- extension = ""
 
 
 
127
 
128
- if "png" in content_type:
129
- extension = ".png"
130
 
131
- elif "jpeg" in content_type or "jpg" in content_type:
132
- extension = ".jpg"
 
 
133
 
134
- elif "mp3" in content_type or "audio" in content_type:
135
- extension = ".mp3"
136
 
137
- elif "spreadsheet" in content_type or "excel" in content_type:
138
- extension = ".xlsx"
139
 
140
- elif "pdf" in content_type:
141
- extension = ".pdf"
142
 
143
- elif "python" in content_type or "text" in content_type:
144
- extension = ".txt"
 
145
 
146
- else:
147
- # Try to infer from Content-Disposition.
148
- disposition = response.headers.get(
149
- "content-disposition",
150
- ""
151
- )
152
 
153
- match = re.search(
154
- r'filename="?([^"]+)"?',
155
- disposition
156
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
157
 
158
- if match:
159
- extension = os.path.splitext(
160
- match.group(1)
161
- )[1]
 
 
 
 
 
162
 
163
- if not extension:
164
- extension = ".bin"
 
165
 
166
- path = os.path.join(
167
- tempfile.gettempdir(),
168
- f"gaia_{task_id}{extension}"
169
  )
170
 
171
- with open(path, "wb") as f:
172
- f.write(response.content)
 
 
 
173
 
174
- return path
175
 
176
 
177
  # ============================================================
178
- # FILE INSPECTION
179
  # ============================================================
180
 
181
- def inspect_file(path):
182
- extension = os.path.splitext(
183
- path
184
- )[1].lower()
185
 
186
- # ---------------- TXT / PY / CSV ----------------
 
 
187
 
188
- if extension in [
189
- ".txt",
190
- ".py",
191
- ".md",
192
- ".csv",
193
- ".tsv"
194
- ]:
 
 
 
 
 
195
 
196
- with open(
 
 
 
 
 
 
197
  path,
198
- "r",
199
- encoding="utf-8",
200
- errors="ignore"
201
- ) as f:
202
- return f.read()[:50000]
203
 
204
- # ---------------- EXCEL ----------------
 
 
205
 
206
- if extension in [
207
- ".xlsx",
208
- ".xls"
209
- ]:
210
 
211
- excel = pd.ExcelFile(path)
 
 
212
 
213
- output = []
214
 
215
- for sheet in excel.sheet_names:
216
 
217
- df = pd.read_excel(
218
- path,
219
- sheet_name=sheet
220
- )
221
 
222
- output.append(
223
- f"\n--- SHEET: {sheet} ---\n"
224
- )
225
 
226
- output.append(
227
- f"Columns: {list(df.columns)}\n"
228
- )
229
 
230
- output.append(
231
- f"Rows: {len(df)}\n"
232
- )
 
 
233
 
234
- output.append(
235
- df.head(50).to_string(
236
- index=False
237
- )
238
  )
239
 
240
- return "\n".join(output)[:50000]
241
 
242
- # ---------------- PDF ----------------
243
 
244
- if extension == ".pdf":
 
 
 
 
 
 
 
245
 
246
- try:
247
- import PyPDF2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
248
 
249
- reader = PyPDF2.PdfReader(path)
250
 
251
- pages = []
 
 
 
252
 
253
- for page in reader.pages:
254
- pages.append(
255
- page.extract_text() or ""
256
- )
257
 
258
- return "\n".join(pages)[:50000]
259
 
260
- except Exception as e:
261
- return f"Could not read PDF: {e}"
262
 
263
  return (
264
- f"File downloaded successfully at:\n"
265
- f"{path}\n"
266
- f"File type: {extension}\n"
267
- f"Use an appropriate tool to analyze it."
268
  )
269
 
270
 
@@ -273,39 +328,40 @@ def inspect_file(path):
273
  # ============================================================
274
 
275
  def run_python(code):
276
- with tempfile.NamedTemporaryFile(
277
- mode="w",
278
- suffix=".py",
279
- delete=False,
280
- encoding="utf-8"
281
- ) as f:
282
 
283
- f.write(code)
 
 
284
 
285
- path = f.name
 
 
 
286
 
287
- try:
288
 
289
- result = subprocess.run(
290
- ["python", path],
291
- capture_output=True,
292
- text=True,
293
- timeout=30
294
- )
295
 
296
- return (
297
- "STDOUT:\n"
298
- + result.stdout
299
- + "\nSTDERR:\n"
300
- + result.stderr
301
- )[:30000]
 
 
 
302
 
303
- finally:
 
 
 
 
304
 
305
- try:
306
- os.remove(path)
307
- except OSError:
308
- pass
309
 
310
 
311
  # ============================================================
@@ -314,12 +370,21 @@ def run_python(code):
314
 
315
  def transcribe_audio(path):
316
 
317
- result = client.automatic_speech_recognition(
318
  path,
319
- model=ASR_MODEL
320
- )
 
 
 
 
 
 
 
321
 
322
- return result.text
 
 
323
 
324
 
325
  # ============================================================
@@ -328,97 +393,121 @@ def transcribe_audio(path):
328
 
329
  def analyze_image(path, question):
330
 
331
- with open(path, "rb") as f:
332
- image_bytes = f.read()
333
 
334
  encoded = base64.b64encode(
335
- image_bytes
336
  ).decode("utf-8")
337
 
338
- # Determine MIME type.
339
- extension = os.path.splitext(
340
- path
341
- )[1].lower()
 
342
 
343
- if extension == ".jpg" or extension == ".jpeg":
344
- mime = "image/jpeg"
345
- else:
346
- mime = "image/png"
 
 
 
 
 
347
 
348
- response = client.chat.completions.create(
349
- model=VISION_MODEL,
350
- messages=[
351
- {
352
- "role": "user",
353
- "content": [
354
- {
355
- "type": "text",
356
- "text": question
357
- },
358
- {
359
- "type": "image_url",
360
- "image_url": {
361
- "url":
362
- f"data:{mime};base64,{encoded}"
363
- }
364
  }
365
- ]
366
- }
367
- ],
368
- max_tokens=1000,
369
- temperature=0
 
 
 
 
 
370
  )
371
 
372
- return response.choices[0].message.content
 
 
 
 
 
373
 
374
 
375
  # ============================================================
376
- # TOOL DISPATCHER
377
  # ============================================================
378
 
379
  def execute_tool(
380
  tool_name,
381
- tool_input,
382
- task_id,
383
- question
384
  ):
385
 
386
  if tool_name == "web_search":
387
- return web_search(tool_input)
388
 
389
- if tool_name == "read_webpage":
390
- return read_webpage(tool_input)
 
391
 
392
- if tool_name == "download_file":
393
- path = download_file(task_id)
394
 
395
- return (
396
- f"FILE_PATH:\n{path}\n\n"
397
- f"{inspect_file(path)}"
398
  )
399
 
400
  if tool_name == "inspect_file":
401
 
402
- path = tool_input.strip()
403
-
404
- return inspect_file(path)
405
 
406
  if tool_name == "run_python":
407
- return run_python(tool_input)
408
 
409
- if tool_name == "transcribe_audio":
 
 
410
 
411
- path = tool_input.strip()
412
 
413
- return transcribe_audio(path)
 
 
414
 
415
  if tool_name == "analyze_image":
416
 
417
- path = tool_input.strip()
 
 
 
418
 
419
- return analyze_image(
420
- path,
421
- question
 
 
 
 
 
422
  )
423
 
424
- return f"Unknown tool: {tool_name}"
 
 
 
4
  import base64
5
  import tempfile
6
  import subprocess
7
+ from pathlib import Path
8
 
9
  import requests
10
  import pandas as pd
11
 
12
  from bs4 import BeautifulSoup
 
13
  from huggingface_hub import InferenceClient
14
 
15
 
 
21
 
22
  HF_TOKEN = os.getenv("HF_TOKEN")
23
 
24
+ TEXT_MODEL = "Qwen/Qwen2.5-72B-Instruct"
 
 
 
25
 
26
+ VISION_MODEL = "Qwen/Qwen2.5-VL-72B-Instruct"
 
 
 
27
 
28
+ AUDIO_MODEL = "openai/whisper-large-v3"
 
 
 
29
 
30
  client = InferenceClient(
31
  token=HF_TOKEN
 
37
  # ============================================================
38
 
39
  def web_search(query):
40
+
41
+ try:
42
+
43
+ from ddgs import DDGS
44
+
45
+ except ImportError:
46
+
47
+ from duckduckgo_search import DDGS
48
+
49
  results = []
50
 
51
  with DDGS() as ddgs:
52
+
53
+ for item in ddgs.text(
54
  query,
55
+ max_results=8
56
  ):
57
+
58
+ results.append(
59
+ {
60
+ "title": item.get("title", ""),
61
+ "url": item.get("href", ""),
62
+ "snippet": item.get("body", "")
63
+ }
64
+ )
65
 
66
  return json.dumps(
67
  results,
 
75
  # ============================================================
76
 
77
  def read_webpage(url):
78
+
79
  response = requests.get(
80
  url,
81
+ timeout=30,
82
  headers={
83
+ "User-Agent": (
84
+ "Mozilla/5.0 "
85
+ "GAIA-Agent/1.0"
86
+ )
87
  }
88
  )
89
 
 
94
  "html.parser"
95
  )
96
 
97
+ for tag in soup(
98
+ [
99
+ "script",
100
+ "style",
101
+ "noscript",
102
+ "svg"
103
+ ]
104
+ ):
105
  tag.decompose()
106
 
107
  text = soup.get_text(
 
109
  strip=True
110
  )
111
 
112
+ # Prevent enormous webpages from consuming context.
113
+ return text[:30000]
114
 
115
 
116
  # ============================================================
117
  # DOWNLOAD GAIA ATTACHMENT
118
  # ============================================================
119
 
120
+ def download_file(task_id, filename_hint=""):
121
+
122
  url = f"{SCORING_API}/files/{task_id}"
123
 
124
  response = requests.get(
 
133
  ""
134
  ).lower()
135
 
136
+ content_disposition = response.headers.get(
137
+ "content-disposition",
138
+ ""
139
+ )
140
 
141
+ filename = None
 
142
 
143
+ match = re.search(
144
+ r'filename="?([^"]+)"?',
145
+ content_disposition
146
+ )
147
 
148
+ if match:
149
+ filename = match.group(1)
150
 
151
+ if not filename:
 
152
 
153
+ if filename_hint:
 
154
 
155
+ filename = Path(
156
+ filename_hint
157
+ ).name
158
 
159
+ else:
 
 
 
 
 
160
 
161
+ if "audio" in content_type:
162
+ extension = ".mp3"
163
+
164
+ elif "image" in content_type:
165
+ extension = ".png"
166
+
167
+ elif "spreadsheet" in content_type:
168
+ extension = ".xlsx"
169
+
170
+ elif "pdf" in content_type:
171
+ extension = ".pdf"
172
+
173
+ elif "python" in content_type:
174
+ extension = ".py"
175
+
176
+ else:
177
+ extension = ".bin"
178
 
179
+ filename = (
180
+ f"{task_id}{extension}"
181
+ )
182
+
183
+ safe_name = re.sub(
184
+ r"[^A-Za-z0-9_.-]",
185
+ "_",
186
+ filename
187
+ )
188
 
189
+ folder = Path(
190
+ tempfile.gettempdir()
191
+ ) / "gaia_files"
192
 
193
+ folder.mkdir(
194
+ parents=True,
195
+ exist_ok=True
196
  )
197
 
198
+ path = folder / safe_name
199
+
200
+ path.write_bytes(
201
+ response.content
202
+ )
203
 
204
+ return str(path)
205
 
206
 
207
  # ============================================================
208
+ # TEXT FILE INSPECTION
209
  # ============================================================
210
 
211
+ def inspect_text_file(path):
 
 
 
212
 
213
+ text = Path(path).read_text(
214
+ errors="ignore"
215
+ )
216
 
217
+ return text[:30000]
218
+
219
+
220
+ # ============================================================
221
+ # EXCEL INSPECTION
222
+ # ============================================================
223
+
224
+ def inspect_excel(path):
225
+
226
+ excel = pd.ExcelFile(path)
227
+
228
+ output = []
229
 
230
+ output.append(
231
+ f"Sheets: {excel.sheet_names}"
232
+ )
233
+
234
+ for sheet in excel.sheet_names:
235
+
236
+ df = pd.read_excel(
237
  path,
238
+ sheet_name=sheet
239
+ )
 
 
 
240
 
241
+ output.append(
242
+ f"\n=== SHEET: {sheet} ==="
243
+ )
244
 
245
+ output.append(
246
+ f"Shape: {df.shape}"
247
+ )
 
248
 
249
+ output.append(
250
+ df.to_string(index=False)
251
+ )
252
 
253
+ return "\n".join(output)[:40000]
254
 
 
255
 
256
+ # ============================================================
257
+ # PDF INSPECTION
258
+ # ============================================================
 
259
 
260
+ def inspect_pdf(path):
 
 
261
 
262
+ try:
 
 
263
 
264
+ from PyPDF2 import PdfReader
265
+
266
+ reader = PdfReader(path)
267
+
268
+ pages = []
269
 
270
+ for page in reader.pages:
271
+
272
+ pages.append(
273
+ page.extract_text() or ""
274
  )
275
 
276
+ return "\n".join(pages)[:40000]
277
 
278
+ except Exception as e:
279
 
280
+ return f"PDF ERROR: {e}"
281
+
282
+
283
+ # ============================================================
284
+ # GENERIC FILE INSPECTION
285
+ # ============================================================
286
+
287
+ def inspect_file(path):
288
 
289
+ path = str(path)
290
+
291
+ extension = (
292
+ Path(path)
293
+ .suffix
294
+ .lower()
295
+ )
296
+
297
+ if extension in [
298
+ ".txt",
299
+ ".py",
300
+ ".md",
301
+ ".csv",
302
+ ".tsv",
303
+ ".json",
304
+ ".xml"
305
+ ]:
306
 
307
+ return inspect_text_file(path)
308
 
309
+ if extension in [
310
+ ".xlsx",
311
+ ".xls"
312
+ ]:
313
 
314
+ return inspect_excel(path)
 
 
 
315
 
316
+ if extension == ".pdf":
317
 
318
+ return inspect_pdf(path)
 
319
 
320
  return (
321
+ f"Unsupported file type: "
322
+ f"{extension}"
 
 
323
  )
324
 
325
 
 
328
  # ============================================================
329
 
330
  def run_python(code):
 
 
 
 
 
 
331
 
332
+ folder = Path(
333
+ tempfile.gettempdir()
334
+ ) / "gaia_python"
335
 
336
+ folder.mkdir(
337
+ parents=True,
338
+ exist_ok=True
339
+ )
340
 
341
+ filename = folder / "script.py"
342
 
343
+ filename.write_text(
344
+ code,
345
+ encoding="utf-8"
346
+ )
 
 
347
 
348
+ process = subprocess.run(
349
+ [
350
+ "python",
351
+ str(filename)
352
+ ],
353
+ capture_output=True,
354
+ text=True,
355
+ timeout=30
356
+ )
357
 
358
+ output = (
359
+ process.stdout
360
+ + "\n"
361
+ + process.stderr
362
+ )
363
 
364
+ return output[-20000:]
 
 
 
365
 
366
 
367
  # ============================================================
 
370
 
371
  def transcribe_audio(path):
372
 
373
+ with open(
374
  path,
375
+ "rb"
376
+ ) as audio_file:
377
+
378
+ result = client.automatic_speech_recognition(
379
+ audio_file,
380
+ model=AUDIO_MODEL
381
+ )
382
+
383
+ if hasattr(result, "text"):
384
 
385
+ return result.text
386
+
387
+ return str(result)
388
 
389
 
390
  # ============================================================
 
393
 
394
  def analyze_image(path, question):
395
 
396
+ data = Path(path).read_bytes()
 
397
 
398
  encoded = base64.b64encode(
399
+ data
400
  ).decode("utf-8")
401
 
402
+ extension = (
403
+ Path(path)
404
+ .suffix
405
+ .lower()
406
+ )
407
 
408
+ mime = {
409
+ ".png": "image/png",
410
+ ".jpg": "image/jpeg",
411
+ ".jpeg": "image/jpeg",
412
+ ".webp": "image/webp"
413
+ }.get(
414
+ extension,
415
+ "image/png"
416
+ )
417
 
418
+ image_url = (
419
+ f"data:{mime};base64,{encoded}"
420
+ )
421
+
422
+ messages = [
423
+ {
424
+ "role": "user",
425
+ "content": [
426
+ {
427
+ "type": "text",
428
+ "text": question
429
+ },
430
+ {
431
+ "type": "image_url",
432
+ "image_url": {
433
+ "url": image_url
434
  }
435
+ }
436
+ ]
437
+ }
438
+ ]
439
+
440
+ response = client.chat_completion(
441
+ model=VISION_MODEL,
442
+ messages=messages,
443
+ max_tokens=1500,
444
+ temperature=0.1
445
  )
446
 
447
+ return (
448
+ response
449
+ .choices[0]
450
+ .message
451
+ .content
452
+ )
453
 
454
 
455
  # ============================================================
456
+ # TOOL DISPATCH
457
  # ============================================================
458
 
459
  def execute_tool(
460
  tool_name,
461
+ tool_input
 
 
462
  ):
463
 
464
  if tool_name == "web_search":
 
465
 
466
+ return web_search(
467
+ str(tool_input)
468
+ )
469
 
470
+ if tool_name == "read_webpage":
 
471
 
472
+ return read_webpage(
473
+ str(tool_input)
 
474
  )
475
 
476
  if tool_name == "inspect_file":
477
 
478
+ return inspect_file(
479
+ str(tool_input)
480
+ )
481
 
482
  if tool_name == "run_python":
 
483
 
484
+ return run_python(
485
+ str(tool_input)
486
+ )
487
 
488
+ if tool_name == "transcribe_audio":
489
 
490
+ return transcribe_audio(
491
+ str(tool_input)
492
+ )
493
 
494
  if tool_name == "analyze_image":
495
 
496
+ if isinstance(
497
+ tool_input,
498
+ dict
499
+ ):
500
 
501
+ return analyze_image(
502
+ tool_input["path"],
503
+ tool_input["question"]
504
+ )
505
+
506
+ return (
507
+ "analyze_image requires "
508
+ "a path and question."
509
  )
510
 
511
+ return (
512
+ f"Unknown tool: {tool_name}"
513
+ )