Spaces:
Sleeping
Sleeping
Update tools.py
Browse files
tools.py
CHANGED
@@ -1,115 +1,89 @@
|
|
1 |
import os
|
2 |
-
import
|
3 |
-
|
4 |
-
|
|
|
5 |
|
6 |
-
#
|
7 |
-
|
8 |
-
# It's explicitly told that the "Final Answer:" prefix is for its internal use only.
|
9 |
-
REACT_PROMPT = """
|
10 |
-
You are a helpful and intelligent agent designed to solve complex problems. You have access to a set of tools to help you.
|
11 |
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
|
|
|
|
28 |
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
33 |
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
|
|
42 |
|
43 |
-
|
44 |
-
|
45 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
46 |
|
47 |
-
|
48 |
-
|
49 |
-
"read_file_from_api": read_file_from_api,
|
50 |
-
"python_interpreter": python_interpreter
|
51 |
-
}
|
52 |
-
print("GeminiAgent initialized successfully with model 'gemini-2.5-pro'.")
|
53 |
-
|
54 |
-
def __call__(self, question: str) -> str:
|
55 |
-
# The task_id is often encoded in the question for GAIA.
|
56 |
-
task_id_match = re.search(r'gaia-id:(\S+)', question)
|
57 |
-
task_id = task_id_match.group(1) if task_id_match else "unknown"
|
58 |
|
59 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
60 |
|
61 |
-
|
62 |
-
for turn in range(10):
|
63 |
-
print(f"\n--- Turn {turn + 1} ---\n")
|
64 |
-
|
65 |
-
# 1. THOUGHT + ACTION
|
66 |
-
response = self.model.generate_content(prompt)
|
67 |
-
|
68 |
-
# Handle cases where the model response might be empty or blocked
|
69 |
-
if not response.parts:
|
70 |
-
print("Warning: Model returned an empty response.")
|
71 |
-
prompt += "\nObservation: The model returned an empty response. Please try again."
|
72 |
-
continue
|
73 |
-
|
74 |
-
response_text = response.text
|
75 |
-
print(f"LLM Response:\n{response_text}\n")
|
76 |
-
|
77 |
-
# --- PARSING LOGIC THAT COMPLIES WITH SUBMISSION RULES ---
|
78 |
-
# 2. Check for the "Final Answer:" prefix.
|
79 |
-
final_answer_match = re.search(r"Final Answer: (.*)", response_text, re.DOTALL)
|
80 |
-
if final_answer_match:
|
81 |
-
# If the prefix is found, extract ONLY the answer part.
|
82 |
-
answer = final_answer_match.group(1).strip()
|
83 |
-
print(f"Final Answer signal detected. Extracting and returning: '{answer}'")
|
84 |
-
# This return value is what gets submitted to the API. It does NOT contain the prefix.
|
85 |
-
return answer
|
86 |
-
|
87 |
-
# 3. ACT - If no final answer, look for a tool to use.
|
88 |
-
action_match = re.search(r"Action: (\w+)\[(.*)\]", response_text, re.DOTALL)
|
89 |
-
if not action_match:
|
90 |
-
# This can happen if the model is confused. We'll let it try again.
|
91 |
-
observation = "No valid 'Action:' or 'Final Answer:' found in your response. Please think step-by-step and select a tool or provide the final answer."
|
92 |
-
else:
|
93 |
-
tool_name = action_match.group(1).strip()
|
94 |
-
tool_input = action_match.group(2).strip()
|
95 |
-
|
96 |
-
if tool_name not in self.tools:
|
97 |
-
observation = f"Error: Unknown tool '{tool_name}'. Please choose from the available tools."
|
98 |
-
else:
|
99 |
-
try:
|
100 |
-
# Special handling for the file reader tool to pass the task_id
|
101 |
-
if tool_name == "read_file_from_api":
|
102 |
-
observation = self.tools[tool_name](task_id)
|
103 |
-
else:
|
104 |
-
observation = self.tools[tool_name](tool_input)
|
105 |
-
except Exception as e:
|
106 |
-
observation = f"Error executing tool {tool_name}: {e}"
|
107 |
-
|
108 |
-
print(f"Observation:\n{observation}\n")
|
109 |
-
|
110 |
-
# 4. OBSERVE - Append the full turn to the prompt for the next loop.
|
111 |
-
prompt += f"{response_text}\nObservation: {observation}\n"
|
112 |
-
|
113 |
-
# Fallback if the agent gets stuck in a loop
|
114 |
-
print("Agent failed to find an answer within the turn limit.")
|
115 |
-
return "Agent failed to find an answer within 10 turns."
|
|
|
1 |
import os
|
2 |
+
import requests
|
3 |
+
from duckduckgo_search import DDGS
|
4 |
+
import pandas as pd
|
5 |
+
import io
|
6 |
|
7 |
+
# This is the base URL for the competition API, used to construct file URLs.
|
8 |
+
GAIA_API_URL = "https://agents-course-unit4-scoring.hf.space"
|
|
|
|
|
|
|
9 |
|
10 |
+
def web_search(query: str) -> str:
|
11 |
+
"""
|
12 |
+
Performs a web search using the DuckDuckGo search engine and returns the top results.
|
13 |
+
Use this to find current information, facts, or to answer general knowledge questions.
|
14 |
+
|
15 |
+
Args:
|
16 |
+
query (str): The search query.
|
17 |
+
|
18 |
+
Returns:
|
19 |
+
str: A formatted string of the search results.
|
20 |
+
"""
|
21 |
+
print(f"Tool: Performing web search for '{query}'...")
|
22 |
+
try:
|
23 |
+
with DDGS() as ddgs:
|
24 |
+
results = [r for r in ddgs.text(query, max_results=5)]
|
25 |
+
return "\n".join([f"[{i+1}] {r['title']}: {r['body']}" for i, r in enumerate(results)]) if results else "No results found."
|
26 |
+
except Exception as e:
|
27 |
+
return f"Error during web search: {e}"
|
28 |
|
29 |
+
def read_file_from_api(task_id: str) -> str:
|
30 |
+
"""
|
31 |
+
Downloads and reads the content of a file associated with a specific task_id from the GAIA competition API.
|
32 |
+
Only use this tool when the user's question explicitly mentions a file or attachment.
|
33 |
+
|
34 |
+
Args:
|
35 |
+
task_id (str): The task ID associated with the file to download.
|
36 |
+
|
37 |
+
Returns:
|
38 |
+
str: The content of the file as a string, or an error message.
|
39 |
+
"""
|
40 |
+
print(f"Tool: Reading file for task_id '{task_id}'...")
|
41 |
+
file_url = f"{GAIA_API_URL}/files/{task_id}"
|
42 |
+
try:
|
43 |
+
response = requests.get(file_url, timeout=10)
|
44 |
+
response.raise_for_status()
|
45 |
+
# We assume the content is text-based (txt, csv, json, etc.) for direct reading
|
46 |
+
return response.text
|
47 |
+
except requests.exceptions.RequestException as e:
|
48 |
+
return f"Error reading file from API: {e}"
|
49 |
|
50 |
+
def python_interpreter(code: str) -> str:
|
51 |
+
"""
|
52 |
+
Executes a given string of Python code and returns its standard output.
|
53 |
+
This tool is highly useful for calculations, data manipulation (using pandas), or any complex logic.
|
54 |
+
The code runs in a restricted environment. Only print the final result.
|
55 |
+
It has access to the 'pandas' library (as pd).
|
56 |
+
|
57 |
+
Args:
|
58 |
+
code (str): A string containing valid Python code.
|
59 |
|
60 |
+
Returns:
|
61 |
+
str: The captured stdout from the executed code, or the error.
|
62 |
+
"""
|
63 |
+
print(f"Tool: Executing Python code:\n---\n{code}\n---")
|
64 |
+
# WARNING: Executing arbitrary code is a security risk.
|
65 |
+
# In a real-world application, this should be done in a sandboxed environment.
|
66 |
+
local_scope = {"pd": pd, "io": io}
|
67 |
+
|
68 |
+
# Use a string stream to capture the output of 'print' statements
|
69 |
+
string_stream = io.StringIO()
|
70 |
+
|
71 |
+
try:
|
72 |
+
# Redirect stdout to our string stream
|
73 |
+
import sys
|
74 |
+
original_stdout = sys.stdout
|
75 |
+
sys.stdout = string_stream
|
76 |
|
77 |
+
# Execute the code
|
78 |
+
exec(code, {"__builtins__": __builtins__}, local_scope)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
79 |
|
80 |
+
except Exception as e:
|
81 |
+
# Restore stdout and return the error
|
82 |
+
sys.stdout = original_stdout
|
83 |
+
print(f"Error executing python code: {e}")
|
84 |
+
return f"Error: {type(e).__name__}: {e}"
|
85 |
+
finally:
|
86 |
+
# Always restore stdout
|
87 |
+
sys.stdout = original_stdout
|
88 |
|
89 |
+
return string_stream.getvalue()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|