mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-28 06:51:16 +08:00
fix(agent): ordering fix in _copy_reasoning_content_for_api — cross-provider reasoning isolation
Fix logic-ordering bug where normalized_reasoning promotion returns before the DeepSeek/Kimi needs_empty_reasoning guard, causing cross-provider reasoning content (MiniMax → DeepSeek) to leak into reasoning_content and trigger HTTP 400. Changes: - Reorder branching: existing reasoning_content check first - Add 'not has_reasoning' guard so poisoned histories (no reasoning) still get '' injected for DeepSeek/Kimi - Healthy same-provider reasoning promotion path unchanged Refs: #15250, #15213
This commit is contained in:
41
run_agent.py
41
run_agent.py
@@ -7744,25 +7744,42 @@ class AIAgent:
|
||||
if source_msg.get("role") != "assistant":
|
||||
return
|
||||
|
||||
explicit_reasoning = source_msg.get("reasoning_content")
|
||||
if isinstance(explicit_reasoning, str):
|
||||
api_msg["reasoning_content"] = explicit_reasoning
|
||||
# 1. Explicit reasoning_content already set — preserve it verbatim
|
||||
# (includes DeepSeek/Kimi's own empty-string placeholder written at
|
||||
# creation time, and any valid reasoning content from the same provider).
|
||||
existing = source_msg.get("reasoning_content")
|
||||
if isinstance(existing, str):
|
||||
api_msg["reasoning_content"] = existing
|
||||
return
|
||||
|
||||
# 2. DeepSeek / Kimi thinking mode: tool-call turns with neither
|
||||
# reasoning_content nor reasoning are "poisoned history" — a prior
|
||||
# provider (MiniMax, etc.) left them empty. DeepSeek rejects HTTP 400
|
||||
# if reasoning_content is absent on replay. Inject "" to satisfy the
|
||||
# provider's requirement without forwarding cross-provider content.
|
||||
has_reasoning = isinstance(source_msg.get("reasoning"), str) and source_msg.get("reasoning")
|
||||
needs_empty_reasoning = (
|
||||
not has_reasoning
|
||||
and source_msg.get("tool_calls")
|
||||
and (
|
||||
self._needs_kimi_tool_reasoning()
|
||||
or self._needs_deepseek_tool_reasoning()
|
||||
)
|
||||
)
|
||||
if needs_empty_reasoning:
|
||||
api_msg["reasoning_content"] = ""
|
||||
return
|
||||
|
||||
# 3. Healthy session: promote 'reasoning' field to 'reasoning_content'
|
||||
# for providers that use the internal 'reasoning' key.
|
||||
normalized_reasoning = source_msg.get("reasoning")
|
||||
if isinstance(normalized_reasoning, str) and normalized_reasoning:
|
||||
api_msg["reasoning_content"] = normalized_reasoning
|
||||
return
|
||||
|
||||
# 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"] = ""
|
||||
# 4. 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)
|
||||
|
||||
@staticmethod
|
||||
def _sanitize_tool_calls_for_strict_api(api_msg: dict) -> dict:
|
||||
|
||||
Reference in New Issue
Block a user