From dc73d6168214daf11222043e5fbe3782d0aeadce Mon Sep 17 00:00:00 2001 From: jxxghp Date: Sun, 24 May 2026 09:30:41 +0800 Subject: [PATCH] feat: add china operator llm providers --- app/agent/llm/provider.py | 82 ++++++++++++ scripts/local_setup.py | 18 +++ tests/test_llm_provider_registry.py | 121 ++++++++++++++++++ tests/test_local_setup_llm_provider_prompt.py | 42 +++++- 4 files changed, 261 insertions(+), 2 deletions(-) diff --git a/app/agent/llm/provider.py b/app/agent/llm/provider.py index 9105ab16..e67a852b 100644 --- a/app/agent/llm/provider.py +++ b/app/agent/llm/provider.py @@ -672,6 +672,88 @@ class LLMProviderManager(metaclass=Singleton): description="腾讯兼容端点。", sort_order=170, ), + ProviderSpec( + id="china-unicom", + name="中国联通", + runtime="openai_compatible", + default_base_url="https://aigw-gzgy2.cucloud.cn:8443/v1", + base_url_presets=( + url_preset( + id="china-unicom-coding-openai", + label="Coding Plan / OpenAI", + value="https://aigw-gzgy2.cucloud.cn:8443/v1", + model_list_strategy="manual", + ), + url_preset( + id="china-unicom-coding-anthropic", + label="Coding Plan / Anthropic", + value="https://aigw-gzgy2.cucloud.cn:8443", + runtime="anthropic_compatible", + model_list_strategy="manual", + ), + ), + base_url_editable=True, + api_key_hint="填写联通云 AISP / Coding Plan 专属 API Key;模型名称请按控制台可用模型 ID 手动填写。", + supports_model_refresh=False, + model_list_strategy="manual", + description="联通云 AISP Coding Plan 兼容端点,支持 OpenAI 与 Anthropic 协议地址预设。", + sort_order=172, + ), + ProviderSpec( + id="china-mobile", + name="中国移动", + runtime="openai_compatible", + default_base_url="https://ecloud.10086.cn/api", + base_url_presets=( + url_preset( + id="china-mobile-moma", + label="MoMA / 移动云", + value="https://ecloud.10086.cn/api", + ), + url_preset( + id="china-mobile-coding", + label="Coding Plan / 移动智算包", + value="https://zhenze-huhehaote.cmecloud.cn/api/coding/v1", + ), + ), + base_url_editable=True, + api_key_hint="填写中国移动 MoMA / 移动云 Token 服务 API Key;如控制台下发专属域名,请覆盖 Base URL。", + supports_model_refresh=False, + model_list_strategy="manual", + description="中国移动 MoMA / 移动云 OpenAI-compatible Token 服务,支持专属域名覆盖。", + sort_order=174, + ), + ProviderSpec( + id="china-telecom", + name="中国电信", + runtime="openai_compatible", + default_base_url="https://wishub-x6.ctyun.cn/v1", + base_url_presets=( + url_preset( + id="china-telecom-token-service", + label="Token 服务 / 息壤", + value="https://wishub-x6.ctyun.cn/v1", + ), + url_preset( + id="china-telecom-coding-openai", + label="编码套餐 / OpenAI", + value="https://wishub-x6.ctyun.cn/coding/v1", + model_list_strategy="manual", + ), + url_preset( + id="china-telecom-coding-anthropic", + label="编码套餐 / Anthropic", + value="https://wishub-x6.ctyun.cn/coding/v1", + runtime="anthropic_compatible", + model_list_strategy="manual", + ), + ), + base_url_editable=True, + api_key_label="App Key", + api_key_hint="填写天翼云 Token 服务 / 息壤 App Key;编码套餐模型请按控制台展示的模型 ID 手动填写。", + description="天翼云 Token 服务(原模型推理服务)OpenAI-compatible 端点,支持通用与编码套餐地址预设。", + sort_order=176, + ), ProviderSpec( id="ollama-cloud", name="Ollama Cloud", diff --git a/scripts/local_setup.py b/scripts/local_setup.py index 1b9b5114..8bf6cd4d 100644 --- a/scripts/local_setup.py +++ b/scripts/local_setup.py @@ -90,6 +90,21 @@ LLM_PROVIDER_DEFAULTS = { "model": "", "base_url": "https://modelservice.jdcloud.com/v1", }, + "china-unicom": { + "model": "", + "base_url": "https://aigw-gzgy2.cucloud.cn:8443/v1", + "base_url_preset": "china-unicom-coding-openai", + }, + "china-mobile": { + "model": "", + "base_url": "https://ecloud.10086.cn/api", + "base_url_preset": "china-mobile-moma", + }, + "china-telecom": { + "model": "", + "base_url": "https://wishub-x6.ctyun.cn/v1", + "base_url_preset": "china-telecom-token-service", + }, "kuaishou-wanqing": { "model": "", "base_url": "https://wanqing.streamlakeapi.com/api/gateway/v1/endpoints", @@ -105,6 +120,9 @@ LLM_PROVIDER_FALLBACK_CHOICES = { "openrouter": "OpenRouter", "groq": "Groq", "jdcloud": "京东云", + "china-unicom": "中国联通", + "china-mobile": "中国移动", + "china-telecom": "中国电信", "kuaishou-wanqing": "快手万擎", } RUNTIME_PACKAGE = { diff --git a/tests/test_llm_provider_registry.py b/tests/test_llm_provider_registry.py index a5a9373b..30b41830 100644 --- a/tests/test_llm_provider_registry.py +++ b/tests/test_llm_provider_registry.py @@ -342,6 +342,127 @@ class LlmProviderRegistryTest(unittest.TestCase): self.assertEqual(models, []) + def test_builtin_provider_includes_china_operator_token_services(self): + """三大运营商 Token 服务应作为内置 OpenAI-compatible provider 暴露。""" + manager = LLMProviderManager() + + unicom = manager.get_provider("china-unicom") + mobile = manager.get_provider("china-mobile") + telecom = manager.get_provider("china-telecom") + + self.assertEqual(unicom.name, "中国联通") + self.assertEqual(unicom.default_base_url, "https://aigw-gzgy2.cucloud.cn:8443/v1") + self.assertEqual( + tuple((preset.id, preset.value, preset.runtime) for preset in unicom.base_url_presets), + ( + ( + "china-unicom-coding-openai", + "https://aigw-gzgy2.cucloud.cn:8443/v1", + None, + ), + ( + "china-unicom-coding-anthropic", + "https://aigw-gzgy2.cucloud.cn:8443", + "anthropic_compatible", + ), + ), + ) + self.assertTrue(unicom.base_url_editable) + self.assertFalse(unicom.supports_model_refresh) + self.assertEqual(unicom.model_list_strategy, "manual") + + self.assertEqual(mobile.name, "中国移动") + self.assertEqual(mobile.default_base_url, "https://ecloud.10086.cn/api") + self.assertEqual( + tuple((preset.id, preset.value) for preset in mobile.base_url_presets), + ( + ("china-mobile-moma", "https://ecloud.10086.cn/api"), + ( + "china-mobile-coding", + "https://zhenze-huhehaote.cmecloud.cn/api/coding/v1", + ), + ), + ) + self.assertTrue(mobile.base_url_editable) + self.assertFalse(mobile.supports_model_refresh) + self.assertEqual(mobile.model_list_strategy, "manual") + + self.assertEqual(telecom.name, "中国电信") + self.assertEqual(telecom.default_base_url, "https://wishub-x6.ctyun.cn/v1") + self.assertEqual(telecom.api_key_label, "App Key") + self.assertEqual( + tuple( + (preset.id, preset.value, preset.runtime, preset.model_list_strategy) + for preset in telecom.base_url_presets + ), + ( + ( + "china-telecom-token-service", + "https://wishub-x6.ctyun.cn/v1", + None, + None, + ), + ( + "china-telecom-coding-openai", + "https://wishub-x6.ctyun.cn/coding/v1", + None, + "manual", + ), + ( + "china-telecom-coding-anthropic", + "https://wishub-x6.ctyun.cn/coding/v1", + "anthropic_compatible", + "manual", + ), + ), + ) + self.assertTrue(telecom.base_url_editable) + + def test_china_operator_manual_model_presets_return_empty_model_list(self): + """未提供稳定全局模型目录的运营商套餐应回退为手动填写模型。""" + manager = LLMProviderManager() + + unicom_models = asyncio.run(manager.list_models(provider_id="china-unicom")) + mobile_models = asyncio.run(manager.list_models(provider_id="china-mobile")) + telecom_coding_models = asyncio.run( + manager.list_models( + provider_id="china-telecom", + base_url_preset_id="china-telecom-coding-openai", + ) + ) + + self.assertEqual(unicom_models, []) + self.assertEqual(mobile_models, []) + self.assertEqual(telecom_coding_models, []) + + def test_china_operator_anthropic_presets_resolve_runtime(self): + """运营商提供 Anthropic 协议地址时应切换到 anthropic_compatible runtime。""" + manager = LLMProviderManager() + + unicom_runtime = asyncio.run( + manager.resolve_runtime( + provider_id="china-unicom", + model=None, + api_key="sk-test", + base_url="https://aigw-gzgy2.cucloud.cn:8443", + base_url_preset_id="china-unicom-coding-anthropic", + ) + ) + telecom_runtime = asyncio.run( + manager.resolve_runtime( + provider_id="china-telecom", + model=None, + api_key="cp-test", + base_url="https://wishub-x6.ctyun.cn/coding/v1", + base_url_preset_id="china-telecom-coding-anthropic", + ) + ) + + self.assertEqual(unicom_runtime["runtime"], "anthropic_compatible") + self.assertEqual(unicom_runtime["base_url"], "https://aigw-gzgy2.cucloud.cn:8443") + self.assertEqual(telecom_runtime["runtime"], "anthropic_compatible") + self.assertEqual(telecom_runtime["base_url"], "https://wishub-x6.ctyun.cn/coding") + def test_builtin_minimax_provider_merges_general_and_coding_presets(self): manager = LLMProviderManager() diff --git a/tests/test_local_setup_llm_provider_prompt.py b/tests/test_local_setup_llm_provider_prompt.py index af89f140..33cf081d 100644 --- a/tests/test_local_setup_llm_provider_prompt.py +++ b/tests/test_local_setup_llm_provider_prompt.py @@ -185,7 +185,8 @@ class LocalSetupLlmProviderPromptTests(unittest.TestCase): self.assertEqual(provider, "my-provider_01") - def test_fallback_provider_choices_include_baidu_jdcloud_and_wanqing(self): + def test_fallback_provider_choices_include_builtin_domestic_token_providers(self): + """本地向导兜底列表应覆盖内置国内 Token 套餐 provider。""" module = load_local_setup_module() self.assertEqual( @@ -197,8 +198,21 @@ class LocalSetupLlmProviderPromptTests(unittest.TestCase): module.LLM_PROVIDER_FALLBACK_CHOICES["kuaishou-wanqing"], "快手万擎", ) + self.assertEqual( + module.LLM_PROVIDER_FALLBACK_CHOICES["china-unicom"], + "中国联通", + ) + self.assertEqual( + module.LLM_PROVIDER_FALLBACK_CHOICES["china-mobile"], + "中国移动", + ) + self.assertEqual( + module.LLM_PROVIDER_FALLBACK_CHOICES["china-telecom"], + "中国电信", + ) - def test_local_setup_defaults_include_baidu_jdcloud_and_wanqing_base_urls(self): + def test_local_setup_defaults_include_builtin_domestic_token_base_urls(self): + """本地向导默认 Base URL 应覆盖内置国内 Token 套餐 provider。""" module = load_local_setup_module() self.assertEqual( @@ -217,6 +231,30 @@ class LocalSetupLlmProviderPromptTests(unittest.TestCase): module.LLM_PROVIDER_DEFAULTS["kuaishou-wanqing"]["base_url_preset"], "kuaishou-wanqing-usage", ) + self.assertEqual( + module.LLM_PROVIDER_DEFAULTS["china-unicom"]["base_url"], + "https://aigw-gzgy2.cucloud.cn:8443/v1", + ) + self.assertEqual( + module.LLM_PROVIDER_DEFAULTS["china-unicom"]["base_url_preset"], + "china-unicom-coding-openai", + ) + self.assertEqual( + module.LLM_PROVIDER_DEFAULTS["china-mobile"]["base_url"], + "https://ecloud.10086.cn/api", + ) + self.assertEqual( + module.LLM_PROVIDER_DEFAULTS["china-mobile"]["base_url_preset"], + "china-mobile-moma", + ) + self.assertEqual( + module.LLM_PROVIDER_DEFAULTS["china-telecom"]["base_url"], + "https://wishub-x6.ctyun.cn/v1", + ) + self.assertEqual( + module.LLM_PROVIDER_DEFAULTS["china-telecom"]["base_url_preset"], + "china-telecom-token-service", + ) def test_collect_agent_config_prompts_for_duplicate_base_url_presets(self): module = load_local_setup_module()