lmproto: carry thinking options on the wire

This commit is contained in:
2026-10-06 21:07:04 +08:00
parent ffd4dbbe26
commit 445aba7e5f
5 changed files with 106 additions and 7 deletions
+3
View File
@@ -34,6 +34,8 @@ class AnthropicClient:
decoder: msgspec.json.Decoder[AnthropicMessageResponse]
stream_decoder: msgspec.json.Decoder[AnthropicStreamEvent]
models_decoder: msgspec.json.Decoder[AnthropicModelsResponse]
# Explicit ``thinking`` budget for this client (0 = derive from the effort).
thinking_budget_tokens: int = 0
_api_key: str
_api_version: str
@@ -43,6 +45,7 @@ class AnthropicClient:
self.decoder = msgspec.json.Decoder(AnthropicMessageResponse)
self.stream_decoder = msgspec.json.Decoder(AnthropicStreamEvent)
self.models_decoder = msgspec.json.Decoder(AnthropicModelsResponse)
self.thinking_budget_tokens = max(0, config.thinking_budget_tokens)
self._api_key = config.api_key
self._api_version = config.anthropic_version
+3
View File
@@ -20,3 +20,6 @@ class AnthropicConfig:
base_url: str = DEFAULT_ANTHROPIC_BASE_URL
anthropic_version: str = DEFAULT_ANTHROPIC_VERSION
timeout: HttpTimeout = (DEFAULT_HTTP_CONNECT_TIMEOUT, DEFAULT_HTTP_READ_TIMEOUT)
# Explicit ``thinking.budget_tokens`` for reasoning models (0 = derive it
# from the configured reasoning_effort; see config/reasoning.py).
thinking_budget_tokens: int = 0
+13
View File
@@ -62,6 +62,18 @@ class AnthropicMetadata(Struct, omit_defaults=True):
user_id: str | Unset = UNSET
class AnthropicThinkingConfig(Struct, omit_defaults=True):
"""``thinking`` request block (extended thinking).
``budget_tokens`` is the reasoning budget the model may spend before it
answers; the API requires it to be at least 1024 and smaller than
``max_tokens`` (see ``agent.messages_bridge``, which keeps both valid).
"""
type: Literal["enabled", "disabled"] = "enabled"
budget_tokens: int | Unset = UNSET
class AnthropicMessagesParam(Struct, omit_defaults=True):
model: str
max_tokens: int = 8192
@@ -75,6 +87,7 @@ class AnthropicMessagesParam(Struct, omit_defaults=True):
temperature: float | Unset = UNSET
top_p: float | Unset = UNSET
top_k: int | Unset = UNSET
thinking: AnthropicThinkingConfig | Unset = UNSET
class AnthropicUsage(Struct, omit_defaults=True):
@@ -16,16 +16,17 @@ if TYPE_CHECKING:
from .model import ChatCompletionsParam
def _inject_thinking(data: bytes) -> bytes:
"""Inject ``thinking: {type: "enabled"}`` into the encoded request body.
def _inject_thinking(data: bytes, param: ChatCompletionsParam) -> bytes:
"""Inject the ``thinking`` flag 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.
explicitly set (default is ``enabled``); ``reasoning_effort = "none"`` is the
way to ask for it off. 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"}
body["thinking"] = {"type": "disabled" if param.reasoning_effort == "none" else "enabled"}
return json.dumps(body, separators=(",", ":")).encode("utf-8")
@@ -49,7 +50,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 = _inject_thinking(self.encoder.encode(param))
data = _inject_thinking(self.encoder.encode(param), param)
if stream:
resp = await self.session.post(
"/chat/completions",