Maheen001 commited on
Commit
dd82506
Β·
verified Β·
1 Parent(s): c9b0c9c

Update agent/agent_core.py

Browse files
Files changed (1) hide show
  1. agent/agent_core.py +147 -170
agent/agent_core.py CHANGED
@@ -1,13 +1,9 @@
1
  """
2
  LifeAdmin AI - Core Agent Logic
3
- Final stable version (HF / Gradio-compatible).
4
- Provides:
5
- - plan()
6
- - execute_task()
7
- - reflect()
8
- - execute() -> (final_answer, thoughts)
9
- - process_files_to_rag()
10
- - manual_tool_call()
11
  """
12
 
13
  import asyncio
@@ -24,9 +20,10 @@ from agent.memory import MemoryStore
24
  from utils.llm_utils import get_llm_response
25
 
26
 
27
- # -------------------------
28
- # Data models
29
- # -------------------------
 
30
  class TaskStatus(Enum):
31
  PENDING = "pending"
32
  IN_PROGRESS = "in_progress"
@@ -60,124 +57,111 @@ class AgentTask:
60
  error: Optional[str] = None
61
 
62
 
63
- # -------------------------
64
- # LifeAdminAgent
65
- # -------------------------
 
66
  class LifeAdminAgent:
 
67
  def __init__(self):
68
  self.mcp_client = MCPClient()
69
  self.rag_engine = RAGEngine()
70
  self.memory = MemoryStore()
71
  self.thoughts: List[AgentThought] = []
72
 
73
- # ensure data directories exist
74
- Path("data/uploads").mkdir(parents=True, exist_ok=True)
75
- Path("data/outputs").mkdir(parents=True, exist_ok=True)
76
 
77
  def reset(self):
78
- """Reset thoughts / context for a new request"""
79
  self.thoughts = []
80
 
81
- # ---------------------
82
- # Planning
83
- # ---------------------
 
84
  async def plan(self, user_request: str, files: List[str] = None) -> List[AgentTask]:
85
- """Create an execution plan (list of AgentTask) using LLM + RAG + memory"""
86
  self.thoughts.append(AgentThought(
87
  step=len(self.thoughts) + 1,
88
  type="planning",
89
- content=f"Analyzing request: {user_request}"
90
  ))
91
 
92
- # list tools
93
- try:
94
- tools = await self.mcp_client.list_tools()
95
- except Exception:
96
- tools = []
97
- tool_desc = "\n".join([f"- {t['name']}: {t.get('description','')}" for t in tools]) if tools else "No tool metadata available"
98
 
99
- # RAG search
100
  rag_docs = []
101
- if user_request and user_request.strip():
102
- try:
103
- rag_docs = await self.rag_engine.search(user_request, k=3)
104
- except Exception:
105
- rag_docs = []
106
 
107
- rag_context = "\n".join([d.get("text","")[:250] for d in rag_docs]) if rag_docs else "No relevant docs"
108
 
109
- memory_context = self.memory.get_relevant_memories(user_request) if self.memory else "No memory"
110
 
111
- planning_prompt = f"""
112
- You are an autonomous life admin assistant. Produce a JSON array of tasks (no extra text).
113
- User request: {user_request}
114
- Available files: {files or []}
115
- Available tools:
 
116
  {tool_desc}
117
- RAG context:
118
  {rag_context}
119
- Memory:
120
  {memory_context}
121
-
122
- Return ONLY valid JSON array of tasks. Each task must contain:
123
- - id (string)
124
- - description (string)
125
- - tool (one of the tool names)
126
- - args (a JSON object)
127
-
128
- Example:
129
  [
130
  {{
131
- "id": "task_1",
132
- "description": "Extract text from invoice.pdf",
133
  "tool": "ocr_extract_text",
134
- "args": {{"file_path": "data/uploads/invoice.pdf", "language": "en"}}
135
  }}
136
  ]
137
  """
138
- self.thoughts.append(AgentThought(
139
- step=len(self.thoughts) + 1,
140
- type="planning",
141
- content="Asking LLM to create a plan..."
142
- ))
143
 
144
- try:
145
- plan_text = await get_llm_response(planning_prompt, temperature=0.2)
146
- plan_text = plan_text.strip()
147
 
