我为什么给 Agent 上下文组装预留后续必需片段预算
我为什么给 Agent 上下文组装预留后续必需片段预算
最近看 interview-guide 里的 Agent 上下文组装逻辑时,我注意到一个很容易被忽略、但实际上很影响稳定性的细节:上下文预算不是“按优先级从前往后塞满”就完了,而是要主动给后面的必需片段留位置。
这件事听起来有点细,但我觉得它比“多截断一点文本”重要得多。因为 Agent 的上下文不是普通的字符串拼接,它背后是一次有顺序、有层次的决策输入。如果前面的高优先级内容把预算吃光,后面本该保底出现的信息直接消失,模型拿到的就不是“精简版上下文”,而是“结构被破坏的上下文”。
为什么只看优先级还不够
很多人做这类上下文裁剪,第一反应都是对的:最新用户消息最重要,当前目标次之,记忆摘要、事实、资源绑定、已用工具再往后排。AgentContextAssemblyService 也是先把这些候选片段按固定顺序列出来,这一步没问题,代码在 interview-guide/app/src/main/java/interview/guide/modules/agent/service/AgentContextAssemblyService.java 里写得也比较清楚。
问题出在第二步。如果系统只是按顺序遍历,然后对当前片段说“剩多少就吃多少”,那第一个长片段就很可能把预算挤爆。结果就是排序虽然稳定了,但最终保留下来的上下文并不稳定。一次用户消息长一点,后面的 goal、memory_state 或 resource_bindings 就可能被挤没;下一次用户消息短一点,这些字段又冒出来。对模型来说,这种抖动比单纯截断更难处理。
我更认同的做法是:优先级决定先后,但必需片段要有保底预算。也就是说,前面的内容再重要,也不能把后面必须解释当前会话状态的片段全部吃掉。
这段实现真正聪明的地方
AgentContextAssemblyService 里最关键的不是 trimToLength(),而是 reserveRequiredBudget() 这一步。它会在处理当前片段之前,先往后看一眼,把后续所有 required=true 的片段做一遍最小预算预留。这样当前片段能使用的最大字符数,不是“剩余总预算”,而是“剩余总预算 - 后续必需片段保底空间”。
对应代码可以直接看这几个位置:
interview-guide/app/src/main/java/interview/guide/modules/agent/service/AgentContextAssemblyService.javainterview-guide/app/src/test/java/interview/guide/modules/agent/service/AgentContextAssemblyServiceTest.java
实现上它做了三件很对的事。
第一,候选片段先固定顺序,再裁剪。这样同样输入一定得到同样的分段顺序,不会出现一次先放 confirmed_facts、一次先放 used_tools 的漂移。
第二,必需片段和可选片段区别对待。可选片段在预算太紧时可以直接 OMITTED,原因是 budget_exhausted;但必需片段即使空间很紧,也会尽量保住最小可解释长度,而不是直接消失。这一点很重要,因为 required 不只是“优先级高”,而是“缺了以后上下文会失真”。
第三,预算核算按真实渲染成本来算,而不是只算正文字符数。像标题前缀、换行分隔这些最后真的会进 prompt 的内容,也会算进 renderSectionCost()。这让 AgentContextBudget 记录的 usedChars 更可信,后面做调试或者压测时,不会出现“明明说用了 400 字,实际 prompt 多了一截”的错位。
为什么这是 Agent 工程问题,不只是字符串处理
我觉得这类代码最容易被低估的地方,就是很多人会把它看成“普通文本截断”。其实不是。它更像是把多种上下文源压缩成一个仍然可推理的输入结构。
比如这里的 latest_user_message 和 goal 虽然优先级最高,但它们不会重复渲染进 promptContextSummary;memory_state、confirmed_facts、resource_bindings 则承担了“把会话状态讲明白”的职责。如果预算策略只追求把前面的文本尽量塞满,很可能得到一个看似字数利用率很高、但语义结构很差的 prompt。
所以我现在会把这类问题理解成两个层次:
一层是容量控制,避免上下文无限膨胀;
一层是信息保真,保证裁剪后还保留足够的行为解释力。
reserveRequiredBudget() 解决的其实就是第二层。它不是在追求绝对公平,而是在维护一个最小可运行语义结构。这个思路很像线上接口做降级时,不是把所有能力一起打折,而是先守住主流程。
如果面试里聊到上下文工程,我会怎么讲
如果有人问我怎么做 Agent 上下文裁剪,我现在不会只说“按优先级截断”。这个答案太薄了,因为它默认所有片段都只是普通字符串。
我会直接补一句:上下文源需要先分成必需片段和可选片段,再按优先级装配,同时给后续必需片段预留最小预算。否则高优先级长文本会把低位但必要的状态信息挤掉,最后 prompt 看起来更短了,实际可用性反而更差。
拿这个仓库里的实现来说,AgentContextAssemblyService 的价值不在于它做了多少裁剪,而在于它把“稳定顺序、预算预留、显式省略原因、真实成本统计”这几件事放到了一起。这样后面不管是排查模型回答失真,还是解释为什么某段上下文没进 prompt,都有明确依据。
很多 Agent 系统后面答非所问,不一定是模型笨,也不一定是提示词写得差,反而经常是上下文装配太粗糙。这个坑挺工程化,但也挺真实。🙂
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐
所有评论(0)