KPatelis commited on
Commit
3b7ebef
·
verified ·
1 Parent(s): 6d5c14c

Fix replacing str resp with tool call

Browse files
Files changed (3) hide show
  1. agent.py +25 -18
  2. config.yaml +0 -2
  3. tools.py +19 -19
agent.py CHANGED
@@ -14,14 +14,13 @@ import requests
14
  from pathlib import Path
15
  from dotenv import load_dotenv
16
 
17
- from pydantic import BaseModel, Field
18
-
19
  from langgraph.graph import START, END, StateGraph
20
  from langgraph.prebuilt import tools_condition, ToolNode
21
 
22
  from sentence_transformers import SentenceTransformer, CrossEncoder
23
  from langchain_huggingface import ChatHuggingFace, HuggingFaceEndpoint
24
  from langchain_core.messages import HumanMessage, SystemMessage
 
25
  from supabase.client import Client, create_client
26
 
27
  from utils import load_config, load_prompt, init_bm25_index, reciprocal_rank_fusion
@@ -79,19 +78,20 @@ _system_prompt = load_prompt("prompts/prompt.yaml")
79
  _thinking_enabled = config["models"]["llm"]["parameters"].get("thinking_enabled", True)
80
 
81
 
82
- class FinalAnswer(BaseModel):
83
- """Strict GAIA-format answer extracted from the solver's reasoning."""
84
- answer: str = Field(
85
- description=(
86
- "The raw answer value only. "
87
- "Numbers: plain digits, no commas, no units, no symbols (write '1000000', not '1,000,000' or '$50'). "
88
- "Strings: no articles ('a', 'an', 'the'), no markdown, no surrounding quotes, no trailing punctuation. "
89
- "Lists: comma-separated with no extra spaces, in the order requested by the question."
90
- )
91
- )
 
92
 
93
 
94
- formatter_llm = agent_llm.with_structured_output(FinalAnswer)
95
 
96
 
97
  # ============================================
@@ -304,20 +304,27 @@ def formatter_node(state: AgentState) -> AgentState:
304
 
305
  prompt = [
306
  SystemMessage(content=(
307
- "You extract the final answer from an agent's reasoning. "
308
- "Apply the GAIA formatting rules exactly. "
309
- "If the agent never produced an answer, return an empty string."
 
310
  )),
311
  HumanMessage(content=(
312
  f"Question:\n{question}\n\n"
313
  f"Agent reasoning and conclusion:\n{solver_output}\n\n"
314
- "Extract the final answer."
315
  )),
316
  ]
317
 
318
  try:
319
  result = formatter_llm.invoke(prompt)
320
- return {"final_answer": result.answer.strip()}
 
 
 
 
 
 
321
  except Exception as e:
322
  print(f"Formatter error: {e}")
323
  match = re.search(r'FINAL ANSWER:\s*(.*)', solver_output, re.DOTALL | re.IGNORECASE)
 
14
  from pathlib import Path
15
  from dotenv import load_dotenv
16
 
 
 
17
  from langgraph.graph import START, END, StateGraph
18
  from langgraph.prebuilt import tools_condition, ToolNode
19
 
20
  from sentence_transformers import SentenceTransformer, CrossEncoder
21
  from langchain_huggingface import ChatHuggingFace, HuggingFaceEndpoint
22
  from langchain_core.messages import HumanMessage, SystemMessage
23
+ from langchain_core.tools import tool
24
  from supabase.client import Client, create_client
25
 
26
  from utils import load_config, load_prompt, init_bm25_index, reciprocal_rank_fusion
 
78
  _thinking_enabled = config["models"]["llm"]["parameters"].get("thinking_enabled", True)
79
 
80
 
81
+ @tool
82
+ def emit_final_answer(answer: str) -> str:
83
+ """Emit the final answer to the GAIA question in the strict scoring format.
84
+
85
+ Args:
86
+ answer: The raw answer value only.
87
+ Numbers: plain digits, no commas, no units, no symbols (write '1000000', not '1,000,000' or '$50').
88
+ Strings: no articles ('a', 'an', 'the'), no markdown, no surrounding quotes, no trailing punctuation.
89
+ Lists: comma-separated with no extra spaces, in the order requested by the question.
90
+ """
91
+ return answer
92
 
93
 
94
+ formatter_llm = agent_llm.bind_tools([emit_final_answer], tool_choice="emit_final_answer")
95
 
96
 
97
  # ============================================
 
304
 
305
  prompt = [
306
  SystemMessage(content=(
307
+ "You extract the final answer from an agent's reasoning and apply the GAIA "
308
+ "formatting rules exactly. You MUST call the `emit_final_answer` tool with "
309
+ "the extracted value. If the agent never produced an answer, call it with an "
310
+ "empty string."
311
  )),
312
  HumanMessage(content=(
313
  f"Question:\n{question}\n\n"
314
  f"Agent reasoning and conclusion:\n{solver_output}\n\n"
315
+ "Extract the final answer and call emit_final_answer."
316
  )),
317
  ]
318
 
319
  try:
320
  result = formatter_llm.invoke(prompt)
