Skip to content

Surface Responses API reasoning item content as TextReasoningContent - #7752

Open
LouisDeconinck wants to merge 3 commits into
dotnet:mainfrom
LouisDeconinck:fix/responses-reasoning-content-7714
Open

LouisDeconinck wants to merge 3 commits into
dotnet:mainfrom
LouisDeconinck:fix/responses-reasoning-content-7714

Conversation

@LouisDeconinck

@LouisDeconinck LouisDeconinck commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Fixes #7714

OpenAIResponsesChatClient.ToChatMessages read reasoning text only from a ReasoningResponseItem's summary parts via GetSummaryText(). Responses API implementations that return raw chain-of-thought in the item's content field instead of summary (e.g. OpenRouter with gpt-oss-family models) produced a TextReasoningContent with empty Text, so the reasoning never surfaced to callers or OTel traces.

The OpenAI client doesn't expose content as a property (per openai/openai-dotnet#1329 the field exists in the schema but is never returned by the OpenAI service), but it is preserved in the item's JsonPatch. This change falls back to reading $.content from the patch when the summary is empty, concatenating the text of its reasoning_text parts — mirroring how #7295 reads reasoning_content via patch in OpenAIChatClient, and how the streaming branch surfaces response.reasoning_text.delta payloads.

No request-side change is needed: the reasoning item's RawRepresentation is preserved and round-trips the content field as-is.

Tests

  • New ReasoningContent_NonStreaming test asserts a reasoning item carrying content (with an empty summary) produces a TextReasoningContent whose Text contains the concatenated reasoning. Verified the test fails before the fix (Text was empty).
  • Microsoft.Extensions.AI.OpenAI.Tests: 390 passed / 0 failed on net8.0, net9.0, and net10.0.
Microsoft Reviewers: Open in CodeFlow

OpenAIResponsesChatClient.ToChatMessages only read reasoning text from
a ReasoningResponseItem's summary parts. Responses API implementations
that return raw reasoning in the item's "content" field instead (e.g.
OpenRouter) produced an empty TextReasoningContent, so the reasoning
never surfaced in responses or telemetry.

The OpenAI client doesn't surface "content" as a property but
preserves it in the item's JsonPatch, so fall back to concatenating
the text of its reasoning_text parts when the summary is empty,
matching the approach used for reasoning_content in OpenAIChatClient.
The field already round-trips through the preserved raw item, so no
request-side change is needed.

Fixes dotnet#7714
Copilot AI lite review requested due to automatic review settings September 13, 2026 19:18
@LouisDeconinck
LouisDeconinck requested a review from a team as a code owner September 13, 2026 19:18

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 fallback should only extract text from reasoning_text parts.

Pull request overview

Updates the OpenAI Responses API adapter to surface reasoning text stored in content when summary is empty.

Changes:

  • Adds JSON-patch fallback extraction for reasoning content.
  • Adds regression coverage for concatenated reasoning text.
File summaries
File Description
test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIResponseClientTests.cs Adds non-streaming reasoning-content regression coverage.
src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs Implements the content fallback for reasoning text.
Review details

Suppressed comments (1)

