Journal for generation LLM

This commit is contained in:
Dejvino
2026-07-03 21:15:37 +02:00
parent b6f56f22fd
commit 52deb1db6a
6 changed files with 84 additions and 32 deletions
+3 -2
View File
@@ -1,6 +1,6 @@
from __future__ import annotations
from .paths import CHAR_PATH, WORLD_PATH
from .paths import CHAR_PATH, WORLD_PATH, JOURNAL_PATH
from .prompts import SYSTEM_PROMPT
from . import state
@@ -10,7 +10,8 @@ def build_system_prompt(recent_narrative: str | None = None, recent_log: str | N
char = state.read_file(CHAR_PATH) or "*No character sheet.*"
world = state.truncate_world(state.read_file(WORLD_PATH) or "") or "*No world state.*"
log = recent_log if recent_log is not None else state.read_recent_log()
journal = state.read_file(JOURNAL_PATH) or "*No journal entries.*"
story = recent_narrative if recent_narrative is not None else state.read_recent_book(2)
return SYSTEM_PROMPT.substitute(
character=char, world=world, log=log, story=story,
character=char, world=world, log=log, journal=journal, story=story,
)
+5
View File
@@ -73,5 +73,10 @@ $world
### Log
$log
### Journal (TODO / DONE)
$journal
**journal_update rule**: When calling `journal_update`, you MUST use the EXACT wording of the TODO items from the Journal above. Do not rephrase, paraphrase, or invent alternate descriptions — match the TODO text character-for-character. Mark items as `done` exactly as they appear in TODO. Add new items with exact wording matching their entry in the list.
### Story
$story""")
+25 -15
View File
@@ -4,7 +4,7 @@ import json
import re
from .llm import call_llm
from .paths import CHAR_PATH, WORLD_PATH
from .paths import CHAR_PATH, WORLD_PATH, JOURNAL_PATH
from . import state
@@ -108,6 +108,7 @@ TURN_VALIDATION_PROMPT = """You are a strict RPG game master validating a genera
3. **State Correctness**: Do the planned state changes match the narrative? Are they valid given current state?
4. **Self-Contained Narrative**: The narrative must read clearly on its own — explicitly describe what the character did in response to the action. Do not skip the character's action and jump straight to consequences. Each turn must make sense without referencing the player action line.
5. **Log Entry**: Does the log entry accurately summarise the narrative in 1-2 short, dense sentences? Should be specific, factual, and immediately readable.
6. **Journal Progress**: Are TODO items being addressed? If the narrative resolves an open TODO, the turn must call `journal_update` to mark it done. Unchecked items left stale too long may need prompting.
## Character (before changes)
{character}
@@ -121,6 +122,9 @@ TURN_VALIDATION_PROMPT = """You are a strict RPG game master validating a genera
## Session Log
{log}
## Journal
{journal}
## Player Action
{action}
@@ -142,6 +146,7 @@ Check all criteria. **Completeness** is critical — scan the narrative for ever
- **Cash changed** → must have `modify_vitals`
- **World changed** → must have `world_update`
- **NPC/location/thread changes** → must have `world_update` or `add_note`
- **TODO resolved** → must have `journal_update` with `done`
Missing tool calls = regenerate. Also check that:
- The narrative explicitly describes the character acting — not just the world reacting
@@ -153,21 +158,21 @@ Missing tool calls = regenerate. Also check that:
For log entry: must be a tight summary of the narrative's key events — specific entities, actions, outcomes. Vague, rambling, or mismatched log entries should be flagged for regenerate.
Reply with ONLY a JSON object using one of these formats:
Reply with ONLY a ```tool block. Examples:
Valid:
```json
{{"valid": true, "reason": "ok", "action": "ok"}}
```tool
{{"tool": "validate", "args": {{"valid": true, "reason": "ok", "action": "ok"}}}}
```
Reject (player action itself was impossible or nonsensical):
```json
{{"valid": false, "reason": "explain why the action is impossible", "action": "reject"}}
```tool
{{"tool": "validate", "args": {{"valid": false, "reason": "explain why the action is impossible", "action": "reject"}}}}
```
Regenerate (turn had fixable issues like wrong state changes or minor inconsistencies):
```json
{{"valid": false, "reason": "describe what the LLM should fix", "action": "regenerate"}}
```tool
{{"tool": "validate", "args": {{"valid": false, "reason": "describe what the LLM should fix", "action": "regenerate"}}}}
```
"""
@@ -206,11 +211,12 @@ def validate_turn(
world = state.truncate_world(state.read_file(WORLD_PATH) or "") or "*No world state.*"
recent = story.strip() or state.read_recent_book() or "*No prior story.*"
log_entries = log.strip() or state.read_recent_log() or "*No recent events.*"
journal = state.read_file(JOURNAL_PATH) or "*No journal entries.*"
change_summary = _format_changes(changes or [])
prompt = TURN_VALIDATION_PROMPT.format(
character=char, world=world, story=recent,
log=log_entries, action=player_action,
log=log_entries, journal=journal, action=player_action,
narrative=narrative, log_entry=log_entry or "*No log entry provided.*",
changes=change_summary,
)
@@ -229,15 +235,19 @@ def validate_turn(
if not text:
return False, "Not sure", "reject"
cleaned = text.strip()
m = re.search(r"```(?:json)?\s*\n?(.*?)```", cleaned, re.DOTALL)
m = re.search(r"```tool\s*\n?(.*?)```", text, re.DOTALL)
if m:
cleaned = m.group(1).strip()
else:
cleaned = text.strip()
try:
data = json.loads(cleaned)
valid = data.get("valid", True)
reason = data.get("reason", "")
action = data.get("action", "ok")
if data.get("tool") != "validate":
return False, "Unrecognized", "reject"
args = data.get("args", {})
valid = args.get("valid", True)
reason = args.get("reason", "")
action = args.get("action", "ok")
if action not in ("ok", "reject", "regenerate"):
action = "ok" if valid else "reject"
if on_debug:
@@ -249,7 +259,7 @@ def validate_turn(
if attempt == 0:
messages.append({
"role": "system",
"content": "Your previous response was not valid JSON. Reply with ONLY a JSON object:\n\n```json\n{\"valid\": true, \"reason\": \"ok\", \"action\": \"ok\"}\n```\nor\n```json\n{\"valid\": false, \"reason\": \"...\", \"action\": \"reject\"}\n```\nor\n```json\n{\"valid\": false, \"reason\": \"...\", \"action\": \"regenerate\"}\n```"
"content": "Your previous response was not valid. Reply with ONLY a ```tool block:\n\n```tool\n{\"tool\": \"validate\", \"args\": {\"valid\": true, \"reason\": \"ok\", \"action\": \"ok\"}}\n```\nor\n```tool\n{\"tool\": \"validate\", \"args\": {\"valid\": false, \"reason\": \"...\", \"action\": \"reject\"}}\n```\nor\n```tool\n{\"tool\": \"validate\", \"args\": {\"valid\": false, \"reason\": \"...\", \"action\": \"regenerate\"}}\n```"
})
return False, "Unrecognized", "reject"