Files
becomingone/spatial_engine/commands/ai_parser.py
T

108 lines
4.0 KiB
Python

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'}")