mirror of
https://github.com/NCBM/plyngent.git
synced 2026-11-07 15:29:25 +08:00
config: default deepseek anthropic convention url
This commit is contained in:
@@ -6,7 +6,8 @@ from msgspec import Struct, field
|
||||
# per-model ``ModelConfig.convention`` override).
|
||||
# - "" (default) / "openai" → OpenAI-compatible chat completions
|
||||
# - "responses" → OpenAI Responses API (``POST /responses``)
|
||||
# - "anthropic" → legacy value, ignored (still chat completions)
|
||||
# - "anthropic" → Anthropic Messages API (``POST /messages`` on
|
||||
# ``https://api.deepseek.com/anthropic``)
|
||||
type DeepSeekConvention = Literal["", "openai", "anthropic", "responses"]
|
||||
|
||||
# Built-in persona when ``[agent].system_prompt`` is omitted.
|
||||
@@ -255,10 +256,11 @@ def _default_deepseek_models() -> dict[str, ModelConfig]:
|
||||
class DeepseekProvider(ProviderConfig, tag="deepseek"):
|
||||
"""DeepSeek API provider.
|
||||
|
||||
``convention`` selects the API surface on ``https://api.deepseek.com``:
|
||||
``convention`` selects the API surface on DeepSeek's endpoints:
|
||||
- ``""`` / ``"openai"`` → OpenAI-compatible chat completions (default)
|
||||
- ``"responses"`` → OpenAI Responses API (``POST /responses``)
|
||||
- ``"anthropic"`` → legacy value, ignored (still chat completions)
|
||||
- ``"anthropic"`` → Anthropic Messages API (``POST /messages`` on
|
||||
``https://api.deepseek.com/anthropic``)
|
||||
|
||||
``extras`` keeps arbitrary provider keys; the legacy ``extras.convention``
|
||||
key is parsed but ignored (prefer the typed ``convention`` field).
|
||||
|
||||
@@ -19,6 +19,9 @@ if TYPE_CHECKING:
|
||||
|
||||
type ProviderPreset = Literal["openai", "openai-compatible", "anthropic", "deepseek"]
|
||||
|
||||
DEFAULT_DEEPSEEK_BASE_URL = "https://api.deepseek.com"
|
||||
DEFAULT_DEEPSEEK_ANTHROPIC_BASE_URL = "https://api.deepseek.com/anthropic"
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class EffectiveProvider:
|
||||
@@ -54,11 +57,22 @@ def default_url_for_preset(preset: ProviderPreset) -> str:
|
||||
if preset == "anthropic":
|
||||
return "https://api.anthropic.com/v1"
|
||||
if preset == "deepseek":
|
||||
return "https://api.deepseek.com"
|
||||
return DEFAULT_DEEPSEEK_BASE_URL
|
||||
# Generic OpenAI-compatible providers usually require a host-specific URL.
|
||||
return ""
|
||||
|
||||
|
||||
def _default_url_for_deepseek_convention(convention: str) -> str:
|
||||
"""Default DeepSeek base URL for an API convention.
|
||||
|
||||
The Anthropic-convention endpoint lives under ``/anthropic``; chat and
|
||||
Responses share the plain ``https://api.deepseek.com`` base.
|
||||
"""
|
||||
if convention == "anthropic":
|
||||
return DEFAULT_DEEPSEEK_ANTHROPIC_BASE_URL
|
||||
return DEFAULT_DEEPSEEK_BASE_URL
|
||||
|
||||
|
||||
def validate_model_preset(preset: str) -> ProviderPreset:
|
||||
if preset in {"openai", "openai-compatible", "anthropic", "deepseek"}:
|
||||
return cast("ProviderPreset", preset)
|
||||
@@ -77,9 +91,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.
|
||||
Exception: a ``deepseek`` preset may pick the Responses or Anthropic
|
||||
surface via the ``convention`` field (``"responses"`` / ``"anthropic"``);
|
||||
model-level ``convention`` wins over the provider-level one.
|
||||
"""
|
||||
parent_preset = provider_preset(provider)
|
||||
model_cfg = model_config_for(provider, model)
|
||||
@@ -87,13 +101,6 @@ def resolve_effective_provider(provider: Provider, *, model: str | None = None)
|
||||
preset = validate_model_preset(model_cfg.preset.strip())
|
||||
else:
|
||||
preset = parent_preset
|
||||
url = model_cfg.url.strip() if model_cfg is not None and model_cfg.url.strip() else provider.url.strip()
|
||||
if not url:
|
||||
url = default_url_for_preset(preset)
|
||||
|
||||
provider_tools: list[dict[str, object]] = []
|
||||
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 = ""
|
||||
@@ -102,6 +109,17 @@ def resolve_effective_provider(provider: Provider, *, model: str | None = None)
|
||||
if model_cfg is not None and model_cfg.convention:
|
||||
convention = model_cfg.convention
|
||||
|
||||
url = model_cfg.url.strip() if model_cfg is not None and model_cfg.url.strip() else provider.url.strip()
|
||||
if not url:
|
||||
if preset == "deepseek":
|
||||
url = _default_url_for_deepseek_convention(convention)
|
||||
else:
|
||||
url = default_url_for_preset(preset)
|
||||
|
||||
provider_tools: list[dict[str, object]] = []
|
||||
if preset == "openai" and isinstance(provider, OpenAIProvider) and parent_preset == "openai":
|
||||
provider_tools = [dict(item) for item in provider.provider_tools]
|
||||
|
||||
return EffectiveProvider(
|
||||
preset=preset,
|
||||
access_key_or_token=provider.access_key_or_token,
|
||||
|
||||
@@ -247,6 +247,45 @@ def test_deepseek_effective_convention_precedence() -> None:
|
||||
assert resolve_effective_provider(OpenAIProvider(access_key_or_token="sk")).convention == ""
|
||||
|
||||
|
||||
def test_deepseek_anthropic_convention_default_url() -> None:
|
||||
from plyngent.config import resolve_effective_provider
|
||||
from plyngent.config.routing import DEFAULT_DEEPSEEK_ANTHROPIC_BASE_URL, DEFAULT_DEEPSEEK_BASE_URL
|
||||
|
||||
provider = DeepseekProvider(access_key_or_token="sk-test", convention="anthropic")
|
||||
effective = resolve_effective_provider(provider)
|
||||
assert effective.convention == "anthropic"
|
||||
assert effective.url == DEFAULT_DEEPSEEK_ANTHROPIC_BASE_URL
|
||||
|
||||
# Custom provider url wins over the convention default.
|
||||
provider = DeepseekProvider(
|
||||
access_key_or_token="sk-test",
|
||||
url="https://gateway.example/anthropic",
|
||||
convention="anthropic",
|
||||
)
|
||||
assert resolve_effective_provider(provider).url == "https://gateway.example/anthropic"
|
||||
|
||||
# Other conventions keep the plain deepseek base.
|
||||
for convention in ("", "openai", "responses"):
|
||||
provider = DeepseekProvider(access_key_or_token="sk-test", convention=convention)
|
||||
assert resolve_effective_provider(provider).url == DEFAULT_DEEPSEEK_BASE_URL
|
||||
|
||||
|
||||
def test_deepseek_anthropic_model_convention_default_url() -> None:
|
||||
from plyngent.config import ModelConfig, resolve_effective_provider
|
||||
from plyngent.config.routing import DEFAULT_DEEPSEEK_ANTHROPIC_BASE_URL
|
||||
|
||||
provider = DeepseekProvider(
|
||||
access_key_or_token="sk-test",
|
||||
models={"claude-proxy": ModelConfig(convention="anthropic")},
|
||||
)
|
||||
effective = resolve_effective_provider(provider, model="claude-proxy")
|
||||
assert effective.convention == "anthropic"
|
||||
assert effective.url == DEFAULT_DEEPSEEK_ANTHROPIC_BASE_URL
|
||||
# Provider default url for a model without convention stays the deepseek base.
|
||||
other = resolve_effective_provider(provider, model=None)
|
||||
assert other.url == "https://api.deepseek.com"
|
||||
|
||||
|
||||
def test_read_empty_config() -> None:
|
||||
config = plyngent.config.load(Path(__file__).parent / "plyngent-empty.toml")
|
||||
assert isinstance(config.providers, Mapping)
|
||||
|
||||
Reference in New Issue
Block a user