mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-04-30 14:56:01 +00:00
fix(provider): handle incomplete DeepSeek reasoning history
This commit is contained in:
parent
3b82e14f85
commit
82b8a3af7e
@ -384,6 +384,47 @@ class OpenAICompatProvider(LLMProvider):
|
|||||||
clean["tool_call_id"] = map_id(clean["tool_call_id"])
|
clean["tool_call_id"] = map_id(clean["tool_call_id"])
|
||||||
return self._enforce_role_alternation(sanitized)
|
return self._enforce_role_alternation(sanitized)
|
||||||
|
|
||||||
|
def _drop_deepseek_incomplete_reasoning_history(
|
||||||
|
self,
|
||||||
|
messages: list[dict[str, Any]],
|
||||||
|
reasoning_effort: str | None,
|
||||||
|
) -> list[dict[str, Any]]:
|
||||||
|
if (
|
||||||
|
not self._spec
|
||||||
|
or self._spec.name != "deepseek"
|
||||||
|
or not reasoning_effort
|
||||||
|
or reasoning_effort.lower() == "none"
|
||||||
|
):
|
||||||
|
return messages
|
||||||
|
|
||||||
|
bad_idx = None
|
||||||
|
for idx, msg in enumerate(messages):
|
||||||
|
if (
|
||||||
|
msg.get("role") == "assistant"
|
||||||
|
and msg.get("tool_calls")
|
||||||
|
and not msg.get("reasoning_content")
|
||||||
|
):
|
||||||
|
bad_idx = idx
|
||||||
|
if bad_idx is None:
|
||||||
|
return messages
|
||||||
|
|
||||||
|
keep_from = None
|
||||||
|
for idx in range(bad_idx + 1, len(messages)):
|
||||||
|
if messages[idx].get("role") == "user":
|
||||||
|
keep_from = idx
|
||||||
|
break
|
||||||
|
|
||||||
|
if keep_from is None:
|
||||||
|
trimmed = messages[:bad_idx]
|
||||||
|
else:
|
||||||
|
prefix = [msg for msg in messages[:keep_from] if msg.get("role") == "system"]
|
||||||
|
trimmed = prefix + messages[keep_from:]
|
||||||
|
logger.warning(
|
||||||
|
"Dropped {} DeepSeek thinking history message(s) with incomplete reasoning_content",
|
||||||
|
len(messages) - len(trimmed),
|
||||||
|
)
|
||||||
|
return trimmed
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# Build kwargs
|
# Build kwargs
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
@ -424,6 +465,10 @@ class OpenAICompatProvider(LLMProvider):
|
|||||||
if spec and spec.strip_model_prefix:
|
if spec and spec.strip_model_prefix:
|
||||||
model_name = model_name.split("/")[-1]
|
model_name = model_name.split("/")[-1]
|
||||||
|
|
||||||
|
messages = self._drop_deepseek_incomplete_reasoning_history(
|
||||||
|
messages,
|
||||||
|
reasoning_effort,
|
||||||
|
)
|
||||||
kwargs: dict[str, Any] = {
|
kwargs: dict[str, Any] = {
|
||||||
"model": model_name,
|
"model": model_name,
|
||||||
"messages": self._sanitize_messages(self._sanitize_empty_content(messages)),
|
"messages": self._sanitize_messages(self._sanitize_empty_content(messages)),
|
||||||
|
|||||||
@ -585,6 +585,81 @@ def test_openai_compat_preserves_message_level_reasoning_fields() -> None:
|
|||||||
assert sanitized[1]["tool_calls"][0]["extra_content"] == {"google": {"thought_signature": "sig"}}
|
assert sanitized[1]["tool_calls"][0]["extra_content"] == {"google": {"thought_signature": "sig"}}
|
||||||
|
|
||||||
|
|
||||||
|
def _deepseek_kwargs(messages: list[dict]) -> dict:
|
||||||
|
with patch("nanobot.providers.openai_compat_provider.AsyncOpenAI"):
|
||||||
|
provider = OpenAICompatProvider(
|
||||||
|
api_key="sk-test",
|
||||||
|
default_model="deepseek-v4-flash",
|
||||||
|
spec=find_by_name("deepseek"),
|
||||||
|
)
|
||||||
|
|
||||||
|
return provider._build_kwargs(
|
||||||
|
messages=messages,
|
||||||
|
tools=None,
|
||||||
|
model="deepseek-v4-flash",
|
||||||
|
max_tokens=1024,
|
||||||
|
temperature=0.7,
|
||||||
|
reasoning_effort="high",
|
||||||
|
tool_choice=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _tool_call(call_id: str) -> dict:
|
||||||
|
return {
|
||||||
|
"id": call_id,
|
||||||
|
"type": "function",
|
||||||
|
"function": {"name": "my", "arguments": "{}"},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_deepseek_thinking_drops_tool_history_missing_reasoning_content() -> None:
|
||||||
|
kwargs = _deepseek_kwargs([
|
||||||
|
{"role": "system", "content": "system"},
|
||||||
|
{"role": "user", "content": "can we use wechat?"},
|
||||||
|
{"role": "assistant", "content": "", "tool_calls": [_tool_call("call_bad")]},
|
||||||
|
{"role": "tool", "tool_call_id": "call_bad", "name": "my", "content": "channels"},
|
||||||
|
{"role": "user", "content": "continue"},
|
||||||
|
])
|
||||||
|
|
||||||
|
assert kwargs["messages"] == [
|
||||||
|
{"role": "system", "content": "system"},
|
||||||
|
{"role": "user", "content": "continue"},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_deepseek_thinking_keeps_tool_history_with_reasoning_content() -> None:
|
||||||
|
kwargs = _deepseek_kwargs([
|
||||||
|
{"role": "user", "content": "can we use wechat?"},
|
||||||
|
{
|
||||||
|
"role": "assistant",
|
||||||
|
"content": "",
|
||||||
|
"reasoning_content": "I should inspect supported channels.",
|
||||||
|
"tool_calls": [_tool_call("call_good")],
|
||||||
|
},
|
||||||
|
{"role": "tool", "tool_call_id": "call_good", "name": "my", "content": "channels"},
|
||||||
|
{"role": "user", "content": "continue"},
|
||||||
|
])
|
||||||
|
|
||||||
|
assistant = kwargs["messages"][1]
|
||||||
|
assert assistant["role"] == "assistant"
|
||||||
|
assert assistant["reasoning_content"] == "I should inspect supported channels."
|
||||||
|
assert kwargs["messages"][2]["role"] == "tool"
|
||||||
|
|
||||||
|
|
||||||
|
def test_deepseek_thinking_drops_current_bad_tool_turn_without_followup_user() -> None:
|
||||||
|
kwargs = _deepseek_kwargs([
|
||||||
|
{"role": "system", "content": "system"},
|
||||||
|
{"role": "user", "content": "can we use wechat?"},
|
||||||
|
{"role": "assistant", "content": "", "tool_calls": [_tool_call("call_bad")]},
|
||||||
|
{"role": "tool", "tool_call_id": "call_bad", "name": "my", "content": "channels"},
|
||||||
|
])
|
||||||
|
|
||||||
|
assert kwargs["messages"] == [
|
||||||
|
{"role": "system", "content": "system"},
|
||||||
|
{"role": "user", "content": "can we use wechat?"},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def test_openai_compat_keeps_tool_calls_after_consecutive_assistant_messages() -> None:
|
def test_openai_compat_keeps_tool_calls_after_consecutive_assistant_messages() -> None:
|
||||||
with patch("nanobot.providers.openai_compat_provider.AsyncOpenAI"):
|
with patch("nanobot.providers.openai_compat_provider.AsyncOpenAI"):
|
||||||
provider = OpenAICompatProvider()
|
provider = OpenAICompatProvider()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user