Skip to content

Python: fix: preserve parallel function_result contents in AG-UI conversion - #7980

Merged
Eduard van Valkenburg (eavanvalkenburg) merged 4 commits into
microsoft:mainfrom
manjunathshiva:python-agui-parallel-function-results-7979
Sep 1, 2026
Merged

Python: fix: preserve parallel function_result contents in AG-UI conversion#7980
Eduard van Valkenburg (eavanvalkenburg) merged 4 commits into
microsoft:mainfrom
manjunathshiva:python-agui-parallel-function-results-7979

Conversation

@manjunathshiva

Copy link
Copy Markdown
Contributor

Motivation & Context

When a single Agent Framework Message with role="tool" carries multiple function_result contents — which is common when a model issues parallel tool calls in one turn — agent_framework_messages_to_agui emitted only one AG-UI tool message (the last function_result) and silently dropped the rest.

Because the upstream assistant message still lists every tool_call, clients (e.g. assistant-ui's fromAgUiMessages) treat the missing results as unresolved tool calls and render Allow / Deny approval prompts on historical chats. No exception is raised — it is silent data loss during conversion.

Description & Review Guide

  • What are the major changes?
    In agent_framework_messages_to_agui (_message_adapters.py), the per-message loop used scalar content_text / tool_result_call_id variables that each function_result overwrote, so only the last survived. The loop now collects all function_result contents and emits one AG-UI tool message per result, each with its own toolCallId and content.
    To keep the split messages uniquely addressable (so downstream clients keyed by message id don't re-collapse them), the first result keeps the source message_id and subsequent results get "{message_id}-{idx}".

  • What is the impact of these changes?
    Parallel tool results now round-trip through AG-UI intact; historical conversations no longer show phantom Allow/Deny prompts. The single-result path is unchanged (same id, same output), so existing behavior is preserved. No public API changes.

  • What do you want reviewers to focus on?
    The id scheme for split messages (message_id for the first, "{message_id}-{idx}" for the rest) — whether that suffixing convention is preferable to generating fresh event ids for all but the first.

Related Issue

Fixes #7979

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.

…ersion

agent_framework_messages_to_agui collapsed a tool Message carrying
multiple function_result contents (parallel tool calls) into a single
AG-UI tool message, keeping only the last call_id/result. Clients then
saw the missing results as pending tool approvals.

Emit one AG-UI tool message per function_result, each with its own
toolCallId and content, with distinct ids so downstream clients keyed
by id do not re-collapse them.

Fixes microsoft#7979

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 silent loss of parallel tool results during Agent Framework-to-AG-UI conversion.

Changes:

  • Emits one AG-UI tool message per function result.
  • Adds distinct IDs and parallel-result regression tests.

Reviewed changes

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

File Description
_message_adapters.py Splits batched function results into tool messages.
test_message_adapters.py Tests parallel and None results.
Suppressed comments (1)

python/packages/ag-ui/agent_framework_ag_ui/_message_adapters.py:1031

  • The unconditional continue discards any text or function-call contents collected from the same Message. Mixed finalized messages are a supported shape (for example, tests/ag_ui/test_workflow_run.py:830-833 contains a function result followed by assistant text), so converting such history now silently loses that assistant output. Emit the split tool results, then also emit a separately identified normal AG-UI message when content_text or tool_calls is present.
            continue

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

Comment thread python/packages/ag-ui/agent_framework_ag_ui/_message_adapters.py Outdated
A single Agent Framework message can carry function_result contents
alongside text or function_call contents (e.g. a finalized assistant
turn). The parallel-result fix emitted the tool messages then returned
early, dropping that leftover content.

Emit a separate, distinctly-identified AG-UI message for any remaining
text/tool_calls after the split tool results, so mixed messages are
preserved instead of silently losing the assistant output.
Deriving suffixes from the source id (f"{base_id}-{idx}") could collide
with a legitimate message id elsewhere in the converted history, letting
id-keyed clients re-collapse a result. Keep the source id for the first
result and generate an independent id for each additional message.
Merged via the queue into microsoft:main with commit 8ea6f83 Sep 1, 2026
37 checks passed
# message an independent generated id. Deriving suffixes from the source
# id (e.g. f"{base_id}-1") risks colliding with a legitimate id elsewhere
# in the history, which would let id-keyed clients re-collapse results.
for idx, fr in enumerate(function_results):

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.

I think we need a follow up here...

Could we preserve the original call/result order when splitting mixed messages? This loop emits every function_result before the separate message containing tool_calls, so a framework-produced [function_call, function_result] message round-trips as [function_result, function_call]. Providers require a tool result to follow its matching assistant call, so AGUIChatClient can send an orphan result that is rejected or dropped while the later call remains pending. Walking msg.contents and flushing assistant call/text segments before each result would preserve parallel results without reversing the transcript.

@manjunathshiva Manjunath Janardhan (manjunathshiva) Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

You're right, thanks for the careful follow-up. Emitting the results before the tool_calls segment reverses a [function_call, function_result] message into [function_result, function_call], which orphans the result against its call. Walking msg.contents in order and flushing any accumulated assistant call/text segment before each function_result preserves the original call→result ordering, keeps parallel results intact, and keeps trailing assistant text after the results. I'll open a follow-up PR with that change plus a regression test for the [function_call, function_result] round-trip, and link it here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Opened the follow-up: #8005. It walks msg.contents in order and flushes the assistant call/text segment before each function_result, so [function_call, function_result] now emits as [assistant(call), tool(result)] instead of reversing. Added regression tests for the ordering plus the parallel-result and trailing-text cases from #7980.

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]: agent_framework_messages_to_agui drops parallel function_result contents

4 participants