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.
Files changed (162) hide show
  1. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/PKG-INFO +424 -104
  2. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/README.md +422 -103
  3. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/api_key_manager.egg-info/PKG-INFO +424 -104
  4. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/api_key_manager.egg-info/SOURCES.txt +18 -6
  5. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/api_key_manager.egg-info/requires.txt +1 -0
  6. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/__init__.py +2 -2
  7. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/api_models.py +0 -1
  8. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/checker.py +2 -5
  9. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/cli.py +9 -11
  10. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/config.py +3 -2
  11. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/core.py +43 -43
  12. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/detector.py +353 -371
  13. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/errors.py +14 -14
  14. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/i18n.py +3 -3
  15. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/logger.py +6 -7
  16. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/model_capabilities.py +70 -70
  17. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/parser.py +5 -6
  18. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/providers/__init__.py +36 -35
  19. api_key_manager-4.0.0/key_manager/providers/ai302.py +7 -0
  20. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/providers/anthropic.py +110 -109
  21. api_key_manager-4.0.0/key_manager/providers/baichuan.py +7 -0
  22. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/providers/base.py +144 -61
  23. api_key_manager-4.0.0/key_manager/providers/cerebras.py +7 -0
  24. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/providers/cohere.py +91 -90
  25. api_key_manager-4.0.0/key_manager/providers/config_providers.py +134 -0
  26. api_key_manager-4.0.0/key_manager/providers/cstcloud.py +8 -0
  27. api_key_manager-4.0.0/key_manager/providers/dashscope.py +30 -0
  28. api_key_manager-4.0.0/key_manager/providers/dashscope_coding.py +8 -0
  29. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/providers/deepseek.py +161 -133
  30. api_key_manager-4.0.0/key_manager/providers/dmxapi.py +7 -0
  31. api_key_manager-4.0.0/key_manager/providers/doubao.py +7 -0
  32. api_key_manager-4.0.0/key_manager/providers/fireworks.py +7 -0
  33. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/providers/google.py +2 -1
  34. api_key_manager-4.0.0/key_manager/providers/grok.py +7 -0
  35. api_key_manager-4.0.0/key_manager/providers/groq.py +7 -0
  36. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/providers/huggingface.py +37 -37
  37. api_key_manager-4.0.0/key_manager/providers/hyperbolic.py +7 -0
  38. api_key_manager-4.0.0/key_manager/providers/infini.py +33 -0
  39. api_key_manager-4.0.0/key_manager/providers/infini_coding.py +8 -0
  40. api_key_manager-4.0.0/key_manager/providers/kimi.py +31 -0
  41. api_key_manager-4.0.0/key_manager/providers/kimi_coding.py +8 -0
  42. api_key_manager-4.0.0/key_manager/providers/longcat.py +7 -0
  43. api_key_manager-4.0.0/key_manager/providers/mimo.py +7 -0
  44. api_key_manager-4.0.0/key_manager/providers/mimo_plan.py +8 -0
  45. api_key_manager-4.0.0/key_manager/providers/minimax.py +7 -0
  46. api_key_manager-4.0.0/key_manager/providers/minimax_plan.py +8 -0
  47. api_key_manager-4.0.0/key_manager/providers/mistral.py +7 -0
  48. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/providers/models_registry.py +2962 -2962
  49. api_key_manager-4.0.0/key_manager/providers/modelscope.py +21 -0
  50. api_key_manager-4.0.0/key_manager/providers/nvidia.py +7 -0
  51. api_key_manager-4.0.0/key_manager/providers/ocoolai.py +7 -0
  52. api_key_manager-4.0.0/key_manager/providers/openai.py +56 -0
  53. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/providers/opencode.py +107 -89
  54. api_key_manager-4.0.0/key_manager/providers/opencode_zen.py +8 -0
  55. api_key_manager-4.0.0/key_manager/providers/openrouter.py +29 -0
  56. api_key_manager-4.0.0/key_manager/providers/perplexity.py +7 -0
  57. api_key_manager-4.0.0/key_manager/providers/poe.py +7 -0
  58. api_key_manager-4.0.0/key_manager/providers/ppio.py +7 -0
  59. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/providers/replicate.py +37 -37
  60. api_key_manager-4.0.0/key_manager/providers/siliconflow.py +31 -0
  61. api_key_manager-4.0.0/key_manager/providers/stepfun.py +30 -0
  62. api_key_manager-4.0.0/key_manager/providers/tencent_hunyuan.py +8 -0
  63. api_key_manager-4.0.0/key_manager/providers/together.py +32 -0
  64. api_key_manager-4.0.0/key_manager/providers/yi.py +7 -0
  65. api_key_manager-4.0.0/key_manager/providers/zai.py +7 -0
  66. api_key_manager-4.0.0/key_manager/providers/zhipu.py +37 -0
  67. api_key_manager-4.0.0/key_manager/providers/zhipu_coding.py +8 -0
  68. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/proxy.py +2 -2
  69. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/ssrf.py +4 -5
  70. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/storage.py +43 -6
  71. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/tester.py +3 -3
  72. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/validator.py +3 -2
  73. api_key_manager-4.0.0/key_manager/web/__init__.py +35 -0
  74. api_key_manager-4.0.0/key_manager/web/_app.py +168 -0
  75. api_key_manager-4.0.0/key_manager/web/middleware.py +197 -0
  76. api_key_manager-4.0.0/key_manager/web/progress.py +78 -0
  77. api_key_manager-4.0.0/key_manager/web/routes/__init__.py +19 -0
  78. api_key_manager-4.0.0/key_manager/web/routes/balance.py +93 -0
  79. api_key_manager-4.0.0/key_manager/web/routes/check.py +322 -0
  80. api_key_manager-4.0.0/key_manager/web/routes/keys.py +242 -0
  81. api_key_manager-4.0.0/key_manager/web/routes/misc.py +254 -0
  82. api_key_manager-4.0.0/key_manager/web/routes/models.py +279 -0
  83. api_key_manager-4.0.0/key_manager/web/routes/providers.py +229 -0
  84. api_key_manager-4.0.0/key_manager/web/routes/stats.py +91 -0
  85. api_key_manager-4.0.0/key_manager/web/routes/test.py +441 -0
  86. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/webhook.py +9 -11
  87. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/pyproject.toml +2 -2
  88. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/tests/test_api_endpoints.py +24 -80
  89. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/tests/test_base_check.py +4 -27
  90. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/tests/test_bug_fixes.py +34 -35
  91. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/tests/test_checker.py +6 -14
  92. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/tests/test_core.py +0 -5
  93. api_key_manager-4.0.0/tests/test_deepseek_balance.py +203 -0
  94. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/tests/test_e2e.py +20 -72
  95. api_key_manager-3.0.1/tests/test_parser_supplement.py → api_key_manager-4.0.0/tests/test_parser.py +158 -19
  96. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/tests/test_provider_detection.py +19 -88
  97. api_key_manager-4.0.0/tests/test_provider_refactoring.py +262 -0
  98. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/tests/test_security.py +79 -3
  99. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/tests/test_storage.py +19 -3
  100. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/tests/test_tester.py +0 -11
  101. api_key_manager-4.0.0/tests/test_token_limit_fast.py +161 -0
  102. api_key_manager-4.0.0/tests/test_v320_changes.py +193 -0
  103. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/tests/test_validator.py +190 -8
  104. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/tests/test_webhook.py +0 -10
  105. api_key_manager-3.0.1/key_manager/providers/ai302.py +0 -76
  106. api_key_manager-3.0.1/key_manager/providers/baichuan.py +0 -69
  107. api_key_manager-3.0.1/key_manager/providers/cerebras.py +0 -76
  108. api_key_manager-3.0.1/key_manager/providers/cstcloud.py +0 -94
  109. api_key_manager-3.0.1/key_manager/providers/dashscope.py +0 -92
  110. api_key_manager-3.0.1/key_manager/providers/dashscope_coding.py +0 -94
  111. api_key_manager-3.0.1/key_manager/providers/dmxapi.py +0 -76
  112. api_key_manager-3.0.1/key_manager/providers/doubao.py +0 -76
  113. api_key_manager-3.0.1/key_manager/providers/fireworks.py +0 -76
  114. api_key_manager-3.0.1/key_manager/providers/grok.py +0 -75
  115. api_key_manager-3.0.1/key_manager/providers/groq.py +0 -76
  116. api_key_manager-3.0.1/key_manager/providers/hyperbolic.py +0 -76
  117. api_key_manager-3.0.1/key_manager/providers/infini.py +0 -102
  118. api_key_manager-3.0.1/key_manager/providers/infini_coding.py +0 -96
  119. api_key_manager-3.0.1/key_manager/providers/kimi.py +0 -93
  120. api_key_manager-3.0.1/key_manager/providers/kimi_coding.py +0 -96
  121. api_key_manager-3.0.1/key_manager/providers/longcat.py +0 -76
  122. api_key_manager-3.0.1/key_manager/providers/mimo.py +0 -76
  123. api_key_manager-3.0.1/key_manager/providers/mimo_plan.py +0 -107
  124. api_key_manager-3.0.1/key_manager/providers/minimax.py +0 -69
  125. api_key_manager-3.0.1/key_manager/providers/minimax_plan.py +0 -94
  126. api_key_manager-3.0.1/key_manager/providers/mistral.py +0 -76
  127. api_key_manager-3.0.1/key_manager/providers/modelscope.py +0 -120
  128. api_key_manager-3.0.1/key_manager/providers/nvidia.py +0 -76
  129. api_key_manager-3.0.1/key_manager/providers/ocoolai.py +0 -76
  130. api_key_manager-3.0.1/key_manager/providers/openai.py +0 -101
  131. api_key_manager-3.0.1/key_manager/providers/opencode_zen.py +0 -89
  132. api_key_manager-3.0.1/key_manager/providers/openrouter.py +0 -91
  133. api_key_manager-3.0.1/key_manager/providers/perplexity.py +0 -75
  134. api_key_manager-3.0.1/key_manager/providers/poe.py +0 -76
  135. api_key_manager-3.0.1/key_manager/providers/ppio.py +0 -76
  136. api_key_manager-3.0.1/key_manager/providers/siliconflow.py +0 -93
  137. api_key_manager-3.0.1/key_manager/providers/stepfun.py +0 -99
  138. api_key_manager-3.0.1/key_manager/providers/tencent_hunyuan.py +0 -94
  139. api_key_manager-3.0.1/key_manager/providers/together.py +0 -101
  140. api_key_manager-3.0.1/key_manager/providers/yi.py +0 -69
  141. api_key_manager-3.0.1/key_manager/providers/zai.py +0 -76
  142. api_key_manager-3.0.1/key_manager/providers/zhipu.py +0 -99
  143. api_key_manager-3.0.1/key_manager/providers/zhipu_coding.py +0 -96
  144. api_key_manager-3.0.1/key_manager/web.py +0 -1676
  145. api_key_manager-3.0.1/tests/test_detector.py +0 -317
  146. api_key_manager-3.0.1/tests/test_parser.py +0 -149
  147. api_key_manager-3.0.1/tests/test_ssrf_supplement.py +0 -81
  148. api_key_manager-3.0.1/tests/test_validator_supplement.py +0 -196
  149. api_key_manager-3.0.1/tests/test_web_fixes.py +0 -161
  150. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/api_key_manager.egg-info/dependency_links.txt +0 -0
  151. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/api_key_manager.egg-info/entry_points.txt +0 -0
  152. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/api_key_manager.egg-info/top_level.txt +0 -0
  153. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/__main__.py +0 -0
  154. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/py.typed +0 -0
  155. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/key_manager/url_override.py +0 -0
  156. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/setup.cfg +0 -0
  157. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/tests/test_errors.py +0 -0
  158. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/tests/test_i18n.py +0 -0
  159. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/tests/test_logger.py +0 -0
  160. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/tests/test_openapi.py +0 -0
  161. {api_key_manager-3.0.1 → api_key_manager-4.0.0}/tests/test_providers.py +0 -0
  162. {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.0.1
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. 模式匹配 - 检查唯一前缀(如 sk-proj- → OpenAI,AIza → Google)
288
+ 1. 前缀匹配 - 检查唯一前缀(如 sk-proj- → OpenAI,AIza → Google)
228
289
  2. 格式匹配 - 检查特殊格式(如智谱的 {id}.{secret} 格式)
229
- 3. 全并发探测 - 同时向所有服务商发送请求(40+ 服务商 × 5 模型 = 200+ 请求)
290
+ 3. 全并发探测 - 同时向所有服务商发送请求
230
291
  4. 签名匹配 - 如果无200响应,通过错误响应体签名识别服务商
231
292
  ```
232
293
 
233
- #### 全并发探测逻辑
294
+ ### ⚠️ 关键概念:/v1/models vs /chat/completions
234
295
 
235
- 当密钥无法通过前缀或格式识别时,系统会:
296
+ **这两个端点的作用完全不同,不能混用!**
236
297
 
237
- 1. Cherry Studio 同步的模型列表中获取每个服务商的前5个模型
238
- 2. 并发向所有服务商发送请求(每个服务商尝试5个模型)
239
- 3. 第一个返回200的服务商立即胜出
240
- 4. 如果所有请求都失败,通过签名匹配识别
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
- async def detect_provider(client, key):
245
- # Step 1: 模式匹配
246
- pattern_match = detect_by_pattern(key) # 如 sk-proj- OpenAI
247
- if pattern_match:
248
- return pattern_match
249
-
250
- # Step 2: 格式匹配
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
- #### 智谱/Z.AI 密钥格式
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
- 这种格式的密钥会被自动识别为智谱或 Z.AI(两者共享相同的 GLM 模型)。
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
- "google": ["generativelanguage"],
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 | 模式匹配 | 唯一前缀,如 `sk-proj-` → OpenAI |
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.py # FastAPI 应用
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
- └── test_webhook.py # Webhook 测试
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` | 54 |
613
- | 密钥解析 | `test_parser.py` | 12 |
614
- | 验证器 | `test_validator.py` | 5 |
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` | 12 |
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` | 17 |
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
- **总测试数**: 583+ | **覆盖率**: 88%
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.get_keys()
877
+ keys = client.keys()
646
878
 
647
879
  # 验证单个密钥
648
- result = client.check_single_key(key="sk-xxx", provider="openai")
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.getKeys();
896
+ const keys = await client.keys();
665
897
 
666
898
  // 验证单个密钥
667
- const result = await client.checkSingleKey({ key: 'sk-xxx', provider: 'openai' });
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 等问题