148
- # try to extract JSON if wrapped in code fences
149
- if "```json" in plan_text:
150
- plan_text = plan_text.split("```json", 1)[1].split("```", 1)[0].strip()
151
- elif "```" in plan_text:
152
- plan_text = plan_text.split("```", 1)[1].split("```", 1)[0].strip()
153
 
154
- tasks_data = json.loads(plan_text)
155
- tasks = [AgentTask(**t) for t in tasks_data]
156
- self.thoughts.append(AgentThought(
157
- step=len(self.thoughts) + 1,
158
- type="planning",
159
- content=f"Plan created with {len(tasks)} tasks."
160
- ))
161
- return tasks
162
- except Exception as e:
163
  self.thoughts.append(AgentThought(
164
- step=len(self.thoughts) + 1,
165
  type="planning",
166
- content=f"Planning failed: {str(e)}"
167
  ))
168
  return []
169
 
170
- # ---------------------
171
- # Execution of a single task
172
- # ---------------------
173
- async def execute_task(self, task: AgentTask) -> AgentTask:
174
  self.thoughts.append(AgentThought(
175
- step=len(self.thoughts) + 1,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
  type="tool_call",
177
- content=f"Executing task: {task.description}",
178
  tool_name=task.tool,
179
  tool_args=task.args
180
  ))
 
181
  task.status = TaskStatus.IN_PROGRESS
182
 
183
  try:
@@ -186,160 +170,153 @@ Example:
186
  task.status = TaskStatus.COMPLETED
187
 
188
  self.thoughts.append(AgentThought(
189
- step=len(self.thoughts) + 1,
190
  type="tool_call",
191
- content=f"Completed: {task.description}",
192
  tool_name=task.tool,
193
  tool_result=result
194
  ))
195
- return task
196
  except Exception as e:
197
  task.status = TaskStatus.FAILED
198
  task.error = str(e)
 
199
  self.thoughts.append(AgentThought(
200
- step=len(self.thoughts) + 1,
201
  type="tool_call",
202
- content=f"Failed: {task.description} - {str(e)}",
203
  tool_name=task.tool
204
  ))
205
- return task
206
 
207
- # ---------------------
208
- # Reflection / final answer
209
- # ---------------------
210
- async def reflect(self, tasks: List[AgentTask], original_request: str) -> str:
 
 
 
 
211
  self.thoughts.append(AgentThought(
212
- step=len(self.thoughts) + 1,
213
  type="reflection",
214
- content="Synthesizing results..."
215
  ))
216
 
217
- summary_lines = []
218
  for t in tasks:
219
  if t.status == TaskStatus.COMPLETED:
220
- summary_lines.append(f"βœ“ {t.description}: {str(t.result)[:300]}")
221
  else:
222
- summary_lines.append(f"βœ— {t.description}: {t.error}")
223
-
224
- reflection_prompt = f"""
225
- You are the agent summarizing execution results.
226
- Original request: {original_request}
227
- Execution summary:
228
- {chr(10).join(summary_lines)}
229
-
230
- Write a clear, friendly reply telling the user what was done, outputs created, any errors, and next steps.
231
  """
232
 
233
- try:
234
- answer = await get_llm_response(reflection_prompt, temperature=0.5)
235
- except Exception as e:
236
- answer = f"Reflection failed: {str(e)}"
237
 
238
  self.thoughts.append(AgentThought(
239
- step=len(self.thoughts) + 1,
240
  type="answer",
241
  content=answer
242
  ))
243
 
244
- # store short memory
245
  try:
246
  self.memory.add_memory(
247
- content=f"Request: {original_request}\nResult: {answer}",
248
- memory_type="task_completion",
249
  metadata={"timestamp": time.time()}
250
  )
251
  except Exception:
 
252
  pass
253
 
254
  return answer
255
 
256
- # ---------------------
257
- # Main execute (no streaming)
258
- # ---------------------
259
- async def execute(self, user_request: str, files: List[str] = None) -> (str, List[AgentThought]):
 
260
  """
261
- Run plan -> execute each task -> reflect
262
- Returns: (final_answer, list_of_thoughts)
263
  """
 
264
  self.reset()
