ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

【AgentScope 2.0】10-上下文工程:压缩与卸载——Agent 的工作记忆怎么不爆、不臃肿

【AgentScope 2.0】10-上下文工程:压缩与卸载——Agent 的工作记忆怎么不爆、不臃肿 源码地址后端地址 前端地址第 09 集结尾预告了上下文工程。这一篇讲 Agent 上下文的两个「保命机制」压缩Compaction和卸载Eviction。一句话先给结论模型窗口有硬上限但对话会一直增长、工具结果会很大——压缩把旧对话合成摘要卸载把超大工具结果搬出上下文。平台侧做三件事配好阈值、覆盖框架默认排除集、把压缩摘要正确渲染给前端。为什么需要它上下文不是无限的Agent 每一轮推理都要把整个上下文送给模型。上下文有两个敌人会超长模型窗口是硬上限几万到几十万 token超出直接 400。长对话不处理早晚撞墙。会臃肿单个工具调用可能返回巨大结果——grep一个全仓库匹配几百行、list_files一个巨型目录。这些结果只有「当时」有用塞进上下文却是每轮都要付的 token 账单。框架HarnessAgent对这两个敌人内置了两套机制对应两个配置类都是io.agentscope.harness.agent.memory.compaction包CompactionConfig对话压缩和ToolResultEvictionConfig大工具结果卸载。平台在BaseFinanceAgentFactory里装配第 147-153 行.compaction(CompactionConfig.builder().triggerMessages(50)// 上下文消息数达到 50 条触发压缩.keepMessages(20)// 压缩后保留尾部 20 条.build())// V2.23: 覆盖默认排除集见 {link #buildToolResultEvictionConfig}。.toolResultEviction(buildToolResultEvictionConfig())记住两个数字50 条触发、留 20 条、80K 字符卸载阈值。压缩Compaction旧对话变摘要细节换空间压缩的契约框架CompactionMiddleware平台侧能看到的行为在 ep09 已经见过触发上下文消息数达到triggerMessages50→ 触发一轮压缩。产物压缩后 context 被清空重放——一条名为__compaction_summary__的 USER 消息ConversationCompactor.SUMMARY_MSG_NAME承载历史摘要后面跟着keepMessages20条保留的尾部消息。AgentState.summary字段恒为空串——这个细节是 ep09 那个「压缩后刷新丢失」事故的根因之一别忘。落地压缩产物经DbAgentStateStore落库——旧行archived1归档摘要尾部全量重写为 NORMAL blockep09 的「归档 全量重写」路径。渲染前端历史接口把__compaction_summary__显示为system 消息而不是用户发言——这是本集要重点看的一段代码。ChatHistoryService.assemble()第 124-169 行把 DB block 还原成前端消息。它的类头注释第 37-45 行写清了转换规则其中默认分支USER 消息里藏着 V2.24 的适配第 154-165 行default-{// USER含 null 容错// V2.24: harness 压缩摘要name__compaction_summary__ 的 USER 消息// ConversationCompactor.SUMMARY_MSG_NAME显示为 system 消息而非用户发言if(__compaction_summary__.equals(m.getName())){out.add(newHistoryMessageDto(system,m.getTextContent(),null,epochOf(m,block)));}else{out.add(newHistoryMessageDto(user,m.getTextContent(),null,epochOf(m,block)));}lastAssistantIdx-1;}为什么要按 name 特判因为压缩摘要虽然物理上是一条 USER 消息为了进 context 参与模型推理但对用户来说它不是「我说的话」——它是对已归档历史的总结属于系统信息。同一处还兼容 V2.7 的老压缩产物block_typeSUMMARY的块content 为纯文本直接渲染为 system第 128-134 行。这条路径串起来就是完整的压缩闭环触发 → 生成摘要 → 落库归档重写→ 历史接口还原成 system 消息。任何一环断了要么上下文不压缩超长 400要么压缩后历史丢失ep09 事故要么用户看到「自己说过一句摘要」体验崩坏。卸载Eviction大工具结果落盘上下文只留预览压缩处理「历史太多」卸载处理「单条太大」。框架ToolResultEvictionConfig的默认行为工具结果超过 80K 字符DEFAULT_MAX_RESULT_CHARS落盘到large_tool_results/上下文只放头尾预览——结果仍可查文件在模型不再为它每轮付 token。但这里有个平台必须覆盖的坑框架默认排除集。ToolResultEvictionConfig.defaults()把list_files/grep_files/glob_files列入排除集Javadoc 理由写的是 “self-limiting outputs”——认为这三个工具的输出有天然上限卸载反而有害。BaseFinanceAgentFactory.buildToolResultEvictionConfig()的注释第 195-217 行把证伪过程完整记了下来实测证伪这三个工具对大仓库返回全量匹配行、无任何上限FilesystemTool结果用Collectors.joining(\n)拼接无MAX_*/ 截断 / 头尾预览。单轮调用即可把上下文撑到 200 万 token 触发模型 400ac_model_call_logrun_idda95ee29, 2,282,437 tokens。于是 V2.23 覆盖排除集第 218-225 行ToolResultEvictionConfigbuildToolResultEvictionConfig(){returnToolResultEvictionConfig.builder().excludedToolNames(java.util.Set.of(read_file,// 卸载文件内容会触发 read_file 重读循环自带 offset/limit 分页write_file,edit_file,// 仅返回极小成功消息memory_search,memory_get,session_search))// 小结果/自分页.build();}移除了三个无上限 FS 工具让 80K 阈值对它们生效保留了六个——每个保留都有理由不是拍脑袋read_file它本身就是「读文件」工具如果它的输出被卸载到文件再让模型read_file去重读会陷入卸载→重读→重读结果又太大→再卸载的循环而且它自带offset/limit分页模型可以主动控制读多少。write_file/edit_file只返回极小的成功消息本来就不可能超 80K。memory_search/memory_get/session_search小结果或自分页。这段注释是写事故复盘的标准姿势为什么覆盖默认值的假设是什么、怎么证伪的实测数据2,282,437 tokens、决策依据每个保留项的行为特征。第 12 集事故全景复盘会展开那次 200 万 token 事件这里先记住机制。避坑默认值不等于安全默认值这一集最值得带走的方法论都在排除集事故里1. 框架默认排除集是「设计者的假设」不是「经过验证的事实」。defaults()标 “self-limiting” 的三个工具实测输出无上限——假设被证伪事故已经发生200 万 token、模型 400。凡是「这个工具输出应该不大」的判断都要用真实仓库实测验证而不是信 Javadoc。2. 排除集不是越小越好——每个工具的输出特征决定它在哪边。read_file被保留排除是有意的卸载对它是有害的重读循环。同一个机制对grep_files是救命的超大输出搬出上下文对read_file是添乱的结果搬到文件里、模型再读一遍。判断标准是「这个工具的输出模型之后还需要吗」一次性大输出 → 卸载需要后续读取的内容 → 不卸载。3. 单测把「契约」钉死防止默认值回归。BaseFinanceAgentFactoryTest.buildToolResultEvictionConfig_excludesFsListGrepGlob第 462-491 行断言了三条三个 FS 工具不在排除集assertFalse附事故理由、六个工具在排除集assertTrue附设计理由、阈值仍是默认 80KDEFAULT_MAX_RESULT_CHARS第 488-490 行注释「仅改排除集不改阈值」。「只断言了我想要的值」是不够的——它还断言了「不想要的值必须不在」防止未来有人看框架默认值顺眼又加回去。阈值断言则守住最小变更原则修复事故只动排除集不顺手改阈值。4. 压缩配置要跟存储层「互相知道」。triggerMessages/keepMessages是框架侧的触发条件但压缩一旦发生存储层靠条数缩水检测它ep09 的 V2.24 修复、渲染层靠namecompaction_summary识别它本集。三处对「压缩」的理解必须一致框架压缩后 context 是「摘要尾部」、存储层据此判断归档、渲染层据此把摘要显示为 system。任何一层用了不同的信号比如看 summary 字段就脱节。设计权衡为什么「交钥匙」「平台覆盖」对比一下 v1 的设计DbAgentStateStore类头注释 50-52 行记录了演进早期版本平台自实现了消息截断器BlockTruncatorHARNESS Tier-0 起改用框架内置的toolResultEviction——大工具结果卸载交给框架落盘预览平台只负责配置和覆盖排除集。这个取舍很清楚框架内置触发时机、阈值、落盘位置、预览格式都是框架契约升级框架自动获得修复平台不用维护一套自研截断逻辑。平台覆盖业务语义哪些工具的输出必须卸载只有平台知道用excludedToolNames覆盖默认值并用单测锁定。存储/渲染适配压缩产物怎么落库归档重写、怎么呈现system 消息是平台的事因为表结构和前端消息形状都是平台的。「框架的能力 平台的判断」各管各的边界这是整个系列反复出现的架构模式。动手15 分钟验证压缩与卸载的契约第一步零 Key跑单测钉死两个契约。在项目根目录mvn-plloser-modules/loser-agent-apptest-DtestBaseFinanceAgentFactoryTest#buildToolResultEvictionConfig_excludesFsListGrepGlobmvn-plloser-modules/loser-agent-apptest-DtestChatHistoryServiceTest第一个断言排除集内容三个 FS 工具不在、六个工具在、阈值仍 80K第二个断言__compaction_summary__消息渲染为 system。跑绿了本集的两个核心契约就被你亲手验证过了。第二步需真 Key亲眼看到一次压缩。把BaseFinanceAgentFactory第 148 行triggerMessages(50)临时改成triggerMessages(3)、keepMessages(1)重启后连发 4-5 条消息触发压缩需要真实模型调用来跑完整 call 循环然后# 压缩发生后旧行归档、摘要尾部重写为 NORMALseq 高位续排SELECT seq, role, block_type, archived, LEFT(content,30)FROM ac_agent_block WHEREthread_id你的threadIdORDER BYseq;# 历史接口压缩摘要应显示为 system 消息curl-shttp://localhost:8080/agui/sessions/threadId/messages-HAuthorization: Bearer token# 压缩事件计数curl-slocalhost:8080/actuator/prometheus|grepblock_truncate你会看到1…N 行archived1新行里第一条是__compaction_summary__的摘要、后面是保留的尾部消息前端渲染成灰色 system 条目。改完记得把配置改回来。第三步需真 Key 大仓库看卸载落盘。让 agent 对一个大型目录执行list_files或grep_files结果超过 80K 字符后去 workspace 下找large_tool_results/目录——超长输出以文件形式躺在那里上下文里只有头尾预览。小结记住三件事压缩对付「历史太多」消息数到triggerMessages50触发压缩产物是__compaction_summary__摘要消息 keepMessages20条尾部存储层归档重写ep09渲染层把摘要显示为 system本集。卸载对付「单条太大」工具结果超 80K 字符落盘large_tool_results/、上下文放头尾预览平台 V2.23 覆盖框架默认排除集——移除三个无上限的 FS 工具曾把上下文撑到 200 万 token 触发 400保留六个含read_file卸载它会触发重读循环。默认值不等于安全默认值排除集的 “self-limiting” 假设被实测证伪契约用单测钉死断言「必须在」也断言「必须不在」修复只动排除集不动阈值。下一篇进入记忆系统——MemoryService与ac_agent_memoryAgent 怎么把长期记忆沉淀进MEMORY.md、跨会话检索以及它和会话持久化短记忆的分工。
返回列表