alfulanny commited on
Commit
9bd1513
·
verified ·
1 Parent(s): 7d726d9

Upload 3 files

Browse files
Files changed (3) hide show
  1. main.py +242 -0
  2. requirements.txt +12 -0
  3. smolagents_agent.py +304 -0
main.py ADDED
@@ -0,0 +1,242 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import gradio as gr
3
+ import requests
4
+ import inspect
5
+ import pandas as pd
6
+ from smolagents_agent import SmolagentsGAIAgent
7
+ from huggingface_hub import login
8
+
9
+ # --- Constants ---
10
+
11
+ login(os.getenv("HF_TOKEN"))
12
+ DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
13
+
14
+ def run_and_submit_all(profile: gr.OAuthProfile | None):
15
+ """
16
+ Fetches all questions, runs the AdvancedAgent on them, submits all answers,
17
+ and displays the results.
18
+ """
19
+ # --- Determine HF Space Runtime URL and Repo URL ---
20
+ space_id = os.getenv("SPACE_ID")
21
+
22
+ if profile:
23
+ username = f"{profile.username}"
24
+ print(f"User logged in: {username}")
25
+ else:
26
+ print("User not logged in.")
27
+ return "Please Login to Hugging Face with the button.", None
28
+
29
+ api_url = DEFAULT_API_URL
30
+ questions_url = f"{api_url}/questions"
31
+ submit_url = f"{api_url}/submit"
32
+
33
+ # 1. Instantiate Smolagents GAIA Agent
34
+ try:
35
+ agent = SmolagentsGAIAgent()
36
+ print("Smolagents GAIA Agent initialized successfully!")
37
+ except Exception as e:
38
+ print(f"Error instantiating smolagents agent: {e}")
39
+ return f"Error initializing smolagents agent: {e}", None
40
+
41
+ agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
42
+ print(f"Agent code link: {agent_code}")
43
+
44
+ # 2. Fetch Questions
45
+ print(f"Fetching questions from: {questions_url}")
46
+ try:
47
+ response = requests.get(questions_url, timeout=15)
48
+ response.raise_for_status()
49
+ questions_data = response.json()
50
+ if not questions_data:
51
+ print("Fetched questions list is empty.")
52
+ return "Fetched questions list is empty or invalid format.", None
53
+ print(f"Fetched {len(questions_data)} questions.")
54
+ except requests.exceptions.RequestException as e:
55
+ print(f"Error fetching questions: {e}")
56
+ return f"Error fetching questions: {e}", None
57
+ except requests.exceptions.JSONDecodeError as e:
58
+ print(f"Error decoding JSON response from questions endpoint: {e}")
59
+ print(f"Response text: {response.text[:500]}")
60
+ return f"Error decoding server response for questions: {e}", None
61
+ except Exception as e:
62
+ print(f"An unexpected error occurred fetching questions: {e}")
63
+ return f"An unexpected error occurred fetching questions: {e}", None
64
+
65
+ # 3. Run Advanced Agent
66
+ results_log = []
67
+ answers_payload = []
68
+ print(f"Running multi-agent system on {len(questions_data)} questions...")
69
+
70
+ for i, item in enumerate(questions_data):
71
+ task_id = item.get("task_id")
72
+ question_text = item.get("question")
73
+
74
+ if not task_id or question_text is None:
75
+ print(f"Skipping item with missing task_id or question: {item}")
76
+ continue
77
+
78
+ try:
79
+ print(f"\n--- Processing Question {i+1}/{len(questions_data)} ---")
80
+ print(f"Task ID: {task_id}")
81
+ print(f"Question: {question_text[:100]}...")
82
+
83
+ # Run the smolagents agent
84
+ submitted_answer = agent.process_question(question_text)
85
+
86
+ answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
87
+ results_log.append({
88
+ "Task ID": task_id,
89
+ "Question": question_text,
90
+ "Submitted Answer": submitted_answer
91
+ })
92
+
93
+ print(f"Answer: {submitted_answer[:100]}...")
94
+
95
+ except Exception as e:
96
+ print(f"Error running agent on task {task_id}: {e}")
97
+ error_answer = f"AGENT ERROR: {e}"
98
+ answers_payload.append({"task_id": task_id, "submitted_answer": error_answer})
99
+ results_log.append({
100
+ "Task ID": task_id,
101
+ "Question": question_text,
102
+ "Submitted Answer": error_answer
103
+ })
104
+
105
+ if not answers_payload:
106
+ print("Smolagents agent did not produce any answers to submit.")
107
+ return "Smolagents agent did not produce any answers to submit.", pd.DataFrame(results_log)
108
+
109
+ # 4. Prepare Submission
110
+ submission_data = {
111
+ "username": username.strip(),
112
+ "agent_code": agent_code,
113
+ "answers": answers_payload
114
+ }
115
+
116
+ status_update = f"Smolagents agent finished. Submitting {len(answers_payload)} answers for user '{username}'..."
117
+ print(status_update)
118
+
119
+ # 5. Submit
120
+ print(f"Submitting {len(answers_payload)} answers to: {submit_url}")
121
+ try:
122
+ response = requests.post(submit_url, json=submission_data, timeout=60)
123
+ response.raise_for_status()
124
+ result_data = response.json()
125
+
126
+ final_status = (
127
+ f"Submission Successful!\n"
128
+ f"User: {result_data.get('username')}\n"
129
+ f"Overall Score: {result_data.get('score', 'N/A')}% "
130
+ f"({result_data.get('correct_count', '?')}/{result_data.get('total_attempted', '?')} correct)\n"
131
+ f"Message: {result_data.get('message', 'No message received.')}"
132
+ )
133
+
134
+ print("Submission successful!")
135
+ results_df = pd.DataFrame(results_log)
136
+ return final_status, results_df
137
+
138
+ except requests.exceptions.HTTPError as e:
139
+ error_detail = f"Server responded with status {e.response.status_code}."
140
+ try:
141
+ error_json = e.response.json()
142
+ error_detail += f" Detail: {error_json.get('detail', e.response.text)}"
143
+ except requests.exceptions.JSONDecodeError:
144
+ error_detail += f" Response: {e.response.text[:500]}"
145
+
146
+ status_message = f"Submission Failed: {error_detail}"
147
+ print(status_message)
148
+ results_df = pd.DataFrame(results_log)
149
+ return status_message, results_df
150
+
151
+ except requests.exceptions.Timeout:
152
+ status_message = "Submission Failed: The request timed out."
153
+ print(status_message)
154
+ results_df = pd.DataFrame(results_log)
155
+ return status_message, results_df
156
+
157
+ except requests.exceptions.RequestException as e:
158
+ status_message = f"Submission Failed: Network error - {e}"
159
+ print(status_message)
160
+ results_df = pd.DataFrame(results_log)
161
+ return status_message, results_df
162
+
163
+ except Exception as e:
164
+ status_message = f"An unexpected error occurred during submission: {e}"
165
+ print(status_message)
166
+ results_df = pd.DataFrame(results_log)
167
+ return status_message, results_df
168
+
169
+
170
+ # --- Build Gradio Interface using Blocks ---
171
+ with gr.Blocks(title="Multi-Agent System - GAIA Benchmark") as demo:
172
+ gr.Markdown("# Smolagents GAIA Agent Evaluation")
173
+ gr.Markdown(
174
+ """
175
+ ## Your Smolagents Agent Features:
176
+ - **Calculator Tool**: Mathematical calculations and equation solving
177
+ - **Web Search Tool**: Real-time information retrieval from the web
178
+ - **Wikipedia Tool**: Access to structured knowledge and facts
179
+ - **File Processing Tools**: PDF and CSV document analysis
180
+ - **Reasoning Tool**: Logical analysis and problem decomposition
181
+ - **Visit Webpage Tool**: Direct webpage content extraction
182
+
183
+ ## Instructions:
184
+ 1. **Login** to Hugging Face using the button below
185
+ 2. **Click** 'Run Evaluation & Submit All Answers'
186
+ 3. **Wait** for your smolagents agent to process all questions (this may take time)
187
+ 4. **View** your score and detailed results
188
+
189
+ ## How the Smolagents Agent Works:
190
+ - **Question Classification**: Automatically routes questions to appropriate tools
191
+ - **Tool Integration**: Seamlessly uses multiple tools for comprehensive answers
192
+ - **Code Generation**: Leverages Python code execution for complex tasks
193
+ - **Iterative Refinement**: Improves answers through multiple reasoning steps
194
+
195
+ **Note**: This evaluation may take several minutes as the agent processes questions using the GAIA benchmark.
196
+ """
197
+ )
198
+
199
+ gr.LoginButton()
200
+
201
+ run_button = gr.Button("Run Smolagents GAIA Evaluation & Submit All Answers", variant="primary")
202
+
203
+ status_output = gr.Textbox(
204
+ label="📊 Run Status / Submission Result",
205
+ lines=8,
206
+ interactive=False
207
+ )
208
+
209
+ results_table = gr.DataFrame(
210
+ label="📋 Questions and Agent Answers",
211
+ wrap=True
212
+ )
213
+
214
+ run_button.click(
215
+ fn=run_and_submit_all,
216
+ outputs=[status_output, results_table]
217
+ )
218
+
219
+ if __name__ == "__main__":
220
+ print("\n" + "="*50 + " Smolagents GAIA Agent App Starting " + "="*50)
221
+
222
+ # Check environment variables
223
+ space_host_startup = os.getenv("SPACE_HOST")
224
+ space_id_startup = os.getenv("SPACE_ID")
225
+
226
+ if space_host_startup:
227
+ print(f"[OK] SPACE_HOST found: {space_host_startup}")
228
+ print(f" Runtime URL: https://{space_host_startup}.hf.space")
229
+ else:
230
+ print("[INFO] SPACE_HOST not found (running locally)")
231
+
232
+ if space_id_startup:
233
+ print(f"[OK] SPACE_ID found: {space_id_startup}")
234
+ print(f" Repo URL: https://huggingface.co/spaces/{space_id_startup}")
235
+ print(f" Code URL: https://huggingface.co/spaces/{space_id_startup}/tree/main")
236
+ else:
237
+ print("[INFO] SPACE_ID not found (running locally)")
238
+
239
+ print("="*(100 + len(" Smolagents GAIA Agent App Starting ")) + "\n")
240
+
241
+ print("Launching Smolagents GAIA Agent Evaluation Interface...")
242
+ demo.launch(debug=True, share=False)
requirements.txt ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ gradio[oauth]
2
+ requests
3
+ pandas
4
+ numpy
5
+ duckduckgo-search
6
+ wikipedia
7
+ PyPDF2
8
+ python-docx
9
+ beautifulsoup4
10
+ sympy
11
+ smolagents[litellm]
12
+ python-dotenv
smolagents_agent.py ADDED
@@ -0,0 +1,304 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from smolagents import Tool, CodeAgent, InferenceClientModel, LiteLLMModel
2
+ from smolagents import DuckDuckGoSearchTool, VisitWebpageTool
3
+ import os
4
+ import re
5
+ import math
6
+ import ast
7
+ import pandas as pd
8
+ from typing import Optional
9
+ from dotenv import load_dotenv
10
+
11
+ # Load environment variables from .env file
12
+ load_dotenv()
13
+
14
+ # Custom Calculator Tool
15
+ class CalculatorTool(Tool):
16
+ name = "calculator"
17
+ description = "A tool to perform mathematical calculations and solve equations"
18
+ inputs = {
19
+ "expression": {
20
+ "type": "string",
21
+ "description": "The mathematical expression to evaluate (e.g., '2 + 3 * 4', 'sqrt(16)', 'pi * r**2')"
22
+ }
23
+ }
24
+ output_type = "string"
25
+
26
+ def forward(self, expression: str) -> str:
27
+ try:
28
+ # Clean and preprocess expression
29
+ expression = expression.strip()
30
+
31
+ # Replace common math functions
32
+ replacements = {
33
+ 'sin': 'math.sin',
34
+ 'cos': 'math.cos',
35
+ 'tan': 'math.tan',
36
+ 'log': 'math.log',
37
+ 'sqrt': 'math.sqrt',
38
+ 'pi': 'math.pi',
39
+ 'e': 'math.e',
40
+ '^': '**'
41
+ }
42
+
43
+ for old, new in replacements.items():
44
+ expression = expression.replace(old, new)
45
+
46
+ # Safe evaluation
47
+ allowed_names = {
48
+ 'math': math,
49
+ 'abs': abs,
50
+ 'min': min,
51
+ 'max': max,
52
+ 'round': round,
53
+ 'sum': sum
54
+ }
55
+
56
+ result = eval(expression, {"__builtins__": {}}, allowed_names)
57
+ return str(result)
58
+
59
+ except Exception as e:
60
+ return f"Calculation error: {str(e)}"
61
+
62
+ # Custom Wikipedia Search Tool (since WikipediaSearchTool might not be available)
63
+ class WikipediaTool(Tool):
64
+ name = "wikipedia_search"
65
+ description = "Search Wikipedia for information about a topic"
66
+ inputs = {
67
+ "topic": {
68
+ "type": "string",
69
+ "description": "The topic to search for on Wikipedia"
70
+ }
71
+ }
72
+ output_type = "string"
73
+
74
+ def forward(self, topic: str) -> str:
75
+ try:
76
+ import wikipedia
77
+ summary = wikipedia.summary(topic, sentences=3)
78
+ return summary
79
+ except wikipedia.exceptions.DisambiguationError as e:
80
+ options = e.options[:3]
81
+ return f"Multiple options found: {', '.join(options)}. Please be more specific."
82
+ except Exception as e:
83
+ return f"Wikipedia search error: {str(e)}"
84
+
85
+ # File Processing Tools
86
+ class PDFReaderTool(Tool):
87
+ name = "pdf_reader"
88
+ description = "Extract text content from a PDF file"
89
+ inputs = {
90
+ "file_path": {
91
+ "type": "string",
92
+ "description": "Path to the PDF file to read"
93
+ }
94
+ }
95
+ output_type = "string"
96
+
97
+ def forward(self, file_path: str) -> str:
98
+ try:
99
+ import PyPDF2
100
+ with open(file_path, 'rb') as file:
101
+ pdf_reader = PyPDF2.PdfReader(file)
102
+ text = ""
103
+ for page in pdf_reader.pages:
104
+ text += page.extract_text()
105
+ return text.strip()
106
+ except Exception as e:
107
+ return f"PDF reading error: {str(e)}"
108
+
109
+ class CSVAnalyzerTool(Tool):
110
+ name = "csv_analyzer"
111
+ description = "Analyze and summarize CSV file data"
112
+ inputs = {
113
+ "file_path": {
114
+ "type": "string",
115
+ "description": "Path to the CSV file to analyze"
116
+ }
117
+ }
118
+ output_type = "string"
119
+
120
+ def forward(self, file_path: str) -> str:
121
+ try:
122
+ df = pd.read_csv(file_path)
123
+ summary = f"Shape: {df.shape}\n"
124
+ summary += f"Columns: {list(df.columns)}\n"
125
+ summary += f"Sample data:\n{df.head().to_string()}\n"
126
+
127
+ if df.select_dtypes(include=[float, int]).shape[1] > 0:
128
+ summary += f"Statistics:\n{df.describe().to_string()}"
129
+
130
+ return summary
131
+ except Exception as e:
132
+ return f"CSV analysis error: {str(e)}"
133
+
134
+ # Reasoning Tool
135
+ class ReasoningTool(Tool):
136
+ name = "reasoning_helper"
137
+ description = "Help with logical reasoning and problem decomposition"
138
+ inputs = {
139
+ "question": {
140
+ "type": "string",
141
+ "description": "The reasoning question or problem to analyze"
142
+ }
143
+ }
144
+ output_type = "string"
145
+
146
+ def forward(self, question: str) -> str:
147
+ # Simple reasoning helper - decompose questions
148
+ sub_questions = []
149
+
150
+ # Look for multi-part questions
151
+ if any(word in question.lower() for word in ['and', 'also', 'additionally', 'furthermore']):
152
+ parts = re.split(r'\band\b|\balso\b|\badditionally\b|\bfurthermore\b', question, flags=re.IGNORECASE)
153
+ sub_questions = [part.strip() for part in parts if part.strip()]
154
+
155
+ # Look for numbered questions
156
+ numbered_pattern = r'(\d+)\.\s*(.+?)(?=\d+\.|$)'
157
+ matches = re.findall(numbered_pattern, question)
158
+ if matches:
159
+ sub_questions = [match[1].strip() for match in matches]
160
+
161
+ if sub_questions:
162
+ return f"This appears to be a multi-part question. Breaking it down:\n" + "\n".join(f"- {q}" for q in sub_questions)
163
+ else:
164
+ return "This appears to be a single reasoning question. Consider breaking it down into smaller steps or gathering more information."
165
+
166
+ # Main Smolagents Agent
167
+ class SmolagentsGAIAgent:
168
+ def __init__(self):
169
+ # Initialize tools
170
+ self.calculator = CalculatorTool()
171
+ self.wikipedia = WikipediaTool()
172
+ self.pdf_reader = PDFReaderTool()
173
+ self.csv_analyzer = CSVAnalyzerTool()
174
+ self.reasoning = ReasoningTool()
175
+
176
+ # Use built-in search tools
177
+ self.web_search = DuckDuckGoSearchTool()
178
+ self.visit_webpage = VisitWebpageTool()
179
+
180
+ # Collect all tools
181
+ self.tools = [
182
+ self.calculator,
183
+ self.wikipedia,
184
+ self.pdf_reader,
185
+ self.csv_analyzer,
186
+ self.reasoning,
187
+ self.web_search,
188
+ self.visit_webpage
189
+ ]
190
+
191
+ # Initialize model
192
+ self.model = self._initialize_model()
193
+
194
+ # Create the agent
195
+ self.agent = CodeAgent(
196
+ tools=self.tools,
197
+ model=self.model,
198
+ max_steps=10, # Limit steps for GAIA efficiency
199
+ verbosity_level=1
200
+ )
201
+
202
+ def _initialize_model(self):
203
+ """Initialize the language model"""
204
+ hf_token = os.getenv("HF_TOKEN")
205
+
206
+ # First try local Ollama (recommended by course)
207
+ try:
208
+ model = LiteLLMModel(
209
+ model_id="ollama_chat/qwen2:7b",
210
+ api_base="http://127.0.0.1:11434"
211
+ )
212
+ print("Using local Ollama model (qwen2:7b)")
213
+ return model
214
+ except Exception as e:
215
+ print(f"Local Ollama not available: {e}")
216
+
217
+ # Fallback to HF Inference API if token available
218
+ if hf_token:
219
+ try:
220
+ model = InferenceClientModel(
221
+ model_id="Qwen/Qwen2.5-7B-Instruct",
222
+ token=hf_token
223
+ )
224
+ print("Using Hugging Face Inference API")
225
+ return model
226
+ except Exception as e:
227
+ print(f"HF Inference API failed: {e}")
228
+
229
+ print("No language model available. Please:")
230
+ print(" 1. Install Ollama: https://ollama.ai/")
231
+ print(" 2. Run: ollama pull qwen2:7b")
232
+ print(" 3. Run: ollama serve")
233
+ print(" Or ensure HF_TOKEN has proper permissions")
234
+ return None
235
+
236
+ def classify_question(self, question: str) -> str:
237
+ """Classify question type for routing"""
238
+ question_lower = question.lower()
239
+
240
+ # Mathematical questions
241
+ math_keywords = ['calculate', 'compute', 'solve', 'equation', 'formula', 'sum', 'product', 'area', 'radius', 'sqrt']
242
+ has_math = any(keyword in question_lower for keyword in math_keywords)
243
+ has_arithmetic = bool(re.search(r'\d+[\s\+\-\*\/x]\d+', question))
244
+
245
+ if has_math or has_arithmetic or (re.search(r'\d+', question) and ('what' in question_lower or 'how' in question_lower)):
246
+ return "mathematical"
247
+
248
+ # File processing
249
+ file_keywords = ['pdf', 'document', 'file', 'csv', 'excel', 'text', 'read']
250
+ if any(keyword in question_lower for keyword in file_keywords):
251
+ return "file_processing"
252
+
253
+ # Reasoning
254
+ reasoning_keywords = ['why', 'explain', 'reason', 'logic', 'conclusion', 'infer', 'deduce']
255
+ if any(keyword in question_lower for keyword in reasoning_keywords):
256
+ return "reasoning"
257
+
258
+ # Factual (default)
259
+ return "factual"
260
+
261
+ def process_question(self, question: str) -> str:
262
+ """Process a GAIA question using the smolagents framework"""
263
+ if not self.model:
264
+ return "Error: No language model available. Please set HF_TOKEN or run local Ollama."
265
+
266
+ try:
267
+ # Classify and route the question
268
+ question_type = self.classify_question(question)
269
+
270
+ # Create a focused prompt based on question type
271
+ if question_type == "mathematical":
272
+ prompt = f"Solve this mathematical problem step by step: {question}"
273
+ elif question_type == "factual":
274
+ prompt = f"Find accurate information for this question: {question}"
275
+ elif question_type == "reasoning":
276
+ prompt = f"Reason step by step to answer this question: {question}"
277
+ elif question_type == "file_processing":
278
+ prompt = f"Process this file-related question: {question}"
279
+ else:
280
+ prompt = question
281
+
282
+ # Run the agent
283
+ result = self.agent.run(prompt)
284
+ return str(result)
285
+
286
+ except Exception as e:
287
+ return f"Agent processing error: {str(e)}"
288
+
289
+ # Test the agent
290
+ if __name__ == "__main__":
291
+ agent = SmolagentsGAIAgent()
292
+
293
+ test_questions = [
294
+ "What is the capital of France?",
295
+ "Calculate 15 + 27 * 3",
296
+ "Who wrote Romeo and Juliet?",
297
+ "What is the square root of 144?",
298
+ "Explain why the sky is blue"
299
+ ]
300
+
301
+ for question in test_questions:
302
+ print(f"\nQ: {question}")
303
+ answer = agent.process_question(question)
304
+ print(f"A: {answer[:200]}...")