
上下文工程
为什么上下文重要
Section titled “为什么上下文重要”大语言模型的注意力机制存在 n² 的复杂度关系:输入序列长度翻倍,注意力计算量翻四倍。更关键的是,token 越多,模型的注意力越稀释——“长上下文”不等于”好理解”。
长上下文的隐性成本
Section titled “长上下文的隐性成本”| 维度 | 短上下文(~10K tokens) | 长上下文(~100K tokens) |
|---|---|---|
| API 延迟 | 1-3 秒 | 5-15 秒 |
| 输入费用 | 低 | token 数 × 单价 |
| 注意力质量 | 精准,关键信息密度高 | 稀释,模型可能”遗忘”中间部分 |
| 缓存命中 | 维护简单 | 缓存碎片化,命中率下降 |
核心思路:把上下文当作有限资源管理,而非无限垃圾桶。
Peri 的设计哲学就是帮你管理这一资源——系统提示词冻结缓存、按需文件加载、自动压缩、子代理隔离——全是为了用最少的 token 传达最精准的信息。
Peri 的冻结前缀缓存
Section titled “Peri 的冻结前缀缓存”传统方式的问题
Section titled “传统方式的问题”传统 AI 编码助手每次请求都发送完整的系统提示词。一个典型的编码助手系统提示词可能占用 8K-15K tokens——每次 /turn 都原样发送,这些 8K-15K token 反复计费。
sequenceDiagram
participant U as 用户
participant A as 传统助手
participant API as API
U->>A: 第1轮对话
A->>API: System(15K) + Context + 本轮输入 → 计15K输入
U->>A: 第2轮对话
A->>API: System(15K) + 全部历史 + 本轮输入 → 又计15K
U->>A: 第3轮对话
A->>API: System(15K) + 全部历史 + 本轮输入 → 又计15K
Note over A,API: 10轮对话累计 150K 系统提示词 token 费用
Peri 的冻结前缀方案
Section titled “Peri 的冻结前缀方案”Peri 利用 Anthropic 的 Prompt Caching API,将系统提示词(包括 CLAUDE.md、skill 上下文、hook 指令等)作为**冻结前缀(frozen prefix)**缓存。这些 token 在会话生命周期内不变,95-99% 的对话 token 从缓存命中,不计费或按缓存价格计费。
sequenceDiagram
participant U as 用户
participant P as Peri
participant API as API
U->>P: 第1轮对话
P->>API: [FROZEN System 15K] + 动态Context + 本轮输入
Note over P,API: 缓存 MISS: 计15K(唯一一次)
U->>P: 第2轮对话
P->>API: [缓存命中 15K] + 动态Context + 本轮输入
Note over P,API: 缓存 HIT: 15K 不计费或低费率
U->>P: 第3-10轮对话
P->>API: [缓存命中 15K] + 动态Context + 本轮输入
Note over P,API: 每轮都在命中
以一个 15K tokens 的冻结前缀为例:
| 传统方式(10轮) | Peri(10轮) | |
|---|---|---|
| 系统提示词 token 计费 | 150K | 15K |
| 缓存命中率 | 0% | 95-99% |
| 等效节省 | — | ~90% |
冻结前缀中的内容不会被压缩(compaction)修剪——它们是”永久上下文”。因此,写入 CLAUDE.md 的内容是真正的”免费指令”(仅首次收取缓存写入费)。
CLAUDE.md 的上下文工程视角
Section titled “CLAUDE.md 的上下文工程视角”CLAUDE.md 不是文档 dump,而是浓缩指令——它占据冻结前缀,每次会话都要携带。你的每个字都在决定 Peri 将注意力分配到何处。
好的 CLAUDE.md vs 差的 CLAUDE.md
Section titled “好的 CLAUDE.md vs 差的 CLAUDE.md”差的设计(信息 dump):
# 项目说明本项目是一个用 Rust 编写的终端 AI 编码助手。使用 tokio 异步运行时,ratatui 作为 TUI 框架,clap 处理命令行参数。项目结构分为 agent、tui、tools、mcp 等模块。agent 负责 ReAct 循环,tui 负责界面渲染,tools 负责工具定义和执行,mcp 负责与外部 MCP 服务器通信...(继续 200 行项目介绍)Peri 加载进来全是描述性信息,没有一个指令告诉它”该怎么做”。
好的设计(浓缩指令):
# 编码规范- 禁止 println!,统一用 tracing(info/warn/error)- CJK 截断用 chars().take(),禁止 bytes().take()- 错误处理统一用 anyhow::Result,禁止裸 unwrap()
# 模块速查| 模块 | 入口 | 关键规则 ||------|------|----------|| peri-agent | agent.rs | 中间件顺序不可变 || peri-mcp | client.rs | 连接超时 5 秒 |
# TRAP- [TRAP] Windows 路径含反斜杠导致 glob 失败 → 统一 normalize 后再 glob每一行都是可执行指令,Peri 不需要从中”提取信息”——直接遵守。
用 @import 引用拆分大文件
Section titled “用 @import 引用拆分大文件”主 CLAUDE.md 放全局规则,细节用 <!-- @import path --> 引用展开(仅 CLAUDE.md 系列文件解析,AGENTS.md 不处理):
# 项目根 CLAUDE.md(< 50 行)<!-- @import .claude/rules/coding-style.md --><!-- @import .claude/rules/testing.md -->被引用的文件也参与冻结前缀缓存,但在文件层面分离,方便维护。路径相对于当前文件所在目录,递归深度上限 3。Peri 自身的 CLAUDE.md 就是这样做的大约 200 行主文件引用规则文件。
当对话历史接近模型的 token 限制时,Peri 触发自动压缩(compaction)。压缩器遍历对话历史,将已完成的操作总结为结构化摘要,丢弃原始的工具调用细节。
PreCompact 事件在压缩前触发——你可以通过 hook 在压缩前备份关键信息,或注入指令指定哪些内容必须保留。
PostCompact 在压缩后触发——验证压缩结果是否丢了关键上下文。
{ "hooks": { "PreCompact": [ { "matcher": "", "hooks": [ { "type": "command", "command": "echo 'compaction开始' >> /tmp/peri-compaction.log" } ] } ] }}压缩保留策略(内置规则)
Section titled “压缩保留策略(内置规则)”压缩保留什么由内置规则决定,不需要(也无法)在 CLAUDE.md 中声明。Micro Compact 有一份黑名单工具列表,这些工具的消息(输入 + 输出)不参与截断:
| 工具 | 保留原因 |
|---|---|
Agent | 子任务描述等结构化参数不可恢复,丢失会导致子代理调度失败 |
AskUserQuestion | 用户答案不可恢复,丢失会导致对话断裂 |
goal | 长期目标状态,丢失会导致 agent 漂移方向 |
TodoWrite | 任务列表结构,丢失会导致工作记忆重置 |
此外,Smart Compact(遗留实现,默认关闭)会保留最近 5 条 User/Assistant 消息和最近 3 个工具调用结果。压缩完成后会追加续接指令 [Context has been compacted. Continue working based on the summary above.]。
压缩后,Peri 的上下文变成一个”精简摘要 + 冻结前缀 + 最新几轮对话”的结构,既保留了项目知识,又释放了空间继续推理。
渐进式信息加载
Section titled “渐进式信息加载”Peri 不会在会话开始时把所有项目文件读进上下文。它的工作方式是:
- 收到任务 → 分析需求
- 用 glob 发现文件 → 只获取匹配的文件路径
- 用 grep 定位代码 → 只读取匹配行附近的上下文
- 按需 Read → 只在需要完整内容时读取整个文件
每一步都只向上下文添加最少的信息。
flowchart TD
T[任务: 修改用户认证逻辑] --> G[glob: **/auth*]
G --> GR[grep: verify_token | login | authenticate]
GR --> R[Read: 仅读匹配文件中的相关函数]
R --> E[Edit: 精确修改]
E --> V[验证: cargo check]
工具结果也是 token 高效的
Section titled “工具结果也是 token 高效的”Peri 的工具输出自动截断——过长的 grep 结果、文件内容会被压缩为摘要。你不需要手动限制工具范围,Peri 内置了输出裁剪机制。
每个子代理拥有独立的上下文窗口,不会污染主对话。
flowchart LR
M[主 Agent
上下文: 冻结前缀 + 摘要 + 最新对话] --> |派发任务| S1[子代理 1
独立上下文]
M --> |派发任务| S2[子代理 2
独立上下文]
M --> |派发任务| S3[子代理 3
独立上下文]
S1 --> |结果摘要| M
S2 --> |结果摘要| M
S3 --> |结果摘要| M
这意味着:
- 子代理执行过程中产生的大量搜索和读取不会挤占主对话的上下文
- 主 Agent 收到的是子代理的结构化摘要,而非原始工具调用日志
- 可以并行搜索多个代码区域而不互相干扰
子代理适合处理”搜索-分析-总结”类的密集型任务——它们吃掉 token,但只向主 Agent 返回提炼后的结论。
长时间任务(如重构 20 个模块、批量迁移测试)面临 token 积累问题。三种处理策略:
策略 1:压缩 + 结构化笔记
Section titled “策略 1:压缩 + 结构化笔记”Peri 在压缩时会保留”结构化笔记”——你可以在任务开始前让 Peri 建立笔记文件:
在 notes/task-log.md 中创建任务日志。每完成一个模块,记录:模块名、改动摘要、验证结果。压缩时优先保留此文件的内容。压缩后 Peri 通过笔记文件恢复上下文,而非依赖对话历史。
策略 2:子代理架构
Section titled “策略 2:子代理架构”将长任务拆成独立子代理,每个子代理处理一个相对独立的子任务。子代理完成后返回结果摘要,主 Agent 负责编排。
主 Agent:编排重构流程 ├── 子代理 1:重构 src/models/(独立上下文,完成即返回摘要) ├── 子代理 2:重构 src/services/(独立上下文) └── 子代理 3:更新测试(独立上下文)子代理的内部对话历史在完成后销毁——主 Agent 只留下三份摘要。
策略 3:Combine
Section titled “策略 3:Combine”两种策略可以组合:让每个子代理在自己的上下文中使用压缩,处理更大的子任务。主 Agent 用笔记文件协调全局状态。
拆分大任务。 一句”重构整个项目”会让 Peri 在单一上下文中同时处理几百行新老代码对比——注意力崩了。拆成”先重构模块 A → 验证 → 再重构模块 B”。
精简你的 CLAUDE.md。 每季度回顾一次——哪些规则 Peri 已经不再需要?哪些描述可以用 @import 引用拆出去?过了 200 行就考虑重构。
主动 /compact。 不等到自动触发。当对话超过 15 轮时,主动执行 /compact 释放空间。压缩后确认 Peri 仍理解任务目标再继续。
给 Peri 明确的”当前关注点”。 每次 /clear 或 /compact 后的第一条消息,先重申当前任务的目标和进度:
(续)当前在重构 auth 模块,已完成:- JWT 签发逻辑迁移 ✓- 中间件提取 ✓下一步:迁移 refresh token 逻辑这让 Peri 不需要从上下文中”猜测”当前状态。
分批改,分批交。 30 个文件的重构,不要一个 commit 搞定。每改 5-8 个文件,验证通过就 commit。如果某个批次出问题,损失可控,/clear 后也很容易恢复。