diff --git a/opencane/config/schema.py b/opencane/config/schema.py index 90db0cf351..89d9417417 100644 --- a/opencane/config/schema.py +++ b/opencane/config/schema.py @@ -200,6 +200,7 @@ class ProvidersConfig(BaseModel): moonshot: ProviderConfig = Field(default_factory=ProviderConfig) minimax: ProviderConfig = Field(default_factory=ProviderConfig) aihubmix: ProviderConfig = Field(default_factory=ProviderConfig) # AiHubMix API gateway + siliconflow: ProviderConfig = Field(default_factory=ProviderConfig) # SiliconFlow API gateway class GatewayConfig(BaseModel): diff --git a/opencane/providers/registry.py b/opencane/providers/registry.py index b5c63d1454..5a7d312028 100644 --- a/opencane/providers/registry.py +++ b/opencane/providers/registry.py @@ -121,6 +121,25 @@ def label(self) -> str: model_overrides=(), ), + # SiliconFlow: OpenAI-compatible gateway hosting multiple models. + # Keep model org prefixes (e.g. "Qwen/Qwen2.5-14B-Instruct"). + ProviderSpec( + name="siliconflow", + keywords=("siliconflow",), + env_key="OPENAI_API_KEY", # OpenAI-compatible + display_name="SiliconFlow", + litellm_prefix="openai", # → openai/{model} + skip_prefixes=(), + env_extras=(), + is_gateway=True, + is_local=False, + detect_by_key_prefix="", + detect_by_base_keyword="siliconflow", + default_api_base="https://api.siliconflow.cn/v1", + strip_model_prefix=False, + model_overrides=(), + ), + # === Standard providers (matched by model-name keywords) =============== # Anthropic: LiteLLM recognizes "claude-*" natively, no prefix needed. diff --git a/tests/test_provider_routing.py b/tests/test_provider_routing.py index 94d4ddebed..6c7313c909 100644 --- a/tests/test_provider_routing.py +++ b/tests/test_provider_routing.py @@ -1,5 +1,5 @@ from opencane.config.schema import Config -from opencane.providers.registry import find_by_model +from opencane.providers.registry import find_by_model, find_gateway def test_find_by_model_prefers_explicit_prefix_over_keyword_match() -> None: @@ -16,3 +16,17 @@ def test_config_match_prefers_explicit_prefix_when_multiple_provider_keys_presen assert cfg.get_provider_name() == "deepseek" + +def test_find_gateway_detects_siliconflow_by_api_base_keyword() -> None: + spec = find_gateway(api_base="https://api.siliconflow.cn/v1") + assert spec is not None + assert spec.name == "siliconflow" + + +def test_config_uses_siliconflow_gateway_defaults() -> None: + cfg = Config() + cfg.agents.defaults.model = "siliconflow/Qwen/Qwen2.5-14B-Instruct" + cfg.providers.siliconflow.api_key = "sf-key" + + assert cfg.get_provider_name() == "siliconflow" + assert cfg.get_api_base() == "https://api.siliconflow.cn/v1"