Skip to content

fix(openai): round-trip reasoning_content on DeepSeek tool_calls turns#4022

Merged
esengine merged 2 commits into
main-v2from
fix/deepseek-reasoning-roundtrip-400
Jun 11, 2026
Merged

fix(openai): round-trip reasoning_content on DeepSeek tool_calls turns#4022
esengine merged 2 commits into
main-v2from
fix/deepseek-reasoning-roundtrip-400

Conversation

@esengine

Copy link
Copy Markdown
Owner

Summary

Two related fixes around DeepSeek thinking mode, found while running the agent loop against the live API with the real provider.

1. fix(openai) — round-trip reasoning_content on DeepSeek tool_calls turns

DeepSeek's thinking mode now requires reasoning_content to be passed back on an assistant turn that carries tool_calls. The provider stripped it unconditionally, so the request fails with:

400 "The `reasoning_content` in the thinking mode must be passed back to the API."

The failure is cache-state dependent, which is why it looked intermittent:

Condition Result
Warm consecutive turns in a live session ✅ tolerated — DeepSeek still holds the reasoning server-side
Cold replay: session resume, compaction, post-cache-TTL, tab switch ❌ hard 400

Reproduced with the real provider on both deepseek-v4-flash and deepseek-v4-pro. A plain assistant text turn does not require it (verified), so the round-trip is scoped to deepseek && assistant && len(tool_calls) > 0.

Cache safety: the reasoning enters the cached prefix and is reused on later turns — measured cacheHit 256 → 384, cacheMiss → 27 across rounds, no cache collapse. Cost is one miss per chain (DeepSeek-mandated), not a regression.

Edge case left as-is: a pre-fix archive whose tool_calls turn has no stored reasoning still 400s on resume — there's no data to round-trip.

2. feat(agent) — diagnostics on the empty-final notice

empty final answer blocked ... retrying carried no context, so we couldn't tell its causes apart. A live sweep (flash + pro) shows natural multi-round loops basically never hit it (0/6 realistic tasks, 0/10 warm wrap-ups); the reproduced triggers are all niche — the <think>-leading think-splitter path, or the model genuinely emitting no/whitespace content.

The notice now reports the provider, finish_reason, and reasoning length so each occurrence is self-diagnosing (length-truncation vs reasoning-only vs swallowed answer) instead of a guess.

Test

  • go test ./internal/provider/openai/ ./internal/agent/ — pass
  • New: TestBuildRequestRoundTripsReasoningOnDeepSeekToolCalls, TestEmptyFinalNotice; tightened TestBuildRequestDropsReasoningOnPlainAssistantTurn

reasonix added 2 commits June 11, 2026 03:50
DeepSeek's thinking mode now rejects an assistant tool_calls turn whose
reasoning_content was dropped on replay (400 "reasoning_content … must be
passed back to the API"). The provider stripped it unconditionally, so any
cache-miss replay of a tool-calling history — session resume, compaction,
or a turn after the prompt cache expires — 400s, while warm consecutive
turns are tolerated because DeepSeek still holds the reasoning server-side.

Round reasoning_content back, but only on the assistant turn that carries
tool calls and only for the DeepSeek protocol: a plain assistant text turn
does not require it, and other backends still bill it as input for nothing.
Reasoning enters the cached prefix and is reused on later turns, so the
cost is one miss per chain, not a cache collapse.
The empty-final warning carried no context, so reports couldn't tell its
three causes apart: a length-truncated turn, a reasoning-only stop, or a
provider that swallowed the answer. Include the provider, the finish
reason, and the reasoning length so each occurrence is self-diagnosing.
@esengine esengine requested a review from SivanCola as a code owner June 11, 2026 10:51
@github-actions github-actions Bot added v2 Go rewrite (1.x) — main-v2 branch, active development agent Core agent loop (internal/agent, internal/control) provider Model providers & selection (internal/provider) labels Jun 11, 2026
@esengine esengine merged commit 0263ee9 into main-v2 Jun 11, 2026
14 checks passed
@esengine esengine deleted the fix/deepseek-reasoning-roundtrip-400 branch June 11, 2026 10:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent Core agent loop (internal/agent, internal/control) provider Model providers & selection (internal/provider) v2 Go rewrite (1.x) — main-v2 branch, active development

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant