From c129498efaad76f11ec2dda3f9eff966f914de03 Mon Sep 17 00:00:00 2001 From: worldmozara Date: Tue, 14 Jul 2026 17:25:18 +0800 Subject: [PATCH] core/runtime: map Provider config to protocol clients create_client supports openai, openai-compatible, and deepseek (openai convention); anthropic and deepseek anthropic raise. --- src/plyngent/runtime/__init__.py | 3 ++ src/plyngent/runtime/client_factory.py | 70 ++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 src/plyngent/runtime/__init__.py create mode 100644 src/plyngent/runtime/client_factory.py diff --git a/src/plyngent/runtime/__init__.py b/src/plyngent/runtime/__init__.py new file mode 100644 index 0000000..070e800 --- /dev/null +++ b/src/plyngent/runtime/__init__.py @@ -0,0 +1,3 @@ +from .client_factory import ProviderNotSupportedError as ProviderNotSupportedError +from .client_factory import create_client as create_client +from .client_factory import provider_to_openai_config as provider_to_openai_config diff --git a/src/plyngent/runtime/client_factory.py b/src/plyngent/runtime/client_factory.py new file mode 100644 index 0000000..9a64b74 --- /dev/null +++ b/src/plyngent/runtime/client_factory.py @@ -0,0 +1,70 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from plyngent.config.models import ( + DeepseekProvider, + OpenAICompatibleProvider, + OpenAIProvider, + Provider, +) +from plyngent.lmproto.deepseek import DeepseekOpenAIClient +from plyngent.lmproto.openai_compatible import OpenAIClient, OpenAIConfig + +if TYPE_CHECKING: + from collections.abc import Mapping + +DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1" +DEFAULT_DEEPSEEK_BASE_URL = "https://api.deepseek.com/v1" + +type OpenAICompatibleClient = OpenAIClient | DeepseekOpenAIClient + + +class ProviderNotSupportedError(NotImplementedError): + """Raised when a provider preset cannot be turned into a runtime client.""" + + +def provider_to_openai_config(provider: OpenAIProvider | OpenAICompatibleProvider | DeepseekProvider) -> OpenAIConfig: + """Map a provider config entry to :class:`OpenAIConfig`.""" + if isinstance(provider, OpenAIProvider): + base_url = provider.url or DEFAULT_OPENAI_BASE_URL + elif isinstance(provider, DeepseekProvider): + base_url = provider.url or DEFAULT_DEEPSEEK_BASE_URL + else: + base_url = provider.url + if not base_url: + msg = "openai-compatible provider requires a non-empty url" + raise ProviderNotSupportedError(msg) + return OpenAIConfig( + access_key_or_token=provider.access_key_or_token, + base_url=base_url, + ) + + +def _deepseek_convention(extras: Mapping[str, str]) -> str: + return extras.get("convention", "openai").lower() + + +def create_client(provider: Provider) -> OpenAICompatibleClient: + """Build a protocol client for the given provider config entry. + + Raises: + ProviderNotSupportedError: When the provider preset (or DeepSeek convention) + has no implemented client yet. + """ + if isinstance(provider, OpenAIProvider): + return OpenAIClient(provider_to_openai_config(provider)) + if isinstance(provider, OpenAICompatibleProvider): + return OpenAIClient(provider_to_openai_config(provider)) + if isinstance(provider, DeepseekProvider): + convention = _deepseek_convention(provider.extras) + if convention in {"openai", "openai_compat", "openai-compatible"}: + return DeepseekOpenAIClient(provider_to_openai_config(provider)) + if convention == "anthropic": + msg = "deepseek anthropic convention is not implemented" + raise ProviderNotSupportedError(msg) + msg = f"unknown deepseek convention {convention!r}" + raise ProviderNotSupportedError(msg) + # Remaining Provider variant: AnthropicProvider + msg = "anthropic provider client is not implemented" + raise ProviderNotSupportedError(msg)