src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs:971

  • This fallback appends the text property from every object in content, rather than only from reasoning_text parts. If a response contains another content-part type with a text field, that text will be incorrectly exposed as TextReasoningContent; check the part's type before appending.
                    if (part.ValueKind == JsonValueKind.Object &&
                        part.TryGetProperty("text"u8, out JsonElement textElement) &&
                        textElement.ValueKind == JsonValueKind.String)
                    {
  • Files reviewed: 2/2 changed files
  • Comments generated: 0
  • Review effort level: Lite

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

The patch-based fallback appended every content part's text, so a part
of another type carrying a text field would leak into
TextReasoningContent. Check the part's type before appending, and cover
it with a non-reasoning_text part in the regression test.
@dotnet-comment-bot

Copy link
Copy Markdown
Collaborator

‼️ Found issues ‼️

Project Coverage Type Expected Actual
Microsoft.Extensions.Diagnostics.Testing Line 99 98.65 🔻
Microsoft.Extensions.Telemetry Line 93 92.64 🔻
Microsoft.Extensions.AI.OpenAI Line 75 68.47 🔻
Microsoft.Extensions.AI.OpenAI Branch 75 57.98 🔻
Microsoft.Extensions.DataIngestion.MarkItDown Line 75 4.46 🔻
Microsoft.Extensions.DataIngestion.MarkItDown Branch 75 0 🔻
Microsoft.Extensions.Diagnostics.ResourceMonitoring Line 99 96.03 🔻
Microsoft.Extensions.Diagnostics.ResourceMonitoring Branch 99 92.76 🔻
Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes Line 99 97.73 🔻
Microsoft.Extensions.ServiceDiscovery.Abstractions Line 75 42.11 🔻
Microsoft.Extensions.ServiceDiscovery.Abstractions Branch 75 42.86 🔻
Microsoft.Extensions.ServiceDiscovery Line 75 67.21 🔻
Microsoft.Extensions.ServiceDiscovery Branch 75 71.43 🔻
Microsoft.Extensions.ServiceDiscovery.Dns Line 75 69.93 🔻
Microsoft.Extensions.ServiceDiscovery.Yarp Line 75 73.85 🔻
Microsoft.Extensions.ServiceDiscovery.Yarp Branch 75 70 🔻
Microsoft.Extensions.VectorData.Abstractions Line 75 37.39 🔻
Microsoft.Extensions.VectorData.Abstractions Branch 75 22.73 🔻

🎉 Good job! The coverage increased 🎉
Update MinCodeCoverage in the project files.

Project Expected Actual
Microsoft.Extensions.Http.Diagnostics 94 95
Microsoft.Gen.BuildMetadata 97 100
Microsoft.Gen.MetadataExtractor 57 73
Microsoft.Gen.MetricsReports 67 69
Microsoft.Extensions.AI.Abstractions 82 86
Microsoft.Extensions.AI.Evaluation.NLP 0 78
Microsoft.Extensions.Caching.Hybrid 82 85
Microsoft.Extensions.DataIngestion 75 89
Microsoft.Extensions.DataIngestion.Markdig 75 90
Microsoft.Extensions.Http.Resilience 97 100

Full code coverage report: https://dev.azure.com/dnceng-public/public/_build/results?buildId=1600596&view=codecoverage-tab

@LouisDeconinck

Copy link
Copy Markdown
Contributor Author

Added ReasoningContent_SkipsMalformedParts_NonStreaming covering the defensive paths in GetReasoningContentText: missing content key, non-array content, non-object parts, and parts missing type/text or with non-string text.

Note on the coverage report above: the flagged project minimums (e.g. Microsoft.Extensions.AI.OpenAI at ~68.5% line vs 75%) are standing whole-project baselines — the same numbers appear on unrelated PRs — not something introduced by this diff. The new code paths are covered.

@dotnet-comment-bot

Copy link
Copy Markdown
Collaborator

‼️ Found issues ‼️

Project Coverage Type Expected Actual
Microsoft.Extensions.Diagnostics.Testing Line 99 98.65 🔻
Microsoft.Extensions.Telemetry Line 93 92.64 🔻
Microsoft.Extensions.AI.OpenAI Line 75 68.5 🔻
Microsoft.Extensions.AI.OpenAI Branch 75 58.17 🔻
Microsoft.Extensions.DataIngestion.MarkItDown Line 75 4.46 🔻
Microsoft.Extensions.DataIngestion.MarkItDown Branch 75 0 🔻
Microsoft.Extensions.Diagnostics.ResourceMonitoring Line 99 96.03 🔻
Microsoft.Extensions.Diagnostics.ResourceMonitoring Branch 99 92.76 🔻
Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes Line 99 97.73 🔻
Microsoft.Extensions.ServiceDiscovery.Dns Line 75 69.93 🔻
Microsoft.Extensions.ServiceDiscovery Line 75 68.88 🔻
Microsoft.Extensions.ServiceDiscovery Branch 75 71.43 🔻
Microsoft.Extensions.ServiceDiscovery.Abstractions Line 75 42.11 🔻
Microsoft.Extensions.ServiceDiscovery.Abstractions Branch 75 42.86 🔻
Microsoft.Extensions.ServiceDiscovery.Yarp Line 75 73.85 🔻
Microsoft.Extensions.ServiceDiscovery.Yarp Branch 75 70 🔻
Microsoft.Extensions.VectorData.Abstractions Line 75 37.39 🔻
Microsoft.Extensions.VectorData.Abstractions Branch 75 22.73 🔻

🎉 Good job! The coverage increased 🎉
Update MinCodeCoverage in the project files.

Project Expected Actual
Microsoft.Extensions.Http.Diagnostics 94 95
Microsoft.Gen.BuildMetadata 97 100
Microsoft.Gen.MetadataExtractor 57 73
Microsoft.Gen.MetricsReports 67 69
Microsoft.Extensions.AI.Abstractions 82 86
Microsoft.Extensions.AI.Evaluation.NLP 0 78
Microsoft.Extensions.Caching.Hybrid 82 84
Microsoft.Extensions.DataIngestion 75 89
Microsoft.Extensions.DataIngestion.Markdig 75 90
Microsoft.Extensions.Http.Resilience 97 100

Full code coverage report: https://dev.azure.com/dnceng-public/public/_build/results?buildId=1600976&view=codecoverage-tab

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OpenAIResponsesChatClient.ToChatMessages never surfaces reasoning.content — only reads GetSummaryText()

3 participants