进阶 📋 7 个步骤 第 421 / 470 篇

给 Agent 做 Token 成本治理:模型路由、提示缓存与上下文瘦身七步法

Agent 成本治理落地顺序:成本标签量化、上下文瘦身、模型分级路由、提示缓存结构改造、结果与语义缓存、批处理换成本、单任务成本看板。

2026.09.13· 26 分钟阅读· 约 2552 字· 📉 成本治理 / 🔀 模型路由

Agent 和普通大模型调用在成本结构上有一个本质区别:它不是调一次,而是循环。规划、推理、调工具、看结果、再循环,每一轮都把上下文重新发一遍。在按 token 计费的模式下,循环深度和上下文长度是相乘关系——一个 10 步的 Agent,带着 4000 token 系统提示与 8000 token 历史,可能把 12000 token 重复发送十次。

这篇教程不讲「换便宜的模型」这种一句话结论,而是给出一套可落地的治理顺序:先量化、再瘦身、后路由、加缓存、上批处理、建看板。每一步都能独立见效,也都能被验证。

🎯 适合人群:Agent 已经跑在测试或生产环境、开始收到账单压力的开发者。示例用 Python,思路与语言无关。

Step 1:先量化,否则优化就是猜

1 给每次调用打上成本标签

优化的前提是能归因。建议在调用封装层统一记录四类字段:模型、提示词模板、业务归属、调用阶段。没有这层标签,你无法回答「钱花在哪个环节」这个问题。

import time, json, logging

def call_llm(model, messages, *, stage, template, tenant, **kw):
    t0 = time.time()
    resp = client.chat.completions.create(model=model, messages=messages, **kw)
    u = resp.usage
    logging.info(json.dumps({
        "event": "llm_call",
        "model": model,
        "stage": stage,          # 例如 plan / tool / summarize / judge
        "template": template,    # 提示词模板标识
        "tenant": tenant,        # 业务归属
        "in_tokens": u.prompt_tokens,
        "out_tokens": u.completion_tokens,
        "latency_ms": int((time.time() - t0) * 1000),
    }, ensure_ascii=False))
    return resp
💡 只看总账是没用的。按 stage 聚合后,通常会看到一个反直觉的结果:真正吃掉预算的往往不是规划步骤,而是反复把工具返回的大块 JSON 塞回上下文

Step 2:上下文瘦身——最省钱的一层,且不需要换模型

2 工具输出压缩 + 历史摘要 + 输出上限

这一层的收益通常在 10%–15% 起,且不牺牲质量,属于「免费层」。三个动作按优先级排列:

# ① 工具输出:只留下一步真正需要的字段,不要把整块 JSON 回灌
def slim_tool_result(raw: dict, keep=("id", "status", "owner")):
    return {k: raw.get(k) for k in keep if k in raw}

# ② 历史:把中段对话压成一条摘要,只保留系统提示 + 最近若干轮
def trim_context(messages, max_recent=6, summary_model="gpt-4o-mini"):
    if len(messages) <= max_recent:
        return messages
    system = [m for m in messages if m["role"] == "system"]
    recent = messages[-max_recent:]
    middle = messages[len(system):-max_recent]
    summary = call_llm(summary_model, [{
        "role": "user",
        "content": "把以下对话压成给下一步用的要点,保留结论与约束,删掉过程:\n" + str(middle),
    }], stage="summarize", template="ctx_trim", tenant="internal")
    return system + [{"role": "user",
                      "content": "前情摘要:" + summary.choices[0].message.content}] + recent

③ 给输出设硬上限。会「自言自语」的 Agent 可能产出比最终答案多 3–10 倍的输出 token,而输出通常比输入贵 4–5 倍。为每个工作流单独设置输出上限,比全局设一个值更合理。

压缩不能压掉约束。摘要时如果丢掉了「必须引用来源」「金额不得四舍五入」这类硬约束,Agent 后面的行为会漂移。建议把约束单独维护成一个不可压缩的段落,始终放在提示词里,而不是混在历史里被摘要掉。

Step 3:模型分级路由——最大的单一杠杆

3 按「失败代价」而不是「单次价格」路由

模型价格带大致分三档,最便宜与最贵之间可能相差百倍。把 60%–80% 的子任务路由到小模型,是收益最集中的一步。

档位典型任务判断信号
小 / 快抽取、分类、改写、单步查询有明确正确答案、歧义低
摘要、起草、常规问答需要一点组织能力但不需要长链推理
多步规划、工具选择、代码生成答错会引发级联失败
def route(task_type: str) -> str:
    # 规则优先:能用规则判断的,不要交给模型判断
    if task_type in ("extract", "classify", "reformat"):
        return "gpt-4o-mini"
    if task_type in ("summarize", "draft"):
        return "gpt-4o"
    return "o-series-deep"   # 规划类,保持强模型

# 关键判断:这次失败会不会触发「带着大上下文重跑」?
# 会  → 一开始就用强模型,贵得值
# 不会 → 先让小模型试,失败再升级

