From 1044ede559edcb100501d823af4c45d4a20fc830 Mon Sep 17 00:00:00 2001 From: Thibault Jaigu Date: Fri, 25 Sep 2026 13:14:55 +0100 Subject: [PATCH] =?UTF-8?q?feat(ai):=20=E6=96=B0=E5=A2=9E=20Requesty=20?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E6=9C=8D=E5=8A=A1=E9=A2=84=E8=AE=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 与 OpenRouter 相同,使用 OpenAI 兼容接口 https://router.requesty.ai/v1,从上游 /models 获取模型列表。同步更新 provider 校验、设置页展示、预设测试与文档。 --- docs/chat-ai.md | 2 +- frontend/components/AiSettings.vue | 1 + src/wechat_decrypt_tool/ai/providers.py | 1 + src/wechat_decrypt_tool/ai/schemas.py | 2 +- tests/test_ai_api.py | 6 +++--- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/chat-ai.md b/docs/chat-ai.md index 330c48f6..02353436 100644 --- a/docs/chat-ai.md +++ b/docs/chat-ai.md @@ -16,7 +16,7 @@ ### AI 服务预设 -「新增服务」提供 14 项预设:DeepSeek、Claude、Kimi、OpenAI、Google Gemini、通义千问(阿里云百炼)、智谱 GLM、豆包(火山方舟)、硅基流动、OpenRouter、Groq、Ollama、LM Studio 和自定义。可以按名称或别名搜索,例如「谷歌」「千问」「GLM」「火山」;自定义入口排在列表末尾。 +「新增服务」提供 15 项预设:DeepSeek、Claude、Kimi、OpenAI、Google Gemini、通义千问(阿里云百炼)、智谱 GLM、豆包(火山方舟)、硅基流动、OpenRouter、Requesty、Groq、Ollama、LM Studio 和自定义。可以按名称或别名搜索,例如「谷歌」「千问」「GLM」「火山」;自定义入口排在列表末尾。 预设会填写配置名称、API 地址和协议,地址仍可修改。国内平台默认使用国内通用 API 地址,不是 Coding Plan 专用入口。Gemini 使用 `/v1beta/openai` 兼容接口。切换预设会清空密钥、模型选择和图片能力,需要重新确认;不会复用原服务的密钥。 diff --git a/frontend/components/AiSettings.vue b/frontend/components/AiSettings.vue index 9659748a..ecc7345c 100644 --- a/frontend/components/AiSettings.vue +++ b/frontend/components/AiSettings.vue @@ -175,6 +175,7 @@ const providerPresentation = { doubao: { label: '豆包', caption: '火山方舟', hint: '火山方舟 · 国内通用接口', aliases: '字节 火山引擎 Volcengine Ark' }, siliconflow: { caption: 'SiliconCloud', hint: '硅基流动 · 国内模型服务', aliases: 'SiliconCloud' }, openrouter: { caption: '多模型聚合服务', hint: '聚合多个服务商的模型', aliases: '' }, + requesty: { caption: '多模型聚合服务', hint: '聚合多个服务商的模型', aliases: '' }, groq: { hint: 'Groq 官方兼容接口', aliases: '' }, ollama: { caption: '本地模型服务', hint: '本地模型 · 默认端口 11434', aliases: '本机 local' }, lmstudio: { caption: '本地模型服务', hint: '本地模型 · 默认端口 1234', aliases: '本机 local' }, diff --git a/src/wechat_decrypt_tool/ai/providers.py b/src/wechat_decrypt_tool/ai/providers.py index 9fa59fd2..30f86c19 100644 --- a/src/wechat_decrypt_tool/ai/providers.py +++ b/src/wechat_decrypt_tool/ai/providers.py @@ -57,6 +57,7 @@ def analysis_messages(prompt, schema=None, images=None): {"provider": "doubao", "name": "豆包(火山方舟)", "base_url": "https://ark.cn-beijing.volces.com/api/v3", "protocol": "openai"}, {"provider": "siliconflow", "name": "硅基流动", "base_url": "https://api.siliconflow.cn/v1", "protocol": "openai"}, {"provider": "openrouter", "name": "OpenRouter", "base_url": "https://openrouter.ai/api/v1", "protocol": "openai"}, + {"provider": "requesty", "name": "Requesty", "base_url": "https://router.requesty.ai/v1", "protocol": "openai"}, {"provider": "groq", "name": "Groq", "base_url": "https://api.groq.com/openai/v1", "protocol": "openai"}, {"provider": "ollama", "name": "Ollama", "base_url": "http://127.0.0.1:11434/v1", "protocol": "openai"}, {"provider": "lmstudio", "name": "LM Studio", "base_url": "http://127.0.0.1:1234/v1", "protocol": "openai"}, diff --git a/src/wechat_decrypt_tool/ai/schemas.py b/src/wechat_decrypt_tool/ai/schemas.py index c7271711..cfaa3b1b 100644 --- a/src/wechat_decrypt_tool/ai/schemas.py +++ b/src/wechat_decrypt_tool/ai/schemas.py @@ -21,7 +21,7 @@ class ProviderInput(BaseModel): name: str = Field(min_length=1, max_length=80) provider: Literal[ "deepseek", "xiaomi", "claude", "kimi", "openai", "gemini", "qwen", "zhipu", - "doubao", "siliconflow", "openrouter", "groq", "ollama", "lmstudio", "custom", + "doubao", "siliconflow", "openrouter", "requesty", "groq", "ollama", "lmstudio", "custom", ] = "deepseek" protocol: Literal["openai", "anthropic"] = "openai" base_url: str = Field(min_length=1, max_length=2048) diff --git a/tests/test_ai_api.py b/tests/test_ai_api.py index 6ca5db2f..394b4e95 100644 --- a/tests/test_ai_api.py +++ b/tests/test_ai_api.py @@ -19,8 +19,8 @@ def test_all_presets_can_be_created_read_and_edited(tmp_path): async def run(): async with httpx.AsyncClient(transport=httpx.ASGITransport(app=app, client=("127.0.0.1", 1)), base_url="http://localhost") as client: presets = (await client.get("/api/ai/settings")).json()["presets"] - assert len(presets) == 15 - assert {p["provider"] for p in presets} == {"deepseek", "xiaomi", "claude", "kimi", "custom", "openai", "gemini", "qwen", "zhipu", "doubao", "siliconflow", "openrouter", "groq", "ollama", "lmstudio"} + assert len(presets) == 16 + assert {p["provider"] for p in presets} == {"deepseek", "xiaomi", "claude", "kimi", "custom", "openai", "gemini", "qwen", "zhipu", "doubao", "siliconflow", "openrouter", "requesty", "groq", "ollama", "lmstudio"} assert presets[-1]["provider"] == "custom" for preset in presets: assert "model" not in preset @@ -35,7 +35,7 @@ async def run(): settings = await client.get("/api/ai/settings") assert "private-key" not in settings.text profiles = settings.json()["profiles"] - assert len(profiles) == 15 + assert len(profiles) == 16 assert all(p["name"] == "已编辑" and p["model"] == "another-model" for p in profiles) assert {p["provider"] for p in profiles} == {p["provider"] for p in presets} assert (await client.post("/api/ai/profiles", json={**presets[0], "provider": "unknown", "model": "test"})).status_code == 422