Skip to content

fix(wiki): recover from streamed chunk-repetition abort instead of failing the page (RES-947) - #334

Open
jhkchan wants to merge 1 commit into
mainfrom
feature/res-947-wiki-stream-repetition-retry
Open

fix(wiki): recover from streamed chunk-repetition abort instead of failing the page (RES-947)#334
jhkchan wants to merge 1 commit into
mainfrom
feature/res-947-wiki-stream-repetition-retry

Conversation

@jhkchan

@jhkchan jhkchan commented Jul 17, 2026

Copy link
Copy Markdown
Member

Problem (RES-947)

Dense OCR'd tables prime the wiki model to reproduce giant --- GFM separator walls. litellm's streaming repetition guard aborts with MidStreamFallbackError: ... The model is repeating the same chunk = ..., which propagated to the "subtopic page failed hard" branch → the page fell back to empty. This hits the exact content dominant in an architecture/engineering corpus (drawing schedules, dimension tables).

Live evidence (RLP POC): WikiCompiler: subtopic page failed hard (litellm.MidStreamFallbackError: litellm.InternalServerError: The model is repeating the same chunk = ------…).

Fix

Handle it at the source in _llm_generate_json. The repetition guard only runs in litellm's streaming handler, so when a streamed dispatch aborts with a repetition error, retry the call once non-streamed. The full content then flows to _call_llm's existing _is_degenerate_content dash-wall guard → deterministic Key Facts splice, rather than crashing. Fix lives in the shared helper, so every page kind inherits it (overview/people/activity/resources/glossary/subtopic).

  • New _is_stream_repetition_abort(exc) — message-substring is the robust primary signal (MidStreamFallbackError isn't exported at litellm top level).
  • New stream_override param on _llm_generate_json.
  • Non-repetition errors re-raised unchanged; no retry when already non-streamed.

Tests

tests/wiki/test_compiler_stream_repetition.py: detector; streamed-abort → non-streamed retry returns good content (dispatch called stream=True then stream=False); non-repetition errors propagate; no retry when already non-streamed. Existing test_compiler_degenerate_guard + test_compiler_retry_gating still pass — 17 passed locally.

Part of epic RES-943 (RLP full-corpus scale + no-cloud gaps).

🤖 Generated with Claude Code

https://claude.ai/code/session_01PMMM6KQXmzAEA42UxpyMUm

…iling the page (RES-947)

Dense OCR'd tables prime the wiki model to reproduce giant `---` GFM separator
walls; litellm's streaming repetition guard then aborts with
`MidStreamFallbackError: ... The model is repeating the same chunk = ...`, and
the exception propagated up to the "subtopic page failed hard" branch — the page
fell back to empty on the exact content type dominant in an architecture corpus
(drawing schedules, dimension tables).

Handle it at the source in `_llm_generate_json`: the repetition guard only runs
in litellm's streaming handler, so when a streamed dispatch aborts with a
repetition error we retry the call once non-streamed. The full (possibly still
dash-heavy) content then flows to `_call_llm`'s existing `_is_degenerate_content`
dash-wall guard → deterministic Key Facts splice, instead of crashing. Because
the fix lives in the shared helper, every page kind inherits it.

- Adds `_is_stream_repetition_abort(exc)` (message-substring primary signal;
  `MidStreamFallbackError` isn't exported at litellm top level).
- Adds `stream_override` param to `_llm_generate_json` (forces stream on/off).
- Non-repetition errors are re-raised unchanged; no retry when already non-streamed.

Adds tests/wiki/test_compiler_stream_repetition.py (detector, retry-recovers,
re-raises-other-errors, no-retry-when-non-streamed). Existing degenerate-guard
and retry-gating suites still pass (17 passed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMMM6KQXmzAEA42UxpyMUm
@jhkchan

jhkchan commented Jul 17, 2026

Copy link
Copy Markdown
Member Author

Independent adversarial review — APPROVE (nits only).

No P0/P1 defect. Verified against the installed litellm: the real production exception from raise_on_model_repetition is litellm.InternalServerError with message "The model is repeating the same chunk = ...", and the detector's substring check fires on it. Detector does not over-match (ValueError, unrelated InternalServerError, CancelledError → False), the guarded litellm.exceptions import can't break module load, stream_override genuinely reaches dispatch_completion(stream=...), retry is exactly-once with correct re-raise and no infinite loop, no shared-state race.

Nits (follow-up):

  • [P2] Docstring attributes the abort to MidStreamFallbackError, but that class is Router-only (litellm.Router) — since we call litellm.acompletion directly, that isinstance branch is effectively dead here; the real signal is the InternalServerError message substring. Reword; keep the isinstance as defensive-only.
  • [P2 test fidelity] All retry tests raise a hand-authored RuntimeError("litellm.MidStreamFallbackError: ..."), not the real litellm.InternalServerError. Add one test using the real exception so a litellm message/rename can't silently break detection.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant