AI 助手一天烧了我 400 块……查完账单想砸电脑

0 阅读5分钟

上周五收到账单邮件,我差点以为邮件发错了。

日均 43,月度预计43,月度预计 1,290。

我做的东西不复杂——一个内部用的 MCP 助手,连了数据库、Jira 和 Slack。每天 500 次调用,上线前我估算了半天,觉得每次大概 0.01,一天应该0.01,一天应该 5 左右。

实际 8.6 倍。

坐下来翻 Token 日志,花了两个小时才搞清楚为什么。


原来是这么烧的

你们有没有认真想过 MCP Agent 的 Token 是怎么消耗的?

我之前没想过。我以为就是 prompt 进去、回答出来,按长度收费。

实际情况是:每一步调用,你都在重复发之前所有步骤的内容。

第 1 步发出去:System + 用户需求 + 23个工具的 JSON schema
                 → 约 2,800 tokens

第 2 步发出去:System + 用户需求 + 23个工具的 JSON schema
               + 第1步的工具调用 + 第1步工具的返回结果
               → 约 5,600 tokens

第 3 步发出去:以上所有 + 第2步的 → 约 9,200 tokens
...
第 8 步发出去:以上所有 → 约 64,400 tokens!

8 步任务,发出的 Token 总量是 64,400 输入 + 2,200 输出。

用 DeepSeek-V3 算:贵,但比那些海外大模型贵的多少?
实测还是得用更强的推理模型来保证工具调用的准确性,单次 $0.2+。

500 次一天,就是这 $100+。

我他妈的工具列表里有 23 个 MCP 工具,每步都全发一遍。23 个工具的 JSON schema 大概 3,500 tokens,8 步 × 重复 = 白白烧了 28,000 tokens。

这笔钱冤枉到离谱。


我是怎么把它压下来的

试了一堆方案,有用的有没用的,挑几个说。

第一个招:Prompt Caching

这是投入产出比最高的改动,没有之一。

System Prompt 和 Tool List 每次调用都是一样的。缓存它们,后续调用读缓存价格是普通输入的 1/10。

改法很简单,加几个字段:

response = client.messages.create(
    model="deepseek/deepseek-chat",
    system=[
        {
            "type": "text",
            "text": SYSTEM_PROMPT,
            "cache_control": {"type": "ephemeral"}  # 就这一行
        }
    ],
    tools=[
        {**tool, "cache_control": {"type": "ephemeral"}}
        for tool in TOOLS  # tool list 也缓存
    ],
    messages=history,
    max_tokens=1024,
)

改完之后,Step 3 之后的调用缓存命中率接近 94%。

单次费用:0.2260.226 → 0.089。省了 61%

就这一个改动,月费从 1,290降到1,290 降到 503。


第二个招:工具列表别全发

我什么时候需要 23 个工具?从来没有。每次任务顶多用 3-4 个。

但我图省事,直接把全部工具列表丢进去了,让模型自己选。

结果呢,每步多了 3,500 tokens 的废话。

现在的做法:在调用主模型之前,先用便宜模型做一次任务分类,把相关工具子集挑出来:

TOOL_GROUPS = {
    "data_query": ["sql_query", "data_export", "chart_gen"],
    "project":    ["jira_create", "jira_update", "jira_search"],
    "comms":      ["slack_send", "email_draft", "cal_check"],
    "code":       ["gh_search", "gh_pr", "code_review"],
}

def get_tools_for_task(task: str) -> list:
    # 用 Qwen-Turbo 分类,几乎不花钱
    category = quick_classify(task, model="qwen/qwen-turbo")
    return TOOL_GROUPS.get(category, list(ALL_TOOLS.keys()))

分类调用很便宜,但每步省 ~3,000 tokens,8 步省 24,000 tokens。算下来追加节省了 ~11%。


第三个招:分层模型路由

说实话这个我之前有抵触:感觉用便宜模型会让质量变差。

实测发现,约 40% 的步骤根本不需要复杂推理——格式转换、结构化解析、简单判断——Qwen-Turbo 就能搞定。

