benjipeng commited on
Commit
808bca2
·
verified ·
1 Parent(s): fd09c06

Update tools.py

Browse files
Files changed (1) hide show
  1. tools.py +80 -106
tools.py CHANGED
@@ -1,115 +1,89 @@
1
  import os
2
- import re
3
- import google.generativeai as genai
4
- from tools import web_search, read_file_from_api, python_interpreter
 
5
 
6
- # --- The ReAct Prompt Template ---
7
- # This master prompt is the "brain" of the agent. It tells the LLM how to behave.
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
- Your task is to answer the user's question accurately. To do this, you must operate in a loop of Thought, Action, and Observation.
13
-
14
- 1. **Thought:** First, reason about the problem and your strategy.
15
- 2. **Action:** Based on your thought, choose ONE of the following tools to use. The format must be `Action: tool_name[input]`.
16
- 3. **Observation:** After you perform an action, you will receive an observation.
17
- 4. **Repeat:** You will repeat this process until you are certain of the final answer.
18
-
19
- Your available tools are:
20
- - `web_search[query]`: Searches the web to find up-to-date information or facts.
21
- - `read_file_from_api[task_id]`: Reads a file required by the question. The `task_id` is implicitly available from the context.
22
- - `python_interpreter[code]`: Executes Python code for calculations or complex logic.
23
-
24
- **CRITICAL INSTRUCTION:** When you have the final answer, you MUST use the following format for your last step:
25
- `Final Answer: [The single, exact answer]`
26
-
27
- This `Final Answer:` prefix is a signal for the system to stop. The system will automatically extract *only the text after the prefix* for the submission. Do not add any other text, explanation, or formatting around the final answer.
 
 
28
 
29
- ---
30
- Here is the problem:
31
- Question: {question}
32
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
 
34
- class GeminiAgent:
35
- def __init__(self):
36
- print("Initializing GeminiAgent (ReAct)...")
37
- api_key = os.getenv("GEMINI_API_KEY")
38
- if not api_key:
39
- raise ValueError("GEMINI_API_KEY secret not found! Please set it in your Space's settings.")
40
-
41
- genai.configure(api_key=api_key)
 
42
 
43
- # --- CORRECTED MODEL NAME ---
44
- # Using the state-of-the-art gemini-2.5-pro model.
45
- self.model = genai.GenerativeModel('gemini-2.5-pro')
 
 
 
 
 
 
 
 
 
 
 
 
 
46
 
47
- self.tools = {
48
- "web_search": web_search,
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
- prompt = REACT_PROMPT.format(question=question)
 
 
 
 
 
 
 
60
 
61
- # ReAct loop - Max 10 turns to prevent runaways
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()