Skip to content

Comments

Python: Fix doubled tool_call arguments in MESSAGES_SNAPSHOT when streaming#4200

Merged
eavanvalkenburg merged 2 commits intomicrosoft:mainfrom
LEDazzio01:fix/4194-doubled-tool-call-arguments
Feb 24, 2026
Merged

Python: Fix doubled tool_call arguments in MESSAGES_SNAPSHOT when streaming#4200
eavanvalkenburg merged 2 commits intomicrosoft:mainfrom
LEDazzio01:fix/4194-doubled-tool-call-arguments

Conversation

@LEDazzio01
Copy link
Contributor

Motivation and Context

Fixes #4194MESSAGES_SNAPSHOT events contain doubled function.arguments strings for tool calls when streaming with client-side tools.

When a streaming provider sends a full-arguments replay after incremental deltas complete, _emit_tool_call() unconditionally appends the replay to the accumulated arguments via +=, causing the arguments string to double (e.g., {"todoText":"buy groceries"}{"todoText":"buy groceries"}). This breaks any client or middleware that relies on MESSAGES_SNAPSHOT for state reconstruction, since the doubled string is not valid JSON.

Description

Root cause: In _run_common.py, the _emit_tool_call function tracks accumulated arguments in flow.tool_calls_by_id[tool_call_id]["function"]["arguments"] using +=. Unlike _emit_text() which already has a guard against full-message replays, _emit_tool_call had no equivalent protection.

Fix: Add a duplicate detection guard (mirroring the existing pattern in _emit_text) that checks whether the incoming delta exactly equals the already-accumulated arguments string. When matched, this indicates a full-arguments replay rather than an incremental delta, and the append is skipped:

         if tool_call_id in flow.tool_calls_by_id:
-            flow.tool_calls_by_id[tool_call_id]["function"]["arguments"] += delta
+            accumulated = flow.tool_calls_by_id[tool_call_id]["function"]["arguments"]
+            # Guard against full-argument replay: if the accumulated arguments
+            # already equal the incoming delta, this is a non-delta replay of
+            # the complete arguments string. Skip the append to prevent
+            # doubling in MESSAGES_SNAPSHOT.  (Fixes #4194)
+            if accumulated and delta == accumulated:
+                logger.debug(
+                    "Skipping duplicate full-arguments replay for tool_call_id=%s",
+                    tool_call_id,
+                )
+            else:
+                flow.tool_calls_by_id[tool_call_id]["function"]["arguments"] += delta

Key properties:

  • ToolCallArgsEvent deltas are still emitted correctly for real-time streaming — only the internal snapshot accumulator is guarded
  • The guard only triggers on exact equality (accumulated == delta), so normal incremental streaming is unaffected
  • Follows the same pattern already established in _emit_text() for text content replay detection

Contribution Checklist

  • The code builds clean without any errors or warnings
  • The PR follows the Contribution Guidelines
  • All unit tests pass, and I have added new tests where possible
  • Is this a breaking change? No — this is a bug fix that prevents invalid doubled arguments in snapshots.

Copilot AI review requested due to automatic review settings February 23, 2026 23:08
@LEDazzio01
Copy link
Contributor Author

I have sole ownership of intellectual property rights to my Submissions and I am not making Submissions in the course of work for my employer.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes a bug where MESSAGES_SNAPSHOT events contained doubled function.arguments strings when streaming tool calls. The issue occurred when streaming providers send a full-arguments replay after incremental deltas, causing the arguments to be accumulated twice. The fix adds duplicate detection logic (similar to the existing text content handling) to skip re-accumulating arguments when the incoming delta equals the already-accumulated value.

Changes:

  • Added duplicate detection guard in _emit_tool_call() to prevent doubling tool call arguments in MESSAGES_SNAPSHOT events
  • Follows the existing pattern from _emit_text() for handling provider full-content replays
  • Includes detailed inline comments explaining the fix and referencing issue #4194

@LEDazzio01
Copy link
Contributor Author

@microsoft-github-policy-service agree

@markwallace-microsoft
Copy link
Member

markwallace-microsoft commented Feb 24, 2026

Python Test Coverage

Python Test Coverage Report •
FileStmtsMissCoverMissing
packages/ag-ui/agent_framework_ag_ui
   _run_common.py1992985%46, 51–52, 54, 61, 65, 69–70, 72, 96, 173, 221–222, 236, 263–265, 290–291, 297–302, 389, 391, 394–395
TOTAL21565309385% 

Python Unit Test Overview

Tests Skipped Failures Errors Time
4322 246 💤 0 ❌ 0 🔥 1m 15s ⏱️

Copy link
Contributor

@moonbox3 moonbox3 left a comment

Choose a reason for hiding this comment

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

LGTM. Thanks for fixing, @LEDazzio01.

When streaming with client-side tools, some providers send a full-
arguments replay after the streaming deltas complete. The `_emit_tool_call`
function unconditionally appends every arguments delta to the internal
`flow.tool_calls_by_id` tracking dictionary via `+=`. When the replay
contains the exact same complete arguments string that was already
accumulated from prior deltas, the arguments get doubled (e.g.,
`{"todoText":"buy groceries"}{"todoText":"buy groceries"}`).

This causes `MESSAGES_SNAPSHOT` events to contain invalid doubled JSON in
`tool_calls[].function.arguments`, breaking any client or middleware that
relies on snapshots for state reconstruction.

The fix adds a guard (mirroring the existing duplicate guard in
`_emit_text`) that detects when the incoming delta exactly equals the
already-accumulated arguments string, indicating a full-arguments replay
rather than an incremental delta. In this case the append is skipped,
preventing the doubling.

The `ToolCallArgsEvent` deltas are still emitted correctly for real-time
streaming — only the internal snapshot accumulator is guarded.

Fixes microsoft#4194
Address Copilot review feedback:
1. Move duplicate full-arguments replay detection BEFORE emitting
   ToolCallArgsEvent, for consistency with _emit_text() which returns
   early without emitting any events on replay detection.
2. Add test_emit_tool_call_skips_duplicate_full_arguments_replay() to
   verify the duplicate detection behavior for tool call arguments,
   matching the existing test pattern for text content.
@eavanvalkenburg eavanvalkenburg force-pushed the fix/4194-doubled-tool-call-arguments branch from 3b31dee to 6f2f9fa Compare February 24, 2026 09:43
@eavanvalkenburg eavanvalkenburg added this pull request to the merge queue Feb 24, 2026
Merged via the queue into microsoft:main with commit f78fa27 Feb 24, 2026
29 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Python: [Bug]: MESSAGES_SNAPSHOT contains doubled tool_call arguments when streaming with client-side tools

4 participants