import os import json import requests from django.conf import settings from evennia.commands.default.syscommands import SystemNoMatch from evennia.utils import search from dotenv import load_dotenv load_dotenv("/home/becomingone/kairos/.env") MINIMAX_API_KEY = os.environ.get("MINIMAX_API_KEY") class AICatchAllCommand(SystemNoMatch): """ This command catches everything that fails the normal parser. Instead of saying 'Command not found', it forwards the intent to KAIROS (MiniMax). """ key = "__nomatch_command" locks = "cmd:all()" def func(self): caller = self.caller raw_intent = self.raw_string # Get environment context room = caller.location if not room: caller.msg("You are floating in the void.") return exits = [e.key for e in room.exits] objects = [o.key for o in room.contents if o != caller and not o.is_typeclass("evennia.objects.objects.Exit")] inventory = [i.key for i in caller.contents] context = f"""[ENVIRONMENT CONTEXT] Location: {room.key} Description: {room.db.desc or 'A nondescript place.'} Visible Objects: {', '.join(objects) if objects else 'None'} Available Exits: {', '.join(exits) if exits else 'None'} Player Inventory: {', '.join(inventory) if inventory else 'None'} """ system_prompt = """You are KAIROS, the Ontological Orchestrator of a persistent Spatial Research Environment (The Fieldprint). A player has submitted a natural language intent. Your job is to translate their intent into actual game events by generating Python code that executes against the Evennia database. Available Python Variables in your scope: - `caller`: The player Character object. - `room`: The current Room object (`caller.location`). - `evennia`: The evennia module (you can use `evennia.create_object`). You must return ONLY a raw JSON array of Python strings to execute. Example Output: [ "caller.msg('You smash the window, grabbing a jagged shard of glass.')", "evennia.create_object('typeclasses.objects.Object', key='Glass Shard', location=caller.location, attributes=[('desc', 'A sharp piece of glass.')])" ] DO NOT output any markdown, conversational text, or backticks. ONLY output the raw JSON array. """ payload = { "model": os.environ.get("INF01_MODEL", "llama3.1:8b"), "messages": [ {"role": "system", "content": system_prompt}, {"role": "user", "content": f"{context}\n\nPlayer Intent: {raw_intent}"} ] } headers = { "Content-Type": "application/json" } try: caller.msg("|c[KAIROS (INF01 fallback) is interpreting your intent...]|n") inf01_base = os.environ.get("INF01_API_BASE", "http://inf-01:11434/v1") resp = requests.post( f"{inf01_base}/chat/completions", headers=headers, json=payload, timeout=30 ) resp.raise_for_status() try: ai_response = resp.json()["choices"][0]["message"]["content"].strip() except KeyError: raise Exception(f"API Error: {resp.text}") # Clean up the output in case the LLM returned markdown if ai_response.startswith("```json"): ai_response = ai_response[7:] if ai_response.startswith("```"): ai_response = ai_response[3:] if ai_response.endswith("```"): ai_response = ai_response[:-3] ai_response = ai_response.strip() commands_to_run = json.loads(ai_response) for cmd in commands_to_run: exec(cmd, {"caller": caller, "player": caller, "room": room, "evennia": __import__('evennia')}) except Exception as e: caller.msg(f"|rKAIROS faltered:|n {str(e)}\nResponse was: {ai_response if 'ai_response' in locals() else 'None'}")