265
 
266
  tasks = await self.plan(user_request, files)
267
  if not tasks:
268
- err_msg = "Could not create an execution plan. Try rephrasing your request."
269
- self.thoughts.append(AgentThought(
270
- step=len(self.thoughts) + 1,
271
- type="answer",
272
- content=err_msg
273
- ))
274
- return err_msg, self.thoughts
275
 
276
  executed = []
277
  for t in tasks:
278
- executed_task = await self.execute_task(t)
279
- executed.append(executed_task)
280
 
281
  final_answer = await self.reflect(executed, user_request)
282
  return final_answer, self.thoughts
283
 
284
- # ---------------------
285
- # Utility: provide thought trace for UI
286
- # ---------------------
287
- def get_thought_trace(self) -> List[Dict[str, Any]]:
 
288
  return [asdict(t) for t in self.thoughts]
289
 
290
- # ---------------------
291
- # Add uploaded files into RAG index (helper used by UI)
292
- # ---------------------
 
293
  async def process_files_to_rag(self, files: List[Dict[str, str]]):
294
  """
295
- files: list of dicts {'path': <path>, 'name': <filename>}
296
- Extract text using available local tools (pdf/text/ocr) and add to RAG.
297
  """
298
  for file_info in files:
299
- path = file_info.get("path")
300
- name = file_info.get("name", Path(path).name if path else "")
301
  try:
302
- text = ""
303
- if path and path.lower().endswith(".pdf"):
304
- # try utils.pdf_utils
305
- try:
306
- from utils.pdf_utils import extract_text_from_pdf
307
- text = extract_text_from_pdf(path)
308
- except Exception:
309
- text = ""
310
- elif path and path.lower().endswith((".png", ".jpg", ".jpeg", ".tiff")):
311
- # use MCP OCR tool (via client) or local easyocr
312
  try:
313
- result = await self.mcp_client.call_tool("ocr_extract_text", {"file_path": path, "language": "en"})
314
- text = result.get("text", "")
315
  except Exception:
 
316
  text = ""
317
  else:
318
- # read plain text files
319
  try:
320
- with open(path, "r", encoding="utf-8") as f:
321
  text = f.read()
322
  except Exception:
323
  text = ""
324
 
325
- if text and len(text.strip()) > 20:
326
- try:
327
- await self.rag_engine.add_document(text=text, metadata={"filename": name, "path": path})
328
- except Exception:
329
- pass
330
- except Exception:
331
- continue
332
 
333
- # ---------------------
334
- # Manual tool call wrapper for UI (guarantees consistent return shape)
335
- # ---------------------
336
  async def manual_tool_call(self, tool_name: str, args: Dict[str, Any]) -> Dict[str, Any]:
337
  """
338
- Calls an MCP tool (via MCPClient). Returns dict:
339
- {'success': bool, 'result': <tool_result> or None, 'error': <err_msg> or None}
340
  """
341
  try:
342
  result = await self.mcp_client.call_tool(tool_name, args)
343
- return {"success": True, "result": result, "error": None}
344
  except Exception as e:
345
- return {"success": False, "result": None, "error": str(e)}
 
1
  """
2
  LifeAdmin AI - Core Agent Logic
3
+ Final stable version (No async-generators – fully HF compatible)
4
+ Includes async helpers used by the UI:
5
+ - process_files_to_rag(files)
6
+ - manual_tool_call(tool_name, args)
 
 
 
 
7
  """
8
 
9
  import asyncio
 
20
  from utils.llm_utils import get_llm_response
21
 
22
 
23
+ # =============================
24
+ # DATA MODELS
25
+ # =============================
26
+
27
  class TaskStatus(Enum):
28
  PENDING = "pending"
29
  IN_PROGRESS = "in_progress"
 
57
  error: Optional[str] = None
58
 
59
 
60
+ # =============================
61
+ # MAIN AGENT CLASS
62
+ # =============================
63
+
64
  class LifeAdminAgent:
65
+
66
  def __init__(self):
67
  self.mcp_client = MCPClient()
68
  self.rag_engine = RAGEngine()
69
  self.memory = MemoryStore()
70
  self.thoughts: List[AgentThought] = []
71
 
