1. 从 1.4 万 Token 到 200 Token:一次让我肉疼的账单复盘
第一次看到月度账单的时候,我盯着那个数字看了足足半分钟,以为自己看错了小数点。那段时间我在做一个代码辅助类的工具链,日常调用量不算夸张,但账单硬是比预期高出一大截。排查了一圈,模型没换、调用次数没暴涨、缓存也开了,最后把矛头指向了一个平时最容易被忽略的东西——系统提示词。
我拉出后台的请求日志,把每次调用的输入 Token 拆开看,结果很扎心:单次请求里,真正属于用户问题的部分平均只有几百 Token,而系统提示词那一坨,稳定在 1.4 万 Token 上下。也就是说,我每问一句"帮我看看这段代码哪里有问题",都要先白白烧掉一万四千个 Token 的"入场费"。按当时的调用频率算下来,光系统提示词这一项,就吃掉了整体成本的八成以上。
这件事让我彻底改变了对"提示词工程"的理解。以前我总觉得系统提示词写得越详细越安全,角色设定、行为准则、输出格式、边界条件、示例对话,恨不得把能想到的都塞进去。但真实跑起来才发现,系统提示词不是越长越聪明,而是越长越贵,而且贵得毫无意义——因为绝大部分内容在绝大多数请求里根本用不上。
后来我把整套方案从原来那套重型框架迁移到了一个更轻量的实现(下文统一叫它 Pi),核心动作就一件事:把系统提示词从 1.4 万 Token 砍到 200 Token 左右,同时保证功能不掉链子。最终账单降了大约 85%,而工具的实际可用性反而提升了。这篇就把整个迁移过程、踩过的坑、以及每一步背后的取舍逻辑,完整地摊开讲一遍。
如果你也在做类似的代码辅助工具、Agent 应用,或者任何需要频繁调用大模型的场景,并且发现账单里有一大块说不清道不明的开销,那这篇内容大概率能帮你省下一笔真金白银。我会尽量说人话,把"为什么这么改"讲透,而不是只丢一堆配置让你抄。
2. 拆解那 1.4 万 Token:系统提示词到底胖在哪
2.1 把提示词按功能分块称重
要减肥,先得知道肥肉长在哪。我把原来那份 1.4 万 Token 的系统提示词完整导出,按功能切成几块,然后逐块估算 Token 占比。这里用的是最朴素的办法:把每一块单独丢进分词工具里数,虽然不同模型的分词器有差异,但比例关系是准的。
| 功能模块 | 大致 Token 占比 | 是否每次请求都必要 |
|---|---|---|
| 角色与人设描述 | 约 12% | 否,多数场景用不上 |
| 行为准则与禁令清单 | 约 25% | 部分必要,但严重冗余 |
| 输出格式模板与示例 | 约 30% | 否,可按需注入 |
| 工具调用说明 | 约 18% | 仅工具场景必要 |
| 边界条件与异常处理 | 约 10% | 否,可后置 |
| 其他杂项与重复表述 | 约 5% | 完全可删 |
看完这张表你应该能感觉到问题所在了:真正每次请求都必须存在的,可能连 20% 都不到。剩下 80% 的内容,要么是特定场景才需要,要么是同一件事换着说法重复了三遍。
2.2 冗余是怎么一点点堆出来的
我复盘了一下这份提示词的"生长史",发现它完全是典型的"补丁式膨胀"。最开始只有几百 Token,后来遇到一个边界情况,加一段说明;用户反馈输出格式不对,加一段格式约束;某次工具调用出错,又加一段工具使用规范。每次加的时候都觉得"就加一点点,没关系",但半年下来就滚成了一万多。
更隐蔽的问题是重复表述。比如"不要编造不存在的信息"这条,在角色描述里出现了一次,在行为准则里又出现一次,在异常处理里还换了个说法出现一次。模型读三遍和读一遍,效果几乎没差别,但 Token 是实打实付了三份钱。
还有一个容易被忽略的点:示例对话的 Token 成本极高。一个完整的 few-shot 示例,动辄几百上千 Token,如果塞了五六个示例,光这一项就能占掉三四千。而实际上,很多示例在真实请求里根本触发不到。
2.3 为什么"长提示词更安全"是个错觉
很多人(包括当时的我)有个根深蒂固的错觉:提示词写得越全,模型越不容易出错。但实际测下来,这个假设在超过某个长度后会失效。
原因有两个。第一,注意力会被稀释。当系统提示词长达上万 Token 时,模型对其中某一条具体规则的关注度是下降的,你写得再多,它也可能"看漏"。第二,规则之间会打架。我那份提示词里就有互相矛盾的条目,比如一处要求"回答尽量简洁",另一处又要求"详细解释每一步",模型只能自己猜,结果就是输出风格飘忽不定。
所以砍提示词不是单纯为了省钱,它同时也在提升输出的稳定性和可预测性。这一点是我迁移之后才真正体会到的——账单降了是明面上的收益,输出质量变稳是暗地里的惊喜。
3. 迁移到 Pi 的核心思路:把"常驻"和"按需"彻底分开
3.1 常驻提示词只保留"不可变的最小集"
迁移的第一原则,是重新定义什么叫"系统提示词"。在 Pi 的方案里,常驻部分只保留那些每一次请求都绝对必要、且永远不变的内容。我最后压到 200 Token 左右,里面只有三样东西:
- 一句话的角色定位,比如"你是一个代码辅助助手";
- 最核心的一到两条硬约束,比如"不确定的信息要明确说明";
- 输出语言和基本风格的一句话约定。
就这些。没有长篇人设,没有格式模板,没有示例对话,没有工具说明。你可能会担心这样模型会不会"放飞自我",实测下来完全不会——因为剩下的约束,我们换了一种更聪明的方式注入。
3.2 按需注入:让每一段提示词都"物有所值"
关键思路是:把原来常驻的大块内容,拆成一个个独立的小模块,只在真正需要的时候才拼进请求里。
举个具体例子。原来"输出格式模板"是常驻的,每次请求都带着。但实际只有当我需要结构化输出(比如让它返回 JSON)时,格式约束才有意义。于是我把格式模板抽成一个独立片段,只有当次任务确实需要结构化输出时,才把它拼到用户消息前面。
同理,工具调用说明只在涉及工具的场景注入,异常处理规范只在检测到复杂任务时注入。这样一来,平均每次请求实际携带的提示词总量,从 1.4 万降到了几百到一千出头,而且每一段都是当前任务真正用得上的。
3.3 用"任务分类"决定注入哪些模块
按需注入要落地,得有个判断依据。我的做法是在请求进入模型之前,先做一次极轻量的任务分类。这个分类不需要调用大模型,用关键词匹配加简单规则就够了。
比如请求里出现"返回 JSON""结构化""字段"这类词,就判定为需要格式模块;出现"调用""执行""运行"这类词,就判定为可能需要工具模块。分类逻辑本身几乎不消耗资源,但它决定了这次请求要拼哪些片段,直接影响到成本和效果。
提示:任务分类的规则不要写得太复杂,否则维护成本会超过它省下的钱。我一开始写了三十多条规则,后来砍到八条,效果反而更稳,因为规则越多越容易互相冲突。
3.4 为什么这套思路比"精简长提示词"更彻底
有人可能会问:我直接把那 1.4 万 Token 压缩成 3000 Token 不就行了,何必搞这么复杂?
区别在于上限。精简长提示词只是把固定成本从 1.4 万降到 3000,但每次请求还是要付这 3000。而按需注入是把固定成本降到 200,剩下的按场景浮动。对于绝大多数请求来说,实际成本远低于 3000。更重要的是,按需注入的架构可扩展——以后要加新功能,只需要新增一个片段,而不是往那个已经臃肿的常驻提示词里再塞一段。
4. 实操落地:从旧框架切到 Pi 的完整步骤
4.1 第一步:把旧提示词做"考古式"拆解
别急着删。先把现有系统提示词完整导出,然后逐段标注:这段是干什么的、什么场景下才需要、删掉会怎样。我当时的做法是给每一段打三个标签——"常驻/按需/可删"。
这个过程很枯燥,但极其值得。因为你会发现,很多段落你自己都说不清它为什么在那,只是"一直就在那"。凡是说不清用途的,基本都可以归到"可删"。
4.2 第二步:建立片段库,而不是一个大文件
拆完之后,把保留下来的内容按功能归类成独立片段。我的片段库大概长这样:
prompts/ base.txt # 常驻最小集,约 200 Token format_json.txt # 结构化输出约束 format_markdown.txt tool_usage.txt # 工具调用说明 error_handling.txt few_shot_code.txt # 代码场景示例每个文件只干一件事,内容尽量短。这样在代码里拼装的时候,逻辑非常清晰:需要什么就加载什么。
4.3 第三步:写拼装逻辑,注意顺序
拼装顺序会影响模型的理解。我的经验顺序是:常驻基础 → 任务相关约束 → 格式要求 → 示例 → 用户输入。把用户输入放在最后,是因为模型对末尾内容的注意力通常更强,把真正的问题放在最后,回答会更聚焦。
def build_prompt(user_input, task_type): parts = [load("base.txt")] if task_type == "structured": parts.append(load("format_json.txt")) if task_type == "tool": parts.append(load("tool_usage.txt")) if task_type == "complex": parts.append(load("error_handling.txt")) parts.append(user_input) return "\n\n".join(parts)这段逻辑简单到几乎不需要注释,但它就是省钱的核心。原来每次都要背着一万四的包袱,现在轻装上阵。
4.4 第四步:灰度切换,别一把梭
迁移最忌讳的就是全量切换。我的做法是先切 10% 的流量到新方案,对比两边的输出质量和成本,跑够样本量再逐步放大。中间确实发现过问题,比如某个格式片段在特定场景下没被正确注入,导致输出格式错乱。如果一上来就全量切,这种问题会直接暴露给所有用户。
灰度期间我重点盯三个指标:单次请求平均 Token 数、输出格式错误率、用户重试率。前两个是成本和质量的直接体现,第三个能反映用户是否觉得"答得不对"。
4.5 第五步:把省下来的钱花在刀刃上
成本降下来之后,别急着高兴。我做的第一件事是把省下的预算重新分配到"提升效果"上,比如在真正复杂的任务里允许注入更多示例,或者对高价值请求使用更强的模型。省钱不是目的,把钱花在真正影响效果的地方才是。这也是为什么我强调按需注入——它让你有能力对不同的请求做差异化投入。
5. 迁移过程中踩过的坑与排查链路
5.1 坑一:片段注入顺序错了,模型开始"失忆"
灰度期间出现过一次诡异现象:某些请求里,模型完全忽略了我注入的格式约束,返回的还是自由文本。排查了半天,最后发现是拼装顺序的问题——我把格式片段放在了用户输入之后,模型读到用户问题时,格式约束已经被"淹没"了。
修复很简单,把格式约束移到用户输入之前。但这个坑让我意识到,按需注入不是简单地把片段拼起来,顺序本身就是一种信息。后来我固定了拼装顺序,并在代码里加了断言,防止以后有人乱改。
5.2 坑二:任务分类误判,该注入的没注入
有一次用户明确要求返回 JSON,但分类规则没匹配上,结果格式片段没注入,输出自然不是 JSON。用户重试了三次,体验很差。
排查链路是这样的:先看请求日志确认分类结果,再看拼装后的完整提示词,最后对比预期。定位到是关键词覆盖不全。修复方式不是加更多关键词,而是加一个兜底规则——如果用户输入里出现了明显的格式信号(比如大括号、方括号),就强制注入格式片段。兜底规则比穷举关键词可靠得多。
5.3 坑三:过度精简导致边界情况失控
砍到 200 Token 之后,有一类请求开始出问题:涉及模糊需求时,模型不再主动追问,而是直接给一个可能不对的答案。原因是原来那条"遇到不确定要追问"的约束被我一起砍掉了。
这个坑的教训是:精简不等于无脑删,要区分"冗余"和"关键约束"。后来我把"不确定要说明"这条加回了常驻集,因为它几乎在所有场景下都必要。常驻集可以小,但必须覆盖那些"任何场景都不能少"的底线规则。
5.4 坑四:缓存策略没跟上,白省了
按需注入之后,提示词内容变得高度动态,这会影响缓存命中率。我一开始没注意,后来发现缓存基本失效了,省下的 Token 又被缓存失效抵消了一部分。
解决办法是把常驻部分和动态部分分开处理:常驻的 200 Token 走缓存,动态片段不缓存。这样既保住了缓存收益,又保留了按需注入的灵活性。这个细节很容易被忽略,但对最终账单影响不小。
5.5 排查这类问题的通用思路
踩完这几个坑,我总结出一套排查流程,遇到"输出不对"时按顺序走:
- 先看分类结果对不对,判断该注入的片段有没有被选中;
- 再看拼装后的完整提示词,确认顺序和内容;
- 然后单独用这份提示词复现,排除其他变量;
- 最后对比新旧方案的差异,定位是哪一段的增删导致的。
这套流程的好处是每一步都能独立验证,不会在一堆可能性里瞎猜。按需注入的系统,问题往往出在"拼装"环节,而不是模型本身。
6. 账单降 85% 之后,我重新理解了提示词工程
6.1 成本结构变了,优化重点也变了
迁移之前,我的成本大头是固定开销,优化空间有限,因为那 1.4 万 Token 是雷打不动的。迁移之后,固定开销几乎可以忽略,成本变成了"按需浮动"。这时候优化的重点就从"怎么把提示词写短"变成了"怎么让分类更准、注入更精准"。
这个转变很有意思:当固定成本消失后,你才有余力去做精细化的效果优化。以前每加一个示例都要掂量成本,现在可以放心地在真正需要的场景里加。
6.2 提示词不是资产,是负债
这是我这次迁移最大的认知转变。以前我把那份精心打磨的长提示词当成核心资产,觉得它凝聚了大量经验。但换个角度看,每一段常驻提示词都是一笔持续支出的负债,它在你每次请求时都要收费,哪怕这次根本用不上。
真正有价值的不是"写了一份很全的提示词",而是"建立了一套能按需组装提示词的机制"。前者是静态的、会膨胀的、越来越贵的;后者是动态的、可维护的、成本可控的。
6.3 给还在用重型提示词的人几句实在话
如果你现在也在维护一份几千甚至上万 Token 的系统提示词,我的建议是:先别急着大改,花一个小时把它按功能拆开,标出哪些是每次都用得上的。你大概率会发现,真正必要的部分少得惊人。
然后从最小的改动开始——把那些明显只在特定场景才需要的段落抽出来,改成按需注入。哪怕只做这一步,成本也会有肉眼可见的下降。等跑顺了,再逐步把常驻集压到最小。
最后分享一个我一直在用的小习惯:每次想往常驻提示词里加东西之前,先问自己一句"这条在多少比例的请求里真的用得上"。如果答案是"不到一半",那它就不该常驻,而应该进片段库。这个习惯帮我挡住了无数次"就加一点点"的冲动,也是这套方案能长期保持轻量的关键。