321
+ for tc in getattr(result, "tool_calls", None) or []:
322
+ if tc.get("name") == "emit_final_answer":
323
+ return {"final_answer": str(tc.get("args", {}).get("answer", "")).strip()}
324
+ # Model returned text instead of calling the tool — regex over its content.
325
+ content = result.content or ""
326
+ match = re.search(r'FINAL ANSWER:\s*(.*)', content, re.DOTALL | re.IGNORECASE)
327
+ return {"final_answer": (match.group(1).strip() if match else content.strip())}
328
  except Exception as e:
329
  print(f"Formatter error: {e}")
330
  match = re.search(r'FINAL ANSWER:\s*(.*)', solver_output, re.DOTALL | re.IGNORECASE)
config.yaml CHANGED
@@ -37,8 +37,6 @@ models:
37
 
38
  graph:
39
  recursion_limit: 20 # Max steps before the graph terminates
40
- thread_id: "default-user" # Default session identifier
41
- memory_type: "sqlite" # Persistence method for checkpointers
42
 
43
  api:
44
  base_url: "https://agents-course-unit4-scoring.hf.space"
 
37
 
38
  graph:
39
  recursion_limit: 20 # Max steps before the graph terminates
 
 
40
 
41
  api:
42
  base_url: "https://agents-course-unit4-scoring.hf.space"
tools.py CHANGED
@@ -135,13 +135,13 @@ def fetch_webpage(url: str) -> str:
135
  try:
136
  downloaded = trafilatura.fetch_url(url)
137
  if downloaded is None:
138
- return f"Error: Could not fetch {url}"
139
  text = trafilatura.extract(downloaded, include_tables=True, include_links=False)
140
  if text is None:
141
- return f"Error: Could not extract content from {url}"
142
  return f"Page content from {url}:\n\n{text}"
143
  except Exception as e:
144
- return f"Error fetching webpage: {e}"
145
 
146
 
147
  @tool
@@ -169,11 +169,11 @@ def python_eval(code: str) -> str:
169
  os.unlink(tmp_path)
170
  if result.returncode == 0:
171
  return f"Output:\n{result.stdout}"
172
- return f"Error (exit {result.returncode}):\n{result.stderr}"
173
  except subprocess.TimeoutExpired:
174
- return "Error: execution timed out (30s limit)"
175
  except Exception as e:
176
- return f"Error: {e}"
177
 
178
 
179
  # ============================================
@@ -194,7 +194,7 @@ def analyze_image(image_path: str, question: str) -> str:
194
  """
195
  try:
196
  if not os.path.exists(image_path):
197
- return f"Error: Image file not found at {image_path}"
198
 
199
  with open(image_path, "rb") as img_file:
200
  image_data = base64.b64encode(img_file.read()).decode("utf-8")
@@ -221,7 +221,7 @@ def analyze_image(image_path: str, question: str) -> str:
221
  return output.choices[0].message.content
222
 
223
  except Exception as e:
224
- return f"Error analyzing image with VLM: {str(e)}"
225
 
226
 
227
  # ============================================
@@ -251,7 +251,7 @@ def read_pdf(file_path: str) -> str:
251
 
252
  return "\n\n".join(text) if text else "[Empty PDF]"
253
  except Exception as e:
254
- return f"Error reading PDF: {e}"
255
 
256
 
257
  @tool
@@ -283,7 +283,7 @@ def read_docx(file_path: str) -> str:
283
 
284
  return "\n\n".join(text_parts) if text_parts else "[Empty document]"
285
  except Exception as e:
286
- return f"Error reading DOCX: {e}"
287
 
288
 
289
  @tool
@@ -313,7 +313,7 @@ def read_pptx(file_path: str) -> str:
313
 
314
  return "\n\n".join(text) if text else "[Empty presentation]"
315
  except Exception as e:
316
- return f"Error reading PPTX: {e}"
317
 
318
 
319
  @tool
@@ -331,7 +331,7 @@ def read_text_file(file_path: str) -> str:
331
  with open(file_path, 'r', encoding='utf-8', errors='replace') as f:
332
  return f.read()
333
  except Exception as e:
334
- return f"Error reading text file: {e}"
335
 
336
 
337
  # ============================================
@@ -362,7 +362,7 @@ def read_csv(file_path: str) -> str:
362
  output += f"\n\nComplete data:\n{df}"
363
  return output
364
  except Exception as e:
365
- return f"Error reading CSV: {e}"
366
 
367
 
368
  @tool
@@ -400,7 +400,7 @@ def read_excel(file_path: str, sheet_id: int = 0) -> str:
400
  output += f"\n\nComplete data:\n{df}"
401
  return output
402
  except Exception as e:
403
- return f"Error reading Excel: {e}"
404
 
405
 
406
  @tool
@@ -419,7 +419,7 @@ def read_jsonld(file_path: str) -> str:
419
  data = json.load(f)
420
  return f"JSON-LD Content:\n{json.dumps(data, indent=2)}"
421
  except Exception as e:
422
- return f"Error reading JSON-LD: {e}"
423
 
424
 
425
  @tool
@@ -465,7 +465,7 @@ def read_pdb(file_path: str) -> str:
465
 
466
  return "\n".join(info)
467
  except Exception as e:
468
- return f"Error reading PDB: {e}"
469
 
470
 
471
  # ============================================
@@ -487,7 +487,7 @@ def transcribe_audio(file_path: str) -> str:
487
  result = _hf_client.automatic_speech_recognition(audio=file_path, model=_asr_model_name)
488
  return f"Audio Transcription:\n{result.text}"
489
  except Exception as e:
490
- return f"Error transcribing audio: {e}"
491
 
492
 
493
  # ============================================
@@ -510,7 +510,7 @@ def read_python_file(file_path: str) -> str:
510
  code = f.read()
511
  return f"Python Code:\n```python\n{code}\n```"
512
  except Exception as e:
513
- return f"Error reading Python file: {e}"
514
 
515
 
516
  # ============================================
@@ -550,7 +550,7 @@ def extract_zip(file_path: str) -> str:
550
 
551
  return "\n".join(results)
552
  except Exception as e:
553
- return f"Error extracting ZIP: {e}"
554
 
555
 
556
  # ============================================
 
135
  try:
136
  downloaded = trafilatura.fetch_url(url)
137
  if downloaded is None:
138
+ return f"[fetch_webpage] could not fetch {url}"
139
  text = trafilatura.extract(downloaded, include_tables=True, include_links=False)
140
  if text is None:
141
+ return f"[fetch_webpage] could not extract content from {url}"
142
  return f"Page content from {url}:\n\n{text}"
143
  except Exception as e:
144
+ return f"[fetch_webpage] failed: {e}"
145
 
146
 
147
  @tool
 
169
  os.unlink(tmp_path)
170
  if result.returncode == 0:
171
  return f"Output:\n{result.stdout}"
172
+ return f"[python_eval] exit {result.returncode}:\n{result.stderr}"
173
  except subprocess.TimeoutExpired:
174
+ return "[python_eval] execution timed out (30s limit)"
175
  except Exception as e:
176
+ return f"[python_eval] failed: {e}"
177
 
178
 
179
  # ============================================
 
194
  """
