Repository navigation
.NET: Fix AG-UI "Unknown chat role: reasoning" on follow-up turns - #8465
Atharva Vichare (atty57) wants to merge 3 commits into
Conversation
There was a problem hiding this comment.
🔵 Needs a closer look
Discarding reasoning history is a protocol-level design choice requiring maintainer review, and the endpoint behavior lacks integration coverage.
Pull request overview
Filters AG-UI reasoning messages from follow-up request history to prevent conversion failures.
Changes:
- Removes reasoning messages before request conversion.
- Adds a focused deserialization/conversion unit test.
File summaries
| File | Description |
|---|---|
AGUIEndpointRouteBuilderExtensions.cs |
Filters unsupported reasoning messages. |
AGUIEndpointRouteBuilderExtensionsTests.cs |
Tests filtering and preserved ordering. |
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 1
- Review effort level: Balanced
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
🔵 Needs a closer look
The HTTP endpoint regression needs integration coverage to verify the filtering is wired into the request pipeline.
0 open findings
1 resolved since last review
🧠 Review effort: Balanced
Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.
| var streamOptions = context.GetEndpoint()?.Metadata.GetMetadata<AGUIStreamOptions>() | ||
| ?? context.RequestServices.GetService<IOptions<AGUIStreamOptions>>()?.Value; | ||
|
|
||
| RemoveReasoningMessages(input); |
There was a problem hiding this comment.
this will be disruptive to other clients, we should instead introduce additional options for the Map method, and that option could introduce a filtering callback for the messages. Then your code could provide that callback.
There was a problem hiding this comment.
Makes sense , defaulting to drop reasoning messages for everyone is too heavy-handed. I'll put it behind options on MapAGUIServer instead:
public sealed class AGUIServerOptions
{
public Func<IEnumerable<AGUIMessage>, IEnumerable<AGUIMessage>>? MessageFilter { get; set; }
}The endpoint runs options.MessageFilter (identity when it's unset) before ToChatRequestContext, and the reasoning-message filter ships as an opt-in callback people can pass in.
One thing I want your call on for the default path. With no filter configured, a follow-up turn that carries a reasoning message still hits Unknown chat role: reasoning and 500s which is the original #8462 report. Two ways to go:
- Keep it throwing, so filtering stays purely opt-in.
- Teach the converter to handle the reasoning role itself (map it to a reasoning content part, or just skip it), so the 500 goes away no matter what options are set.
Either way I'll add the endpoint-level test Copilot asked for, hitting this through MapAGUIServer rather than the helper directly.
There was a problem hiding this comment.
westey (@westey-m) what would you think here about either:
- having a set of default options for copilot/AGUI SDK, etc...
- having a set of overloads MapAGUIForCopilot....
To make it easier to nudge people to use the correct default configuration?
There was a problem hiding this comment.
My preference is the second one a dedicated MapAGUIForCopilot-style overload over changing or loading defaults on the general Map path , It would keep the general Map path unopinionated and non-breaking for other AG-UI clients also It makes the Copilot-specific behavior (dropping reasoning history on follow-up turns) explicit and discoverable right at the call site, instead of buried in a default that people have to know to override.
There was a problem hiding this comment.
Thank you for the additional information.
I'd also like westey (@westey-m) input here

Motivation & Context
With a reasoning-enabled agent hosted via
MapAGUIServerand a CopilotKit web chat, the first turn works, but every follow-up turn fails with HTTP 500:CopilotKit sends the earlier
reasoningmessage back inmessages.AGUI.Abstractions0.0.6 deserializes it into anAGUIReasoningMessage, butMapChatRolehas no mapping for thereasoningrole and throws. The conversation can't continue after that.Description & Review Guide
What are the major changes?
AGUIEndpointRouteBuilderExtensions.RemoveReasoningMessages(RunAgentInput), which removesAGUIReasoningMessageentries from the request history. The endpoint calls it just beforeToChatRequestContext.What is the impact of these changes? Follow-up turns no longer fail when the client sends reasoning back. Reasoning from earlier turns is not passed to the agent. It never reached the agent before this change either, because those requests failed. There are no public API changes.
What do you want reviewers to focus on?
Maintainers, I'd like your opinion on the approach before this is marked ready. The change is small, but it makes a design decision, and I'm happy to rework it:
AGUI.Abstractions). Thereasoningrole is part of the AG-UI protocol, soMapChatRole/AsChatMessagesarguably should handle it there. This PR could then be closed, or kept as a short-term workaround until a new package version ships.AGUIReasoningMessageintoTextReasoningContenton the assistant message it belongs to. That keeps reasoning context across turns, which may matter for Responses API reasoning continuity. The open questions are how to attach it to the right message and whether every provider accepts it.Should the chosen behavior be configurable (for example through
AGUIStreamOptions), or just fixed?Related Issue
Fixes #8462
Contribution Checklist
breaking changelabel (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.