「便宜模型更贵」是真实存在的。如果一个小模型在复杂步骤上失败,重试意味着把整段长上下文再发一遍,成本可能远超一开始就用强模型。所以路由的判断依据应该是失败的代价,而不是单次调用的标价。

Step 4:提示缓存——结构比参数更重要

4 稳定前缀在前,动态内容在后

提示缓存复用重复前缀背后的键值张量,稳定部分常能享受 50%–90% 的输入折扣。但缓存收益是有条件的,账要算清楚:写入通常按 1.25 倍计价、读取按 0.1 倍计价,因此需要至少两次读取才能回本。

做法后果
把时间戳、请求 ID、会话 token 放在系统提示顶部每次请求哈希都不同,缓存直接失效
稳定前缀(系统提示 + 工具 schema)放前面,动态内容放最后命中率大幅提升
中途截断对话历史前缀被打断,已缓存条目被逐出
上下文只追加、不改写缓存持续命中,多轮会话越往后越省
💡 业界有一个被反复验证的案例:把动态内容从系统提示顶部挪到稳定块之后,命中率从 7% 左右跃升到 80% 以上,成本随之下降过半。同样的 token、同样的逻辑,只改了一个顺序。上线前请先量一次你的缓存命中率,低于 60% 通常意味着结构有问题。

Step 5:结果缓存与语义缓存

5 不要为同一个计算付两次钱

Agent 会重复调用完全相同的工具:读同一个数据库 schema、抓同一份文档、查同一份配置。把这些结果缓存下来,命中时零边际成本。

import hashlib, time

_cache = {}

def cached_call(fn, *args, ttl=300, **kw):
    key = hashlib.sha256((fn.__name__ + repr(args) + repr(kw)).encode()).hexdigest()
    now = time.time()
    hit = _cache.get(key)
    if hit and now - hit["ts"] < ttl:
        return hit["value"]
    value = fn(*args, **kw)
    _cache[key] = {"value": value, "ts": now}
    return value

再往上一层是语义缓存:把高度相似的用户提问直接返回历史结果,不转发给模型。对于客服、知识问答这类重复度高的场景,通常能吸收 15%–30% 的流量。代价是缓存失效逻辑更复杂,需要有明确的过期与版本策略。

语义缓存的误命中是质量事故。「如何退款」和「如何取消退款」向量距离可能很近,但答案完全相反。建议对高风险意图(金额、权限、法律、医疗)直接禁用语义缓存,只对低风险问答开启。

Step 6:批处理换成本

6 能等 24 小时的任务,价格可以砍半

批处理接口对可容忍延迟的请求通常提供约 50% 的折扣。适合批处理的场景很明确:夜间报表生成、批量文档分类、历史数据富化、批量内容生成、非紧急文档分析。

适合批处理不适合批处理
后台数据富化与归类面向用户的实时对话
定时报表与摘要实时决策(风控、路由)
批量内容生成交互式编码辅助
💡 把「Agent 的离线评估」也放进批处理。回归测试、数据集跑分这类任务天然可等,用批处理跑既省钱又不影响线上。

Step 7:把成本变成一线指标

7 盯「每个成功任务的花费」

成本应该和延迟、错误率放在同一块看板上,而不是月底才在财务报表里看到。核心指标只有一个:cost per successful task(每个成功任务的花费),并按模型与任务类型拆分。

这个指标能同时暴露三件事:某个模型在悄悄重试、某个工具返回了臃肿输出、某个子任务本该路由到更便宜的档位。当单任务成本突然抬升,那通常是一个 bug,而不是一张账单。

# 建议固定跟踪的三个信号
# 1) 缓存命中率:稳定提示词场景低于 60% 说明结构有问题
# 2) 每类任务的重试次数:每次重试都是整段上下文的重新发送
# 3) 每成功任务成本:按模型 × 任务类型拆分,观察趋势而非绝对值

常见问题 FAQ

Q1:优化应该按什么顺序做?

按本教程的顺序:先打成本标签(否则无法归因)→ 上下文瘦身(不需要换模型、不牺牲质量)→ 模型路由(收益最大)→ 提示缓存(结构改造)→ 语义缓存与批处理(按场景)→ 建看板持续跟踪。把成本优化当成持续实践而不是一次性项目,效果差别很大。

Q2:模型路由会不会让输出质量变差?

会有边界情况的风险。建议对路由过的链路保留「失败可升级」机制:小模型结果过不了校验就自动升到强模型,并把升级次数记进看板。升级率高说明路由规则需要调整,而不是说明路由方向错了。

Q3:缓存会不会让回答变旧?

会,所以必须有 TTL 与版本策略。提示缓存针对的是「不变的提示词前缀」,不会让答案变旧;语义缓存针对的是「相似提问」,必须按业务设定过期时间,并对高风险意图禁用。

Q4:自托管开源模型能省多少?

高并发、可预测负载下,自托管的边际成本优势明显,但要把 GPU 折旧、运维与弹性损失算进去。低流量场景通常不如按量付费。目前官方及行业暂未披露更多细节,后续将持续跟进迭代动态。

← 返回教程中心