本文整理自 CNB 上的开源课程 《Vibe Agent:从零搭建 Coding Agent》。课程讲义、示例代码与练习仓库均在 CNB 上公开,文中示例出自该仓库对应章节。

上下文炸掉之后,要把它压回去。课程给了四种做法,从删历史到重建历史,复杂度一级一级往上走。

截断:留下头和尾

最直接的做法是保留 system 消息和最近 N 条,中间全丢。十行代码能写完,不产生额外调用,token 总量有硬上限。

1
2
3
4
def truncate(messages, keep_last=6):
system = [m for m in messages if m["role"] == "system"]
others = [m for m in messages if m["role"] != "system"]
return system + others[-keep_last:]

课程 demo 的默认值是保留最近 6 条。优点是简单,缺点也在这里:N 条之前的用户偏好和硬约束直接消失,中间的多步推理链一起没掉。用户会感觉 AI 突然忘了说过的话。截断适合短对话,客服问答、一次性脚本生成这类任务,关键上下文本来就在最近几轮里。

这里有个必须避开的坑。如果对 messages 整体切片,system 消息也会被切掉,角色约束没了,输出立刻失稳。system 永远不参与截断,这条要在代码里显式保证。

摘要:用一次调用换浓缩记忆

比截断聪明一步:中间那段不直接删,调一次 LLM 压成摘要,再拼上 system 和最近 N 条原文。用一次调用的成本,换回对话历史的浓缩版本。

1
2
3
4
SUMMARY_PROMPT = """把下面的对话压缩成 100 字以内的中文摘要。
必须保留:用户偏好、硬约束、重要事实、未完成的任务。
可以丢弃:客套话、重复确认、模型的解释性回复。
"""

两个技巧让摘要更可靠。一是明确告诉模型保留什么、丢弃什么,上面这段 prompt 就是。二是让摘要输出结构化,按用户偏好、已确认事实、未决问题分行写,后续轮次的模型更容易找到需要的那条信息。

代价是多一次 LLM 调用的成本和延迟,摘要质量取决于模型,重要事实可能在压缩里被并成一句话。后者比前者更难发现:摘要读起来通顺,细节已经没了。

分层:重要事实单独拎出来

截断靠位置,摘要靠运气,分层靠规则。做法是把上下文分成四层:原始 system 原样保留,关键长期事实单独抽成一条,早期对话压成摘要,最近 N 轮保持原文。前面两层永不参与压缩。

事实怎么抽?课程用一个关键词表兜底:过敏、预算、重要、对了、一定要、不要。用户消息里命中这些词的,整条提出来放进事实槽。

1
2
3
4
5
6
7
8
9
KEY_FACT_KEYWORDS = {"过敏", "预算", "重要", "对了", "一定要", "不要"}

def extract_key_facts(messages):
return [
m["content"].strip()
for m in messages
if m["role"] == "user"
and any(k in m["content"] for k in KEY_FACT_KEYWORDS)
]

demo 在一段 28 轮的历史里埋了两个事实:第 2 轮的“我对花粉过敏”,最后一轮的“预算是每天 500 元”。截断能保住预算,因为它在末尾;过敏在第 2 轮,直接砍掉。摘要能不能保住要看 LLM 怎么写那 100 字。分层把两条都提到事实槽里,位置再远也在。

四种策略处理完的规模与保留情况:

策略 条数 字符数 关键事实
不处理 57 923 两条都在
截断 7 106 过敏被砍掉
摘要 8 207 取决于摘要写法
分层 9 267 两条都保住

字符数从 923 压到一百到两百多,条数只剩个位数,差距全在关键事实有没有留下。

这套做法有边界。关键词表覆盖不到隐式约束,比如“我不能吃辣”里没有任何预设词,抽不出来。升级方案是用小模型做实体抽取,或者让大模型单独跑一次事实抽取,准确率提高,成本和延迟也上去。

分层适合多轮长对话,尤其是用户会不断补约束的场景:旅行规划里预算和偏好反复调整,Coding Agent 边写边改需求。这类硬限制必须跨过所有工具调用一直保留。

GSSC 与选型

前三种都是单点优化,GSSC 把上下文构造拆成四个阶段:Gather 从对话历史、Memory、工具输出和文件系统收集候选信息,Select 按当前任务相关度打分丢掉低相关的,Summarize 压缩长内容,Compose 按 system、facts、examples、context 的结构拼成最终 messages。

这是 Anthropic、Cursor、Claude Code 这类产品背后的上下文管线。适用场景是信息源不止对话历史,还包括文档、代码库和 Skills 的时候。每个阶段可以单独调优、单独观测,也能进 CI 做回归。代价是设计和维护成本,小项目用不上。

四种策略怎么选,课程给了一张对照表:

任务特点 推荐策略
演示、短对话 截断
中等长度、单一任务 摘要
多轮、用户不断补约束 分层
多信息源的生产系统 GSSC

实际工程里通常组合使用。主链路走分层,保证重要事实不丢;system 和事实槽两层永不压缩;早期摘要层内部再跑一遍 select 和 summarize,按相关度决定压什么。课程 lab 实现的是分层,因为它覆盖了最主要的一类失效:早期约束在长对话里消失。

四种策略的复杂度递增,丢信息的风险递减,没有一种是通用解。判断标准回到任务本身:这段对话里最重要的信息是什么,它出现在第几轮,会不会在压缩时被丢掉。

这对搭 Agent 意味着什么

上下文管理是每轮都要做一次的决策,不是启动时配一次的参数。截断适合先跑通,分层是长任务的下限,GSSC 把这件事变成可维护的工程。任务跑长之后还能不能稳定拿到早期约束,是检验方案的直接标准。

本轮对话管住了,跨会话的偏好和习惯还在外面。那是 Memory 系统要处理的事。