config: add typed DeepSeek convention field for API surface

This commit is contained in:
2026-08-01 01:59:39 +08:00
parent a877dba8c1
commit 8ad33af652
5 changed files with 149 additions and 3 deletions
+1
View File
@@ -10,6 +10,7 @@ from .models import DEFAULT_TOOL_DIRECTIVES as DEFAULT_TOOL_DIRECTIVES
from .models import AgentConfig as AgentConfig
from .models import AnthropicProvider as AnthropicProvider
from .models import DatabaseConfig as DatabaseConfig
from .models import DeepSeekConvention as DeepSeekConvention
from .models import DeepseekProvider as DeepseekProvider
from .models import HttpTimeoutConfig as HttpTimeoutConfig
from .models import ModelConfig as ModelConfig
+21 -2
View File
@@ -1,7 +1,14 @@
from typing import Any
from typing import Any, Literal
from msgspec import Struct, field
# DeepSeek API surface convention (``DeepseekProvider.convention`` and
# per-model ``ModelConfig.convention`` override).
# - "" (default) / "openai" → OpenAI-compatible chat completions
# - "responses" → OpenAI Responses API (``POST /responses``)
# - "anthropic" → legacy value, ignored (still chat completions)
type DeepSeekConvention = Literal["", "openai", "anthropic", "responses"]
# Built-in persona when ``[agent].system_prompt`` is omitted.
# Set ``system_prompt = ""`` to omit the persona block only.
# Override with a multi-line TOML literal (prefer ''' so nested " is fine).
@@ -162,6 +169,8 @@ class ModelConfig(Struct, omit_defaults=True):
cost_factor: float = 1.0
preset: str = ""
url: str = ""
# DeepSeek API surface override ("" = inherit provider convention).
convention: DeepSeekConvention = ""
class HttpTimeoutConfig(Struct, omit_defaults=True):
@@ -244,9 +253,19 @@ def _default_deepseek_models() -> dict[str, ModelConfig]:
class DeepseekProvider(ProviderConfig, tag="deepseek"):
"""Deepseek API provider with optional extras (e.g. convention)."""
"""DeepSeek API provider.
``convention`` selects the API surface on ``https://api.deepseek.com``:
- ``""`` / ``"openai"`` → OpenAI-compatible chat completions (default)
- ``"responses"`` → OpenAI Responses API (``POST /responses``)
- ``"anthropic"`` → legacy value, ignored (still chat completions)
``extras`` keeps arbitrary provider keys; the legacy ``extras.convention``
key is parsed but ignored (prefer the typed ``convention`` field).
"""
models: dict[str, ModelConfig] = field(default_factory=_default_deepseek_models)
convention: DeepSeekConvention = ""
extras: dict[str, str] = field(default_factory=dict)
+16
View File
@@ -5,6 +5,7 @@ from typing import TYPE_CHECKING, Literal, cast
from .models import (
AnthropicProvider,
DeepseekProvider,
HttpTimeoutConfig,
ModelConfig,
OpenAICompatibleProvider,
@@ -30,6 +31,9 @@ class EffectiveProvider:
timeout: float | HttpTimeoutConfig | None
provider_tools: list[dict[str, object]]
source: Provider
# DeepSeek API surface (see DeepSeekConvention): "openai" / "anthropic" /
# "responses" for deepseek presets; "" (unused) for other presets.
convention: str = ""
def provider_preset(provider: Provider) -> ProviderPreset:
@@ -73,6 +77,9 @@ def resolve_effective_provider(provider: Provider, *, model: str | None = None)
``preset`` always decides API conventions: ``openai`` means Responses,
``openai-compatible`` means Chat Completions, ``anthropic`` means Messages.
Exception: a ``deepseek`` preset may pick the Responses surface via the
``convention`` field (``"responses"``); model-level ``convention`` wins
over the provider-level one.
"""
parent_preset = provider_preset(provider)
model_cfg = model_config_for(provider, model)
@@ -88,6 +95,13 @@ def resolve_effective_provider(provider: Provider, *, model: str | None = None)
if preset == "openai" and isinstance(provider, OpenAIProvider) and parent_preset == "openai":
provider_tools = [dict(item) for item in provider.provider_tools]
# DeepSeek API surface: model override > provider convention > chat default.
convention = ""
if preset == "deepseek" and isinstance(provider, DeepseekProvider):
convention = provider.convention or "openai"
if model_cfg is not None and model_cfg.convention:
convention = model_cfg.convention
return EffectiveProvider(
preset=preset,
access_key_or_token=provider.access_key_or_token,
@@ -96,6 +110,7 @@ def resolve_effective_provider(provider: Provider, *, model: str | None = None)
timeout=provider.timeout,
provider_tools=provider_tools,
source=provider,
convention=convention,
)
@@ -108,4 +123,5 @@ def effective_provider_to_config_dict(effective: EffectiveProvider) -> Mapping[s
"model": effective.model or "",
"timeout": effective.timeout,
"provider_tools": effective.provider_tools,
"convention": effective.convention,
}