Skip to content

Python: Fix duplicate arguments in declaration-only tool streaming#7110

Open
kartikmadan11 wants to merge 1 commit into
microsoft:mainfrom
kartikmadan11:fix/6973-declaration-only-streaming-duplicate-args
Open

Python: Fix duplicate arguments in declaration-only tool streaming#7110
kartikmadan11 wants to merge 1 commit into
microsoft:mainfrom
kartikmadan11:fix/6973-declaration-only-streaming-duplicate-args

Conversation

@kartikmadan11

Copy link
Copy Markdown
Contributor

Motivation & Context

When streaming declaration-only function calls, the final response contains duplicated arguments.

Fixes #6973

Description & Review Guide

What are the major changes?

  • Modified _process_function_requests() in _tools.py (line 2403) to filter out function_call type items from the streamed result update
  • Added two test cases covering chunked and single-chunk streaming scenarios

What is the impact?

  • Eliminates argument duplication for declaration-only streaming tool calls
  • Preserves correct behavior for function_result and approval items

Root Cause: The _stream() loop yields function_call chunks as they arrive, then later re-yields those same items from function_call_results. The _process_update() callback concatenates arguments with +=, so duplicate yields cause duplication.

Solution: Filter out function_call type items from the result's function_call_results before yielding, since they were already emitted by the inner stream.

Tests Added

  • test_declaration_only_tool_streaming_no_argument_duplication() - Verifies multi-chunk streaming
  • test_declaration_only_tool_streaming_single_chunk_no_duplication() - Verifies single-chunk streaming

Both tests pass ✅

Copilot AI review requested due to automatic review settings July 14, 2026 13:04
@giles17 giles17 added the python Usage: [Issues, PRs], Target: Python label Jul 14, 2026

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.

Pull request overview

Fixes a Python streaming bug in the core function-invocation layer where declaration-only tool calls could end up with duplicated arguments in the final aggregated response due to the same function_call content being yielded twice during streaming.

Changes:

  • Filters function_call items out of the post-processing streamed function_call_results update to prevent _process_update from concatenating the same arguments twice.
  • Adds regression tests for both multi-chunk and single-chunk streaming declaration-only tool calls.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
python/packages/core/agent_framework/_tools.py Adjusts streaming post-processing to avoid re-emitting function_call items that were already emitted by the inner stream.
python/packages/core/tests/core/test_function_invocation_logic.py Adds tests ensuring declaration-only tool streaming does not duplicate function-call arguments.

Comment thread python/packages/core/agent_framework/_tools.py
@kartikmadan11 kartikmadan11 force-pushed the fix/6973-declaration-only-streaming-duplicate-args branch from 026ca32 to 7def61f Compare July 14, 2026 14:37
@kartikmadan11

Copy link
Copy Markdown
Contributor Author

Great catch, Copilot! I've addressed the concern about empty updates.

The fix:
When the filtered function_call_results list is empty (after excluding already-streamed function_call items), we now clear update_role to suppress the update. This prevents emitting confusing empty ChatResponseUpdate events.

Implementation:

  • Filter out function_call items: filtered_results = [r for r in function_call_results if r.type != "function_call"]
  • If the filtered list is empty, set result["update_role"] = None
  • The existing yield check if role := result.get("update_role") will skip the empty update

This way declaration-only tools in streaming mode won't emit spurious empty updates—only the already-streamed chunks and any results/approvals will be emitted.

…lls (microsoft#6973)

When streaming a declaration-only function call, the _stream() loop yields
function_call chunks from the inner stream (line ~2800), then later re-yields
the same function_call items from function_call_results (line ~2847).
The _process_update callback concatenates streaming arguments via +=, so
duplicate yields cause the arguments to be concatenated twice:
{"location":"Seattle"}{"location":"Seattle"}

Filter out function_call type items from the streamed update, since they
were already emitted by the inner stream. Only yield function_result and
approval items, which are newly generated by _process_function_requests.

Tests: both split-chunk and single-chunk streaming of declaration-only tools.

Fixes microsoft#6973
@kartikmadan11 kartikmadan11 force-pushed the fix/6973-declaration-only-streaming-duplicate-args branch from 7def61f to c06f65d Compare July 14, 2026 17:22
# Exclude function_call items from the streamed update: the inner stream already yielded
# them as they arrived, so re-yielding them would cause argument strings to be concatenated
# twice in _process_update (which uses +=) for declaration-only tools.
filtered_results = [r for r in function_call_results if r.type != "function_call"]

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.

Could we preserve the finalized declaration-only call’s control metadata here? The earlier chunks contain the arguments, but user_input_request=True and id are added later to function_call_results. Filtering the finalized call entirely means streaming workflows never receive that signal, so AgentExecutor cannot emit request_info or pause for the client-side tool. Could the streaming path suppress only the duplicated arguments while retaining the finalized metadata?

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]: Duplicate arguments in declaration-only function call when streamed

4 participants