Skip to content

fix(clean_chat_output): strip leading turn-marker prefix at start-of-string#1853

Merged
noahgift merged 1 commit into
mainfrom
fix/clean-chat-prefix-stripping
May 21, 2026
Merged

fix(clean_chat_output): strip leading turn-marker prefix at start-of-string#1853
noahgift merged 1 commit into
mainfrom
fix/clean-chat-prefix-stripping

Conversation

@noahgift

Copy link
Copy Markdown
Contributor

Summary

  • Strip leading "Human:" / "User:" / "Assistant:" prefix at start-of-string before the existing stop-sequence pass in clean_chat_output.
  • Closes the start-of-string corner case where existing \nHuman: / \n\nHuman: stop sequences require a preceding newline.
  • 6 new pin tests in api::tests::format_chat_02 covering: leading Human / User / Assistant, leading whitespace, inline post-leading-strip stop, no-false-positive on mid-sentence "Human:".

Why

Empirically observed at paiml/claude-code-parity-apr Phase 6 sub-bench B (M291) on Qwen3-Coder-30B-A3B: model began turns 1-20 with "Human: ..." which slipped through the cleaner verbatim. The new stop_token + EOS plumbing in #1852 stops generation at <|im_end|>, but if the model emitted "Human:" before the EOS, the prefix stayed.

Fix preserves mid-sentence "Human:" (only stripped at start-of-string) and still fires existing \nHuman: truncation for embedded turn-boundary leaks.

Test plan

  • cargo test -p aprender-serve --lib clean_chat_output_leading — 4 new leading-prefix tests pass
  • cargo test -p aprender-serve --lib test_clean_chat_output_inline_human_after_leading_strip — combined leading-strip + inline-stop case
  • cargo test -p aprender-serve --lib test_clean_chat_output_no_false_positive_on_human_in_middle — mid-sentence "Human:" preserved
  • No regression in 95+ existing clean_chat_output* tests

Cross-references

  • paiml/claude-code-parity-apr M291 V1_004 follow-up snapshot
  • aprender#1852 (EOS stop_token + clean_chat_output wire-up in MoE chat path)
  • aprender#1849 (few-shot prompt examples)

Refs: CCPA M291.

🤖 Generated with Claude Code

…:" prefix

The existing stop sequences in `clean_chat_output` anchor on "\nHuman:" /
"\n\nHuman:" — requiring a preceding newline. When a model leaks the
turn marker at start-of-string (no newline before it), the truncate-at-
earliest loop misses the marker and the prefix bleeds into the captured
chat reply verbatim.

Empirically observed at paiml/claude-code-parity-apr Phase 6 sub-bench
B (M291) on Qwen3-Coder-30B-A3B: the model began turns 1-20 with
"Human: ..." which the dense-path / MoE-path cleaner alike failed to
strip. (The new stop_token + EOS plumbing in #1852 stops generation
at "<|im_end|>" — but if the model emitted "Human:" *before* the EOS,
the prefix stayed.)

Fix: before the stop-sequence pass, trim leading whitespace and strip
a leading "Human:" / "User:" / "Assistant:" prefix if present. The
mid-sentence "Human:" case is preserved (only stripped at start-of-
string), and the existing "\nHuman:" truncation still fires for
embedded turn-boundary leaks.

6 new pin tests in api::tests::format_chat_02:
  test_clean_chat_output_leading_human_prefix
  test_clean_chat_output_leading_user_prefix
  test_clean_chat_output_leading_assistant_prefix
  test_clean_chat_output_leading_prefix_with_whitespace
  test_clean_chat_output_inline_human_after_leading_strip
  test_clean_chat_output_no_false_positive_on_human_in_middle

Refs: CCPA M291 V1_004 follow-up snapshot.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@noahgift noahgift merged commit c2ef16e into main May 21, 2026
19 of 21 checks passed
@noahgift noahgift deleted the fix/clean-chat-prefix-stripping branch May 21, 2026 01:27
noahgift added a commit that referenced this pull request May 21, 2026
…on invariants (#1859)

Author the provable contract behind `clean_chat_output` so the six
invariants established by the M287 → #1852#1853 cascade are
falsifier-backed instead of merely tested.

## Why

The cascade fixed three things in concert:
- #1852: EOS stop-token detection (`<|im_end|>` / `<|endoftext|>`)
- #1853: leading "Human:"/"User:"/"Assistant:" prefix strip
- M287 surface: 'Human: I need to...' runaway pattern post-EOS-miss

The implementation (`crates/aprender-serve/src/api/realize_handlers.rs::clean_chat_output`)
already lives; this contract retroactively codifies its guarantees so
future stop-sequence changes require a contract bump alongside the
code change. Hooks `pv lint` / contract-coverage audits onto a
previously-uncontracted sanitization layer.

## Six falsifiers

- V1_001: leading "Human:" / "User:" / "Assistant:" stripped
- V1_002: stop sequence inside body truncates at first occurrence
- V1_003: earliest stop sequence wins when multiple are present
- V1_004: clean text passes through (trim-only)
- V1_005: empty / whitespace / stop-only collapses to ""
- V1_006: STOP_SEQUENCES code constant ↔ contract YAML stay synced
  (manual audit for now; could be pv-lint check later)

## Evidence

All six are already covered by existing unit tests in
`crates/aprender-serve/src/api/realize_handlers_clean_chat.rs` and
`crates/aprender-serve/src/api/tests/format_chat_02.rs`.

## Validation

`pv validate contracts/clean-chat-output-v1.yaml` → "Contract is valid."

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
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