.NET: DRAFT feat: Implement message filtering to exclude non-portable content typ…#5410
.NET: DRAFT feat: Implement message filtering to exclude non-portable content typ…#5410tarockey wants to merge 4 commits intomicrosoft:mainfrom
Conversation
…es before forwarding Co-authored-by: Copilot <copilot@github.com>
… executors Co-authored-by: Copilot <copilot@github.com>
There was a problem hiding this comment.
Pull request overview
This PR addresses a .NET Workflows interoperability bug where provider-specific, output-only Responses API items (e.g., reasoning/tool call artifacts) were being forwarded between agents and then round-tripped as invalid input, causing invalid_payload errors when chaining Foundry-backed agents.
Changes:
- Added a message sanitization step in
AIAgentHostExecutorto forward only “portable”AIContenttypes and to stripChatMessage.RawRepresentation. - Dropped messages that become empty after content filtering (at the message level).
- Added unit tests covering filtering of non-portable content and stripping of
RawRepresentation.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/AIAgentHostExecutor.cs | Filters forwarded response messages to remove output-only artifacts and strips message-level RawRepresentation before forwarding. |
| dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/AIAgentHostExecutorTests.cs | Adds tests validating content filtering behavior and RawRepresentation stripping for forwarded messages. |
| AuthorName = message.AuthorName, | ||
| MessageId = message.MessageId, | ||
| CreatedAt = message.CreatedAt, | ||
| AdditionalProperties = message.AdditionalProperties, |
There was a problem hiding this comment.
AdditionalProperties = message.AdditionalProperties copies the dictionary by reference into the forwarded ChatMessage. Since AdditionalPropertiesDictionary is mutable and is cloned elsewhere when merging (e.g., MessageMerger uses new(current)), this can unintentionally couple mutations between the original response message and the forwarded message. Consider cloning the dictionary (or omitting provider-specific entries) when constructing the sanitized message.
| AdditionalProperties = message.AdditionalProperties, | |
| AdditionalProperties = message.AdditionalProperties is null ? null : new(message.AdditionalProperties), |
| // Assert: no ChatMessage lists should be forwarded (only TurnToken) | ||
| List<ChatMessage> forwardedMessages = testContext.QueuedMessages[executor.Id] | ||
| .Select(e => e.Message) | ||
| .OfType<List<ChatMessage>>() | ||
| .SelectMany(list => list) | ||
| .ToList(); |
There was a problem hiding this comment.
This test’s assertion comment says “no ChatMessage lists should be forwarded (only TurnToken)”, but AIAgentHostExecutor forwards incoming messages by default (AIAgentHostOptions.ForwardIncomingMessages = true), so a List<ChatMessage> (often empty) can still be sent before the response forwarding. To make the test accurately validate “no forwarded chat messages”, consider either setting ForwardIncomingMessages = false for this test or asserting on the envelopes (e.g., that no non-empty List<ChatMessage> was sent).
…stExecutor.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…stExecutor.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This is a draft PR, happy to close it or modify based on discussion.
Motivation and Context
Closes #5338
When a Foundry server-side agent with server-side tools (MCP, web search, code interpreter, Fabric Data Agent, etc.) runs as part of a multi-agent workflow, the Responses API returns output-only items like
mcp_list_tools,reasoning,web_search_call, andfabric_dataagent_preview_callin its response. These items were being forwarded as-is to the next agent in the workflow viacontext.SendMessageAsync(response.Messages...).When the receiving agent sent its own Responses API request, the Microsoft.Extensions.AI library round-tripped these items via
ChatMessage.RawRepresentation, including them as input items. The Responses API rejected the request withinvalid_payload/invalid_request_errorbecause these are output-only item types that are not valid as input.This makes multi-agent workflows involving Foundry server-side agents with server-side tools unusable.
Description
Adds
FilterForwardableMessagestoAIAgentHostExecutorthat sanitizes response messages before forwarding them into the workflow. The filter does two things:Strips non-portable content: Only content items with recognized portable types (
TextContent,DataContent,UriContent,FunctionCallContent,FunctionResultContent,ToolApprovalRequestContent,ToolApprovalResponseContent,HostedFileContent,ErrorContent) are retained. Server-side artifacts like reasoning tokens and provider-specific tool call items are dropped.Strips
RawRepresentation: NewChatMessageobjects are constructed without the provider-specificRawRepresentation, preventing the Microsoft.Extensions.AI library from round-tripping the original output-only items (e.g.,mcp_list_tools) as input to the next agent.Messages that contain zero portable content items after filtering are dropped entirely.
What is NOT affected:
AgentResponseUpdates yielded viaYieldOutputAsync— these fire before the filter and retain full fidelityAgentResponseevents emitted viaEmitAgentResponseEvents— unaffectedRawRepresentationis[JsonIgnore]and was never persistedAIContent.RawRepresentationon individual content items — preserved; only the message-levelRawRepresentationis strippedKnown follow-up:
HandoffAgentExecutorhas a similar unfiltered path atsharedState.Conversation.AddMessages(result.Response.Messages)that would need an equivalent fix if Foundry server-side agents participate in handoff workflows.Contribution Checklist