mirror of
https://github.com/NCBM/plyngent.git
synced 2026-11-07 23:47:54 +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).
|
# per-model ``ModelConfig.convention`` override).
|
||||||
# - "" (default) / "openai" → OpenAI-compatible chat completions
|
# - "" (default) / "openai" → OpenAI-compatible chat completions
|
||||||
# - "responses" → OpenAI Responses API (``POST /responses``)
|
# - "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"]
|
type DeepSeekConvention = Literal["", "openai", "anthropic", "responses"]
|
||||||
|
|
||||||
# Built-in persona when ``[agent].system_prompt`` is omitted.
|
# 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"):
|
class DeepseekProvider(ProviderConfig, tag="deepseek"):
|
||||||
"""DeepSeek API provider.
|
"""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)
|
- ``""`` / ``"openai"`` → OpenAI-compatible chat completions (default)
|
||||||
- ``"responses"`` → OpenAI Responses API (``POST /responses``)
|
- ``"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``
|
``extras`` keeps arbitrary provider keys; the legacy ``extras.convention``
|
||||||
key is parsed but ignored (prefer the typed ``convention`` field).
|
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"]
|
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)
|
@dataclass(frozen=True, slots=True)
|
||||||
class EffectiveProvider:
|
class EffectiveProvider:
|
||||||
@@ -54,11 +57,22 @@ def default_url_for_preset(preset: ProviderPreset) -> str:
|
|||||||
if preset == "anthropic":
|
if preset == "anthropic":
|
||||||
return "https://api.anthropic.com/v1"
|
return "https://api.anthropic.com/v1"
|
||||||
if preset == "deepseek":
|
if preset == "deepseek":
|
||||||
return "https://api.deepseek.com"
|
return DEFAULT_DEEPSEEK_BASE_URL
|
||||||
# Generic OpenAI-compatible providers usually require a host-specific URL.
|
# Generic OpenAI-compatible providers usually require a host-specific URL.
|
||||||
return ""
|
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:
|
def validate_model_preset(preset: str) -> ProviderPreset:
|
||||||
if preset in {"openai", "openai-compatible", "anthropic", "deepseek"}:
|
if preset in {"openai", "openai-compatible", "anthropic", "deepseek"}:
|
||||||
return cast("ProviderPreset", preset)
|
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,
|
``preset`` always decides API conventions: ``openai`` means Responses,
|
||||||
``openai-compatible`` means Chat Completions, ``anthropic`` means Messages.
|
``openai-compatible`` means Chat Completions, ``anthropic`` means Messages.
|
||||||
Exception: a ``deepseek`` preset may pick the Responses surface via the
|
Exception: a ``deepseek`` preset may pick the Responses or Anthropic
|
||||||
``convention`` field (``"responses"``); model-level ``convention`` wins
|
surface via the ``convention`` field (``"responses"`` / ``"anthropic"``);
|
||||||
over the provider-level one.
|
model-level ``convention`` wins over the provider-level one.
|
||||||
"""
|
"""
|
||||||
parent_preset = provider_preset(provider)
|
parent_preset = provider_preset(provider)
|
||||||
model_cfg = model_config_for(provider, model)
|
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())
|
preset = validate_model_preset(model_cfg.preset.strip())
|
||||||
else:
|
else:
|
||||||
preset = parent_preset
|
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.
|
# DeepSeek API surface: model override > provider convention > chat default.
|
||||||
convention = ""
|
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:
|
if model_cfg is not None and model_cfg.convention:
|
||||||
convention = 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(
|
return EffectiveProvider(
|
||||||
preset=preset,
|
preset=preset,
|
||||||
access_key_or_token=provider.access_key_or_token,
|
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 == ""
|
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:
|
def test_read_empty_config() -> None:
|
||||||
config = plyngent.config.load(Path(__file__).parent / "plyngent-empty.toml")
|
config = plyngent.config.load(Path(__file__).parent / "plyngent-empty.toml")
|
||||||
assert isinstance(config.providers, Mapping)
|
assert isinstance(config.providers, Mapping)
|
||||||
|
|||||||
Reference in New Issue
Block a user