From 3b03bf737a4ada46dac77d1c7d58406b51c8f1b2 Mon Sep 17 00:00:00 2001 From: parththakkar106 Date: Tue, 21 Jul 2026 16:34:38 +0530 Subject: [PATCH] Show raw AI output in Insights per turn MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Store the model's literal reply (before the world-state block is stripped) in each turn's snapshot, and render it in the Insights panel when inspecting a past AI message (the 🔍 button). Makes it possible to see exactly what the model emitted — including whether it sent a state delta and with what paths — which is the fastest way to debug under-reported or misrouted stat changes. Co-Authored-By: Claude Opus 4.8 --- backend/app/routers/adventures.py | 5 +++++ backend/tests/test_worldstate_integration.py | 8 ++++++++ frontend/src/pages/Play.jsx | 8 ++++++++ 3 files changed, 21 insertions(+) diff --git a/backend/app/routers/adventures.py b/backend/app/routers/adventures.py index e79816a..12714e4 100644 --- a/backend/app/routers/adventures.py +++ b/backend/app/routers/adventures.py @@ -354,6 +354,9 @@ async def generate_turn( return text = "".join(chunks).strip() + # The model's literal reply, kept for the Insights "Raw AI output" view — + # this still contains any world-state block before it gets stripped below. + raw_output = text if not text: # If the model streamed reasoning but no story text, it spent its whole # budget thinking — say so instead of a mysterious "empty response". @@ -390,6 +393,8 @@ async def generate_turn( adventure.world_state = new_world_state snapshot["world_state"] = {"delta": delta, "report": ws_report, "state": new_world_state} + snapshot["raw_output"] = raw_output + ai_action = models.Action( adventure_id=adventure.id, index=ai_index, diff --git a/backend/tests/test_worldstate_integration.py b/backend/tests/test_worldstate_integration.py index 82fb4bb..0903d27 100644 --- a/backend/tests/test_worldstate_integration.py +++ b/backend/tests/test_worldstate_integration.py @@ -130,6 +130,14 @@ def test_turn_applies_clamped_delta_and_strips_block(client): # The state block is not shown to the player. assert "```state" not in _last_ai_text(client.adv_id) assert "goblin's blade" in _last_ai_text(client.adv_id) + # ...but the raw model reply (with the block) is kept for the Insights view. + db = SessionLocal() + try: + snap = db.get(models.Adventure, client.adv_id).actions[-1].context_snapshot + finally: + db.close() + assert "```state" in snap["raw_output"] + assert '"player.hp": -80' in snap["raw_output"] def test_world_state_endpoint(client): diff --git a/frontend/src/pages/Play.jsx b/frontend/src/pages/Play.jsx index 3dfc424..000f6ff 100644 --- a/frontend/src/pages/Play.jsx +++ b/frontend/src/pages/Play.jsx @@ -769,6 +769,14 @@ function InsightsPanel({ advId, inspectActionId, onClearInspect, refreshKey }) { ))} + {report.raw_output && ( +
+
+ Raw AI output (before state block was stripped) +
+
{report.raw_output}
+
+ )} ) }