Skip to content

.NET: fix parallel tool call rendering in AGUI translation layer - #6009

Merged
Roger Barreto (rogerbarreto) merged 1 commit into
microsoft:mainfrom
rogerbarreto:issues/ag-ui-bug-check
May 25, 2026
Merged

.NET: fix parallel tool call rendering in AGUI translation layer#6009
Roger Barreto (rogerbarreto) merged 1 commit into
microsoft:mainfrom
rogerbarreto:issues/ag-ui-bug-check

Conversation

@rogerbarreto

@rogerbarretoRoger Barreto (rogerbarreto) commented May 21, 2026

Copy link
Copy Markdown
Member

Summary

Fix three interlocked bugs in the AGUI translation layer that prevent parallel tool calls from rendering correctly in AG-UI protocol clients.

Bugs fixed

#BugImpact
1Synthetic streamingMessageId leaks into ToolCallStartEvent.ParentMessageIdAll parallel tool calls collapse into one assistant-message card in the FE
2ToolCallResultEvent.MessageId shared across all results in a round tripOnly the first tool result renders; subsequent results overwrite it
3AsChatMessages doesn't coalesce consecutive assistant-tool-call messagesMulti-turn replay produces consecutive assistant(tool_calls) without intervening tool messages - Azure OpenAI HTTP 400

Why PR #5800 didn't resolve this

PR #5800's ContainsToolResult branch is gated by IsNullOrWhiteSpace(chatResponse.MessageId). In the production path, MEAI's FunctionInvokingChatClient prefills MessageId (non-empty), so the branch is skipped entirely. Bug #1 (tool-call chunks carry FunctionCallContent, not FunctionResultContent) was never addressed by that fix.

Changes

ChatResponseUpdateAGUIExtensions.cs:

  • FixThis repo is missing a LICENSE file #1: Replace streamingMessageId with textStreamingFallback scoped to text events only. Tool-call chunks pass through the raw provider MessageId (empty when omitted by OpenAI), so each parallel tool call gets a distinct identity in the FE.
  • FixAdding Microsoft SECURITY.MD #2: Set ToolCallResultEvent.MessageId to result-{CallId} - deterministic, unique per result regardless of MEAI batching.
  • Remove dead ContainsToolResult helper (superseded by fixAdding Microsoft SECURITY.MD #2).

AGUIChatMessageExtensions.cs:

  • FixThis repo is missing important files #3: Buffer consecutive AGUIAssistantMessages with ToolCalls and flush as a single ChatMessage with multiple FunctionCallContent entries, restoring the assistant(tool_calls) -> tool, tool, ... alternation Azure OpenAI requires.

Reproduction

A standalone console repro app was built to independently validate all three bugs on current main before fixing. After fixing, the repro confirms all bugs resolved.

Tests

  • 2 new streaming tests (ParallelToolCalls, ToolCallResults with shared MEAI MessageId)
  • 3 new coalescing tests (consecutive, single, end-of-stream scenarios)
  • Updated 2 PR .NET: fix: avoid AGUI tool result message id collisions #5800 test assertions for new contract (tool-call ParentMessageId reflects raw provider value)
  • All 215 AGUI unit tests pass
  • All 35 AspNetCore AGUI unit tests pass

Closes#6015

Fix three interlocked bugs that prevent parallel tool calls from rendering
correctly in AG-UI protocol clients:
Bug #1: Scope synthetic MessageId fallback to text events only. The shared
streamingMessageId was leaking into ToolCallStartEvent.ParentMessageId,
causing all parallel tool calls to collapse into one FE card.
Bug #2: Make ToolCallResultEvent.MessageId deterministically unique using
result-{CallId} format. MEAI's FunctionInvokingChatClient batches all
results with a shared MessageId, collapsing them in FE reconciliation.
Bug #3: Coalesce consecutive assistant-tool-call messages in AsChatMessages.
Once Bug #1 is fixed, the FE produces separate AGUIAssistantMessage per
tool call. On multi-turn replay these become consecutive assistant messages
without intervening tool results, triggering HTTP 400 from Azure OpenAI.
Remove the now-dead ContainsToolResult helper introduced by PR microsoft#5800.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

CopilotAI 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

This PR fixes three related issues in the .NET AGUI translation layer that prevented parallel tool calls and their results from rendering correctly in AG-UI protocol clients, and that could also produce invalid chat history for Azure OpenAI when replaying tool-call sequences.

Changes:

  • Scope text streaming fallback MessageId generation to text events only, so tool-call events use the raw provider MessageId and don’t collapse parallel tool calls under one parent.
  • Make ToolCallResultEvent.MessageId unique and deterministic per tool call (result-{callId}) to prevent FE overwrites when MEAI batches multiple tool results under one shared MessageId.
  • Coalesce consecutive assistant tool_calls messages into a single assistant message with multiple FunctionCallContent entries to preserve the required assistant(tool_calls) → tool → tool alternation for Azure OpenAI.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

FileDescription
dotnet/src/Microsoft.Agents.AI.AGUI/Shared/ChatResponseUpdateAGUIExtensions.csFixes message-id fallback behavior for text vs tool-call surfaces; assigns deterministic per-result ids; removes dead helper.
dotnet/src/Microsoft.Agents.AI.AGUI/Shared/AGUIChatMessageExtensions.csCoalesces consecutive assistant tool-call messages into one OpenAI-compatible ChatMessage.
dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/AGUIStreamingMessageIdTests.csUpdates prior assertions and adds new streaming regressions for parallel tool calls and batched tool results.
dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/AGUIChatMessageExtensionsTests.csAdds tests covering coalescing behavior (consecutive, single-message, end-of-stream).

@github-merge-queue
github-merge-queueBot removed this pull request from the merge queue due to failed status checks May 22, 2026
Merged via the queue into microsoft:main with commit de6d026May 25, 2026
42 of 45 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

.NETUsage: [Issues, PRs], Target: .Net

Projects

None yet

5 participants

@rogerbarreto@SergeyMenshykh@westey-m@moonbox3