Skip to content

Commit a1d4fd5

Browse files
committed
feat: enhance request payload handling for DeepSeek API integration
1 parent fccb58d commit a1d4fd5

File tree

2 files changed

+65
-58
lines changed

2 files changed

+65
-58
lines changed

apps/models_provider/impl/base_chat_open_ai.py

Lines changed: 8 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,31 @@
11
# coding=utf-8
22
import base64
3-
import json
43
from concurrent.futures import ThreadPoolExecutor
5-
from requests.exceptions import ConnectTimeout, ReadTimeout
64
from typing import Dict, Optional, Any, Iterator, cast, Union, Sequence, Callable, Mapping
75

86
from langchain_core.language_models import LanguageModelInput
97
from langchain_core.messages import BaseMessage, get_buffer_string, BaseMessageChunk, HumanMessageChunk, AIMessageChunk, \
108
SystemMessageChunk, FunctionMessageChunk, ChatMessageChunk
11-
from langchain_core.messages.ai import UsageMetadata, AIMessage
9+
from langchain_core.messages.ai import UsageMetadata
1210
from langchain_core.messages.tool import tool_call_chunk, ToolMessageChunk
1311
from langchain_core.outputs import ChatGenerationChunk
1412
from langchain_core.runnables import RunnableConfig, ensure_config
1513
from langchain_core.tools import BaseTool
1614
from langchain_openai import ChatOpenAI
1715
from langchain_openai.chat_models.base import _create_usage_metadata
16+
from requests.exceptions import ReadTimeout
1817

1918
from common.config.tokenizer_manage_config import TokenizerManage
2019
from common.utils.logger import maxkb_logger
2120

21+
2222
def custom_get_token_ids(text: str):
2323
tokenizer = TokenizerManage.get_tokenizer()
2424
return tokenizer.encode(text)
2525

26+
2627
def _convert_delta_to_message_chunk(
27-
_dict: Mapping[str, Any], default_class: type[BaseMessageChunk]
28+
_dict: Mapping[str, Any], default_class: type[BaseMessageChunk]
2829
) -> BaseMessageChunk:
2930
"""Convert to a LangChain message chunk."""
3031
id_ = _dict.get("id")
@@ -80,6 +81,7 @@ def _convert_delta_to_message_chunk(
8081
return ChatMessageChunk(content=content, role=role, id=id_)
8182
return default_class(content=content, id=id_) # type: ignore[call-arg]#
8283

84+
8385
class BaseChatOpenAI(ChatOpenAI):
8486
usage_metadata: dict = {}
8587
custom_get_token_ids = custom_get_token_ids
@@ -219,64 +221,13 @@ def invoke(
219221
'token_usage'] if 'token_usage' in chat_result.response_metadata else chat_result.usage_metadata
220222
return chat_result
221223

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
273-
274224
def upload_file_and_get_url(self, file_stream, file_name):
275225
"""上传文件并获取文件URL"""
276226
base64_video = base64.b64encode(file_stream).decode("utf-8")
277227
video_format = get_video_format(file_name)
278228
return f'data:{video_format};base64,{base64_video}'
279229

230+
280231
def get_video_format(file_name):
281232
extension = file_name.split('.')[-1].lower()
282233
format_map = {
@@ -285,4 +236,4 @@ def get_video_format(file_name):
285236
'mov': 'video/mov',
286237
'wmv': 'video/x-ms-wmv'
287238
}
288-
return format_map.get(extension, 'video/mp4')
239+
return format_map.get(extension, 'video/mp4')

apps/models_provider/impl/deepseek_model_provider/model/llm.py

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66
@Author :Brian Yang
77
@Date :5/12/24 7:44 AM
88
"""
9-
from typing import Dict
9+
import json
10+
from typing import Dict, Any
11+
12+
from langchain_core.language_models import LanguageModelInput
13+
from langchain_core.messages import AIMessage
1014

1115
from models_provider.base_model_provider import MaxKBBaseModel
1216
from models_provider.impl.base_chat_open_ai import BaseChatOpenAI
@@ -29,3 +33,55 @@ def new_instance(model_type, model_name, model_credential: Dict[str, object], **
2933
extra_body=optional_params
3034
)
3135
return deepseek_chat_open_ai
36+
37+
def _get_request_payload(
38+
self,
39+
input_: LanguageModelInput,
40+
*,
41+
stop: list[str] | None = None,
42+
**kwargs: Any,
43+
) -> dict:
44+
# Get original messages to preserve reasoning_content before base conversion
45+
messages = self._convert_input(input_).to_messages()
46+
# Store reasoning_content for AIMessages with tool_calls
47+
# According to DeepSeek API docs, reasoning_content is REQUIRED when tool_calls
48+
# are present during the tool invocation process (within same question/turn).
49+
# See: https://api-docs.deepseek.com/guides/thinking_mode#tool-calls
50+
reasoning_content_map = {}
51+
for i, msg in enumerate(messages):
52+
if (
53+
isinstance(msg, AIMessage)
54+
and (msg.tool_calls or msg.invalid_tool_calls)
55+
and (reasoning := msg.additional_kwargs.get("reasoning_content"))
56+
):
57+
reasoning_content_map[i] = reasoning
58+
59+
payload = super()._get_request_payload(input_, stop=stop, **kwargs)
60+
61+
# Restore reasoning_content for assistant messages with tool_calls
62+
# This is required by DeepSeek API - missing it causes 400 error
63+
if "messages" in payload and reasoning_content_map:
64+
for i, message in enumerate(payload["messages"]):
65+
if (
66+
i in reasoning_content_map
67+
and message.get("role") == "assistant"
68+
and message.get("tool_calls")
69+
):
70+
message["reasoning_content"] = reasoning_content_map[i]
71+
72+
# Apply DeepSeek-specific message formatting
73+
for message in payload["messages"]:
74+
if message["role"] == "tool" and isinstance(message["content"], list):
75+
message["content"] = json.dumps(message["content"])
76+
elif message["role"] == "assistant" and isinstance(
77+
message["content"], list
78+
):
79+
# DeepSeek API expects assistant content to be a string, not a list.
80+
# Extract text blocks and join them, or use empty string if none exist.
81+
text_parts = [
82+
block.get("text", "")
83+
for block in message["content"]
84+
if isinstance(block, dict) and block.get("type") == "text"
85+
]
86+
message["content"] = "".join(text_parts) if text_parts else ""
87+
return payload

0 commit comments

Comments
 (0)