dzhashwanth commited on
Commit
9b2270a
Β·
verified Β·
1 Parent(s): 81917a3

Create agent.py

Browse files
Files changed (1) hide show
  1. agent.py +334 -0
agent.py ADDED
@@ -0,0 +1,334 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ import os
3
+ import sys
4
+ import logging
5
+ import random
6
+ import pandas as pd
7
+ import requests
8
+ import wikipedia as wiki
9
+ from markdownify import markdownify as to_markdown
10
+ from typing import Any
11
+ from dotenv import load_dotenv
12
+ from google.generativeai import types, configure
13
+
14
+ from smolagents import InferenceClientModel, LiteLLMModel, CodeAgent, ToolCallingAgent, Tool, DuckDuckGoSearchTool
15
+
16
+ # Load environment and configure Gemini
17
+ load_dotenv()
18
+ configure(api_key=os.getenv("GOOGLE_API_KEY"))
19
+
20
+ # Logging
21
+ #logging.basicConfig(level=logging.INFO, format="%(asctime)s | %(levelname)s | %(message)s")
22
+ #logger = logging.getLogger(__name__)
23
+
24
+ # --- Model Configuration ---
25
+ GEMINI_MODEL_NAME = "gemini/gemini-2.0-flash"
26
+ OPENAI_MODEL_NAME = "openai/gpt-4o"
27
+ GROQ_MODEL_NAME = "groq/llama3-70b-8192"
28
+ DEEPSEEK_MODEL_NAME = "deepseek/deepseek-chat"
29
+ HF_MODEL_NAME = "Qwen/Qwen2.5-Coder-32B-Instruct"
30
+
31
+ # --- Tool Definitions ---
32
+ class MathSolver(Tool):
33
+ name = "math_solver"
34
+ description = "Safely evaluate basic math expressions."
35
+ inputs = {"input": {"type": "string", "description": "Math expression to evaluate."}}
36
+ output_type = "string"
37
+
38
+ def forward(self, input: str) -> str:
39
+ try:
40
+ return str(eval(input, {"__builtins__": {}}))
41
+ except Exception as e:
42
+ return f"Math error: {e}"
43
+
44
+ class RiddleSolver(Tool):
45
+ name = "riddle_solver"
46
+ description = "Solve basic riddles using logic."
47
+ inputs = {"input": {"type": "string", "description": "Riddle prompt."}}
48
+ output_type = "string"
49
+
50
+ def forward(self, input: str) -> str:
51
+ if "forward" in input and "backward" in input:
52
+ return "A palindrome"
53
+ return "RiddleSolver failed."
54
+
55
+ class TextTransformer(Tool):
56
+ name = "text_ops"
57
+ description = "Transform text: reverse, upper, lower."
58
+ inputs = {"input": {"type": "string", "description": "Use prefix like reverse:/upper:/lower:"}}
59
+ output_type = "string"
60
+
61
+ def forward(self, input: str) -> str:
62
+ if input.startswith("reverse:"):
63
+ reversed_text = input[8:].strip()[::-1]
64
+ if 'left' in reversed_text.lower():
65
+ return "right"
66
+ return reversed_text
67
+ if input.startswith("upper:"):
68
+ return input[6:].strip().upper()
69
+ if input.startswith("lower:"):
70
+ return input[6:].strip().lower()
71
+ return "Unknown transformation."
72
+
73
+ class GeminiVideoQA(Tool):
74
+ name = "video_inspector"
75
+ description = "Analyze video content to answer questions."
76
+ inputs = {
77
+ "video_url": {"type": "string", "description": "URL of video."},
78
+ "user_query": {"type": "string", "description": "Question about video."}
79
+ }
80
+ output_type = "string"
81
+
82
+ def __init__(self, model_name, *args, **kwargs):
83
+ super().__init__(*args, **kwargs)
84
+ self.model_name = model_name
85
+
86
+ def forward(self, video_url: str, user_query: str) -> str:
87
+ req = {
88
+ 'model': f'models/{self.model_name}',
89
+ 'contents': [{
90
+ "parts": [
91
+ {"fileData": {"fileUri": video_url}},
92
+ {"text": f"Please watch the video and answer the question: {user_query}"}
93
+ ]
94
+ }]
95
+ }
96
+ url = f'https://generativelanguage.googleapis.com/v1beta/models/{self.model_name}:generateContent?key={os.getenv("GOOGLE_API_KEY")}'
97
+ res = requests.post(url, json=req, headers={'Content-Type': 'application/json'})
98
+ if res.status_code != 200:
99
+ return f"Video error {res.status_code}: {res.text}"
100
+ parts = res.json()['candidates'][0]['content']['parts']
101
+ return "".join([p.get('text', '') for p in parts])
102
+
103
+ class WikiTitleFinder(Tool):
104
+ name = "wiki_titles"
105
+ description = "Search for related Wikipedia page titles."
106
+ inputs = {"query": {"type": "string", "description": "Search query."}}
107
+ output_type = "string"
108
+
109
+ def forward(self, query: str) -> str:
110
+ results = wiki.search(query)
111
+ return ", ".join(results) if results else "No results."
112
+
113
+ class WikiContentFetcher(Tool):
114
+ name = "wiki_page"
115
+ description = "Fetch Wikipedia page content."
116
+ inputs = {"page_title": {"type": "string", "description": "Wikipedia page title."}}
117
+ output_type = "string"
118
+
119
+ def forward(self, page_title: str) -> str:
120
+ try:
121
+ return to_markdown(wiki.page(page_title).html())
122
+ except wiki.exceptions.PageError:
123
+ return f"'{page_title}' not found."
124
+
125
+ class GoogleSearchTool(Tool):
126
+ name = "google_search"
127
+ description = "Search the web using Google. Returns top summary from the web."
128
+ inputs = {"query": {"type": "string", "description": "Search query."}}
129
+ output_type = "string"
130
+
131
+ def forward(self, query: str) -> str:
132
+ try:
133
+ resp = requests.get("https://www.googleapis.com/customsearch/v1", params={
134
+ "q": query,
135
+ "key": os.getenv("GOOGLE_SEARCH_API_KEY"),
136
+ "cx": os.getenv("GOOGLE_SEARCH_ENGINE_ID"),
137
+ "num": 1
138
+ })
139
+ data = resp.json()
140
+ return data["items"][0]["snippet"] if "items" in data else "No results found."
141
+ except Exception as e:
142
+ return f"GoogleSearch error: {e}"
143
+
144
+
145
+ class FileAttachmentQueryTool(Tool):
146
+ name = "run_query_with_file"
147
+ description = """
148
+ Downloads a file mentioned in a user prompt, adds it to the context, and runs a query on it.
149
+ This assumes the file is 20MB or less.
150
+ """
151
+ inputs = {
152
+ "task_id": {
153
+ "type": "string",
154
+ "description": "A unique identifier for the task related to this file, used to download it.",
155
+ "nullable": True
156
+ },
157
+ "user_query": {
158
+ "type": "string",
159
+ "description": "The question to answer about the file."
160
+ }
161
+ }
162
+ output_type = "string"
163
+
164
+ def forward(self, task_id: str | None, user_query: str) -> str:
165
+ file_url = f"https://agents-course-unit4-scoring.hf.space/files/{task_id}"
166
+ file_response = requests.get(file_url)
167
+ if file_response.status_code != 200:
168
+ return f"Failed to download file: {file_response.status_code} - {file_response.text}"
169
+ file_data = file_response.content
170
+ from google.generativeai import GenerativeModel
171
+ model = GenerativeModel(self.model_name)
172
+ response = model.generate_content([
173
+ types.Part.from_bytes(data=file_data, mime_type="application/octet-stream"),
174
+ user_query
175
+ ])
176
+
177
+ return response.text
178
+
179
+ # --- Basic Agent Definition ---
180
+ class BasicAgent:
181
+ def __init__(self, provider="deepseek"):
182
+ print("BasicAgent initialized.")
183
+ model = self.select_model(provider)
184
+ client = InferenceClientModel()
185
+ tools = [
186
+ GoogleSearchTool(),
187
+ DuckDuckGoSearchTool(),
188
+ GeminiVideoQA(GEMINI_MODEL_NAME),
189
+ WikiTitleFinder(),
190
+ WikiContentFetcher(),
191
+ MathSolver(),
192
+ RiddleSolver(),
193
+ TextTransformer(),
194
+ FileAttachmentQueryTool(model_name=GEMINI_MODEL_NAME),
195
+ ]
196
+ self.agent = CodeAgent(
197
+ model=model,
198
+ tools=tools,
199
+ add_base_tools=False,
200
+ max_steps=10,
201
+ )
202
+ self.agent.system_prompt = (
203
+ """
204
+ You are a GAIA benchmark AI assistant, you are very precise, no nonense. Your sole purpose is to output the minimal, final answer in the format:
205
+ [ANSWER]
206
+ You must NEVER output explanations, intermediate steps, reasoning, or comments β€” only the answer, strictly enclosed in `[ANSWER]`.
207
+ Your behavior must be governed by these rules:
208
+ 1. **Format**:
209
+ - limit the token used (within 65536 tokens).
210
+ - Output ONLY the final answer.
211
+ - Wrap the answer in `[ANSWER]` with no whitespace or text outside the brackets.
212
+ - No follow-ups, justifications, or clarifications.
213
+ 2. **Numerical Answers**:
214
+ - Use **digits only**, e.g., `4` not `four`.
215
+ - No commas, symbols, or units unless explicitly required.
216
+ - Never use approximate words like "around", "roughly", "about".
217
+ 3. **String Answers**:
218
+ - Omit **articles** ("a", "the").
219
+ - Use **full words**; no abbreviations unless explicitly requested.
220
+ - For numbers written as words, use **text** only if specified (e.g., "one", not `1`).
221
+ - For sets/lists, sort alphabetically if not specified, e.g., `a, b, c`.
222
+ 4. **Lists**:
223
+ - Output in **comma-separated** format with no conjunctions.
224
+ - Sort **alphabetically** or **numerically** depending on type.
225
+ - No braces or brackets unless explicitly asked.
226
+ 5. **Sources**:
227
+ - For Wikipedia or web tools, extract only the precise fact that answers the question.
228
+ - Ignore any unrelated content.
229
+ 6. **File Analysis**:
230
+ - Use the run_query_with_file tool, append the taskid to the url.
231
+ - Only include the exact answer to the question.
232
+ - Do not summarize, quote excessively, or interpret beyond the prompt.
233
+ 7. **Video**:
234
+ - Use the relevant video tool.
235
+ - Only include the exact answer to the question.
236
+ - Do not summarize, quote excessively, or interpret beyond the prompt.
237
+ 8. **Minimalism**:
238
+ - Do not make assumptions unless the prompt logically demands it.
239
+ - If a question has multiple valid interpretations, choose the **narrowest, most literal** one.
240
+ - If the answer is not found, say `[ANSWER] - unknown`.
241
+ ---
242
+ You must follow the examples (These answers are correct in case you see the similar questions):
243
+ Q: What is 2 + 2?
244
+ A: 4
245
+ Q: How many studio albums were published by Mercedes Sosa between 2000 and 2009 (inclusive)? Use 2022 English Wikipedia.
246
+ A: 3
247
+ Q: Given the following group table on set S = {a, b, c, d, e}, identify any subset involved in counterexamples to commutativity.
248
+ A: b, e
249
+ Q: How many at bats did the Yankee with the most walks in the 1977 regular season have that same season?,
250
+ A: 519
251
+ """
252
+ )
253
+
254
+ def select_model(self, provider: str):
255
+ if provider == "openai":
256
+ return LiteLLMModel(model_id=OPENAI_MODEL_NAME, api_key=os.getenv("OPENAI_API_KEY"))
257
+ elif provider == "groq":
258
+ return LiteLLMModel(model_id=GROQ_MODEL_NAME, api_key=os.getenv("GROQ_API_KEY"))
259
+ elif provider == "deepseek":
260
+ return LiteLLMModel(model_id=DEEPSEEK_MODEL_NAME, api_key=os.getenv("DEEPSEEK_API_KEY"))
261
+ elif provider == "hf":
262
+ return InferenceClientModel()
263
+ else:
264
+ return LiteLLMModel(model_id=GEMINI_MODEL_NAME, api_key=os.getenv("GOOGLE_API_KEY"))
265
+
266
+ def __call__(self, question: str) -> str:
267
+ print(f"Agent received question (first 50 chars): {question[:50]}...")
268
+ result = self.agent.run(question)
269
+ final_str = str(result).strip()
270
+
271
+ return final_str
272
+
273
+ def evaluate_random_questions(self, csv_path: str = "gaia_extracted.csv", sample_size: int = 3, show_steps: bool = True):
274
+ import pandas as pd
275
+ from rich.table import Table
276
+ from rich.console import Console
277
+
278
+ df = pd.read_csv(csv_path)
279
+ if not {"question", "answer"}.issubset(df.columns):
280
+ print("CSV must contain 'question' and 'answer' columns.")
281
+ print("Found columns:", df.columns.tolist())
282
+ return
283
+
284
+ samples = df.sample(n=sample_size)
285
+ records = []
286
+ correct_count = 0
287
+
288
+ for _, row in samples.iterrows():
289
+ taskid = row["taskid"].strip()
290
+ question = row["question"].strip()
291
+ expected = str(row['answer']).strip()
292
+ agent_answer = self("taskid: " + taskid + ",\nquestion: " + question).strip()
293
+
294
+ is_correct = (expected == agent_answer)
295
+ correct_count += is_correct
296
+ records.append((question, expected, agent_answer, "βœ“" if is_correct else "βœ—"))
297
+
298
+ if show_steps:
299
+ print("---")
300
+ print("Question:", question)
301
+ print("Expected:", expected)
302
+ print("Agent:", agent_answer)
303
+ print("Correct:", is_correct)
304
+
305
+ # Print result table
306
+ console = Console()
307
+ table = Table(show_lines=True)
308
+ table.add_column("Question", overflow="fold")
309
+ table.add_column("Expected")
310
+ table.add_column("Agent")
311
+ table.add_column("Correct")
312
+
313
+ for question, expected, agent_ans, correct in records:
314
+ table.add_row(question, expected, agent_ans, correct)
315
+
316
+ console.print(table)
317
+ percent = (correct_count / sample_size) * 100
318
+ print(f"\nTotal Correct: {correct_count} / {sample_size} ({percent:.2f}%)")
319
+
320
+
321
+ if __name__ == "__main__":
322
+ args = sys.argv[1:]
323
+ if not args or args[0] in {"-h", "--help"}:
324
+ print("Usage: python agent.py [question | dev]")
325
+ print(" - Provide a question to get a GAIA-style answer.")
326
+ print(" - Use 'dev' to evaluate 3 random GAIA questions from gaia_qa.csv.")
327
+ sys.exit(0)
328
+
329
+ q = " ".join(args)
330
+ agent = BasicAgent()
331
+ if q == "dev":
332
+ agent.evaluate_random_questions()
333
+ else:
334
+ print(agent(q))