LLM 的上下文窗口就像一个有限容量的背包 🎒。每次对话、每个工具调用的结果都会往背包里放东西。随着对话进行,背包越来越满...
上下文管理就是一套帮你"管理背包"的机制,确保 AI 能够持续、高效地工作。
graph TB
A[上下文管理] --> B[结构划分]
A --> C[Token 监控]
A --> D[压缩机制]
B --> B1[系统提示]
B --> B2[可压缩区]
B --> B3[保留区]
D --> D1[工具结果压缩]
D --> D2[对话压缩]
CoPaw 将上下文划分为三个区域:
graph LR
A[系统提示<br>System Prompt] -->|始终保留| B[可压缩区<br>Compactable Messages]
B -->|超限压缩| C[保留区<br>Recent Messages]
| 区域 | 说明 | 处理方式 |
|---|---|---|
| 系统提示 | AI 的"角色设定"和基础指令 | 始终保留,永不压缩 |
| 可压缩区 | 历史对话消息 | Token 计数,超限时压缩为摘要 |
| 保留区 | 最近 N 条消息 | 保持原样,确保上下文连贯 |
┌─────────────────────────────────────────┐
│ System Prompt (固定) │ ← 始终保留
│ "你是一个 AI 助手..." │
├─────────────────────────────────────────┤
│ 压缩摘要 (可选) │ ← 压缩后生成
│ "之前帮用户完成了登录功能..." │
├─────────────────────────────────────────┤
│ 可压缩区 │ ← 超限时会被压缩
│ [消息1] 用户: 帮我写个登录功能 │
│ [消息2] 助手: 好的,我来实现... │
│ [消息3] 工具调用结果... │
│ ... │
├─────────────────────────────────────────┤
│ 保留区 │ ← 始终保留
│ [消息N-2] 用户: 再加个注册功能 │
│ [消息N-1] 助手: 好的... │
│ [消息N] 用户: 完成! │
└─────────────────────────────────────────┘
graph LR
Agent[Agent] -->|每轮推理前| Hook[MemoryCompactionHook]
Hook --> TC[compact_tool_result<br>压缩工具输出]
TC --> CC[check_context<br>Token 计数]
CC -->|超限| CM[compact_memory<br>生成摘要]
graph LR
M[messages] --> TC[compact_tool_result<br>压缩超长工具输出]
TC --> CC[check_context<br>计算剩余空间]
CC --> D{messages_to_compact<br>非空?}
D -->|否| K[返回原消息 + 原摘要]
D -->|是| V{is_valid?}
V -->|否| K
V -->|是| CM[compact_memory<br>生成摘要]
CM --> R[返回 messages_to_keep + 新摘要]
执行顺序:
compact_tool_result— 压缩超长工具输出(如果启用)check_context— 检查上下文是否超限compact_memory— 生成压缩摘要
当上下文接近限制时,CoPaw 会自动触发压缩,将旧对话浓缩为结构化摘要。
当 enable_tool_result_compact 开启时,自动压缩超长的工具输出:
graph LR
M[messages] --> L{遍历 tool_result<br>len > threshold?}
L -->|否| K[保留原样]
L -->|是| T[截断到 threshold]
T --> S[完整内容写入<br>tool_result/uuid.txt]
S --> R[消息追加文件路径引用]
R --> C[清理过期文件]
- 完整内容保存到
tool_result/目录 - 消息中保留截断内容 + 文件路径引用
- 过期文件自动清理
基于 Token 计数判断上下文是否超限,自动拆分为「待压缩」和「保留」两组消息。
graph LR
M[messages] --> H[Token 计数]
H --> C{total > threshold?}
C -->|否| K[返回全部消息]
C -->|是| S[从尾部向前保留<br>reserve tokens]
S --> CP[messages_to_compact<br>早期消息]
S --> KP[messages_to_keep<br>近期消息]
S --> V{is_valid<br>工具调用对齐?}
- 核心逻辑:从尾部向前保留
memory_compact_reservetokens,超出部分标记为待压缩 - 完整性保证:不拆分 user-assistant 对话对,不拆分 tool_use/tool_result 配对
使用 ReActAgent 将历史对话压缩为结构化上下文摘要:
graph LR
M[messages] --> H[format_msgs_to_str]
H --> A[ReActAgent<br>reme_compactor]
P[previous_summary] -->|增量更新| A
A --> S[结构化摘要]
主动触发压缩:
/compact
执行后返回:
**Compact Complete!**
- Messages compacted: 12
**Compressed Summary:**
<压缩摘要内容>
返回内容说明:
- 📊 Messages compacted - 压缩了多少条消息
- 📝 Compressed Summary - 生成的摘要内容
压缩生成的摘要是一份结构化上下文摘要,包含继续工作所需的关键信息:
graph TB
A[压缩摘要] --> B[Goal]
A --> C[Constraints]
A --> D[Progress]
A --> E[Key Decisions]
A --> F[Next Steps]
A --> G[Critical Context]
| 字段 | 内容 | 举例 |
|---|---|---|
| Goal | 用户目标 | "构建一个用户登录系统" |
| Constraints | 约束和偏好 | "使用 TypeScript,不要用任何框架" |
| Progress | 完成/进行中/阻塞的任务 | "登录接口已完成,注册接口进行中" |
| Key Decisions | 关键决策及原因 | "选择 JWT 而非 Session,因为需要无状态" |
| Next Steps | 接下来要做什么 | "实现密码重置功能" |
| Critical Context | 继续工作所需的数据 | "主文件在 src/auth.ts" |
- 增量更新:传入
previous_summary时,自动将新对话与旧摘要合并 - 信息保留:压缩会保留确切的文件路径、函数名称和错误消息,确保上下文无缝衔接
配置文件位于 ~/.copaw/config.json 中的 agents.running 部分:
| 上下文管理参数 | 默认值 | 说明 |
|---|---|---|
max_input_length |
131072 |
模型上下文窗口大小(tokens),即"背包容量" |
memory_compact_ratio |
0.75 |
触发压缩的阈值比例,达到 max_input_length * ratio 时压缩 |
memory_reserve_ratio |
0.1 |
压缩时保留的最近消息比例,保留 max_input_length * ratio tokens |
| 工具压缩参数 | 默认值 | 说明 |
|---|---|---|
enable_tool_result_compact |
false |
是否压缩超长工具输出 |
tool_result_compact_keep_n |
5 |
压缩工具结果时保留的最近 N 条 |
计算关系:
memory_compact_threshold=max_input_length * memory_compact_ratio(触发压缩的阈值)memory_compact_reserve=max_input_length * memory_reserve_ratio(保留的最近消息 tokens)
示例配置:
{
"agents": {
"running": {
"max_input_length": 128000,
"memory_compact_ratio": 0.7,
"memory_reserve_ratio": 0.1,
"enable_tool_result_compact": true,
"tool_result_compact_keep_n": 3
}
}
}