先从官网的介绍出发:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 配置
// 配置
AutoContextConfig config = AutoContextConfig.builder()
.msgThreshold(30)
.lastKeep(10)
.tokenRatio(0.3)
.build();

// 创建内存
AutoContextMemory memory = new AutoContextMemory(config, model);

// 创建 Agent,使用 AutoContextHook 自动处理集成
ReActAgent agent = ReActAgent.builder()
.name("Assistant")
.model(model)
.memory(memory)
.toolkit(new Toolkit())
.enablePlan() // 启用 PlanNotebook 支持(可选,但推荐)
.hook(new AutoContextHook()) // 自动注册 ContextOffloadTool 并附加 PlanNotebook
.build();

AutoContextConfig

参数 类型 默认值 说明
msgThreshold int 100 触发压缩的消息数量阈值
maxToken long 128 * 1024 上下文窗口的最大 token 限制
tokenRatio double 0.75 触发压缩的 token 比例阈值 (0.0-1.0)
lastKeep int 50 保持未压缩的最近消息数量(仅在策略 1 和策略 2 中生效)
largePayloadThreshold long 5 * 1024 大型消息阈值(字符数)
offloadSinglePreview int 200 卸载消息的预览长度(字符数)
minConsecutiveToolMessages int 6 压缩所需的最小连续工具消息数量
currentRoundCompressionRatio double 0.3 当前轮次消息的压缩比例 (0.0-1.0),默认 30%
customPrompt PromptConfig null 定制上下文压缩 prompt 配置(可选,未设置时使用默认 prompt)

其他其实比较好理解,基本上就是字面意思,其实对于这个config我最感兴趣的还是大佬的prompt究竟是怎么写的。

PromptConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185

// ============================================================================
// Common: Compression Message List Scope Marker
// ============================================================================

/**
* Generic prompt end marker for compression operations.
*
* <p>This marker is used to indicate the scope of messages that need to be compressed. It
* serves as a boundary marker, indicating that all messages above this marker are the target
* for compression.
*/
public static final String COMPRESSION_MESSAGE_LIST_END =
"Above is the message list that needs to be compressed.";

// ============================================================================
// Strategy 1: Previous Round Tool Invocation Compression
// ============================================================================

/** Prompt for compressing previous round tool invocations independently. */
public static final String PREVIOUS_ROUND_TOOL_INVOCATION_COMPRESS_PROMPT =
"You are an expert content compression specialist. Your task is to intelligently"
+ " compress and summarize the following tool invocation history:\n"
+ " - Preserve: tool name, exact arguments (with values), and a concise factual"
+ " summary of the output.\n"
+ " - For repeated calls to the same tool:\n"
+ " • Consolidate identical calls (same args, same result) into one entry"
+ " with frequency note.\n"
+ " • Only list distinct argument combinations that led to different"
+ " outcomes.\n"
+ " • Omit non-essential varying parameters (e.g., timestamps, request IDs)"
+ " if behavior is unchanged.\n"
+ " - Treat a tool as a write/change operation if its name or output implies"
+ " side effects (e.g., contains 'write', 'update', 'delete', 'create', or returns"
+ " confirmation like 'written').\n"
+ " For such operations, preserve critical details: file paths, data keys,"
+ " content snippets, state changes, and success/error indicators.\n"
+ " - Output must be plain text—no markdown, JSON, bullets, headers, or"
+ " meta-comments.\n"
+ " - If any tool output appears truncated or corrupted, include '[TRUNCATED]'.";

// ============================================================================
// Strategy 2-3: Large Message Offloading
// ============================================================================

// ============================================================================
// Strategy 4: Previous Round Conversation Summary
// ============================================================================

/** Prompt for summarizing previous round conversations. */
public static final String PREVIOUS_ROUND_CONVERSATION_SUMMARY_PROMPT =
"You are an expert dialogue compressor for autonomous agents. Your task is to rewrite"
+ " the assistant's final response from the previous round as a self-contained,"
+ " concise reply that incorporates all essential facts learned during the"
+ " round—without referencing tools, functions, or internal execution steps.\n"
+ "\n"
+ "Input includes: the user's original question, the assistant's original response,"
+ " and the results of any tool executions that informed that response.\n"
+ "\n"
+ "Your output will REPLACE the original assistant message in the conversation"
+ " history, forming a clean USER -> ASSISTANT pair for future context.\n"
+ "\n"
+ "Guidelines:\n"
+ " - NEVER mention tools, functions, API calls, or execution steps (e.g., avoid"
+ " 'I called...', 'The system returned...', 'After running X...').\n"
+ " - INSTEAD, state all findings as direct, factual knowledge the assistant now"
+ " possesses.\n"
+ " - PRESERVE CRITICAL FACTS from tool results, especially:\n"
+ " • File paths and their contents, changes, or creation (e.g.,"
+ " '/etc/app.conf sets port=8080')\n"
+ " • Exact error messages when diagnostic (e.g., 'Permission denied (errno"
+ " 13)', 'timeout after 30s')\n"
+ " • IDs, URLs, ports, status codes, configuration values, and data keys\n"
+ " • Outcomes of write/change operations (e.g., 'Wrote maintenance flag to"
+ " /tmp/status', 'Updated user_id=789 email in database')\n"
+ " • Service states or process info (e.g., 'auth-service is stopped',"
+ " 'PID=4567')\n"
+ " - If an action was performed (e.g., file written, service restarted), clearly"
+ " state WHAT changed and WHERE.\n"
+ " - If something failed or was incomplete, specify the limitation (e.g., 'Could"
+ " not restart: permission denied').\n"
+ " - Consolidate redundant information; omit generic success messages with no"
+ " actionable detail.\n"
+ " - Use clear, informative language—avoid meta-phrases like 'Based on logs...'"
+ " or 'As observed...'.\n"
+ " - Output must be plain text: no markdown, bullets, JSON, XML, or section"
+ " headers.";

/** Format for context offload tag. */
public static final String CONTEXT_OFFLOAD_TAG_FORMAT = "<!-- CONTEXT_OFFLOAD: uuid=%s -->";

// ============================================================================
// Strategy 5: Current Round Large Message Summary
// ============================================================================

/** Prompt for summarizing current round large messages. */
public static final String CURRENT_ROUND_LARGE_MESSAGE_SUMMARY_PROMPT =
"You are an expert content compression specialist. Your task is to intelligently"
+ " summarize the following message content. This message exceeds the size"
+ " threshold and needs to be compressed while preserving all critical"
+ " information.\n"
+ "\n"
+ "IMPORTANT: This is content from the CURRENT ROUND. Please be EXTRA CAREFUL and"
+ " CONSERVATIVE when compressing. Preserve as much content as possible according"
+ " to the requirements below, as this information is actively being used in the"
+ " current conversation.\n"
+ "\n"
+ "Please provide a concise summary that:\n"
+ " - Preserves all critical information and key details\n"
+ " - Maintains important context that would be needed for future reference\n"
+ " - Highlights any important outcomes, results, or status information\n"
+ " - Retains tool call information if present (tool names, IDs, key"
+ " parameters)";

// ============================================================================
// Strategy 6: Current Round Message Compression
// ============================================================================

/** Prompt for compressing current round messages (main instruction, without character count requirement). */
public static final String CURRENT_ROUND_MESSAGE_COMPRESS_PROMPT =
"You are an expert context consolidator for autonomous agents. Your task is to"
+ " integrate new tool execution results into the current conversation context.\n"
+ "\n"
+ "INPUT STRUCTURE:\n"
+ "- The input consists of:\n"
+ " (a) Optionally, a prior compressed context block ending with <!--"
+ " CONTEXT_OFFLOAD: uuid=... -->\n"
+ " (b) Followed by zero or more alternating tool_use and tool_result messages"
+ " from the current turn.\n"
+ "- There is NO user message in the input.\n"
+ "- Plan-related tools have already been filtered out upstream.\n"
+ "\n"
+ "YOUR WORKFLOW:\n"
+ "1. IF the input contains a line matching <!-- CONTEXT_OFFLOAD: uuid=... -->:\n"
+ " - Preserve all text BEFORE this line exactly as the prior context.\n"
+ " - Process ONLY the tool_use/tool_result pairs AFTER this line.\n"
+ "2. ELSE (no offload marker found):\n"
+ " - Treat the entire input as new tool interactions from the first compression"
+ " round.\n"
+ " - Generate a summary based solely on these tool calls and their results.\n"
+ "3. For each tool_use/tool_result pair:\n"
+ " - Summarize as a factual, first-person statement:\n"
+ " \"I called [tool_name] with [arg1=value1, ...]; it returned: [key"
+ " details].\"\n"
+ " - Preserve all technical specifics: file paths, IDs, error codes, config"
+ " values, state changes.\n"
+ " - If a result is truncated or malformed, include it verbatim prefixed with"
+ " [UNPARSED OUTPUT].\n"
+ "\n"
+ "OUTPUT REQUIREMENTS:\n"
+ "- A single plain-text block containing:\n"
+ " [prior context (if any)]\\n"
+ "[new tool summaries]\n"
+ "- DO NOT include any <!-- CONTEXT_OFFLOAD --> tag in your output.\n"
+ "- DO NOT mention user requests, intentions, or questions (they are not in the"
+ " input).\n"
+ "- DO NOT use markdown, JSON, bullets, or phrases like \"as before\", \"new"
+ " actions:\".\n"
+ "- The output will be used as the new compressed context, and a new offload tag"
+ " will be appended externally.\n"
+ "\n"
+ "SAFE TO REMOVE FROM tool_result:\n"
+ "- Boilerplate text (licenses, auto-comments)\n"
+ "- Redundant success messages with no actionable data\n"
+ "- Repeated log prefixes (if core content is retained)\n"
+ "\n"
+ "STRICTLY AVOID:\n"
+ "- Including raw tool_use/tool_result JSON\n"
+ "- Re-compressing or altering prior context\n"
+ "- Adding any offload marker (old or new)";

