Add per-game adventure text with RAG-based retrieval
Players can now paste the full text of a freely available adventure when creating a game, and the DM will follow it instead of inventing its own plot/NPCs/locations. Reuses the existing rulebook RAG pipeline (chunking, local embedding) rather than injecting the raw text into every turn, since real adventure modules range from a few pages to hundreds — far beyond what fits in a prompt. - New adventure_chunks table (game-scoped, unlike the global rulebook_chunks) + Game.adventure_text storing the raw source. - ingest_adventure_text() chunks/embeds synchronously during POST /api/games when adventure_text is non-empty. - build_adventure_rag_block() retrieves by similarity to the latest player message, scoped to game_id — plus always includes chunk 0 (the adventure's opening) regardless of the query, since early Session-0 messages rarely resemble the adventure's actual hook and pure similarity search could miss the beginning entirely. - System prompt instructs the DM to follow provided adventure excerpts strictly, deviating only when players clearly go off-script. - Frontend: optional adventure-text field in the game creation form, a has_adventure flag surfaced as a small badge on the game card and session header. Verified with an isolated two-game test that chunks/retrieval never leak across games (the critical failure mode here), and live against x.ai: a custom-written one-shot's specific NPCs, location, and hook appeared verbatim in the DM's opening scene instead of invented ones. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,7 @@ from app.llm.json_utils import fix_double_escaped_unicode
|
||||
from app.llm.tools import character_sheet, dice, end_game, monster, world_state
|
||||
from app.models.game import Game
|
||||
from app.models.message import Message
|
||||
from app.rag.adventure_retrieval import build_adventure_rag_block
|
||||
from app.rag.retrieval import build_rag_block
|
||||
|
||||
logger = logging.getLogger("app.llm.orchestrator")
|
||||
@@ -102,6 +103,10 @@ async def run_dm_turn(
|
||||
if rag_block:
|
||||
system_prompt = f"{system_prompt}\n\n{rag_block}"
|
||||
|
||||
adventure_block = await build_adventure_rag_block(session, game_id, latest_player_message)
|
||||
if adventure_block:
|
||||
system_prompt = f"{system_prompt}\n\n{adventure_block}"
|
||||
|
||||
messages = [{"role": "system", "content": system_prompt}]
|
||||
messages.extend(await build_context(session, game_id))
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ Regeln & Wissensquellen
|
||||
* Du bist Regelexperte: Bei Unsicherheiten der Spieler erklärst du Mechaniken (Kämpfe, Zauber, Fertigkeiten, Zustände) verständlich und korrekt.
|
||||
* Wenn Handbücher/Regelwerke hochgeladen wurden, richtest du dich strikt nach deren Inhalt und zitierst/nennst bei Bedarf die relevante Regel. Wurde nichts hochgeladen, greifst du auf dein allgemeines D&D-5e-Regelwissen zurück und weist transparent darauf hin, falls du bei einer Detailregel unsicher bist, statt sie zu erfinden.
|
||||
* Bei Widersprüchen zwischen Spielspaß und exakter Regelauslegung fragst du kurz nach, welche Priorität die Gruppe bevorzugt (z. B. eher erzählerisch-locker oder regelgenau).
|
||||
* Stehen unten Auszüge aus einem vorgegebenen Abenteuer, hält sich die Handlung strikt daran – Ort, NPCs, Encounter, Plot so, wie im Text vorgegeben, statt eigene zu erfinden. Weiche nur ab, wenn die Spieler klar einen eigenen Weg einschlagen oder der Text an der Stelle keine Vorgabe macht; führe dann sinnvoll weiter und kehre zum Abenteuer zurück, sobald es passt.
|
||||
|
||||
Sprache
|
||||
Antworte immer in der Sprache, in der die Spieler mit dir kommunizieren. Wechselt die Gruppe die Sprache, wechselst du mit.
|
||||
|
||||
Reference in New Issue
Block a user