Maheen001 commited on
Commit
865c655
·
verified ·
1 Parent(s): 6ace6f3

Update agent/agent_core.py

Browse files
Files changed (1) hide show
  1. agent/agent_core.py +183 -224
agent/agent_core.py CHANGED
@@ -16,6 +16,10 @@ from agent.memory import MemoryStore
16
  from utils.llm_utils import get_llm_response
17
 
18
 
 
 
 
 
19
  class TaskStatus(Enum):
20
  PENDING = "pending"
21
  IN_PROGRESS = "in_progress"
@@ -27,13 +31,13 @@ class TaskStatus(Enum):
27
  class AgentThought:
28
  """Represents a thought/step in agent reasoning"""
29
  step: int
30
- type: str # 'planning', 'tool_call', 'reflection', 'answer'
31
  content: str
32
  tool_name: Optional[str] = None
33
  tool_args: Optional[Dict] = None
34
  tool_result: Optional[Any] = None
35
  timestamp: float = None
36
-
37
  def __post_init__(self):
38
  if self.timestamp is None:
39
  self.timestamp = time.time()
@@ -41,7 +45,7 @@ class AgentThought:
41
 
42
  @dataclass
43
  class AgentTask:
44
- """Represents a task to be executed"""
45
  id: str
46
  description: str
47
  tool: str
@@ -51,328 +55,283 @@ class AgentTask:
51
  error: Optional[str] = None
52
 
53
 
 
 
 
 
54
  class LifeAdminAgent:
55
- """Main autonomous agent with planning, tool calling, and reflection"""
56
-
57
  def __init__(self):
58
  self.mcp_client = MCPClient()
59
  self.rag_engine = RAGEngine()
60
  self.memory = MemoryStore()
61
  self.thoughts: List[AgentThought] = []
62
  self.current_context = {}
63
-
 
 
 
 
64
  def reset_context(self):
65
- """Reset agent context for new task"""
66
  self.thoughts = []
67
  self.current_context = {}
68
-
 
 
 
 
69
  async def plan(self, user_request: str, available_files: List[str] = None) -> List[AgentTask]:
70
- """
71
- Create execution plan from user request
72
-
73
- Args:
74
- user_request: Natural language request from user
75
- available_files: List of uploaded files
76
-
77
- Returns:
78
- List of tasks to execute
79
- """
80
  self.thoughts.append(AgentThought(
81
  step=len(self.thoughts) + 1,
82
- type='planning',
83
  content=f"Analyzing request: {user_request}"
84
  ))
85
-
86
- # Get available tools
87
  tools = await self.mcp_client.list_tools()
88
  tool_descriptions = "\n".join([
89
- f"- {tool['name']}: {tool.get('description', '')}"
90
- for tool in tools
91
  ])
92
-
93
- # Search RAG for relevant context
94
  relevant_docs = []
95
- if user_request:
96
  relevant_docs = await self.rag_engine.search(user_request, k=3)
97
-
98
- context = "\n".join([doc['text'][:200] for doc in relevant_docs]) if relevant_docs else "No previous documents"
99
-
100
- # Get memory
 
 
101
  memory_context = self.memory.get_relevant_memories(user_request)
102
-
103
- # Create planning prompt
104
- planning_prompt = f"""You are an autonomous life admin agent. Create a step-by-step execution plan.
105
 
106
- USER REQUEST: {user_request}
 
 
 
 
 
107
 
108
- AVAILABLE FILES: {', '.join(available_files) if available_files else 'None'}
 
109
 
110
  AVAILABLE TOOLS:
111
  {tool_descriptions}
112
 
113
- RELEVANT CONTEXT:
114
- {context}
115
 
116
  MEMORY:
117
  {memory_context}
118
 
119
- Create a JSON plan with tasks. Each task should have:
120
- - id: unique identifier
121
- - description: what this task does
122
- - tool: which tool to use
123
- - args: arguments for the tool (as a dict)
124
-
125
- Return ONLY valid JSON array of tasks, no other text.
126
-
127
- Example format:
128
  [
129
  {{
130
- "id": "task_1",
131
- "description": "Extract text from document",
132
  "tool": "ocr_extract_text",
133
- "args": {{"file_path": "document.pdf", "language": "en"}}
134
  }}
135
  ]
136
  """
137
-
138
  self.thoughts.append(AgentThought(
139
  step=len(self.thoughts) + 1,
140
- type='planning',
141
- content="Creating execution plan with LLM..."
142
  ))
143
-
144
  try:
