fix(openai): round-trip reasoning_content on DeepSeek tool_calls turns#4022
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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-tripreasoning_contenton DeepSeek tool_calls turnsDeepSeek's thinking mode now requires
reasoning_contentto be passed back on an assistant turn that carriestool_calls. The provider stripped it unconditionally, so the request fails with:The failure is cache-state dependent, which is why it looked intermittent:
Reproduced with the real provider on both
deepseek-v4-flashanddeepseek-v4-pro. A plain assistant text turn does not require it (verified), so the round-trip is scoped todeepseek && assistant && len(tool_calls) > 0.Cache safety: the reasoning enters the cached prefix and is reused on later turns — measured
cacheHit 256 → 384,cacheMiss → 27across 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 noticeempty final answer blocked ... retryingcarried 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/— passTestBuildRequestRoundTripsReasoningOnDeepSeekToolCalls,TestEmptyFinalNotice; tightenedTestBuildRequestDropsReasoningOnPlainAssistantTurn