Viske commited on
Commit
30e4cae
·
verified ·
1 Parent(s): 817c39e

Create tools.py

Browse files
Files changed (1) hide show
  1. tools.py +424 -0
tools.py ADDED
@@ -0,0 +1,424 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+ 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
+
16
+ # ============================================================
17
+ # CONFIG
18
+ # ============================================================
19
+
20
+ 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
41
+ )
42
+
43
+
44
+ # ============================================================
45
+ # WEB SEARCH
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,
64
+ ensure_ascii=False,
65
+ indent=2
66
+ )
67
+
68
+
69
+ # ============================================================
70
+ # WEBPAGE READER
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
+
82
+ response.raise_for_status()
83
+
84
+ soup = BeautifulSoup(
85
+ response.text,
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(
99
+ "\n",
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(
115
+ url,
116
+ timeout=60
117
+ )
118
+
119
+ response.raise_for_status()
120
+
121
+ content_type = response.headers.get(
122
+ "content-type",
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
+
271
+ # ============================================================
272
+ # PYTHON EXECUTION
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
+ # ============================================================
312
+ # AUDIO TRANSCRIPTION
313
+ # ============================================================
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
+ # ============================================================
326
+ # IMAGE ANALYSIS
327
+ # ============================================================
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}"