/**
* Character count requirement template for current round message compression.
* This should be placed after the message list end marker.
* Format parameters: %d (originalCharCount), %d (targetCharCount), %.0f (compressionRatioPercent), %.0f (compressionRatioPercent).
*/
public static final String CURRENT_ROUND_MESSAGE_COMPRESS_CHAR_REQUIREMENT =
"\n"
+ "COMPRESSION REQUIREMENT:\n"
+ "The original content contains approximately %d characters. You MUST compress it"
+ " to approximately %d characters (%.0f%% of original). This is a STRICT"
+ " requirement.\n"
+ "\n"
+ "Please ensure your output meets this character limit while following the"
+ " compression principles above.";

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157

// ============================================================================
// 通用:压缩消息列表范围标记
// ============================================================================

/**
* 压缩操作的通用提示结束标记。
*
* <p>此标记用于指示需要压缩的消息范围。它作为边界标记,
* 表示此标记上方的所有消息都是压缩的目标。
*/
public static final String COMPRESSION_MESSAGE_LIST_END =
"以上是需要压缩的消息列表。";

// ============================================================================
// 策略 1:上一轮工具调用压缩
// ============================================================================

/** 用于独立压缩上一轮工具调用的提示词。 */
public static final String PREVIOUS_ROUND_TOOL_INVOCATION_COMPRESS_PROMPT =
"你是一个专业的内容压缩专家。你的任务是智能地"
+ "压缩和总结以下工具调用历史:\n"
+ " - 保留:工具名称、精确参数(带值)和输出的简洁事实摘要。\n"
+ " - 对于重复调用同一工具的情况:\n"
+ " • 将相同的调用(相同参数、相同结果)合并为一条,并标注频率。\n"
+ " • 仅列出导致不同结果的不同参数组合。\n"
+ " • 如果行为未改变,省略非必要的变化参数(如时间戳、请求 ID)。\n"
+ " - 如果工具名称或输出暗示有副作用(例如包含 'write'、'update'、"
+ "'delete'、'create',或返回 'written' 等确认信息),则将该工具视为写/更改操作。\n"
+ " 对于此类操作,保留关键细节:文件路径、数据键、内容片段、"
+ "状态变更和成功/错误指示。\n"
+ " - 输出必须是纯文本——不要使用 markdown、JSON、列表、标题或元注释。\n"
+ " - 如果任何工具输出被截断或损坏,请包含 '[TRUNCATED]'。";

// ============================================================================
// 策略 2-3:大消息卸载
// ============================================================================

// ============================================================================
// 策略 4:上一轮对话总结
// ============================================================================

