Hi team,
Ran into this building a Microsoft.Extensions.AI agent against
OpenRouter (tested Qwen and Nvidia Nemotron models via the Responses API).
The models' actual reasoning text never showed up anywhere —
not in TextReasoningContent.Text, not in OTel traces.
Opened a ticket with the openai-dotnet (1329) team first, since I assumed
it was a client-model gap there. Their answer (pasted below) clarified
it's actually expected on their side, and pointed at the real mechanism:
▎ Although reasoning.content exists in the official Responses API schema,
▎ it is never returned by the OpenAI service... The field was added to the
▎ schema specifically for raw chain of thought from the open-weight gpt-oss
▎ models... That said, this is a good fit for the JsonPatch support that
▎ we added starting in v2.6.0... For your scenario, you can read the
▎ reasoning content directly:
▎
▎ string reasoningText = reasoningItem.Patch.GetString("$.content[0].text"u8);
▎
▎ The field is also preserved by the patch when the item is used in
▎ a subsequent request.
That's a real, working fix — but only if you're calling
OpenAIResponseClient directly.
Going through Microsoft.Extensions.AI's AIAgent/IChatClient abstraction, you never get a ReasoningResponseItem to call .Patch on — OpenAIResponsesChatClient.ToChatMessages (line ~207) already converts it before your code ever sees it:
case ReasoningResponseItem reasoningItem:
message.Contents.Add(CreateReasoningContent(reasoningItem.GetSummaryText(), reasoningItem.EncryptedContent, reasoningItem.Id, outputItem));
break;
GetSummaryText() only reads SummaryParts — it never sees content,
so it comes back empty and TextReasoningContent.Text stays empty
too. Same story for OTel: OtelMessageSerializer reads
TextReasoningContent.Text directly, so it never sees this either.
Worth noting: this exact same file already gets it right for streaming.
StreamingResponseReasoningTextDeltaUpdate handles a real, separate
response.reasoning_text.delta event and reads
reasoningTextDeltaUpdate.Delta straight into CreateReasoningContent,
no gap at all. So this isn't a question of whether the concept fits —
it's already implemented and working one code path over, in the same
class. This is purely the non-streaming branch not having the same
parity yet.
I also noticed #7295 already landed a similar pattern — JsonPatch
read of a non-standard field, surfaced as TextReasoningContent — for
OpenAIChatClient (Chat Completions), reading reasoning_content.
This issue is the equivalent gap on the Responses API side, reading
content instead.
Also related: #7525, which covers the Chat Completions side not
writing reasoning_content back out on replay. This one's
narrower — no write-back needed here at all, since the Responses API's
content field already round-trips for free through the existing
generic Patch mechanism (confirmed by the OpenAI SDK team's reply
above). Just the read side needs the fallback.
Suggested fix, matching #7295's approach and the existing
streaming branch, at OpenAIResponsesChatClient.cs:
case ReasoningResponseItem reasoningItem:
string reasoningText = reasoningItem.GetSummaryText();
if (string.IsNullOrEmpty(reasoningText))
{
reasoningText = reasoningItem.Patch.GetString("$.content[0].text"u8);
}
message.Contents.Add(CreateReasoningContent(reasoningText, reasoningItem.EncryptedContent, reasoningItem.Id, outputItem));
break;
Would be happy for any direction on this,
Thank you!
Hi team,
Ran into this building a Microsoft.Extensions.AI agent against
OpenRouter (tested Qwen and Nvidia Nemotron models via the Responses API).
The models' actual reasoning text never showed up anywhere —
not in TextReasoningContent.Text, not in OTel traces.
Opened a ticket with the openai-dotnet (1329) team first, since I assumed
it was a client-model gap there. Their answer (pasted below) clarified
it's actually expected on their side, and pointed at the real mechanism:
▎ Although reasoning.content exists in the official Responses API schema,
▎ it is never returned by the OpenAI service... The field was added to the
▎ schema specifically for raw chain of thought from the open-weight gpt-oss
▎ models... That said, this is a good fit for the JsonPatch support that
▎ we added starting in v2.6.0... For your scenario, you can read the
▎ reasoning content directly:
▎
▎ string reasoningText = reasoningItem.Patch.GetString("$.content[0].text"u8);
▎
▎ The field is also preserved by the patch when the item is used in
▎ a subsequent request.
That's a real, working fix — but only if you're calling
OpenAIResponseClient directly.
Going through Microsoft.Extensions.AI's AIAgent/IChatClient abstraction, you never get a ReasoningResponseItem to call .Patch on — OpenAIResponsesChatClient.ToChatMessages (line ~207) already converts it before your code ever sees it:
case ReasoningResponseItem reasoningItem:
message.Contents.Add(CreateReasoningContent(reasoningItem.GetSummaryText(), reasoningItem.EncryptedContent, reasoningItem.Id, outputItem));
break;
GetSummaryText() only reads SummaryParts — it never sees content,
so it comes back empty and TextReasoningContent.Text stays empty
too. Same story for OTel: OtelMessageSerializer reads
TextReasoningContent.Text directly, so it never sees this either.
Worth noting: this exact same file already gets it right for streaming.
StreamingResponseReasoningTextDeltaUpdate handles a real, separate
response.reasoning_text.delta event and reads
reasoningTextDeltaUpdate.Delta straight into CreateReasoningContent,
no gap at all. So this isn't a question of whether the concept fits —
it's already implemented and working one code path over, in the same
class. This is purely the non-streaming branch not having the same
parity yet.
I also noticed #7295 already landed a similar pattern — JsonPatch
read of a non-standard field, surfaced as TextReasoningContent — for
OpenAIChatClient (Chat Completions), reading reasoning_content.
This issue is the equivalent gap on the Responses API side, reading
content instead.
Also related: #7525, which covers the Chat Completions side not
writing reasoning_content back out on replay. This one's
narrower — no write-back needed here at all, since the Responses API's
content field already round-trips for free through the existing
generic Patch mechanism (confirmed by the OpenAI SDK team's reply
above). Just the read side needs the fallback.
Suggested fix, matching #7295's approach and the existing
streaming branch, at OpenAIResponsesChatClient.cs:
case ReasoningResponseItem reasoningItem:
string reasoningText = reasoningItem.GetSummaryText();
if (string.IsNullOrEmpty(reasoningText))
{
reasoningText = reasoningItem.Patch.GetString("$.content[0].text"u8);
}
message.Contents.Add(CreateReasoningContent(reasoningText, reasoningItem.EncryptedContent, reasoningItem.Id, outputItem));
break;
Would be happy for any direction on this,
Thank you!