core/deepseek: inject thinking parameter to satisfy API reasoning mode requirement

DeepSeek's reasoning model requires the `thinking` parameter to be
explicitly set in every request (default is enabled). The agent loop
constructs the base ChatCompletionsParam which lacks this field, so the
DeepseekOpenAIClient now injects `thinking: {type: "enabled"}` into the
JSON request body after msgspec encoding.

Fixes the HTTP 400 error:
"The `reasoning_content` in the thinking mode must be passed back to the API."
which occurred when a cancelled turn was retried and the request history
contained assistant messages with reasoning_content but no thinking param.
This commit is contained in:
2026-07-28 23:32:02 +08:00
parent fc98b080e1
commit 3a5c715e8f
@@ -1,3 +1,6 @@
from __future__ import annotations
import json
from typing import TYPE_CHECKING, Literal, overload
import msgspec
@@ -13,6 +16,19 @@ if TYPE_CHECKING:
from .model import ChatCompletionsParam
def _inject_thinking(data: bytes) -> bytes:
"""Inject ``thinking: {type: "enabled"}`` into the encoded request body.
DeepSeek's reasoning model requires the ``thinking`` parameter to be
explicitly set (default is ``enabled``). The agent loop constructs the
base ``ChatCompletionsParam`` which lacks this field, so we add it
after msgspec encoding.
"""
body = json.loads(data)
body["thinking"] = {"type": "enabled"}
return json.dumps(body, separators=(",", ":")).encode("utf-8")
class DeepseekOpenAIClient(BaseOpenAIClient):
def __init__(self, config: OpenAIConfig) -> None:
super().__init__(config)
@@ -31,7 +47,7 @@ class DeepseekOpenAIClient(BaseOpenAIClient):
self, param: ChatCompletionsParam, *, stream: bool = False
) -> ChatCompletionResponse | AsyncIterator[ChatCompletionChunk]:
param = coerce_chat_completions_param_any(msgspec.structs.replace(param, stream=stream))
data = self.encoder.encode(param)
data = _inject_thinking(self.encoder.encode(param))
if stream:
resp = await self.session.post(
"/chat/completions",