145
- plan_response = await get_llm_response(planning_prompt, temperature=0.3)
146
-
147
- # Extract JSON from response
148
- plan_text = plan_response.strip()
149
- if '```json' in plan_text:
150
- plan_text = plan_text.split('```json')[1].split('```')[0].strip()
151
- elif '```' in plan_text:
152
- plan_text = plan_text.split('```')[1].split('```')[0].strip()
153
-
154
- tasks_data = json.loads(plan_text)
155
-
156
  tasks = [
157
- AgentTask(**{**task, 'status': TaskStatus.PENDING})
158
- for task in tasks_data
159
  ]
160
-
161
  self.thoughts.append(AgentThought(
162
  step=len(self.thoughts) + 1,
163
- type='planning',
164
- content=f"Created plan with {len(tasks)} tasks"
165
  ))
166
-
167
  return tasks
168
-
169
  except Exception as e:
170
  self.thoughts.append(AgentThought(
171
  step=len(self.thoughts) + 1,
172
- type='planning',
173
- content=f"Planning failed: {str(e)}"
174
  ))
175
  return []
176
-
 
 
 
 
177
  async def execute_task(self, task: AgentTask) -> AgentTask:
178
- """Execute a single task using MCP tools"""
179
-
180
  self.thoughts.append(AgentThought(
181
  step=len(self.thoughts) + 1,
182
- type='tool_call',
183
- content=f"Executing: {task.description}",
184
  tool_name=task.tool,
185
  tool_args=task.args
186
  ))
187
-
188
  task.status = TaskStatus.IN_PROGRESS
189
-
190
  try:
191
- # Call MCP tool
192
  result = await self.mcp_client.call_tool(task.tool, task.args)
193
-
194
- task.result = result
195
  task.status = TaskStatus.COMPLETED
196
-
 
197
  self.thoughts.append(AgentThought(
198
  step=len(self.thoughts) + 1,
199
- type='tool_call',
200
  content=f"✓ Completed: {task.description}",
201
  tool_name=task.tool,
202
  tool_result=result
203
  ))
204
-
205
- return task
206
-
207
  except Exception as e:
208
- task.error = str(e)
209
  task.status = TaskStatus.FAILED
210
-
 
211
  self.thoughts.append(AgentThought(
212
  step=len(self.thoughts) + 1,
213
- type='tool_call',
214
- content=f"✗ Failed: {task.description} - {str(e)}",
215
  tool_name=task.tool
216
  ))
217
-
218
- return task
219
-
220
- async def reflect(self, tasks: List[AgentTask], original_request: str) -> str:
221
- """
222
- Reflect on execution results and create final answer
223
-
224
- Args:
225
- tasks: Executed tasks
226
- original_request: Original user request
227
-
228
- Returns:
229
- Final answer string
230
- """
231
  self.thoughts.append(AgentThought(
232
  step=len(self.thoughts) + 1,
233
- type='reflection',
234
- content="Analyzing results and creating response..."
235
  ))
236
-
237
- # Compile results
238
- results_summary = []
239
- for task in tasks:
240
- if task.status == TaskStatus.COMPLETED:
241
- results_summary.append(f"✓ {task.description}: {str(task.result)[:200]}")
242
  else:
243
- results_summary.append(f"✗ {task.description}: {task.error}")
244
-
245
- reflection_prompt = f"""You are an autonomous life admin agent. Review the execution results and create a helpful response.
246
 
247
- ORIGINAL REQUEST: {original_request}
 
248
 
249
- EXECUTION RESULTS:
250
- {chr(10).join(results_summary)}
251
 
252
- Provide a clear, helpful response to the user about what was accomplished. Be specific about:
253
- 1. What tasks were completed successfully
254
- 2. What outputs were created (files, calendar events, etc.)
255
- 3. Any issues encountered
256
- 4. Next steps if applicable
257
 
258
- Keep response concise but informative.
 
 
 
 
259
  """
260
-
261
  try:
262
- final_answer = await get_llm_response(reflection_prompt, temperature=0.7)
263
-
264
  self.thoughts.append(AgentThought(
265
  step=len(self.thoughts) + 1,
266
- type='answer',
267
- content=final_answer
268
  ))
269
-
270
- # Store in memory
271
  self.memory.add_memory(
272
- f"Request: {original_request}\nResult: {final_answer}",
273
- metadata={'type': 'task_completion', 'timestamp': time.time()}
274
  )
275
-
276
- return final_answer
277
-
278
  except Exception as e:
279
- error_msg = f"Reflection failed: {str(e)}"
 
280
  self.thoughts.append(AgentThought(
281
  step=len(self.thoughts) + 1,
282
- type='answer',
283
- content=error_msg
284
  ))
