From 236a16b33151406a837a41a6a69694e35529f9a3 Mon Sep 17 00:00:00 2001 From: serversdown Date: Mon, 15 Jun 2026 23:52:35 +0000 Subject: [PATCH] feat: inspect the full prompt in the live log MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "context built" event now carries the fully-rendered prompt (persona, gists, recalled details, recent turns, the new message) plus a total char count. The log panel renders it as a collapsed "view full prompt" block — clean by default, one click to see exactly what hit the model. Co-Authored-By: Claude Opus 4.8 (1M context) --- lyra/chat.py | 16 +++++++++++----- lyra/web/static/index.html | 6 +++++- lyra/web/static/style.css | 22 ++++++++++++++++++++++ 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/lyra/chat.py b/lyra/chat.py index 971bc7d..551d58f 100644 --- a/lyra/chat.py +++ b/lyra/chat.py @@ -30,6 +30,11 @@ def _detail_note(exchanges: list[memory.Exchange]) -> Message: return {"role": "system", "content": body} +def _render(messages: list[Message]) -> str: + """Human-readable dump of the exact prompt, for the live-log inspector.""" + return "\n\n".join(f"[{m['role']}]\n{m['content']}" for m in messages) + + def build_messages(session_id: str, user_msg: str) -> list[Message]: """Assemble the full, tiered message list for one turn.""" messages: list[Message] = [{"role": "system", "content": persona.system_prompt()}] @@ -51,16 +56,17 @@ def build_messages(session_id: str, user_msg: str) -> list[Message]: if recalled: messages.append(_detail_note(recalled)) - logbus.log( - "debug", "context built", - recent=len(recent), summaries=len(summaries), details=len(recalled), - ) - # Tier 3: current session, full fidelity. for ex in recent: messages.append({"role": ex.role, "content": ex.content}) messages.append({"role": "user", "content": user_msg}) + + logbus.log( + "debug", "context built", + recent=len(recent), summaries=len(summaries), details=len(recalled), + chars=sum(len(m["content"]) for m in messages), detail=_render(messages), + ) return messages diff --git a/lyra/web/static/index.html b/lyra/web/static/index.html index 3cbb822..adb30a6 100644 --- a/lyra/web/static/index.html +++ b/lyra/web/static/index.html @@ -734,7 +734,10 @@ const level = event.level || 'info'; const time = new Date((event.ts || 0) * 1000).toLocaleTimeString(); - const fields = event.fields || {}; + const fields = Object.assign({}, event.fields || {}); + // `detail` is rendered as an expandable block, not an inline field. + const detail = fields.detail; + delete fields.detail; const fieldStr = Object.keys(fields).length ? Object.entries(fields).map(([k, v]) => `${k}=${v}`).join(' ') : ''; @@ -746,6 +749,7 @@ ${escapeHtml(level)} ${escapeHtml(event.msg || '')} ${fieldStr ? `${escapeHtml(fieldStr)}` : ''} + ${detail ? `
view full prompt
${escapeHtml(detail)}
` : ''} `; thinkingContent.appendChild(eventDiv); diff --git a/lyra/web/static/style.css b/lyra/web/static/style.css index bdfbb46..a93bf8a 100644 --- a/lyra/web/static/style.css +++ b/lyra/web/static/style.css @@ -941,3 +941,25 @@ select:hover { .log-error .log-level, .log-error .log-msg { color: #fca5a5; } .log-system { border-left-color: #00ff66; } .log-system .log-level { color: #00ff66; } + +.log-detail { width: 100%; margin-top: 4px; } +.log-detail summary { + cursor: pointer; + color: var(--accent); + font-size: 0.72rem; + user-select: none; +} +.log-detail pre { + margin: 6px 0 0; + padding: 8px; + max-height: 340px; + overflow: auto; + background: rgba(0,0,0,0.25); + border-left: 2px solid var(--accent); + border-radius: 4px; + font-size: 0.72rem; + line-height: 1.4; + white-space: pre-wrap; + word-break: break-word; + color: var(--text); +}