SergeyO7 commited on
Commit
b9e0271
·
verified ·
1 Parent(s): fc0917b

Create agent.py

Browse files
Files changed (1) hide show
  1. agent.py +257 -0
agent.py ADDED
@@ -0,0 +1,257 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from smolagents import CodeAgent, LiteLLMModel, tool, Tool, load_tool, DuckDuckGoSearchTool, WikipediaSearchTool
2
+ import asyncio
3
+ import os
4
+ import re
5
+ import pandas as pd
6
+ from typing import Optional
7
+ from token_bucket import Limiter, MemoryStorage
8
+ import yaml
9
+ from PIL import Image, ImageOps
10
+ import requests
11
+ from io import BytesIO
12
+ from markdownify import markdownify
13
+ import whisper
14
+ import time
15
+ import shutil
16
+ import traceback
17
+ from langchain_community.document_loaders import ArxivLoader
18
+ import logging
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+ @tool
23
+ def search_arxiv(query: str) -> str:
24
+ """Search Arxiv for a query and return maximum 3 result.
25
+
26
+ Args:
27
+ query: The search query.
28
+ Returns:
29
+ str: Formatted search results
30
+ """
31
+ search_docs = ArxivLoader(query=query, load_max_docs=3).load()
32
+ formatted_search_docs = "\n\n---\n\n".join(
33
+ [
34
+ f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content[:1000]}\n</Document>'
35
+ for doc in search_docs
36
+ ])
37
+ return {"arxiv_results": formatted_search_docs}
38
+
39
+ class VisitWebpageTool(Tool):
40
+ name = "visit_webpage"
41
+ description = "Visits a webpage at the given url and reads its content as a markdown string. Use this to browse webpages."
42
+ inputs = {'url': {'type': 'string', 'description': 'The url of the webpage to visit.'}}
43
+ output_type = "string"
44
+
45
+ def forward(self, url: str) -> str:
46
+ try:
47
+ response = requests.get(url, timeout=50)
48
+ response.raise_for_status()
49
+ markdown_content = markdownify(response perspective(response.text).strip()
50
+ markdown_content = re.sub(r"\n{3,}", "\n\n", markdown_content)
51
+ from smolagents.utils import truncate_content
52
+ return truncate_content(markdown_content, 10000)
53
+ except requests.exceptions.Timeout:
54
+ return "The request timed out. Please try again later or check the URL."
55
+ except requests.exceptions.RequestException as e:
56
+ return f"Error fetching the webpage: {str(e)}"
57
+ except Exception as e:
58
+ return f"An unexpected error occurred: {str(e)}"
59
+
60
+ def __init__(self, *args, **kwargs):
61
+ self.is_initialized = False
62
+
63
+ class SpeechToTextTool(Tool):
64
+ name = "speech_to_text"
65
+ description = (
66
+ "Converts an audio file to text using OpenAI Whisper."
67
+ )
68
+ inputs = {
69
+ "audio_path": {"type": "string", "description": "Path to audio file (.mp3, .wav)"},
70
+ }
71
+ output_type = "string"
72
+
73
+ def __init__(self):
74
+ super().__init__()
75
+ self.model = whisper.load_model("base")
76
+
77
+ def forward(self, audio_path: str) -> str:
78
+ if not os.path.exists(audio_path):
79
+ return f"Error: File not found at {audio_path}"
80
+ result = self.model.transcribe(audio_path)
81
+ return result.get("text", "")
82
+
83
+ class ExcelReaderTool(Tool):
84
+ name = "excel_reader"
85
+ description = """
86
+ This tool reads and processes Excel files (.xlsx, .xls).
87
+ It can extract data, calculate statistics, and perform data analysis on spreadsheets.
88
+ """
89
+ inputs = {
90
+ "excel_path": {
91
+ "type": "string",
92
+ "description": "The path to the Excel file to read",
93
+ },
94
+ "sheet_name": {
95
+ "type": "string",
96
+ "description": "The name of the sheet to read (optional, defaults to first sheet)",
97
+ "nullable": True
98
+ }
99
+ }
100
+ output_type = "string"
101
+
102
+ def forward(self, excel_path: str, sheet_name: str = None) -> str:
103
+ try:
104
+ if not os.path.exists(excel_path):
105
+ return f"Error: Excel file not found at {excel_path}"
106
+ import pandas as pd
107
+ if sheet_name:
108
+ df = pd.read_excel(excel_path, sheet_name=sheet_name)
109
+ else:
110
+ df = pd.read_excel(excel_path)
111
+ info = {
112
+ "shape": df.shape,
113
+ "columns": list(df.columns),
114
+ "dtypes": df.dtypes.to_dict(),
115
+ "head": df.head(5).to_dict()
116
+ }
117
+ result = f"Excel file: {excel_path}\n"
118
+ result += f"Shape: {info['shape'][0]} rows × {info['shape'][1]} columns\n\n"
119
+ result += "Columns:\n"
120
+ for col in info['columns']:
121
+ result += f"- {col} ({info['dtypes'].get(col)})\n"
122
+ result += "\nPreview (first 5 rows):\n"
123
+ result += df.head(5).to_string()
124
+ return result
125
+ except Exception as e:
126
+ return f"Error reading Excel file: {str(e)}"
127
+
128
+ class PythonCodeReaderTool(Tool):
129
+ name = "read_python_code"
130
+ description = "Reads a Python (.py) file and returns its content as a string."
131
+ inputs = {
132
+ "file_path": {"type": "string", "description": "The path to the Python file to read"}
133
+ }
134
+ output_type = "string"
135
+
136
+ def forward(self, file_path: str) -> str:
137
+ try:
138
+ if not os.path.exists(file_path):
139
+ return f"Error: Python file not found at {file_path}"
140
+ with open(file_path, "r", encoding="utf-8") as file:
141
+ content = file.read()
142
+ return content
143
+ except Exception as e:
144
+ return f"Error reading Python file: {str(e)}"
145
+
146
+ from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type
147
+
148
+ class RetryDuckDuckGoSearchTool(DuckDuckGoSearchTool):
149
+ @retry(
150
+ stop=stop_after_attempt(3),
151
+ wait=wait_exponential(multiplier=1, min=4, max=10),
152
+ retry=retry_if_exception_type(Exception)
153
+ )
154
+ def forward(self, query: str) -> str:
155
+ return super().forward(query)
156
+
157
+ class MagAgent:
158
+ def __init__(self, rate_limiter: Optional[Limiter] = None):
159
+ """Initialize the MagAgent with search tools."""
160
+ logger.info("Initializing MagAgent")
161
+ self.rate_limiter = rate_limiter
162
+
163
+ print("Initializing MagAgent with search tools...")
164
+ model = LiteLLMModel(
165
+ model_id="gemini/gemini-2.0-flash",
166
+ api_key=os.environ.get("GEMINI_KEY"),
167
+ max_tokens=8192
168
+ )
169
+
170
+ self.imports = [
171
+ "pandas",
172
+ "numpy",
173
+ "os",
174
+ "requests",
175
+ "tempfile",
176
+ "datetime",
177
+ "json",
178
+ "time",
179
+ "re",
180
+ "openpyxl",
181
+ "pathlib",
182
+ "sys",
183
+ ]
184
+
185
+ self.tools = [
186
+ RetryDuckDuckGoSearchTool(),
187
+ WikipediaSearchTool(),
188
+ SpeechToTextTool(),
189
+ ExcelReaderTool(),
190
+ VisitWebpageTool(),
191
+ PythonCodeReaderTool(),
192
+ search_arxiv,
193
+ ]
194
+
195
+ self.prompt = (
196
+ """
197
+ You are an advanced AI assistant specialized in solving complex, real-world tasks from the GAIA benchmark, requiring multi-step reasoning, factual accuracy, and use of external tools.
198
+
199
+ Follow these principles:
200
+ - Be precise and concise. The final answer must strictly match the required format with no extra commentary.
201
+ - Use tools intelligently. If a question involves external information, structured data, images, or audio, call the appropriate tool to retrieve or process it.
202
+ - If the question includes direct speech or quoted text (e.g., "Isn't that hot?"), treat it as a precise query and preserve the quoted structure in your response, including quotation marks for direct quotes (e.g., final_answer('"Extremely."')).
203
+ - If the question references an attachment, the file path is provided in the FILE section. Use the appropriate tool based on the file extension to process it.
204
+ - When processing external data (e.g., YouTube transcripts, web searches), expect potential issues like missing punctuation, inconsistent formatting, or conversational text.
205
+ - If the input is ambiguous, prioritize extracting key information relevant to the question.
206
+ - Provide answers that are concise, accurate, and properly punctuated according to standard English grammar.
207
+ - Use quotation marks for direct quotes (e.g., "Extremely.") and appropriate punctuation for lists, sentences, or clarifications.
208
+ - If asked about the name of a place or city, use the full complete name without abbreviations (e.g., use Saint Petersburg instead of St.Petersburg).
209
+ - If you cannot retrieve or process data (e.g., due to blocked requests), return a clear error message: "Unable to retrieve data. Please refine the question or check external sources."
210
+ - Reason step-by-step. Think through the solution logically and plan your actions carefully before answering.
211
+ - Validate information. Always verify facts when possible instead of guessing.
212
+ - Use code if needed. For calculations, parsing, or transformations, generate Python code and execute it. Be cautious, as some questions contain time-consuming tasks, so analyze the question and choose the most efficient solution.
213
+ - Use `final_answer` to give the final answer.
214
+ - Use the name of the file ONLY FROM the "FILE:" section. THIS IS ALWAYS A FILE.
215
+ IMPORTANT: When giving the final answer, output only the direct required result without any extra text like "Final Answer:" or explanations. YOU MUST RESPOND IN THE EXACT FORMAT AS THE QUESTION.
216
+ QUESTION: {question}
217
+ FILE: {context}
218
+ ANSWER:
219
+ """
220
+ )
221
+
222
+ self.agent = CodeAgent(
223
+ model=model,
224
+ tools=self.tools,
225
+ add_base_tools=True,
226
+ additional_authorized_imports=self.imports,
227
+ verbosity_level=2,
228
+ max_steps=20
229
+ )
230
+ print("MagAgent initialized.")
231
+
232
+ async def __call__(self, question: str, file_path: Optional[str] = None) -> str:
233
+ """Process a question asynchronously using the MagAgent."""
234
+ print(f"MagAgent received question (first 50 chars): {question[:50]}... File path: {file_path}")
235
+ try:
236
+ if self.rate_limiter:
237
+ while not self.rate_limiter.consume(1):
238
+ print(f"Rate limit reached. Waiting...")
239
+ await asyncio.sleep(4)
240
+ # Format the prompt with question and file_path as context
241
+ task = self.prompt.format(
242
+ question=question,
243
+ context=file_path if file_path else ""
244
+ )
245
+ print(f"Calling agent.run...")
246
+ response = await asyncio.to_thread(self.agent.run, task=task)
247
+ print(f"Agent.run completed.")
248
+ response = str(response)
249
+ if not response:
250
+ print(f"No answer found.")
251
+ response = "No answer found."
252
+ print(f"MagAgent response: {response[:50]}...")
253
+ return response
254
+ except Exception as e:
255
+ error_msg = f"Error processing question: {str(e)}. Check API key or network connectivity."
256
+ print(error_msg)
257
+ return error_msg