Skip to content

Commit fccb58d

Browse files
committed
feat: enhance request payload handling for DeepSeek API compatibility
1 parent 48cf976 commit fccb58d

File tree

1 file changed

+53
-1
lines changed

1 file changed

+53
-1
lines changed

apps/models_provider/impl/base_chat_open_ai.py

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
# coding=utf-8
22
import base64
3+
import json
34
from concurrent.futures import ThreadPoolExecutor
45
from requests.exceptions import ConnectTimeout, ReadTimeout
56
from typing import Dict, Optional, Any, Iterator, cast, Union, Sequence, Callable, Mapping
67

78
from langchain_core.language_models import LanguageModelInput
89
from langchain_core.messages import BaseMessage, get_buffer_string, BaseMessageChunk, HumanMessageChunk, AIMessageChunk, \
910
SystemMessageChunk, FunctionMessageChunk, ChatMessageChunk
10-
from langchain_core.messages.ai import UsageMetadata
11+
from langchain_core.messages.ai import UsageMetadata, AIMessage
1112
from langchain_core.messages.tool import tool_call_chunk, ToolMessageChunk
1213
from langchain_core.outputs import ChatGenerationChunk
1314
from langchain_core.runnables import RunnableConfig, ensure_config
@@ -218,6 +219,57 @@ def invoke(
218219
'token_usage'] if 'token_usage' in chat_result.response_metadata else chat_result.usage_metadata
219220
return chat_result
220221

222+
def _get_request_payload(
223+
self,
224+
input_: LanguageModelInput,
225+
*,
226+
stop: list[str] | None = None,
227+
**kwargs: Any,
228+
) -> dict:
229+
# Get original messages to preserve reasoning_content before base conversion
230+
messages = self._convert_input(input_).to_messages()
231+
# Store reasoning_content for AIMessages with tool_calls
232+
# According to DeepSeek API docs, reasoning_content is REQUIRED when tool_calls
233+
# are present during the tool invocation process (within same question/turn).
234+
# See: https://api-docs.deepseek.com/guides/thinking_mode#tool-calls
235+
reasoning_content_map = {}
236+
for i, msg in enumerate(messages):
237+
if (
238+
isinstance(msg, AIMessage)
239+
and (msg.tool_calls or msg.invalid_tool_calls)
240+
and (reasoning := msg.additional_kwargs.get("reasoning_content"))
241+
):
242+
reasoning_content_map[i] = reasoning
243+
244+
payload = super()._get_request_payload(input_, stop=stop, **kwargs)
245+
246+
# Restore reasoning_content for assistant messages with tool_calls
247+
# This is required by DeepSeek API - missing it causes 400 error
248+
if "messages" in payload and reasoning_content_map:
249+
for i, message in enumerate(payload["messages"]):
250+
if (
251+
i in reasoning_content_map
252+
and message.get("role") == "assistant"
253+
and message.get("tool_calls")
254+
):
255+
message["reasoning_content"] = reasoning_content_map[i]
256+
257+
# Apply DeepSeek-specific message formatting
258+
for message in payload["messages"]:
259+
if message["role"] == "tool" and isinstance(message["content"], list):
260+
message["content"] = json.dumps(message["content"])
261+
elif message["role"] == "assistant" and isinstance(
262+
message["content"], list
263+
):
264+
# DeepSeek API expects assistant content to be a string, not a list.
265+
# Extract text blocks and join them, or use empty string if none exist.
266+
text_parts = [
267+
block.get("text", "")
268+
for block in message["content"]
269+
if isinstance(block, dict) and block.get("type") == "text"
270+
]
271+
message["content"] = "".join(text_parts) if text_parts else ""
272+
return payload
221273

222274
def upload_file_and_get_url(self, file_stream, file_name):
223275
"""上传文件并获取文件URL"""

0 commit comments

Comments
 (0)