285
- return error_msg
286
-
287
- async def execute(self, user_request: str, files: List[str] = None, stream_thoughts: bool = False):
 
 
 
 
 
288
  """
289
- Main execution loop - plan, execute, reflect
290
-
291
- Args:
292
- user_request: User's natural language request
293
- files: Uploaded files to process
294
- stream_thoughts: Whether to yield thoughts as they happen
295
-
296
- Yields:
297
- Thoughts if stream_thoughts=True
298
-
299
- Returns:
300
- Final answer and complete thought trace
301
  """
 
302
  self.reset_context()
303
-
304
- # Phase 1: Planning
305
- if stream_thoughts:
306
- yield self.thoughts[-1] if self.thoughts else None
307
-
308
- tasks = await self.plan(user_request, files)
309
-
310
  if stream_thoughts:
311
- for thought in self.thoughts[-2:]: # Last 2 planning thoughts
312
- yield thought
313
-
314
  if not tasks:
315
- error_thought = AgentThought(
 
316
  step=len(self.thoughts) + 1,
317
- type='answer',
318
- content="Could not create execution plan. Please rephrase your request."
319
  )
320
- self.thoughts.append(error_thought)
321
- return error_thought.content, self.thoughts
322
-
323
- # Phase 2: Execution
324
- executed_tasks = []
325
- for task in tasks:
326
- executed_task = await self.execute_task(task)
327
- executed_tasks.append(executed_task)
328
-
329
  if stream_thoughts:
330
- yield self.thoughts[-1] # Latest thought
331
-
332
- # Phase 3: Reflection
333
- final_answer = await self.reflect(executed_tasks, user_request)
334
-
 
 
 
 
 
 
 
 
335
  if stream_thoughts:
336
- yield self.thoughts[-1] # Final answer thought
337
-
 
 
338
  return final_answer, self.thoughts
339
-
 
 
 
 
340
  def get_thought_trace(self) -> List[Dict]:
341
- """Get formatted thought trace for UI display"""
342
- return [asdict(thought) for thought in self.thoughts]
343
-
344
- async def process_files_to_rag(self, files: List[Dict[str, str]]):
345
- """Process uploaded files and add to RAG engine"""
346
- for file_info in files:
347
- try:
348
- # Extract text based on file type
349
- if file_info['path'].endswith('.pdf'):
350
- from utils.pdf_utils import extract_text_from_pdf
351
- text = extract_text_from_pdf(file_info['path'])
352
- elif file_info['path'].endswith(('.png', '.jpg', '.jpeg')):
353
- # Use OCR tool
354
- result = await self.mcp_client.call_tool(
355
- 'ocr_extract_text',
356
- {'file_path': file_info['path'], 'language': 'en'}
357
- )
358
- text = result.get('text', '')
359
- else:
360
- with open(file_info['path'], 'r', encoding='utf-8') as f:
361
- text = f.read()
362
-
363
- # Add to RAG
364
- await self.rag_engine.add_document(
365
- text=text,
366
- metadata={'filename': file_info['name'], 'path': file_info['path']}
367
- )
368
-
369
- except Exception as e:
370
- print(f"Error processing {file_info['name']}: {e}")
371
-
372
- async def manual_tool_call(self, tool_name: str, args: Dict[str, Any]) -> Any:
373
- """Direct tool call for manual mode"""
374
- try:
375
- result = await self.mcp_client.call_tool(tool_name, args)
376
- return {'success': True, 'result': result}
377
- except Exception as e:
378
- return {'success': False, 'error': str(e)}
 
16
  from utils.llm_utils import get_llm_response
17
 
18
 
19
+ # -------------------------
20
+ # ENUMS & MODELS
21
+ # -------------------------
22
+
23
  class TaskStatus(Enum):
24
  PENDING = "pending"
25
  IN_PROGRESS = "in_progress"
 
31
  class AgentThought:
32
  """Represents a thought/step in agent reasoning"""
33
  step: int
34
+ type: str # planning | tool_call | reflection | answer
35
  content: str
36
  tool_name: Optional[str] = None
37
  tool_args: Optional[Dict] = None
38
  tool_result: Optional[Any] = None
39
  timestamp: float = None
40
+
41
  def __post_init__(self):
42
  if self.timestamp is None:
43
  self.timestamp = time.time()
 
45
 
46
  @dataclass
47
  class AgentTask:
48
+ """Represents an atomic MCP operation"""
49
  id: str
50
  description: str
51
  tool: str
 
55
  error: Optional[str] = None
56
 
57
 
58
+ # -------------------------
59
+ # MAIN AGENT CLASS
60
+ # -------------------------
61
+
62
  class LifeAdminAgent:
