From 45e741ade323be50d591a0f6d3bb1067e6c93542 Mon Sep 17 00:00:00 2001 From: Till Immer Date: Sun, 5 Jul 2026 16:07:43 +0000 Subject: [PATCH] agent.py aktualisiert --- agent.py | 98 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 52 insertions(+), 46 deletions(-) diff --git a/agent.py b/agent.py index 3c0b2f4..09ac53c 100644 --- a/agent.py +++ b/agent.py @@ -1,51 +1,66 @@ from llm.bedrock import BedrockClient -from jarvis_mcp.client import MCPClient # oder wo auch immer dein MCP-Client liegt +from jarvis_mcp.client import MCPClient from config import MCP_URL from util.logger import get_logger import asyncio +from pathlib import Path + +# Import des neuen Web-Search Tools +from tools.web_search import web_search_tool, web_search class Agent: def __init__(self): self.logger = get_logger("NORA") - + self.llm = BedrockClient() self.mcp = MCPClient(MCP_URL) - + self.tools = [] - - # System-Prompt als erste User-Nachricht (Nova 2 Lite kompatibel) + + # Personality / System-Prompt (als User-Message für Nova 2 Lite) + self.system_prompt = self._load_system_prompt() + self.messages = [ { "role": "user", - "content": [ - { - "text": ( - "Du bist N.O.R.A (Neural Operations for Residential Automation), ein freundlicher und kompetenter Smart-Home Assistent. Du bist ähnlich wie die KI Jarvis. höfflich, zielorientiert und leicht zynisch und ironisch. Du antwortest in kurzen Sätzen ausser es wird eine lange Erklärung verlangt. " - "für OpenHAB. Du steuerst Geräte und beantwortest Fragen über das Zuhause. " - "Nutze Tools, wenn sinnvoll. Antworte auf Deutsch und sei hilfreich." - ) - } - ] + "content": [{"text": self.system_prompt}] } ] + + def _load_system_prompt(self) -> str: + """Lädt Personality aus externer Datei""" + try: + prompt_path = Path("prompts/nora_system.txt") + if prompt_path.exists(): + return prompt_path.read_text(encoding="utf-8").strip() + else: + self.logger.warning("System-Prompt-Datei nicht gefunden. Verwende Standard.") + return ( + "Du bist N.O.R.A (Neural Operations for Residential Automation), " + "ein freundlicher, kompetenter und leicht ironischer Smart-Home Assistent. " + "Du antwortest natürlich und auf Deutsch. Nutze Tools wenn sinnvoll." + ) + except Exception: + return "Du bist N.O.R.A, ein hilfreicher Smart-Home Assistent." + # ------------------------- # TOOL LOADING # ------------------------- async def load_tools(self): try: + # 1. OpenHAB Tools vom MCP Server laden res = await self.mcp.list_tools() - + self.tools = [] for t in res.tools: schema = t.inputSchema - - # Bedrock-kompatibles Format erzwingen + # Bedrock-kompatibles Format if isinstance(schema, dict) and "json" not in schema: bedrock_schema = {"json": schema} else: bedrock_schema = schema - + self.tools.append({ "toolSpec": { "name": t.name, @@ -53,36 +68,38 @@ class Agent: "inputSchema": bedrock_schema } }) - - self.logger.info(f"{len(self.tools)}OpenHAB Tools erfolgreich für Bedrock angepasst und geladen") - # Nach dem Laden der MCP-Tools: - from tools.web_search import web_search_tool, web_search - - # Manuell hinzufügen - if web_search_tool not in self.tools: + + self.logger.info(f"{len(self.tools)} OpenHAB-Tools erfolgreich geladen") + + # 2. Web-Search Tool manuell hinzufügen + if not any(t.get("toolSpec", {}).get("name") == "web_search" for t in self.tools): self.tools.append(web_search_tool) - - self.logger.info(f"{len(self.tools)} Tools geladen (inkl. Web-Suche)") + self.logger.info("Web-Search Tool hinzugefügt (DuckDuckGo)") + + self.logger.info(f"Gesamt: {len(self.tools)} Tools geladen") + except Exception as e: self.logger.error(f"Tool Load Error: {e}") self.tools = [] - # ------------------------- # TOOL EXECUTION # ------------------------- async def _run_tool(self, tool): try: - # Robustere Extraktion - tool_use = tool.get("toolUse", tool) # falls schon extrahiert + tool_use = tool.get("toolUse", tool) name = tool_use.get("name") args = tool_use.get("input", {}) or {} self.logger.info(f"Tool Call → {name} | args={args}") + # Web-Search Tool + if name == "web_search": + return await web_search(**args) + + # Normale MCP Tools (OpenHAB) result = await self.mcp.call_tool(name, args) - # MCP result stabilisieren if hasattr(result, "content"): return result.content return result @@ -90,7 +107,7 @@ class Agent: except Exception as e: self.logger.error(f"Tool Error ({name}): {e}") return f"ERROR: {e}" - + # ------------------------- # MAIN LOOP # ------------------------- @@ -100,18 +117,16 @@ class Agent: "content": [{"text": user_input}] }) - for iteration in range(10): # etwas mehr Schleifendurchläufe erlaubt + for iteration in range(12): try: response = await self.llm.chat( messages=self.messages, tools=self.tools if self.tools else None ) - except Exception as e: self.logger.error(f"LLM Error: {e}") return f"LLM Fehler: {e}" - # Response-Struktur von Bedrock Converse output = response.get("output", {}) message = output.get("message", {}) content = message.get("content", []) @@ -119,28 +134,23 @@ class Agent: tool_uses = [] final_text = [] - # Verbessertes Parsing for c in content: if "toolUse" in c: tool_uses.append(c) elif "text" in c: final_text.append(c["text"]) - # TOOL EXECUTION PATH if tool_uses: self.logger.info(f"{len(tool_uses)} Tool(s) detected") - # Assistant-Nachricht mit Tool-Call speichern self.messages.append({ "role": "assistant", "content": content }) tool_results = [] - for tool in tool_uses: result = await self._run_tool(tool) - tool_results.append({ "toolResult": { "toolUseId": tool.get("toolUse", tool).get("toolUseId"), @@ -148,23 +158,19 @@ class Agent: } }) - # Tool-Ergebnisse zurück an das Modell self.messages.append({ "role": "user", "content": tool_results }) + continue - continue # nächste Runde für finale Antwort - - # FINAL RESPONSE + # Finale Antwort if final_text: text = "\n".join(final_text) - self.messages.append({ "role": "assistant", "content": [{"text": text}] }) - return text return "Tool Loop Limit erreicht. Bitte versuche es erneut." \ No newline at end of file