api-key-manager 3.0.1__tar.gz → 4.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/PKG-INFO +424 -104
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/README.md +422 -103
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/api_key_manager.egg-info/PKG-INFO +424 -104
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/api_key_manager.egg-info/SOURCES.txt +18 -6
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/api_key_manager.egg-info/requires.txt +1 -0
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/__init__.py +2 -2
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/api_models.py +0 -1
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/checker.py +2 -5
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/cli.py +9 -11
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/config.py +3 -2
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/core.py +43 -43
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/detector.py +353 -371
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/errors.py +14 -14
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/i18n.py +3 -3
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/logger.py +6 -7
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/model_capabilities.py +70 -70
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/parser.py +5 -6
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/providers/__init__.py +36 -35
- api_key_manager-4.0.0/key_manager/providers/ai302.py +7 -0
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/providers/anthropic.py +110 -109
- api_key_manager-4.0.0/key_manager/providers/baichuan.py +7 -0
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/providers/base.py +144 -61
- api_key_manager-4.0.0/key_manager/providers/cerebras.py +7 -0
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/providers/cohere.py +91 -90
- api_key_manager-4.0.0/key_manager/providers/config_providers.py +134 -0
- api_key_manager-4.0.0/key_manager/providers/cstcloud.py +8 -0
- api_key_manager-4.0.0/key_manager/providers/dashscope.py +30 -0
- api_key_manager-4.0.0/key_manager/providers/dashscope_coding.py +8 -0
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/providers/deepseek.py +161 -133
- api_key_manager-4.0.0/key_manager/providers/dmxapi.py +7 -0
- api_key_manager-4.0.0/key_manager/providers/doubao.py +7 -0
- api_key_manager-4.0.0/key_manager/providers/fireworks.py +7 -0
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/providers/google.py +2 -1
- api_key_manager-4.0.0/key_manager/providers/grok.py +7 -0
- api_key_manager-4.0.0/key_manager/providers/groq.py +7 -0
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/providers/huggingface.py +37 -37
- api_key_manager-4.0.0/key_manager/providers/hyperbolic.py +7 -0
- api_key_manager-4.0.0/key_manager/providers/infini.py +33 -0
- api_key_manager-4.0.0/key_manager/providers/infini_coding.py +8 -0
- api_key_manager-4.0.0/key_manager/providers/kimi.py +31 -0
- api_key_manager-4.0.0/key_manager/providers/kimi_coding.py +8 -0
- api_key_manager-4.0.0/key_manager/providers/longcat.py +7 -0
- api_key_manager-4.0.0/key_manager/providers/mimo.py +7 -0
- api_key_manager-4.0.0/key_manager/providers/mimo_plan.py +8 -0
- api_key_manager-4.0.0/key_manager/providers/minimax.py +7 -0
- api_key_manager-4.0.0/key_manager/providers/minimax_plan.py +8 -0
- api_key_manager-4.0.0/key_manager/providers/mistral.py +7 -0
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/providers/models_registry.py +2962 -2962
- api_key_manager-4.0.0/key_manager/providers/modelscope.py +21 -0
- api_key_manager-4.0.0/key_manager/providers/nvidia.py +7 -0
- api_key_manager-4.0.0/key_manager/providers/ocoolai.py +7 -0
- api_key_manager-4.0.0/key_manager/providers/openai.py +56 -0
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/providers/opencode.py +107 -89
- api_key_manager-4.0.0/key_manager/providers/opencode_zen.py +8 -0
- api_key_manager-4.0.0/key_manager/providers/openrouter.py +29 -0
- api_key_manager-4.0.0/key_manager/providers/perplexity.py +7 -0
- api_key_manager-4.0.0/key_manager/providers/poe.py +7 -0
- api_key_manager-4.0.0/key_manager/providers/ppio.py +7 -0
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/providers/replicate.py +37 -37
- api_key_manager-4.0.0/key_manager/providers/siliconflow.py +31 -0
- api_key_manager-4.0.0/key_manager/providers/stepfun.py +30 -0
- api_key_manager-4.0.0/key_manager/providers/tencent_hunyuan.py +8 -0
- api_key_manager-4.0.0/key_manager/providers/together.py +32 -0
- api_key_manager-4.0.0/key_manager/providers/yi.py +7 -0
- api_key_manager-4.0.0/key_manager/providers/zai.py +7 -0
- api_key_manager-4.0.0/key_manager/providers/zhipu.py +37 -0
- api_key_manager-4.0.0/key_manager/providers/zhipu_coding.py +8 -0
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/proxy.py +2 -2
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/ssrf.py +4 -5
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/storage.py +43 -6
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/tester.py +3 -3
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/validator.py +3 -2
- api_key_manager-4.0.0/key_manager/web/__init__.py +35 -0
- api_key_manager-4.0.0/key_manager/web/_app.py +168 -0
- api_key_manager-4.0.0/key_manager/web/middleware.py +197 -0
- api_key_manager-4.0.0/key_manager/web/progress.py +78 -0
- api_key_manager-4.0.0/key_manager/web/routes/__init__.py +19 -0
- api_key_manager-4.0.0/key_manager/web/routes/balance.py +93 -0
- api_key_manager-4.0.0/key_manager/web/routes/check.py +322 -0
- api_key_manager-4.0.0/key_manager/web/routes/keys.py +242 -0
- api_key_manager-4.0.0/key_manager/web/routes/misc.py +254 -0
- api_key_manager-4.0.0/key_manager/web/routes/models.py +279 -0
- api_key_manager-4.0.0/key_manager/web/routes/providers.py +229 -0
- api_key_manager-4.0.0/key_manager/web/routes/stats.py +91 -0
- api_key_manager-4.0.0/key_manager/web/routes/test.py +441 -0
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/webhook.py +9 -11
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/pyproject.toml +2 -2
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/tests/test_api_endpoints.py +24 -80
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/tests/test_base_check.py +4 -27
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/tests/test_bug_fixes.py +34 -35
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/tests/test_checker.py +6 -14
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/tests/test_core.py +0 -5
- api_key_manager-4.0.0/tests/test_deepseek_balance.py +203 -0
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/tests/test_e2e.py +20 -72
- api_key_manager-3.0.1/tests/test_parser_supplement.py → api_key_manager-4.0.0/tests/test_parser.py +158 -19
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/tests/test_provider_detection.py +19 -88
- api_key_manager-4.0.0/tests/test_provider_refactoring.py +262 -0
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/tests/test_security.py +79 -3
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/tests/test_storage.py +19 -3
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/tests/test_tester.py +0 -11
- api_key_manager-4.0.0/tests/test_token_limit_fast.py +161 -0
- api_key_manager-4.0.0/tests/test_v320_changes.py +193 -0
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/tests/test_validator.py +190 -8
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/tests/test_webhook.py +0 -10
- api_key_manager-3.0.1/key_manager/providers/ai302.py +0 -76
- api_key_manager-3.0.1/key_manager/providers/baichuan.py +0 -69
- api_key_manager-3.0.1/key_manager/providers/cerebras.py +0 -76
- api_key_manager-3.0.1/key_manager/providers/cstcloud.py +0 -94
- api_key_manager-3.0.1/key_manager/providers/dashscope.py +0 -92
- api_key_manager-3.0.1/key_manager/providers/dashscope_coding.py +0 -94
- api_key_manager-3.0.1/key_manager/providers/dmxapi.py +0 -76
- api_key_manager-3.0.1/key_manager/providers/doubao.py +0 -76
- api_key_manager-3.0.1/key_manager/providers/fireworks.py +0 -76
- api_key_manager-3.0.1/key_manager/providers/grok.py +0 -75
- api_key_manager-3.0.1/key_manager/providers/groq.py +0 -76
- api_key_manager-3.0.1/key_manager/providers/hyperbolic.py +0 -76
- api_key_manager-3.0.1/key_manager/providers/infini.py +0 -102
- api_key_manager-3.0.1/key_manager/providers/infini_coding.py +0 -96
- api_key_manager-3.0.1/key_manager/providers/kimi.py +0 -93
- api_key_manager-3.0.1/key_manager/providers/kimi_coding.py +0 -96
- api_key_manager-3.0.1/key_manager/providers/longcat.py +0 -76
- api_key_manager-3.0.1/key_manager/providers/mimo.py +0 -76
- api_key_manager-3.0.1/key_manager/providers/mimo_plan.py +0 -107
- api_key_manager-3.0.1/key_manager/providers/minimax.py +0 -69
- api_key_manager-3.0.1/key_manager/providers/minimax_plan.py +0 -94
- api_key_manager-3.0.1/key_manager/providers/mistral.py +0 -76
- api_key_manager-3.0.1/key_manager/providers/modelscope.py +0 -120
- api_key_manager-3.0.1/key_manager/providers/nvidia.py +0 -76
- api_key_manager-3.0.1/key_manager/providers/ocoolai.py +0 -76
- api_key_manager-3.0.1/key_manager/providers/openai.py +0 -101
- api_key_manager-3.0.1/key_manager/providers/opencode_zen.py +0 -89
- api_key_manager-3.0.1/key_manager/providers/openrouter.py +0 -91
- api_key_manager-3.0.1/key_manager/providers/perplexity.py +0 -75
- api_key_manager-3.0.1/key_manager/providers/poe.py +0 -76
- api_key_manager-3.0.1/key_manager/providers/ppio.py +0 -76
- api_key_manager-3.0.1/key_manager/providers/siliconflow.py +0 -93
- api_key_manager-3.0.1/key_manager/providers/stepfun.py +0 -99
- api_key_manager-3.0.1/key_manager/providers/tencent_hunyuan.py +0 -94
- api_key_manager-3.0.1/key_manager/providers/together.py +0 -101
- api_key_manager-3.0.1/key_manager/providers/yi.py +0 -69
- api_key_manager-3.0.1/key_manager/providers/zai.py +0 -76
- api_key_manager-3.0.1/key_manager/providers/zhipu.py +0 -99
- api_key_manager-3.0.1/key_manager/providers/zhipu_coding.py +0 -96
- api_key_manager-3.0.1/key_manager/web.py +0 -1676
- api_key_manager-3.0.1/tests/test_detector.py +0 -317
- api_key_manager-3.0.1/tests/test_parser.py +0 -149
- api_key_manager-3.0.1/tests/test_ssrf_supplement.py +0 -81
- api_key_manager-3.0.1/tests/test_validator_supplement.py +0 -196
- api_key_manager-3.0.1/tests/test_web_fixes.py +0 -161
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/api_key_manager.egg-info/dependency_links.txt +0 -0
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/api_key_manager.egg-info/entry_points.txt +0 -0
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/api_key_manager.egg-info/top_level.txt +0 -0
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/__main__.py +0 -0
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/py.typed +0 -0
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/url_override.py +0 -0
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/setup.cfg +0 -0
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/tests/test_errors.py +0 -0
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/tests/test_i18n.py +0 -0
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/tests/test_logger.py +0 -0
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/tests/test_openapi.py +0 -0
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/tests/test_providers.py +0 -0
- {api_key_manager-3.0.1 → api_key_manager-4.0.0}/tests/test_proxy.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: api-key-manager
|
|
3
|
-
Version:
|
|
3
|
+
Version: 4.0.0
|
|
4
4
|
Summary: Batch manage API keys for 45+ AI providers with CLI and Web interfaces
|
|
5
5
|
Author: Townrain
|
|
6
6
|
License: MIT
|
|
@@ -25,6 +25,7 @@ Requires-Dist: fastapi>=0.115.0
|
|
|
25
25
|
Requires-Dist: uvicorn>=0.32.0
|
|
26
26
|
Requires-Dist: python-multipart>=0.0.9
|
|
27
27
|
Requires-Dist: cryptography>=42.0.0
|
|
28
|
+
Requires-Dist: jinja2>=3.0.0
|
|
28
29
|
Provides-Extra: dev
|
|
29
30
|
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
30
31
|
Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
|
|
@@ -42,6 +43,8 @@ Requires-Dist: ruff>=0.4.0; extra == "dev"
|
|
|
42
43
|
- **能力测试** - 测试 Token 上限和并发能力
|
|
43
44
|
- **模型筛选** - 按类型筛选模型(推理/视觉/联网/免费/嵌入/重排/工具)
|
|
44
45
|
- **智能检测** - 前缀匹配 + 模式匹配 + 错误签名匹配,三级提供商自动识别
|
|
46
|
+
- **自动发现** - 新增服务商只需创建 Python 文件,自动注册
|
|
47
|
+
- **可扩展架构** - 支持 YAML 配置自定义服务商,Web API 管理
|
|
45
48
|
- **Web 界面** - 赛博朋克风格的管理界面
|
|
46
49
|
- **代理支持** - 支持 HTTP/SOCKS 代理
|
|
47
50
|
- **加密存储** - AES-256-GCM 加密存储 API 密钥,随机盐值
|
|
@@ -118,6 +121,64 @@ Requires-Dist: ruff>=0.4.0; extra == "dev"
|
|
|
118
121
|
| Kimi Coding | 编程版 |
|
|
119
122
|
| Infini Coding | 编程版 |
|
|
120
123
|
|
|
124
|
+
## 添加自定义服务商
|
|
125
|
+
|
|
126
|
+
### 方式一:Python 文件(推荐)
|
|
127
|
+
|
|
128
|
+
创建 `key_manager/providers/my_llm.py`:
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
from .base import ProviderBase
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class MyLlmProvider(ProviderBase):
|
|
135
|
+
name = "my-llm"
|
|
136
|
+
base_url = "https://api.my-llm.com/v1"
|
|
137
|
+
check_endpoint = "/models"
|
|
138
|
+
check_model = "my-model"
|
|
139
|
+
display_name = "My LLM"
|
|
140
|
+
key_prefixes = ["myllm-"]
|
|
141
|
+
error_signatures = ["my-llm", "invalid api key"]
|
|
142
|
+
website_url = "https://my-llm.com"
|
|
143
|
+
docs_url = "https://docs.my-llm.com"
|
|
144
|
+
|
|
145
|
+
def build_headers(self, key: str) -> dict:
|
|
146
|
+
return {"Authorization": f"Bearer {key}"}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
重启服务即可自动发现。
|
|
150
|
+
|
|
151
|
+
### 方式二:YAML 配置
|
|
152
|
+
|
|
153
|
+
在 `config.yaml` 中添加:
|
|
154
|
+
|
|
155
|
+
```yaml
|
|
156
|
+
providers:
|
|
157
|
+
custom:
|
|
158
|
+
- name: "my-llm"
|
|
159
|
+
base_url: "https://api.my-llm.com/v1"
|
|
160
|
+
check_endpoint: "/models"
|
|
161
|
+
check_model: "my-model"
|
|
162
|
+
display_name: "My LLM"
|
|
163
|
+
key_prefixes: ["myllm-"]
|
|
164
|
+
auth_type: bearer
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### 方式三:Web API
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
# 添加服务商
|
|
171
|
+
curl -X POST http://localhost:18001/api/providers \
|
|
172
|
+
-H "Content-Type: application/json" \
|
|
173
|
+
-d '{"name":"my-llm","base_url":"https://api.my-llm.com/v1","check_endpoint":"/models"}'
|
|
174
|
+
|
|
175
|
+
# 列出所有服务商
|
|
176
|
+
curl http://localhost:18001/api/providers
|
|
177
|
+
|
|
178
|
+
# 删除服务商
|
|
179
|
+
curl -X DELETE http://localhost:18001/api/providers/my-llm
|
|
180
|
+
```
|
|
181
|
+
|
|
121
182
|
## 快速开始
|
|
122
183
|
|
|
123
184
|
### 安装
|
|
@@ -221,133 +282,246 @@ set KEY_MANAGER_API_KEY=your-api-key
|
|
|
221
282
|
|
|
222
283
|
系统采用**全并发探测**策略,自动识别 45+ 个 AI 服务商的 API 密钥。
|
|
223
284
|
|
|
224
|
-
|
|
285
|
+
### 检测流程(重要!)
|
|
225
286
|
|
|
226
287
|
```
|
|
227
|
-
1.
|
|
288
|
+
1. 前缀匹配 - 检查唯一前缀(如 sk-proj- → OpenAI,AIza → Google)
|
|
228
289
|
2. 格式匹配 - 检查特殊格式(如智谱的 {id}.{secret} 格式)
|
|
229
|
-
3. 全并发探测 -
|
|
290
|
+
3. 全并发探测 - 同时向所有服务商发送请求
|
|
230
291
|
4. 签名匹配 - 如果无200响应,通过错误响应体签名识别服务商
|
|
231
292
|
```
|
|
232
293
|
|
|
233
|
-
|
|
294
|
+
### ⚠️ 关键概念:/v1/models vs /chat/completions
|
|
234
295
|
|
|
235
|
-
|
|
296
|
+
**这两个端点的作用完全不同,不能混用!**
|
|
236
297
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
298
|
+
| 端点 | 作用 | 返回200的含义 |
|
|
299
|
+
|------|------|--------------|
|
|
300
|
+
| `/v1/models` | 获取模型列表 | 只表示可以获取模型列表,**不能**判断密钥是否有效 |
|
|
301
|
+
| `/chat/completions` | 调用模型 | 表示密钥对该提供商有效,**这才是判断提供商的依据** |
|
|
302
|
+
|
|
303
|
+
**常见错误:**
|
|
304
|
+
- ❌ 用 `/v1/models` 返回200来判断提供商 → 错误!
|
|
305
|
+
- ✅ 用 `/chat/completions` 返回200来判断提供商 → 正确!
|
|
306
|
+
|
|
307
|
+
### 检测流程详解
|
|
308
|
+
|
|
309
|
+
#### Step 1: 前缀匹配
|
|
310
|
+
|
|
311
|
+
检查密钥是否匹配唯一前缀:
|
|
241
312
|
|
|
242
313
|
```python
|
|
243
|
-
#
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
format_candidates = detect_by_format(key) # 如 {id}.{secret} → 智谱/Z.AI
|
|
252
|
-
if format_candidates:
|
|
253
|
-
return format_candidates[0]
|
|
254
|
-
|
|
255
|
-
# Step 3: 全并发探测
|
|
256
|
-
tasks = []
|
|
257
|
-
for name, provider in PROVIDERS.items():
|
|
258
|
-
models = PROVIDER_MODELS.get(name, [])[:5]
|
|
259
|
-
for model in models:
|
|
260
|
-
tasks.append(try_model(name, model))
|
|
261
|
-
|
|
262
|
-
# 第一个返回200的胜出
|
|
263
|
-
for coro in asyncio.as_completed(tasks):
|
|
264
|
-
name, valid, body = await coro
|
|
265
|
-
if valid:
|
|
266
|
-
return name
|
|
267
|
-
|
|
268
|
-
# Step 4: 签名匹配
|
|
269
|
-
return match_by_signature(error_bodies)
|
|
314
|
+
# 唯一前缀 → 直接返回
|
|
315
|
+
"sk-proj-" → OpenAI
|
|
316
|
+
"sk-ant-api03-" → Anthropic
|
|
317
|
+
"AIza" → Google
|
|
318
|
+
"ms-" → ModelScope
|
|
319
|
+
|
|
320
|
+
# 共享前缀 → 需要进一步探测
|
|
321
|
+
"sk-" → 20+ 提供商(DeepSeek, OpenAI, 等)
|
|
270
322
|
```
|
|
271
323
|
|
|
272
|
-
|
|
324
|
+
#### Step 2: 格式匹配
|
|
273
325
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
智谱和 Z.AI 使用 `{id}.{secret}` 格式的密钥(非 `sk-` 开头):
|
|
326
|
+
检查密钥是否匹配特殊格式:
|
|
277
327
|
|
|
278
328
|
```python
|
|
279
|
-
#
|
|
329
|
+
# 智谱/Z.AI 格式:{id}.{secret}
|
|
280
330
|
50bcde33b8774aa8a2cc1bd6d39444ae.ifriyNWRLStzpLEs
|
|
281
|
-
|
|
282
|
-
# 正则表达式匹配
|
|
283
|
-
ZHIPU_KEY_PATTERN = re.compile(r'^[a-zA-Z0-9]{20,50}\.[a-zA-Z0-9]{10,50}$')
|
|
331
|
+
→ 返回 ["zhipu", "zai"]
|
|
284
332
|
```
|
|
285
333
|
|
|
286
|
-
|
|
334
|
+
#### Step 3: 全并发探测(核心逻辑)
|
|
287
335
|
|
|
288
|
-
|
|
336
|
+
**重要:这一步用 `/chat/completions` 验证,不是 `/v1/models`!**
|
|
289
337
|
|
|
290
|
-
|
|
338
|
+
```python
|
|
339
|
+
async def detect_provider(client, key):
|
|
340
|
+
# Step 3.1: 获取所有提供商的模型列表(/v1/models)
|
|
341
|
+
# 这一步只是为了获取模型列表,不能判断提供商
|
|
342
|
+
models = {}
|
|
343
|
+
for name, provider in PROVIDERS.items():
|
|
344
|
+
resp = await client.get(f"{provider.base_url}/v1/models")
|
|
345
|
+
if resp.status_code == 200:
|
|
346
|
+
models[name] = extract_models(resp.json())
|
|
347
|
+
|
|
348
|
+
# Step 3.2: 并发测试所有(提供商,模型)对的 /chat/completions
|
|
349
|
+
# 这一步才是判断提供商的依据!
|
|
350
|
+
tasks = []
|
|
351
|
+
for name, model_list in models.items():
|
|
352
|
+
for model in model_list:
|
|
353
|
+
tasks.append(try_chat_completion(name, model))
|
|
354
|
+
|
|
355
|
+
# 第一个返回200的提供商胜出
|
|
356
|
+
for coro in asyncio.as_completed(tasks):
|
|
357
|
+
name, valid, status_code = await coro
|
|
358
|
+
if valid: # /chat/completions 返回200
|
|
359
|
+
return name
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
#### Step 4: 签名匹配
|
|
363
|
+
|
|
364
|
+
如果所有 `/chat/completions` 都失败,通过错误响应体识别提供商:
|
|
291
365
|
|
|
292
366
|
```python
|
|
367
|
+
# 错误签名匹配
|
|
293
368
|
UNIQUE_SIGNATURES = {
|
|
294
|
-
# 国内服务商
|
|
295
369
|
"dashscope": ["model-studio", "modelstudio", "apikey-error"],
|
|
296
|
-
"tencent-hunyuan": ["hunyuan", "console.cloud.tencent.com"],
|
|
297
|
-
"baichuan": ["baichuan-ai.com", "platform.baichuan-ai.com"],
|
|
298
|
-
"minimax": ["authorized_error", "login fail"],
|
|
299
|
-
"yi": ["illegal apikey"],
|
|
300
|
-
"kimi": ["invalid_authentication_error"],
|
|
301
|
-
"siliconflow": ["api key is invalid"],
|
|
302
|
-
"stepfun": ["incorrect api key provided", "invalid_api_key"],
|
|
303
|
-
"doubao": ["authenticationerror"],
|
|
304
|
-
"infini": ["请使用正确的api key进行请求"],
|
|
305
|
-
"zhipu": ["令牌已过期或验证不正确"],
|
|
306
|
-
"mimo": ["invalid api key", "please provide valid api key"],
|
|
307
|
-
# 国外服务商
|
|
308
|
-
"deepseek": ["authentication fails"],
|
|
309
370
|
"anthropic": ["request not allowed", "anthropic", "x-api-key"],
|
|
310
|
-
"openrouter": ["missing authentication header"],
|
|
311
|
-
"mistral": ["mistral", "la plateforme"],
|
|
312
|
-
"replicate": ["unauthenticated", "you did not pass a valid authentication token"],
|
|
313
|
-
"huggingface": ["huggingface", "hf_"],
|
|
314
|
-
"fireworks": ["fireworks", "accounts/fireworks"],
|
|
315
|
-
"perplexity": ["perplexity"],
|
|
316
|
-
"grok": ["console.x.ai"],
|
|
317
371
|
"openai": ["platform.openai.com"],
|
|
318
|
-
|
|
319
|
-
"groq": ["groq"],
|
|
372
|
+
# ... 更多签名
|
|
320
373
|
}
|
|
321
|
-
```
|
|
322
|
-
|
|
323
|
-
#### 签名匹配算法
|
|
324
|
-
|
|
325
|
-
```python
|
|
326
|
-
def score_provider(provider_name, error_body, status_code):
|
|
327
|
-
body = error_body.lower()
|
|
328
|
-
score = 0
|
|
329
|
-
weight = 100 # 每个匹配的签名加100分
|
|
330
|
-
|
|
331
|
-
for sig in UNIQUE_SIGNATURES.get(provider_name, []):
|
|
332
|
-
if sig.lower() in body:
|
|
333
|
-
score += weight
|
|
334
|
-
|
|
335
|
-
return score
|
|
336
374
|
|
|
337
375
|
# 匹配阈值:至少2个签名匹配(200分)才返回结果
|
|
338
376
|
if best_score >= 200:
|
|
339
377
|
return best_name
|
|
340
378
|
```
|
|
341
379
|
|
|
380
|
+
### 免费模型的重要性
|
|
381
|
+
|
|
382
|
+
**某些提供商(如 OpenCode Zen)提供免费模型,这些模型对检测非常重要!**
|
|
383
|
+
|
|
384
|
+
```python
|
|
385
|
+
# OpenCode Zen 的模型列表:
|
|
386
|
+
# - claude-fable-5 (付费)
|
|
387
|
+
# - deepseek-v4-flash-free (免费) ✅
|
|
388
|
+
# - mimo-v2.5-free (免费) ✅
|
|
389
|
+
# - ...
|
|
390
|
+
|
|
391
|
+
# 如果密钥只对免费模型有效:
|
|
392
|
+
# - /chat/completions + claude-fable-5 → 401 (付费模型,无权限)
|
|
393
|
+
# - /chat/completions + deepseek-v4-flash-free → 200 (免费模型,有权限) ✅
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
**检测逻辑会并发测试所有模型,包括免费模型。只要有一个模型返回200,提供商就会被正确识别。**
|
|
397
|
+
|
|
342
398
|
### 检测优先级
|
|
343
399
|
|
|
344
|
-
| 优先级 | 方法 | 说明 |
|
|
345
|
-
|
|
346
|
-
| 1 |
|
|
347
|
-
| 2 | 格式匹配 | 特殊格式,如 `{id}.{secret}` → 智谱 |
|
|
348
|
-
| 3 | 全并发探测 | 第一个返回200
|
|
349
|
-
| 4 | 签名匹配 | 通过错误响应体识别,需至少2个签名匹配 |
|
|
400
|
+
| 优先级 | 方法 | 端点 | 说明 |
|
|
401
|
+
|--------|------|------|------|
|
|
402
|
+
| 1 | 前缀匹配 | - | 唯一前缀,如 `sk-proj-` → OpenAI |
|
|
403
|
+
| 2 | 格式匹配 | - | 特殊格式,如 `{id}.{secret}` → 智谱 |
|
|
404
|
+
| 3 | 全并发探测 | `/chat/completions` | 第一个返回200的提供商胜出 |
|
|
405
|
+
| 4 | 签名匹配 | - | 通过错误响应体识别,需至少2个签名匹配 |
|
|
406
|
+
|
|
407
|
+
### 常见问题
|
|
408
|
+
|
|
409
|
+
#### Q: 为什么我的密钥被错误识别为其他提供商?
|
|
410
|
+
|
|
411
|
+
**A: 可能是因为:**
|
|
412
|
+
1. 密钥使用共享前缀(如 `sk-`),需要通过 `/chat/completions` 验证
|
|
413
|
+
2. 密钥只对免费模型有效,但检测逻辑没有测试免费模型
|
|
414
|
+
3. 提供商的 `/v1/models` 返回200,但 `/chat/completions` 返回401
|
|
415
|
+
|
|
416
|
+
#### Q: 为什么 `/v1/models` 返回200,但检测失败?
|
|
417
|
+
|
|
418
|
+
**A: 因为 `/v1/models` 返回200不能判断提供商!**
|
|
419
|
+
- `/v1/models` 只表示可以获取模型列表
|
|
420
|
+
- `/chat/completions` 返回200才能判断提供商
|
|
421
|
+
|
|
422
|
+
#### Q: 如何确保检测正确?
|
|
423
|
+
|
|
424
|
+
**A: 确保以下几点:**
|
|
425
|
+
1. 密钥对至少一个模型有调用权限(包括免费模型)
|
|
426
|
+
2. 提供商的 `/chat/completions` 端点正常工作
|
|
427
|
+
3. 密钥没有过期或被撤销
|
|
428
|
+
|
|
429
|
+
### 检测流程图
|
|
350
430
|
|
|
431
|
+
```
|
|
432
|
+
输入: API Key
|
|
433
|
+
│
|
|
434
|
+
▼
|
|
435
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
436
|
+
│ Step 1: 前缀匹配 │
|
|
437
|
+
│ - 唯一前缀 → 直接返回(如 sk-proj- → OpenAI) │
|
|
438
|
+
│ - 共享前缀 → 继续下一步 │
|
|
439
|
+
└─────────────────────────────────────────────────────────────┘
|
|
440
|
+
│
|
|
441
|
+
▼
|
|
442
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
443
|
+
│ Step 2: 格式匹配 │
|
|
444
|
+
│ - 智谱格式 {id}.{secret} → 返回 ["zhipu", "zai"] │
|
|
445
|
+
└─────────────────────────────────────────────────────────────┘
|
|
446
|
+
│
|
|
447
|
+
▼
|
|
448
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
449
|
+
│ Step 3: 全并发探测(用 /chat/completions 验证) │
|
|
450
|
+
│ │
|
|
451
|
+
│ 3.1 获取所有提供商的模型列表(/v1/models) │
|
|
452
|
+
│ - 这一步只是为了获取模型,不能判断提供商 │
|
|
453
|
+
│ │
|
|
454
|
+
│ 3.2 并发测试所有(提供商,模型)对的 /chat/completions │
|
|
455
|
+
│ - 第一个返回200的提供商胜出 │
|
|
456
|
+
│ - 包括免费模型和付费模型 │
|
|
457
|
+
└─────────────────────────────────────────────────────────────┘
|
|
458
|
+
│
|
|
459
|
+
▼
|
|
460
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
461
|
+
│ Step 4: 签名匹配(如果 /chat/completions 都失败) │
|
|
462
|
+
│ - 通过错误响应体中的关键词识别提供商 │
|
|
463
|
+
│ - 需要至少2个签名匹配(200分)才返回结果 │
|
|
464
|
+
└─────────────────────────────────────────────────────────────┘
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
### 代码实现
|
|
468
|
+
|
|
469
|
+
检测逻辑在 `key_manager/detector.py` 中实现:
|
|
470
|
+
|
|
471
|
+
```python
|
|
472
|
+
async def detect_provider(client, key: str, suspected_provider: str = None) -> str:
|
|
473
|
+
"""Detect provider by concurrently probing ALL providers with multiple models.
|
|
474
|
+
|
|
475
|
+
Strategy:
|
|
476
|
+
1. If suspected_provider given, try it first
|
|
477
|
+
2. If key matches unique pattern, try that provider
|
|
478
|
+
3. Otherwise, concurrently probe ALL providers with their top 5 models
|
|
479
|
+
4. First provider returning 200 wins
|
|
480
|
+
"""
|
|
481
|
+
# Step 1: If suspected provider, try it first
|
|
482
|
+
if suspected_provider:
|
|
483
|
+
provider_name = suspected_provider.lower()
|
|
484
|
+
if provider_name in PROVIDERS:
|
|
485
|
+
return provider_name
|
|
486
|
+
|
|
487
|
+
# Step 2: Try format matching (e.g., Zhipu's {id}.{secret})
|
|
488
|
+
format_candidates = detect_by_format(key)
|
|
489
|
+
if format_candidates:
|
|
490
|
+
for name in format_candidates:
|
|
491
|
+
if name in PROVIDERS:
|
|
492
|
+
return name
|
|
493
|
+
|
|
494
|
+
# Step 3: Try prefix matching
|
|
495
|
+
prefix_candidates = detect_by_prefix(key)
|
|
496
|
+
if prefix_candidates:
|
|
497
|
+
if len(prefix_candidates) == 1:
|
|
498
|
+
return prefix_candidates[0]
|
|
499
|
+
# If multiple candidates, continue to Step 4
|
|
500
|
+
|
|
501
|
+
# Step 4: Concurrently probe ALL providers
|
|
502
|
+
# First, get models from all providers concurrently
|
|
503
|
+
model_tasks = [get_provider_models(name, provider) for name, provider in PROVIDERS.items()]
|
|
504
|
+
model_results = await asyncio.gather(*model_tasks)
|
|
505
|
+
|
|
506
|
+
# Build tasks: (provider_name, model) pairs
|
|
507
|
+
tasks = []
|
|
508
|
+
for name, models, is_valid in model_results:
|
|
509
|
+
if models:
|
|
510
|
+
for model in models:
|
|
511
|
+
tasks.append((name, model))
|
|
512
|
+
|
|
513
|
+
# Concurrently check all (provider, model) pairs
|
|
514
|
+
all_tasks = [try_model(name, model) for name, model in tasks]
|
|
515
|
+
|
|
516
|
+
# First provider returning 200 wins
|
|
517
|
+
for coro in asyncio.as_completed(all_tasks):
|
|
518
|
+
name, valid, body, status_code = await coro
|
|
519
|
+
if valid:
|
|
520
|
+
return name
|
|
521
|
+
|
|
522
|
+
# Step 5: Signature matching
|
|
523
|
+
return match_by_signature(error_bodies)
|
|
524
|
+
```
|
|
351
525
|
|
|
352
526
|
## 错误信息简化
|
|
353
527
|
|
|
@@ -445,7 +619,20 @@ key/
|
|
|
445
619
|
├── key_manager/ # 核心包
|
|
446
620
|
│ ├── __init__.py # 包导出
|
|
447
621
|
│ ├── cli.py # CLI 入口
|
|
448
|
-
│ ├── web
|
|
622
|
+
│ ├── web/ # Web 模块 (v4.0.0 重构)
|
|
623
|
+
│ │ ├── __init__.py # 包导出
|
|
624
|
+
│ │ ├── _app.py # FastAPI 应用入口
|
|
625
|
+
│ │ ├── middleware.py # 中间件和错误处理器
|
|
626
|
+
│ │ ├── progress.py # ProgressTracker和SSE辅助
|
|
627
|
+
│ │ └── routes/ # 路由模块
|
|
628
|
+
│ │ ├── keys.py # 密钥管理路由
|
|
629
|
+
│ │ ├── check.py # 验证路由
|
|
630
|
+
│ │ ├── test.py # 测试路由
|
|
631
|
+
│ │ ├── balance.py # 余额查询路由
|
|
632
|
+
│ │ ├── models.py # 模型路由
|
|
633
|
+
│ │ ├── providers.py # 提供商路由
|
|
634
|
+
│ │ ├── stats.py # 统计路由
|
|
635
|
+
│ │ └── misc.py # 杂项路由
|
|
449
636
|
│ ├── config.py # 配置加载
|
|
450
637
|
│ ├── storage.py # AES-256-GCM 加密存储
|
|
451
638
|
│ ├── errors.py # 结构化错误码
|
|
@@ -467,7 +654,48 @@ key/
|
|
|
467
654
|
│ ├── openai.py # OpenAI
|
|
468
655
|
│ ├── anthropic.py # Anthropic
|
|
469
656
|
│ └── ... # 更多提供商
|
|
657
|
+
├── static/ # 前端静态资源 (v4.0.0 重构)
|
|
658
|
+
│ ├── css/
|
|
659
|
+
│ │ ├── tokens.css # CSS 变量
|
|
660
|
+
│ │ ├── base.css # 基础样式
|
|
661
|
+
│ │ ├── components.css # 组件入口 (@import)
|
|
662
|
+
│ │ ├── components/ # 独立组件样式
|
|
663
|
+
│ │ │ ├── button.css # 按钮
|
|
664
|
+
│ │ │ ├── form.css # 表单控件
|
|
665
|
+
│ │ │ ├── card.css # 卡片、操作区
|
|
666
|
+
│ │ │ ├── table.css # 表格、徽章、分页
|
|
667
|
+
│ │ │ ├── stat.css # 统计卡片
|
|
668
|
+
│ │ │ ├── nav.css # 导航标签
|
|
669
|
+
│ │ │ └── overlay.css # 进度、Toast、日志
|
|
670
|
+
│ │ ├── modals.css # 模态框样式
|
|
671
|
+
│ │ └── animations.css # 动画
|
|
672
|
+
│ └── js/
|
|
673
|
+
│ ├── state.js # 全局状态
|
|
674
|
+
│ ├── utils.js # 工具函数
|
|
675
|
+
│ ├── api/ # API 模块
|
|
676
|
+
│ │ ├── client.js # 通用 fetch 逻辑
|
|
677
|
+
│ │ ├── index.js # 重新导出
|
|
678
|
+
│ │ ├── stats.js # 统计 API
|
|
679
|
+
│ │ ├── keys.js # 密钥 API
|
|
680
|
+
│ │ ├── check.js # 验证 API
|
|
681
|
+
│ │ ├── test.js # 测试 API
|
|
682
|
+
│ │ ├── balance.js # 余额 API
|
|
683
|
+
│ │ ├── models.js # 模型 API
|
|
684
|
+
│ │ ├── providers.js # 提供商 API
|
|
685
|
+
│ │ └── misc.js # 杂项 API
|
|
686
|
+
│ ├── toast.js # Toast 通知
|
|
687
|
+
│ ├── progress.js # 进度覆盖层
|
|
688
|
+
│ ├── confirm.js # 确认模态框
|
|
689
|
+
│ ├── keys-table.js # 表格渲染
|
|
690
|
+
│ ├── providers.js # 提供商网格
|
|
691
|
+
│ ├── batch.js # 批量结果
|
|
692
|
+
│ ├── modals.js # 模态框
|
|
693
|
+
│ ├── model-detect.js # 模型检测
|
|
694
|
+
│ └── init.js # 入口点
|
|
695
|
+
├── templates/
|
|
696
|
+
│ └── index.html # Web UI 入口 (467行)
|
|
470
697
|
├── tests/ # 测试套件
|
|
698
|
+
│ ├── conftest.py # 共享 fixtures 和帮助函数
|
|
471
699
|
│ ├── test_detector.py # 提供商检测测试
|
|
472
700
|
│ ├── test_parser.py # 解析器测试
|
|
473
701
|
│ ├── test_validator.py # 验证器测试
|
|
@@ -478,7 +706,9 @@ key/
|
|
|
478
706
|
│ ├── test_errors.py # 错误系统测试
|
|
479
707
|
│ ├── test_i18n.py # 国际化测试
|
|
480
708
|
│ ├── test_e2e.py # 端到端测试
|
|
481
|
-
│
|
|
709
|
+
│ ├── test_webhook.py # Webhook 测试
|
|
710
|
+
│ ├── test_bug_fixes.py # Bug 修复回归测试
|
|
711
|
+
│ └── test_provider_refactoring.py # 提供商重构测试
|
|
482
712
|
├── sdk/ # SDK
|
|
483
713
|
│ ├── python/ # Python SDK
|
|
484
714
|
│ └── typescript/ # TypeScript SDK
|
|
@@ -609,24 +839,26 @@ python -m pytest tests/ --cov=key_manager --cov-report=term-missing
|
|
|
609
839
|
|
|
610
840
|
| 模块 | 测试文件 | 测试数 |
|
|
611
841
|
|------|---------|--------|
|
|
612
|
-
| 提供商检测 | `test_detector.py` |
|
|
613
|
-
| 密钥解析 | `test_parser.py` |
|
|
614
|
-
| 验证器 | `test_validator.py` |
|
|
842
|
+
| 提供商检测 | `test_detector.py` | 44 |
|
|
843
|
+
| 密钥解析 | `test_parser.py` | 24 |
|
|
844
|
+
| 验证器 | `test_validator.py` | 13 |
|
|
615
845
|
| 检查器 | `test_checker.py` | 4 |
|
|
616
846
|
| 提供商合约 | `test_providers.py` | 220 |
|
|
617
|
-
| 安全回归 | `test_security.py` |
|
|
847
|
+
| 安全回归 | `test_security.py` | 21 |
|
|
618
848
|
| 加密存储 | `test_storage.py` | 26 |
|
|
619
849
|
| 错误系统 | `test_errors.py` | 28 |
|
|
620
850
|
| 国际化 | `test_i18n.py` | 37 |
|
|
621
|
-
| 端到端 | `test_e2e.py` |
|
|
851
|
+
| 端到端 | `test_e2e.py` | 18 |
|
|
622
852
|
| Webhook | `test_webhook.py` | 35 |
|
|
623
853
|
| OpenAPI | `test_openapi.py` | 26 |
|
|
624
854
|
| 代理检测 | `test_proxy.py` | 19 |
|
|
625
855
|
| 日志系统 | `test_logger.py` | 21 |
|
|
626
856
|
| 能力测试 | `test_tester.py` | 11 |
|
|
627
857
|
| 核心门面 | `test_core.py` | 22 |
|
|
858
|
+
| Bug 修复 | `test_bug_fixes.py` | 15 |
|
|
859
|
+
| 提供商重构 | `test_provider_refactoring.py` | 32 |
|
|
628
860
|
|
|
629
|
-
**总测试数**:
|
|
861
|
+
**总测试数**: 728+ | **覆盖率**: 80%
|
|
630
862
|
## SDK 使用
|
|
631
863
|
|
|
632
864
|
### Python SDK
|
|
@@ -642,10 +874,10 @@ from key_manager_sdk import KeyManagerClient
|
|
|
642
874
|
client = KeyManagerClient(base_url="http://localhost:18001")
|
|
643
875
|
|
|
644
876
|
# 获取密钥列表
|
|
645
|
-
keys = client.
|
|
877
|
+
keys = client.keys()
|
|
646
878
|
|
|
647
879
|
# 验证单个密钥
|
|
648
|
-
result = client.
|
|
880
|
+
result = client.check_single(key="sk-xxx", provider="openai")
|
|
649
881
|
```
|
|
650
882
|
|
|
651
883
|
### TypeScript SDK
|
|
@@ -661,10 +893,10 @@ npm install @api-key-manager/sdk
|
|
|
661
893
|
const client = new KeyManagerClient({ baseUrl: 'http://localhost:18001' });
|
|
662
894
|
|
|
663
895
|
// 获取密钥列表
|
|
664
|
-
const keys = await client.
|
|
896
|
+
const keys = await client.keys();
|
|
665
897
|
|
|
666
898
|
// 验证单个密钥
|
|
667
|
-
const result = await client.
|
|
899
|
+
const result = await client.checkSingle({ key: 'sk-xxx', provider: 'openai' });
|
|
668
900
|
```
|
|
669
901
|
|
|
670
902
|
## 依赖
|
|
@@ -717,6 +949,94 @@ const result = await client.checkSingleKey({ key: 'sk-xxx', provider: 'openai' }
|
|
|
717
949
|
## 更新日志
|
|
718
950
|
|
|
719
951
|
|
|
952
|
+
### v4.0.0 (2026-06-21)
|
|
953
|
+
|
|
954
|
+
- **前端模块化重构**: 将单体 `index.html` (4725行) 重构为模块化结构:
|
|
955
|
+
- CSS 拆分: 5个主题文件 + 7个组件文件
|
|
956
|
+
- `tokens.css` — CSS 变量
|
|
957
|
+
- `base.css` — 基础样式、布局、响应式
|
|
958
|
+
- `components.css` — 组件入口 (@import)
|
|
959
|
+
- `components/` — 7个独立组件 (button, form, card, table, stat, nav, overlay)
|
|
960
|
+
- `modals.css` — 模态框样式
|
|
961
|
+
- `animations.css` — 动画和过渡
|
|
962
|
+
- JS 模块化: 12个ES模块
|
|
963
|
+
- `state.js` — 全局状态管理
|
|
964
|
+
- `utils.js` — 纯工具函数
|
|
965
|
+
- `api/` — 9个API模块 (client, stats, keys, check, test, balance, models, providers, misc)
|
|
966
|
+
- UI模块: toast, progress, confirm, keys-table, providers, batch, modals, model-detect
|
|
967
|
+
- `init.js` — 入口点和事件绑定
|
|
968
|
+
- `index.html` 精简至 467行
|
|
969
|
+
- **API模块拆分**: 按后端路由结构拆分前端API模块:
|
|
970
|
+
- `api/client.js` — 通用fetch逻辑 (safeFetch)
|
|
971
|
+
- `api/index.js` — 重新导出所有函数
|
|
972
|
+
- 每个路由模块对应独立文件
|
|
973
|
+
- **静态文件服务**: 添加 `StaticFiles` 中间件
|
|
974
|
+
- **错误处理增强**: loadKeys/loadStats 添加 try-catch 错误处理
|
|
975
|
+
- **ES模块修复**: 修复 State 对象只读绑定问题
|
|
976
|
+
|
|
977
|
+
- **Web模块重构**: 将单体 `web.py` (2094行) 重构为模块化包结构:
|
|
978
|
+
- `web/_app.py` — 应用入口 (239行)
|
|
979
|
+
- `web/middleware.py` — 中间件和错误处理器
|
|
980
|
+
- `web/progress.py` — ProgressTracker和SSE辅助
|
|
981
|
+
- `web/routes/` — 8个路由模块 (keys, check, test, balance, models, providers, stats, misc)
|
|
982
|
+
- **测试套件重构**:
|
|
983
|
+
- 合并补充测试文件 (test_parser_supplement, test_validator_supplement, test_ssrf_supplement)
|
|
984
|
+
- 删除重复测试文件 (test_web_fixes)
|
|
985
|
+
- 修复 mock 路径以匹配新的 web 模块结构
|
|
986
|
+
- 提取共享 fixtures 和帮助函数到 conftest.py
|
|
987
|
+
- 移除冗余 @pytest.mark.asyncio 装饰器
|
|
988
|
+
- 修复速率限制中间件的测试隔离问题
|
|
989
|
+
- **代码去重**: 消除 `api_check_single` 中的重复模型检查逻辑
|
|
990
|
+
- **调试系统清理**: 移除 `sys.path.insert` hack,使用干净的可选导入模式
|
|
991
|
+
- **重复路由修复**: 合并重复的 `GET /api/providers` 路由
|
|
992
|
+
- **日志显示修复**: 修复日志显示为 `[object Object]` 的问题
|
|
993
|
+
- **可扩展性**: 在 `routes/__init__.py` 中添加新路由、中间件和错误处理器的扩展指南
|
|
994
|
+
|
|
995
|
+
- **提供商系统重构**:
|
|
996
|
+
- 删除 `check_model` 属性,改为先从 API 获取模型列表再测试
|
|
997
|
+
- 删除所有提供商中的冗余方法(build_headers, get_models, test_token_limit, test_concurrency, _probe, check_real)
|
|
998
|
+
- 标准提供商从 ~77 行减少到 ~5-8 行
|
|
999
|
+
- 总共减少 ~2,400 行冗余代码
|
|
1000
|
+
- 添加 `chat_endpoint` 属性,自动从 `check_endpoint` 推导 chat completions 端点
|
|
1001
|
+
|
|
1002
|
+
- **检测逻辑修复**:
|
|
1003
|
+
- 修复检测逻辑不再因为 `/v1/models` 返回 200 就立即返回提供商
|
|
1004
|
+
- `/v1/models` 只用于获取模型列表,不能判断提供商
|
|
1005
|
+
- `/chat/completions` 返回 200 才能判断提供商
|
|
1006
|
+
- 添加前缀匹配逻辑,支持共享前缀(如 `sk-`)的提供商检测
|
|
1007
|
+
- 免费模型对检测非常重要,检测逻辑会测试所有模型包括免费模型
|
|
1008
|
+
|
|
1009
|
+
- **文档更新**:
|
|
1010
|
+
- 更新 README.md 的"提供商智能检测"部分,添加详细的检测流程说明
|
|
1011
|
+
- 更新 docs/DEVELOPMENT.md 的"检测系统"部分
|
|
1012
|
+
- 更新 docs/PROVIDER_REFACTORING_PLAN.md,添加"检测逻辑详解"部分
|
|
1013
|
+
- 在 detector.py 中添加详细注释,说明 `/v1/models` 和 `/chat/completions` 的区别
|
|
1014
|
+
|
|
1015
|
+
- **Bug 修复与改进**:
|
|
1016
|
+
- 修复 SDK 文档方法名与实际不符(`get_keys()` → `keys()` 等)
|
|
1017
|
+
- 修复删除密钥时报错 "No passphrase found",新增自动生成加密密钥功能
|
|
1018
|
+
- 修复签名验证报告端点 404 错误,改为动态生成报告
|
|
1019
|
+
- 修复添加自定义服务商按钮缺失,新增前端 UI
|
|
1020
|
+
### v3.2.0 (2026-06-19)
|
|
1021
|
+
|
|
1022
|
+
- **模型检测按模型测试**: 支持在模型检测模态框中直接测试Token限制和并发能力
|
|
1023
|
+
- **提供商管理UI**: 添加前端UI用于添加/管理自定义提供商
|
|
1024
|
+
- **移除check_model**: 从ProviderBase中移除check_model属性,提供商必须从API获取模型
|
|
1025
|
+
- **并发测试API改进**: 改进错误处理,提供详细错误信息
|
|
1026
|
+
- **提供商Token测试修复**: 修复提供商使用错误模型或错误API端点的问题
|
|
1027
|
+
- **余额API修复**: 修复支持余额查询的服务商的余额检测
|
|
1028
|
+
|
|
1029
|
+
|
|
1030
|
+
### v3.1.0 (2026-06-19)
|
|
1031
|
+
|
|
1032
|
+
- **提供商自动发现**: 使用pkgutil自动发现替代47个显式导入
|
|
1033
|
+
- **提供商元数据属性**: 所有提供商声明display_name、key_prefixes等类属性
|
|
1034
|
+
- **默认实现**: test_token_limit、test_concurrency等方法现在是具体方法
|
|
1035
|
+
- **YAML配置支持**: 可在config.yaml中定义自定义提供商
|
|
1036
|
+
- **提供商Web API**: 添加/api/providers CRUD端点
|
|
1037
|
+
- **代码精简**: 39个标准提供商从~100行减少到~15行
|
|
1038
|
+
|
|
1039
|
+
|
|
720
1040
|
### v3.0.0 (2026-06-15)
|
|
721
1041
|
|
|
722
1042
|
- **精确匹配修复**: 能力检测从子串匹配改为 ^$ 锚定精确匹配,修复 o1 误匹配 o1-mini 等问题
|