63
+
 
64
  def __init__(self):
65
  self.mcp_client = MCPClient()
66
  self.rag_engine = RAGEngine()
67
  self.memory = MemoryStore()
68
  self.thoughts: List[AgentThought] = []
69
  self.current_context = {}
70
+
71
+ # -------------------------------------------
72
+ # RESET
73
+ # -------------------------------------------
74
+
75
  def reset_context(self):
 
76
  self.thoughts = []
77
  self.current_context = {}
78
+
79
+ # -------------------------------------------
80
+ # PLANNING PHASE
81
+ # -------------------------------------------
82
+
83
  async def plan(self, user_request: str, available_files: List[str] = None) -> List[AgentTask]:
84
+
 
 
 
 
 
 
 
 
 
85
  self.thoughts.append(AgentThought(
86
  step=len(self.thoughts) + 1,
87
+ type="planning",
88
  content=f"Analyzing request: {user_request}"
89
  ))
90
+
91
+ # List tools available through MCP
92
  tools = await self.mcp_client.list_tools()
93
  tool_descriptions = "\n".join([
94
+ f"- {tool['name']}: {tool.get('description', '')}" for tool in tools
 
95
  ])
96
+
97
+ # RAG context
98
  relevant_docs = []
99
+ if user_request.strip():
100
  relevant_docs = await self.rag_engine.search(user_request, k=3)
101
+
102
+ rag_context = "\n".join(
103
+ [doc["text"][:200] for doc in relevant_docs]
104
+ ) if relevant_docs else "No relevant documents"
105
+
106
+ # Memory
107
  memory_context = self.memory.get_relevant_memories(user_request)
 
 
 
108
 
109
+ # Build plan prompt
110
+ planning_prompt = f"""
111
+ You are an autonomous assistant. Create a JSON task plan.
112
+
113
+ USER REQUEST:
114
+ {user_request}
115
 
116
+ AVAILABLE FILES:
117
+ {', '.join(available_files) if available_files else 'None'}
118
 
119
  AVAILABLE TOOLS:
120
  {tool_descriptions}
121
 
122
+ RAG CONTEXT:
123
+ {rag_context}
124
 
125
  MEMORY:
126
  {memory_context}
127
 
128
+ Return ONLY valid JSON list of tasks like:
 
 
 
 
 
 
 
 
129
  [
130
  {{
131
+ "id": "t1",
132
+ "description": "Extract text",
133
  "tool": "ocr_extract_text",
134
+ "args": {{"file_path": "invoice.pdf"}}
135
  }}
136
  ]
137
  """
138
+
139
  self.thoughts.append(AgentThought(
140
  step=len(self.thoughts) + 1,
141
+ type="planning",
142
+ content="Generating plan with LLM..."
143
  ))
144
+
145
  try:
146
+ raw = await get_llm_response(planning_prompt, temperature=0.2)
147
+ txt = raw.strip()
148
+
149
+ # Remove markdown wrappers
150
+ if "```json" in txt:
151
+ txt = txt.split("```json")[1].split("```")[0].strip()
152
+ elif "```" in txt:
153
+ txt = txt.split("```")[1].split("```")[0].strip()
154
+
155
+ plan_json = json.loads(txt)
156
+
157
  tasks = [
158
+ AgentTask(**task, status=TaskStatus.PENDING)
159
+ for task in plan_json
160
  ]
161
+
162
  self.thoughts.append(AgentThought(
163
  step=len(self.thoughts) + 1,
164
+ type="planning",
165
+ content=f"Plan created: {len(tasks)} tasks"
166
  ))
167
+
168
  return tasks
169
+
170
  except Exception as e:
171
  self.thoughts.append(AgentThought(
172
  step=len(self.thoughts) + 1,
173
+ type="planning",
174
+ content=f"Planning failed: {e}"
175
  ))
176
  return []
177
+
178
+ # -------------------------------------------
179
+ # TOOL EXECUTION PHASE
180
+ # -------------------------------------------
181
+
182
  async def execute_task(self, task: AgentTask) -> AgentTask:
183
+
 
184
  self.thoughts.append(AgentThought(
185
  step=len(self.thoughts) + 1,
186
+ type="tool_call",
187
+ content=f"Executing task: {task.description}",
188
  tool_name=task.tool,
189
  tool_args=task.args
190
  ))
191
+
192
  task.status = TaskStatus.IN_PROGRESS
193
+
194
  try:
 
195
  result = await self.mcp_client.call_tool(task.tool, task.args)
196
+
 
197
  task.status = TaskStatus.COMPLETED
