Skip to content

fix(langgraph): replace partial accumulator when final canonical content arrives#218

Merged
blove merged 8 commits into
mainfrom
claude/langgraph-streaming-content-dedup
May 9, 2026
Merged

fix(langgraph): replace partial accumulator when final canonical content arrives#218
blove merged 8 commits into
mainfrom
claude/langgraph-streaming-content-dedup

Conversation

@blove

@blove blove commented May 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Targets Finding C from the live smoke pass: visible answer in <chat-streaming-md> rendered ~1.83× expected length when the AI message content array contained both a reasoning block and a text block.

Root cause

accumulateContent in libs/langgraph/src/lib/internals/stream-manager.bridge.ts falls back to existing + incoming text concatenation when neither side is a strict prefix. Streaming chunks accumulate the visible answer; the final canonical message arrives with full text but with small formatting differences (whitespace / Unicode normalization), the prefix check fails, and the fallback produces visible duplication.

Fix

Narrow heuristic that only triggers on a specific final-canonical shape:

```ts
function isFinalCanonicalReasoningContent(content: unknown): boolean {
// true when content is an array containing BOTH a reasoning/thinking block
// AND a text/output_text block — the shape used for final canonical messages.
}

function accumulateContent(existing, incoming) {
// ...existing prefix-detection logic kept...
if (isFinalCanonicalReasoningContent(incoming)) return incomingText; // NEW
return existingText + incomingText; // legitimate delta-append path preserved
}
```

The legitimate sequential-string-delta path (existing='hello', incoming='world', neither a prefix) is preserved — only the final-canonical-array shape is intercepted.

Verification

  • Captured a real chunk sequence from the running examples/chat backend (gpt-5 + reasoning.effort=high, the puzzle prompt) into a JSON fixture (1213 events, 841 canonical text chars). Replay through mergeMessages produces visible text length matching server canonical (within 20 chars).
  • Reference research (CopilotKit + Hashbrown source code) showed both alternative consumer libraries use delta-only wire protocols and never need this dedupe; the @Ngaf adapter is the only one trying to recover delta-like behavior from a hybrid snapshot+chunk source. Stays with the current model and hardens the fallback.
  • 9 new helper unit tests pin both the legitimate delta-append path and the bug-fix replace path.

Test plan

Verified locally

  • `nx run langgraph:lint` — green
  • `nx run langgraph:test` — 53 tests pass (44 pre-existing + 9 new)

Pending visual verification

  • After merge: re-smoke against the canonical demo with model=gpt-5, effort=high. Puzzle prompt produces a single non-duplicated visible answer.

Spec: `docs/superpowers/specs/2026-05-08-langgraph-streaming-content-dedup-design.md`
Plan: `docs/superpowers/plans/2026-05-08-langgraph-streaming-content-dedup.md`

Note

The captured fixture didn't reproduce the original 1.83× duplication (chunks happened to accumulate byte-for-byte to canonical), so the fix is best-understood as defense-in-depth: the synthetic Phase 4 unit test directly exercises the bug path (existing='partial answer' + incoming reasoning+text array → returns canonical, not appended). Once merged, the next live smoke pass against the demo will confirm whether the original visible duplication is gone or whether a different code path needs investigation.

blove and others added 8 commits May 8, 2026 17:02
Targets Finding C from the live smoke pass: visible answer renders
~1.83x expected length in chat-streaming-md when the AI message
content array has both a reasoning block (with summary items) and a
text block.

Root cause: accumulateContent's fallback blindly appends when neither
side is a strict prefix. Streaming accumulator (~690 chars) plus final
canonical text (830 chars) differ by trailing whitespace or
normalization, so the prefix check fails and the bug fires.

Reference research grounded the approach. Stays with the current
model; hardens the fallback with a captured-fixture replay test plus
9 helper unit tests pinning both the genuine delta-append path and
the bug-fix path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Six-phase plan: branch, capture script (Node ESM using LangGraph SDK
directly to dump chunks as JSON fixture), failing replay test, targeted
fix in accumulateContent (narrow heuristic for final-canonical
reasoning+text array shape), 9 helper unit tests pinning both legitimate
delta-append and bug-fix replace paths, verification and PR.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented May 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cacheplane Ready Ready Preview, Comment May 9, 2026 0:13am

Request Review

@blove blove merged commit a97721f into main May 9, 2026
14 checks passed
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