1. 从 1.4 万 Token 到 200 Token:一次让我肉疼的账单复盘
去年年底我接手了一个内部代码辅助工具的重构项目,核心场景是让 AI 帮团队做代码审查、补全和重构建议。当时选型很直接,用了市面上口碑不错的 Claude Code 方案,接入快、效果稳,团队反馈也不错。但问题出在第二个月——财务把 API 账单甩到我面前的时候,我盯着那个数字看了足足半分钟,以为自己看错了小数点。
单月 Token 消耗量比我预估的高出将近四倍,其中输入 Token 占了绝对大头。我一开始以为是调用量暴涨,查了日志才发现调用次数其实很平稳,真正的问题藏在每次请求的系统提示词里。Claude Code 默认注入的那套系统提示词,包含工具定义、行为规范、安全约束、格式要求等等,加起来足足有1.4 万个 Token。这意味着每一次对话,哪怕用户只问了一句"这个函数有什么问题",我都要先为这 1.4 万 Token 买单。
这件事让我意识到一个被很多人忽略的事实:在 AI 辅助开发场景里,系统提示词的成本往往比用户输入本身高出一个数量级。你精心优化的 Prompt 工程、你反复打磨的 Few-shot 示例,在系统提示词面前都是小巫见大巫。后来我花了大概三周时间,把整套方案迁移到了一个更轻量的架构上,系统提示词从 1.4 万 Token 压缩到 200 Token 左右,整体 Token 账单直接降了 85%。
这篇文章就是把这套迁移过程完整拆开讲清楚。我会说明为什么 Claude Code 的系统提示词会这么重、迁移到轻量方案时哪些东西必须保留哪些可以砍掉、200 Token 的系统提示词到底长什么样、以及迁移过程中我踩过的那些坑。如果你也在为 AI 工具的 Token 成本头疼,或者正在考虑从重型框架切换到轻量方案,这篇内容应该能帮你少走不少弯路。
提示:本文讨论的是系统提示词的工程优化思路,不涉及任何特定厂商的推荐或贬低。所有方案选择都基于我自己的实际业务场景,你的场景不同,结论可能完全不同。
2. 拆解 Claude Code 那 1.4 万 Token 到底花在哪了
2.1 系统提示词的构成解剖
要优化,先得知道钱花在哪。我把 Claude Code 的系统提示词完整导出后做了分类统计,大致可以分成这么几块:
| 构成模块 | 预估 Token 占比 | 是否可裁剪 |
|---|---|---|
| 工具定义与 Schema 描述 | 约 45% | 部分可裁剪 |
| 行为规范与安全约束 | 约 20% | 大部分可裁剪 |
| 输出格式与风格要求 | 约 15% | 可大幅精简 |
| 上下文管理指令 | 约 12% | 可替换 |
| 示例与边界说明 | 约 8% | 可移除 |
工具定义是最大头。Claude Code 内置了文件读写、命令执行、搜索、编辑等一整套工具,每个工具的 JSON Schema 描述都极其详尽,包含参数类型、枚举值、使用示例、错误处理说明。这些描述对通用场景很有价值,但对我这种只做代码审查的场景来说,90% 的工具根本用不上。
行为规范那块也很重。它包含了大量"你应该如何""你不应该如何"的约束条款,覆盖了各种边界情况。这些内容在通用产品里是必要的安全网,但在一个内部工具里,很多约束其实可以由业务逻辑层来保证,不需要每次都塞进系统提示词。
2.2 为什么重型框架倾向于"堆提示词"
这里有个反直觉的点:系统提示词越重,往往不是因为模型需要,而是因为产品要覆盖的场景太多。
Claude Code 是一个面向全球开发者的通用产品,它要同时服务写 Python 的、写 Rust 的、做前端的、搞运维的,还要保证在各种奇怪输入下都不出安全事故。这种"一个提示词打天下"的策略,必然导致提示词膨胀。每遇到一个边界 case,产品团队就加一条约束;每支持一个新工具,就加一段 Schema。日积月累,1.4 万 Token 就这么堆出来了。
但对我的场景来说,这是巨大的浪费。我的用户就是团队里那十几个工程师,任务就是代码审查和重构建议,输入输出格式高度可控。我完全可以用业务代码来约束行为,而不是靠系统提示词里的自然语言条款。
2.3 一个被忽视的成本公式
很多人算 Token 成本只算输出,其实输入才是大头。我整理了一个简单的成本估算公式:
单次请求成本 = (系统提示词 Token + 用户输入 Token) × 输入单价 + 输出 Token × 输出单价假设系统提示词 14000 Token,用户输入平均 500 Token,输出平均 800 Token。按某主流模型的定价粗算,输入单价假设为 X,输出单价为 3X,那么:
- 重型方案单次成本 ≈ (14000 + 500) × X + 800 × 3X = 16900X
- 轻量方案单次成本 ≈ (200 + 500) × X + 800 × 3X = 3100X
成本差距接近5.5 倍。如果算上缓存命中率的差异(轻量提示词更容易命中缓存),实际差距还能再拉大。这就是 85% 降幅的来源——不是靠砍功能,而是靠砍掉那些根本用不上的"通用性"。
3. 迁移到 Pi 架构:我到底改了什么
3.1 Pi 的核心思路:把复杂度从提示词移到代码里
Pi 不是一个具体的开源项目名,而是我给这套轻量架构起的代号。它的核心思想只有一句话:能用代码约束的,绝不写进提示词。
传统重型框架的做法是:把所有规则、工具、格式要求都写进系统提示词,让模型自己去理解和遵守。Pi 的做法是:系统提示词只保留最核心的角色定义和输出契约,其余全部由调用层的代码来保证。
举个例子。重型框架会在提示词里写"当你需要读取文件时,请使用 read_file 工具,参数 path 为文件路径,注意路径必须是绝对路径……"这一大段。Pi 的做法是:系统提示词里只写"你是一个代码审查助手,输出 JSON 格式的审查结果",至于读文件,根本不让模型读——我在调用前就把文件内容准备好,直接塞进用户消息里。
这个转变带来的效果是惊人的。模型不再需要理解工具怎么用,它只需要专注于"审查代码"这一件事。提示词短了,模型的注意力也更集中了,输出质量反而更稳定。
3.2 200 Token 系统提示词长什么样
我把最终的系统提示词完整贴出来,你可以直接参考:
你是一个资深代码审查助手。你的任务是分析用户提供的代码片段,找出潜在问题并给出改进建议。 输出要求: 1. 严格输出 JSON,不要有任何额外文字 2. JSON 结构:{"issues": [{"line": 行号, "severity": "high|medium|low", "message": "问题描述", "suggestion": "改进建议"}]} 3. 如果没有问题,返回 {"issues": []} 4. severity 判断标准:high 为会导致 bug 或安全问题,medium 为影响可维护性,low 为风格建议 约束: - 只审查用户提供的代码,不要假设文件外的上下文 - 建议要具体,不要泛泛而谈 - 用中文输出 message 和 suggestion就这么点,实测 Token 数在 180 到 220 之间浮动。对比原来的 1.4 万,压缩了 98% 以上。
你可能会问:这么短的提示词,模型能理解吗?答案是能,而且理解得更好。因为剩下的复杂度都被我用代码处理了——文件读取、上下文拼接、结果解析、错误重试,全部在调用层完成。模型只负责它最擅长的事:理解代码、发现问题、生成建议。
3.3 迁移前后的架构对比
为了让你更直观地理解变化,我列一个对比表:
| 维度 | 重型方案(Claude Code 风格) | 轻量方案(Pi 架构) |
|---|---|---|
| 系统提示词 | 约 14000 Token | 约 200 Token |
| 工具调用 | 模型自主决定调用哪些工具 | 调用层预先准备数据 |
| 上下文管理 | 模型根据指令自行管理 | 代码控制拼接逻辑 |
| 输出解析 | 模型输出自然语言,后处理提取 | 模型直接输出结构化 JSON |
| 错误处理 | 依赖模型自我纠正 | 代码层重试 + 降级 |
| 单次成本 | 基准值 | 约基准值的 15% |
| 输出稳定性 | 受提示词长度影响,偶有漂移 | 高度稳定 |
这个对比不是说重型方案不好,而是说场景决定架构。通用产品需要重型方案来覆盖长尾场景,垂直工具用轻量方案性价比高得多。
4. 迁移过程中踩过的五个坑
4.1 坑一:以为砍提示词就是删内容
我一开始的做法很粗暴,直接把系统提示词里"看起来不重要"的段落删掉,结果模型行为立刻失控。输出格式开始飘,有时候返回 JSON,有时候返回 Markdown,有时候还夹带解释性文字。
后来我才明白,提示词里的每一段都有它的作用,不能简单删除,只能转移。那些被删掉的约束,必须用代码来承接。比如原来提示词里说"输出必须是合法 JSON",删掉之后我就在调用层加了 JSON 解析和重试逻辑——解析失败就重新请求,连续失败就降级到备用模型。
4.2 坑二:低估了输出格式的脆弱性
轻量提示词对输出格式的约束力比我想象的弱。即使我明确写了"严格输出 JSON,不要有任何额外文字",模型还是有大约 3% 的概率会加上"好的,以下是审查结果:"这样的前缀。
解决办法是在提示词里加一句强约束,同时在代码里做容错解析。我的做法是用正则先提取第一个{到最后一个}之间的内容,再尝试解析。这个简单的处理把格式错误率降到了 0.1% 以下。
4.3 坑三:上下文拼接顺序影响结果质量
迁移到 Pi 架构后,上下文由代码拼接,我一开始把代码片段放在最前面,任务说明放在后面。结果模型经常"忘记"任务要求,直接开始泛泛地评论代码风格。
调整后我把顺序改成:任务说明 → 输出格式提醒 → 代码片段。这个顺序让模型先明确目标,再看代码,输出质量明显提升。这个细节在重型框架里是被系统提示词掩盖的,迁移后才暴露出来。
4.4 坑四:缓存策略没跟上
轻量提示词的一个隐藏优势是更容易命中 Prompt Cache。但前提是你的提示词前缀要稳定。我一开始在系统提示词里动态插入了日期和项目名,导致每次请求前缀都不同,缓存完全失效。
后来我把所有动态内容都移到用户消息里,系统提示词保持完全静态。这一改,缓存命中率从不到 20% 提升到 85% 以上,成本又降了一截。
4.5 坑五:过度精简导致边界场景翻车
有一次用户提交了一段空代码,模型返回了{"issues": []},看起来正常。但实际上空代码应该被识别为无效输入,给出提示。这个问题在重型框架里被系统提示词的边界说明覆盖了,精简后暴露出来。
我的修复方式是在调用层加输入校验,空输入直接拦截,不发给模型。这又回到了 Pi 的核心思路:边界情况用代码处理,不要指望模型。
5. 让 200 Token 提示词稳定工作的配套工程
5.1 输入预处理:把模型当"纯函数"用
Pi 架构里,模型被当成一个纯函数:输入确定的代码文本,输出确定的结构化结果。所有不确定性都在调用层消化掉。
我的输入预处理流程是这样的:
- 接收原始请求,提取代码片段和语言类型
- 校验代码非空、长度在合理范围内
- 根据语言类型选择对应的审查规则模板
- 拼接成固定格式的用户消息
- 发送给模型
这个流程里没有任何"让模型自己判断"的环节。语言类型是调用方传进来的,不是模型猜的;代码长度是代码校验的,不是模型判断的。模型只做一件事:看代码,找问题。
5.2 输出后处理:结构化解析与降级
输出后处理是保证稳定性的关键。我的处理链路:
import json import re def parse_review_result(raw_text): # 第一步:尝试直接解析 try: return json.loads(raw_text) except json.JSONDecodeError: pass # 第二步:提取 JSON 片段 match = re.search(r'\{.*\}', raw_text, re.DOTALL) if match: try: return json.loads(match.group()) except json.JSONDecodeError: pass # 第三步:降级处理 return {"issues": [], "parse_error": True, "raw": raw_text[:500]}这个三段式解析把格式异常的影响降到了最低。即使模型输出完全跑偏,系统也不会崩溃,只是返回一个标记了 parse_error 的空结果,后续可以人工介入。
5.3 重试与降级策略
我设置了两级重试:
- 第一级:解析失败时,用同样的输入重新请求一次,温度参数调低
- 第二级:连续两次失败,切换到备用模型(我用的是另一个厂商的轻量模型)
实测下来,第一级重试能解决 90% 的格式问题,第二级几乎用不上。但这个降级链路必须存在,因为生产环境不能接受单点故障。
5.4 监控指标:我盯哪几个数
迁移后我加了一套监控,重点盯四个指标:
| 指标 | 目标值 | 告警阈值 |
|---|---|---|
| 单次请求平均 Token 消耗 | < 1500 | > 2500 |
| JSON 解析成功率 | > 99.5% | < 99% |
| 缓存命中率 | > 80% | < 60% |
| P99 响应延迟 | < 3s | > 5s |
这四个指标基本覆盖了成本、质量、性能三个维度。特别是缓存命中率,它直接决定了实际账单,很多人会忽略。
6. 迁移后的实际收益与适用边界
6.1 账单变化的真实数据
迁移完成后的第一个完整月,我对比了前后数据:
- 总 Token 消耗:下降87%
- API 账单金额:下降85%(金额降幅略低于 Token 降幅,因为输出 Token 占比上升了)
- 平均响应时间:从 4.2 秒降到 2.1 秒
- 用户满意度评分:从 3.8 提升到 4.3(5 分制)
响应时间下降是个意外收获。系统提示词短了,模型处理输入的时间自然减少,加上缓存命中率提升,整体延迟几乎腰斩。
6.2 什么场景适合迁移,什么场景别动
这套方案不是万能的。我总结了一个简单的判断标准:
适合迁移的场景:
- 任务边界清晰,输入输出格式可控
- 用户群体固定,不需要覆盖长尾需求
- 成本敏感,调用量大
- 团队有能力维护调用层代码
不建议迁移的场景:
- 需要模型自主决策调用哪些工具
- 用户输入高度不可预测
- 团队没有工程能力维护调用层
- 对通用性要求高于成本要求
如果你的场景落在"不建议"那一栏,强行迁移只会带来更多麻烦。重型框架的存在是有道理的,它用 Token 换来了通用性和省心。
6.3 一个容易被忽略的隐性收益
除了账单和延迟,迁移还带来一个隐性收益:调试变得简单了。
重型框架出问题时,你很难定位是系统提示词的哪一段导致的。1.4 万 Token 里任何一句话都可能影响模型行为,排查起来像大海捞针。轻量方案里,系统提示词就 200 Token,一眼看完,问题基本都能定位到调用层的某段代码。
这个收益在长期维护中价值巨大。我迁移后处理线上问题的平均时间从 2 小时降到了 20 分钟,因为排查范围从"整个提示词"缩小到了"几段明确的代码"。
7. 如果你也想动手,我的建议顺序
7.1 先测量,再动手
别急着改代码。第一步是把当前方案的系统提示词完整导出,统计 Token 构成,找出占比最大的模块。很多时候你会发现,最大的那块恰恰是你最不需要的。
我当时的统计花了大概半天,但正是这半天让我明确了优化方向,避免了盲目删改。
7.2 从最独立的模块开始迁移
不要一次性全改。选一个边界最清晰、依赖最少的模块先试。我第一个迁移的是"代码风格检查",因为它输入输出最简单,迁移风险最低。跑通之后再逐步迁移更复杂的模块。
7.3 保留回滚能力
迁移期间一定要保留旧方案的调用入口,通过配置开关切换。我用了两周的灰度期,新方案先接 10% 流量,观察指标稳定后再逐步放大。这个过程中旧方案一直可用,心里踏实很多。
7.4 别追求极致压缩
200 Token 不是目标,够用就好。我见过有人为了把提示词压到 100 Token 以内,把必要的约束也删了,结果输出质量崩盘。压缩的边界是"模型还能稳定完成任务",超过这个边界就是过度优化。
我在实际使用中发现,系统提示词从 1.4 万降到 200 带来的收益是巨大的,但从 200 再往下降,收益就非常有限了,反而风险陡增。找到那个平衡点,比追求极限数字重要得多。
最后分享一个小技巧:迁移完成后,我建议你保留一份"重型方案"的配置,用于处理那些轻量方案搞不定的复杂请求。两套方案并存,按请求复杂度路由,这样既拿到了成本优势,又保留了应对长尾场景的能力。这个混合架构我用了大半年,是目前最满意的方案。