runtime: route DeepSeek convention=responses to Responses client

This commit is contained in:
2026-08-01 02:03:52 +08:00
parent bfafe74a6f
commit 4b7cc93917
2 changed files with 45 additions and 2 deletions
+8 -2
View File
@@ -6,7 +6,7 @@ from typing import TYPE_CHECKING
from plyngent.config.routing import EffectiveProvider, resolve_effective_provider from plyngent.config.routing import EffectiveProvider, resolve_effective_provider
from plyngent.lmproto.anthropic import AnthropicClient from plyngent.lmproto.anthropic import AnthropicClient
from plyngent.lmproto.anthropic.config import AnthropicConfig as AnthropicConfigCls from plyngent.lmproto.anthropic.config import AnthropicConfig as AnthropicConfigCls
from plyngent.lmproto.deepseek import DeepseekOpenAIClient from plyngent.lmproto.deepseek import DeepseekOpenAIClient, DeepseekResponsesClient
from plyngent.lmproto.openai import OpenAIClient from plyngent.lmproto.openai import OpenAIClient
from plyngent.lmproto.openai_compatible import OpenAICompatibleClient, OpenAIConfig from plyngent.lmproto.openai_compatible import OpenAICompatibleClient, OpenAIConfig
from plyngent.lmproto.openai_compatible.config import ( from plyngent.lmproto.openai_compatible.config import (
@@ -22,7 +22,9 @@ DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1"
DEFAULT_ANTHROPIC_BASE_URL = "https://api.anthropic.com/v1" DEFAULT_ANTHROPIC_BASE_URL = "https://api.anthropic.com/v1"
DEFAULT_DEEPSEEK_BASE_URL = "https://api.deepseek.com" DEFAULT_DEEPSEEK_BASE_URL = "https://api.deepseek.com"
type ProtocolClient = OpenAIClient | OpenAICompatibleClient | DeepseekOpenAIClient | AnthropicClient type ProtocolClient = (
OpenAIClient | OpenAICompatibleClient | DeepseekOpenAIClient | DeepseekResponsesClient | AnthropicClient
)
# Backward-compatible name used by older imports/tests. # Backward-compatible name used by older imports/tests.
type OpenAICompatibleClientUnion = ProtocolClient type OpenAICompatibleClientUnion = ProtocolClient
@@ -97,6 +99,8 @@ def create_client(provider: Provider, *, model: str | None = None) -> ProtocolCl
``preset`` always decides API conventions: ``openai`` → /responses, ``preset`` always decides API conventions: ``openai`` → /responses,
``openai-compatible`` → /chat/completions, ``anthropic`` → /messages. ``openai-compatible`` → /chat/completions, ``anthropic`` → /messages.
Exception: a ``deepseek`` preset picks the Responses surface when its
effective ``convention`` is ``"responses"``, else chat completions.
""" """
effective = resolve_effective_provider(provider, model=model) effective = resolve_effective_provider(provider, model=model)
if effective.preset == "openai": if effective.preset == "openai":
@@ -104,6 +108,8 @@ def create_client(provider: Provider, *, model: str | None = None) -> ProtocolCl
if effective.preset == "openai-compatible": if effective.preset == "openai-compatible":
return OpenAICompatibleClient(provider_to_openai_config(effective)) return OpenAICompatibleClient(provider_to_openai_config(effective))
if effective.preset == "deepseek": if effective.preset == "deepseek":
if effective.convention == "responses":
return DeepseekResponsesClient(provider_to_openai_config(effective))
return DeepseekOpenAIClient(provider_to_openai_config(effective)) return DeepseekOpenAIClient(provider_to_openai_config(effective))
if effective.preset == "anthropic": if effective.preset == "anthropic":
try: try:
+37
View File
@@ -73,6 +73,43 @@ def test_deepseek_ignores_legacy_convention_extra() -> None:
assert isinstance(client, DeepseekOpenAIClient) assert isinstance(client, DeepseekOpenAIClient)
def test_deepseek_responses_convention() -> None:
from plyngent.lmproto.deepseek import DeepseekResponsesClient
provider = DeepseekProvider(access_key_or_token="sk-test", convention="responses")
client = create_client(provider)
assert isinstance(client, DeepseekResponsesClient)
assert client.kind == "responses"
assert provider_to_openai_config(provider).base_url == "https://api.deepseek.com"
def test_deepseek_default_and_legacy_conventions_stay_chat() -> None:
from plyngent.lmproto.deepseek import DeepseekResponsesClient
default_client = create_client(DeepseekProvider(access_key_or_token="sk-test"))
assert isinstance(default_client, DeepseekOpenAIClient)
assert not isinstance(default_client, DeepseekResponsesClient)
legacy = DeepseekProvider(access_key_or_token="sk-test", convention="anthropic")
assert isinstance(create_client(legacy), DeepseekOpenAIClient)
def test_deepseek_model_level_convention_override() -> None:
from plyngent.lmproto.deepseek import DeepseekResponsesClient
provider = DeepseekProvider(
access_key_or_token="sk-test",
models={
"deepseek-v4-flash": ModelConfig(convention="responses"),
"deepseek-v4-pro": ModelConfig(),
},
)
flash = create_client(provider, model="deepseek-v4-flash")
assert isinstance(flash, DeepseekResponsesClient)
assert flash.kind == "responses"
pro = create_client(provider, model="deepseek-v4-pro")
assert isinstance(pro, DeepseekOpenAIClient)
def test_anthropic_client_created() -> None: def test_anthropic_client_created() -> None:
from plyngent.lmproto.anthropic import AnthropicClient from plyngent.lmproto.anthropic import AnthropicClient