Skip to content

fix(agent): extract thinking from content-list blocks for DeepSeek V4 Pro#22643

Closed
wesleysimplicio wants to merge 1 commit into
NousResearch:mainfrom
wesleysimplicio:fix/deepseek-thinking-content-array
Closed

fix(agent): extract thinking from content-list blocks for DeepSeek V4 Pro#22643
wesleysimplicio wants to merge 1 commit into
NousResearch:mainfrom
wesleysimplicio:fix/deepseek-thinking-content-array

Conversation

@wesleysimplicio

Copy link
Copy Markdown
Contributor

Summary

DeepSeek V4 Pro returns thinking content as typed blocks inside the content array:

[
  {"type": "thinking", "thinking": "deep analysis..."},
  {"type": "output", "text": "final answer"}
]

_extract_reasoning only handled content as a plain string, so the thinking text was silently dropped. On the next turn the missing block caused:

HTTP 400: The content[].thinking in the thinking mode must be passed back to the API.

Root Cause

Lines 3531–3545 in run_agent.py:

if not reasoning_parts and isinstance(content, str) and content:
    # inline <think> tag extraction …

The isinstance(content, str) guard skips the branch entirely when content is a list, which is exactly what DeepSeek V4 Pro returns in thinking mode.

Fix

Add a list-content branch before the string-content branch. When content is a list and no structured reasoning field was already found, scan for {"type": "thinking"} items and accumulate their "thinking" value into reasoning_parts. Structured fields (reasoning, reasoning_content, reasoning_details) still take priority — existing provider behaviour is unchanged.

Tests

Three new tests added to TestExtractReasoning in tests/run_agent/test_run_agent.py:

  • test_content_list_thinking_blocks_extracted — verifies thinking text is extracted from a DeepSeek-style content list.
  • test_content_list_non_thinking_blocks_ignored — verifies non-thinking block types are not treated as reasoning.
  • test_content_list_thinking_prefers_structured_field — verifies structured reasoning field wins when both are present.

Closes #21944

… Pro

DeepSeek V4 Pro returns thinking content as typed blocks inside the
content array rather than as a top-level reasoning_content field:

  [{"type": "thinking", "thinking": "..."}, {"type": "output", ...}]

_extract_reasoning only handled content as a plain string, so the
thinking text was silently dropped.  On the next turn the session was
replayed without the thinking block, causing:

  HTTP 400: The content[].thinking in the thinking mode must be
  passed back to the API.

Fix: when content is a list and no structured reasoning field was
found, scan for items with type=='thinking' and accumulate their
'thinking' (or 'text') value into reasoning_parts.  Structured fields
(reasoning, reasoning_content, reasoning_details) still take priority
so existing provider behaviour is unchanged.

Closes NousResearch#21944
Copilot AI review requested due to automatic review settings May 9, 2026 15:23

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 DeepSeek V4 Pro “thinking mode” multi-turn failures by attempting to extract reasoning from assistant_message.content when it is a typed-block list (e.g. [{type:"thinking", ...}, ...]) so that thinking can be persisted and replayed.

Changes:

  • Add a _extract_reasoning() branch to scan content lists for type: "thinking" blocks and accumulate their text.
  • Add regression tests covering list-content thinking extraction and precedence vs structured reasoning.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
run_agent.py Adds list-typed content parsing in _extract_reasoning() to capture DeepSeek-style thinking blocks.
tests/run_agent/test_run_agent.py Adds unit tests validating extraction behavior for DeepSeek-style content lists.

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

Comment thread run_agent.py
Comment on lines 3531 to +3544
content = getattr(assistant_message, "content", None)
if not reasoning_parts and isinstance(content, list):
# DeepSeek V4 Pro (and compatible providers) return content as a
# list of typed blocks, e.g.:
# [{"type": "thinking", "thinking": "..."}, {"type": "output", ...}]
# Without this branch the thinking text is silently dropped and the
# next turn fails with HTTP 400 ("thinking must be passed back").
# Refs #21944.
for block in content:
if isinstance(block, dict) and block.get("type") == "thinking":
thinking_text = block.get("thinking") or block.get("text") or ""
thinking_text = thinking_text.strip()
if thinking_text and thinking_text not in reasoning_parts:
reasoning_parts.append(thinking_text)
Comment on lines +520 to +534
def test_content_list_thinking_blocks_extracted(self, agent):
"""DeepSeek V4 Pro returns content as a typed-block list (issue #21944).

Without this branch thinking text is silently dropped → HTTP 400 on
the next turn ("thinking must be passed back to the API").
"""
msg = _mock_assistant_msg(
content=[
{"type": "thinking", "thinking": "deep analysis here"},
{"type": "output", "text": "final answer"},
]
)
result = agent._extract_reasoning(msg)
assert result == "deep analysis here"

@teknium1

teknium1 commented May 9, 2026

Copy link
Copy Markdown
Contributor

Merged via salvage PR #22798. salvage cherry-picked your commit; authorship preserved (also closes #21946 byte-identical duplicate). Thanks for the contribution!

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 P1 High — major feature broken, no workaround provider/deepseek DeepSeek API type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: DeepSeek V4 Pro thinking content not persisted in session (HTTP 400 on multi-turn)

4 participants