198
+ task.result = result
199
+
200
  self.thoughts.append(AgentThought(
201
  step=len(self.thoughts) + 1,
202
+ type="tool_call",
203
  content=f"✓ Completed: {task.description}",
204
  tool_name=task.tool,
205
  tool_result=result
206
  ))
207
+
 
 
208
  except Exception as e:
 
209
  task.status = TaskStatus.FAILED
210
+ task.error = str(e)
211
+
212
  self.thoughts.append(AgentThought(
213
  step=len(self.thoughts) + 1,
214
+ type="tool_call",
215
+ content=f"✗ Failed: {task.description} {e}",
216
  tool_name=task.tool
217
  ))
218
+
219
+ return task
220
+
221
+ # -------------------------------------------
222
+ # REFLECTION PHASE
223
+ # -------------------------------------------
224
+
225
+ async def reflect(self, tasks: List[AgentTask], request: str) -> str:
226
+
 
 
 
 
 
227
  self.thoughts.append(AgentThought(
228
  step=len(self.thoughts) + 1,
229
+ type="reflection",
230
+ content="Analyzing final results..."
231
  ))
232
+
233
+ results_string = []
234
+ for t in tasks:
235
+ if t.status == TaskStatus.COMPLETED:
236
+ short = str(t.result)[:200]
237
+ results_string.append(f"✓ {t.description}: {short}")
238
  else:
239
+ results_string.append(f"✗ {t.description}: {t.error}")
 
 
240
 
241
+ reflection_prompt = f"""
242
+ Summarize the final results of the following tasks:
243
 
244
+ REQUEST:
245
+ {request}
246
 
247
+ RESULTS:
248
+ {chr(10).join(results_string)}
 
 
 
249
 
250
+ Give a clear, helpful answer:
251
+ - What succeeded
252
+ - What failed
253
+ - What files/events/emails were produced
254
+ - Next steps
255
  """
256
+
257
  try:
258
+ answer = await get_llm_response(reflection_prompt, temperature=0.5)
259
+
260
  self.thoughts.append(AgentThought(
261
  step=len(self.thoughts) + 1,
262
+ type="answer",
263
+ content=answer
264
  ))
265
+
266
+ # Write to memory
267
  self.memory.add_memory(
268
+ f"Request: {request}\nAnswer: {answer}",
269
+ metadata={"type": "task_completion", "timestamp": time.time()}
270
  )
271
+
272
+ return answer
273
+
274
  except Exception as e:
275
+ errmsg = f"Reflection failed: {e}"
276
+
277
  self.thoughts.append(AgentThought(
278
  step=len(self.thoughts) + 1,
279
+ type="answer",
280
+ content=errmsg
281
  ))
282
+
283
+ return errmsg
284
+
285
+ # -------------------------------------------
286
+ # STREAMING EXECUTION LOOP (FIXED)
287
+ # -------------------------------------------
288
+
289
+ async def execute(self, request: str, files: List[str] = None, stream_thoughts=False):
290
  """
291
+ If stream_thoughts=True yields AgentThought objects
292
+ If stream_thoughts=False → returns (answer, thoughts)
 
 
 
 
 
 
 
 
 
 
293
  """
294
+
295
  self.reset_context()
296
+
297
+ # --- PLANNING ---
298
+ tasks = await self.plan(request, files)
 
 
 
 
299
  if stream_thoughts:
300
+ for th in self.thoughts:
301
+ yield th
302
+
303
  if not tasks:
304
+ # DO NOT return a value — async generator cannot return a value
305
+ thought = AgentThought(
306
  step=len(self.thoughts) + 1,
307
+ type="answer",
308
+ content="Could not create plan. Try rephrasing."
309
  )
310
+ self.thoughts.append(thought)
 
 
 
 
 
 
 
 
311
  if stream_thoughts:
312
+ yield thought
313
+ return
314
+
315
+ # --- EXECUTION ---
316
+ executed = []
317
+ for t in tasks:
318
+ done = await self.execute_task(t)
319
+ executed.append(done)
320
+ if stream_thoughts:
321
+ yield self.thoughts[-1]
322
+
323
+ # --- REFLECTION ---
324
+ final_answer = await self.reflect(executed, request)
325
  if stream_thoughts:
326
+ yield self.thoughts[-1]
327
+ return
328
+
329
+ # If NOT streaming: return normal output
330
  return final_answer, self.thoughts
331
+
332
+ # -------------------------------------------
333
+ # UTILITY
334
+ # -------------------------------------------
335
+
336
  def get_thought_trace(self) -> List[Dict]:
337
+ return [asdict(t) for t in self.thoughts]