Skip to content

fix(reasoning): promote reasoning field before empty-pad for DeepSeek/Kimi (#15812)#15830

Closed
briandevans wants to merge 1 commit into
NousResearch:mainfrom
briandevans:fix/reasoning-field-promotion-before-empty-pad-15812
Closed

fix(reasoning): promote reasoning field before empty-pad for DeepSeek/Kimi (#15812)#15830
briandevans wants to merge 1 commit into
NousResearch:mainfrom
briandevans:fix/reasoning-field-promotion-before-empty-pad-15812

Conversation

@briandevans

Copy link
Copy Markdown
Contributor

Summary

  • _copy_reasoning_content_for_api had a step-ordering bug introduced by PR fix(agent): ordering fix in _copy_reasoning_content_for_api — cross-provider reasoning isolation #15749: the tool-call empty-string injection (old step 2) ran before the reasoning field promotion (old step 3)
  • A tool-call message with a valid reasoning field but no reasoning_content would hit the empty-string path, get reasoning_content="" injected, and return early — the reasoning value was silently discarded
  • Fix: move the reasoningreasoning_content promotion to step 2, before the empty-string fallbacks

The bug

run_agent.py_copy_reasoning_content_for_api (pre-fix):

# Step 1: explicit reasoning_content → preserve ✓
# Step 2: tool_calls + DeepSeek/Kimi → inject ""  ← fires here
#         RETURNS EARLY
# Step 3: reasoning field → promote               ← never reached

A message like {"role": "assistant", "reasoning": "thought trace", "tool_calls": [...]} on a DeepSeek session would return reasoning_content="" instead of reasoning_content="thought trace".

The fix

Reorder steps 2 and 3 so reasoning promotion runs first:

# Step 1: explicit reasoning_content → preserve
# Step 2: reasoning field present → promote to reasoning_content  ← moved up
# Step 3: tool_calls + DeepSeek/Kimi + no reasoning → inject ""
# Step 4: all other DeepSeek/Kimi + no reasoning → inject ""
# Step 5: non-string cleanup

Empty-string injection now only fires when neither reasoning_content nor reasoning is present — the poisoned-history case it was designed for.

Test plan

  • Before: test_deepseek_reasoning_field_promotedAssertionError: assert '' == 'thought trace'
  • After: all 21 tests in test_deepseek_reasoning_content_echo.py pass
  • Regression guard: reverted run_agent.pytest_deepseek_reasoning_field_promoted failed with '' != 'thought trace'; restored → 21 passing
  • Broader suite: tests/run_agent/ — 1048 passed, 1 pre-existing baseline failure (test_inf_stays_string_for_integer_only reproduces on clean origin/main)

Related

🤖 Generated with Claude Code

…/Kimi (NousResearch#15812)

PR NousResearch#15749 reordered _copy_reasoning_content_for_api so the
tool-call empty-string injection (step 2) ran before the reasoning
field promotion (step 3). A tool-call message that had a valid
'reasoning' field but no 'reasoning_content' would hit step 2,
get reasoning_content="" injected, and return early — the reasoning
field was never promoted and was silently lost.

Fix: move the 'reasoning' → 'reasoning_content' promotion to step 2,
before the empty-string fallback paths. Empty-string injection now
only fires when neither reasoning_content nor reasoning is present,
which is the poisoned-history case it was designed to handle.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 26, 2026 01:16

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.

Pull request overview

Fixes a regression in AIAgent._copy_reasoning_content_for_api where DeepSeek/Kimi tool-call messages that have a valid internal reasoning field could incorrectly get reasoning_content="" injected and return early, dropping the reasoning content on API replay.

Changes:

  • Reorders logic in _copy_reasoning_content_for_api so reasoning → reasoning_content promotion happens before the DeepSeek/Kimi empty-string fallback.
  • Narrows the “poisoned history” empty-string injection (for tool-call turns) to only apply when neither reasoning_content nor reasoning is present.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@teknium1

Copy link
Copy Markdown
Contributor

Closing as redundant — the DeepSeek reasoning_content thinking-mode 400 and cross-provider leak chain of issues is now fully covered on main:

21 regression tests in tests/run_agent/test_deepseek_reasoning_content_echo.py + 2 new tests for the cross-provider scenario exercise every known path. Thanks for the submission — appreciate the digging on this area.

@teknium1 teknium1 closed this Apr 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent loop, run_agent.py, prompt builder P2 Medium — degraded but workaround exists provider/deepseek DeepSeek API provider/kimi Kimi / Moonshot type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

regression: #15749 breaks reasoning field promotion for DeepSeek/Kimi tool-call messages

4 participants