/** 用于总结上一轮对话的提示词。 */
public static final String PREVIOUS_ROUND_CONVERSATION_SUMMARY_PROMPT =
"你是自主智能体的专业对话压缩专家。你的任务是将上一轮助手的"
+ "最终回复重写为一个自包含、简洁的回复,其中包含本轮学到的"
+ "所有关键事实——而不引用工具、函数或内部执行步骤。\n"
+ "\n"
+ "输入包括:用户的原始问题、助手的原始回复,以及影响该回复"
+ "的任何工具执行结果。\n"
+ "\n"
+ "你的输出将替换对话历史中的原始助手消息,为未来上下文形成"
+ "一个干净的 用户 -> 助手 配对。\n"
+ "\n"
+ "指南:\n"
+ " - 绝不提及工具、函数、API 调用或执行步骤(例如,避免"
+ " '我调用了...'、'系统返回了...'、'运行 X 之后...')。\n"
+ " - 相反,将所有发现陈述为助手现在拥有的直接事实知识。\n"
+ " - 保留工具结果中的关键事实,特别是:\n"
+ " • 文件路径及其内容、更改或创建(例如,"
+ "'/etc/app.conf 设置 port=8080')\n"
+ " • 诊断时的精确错误消息(例如,'Permission denied (errno 13)'、"
+ "'timeout after 30s')\n"
+ " • ID、URL、端口、状态码、配置值和数据键\n"
+ " • 写/更改操作的结果(例如,'将维护标志写入 /tmp/status'、"
+ "'在数据库中更新了 user_id=789 的邮箱')\n"
+ " • 服务状态或进程信息(例如,'auth-service 已停止'、"
+ "'PID=4567')\n"
+ " - 如果执行了操作(例如,写入文件、重启服务),清楚说明"
+ "更改了什么以及在哪里更改。\n"
+ " - 如果某些操作失败或未完成,说明限制(例如,'无法重启:权限被拒绝')。\n"
+ " - 合并冗余信息;省略没有可操作细节的通用成功消息。\n"
+ " - 使用清晰、信息丰富的语言——避免使用 '根据日志...' 或"
+ "'如观察到的...' 等元短语。\n"
+ " - 输出必须是纯文本:不使用 markdown、列表、JSON、XML 或章节标题。";

/** 上下文卸载标签格式。 */
public static final String CONTEXT_OFFLOAD_TAG_FORMAT = "<!-- CONTEXT_OFFLOAD: uuid=%s -->";

// ============================================================================
// 策略 5:当前轮大消息总结
// ============================================================================

/** 用于总结当前轮大消息的提示词。 */
public static final String CURRENT_ROUND_LARGE_MESSAGE_SUMMARY_PROMPT =
"你是一个专业的内容压缩专家。你的任务是智能地总结以下消息内容。"
+ "此消息超过大小阈值,需要在保留所有关键信息的同时进行压缩。\n"
+ "\n"
+ "重要提示:这是来自当前轮次的内容。压缩时请格外小心和保守。"
+ "根据以下要求尽可能多地保留内容,因为这些信息正在当前对话中积极使用。\n"
+ "\n"
+ "请提供一个简洁的摘要,满足以下要求:\n"
+ " - 保留所有关键信息和重要细节\n"
+ " - 维护未来参考所需的重要上下文\n"
+ " - 突出任何重要结果、成果或状态信息\n"
+ " - 如果存在,保留工具调用信息(工具名称、ID、关键参数)";

// ============================================================================
// 策略 6:当前轮消息压缩
// ============================================================================

/** 用于压缩当前轮消息的提示词(主要指令,不包含字符数要求)。 */
public static final String CURRENT_ROUND_MESSAGE_COMPRESS_PROMPT =
"你是自主智能体的专业上下文整合专家。你的任务是将新的工具执行"
+ "结果整合到当前对话上下文中。\n"
+ "\n"
+ "输入结构:\n"
+ "- 输入包括:\n"
+ " (a) 可选地,一个以 <!-- CONTEXT_OFFLOAD: uuid=... --> 结尾的先前压缩上下文块\n"
+ " (b) 后跟零个或多个当前轮次交替的 tool_use 和 tool_result 消息。\n"
+ "- 输入中没有用户消息。\n"
+ "- 与计划相关的工具已在上游过滤掉。\n"
+ "\n"
+ "你的工作流程:\n"
+ "1. 如果输入包含匹配 <!-- CONTEXT_OFFLOAD: uuid=... --> 的行:\n"
+ " - 精确保留此行之前所有文本作为先前上下文。\n"
+ " - 仅处理此行之后 tool_use/tool_result 配对。\n"
+ "2. 否则(未找到卸载标记):\n"
+ " - 将整个输入视为第一次压缩轮次的新工具交互。\n"
+ " - 仅基于这些工具调用及其结果生成摘要。\n"
+ "3. 对于每个 tool_use/tool_result 配对:\n"
+ " - 以事实性的第一人称陈述进行总结:\n"
+ " \"我调用了 [tool_name],参数为 [arg1=value1, ...];返回:[关键细节]。\"\n"
+ " - 保留所有技术细节:文件路径、ID、错误码、配置值、状态变更。\n"
+ " - 如果结果被截断或格式错误,逐字包含并在前面加上 [UNPARSED OUTPUT]。\n"
+ "\n"
+ "输出要求:\n"
+ "- 一个纯文本块,包含:\n"
+ " [先前上下文(如有)]\\n"
+ "[新工具摘要]\n"
+ "- 不要在输出中包含任何 <!-- CONTEXT_OFFLOAD --> 标签。\n"
+ "- 不要提及用户请求、意图或问题(它们不在输入中)。\n"
+ "- 不要使用 markdown、JSON、列表或'如前所述'、'新操作:'等短语。\n"
+ "- 输出将用作新的压缩上下文,新的卸载标签将在外部附加。\n"
+ "\n"
+ "可以从 tool_result 中安全移除:\n"
+ "- 样板文本(许可证、自动注释)\n"
+ "- 没有可操作数据的冗余成功消息\n"
+ "- 重复的日志前缀(如果保留了核心内容)\n"
+ "\n"
+ "严格避免:\n"
+ "- 包含原始 tool_use/tool_result JSON\n"
+ "- 重新压缩或更改先前上下文\n"
+ "- 添加任何卸载标记(旧的或新的)";

