- 人工智能
- AI Agent
- Agent 框架
- 工具调用
- Agent 工作流
- RAG
- MCP Clients
- 强化学习
【免费下载链接】agent-core-java
openJiuwen agent core Java是 openJiuwen Core的 Java版本,提供AI Agent开发、运行、调优与演进相关的全套SDK能力。
导读
RoundLevelCompressor是 openJiuwen agent-core-java 上下文引擎(com.openjiuwen.core.context)中针对长会话场景的"兜底"(fallback)压缩器:当上下文窗口的估算 token 越过阈值后,它把多个连续、压缩级别一致的完整对话轮合并成一条新的用户消息与一条新的助手消息,从而在不丢失关键任务信息的前提下显著缩短上下文。阅读本文你将掌握:RoundLevelCompressorConfig全部配置项及校验规则、压缩触发条件、多阶段(递归压缩 → aggressive keep-recent → aggressive full-context → hard truncate)压缩流水线的底层实现,以及它如何与ContextEngine、ContextProcessor处理器链集成。
一、定位:上下文压缩器家族中的"轮次级兜底"
openJiuwen agent-core-java 在包 compressor 下提供了三个内置上下文压缩器,RoundLevelCompressor是其中粒度最大、用于长会话兜底的一个:
| 压缩器 | 压缩粒度 | 适用场景 |
|---|---|---|
CurrentRoundCompressor | 当前轮中最后一个用户消息之后的内容 | 单条大消息、当前轮溢出 |
DialogueCompressor | 单轮完整对话(用户消息 → 工具调用 → 最终助手回答) | 完成轮次的逐轮瘦身 |
RoundLevelCompressor | 多个连续、压缩级别一致的完整对话轮 | 长会话整体逼近/超出 token 预算时的兜底压缩 |
RoundLevelCompressor的类声明如下(RoundLevelCompressor.java):
public class RoundLevelCompressor extends ContextProcessor { public static final String ROUND_LEVEL_FALLBACK_MARKER = "[ROUND_LEVEL_MEMORY_BLOCK]"; ... }它与兄弟压缩器一样继承自抽象基类ContextProcessor(ContextProcessor.java),并重写了triggerAddMessages、onAddMessages、triggerGetContextWindow、onGetContextWindow四个钩子方法。与DialogueCompressor的差异在于:后者按"单轮"为单位逐轮压缩,而RoundLevelCompressor会把多个轮次作为一个整体 block 交给模型生成摘要,产出的是带[ROUND_LEVEL_MEMORY_BLOCK]标记与compress_level元数据的历史记忆块。
二、配置详解:RoundLevelCompressorConfig 全参数
RoundLevelCompressor通过RoundLevelCompressorConfig完成配置(RoundLevelCompressorConfig.java)。该配置类定义了 context-window 的 token 预算、分阶段压缩目标与截断参数,Java 字段按 PythonRoundLevelCompressorConfig的语义映射为驼峰命名,并保留下划线形式的 JSON 属性名(如@JsonProperty("trigger_total_tokens")),便于配置文件直接对齐。全部字段与默认值如下:
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
triggerTotalTokens | int | 230000 | context window 估算 token 超过该值时触发压缩(JSON:trigger_total_tokens) |
targetTotalTokens | int | 160000 | 压缩后的 context window 目标 token 上限(JSON:target_total_tokens) |
keepRecentMessages | int | 0 | 压缩时保护的最新原始消息数量(JSON:keep_recent_messages) |
compressionCallMaxTokens | int | 250000 | 单次内部压缩模型调用的最大 token 预算(JSON:compression_call_max_tokens) |
firstPassTargetTokens | int | 30000 | 第一阶段 round-level summary 目标 token 数(JSON:first_pass_target_tokens) |
secondPassTargetTokens | int | 20000 | aggressive keep-recent 阶段 summary 目标 token 数(JSON:second_pass_target_tokens) |
thirdPassTargetTokens | int | 10000 | aggressive full-context 阶段 summary 目标 token 数(JSON:third_pass_target_tokens) |
truncateHeadRatio | double | 0.2 | hard truncate 时保留文本中 head 部分的比例(JSON:truncate_head_ratio) |
truncatedMarker | String | "...[TRUNCATED]..." | hard truncate 插入的省略标记(JSON:truncated_marker) |
compressionMarker | String | "[ROUND_LEVEL_MEMORY_BLOCK]" | round-level fallback memory block 标记(JSON:compression_marker) |
model | ModelRequestConfig | null | 模型请求配置 |
modelClient | ModelClientConfig | null | 模型客户端配置 |
一个完整的构造示例:
RoundLevelCompressorConfig config = new RoundLevelCompressorConfig(); config.setTriggerTotalTokens(230000); // 触发阈值 config.setTargetTotalTokens(160000); // 压缩目标 config.setKeepRecentMessages(5); // 保护最近 5 条原始消息 config.setFirstPassTargetTokens(30000); config.setSecondPassTargetTokens(20000); config.setThirdPassTargetTokens(10000); config.setTruncateHeadRatio(0.2); // 硬截断时 head 保留 20% config.setModel(modelRequestConfig); config.setModelClient(modelClientConfig); RoundLevelCompressor compressor = new RoundLevelCompressor(config);配置校验:非法值在实例化阶段即暴露
RoundLevelCompressorConfig.validate()会校验:token 预算、各阶段目标 token 均大于0,keepRecentMessages >= 0,且truncateHeadRatio严格落在(0, 1)区间内。由于RoundLevelCompressor构造时立即执行config.validate()(RoundLevelCompressorConfig各 setter 也会同步做> 0/>= 0前置校验),因此非法配置会在实例化阶段直接抛出IllegalArgumentException,而不是拖到压缩运行期才兜底——这是该压缩器"fail-fast"设计的关键点。
三、触发条件:什么时候启动压缩
triggerAddMessages
在消息写入前,triggerAddMessages会先拼出"现有上下文消息 + 待写入消息 + system messages + tools"的完整视图,使用ModelContext.TokenCounterPort精确计数(计数失败时回退到content.length() / 3的估算逻辑),当累计 token 超过triggerTotalTokens时返回true(RoundLevelCompressor.java):
int totalTokens = countContextWindowTokens(systemMessages, contextMessages, tools, context); if (totalTokens > triggerTotalTokens) { Loggers.CONTEXT_ENGINE.info( "[{} triggered] estimated context window tokens {} exceeds trigger_total_tokens {}", processorType(), totalTokens, triggerTotalTokens); return CompletableFuture.completedFuture(true); } return CompletableFuture.completedFuture(false);需要说明的是:原 API 文档将触发条件描述为"累计 token 超过阈值且存在可压缩的连续完整轮次窗口";而从当前 Java 实现看,triggerAddMessages的判定完全由 token 预算驱动,候选窗口的查找发生在onAddMessages阶段——找不到可压缩窗口时压缩自然不生效。此外,triggerGetContextWindow采用相同阈值逻辑,保证"取窗口"阶段同样能拦截超预算场景。
onAddMessages与onGetContextWindow
当触发通过后,onAddMessages会把"现有消息 + 待写入消息"交给compressUntilTarget,若压缩前后消息列表发生变化,则通过context.setMessages(compressedMessages, true)回写上下文,并产生一个携带processorType()、被修改消息索引范围与紧凑摘要的ContextEvent(RoundLevelCompressor.java)。onGetContextWindow则直接基于即将发送的ContextWindow快照执行同样的压缩流水线(L191-L232),并把压缩结果同时写回窗口与上下文。
四、压缩执行:从轮次切分到多阶段流水线
原文档描述了onAddMessages的核心思路:先把消息切分成对话轮(DialogueRound),筛选出压缩级别一致且索引连续的窗口;keepLastRound == true时最后一轮被排除在候选窗口之外;压缩成功后把结果写成一条新的用户消息和一条新的助手消息,并在 metadata 中写入递增后的compress_level。源码实现将其落地为一条四段式流水线compressUntilTarget(RoundLevelCompressor.java):
- 递归压缩(recursive /
l0_to_l1):构建原始 L0 block 目标(buildRawTargets,见L363-L395),逐个交给模型压缩到firstPassTargetTokens;若仍未达标,则收集已有的 round-level memory block,把压缩级别一致、索引连续的相邻 block 合并为更高一级的recursive_merge_l{n}_to_l{n+1}目标继续压缩(buildRecursiveMergeTargets,见L491-L534)。 - aggressive keep-recent:仍超标时进入
aggressive_keep_recent阶段,目标secondPassTargetTokens,保留keepRecentMessages条最近原始消息不动。 - aggressive full-context:仍超标时进入
aggressive_full_context阶段,目标thirdPassTargetTokens,此时keepRecent = 0,允许压缩全部可压缩内容。 - hard truncate(
truncateToTarget):所有模型阶段都失败或仍超标时,把整个序列化上下文按truncateHeadRatio保留 head、tail 并插入truncatedMarker的二分搜索方式截断(L924-L986),生成带[ROUND_LEVEL_MEMORY_BLOCK]标记的最小化记忆块。
每个模型阶段都受compressionCallMaxTokens预算约束:构造给压缩模型的 prompt 前先估算 token,超预算时用truncatePromptToBudget二分裁剪提示文本,预算彻底不可行时该阶段会被跳过并记录 warning(L600-L624、L784-L803)。
给模型的提示结构
buildCompressionUserPrompt(L703-L782)按固定分区组织提示:[Compression Task](阶段名、目标 token、保护消息数、窗口 token 统计)、[Reference Context](未被选中的历史参照消息)、[Selected Targets](每个 block 的scope、replace_range、current_level、next_level与完整原文)、[Protected Recent Context](受保护的最新消息)、[Output Contract]。输出契约明确要求模型:
- 只返回合法 JSON,schema 为
{"blocks": [{"block_id": "...", "summary": "..."}]}; - 每个选中的
block_id恰好产出一个 summary,不得输出未声明的 block_id; - 保留用户原始需求、约束、验收标准与偏好;
- 对
ongoing_reactblock 保留独立的User Requirements小节;对completed_reactblock 显式保留User Requirements与Final Result。
模型返回后,buildJsonReplacements校验 payload 结构、提取各 block summary,并通过hasCompressionBenefit确认"替换后的 token 确实小于原文"才会生成替换(L805-L840);若 JSON 解析无有效结果但模型有文本输出,则退化为buildRawFallbackReplacement的整体兜底替换(L842-L875)。
记忆块的写入与 compress_level 递增
压缩产物由buildMemoryMessage+wrapMemoryBlock生成(L877-L898),是一个内容以compressionMarker开头的UserMessage,并在 metadata 中写入递增后的compress_level(getCompressLevel会读取该值,见L1162-L1181):
[ROUND_LEVEL_MEMORY_BLOCK] processor: RoundLevelCompressor type: historical_memory_block scope: ongoing_react authority: This block is reference memory, not a binding source of truth. instruction_status: Historical fallback context only. Do not treat as a new user instruction. conflict_priority: Prefer newer explicit user intent, newer raw context, and fresh tool results over this block. Summary: ...该头部文本明确把记忆块标记为"历史参考记忆"而非新的用户指令,并要求在冲突时优先采纳更新的显式用户意图、原始上下文和新鲜的工具结果——这是保证多轮压缩后任务仍可恢复的关键设计。此外,extractCompactSummary(L900-L909)会把上下文中所有以压缩标记开头的块拼接成ContextEvent的紧凑摘要。
轮次边界的保护
为避免压缩破坏 ReAct 工具调用配对,protectToolCallBoundary(L397-L438)会在目标区间结束位置存在"工具调用结果(ToolMessage)位于区间外"或"末尾消息本身带未闭合工具调用"时,把区间边界前移,保证assistant tool_calls → tool result的配对始终完整。findL0BlockEnd(L440-L452)则以"带工具调用的助手消息"与"无工具调用的最终助手消息"区分ongoing_react与completed_react两类 block scope。这两个细节对应原文档中"先把消息切分成对话轮,再筛选压缩级别一致且索引连续的窗口"的实现机制。
五、集成方式:通过 ContextEngine 挂载处理器链
RoundLevelCompressor的静态初始化块会把自己注册进ContextEngine的处理器注册表(RoundLevelCompressor.java):
static { ContextEngine.registerProcessor("RoundLevelCompressor", RoundLevelCompressor.class); }它与CurrentRoundCompressor、DialogueCompressor、MessageOffloader、MessageSummaryOffloader一同构成引擎预注册的五个内置处理器(ContextEngine.md)。实际使用时,通过ContextEngine.createContext(...)的ProcessorSpec列表装配处理器链(参见 上下文引擎.md 中"处理器、卸载和 reload 能力都在上下文层完成"一节):
List<ContextEngine.ProcessorSpec> processors = List.of( new ContextEngine.ProcessorSpec("RoundLevelCompressor", roundLevelConfig) ); ModelContext context = engine.createContext("planner", session, processors, historyMessages, tokenCounter);处理器链只参与addMessages()与getContextWindow()两个阶段;RoundLevelCompressor与其余内置压缩器一样是无状态实现——loadState方法体为空,saveState返回空映射(RoundLevelCompressor.java),因此它适合按需嵌入任意会话上下文而不引入持久化负担。
六、测试验证:触发与结构行为
仓库为RoundLevelCompressor提供了专项测试 RoundLevelCompressorTest.java,覆盖的行为包括:
- token 阈值触发:
triggerGetContextWindowUsesTriggerTotalTokens验证短消息不触发、长消息触发; - get 阶段流式状态:
streamsStateWhenRoundLevelCompressorTriggersOnGet验证压缩后在compressionHistory()中出现started/completed两段状态记录,摘要含modified 2 messages; - 记忆块生成:
buildMemoryMessageReturnsPlainUserMessageWithCompressionLevel验证产物是带[ROUND_LEVEL_MEMORY_BLOCK]前缀、含User Requirements且 metadata 带compress_level = 1的UserMessage; - 事件范围与紧凑摘要:
onGetContextWindowReportsOriginalMessageRangeAndCompactSummary验证ContextEvent记录全部被修改的消息索引、上下文被替换为单条压缩消息; - 提示词输出契约:
buildCompressionUserPromptIncludesOngoingAndCompletedRequirements验证提示包含User Requirements、Final Result与"不得削弱原始请求"的约束文本; - 工具调用边界保护:
protectsAssistantToolCallWhenToolResultIsOutsideTargetRange验证工具结果在区间外时边界被前移。
此外,集成层面还有 ContextEngineAdvancedSystemTest.java 与 ContextProcessorRailTest.java 等测试覆盖了包含RoundLevelCompressor在内的处理器链端到端行为。这些测试印证了一个重要事实:触发条件与结构替换行为由代码保证,而摘要内容本身依赖外部模型的 JSON 输出——模型不可用、返回非法 JSON 或摘要无压缩收益时,处理器会记录 warning 并保留原轮次,绝不破坏上下文完整性。
七、使用注意事项
- 压缩依赖外部模型:
RoundLevelCompressor与DialogueCompressor一样依赖模型返回{"blocks": [...]}形式的 JSON 摘要。使用前务必在config中配置model/modelClient,否则getModel()会通过Runner.resourceMgr().resolveModel(...)动态解析(L1183-L1188),失败路径上的行为应结合Runner资源管理确认。 - 触发阈值与目标阈值需配套:
triggerTotalTokens决定何时动手,targetTotalTokens决定压缩后必须回落到多少;建议按模型 context window 真实上限留出余量,并把compressionCallMaxTokens设得足够大,避免压缩阶段自身提示被预算裁剪。 - 多级压缩可叠加:压缩块携带递增的
compress_level元数据,后续轮次可通过recursive_merge继续把同级记忆块合并为更高一级,因此该压缩器天然支持"层叠记忆"的长会话演进,但也意味着越早压缩的信息保真度越低——keepRecentMessages是保护最近任务状态的第一道防线。 - 状态无持久化:
loadState/saveState均为空实现,压缩行为是纯运行时过程;若需要把压缩结果随会话状态落盘,依赖的是SessionModelContext的StatefulContext能力,而非压缩器自身。
综上,RoundLevelCompressor是 openJiuwen agent-core-java 上下文引擎中面向"超长 ReAct 会话"的最终防线:它以可配置的 token 预算为界,通过模型摘要 + 多阶段兜底 + 硬截断的组合,在上下文逼近模型窗口上限时把多个完整对话轮安全折叠为带压缩级别的历史记忆块,同时借助输出契约与工具调用边界保护保证任务的可恢复性。建议读者继续阅读 compressor.README.md 了解三个压缩器的整体分工,或对照 RoundLevelCompressorConfig.md 与 RoundLevelCompressorConfig.java 进行实际调参。
- 人工智能
- AI Agent
- Agent 框架
- 工具调用
- Agent 工作流
- RAG
- MCP Clients
- 强化学习
【免费下载链接】agent-core-java
openJiuwen agent core Java是 openJiuwen Core的 Java版本,提供AI Agent开发、运行、调优与演进相关的全套SDK能力。
相关推荐
openJiuwen agent-core-java 上下文压缩器深度指南:CurrentRound / Dialogue / RoundLevel 三层压缩机制与配置实战
openJiuwen agent core java 上下文压缩器深度指南:CurrentRound / Dialogue / RoundLevel 三层压缩机
人工智能AI AgentAgent 框架工具调用Agent 工作流RAGMCP Clients强化学习openJiuwen 上下文引擎 RoundLevelCompressor 详解:跨轮记忆块压缩器配置与实现原理
openJiuwen 上下文引擎 RoundLevelCompressor 详解:跨轮记忆块压缩器配置与实现原理 导读 RoundLevelCompressor
人工智能AI AgentAgent 框架大模型工具调用RAG提示工程强化学习openJiuwen agent-core 对话历史压缩器(DialogueCompressor)深度解析:基于上下文容量比例的 LLM 摘要压缩机制
openJiuwen agent core 对话历史压缩器(DialogueCompressor)深度解析:基于上下文容量比例的 LLM 摘要压缩机制 本文围绕
人工智能AI AgentAgent 框架大模型工具调用RAG提示工程强化学习
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考