Fixed SD RPG
This commit is contained in:
+63
-15
@@ -2,7 +2,7 @@ import json
|
||||
import os
|
||||
import random
|
||||
|
||||
from services.llm import send_message_with_model
|
||||
from services.llm import LLMError, send_message_with_model
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -18,8 +18,10 @@ Return ONLY valid JSON (no markdown):
|
||||
"check_reason": "brief reason why a check is needed (e.g. 'jumping over a pit')",
|
||||
"directives": ["short imperative rules for the next character reply"],
|
||||
"resolution_text": "what actually happens as result of the action — written as narrator prose (1-2 sentences). Only if needs_check=true and roll/outcome provided.",
|
||||
"status_quo_update": "optional short update about the world state"
|
||||
"status_quo_update": "optional short update about the world state",
|
||||
"scene_update": {"place": "", "time_of_day": "", "day": "", "weather": "", "exits": [], "layout_note": ""}
|
||||
}
|
||||
scene_update: only include keys that changed (partial). Omit scene_update if nothing changed.
|
||||
If needs_check=false: directives may still guide tone/pacing, resolution_text must be empty string.
|
||||
If needs_check=true and roll/outcome are provided: resolution_text MUST reflect the outcome.
|
||||
- critical failure (1): embarrassing or painful failure with extra complication
|
||||
@@ -32,17 +34,25 @@ After the character replied, update persistent state.
|
||||
Return ONLY valid JSON (no markdown):
|
||||
{
|
||||
"status_quo_update": "what changed in the world/state (1-3 sentences)",
|
||||
"facts": ["durable facts only"],
|
||||
"facts": ["durable fact strings OR {\"text\":\"...\",\"rp_day\":\"день 1\"}"],
|
||||
"choices": [{"id":"a","label":"..."}, ...],
|
||||
"affinity_delta": 0,
|
||||
"stats_delta": {"lust": 0, "stamina": 0, "tension": 0},
|
||||
"scene_update": {"place": "", "place_id": "", "time_of_day": "", "day": "", "weather": "", "exits": [], "layout_note": ""},
|
||||
"quest_updates": [{"title": "quest title", "status": "active|done|failed"}],
|
||||
"outfit_update": ["danbooru_tag", "danbooru_tag"]
|
||||
}
|
||||
Rules:
|
||||
- status_quo_update: internal DM state only (facts, location, mood). Never address the player, never use headers like "Status quo"/"Статус кво", P.S., or author commentary.
|
||||
- affinity_delta: integer -2..+2. Positive if character warmed up to player, negative if pushed away. 0 if neutral.
|
||||
- stats_delta: each lust/stamina/tension -2..+2 (0 if unchanged). lust=arousal, stamina=energy, tension=stress.
|
||||
- scene_update: partial location/time schema; only keys that changed. Do not duplicate all of status_quo into scene_update.
|
||||
- quest_updates: only include if a quest was clearly started, completed, or failed. Empty array otherwise.
|
||||
- choices: 0-4 options for what the player can do next.
|
||||
- outfit_update: ONLY include if the character's clothing visibly changed (put on, took off, changed outfit). Use exact danbooru-style underscore_tags (e.g. ["white_dress", "red_ribbon", "barefoot"]). Empty array if no change."""
|
||||
- choices: 0-4 options for what the player can do next. REQUIRED when scripted beats are exhausted — never return an empty choices array unless the session truly ended.
|
||||
- outfit_update: ONLY if clothing visibly changed. Use danbooru underscore_tags WITH COLOR when possible
|
||||
(e.g. white_tank_top, black_sports_shorts, gold_championship_belt, blue_jeans, red_ribbon).
|
||||
Every garment tag should include a color prefix unless the item is inherently colorless (barefoot, nude).
|
||||
Never bare generic tags like sports_shorts or torn_tank_top without a color. Empty array if no change."""
|
||||
|
||||
|
||||
async def narrator_pre(
|
||||
@@ -53,6 +63,7 @@ async def narrator_pre(
|
||||
user_message: str,
|
||||
roll: int | None = None,
|
||||
outcome: str | None = None,
|
||||
extra_context: str = "",
|
||||
) -> dict:
|
||||
roll_block = f"Roll d20={roll}\nOutcome={outcome}\n\n" if roll is not None else ""
|
||||
user = (
|
||||
@@ -63,10 +74,20 @@ async def narrator_pre(
|
||||
f"Facts:\n{facts_block}\n\n"
|
||||
f"Recent context:\n{context}\n"
|
||||
)
|
||||
raw = await send_message_with_model(
|
||||
[{"role": "system", "content": NARRATOR_PRE_SYSTEM}, {"role": "user", "content": user}],
|
||||
NARRATOR_MODEL,
|
||||
)
|
||||
if extra_context:
|
||||
user += f"\n--- Session state ---\n{extra_context}\n---\n"
|
||||
try:
|
||||
raw = await send_message_with_model(
|
||||
[{"role": "system", "content": NARRATOR_PRE_SYSTEM}, {"role": "user", "content": user}],
|
||||
NARRATOR_MODEL,
|
||||
)
|
||||
except LLMError as e:
|
||||
logger.warning("Narrator-pre LLM failed (model=%s): %s", NARRATOR_MODEL, e)
|
||||
return {"needs_check": False, "directives": [], "status_quo_update": "", "resolution_text": "", "_ok": False}
|
||||
except Exception as e:
|
||||
logger.warning("Narrator-pre unexpected error: %s", e)
|
||||
return {"needs_check": False, "directives": [], "status_quo_update": "", "resolution_text": "", "_ok": False}
|
||||
|
||||
cleaned = raw.strip()
|
||||
if cleaned.startswith("```"):
|
||||
cleaned = cleaned.split("\n", 1)[1] if "\n" in cleaned else cleaned
|
||||
@@ -76,10 +97,11 @@ async def narrator_pre(
|
||||
try:
|
||||
data = json.loads(cleaned)
|
||||
if isinstance(data, dict):
|
||||
data["_ok"] = True
|
||||
return data
|
||||
except Exception:
|
||||
logger.warning("Narrator-pre JSON parse failed. Raw=%.500s", raw)
|
||||
return {"needs_check": False, "directives": [], "status_quo_update": "", "resolution_text": ""}
|
||||
return {"needs_check": False, "directives": [], "status_quo_update": "", "resolution_text": "", "_ok": False}
|
||||
|
||||
|
||||
async def narrator_post(
|
||||
@@ -87,17 +109,42 @@ async def narrator_post(
|
||||
context: str,
|
||||
global_plot: str,
|
||||
facts_block: str,
|
||||
is_opening: bool = False,
|
||||
extra_context: str = "",
|
||||
) -> dict:
|
||||
opening_block = ""
|
||||
if is_opening:
|
||||
opening_block = (
|
||||
"\n\nOPENING SCENE: This is the first greeting, not a mid-conversation reply. "
|
||||
"Extract the character's INITIAL visible clothing from the greeting into outfit_update "
|
||||
"(danbooru underscore tags WITH color prefixes: white_shirt, black_shorts, gold_belt), "
|
||||
"even if clothing did not change during the scene. "
|
||||
"Set status_quo to describe the opening situation. "
|
||||
"Fill scene_update from greeting and scenario (place, time_of_day, day, layout_note). "
|
||||
"If the greeting shows clear warmth or hostility toward the player, set affinity_delta "
|
||||
"non-zero (-2..+2); use 0 only if truly neutral.\n"
|
||||
)
|
||||
user = (
|
||||
f"Persona: {persona_name}\n\n"
|
||||
f"Global plot:\n{global_plot}\n\n"
|
||||
f"Facts:\n{facts_block}\n\n"
|
||||
f"Recent context:\n{context}\n"
|
||||
f"{opening_block}"
|
||||
)
|
||||
raw = await send_message_with_model(
|
||||
[{"role": "system", "content": NARRATOR_POST_SYSTEM}, {"role": "user", "content": user}],
|
||||
NARRATOR_MODEL,
|
||||
)
|
||||
if extra_context:
|
||||
user += f"\n--- Session state ---\n{extra_context}\n---\n"
|
||||
try:
|
||||
raw = await send_message_with_model(
|
||||
[{"role": "system", "content": NARRATOR_POST_SYSTEM}, {"role": "user", "content": user}],
|
||||
NARRATOR_MODEL,
|
||||
)
|
||||
except LLMError as e:
|
||||
logger.warning("Narrator-post LLM failed (model=%s): %s", NARRATOR_MODEL, e)
|
||||
return {"status_quo_update": "", "facts": [], "choices": [], "affinity_delta": 0, "quest_updates": [], "_ok": False}
|
||||
except Exception as e:
|
||||
logger.warning("Narrator-post unexpected error: %s", e)
|
||||
return {"status_quo_update": "", "facts": [], "choices": [], "affinity_delta": 0, "quest_updates": [], "_ok": False}
|
||||
|
||||
cleaned = raw.strip()
|
||||
if cleaned.startswith("```"):
|
||||
cleaned = cleaned.split("\n", 1)[1] if "\n" in cleaned else cleaned
|
||||
@@ -107,7 +154,8 @@ async def narrator_post(
|
||||
try:
|
||||
data = json.loads(cleaned)
|
||||
if isinstance(data, dict):
|
||||
data["_ok"] = True
|
||||
return data
|
||||
except Exception:
|
||||
logger.warning("Narrator-post JSON parse failed. Raw=%.500s", raw)
|
||||
return {"status_quo_update": "", "facts": [], "choices": [], "affinity_delta": 0, "quest_updates": []}
|
||||
return {"status_quo_update": "", "facts": [], "choices": [], "affinity_delta": 0, "quest_updates": [], "_ok": False}
|
||||
|
||||
Reference in New Issue
Block a user