上下文窗口就那么大,Agent 设计应该怎么分
上一篇讲工具。工具查回来、算出来的东西,最后都要塞进模型的上下文,好让它做下一步判断。这篇就讲怎么经营这块上下文。
先说一件容易被忽略的事:模型没有记性。它不「记得」上一轮发生过什么。每一轮,它知道的一切,就是这一次喂进它窗口的那些字。窗口里有的,它才看得见;窗口里没有的,对它就不存在。
所以经营好这个窗口,是做专用 Agent 里一门要专门花心思的手艺。忽视它,你的 Agent 会又贵、又慢、还越聊越糊涂。
为什么?因为窗口是稀缺的。一趟自驾行程排下来,模型可能要调十几次工具:搜目的地、并行核实五六个垭口、估七八段驾时、查机酒。如果每个工具的原始返回都原样堆进上下文,窗口会以肉眼可见的速度膨胀。尤其是联网搜索,它的返回又长(每条结果一大段摘要)又多(一趟排线要搜很多次),是上下文膨胀的头号主犯。
不管它,后果是复利式的:等模型搜到第十次,前九次的完整网页摘要都还压在窗口里,它每一轮都要重读一遍早就用过、再也用不上的东西。又贵,又慢,又糊涂。
那怎么办?我在野行 Y 里做的第一件事,是把一个工具返回的两个读者分开伺候。
一个工具的返回值,其实有两个去处,它们要的东西完全不同。一个是喂回模型的上下文,模型要拿它做下一步判断,越精炼越好,无关字段都是负担。另一个是给用户看的活动流,「搜到 5 条:稻城亚丁攻略、折多山路况」这种一行人话,让用户知道 Agent 正在干嘛。
野行 Y 用两个函数把它们分开。喂给模型的那份做瘦身:一条联网搜索结果,模型真正用得上的只有标题和一小段摘要,用来判断这个景点是不是真的、这条路通不通。url、多余字段、超长正文,模型写行程时根本用不到。所以进模型之前,砍掉它们,每条只留标题和截到一百五十字的摘要,最多留六条。而给用户看的那份不动,仍然拿完整数据,另压成一行人话。同一个返回,喂模型的瘦,给人看的全,各取所需。实测下来,单次搜索的 token 降了一半多,因为是复利的,整趟排线省下来的量很可观。

这里有个容易做错的地方,值得强调:瘦身别无脑地把所有工具结果都截断。
野行 Y 里只有联网搜索被瘦身,别的工具原样放行。为什么?因为像查真实机酒这类工具,它的返回是要原样透传给用户去选的真实航班和酒店,价格、航班号、订票链接一个字段都不能少,砍了就出错。而估驾时那种工具,返回本就紧凑,没必要动。判断哪个字段模型用得到、哪个工具的数据碰都不能碰,这才是上下文工程的核心。压缩这个动作谁都会写,知道压什么、留什么,是领域判断。
瘦身是「砍字段」,还有一招更进一步:在信息进窗口之前,就把它蒸馏成结论。
上一篇提到的并行核实工具是最好的例子。模型一次交给它五个要核实的问题,比如五个垭口的路况,它并行搜完,拿到的是五坨原始搜索摘要。如果直接塞进上下文,又是五大段。但它没这么干。它在返回之前,先用一次模型调用,把每个主题的摘要压成一句事实结论,比如「折多山:十月通常可通行,遇强降雪偶有临时管制,出发前查路况」。进到主循环上下文里的,是五句这样的结论,不再是五坨摘要。压缩发生在信息进窗口之前,在工具边界就地完成。噪音留在工具内部,干货回主循环。

窗口里的内容,除了对话历史和工具返回,还有一部分是你主动塞进去的。这部分同样是上下文工程,而且往往决定了 Agent 像不像个懂行的人。
野行 Y 每次运行,会把「今天是几月几号」实时拼进 system prompt。听着是小事,可少了它,模型会拿训练时记住的那个默认日期给你算机票、给你填一个早就过去的出发日。一句注入,根治一类幻觉。老用户开一趟新行程,系统还会把他的画像,去过哪些地方、平时偏好什么,提炼成一小段拼进第一轮。于是 Agent 一上来就「记得」这个人,能说「你上次去过川西,这次给你换个方向」。注意是提炼成一小段,别把用户所有历史全倒进去,那又会把窗口撑爆。
回头看,上下文工程做的就是这么几件事:往窗口里放什么、把什么挡在外面、太长了在哪压、除了对话还要注入什么。它是模型每一轮判断的唯一依据,值得你像经营一块稀缺的地一样经营它。
不过这块窗口只管一次会话里的事。用户是谁、去过哪、偏好什么,这些不该每次从零开始,得有个地方跨会话记住。那是另一套东西,叫记忆系统。下一篇,讲怎么让 Agent 记得你,又不至于记岔了、把没发生的事当成真的。
更多推荐


所有评论(0)