算 Token 账的多 Agent 团队,TaoToken 的 Key 统一发放

发布时间:2026/9/18 15:58:59
算 Token 账的多 Agent 团队,TaoToken 的 Key 统一发放 1. 一张多 Agent 账单为什么总也对不上CrewAI 流水线跑完一轮调研账单却对不上——三个 Agent、四千多次模型调用Key 散在四个成员手里没人说得清哪一步烧掉大头。后来我们把 Key 收回到 TaoTokenhttps://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentcrewai-key-issue统一发放Base URL 固定成https://taotoken.net/api账才第一次闭合。这是我作为团队成本负责人踩过的最实在的一个坑。项目本身没出问题研究员 Agent 去抓资料撰稿 Agent 写初稿校对 Agent 核事实最终报告质量也确实比单模型硬写强得多。问题出在“钱去哪了”。单轮对话的成本模型很简单一次请求一次计费看账单就知道是哪句话贵。多 Agent 完全是另一回事。一个 Task 交到 Agent 手上它不是“思考一次就交付”而是走一轮 ReAct 循环想一步、调一个工具、拿回结果、再想一步、再调一次直到它自己觉得可以收工。每一次“再想一步”都是一次独立的模型调用都单独计一次输入和输出。更麻烦的是三处隐性放大第一处是工具往返的再推理。研究员 Agent 搜了 8 个网页每拿回一页内容都要把新内容塞回上下文重新推理一遍。八次搜索可能就是十几次调用而不是一次。第二处是任务交接时的上下文重放。CrewAI 的顺序流程会把上游产出塞给下游。撰稿 Agent 读到的不是一句话而是调研 Agent 的全套过程材料。这份材料在每一轮调用里都要重发一次输入 token 是按“轮次”翻倍的不是按“任务”计的。第三处是失败重试。模型偶尔返回不合规的 JSON框架会重试工具超时Agent 会换个思路再试。这些重试在日志里只是一行 warning在账单里却是实打实的调用次数。所以一个多 Agent 团队的真实成本等于“Agent 数 × 每任务循环轮次 × 每次输入的上下文长度”。这个乘法里任何一个因子失控月底账单就会给你一个惊喜。要算清这笔账第一步不是换便宜模型而是让每一次调用都能被归因到某个 Agent、某个任务、某次运行。而要做到这一点前提是所有调用走同一把 Key、同一个入口。2. 把散落的 Key 收成一处统一发放流程我们最早的做法是“谁用谁申请”。研究员自己注册一个账号撰稿同学用另一个本地调试再随手建一个。跑起来没问题但一旦要核算成本就变成了考古现场同一个 Agent 在不同机器上用的是不同的 Key账单无法合并成员离职后 Key 还挂在 CI 里没人知道该不该删生产环境和本地调试混用一把 Key一次压测把整个月的量打满想按项目拆账发现平台侧根本没有项目维度只有一把把孤立的 Key。统一发放之后流程收敛成了五步做起来不复杂但每一步都要落到人第一步按“环境 用途”建 Key而不是按人建。我们最终的命名规范是crewai-dev、crewai-staging、crewai-prod、crewai-ci。人换人不重要环境边界必须清晰。控制台入口在 https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentcrewai-console 创建时把备注写清楚是给哪个 Crew、哪个流程用的。第二步申请走单发放留痕。我们用一个最简单的表格登记申请人、用途、环境、Key 名称、创建日期、预计月用量、回收日期。听起来很土但它解决了一个关键问题——半年后有人问“这把 Key 是谁的”能查得到。第三步Key 只进环境变量不进代码仓库。这一点没有例外。任何把 Key 写进agents.yaml、写进调试脚本、写进 notebook 的做法最终都会出现在某次git log里。.env必须进.gitignore。第四步注入方式统一。本地开发用.envCI 用 Secret 变量生产用容器的环境变量注入。三种场景用的是同一套变量名这样代码一行都不用改。第五步轮换和吊销。我们固定每季度轮换一次成员变更时立刻吊销。轮换的做法是先在控制台建新 Key改环境变量、灰度验证再删旧 Key。中间有个重叠窗口但不会有中断。这套流程真正带来的价值不是“安全”而是可归因。当所有 CrewAI 调用都从同一把crewai-prod出来账单上的数字才和某条流水线对应得上。官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentcrewai-key-flow 上有控制台和文档的入口先看一遍再建 Key 会省不少返工。3. CrewAI 接 TaoTokenBase URL 与环境变量怎么写统一 Key 之后下一个问题是怎么让 CrewAI 真的走这个入口。CrewAI 底层通过 LiteLLM 调模型所以配置方式有两种环境变量兜底或者显式实例化 LLM 对象。生产环境我建议后者因为显式配置更容易被代码审查看见。最简版本是.env# .env —— 不要提交到仓库 OPENAI_API_KEYYOUR_API_KEY OPENAI_API_BASEhttps://taotoken.net/api # 部分 LiteLLM 版本读的是 OPENAI_BASE_URL两个都写上更稳 OPENAI_BASE_URLhttps://taotoken.net/api # 可选给每个流程留一个可读的标识便于对账 CREW_ENVstaging然后在代码里显式指定 LLMimport os from crewai import Agent, Crew, LLM, Process, Task llm LLM( modelopenai/gpt-4o-mini, base_urlhttps://taotoken.net/api, api_keyos.environ[OPENAI_API_KEY], temperature0.2, # 把环境标识带进元数据方便后面按环境聚合 metadata{env: os.environ.get(CREW_ENV, dev)}, ) researcher Agent( role资深行业研究员, goal收集可溯源的行业数据每条结论必须带来源链接, backstory十年产业研究经验习惯先存证据再下判断拒绝没有出处的数字。, llmllm, verboseTrue, ) writer Agent( role科技专栏作者, goal基于研究员的素材输出结构化长文, backstory擅长把零散材料组织成有逻辑链条的长文不添加素材之外的结论。, llmllm, verboseTrue, )这里有几个容易踩的点值得单拎出来模型名要和入口匹配。LiteLLM 通过前缀判断走哪家协议openai/前缀配合自定义base_url是通用做法。如果模型名和入口协议对不上最常见的表现是 404 或者 “model not found”而不是鉴权失败。排查时先确认base_url有没有多余斜杠再确认模型名。Base URL 不要带路径尾巴。我们统一填https://taotoken.net/api不要自己在后面拼/v1或/chat/completions客户端 SDK 会自己补。多写一段路径是最常见的 404 来源。不要把 Anthropic 的环境变量套给 CrewAI。有人看到ANTHROPIC_BASE_URL就顺手复制过去结果 CrewAI 读的是 OpenAI 兼容变量配置根本没生效调用还在走默认地址。变量名要和客户端匹配这是排障的第一原则。生产环境别用.env。容器里直接用环境变量注入本地开发才用.env。这样两者行为一致不会出现“本地好的、线上报错”的情况。配好之后第一次跑建议先跑一个最小 Crew一个 Agent、一个 TaskverboseTrue看终端有没有正常打出模型响应。确认通了再往上加 Agent。先证明链路通再谈成本这个顺序不能反。4. 多 Agent 调用次数统计表用回调把每笔调用记下来账单解决了“入口统一”但还没解决“归因到 Agent”。要做到这一点得在调用层加一层记账。CrewAI 底层是 LiteLLM而 LiteLLM 提供了回调钩子可以在每次调用成功或失败时插入自定义逻辑。这就是我们那张调用次数统计表的数据源。先写记账器# token_ledger.py import json import time from pathlib import Path import litellm from litellm.integrations.custom_logger import CustomLogger LEDGER_PATH Path(token_ledger.jsonl) class TokenLedger(CustomLogger): 把每次 LLM 调用写一行 JSONL用于后续聚合。 def _write(self, kind: str, kwargs: dict, response_obj, error: str | None None): usage getattr(response_obj, usage, None) params kwargs.get(litellm_params, {}) or {} metadata params.get(metadata, {}) or {} row { ts: round(time.time(), 3), kind: kind, model: kwargs.get(model) or params.get(model, unknown), env: metadata.get(env, unknown), agent: metadata.get(agent, unknown), prompt_tokens: getattr(usage, prompt_tokens, 0) if usage else 0, completion_tokens: getattr(usage, completion_tokens, 0) if usage else 0, error: error, } with LEDGER_PATH.open(a, encodingutf-8) as f: f.write(json.dumps(row, ensure_asciiFalse) \n) def log_success_event(self, kwargs, response_obj, start_time, end_time): self._write(success, kwargs, response_obj) def log_failure_event(self, kwargs, response_obj, start_time, end_time): self._write(failure, kwargs, response_obj, errorcall_failed) ledger TokenLedger() litellm.callbacks [ledger]不同 LiteLLM 版本里metadata的挂载位置略有差异有的在kwargs[metadata]有的在kwargs[litellm_params][metadata]。上面两种都取了取不到就退化成unknown不会中断主流程。记账代码的第一要求是“永远不能把主业务搞挂”所以这里所有的取值都用get加默认值。接着让每个 Agent 带上身份标签。做法是为每个 Agent 单独实例化 LLM在metadata里写死agentdef build_llm(agent_name: str) - LLM: return LLM( modelopenai/gpt-4o-mini, base_urlhttps://taotoken.net/api, api_keyos.environ[OPENAI_API_KEY], temperature0.2, metadata{agent: agent_name, env: os.environ.get(CREW_ENV, dev)}, ) researcher Agent(role资深行业研究员, goal..., backstory..., llmbuild_llm(researcher)) writer Agent(role科技专栏作者, goal..., backstory..., llmbuild_llm(writer)) reviewer Agent(role内容审校专家, goal..., backstory..., llmbuild_llm(reviewer))然后写一个聚合脚本把 JSONL 汇总成表# report_tokens.py import json from collections import defaultdict from pathlib import Path rows [] for line in Path(token_ledger.jsonl).read_text(encodingutf-8).splitlines(): line line.strip() if line: rows.append(json.loads(line)) agg defaultdict(lambda: {calls: 0, prompt: 0, completion: 0, failed: 0}) for r in rows: key r.get(agent, unknown) agg[key][calls] 1 agg[key][prompt] r.get(prompt_tokens, 0) agg[key][completion] r.get(completion_tokens, 0) if r.get(kind) failure: agg[key][failed] 1 header f{agent:16}{calls:8}{failed:8}{prompt:12}{completion:12} print(header) print(- * len(header)) for name, v in sorted(agg.items(), keylambda kv: -kv[1][calls]): print(f{name:16}{v[calls]:8}{v[failed]:8}{v[prompt]:12}{v[completion]:12})跑一轮典型的三段式调研任务后我们的表大概是这个样子数字是示例用来演示结构agent calls failed prompt completion researcher 38 2 91240 6800 writer 11 0 48300 9200 reviewer 7 0 21400 3100 unknown 3 0 5400 700这张表一出来成本讨论就从“感觉贵”变成了“研究员 Agent 占了多少”。我们的实际情况是调研 Agent 的调用次数是撰稿的 3 倍多输入 token 接近 2 倍但它创造的素材量也确实是后面两个环节的基础。这意味着优化重点不该是砍掉研究员而是减少它的无效搜索往返——比如限制单任务最大工具调用次数或者在提示词里要求它“先规划再搜索”。unknown那几行也不能忽略它们通常来自框架内部的系统调用、标题生成或结构化输出重试。如果unknown占比超过 10%就说明有部分调用没有被归因需要检查是否有代码绕过了统一配置。5. 团队其他工具怎么接Claude Code / Codex / CC Switch 配置差异CrewAI 是流水线里的“批量生产”环节但团队里还有两类会用 Key 的地方写代码时的 Claude Code以及命令行里的 Codex。作为成本负责人我不希望这三处各拿一把 Key所以统一走同一个入口——但配置方式完全不同绝对不能互相复制。Claude Code 走的是 Anthropic 协议配置写在settings.json的env段里{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_AUTH_TOKEN: YOUR_API_KEY } }具体字段和路径以 https://taotoken.net/doc/ClaudeCodeAnthropic?utm_sourcetaotoken_aicg_blog_endutm_contentcrewai-claude-doc 上的文档为准。配完可以用/status之类的内置命令确认当前生效的地址避免改了文件但没被读取。Codex 走 OpenAI 兼容协议配置写在config.tomlmodel_provider taotoken model gpt-5 [model_providers.taotoken] name TaoToken base_url https://taotoken.net/api env_key TAOTOKEN_API_KEY wire_api chat然后在环境变量里放TAOTOKEN_API_KEYYOUR_API_KEY。注意这里读的是TAOTOKEN_API_KEY不是OPENAI_API_KEY也不是任何ANTHROPIC_*。把 Anthropic 的变量名套到 Codex 上最常见的结果是“配置看起来对、实际报鉴权错”排查时很容易被误导。CC Switch 这类多配置切换工具本质上是把三件事抽出来做切换Base URL、API Key、模型 ID。用它的好处是开发机上可以同时保留“演示环境”和“生产环境”两套配置切换时不用手改文件。但要注意切过去之后一定要实跑一次最小请求验证因为切换工具只负责写文件不负责验证入口是否可达。三处配置的核心差异总结成一句话同一条链路两套协议。CrewAI 和 Codex 用 OpenAI 兼容变量Claude Code 用 Anthropic 变量。统一 Key 是目标但变量名必须跟着客户端走。6. 从统计表里能看出什么四个真实的降本动作有了调用次数统计表和统一 Key优化就不再靠猜。我们实际做过并且有效的动作有四个。第一给研究类 Agent 设工具调用上限。在 Task 描述里明确“最多检索 6 个来源超出部分自行取舍”并配合提示词要求“先列检索计划再执行”。这一条把研究员 Agent 的平均调用轮次压下来一大截而最终报告质量几乎没变化——因为它原本的很多搜索是重复的。第二任务交接只传结构化的中间产物不传原始全文。我们让研究员 Agent 用固定 schema 输出结论、证据摘要、来源链接三段式。撰稿 Agent 拿到的是这份精简材料而不是几十页原始文本。输入 token 明显下降同时审校环节反而更容易核对因为证据摘要本身就是可复核的。第三把失败重试单独计数。统计表里我们专门留了failed列。有一次发现某个 Agent 的失败率高得异常排查后是工具返回格式不稳定导致模型反复重试。修了工具侧的输出格式之后失败调用基本归零。重试是纯浪费它不产生任何交付价值。第四低价值环节换轻量模型。标题生成、格式校验、字段抽取这类任务不需要最强的模型。我们把这些环节单独拆成轻量模型调用把强模型留给真正的推理和写作。成本结构一下子清楚了贵的地方贵得有理由便宜的地方彻底便宜下来。反过来有几个坑值得提前警告简单问答不要动用多 Agent。一个 Crew 启动的固定开销就不小如果任务本身只需要一次问答多 Agent 只会让账单变长、结果变慢。别只看模型单价。便宜模型如果每任务多跑五轮循环总成本可能比贵模型还高。要看的是“每任务总 token”不是“每千 token 单价”。流水线越长错误传导越明显。上游错了下游会在错误基础上继续生产最后交出一份看起来很完整、但事实全错的报告。关键节点之间要加校验不合格就打回重跑不能让错误静默流过。强审计场景慎用纯自主模式。如果每一步输入输出都要留痕就优先用受控编排把分支逻辑写死、把人工审批节点接上而不是完全交给 Agent 临场判断。7. 把账算清才谈得上规模化多 Agent 真正难的从来不是“搭起来”而是“搭起来之后还敢继续跑”。一个跑一轮就停的 Demo怎么配都行一旦要每天跑几十条流水线、要按项目分摊成本、要向上面解释预算就必须回答三个问题这笔钱花在了哪个 Agent 上、哪一步是浪费、下一轮怎么改。统一 Key 发放解决的是“入口唯一”Base URL 固定解决的是“链路唯一”回调记账解决的是“归因唯一”。三件事做完多 Agent 就从一门玄学变成了一笔可以管理的开销。如果你现在也在跑 CrewAI 或者类似的多 Agent 流程建议按这个顺序推进先去官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentcrewai-start 建一把专用 Key把 Base URL 换成https://taotoken.net/api再补上那二十几行记账回调。跑完一轮你就会第一次看到自己团队的调用次数表。需要对照模型能力选型或者估算预算可以先去模型对话页跑几个真实样本https://taotoken.net/models/detail/chat?utm_sourcetaotoken_aicg_blog_endutm_contentcrewai-chat如果团队是长期、高频使用按用量打包的 Coding Plan 通常比零散调用更好控制预算https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcrewai-plan准备好正式发放 Key从控制台建第一把带环境标识的 Keyhttps://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentcrewai-keys团队里同时用 Claude Code 的同学配置方式看这份文档别和 CrewAI 的变量混用https://taotoken.net/doc/ClaudeCodeAnthropic?utm_sourcetaotoken_aicg_blog_endutm_contentcrewai-doc