api-key-manager 2.1.0__py3-none-any.whl
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-2.1.0.dist-info/METADATA +709 -0
- api_key_manager-2.1.0.dist-info/RECORD +73 -0
- api_key_manager-2.1.0.dist-info/WHEEL +5 -0
- api_key_manager-2.1.0.dist-info/entry_points.txt +2 -0
- api_key_manager-2.1.0.dist-info/top_level.txt +1 -0
- key_manager/__init__.py +16 -0
- key_manager/__main__.py +5 -0
- key_manager/api_models.py +358 -0
- key_manager/checker.py +51 -0
- key_manager/cli.py +270 -0
- key_manager/config.py +61 -0
- key_manager/core.py +205 -0
- key_manager/detector.py +335 -0
- key_manager/errors.py +179 -0
- key_manager/i18n.py +142 -0
- key_manager/logger.py +207 -0
- key_manager/model_capabilities.py +412 -0
- key_manager/parser.py +153 -0
- key_manager/providers/__init__.py +283 -0
- key_manager/providers/ai302.py +109 -0
- key_manager/providers/anthropic.py +109 -0
- key_manager/providers/baichuan.py +97 -0
- key_manager/providers/base.py +312 -0
- key_manager/providers/cerebras.py +109 -0
- key_manager/providers/cohere.py +90 -0
- key_manager/providers/cstcloud.py +122 -0
- key_manager/providers/dashscope.py +120 -0
- key_manager/providers/dashscope_coding.py +122 -0
- key_manager/providers/deepseek.py +166 -0
- key_manager/providers/dmxapi.py +109 -0
- key_manager/providers/doubao.py +109 -0
- key_manager/providers/fireworks.py +109 -0
- key_manager/providers/google.py +99 -0
- key_manager/providers/grok.py +109 -0
- key_manager/providers/groq.py +109 -0
- key_manager/providers/huggingface.py +54 -0
- key_manager/providers/hyperbolic.py +109 -0
- key_manager/providers/infini.py +135 -0
- key_manager/providers/infini_coding.py +124 -0
- key_manager/providers/kimi.py +121 -0
- key_manager/providers/kimi_coding.py +124 -0
- key_manager/providers/longcat.py +123 -0
- key_manager/providers/mimo.py +109 -0
- key_manager/providers/mimo_plan.py +140 -0
- key_manager/providers/minimax.py +97 -0
- key_manager/providers/minimax_plan.py +122 -0
- key_manager/providers/mistral.py +109 -0
- key_manager/providers/models_registry.py +2901 -0
- key_manager/providers/modelscope.py +134 -0
- key_manager/providers/nvidia.py +109 -0
- key_manager/providers/ocoolai.py +109 -0
- key_manager/providers/openai.py +140 -0
- key_manager/providers/openrouter.py +119 -0
- key_manager/providers/perplexity.py +109 -0
- key_manager/providers/poe.py +109 -0
- key_manager/providers/ppio.py +109 -0
- key_manager/providers/replicate.py +54 -0
- key_manager/providers/siliconflow.py +121 -0
- key_manager/providers/stepfun.py +132 -0
- key_manager/providers/tencent_hunyuan.py +122 -0
- key_manager/providers/together.py +134 -0
- key_manager/providers/yi.py +97 -0
- key_manager/providers/zai.py +109 -0
- key_manager/providers/zhipu.py +127 -0
- key_manager/providers/zhipu_coding.py +124 -0
- key_manager/proxy.py +70 -0
- key_manager/ssrf.py +68 -0
- key_manager/storage.py +134 -0
- key_manager/tester.py +137 -0
- key_manager/url_override.py +5 -0
- key_manager/validator.py +185 -0
- key_manager/web.py +1512 -0
- key_manager/webhook.py +257 -0
|
@@ -0,0 +1,709 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: api-key-manager
|
|
3
|
+
Version: 2.1.0
|
|
4
|
+
Summary: Batch manage API keys for 44+ AI providers with CLI and Web interfaces
|
|
5
|
+
Author: Townrain
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Townrain/API-Key-Manager
|
|
8
|
+
Project-URL: Documentation, https://github.com/Townrain/API-Key-Manager#readme
|
|
9
|
+
Project-URL: Repository, https://github.com/Townrain/API-Key-Manager
|
|
10
|
+
Keywords: api-key,ai,openai,anthropic,google,deepseek
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
Requires-Dist: httpx>=0.27.2
|
|
22
|
+
Requires-Dist: pyyaml>=6.0.2
|
|
23
|
+
Requires-Dist: rich>=13.9.0
|
|
24
|
+
Requires-Dist: fastapi>=0.115.0
|
|
25
|
+
Requires-Dist: uvicorn>=0.32.0
|
|
26
|
+
Requires-Dist: python-multipart>=0.0.9
|
|
27
|
+
Requires-Dist: cryptography>=42.0.0
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
30
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
|
|
31
|
+
Requires-Dist: pytest-cov>=5.0.0; extra == "dev"
|
|
32
|
+
Requires-Dist: ruff>=0.4.0; extra == "dev"
|
|
33
|
+
|
|
34
|
+
# API Key Manager
|
|
35
|
+
|
|
36
|
+
批量管理 45+ AI 服务商 API 密钥的 Python 工具,支持 CLI 和 Web 两种界面。
|
|
37
|
+
|
|
38
|
+
## 功能特性
|
|
39
|
+
|
|
40
|
+
- **批量导入** - 从 JSON 文件导入 API 密钥,自动去重
|
|
41
|
+
- **密钥验证** - 并发验证密钥有效性,支持 45+ AI 服务商
|
|
42
|
+
- **能力测试** - 测试 Token 上限和并发能力
|
|
43
|
+
- **模型筛选** - 按类型筛选模型(推理/视觉/联网/免费/嵌入/重排/工具)
|
|
44
|
+
- **智能检测** - 前缀匹配 + 模式匹配 + 错误签名匹配,三级提供商自动识别
|
|
45
|
+
- **Web 界面** - 赛博朋克风格的管理界面
|
|
46
|
+
- **代理支持** - 支持 HTTP/SOCKS 代理
|
|
47
|
+
- **加密存储** - AES-256-GCM 加密存储 API 密钥,随机盐值
|
|
48
|
+
- **安全防护** - 路径遍历防护、SSRF 防护、时序安全认证
|
|
49
|
+
- **API 文档** - Swagger UI 和 Redoc 自动文档
|
|
50
|
+
- **国际化** - 支持中英文错误信息
|
|
51
|
+
- **SDK 支持** - Python 和 TypeScript 客户端库
|
|
52
|
+
- **Webhook 通知** - 事件驱动的 Webhook 通知系统
|
|
53
|
+
|
|
54
|
+
## 支持的 AI 服务商
|
|
55
|
+
|
|
56
|
+
### 国际
|
|
57
|
+
|
|
58
|
+
| 服务商 | 前缀 |
|
|
59
|
+
|--------|------|
|
|
60
|
+
| OpenAI | `sk-proj-` |
|
|
61
|
+
| Anthropic | `sk-ant-api03-` |
|
|
62
|
+
| Google Gemini | `AIza` |
|
|
63
|
+
| DeepSeek | `sk-` |
|
|
64
|
+
| Groq | `gsk_` |
|
|
65
|
+
| Mistral | `sk-` |
|
|
66
|
+
| Cohere | `sk-` |
|
|
67
|
+
| Perplexity | `pplx-` |
|
|
68
|
+
| Together AI | `sk-` |
|
|
69
|
+
| Replicate | `r8_` |
|
|
70
|
+
| Hugging Face | `hf_` |
|
|
71
|
+
| Fireworks | `fw_` |
|
|
72
|
+
| OpenRouter | `sk-or-v1-` |
|
|
73
|
+
| Grok (xAI) | `xai-` |
|
|
74
|
+
| Cerebras | `sk-` |
|
|
75
|
+
| NVIDIA | `sk-` |
|
|
76
|
+
| Hyperbolic | `sk-` |
|
|
77
|
+
| Poe | `sk-` |
|
|
78
|
+
|
|
79
|
+
### 中国
|
|
80
|
+
|
|
81
|
+
| 服务商 | 前缀 | 显示名 |
|
|
82
|
+
|--------|------|--------|
|
|
83
|
+
| DashScope | `sk-sp-` | 阿里百炼 |
|
|
84
|
+
| ModelScope | `ms-` | 魔搭 |
|
|
85
|
+
| Zhipu GLM | `sk-` | 智谱 |
|
|
86
|
+
| Kimi | `sk-` | 月之暗面 |
|
|
87
|
+
| MiniMax | `sk-` | MiniMax |
|
|
88
|
+
| SiliconFlow | `sk-` | 硅基流动 |
|
|
89
|
+
| Baichuan | `sk-` | 百川 |
|
|
90
|
+
| Yi | `sk-` | 零一万物 |
|
|
91
|
+
| StepFun | `sk-` | 阶跃星辰 |
|
|
92
|
+
| Doubao | `sk-` | 豆包 |
|
|
93
|
+
| Infini | `sk-` | 无问芯穹 |
|
|
94
|
+
| MiMo | `sk-` | 小米 |
|
|
95
|
+
| Tencent Hunyuan | `sk-` | 腾讯混元 |
|
|
96
|
+
| CSTCloud | `sk-` | 中算云 |
|
|
97
|
+
|
|
98
|
+
### 新增服务商
|
|
99
|
+
|
|
100
|
+
| 服务商 | 说明 |
|
|
101
|
+
|--------|------|
|
|
102
|
+
| LongCat | 新增 |
|
|
103
|
+
| AI302 | 新增 |
|
|
104
|
+
| PPIO | 新增 |
|
|
105
|
+
| DMXAPI | 新增 |
|
|
106
|
+
| OCoolAI | 新增 |
|
|
107
|
+
| ZAI | 新增 |
|
|
108
|
+
| MiMo Plan | 计划版 |
|
|
109
|
+
| MiniMax Plan | 计划版 |
|
|
110
|
+
| DashScope Coding | 编程版 |
|
|
111
|
+
| Zhipu Coding | 编程版 |
|
|
112
|
+
| Kimi Coding | 编程版 |
|
|
113
|
+
| Infini Coding | 编程版 |
|
|
114
|
+
|
|
115
|
+
## 快速开始
|
|
116
|
+
|
|
117
|
+
### 安装
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
# 克隆项目
|
|
121
|
+
git clone https://github.com/Townrain/API-Key-Manager.git
|
|
122
|
+
cd key
|
|
123
|
+
|
|
124
|
+
# 安装依赖
|
|
125
|
+
pip install -r requirements.txt
|
|
126
|
+
|
|
127
|
+
# 安装开发依赖(含测试工具)
|
|
128
|
+
pip install -e ".[dev]"
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### CLI 使用
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
# 导入密钥
|
|
135
|
+
python main.py import --file data/input/example.json
|
|
136
|
+
python main.py import --dir ./data/input
|
|
137
|
+
|
|
138
|
+
# 验证密钥
|
|
139
|
+
python main.py check
|
|
140
|
+
python main.py check --provider openai
|
|
141
|
+
python main.py check --key sk-xxx
|
|
142
|
+
|
|
143
|
+
# 测试密钥
|
|
144
|
+
python main.py test
|
|
145
|
+
python main.py test --skip-token
|
|
146
|
+
python main.py test --skip-concurrency
|
|
147
|
+
|
|
148
|
+
# 列出密钥
|
|
149
|
+
python main.py list --provider anthropic --status valid
|
|
150
|
+
python main.py list --status invalid
|
|
151
|
+
|
|
152
|
+
# 生成报告
|
|
153
|
+
python main.py report --days 7
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Web 界面
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
# 启动 Web 服务器
|
|
160
|
+
python web.py
|
|
161
|
+
|
|
162
|
+
# 访问以下地址:
|
|
163
|
+
# 主界面:http://localhost:18001
|
|
164
|
+
# API 文档:http://localhost:18001/docs
|
|
165
|
+
# Redoc:http://localhost:18001/redoc
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## 安全特性
|
|
169
|
+
|
|
170
|
+
### 加密存储
|
|
171
|
+
|
|
172
|
+
API 密钥默认使用 AES-256-GCM 加密存储,每次加密使用随机盐值:
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
# 设置加密密钥(环境变量)
|
|
176
|
+
set KEY_MANAGER_SECRET=your-secret-key
|
|
177
|
+
|
|
178
|
+
# 启动服务
|
|
179
|
+
python web.py
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
加密后的 `keys.json` 格式:
|
|
183
|
+
```json
|
|
184
|
+
{
|
|
185
|
+
"encrypted": true,
|
|
186
|
+
"salt": "base64-encoded-random-salt",
|
|
187
|
+
"nonce": "base64-encoded-nonce",
|
|
188
|
+
"data": "base64-encoded-ciphertext"
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### 安全防护
|
|
193
|
+
|
|
194
|
+
- **路径遍历防护** - 导入端点验证路径在允许目录内
|
|
195
|
+
- **SSRF 防护** - `custom_base_url` 验证域名白名单,阻止私有 IP
|
|
196
|
+
- **时序安全认证** - 使用 `hmac.compare_digest()` 防止时序攻击
|
|
197
|
+
- **认证警告** - 未配置 API Key 时启动警告
|
|
198
|
+
- **密钥掩码** - API 响应中只返回 `key_masked`,不暴露完整密钥
|
|
199
|
+
|
|
200
|
+
### API 认证
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
# 设置 API Key(环境变量)
|
|
204
|
+
set KEY_MANAGER_API_KEY=your-api-key
|
|
205
|
+
|
|
206
|
+
# 或在 config.yaml 中配置
|
|
207
|
+
# auth:
|
|
208
|
+
# api_key: "your-api-key"
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## 提供商智能检测
|
|
212
|
+
|
|
213
|
+
### 检测策略
|
|
214
|
+
|
|
215
|
+
系统采用**全并发探测**策略,自动识别 45+ 个 AI 服务商的 API 密钥。
|
|
216
|
+
|
|
217
|
+
#### 检测流程
|
|
218
|
+
|
|
219
|
+
```
|
|
220
|
+
1. 模式匹配 - 检查唯一前缀(如 sk-proj- → OpenAI,AIza → Google)
|
|
221
|
+
2. 格式匹配 - 检查特殊格式(如智谱的 {id}.{secret} 格式)
|
|
222
|
+
3. 全并发探测 - 同时向所有服务商发送请求(40+ 服务商 × 5 模型 = 200+ 请求)
|
|
223
|
+
4. 签名匹配 - 如果无200响应,通过错误响应体签名识别服务商
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
#### 全并发探测逻辑
|
|
227
|
+
|
|
228
|
+
当密钥无法通过前缀或格式识别时,系统会:
|
|
229
|
+
|
|
230
|
+
1. 从 Cherry Studio 同步的模型列表中获取每个服务商的前5个模型
|
|
231
|
+
2. 并发向所有服务商发送请求(每个服务商尝试5个模型)
|
|
232
|
+
3. 第一个返回200的服务商立即胜出
|
|
233
|
+
4. 如果所有请求都失败,通过签名匹配识别
|
|
234
|
+
|
|
235
|
+
```python
|
|
236
|
+
# 检测流程示例
|
|
237
|
+
async def detect_provider(client, key):
|
|
238
|
+
# Step 1: 模式匹配
|
|
239
|
+
pattern_match = detect_by_pattern(key) # 如 sk-proj- → OpenAI
|
|
240
|
+
if pattern_match:
|
|
241
|
+
return pattern_match
|
|
242
|
+
|
|
243
|
+
# Step 2: 格式匹配
|
|
244
|
+
format_candidates = detect_by_format(key) # 如 {id}.{secret} → 智谱/Z.AI
|
|
245
|
+
if format_candidates:
|
|
246
|
+
return format_candidates[0]
|
|
247
|
+
|
|
248
|
+
# Step 3: 全并发探测
|
|
249
|
+
tasks = []
|
|
250
|
+
for name, provider in PROVIDERS.items():
|
|
251
|
+
models = PROVIDER_MODELS.get(name, [])[:5]
|
|
252
|
+
for model in models:
|
|
253
|
+
tasks.append(try_model(name, model))
|
|
254
|
+
|
|
255
|
+
# 第一个返回200的胜出
|
|
256
|
+
for coro in asyncio.as_completed(tasks):
|
|
257
|
+
name, valid, body = await coro
|
|
258
|
+
if valid:
|
|
259
|
+
return name
|
|
260
|
+
|
|
261
|
+
# Step 4: 签名匹配
|
|
262
|
+
return match_by_signature(error_bodies)
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### 特殊密钥格式支持
|
|
266
|
+
|
|
267
|
+
#### 智谱/Z.AI 密钥格式
|
|
268
|
+
|
|
269
|
+
智谱和 Z.AI 使用 `{id}.{secret}` 格式的密钥(非 `sk-` 开头):
|
|
270
|
+
|
|
271
|
+
```python
|
|
272
|
+
# 示例密钥格式
|
|
273
|
+
50bcde33b8774aa8a2cc1bd6d39444ae.ifriyNWRLStzpLEs
|
|
274
|
+
|
|
275
|
+
# 正则表达式匹配
|
|
276
|
+
ZHIPU_KEY_PATTERN = re.compile(r'^[a-zA-Z0-9]{20,50}\.[a-zA-Z0-9]{10,50}$')
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
这种格式的密钥会被自动识别为智谱或 Z.AI(两者共享相同的 GLM 模型)。
|
|
280
|
+
|
|
281
|
+
### 错误签名匹配
|
|
282
|
+
|
|
283
|
+
当所有并发请求都失败时,系统通过错误响应体中的关键词识别服务商:
|
|
284
|
+
|
|
285
|
+
```python
|
|
286
|
+
UNIQUE_SIGNATURES = {
|
|
287
|
+
# 国内服务商
|
|
288
|
+
"dashscope": ["model-studio", "modelstudio", "apikey-error"],
|
|
289
|
+
"tencent-hunyuan": ["hunyuan", "console.cloud.tencent.com"],
|
|
290
|
+
"baichuan": ["baichuan-ai.com", "platform.baichuan-ai.com"],
|
|
291
|
+
"minimax": ["authorized_error", "login fail"],
|
|
292
|
+
"yi": ["illegal apikey"],
|
|
293
|
+
"kimi": ["invalid_authentication_error"],
|
|
294
|
+
"siliconflow": ["api key is invalid"],
|
|
295
|
+
"stepfun": ["incorrect api key provided", "invalid_api_key"],
|
|
296
|
+
"doubao": ["authenticationerror"],
|
|
297
|
+
"infini": ["请使用正确的api key进行请求"],
|
|
298
|
+
"zhipu": ["令牌已过期或验证不正确"],
|
|
299
|
+
"mimo": ["invalid api key", "please provide valid api key"],
|
|
300
|
+
# 国外服务商
|
|
301
|
+
"deepseek": ["authentication fails"],
|
|
302
|
+
"anthropic": ["request not allowed", "anthropic", "x-api-key"],
|
|
303
|
+
"openrouter": ["missing authentication header"],
|
|
304
|
+
"mistral": ["mistral", "la plateforme"],
|
|
305
|
+
"replicate": ["unauthenticated", "you did not pass a valid authentication token"],
|
|
306
|
+
"huggingface": ["huggingface", "hf_"],
|
|
307
|
+
"fireworks": ["fireworks", "accounts/fireworks"],
|
|
308
|
+
"perplexity": ["perplexity"],
|
|
309
|
+
"grok": ["console.x.ai"],
|
|
310
|
+
"openai": ["platform.openai.com"],
|
|
311
|
+
"google": ["generativelanguage"],
|
|
312
|
+
"groq": ["groq"],
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
#### 签名匹配算法
|
|
317
|
+
|
|
318
|
+
```python
|
|
319
|
+
def score_provider(provider_name, error_body, status_code):
|
|
320
|
+
body = error_body.lower()
|
|
321
|
+
score = 0
|
|
322
|
+
weight = 100 # 每个匹配的签名加100分
|
|
323
|
+
|
|
324
|
+
for sig in UNIQUE_SIGNATURES.get(provider_name, []):
|
|
325
|
+
if sig.lower() in body:
|
|
326
|
+
score += weight
|
|
327
|
+
|
|
328
|
+
return score
|
|
329
|
+
|
|
330
|
+
# 匹配阈值:至少2个签名匹配(200分)才返回结果
|
|
331
|
+
if best_score >= 200:
|
|
332
|
+
return best_name
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### 检测优先级
|
|
336
|
+
|
|
337
|
+
| 优先级 | 方法 | 说明 |
|
|
338
|
+
||------||------|
|
|
339
|
+
| 1 | 模式匹配 | 唯一前缀,如 `sk-proj-` → OpenAI |
|
|
340
|
+
| 2 | 格式匹配 | 特殊格式,如 `{id}.{secret}` → 智谱 |
|
|
341
|
+
| 3 | 全并发探测 | 第一个返回200的服务商胜出 |
|
|
342
|
+
| 4 | 签名匹配 | 通过错误响应体识别,需至少2个签名匹配 |
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
## 错误信息简化
|
|
346
|
+
|
|
347
|
+
系统会自动将服务商返回的原始错误信息简化为用户友好的提示:
|
|
348
|
+
|
|
349
|
+
| 原始错误信息 | 简化后 |
|
|
350
|
+
|-------------|--------|
|
|
351
|
+
| `Access denied, please make sure your account is in good standing...` | 余额不足 |
|
|
352
|
+
| `Invalid API Key` | Key 无效 |
|
|
353
|
+
| `Authentication fails` | 认证失败 |
|
|
354
|
+
| `Token expired` | Key 已过期 |
|
|
355
|
+
| `Rate limit exceeded` | 请求过于频繁 |
|
|
356
|
+
| `Account suspended` | 账号被封禁 |
|
|
357
|
+
| `Access denied` | 无权限访问 |
|
|
358
|
+
| `Model does not exist` | 模型不存在 |
|
|
359
|
+
|
|
360
|
+
错误信息简化在 `base.py` 的 `simplify_error()` 函数中实现,支持:
|
|
361
|
+
|
|
362
|
+
- 基于状态码的简化(401 → Key 无效,402 → 余额不足,429 → 请求过于频繁)
|
|
363
|
+
- 基于关键词的模式匹配(authentication、expired、rate limit 等)
|
|
364
|
+
- 长错误信息截断(超过100字符时截断并添加省略号)
|
|
365
|
+
|
|
366
|
+
## 项目结构
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
## 模型检测
|
|
370
|
+
|
|
371
|
+
### 模型列表来源
|
|
372
|
+
|
|
373
|
+
系统从 Cherry Studio 同步模型数据,生成 `models_registry.py` 文件,包含每个服务商的静态模型列表:
|
|
374
|
+
|
|
375
|
+
```python
|
|
376
|
+
PROVIDER_MODELS = {
|
|
377
|
+
"openai": ["gpt-4o", "gpt-4o-mini", "gpt-3.5-turbo", ...],
|
|
378
|
+
"anthropic": ["claude-3-opus", "claude-3-sonnet", ...],
|
|
379
|
+
"dashscope": ["qwen-turbo", "qwen-plus", "qwen-max", ...],
|
|
380
|
+
"siliconflow": ["Qwen/Qwen2.5-7B-Instruct", ...],
|
|
381
|
+
# ... 45+ 服务商
|
|
382
|
+
}
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
### 模型检测流程
|
|
386
|
+
|
|
387
|
+
当用户点击「检测可用模型」时:
|
|
388
|
+
|
|
389
|
+
1. 获取服务商的模型列表(优先使用 API 返回,回退到静态列表)
|
|
390
|
+
2. 并发检测每个模型的可用性(batch_size 动态调整)
|
|
391
|
+
3. 每个模型发送一个最小请求(`POST /chat/completions`,`max_tokens: 5`)
|
|
392
|
+
4. 返回200的模型标记为可用,其他标记为失败
|
|
393
|
+
5. 失败的模型会串行重试(最多2次)
|
|
394
|
+
|
|
395
|
+
```python
|
|
396
|
+
# 模型检测逻辑
|
|
397
|
+
async def check_model(http, model):
|
|
398
|
+
resp = await client.post(
|
|
399
|
+
f"{provider.get_base_url()}/chat/completions",
|
|
400
|
+
json={"model": model, "messages": [...], "max_tokens": 5}
|
|
401
|
+
)
|
|
402
|
+
return model, 200 if resp.status_code == 200 else resp.status_code
|
|
403
|
+
|
|
404
|
+
# 动态并发控制
|
|
405
|
+
batch_size = 5 # 初始并发数
|
|
406
|
+
for i in range(0, len(models), batch_size):
|
|
407
|
+
batch = models[i:i+batch_size]
|
|
408
|
+
results = await asyncio.gather(*[check_model(http, m) for m in batch])
|
|
409
|
+
|
|
410
|
+
# 全部成功 → 并发数 +1
|
|
411
|
+
if all_success:
|
|
412
|
+
batch_size += 1
|
|
413
|
+
# 有失败 → 保持当前并发数
|
|
414
|
+
|
|
415
|
+
# 失败模型串行重试
|
|
416
|
+
for model in failed_models:
|
|
417
|
+
_, code = await check_model(http, model)
|
|
418
|
+
if code == 200:
|
|
419
|
+
# 重试成功
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### 模型能力检测
|
|
423
|
+
|
|
424
|
+
系统支持按类型筛选模型:
|
|
425
|
+
|
|
426
|
+
| 类型 | 说明 | 筛选方法 |
|
|
427
|
+
|------|------|----------|
|
|
428
|
+
| 视觉模型 | 支持图像输入 | `is_vision_model()` |
|
|
429
|
+
| 工具模型 | 支持函数调用 | `is_tool_model()` |
|
|
430
|
+
| 推理模型 | 支持思维链 | `is_reasoning_model()` |
|
|
431
|
+
| 联网模型 | 支持网络搜索 | `is_websearch_model()` |
|
|
432
|
+
| 免费模型 | 免费额度 | `is_free_model()` |
|
|
433
|
+
| 嵌入模型 | 文本嵌入 | `is_embedding_model()` |
|
|
434
|
+
| 重排模型 | 搜索重排 | `is_rerank_model()` |
|
|
435
|
+
|
|
436
|
+
能力数据从 Cherry Studio 同步,存储在 `data/model_capabilities.json` 中。
|
|
437
|
+
|
|
438
|
+
## 项目结构
|
|
439
|
+
```
|
|
440
|
+
key/
|
|
441
|
+
├── key_manager/ # 核心包
|
|
442
|
+
│ ├── __init__.py # 包导出
|
|
443
|
+
│ ├── cli.py # CLI 入口
|
|
444
|
+
│ ├── web.py # FastAPI 应用
|
|
445
|
+
│ ├── config.py # 配置加载
|
|
446
|
+
│ ├── storage.py # AES-256-GCM 加密存储
|
|
447
|
+
│ ├── errors.py # 结构化错误码
|
|
448
|
+
│ ├── api_models.py # Pydantic 模型
|
|
449
|
+
│ ├── parser.py # JSON 导入 + 路径验证
|
|
450
|
+
│ ├── detector.py # 智能提供商检测
|
|
451
|
+
│ ├── validator.py # 并发验证引擎
|
|
452
|
+
│ ├── checker.py # 重试包装器
|
|
453
|
+
│ ├── tester.py # 能力测试
|
|
454
|
+
│ ├── ssrf.py # SSRF 防护
|
|
455
|
+
│ ├── logger.py # 日志系统
|
|
456
|
+
│ ├── proxy.py # 代理检测
|
|
457
|
+
│ ├── webhook.py # Webhook 通知
|
|
458
|
+
│ ├── i18n.py # 国际化
|
|
459
|
+
│ ├── model_capabilities.py # 模型能力检测
|
|
460
|
+
│ └── providers/ # 45+ 提供商实现
|
|
461
|
+
│ ├── __init__.py # 注册表
|
|
462
|
+
│ ├── base.py # ABC 接口
|
|
463
|
+
│ ├── openai.py # OpenAI
|
|
464
|
+
│ ├── anthropic.py # Anthropic
|
|
465
|
+
│ └── ... # 更多提供商
|
|
466
|
+
├── tests/ # 测试套件
|
|
467
|
+
│ ├── test_detector.py # 提供商检测测试
|
|
468
|
+
│ ├── test_parser.py # 解析器测试
|
|
469
|
+
│ ├── test_validator.py # 验证器测试
|
|
470
|
+
│ ├── test_checker.py # 检查器测试
|
|
471
|
+
│ ├── test_providers.py # 提供商合约测试
|
|
472
|
+
│ ├── test_security.py # 安全回归测试
|
|
473
|
+
│ ├── test_storage.py # 加密存储测试
|
|
474
|
+
│ ├── test_errors.py # 错误系统测试
|
|
475
|
+
│ ├── test_i18n.py # 国际化测试
|
|
476
|
+
│ ├── test_e2e.py # 端到端测试
|
|
477
|
+
│ └── test_webhook.py # Webhook 测试
|
|
478
|
+
├── sdk/ # SDK
|
|
479
|
+
│ ├── python/ # Python SDK
|
|
480
|
+
│ └── typescript/ # TypeScript SDK
|
|
481
|
+
├── config.yaml # 配置文件
|
|
482
|
+
├── pyproject.toml # 项目配置
|
|
483
|
+
├── main.py # CLI 入口
|
|
484
|
+
└── web.py # Web 入口
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
## 配置说明
|
|
488
|
+
|
|
489
|
+
编辑 `config.yaml`:
|
|
490
|
+
|
|
491
|
+
```yaml
|
|
492
|
+
# 代理设置
|
|
493
|
+
proxy: "http://127.0.0.1:7890" # 或 socks5://127.0.0.1:7890
|
|
494
|
+
|
|
495
|
+
# 验证设置
|
|
496
|
+
check:
|
|
497
|
+
concurrency: 100 # 并发数
|
|
498
|
+
timeout_seconds: 30 # 超时时间
|
|
499
|
+
retry_failed: true # 失败重试
|
|
500
|
+
retry_count: 2 # 重试次数
|
|
501
|
+
|
|
502
|
+
# 测试设置
|
|
503
|
+
test:
|
|
504
|
+
token_steps:
|
|
505
|
+
- 1024
|
|
506
|
+
- 4096
|
|
507
|
+
- 16384
|
|
508
|
+
- 65536
|
|
509
|
+
concurrency_steps:
|
|
510
|
+
- 1
|
|
511
|
+
- 5
|
|
512
|
+
- 10
|
|
513
|
+
- 20
|
|
514
|
+
|
|
515
|
+
# 认证设置
|
|
516
|
+
auth:
|
|
517
|
+
api_key: "your-secret-api-key" # API 认证
|
|
518
|
+
|
|
519
|
+
# 速率限制
|
|
520
|
+
rate_limit:
|
|
521
|
+
requests_per_minute: 60
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
## API 端点
|
|
525
|
+
|
|
526
|
+
| 方法 | 端点 | 说明 |
|
|
527
|
+
|------|------|------|
|
|
528
|
+
| GET | `/api/keys` | 获取密钥列表 |
|
|
529
|
+
| GET | `/api/keys/export` | 导出有效密钥 |
|
|
530
|
+
| POST | `/api/import` | 导入密钥 |
|
|
531
|
+
| POST | `/api/import/upload` | 上传 JSON 文件导入 |
|
|
532
|
+
| POST | `/api/check/single` | 验证单个密钥 |
|
|
533
|
+
| POST | `/api/check/batch` | 批量验证 |
|
|
534
|
+
| POST | `/api/test/single` | 测试单个密钥 |
|
|
535
|
+
| POST | `/api/test/token` | 测试 Token 上限 |
|
|
536
|
+
| POST | `/api/test/concurrency` | 测试并发能力 |
|
|
537
|
+
| GET | `/api/models` | 获取模型列表 |
|
|
538
|
+
| POST | `/api/models/check` | 检测可用模型(SSE 流) |
|
|
539
|
+
| GET | `/api/providers` | 获取服务商列表 |
|
|
540
|
+
| GET | `/api/stats` | 获取统计信息 |
|
|
541
|
+
| GET | `/api/logs` | 获取日志 |
|
|
542
|
+
| POST | `/api/webhooks` | 创建 Webhook |
|
|
543
|
+
| GET | `/docs` | Swagger UI 文档 |
|
|
544
|
+
| GET | `/redoc` | Redoc 文档 |
|
|
545
|
+
|
|
546
|
+
## Webhook 使用
|
|
547
|
+
|
|
548
|
+
### 支持的事件类型
|
|
549
|
+
|
|
550
|
+
| 事件 | 说明 |
|
|
551
|
+
|------|------|
|
|
552
|
+
| `key.imported` | 密钥导入完成 |
|
|
553
|
+
| `key.checked` | 密钥验证完成 |
|
|
554
|
+
| `key.tested` | 密钥测试完成 |
|
|
555
|
+
| `key.deleted` | 密钥删除 |
|
|
556
|
+
| `batch.check.completed` | 批量验证完成 |
|
|
557
|
+
| `batch.test.completed` | 批量测试完成 |
|
|
558
|
+
| `error.occurred` | 发生错误 |
|
|
559
|
+
|
|
560
|
+
### 配置 Webhook
|
|
561
|
+
|
|
562
|
+
```yaml
|
|
563
|
+
webhooks:
|
|
564
|
+
- url: "https://example.com/webhook"
|
|
565
|
+
events:
|
|
566
|
+
- "key.imported"
|
|
567
|
+
- "key.checked"
|
|
568
|
+
secret: "your-webhook-secret" # HMAC-SHA256 签名
|
|
569
|
+
active: true
|
|
570
|
+
max_retries: 3
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
### 签名验证
|
|
574
|
+
|
|
575
|
+
```python
|
|
576
|
+
import hmac
|
|
577
|
+
import hashlib
|
|
578
|
+
import json
|
|
579
|
+
|
|
580
|
+
def verify_signature(payload, secret, signature):
|
|
581
|
+
body = json.dumps(payload, separators=(",", ":"), sort_keys=True)
|
|
582
|
+
expected = hmac.new(
|
|
583
|
+
secret.encode("utf-8"),
|
|
584
|
+
body.encode("utf-8"),
|
|
585
|
+
hashlib.sha256,
|
|
586
|
+
).hexdigest()
|
|
587
|
+
return signature == f"sha256={expected}"
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
## 测试
|
|
591
|
+
|
|
592
|
+
```bash
|
|
593
|
+
# 运行所有测试
|
|
594
|
+
python -m pytest tests/ -v
|
|
595
|
+
|
|
596
|
+
# 运行特定测试
|
|
597
|
+
python -m pytest tests/test_security.py -v
|
|
598
|
+
python -m pytest tests/test_detector.py -v
|
|
599
|
+
|
|
600
|
+
# 运行测试并查看覆盖率
|
|
601
|
+
python -m pytest tests/ --cov=key_manager --cov-report=term-missing
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
### 测试覆盖
|
|
605
|
+
|
|
606
|
+
| 模块 | 测试文件 | 测试数 |
|
|
607
|
+
|------|---------|--------|
|
|
608
|
+
| 提供商检测 | `test_detector.py` | 54 |
|
|
609
|
+
| 密钥解析 | `test_parser.py` | 12 |
|
|
610
|
+
| 验证器 | `test_validator.py` | 5 |
|
|
611
|
+
| 检查器 | `test_checker.py` | 4 |
|
|
612
|
+
| 提供商合约 | `test_providers.py` | 30 |
|
|
613
|
+
| 安全回归 | `test_security.py` | 12 |
|
|
614
|
+
| 加密存储 | `test_storage.py` | 26 |
|
|
615
|
+
| 错误系统 | `test_errors.py` | 28 |
|
|
616
|
+
| 国际化 | `test_i18n.py` | 37 |
|
|
617
|
+
| 端到端 | `test_e2e.py` | 17 |
|
|
618
|
+
| Webhook | `test_webhook.py` | 35 |
|
|
619
|
+
| OpenAPI | `test_openapi.py` | 26 |
|
|
620
|
+
|
|
621
|
+
## SDK 使用
|
|
622
|
+
|
|
623
|
+
### Python SDK
|
|
624
|
+
|
|
625
|
+
```bash
|
|
626
|
+
cd sdk/python
|
|
627
|
+
pip install -e .
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
```python
|
|
631
|
+
from key_manager_sdk import KeyManagerClient
|
|
632
|
+
|
|
633
|
+
client = KeyManagerClient(base_url="http://localhost:18001")
|
|
634
|
+
|
|
635
|
+
# 获取密钥列表
|
|
636
|
+
keys = client.get_keys()
|
|
637
|
+
|
|
638
|
+
# 验证单个密钥
|
|
639
|
+
result = client.check_single_key(key="sk-xxx", provider="openai")
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
### TypeScript SDK
|
|
643
|
+
|
|
644
|
+
```bash
|
|
645
|
+
cd sdk/typescript
|
|
646
|
+
npm install
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
```typescript
|
|
650
|
+
npm install @api-key-manager/sdk
|
|
651
|
+
|
|
652
|
+
const client = new KeyManagerClient({ baseUrl: 'http://localhost:18001' });
|
|
653
|
+
|
|
654
|
+
// 获取密钥列表
|
|
655
|
+
const keys = await client.getKeys();
|
|
656
|
+
|
|
657
|
+
// 验证单个密钥
|
|
658
|
+
const result = await client.checkSingleKey({ key: 'sk-xxx', provider: 'openai' });
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
## 依赖
|
|
662
|
+
|
|
663
|
+
- Python 3.10+
|
|
664
|
+
- httpx - 异步 HTTP 客户端
|
|
665
|
+
- FastAPI - Web 框架
|
|
666
|
+
- uvicorn - ASGI 服务器
|
|
667
|
+
- PyYAML - 配置解析
|
|
668
|
+
- Rich - 终端美化
|
|
669
|
+
- cryptography - 加密存储
|
|
670
|
+
- pydantic - 数据验证
|
|
671
|
+
|
|
672
|
+
## 已知问题和限制
|
|
673
|
+
|
|
674
|
+
### 1. 中转站服务商误识别
|
|
675
|
+
|
|
676
|
+
由于某些中转站服务商(如 Z.AI、DMXAPI、OCoolAI 等)使用与原厂相同的 API 端点和模型,当密钥无效时,错误响应可能包含原厂的签名关键词,导致误识别。
|
|
677
|
+
|
|
678
|
+
例如:一个硅基流动的密钥,如果被发送到阿里百炼的端点,阿里百炼会返回包含 "model-studio" 和 "apikey-error" 的错误响应,这可能导致系统误认为该密钥是阿里百炼的。
|
|
679
|
+
|
|
680
|
+
**解决方案**:系统要求至少2个签名匹配(200分)才返回识别结果,以减少误报。
|
|
681
|
+
|
|
682
|
+
### 2. 签名匹配的局限性
|
|
683
|
+
|
|
684
|
+
签名匹配依赖于服务商返回的错误响应体中的关键词。如果服务商更改了错误消息格式,签名可能失效。
|
|
685
|
+
|
|
686
|
+
**建议**:定期运行 `verify_signatures.py` 脚本验证签名的有效性。
|
|
687
|
+
|
|
688
|
+
### 3. 并发检测的超时问题
|
|
689
|
+
|
|
690
|
+
全并发探测时,某些服务商可能响应较慢(超过10秒超时)。这可能导致有效的服务商被跳过。
|
|
691
|
+
|
|
692
|
+
**解决方案**:系统会对失败的模型进行串行重试(最多2次)。
|
|
693
|
+
|
|
694
|
+
### 4. 智谱/Z.AI 密钥的双重检测
|
|
695
|
+
|
|
696
|
+
智谱和 Z.AI 使用相同的 GLM 模型和 API 格式,但使用不同的 Base URL:
|
|
697
|
+
|
|
698
|
+
- 智谱:`https://open.bigmodel.cn/api/paas/v4`
|
|
699
|
+
- Z.AI:`https://api.z.ai/api/paas/v4`
|
|
700
|
+
|
|
701
|
+
同一个密钥可能在两个平台都能工作,系统会返回第一个响应200的服务商。
|
|
702
|
+
|
|
703
|
+
### 5. 模型列表的时效性
|
|
704
|
+
|
|
705
|
+
模型列表从 Cherry Studio 同步,每日更新一次。新发布的模型可能需要等待同步后才能被检测到。
|
|
706
|
+
|
|
707
|
+
## 许可证
|
|
708
|
+
|
|
709
|
+
MIT License
|