fix(agent): extract thinking from content-list blocks for DeepSeek V4 Pro#22643
Closed
wesleysimplicio wants to merge 1 commit into
Closed
fix(agent): extract thinking from content-list blocks for DeepSeek V4 Pro#22643wesleysimplicio wants to merge 1 commit into
wesleysimplicio wants to merge 1 commit into
Conversation
… 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
Contributor
There was a problem hiding this comment.
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 scancontentlists fortype: "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 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" | ||
|
|
This was referenced May 9, 2026
Merged
Contributor
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
DeepSeek V4 Pro returns thinking content as typed blocks inside the
contentarray:[ {"type": "thinking", "thinking": "deep analysis..."}, {"type": "output", "text": "final answer"} ]_extract_reasoningonly handledcontentas a plain string, so the thinking text was silently dropped. On the next turn the missing block caused:Root Cause
Lines 3531–3545 in
run_agent.py:The
isinstance(content, str)guard skips the branch entirely whencontentis 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
contentis a list and no structured reasoning field was already found, scan for{"type": "thinking"}items and accumulate their"thinking"value intoreasoning_parts. Structured fields (reasoning,reasoning_content,reasoning_details) still take priority — existing provider behaviour is unchanged.Tests
Three new tests added to
TestExtractReasoningintests/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 structuredreasoningfield wins when both are present.Closes #21944