模型协议代理(实验)
⚠️ 实验性功能:接口、字段和限制以后可能调整。不影响普通飞书机器人的使用。
botmux model-proxy serve 会在本机 127.0.0.1 起一个带鉴权的 OpenAI Chat Completions 兼容入口。只会说 OpenAI 协议的工具(比如用 OpenAI SDK 写的脚本、代码评审工具),把 baseURL 指过来,就能直接用 CLI(Codex、Claude Code 等)自己的登录去调模型,不需要另外申请 API key。
适合什么场景
几个要点先说清:
- 只监听本机回环(
127.0.0.1),配置里也只能写这个地址,外网访问不到。 - CLI 不会动你的文件:每次调用都关掉 CLI 的原生工具,只做推理。返回的
tool_calls只是建议,执行权在你的工具手里。 - 不借用别的登录:代理只用你指定的那个专用机器人目录里的登录,不会去拿聊天机器人、全局登录或其它机器人的凭据。
快速上手
第 1 步:起一个专用的 core-only 实例
代理本身不调模型,它把请求转给一个独立的 core-only 实例(一种不连飞书、只走本机 HTTP 的 botmux,见 Core-only)。这个实例要满足:
- 是独立进程,有自己的数据目录和端口。不要复用、也不要为此重启正在跑的飞书机器人 daemon。
- 机器人 ID 形如
local_<名字>,名字里只用字母、数字、_、-(代理配置不接受.)。 - 用下表里已经接通的 CLI,并且在该机器人的专用目录里完成原生登录。
这个命令在前台运行,后面的步骤请另开一个终端,并在新终端里重新 export STATE_DIR=...(和上面同一个目录)。
登录文件放在 $STATE_DIR/bots/<机器人 ID>/<子目录> 下:
以 Codex 为例,可以用 CODEX_HOME="$STATE_DIR/bots/local_reasoner/codex" codex login 登录这个目录。
这个实例必须保持「干净」:不要开沙盒、不要设
--backend、不要给它配额外的环境变量、启动命令或 wrapper。带了这些配置时,代理启动会直接报inference_profile_unsupported。
第 2 步:写代理配置文件
把下面的内容存成一个文件,比如 $STATE_DIR/model-proxy.json:
保存后把权限收紧到只有自己能读写,否则代理拒绝启动:
- 文件必须是属于当前用户的普通文件,权限正好是
0600。 - 它所在的目录(以及上层目录)不能被其他用户写。
第 3 步:生成 token 并启动代理
启动成功会打印一行 JSON:
代理在前台运行,Ctrl+C(SIGINT)或 SIGTERM 退出,退出时会取消还在跑的调用。
第 4 步:在工具里接入
想先确认连通,可以列一下这个 token 能用的模型:
配置文件详解
顶层字段
配置里出现未知字段会直接报错,不会被忽略。
models:模型别名
客户端只看到别名(例如 review-model),看不到背后是哪个机器人、哪个原生模型。
clients:客户端与 token
- token 至少 32 个字符、不含空白;不同客户端的 token 不能相同。
- 这个 token 和 CLI 的订阅凭据没有关系,它只决定「能用哪些别名」。
- 换 token 要重启代理。
支持的接口和字段
只有两个接口,都要带 Authorization: Bearer <token>:
GET /v1/models:列出当前 token 能用的别名。POST /v1/chat/completions:非流式对话补全,Content-Type必须是application/json。
工具参数和 json_schema 支持的 JSON Schema 关键字:type、properties、required、布尔型 additionalProperties、items、enum、description、title、数值上下界、字符串长度、数组长度。$ref、anyOf/oneOf 这类组合、正则等不支持,会直接报错。
多轮工具调用
和 OpenAI 的用法一样:模型返回 tool_calls → 你的工具执行 → 把结果作为 tool 消息(带 tool_call_id)放回下一次请求。代理会检查历史是否完整:
tool_call_id不能重复,每个调用都要有对应的结果。- 结果没回齐之前,不能插入别的消息。
模型的回答也会被校验:调用了不存在的函数、参数不符合 schema、违反 tool_choice,或要求 JSON 却返回了非法 JSON,都返回 502,而不是把坏结果当成功交给你。
返回内容
- 成功时
finish_reason是stop或tool_calls。不会返回length。 - 工具调用 ID 是稳定生成的,同一个幂等请求重放时 ID 不变。
- 标准的
usage字段固定是null。真实用量放在botmux.native_invocation_usage里,它是整次 CLI 调用的汇总(可能包含内部重试),不能当成单次 API 账单。有些工具会把null显示成 0,这不代表没有消耗。
幂等与超时
- 带
Idempotency-Key请求头(字母数字和_.:-,最长 128)时,同一客户端、同一别名下相同的 key + 相同的请求会直接重放结果,不会再调一次 CLI;key 相同但请求不同返回 409。 - 不带 key 的两次请求是两次独立调用。
- 客户端断开连接会取消调用;同一个代理进程里,多个等待同一结果的连接只断一个不会影响其它的。
- 建议每个推理机器人只跑一个代理进程。
常见错误
错误格式统一为 {"error":{"message":"<错误码>","type":"...","param":...,"code":"<错误码>"}},不会带出 CLI 的报错输出、凭据或本机路径。
常见问题
启动时报 model_proxy_config_missing 或权限相关错误?
检查 --config 路径是否存在、文件是否 chmod 600、所在目录有没有被其他用户可写。
启动时报 model_proxy_token_requires_32_nonwhitespace_characters?
tokenEnv 指向的环境变量没设置,或者 token 太短、有空格。
报 inference_profile_offline?
代理没找到专用实例。确认实例正在运行,并且启动代理时 SESSION_DATA_DIR 指向实例的数据目录(第 1 步的 $STATE_DIR/data)。
和直接调模型 API 效果完全一样吗? 不完全一样。对话角色和工具定义会先被整理成文本交给 CLI,再从结构化输出里还原成响应。角色顺序和工具关联会保留,但这不是原生的逐字透传,CLI 内部的提示词和重试也会影响结果。
输出上限能当费用上限用吗?
不能。max_completion_tokens 只作用于 Claude 的生成请求,CLI 内部的封装和重试也会消耗 token。
相关文档
- Core-only(apiOnly)API 控制:专用实例怎么启动、数据目录和安全边界
- CLI 命令速查
- 支持的 CLI
