Skip to content

Python: fix(openai): preserve reasoning_content on tool-call follow-up messages - #8405

Merged
Eduard van Valkenburg (eavanvalkenburg) merged 2 commits into
microsoft:mainfrom
anishmehta24:fix/openai-chat-reasoning-content-tool-followup
Sep 16, 2026
Merged

Eduard van Valkenburg (eavanvalkenburg) merged 2 commits into
microsoft:mainfrom
anishmehta24:fix/openai-chat-reasoning-content-tool-followup

Conversation

@anishmehta24

Copy link
Copy Markdown
Contributor

Motivation & Context

With OpenAIChatCompletionClient against OpenAI-compatible thinking models (reproduced with DeepSeek, thinking.type=enabled), a response containing text, a tool call and reasoning was serialised on the follow-up request as two assistant messages: {content} followed by {tool_calls, reasoning_*}. DeepSeek requires the reasoning on the message that carries the content and returns 400: The reasoning_content in the thinking mode must be passed back to the API.

Description & Review Guide

  • Major changes: _build_openai_messages now attaches a turn's tool calls to the assistant dict built from that turn's text (and text that follows coalesced tool calls to the tool_calls dict), so content, tool_calls and the buffered reasoning field travel as one assistant message. Six unit tests, including an end-to-end DeepSeek-style reasoning_content round trip through the existing response_parser/message_preparer hooks.
  • Impact: assistant turns with text and tool calls are sent as a single message, which is the canonical Chat Completions shape. Reasoning text surfaced by a response_parser keeps its own dict, so message_preparer correlation (the vLLM example) is unchanged; refusals and non-text content are not merged. No stock reasoning_content branch was added, per packages/openai/AGENTS.md; a parser/preparer pair is still needed to surface and echo DeepSeek's field, and it now works.
  • Focus for reviewers: the merge conditions in _build_openai_messages (only plain text and tool calls of the same turn, only when adjacent) and whether the single-message shape is acceptable for all providers.

Prepared with AI assistance (Claude Code) and human-verified: the new tests fail on main and pass with the fix; poe fmt, poe lint, poe pyright -P openai, poe mypy -P openai and the openai and foundry_local test suites were run locally.

Related Issue

Fixes #8382

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
  • This is not a breaking change.

🤖 Generated with Claude Code

When one assistant turn contains text, tool calls and reasoning, the Chat
Completions serializer emitted two assistant dicts (text-only, then
tool_calls) and the buffered reasoning field only travelled with the
second one. OpenAI-compatible providers such as DeepSeek in thinking mode
reject the tool-result follow-up because the message that carries the
content no longer carries its reasoning.

Attach the tool calls of a turn to the assistant dict built from that
turn's plain text (and text that follows coalesced tool calls to the
tool_calls dict), so content, tool_calls and the reasoning field are sent
as a single assistant message. Reasoning text surfaced via the
response_parser hook keeps its own dict so message_preparer correlation
is unchanged; refusals, images and other content are not merged.

Fixes microsoft#8382

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@eavanvalkenburg

Copy link
Copy Markdown
Member

Thanks for the contribution. Before this is ready, please complete the Microsoft CLA; license/cla is still queued, and the current DevFlow PR Review run has cancelled review and team_check jobs. Once those blockers are cleared, please re-request review so the approval-gated workflows can be run. Thanks!

@anishmehta24

Copy link
Copy Markdown
Contributor Author

@microsoft-github-policy-service agree

@anishmehta24

Copy link
Copy Markdown
Contributor Author

Eduard van Valkenburg (@eavanvalkenburg) CLA is signed and license/cla is green now. The previously queued jobs have run; only merge-gatekeeper is still pending. Thanks for the quick review!

Merged via the queue into microsoft:main with commit fca126d Sep 16, 2026
46 checks passed

This branch was previously deployed

1 inactive deployment
github-app-auth — c298d03d Deployed Sep 16, 2026 by eavanvalkenburg via add_label #22976
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

python Usage: [Issues, PRs], Target: Python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Python: [Bug]: Preserve reasoning_content when invoking tools with OpenAI-compatible Chat Completions providers

3 participants