终于动态选择工具和kv cache不冲突了
动态筛选工具塞给 Agent,以为在省 Token,结果把 KV Cache 全干废了。后来看了 Claude Code 的做法,才明白正确姿势是只往末尾加东西。
前阵子做 Agent,我把好多个工具的 JSON Schema 全塞给了它。
效果嘛。模型面对一堆密密麻麻的工具定义,Token 烧得飞快,工具还老选错。
然后我就想,那我动态筛一下呗。每次请求前先猜用户要什么工具,挑几个拼进去就好了。
听起来很聪明对吧。
后来我去看了一眼推理引擎底层是怎么跑的,然后默默把这段代码删了。
先说个背景。大模型的推理引擎搞了个缓存机制,叫 KV Cache。算过的中间结果存显存里,下次请求开头一样就直接复用,不重算。
但这个"开头一样"的要求极其变态:从第 0 个 Token 开始,逐字节一致。中间多一个逗号,后面所有词的位置编码全偏移,缓存从那个位置往后全部作废。
回头看我的"动态工具筛选"——
第一轮:[系统指令] + [工具1] + [聊天记录],缓存好了。
第二轮:用户要算数,我在前面加了个计算器工具。变成 [系统指令] + [工具1, 工具2] + [聊天记录]。
就这一个改动,后面几万字聊天记录的位置全变了。缓存归零,从头再算。
我以为自己在优化成本。其实我在给云计算厂商做慈善。
我删完代码以后,发现最近 Claude Code 和新版 GPT 原生支持了动态工具检索,他们是怎么做到维持缓存的?
说白了就一个原则:只往末尾加东西,绝不往前面插。
简单的说就是:System Prompt 开头写得极其简单,就一句话——你有很多工具,需要的时候自己搜。这部分永远不变,永久缓存。用户问了个问题,模型自己判断工具。系统找到工具的定义,但不往开头塞,而是当成一条新消息追加到对话最末尾。
前面几万字一个字没动,缓存全部命中。模型只对末尾新加的几百个 Token 做增量计算。
这里有个容易忽略的前提:工具定义追加在最后面,模型读完之后得还记得前面在干嘛。这其实是指令微调的功劳,新一代模型在抗"近因效应"上做了专门训练。不然工具说明书放在最后,模型读完就把自己是谁给忘了,那也白搭。
顺便说两个相关的坑。
以前为了控制上下文长度,大家习惯用滑动窗口——只保留最近几轮对话,把最早的删掉。但你删掉第一条,后面所有对话的位置就全变了,缓存一样崩。你以为在省钱,其实在烧钱。
替代方案是让记录一直追加,快到上限了让模型自己总结一份摘要,塞回 System Prompt 当新的前缀,清空旧记录重新来。会有一次重算,但换来大量空间。
还有就是核心工具别走动态检索,直接写死在系统指令里享受永久缓存。Tool Search 留给那种几百个 API 的长尾工具库。
幸好这些代码可以让 AI 自己写了。