# agent/agent.py from typing import Any, Dict, List import json from .llm import DeepSeekClient from infrastructure.persistence.memory import Memory from .registry import make_tools, Tool from .prompts import PromptBuilder from .config import settings class Agent: def __init__(self, llm: DeepSeekClient, memory: Memory, max_tool_iterations: int = 5): self.llm = llm self.memory = memory self.tools: Dict[str, Tool] = make_tools(memory) self.prompt_builder = PromptBuilder(self.tools) self.max_tool_iterations = max_tool_iterations def _parse_intent(self, text: str) -> Dict[str, Any] | None: try: data = json.loads(text) except json.JSONDecodeError: return None if not isinstance(data, dict): return None action = data.get("action") if not isinstance(action, dict): return None name = action.get("name") if not isinstance(name, str): return None return data def _execute_action(self, intent: Dict[str, Any]) -> Dict[str, Any]: action = intent["action"] name: str = action["name"] args: Dict[str, Any] = action.get("args", {}) or {} tool = self.tools.get(name) if not tool: return {"error": "unknown_tool", "tool": name} try: result = tool.func(**args) except TypeError as e: # Mauvais arguments return {"error": "bad_args", "message": str(e)} return result def step(self, user_input: str) -> str: """ Execute one agent step with iterative tool execution: - Build system prompt - Query LLM - Loop: If JSON intent -> execute tool, add result to conversation, query LLM again - Continue until LLM responds with text (no tool call) or max iterations reached - Return final text response """ print("Starting a new step...") print("User input:", user_input) print("Current memory state:", self.memory.data) # Build system prompt using PromptBuilder system_prompt = self.prompt_builder.build_system_prompt(self.memory.data) # Initialize conversation with system prompt messages: List[Dict[str, Any]] = [ {"role": "system", "content": system_prompt}, ] # Add conversation history from memory (last N messages for context) # Only add user/assistant messages, NOT system messages history = self.memory.get("history", []) max_history = settings.max_history_messages if history and max_history > 0: # Filter to keep only user and assistant messages filtered_history = [ msg for msg in history if msg.get("role") in ("user", "assistant") ] recent_history = filtered_history[-max_history:] messages.extend(recent_history) print(f"Added {len(recent_history)} messages from history (filtered)") # Add current user input messages.append({"role": "user", "content": user_input}) # Tool execution loop iteration = 0 while iteration < self.max_tool_iterations: print(f"\n--- Iteration {iteration + 1} ---") # Get LLM response print(messages) llm_response = self.llm.complete(messages) print("LLM response:", llm_response) # Try to parse as tool intent intent = self._parse_intent(llm_response) if not intent: # No tool call - this is the final text response print("No tool intent detected, returning final response") # Save to history self.memory.append_history("user", user_input) self.memory.append_history("assistant", llm_response) return llm_response # Tool call detected - execute it print("Intent detected:", intent) tool_result = self._execute_action(intent) print("Tool result:", tool_result) # Add assistant's tool call and result to conversation messages.append({ "role": "assistant", "content": json.dumps(intent, ensure_ascii=False) }) messages.append({ "role": "user", "content": json.dumps( {"tool_result": tool_result}, ensure_ascii=False ) }) iteration += 1 # Max iterations reached - ask LLM for final response print(f"\n--- Max iterations ({self.max_tool_iterations}) reached, requesting final response ---") messages.append({ "role": "user", "content": "Merci pour ces résultats. Peux-tu maintenant me donner une réponse finale en texte naturel ?" }) final_response = self.llm.complete(messages) # Save to history self.memory.append_history("user", user_input) self.memory.append_history("assistant", final_response) return final_response