InfinityCoder5607 commited on
Commit
c40eaa2
·
1 Parent(s): a2037d8
app.py CHANGED
@@ -6,7 +6,9 @@ import pandas as pd
6
  import spaces
7
  from langchain_openai import ChatOpenAI
8
  from langchain.agents import create_agent
 
9
  from langchain_tavily import TavilySearch
 
10
 
11
  # (Keep Constants as is)
12
  # --- Constants ---
@@ -25,69 +27,423 @@ def zerogpu_function():
25
  # fixed_answer = "This is a default answer."
26
  # print(f"Agent returning fixed answer: {fixed_answer}")
27
  # return fixed_answer
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  search_tool = TavilySearch(
29
- max_results=5,
30
  topic="general",
31
- search_depth="advanced",
 
32
 
33
  )
34
  model = ChatOpenAI(
35
- model="qwen-plus",
36
  api_key=os.environ["DASHSCOPE_API_KEY"],
37
  base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
38
  temperature=0,
 
39
  )
40
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  class BasicAgent:
42
  def __init__(self):
43
  print("BasicAgent initialized.")
44
 
45
  agent_model = model
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
 
47
  self.agent = create_agent(
48
  model=agent_model,
49
- tools=[search_tool],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  system_prompt="""
51
- You are solving GAIA benchmark questions.
52
-
53
- You have access to a web search tool.
54
-
55
- Use web search whenever:
56
- - The question asks about specific facts that may not be in your knowledge.
57
- - The question refers to Wikipedia, websites, historical records,
58
- specific people, articles, dates, publications, or other information
59
- that should be verified.
60
- - You are uncertain about a factual answer.
61
-
62
- You still cannot access files or attachments.
63
- You do not have Python or a calculator.
64
-
65
- When searching:
66
- - Search multiple times if necessary.
67
- - Use different search queries when the first search is insufficient.
68
- - Carefully reason over the search results before answering.
69
-
70
- IMPORTANT:
71
- - Return only the final answer.
72
- - Do not explain your reasoning.
73
- - Do not include phrases such as "FINAL ANSWER".
74
- - Follow the exact output format requested by the question.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  """
76
  )
77
 
78
- def __call__(self, question: str) -> str:
79
- print(f"Agent received question: {question[:100]}...")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
 
81
  result = self.agent.invoke({
82
  "messages": [
83
  {
84
  "role": "user",
85
- "content": question
86
  }
87
- ]
88
- })
 
 
 
 
89
 
90
- answer = result["messages"][-1].content
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
 
92
  print(f"Agent answer: {answer}")
93
 
@@ -99,7 +455,6 @@ IMPORTANT:
99
 
100
 
101
 
102
-
103
  def run_and_submit_all( profile: gr.OAuthProfile | None):
