Files
hermes-webui/tests/test_live_snapshot_echo_dedup.py
happy5318 3369bc6a6d fix(run-journal): match echo suffixes with a window-free backward walk
Review follow-up: the bounded echo probe folded at most len(text)*3 raw characters, so a compact-equivalent suffix whose raw span was stretched by interior whitespace was missed - the echo strip was dropped and the interim text was duplicated in the restored transcript. The same retired window existed in the reverse helper (_strip_compact_echo_suffix), pinned by its own test.

Both now share _find_compact_echo_suffix_start: a backward walk over value/suffix that skips whitespace in value, inspects only the echo span, and allocates nothing. It restores the previous output semantics (origin/master folded the whole reasoning text) and is cheaper than the windowed version (no per-call tail fold).

Tests: new journal-reconstruction regression (a 5000-space-stretched echo must appear exactly once; genuine interim text must not be treated as an echo), the suffix-helper oracle updated to window-free semantics, and the window-contract test now pins that a stretched echo IS stripped.
2026-09-21 12:34:30 +08:00

105 lines
3.6 KiB
Python

"""Journal-reconstruction regression: a whitespace-stretched reasoning echo
must be de-duplicated exactly once.
``_run_journal_live_snapshot`` strips reasoning echoes of interim assistant
text so the restored transcript does not show the same content in both the
thinking block and the assistant output (run-state consistency invariant:
replay must not duplicate interim assistant text). The previous bounded echo
probe could miss a compact-equivalent suffix whose raw span exceeded its fold
window, leaving the duplicate in the snapshot; the backward walk has no
window.
Both directions are pinned: a stretched echo is stripped (appears once), and
genuine interim text that is NOT a reasoning echo is left alone.
"""
import json
import re
import api.routes as routes
from api import run_journal as RJ
def _compact(value):
return re.sub(r"\s+", "", str(value or ""))
def _write_journal(root, session_id, run_id, events):
session_root = root / RJ.RUN_JOURNAL_DIR_NAME / session_id
session_root.mkdir(parents=True, exist_ok=True)
path = session_root / f"{run_id}.jsonl"
rows = []
for seq, (event, payload) in enumerate(events, start=1):
rows.append(
json.dumps(
{
"version": 1,
"event_id": f"{run_id}:{seq}",
"seq": seq,
"run_id": run_id,
"session_id": session_id,
"event": event,
"type": event,
"created_at": 1000 + seq,
"payload": payload,
}
)
)
path.write_text("\n".join(rows) + "\n", encoding="utf-8")
return path
def test_whitespace_stretched_reasoning_echo_appears_once(tmp_path, monkeypatch):
gap = " " * 5000 # raw echo span far beyond any fixed fold window
_write_journal(
tmp_path,
"session_1",
"run_1",
[
("reasoning", {"text": "prefix " + "alpha" + gap + "beta"}),
("interim_assistant", {"text": "alpha beta"}),
],
)
monkeypatch.setattr(RJ, "_default_session_dir", lambda: tmp_path)
snapshot = routes._run_journal_live_snapshot("run_1")
message = snapshot["messages"][-1]
reasoning_text = message.get("reasoning") or ""
content_text = message.get("content") or ""
occurrences = _compact(reasoning_text).count("alphabeta") + _compact(
content_text
).count("alphabeta")
assert occurrences == 1, (
f"the interim echo must appear once after reconstruction; "
f"reasoning={reasoning_text[-60:]!r} content={content_text[-60:]!r}"
)
# The echo span is stripped from the reasoning copy; the prefix stays.
assert "alphabeta" not in _compact(reasoning_text)
assert "alpha beta" in content_text
def test_genuine_interim_text_is_not_treated_as_echo(tmp_path, monkeypatch):
gap = " " * 5000
_write_journal(
tmp_path,
"session_1",
"run_1",
[
("reasoning", {"text": "prefix " + "alpha" + gap + "beta"}),
("interim_assistant", {"text": "gamma delta"}),
],
)
monkeypatch.setattr(RJ, "_default_session_dir", lambda: tmp_path)
snapshot = routes._run_journal_live_snapshot("run_1")
message = snapshot["messages"][-1]
reasoning_text = message.get("reasoning") or ""
content_text = message.get("content") or ""
# The genuine interim answer is preserved in full...
assert "gamma delta" in content_text
# ...and the reasoning transcript is not suppressed by the matcher.
assert _compact(reasoning_text).count("alphabeta") == 1
assert "prefix" in reasoning_text