mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-09-20 19:46:16 +00:00
* fix(models): preserve DeepSeek thinking tool history * fix(runtime): harden model response recovery * fix(runtime): tighten model response recovery * fix(runtime): protect run-scoped retry state * fix(runtime): complete model recovery review fixes * fix(runtime): preserve empty-response diagnostics * fix(runtime): strip native tool calls on length caps * docs(middleware): fit recovery guidance within inherited size limit --------- Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
103 lines
3.9 KiB
Python
103 lines
3.9 KiB
Python
"""Patched ChatDeepSeek that preserves reasoning_content in multi-turn conversations.
|
|
|
|
This module provides a patched version of ChatDeepSeek that properly handles
|
|
reasoning_content when sending messages back to the API. The original implementation
|
|
stores reasoning_content in additional_kwargs but doesn't include it when making
|
|
subsequent API calls, which causes errors with APIs that require reasoning_content
|
|
on all assistant messages when thinking mode is enabled.
|
|
"""
|
|
|
|
from typing import Any
|
|
|
|
from langchain_core.language_models import LanguageModelInput
|
|
from langchain_core.messages import AIMessage
|
|
from langchain_deepseek import ChatDeepSeek
|
|
|
|
from deerflow.models.assistant_payload_replay import restore_assistant_payloads, restore_reasoning_content
|
|
|
|
|
|
def _thinking_enabled(*sources: Any) -> bool:
|
|
"""Return whether the request explicitly enables DeepSeek thinking mode."""
|
|
for source in sources:
|
|
if not isinstance(source, dict):
|
|
continue
|
|
thinking = source.get("thinking")
|
|
if isinstance(thinking, dict) and thinking.get("type") == "enabled":
|
|
return True
|
|
extra_body = source.get("extra_body")
|
|
if isinstance(extra_body, dict):
|
|
nested = extra_body.get("thinking")
|
|
if isinstance(nested, dict) and nested.get("type") == "enabled":
|
|
return True
|
|
return False
|
|
|
|
|
|
def _restore_deepseek_assistant_payload(
|
|
payload_msg: dict[str, Any],
|
|
orig_msg: AIMessage,
|
|
*,
|
|
thinking_enabled: bool,
|
|
) -> None:
|
|
"""Restore assistant history and required thinking-mode placeholders."""
|
|
restore_reasoning_content(payload_msg, orig_msg)
|
|
has_tool_calls = bool(payload_msg.get("tool_calls"))
|
|
if has_tool_calls and payload_msg.get("content") is None:
|
|
# DeepSeek requires an empty string, rather than null, for tool-call history.
|
|
payload_msg["content"] = ""
|
|
if thinking_enabled and has_tool_calls and "reasoning_content" not in payload_msg:
|
|
# Thinking-mode tool turns require this field even when no reasoning was emitted.
|
|
payload_msg["reasoning_content"] = ""
|
|
|
|
|
|
class PatchedChatDeepSeek(ChatDeepSeek):
|
|
"""ChatDeepSeek with proper reasoning_content preservation.
|
|
|
|
When using thinking/reasoning enabled models, the API expects reasoning_content
|
|
to be present on ALL assistant messages in multi-turn conversations. This patched
|
|
version ensures reasoning_content from additional_kwargs is included in the
|
|
request payload.
|
|
"""
|
|
|
|
@classmethod
|
|
def is_lc_serializable(cls) -> bool:
|
|
return True
|
|
|
|
@property
|
|
def lc_secrets(self) -> dict[str, str]:
|
|
return {"api_key": "DEEPSEEK_API_KEY", "openai_api_key": "DEEPSEEK_API_KEY"}
|
|
|
|
def _get_request_payload(
|
|
self,
|
|
input_: LanguageModelInput,
|
|
*,
|
|
stop: list[str] | None = None,
|
|
**kwargs: Any,
|
|
) -> dict:
|
|
"""Get request payload with reasoning_content preserved.
|
|
|
|
Overrides the parent method to inject reasoning_content from
|
|
additional_kwargs into assistant messages in the payload.
|
|
"""
|
|
original_messages = self._convert_input(input_).to_messages()
|
|
request_messages = [message for message in original_messages if not (isinstance(message, AIMessage) and (message.additional_kwargs or {}).get("deerflow_error_fallback"))]
|
|
|
|
# Call parent to get the base payload
|
|
payload = super()._get_request_payload(request_messages, stop=stop, **kwargs)
|
|
|
|
request_thinking_enabled = _thinking_enabled(
|
|
payload,
|
|
kwargs,
|
|
{"extra_body": getattr(self, "extra_body", None)},
|
|
)
|
|
restore_assistant_payloads(
|
|
payload.get("messages", []),
|
|
request_messages,
|
|
lambda payload_msg, orig_msg: _restore_deepseek_assistant_payload(
|
|
payload_msg,
|
|
orig_msg,
|
|
thinking_enabled=request_thinking_enabled,
|
|
),
|
|
)
|
|
|
|
return payload
|