104
  """
105
  Fetches all questions, runs the BasicAgent on them, submits all answers,
@@ -161,7 +516,7 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
161
  print(f"Skipping item with missing task_id or question: {item}")
162
  continue
163
  try:
164
- submitted_answer = agent(question_text)
165
  answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
166
  results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
167
  except Exception as e:
@@ -274,4 +629,4 @@ if __name__ == "__main__":
274
  print("-"*(60 + len(" App Starting ")) + "\n")
275
 
276
  print("Launching Gradio Interface for Basic Agent Evaluation...")
277
- demo.launch(debug=True, share=False)
 
6
  import spaces
7
  from langchain_openai import ChatOpenAI
8
  from langchain.agents import create_agent
9
+ from langchain.agents.middleware import ToolCallLimitMiddleware
10
  from langchain_tavily import TavilySearch
11
+ from langchain_core.messages import SystemMessage
12
 
13
  # (Keep Constants as is)
14
  # --- Constants ---
 
27
  # fixed_answer = "This is a default answer."
28
  # print(f"Agent returning fixed answer: {fixed_answer}")
29
  # return fixed_answer
30
+ from datasets import load_dataset
31
+ from huggingface_hub import hf_hub_download
32
+ from langchain.tools import tool
33
+ from pathlib import Path
34
+ import base64
35
+ from langfuse import get_client
36
+ from langfuse.langchain import CallbackHandler
37
+ from openai import OpenAI
38
+ langfuse = get_client()
39
+ langfuse_handler = CallbackHandler()
40
+ gaia_dataset = load_dataset(
41
+ "gaia-benchmark/GAIA",
42
+ "2023_all",
43
+ split="validation",
44
+ )
45
+
46
+ task_map = {
47
+ item["task_id"]: item
48
+ for item in gaia_dataset
49
+ }
50
+ omni_client = OpenAI(
51
+ api_key=os.environ["DASHSCOPE_API_KEY"],
52
+ base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
53
+ )
54
+ @tool
55
+ def read_text_file(file_path: str) -> str:
56
+ """Read a local text or Python file and return its contents."""
57
+
58
+ with open(file_path, "r", encoding="utf-8") as f:
59
+ return f.read()
60
+
61
+ @tool
62
+ def download_attachment(task_id: str, file_name: str) -> str:
63
+ """
64
+ Download the attachment associated with a GAIA task.
65
+
66
+ Use this tool when the question has an attachment and you need
67
+ the local file path in order to inspect the file.
68
+
69
+ Args:
70
+ task_id: The GAIA task ID.
71
+ file_name: The name of the file to download.
72
+ Returns:
73
+ The local path of the downloaded attachment.
74
+ """
75
+
76
+ item = task_map.get(task_id)
77
+
78
+ if item is None:
79
+ return f"ERROR: task_id {task_id} was not found. Do not call this tool again."
80
+
81
+ if not file_name or not file_name.strip():
82
+ return "ERROR: This task has no attachment. Do not call this tool again."
83
+
84
+ file_name = item.get("file_name")
85
+ repo_file_path = item.get("file_path")
86
+
87
+ if not file_name or not repo_file_path:
88
+ return "ERROR: This task has no attachment. Do not call this tool again."
89
+
90
+ local_path = hf_hub_download(
91
+ repo_id="gaia-benchmark/GAIA",
92
+ repo_type="dataset",
93
+ filename=repo_file_path,
94
+ )
95
+
96
+ return local_path
97
+
98
+
99
+ from pypdf import PdfReader
100
+
101
+
102
+ @tool
103
+ def read_pdf(file_path: str) -> str:
104
+ """
105
+ Read a local PDF file and return its extracted text.
106
+
107
+ Use this tool for .pdf attachments after download_attachment
108
+ has returned the local file path.
109
+ """
110
+
111
+ reader = PdfReader(file_path)
112
+
113
+ pages = []
114
+
115
+ for page in reader.pages:
116
+ text = page.extract_text()
117
+
118
+ if text:
119
+ pages.append(text)
120
+
121
+ return "\n\n".join(pages)
122
+
123
+ @tool
124
+ def read_excel(file_path: str) -> str:
125
+ """Read an Excel spreadsheet and return its contents."""
126
+
127
+ import pandas as pd
128
+
129
+ df = pd.read_excel(file_path)
130
+
131
+ return df.to_string()
132
+
133
+ @tool
134
+ def analyze_media(file_path: str) -> str:
135
+ """
136
+ Inspect an image or audio attachment and return the factual
137
+ information contained in it.
138
+
139
+ This tool is for perception only.
140
+
141
+ For images:
142
+ - Describe visible objects, text, labels, numbers, positions,
143
+ tables, diagrams, or other observable details accurately.
144
+ - Preserve exact text and numbers when possible.
145
+ - Do NOT solve the user's question.
146
+ - Do NOT infer the final answer.
147
+ - Do NOT perform domain reasoning beyond what is necessary
148
+ to describe the media.
149
+
150
+ For audio:
151
+ - Transcribe the spoken content accurately.
152
+ - Identify clearly observable speakers or sound events when useful.
153
+ - Do NOT solve the user's question.
154
+ - Do NOT infer the final answer.
155
+
156
+ Args:
157
+ file_path: Local path of the media file.
158
+
159
+ Returns:
160
+ A factual description or transcription of the media.
161
+ """
162
+ path = Path(file_path)
163
+
164
+ if not path.exists():
165
+ return f"ERROR: File does not exist: {file_path}"
166
+
167
+ suffix = path.suffix.lower()
168
+
169
+ # 读取文件 → Base64
170
+ with open(path, "rb") as f:
171
+ file_base64 = base64.b64encode(f.read()).decode("utf-8")
172
+
173
+ # ---------- 图片 ----------
174
+ if suffix in {".png", ".jpg", ".jpeg"}:
175
+ media_prompt = """
176
+ Inspect this image carefully.
177
+
178
+ Return only the factual information that is directly observable
179
+ in the image.
180
+
181
+ Preserve exact text, labels, numbers, symbols, spatial relationships,
182
+ and positions when relevant.
183
+
184
+ Do not answer any external question.
185
+ Do not solve the task.
186
+ Do not infer a final answer.
187
+ Your job is only to convert the visual information into an accurate
188
+ text representation for another reasoning agent.
189
+ """
190
+ mime_type = {
191
+ ".png": "image/png",
192
+ ".jpg": "image/jpeg",
193
+ ".jpeg": "image/jpeg",
194
+ }[suffix]
195
+
196
+ content = [
197
+ {
198
+ "type": "image_url",
199
+ "image_url": {
200
+ "url": f"data:{mime_type};base64,{file_base64}"
201
+ },
202
+ },
203
+ {
204
+ "type": "text",
205
+ "text": media_prompt.strip(),
206
+ },
207
+ ]
208
+
209
+ # ---------- 音频 ----------
210
+ elif suffix in {".mp3", ".wav"}:
211
+
212
+ audio_format = suffix[1:] # ".mp3" -> "mp3"
213
+ media_prompt = """
214
+ Listen to this audio carefully.
215
+
216
+ Produce an accurate transcription of the spoken content.
217
+ Preserve names, numbers, dates, and other important details.
218
+
219
+ Do not answer questions about the audio.
220
+ Do not solve any task.
221
+ Your job is only to convert the audio information into text for
222
+ another reasoning agent.
223
+ """
224
+ content = [
225
+ {
226
+ "type": "input_audio",
227
+ "input_audio": {
228
+ "data": f"data:;base64,{file_base64}",
229
+ "format": audio_format,
230
+ },
231
+ },
232
+ {
233
+ "type": "text",
234
+ "text": media_prompt.strip(),
235
+ },
236
+ ]
237
+
238
+ else:
239
+ return f"ERROR: Unsupported media type: {suffix}"
240
+
241
+ # ---------- 调 Qwen Omni ----------
242
+ completion = omni_client.chat.completions.create(
243
+ model="qwen3.5-omni-flash",
244
+ messages=[
245
+ {
246
+ "role": "user",
247
+ "content": content,
248
+ }
249
+ ],
250
+ modalities=["text"],
251
+ stream=True,
252
+ timeout=20,
253
+ )
254
+
255
+ # Omni 是 streaming,把文字拼起来
256
+ result = ""
257
+
258
+ for chunk in completion:
259
+ if (
260
+ chunk.choices
261
+ and chunk.choices[0].delta.content
262
+ ):
263
+ result += chunk.choices[0].delta.content
264
+
265
+ return result.strip()
266
+
267
  search_tool = TavilySearch(
268
+ max_results=3,
269
  topic="general",
270
+ search_depth="basic",
271
+ # include_domains=["wikipedia.org"],
272
 
273
  )
274
  model = ChatOpenAI(
275
+ model="qwen3.5-plus",
276
  api_key=os.environ["DASHSCOPE_API_KEY"],
277
  base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
278
  temperature=0,
279
+ extra_body={"enable_thinking": False},
280
  )
281
 
282
+ from pydantic import BaseModel, Field
283
+ from langchain.agents.structured_output import ToolStrategy
284
+
285
+
286
+ class FinalAnswer(BaseModel):
287
+ answer: str = Field(
288
+ description=(
289
+ "Only the exact final answer requested by the question. "
290
+ "No reasoning, no explanation, no prefix such as "
291
+ "'Final answer:', and no extra commentary."
292
+ )
293
+ )
294
+
295
  class BasicAgent:
296
  def __init__(self):
297
  print("BasicAgent initialized.")
298
 
299
  agent_model = model
300
+ self.search_calls = 0
301
+
302
+ @tool("web_search")
303
+ def limited_search_tool(query: str) -> str:
304
+ """Search Wikipedia for facts needed to answer the current question."""
305
+ if self.search_calls >= 3:
306
+ return (
307
+ "SEARCH_LIMIT_REACHED: You have already used all three allowed "
308
+ "web searches for this question. Answer using the information "
309
+ "already available."
310
+ )
311
+
312
+ self.search_calls += 1
313
+ return str(search_tool.invoke({"query": query}))
314
+
315
+ self.limited_search_tool = limited_search_tool
316
 
317
  self.agent = create_agent(
318
  model=agent_model,
319
+ tools=[
320
+ download_attachment,
321
+ read_text_file,
322
+ analyze_media,
323
+ read_excel,
324
+ read_pdf,
325
+ self.limited_search_tool,
326
+ ],
327
+ middleware=[
328
+ ToolCallLimitMiddleware(
329
+ tool_name="web_search",
330
+ run_limit=3,
331
+ exit_behavior="end",
332
+ )
333
+ ],
334
+ response_format=ToolStrategy(FinalAnswer),
335
  system_prompt="""
