自建 Qwen(chat.qwen.ai)账号池网关:自研打码注册(Python Worker) + Fastify/TS 协议转换与账户体系。
对外协议目标:
- OpenAI Chat Completions:
POST /v1/chat/completions(含 stream SSE) - OpenAI Responses:
POST /v1/responses,包含非流式响应和标准 typed SSE 事件 GET /v1/models:实时同步 Qwen Web 当前模型名称、能力和思考模式
内部把请求转到 Qwen Web Chat API(Bearer JWT),并做:
- 账号入库(SQLite)
- 保活 / 自动重新授权(并发与冷却可配)
- 会话粘性(
X-Session-Id/user) - 会话上下文压缩(gzip)
- 账号轮询(sticky_lru / round_robin / random)
- 单账号串行请求队列、最小请求间隔和一次有界 429 退避重试
- 图片、音频、视频和文档上传到 Qwen 文件接口
Client (OpenAI SDK)
│ /v1/chat/completions
▼
Fastify API (Node/TS) ──账号池/粘性/保活/reauth──► SQLite
│
├─ Qwen Web API (chat.qwen.ai) 协议转换
│
└─ Register Worker (Python) 自研滑块注册(不付费打码)
pip install -r requirements.txt
playwright install chromium
# 本机调试可用有头:HEADLESS=0
uvicorn qwen_register.worker_app:app --host 0.0.0.0 --port 8091cd apps/api
npm install
npm run devcurl -X POST http://127.0.0.1:8080/v1/accounts/register \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d "{\"max_attempts\":5,\"headless\":false}"curl http://127.0.0.1:8080/v1/chat/completions \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-H "X-Session-Id: my-session-1" \
-d '{
"model": "qwen3.7-plus",
"messages": [{"role":"user","content":"你好"}],
"stream": false,
"reasoning_effort": "high",
"web_search": true
}'Python:
from openai import OpenAI
client = OpenAI(base_url="http://127.0.0.1:8080/v1", api_key="your-key")
print(client.chat.completions.create(
model="qwen3.7-plus",
messages=[{"role":"user","content":"hi"}],
reasoning_effort="high",
extra_body={"web_search": True},
))GET /v1/models 每次请求都会通过账号池查询 Qwen 上游 /api/v2/models/。网关会解析上游当前的 data.data 结构,不缓存、不补充静态模型名。上游返回空列表时原样返回空列表;上游查询失败时明确返回错误,避免客户端把过期模型误认为仍然可用。
每个模型除 OpenAI 标准字段外,还会返回以下扩展字段:
| 字段 | 含义 |
|---|---|
name |
Qwen 页面展示名称,例如 Qwen3.7-Plus |
capabilities |
上游能力开关,例如 thinking、search、vision |
context_window |
上游声明的最大上下文长度 |
max_output_tokens |
上游声明的生成或摘要上限 |
thinking_modes |
可用模式,取值为 Auto、Thinking、Fast |
default_thinking_mode |
根据模型元数据推导的默认模式 |
auto_search |
模型是否声明自动搜索或搜索能力 |
qwen |
Qwen 原生思考能力等级、思考格式、模态和聊天类型 |
客户端应先调用 /v1/models,再使用返回对象的 id 发起生成请求。上游成功返回模型时,网关不会额外注入 QWEN_DEFAULT_MODEL。
Chat Completions 和 Responses 都支持以下输入方式:
| 输入 | Qwen 上游结果 |
|---|---|
reasoning_effort: "none" 或 "minimal" |
thinking_mode: "Fast" |
reasoning_effort: "low"、"medium"、"high" 或 "xhigh" |
thinking_mode: "Thinking" |
reasoning_effort: "auto" |
thinking_mode: "Auto" |
reasoning: {"effort":"high"} |
thinking_mode: "Thinking" |
thinking_mode: "Auto"、"Thinking" 或 "Fast" |
直接使用指定模式 |
enable_thinking: true |
thinking_mode: "Thinking" |
enable_thinking: false |
thinking_mode: "Fast" |
thinking_budget: 4096 |
写入 Qwen feature_config.thinking_budget |
没有请求级参数时使用 QWEN_DEFAULT_THINKING_MODE,默认值为 Auto。所有用户消息都会带上 Qwen 当前前端使用的完整配置:thinking_enabled、output_schema、research_mode、auto_thinking、thinking_mode、thinking_format 和 auto_search。
网页搜索默认开启。以下任一请求级字段可以显式控制它:
{
"web_search": false
}{
"enable_search": false
}{
"search": false
}Responses API 或 Chat Completions 请求中出现 {"type":"web_search"}、{"type":"web_search_preview"} 或 {"type":"search"} 工具时,也会启用上游 feature_config.auto_search。请求没有显式设置时使用 QWEN_DEFAULT_WEB_SEARCH,默认值为 1。
网关先调用 Qwen /api/v2/files/getstsToken,再使用临时 STS 凭据把文件上传到 Qwen 指定的 OSS 路径。文档会继续调用 /api/v2/files/parse 并轮询 /api/v2/files/parse/status;完成后才把 Qwen 文件对象放入聊天消息。
支持的 OpenAI 输入块如下:
| 协议 | 输入块 |
|---|---|
| Chat Completions | text、image_url、input_audio、video_url、file |
| Responses | input_text、input_image、input_audio、input_video、input_file |
文件来源可以是 HTTPS URL、data: URL,或相应字段中的 Base64 数据。远程 URL 会解析 DNS,并在每次 HTTP 重定向后重新检查目标地址;回环、链路本地、私网、CGNAT 和带 URL 凭据的地址会被拒绝。默认单文件上限为 100 MiB,请求体上限为 140 MiB。Qwen 模型是否接受具体模态应以 /v1/models 返回的 capabilities 和 qwen.modality 为准。
当前没有实现 OpenAI Files API,因此仅给出 file_id、而不提供 URL 或数据的输入会返回 HTTP 400,不会静默丢弃附件。
客户端应在连续请求中保持同一个 X-Session-Id。未提供该请求头时,网关依次使用 user 的 SHA-256 摘要或新 UUID,并在响应头中返回实际会话 ID。
每个会话保存以下状态:
| 状态 | 作用 |
|---|---|
account_id |
会话固定使用同一 Qwen 账号 |
qwen_chat_id |
复用 Qwen 上游会话 |
last_message_id |
作为下一轮 Qwen parent_id |
context_blob |
gzip + Base64URL 压缩的最近 40 条消息 |
expires_at |
会话粘连过期时间 |
当 Qwen 父消息链完整时,网关只发送最新用户消息和 parent_id;父链缺失或账号需要重绑时,网关清空旧 Qwen ID,并用本地压缩上下文重建请求。不同会话按 sticky_lru、round_robin 或 random 选择账号。同一个会话在服务内严格串行,同一个账号也只有一个真正的 Qwen 请求在途;其他请求在内存队列等待。数据库 inflight 只统计已经离开队列并访问 Qwen 的请求,进程启动时会复位异常退出遗留的租约。
能够可靠转换的 Chat Completions 参数包括 model、messages、stream、temperature、top_p、max_tokens、max_completion_tokens、stream_options.include_usage、思考扩展字段、网页搜索字段和受支持的多模态内容。
能够可靠转换的 Responses 参数包括 model、input、instructions、stream、temperature、top_p、max_output_tokens、reasoning、网页搜索字段和受支持的多模态内容。流式返回包含 response.created、response.in_progress、response.output_item.added、response.content_part.added、response.output_text.delta、各级 done 事件以及 response.completed;失败使用 response.failed,不发送 Chat Completions 的 [DONE]。
Qwen Web 没有等价能力的参数会返回 HTTP 400,包括函数或自定义工具调用、JSON Schema 结构化输出、音频输出、多个候选结果、stored Responses、previous_response_id、conversation、background mode、logprobs、penalties、prediction、seed 和 stop。这样客户端不会误以为参数已经生效。
浏览器管理控制台位于 /admin。它使用独立管理员账号密码和 HttpOnly Cookie 登录,不要求浏览器持有终端 API key。控制台采用与 MiMo2API 一致的运维工作台布局,包含服务概览、账号池、动态模型、注册机、可编辑运行配置和密钥管理。模型页可以立即重新同步上游;运行配置与注册参数经过范围校验后写入 SQLite 并在进程内生效;注册任务在后台串行运行,可轮询进度和停止后续账号。每个新账号在注册开始时生成独立高强度随机密码,结果随账号写入 SQLite 并仅供系统生命周期使用;账号密码和 token 不在管理页面或管理接口回显。
密钥页只管理终端 API key 和系统级密钥。所有密钥默认隐藏,点击对应的小眼睛按钮可显示完整明文。
普通 OpenAI 与账号管理 API 始终要求 Authorization: Bearer <API key>。启动时的 API_KEY 会作为 Bootstrap API key 写入 SQLite;控制台生成的其他 key 同样以明文写入 api_keys 表。撤销后新请求立即返回 HTTP 401。Bootstrap key 由 K8s Secret 管理,不能在控制台撤销。
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /health |
健康检查 + worker |
| GET | /v1/accounts |
账号列表 |
| POST | /v1/accounts/register |
调 Python Worker 注册并入库 |
| POST | /v1/accounts/import |
手工导入 email/password/token |
| POST | /v1/accounts/:id/reauth |
重新授权(signin) |
| POST | /v1/keepalive/run |
立即跑一轮保活 |
| GET | /v1/models |
OpenAI models |
| POST | /v1/chat/completions |
OpenAI chat |
| POST | /v1/responses |
OpenAI responses 子集 |
管理员接口还提供 /admin/api/models/refresh、/admin/api/runtime 和 /admin/api/registration/*,分别用于上游模型刷新、SQLite 热配置和后台注册任务。管理员写操作同时要求登录 Cookie 与 CSRF token。
bx-ua、bx-umidtoken 和 bx-v 不是静态配置。Python Worker 保持一个与 Qwen Web 相同 User-Agent 的 Playwright 浏览器,加载 Qwen 当前页面和 AWSC/Baxia 模块。API 每次准备访问 Qwen 上游时,都会把本次目标 URL、HTTP 方法和请求体发送到 Worker;Worker 在页面内发起一条被本地路由立即中止的探测请求,从请求发出瞬间提取本次新生成的风控头及当前前端版本,再交给 API 的真实请求。任何一次生成失败都会使该上游请求明确失败,不会复用旧值或回退到固定环境变量。
| 变量 | 默认 | 说明 |
|---|---|---|
API_KEY |
无 | 启动时写入 SQLite 的 Bootstrap API key;普通 API 没有有效 key 时一律拒绝 |
ADMIN_USERNAME |
admin |
/admin 管理员账号 |
ADMIN_PASSWORD |
无 | /admin 管理员密码;生产必须由 Secret 注入 |
ADMIN_SESSION_TTL_MS |
28800000 | 管理员登录会话有效期 |
ADMIN_COOKIE_SECURE |
1 | 管理 Cookie 是否只允许 HTTPS;本地 HTTP 调试设为 0 |
REGISTER_WORKER_URL |
http://127.0.0.1:8091 |
打码 Worker |
WORKER_TOKEN |
空 | Worker 共享密钥 |
REGISTER_HEADLESS |
0 | 注册 Worker 是否使用无头浏览器;容器默认通过 Xvfb 使用有界面模式以降低 WAF 指纹异常 |
REGISTER_BROWSER |
camoufox | 注册浏览器后端;Camoufox 启动失败时自动回退 Patchright |
REGISTER_USE_PROXY |
1 | 注册时是否启用 Worker 代理池,默认开启 |
REGISTER_MAX_ATTEMPTS |
5 | 单账号内部最大注册尝试次数 |
REGISTER_BETWEEN_ATTEMPTS_SEC |
8 | 单账号内部重试间隔秒数 |
REGISTER_BATCH_COUNT |
1 | 后台注册任务默认账号数量 |
REGISTER_BATCH_INTERVAL_SEC |
60 | 批次中两个账号之间的间隔秒数 |
KEEPALIVE_INTERVAL_MS |
600000 | 保活周期 |
KEEPALIVE_CONCURRENCY |
3 | 保活并发 |
REAUTH_CONCURRENCY |
2 | 重新授权并发 |
REAUTH_COOLDOWN_MS |
60000 | 单账号 reauth 冷却 |
ACCOUNT_PICK_STRATEGY |
sticky_lru |
轮询策略 |
MAX_INFLIGHT_PER_ACCOUNT |
1 | 单账号真正访问 Qwen 的最大在途请求数 |
QWEN_ACCOUNT_REQUEST_INTERVAL_MS |
1000 | 同一账号两次上游请求之间的最小间隔 |
QWEN_RATE_LIMIT_RETRY_MS |
3000 | 上游 429 后唯一一次重试前的等待时间 |
QWEN_MAX_UPLOAD_BYTES |
104857600 | 单个多模态文件最大字节数 |
BODY_LIMIT_BYTES |
146800640 | Fastify 请求体最大字节数 |
SESSION_COMPRESS |
1 | 会话上下文 gzip |
AUTO_REAUTH |
1 | 401 自动登录刷新 |
QWEN_DEFAULT_MODEL |
qwen3.7-plus |
生成请求未指定 model 时使用的默认模型,可在管理台热更新 |
QWEN_DEFAULT_WEB_SEARCH |
1 | 请求未指定时默认启用网页搜索 |
QWEN_DEFAULT_THINKING_MODE |
Auto |
请求未指定时的思考模式,可选 Auto、Thinking、Fast |
生产环境不在本机构建镜像。.github/workflows/build-and-deploy.yml 在每次 main 推送后完成以下步骤:
- 安装 Node 22 依赖,执行单元测试、TypeScript 类型检查、编译和 Python 语法检查。
- 使用 GitHub Actions Buildx 构建
linux/arm64API 和 Worker 镜像。 - 推送
speedproxy/qwen2api:api-sha-<commit>与worker-sha-<commit>。 - 使用只对 GitOps 仓库有写权限的 deploy key 更新
qwen2api/oracle/kustomization.yaml。 - ArgoCD 自动同步到 Oracle K8s,并使用
Recreate保证 SQLite PVC 单写。
源仓库需要三个 Actions Secret:DOCKERHUB_USERNAME、DOCKERHUB_TOKEN、GITOPS_DEPLOY_KEY。K8s 运行时 Secret qwen2api-secrets 和拉取 Secret qwen2api-dockerhub 不进入 Git。生产入口为 https://qwen2api.mnnu.eu.org/v1,长流式直连入口为 https://qwen2api-direct.mnnu.eu.org/v1。
| 客户端 (OpenAI) | 网关 | 上游 (Qwen Web) |
|---|---|---|
messages[] |
转为 Qwen 2.1 完整消息对象 | /api/v2/chat/completions?chat_id=... messages |
stream: true |
SSE 重打包 | Qwen SSE / 增量字段 |
/v1/responses input |
转为 messages;返回 Responses typed events | 同上 chat 路径 |
X-Session-Id |
粘账号 + qwen chat_id | chats/new + 复用 chat_id |
| 图片/音频/视频/文档 | STS 上传、文档解析、Qwen files 对象 | /api/v2/files/* + OSS |
| 思考参数 | 统一映射 | message.feature_config.thinking_* |
| 搜索参数 | 默认开启,可按请求关闭 | message.feature_config.auto_search |
上游字段若随前端版本变化,主要改 apps/api/src/qwenChat.ts。