[WIP] Fix reasoning_content preservation during message coalescing#7408
Closed
[WIP] Fix reasoning_content preservation during message coalescing#7408
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
reasoning_content(AdditionalProperties) in streaming path - accumulate reasoning text and add to final syntheticChatResponseUpdate.AdditionalPropertiesreasoning_contentin non-streaming path - add toChatMessage.AdditionalPropertiesinFromOpenAIChatCompletionAdditionalProperties["reasoning_content"]back to OpenAI wire format inToOpenAIChatMessagesfor assistant messagesreasoning_contentinAdditionalPropertiesreasoning_contentin coalescedChatMessage.AdditionalPropertiesToOpenAIChatMessagesround-tripsreasoning_contentfromAdditionalPropertiesinto the request JSONOriginal prompt
Fix: Preserve
reasoning_content(AdditionalProperties) during streaming-to-message coalescing and round-trip it back to the OpenAI wire formatContext
GitHub Issue: #7405
When using
IChatClientwithFunctionInvokingChatClientand a DeepSeek reasoning model (e.g.deepseek-reasoner) viaGetStreamingResponseAsync, an HTTP 400 error is thrown as soon as a tool call is involved:DeepSeek reasoning models require that any assistant message in the conversation history that was originally generated with a
reasoning_contentfield must include that field when the conversation is sent back in subsequent requests.Root Cause
The problem is a two-part data loss during the tool-calling loop:
OpenAIChatClient.FromOpenAIStreamingChatCompletionAsync: When streamingChatCompletionUpdatechunks are accumulated into a finalChatMessage/ChatCompletion, anyAdditionalPropertiespresent on the streaming chunks (includingreasoning_content) are not merged into the resultingChatMessage.AdditionalProperties. This means the round-tripped message loses all provider-specific metadata.ToOpenAIChatMessages(or similar serialization method): When convertingChatMessageback to the OpenAI API wire format for subsequent requests,AdditionalPropertieson theChatMessageare not serialized back into the request JSON. So even if they were preserved, they wouldn't make it into the next API call.Required Changes
In
OpenAIChatClient(streaming coalescing): When streamingChatCompletionUpdatechunks are accumulated into a finalChatMessage, ensure thatAdditionalPropertiesfrom the streaming response (specificallyreasoning_contentwhich DeepSeek delivers as a provider-specific extension) are preserved on the resultingChatMessage.AdditionalProperties.In the OpenAI message mapping layer (the code that converts
ChatMessage→ OpenAIChatCompletionOptionsmessages, e.g.,ToOpenAIChatMessagesor similar): Ensure thatAdditionalPropertiesonChatMessageobjects are serialized back into the outgoing request JSON, so that fields likereasoning_contentare round-tripped correctly.Add tests to verify that
reasoning_content(andAdditionalPropertiesin general) survive the full round-trip: streaming response → coalesced ChatMessage → serialized back to API request.Key Files to Investigate
src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIChatClient.cs— look atFromOpenAIStreamingChatCompletionAsyncandToOpenAIChatMessagessrc/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs— understand how it reassembles messages between tool-call roundsNotes
FunctionInvokingChatClientis provider-agnostic and works with theIChatClientabstraction. It already preservesAdditionalPropertiesif they're present on theChatMessageit receives. The fix is primarily in theMicrosoft.Extensions.AI.OpenAIadapter layer.reasoning_contentfield is a DeepSeek-specific extension, but the fix should be general enough to handle any provider-specificAdditionalPropertiesthat need round-tripping.The following is the prior conversation context from the user's chat exploration (may be truncated):
User: How should this be addressed?
Assistant: I'm waiting for your confirmation to proceed with the deep research investigation into the repository.
User: @copilot Accepted Confirmation: Are you sure?
Assistant: I've kicked off a deep research investigation into the
dotnet/extensionsrepository to trace exactly where thereasoning_contentfield gets dropped. You can follow its progress here:While that runs, here's my initial analysis of the issue and how it should be addressed:
Root Cause
The problem is a two-part data loss during the tool-calling loop:
FunctionInvokingChatClientperforms iterative tool-call rounds. After receiving a streaming response containing tool calls, it coalesces the streaming updates into aChatMessageand appends it to the conversation history. During this coalescing, provider-specificAdditionalProperties(like DeepSeek'sreasoning_content) are not carried forward onto the reconstructed assistant message.When the conversation (now including that stripped-down assistant message) is sent back for the next round, DeepSeek's API rej...
This pull request was created from Copilot chat.
⌨️ Start Copilot coding agent tasks without leaving your editor — available in VS Code, Visual Studio, JetBrains IDEs and Eclipse.
Microsoft Reviewers: Open in CodeFlow