refactor(deepseek-reasoning): consolidate detection into helpers + regression tests

Extracts _needs_kimi_tool_reasoning() for symmetry with the existing
_needs_deepseek_tool_reasoning() helper, so _copy_reasoning_content_for_api
uses the same detection logic as _build_assistant_message. Future changes
to either provider's signals now only touch one function.

Adds tests/run_agent/test_deepseek_reasoning_content_echo.py covering:
- All 3 DeepSeek detection signals (provider, model, host)
- Poisoned history replay (empty string fallback)
- Plain assistant turns NOT padded
- Explicit reasoning_content preserved
- Reasoning field promoted to reasoning_content
- Existing Kimi/Moonshot detection intact
- Non-thinking providers left alone

21 tests, all pass.
This commit is contained in:
Teknium 2026-04-24 16:08:22 -07:00 committed by Teknium
parent e93cc934c7
commit d58b305adf
2 changed files with 238 additions and 15 deletions

View file

@ -7706,13 +7706,26 @@ class AIAgent:
return msg
def _needs_kimi_tool_reasoning(self) -> bool:
"""Return True when the current provider is Kimi / Moonshot thinking mode.
Kimi ``/coding`` and Moonshot thinking mode both require
``reasoning_content`` on every assistant tool-call message; omitting
it causes the next replay to fail with HTTP 400.
"""
return (
self.provider in {"kimi-coding", "kimi-coding-cn"}
or base_url_host_matches(self.base_url, "api.kimi.com")
or base_url_host_matches(self.base_url, "moonshot.ai")
or base_url_host_matches(self.base_url, "moonshot.cn")
)
def _needs_deepseek_tool_reasoning(self) -> bool:
"""Return True when the current provider is DeepSeek thinking mode.
Used to decide whether to store reasoning_content on tool-call
assistant messages. DeepSeek V4 thinking mode requires this field
on every assistant tool-call turn; omitting it causes HTTP 400
when the message is replayed in a subsequent API request (#15250).
DeepSeek V4 thinking mode requires ``reasoning_content`` on every
assistant tool-call turn; omitting it causes HTTP 400 when the
message is replayed in a subsequent API request (#15250).
"""
provider = (self.provider or "").lower()
model = (self.model or "").lower()
@ -7737,17 +7750,14 @@ class AIAgent:
api_msg["reasoning_content"] = normalized_reasoning
return
provider = (self.provider or "").lower()
model = (self.model or "").lower()
needs_tool_reasoning_echo = (
provider in {"kimi-coding", "kimi-coding-cn", "deepseek"}
or "deepseek" in model
or base_url_host_matches(self.base_url, "api.kimi.com")
or base_url_host_matches(self.base_url, "moonshot.ai")
or base_url_host_matches(self.base_url, "moonshot.cn")
or base_url_host_matches(self.base_url, "api.deepseek.com")
)
if needs_tool_reasoning_echo and source_msg.get("tool_calls"):
# Providers that require an echoed reasoning_content on every
# assistant tool-call turn. Detection logic lives in the per-provider
# helpers so both the creation path (_build_assistant_message) and
# this replay path stay in sync.
if source_msg.get("tool_calls") and (
self._needs_kimi_tool_reasoning()
or self._needs_deepseek_tool_reasoning()
):
api_msg["reasoning_content"] = ""
@staticmethod