在构建长程运行的 Agent 任务时,开发者面临的最直观诱惑往往是“把所有历史一股脑塞进上下文”。
许多开发者认为,既然现代大语言模型的上下文窗口已经扩展到 100 万甚至 200 万 Token,那么简单的 messages.push(...) 就足以应付一切长任务。然而在真实业务(如多步骤代码重构、全自动研报推演)中,这种做法会迅速引发灾难性的系统崩溃。
本文从底层物理机制出发,剖析长上下文下的模型失效本质,建立上下文治理的三维正交坐标系,解构粗粒度摘要压缩必然失败的根因,并给出工业级的细粒度治理方案与纯 TypeScript 架构实现。
破除误区:为什么百万 Token 窗口解决不了上下文治理#
在实际业务测试中,当上下文长度从 2000 Token 膨胀到 5 万甚至 10 万 Token 时,系统通常会遭遇三重致命打击:
- 上下文腐蚀(Context Rot): 所谓上下文腐蚀,是指随着上下文长度的增加,模型从整体内容中精确检索和遵循特定指令的能力呈现渐进式衰减。实验表明,当输入文本极其冗长且包含大量调试废话时,模型的推理准确度反而显著低于仅输入几百 Token 核心约束时的表现。
- 迷失在中间(Lost-in-the-Middle): 现代 Transformer 模型的注意力机制呈现类似 U 型的分布特征:模型对上下文最开头(System Prompt)和最末尾(最新一轮问答)的注意力权重最高,而堆积在中间的大量历史推演,极易被模型忽视。如果业务硬性约束被推到了正文中间,模型就会出现视而不见的违规行为。
- 推理成本与首字延迟(TTFT)不可承受: 单次调用携带 10 万 Token 历史,即便命中缓存,其推理开销与传输延迟依然呈线性暴增。一个经历 30 步推演的任务,若每一步都全量携带膨胀历史,整个任务的 Token 消耗将迅速突破数百万,这在商业化落地中是无法承受的。
因此,上下文绝非无限蓄水池,必须视作一种极其稀缺的注意力预算(Attention Budget)。上下文工程的本质,就是在有限的预算内,以可控的信噪比向模型提供最关键的决策信息。
上下文治理的三维正交物理坐标系#
从第一性原理出发,业界所有复杂的上下文治理手段,本质上都是在回答三个相互独立的物理维度问题:
flowchart TD
subgraph D1["维度一: 治理时机与触发条件 (When)"]
D1_1["静态配额切分 (Static Quota)"]
D1_2["水位线阈值触发 (Watermark Threshold)"]
D1_3["步进反应式清洗 (Per-Step Reactive)"]
end
subgraph D2["维度二: 压缩与折叠策略 (How)"]
D2_1["硬性先进先出截断 (FIFO Truncation)"]
D2_2["语义摘要折叠 (Summary Compaction)"]
D2_3["载荷剔除与引用下沉 (Artifact Offloading)"]
D2_4["状态投影 (State Projection)"]
end
subgraph D3["维度三: 注意力编排与物理布局 (Where)"]
D3_1["时序扁平平铺 (Flat Chronological)"]
D3_2["首尾三明治锚定 (Head-Tail Anchoring)"]
D3_3["动态作用域隔离 (Scoped Context)"]
end维度一:治理时机与触发条件(When)—— 什么时候动手?#
- 静态配额切分(Static Quota): 在发起模型调用前,按固定比例分配 Token 上限。例如为系统指令预留 20%、工具参数与返回预留 40%、历史记录预留 20%、预留模型输出 20%。一旦历史记录超出配额,在请求发出前就拦截并触发治理。
- 水位线阈值触发(Watermark Threshold): 平时允许消息自然追加,设定一个警戒水位线(例如达到上下文预算的 75%)。一旦突破警戒线,触发一次批量的归并与折叠。
- 步进反应式清洗(Per-Step Reactive): 在单步工具调用(Turn-Step)结束后立即就地执行轻量清理(例如直接剥离工具返回中无意义的 HTTP 调试头部)。
维度二:压缩与折叠策略(How)—— 用什么手段瘦身?#
- 硬性先进先出截断(FIFO Truncation): 直接丢弃最旧的历史消息。虽然零额外模型开销,但会丢失早期的初始条件与业务背景。
- 语义摘要折叠(Summary Compaction): 调用轻量模型将过去的 N 步执行轨迹压缩为一段阶段性摘要,用一段叙述替换多条原始消息。
- 载荷剔除与引用下沉(Artifact Offloading): 当工具返回数千行原始网页、SQL 导出结果或超长日志时,将原始文本转存至外部磁盘或对象存储,上下文中仅保留一个包含数据特征与文件标识(Artifact ID)的精简引用凭据。
- 状态投影(State Projection): 跳过自然语言历史的推演,直接通过代码提取系统的当前全局状态(如当前文件改动列表、TODO 看板),并将其作为唯一的上下文背景。
维度三:注意力编排与物理布局(Where)—— 文本摆在什么位置?#
- 时序扁平平铺(Flat Chronological): 按时间自然顺序简单追加。其致命缺陷是在长对话中,早期的硬性约束会被挤压到注意力最低的中间盲区。
- 首尾三明治锚定(Head-Tail Anchoring / Sandwich Layout): 核心系统规则置顶(Head);长程历史或折叠摘要置于中间(Middle);当前待完成的核心任务目标、排除规则与最新环境参数动态锚定在最后一条输入前(Tail),充分激活模型的首尾注意力。
- 动态作用域隔离(Scoped Context): 针对子智能体或独立插件,仅传递其执行当前子任务所需的局部上下文,父级漫长的推演历史对子代理完全不可见。
深度剖析:为什么粗粒度整条消息摘要注定失败#
在工程实践中,很多系统尝试采用如下简陋策略:“当历史消息超出 8000 Token 时,调用一次大模型把前半段消息生成一段文字摘要,然后替换掉它们”。
这种以整条“消息(Message)”为单位的粗粒度摘要,在高精确度业务中会导致严重的系统退化:
1. 物理事实坐标被模糊化(Loss of Exact Handles)#
机器工具的调用依赖高度精确的字面量(Literal),例如绝对文件路径 src/auth/jwt.service.ts、行号 #L42、数据库主键 ID 或具体的异常错误码 ECONNREFUSED。
当大语言模型进行自然语言概括时,其自回归偏置倾向于将细节抽象化,例如写成“认证服务遇到类型错误”。一旦细节被抹平,后续的 Agent 就会因为缺乏精确参数而无法继续调用重构工具。
2. 负向排除分支被抹杀导致死循环(Loss of Pruned Branches)#
在复杂的代码排错或研报搜集任务中,前几轮推演往往包含了大量“被证伪的假设”,例如“已测试方案 A,因端口冲突无法使用”。 粗粒度摘要算法通常会将这些未成功的尝试判定为“无关过程噪声”并直接剔除。这会导致 Agent 遗忘历史上的失败经验,在第 15 轮又重新走回第 3 轮已经被证明走不通的死路,陷入原地踏步的死循环。
3. 业务硬约束发生语义降级(Semantic Drift)#
如果原始指令中包含绝对禁止的红线(如“严禁修改全局环境配置”),经过模型的二手摘要转述后,极易被弱化为“尽量避免改动环境配置”,从而引发失控。
工业级破局:双层解耦与四大不变白名单#
为了彻底根除上述问题,成熟的上下文工程采用了双层解耦架构与四大不变白名单机制:
flowchart TD
subgraph Layer1["解耦一: 机器状态与历史轨迹彻底分离"]
L1_State["全局事实黑板 (Blackboard State)
1. 任务看板 (TODO Tree)
2. 负向剪枝清单 (Pruned Hypotheses)
3. 精确物理句柄 (Touched Files / IDs)"]
L1_History["纯粹历史轨迹 (Execution History)
仅用于推演过程与近期对话承接"]
end
subgraph Layer2["解耦二: 历史消息内部按 Block 细粒度治理"]
B1["ThinkingBlock: 思考推演 -> 超过 2 轮直接整块丢弃"]
B2["ToolResultBlock: 原始巨型输出 -> 下沉为外部引用 (Artifact Ref)"]
B3["Text/ConstraintBlock: 关键规则 -> 标记保护,豁免压缩"]
end豁免压缩的四大不变抽象白名单#
无论业务场景如何变化,以下四类信息在架构层面必须设立“绝对保护白名单”,严禁被自然语言摘要破坏:
- 绝对硬约束与终态目标(Invariable Constraints): 原始用户的核心诉求与边界红线,必须 100% 逐字保留在 Head(置顶)或 Tail(置底),不允许任何模型转述。
- 负向排除分支与已证伪假设(Pruned Branches / Negative Knowledge): 明确记录“已尝试过何种方案、遇到何种明确失败、禁止重复尝试”。这部分信息独立结构化归档,不随消息折叠而丢失。
- 物理事实坐标与精准句柄(Physical Coordinates & Handles): 文件路径、Commit SHA、错误码字面量必须原样保留,禁止模糊概括。
- 机器状态结构化快照(Blackboard Snapshot): 当前时刻的系统实际状态(已修改文件清单、未完成的任务树),由代码直接生成结构化标记,不依赖自然语言推理。
核心实现:基于 GSSC 流水线的上下文治理内核#
在代码实现层面,我们将上下文构建过程抽象为权威基准中的 GSSC 流水线(Gather 获取 - Select 选择 - Structure 结构化 - Compress 压缩)。
下面给出纯 TypeScript 实现的配额仲裁器与动态折叠引擎。
1. 细粒度内容块与消息契约#
消息内部不再使用单一字符串,而是拆分为具有明确语义类型的内容块:
export type BlockType =
| 'text' // 普通对话与核心约束
| 'thought' // 思考过程 (易失效,可快速丢弃)
| 'tool_call' // 工具调用意图与精准参数
| 'tool_result_raw' // 工具返回的超大原始载荷
| 'tool_result_ref'; // 下沉替换后的轻量引用凭证
export interface ContentBlock {
type: BlockType;
content: string;
tokenCount: number;
isProtected?: boolean; // 保护白名单标记:为 true 时严禁被压缩或丢弃
artifactId?: string; // 关联的外部持久化存储句柄
}
export interface ContextMessage {
role: 'system' | 'user' | 'assistant';
blocks: ContentBlock[];
}2. Token 配额仲裁器(TokenBudgetController)#
配额仲裁器负责监控整体预算、设定预留比例,并仲裁何时必须触发治理:
export class TokenBudgetController {
constructor(
private readonly maxBudgetTokens: number = 8000,
private readonly reserveRatio: number = 0.25 // 25% 预算预留给 System 规则与模型输出
) {}
/**
* 计算当前所有消息占用的 Token 总量
*/
public calculateTotalTokens(messages: ContextMessage[]): number {
return messages.reduce((acc, msg) => {
const msgTokens = msg.blocks.reduce((bAcc, b) => bAcc + b.tokenCount, 0);
return acc + msgTokens;
}, 0);
}
/**
* 检查是否超出动态可用预算水位线
*/
public isWatermarkExceeded(messages: ContextMessage[]): boolean {
const currentTokens = this.calculateTotalTokens(messages);
const usableThreshold = this.maxBudgetTokens * (1 - this.reserveRatio);
return currentTokens > usableThreshold;
}
}3. 动态折叠压缩引擎(CompactionEngine)#
折叠引擎分为三步处理:首先将超出阈值的工具大输出存盘并替换为轻量引用,接着清理深层历史中不再需要的旧思考推演,最后对非保护的中间历史进行折叠生成摘要:
export class CompactionEngine {
/**
* 第一步:超大工具返回替换为外部存储引用 (Artifact Offloading)
* 将庞大的工具原始输出替换为包含数据特征与文件引用的极短凭据
*/
public offloadToolResults(messages: ContextMessage[], tokenLimit = 300): ContextMessage[] {
return messages.map(msg => ({
...msg,
blocks: msg.blocks.map(block => {
if (block.type === 'tool_result_raw' && block.tokenCount > tokenLimit) {
return {
type: 'tool_result_ref',
content: `[原始数据已下沉至存储: ${block.artifactId || 'artifact_ref'}, 核心结果: 见状态快照]`,
tokenCount: 25,
isProtected: true, // 引用凭证本身受保护
};
}
return block;
}),
}));
}
/**
* 第二步:清理深层历史中的过期思考推演块
*/
public pruneStaleThoughts(messages: ContextMessage[], keepRecentTurns = 2): ContextMessage[] {
const cutoffIndex = Math.max(0, messages.length - keepRecentTurns);
return messages.map((msg, index) => {
if (index >= cutoffIndex) return msg;
// 深层历史中,直接剥离非受保护的思考块
return {
...msg,
blocks: msg.blocks.filter(b => b.type !== 'thought' || b.isProtected),
};
});
}
/**
* 第三步:保留首尾并折叠中间历史 (Head-Tail Compaction)
* 保留头部硬约束与尾部活跃上下文,中间段执行受控摘要折叠
*/
public compactHistory(
messages: ContextMessage[],
keepTailCount: number,
summarizeFn: (intermediate: ContextMessage[]) => string
): ContextMessage[] {
if (messages.length <= 1 + keepTailCount) return messages;
const head = messages[0]; // 头部硬约束 (System)
const tail = messages.slice(-keepTailCount); // 尾部近期活跃窗口
const intermediate = messages.slice(1, -keepTailCount); // 待折叠的中间带
// 仅针对中间带的非受保护内容执行压缩
const summaryText = summarizeFn(intermediate);
const compactedMiddleMessage: ContextMessage = {
role: 'assistant',
blocks: [
{
type: 'text',
content: `【阶段性执行折叠摘要】:\n${summaryText}`,
tokenCount: 120,
isProtected: true,
},
],
};
return [head, compactedMiddleMessage, ...tail];
}
}故障对比实验:Lost-in-the-Middle 现象与防御实测#
为了验证注意力编排对模型决策的决定性影响,我们设计了一组排错对比实验:
实验设定#
- 业务硬约束:
业务硬红线:当前项目为极简轻量环境,严格禁止通过 npm 安装任何外部第三方库,必须全部使用 Node.js 原生 API 实现。 - 任务流程:模型需要处理一个包含 30 步推演的长程文件迁移与加解密任务。
flowchart LR
subgraph BadCase["方案 A: 自然平铺 (硬约束置于第 15 步中间盲区)"]
A1["第 1~14 步: 正常推演"] --> A2["第 15 步: 插入'严禁安装 npm 依赖'"]
A2 --> A3["第 16~30 步: 产生大量文件调试日志 (累积 6 万 Token)"]
A3 --> A4["第 30 步决策: 遇到加密需求"]
A4 --> A5["[异常] 模型调用 npm install crypto-js 违规"]
end
subgraph GoodCase["方案 B: 首尾双锚定 (Head-Tail Sandwich)"]
B1["Head (置顶): 系统全局硬红线"] --> B2["Middle: 历史折叠摘要 + 外部引用"]
B2 --> B3["Tail (置底): 最新任务前动态附带硬红线提醒"]
B3 --> B4["第 30 步决策: 遇到加密需求"]
B4 --> B5["[通过] 模型准确调用 node:crypto 原生模块实现"]
end实验结果与根因分析#
- 方案 A(自然平铺):
在上下文达到 6 万 Token 时,第 15 步的硬约束深陷 U 型注意力底部的“记忆死角”。当第 30 步面临加密算法编写时,模型完全遗忘了历史中间的红线,习惯性地调用包管理器去安装外部的
crypto-js库,造成严重业务违例。 - 方案 B(首尾双锚定):
约束在 Head 区域保持不变,同时通过运行时调度器在每次向模型派发 Tail 提示词时,将当前未完成的红线动态重新锚定在最后一条 User 消息末尾。模型在最新的前向注意力范围内始终可见该约束,从而稳定选用了 Node.js 内置的
node:crypto库,合规率达到 100%。
总结与工程选型原则#
上下文工程绝不是“在报错时从数组前面删几条消息”,而是一门在有限计算资源与物理注意力限制下平衡信噪比的严谨学科。
在设计生产级长程 Agent 时,推荐采用以下标准组合架构:
- 时机上:采用“静态预留配额(25%)+ 警戒水位线(75%)动态触发”双保险;
- 结构上:彻底分离机器状态看板(Blackboard)与对话轨迹历史(History),杜绝把系统现状委托给自然语言摘要猜测;
- 策略上:优先使用载荷下沉(Artifact Offloading)清洗大型工具返回,再对深层思考进行剪枝,最后采用首尾三明治折叠归纳中间过程;
- 注意力上:核心业务红线必须实施首尾双锚定,彻底化解大模型的 Lost-in-the-Middle 遗忘难题。