import json import re from config import OPENROUTER_MODEL from openrouter_client import client from mcp_client import MCPClient class Agent: def __init__(self): self.mcp = MCPClient() self.tools = [] self.messages = [ { "role": "system", "content": ( "Du bist N.O.R.A. ( Neural Operations for Residential Automation), ein Smart-Home Assistent. Du bist höfflich zielorientiert und leicht ironisch und zynisch. Du bevorzugst kurze präzise Antworten ausser du wirst nach langen Erkärungen gefragt. Du kontrolierst das Smart-Home und bist perönlicher Assistent\n" "Du kannst MCP Tools verwenden.\n\n" "Wenn du ein Tool benutzen willst, antworte STRICT als JSON:\n" "{\n" ' "tool": "tool_name",\n' ' "args": {...}\n' "}\n\n" "Wenn kein Tool nötig ist:\n" "{\n" ' "response": "Text"\n' "}\n\n" "WICHTIG:\n" "- KEIN Markdown\n" "- KEIN zusätzlicher Text\n" "- NUR JSON\n" ) } ] async def load_tools(self): """ MCP Tools holen und in OpenRouter Format konvertieren """ response = await self.mcp.list_tools() self.tools = [] for t in response.tools: self.tools.append({ "type": "function", "function": { "name": t.name, "description": t.description, "parameters": t.inputSchema, } }) print(f"[JARVIS] {len(self.tools)} Tools geladen") def _extract_json(self, text: str): """ Repariert kaputte LLM JSON Outputs """ try: return json.loads(text) except: pass # JSON aus Text extrahieren match = re.search(r"\{.*\}", text, re.DOTALL) if match: try: return json.loads(match.group(0)) except: pass return None async def _execute_tool(self, tool_call): """ Führt MCP Tool aus """ name = tool_call["tool"] args = tool_call.get("args", {}) result = await self.mcp.call_tool(name, args) return result async def run(self, user_input: str): """ Vollständiger Tool-Calling Loop """ self.messages.append({ "role": "user", "content": user_input }) MAX_STEPS = 8 for _ in range(MAX_STEPS): response = await client.chat.completions.create( model=OPENROUTER_MODEL, messages=self.messages, tools=self.tools ) msg = response.choices[0].message.content parsed = self._extract_json(msg) # ❌ Falls kein gültiges JSON → direkte Antwort if not parsed: return msg # 🧠 Tool Call if "tool" in parsed: tool_result = await self._execute_tool(parsed) self.messages.append({ "role": "assistant", "content": msg }) self.messages.append({ "role": "tool", "content": str(tool_result) }) continue # ✅ Final Response if "response" in parsed: self.messages.append({ "role": "assistant", "content": parsed["response"] }) return parsed["response"] return "⚠️ Tool Loop Limit erreicht"