fix(provider): complete DeepSeek reasoning_content round-trip for multi-turn conversations#24250
fix(provider): complete DeepSeek reasoning_content round-trip for multi-turn conversations#24250knefenk wants to merge 3 commits intoanomalyco:devfrom
Conversation
|
The following comment was made by an LLM, it may be inaccurate: Based on my search, I found the following related PRs: Potential Related PRs
NoteThe current PR #24250 is not a duplicate itself — it's a comprehensive fix that extends PR #24218 to cover the complete two-layer bug where |
…ti-turn conversations
Fixes the two-layer bug where reasoning_content is dropped on conversation
replay for DeepSeek thinking mode and OpenRouter-routed DeepSeek models.
Three changes:
1. provider.ts: Auto-enable interleaved for reasoning models
- When model.reasoning is true but interleaved is not explicitly set,
default to { field: "reasoning_content" } instead of false
- This triggers the interleaved transform that extracts reasoning
and passes it via providerOptions
2. transform.ts: Use dynamic SDK key in interleaved transform
- Replace hardcoded "openaiCompatible" with sdkKey(model.api.npm)
- Fixes OpenRouter provider which expects "openrouter" key, not
"openaiCompatible" (prevents key mismatch in providerOptions)
3. transform.ts: Inject reasoning_content for ALL assistant messages
- New fallback transform fires when capabilities.reasoning is true
- Sets reasoning_content: "" in providerOptions for every assistant
message, including historical messages stored before reasoning mode
was enabled (no reasoning part to extract from)
- Also expands DeepSeek detection to check model.id in addition to
model.api.id, covering OpenRouter-routed DeepSeek models
Closes anomalyco#24104
Related: anomalyco#24203 (OpenRouter users still affected by PR anomalyco#24218 alone)
Supersedes partial fix from PR anomalyco#24146 (merged but incomplete)
9ee61bb to
b6a7cdb
Compare
…ges in reasoning fallback The fallback transform only set reasoning_content in providerOptions for array-content messages. String-content assistant messages (e.g., "It's 4.") were converted to array form but didn't get providerOptions set. Now both content types get reasoning_content: "" injected with the correct SDK key, ensuring DeepSeek's API receives it on all assistant turns.
Consideration: Scoping the fix to avoid unintended side effects on other reasoning modelsFirst commit auto-enables This is likely harmless for non-DeepSeek models because:
However, if you want to be conservative and scope the fix to only providers that actually need this pattern, the fallback could be gated more narrowly: // Instead of:
if (model.capabilities.reasoning) {
// Consider:
if (model.capabilities.reasoning && (
model.api.id.includes("deepseek") ||
model.id.includes("deepseek") ||
model.api.npm === "@ai-sdk/openai-compatible" ||
model.api.npm === "@openrouter/ai-sdk-provider"
)) {This ensures the
It would leave other reasoning models (Claude thinking, o-series, Gemini) unaffected and avoid unnecessary providerOptions writes. Happy to push this as an additional commit if you prefer the narrower scope. Leaving it as-is is also fine if you're confident the extra fields are truly no-op for other providers. |
|
is this working for you? As you can see here #24190 (comment) this could be an openrouter issue too. |
|
@rekram1-node this PR addresses the reasoning_content round-trip bug discussed in #24093. The fix is client-side ( |
When the interleaved transform runs on subsequent requests (after DB round-trip), content parts no longer contain reasoning blocks (they were extracted on the first pass). The unconditional [field]: reasoningText overwrites the previously correct providerOptions.reasoning_content with empty string, causing DeepSeek 400: 'The reasoning_content in the thinking mode must be passed back to the API.' Fix: set [field]: reasoningText first, then spread existing providerOptions so that preserved values from DB take priority over empty reasoningText. Closes anomalyco#24442 (co-discovered with @claudianus)
eba2109 to
41eb35a
Compare
Update: Fixed second-pass regression (commit 41eb35a)Issue #24442 identified a regression where the interleaved transform overwrites existing with empty string on subsequent passes (after DB round-trip). Reproduced and confirmed — affects upstream dev (post-#24146), this PR, and the new #24443. The fix (41eb35a): Swapped the order of spread and set so existing values take priority: \
This is equivalent to the approach in PR #24443 — both are correct, maintainer can pick either. Reproduction script and full trace at #24442 (comment) |
Issue for this PR
Closes #24104
Related: #24203
Type of change
What does this PR do?
Fixes the complete two-layer bug where
reasoning_contentis dropped on conversation replay for DeepSeek thinking mode. PR #24218 addresses layer 1 only — this PR covers all three layers:Layer 1 —
interleavednot auto-enabled for reasoning modelsWhen a model has
reasoning: truebut no explicitinterleavedconfig, the interleaved transform is skipped entirely andreasoning_contentis never set inproviderOptions.Layer 2 — Hardcoded
openaiCompatiblekey breaks non-standard SDKsThe interleaved transform hardcodes
providerOptions.openaiCompatible, but OpenRouter's SDK key is"openrouter". The remap logic at line ~336 does not cover this because it only remaps frommodel.providerID, not fromopenaiCompatible.Layer 3 — Historical messages have no reasoning part
Messages stored in DB before reasoning mode was enabled have no reasoning part to extract. The interleaved transform only processes messages with existing reasoning parts, so these get skipped. DeepSeek rejects the request because
reasoning_contentis missing.Changes
interleaved: { field: "reasoning_content" }for models withreasoning: truesdkKey(model.api.npm)) instead of hardcoded"openaiCompatible"— fixes OpenRouter and other non-standard providersreasoning_content: ""for ALL assistant messages whencapabilities.reasoningis true — covers historical messages with no reasoning partmodel.idin addition tomodel.api.id— covers OpenRouter-routed DeepSeek modelsHow did you verify your code works?
"openrouter"key in providerOptionsreasoning_contentinjected when reasoning is activecapabilities.reasoning)interleavedconfig still takes priority (??operator)Screenshots / recordings
N/A
Checklist