/**
* 当前轮消息压缩的字符数要求模板。
* 这应该放在消息列表结束标记之后。
* 格式参数:%d (originalCharCount), %d (targetCharCount), %.0f (compressionRatioPercent), %.0f (compressionRatioPercent)。
*/
public static final String CURRENT_ROUND_MESSAGE_COMPRESS_CHAR_REQUIREMENT =
"\n"
+ "压缩要求:\n"
+ "原始内容包含约 %d 个字符。你必须将其压缩到约 %d 个字符(原始的 %.0f%%)。"
+ "这是一个严格要求。\n"
+ "\n"
+ "请确保你的输出符合此字符限制,同时遵循上述压缩原则。";

AutoContextMemory

根据上述内容,在 AutoContextMemory 中会进行记忆的压缩。

目前看起来主要有四个维度:

  • msgCountReached 消息总条数达到阈值
  • calculateToken, thresholdToken, tokenCounterReached 消息Token达到阈值

Strategy 1: Checking for previous round tool invocations to compress

策略1:先把调用工具的部分进行压缩

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int toolIters = 5;
boolean toolCompressed = false;
int compressionCount = 0;
while (toolIters > 0) {
toolIters--;
List<Msg> currentMsgs = new ArrayList<>(workingMemoryStorage);
Pair<Integer, Integer> toolMsgIndices =
extractPrevToolMsgsForCompress(currentMsgs, autoContextConfig.getLastKeep());
if (toolMsgIndices != null) {
summaryToolsMessages(currentMsgs, toolMsgIndices);
replaceWorkingMessage(currentMsgs);
toolCompressed = true;
compressionCount++;
} else {
break;
}
}

可以看到总共是压缩了5轮,每轮提取出 lastKeep 的消息。
然后对ToolMessage进行summary,压缩 prompt 可以参考上述 PREVIOUS_ROUND_TOOL_INVOCATION_COMPRESS_PROMPT
然后对 Memory 中的消息进行 replace.

1
2
3
4
5
6
7
private List<Msg> replaceWorkingMessage(List<Msg> newMessages) {
workingMemoryStorage.clear();
for (Msg msg : newMessages) {
workingMemoryStorage.add(msg);
}
return new ArrayList<>(workingMemoryStorage);
}

Strategy 2: Checking for previous round large messages (with lastKeep protection)

策略2:把上一轮的大消息给卸载掉(大抵是压不动了)

通过对应的offloaduuid表示当前消息已经被offload掉了

Strategy 3: Checking for previous round large messages (without lastKeepprotection)

策略3:把 lastKeep 的限制去掉,再次卸载大消息

Strategy 4: Checking for previous round conversations to summarize

可以看到,要到第四顺位的策略才会去summarize上一轮用户和assistant的对话

Strategy 5: Checking for current round large messages to summarize

到第五顺位会summarize并且卸载上一轮大会话了

Strategy 6: Checking for current round messages to summarize

最后再总结一轮。

ContextOffloadTool

主要有以下的能力:

reload - 把之前卸载的消息再读取进来 by UUID,当模型发现需要 access original message 的时候会调用

AutoContextHook

这个Hook就更简单了,就是在Hook的时候执行上述操作。

在 preReasoning 的时候进行对应的判断是否压缩操作。(其中提到systemPrompt也会有压缩的可能)