mirror of
https://github.com/NCBM/plyngent.git
synced 2026-11-07 23:47:54 +08:00
core/config: model-level preset and url overrides
This commit is contained in:
@@ -20,6 +20,11 @@ from .models import PluginsConfig as PluginsConfig
|
|||||||
from .models import ProviderConfig as ProviderConfig
|
from .models import ProviderConfig as ProviderConfig
|
||||||
from .models import compose_agent_system_content as compose_agent_system_content
|
from .models import compose_agent_system_content as compose_agent_system_content
|
||||||
from .path import get_default_path as _get_default_path
|
from .path import get_default_path as _get_default_path
|
||||||
|
from .routing import EffectiveProvider as EffectiveProvider
|
||||||
|
from .routing import ProviderPreset as ProviderPreset
|
||||||
|
from .routing import default_url_for_preset as default_url_for_preset
|
||||||
|
from .routing import provider_preset as provider_preset
|
||||||
|
from .routing import resolve_effective_provider as resolve_effective_provider
|
||||||
from .store import ConfigFormatError as ConfigFormatError
|
from .store import ConfigFormatError as ConfigFormatError
|
||||||
from .store import ConfigStore as ConfigStore
|
from .store import ConfigStore as ConfigStore
|
||||||
|
|
||||||
|
|||||||
@@ -142,7 +142,13 @@ class NetworkingConfig(Struct, omit_defaults=True):
|
|||||||
|
|
||||||
|
|
||||||
class ModelConfig(Struct, omit_defaults=True):
|
class ModelConfig(Struct, omit_defaults=True):
|
||||||
"""Capability flags for a model within a provider."""
|
"""Capability flags and optional routing overrides for a model.
|
||||||
|
|
||||||
|
``preset`` / ``url`` override the parent provider for this model only.
|
||||||
|
``preset`` keeps the same meanings as provider presets:
|
||||||
|
``openai`` → /responses, ``openai-compatible`` → /chat/completions,
|
||||||
|
``anthropic`` → /messages (native support planned), ``deepseek`` → DeepSeek.
|
||||||
|
"""
|
||||||
|
|
||||||
text: bool = True
|
text: bool = True
|
||||||
image_in: bool = False
|
image_in: bool = False
|
||||||
@@ -152,6 +158,8 @@ class ModelConfig(Struct, omit_defaults=True):
|
|||||||
video_in: bool = False
|
video_in: bool = False
|
||||||
video_out: bool = False
|
video_out: bool = False
|
||||||
cost_factor: float = 1.0
|
cost_factor: float = 1.0
|
||||||
|
preset: str = ""
|
||||||
|
url: str = ""
|
||||||
|
|
||||||
|
|
||||||
class HttpTimeoutConfig(Struct, omit_defaults=True):
|
class HttpTimeoutConfig(Struct, omit_defaults=True):
|
||||||
@@ -202,6 +210,7 @@ def _default_openai_provider_tools() -> list[dict[str, Any]]:
|
|||||||
class OpenAIProvider(ProviderConfig, tag="openai"):
|
class OpenAIProvider(ProviderConfig, tag="openai"):
|
||||||
"""OpenAI platform provider (agent uses Responses API).
|
"""OpenAI platform provider (agent uses Responses API).
|
||||||
|
|
||||||
|
``url`` should include the API version path (default: ``https://api.openai.com/v1``).
|
||||||
``provider_tools`` are hosted/provider-side tools (e.g. web_search) passed
|
``provider_tools`` are hosted/provider-side tools (e.g. web_search) passed
|
||||||
through to ``POST /responses`` as opaque dicts. They are **not** local
|
through to ``POST /responses`` as opaque dicts. They are **not** local
|
||||||
``ToolRegistry`` handlers and are ignored by non-OpenAI clients.
|
``ToolRegistry`` handlers and are ignored by non-OpenAI clients.
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import TYPE_CHECKING, Literal, cast
|
||||||
|
|
||||||
|
from .models import (
|
||||||
|
AnthropicProvider,
|
||||||
|
HttpTimeoutConfig,
|
||||||
|
ModelConfig,
|
||||||
|
OpenAICompatibleProvider,
|
||||||
|
OpenAIProvider,
|
||||||
|
Provider,
|
||||||
|
)
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Mapping
|
||||||
|
|
||||||
|
|
||||||
|
type ProviderPreset = Literal["openai", "openai-compatible", "anthropic", "deepseek"]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, slots=True)
|
||||||
|
class EffectiveProvider:
|
||||||
|
"""Provider routing after applying model-level ``preset`` / ``url`` overrides."""
|
||||||
|
|
||||||
|
preset: ProviderPreset
|
||||||
|
access_key_or_token: str
|
||||||
|
url: str
|
||||||
|
model: str | None
|
||||||
|
timeout: float | HttpTimeoutConfig | None
|
||||||
|
provider_tools: list[dict[str, object]]
|
||||||
|
source: Provider
|
||||||
|
|
||||||
|
|
||||||
|
def provider_preset(provider: Provider) -> ProviderPreset:
|
||||||
|
"""Return the tagged-union preset for *provider*."""
|
||||||
|
if isinstance(provider, OpenAIProvider):
|
||||||
|
return "openai"
|
||||||
|
if isinstance(provider, OpenAICompatibleProvider):
|
||||||
|
return "openai-compatible"
|
||||||
|
if isinstance(provider, AnthropicProvider):
|
||||||
|
return "anthropic"
|
||||||
|
return "deepseek"
|
||||||
|
|
||||||
|
|
||||||
|
def default_url_for_preset(preset: ProviderPreset) -> str:
|
||||||
|
"""Default base URL; includes the API version path when applicable."""
|
||||||
|
if preset == "openai":
|
||||||
|
return "https://api.openai.com/v1"
|
||||||
|
if preset == "anthropic":
|
||||||
|
return "https://api.anthropic.com/v1"
|
||||||
|
if preset == "deepseek":
|
||||||
|
return "https://api.deepseek.com"
|
||||||
|
# Generic OpenAI-compatible providers usually require a host-specific URL.
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def validate_model_preset(preset: str) -> ProviderPreset:
|
||||||
|
if preset in {"openai", "openai-compatible", "anthropic", "deepseek"}:
|
||||||
|
return cast("ProviderPreset", preset)
|
||||||
|
msg = f"unsupported model preset {preset!r}"
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
|
||||||
|
def model_config_for(provider: Provider, model: str | None) -> ModelConfig | None:
|
||||||
|
if not model:
|
||||||
|
return None
|
||||||
|
return provider.models.get(model)
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_effective_provider(provider: Provider, *, model: str | None = None) -> EffectiveProvider:
|
||||||
|
"""Apply model-level preset/url overrides to *provider*.
|
||||||
|
|
||||||
|
``preset`` always decides API conventions: ``openai`` means Responses,
|
||||||
|
``openai-compatible`` means Chat Completions, ``anthropic`` means Messages.
|
||||||
|
"""
|
||||||
|
parent_preset = provider_preset(provider)
|
||||||
|
model_cfg = model_config_for(provider, model)
|
||||||
|
if model_cfg is not None and model_cfg.preset.strip():
|
||||||
|
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]
|
||||||
|
|
||||||
|
return EffectiveProvider(
|
||||||
|
preset=preset,
|
||||||
|
access_key_or_token=provider.access_key_or_token,
|
||||||
|
url=url,
|
||||||
|
model=model,
|
||||||
|
timeout=provider.timeout,
|
||||||
|
provider_tools=provider_tools,
|
||||||
|
source=provider,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def effective_provider_to_config_dict(effective: EffectiveProvider) -> Mapping[str, object]:
|
||||||
|
"""Debug/status-friendly dict for an effective provider."""
|
||||||
|
return {
|
||||||
|
"preset": effective.preset,
|
||||||
|
"access_key_or_token": effective.access_key_or_token,
|
||||||
|
"url": effective.url,
|
||||||
|
"model": effective.model or "",
|
||||||
|
"timeout": effective.timeout,
|
||||||
|
"provider_tools": effective.provider_tools,
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user