195
  try:
196
  if not os.path.exists(image_path):
197
+ return f"[analyze_image] image file not found at {image_path}"
198
 
199
  with open(image_path, "rb") as img_file:
200
  image_data = base64.b64encode(img_file.read()).decode("utf-8")
 
221
  return output.choices[0].message.content
222
 
223
  except Exception as e:
224
+ return f"[analyze_image] VLM call failed: {e}"
225
 
226
 
227
  # ============================================
 
251
 
252
  return "\n\n".join(text) if text else "[Empty PDF]"
253
  except Exception as e:
254
+ return f"[read_pdf] failed to read PDF: {e}"
255
 
256
 
257
  @tool
 
283
 
284
  return "\n\n".join(text_parts) if text_parts else "[Empty document]"
285
  except Exception as e:
286
+ return f"[read_docx] failed to read DOCX: {e}"
287
 
288
 
289
  @tool
 
313
 
314
  return "\n\n".join(text) if text else "[Empty presentation]"
315
  except Exception as e:
316
+ return f"[read_pptx] failed to read PPTX: {e}"
317
 
318
 
319
  @tool
 
331
  with open(file_path, 'r', encoding='utf-8', errors='replace') as f:
332
  return f.read()
333
  except Exception as e:
334
+ return f"[read_text_file] failed: {e}"
335
 
336
 
337
  # ============================================
 
362
  output += f"\n\nComplete data:\n{df}"
363
  return output
364
  except Exception as e:
365
+ return f"[read_csv] failed to read CSV: {e}"
366
 
367
 
368
  @tool
 
400
  output += f"\n\nComplete data:\n{df}"
401
  return output
402
  except Exception as e:
403
+ return f"[read_excel] failed to read Excel: {e}"
404
 
405
 
406
  @tool
 
419
  data = json.load(f)
420
  return f"JSON-LD Content:\n{json.dumps(data, indent=2)}"
421
  except Exception as e:
422
+ return f"[read_jsonld] failed to read JSON-LD: {e}"
423
 
424
 
425
  @tool
 
465
 
466
  return "\n".join(info)
467
  except Exception as e:
468
+ return f"[read_pdb] failed to read PDB: {e}"
469
 
470
 
471
  # ============================================
 
487
  result = _hf_client.automatic_speech_recognition(audio=file_path, model=_asr_model_name)
488
  return f"Audio Transcription:\n{result.text}"
489
  except Exception as e:
490
+ return f"[transcribe_audio] failed: {e}"
491
 
492
 
493
  # ============================================
 
510
  code = f.read()
511
  return f"Python Code:\n```python\n{code}\n```"
512
  except Exception as e:
513
+ return f"[read_python_file] failed: {e}"
514
 
515
 
516
  # ============================================
 
550
 
551
  return "\n".join(results)
552
  except Exception as e:
553
+ return f"[extract_zip] failed: {e}"
554
 
555
 
556
  # ============================================