Agent 的钱花在哪里
一次 Agent 调用的成本拆解:
erlang
输入 Token 构成:
系统提示 固定,每次调用都付
工具定义(Schema) 固定,注册多少工具就付多少
对话历史 随轮次线性增长
检索内容 动态
输出 Token:
模型思考过程 ReAct 的 Thought 部分
工具调用参数 每次工具调用
最终回复 用户看到的答案
延迟构成:
LLM 推理时间 占绝大部分(通常 > 90%)
工具执行时间 通常 < 10%,但串行叠加可观
优化方向只有两个:减少 Token 数 和 减少等待时间。本文用四个实验量化每种策略的实际收益。
Demo 1:Token 成本拆解------系统提示瘦身
系统提示在每次调用时都会发送给模型,是最容易被忽视的固定成本。
两版系统提示对比:
python
MINIMAL_PROMPT = "You are a helpful assistant."
# → 6 tokens
VERBOSE_PROMPT = """You are an extremely helpful, knowledgeable, and professional AI assistant
for WonderLab's enterprise software platform. You specialize in providing accurate weather
information... Always be thorough, comprehensive, and leave no important detail unexplained."""
# → 107 tokens
Token 对比:
scss
Minimal ( 6 tokens): 'You are a helpful assistant.'
Verbose (107 tokens): 'You are an extremely helpful...'
每次调用多付: 101 tokens
101 tokens 听起来不多。按 GPT-4o 输入 $2.50/1M tokens 计:
- 每天 1 万次调用 → 每天多花 $0.25
- 每天 100 万次调用 → 每天多花 25,每月750
延迟测量(2 次采样,同一 Query):
csharp
Agent Run 1 Run 2 Avg Answer
Minimal 6.90s 3.39s 5.15s The current weather in Beijing is 25°C...
Verbose 3.10s 4.21s 3.66s The current weather in Beijing is 25°C...
Verbose 的平均延迟反而比 Minimal 更低------这是反直觉的结果。
原因:2 次采样完全不足以测量 LLM 延迟。API 返回时间受服务端负载影响,波动区间通常是 ±50%。要得到可靠的延迟数据,至少需要 10-20 次采样后取中位数。这条系统提示延迟差异本质上是噪声。
系统提示精简的收益是 Token 成本,不是延迟。延迟优化需要别的手段。
Prompt Caching(进阶): Claude API 和 OpenAI API 支持显式的提示缓存。在 Claude 中:
python
response = client.messages.create(
model="claude-sonnet-4-6",
system=[{
"type": "text",
"text": LARGE_SYSTEM_PROMPT,
"cache_control": {"type": "ephemeral"}, # 标记为可缓存
}],
messages=[...],
)
# 首次调用:写入缓存(正常计费)
# 后续同前缀调用:命中缓存,约 90% 成本折扣
print(response.usage.cache_read_input_tokens) # 命中数
print(response.usage.cache_creation_input_tokens) # 写入数
对于拥有 10K+ tokens 的大系统提示(RAG 检索结果、工具说明、背景知识),Prompt Caching 是收益最高的单项优化。
Demo 2:模型路由------跳过不必要的 Agent 开销
核心思路: 用一次便宜的分类调用,判断查询是否真的需要 Agent。不需要工具的查询直接给 LLM 回答,省去 Agent 的多轮 ReAct 开销。
python
ROUTING_SYSTEM = """Classify the user query. Reply with ONLY one word:
- "direct" if answerable from general knowledge (no real-time data)
- "agent" if requires tool call (weather, product pricing, calculation)"""
def classify_query(query: str) -> str:
resp = llm.invoke([SystemMessage(ROUTING_SYSTEM), HumanMessage(query)])
return "agent" if "agent" in resp.content.lower() else "direct"
def routed_run(query: str) -> dict:
route = classify_query(query)
if route == "direct":
resp = llm.invoke([HumanMessage(query)]) # 直接回答,无 Agent 开销
else:
result = full_agent.invoke(...) # 完整 Agent 执行
5 个测试用例结果:
python
Query Route Total Tools
What is the capital of France? direct 2194ms []
Explain machine learning in one sentence. direct 2011ms []
What's the weather in Shanghai right now? agent 4213ms ['get_weather']
How much does WonderBot Pro cost per month? agent 6033ms ['get_product_info']
What is 299 multiplied by 12? agent 3878ms ['calculator']
路由分类全部正确。但注意几个数字:
direct查询总耗时 ~2000ms:这包含了路由分类调用(~1s)+ 直接回答调用(~1s)agent查询总耗时 4000-6000ms:路由调用(~1s)+ 完整 Agent(~3-5s)
路由的隐藏成本: 每次路由本身是一次额外的 LLM 调用。对于必须走 Agent 的查询,路由反而让总耗时增加了约 1 秒。路由的收益在于:当大量查询不需要 Agent 时,把这些查询从"多轮 ReAct 循环"变成"单次 LLM 调用"。
经验规则:如果你的业务场景中 > 40% 的查询不需要工具,路由就值得部署。反之,路由只是增加开销。
Demo 3:并行工具调用------3.0x 加速
两个以上工具调用相互独立时,没有理由顺序执行。
python
async def fetch_weather_async(city: str) -> str:
await asyncio.sleep(0.1) # 模拟 100ms I/O 延迟
...
async def run_parallel(cities: list[str]) -> list[str]:
return await asyncio.gather(*[fetch_weather_async(c) for c in cities])
def run_sequential(cities: list[str]) -> list[str]:
results = []
for city in cities:
time.sleep(0.1) # 顺序阻塞
results.append(...)
return results
3 个城市 × 100ms 延迟,3 次采样:
yaml
Sequential avg: 300.4ms (理论 ~300ms)
Parallel avg: 101.4ms (理论 ~100ms)
Speedup : 3.0x (快 66%)
结果完全符合理论值。N 个独立工具调用,并行执行时延迟从 N × t 降到 t。
LangGraph 原生支持并行: create_react_agent 在 LLM 单次响应中返回多个 tool_calls 时,会自动并行执行这些工具。不需要手写 asyncio 管理代码------把工具函数声明为 async def 即可自动享受并行收益。
python
@lc_tool
async def get_weather(city: str) -> str: # ← async 声明
"""Get current weather for a city."""
result = await weather_api.fetch(city) # 非阻塞 I/O
return result
前提是 LLM 在单次响应中能识别出多个工具调用是独立的,并一次发出。弱模型可能仍然顺序调用。
Demo 4:工具结果缓存------0ms vs 100ms
适用场景: 相同参数的工具调用在短时间内重复出现(同一用户反复查同一城市的天气,或同一个 Agent 在多步推理中需要同一信息)。
python
_cache: dict[str, tuple[str, float]] = {}
CACHE_TTL_S = 60.0 # 60 秒过期
def get_weather_cached(city: str) -> tuple[str, bool]:
key = f"weather:{city.lower()}"
now = time.time()
if key in _cache:
result, ts = _cache[key]
if now - ts < CACHE_TTL_S:
return result, True # 命中缓存
# 未命中:调用真实工具
time.sleep(0.1) # 模拟延迟
result = fetch_from_api(city)
_cache[key] = (result, now)
return result, False
6 次调用结果:
sql
City Status Time Note
Beijing MISS 100.2ms 1st call
Shanghai MISS 100.2ms 1st call
Beijing HIT ✓ 0.0ms 2nd call --- cache hit
Shenzhen MISS 100.2ms 1st call
Shanghai HIT ✓ 0.0ms 3rd call --- cache hit
Beijing HIT ✓ 0.0ms 4th call --- cache hit
命中率: 3/6 = 50%
Miss 延迟: ~100ms Hit 延迟: < 1ms
TTL 设置原则:
- 天气数据:5-15 分钟(数据变化慢,用户不需要实时精度)
- 产品价格:数小时(基本不变)
- 库存/实时数据:不缓存或 TTL < 1 分钟
绝对不缓存的工具:有副作用的写操作(写文件、发邮件、提交表单)------相同参数重复执行会产生意想不到的后果。
设计 Checklist
Token 优化
- 测量系统提示 Token 数,每条规则问"这真的必要吗"
- 静态参考文档(产品手册、API 文档)移到 RAG 检索,按需注入,而非塞进系统提示
- 对话历史设置最大轮次(推荐最近 10-20 轮),超出部分摘要替换
- 长期高频调用 → 评估 Claude/OpenAI 的 Prompt Caching 收益
模型路由
- 统计业务场景中"不需要工具"的查询比例,> 40% 才值得路由
- 路由分类器 Prompt 要覆盖你实际的意图边界(不是通用 direct/agent)
- 测量路由引入的额外延迟,与省下的 Agent 开销对比后决策
并行工具调用
- 工具函数声明为
async def,LangGraph 自动并行执行多个独立工具调用 - 识别"必须顺序"的工具(后一个依赖前一个的输出)与"可并行"的工具
- 多模型供应商场景:工具调用可以并发打到不同服务,延迟取最大值而非总和
工具结果缓存
- 幂等工具(相同输入 → 相同输出)优先缓存
- TTL 根据数据新鲜度要求设定,不要用统一值
- 有副作用的写操作永不缓存
- 生产环境:用 Redis 而非内存 dict,支持多实例共享缓存
总结
五个核心结论:
- Token 成本是最可控的成本:系统提示、工具 Schema、对话历史------每一项都可以量化和削减,无需改变模型或架构
- 延迟测量需要足够采样:2 次采样结果有时会违反直觉(verbose 反而更快),至少 10 次采样才能看出稳定规律
- 模型路由有隐藏开销:路由本身是一次额外 LLM 调用,只有"直接查询占比 > 40%"时收益才为正
- 并行工具调用是最干净的优化:N 个独立工具调用 → 延迟从 N×t 降到 t,且 LangGraph 原生支持,写 async 工具函数即可
- 缓存的收益取决于命中率:命中率低于 30% 时缓存引入的复杂性大于收益;TTL 设置比实现缓存本身更重要
下一篇:Harness Engineering 完整体系 ------ 从入门的五要素扩展到完整的 8 层框架,包含动作空间注册表、权限预算系统和完整的威胁模型。
参考资料
欢迎访问 PrimeSkills ------ 一个精心策划的 AI Agent 与技能市场,所有内容均经过真实企业级工作流验证。没有噱头,只有真正有效的东西。
更多实用知识和有趣产品,欢迎访问我的个人主页