Skip to content

Python: fix(ag-ui): keep repeated single-chunk text deltas - #9339

Merged
Eduard van Valkenburg (eavanvalkenburg) merged 5 commits into
microsoft:mainfrom
ManoharPaturi:fix/agui-repeated-text-delta
Oct 16, 2026
Merged

Eduard van Valkenburg (eavanvalkenburg) merged 5 commits into
microsoft:mainfrom
ManoharPaturi:fix/agui-repeated-text-delta

Conversation

@ManoharPaturi

@ManoharPaturi Manohar Paturi (ManoharPaturi) commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Motivation & Context

While streaming through the ag-ui endpoint, a text delta is silently dropped when its text equals the whole accumulated text of the open message. Any answer that starts with a doubled digit or a repeated short token hits this. The model streams "2" then "2" for 22 and the client only ever sees 2. Issue #9336 has the repro.

The replay guard from #4194 could not tell a genuine repeated token from a provider replaying the full text, because after a single streamed chunk the two look identical on the wire.

Description & Review Guide

  • What are the major changes? FlowState now counts the text deltas emitted for the open message, reset wherever accumulated_text is reset. The duplicate check in _emit_text only suppresses the text when the duplicate carries cumulative annotations or the text already streamed in more than one chunk. A plain duplicate after a single chunk now streams as a real delta.
  • What is the impact of these changes? Multi-chunk replays and annotation replays are still suppressed exactly like before, so Python: [Bug]: MESSAGES_SNAPSHOT contains doubled tool_call arguments when streaming with client-side tools #4194 behavior is kept. The one case that changes is a plain duplicate after a single chunk: it used to be dropped, now it is appended. That case is ambiguous on the wire and losing real text there is worse than the alternative, but reviewers should confirm they are ok with that trade.
  • What do you want reviewers to focus on? The guard condition in _emit_text and the reset sites for the new counter.

Related Issue

Fixes #9336

AI Assistance

  • This is an AI-assisted contribution. I reviewed, understood, and verified all submitted content and accept responsibility for it.

Contribution Checklist

  • The code builds clean without any errors or warnings
  • All unit tests pass, and I have added new tests where possible
  • The PR follows the Contribution Guidelines
  • This PR links to an agreed issue with no competing open PR, or the Related Issue section documents a trivial-change or repository-automation exception.
  • This is not a breaking change. If it is a breaking change, add the breaking change label (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.

A duplicate delta equal to the whole accumulated text was always treated
as a full-message replay and dropped. When the message only streamed one
chunk so far, that same shape is a genuine repeated token (like the 2s
in 22) and the text was silently lost.

Only treat the duplicate as a replay when it carries cumulative
annotations or the text already streamed in more than one chunk, and
track the emitted delta count on FlowState.

Fixes microsoft#9336

Signed-off-by: Manohar Paturi <186662190+ManoharPaturi@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@agent-framework-automation agent-framework-automation Bot added the python Usage: [Issues, PRs], Target: Python label Oct 13, 2026
@eavanvalkenburg

Copy link
Copy Markdown
Member

/review

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MAF Automated Review — Iteration 1

Result: Findings reported
Scope: full PR (2 commit(s)): fa37b1f7c2fe, 15b174520e51
Model: gpt-5.6-sol

Overview

The review found 1 verified inline finding(s).

Reviewed the supplied pull-request change set across correctness, security/reliability, architecture, and failure behavior.
1 verified finding remained after source verification (1 medium) across 1 file. Details are attached to the affected lines below.

Affected areas: python/packages/ag-ui/agent_framework_ag_ui/_run_common.py

Comment thread python/packages/ag-ui/agent_framework_ag_ui/_run_common.py
@eavanvalkenburg

Copy link
Copy Markdown
Member

/review

…duplicate

The replay guard still treated any annotated duplicate equal to the
accumulated text as a cumulative replay, dropping a genuine repeated
token that happened to carry annotations. A replay re-sends the
annotations already recorded on the message segment, so the guard now
suppresses only when the duplicate's annotation set covers the recorded
occurrences. A repeated delta with new annotations streams normally.

Follow-up to the review on microsoft#9339.

Signed-off-by: Manohar Paturi <186662190+ManoharPaturi@users.noreply.github.com>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Manohar Paturi (@ManoharPaturi) The current replay heuristic still drops a genuine repeated annotated token when the second delta carries the same annotation payload as the first. For example, streaming "ha" with citation A and then another real "ha" with the same citation A should produce "haha". At python/packages/ag-ui/agent_framework_ag_ui/_run_common.py:678, _annotations_replay_recorded_occurrences returns true because the recorded annotation appears in the incoming set, so the second token is classified as a cumulative replay and never appended.

The added regression covers a repeated token with a different annotation URL, but not this same-annotation case. Please keep the one-prior-delta ambiguity policy consistent with the unannotated path, or defer the replay decision explicitly, and add a regression for two genuine identical annotated deltas.

…ted ones

A duplicate equal to the whole accumulated text was still suppressed
after a single prior chunk whenever its annotations re-sent the recorded
occurrences, which dropped a genuine repeated token carrying the same
citation. Suppression now needs more than one prior chunk for annotated
duplicates too, matching the unannotated policy from the issue. The
genuine path emits the delta with its annotations, spans rebased to the
new chunk position, and the existing occurrence reconciliation still
applies on the suppression path.

Follow-up to the review on microsoft#9339.

Signed-off-by: Manohar Paturi <186662190+ManoharPaturi@users.noreply.github.com>
@ManoharPaturi

Copy link
Copy Markdown
Contributor Author

c33d7e4 takes the consistency option: a duplicate equal to the whole accumulated text is now suppressed only when the text already streamed in more than one chunk, annotated or not, so the exact case you described (a second real token carrying the same citation) streams and produces haha. The genuine path emits the delta with its annotations and the spans rebased to the new chunk position, while the multi-chunk suppression path keeps the occurrence reconciliation for re-sent citations. The two existing one-chunk suppression tests moved to the new expectations, the multi-chunk replay coverage is unchanged, and the identical-annotated-delta regression is included (fails before this change). Full ag-ui suite 1579 passed.

Merged via the queue into microsoft:main with commit 29ac364 Oct 16, 2026
48 checks passed

This branch was successfully deployed

1 active deployment
github-app-auth — c33d7e4d Deployed Oct 15, 2026 by ManoharPaturi via add_label #25437
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

python Usage: [Issues, PRs], Target: Python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Python: [Bug]: ag-ui endpoint drops a text delta that equals the whole text streamed so far

3 participants