From ad0ac894783a3e30a617bb252b167926899123e1 Mon Sep 17 00:00:00 2001 From: FocusFlow Dev Date: Sat, 25 Apr 2026 10:42:53 +0800 Subject: [PATCH] fix: DeepSeek/Kimi thinking mode requires reasoning_content on ALL assistant messages Previously _copy_reasoning_content_for_api only padded reasoning_content when the assistant message had tool_calls. DeepSeek V4 thinking mode requires the field on every assistant turn, including plain text replies without tool_calls. - Remove the 'source_msg.get("tool_calls") and' guard - Update test: plain assistant turns now get padded for DeepSeek/Kimi Fixes #15213 --- run_agent.py | 12 +++++++++++- .../test_deepseek_reasoning_content_echo.py | 6 +++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/run_agent.py b/run_agent.py index 6c73cf3881..aae61643f4 100644 --- a/run_agent.py +++ b/run_agent.py @@ -7785,7 +7785,17 @@ class AIAgent: api_msg["reasoning_content"] = normalized_reasoning return - # 4. reasoning_content was present but not a string (e.g. None after + # 4. DeepSeek / Kimi thinking mode: all assistant messages need + # reasoning_content. Inject "" to satisfy the provider's requirement + # when no explicit reasoning content is present. + if ( + self._needs_kimi_tool_reasoning() + or self._needs_deepseek_tool_reasoning() + ): + api_msg["reasoning_content"] = "" + return + + # 5. reasoning_content was present but not a string (e.g. None after # context compaction). Don't pass null to the API. api_msg.pop("reasoning_content", None) diff --git a/tests/run_agent/test_deepseek_reasoning_content_echo.py b/tests/run_agent/test_deepseek_reasoning_content_echo.py index 98feea8599..eb31d1760e 100644 --- a/tests/run_agent/test_deepseek_reasoning_content_echo.py +++ b/tests/run_agent/test_deepseek_reasoning_content_echo.py @@ -88,13 +88,13 @@ class TestCopyReasoningContentForApi: agent._copy_reasoning_content_for_api(source, api_msg) assert api_msg.get("reasoning_content") == "" - def test_deepseek_assistant_no_tool_call_left_alone(self) -> None: - """Plain assistant turns without tool_calls don't get padded.""" + def test_deepseek_assistant_no_tool_call_gets_padded(self) -> None: + """DeepSeek thinking mode pads ALL assistant turns, even without tool_calls.""" agent = _make_agent(provider="deepseek", model="deepseek-v4-flash") source = {"role": "assistant", "content": "hello"} api_msg: dict = {} agent._copy_reasoning_content_for_api(source, api_msg) - assert "reasoning_content" not in api_msg + assert api_msg.get("reasoning_content") == "" def test_deepseek_explicit_reasoning_content_preserved(self) -> None: """When reasoning_content is already set, it's copied verbatim."""