624644619c
- Character sheets gain structured combat_stats, abilities (name + plain-language explanation), and equipment fields, populated by the upsert_character_sheet tool; the detail page renders them as their own sections instead of one prose block, with a player-card-style attribute grid (full ability names + modifiers). - Fix double-escaped unicode occasionally left in tool-call JSON arguments (a model quirk) and stop the app's own json.dumps calls from re-introducing it (ensure_ascii=False). - Render chat messages as markdown (react-markdown + Tailwind typography) instead of raw text, and widen the games/characters/ chat layout to use more of the screen on desktop. - Sharpen the DM system prompt: state the ability + DC for each offered action option before the player commits, and narrate rolls as DC → result → pass/fail before the story consequence; also stop re-asking Session-0 questions already answered in the game's name/description. - Make the DM's LLM provider configurable (xai default, lmstudio for a local OpenAI-compatible server via host.docker.internal) instead of hardcoded to x.ai. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
57 lines
2.0 KiB
Python
57 lines
2.0 KiB
Python
import json
|
|
|
|
from app.config import settings
|
|
from app.llm.client import get_game_setup_prompt, get_llm_client
|
|
from app.llm.json_utils import fix_double_escaped_unicode
|
|
from app.llm.tools import game_setup as game_setup_tool
|
|
|
|
MAX_ROUNDS = 3
|
|
MAX_TOKENS = 1024
|
|
|
|
TOOLS = [{"type": "function", "function": game_setup_tool.TOOL_SCHEMA}]
|
|
|
|
|
|
async def run_game_setup_turn(client_messages: list[dict]) -> dict:
|
|
"""Stateless setup-wizard turn: the caller owns conversation history (no DB, no game yet).
|
|
|
|
Returns {"messages": <history to resend next turn>, "assistant_text": str | None,
|
|
"proposal": {"name": str, "description": str} | None}.
|
|
"""
|
|
client = get_llm_client()
|
|
messages = [{"role": "system", "content": get_game_setup_prompt()}] + client_messages
|
|
|
|
assistant_text: str | None = None
|
|
proposal: dict | None = None
|
|
|
|
for _ in range(MAX_ROUNDS):
|
|
response = await client.chat.completions.create(
|
|
model=settings.dm_model,
|
|
max_tokens=MAX_TOKENS,
|
|
messages=messages,
|
|
tools=TOOLS,
|
|
)
|
|
message = response.choices[0].message
|
|
if message.content:
|
|
assistant_text = message.content
|
|
|
|
if not message.tool_calls:
|
|
messages.append({"role": "assistant", "content": message.content or ""})
|
|
break
|
|
|
|
messages.append(message.model_dump(exclude_unset=True))
|
|
for tool_call in message.tool_calls:
|
|
args = fix_double_escaped_unicode(json.loads(tool_call.function.arguments))
|
|
if tool_call.function.name == "propose_game_setup":
|
|
proposal = {"name": args.get("name", ""), "description": args.get("description", "")}
|
|
messages.append(
|
|
{
|
|
"role": "tool",
|
|
"tool_call_id": tool_call.id,
|
|
"content": json.dumps({"status": "ok"}, ensure_ascii=False),
|
|
}
|
|
)
|
|
if proposal:
|
|
break
|
|
|
|
return {"messages": messages[1:], "assistant_text": assistant_text, "proposal": proposal}
|