《Agent 拿着你的真 API Key 满世界跑:Mozilla 开源 otari,把预算拦在花钱之前》

一、你的 API Key,现在大概在四五个地方裸奔
把一把真 key 交给编码 Agent 的那一下,你同时失去了三样东西:上限、可见性、吊销权。三样一起没有,风险不是加法,是乘法。
说实话,大多数人的操作是:控制台复制,粘进 shell 配置,完事。然后它开始流浪:笔记本、CI 的环境变量、某次手滑提交的 diff、你贴给同事的报错日志。
想吊销?一吊销三个跑着的东西同时炸,因为它们共用一把 key。接了两家厂商,这笔账只能各算各的。
而且它没有上限。它不会在花到 200 美元时停下来问你一句,只会一直跑到银行发短信。
二、otari 是什么:Mozilla 把自家托管平台的引擎拆出来给你自己跑
网关不是新东西。阿里开源的 AI 原生网关 Higress 就是典型方案。otari 的区别不在”网关”两个字,在把虚拟 key 和预算做进了核心路径。
官方定位就一句:One endpoint for 40+ providers, with virtual keys, budgets, and usage tracking. 说白了,自己跑一个 OpenAI 兼容端点,后面挂 40 多家厂商,顺手把 key、预算、用量一起管了。
谁做的?Mozilla.ai,Mozilla.org 的全资子公司。仓库 Python 写,Apache-2.0,底层调厂商走 Mozilla 自家的 any-llm。官方上线博客发在 2026-05-29,open core 结构:开源核心加 otari.ai 托管平台,同一引擎同一套 API。
三种运行模式,以及一个最容易搞混的说法
Standalone 是单进程,本地存储,既管又跑推理;Hosted 是多租户控制面,推理在别处;Hybrid 从 otari.ai 取凭证并回报用量。OTARI_MODE 没设时,OTARI_AI_TOKEN 在就走 hybrid,否则默认 standalone。
还有个反直觉的地方:官方特意强调模式说的是”这个进程提供什么”,不是”谁在跑”,所以 hosted 并不是 self-hosted 的反面。

三、真正值钱的三件事:副卡、预算前置、账单能拆到一次运行
otari 的价值缩得成三件事:前两件省事故和钱,第三件省你自己。
第一件:虚拟 API key,本质是副卡
厂商凭证留在网关后面,客户端拿到的是 otari 发的虚拟 key。按官方描述,这些 key 哈希存储、有名字、能设过期、绑定到某个用户,作用域按 user、workspace、model 切,随时可吊销。客户端看不到上游凭证。
给 Agent 一把 key,就从”把家门钥匙交给它”变成”给它一张副卡”。能吊销的副卡和一份写着”省着点花”的文档,你要哪个?
第二件:预算是在花钱之前查的
这是我最看重的一条。README 写得很明白:认证、解析凭证、派发之前跑预算检查,事后记录用量。超预算的请求压根没机会出发。
第三件:账单能拆到一次运行
Claude Code 改不了请求体里的 metadata,但能加请求头。所以你可以挂一个 Otari-Tags: agent=bug-fix,run=1234,之后 GET /api/v1/usage?tag=run:1234 就能把这一轮的花费筛出来。想让一次无头运行单独算成一个 session,再加个 Otari-Conversation-Id。提醒一句:按文档,标签只在 standalone 模式下记录。
省钱不止一条路。工具太多本身就是烧 token 的大头,把工具藏起来省 token 的那条路 讲的是另一头的优化,两头一起收才干净。
四、五分钟起一个网关:从 docker run 到第一笔请求
README 里给的最短路径就是一条 docker run,逐字照抄:
docker run --rm -p 8000:8000 -e OTARI_MASTER_KEY=SET_A_MASTER_KEY -e OPENAI_API_KEY=YOUR_OPENAI_KEY \
-e OTARI_CONFIG_YAML='default_pricing: true
providers:
openai:
api_key: ${OPENAI_API_KEY}' \
mzdotai/otari:latest otari serve空库首次启动会自己建一把 key,而且只打印一次,错过就得重来:
No API keys found. Created bootstrap key for first run. Save this key now: tk-...
拿到 tk- 打头的 key 就能发请求,注意 model 的写法是”厂商:模型名”:
curl http://localhost:8000/api/v1/chat/completions \
-H "Authorization: Bearer tk-..." \
-H "Content-Type: application/json" \
-d '{"model":"openai:gpt-4o-mini","messages":[{"role":"user","content":"Say hello."}]}'三件事得先知道。临时容器用 SQLite,一停就没了,要持久化走 docker compose up -d。可选能力靠 profile 开:code-exec、web-search、guardrails。面板在 http://localhost:8000/,想通过面板存厂商 key,先用 otari gen-secret-key 生成 Fernet key 设进 OTARI_SECRET_KEY。
五、把 Claude Code 接进来:三个环境变量和一个反直觉的斜杠
官方文档专门有一页讲这个,三个坑没一个能猜出来。
坑一:base URL 要以 /api 结尾
Claude Code 自己会往上拼 /v1/messages。所以 ANTHROPIC_BASE_URL 得是 http://localhost:8000/api,不是 /api/v1,写错就多一层路径。
坑二:telemetry 里的 user_id 不是 otari 的用户 ID
Claude Code 会在 telemetry 里塞一个 metadata.user_id,跟 otari 的用户体系没关系。standalone 建 key 必须把 reject_user_mismatch 设成 false:
curl "$OTARI_URL/api/v1/keys" -H "Authorization: Bearer $OTARI_MASTER_KEY" -H "Content-Type: application/json" \
-d '{"key_name":"claude-code","user_id":"alice","reject_user_mismatch":false}'坑三:用 AUTH_TOKEN,不要用 API_KEY
export ANTHROPIC_BASE_URL="http://localhost:8000/api" export ANTHROPIC_AUTH_TOKEN="tk-your-otari-key" export ANTHROPIC_MODEL="anthropic:claude-sonnet-4-6" claude
第三个变量要用 ANTHROPIC_AUTH_TOKEN,走 Authorization bearer;别用 ANTHROPIC_API_KEY,那个走 x-api-key 头,standalone 能收,hybrid 的认证不认。按标签记账再补一行 ANTHROPIC_CUSTOM_HEADERS 挂 Otari-Tags。坑过了,剩下的就是敲 claude。
六、超预算那一刻到底会发生什么
结论先说:返回 403,带上 Otari-Error-Code: budget_exceeded。但你在终端里看到的往往不是这一句。
Claude Code 会把每一个 403 都加前缀
坑就在这儿。Claude Code 给所有 403 都加上 “Failed to authenticate” 前缀,超预算在屏幕上跟 key 挂了一模一样。你开始查 key、查环境变量,其实前缀后面那半句才是真原因。看到它先往下读一行;它不会自动重试,Retry-After 会说窗口何时重置。
更隐蔽的一条:最坏情况预占
这机制最容易被当成 bug。otari 在请求真正发出去之前,先按这次请求的”最坏情况”金额把预算占住。所以账面还剩一点余额、但这个调用预估就可能破上限时,照样给你 403。不是算错账,是有意为之。

