ipin-vr/ipin_vr/llm.py

81 lines
2.5 KiB
Python
Raw Normal View History

import json
import urllib.error
import urllib.request
from typing import Optional
from .generator import Clause, Command, random_command
from .lexicon import COLORS, LOCATIONS, OBJECTS, VERBS
_SYSTEM_PROMPT = (
"Return ONLY a JSON object — no prose, no markdown, no code fences.\n"
"Schema: {{\"clauses\":[{{\"verb\":\"<id>\",\"object\":\"<id>\","
"\"location\":\"<id>\",\"color\":\"<id or null>\"}}],\"level\":<int>}}\n"
"Allowed verb IDs: {verbs}\n"
"Allowed object IDs: {objects}\n"
"Allowed location IDs: {locations}\n"
"Allowed color IDs: {colors}"
)
def _system_prompt() -> str:
return _SYSTEM_PROMPT.format(
verbs=", ".join(VERBS),
objects=", ".join(OBJECTS),
locations=", ".join(LOCATIONS),
colors=", ".join(COLORS),
)
def _validate(data: dict, level: int) -> Optional[Command]:
clauses_raw = data.get("clauses")
if not isinstance(clauses_raw, list) or not clauses_raw:
return None
clauses = []
for c in clauses_raw:
v = c.get("verb")
o = c.get("object")
loc = c.get("location")
col = c.get("color")
if v not in VERBS or o not in OBJECTS or loc not in LOCATIONS:
return None
if col is not None and col not in COLORS:
return None
clauses.append(Clause(verb_id=v, object_id=o, location_id=loc, color_id=col))
return Command(clauses=clauses, level=level)
def llm_command(level: int, endpoint: str, model: str) -> Command:
payload = json.dumps({
"model": model,
"messages": [
{"role": "system", "content": _system_prompt()},
{"role": "user", "content": f"Generate a level {level} aphasia therapy command."},
],
"temperature": 0.8,
}).encode()
try:
req = urllib.request.Request(
endpoint,
data=payload,
headers={"Content-Type": "application/json"},
method="POST",
)
with urllib.request.urlopen(req, timeout=10) as resp:
body = json.loads(resp.read())
content = body["choices"][0]["message"]["content"]
start = content.find("{")
end = content.rfind("}") + 1
if start == -1 or end == 0:
raise ValueError("brak JSON w odpowiedzi")
data = json.loads(content[start:end])
cmd = _validate(data, level)
if cmd is None:
raise ValueError("nieznane identyfikatory w odpowiedzi LLM")
return cmd
except Exception:
return random_command(level)