会话能恢复不等于会话能无限增长。把AgentState存进 PostgreSQL解决的是应用重启后还能不能接着聊。任务持续几小时甚至几天后另一个问题会慢慢冒出来用户消息、模型回答、工具调用和工具结果越积越多每轮推理都要带上更长的历史。继续原样发送调用会越来越慢token 成本也会增加最后还可能超过模型的上下文窗口。AgentScope Java 的Compaction会把较早的对话整理成一条摘要最近的消息仍保留原文text较早的对话 最近 2 条消息压缩后一条上下文摘要 最近 2 条原始消息摘要会尽量保留当前目标、已确认信息和待办事项。模型不需要重新读取整段历史也能继续处理当前任务。一、持久化保存会话Compaction 控制会话长度AgentStateStore和Compaction操作的是同一份会话状态但职责不同。能力负责什么AgentStateStore保存和恢复AgentStateCompaction缩短AgentState里的历史消息只有 PostgreSQL没有压缩重启后确实能恢复会话但恢复出来的历史仍会继续增长。只有压缩没有持久化当前进程里的上下文变短了应用重启后仍然找不回原来的任务。两者放在一起链路是这样的text从 PostgreSQL 恢复 AgentState- 加入本轮用户消息- 模型推理前检查压缩条件- 较早消息变成摘要最近消息保留原文- 模型继续处理本轮请求- 更新后的 AgentState 写回 PostgreSQL现有 Web Agent 已经有 Workspace、PostgreSQL 状态存储和 SSE 接口。接入上下文压缩不需要改 Controller、Service、事件对象和接口地址也不用新增 Maven 依赖。代码变化集中在三个位置textapplication.yml- 增加压缩参数和摘要提示词DevAgentProperties- 接收新增配置AgentScopeConfiguration- 创建 CompactionConfig并交给 HarnessAgent二、接入 Compaction只改三个位置1. application.yml 增加压缩参数原有模型、Workspace 和数据源配置保持不变只在app.dev-agent下增加compactionyamlapp:dev-agent:compaction:trigger-messages: 6keep-messages: 2summary-prompt: |请把下面的会话整理成一份供后续任务继续使用的上下文摘要。只保留用户目标、已经确认的事实、尚未完成的事项和明确编号。不要补充会话中没有出现的信息。使用下面的结构## 当前目标## 已确认信息## 待处理事项会话内容{messages}trigger-messages: 6的“消息”不是六次提问而是参与推理的非 System 消息。一次普通问答通常包含一条 User 消息和一条 Assistant 消息如果模型调用工具工具调用会放在 Assistant 消息里执行结果还会追加 Tool 消息。keep-messages: 2表示普通情况下保留最近两条消息原文。如果切分位置落在工具调用和工具结果之间框架会调整边界实际保留条数可能变化。这里的阈值只是为了快速触发效果。正式环境更适合结合模型上下文窗口、工具结果大小和任务平均轮数来定。2. DevAgentProperties 接住新配置DevAgentProperties原来只接收 Agent、Workspace 和模型配置。现在增加一个Compaction字段javapublic record DevAgentProperties(NotBlank String name,NotBlank String systemPrompt,NotBlank String projectRoot,NotBlank String workspaceRoot,Valid Compaction compaction,Valid Model model) {public record Compaction(Min(2) int triggerMessages,Min(1) int keepMessages,NotBlank String summaryPrompt) {}}Valid让嵌套配置参与校验Min给消息阈值和保留条数设置下限。应用启动时就能发现明显错误不必等到会话压缩时再报错。3. 把 CompactionConfig 交给 HarnessAgentapplication.yml里的参数不能直接传给HarnessAgent先将它们转换成 AgentScope 使用的CompactionConfigjavaBeanCompactionConfig compactionConfig(DevAgentProperties properties) {DevAgentProperties.Compaction config properties.compaction();return CompactionConfig.builder().triggerMessages(config.triggerMessages()).keepMessages(config.keepMessages()).keepTokens(0).summaryPrompt(config.summaryPrompt()).flushBeforeCompact(false).offloadBeforeCompact(false).build();}现有配置原来明确关闭了压缩java.disableCompaction()现在把它替换成java.compaction(compactionConfig)HarnessAgent的 Bean 方法只需要多接收一个CompactionConfigjavaBeanHarnessAgent devAgent(Model deepSeekModel,DevAgentProperties properties,CompactionConfig compactionConfig,AgentStateStore agentStateStore,ProjectInfoTools projectInfoTools,FileChangeTool fileChangeTool)throws IOException {HarnessAgent agent HarnessAgent.builder()// 省略已有配置.compaction(compactionConfig).build();// 省略工具注册return agent;}完成这三处调整后压缩能力就接进现有 Agent 了配置对象负责接收参数CompactionConfigBean 负责组装框架配置HarnessAgent负责启用。工具注册、权限规则、Workspace 和AgentStateStore都沿用现有实现。这里有三个容易混淆的配置。keepTokens(0)表示按keepMessages保留最近消息。如果设置为大于 0 的值框架会按固定 token 预算保留尾部设置为-1时才是根据模型上下文窗口动态计算。flushBeforeCompact(false)关闭压缩前的长期记忆提取。当前示例只验证会话摘要不把旧对话另外写入 Memory。offloadBeforeCompact(false)关闭压缩前的原始消息归档。如果系统有审计或历史检索要求可以开启它把压缩前的完整消息保存到 Workspace 下的会话 JSONL 文件。三、压缩发生在模型下一次推理前CompactionMiddleware工作在onReasoning阶段。每次模型准备推理时它先检查当前对话是否达到消息数或 token 阈值。触发后会走下面这条链路text取出当前消息- 暂时分离 System 消息- 找出需要摘要的旧消息- 保留最近消息- 调用模型生成摘要- 用“摘要 最近消息”替换旧上下文- 放回 System 消息- 继续本轮推理System 消息不会进入摘要。Workspace 中的AGENTS.md会作为项目规则参与推理但不会被当成旧对话压缩掉。分界点也不是简单按消息下标切开。假设一条 Assistant 消息发起了工具调用下一条 Tool 消息才返回结果如果正好从中间切断后续模型只会看到半段调用链。ConversationCompactor会调整切分位置尽量把工具调用和对应结果放在同一侧。假设压缩前一共有七条消息配置要求保留最近两条。框架会把前五条整理成摘要最近两条仍保留原文text压缩前较早的 5 条消息 最近一条 Assistant 消息 当前 User 消息压缩后前 5 条消息的摘要 最近一条 Assistant 消息原文 当前 User 消息原文框架会把生成的摘要作为一条新消息放进AgentState.context用它替换较早的原始消息。当前问题完成推理后新的回答还会继续追加到这份上下文中并随更新后的AgentState写回 PostgreSQL。生成摘要本身也要调用一次模型。因此阈值太低并不会更省反而可能频繁增加一次额外的模型请求。四、curl用同一个会话触发压缩下面四次请求使用相同的userId和sessionId。第一次给出任务范围bashcurl -sN -X POST http://localhost:8080/dev-agent/ask \-H Content-Type: application/json \-d {userId: context-user-009,sessionId: context-session-009,message: 任务编号是 CTX-009。需要确认 Java 版本、Spring Boot 版本、启动类、源码目录、构建命令和测试命令。只确认收到不要调用工具。}第二次和第三次补充已经确认的信息bashcurl -sN -X POST http://localhost:8080/dev-agent/ask \-H Content-Type: application/json \-d {userId: context-user-009,sessionId: context-session-009,message: 已确认 Java 版本是 17Spring Boot 版本是 4.1.0。只确认收到不要调用工具。}bashcurl -sN -X POST http://localhost:8080/dev-agent/ask \-H Content-Type: application/json \-d {userId: context-user-009,sessionId: context-session-009,message: 已确认启动类是 AgentScopeJavaApplication源码目录是 src/main/java。只确认收到不要调用工具。}前三轮每轮各产生一条 User 消息和一条 Assistant 消息共六条。压缩条件只在下一次模型推理前检查所以第四次请求加入新的 User 消息后框架看到七条消息并触发压缩bashcurl -sN -X POST http://localhost:8080/dev-agent/ask \-H Content-Type: application/json \-d {userId: context-user-009,sessionId: context-session-009,message: 汇总已经确认的信息并列出还没有确认的事项。不要调用工具。}日志会出现类似内容textCompaction triggered: total7 msgs / token 数 tokens, cutoff5, keeping2 msgsCompaction complete: 7 msgs → 1 summary 2 tail 3 total第一行表示压缩开始当前共有 7 条消息前 5 条会被整理成摘要最近 2 条保留原文。第二行表示压缩完成模型接下来看到的历史不再是原来的 7 条消息而是text1 条摘要 最近 2 条原始消息第四轮回答大致如下text已确认- Java 版本17- Spring Boot 版本4.1.0- 启动类AgentScopeJavaApplication- 源码目录src/main/java待确认- 构建命令- 测试命令这说明较早的原文已被摘要替代但任务编号、已确认信息和待办没有丢失。日志里的3 total是压缩刚完成时的消息数第四轮回答生成后也会追加到上下文并随更新后的AgentState写入 PostgreSQL。五、先分清是哪一种上下文变长Compaction解决的是对话轮数越来越多。它会把较早的消息重新整理成摘要因此适合保留当前目标、已经完成的步骤和接下来要做的事。摘要不是原文备份也不能替代业务数据库。订单号、审批状态、发布批次这类不能出错的信息仍然要放进结构化存储信息更合适的位置当前目标、已完成步骤、下一步Compaction摘要项目背景、工具规则、输出要求AGENTS.md用户偏好、长期约定Memory审批状态、订单号、发布批次业务表或结构化状态压缩前的完整对话会话原始日志还有一种情况对话没进行几轮但某个工具一次返回了几万行日志。这时问题不在历史消息太多而在单条工具结果太大。这类结果由ToolResultEvictionMiddleware处理阈值和预览长度通过ToolResultEvictionConfig配置。超过阈值后完整内容会写入 Workspace对话中只留下开头、结尾和文件位置。它和Compaction的区别可以这样理解text聊了很多轮历史消息越来越长- Compaction 把旧对话整理成摘要某个工具一次返回了大量内容- ToolResultEviction 把完整结果转存到文件当前代码没有单独配置ToolResultEvictionConfigHarnessAgent会使用默认规则单条工具结果超过 8 万字符时才转存。四轮测试没有调用工具因此这项能力不会影响测试结果。Web 接口使用streamEvents()输出 SSE。正常情况下每次模型推理前都会先检查压缩条件达到阈值就主动压缩。但如果阈值设得太高直到模型已经因为上下文超限而拒绝请求当前流式调用不会自动压缩后重试。因此SSE 接口要提前留出余量不要把压缩时机卡在模型上限附近。写在最后长会话真正需要保住的不是每一句原话而是任务还能继续执行所需的信息。Compaction把旧消息整理成摘要最近消息保留原文再把新的上下文写回AgentState。PostgreSQL 负责下次把它找回来AGENTS.md继续提供项目规则关键业务状态则留在结构化存储里。几种信息各回各的位置Agent 才不会把所有状态都压在一段越来越长的聊天记录上。AgentScope Java 2.0 系列1. AgentScope Java 2.0 正式版来了Java Agent 不只要会做事还得能稳定运行2. AgentScope Java 2.0 上手用 HarnessAgent 跑通第一个 Web 接口3. AgentScope Java 2.0 核心架构拆解HarnessAgent、ReActAgent、Toolkit 到底是什么4. AgentScope Java2.0 ToolKit 实战不是把方法丢给模型就完事5. AgentScope Java 2.0 AgentEvent 实战让前端看见 Agent 调工具的过程6. AgentScope Java 2.0 Permission 权限实战Agent 写文件前先问你7. AgentScope Java 2.0 Agent状态实战把 Agent 会话存进 PostgreSQL8. AgentScope Java 2.0 Workspace 实战让 Agent 先读懂 AGENTS.md 再回答
智能对话压缩技术:持久化与上下文管理革新
会话能恢复不等于会话能无限增长。把AgentState存进 PostgreSQL解决的是应用重启后还能不能接着聊。任务持续几小时甚至几天后另一个问题会慢慢冒出来用户消息、模型回答、工具调用和工具结果越积越多每轮推理都要带上更长的历史。继续原样发送调用会越来越慢token 成本也会增加最后还可能超过模型的上下文窗口。AgentScope Java 的Compaction会把较早的对话整理成一条摘要最近的消息仍保留原文text较早的对话 最近 2 条消息压缩后一条上下文摘要 最近 2 条原始消息摘要会尽量保留当前目标、已确认信息和待办事项。模型不需要重新读取整段历史也能继续处理当前任务。一、持久化保存会话Compaction 控制会话长度AgentStateStore和Compaction操作的是同一份会话状态但职责不同。能力负责什么AgentStateStore保存和恢复AgentStateCompaction缩短AgentState里的历史消息只有 PostgreSQL没有压缩重启后确实能恢复会话但恢复出来的历史仍会继续增长。只有压缩没有持久化当前进程里的上下文变短了应用重启后仍然找不回原来的任务。两者放在一起链路是这样的text从 PostgreSQL 恢复 AgentState- 加入本轮用户消息- 模型推理前检查压缩条件- 较早消息变成摘要最近消息保留原文- 模型继续处理本轮请求- 更新后的 AgentState 写回 PostgreSQL现有 Web Agent 已经有 Workspace、PostgreSQL 状态存储和 SSE 接口。接入上下文压缩不需要改 Controller、Service、事件对象和接口地址也不用新增 Maven 依赖。代码变化集中在三个位置textapplication.yml- 增加压缩参数和摘要提示词DevAgentProperties- 接收新增配置AgentScopeConfiguration- 创建 CompactionConfig并交给 HarnessAgent二、接入 Compaction只改三个位置1. application.yml 增加压缩参数原有模型、Workspace 和数据源配置保持不变只在app.dev-agent下增加compactionyamlapp:dev-agent:compaction:trigger-messages: 6keep-messages: 2summary-prompt: |请把下面的会话整理成一份供后续任务继续使用的上下文摘要。只保留用户目标、已经确认的事实、尚未完成的事项和明确编号。不要补充会话中没有出现的信息。使用下面的结构## 当前目标## 已确认信息## 待处理事项会话内容{messages}trigger-messages: 6的“消息”不是六次提问而是参与推理的非 System 消息。一次普通问答通常包含一条 User 消息和一条 Assistant 消息如果模型调用工具工具调用会放在 Assistant 消息里执行结果还会追加 Tool 消息。keep-messages: 2表示普通情况下保留最近两条消息原文。如果切分位置落在工具调用和工具结果之间框架会调整边界实际保留条数可能变化。这里的阈值只是为了快速触发效果。正式环境更适合结合模型上下文窗口、工具结果大小和任务平均轮数来定。2. DevAgentProperties 接住新配置DevAgentProperties原来只接收 Agent、Workspace 和模型配置。现在增加一个Compaction字段javapublic record DevAgentProperties(NotBlank String name,NotBlank String systemPrompt,NotBlank String projectRoot,NotBlank String workspaceRoot,Valid Compaction compaction,Valid Model model) {public record Compaction(Min(2) int triggerMessages,Min(1) int keepMessages,NotBlank String summaryPrompt) {}}Valid让嵌套配置参与校验Min给消息阈值和保留条数设置下限。应用启动时就能发现明显错误不必等到会话压缩时再报错。3. 把 CompactionConfig 交给 HarnessAgentapplication.yml里的参数不能直接传给HarnessAgent先将它们转换成 AgentScope 使用的CompactionConfigjavaBeanCompactionConfig compactionConfig(DevAgentProperties properties) {DevAgentProperties.Compaction config properties.compaction();return CompactionConfig.builder().triggerMessages(config.triggerMessages()).keepMessages(config.keepMessages()).keepTokens(0).summaryPrompt(config.summaryPrompt()).flushBeforeCompact(false).offloadBeforeCompact(false).build();}现有配置原来明确关闭了压缩java.disableCompaction()现在把它替换成java.compaction(compactionConfig)HarnessAgent的 Bean 方法只需要多接收一个CompactionConfigjavaBeanHarnessAgent devAgent(Model deepSeekModel,DevAgentProperties properties,CompactionConfig compactionConfig,AgentStateStore agentStateStore,ProjectInfoTools projectInfoTools,FileChangeTool fileChangeTool)throws IOException {HarnessAgent agent HarnessAgent.builder()// 省略已有配置.compaction(compactionConfig).build();// 省略工具注册return agent;}完成这三处调整后压缩能力就接进现有 Agent 了配置对象负责接收参数CompactionConfigBean 负责组装框架配置HarnessAgent负责启用。工具注册、权限规则、Workspace 和AgentStateStore都沿用现有实现。这里有三个容易混淆的配置。keepTokens(0)表示按keepMessages保留最近消息。如果设置为大于 0 的值框架会按固定 token 预算保留尾部设置为-1时才是根据模型上下文窗口动态计算。flushBeforeCompact(false)关闭压缩前的长期记忆提取。当前示例只验证会话摘要不把旧对话另外写入 Memory。offloadBeforeCompact(false)关闭压缩前的原始消息归档。如果系统有审计或历史检索要求可以开启它把压缩前的完整消息保存到 Workspace 下的会话 JSONL 文件。三、压缩发生在模型下一次推理前CompactionMiddleware工作在onReasoning阶段。每次模型准备推理时它先检查当前对话是否达到消息数或 token 阈值。触发后会走下面这条链路text取出当前消息- 暂时分离 System 消息- 找出需要摘要的旧消息- 保留最近消息- 调用模型生成摘要- 用“摘要 最近消息”替换旧上下文- 放回 System 消息- 继续本轮推理System 消息不会进入摘要。Workspace 中的AGENTS.md会作为项目规则参与推理但不会被当成旧对话压缩掉。分界点也不是简单按消息下标切开。假设一条 Assistant 消息发起了工具调用下一条 Tool 消息才返回结果如果正好从中间切断后续模型只会看到半段调用链。ConversationCompactor会调整切分位置尽量把工具调用和对应结果放在同一侧。假设压缩前一共有七条消息配置要求保留最近两条。框架会把前五条整理成摘要最近两条仍保留原文text压缩前较早的 5 条消息 最近一条 Assistant 消息 当前 User 消息压缩后前 5 条消息的摘要 最近一条 Assistant 消息原文 当前 User 消息原文框架会把生成的摘要作为一条新消息放进AgentState.context用它替换较早的原始消息。当前问题完成推理后新的回答还会继续追加到这份上下文中并随更新后的AgentState写回 PostgreSQL。生成摘要本身也要调用一次模型。因此阈值太低并不会更省反而可能频繁增加一次额外的模型请求。四、curl用同一个会话触发压缩下面四次请求使用相同的userId和sessionId。第一次给出任务范围bashcurl -sN -X POST http://localhost:8080/dev-agent/ask \-H Content-Type: application/json \-d {userId: context-user-009,sessionId: context-session-009,message: 任务编号是 CTX-009。需要确认 Java 版本、Spring Boot 版本、启动类、源码目录、构建命令和测试命令。只确认收到不要调用工具。}第二次和第三次补充已经确认的信息bashcurl -sN -X POST http://localhost:8080/dev-agent/ask \-H Content-Type: application/json \-d {userId: context-user-009,sessionId: context-session-009,message: 已确认 Java 版本是 17Spring Boot 版本是 4.1.0。只确认收到不要调用工具。}bashcurl -sN -X POST http://localhost:8080/dev-agent/ask \-H Content-Type: application/json \-d {userId: context-user-009,sessionId: context-session-009,message: 已确认启动类是 AgentScopeJavaApplication源码目录是 src/main/java。只确认收到不要调用工具。}前三轮每轮各产生一条 User 消息和一条 Assistant 消息共六条。压缩条件只在下一次模型推理前检查所以第四次请求加入新的 User 消息后框架看到七条消息并触发压缩bashcurl -sN -X POST http://localhost:8080/dev-agent/ask \-H Content-Type: application/json \-d {userId: context-user-009,sessionId: context-session-009,message: 汇总已经确认的信息并列出还没有确认的事项。不要调用工具。}日志会出现类似内容textCompaction triggered: total7 msgs / token 数 tokens, cutoff5, keeping2 msgsCompaction complete: 7 msgs → 1 summary 2 tail 3 total第一行表示压缩开始当前共有 7 条消息前 5 条会被整理成摘要最近 2 条保留原文。第二行表示压缩完成模型接下来看到的历史不再是原来的 7 条消息而是text1 条摘要 最近 2 条原始消息第四轮回答大致如下text已确认- Java 版本17- Spring Boot 版本4.1.0- 启动类AgentScopeJavaApplication- 源码目录src/main/java待确认- 构建命令- 测试命令这说明较早的原文已被摘要替代但任务编号、已确认信息和待办没有丢失。日志里的3 total是压缩刚完成时的消息数第四轮回答生成后也会追加到上下文并随更新后的AgentState写入 PostgreSQL。五、先分清是哪一种上下文变长Compaction解决的是对话轮数越来越多。它会把较早的消息重新整理成摘要因此适合保留当前目标、已经完成的步骤和接下来要做的事。摘要不是原文备份也不能替代业务数据库。订单号、审批状态、发布批次这类不能出错的信息仍然要放进结构化存储信息更合适的位置当前目标、已完成步骤、下一步Compaction摘要项目背景、工具规则、输出要求AGENTS.md用户偏好、长期约定Memory审批状态、订单号、发布批次业务表或结构化状态压缩前的完整对话会话原始日志还有一种情况对话没进行几轮但某个工具一次返回了几万行日志。这时问题不在历史消息太多而在单条工具结果太大。这类结果由ToolResultEvictionMiddleware处理阈值和预览长度通过ToolResultEvictionConfig配置。超过阈值后完整内容会写入 Workspace对话中只留下开头、结尾和文件位置。它和Compaction的区别可以这样理解text聊了很多轮历史消息越来越长- Compaction 把旧对话整理成摘要某个工具一次返回了大量内容- ToolResultEviction 把完整结果转存到文件当前代码没有单独配置ToolResultEvictionConfigHarnessAgent会使用默认规则单条工具结果超过 8 万字符时才转存。四轮测试没有调用工具因此这项能力不会影响测试结果。Web 接口使用streamEvents()输出 SSE。正常情况下每次模型推理前都会先检查压缩条件达到阈值就主动压缩。但如果阈值设得太高直到模型已经因为上下文超限而拒绝请求当前流式调用不会自动压缩后重试。因此SSE 接口要提前留出余量不要把压缩时机卡在模型上限附近。写在最后长会话真正需要保住的不是每一句原话而是任务还能继续执行所需的信息。Compaction把旧消息整理成摘要最近消息保留原文再把新的上下文写回AgentState。PostgreSQL 负责下次把它找回来AGENTS.md继续提供项目规则关键业务状态则留在结构化存储里。几种信息各回各的位置Agent 才不会把所有状态都压在一段越来越长的聊天记录上。AgentScope Java 2.0 系列1. AgentScope Java 2.0 正式版来了Java Agent 不只要会做事还得能稳定运行2. AgentScope Java 2.0 上手用 HarnessAgent 跑通第一个 Web 接口3. AgentScope Java 2.0 核心架构拆解HarnessAgent、ReActAgent、Toolkit 到底是什么4. AgentScope Java2.0 ToolKit 实战不是把方法丢给模型就完事5. AgentScope Java 2.0 AgentEvent 实战让前端看见 Agent 调工具的过程6. AgentScope Java 2.0 Permission 权限实战Agent 写文件前先问你7. AgentScope Java 2.0 Agent状态实战把 Agent 会话存进 PostgreSQL8. AgentScope Java 2.0 Workspace 实战让 Agent 先读懂 AGENTS.md 再回答