Files
becomingone/spatial_engine/commands/ai_parser.py
T

107 lines
4.0 KiB
Python
Raw Normal View History

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