cli: resolve thinking settings for the chat session

This commit is contained in:
2026-10-06 21:07:42 +08:00
parent fb8ef88d9f
commit 60bc5957a7
4 changed files with 123 additions and 7 deletions
+12 -3
View File
@@ -21,7 +21,7 @@ from plyngent.cli.models_source import (
fetch_remote_model_ids,
model_choices_for_provider,
)
from plyngent.config import EffectiveProvider, resolve_effective_provider
from plyngent.config import EffectiveProvider, ReasoningConfig, resolve_effective_provider, resolve_reasoning
from plyngent.memory.database.store import normalize_workspace
from plyngent.runtime import create_client
from plyngent.tools import InstanceState, SessionState
@@ -101,7 +101,7 @@ class ReplState:
_remote_models_error: str | None = field(default=None, init=False, repr=False)
def __post_init__(self) -> None:
self.client = create_client(self.provider, model=self.model)
self.client = create_client(self.provider, model=self.model, reasoning=self.reasoning)
self.workspace = Path(self.workspace).expanduser().resolve()
self.instance_state.workspace_root = self.workspace
self.instance_state.workspace.root = self.workspace
@@ -447,6 +447,7 @@ class ReplState:
memory=self.memory,
session_id=self.session_id,
max_rounds=self.max_rounds,
reasoning_effort=self.reasoning.effort,
on_limit=on_limit,
stream=self.stream_enabled,
system_prompt=system_prompt,
@@ -472,7 +473,7 @@ class ReplState:
# Preserve live stream toggle if agent already exists.
if hasattr(self, "agent"):
self.stream_enabled = self.agent.stream
self.client = create_client(self.provider, model=self.model)
self.client = create_client(self.provider, model=self.model, reasoning=self.reasoning)
self.agent = self._make_agent()
# Restore history without re-marking already-stored messages as dirty.
self.agent.replace_messages(messages, persist_from=persist_from)
@@ -493,6 +494,14 @@ class ReplState:
"""Selected provider after model-level preset/url overrides."""
return resolve_effective_provider(self.provider, model=self.model)
@property
def reasoning(self) -> ReasoningConfig:
"""Merged thinking settings for the selected provider + model.
``[agent]`` < provider < model; see :mod:`plyngent.config.reasoning`.
"""
return resolve_reasoning(self.config.agent_config, self.provider, self.model)
def invalidate_remote_models(self) -> None:
"""Drop cached remote model catalog (provider/client change)."""
self._remote_models = None
+19 -4
View File
@@ -3,6 +3,7 @@ from __future__ import annotations
import math
from typing import TYPE_CHECKING
from plyngent.config.reasoning import ReasoningConfig, resolve_reasoning
from plyngent.config.routing import EffectiveProvider, resolve_effective_provider
from plyngent.lmproto.anthropic import AnthropicClient
from plyngent.lmproto.anthropic.config import AnthropicConfig as AnthropicConfigCls
@@ -103,7 +104,7 @@ def provider_to_openai_config(
)
def _anthropic_client(effective: EffectiveProvider) -> AnthropicClient:
def _anthropic_client(effective: EffectiveProvider, reasoning: ReasoningConfig) -> AnthropicClient:
"""Build an Anthropic Messages client (real or DeepSeek convention) for *effective*."""
try:
http_timeout = normalize_http_timeout(effective.timeout)
@@ -115,6 +116,7 @@ def _anthropic_client(effective: EffectiveProvider) -> AnthropicClient:
api_key=effective.access_key_or_token,
base_url=effective.url or DEFAULT_DEEPSEEK_BASE_URL,
timeout=http_timeout,
thinking_budget_tokens=reasoning.thinking_budget_tokens,
)
)
return AnthropicClient(
@@ -122,11 +124,17 @@ def _anthropic_client(effective: EffectiveProvider) -> AnthropicClient:
api_key=effective.access_key_or_token,
base_url=effective.url or DEFAULT_ANTHROPIC_BASE_URL,
timeout=http_timeout,
thinking_budget_tokens=reasoning.thinking_budget_tokens,
)
)
def create_client(provider: Provider, *, model: str | None = None) -> ProtocolClient:
def create_client(
provider: Provider,
*,
model: str | None = None,
reasoning: ReasoningConfig | None = None,
) -> ProtocolClient:
"""Build a protocol client for *provider*, applying model-level routing.
``preset`` always decides API conventions: ``openai`` → /responses,
@@ -134,8 +142,15 @@ def create_client(provider: Provider, *, model: str | None = None) -> ProtocolCl
Exception: a ``deepseek`` preset picks the Responses or Anthropic surface
when its effective ``convention`` is ``"responses"`` / ``"anthropic"``,
else chat completions.
*reasoning* is the merged thinking config for this provider + model (see
:func:`plyngent.config.reasoning.resolve_reasoning`); callers without an
``[agent]`` layer may omit it and get the provider/model layers only. It only
reaches surfaces that cannot read the setting off the request param —
Anthropic needs a token budget, while chat/Responses carry an effort level.
"""
effective = resolve_effective_provider(provider, model=model)
spec = reasoning if reasoning is not None else resolve_reasoning(None, provider, model)
if effective.preset == "openai":
return OpenAIClient(provider_to_openai_config(effective))
if effective.preset == "openai-compatible":
@@ -144,9 +159,9 @@ def create_client(provider: Provider, *, model: str | None = None) -> ProtocolCl
if effective.convention == "responses":
return DeepseekResponsesClient(provider_to_openai_config(effective))
if effective.convention == "anthropic":
return _anthropic_client(effective)
return _anthropic_client(effective, spec)
return DeepseekOpenAIClient(provider_to_openai_config(effective))
if effective.preset == "anthropic":
return _anthropic_client(effective)
return _anthropic_client(effective, spec)
msg = f"provider preset {effective.preset!r} is not supported"
raise ProviderNotSupportedError(msg)