Skip to content

Python: fix: preserve tool call/result order in AG-UI message split - #8005

Open
Manjunath Janardhan (manjunathshiva) wants to merge 3 commits into
microsoft:mainfrom
manjunathshiva:python-agui-tool-message-order-7980
Open

Python: fix: preserve tool call/result order in AG-UI message split#8005
Manjunath Janardhan (manjunathshiva) wants to merge 3 commits into
microsoft:mainfrom
manjunathshiva:python-agui-tool-message-order-7980

Conversation

@manjunathshiva

Copy link
Copy Markdown
Contributor

Motivation & Context

Follow-up to #7980, addressing post-merge review feedback from Evan Mattson (@moonbox3): #7980 (comment)

#7980 taught agent_framework_messages_to_agui to split a message that carries multiple function_result contents into one AG-UI tool message each. But it emitted all tool results first and the assistant function_call/text segment afterwards. So a framework message with contents [function_call, function_result] round-tripped as [function_result, function_call] — the transcript was reversed. Providers require a tool result to follow its matching assistant call, so the reversed order produces an orphan result (rejected or dropped by the provider) while the call stays pending.

Description & Review Guide

  • What are the major changes?

    • New _split_mixed_message_to_agui() helper walks msg.contents in order and flushes any accumulated assistant segment (text + tool_calls) before each function_result, then emits the result as its own tool message. Calls therefore always precede their results.
    • agent_framework_messages_to_agui now dispatches to this helper whenever a message carries any function_result; messages without a result keep the simple single-message path.
    • Behavior preserved from Python: fix: preserve parallel function_result contents in AG-UI conversion #7980: parallel results are not collapsed, trailing summary text after results is kept, the source message id stays on the first emitted message, and every additional message gets an independent generated id (no derived-suffix collisions).
  • What is the impact of these changes?

    • [function_call, function_result][assistant(tool_calls), tool(result)] (was reversed).
    • [function_result, function_result] (parallel) and [function_result, text] (result + trailing text) round-trip unchanged.
    • No public API change.
  • What do you want reviewers to focus on?

    • The ordering guarantee in _split_mixed_message_to_agui and the id-assignment (first-emitted keeps the source id).

Related Issue

Follow-up to #7980 (no separate issue was filed; this fixes a call/result ordering regression identified by Evan Mattson (@moonbox3) during review of the merged PR — linked above).

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 is linked to an issue and there is no other open PR for this issue (see Related Issue above).
  • 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.

When a single Agent Framework message interleaved assistant content
(function_call / text) with function_result content, the AG-UI converter
emitted every tool result first and the assistant call/text segment
afterwards. A [function_call, function_result] message therefore
round-tripped as [function_result, function_call], reversing the
transcript. Providers require a tool result to follow its matching
assistant call, so the reversed order produced an orphan result (rejected
or dropped) while the call stayed pending.

Walk msg.contents in order and flush any accumulated assistant segment
before each function_result, so calls always precede their results.
Parallel results and trailing summary text are preserved, and the source
message id is still kept on the first emitted message with independent
generated ids for the rest.

Follow-up to microsoft#7980 (addresses moonbox3's post-merge review feedback).

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.

🔵 Needs a closer look

Provider-facing call/result serialization is explicitly classified as high risk and warrants final human review.

Pull request overview

Fixes AG-UI serialization so tool calls remain before their corresponding results.

Changes:

  • Splits mixed messages while preserving content order and unique IDs.
  • Adds regression tests for call/result/text ordering.
File summaries
File Description
_message_adapters.py Implements ordered mixed-message splitting.
test_message_adapters.py Tests ordering and ID preservation.
Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 0
  • Review effort level: Balanced

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

@moonbox3

Copy link
Copy Markdown
Contributor

/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)): 80d2a5e20df7, 598d43324088
Model: gpt-5.6-sol-fast

Overview

The new helper correctly preserves call-before-result ordering for the newly tested mixed-message shapes, keeps parallel results distinct, and assigns independent IDs to split messages. However, flushing every accumulated segment before a result regresses the supported text-before-result shape: it can place an assistant message between a pending call and its terminal result, after which production history normalization drops the real result.

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

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

Comment thread python/packages/ag-ui/agent_framework_ag_ui/_message_adapters.py Outdated
The mixed-message split flushed every accumulated segment before each
function_result, including text-only segments. A [text, function_result]
message whose result answers a call in a prior assistant message therefore
emitted an assistant-only text message between the outstanding call and its
result. _sanitize_tool_history then treats the call as abandoned, clears it,
and drops the real result, leaving the provider with an unanswered tool call.

Defer text-only content and emit it after the results; only a segment that
carries tool calls is flushed before a result (with any buffered text
coalesced), so a call still precedes its matching result and no assistant-only
message is ever inserted between an outstanding call and its result.

Addresses the MAF automated-review finding on this PR.
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.

3 participants