336
+ You are an agent solving GAIA benchmark questions.
337
+
338
+ Follow this decision process strictly.
339
+
340
+ 1. ATTACHMENT CHECK
341
+
342
+ Only enter the attachment workflow if the user message exactly includes "There is an attachment for this question.".
343
+
344
+ - Use download_attachment with the provided task_id.
345
+ - After obtaining the local file path, choose the appropriate tool:
346
+ - Text or source-code file -> read_text_file
347
+ - Excel file -> read_excel
348
+ - PDF file -> read_pdf
349
+ - Image or audio file -> analyze_media
350
+ - Use the information returned by the file tool to answer the original question.
351
+ - Do not invent or guess the contents of the attachment.
352
+ - Inspect the attachment before answering if the question depends on it.
353
+
354
+ 2. NO ATTACHMENT
355
+
356
+ If the user message does NOT explicitly state that an attachment is present:
357
+
358
+ - Do NOT call download_attachment.
359
+ - Do NOT call read_text_file.
360
+ - Do NOT call read_excel.
361
+ - Do NOT call read_pdf.
362
+ - Do NOT call analyze_media.
363
+
364
+ Then determine whether the question requires web search or external information.
365
+
366
+ Use web_search only when external or up-to-date information is necessary.
367
+ Prefer answering from the available attachment or your own knowledge.
368
+ Use at most three web searches per question, and use fewer when possible.
369
+
370
+ Make each query precise and Wikipedia-focused.
371
+ Only make a follow-up search when the earlier Wikipedia results are insufficient.
372
+ Do not repeat equivalent or overly broad searches.
373
+
374
+
375
+
376
+ 3. FINAL ANSWER FORMAT
377
+
378
+ Return only the exact final answer requested by the question.
379
+
380
+ Do not include explanations.
381
+ Do not include reasoning.
382
+ Do not include prefixes such as "Final answer:".
383
+ Follow the exact answer format requested by the question.
384
+
385
  """
386
  )
387
 
388
+ def __call__(
389
+ self,
390
+ question: str,
391
+ task_id: str = "",
392
+ file_name: str = ""
393
+ ) -> str:
394
+ self.search_calls = 0
395
+
396
+ content = question
397
+ if file_name:
398
+ content += f"""
399
+
400
+ There is an attachment for this question.
401
+ File name: {file_name}
402
+ Task ID: {task_id}
403
+
404
+ Use an appropriate file tool if you need to inspect the attachment.
405
+ """
406
+ print(f"Agent received question: {content[:100]}...")
407
 
408
  result = self.agent.invoke({
409
  "messages": [
410
  {
411
  "role": "user",
412
+ "content": content
413
  }
414
+ ],
415
+ },
416
+ config={
417
+ "callbacks": [langfuse_handler]
418
+ }
419
+ )
420
 
421
+ structured_response = result.get("structured_response")
422
+ if structured_response is None:
423
+ print("Web-search limit reached; generating a final answer without tools.")
424
+ final_message = model.invoke(
425
+ [
426
+ SystemMessage(
427
+ content=(
428
+ "The research phase is complete because the web-search "
429
+ "limit was reached. Do not call tools or browse. Answer "
430
+ "the original question using the search results already in "
431
+ "this conversation and your own knowledge.\n\n"
432
+ "FINAL ANSWER FORMAT — mandatory:\n"
433
+ "- Return only the exact final answer requested by the original question.\n"
434
+ "- Do not include reasoning, explanations, sources, or prefixes.\n"
435
+ "- Follow the exact requested format, including units, dates, "
436
+ "capitalization, ordering, and number of items."
437
+ )
438
+ ),
439
+ *result["messages"],
440
+ ]
441
+ )
442
+ answer = str(final_message.content).strip()
443
+ print(f"Agent answer after search limit: {answer}")
444
+ return answer or "GIVE UP"
445
+
446
+ answer = structured_response.answer.strip()
447
 
448
  print(f"Agent answer: {answer}")
449
 
 
455
 
456
 
457
 
 
458
  def run_and_submit_all( profile: gr.OAuthProfile | None):
459
  """
