# models-proxy **Repository Path**: bocaicn/models-proxy ## Basic Information - **Project Name**: models-proxy - **Description**: 多模型代理,自动选择可用模型 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-04 - **Last Updated**: 2026-09-04 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Model Proxy 一个**本地 OpenAI 兼容的模型转发代理**,把两条完全不同鉴权体系的上游,统一暴露成一个 `http://:8788/v1` 接口: | 上游 | 覆盖模型 | 鉴权方式 | |---|---|---| | **Copilot**(WorkBuddy 内置模型) | `hy4 preview` / `hy3` / `hunyuan-2.0-thinking` / `glm-5.2` / `glm-5.3-flash` / `deepseek-v4-flash` | **透明转发**客户端的 `Authorization` 会话令牌 | | **DevEco**(华为账户免费额度) | `GLM 5.1-DevEco`(上游实际为 `glm-5.1`) | **复用本机 DevEco Code 的登录凭据**,无需任何 API key | 核心能力:**免费模型自动轮询**。把 WorkBuddy 侧的模型固定为 `Free Model`,代理在后台按方案顺序逐个尝试可用模型,遇到限流 / 鉴权失效 / 模型不可用自动切下一个,前端无感知。 - 监听:`0.0.0.0:8788`(可由环境变量覆盖) - 监控面板:`http://:8788/` - 依赖:**仅 `cryptography` + Python 标准库**,无 Web 框架 --- ## 架构 ``` ┌──────────────────────────────────────────────────────────────┐ │ 客户端(WorkBuddy / curl / 任何 OpenAI SDK) │ │ POST http://127.0.0.1:8788/v1/chat/completions │ │ model = "Free Model" ← 触发免费模型轮询 │ └───────────────────────────┬──────────────────────────────────┘ ▼ ┌──────────────────────────────────────────────────────────────┐ │ Model Proxy(deveco_glm_proxy.py) │ │ │ │ 1. 读 model 字段 → 判断模式 │ │ ├─ 命中 free_model_trigger("Free Model") → 轮询模式 │ │ └─ 其它具体模型名 → 直连模式(失败才回退轮询) │ │ 2. route_for(model) 按 proxy_config.json 选路由 │ │ 3. 顺序尝试 → 错误分类 → 切换/重试/冷却 │ │ 4. SSE 流式原样透传;非流式由代理聚合后返回 │ └──────────┬──────────────────────────────┬────────────────────┘ ▼ ▼ ┌────────────────────────────┐ ┌────────────────────────────────┐ │ 路由 A:copilot │ │ 路由 B:deveco │ │ copilot.tencent.com │ │ cn.devecostudio.huawei.com │ │ /v2/chat/completions │ │ /sse/codeGenie/maas/v2/... │ │ │ │ │ │ 原样透传客户端 │ │ ① 解密本地 token.enc(信封加密) │ │ Authorization 会话令牌, │ │ KEK(keys/kek-v1.bin) → DEK │ │ 代理自身不持有任何密钥。 │ │ → 账户 JWT │ │ │ │ ② jwToken/check 换服务 token │ │ │ │ (20 分钟有效,自动缓存刷新)│ │ │ │ ③ 带 x-deveco-* / Chat-Id 调用 │ │ │ │ 强制 stream:true(SSE) │ │ │ │ ⚠ 免费额度上限 50 次/分钟 │ └────────────────────────────┘ └────────────────────────────────┘ ``` ### 为什么要走代理,而不是直连 WorkBuddy 的内置模型(Hy3 / Hy4 / Hunyuan 等)各自有独立的**免费额度与频率限制**,用超了只能在客户端手动切模型。Model Proxy 把这些模型收拢成一个 `Free Model`,由代理在服务端判断哪个还能用,实现"**永不中断**"的免费额度串联。 --- ## 快速开始 ### 环境要求 - Python 3.10+,且**已安装 `cryptography`** - (仅 DevEco 路由需要)已安装 **DevEco Code / DevEco Studio** 并用华为账户**登录过一次** - 无其它第三方依赖 ```powershell python -m pip install cryptography ``` ### 启动 | 脚本 | 用途 | 特点 | |---|---|---| | `start_proxy.bat` | **手动启动**(双击即可) | 结束时有 `pause`,报错看得见 | | `start_proxy_service.bat` | **无窗口/计划任务**启动 | 刻意**不带** `pause`,被任务计划程序拉起时不会卡住 | | `start_watchdog.bat` | **看门狗**常驻 | 掉线自动拉起,内置单例保护 | 三个脚本都会先 `call _find_python.bat` 自动挑选解释器(见下),然后启动对应 Python 程序。 **解释器探测机制**(`_find_python.bat`):按以下顺序找**第一个 `import cryptography` 成功**的解释器,结果写入 `%PYEXE%`: ``` GLM_PROXY_PYTHON 环境变量 > py -3 启动器 > %LOCALAPPDATA%\Python\bin\python.exe > %LOCALAPPDATA%\Programs\Python\* > %ProgramFiles%\Python3* > PATH 中的 python ``` > ⚠️ 判据是 **能否 import cryptography**,不是"有没有 python"。很多机器上 PATH 第一位是某个内部/精简解释器(没装 cryptography),盲目用裸 `python` 会直接 `ModuleNotFoundError`。 > 脚本**不写死任何机器相关路径**,换机器可直接用;也可用 `GLM_PROXY_PYTHON` 强制指定。 启动成功后控制台输出: ``` DevEco GLM proxy listening on http://0.0.0.0:8788/ (dashboard at / ) ``` 浏览器打开 `http://127.0.0.1:8788/` 即可看到监控面板。 ### 看门狗(可选) `watch_proxy.py` 每 **10 秒**探测一次 `8788` 端口,一旦发现无响应就以 **detached** 方式重新拉起代理,重启记录写入 `proxy_watchdog.log`。 - **单例保护**:通过 `.watchdog.lock` 的 OS 级咨询锁(Windows `msvcrt.locking` / POSIX `fcntl.flock`)保证全局只有一个实例;重复双击会打印"已有看门狗实例在运行(PID=…)"后自动退出,避免"看门狗农场"互相抢端口。 - 锁随进程退出由操作系统自动释放,旧实例异常死亡也不会留下永久死锁;PID 单独写在 `.watchdog.pid`(锁文件持有期间绝不读写,避免与锁冲突)。 - Ctrl+C 只退出看门狗,已被它拉起的代理继续运行。 --- ## 接入 WorkBuddy 代理启动时会自动把项目根目录的 `models.json` **合并**进 `~/.workbuddy/models.json`(按 `id` 去重:已存在则原地更新,新的追加到末尾,WorkBuddy 原有模型全部保留)。所以通常**不需要手动改任何配置**,重启一次 WorkBuddy 即可看到。 当前 `models.json` 提供两个入口: | id / name | 说明 | |---|---| | `Free Model` | **推荐**。进入免费模型轮询,由代理自动挑可用模型 | | `GLM 5.1-DevEco` | 直连 DevEco 路由,明确只用华为免费额度 | 两者 Base URL 都是 `http://127.0.0.1:8788/v1`。 --- ## 两种工作模式 ### 1. Free Model 轮询模式(推荐) 请求的 `model` 等于 `free_model_trigger`(默认 `Free Model`)时触发。按**当前生效方案**的模型顺序逐个尝试: 1. 自动跳过:手动禁用的模型、仍在冷却期(限流 / 不可用未到重置时间)的模型 2. 逐个尝试;某个模型失败 → 按错误分类决定**重试**还是**切下一个** 3. 全部失败 → 返回 `502 {"type":"all_models_failed"}` > 注意:具体用哪个方案由**面板上选择的"当前方案"**决定,而不是由请求里的模型名决定。`Free Model Plan A / Plan B` 这类旧 id 仅作兼容保留。 ### 2. 直连模式 请求指定具体模型(如 `GLM 5.1-DevEco`、`hy3`)时,原样转发给该模型;只有失败且**响应头尚未发出**时,才回退到当前方案轮询。 --- ## 路由规则 由 `proxy_config.json` 的 `routes` 驱动(**修改后按 mtime 自动热加载,无需重启**)。按字典顺序自上而下匹配,模型名(小写后)**包含** `match` 中任一关键字即命中: | 路由 | 当前 `match` | 上游模型 | 上游地址 | |---|---|---|---| | `deveco` | `["deveco"]` | 固定 `glm-5.1` | `cn.devecostudio.huawei.com/sse/codeGenie/maas/v2/chat/completions` | | `copilot` | `["glm","hy3","hy4","hunyuan","deepseek"]` | 原样透传 | `copilot.tencent.com/v2/chat/completions` | 未命中任何规则时,回退到 `default_route`(当前为 `copilot`)。 > ⚠️ **`deveco` 的 match 只有 `deveco`**,这是覆盖代码默认值后的结果。它决定了 `glm-5.2` / `glm-5.3-flash` 是走 copilot、而不是走 DevEco 链路;把 `glm` 加回 `deveco.match` 等于把整条链路换掉,请谨慎修改。 > > ⚠️ **Copilot 上游只认全小写连字符 id**。写成 `GLM-5.3-Flash` / `Deepseek-V4-Flash` 会被上游拒绝(`400 code=11102 "service info not found"`),并被代理标记为"1 小时后重试",很难一眼看出是大小写问题。例外:`GLM 5.1-DevEco` 是**显示名**(含 `deveco` → 走 deveco 路由 → 上游固定用 `glm-5.1`),不受此限制。 > > 内部映射:`hy4 preview`(方案里的写法)→ `hy4-preview`(上游 id)。 --- ## 方案(Plan) 方案只存在于代理端,WorkBuddy 完全不感知。面板下拉框可实时切换,选择会持久化。 | 方案 | 模型顺序 | 适用场景 | |---|---|---| | **Plan A**(默认) | `hy4 preview` → `hy3` → `GLM 5.1-DevEco` | 优先吃 WorkBuddy 内置额度 | | **Plan B** | `hy4 preview` → `hy3` → `GLM 5.1-DevEco` → `glm-5.3-flash` → `deepseek-v4-flash` | 备选池更大,适合额度普遍吃紧时 | 每个方案的**模型启用/禁用状态相互独立**(在 `hy4` 于 Plan A 被禁用,不影响 Plan B),分别持久化。而**限流冷却时间是全局的**——那是上游的属性,与方案无关。 新增模型时**三处需同步**:`deveco_glm_proxy.py` 的 `MODELS`(影响 `/v1/models`)、`plans.*.models`、`fallback_chain`。 --- ## 容错机制 ### 错误分类与处置 | 分类 | 判定依据 | 处置 | |---|---|---| | `ratelimit` | HTTP 429、copilot `code=6004`、报文含"频率限制/quota/rate limit/reset" | 解析并记录**精确重置时间**,标记冷却,切换下一模型 | | `authfail` | HTTP 401 / 403 | 标记鉴权失败,切换下一模型(DevEco 路由会先强制刷新服务 token 重试一次) | | `unavailable` | copilot `code=11102` | 标记 1 小时后重试,切换下一模型 | | `queue` | 报文含 overload / 排队 / waiting / in position | 等待 `queue_wait`(5s) 后重试,最多 `queue_retry`(5) 次 | | `other` | 其余 | 等待 `other_wait`(5s) 后重试,最多 `other_retry`(5) 次 | 达到重置时间后模型**自动恢复可用**(每次轮询前检查)。流式响应一旦开始(响应头已发出),不再切换模型——否则会破坏输出。 ### DevEco 限流保护(50 次/分钟) DevEco 免费额度上限约 50 次/分钟。代理在**返回响应前**做滑动窗口限流: ``` 最近 60s 请求数 >= 45 → 滞留响应,等待流量回落 回落到 < 40 → 恢复返回 单次滞留最长 180s → 超时强制放行(避免无限挂起) ``` 这一层只作用于 **DevEco 路由**(copilot 路由不计数、不滞留)。 ### DevEco 服务 token 生命周期 - 本地 `token.enc` → 信封加密解密(KEK 明文在 `keys/kek-v1.bin`)→ 账户 JWT - JWT → `jwToken/check` → 服务 token,**缓存 20 分钟**,到期自动刷新 - 遇 401/403 会**强制刷新后重试一次** - 启动时后台线程预热爱一次 token;失败会在面板顶部显示红色**登录引导横幅**(不影响 copilot 路由继续可用) --- ## 监控面板 浏览器打开 `http://:8788/`: - **状态卡片**:监听地址、运行时长、服务 Token 剩余、DevEco 登录状态、账户 userId、实时流量(上游/min)、当前模型、当前方案 - **实时日志**:最近 300 条(内存缓冲,同时落盘 `proxy_run.log`) - **方案状态**:方案下拉切换;每个模型的**启用复选框**、状态、限流重置时间;"保存模型启用状态"按方案独立持久化;DevEco token 可一键"刷新" ### HTTP 接口 | 方法 | 路径 | 说明 | |---|---|---| | GET | `/` 、`/dashboard` | 监控面板 HTML | | GET | `/api/status` | 运行状态(token、流量、模型状态、方案、路由) | | GET | `/api/config` | 当前生效配置 | | GET | `/api/logs?limit=150` | 最近日志(上限 300) | | GET | `/v1/models` | 模型列表(`owned_by` 按真实路由标注) | | POST | `/v1/chat/completions` | **OpenAI 兼容**聊天接口(流式 / 非流式) | | POST | `/api/reload-config` | 强制重载 `proxy_config.json` | | POST | `/api/refresh` | 强制刷新 DevEco 服务 token | | POST | `/api/set-plan` | 切换当前方案(`{"plan":"Plan B"}`) | | POST | `/api/save-disabled` | 保存某方案的模型启用状态 | ### 直接调用示例 ```powershell # 非流式 curl http://127.0.0.1:8788/v1/chat/completions ` -H "Content-Type: application/json" ` -d '{"model":"Free Model","messages":[{"role":"user","content":"你好"}],"stream":false}' # 查看状态 curl http://127.0.0.1:8788/api/status ``` --- ## 文件说明 | 文件 | 说明 | |---|---| | `deveco_glm_proxy.py` | **代理核心**:路由、轮询、限流、SSE 转发/聚合、监控面板、HTTP 服务 | | `watch_proxy.py` | 看门狗:端口探活 + 自动拉起 + 单例锁 | | `start_proxy.bat` | 手动启动(带 pause) | | `start_proxy_service.bat` | 计划任务/无窗口启动(无 pause) | | `start_watchdog.bat` | 启动看门狗 | | `_find_python.bat` | 公共辅助:挑选已装 cryptography 的解释器,输出 `%PYEXE%` | | `proxy_config.json` | **主配置**:路由规则、方案定义、默认模型、降级链(热加载) | | `models.json` | 启动时同步到 WorkBuddy 的模型定义 | | `proxy_disabled.json` | 各方案独立的模型禁用列表(运行时生成) | | `proxy_plan_state.json` | 当前生效方案(运行时生成) | | `proxy_run.log` / `proxy_watchdog.log` | 运行日志 / 看门狗日志 | ### 配置加载机制 `load_config()` 是**合并式**:先取代码里的 `_default_config()`,再用 `proxy_config.json` 覆盖 `routes` / `default_model` / `default_route` / `free_model_trigger` / `active_plan` / `fallback_chain` / `plans`(仅当该键存在)。 - 改 `proxy_config.json` → **自动热加载,无需重启**(按 mtime 判断) - 改代码里的默认值 → **必须重启进程** - 首次运行若配置文件不存在,会自动生成一份默认 `proxy_config.json` --- ## 环境变量 | 变量 | 默认值 | 说明 | |---|---|---| | `GLM_PROXY_HOST` | `127.0.0.1`(bat 脚本设为 `0.0.0.0`) | 监听地址。`0.0.0.0` = 局域网可访问 | | `GLM_PROXY_PORT` | `8788` | 监听端口(需与 `models.json` 中的 url 一致) | | `GLM_PROXY_PYTHON` | 无 | 强制指定解释器路径,优先级高于自动探测 | | `DEVECO_CONFIG` | `~/.config/deveco` | DevEco 凭据目录,一般无需设置 | --- ## 排错 | 症状 | 原因与处理 | |---|---| | bat 报 `'pose:' 不是内部或外部命令` / `'cho' 不是内部或外部命令` | `.bat` 换行成了 LF 或编码非 GBK。**cmd 要求 CRLF + GBK**,否则整行粘连后被乱切。仓库已用 `.gitattributes` 锁死 `*.bat text eol=crlf`,别删该文件 | | `ModuleNotFoundError: No module named 'cryptography'` | 解释器选错。执行 `pip install cryptography`,或用 `GLM_PROXY_PYTHON` 指定已装好的解释器 | | 启动时报"端口 8788 已被占用" | 已有代理在跑(可能是看门狗拉起的)。确认后结束旧进程再启动 | | `GLM 5.1-DevEco` 报鉴权失败 / 面板出现红色登录横幅 | DevEco 登录已失效。打开 DevEco 用华为账户登录一次,`~/.config/deveco/token.enc` 会自动刷新,然后点面板"刷新 Token"或重启代理 | | 上游 `400 code=11102 service info not found` | copilot 上游不认该模型名,**必须全小写连字符**。检查 `plans.*.models` 与 `fallback_chain` | | `429 使用量已超出频率限制…可在 <时间> 重置` | 该模型免费额度用尽(**不是故障**)。代理会自动记录重置时间并切换下一模型,到点自动恢复 | | 响应很慢 | DevEco 路由触发了 45/min 软限流,正在滞留等待流量回落。面板"实时流量"可确认 | --- ## 安全注意事项 - `~/.config/deveco/` 下的 `token.enc`、`token.dek`、`keys/kek-*.bin` 是**完整的 DevEco 账户登录凭证**,切勿外泄、切勿提交到仓库。 - `proxy_run.log` 等 `*.log` **已在 `.gitignore` 中排除**——日志可能包含客户端会话令牌,不要手动提交。 - copilot 路由是**透明转发**:代理只是把客户端令牌原样递交给上游,自身不持有、不落盘任何密钥。 - 本代理仅限**本机、本人账户**使用。 - DevEco 账户 JWT 本身有效期约 21 天,到期需重新登录 DevEco 以刷新本地凭据文件(代理会自动读取新文件)。 --- ## 已知限制 - DevEco 端点**强制 SSE**;非流式请求由代理在服务端聚合而成,超长回复会略慢于原生流式。 - 这是基于 DevEco / Copilot 生态私有接口的方案,上游若修改鉴权协议或端点路径,代理需同步更新。 - 流式响应一旦开始就不再切换模型,此后的上游错误会直接暴露给客户端。 - 项目名已更新为 **Model Proxy**;为保持启动脚本、配置项与历史文档的路径引用稳定,`deveco_glm_proxy.py` 等**文件名保持不变**。