def pick_model(step_type: str, complexity: float) -> str:
    if step_type == "format" or complexity < 0.3:
        return "qwen/qwen-turbo"         # 便宜,够用
    elif complexity < 0.6:
        return "deepseek/deepseek-chat"  # 中档
    else:
        return "deepseek/deepseek-r1"    # 复杂推理

不同组合实测结果:

方案单次均价省了多少
全程 DeepSeek-V3$0.089(缓存后)基线
40% Qwen-Turbo + 60% DeepSeek$0.063-29%
混合三档$0.052-42%

质量有没有影响?复杂任务没有。简单步骤的小模型出错率高了一点,但加个重试逻辑基本解决了。


第四个招:修剪历史上下文

Agent 跑到第 7、8 步的时候,第 1 步工具返回的原始数据大概率用不到了,但还是全扛着。

改成:保留最近 3 步的完整 tool response,更早的只留摘要:

def prune_context(messages: list, keep_full: int = 3) -> list:
    tool_msgs = [m for m in messages if m.get("role") == "tool"]
    if len(tool_msgs) <= keep_full:
        return messages
    
    old_ids = {m["tool_use_id"] for m in tool_msgs[:-keep_full]}
    result = []
    for msg in messages:
        if msg.get("role") == "tool" and msg.get("tool_use_id") in old_ids:
            result.append({**msg, "content": f"[摘要] {summarize(msg['content'])}"})
        else:
            result.append(msg)
    return result

8 步任务输入 Token 从 64,400 降到 39,200,省了 39%。


叠加之后是什么效果

前四个方案叠起来,效果如下:

组合单次均价vs 原始
啥都没做$0.226基线
Prompt Caching$0.089-61%
+ 工具裁剪$0.071-69%
+ 模型路由$0.052-77%
+ 上下文修剪$0.041-82%

日均费用:113113 → 21。

月费:3,3903,390 → 630。

有没有感觉之前白花了好多钱……


还有两个进阶玩法(暂时没上,有兴趣的可以试)

语义缓存:把历史请求的 embedding 存下来,新请求相似度 > 0.92 就直接返回缓存结果,整个 LLM 调用都省了。我的场景重复率大约 28%,理论上能再省 23%。还没上生产,但测试环境跑起来挺稳的。

批量 API:非实时任务(比如夜间报告生成)改用 Batches API,价格打五折。这个改动成本低,适合有批处理场景的同学。


一个顺便提的工具

做模型路由的时候,我发现在多个供应商之间切换非常麻烦——每家 API 格式不完全一样,key 要分开管,国内访问有的还需要特殊处理。

后来用了 TheRouter,一个 API Key 统一接入 30+ 模型,接口采用标准 REST 格式,Prompt Caching 的参数也能透传。不用自己维护多套 SDK,路由逻辑写起来简单了很多:

# 用标准 HTTP 客户端就行,接口格式统一
import httpx

def call_model(model: str, messages: list) -> str:
    resp = httpx.post(
        "https://api.therouter.ai/v1/chat/completions",
        headers={"Authorization": "Bearer tr-xxxxxx"},
        json={
            "model": model,   # 换模型只改这一个字符串
            "messages": messages,
        }
    )
    return resp.json()["choices"][0]["message"]["content"]

# 按步骤类型选模型
call_model("qwen/qwen-turbo", messages)       # 格式解析
call_model("deepseek/deepseek-chat", messages) # 工具调用
call_model("deepseek/deepseek-r1", messages)   # 复杂推理

总结一下

MCP Agent 的账单暴涨,根子在三个问题叠加:上下文滚雪球、工具列表冗余、全程旗舰模型。

优先级:

  1. Prompt Caching——改动最小,效果最大,先上这个
  2. 工具列表裁剪——有 10+ 个 MCP 工具必须做
  3. 模型路由——不同步骤用不同模型,便宜的步骤没必要用贵的
  4. 上下文修剪——任务步骤多(>5 步)再考虑

按顺序来,第 1、2 个就能省掉 60-70%。

希望能帮到同样被账单吓到的同学。欢迎评论区聊聊你们的优化经验。


TheRouter — 一个 Key 搞定 30+ AI 模型,国内直连,标准 API 格式兼容。 官网:therouter.ai