72
+ # -----------------------
73
+ # UTIL
74
+ # -----------------------
75
 
76
  def reset(self):
 
77
  self.thoughts = []
78
 
79
+ # -----------------------
80
+ # PLANNING
81
+ # -----------------------
82
+
83
  async def plan(self, user_request: str, 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: {user_request}"
89
  ))
90
 
91
+ tools = await self.mcp_client.list_tools()
92
+ tool_desc = "\n".join([f"- {t['name']}: {t['description']}" for t in tools])
 
 
 
 
93
 
 
94
  rag_docs = []
95
+ if user_request.strip():
96
+ rag_docs = await self.rag_engine.search(user_request, k=3)
 
 
 
97
 
98
+ rag_context = "\n".join([d.get("text", "")[:200] for d in rag_docs]) if rag_docs else "None"
99
 
100
+ memory_context = self.memory.get_relevant_memories(user_request)
101
 
102
+ prompt = f"""
103
+ You are a task planner.
104
+ REQUEST:
105
+ {user_request}
106
+ FILES: {files or []}
107
+ TOOLS:
108
  {tool_desc}
109
+ RAG CONTEXT:
110
  {rag_context}
111
+ MEMORY:
112
  {memory_context}
113
+ Return ONLY JSON list:
 
 
 
 
 
 
 
114
  [
115
  {{
116
+ "id": "task1",
117
+ "description": "Extract text",
118
  "tool": "ocr_extract_text",
119
+ "args": {{"file_path": "x.pdf"}}
120
  }}
121
  ]
122
  """
 
 
 
 
 
123
 
124
+ response = await get_llm_response(prompt, temperature=0.2)
125
+ text = response.strip()
 
126
 
127
+ if "```json" in text:
128
+ text = text.split("```json")[1].split("```")[0].strip()
129
+ elif "```" in text:
130
+ text = text.split("```")[1].split("```")[0].strip()
 
131
 
132
+ try:
133
+ plan_data = json.loads(text)
134
+ tasks = [AgentTask(**t) for t in plan_data]
135
+ except Exception:
 
 
 
 
 
136
  self.thoughts.append(AgentThought(
137
+ step=len(self.thoughts)+1,
138
  type="planning",
139
+ content="Planning failed (invalid JSON)"
140
  ))
141
  return []
142
 
 
 
 
 
143
  self.thoughts.append(AgentThought(
144
+ step=len(self.thoughts)+1,
145
+ type="planning",
146
+ content=f"Created {len(tasks)} tasks"
147
+ ))
148
+
149
+ return tasks
150
+
151
+ # -----------------------
152
+ # EXECUTION
153
+ # -----------------------
154
+
155
+ async def execute_task(self, task: AgentTask):
156
+
157
+ self.thoughts.append(AgentThought(
158
+ step=len(self.thoughts)+1,
159
  type="tool_call",
160
+ content=f"Executing: {task.description}",
161
  tool_name=task.tool,
162
  tool_args=task.args
163
  ))
164
+
165
  task.status = TaskStatus.IN_PROGRESS
166
 
167
  try:
 
170
  task.status = TaskStatus.COMPLETED
171
 
172
  self.thoughts.append(AgentThought(
173
+ step=len(self.thoughts)+1,
174
  type="tool_call",
175
+ content=f"βœ“ Completed",
176
  tool_name=task.tool,
177
  tool_result=result
178
  ))
179
+
180
  except Exception as e:
181
  task.status = TaskStatus.FAILED
182
  task.error = str(e)
183
+
184
  self.thoughts.append(AgentThought(
185
+ step=len(self.thoughts)+1,
186
  type="tool_call",
187
+ content=f"βœ— Failed: {e}",
188
  tool_name=task.tool
189
  ))
 
190
 
191
+ return task
192
+
193
+ # -----------------------
194
+ # REFLECTION
195
+ # -----------------------
196
+
197
+ async def reflect(self, tasks: List[AgentTask], original: str) -> str:
198
+
199
  self.thoughts.append(AgentThought(
200
+ step=len(self.thoughts)+1,
201
  type="reflection",
202
+ content="Summarizing results..."
203
  ))
204
 
205
+ results = []
206
  for t in tasks:
