Agent 账单失控?10 个方法让 Token 消耗降下来
Agent 账单失控?10 个方法让 Token 消耗降下来
上次看到 API 账单的时候,有没有怀疑过自己的 Agent 是不是在偷偷读小说?
一个客服 Agent 上线第一个月花了 200 美元,第二个月 800 美元,第三个月 3500 美元——用户量只涨了 2 倍,账单涨了 17 倍。这不是个例,几乎每个把 Agent 推到生产环境的公司都会经历这个阶段。
问题出在哪?Agent 不像传统的 API 调用,它是一个不断"思考"的循环。每一次工具调用、每一段上下文、每一个多余的 system prompt,都在烧 Token。更关键的是,大多数团队在开发阶段根本不关注 Token 消耗——功能跑通了就行,等账单炸了才开始慌。
这篇文章系统梳理了 10 个 Token 优化方法,从最简单的 Prompt 层面到架构层面,覆盖从"立竿见影"到"长期治理"的完整光谱。每个方法都附带具体的量化效果和落地建议。
本文提纲
- Prompt Caching:效果最大的一个开关
- 上下文窗口管理:别把聊天记录全塞进去
- Prompt 精简:砍掉你 System Prompt 里的废话
- 模型分级路由:简单任务别用大模型
- 结构化输出约束:让模型别"自由发挥"
- RAG 替代长上下文:检索比全量塞入便宜 100 倍
- 工具调用优化:减少 Agent 循环次数
- Batch API:异步任务直接打五折
- Token 监控与预算控制:看不见就管不了
- 缓存层设计:语义级别的去重
1. Prompt Caching:效果最大的一个开关
如果只做一个优化,做这个。
Anthropic 的 Prompt Caching(Claude API)和 OpenAI 的 Cached Input Tokens 都允许你对重复使用的 Prompt 前缀做缓存。缓存命中后,那部分 Token 的费用直接降到原来的 10%(Anthropic)或 50%(OpenAI)。
Agent 场景下,System Prompt、工具定义、few-shot 示例这些内容在每次调用中都完全一样。一个典型的 Agent,System Prompt + 工具定义可能占 3000-8000 Token,而用户实际输入可能只有 200 Token。不缓存的话,每次调用你都在为那 8000 Token 全价买单。
Anthropic Claude Prompt Caching:
# Anthropic Prompt Caching
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
system=[
{
"type": "text",
"text": "<长篇 system prompt 和工具说明...>",
"cache_control": {"type": "ephemeral"} # 标记缓存
}
],
messages=[{"role": "user", "content": "用户问题"}]
)OpenAI 自动缓存:
OpenAI 的缓存是自动的——不需要改代码。只要你的 Prompt 前缀(至少 1024 Token)和之前请求一致,就会命中缓存。缓存保持 5-10 分钟的空闲窗口。
实际效果:一个日均 10 万次调用的客服 Agent,开启 Prompt Caching 后月费从 12000 美元降到 4500 美元。其中 System Prompt(约 5000 Token)从全价变成 1 折,占了节省的大头。
注意:缓存有 5 分钟 TTL(Anthropic)或 5-10 分钟(OpenAI)。如果你的 Agent 调用频率低于这个窗口,缓存命中率会很低。高并发场景效果最好。
2. 上下文窗口管理:别把聊天记录全塞进去
Agent 跑久了,上下文会像滚雪球一样膨胀。第 1 轮对话 500 Token,第 10 轮就变成了 5000 Token,第 50 轮可能突破 20000 Token。如果你的 Agent 还会把工具返回结果全部留在上下文里,膨胀速度更快。
大部分 Agent 框架(LangChain、LlamaIndex、Hermes Agent)的默认行为是把完整的对话历史发给模型。这在开发阶段没问题,但到生产环境就是 Token 黑洞。
解决方案有三个层次:
第一层:滑动窗口截断。只保留最近 N 轮对话,超出部分直接丢弃。最简单但最粗暴:
# 只保留最近 6 轮对话
recent_messages = conversation_history[-12:] # 6 轮 = 12 条消息第二层:摘要压缩。把旧对话用一次便宜的模型调用压缩成摘要,替代原始内容。这比直接丢弃好,因为关键信息被保留了:
# 当对话超过 10 轮时,压缩前 8 轮
if len(conversation_history) > 20:
old_messages = conversation_history[:16]
summary = cheap_model.summarize(old_messages)
conversation_history = [
{"role": "system", "content": f"之前的对话摘要:{summary}"},
*conversation_history[16:]
]第三层:工具结果清理。工具返回的数据往往非常大(一个数据库查询可能返回几千 Token 的 JSON),但在后续对话中几乎不会被引用。把已经处理过的工具结果替换成简短摘要:
# 工具调用完成后,将原始返回替换为摘要
if tool_result_token_count > 500:
history[-1] = {
"role": "tool",
"content": f"[已处理] 查询返回 {len(result)} 条记录,"
f"关键字段:{extracted_fields}"
}实际效果:一个代码编辑 Agent,通过"摘要 + 工具结果清理",将平均上下文从 18000 Token 压缩到 6000 Token,单次调用成本降低约 65%。
3. Prompt 精简:砍掉你 System Prompt 里的废话
去审计一下你的 System Prompt,大概率能砍掉 30-50%。
常见的浪费模式:
重复说明:同一个规则用了三种不同的方式表达。"你必须用中文回答" / "回答语言为中文" / "不要使用英语"——模型不是人,说一次就够了。
过度举例:few-shot 示例是好东西,但 10 个示例和 3 个示例的效果差异通常不大。每个示例都在烧 Token。把 10 个砍到 3 个,省下的 Token 可能让你的 System Prompt 从 8000 降到 3000。
冗余的工具描述:如果你的 Agent 有 20 个工具,每个工具的 description 写了 200 字,光工具定义就 4000 Token 了。精简工具描述,把详细说明放到参数的 description 里(只在工具被选中时才需要详细上下文)。
# ❌ 冗长的工具描述
{
"name": "search_database",
"description": "这个工具用于在公司的客户数据库中搜索客户信息。"
"你可以通过客户姓名、邮箱、电话号码来搜索。"
"支持模糊匹配和精确匹配。返回结果包含客户的基本信息、"
"订单历史和联系记录。如果搜索不到结果会返回空列表。"
"请尽可能提供精确的搜索条件以获得最佳结果..."
}
# ✅ 精简的工具描述
{
"name": "search_database",
"description": "Search customer by name, email, or phone."
}Markdown 格式税:System Prompt 里大量的 Markdown 标题、列表、加粗,这些格式字符也是 Token。一个 2000 字的 Markdown 格式 Prompt,纯格式开销可能有 300-400 Token。
实际效果:我们见过最极端的案例——一个团队把 System Prompt 从 12000 Token 精简到 4500 Token(去掉重复规则、减少示例、精简工具描述),Agent 行为几乎没有变化,但每次调用成本直接降了 60%。
4. 模型分级路由:简单任务别用大模型
不是所有任务都需要 Claude Sonnet 或 GPT-4o。
一个 Agent 的工作流里,大约 60-70% 的子任务是简单的:分类、提取关键词、判断是否需要调用工具、格式化输出。这些任务用 Haiku、GPT-4o-mini 甚至更小的开源模型就能做好,成本只有大模型的 1/10 到 1/30。
路由策略:
def route_model(task_type: str, complexity: str) -> str:
"""根据任务类型和复杂度选择模型"""
routing = {
# 简单任务 → 小模型
("classification", "low"): "claude-haiku",
("extraction", "low"): "claude-haiku",
("formatting", "any"): "claude-haiku",
("summarization", "low"): "claude-haiku",
# 中等任务 → 中模型
("rag_answer", "medium"): "claude-sonnet",
("code_review", "medium"): "claude-sonnet",
# 复杂任务 → 大模型
("reasoning", "high"): "claude-opus",
("code_generation", "high"): "claude-opus",
}
return routing.get((task_type, complexity), "claude-sonnet")更聪明的做法:先用一个极小的模型(Haiku)做意图分类,判断当前请求的复杂度,再路由到对应级别的模型。分类本身只花几十个 Token,但可能帮你省下几千 Token 的调用费用。
实际效果:一个有 3 级路由的客服 Agent,70% 的请求用 Haiku 处理($0.25/M input),25% 用 Sonnet($3/M input),5% 用 Opus($15/M input)。相比全部用 Sonnet,整体成本降低约 75%。
注意:路由不是免费的。如果你的 Agent 一次对话需要多轮调用,每次都走一遍分类路由会增加延迟。对于简单场景,可以用规则路由(关键词匹配)替代模型路由。
5. 结构化输出约束:让模型别"自由发挥"
模型每多输出一个 Token,你就要多付一个 Token 的钱。而很多 Agent 的输出中有大量"废话"——开场白、解释性文字、过渡句——这些内容对下游处理毫无价值。
用结构化输出(JSON Mode、Function Calling、Outlines/Guidance)强制模型只输出你需要的内容,可以从源头减少输出 Token。
对比示例:
# ❌ 无约束输出:模型可能写一大段
response = model.generate("分析这段代码的安全问题,给出修复建议")
# 输出:"好的,我来帮你分析这段代码。首先,我注意到..."
# + 200 Token 的分析 + 150 Token 的建议 + 50 Token 的客套话
# ✅ 结构化输出:只返回你需要的字段
response = model.generate(
"分析这段代码的安全问题",
response_format={
"type": "json_schema",
"json_schema": {
"name": "security_analysis",
"schema": {
"type": "object",
"properties": {
"issues": {
"type": "array",
"items": {
"type": "object",
"properties": {
"severity": {"type": "string", "enum": ["high", "medium", "low"]},
"description": {"type": "string"},
"fix": {"type": "string"}
}
}
}
}
}
}
}
)
# 输出:{"issues": [{"severity": "high", "description": "...", "fix": "..."}]}更进一步的方案:用 Outlines 或 Guidance 在 Token 级别约束输出。这不仅能减少输出长度,还能保证输出格式 100% 合规,省掉后续解析和重试的成本。
实际效果:一个数据分析 Agent,从自由文本输出改为 JSON 结构化输出后,平均输出 Token 从 850 降到 320,降幅 62%。同时,下游解析的失败率从 15% 降到 0。
6. RAG 替代长上下文:检索比全量塞入便宜 100 倍
很多团队在构建 Agent 时,会把所有相关文档全部塞进上下文。"我们有 200K 的上下文窗口,怕什么?"——怕的是你的账单。
假设你有 50 页的产品文档(约 25000 Token),每次 Agent 调用都全量塞入。一天 10000 次调用,就是 2.5 亿 Token 的输入——仅这一项就是每月数千美元。
用 RAG(检索增强生成)替代全量塞入:
# ❌ 全量塞入:每次调用都带上所有文档
context = load_all_documents() # 25000 Token
response = model.generate(system=context, question=user_query)
# ✅ RAG:只检索相关的片段
relevant_chunks = vector_db.search(user_query, top_k=3) # ~1500 Token
context = "\n".join(relevant_chunks)
response = model.generate(system=context, question=user_query)25000 Token → 1500 Token,降幅 94%。即使加上 embedding 和向量检索的成本,整体费用也远低于全量塞入。
但注意一个趋势:随着长上下文模型的出现(Claude 200K、Gemini 1M、GPT-4o 128K),很多团队开始"偷懒"全量塞入。这在开发阶段可以,但生产环境必须做 RAG。Prompt Caching 能缓解部分成本,但检索仍然是更经济的选择——尤其当文档库持续增长时。
一个更精细的策略:混合模式。对于高频常见问题(FAQ),用 RAG;对于需要全局理解的复杂问题,用长上下文 + Prompt Caching。根据查询意图动态切换。
7. 工具调用优化:减少 Agent 循环次数
Agent 的成本不仅取决于每次调用的 Token 数,还取决于调用次数。一个 Agent 如果需要 8 轮工具调用才能完成任务,它的成本就是一个直接返回答案的模型的 8 倍(甚至更多,因为上下文会累积)。
减少循环次数的方法:
并行工具调用。Claude 和 GPT-4o 都支持在一次响应中调用多个工具。如果你的 Agent 需要查询 3 个独立的数据源,让它在一次调用中同时发起 3 个查询,而不是串行 3 轮:
# ✅ 并行调用 3 个独立工具
tool_calls = [
{"name": "get_weather", "args": {"city": "北京"}},
{"name": "get_news", "args": {"category": "tech"}},
{"name": "get_calendar", "args": {"date": "today"}}
]提前终止。在 System Prompt 中明确告诉 Agent 什么时候可以停止:"如果用户的问题已经回答完毕,直接给出最终回复,不要调用多余的工具。"
计划-执行分离。让一个模型先做计划(需要调用哪些工具、按什么顺序),然后直接执行,而不是让 Agent 在每一步都重新"思考"下一步做什么。这能减少大量的中间推理 Token:
# 第一步:规划(用便宜模型)
plan = haiku.generate(f"用户想{user_goal},列出需要的工具调用步骤")
# plan = ["search_docs(query)", "summarize(results)"]
# 第二步:按计划执行(不需要每步都问模型)
for step in plan:
result = execute_tool(step)实际效果:一个客服 Agent 从"每步思考"模式改为"计划-执行"模式后,平均工具调用轮次从 5.3 次降到 2.8 次,单次会话成本降低约 45%。
8. Batch API:异步任务直接打五折
不是所有的 Agent 调用都需要实时响应。批量处理任务(文档分析、数据标注、日志分类、历史数据迁移)完全可以用 Batch API。
OpenAI 的 Batch API 和 Anthropic 的 Message Batches API 都提供 50% 的折扣,代价是结果在 24 小时内返回(实际通常几小时内就完成了)。
# OpenAI Batch API
import openai
client = openai.OpenAI()
# 创建批量任务
batch = client.batches.create(
input_file_id=file_id,
endpoint="/v1/chat/completions",
completion_window="24h",
metadata={"description": "批量分类客服工单"}
)
# 半价,24 小时内完成适用场景:
- 每天需要对数千条客服工单做分类和情感分析
- 批量处理积压的文档/合同/邮件
- 对历史对话数据做质量评估
- 训练数据生成和标注
实际效果:一个每天处理 50000 条工单分类的 Agent,从实时 API 切换到 Batch API 后,这部分成本直接减半——从每月 8000 美元降到 4000 美元,而且因为不占用实时 API 的并发额度,系统稳定性也提升了。
9. Token 监控与预算控制:看不见就管不了
大多数团队在账单爆掉之前,根本不知道自己的 Agent 到底消耗了多少 Token。你需要的是实时可见性和自动预算控制。
第一层:监控。在每次 API 调用后记录 Token 使用量,按 Agent、按用户、按任务类型维度聚合:
import time
from collections import defaultdict
token_usage = defaultdict(lambda: {"input": 0, "output": 0, "cost": 0})
def call_agent_with_tracking(agent_id, messages):
response = client.messages.create(
model="claude-sonnet-4-20250514",
messages=messages
)
# 记录 Token 使用量
usage = response.usage
token_usage[agent_id]["input"] += usage.input_tokens
token_usage[agent_id]["output"] += usage.output_tokens
token_usage[agent_id]["cost"] += calculate_cost(usage)
# 超预算告警
if token_usage[agent_id]["cost"] > BUDGET_LIMIT:
send_alert(f"Agent {agent_id} 超预算!")
return response第二层:预算控制。给每个 Agent、每个用户、每个会话设置 Token 预算。超限后自动降级(切换到更便宜的模型)或直接拒绝:
def call_with_budget(session, messages):
remaining = session.token_budget - session.tokens_used
if remaining < 1000:
# 预算不足,降级到小模型
return call_model("claude-haiku", messages)
return call_model("claude-sonnet", messages)第三层:异常检测。一个正常的 Agent 会话消耗 2000-8000 Token,如果某次会话突然消耗了 50000 Token,大概率是出了问题——上下文没清理、Agent 陷入了死循环、工具返回了异常大的结果。设置阈值告警,在问题扩大之前切断。
工具推荐:LangSmith、Helicone、Langfuse 都提供 LLM 调用的 Token 监控面板。如果是自建系统,至少把 usage.input_tokens 和 usage.output_tokens 记到数据库里,配上 Grafana 看板。
10. 缓存层设计:语义级别的去重
Prompt Caching 解决的是"同一个 Prompt 前缀"的重复。但如果两个用户问了语义相同但文字不同的问题呢?"今天北京天气怎么样"和"北京今天天气如何",模型会给出几乎一样的答案,但你为两次调用都付了全价。
语义缓存:在 Agent 和 LLM 之间加一层缓存。先对用户输入做 embedding,在向量数据库中查找是否有语义相似的历史查询。如果命中(相似度 > 阈值),直接返回缓存的回答,不调用 LLM:
import numpy as np
def agent_with_semantic_cache(user_input):
# 生成 embedding
query_embedding = embed(user_input)
# 语义搜索缓存
cached = vector_cache.search(
query_embedding,
threshold=0.95, # 相似度阈值
max_age="7d" # 缓存有效期
)
if cached:
return cached.answer # 命中,直接返回,0 Token 消耗
# 未命中,调用 LLM
answer = llm.generate(user_input)
# 存入缓存
vector_cache.store(query_embedding, answer, metadata={
"input": user_input,
"timestamp": time.now()
})
return answer适用场景:
- 客服 Agent:大量用户问的问题高度重复("怎么退款"、"怎么改密码")
- FAQ Agent:本质上就是在回答固定集合的问题
- 数据查询 Agent:相同维度不同表述的查询
实际效果:一个电商客服 Agent,通过语义缓存获得了 35% 的命中率——35% 的请求没有调用 LLM,直接返回缓存结果。整体 Token 消耗降低 35%,同时 P99 延迟从 2.3 秒降到 0.1 秒(缓存命中时)。
注意:语义缓存需要仔细设置相似度阈值。太低会返回错误答案("怎么退款"和"退款多久到账"语义相似但答案不同),太高则命中率低。建议从 0.95 开始,根据 badcase 反馈调整。另外,涉及实时数据(天气、库存、订单状态)的查询不适合做缓存,或者需要配合 TTL 短期缓存。
参考文档与链接
- Anthropic Prompt Caching 官方文档 — Cache 前缀标记方法、TTL、定价折扣
- OpenAI Cached Input Tokens 文档 — 自动缓存机制、最低前缀要求
- OpenAI Batch API 文档 — 50% 折扣、24 小时完成窗口
- Anthropic Message Batches API — 异步批量处理
- Outlines - Structured Output — Token 级别输出约束,保证格式合规
- Guidance by Microsoft — 另一个结构化输出框架
- Helicone - LLM Observability — Token 盓控、成本追踪、缓存
- Langfuse - Open Source LLM Engineering — 开源的 LLM 调用监控和评估平台
- GPTCache - Semantic Cache — 开源语义缓存方案
- LangChain Context Window Management — 对话历史管理策略
你的 Agent 每天烧多少 Token?评论区聊聊你的账单和优化心得。觉得有用就点个赞,让更多被账单吓到的朋友看到。
作者: itech001
来源: 公众号:AI人工智能时代
网站: https://www.theaiera.cn/
每日分享最前沿的AI新闻资讯和技术研究。
本文首发于 AI人工智能时代,转载请注明出处。