Skip to content

.NET: Fix AG-UI "Unknown chat role: reasoning" on follow-up turns - #8465

Open
Atharva Vichare (atty57) wants to merge 3 commits into
microsoft:mainfrom
atty57:fix/agui-reasoning-role-8462
Open

Atharva Vichare (atty57) wants to merge 3 commits into
microsoft:mainfrom
atty57:fix/agui-reasoning-role-8462

Conversation

@atty57

Copy link
Copy Markdown
Contributor

Motivation & Context

With a reasoning-enabled agent hosted via MapAGUIServer and a CopilotKit web chat, the first turn works, but every follow-up turn fails with HTTP 500:

System.InvalidOperationException: Unknown chat role: reasoning
   at AGUI.Abstractions.AGUIChatMessageExtensions.MapChatRole(String role)
   at AGUI.Abstractions.AGUIChatMessageExtensions.AsChatMessages(...)
   at AGUI.Server.RunAgentInputExtensions.ToChatRequestContext(...)

CopilotKit sends the earlier reasoning message back in messages. AGUI.Abstractions 0.0.6 deserializes it into an AGUIReasoningMessage, but MapChatRole has no mapping for the reasoning role and throws. The conversation can't continue after that.

Description & Review Guide

  • What are the major changes?

    • Added an internal helper, AGUIEndpointRouteBuilderExtensions.RemoveReasoningMessages(RunAgentInput), which removes AGUIReasoningMessage entries from the request history. The endpoint calls it just before ToChatRequestContext.
    • Added a unit test that deserializes a CopilotKit-style follow-up request (user → reasoning → assistant → user). It first confirms that the unmodified request still throws, then confirms that after the helper runs the conversion succeeds and the other messages stay in order.
  • 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:

    1. Fix it in the AG-UI .NET SDK (AGUI.Abstractions). The reasoning role is part of the AG-UI protocol, so MapChatRole / AsChatMessages arguably should handle it there. This PR could then be closed, or kept as a short-term workaround until a new package version ships.
    2. Keep the reasoning instead of dropping it. Convert each AGUIReasoningMessage into TextReasoningContent on 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.
    3. Drop reasoning history (this PR). It's the smallest fix and the least likely to break anything, but reasoning from earlier turns is lost.

    Should the chosen behavior be configurable (for example through AGUIStreamOptions), or just fixed?

Related Issue

Fixes #8462

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.

Copilot AI balanced review requested due to automatic review settings September 17, 2026 13:44
@agent-framework-automation agent-framework-automation Bot added the .NET Usage: [Issues, PRs], Target: .Net label Sep 17, 2026

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

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.

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

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);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

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.

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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?

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.

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thank you for the additional information.

I'd also like westey (@westey-m) input here

This branch was successfully deployed

2 active deployments
github-app-auth — b4c24f68 Deployed Oct 7, 2026 by atty57 via team_check #6305
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

.NET Usage: [Issues, PRs], Target: .Net

Projects

None yet

Development

Successfully merging this pull request may close these issues.

.NET: [Bug]: Replying in a CopilotKit webchat with reasoning results in "System.InvalidOperationException: Unknown chat role: reasoning"

3 participants