460
  Fetches all questions, runs the BasicAgent on them, submits all answers,
 
516
  print(f"Skipping item with missing task_id or question: {item}")
517
  continue
518
  try:
519
+ submitted_answer = agent(question_text, task_id=task_id, file_name=item.get("file_name", ""))
520
  answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
521
  results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
522
  except Exception as e:
 
629
  print("-"*(60 + len(" App Starting ")) + "\n")
630
 
631
  print("Launching Gradio Interface for Basic Agent Evaluation...")
632
+ demo.launch(debug=True, share=False)
attachments/2023/validation/cca530fc-4052-43b2-b130-b30968d8aa44.png ADDED
try_download.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datasets import load_dataset
2
+ from huggingface_hub import hf_hub_download
3
+
4
+ task_id = "cca530fc-4052-43b2-b130-b30968d8aa44"
5
+
6
+ dataset = load_dataset(
7
+ "gaia-benchmark/GAIA",
8
+ "2023_level1",
9
+ split="validation",
10
+ trust_remote_code=True,
11
+ )
12
+
13
+ for item in dataset:
14
+ if item["task_id"] == task_id:
15
+ print("file_name:", item["file_name"])
16
+ print("file_path:", item["file_path"])
17
+
18
+ file_path = hf_hub_download(
19
+ repo_id="gaia-benchmark/GAIA",
20
+ repo_type="dataset",
21
+ filename=item["file_path"],
22
+ local_dir="attachments",
23
+ )
24
+
25
+ print("下载到:", file_path)
26
+ break