七、不要网关也能用:otari hook 把写着的规矩变成会拦人的检查
这一章我认为最被低估:前面那些都要跑网关、要凭证、要账号,这一章一样都不需要。
Agent Guardrails 是仓库级的规矩,写清楚编码 Agent 可以对工作区做什么,在它干活的过程中检查。文档原话:被禁的工具调用在执行前就被拒绝;一轮以被禁状态收尾的对话,在交还前就被拦下。
它跟 Inference Guardrails 是两回事:后者查推理的输入输出,前者查 Agent 的动作和 diff。规则放在 .otari/,跟着代码提交,由 otari hook 执行,规矩从”靠自觉”变成”提交即生效”。
对照一下思路差别。装 Skill 之前先扫一遍 走的是入口筛查,Agent Guardrails 走的是过程拦截。只有后者拦得住运行时的手滑。
五个命令,先记住三个就够
otari hook setup:把回调写进 Agent 的配置文件。otari guardrails validate:离线校验并 dry-run。otari guardrails check:对一次改动比如 PR 跑一遍,不需要会话。otari guardrails generate:从 AGENTS.md 或 CLAUDE.md 反向生成 gate。otari hook:回调本体,harness 调用,不用手工跑。
setup 必须在 git 仓库里跑,仓库外会被拒;会往 .claude/settings.local.json 写 PreToolUse 和 Stop 两条,因为 Claude Code 在 payload 里自带 hook_event_name,一个回调服务两个事件。改完重启 Claude Code;重复跑是原地更新,不重复添加。
仓库里还没有 guardrail 时,setup 会先提议写一份起步用的 .otari/guardrails.yml:一条 command gate 拒绝 git push --force,一条 path gate 不让 .env 进树,默认都是 advisory。
代价要诚实讲
judge gate 就是一次模型调用。按文档:每个 Stop 事件最多跑 5 个 judge gate,按 priority 从高到低;CI 最多 20 个,本地跑满要设 OTARI_HOOK_MAX_JUDGES=20。多一个就多一次调用,这是实打实的钱。
八、上线之前先看这四条边界
版本号还在 0.30.x。2026-10-20 一天连发 v0.30.0 和 v0.30.1,前一天是 v0.29.0。很活跃,也说明没到 1.0。
缺少独立验证。第三方快讯(notatechguy,2026-10-19,非官方口径)指出:当时没有公开披露的独立安全审计,也没有第三方部署评测;同一份快讯提到它当天登上 GitHub 日榜,涨 39 星到 674 星。热度是真的,独立验证暂时缺位。
仓库状态摆在这儿。查询时星数约 711、fork 93、open issue 315。315 不小。
judge gate 真的在花钱。配几个 judge,每轮对话收尾就有几次模型调用。另外 mozilla-ai 不等于 Mozilla Foundation,责任主体要看清楚。
选型判断很简单:一个人一台机器一把 key,它可能太重了;要给团队发 key、给 CI 报账、给 Agent 定上限,现在就该试。把 key 换成副卡,把对账搬到花钱之前,把规矩从文档挪进仓库。三件事做完,Agent 还是那个 Agent,但它终于有了上限。
回到开头那把在四五个地方流浪的真 key。它该退休了。
关注圈圈,持续带你玩转 AI 工具。
暂无评论内容