207
  if t.status == TaskStatus.COMPLETED:
208
+ results.append(f"βœ“ {t.description}: {str(t.result)[:200]}")
209
  else:
210
+ results.append(f"βœ— {t.description}: {t.error}")
211
+
212
+ prompt = f"""
213
+ Provide a helpful summary for the user.
214
+ REQUEST:
215
+ {original}
216
+ RESULTS:
217
+ {chr(10).join(results)}
218
+ Write a clear, friendly answer.
219
  """
220
 
221
+ answer = await get_llm_response(prompt, temperature=0.4)
 
 
 
222
 
223
  self.thoughts.append(AgentThought(
224
+ step=len(self.thoughts)+1,
225
  type="answer",
226
  content=answer
227
  ))
228
 
229
+ # store memory
230
  try:
231
  self.memory.add_memory(
232
+ content=f"Request: {original}\nAnswer: {answer}",
 
233
  metadata={"timestamp": time.time()}
234
  )
235
  except Exception:
236
+ # don't break on memory errors
237
  pass
238
 
239
  return answer
240
 
241
+ # -----------------------
242
+ # MAIN EXECUTION (FULLY FIXED)
243
+ # -----------------------
244
+
245
+ async def execute(self, user_request: str, files: List[str] = None):
246
  """
247
+ A simple coroutine returning final_answer, thoughts
248
+ No yields β†’ No async generator β†’ No syntax errors
249
  """
250
+
251
  self.reset()
252
 
253
  tasks = await self.plan(user_request, files)
254
  if not tasks:
255
+ # return is allowed now
256
+ return "Could not generate plan. Try rephrasing.", self.thoughts
 
 
 
 
 
257
 
258
  executed = []
259
  for t in tasks:
260
+ executed.append(await self.execute_task(t))
 
261
 
262
  final_answer = await self.reflect(executed, user_request)
263
  return final_answer, self.thoughts
264
 
265
+ # -----------------------
266
+ # EXPORT THOUGHTS
267
+ # -----------------------
268
+
269
+ def get_thought_trace(self):
270
  return [asdict(t) for t in self.thoughts]
271
 
272
+ # -----------------------
273
+ # Additional helpers expected by UI
274
+ # -----------------------
275
+
276
  async def process_files_to_rag(self, files: List[Dict[str, str]]):
277
  """
278
+ Process uploaded files and add text to RAG.
279
+ files: List[{'path': '/abs/path', 'name': 'file.pdf'}]
280
  """
281
  for file_info in files:
 
 
282
  try:
283
+ path = file_info.get('path')
284
+ if not path:
285
+ continue
286
+
287
+ # small heuristic on extension
288
+ if path.lower().endswith('.pdf'):
289
+ from utils.pdf_utils import extract_text_from_pdf
290
+ text = extract_text_from_pdf(path)
291
+ elif path.lower().endswith(('.png', '.jpg', '.jpeg')):
292
+ # Use OCR tool via MCPClient (local fallback)
293
  try:
294
+ res = await self.mcp_client.call_tool('ocr_extract_text', {'file_path': path, 'language': 'en'})
295
+ text = res.get('text', '')
296
  except Exception:
297
+ # Last-resort: empty text
298
  text = ""
299
  else:
300
+ # treat as text file
301
  try:
302
+ with open(path, 'r', encoding='utf-8') as f:
303
  text = f.read()
304
  except Exception:
305
  text = ""
306
 
307
+ # add to RAG
308
+ if text:
309
+ await self.rag_engine.add_document(text=text, metadata={'filename': file_info.get('name'), 'path': path})
310
+ except Exception as e:
311
+ # log (print) but don't raise
312
+ print(f"Error processing {file_info.get('name')}: {e}")
 
313
 
 
 
 
314
  async def manual_tool_call(self, tool_name: str, args: Dict[str, Any]) -> Dict[str, Any]:
315
  """
316
+ Direct tool call helper used by UI buttons. Returns dict with 'success' key.
 
317
  """
318
  try:
319
  result = await self.mcp_client.call_tool(tool_name, args)
320
+ return {'success': True, 'result': result}
321
  except Exception as e:
322
+ return {'success': False, 'error': str(e)}