Mehedi2 commited on
Commit
b0745d5
·
verified ·
1 Parent(s): efdaa45

Update agent.py

Browse files
Files changed (1) hide show
  1. agent.py +271 -233
agent.py CHANGED
@@ -7,267 +7,289 @@ from pathlib import Path
7
  from typing import Optional, Union, Dict, Any, List
8
  from dotenv import load_dotenv
9
 
10
- from langgraph.graph import StateGraph, MessagesState
11
- from langgraph.prebuilt import create_react_agent
12
- from langchain_core.messages import HumanMessage, SystemMessage
13
- from langchain_core.tools import tool
14
- from langchain_openai import ChatOpenAI
15
-
16
  load_dotenv()
17
 
18
-
19
- class OpenRouterLLM(ChatOpenAI):
20
- """Custom OpenRouter LLM wrapper for LangGraph"""
21
 
22
- def __init__(self, model: str = "deepseek/deepseek-v3.1-terminus", **kwargs):
23
- api_key = os.getenv("OPENROUTER_API_KEY") or os.getenv("my_key")
24
-
25
- super().__init__(
26
- model=model,
27
- openai_api_key=api_key,
28
- openai_api_base="https://openrouter.ai/api/v1",
29
- **kwargs
30
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
 
33
- @tool
34
- def search_web(query: str) -> str:
35
- """Search the web using DuckDuckGo for current information."""
36
- try:
37
- # Simple web search using DuckDuckGo
38
- search_url = f"https://api.duckduckgo.com/?q={query}&format=json&no_html=1&skip_disambig=1"
39
- response = requests.get(search_url, timeout=10)
40
-
41
- if response.status_code == 200:
42
- data = response.json()
43
 
44
- # Extract results
45
- results = []
46
- if data.get("AbstractText"):
47
- results.append(f"Abstract: {data['AbstractText']}")
 
 
 
 
 
 
48
 
49
- if data.get("RelatedTopics"):
50
- for topic in data["RelatedTopics"][:3]:
51
- if isinstance(topic, dict) and topic.get("Text"):
52
- results.append(f"Related: {topic['Text']}")
53
 
54
- if results:
55
- return "\n".join(results)
56
- else:
57
- return f"Search performed for '{query}' but no specific results found."
58
- else:
59
- return f"Search failed with status code {response.status_code}"
60
 
61
- except Exception as e:
62
- return f"Search error: {str(e)}"
63
-
64
 
65
- @tool
66
- def search_wikipedia(query: str) -> str:
67
- """Search Wikipedia for factual information."""
68
- try:
69
- # Wikipedia API search
70
- search_url = "https://en.wikipedia.org/api/rest_v1/page/summary/" + query.replace(" ", "_")
71
- response = requests.get(search_url, timeout=10)
72
-
73
- if response.status_code == 200:
74
- data = response.json()
75
- extract = data.get("extract", "")
76
- if extract:
77
- return f"Wikipedia: {extract[:500]}..."
 
 
 
 
 
 
78
  else:
79
- return f"Wikipedia page found for '{query}' but no extract available."
80
- else:
81
- return f"Wikipedia search failed for '{query}'"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
 
83
- except Exception as e:
84
- return f"Wikipedia search error: {str(e)}"
 
85
 
 
 
 
 
 
86
 
87
- @tool
88
- def execute_python(code: str) -> str:
89
- """Execute Python code and return the result."""
90
- try:
91
- # Create a safe execution environment
92
- safe_globals = {
93
- '__builtins__': {
94
- 'print': print,
95
- 'len': len,
96
- 'str': str,
97
- 'int': int,
98
- 'float': float,
99
- 'bool': bool,
100
- 'list': list,
101
- 'dict': dict,
102
- 'tuple': tuple,
103
- 'set': set,
104
- 'range': range,
105
- 'sum': sum,
106
- 'max': max,
107
- 'min': min,
108
- 'abs': abs,
109
- 'round': round,
110
- 'sorted': sorted,
111
- 'enumerate': enumerate,
112
- 'zip': zip,
113
- },
114
- 'math': __import__('math'),
115
- 'json': __import__('json'),
116
- 'datetime': __import__('datetime'),
117
- 'random': __import__('random'),
118
- }
119
 
120
- # Capture output
121
- import io
122
- import sys
 
123
 
124
- old_stdout = sys.stdout
125
- sys.stdout = mystdout = io.StringIO()
 
 
126
 
127
- try:
128
- # Execute the code
129
- exec(code, safe_globals)
130
- output = mystdout.getvalue()
131
- finally:
132
- sys.stdout = old_stdout
 
133
 
134
- return output if output else "Code executed successfully (no output)"
 
 
 
 
 
 
 
 
 
 
 
135
 
136
- except Exception as e:
137
- return f"Python execution error: {str(e)}"
138
-
139
-
140
- @tool
141
- def read_excel_file(file_path: str, sheet_name: Optional[str] = None) -> str:
142
- """Read an Excel file and return its contents as a formatted string."""
143
- try:
144
- file_path_obj = Path(file_path)
145
- if not file_path_obj.exists():
146
- return f"Error: File not found at {file_path}"
147
 
148
- # Try to read the Excel file
149
- if sheet_name and sheet_name.isdigit():
150
- sheet_name = int(sheet_name)
151
- elif sheet_name is None:
152
- sheet_name = 0
153
-
154
- df = pd.read_excel(file_path, sheet_name=sheet_name)
155
 
156
- # Convert to string representation
157
- if len(df) > 20:
158
- # Show first 10 and last 10 rows for large datasets
159
- result = f"Excel file with {len(df)} rows and {len(df.columns)} columns:\n\n"
160
- result += "First 10 rows:\n"
161
- result += df.head(10).to_string(index=False)
162
- result += f"\n\n... ({len(df) - 20} rows omitted) ...\n\n"
163
- result += "Last 10 rows:\n"
164
- result += df.tail(10).to_string(index=False)
165
- else:
166
- result = f"Excel file with {len(df)} rows and {len(df.columns)} columns:\n\n"
167
- result += df.to_string(index=False)
168
-
169
- return result
 
170
 
171
- except Exception as e:
172
- return f"Error reading Excel file: {str(e)}"
173
-
174
-
175
- @tool
176
- def read_text_file(file_path: str) -> str:
177
- """Read a text file and return its contents."""
178
- try:
179
- file_path_obj = Path(file_path)
180
- if not file_path_obj.exists():
181
- return f"Error: File not found at {file_path}"
 
 
 
 
 
 
 
 
 
182
 
183
- # Try different encodings
184
- encodings = ['utf-8', 'utf-16', 'iso-8859-1', 'cp1252']
 
 
 
185
 
186
- for encoding in encodings:
187
- try:
188
- with open(file_path_obj, 'r', encoding=encoding) as f:
189
- content = f.read()
190
- return f"File content ({encoding} encoding):\n\n{content}"
191
- except UnicodeDecodeError:
192
- continue
193
 
194
- return f"Error: Could not decode file with any standard encoding"
 
 
195
 
196
- except Exception as e:
197
- return f"Error reading file: {str(e)}"
198
 
199
 
200
  class GaiaAgent:
201
- """LangGraph-based agent for GAIA tasks using OpenRouter DeepSeek"""
202
 
203
  def __init__(self):
204
- print("Initializing GaiaAgent with LangGraph and OpenRouter DeepSeek...")
205
 
206
  # Initialize the LLM
207
- self.llm = OpenRouterLLM(
208
- model="deepseek/deepseek-v3.1-terminus",
209
- temperature=0.1,
210
- max_tokens=2000
211
- )
212
 
213
- # Define available tools
214
- self.tools = [
215
- search_web,
216
- search_wikipedia,
217
- execute_python,
218
- read_excel_file,
219
- read_text_file,
220
- ]
221
-
222
- # Create the agent
223
- self.agent = create_react_agent(
224
- self.llm,
225
- self.tools,
226
- state_modifier=self._get_system_prompt()
227
- )
228
 
229
  print("GaiaAgent initialized successfully!")
230
 
231
- def _get_system_prompt(self) -> str:
232
- """Get the system prompt for the agent"""
233
- return """You are an advanced AI agent designed to answer complex questions that may require:
234
-
235
- 1. Web searches for current information
236
- 2. Mathematical calculations using Python
237
- 3. File analysis (Excel, text files)
238
- 4. Multi-step reasoning and problem solving
239
-
240
- For GAIA evaluation:
241
- - Provide EXACT, DIRECT answers
242
- - Use tools when necessary to gather information or perform calculations
243
- - For math problems, show your calculation but end with just the number
244
- - For yes/no questions, answer just "Yes" or "No"
245
- - For factual questions, provide just the fact
246
-
247
- When you encounter files:
248
- - Use read_excel_file for .xlsx, .xls files
249
- - Use read_text_file for text-based files
250
- - Analyze the file content to answer the question
251
-
252
- Be thorough in your analysis but concise in your final answer."""
253
-
254
  def __call__(self, task_id: str, question: str) -> str:
255
  """Process a question and return the answer"""
256
  try:
257
  print(f"Processing task {task_id}: {question[:100]}...")
258
 
259
- # Create the input state
260
- messages = [HumanMessage(content=question)]
261
 
262
  # Run the agent
263
- result = self.agent.invoke({"messages": messages})
264
 
265
- # Extract the final answer
266
- final_message = result["messages"][-1]
267
- answer = final_message.content
268
-
269
- # Clean up the answer for GAIA evaluation
270
- clean_answer = self._clean_answer(answer)
271
 
272
  print(f"Agent answer for {task_id}: {clean_answer}")
273
  return clean_answer
@@ -277,31 +299,47 @@ Be thorough in your analysis but concise in your final answer."""
277
  print(f"Error processing task {task_id}: {error_msg}")
278
  return error_msg
279
 
280
- def _clean_answer(self, answer: str) -> str:
281
- """Clean the answer to extract the final result"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
282
  answer = answer.strip()
283
 
284
- # Look for "Final Answer:" pattern
285
  if "final answer:" in answer.lower():
286
- parts = re.split(r'final answer:', answer, flags=re.IGNORECASE)
287
  if len(parts) > 1:
288
- answer = parts[-1].strip()
289
 
290
- # Remove common prefixes
291
- prefixes = [
292
- "The answer is", "Answer:", "Result:", "Solution:",
293
- "Based on", "Therefore", "In conclusion", "So the answer is"
294
  ]
295
 
296
- for prefix in prefixes:
297
- if answer.lower().startswith(prefix.lower()):
298
- answer = answer[len(prefix):].strip()
299
- if answer.startswith(':'):
300
- answer = answer[1:].strip()
301
  break
302
 
303
- # Remove quotes and periods from short answers
304
- if len(answer.split()) <= 3:
305
- answer = answer.strip('"\'.')
306
 
307
  return answer
 
7
  from typing import Optional, Union, Dict, Any, List
8
  from dotenv import load_dotenv
9
 
 
 
 
 
 
 
10
  load_dotenv()
11
 
12
+ # Simple tool-based agent without LangGraph for now
13
+ class SimpleAgent:
14
+ """Simple agent with tool capabilities"""
15
 
16
+ def __init__(self, llm):
17
+ self.llm = llm
18
+ self.tools = {
19
+ 'search_web': self.search_web,
20
+ 'search_wikipedia': self.search_wikipedia,
21
+ 'execute_python': self.execute_python,
22
+ 'read_excel_file': self.read_excel_file,
23
+ 'read_text_file': self.read_text_file,
24
+ }
25
+
26
+ def search_web(self, query: str) -> str:
27
+ """Search the web using DuckDuckGo for current information."""
28
+ try:
29
+ search_url = f"https://api.duckduckgo.com/?q={query}&format=json&no_html=1&skip_disambig=1"
30
+ response = requests.get(search_url, timeout=10)
31
+
32
+ if response.status_code == 200:
33
+ data = response.json()
34
+ results = []
35
+ if data.get("AbstractText"):
36
+ results.append(f"Abstract: {data['AbstractText']}")
37
+
38
+ if data.get("RelatedTopics"):
39
+ for topic in data["RelatedTopics"][:3]:
40
+ if isinstance(topic, dict) and topic.get("Text"):
41
+ results.append(f"Related: {topic['Text']}")
42
+
43
+ if results:
44
+ return "\n".join(results)
45
+ else:
46
+ return f"Search performed for '{query}' but no specific results found."
47
+ else:
48
+ return f"Search failed with status code {response.status_code}"
49
+ except Exception as e:
50
+ return f"Search error: {str(e)}"
51
 
52
+ def search_wikipedia(self, query: str) -> str:
53
+ """Search Wikipedia for factual information."""
54
+ try:
55
+ search_url = "https://en.wikipedia.org/api/rest_v1/page/summary/" + query.replace(" ", "_")
56
+ response = requests.get(search_url, timeout=10)
57
+
58
+ if response.status_code == 200:
59
+ data = response.json()
60
+ extract = data.get("extract", "")
61
+ if extract:
62
+ return f"Wikipedia: {extract[:500]}..."
63
+ else:
64
+ return f"Wikipedia page found for '{query}' but no extract available."
65
+ else:
66
+ return f"Wikipedia search failed for '{query}'"
67
+ except Exception as e:
68
+ return f"Wikipedia search error: {str(e)}"
69
 
70
+ def execute_python(self, code: str) -> str:
71
+ """Execute Python code and return the result."""
72
+ try:
73
+ import io
74
+ import sys
 
 
 
 
 
75
 
76
+ safe_globals = {
77
+ '__builtins__': {
78
+ 'print': print, 'len': len, 'str': str, 'int': int, 'float': float,
79
+ 'bool': bool, 'list': list, 'dict': dict, 'tuple': tuple, 'set': set,
80
+ 'range': range, 'sum': sum, 'max': max, 'min': min, 'abs': abs,
81
+ 'round': round, 'sorted': sorted, 'enumerate': enumerate, 'zip': zip,
82
+ },
83
+ 'math': __import__('math'),
84
+ 'json': __import__('json'),
85
+ }
86
 
87
+ old_stdout = sys.stdout
88
+ sys.stdout = mystdout = io.StringIO()
 
 
89
 
90
+ try:
91
+ exec(code, safe_globals)
92
+ output = mystdout.getvalue()
93
+ finally:
94
+ sys.stdout = old_stdout
 
95
 
96
+ return output if output else "Code executed successfully (no output)"
97
+ except Exception as e:
98
+ return f"Python execution error: {str(e)}"
99
 
100
+ def read_excel_file(self, file_path: str, sheet_name: Optional[str] = None) -> str:
101
+ """Read an Excel file and return its contents."""
102
+ try:
103
+ file_path_obj = Path(file_path)
104
+ if not file_path_obj.exists():
105
+ return f"Error: File not found at {file_path}"
106
+
107
+ if sheet_name and sheet_name.isdigit():
108
+ sheet_name = int(sheet_name)
109
+ elif sheet_name is None:
110
+ sheet_name = 0
111
+
112
+ df = pd.read_excel(file_path, sheet_name=sheet_name)
113
+
114
+ if len(df) > 20:
115
+ result = f"Excel file with {len(df)} rows and {len(df.columns)} columns:\n\n"
116
+ result += "First 10 rows:\n" + df.head(10).to_string(index=False)
117
+ result += f"\n\n... ({len(df) - 20} rows omitted) ...\n\n"
118
+ result += "Last 10 rows:\n" + df.tail(10).to_string(index=False)
119
  else:
120
+ result = f"Excel file with {len(df)} rows and {len(df.columns)} columns:\n\n"
121
+ result += df.to_string(index=False)
122
+
123
+ return result
124
+ except Exception as e:
125
+ return f"Error reading Excel file: {str(e)}"
126
+
127
+ def read_text_file(self, file_path: str) -> str:
128
+ """Read a text file and return its contents."""
129
+ try:
130
+ file_path_obj = Path(file_path)
131
+ if not file_path_obj.exists():
132
+ return f"Error: File not found at {file_path}"
133
+
134
+ encodings = ['utf-8', 'utf-16', 'iso-8859-1', 'cp1252']
135
+
136
+ for encoding in encodings:
137
+ try:
138
+ with open(file_path_obj, 'r', encoding=encoding) as f:
139
+ content = f.read()
140
+ return f"File content ({encoding} encoding):\n\n{content}"
141
+ except UnicodeDecodeError:
142
+ continue
143
 
144
+ return f"Error: Could not decode file with any standard encoding"
145
+ except Exception as e:
146
+ return f"Error reading file: {str(e)}"
147
 
148
+ def run(self, question: str) -> str:
149
+ """Run the agent with tool usage"""
150
+ # First, try to answer directly
151
+ direct_response = self.llm(f"""
152
+ Question: {question}
153
 
154
+ Think step by step. If this question requires:
155
+ - Web search for current information, say "NEED_SEARCH: <search query>"
156
+ - Mathematical calculation, say "NEED_PYTHON: <python code>"
157
+ - Wikipedia lookup, say "NEED_WIKI: <search term>"
158
+ - File analysis (if file path mentioned), say "NEED_FILE: <file_path>"
159
+
160
+ Otherwise, provide a direct answer.
161
+
162
+ Your response:""")
163
+
164
+ # Check if tools are needed
165
+ if "NEED_SEARCH:" in direct_response:
166
+ search_query = direct_response.split("NEED_SEARCH:")[1].strip()
167
+ search_result = self.search_web(search_query)
168
+ return self.llm(f"Question: {question}\n\nSearch results: {search_result}\n\nFinal answer:")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
 
170
+ elif "NEED_PYTHON:" in direct_response:
171
+ code = direct_response.split("NEED_PYTHON:")[1].strip()
172
+ exec_result = self.execute_python(code)
173
+ return self.llm(f"Question: {question}\n\nCalculation result: {exec_result}\n\nFinal answer:")
174
 
175
+ elif "NEED_WIKI:" in direct_response:
176
+ wiki_query = direct_response.split("NEED_WIKI:")[1].strip()
177
+ wiki_result = self.search_wikipedia(wiki_query)
178
+ return self.llm(f"Question: {question}\n\nWikipedia info: {wiki_result}\n\nFinal answer:")
179
 
180
+ elif "NEED_FILE:" in direct_response:
181
+ file_path = direct_response.split("NEED_FILE:")[1].strip()
182
+ if file_path.endswith(('.xlsx', '.xls')):
183
+ file_content = self.read_excel_file(file_path)
184
+ else:
185
+ file_content = self.read_text_file(file_path)
186
+ return self.llm(f"Question: {question}\n\nFile content: {file_content}\n\nFinal answer:")
187
 
188
+ else:
189
+ return direct_response
190
+ class OpenRouterLLM:
191
+ """Simple OpenRouter LLM wrapper"""
192
+
193
+ def __init__(self, model: str = "deepseek/deepseek-v3.1-terminus"):
194
+ self.api_key = os.getenv("OPENROUTER_API_KEY") or os.getenv("my_key")
195
+ self.model = model
196
+ self.base_url = "https://openrouter.ai/api/v1/chat/completions"
197
+
198
+ def __call__(self, prompt: str, max_tokens: int = 1500, temperature: float = 0.1) -> str:
199
+ """Make API call to OpenRouter"""
200
 
201
+ if not self.api_key or not self.api_key.startswith('sk-or-v1-'):
202
+ return "Error: Invalid OpenRouter API key"
 
 
 
 
 
 
 
 
 
203
 
204
+ headers = {
205
+ "Authorization": f"Bearer {self.api_key}",
206
+ "Content-Type": "application/json",
207
+ }
 
 
 
208
 
209
+ payload = {
210
+ "model": self.model,
211
+ "messages": [
212
+ {
213
+ "role": "system",
214
+ "content": "You are a helpful AI assistant. Provide direct, accurate answers. For GAIA evaluation, be precise and concise."
215
+ },
216
+ {
217
+ "role": "user",
218
+ "content": prompt
219
+ }
220
+ ],
221
+ "temperature": temperature,
222
+ "max_tokens": max_tokens,
223
+ }
224
 
225
+ try:
226
+ response = requests.post(self.base_url, headers=headers, json=payload, timeout=30)
227
+
228
+ if response.status_code != 200:
229
+ return f"API Error: {response.status_code}"
230
+
231
+ result = response.json()
232
+
233
+ if "choices" in result and len(result["choices"]) > 0:
234
+ answer = result["choices"][0]["message"]["content"].strip()
235
+ return self._clean_answer(answer)
236
+ else:
237
+ return "Error: No response content received"
238
+
239
+ except Exception as e:
240
+ return f"Error: {str(e)}"
241
+
242
+ def _clean_answer(self, answer: str) -> str:
243
+ """Clean the answer for GAIA evaluation"""
244
+ answer = answer.strip()
245
 
246
+ # Remove common prefixes
247
+ prefixes = [
248
+ "Answer:", "The answer is:", "Final answer:", "Result:",
249
+ "Solution:", "Based on", "Therefore", "In conclusion"
250
+ ]
251
 
252
+ for prefix in prefixes:
253
+ if answer.lower().startswith(prefix.lower()):
254
+ answer = answer[len(prefix):].strip()
255
+ if answer.startswith(':'):
256
+ answer = answer[1:].strip()
257
+ break
 
258
 
259
+ # Remove quotes and periods from short answers
260
+ if len(answer.split()) <= 3:
261
+ answer = answer.strip('"\'.')
262
 
263
+ return answer
 
264
 
265
 
266
  class GaiaAgent:
267
+ """Simple tool-based agent for GAIA tasks"""
268
 
269
  def __init__(self):
270
+ print("Initializing GaiaAgent with OpenRouter DeepSeek...")
271
 
272
  # Initialize the LLM
273
+ self.llm = OpenRouterLLM(model="deepseek/deepseek-v3.1-terminus")
 
 
 
 
274
 
275
+ # Initialize the agent with tools
276
+ self.agent = SimpleAgent(self.llm)
 
 
 
 
 
 
 
 
 
 
 
 
 
277
 
278
  print("GaiaAgent initialized successfully!")
279
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
280
  def __call__(self, task_id: str, question: str) -> str:
281
  """Process a question and return the answer"""
282
  try:
283
  print(f"Processing task {task_id}: {question[:100]}...")
284
 
285
+ # Check if there are file references in the question
286
+ enhanced_question = self._enhance_question_with_file_analysis(question)
287
 
288
  # Run the agent
289
+ answer = self.agent.run(enhanced_question)
290
 
291
+ # Clean up the answer
292
+ clean_answer = self._clean_final_answer(answer)
 
 
 
 
293
 
294
  print(f"Agent answer for {task_id}: {clean_answer}")
295
  return clean_answer
 
299
  print(f"Error processing task {task_id}: {error_msg}")
300
  return error_msg
301
 
302
+ def _enhance_question_with_file_analysis(self, question: str) -> str:
303
+ """Check if question mentions files and enhance accordingly"""
304
+ # Look for file path mentions in the question
305
+ file_patterns = [
306
+ r'/tmp/gaia_cached_files/[^\s]+',
307
+ r'saved locally at:\s*([^\s]+)',
308
+ r'file.*?\.xlsx?',
309
+ r'file.*?\.csv',
310
+ r'file.*?\.txt'
311
+ ]
312
+
313
+ for pattern in file_patterns:
314
+ matches = re.findall(pattern, question, re.IGNORECASE)
315
+ if matches:
316
+ # File found, the agent will handle it automatically
317
+ break
318
+
319
+ return question
320
+
321
+ def _clean_final_answer(self, answer: str) -> str:
322
+ """Final cleaning of the answer"""
323
  answer = answer.strip()
324
 
325
+ # Look for final answer pattern
326
  if "final answer:" in answer.lower():
327
+ parts = answer.lower().split("final answer:")
328
  if len(parts) > 1:
329
+ answer = answer.split(":")[-1].strip()
330
 
331
+ # Remove common unnecessary phrases
332
+ cleanup_phrases = [
333
+ "based on the", "according to", "the answer is", "therefore",
334
+ "in conclusion", "as a result", "so the answer is"
335
  ]
336
 
337
+ for phrase in cleanup_phrases:
338
+ if answer.lower().startswith(phrase):
339
+ answer = answer[len(phrase):].strip()
 
 
340
  break
341
 
342
+ # Clean up formatting
343
+ answer = answer.strip('.,;:"\'')
 
344
 
345
  return answer