Maheen001 commited on
Commit
052a7b4
·
verified ·
1 Parent(s): 2cb3622

Create agent/agent_core.py

Browse files
Files changed (1) hide show
  1. agent/agent_core.py +378 -0
agent/agent_core.py ADDED
@@ -0,0 +1,378 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ LifeAdmin AI - Core Agent Logic
3
+ Autonomous planning, tool orchestration, and execution
4
+ """
5
+
6
+ import asyncio
7
+ import json
8
+ import time
9
+ from typing import List, Dict, Any, Optional
10
+ from dataclasses import dataclass, asdict
11
+ from enum import Enum
12
+
13
+ from agent.mcp_client import MCPClient
14
+ from agent.rag_engine import RAGEngine
15
+ 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"
22
+ COMPLETED = "completed"
23
+ FAILED = "failed"
24
+
25
+
26
+ @dataclass
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()
40
+
41
+
42
+ @dataclass
43
+ class AgentTask:
44
+ """Represents a task to be executed"""
45
+ id: str
46
+ description: str
47
+ tool: str
48
+ args: Dict[str, Any]
49
+ status: TaskStatus = TaskStatus.PENDING
50
+ result: Optional[Any] = None
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)}