Agent 工程体系 · 第 23/98 篇。内容以 2026 年 9 月可验证的公开规范和稳定接口为基线;框架版本敏感能力会明确标注,不把实验行为写成通用保证。
Agent 上下文摘要:触发、保真、滚动更新、校验和恢复
Agent 的上下文摘要,不是把旧消息压缩成一段更短的文字,而是对运行状态进行有损编码:系统主动丢弃部分原始轨迹,只保留后续决策仍然需要的信息。
这一定义带来一个直接结论:
摘要可以替代部分阅读材料,但不能自动替代事实源、工具结果、执行状态和审计记录。
如果把 Agent 的输入仅理解为“最近几轮聊天消息”,摘要很容易做成一个看似节省 Token、实际破坏状态的黑盒。真正需要摘要的对象至少包括:
- 用户消息与助手消息;
- 工具调用及其参数;
- 工具返回值、错误和重试结果;
- 当前任务目标、约束和已确认事实;
- 已经完成的副作用,例如订单创建、文件写入、邮件发送;
- 尚未完成的计划、待确认动作和人工审批状态;
- 长期记忆的读取结果及其来源;
- Agent 的中间状态、版本号和恢复位置。
本文把摘要视为上下文管理系统的一部分,重点讨论四个问题:
- 什么时候触发摘要;
- 如何控制摘要保真度;
- 如何在新消息和工具轨迹到来后滚动更新;
- 如何使用校验和、版本和持久化状态完成校验与恢复。
一、先区分四种“上下文”
摘要系统经常失败,不是因为摘要模型能力不足,而是因为把不同性质的数据混在了一起。
1. 原始事件流
原始事件流是不可变的事实记录,例如:
event_id: 184
role: user
content: “把订单 10086 改成明天送达”
或者:
event_id: 185
type: tool_call
tool: update_delivery_date
arguments: {"order_id": "10086", "date": "2026-09-02"}
以及:
event_id: 186
type: tool_result
status: success
result: {"order_id": "10086", "delivery_date": "2026-09-02"}
原始事件流适合审计、重放和争议处理,不适合无限制地直接放进模型上下文。
2. 工作上下文
工作上下文是当前请求真正需要看到的输入,通常由以下内容拼接而成:
系统规则
+ 当前任务状态
+ 摘要
+ 最近若干轮原文
+ 最近工具结果
+ 检索到的长期记忆
+ 当前用户消息
它不是数据库中的“全部历史”,而是一次模型调用的视图。
3. 摘要
摘要是从历史事件流计算出的压缩表示。它可以是自然语言,也可以是结构化对象:
{
"goal": "将订单 10086 的配送日期改为 2026-09-02",
"confirmed_facts": [
{
"fact": "用户拥有订单 10086 的修改权限",
"source_event_ids": [181]
}
],
"completed_actions": [
{
"action": "更新配送日期",
"status": "succeeded",
"source_event_ids": [185, 186]
}
],
"pending_actions": [],
"constraints": [
"未获得用户授权时不能修改其他订单"
],
"open_questions": [],
"last_event_id": 186
}
4. 检查点
检查点是某个时刻的可恢复状态,至少包含:
摘要版本
事件游标
最近消息
待执行动作
工具调用幂等键
模型调用上下文
校验和
LangGraph 将这类短期、线程范围内的图状态快照称为 checkpointer;而跨线程保存的用户偏好、事实和共享知识属于 store。两者的作用范围不同,不能用长期 Store 替代当前执行线程的检查点。(docs.langchain.com)
二、摘要的目标不是“更短”,而是“在限定任务上等价”
设完整历史为 ,摘要为 ,当前请求和系统规则为 。模型基于上下文产生动作:
摘要后变为:
理想条件不是要求 ,因为这会失去压缩意义,而是要求:
也就是:在当前任务相关的决策上,摘要前后的行为保持一致。
更严格地,可以定义一个任务相关判定函数 :
表示两次输出在安全性、事实、工具参数和任务进度上等价。
这个定义解释了为什么“摘要看起来通顺”远远不够。下面两个摘要都很自然:
用户想要修改订单配送日期,目前正在处理中。
用户已确认将订单 10086 改为 2026-09-02,更新操作已成功完成。
第一个摘要丢失了订单号、目标日期和完成状态。对闲聊任务可能足够,对下一次工具调用则不够。
保真度的四个层次
可以把摘要保真度拆成四个层次:
- 语义保真:保留用户真正想完成的目标;
- 事实保真:保留数值、名称、时间、权限、约束等可验证事实;
- 状态保真:准确区分未开始、执行中、成功、失败、取消和未知;
- 因果保真:保留哪些动作导致了哪些状态变化。
工程上最危险的是只检查第一层。例如摘要仍然说“用户要改配送日期”,但把“工具调用失败”压缩成“正在处理中”,后续 Agent 可能重复执行副作用。
三、什么时候触发摘要
摘要触发通常有三类条件:容量触发、结构触发和风险触发。
1. 容量触发
一次请求可用的上下文预算不是模型的最大上下文窗口。设:
- :模型单次请求的上下文窗口;
- :系统提示、工具定义、当前消息和检索结果占用的 Token;
- :预留输出 Token;
- :推理或隐藏过程所需的预算;
- :安全余量;
- :历史原文占用的 Token。
可用历史预算为:
当:
时,继续追加原文就有超限风险。
需要注意,上下文窗口通常同时受输入、输出以及某些模型的推理 Token 影响,不能只按照消息输入长度估算。OpenAI 文档明确将这些部分都纳入请求生命周期中的上下文限制,并建议使用 Tokenizer 或 tiktoken 进行估算。(developers.openai.com)
实际系统不应等到超限才摘要,而应采用提前触发:
其中 可以是一个低于 1 的阈值,例如 0.7 或 0.8。这个数不是规范要求,而是实现策略;具体值应由输出长度、工具调用密度和失败重试成本决定。
2. 结构触发
即使 Token 还没有超限,也可能需要摘要:
- 一个工具调用链已经完成;
- 一个子任务已经结束;
- 用户切换了主题;
- 当前任务从“规划”进入“执行”;
- 某个重要事实发生了确认或更正;
- 人工审批完成;
- 对话出现多个分支,需要合并主线。
结构触发的原因是:上下文的风险不只来自长度,还来自状态混乱。一个 20 万 Token 的工具轨迹即使仍放得下,也可能让模型难以判断哪个结果是最终结果。
3. 风险触发
以下事件不应只依赖普通摘要:
- 付款、转账、删除、发布、发信等不可逆副作用;
- 权限、身份和合规结论;
- 工具返回相互矛盾;
- 关键外部状态可能已经变化;
- 摘要校验失败;
- 服务重启后从未知位置恢复;
- 并发分支同时修改同一任务。
风险触发通常意味着“摘要 + 原始证据”组合,而不是“摘要替代证据”。
四、为什么不能简单地按消息条数裁剪
最简单的裁剪算法是:
history = history[-20:]
它有三个问题。
问题一:消息边界不等于任务边界
一轮工具调用往往包含:
assistant tool_call
tool result
assistant interpretation
如果只保留最后 20 条,可能留下工具结果,却裁掉调用参数;或者保留调用,却裁掉用户授权。
问题二:最近不等于重要
用户在第 2 轮确认了“只修改订单 10086”,第 50 轮产生了大量日志。按时间裁剪后,权限边界可能消失。
问题三:文本不能表达执行状态
“调用更新接口”与“更新接口返回成功”不是同一件事。裁剪或自由摘要如果没有明确状态枚举,就容易把计划写成事实。
因此,摘要前应先将事件按不可拆分单元分组:
单元 A:用户授权 + 任务目标
单元 B:查询订单 + 查询结果
单元 C:更新调用 + 更新结果
单元 D:用户追问 + 助手解释
裁剪时只删除完整单元,不能删除工具调用和工具结果之间的半条链。
五、摘要应采用“稳定摘要 + 最近原文”的双层结构
单一摘要会持续重写,错误也会被不断放大。更稳妥的上下文视图是:
[稳定摘要 S_k]
[摘要之后的增量事件 E_(k+1...n)]
[当前用户消息]
模型看到的不是一次性生成的全文总结,而是:
其中:
- 是最近一次通过校验的稳定摘要;
- 是摘要之后尚未折叠的原始事件;
- 表示按协议拼接,而不是简单字符串连接。
当增量事件达到触发条件后,再生成:
生成成功后,提交新的摘要版本,并把事件游标推进到 。
这个设计有两个重要性质:
- 摘要失败时,仍可使用旧摘要和原始增量继续运行;
- 摘要错误时,可以从旧检查点和原始事件重新计算。
六、摘要内容必须结构化表达状态
自然语言适合给模型读,结构化字段适合校验和恢复。生产系统可以采用“结构化主数据 + 人类可读说明”:
{
"schema_version": 3,
"task": {
"goal": "为订单 10086 查询可用配送日期",
"status": "waiting_for_user_confirmation"
},
"facts": [
{
"key": "order_id",
"value": "10086",
"confidence": "confirmed",
"source_event_ids": [12]
}
],
"decisions": [
{
"decision": "用户尚未确认是否接受 2026-09-02",
"status": "active",
"source_event_ids": [19]
}
],
"tool_state": {
"query_delivery_options": {
"status": "succeeded",
"request_id": "req-abc",
"source_event_ids": [15, 16]
}
},
"pending": [
{
"action": "等待用户确认配送日期",
"blocking": true
}
],
"omitted_event_range": {
"from": 1,
"to": 16
}
}
状态字段必须使用有限集合
例如:
planned
running
succeeded
failed
cancelled
unknown
unknown 很重要。服务超时并不等于副作用没有发生:
请求支付接口超时
正确状态可能是:
payment_status = unknown
而不是:
payment_status = failed
后者可能导致 Agent 重复扣款。
事实必须带来源
摘要中的事实最好保留:
source_event_ids:来自哪些事件;observed_at:何时观察到;authority:用户、工具、数据库还是模型推断;valid_until:是否有有效期;confidence:确认、推断或未知。
例如:
{
"key": "delivery_date",
"value": "2026-09-02",
"authority": "order_service",
"confidence": "confirmed",
"observed_at": "2026-09-01T10:20:00+08:00"
}
这能区分“用户曾经说过”与“外部系统当前确认”。
七、摘要生成不是一次总结,而是一次状态折叠
一次滚动摘要可以拆成五步。
第一步:确定折叠边界
假设当前状态为:
稳定摘要版本:S7
已折叠事件:1–80
新增事件:81–96
系统先确定本次只处理 81–96,而不是把全部历史重新交给摘要模型。
第二步:锁定不可丢失字段
在调用摘要模型前,系统根据任务类型生成保真约束:
必须保留:
- 订单号
- 用户授权范围
- 工具调用状态
- 成功写操作的 request_id
- 未完成动作
- 失败原因
- 事件范围
这些约束不能只写在摘要提示词里,还应由代码在输出后检查。
第三步:生成候选摘要
候选摘要可以使用结构化输出。伪代码如下:
candidate = summarize(
previous_summary=S7,
new_events=events[81:97],
required_fields=[
"goal",
"facts",
"completed_actions",
"pending_actions",
"tool_state"
]
)
摘要模型的职责是压缩和归纳,不是重新执行工具,也不是凭空修复矛盾。
第四步:合并与规范化
合并时需要处理同一事实的更新:
旧摘要:delivery_date = 2026-09-02
新事件:用户改口要求 2026-09-03
如果新事件来自用户,通常可以将旧值标记为过期:
{
"key": "delivery_date",
"value": "2026-09-03",
"previous_value": "2026-09-02",
"status": "active",
"source_event_ids": [101]
}
但如果新事件只是模型猜测,就不能直接覆盖工具确认值。
第五步:校验后提交
只有通过校验的摘要才能成为新稳定摘要:
生成候选 S8
↓
Schema 校验
↓
关键字段完整性检查
↓
事件游标连续性检查
↓
工具状态一致性检查
↓
写入检查点
↓
原子提交 S8
如果任一步失败,应保留 S7 和 81–96 的原始事件,不应直接覆盖旧摘要。
八、保真度校验:摘要必须能够被反驳
摘要校验不应只检查 JSON 格式。至少需要四类检查。
1. 结构校验
检查字段类型、枚举值和必填字段:
assert summary["schema_version"] == 3
assert summary["task"]["status"] in {
"planned", "running", "waiting_for_user_confirmation",
"succeeded", "failed", "cancelled", "unknown"
}
2. 覆盖校验
对于高风险事件,摘要必须引用来源:
for action in summary["completed_actions"]:
assert action["source_event_ids"]
3. 反事实校验
向另一个模型或规则检查器提出问题:
根据摘要,订单 10086 的更新操作是否已经成功?
如果不能确定,请回答 unknown。
然后与摘要中的状态比较。
4. 工具一致性校验
如果原始工具日志显示:
update_order: timeout
摘要不能出现:
update_order: succeeded
如果工具系统有独立数据库状态,摘要中的副作用状态还应优先由数据库或工具查询结果确认,而不是由摘要模型决定。
九、校验和到底校验什么
校验和不是用来证明摘要“正确”,而是用来检测摘要或事件是否发生了意外变化。
设规范化后的事件序列为:
摘要状态为 ,版本为 ,则可以计算:
其中:
- :摘要协议版本;
cursor:已经折叠到哪个事件;- :被折叠的事件范围;
- :规范化后的摘要;
- :最终校验和。
必须先规范化再计算,否则 JSON 字段顺序、空格和时间格式变化都会产生不同结果。
示例实现:
import hashlib
import json
def canonical_json(value: object) -> bytes:
return json.dumps(
value,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
).encode("utf-8")
def checksum(summary: dict, cursor: int, event_ids: list[int]) -> str:
payload = {
"schema_version": summary["schema_version"],
"cursor": cursor,
"event_ids": event_ids,
"summary": summary,
}
return hashlib.sha256(canonical_json(payload)).hexdigest()
输入:
summary = {
"schema_version": 3,
"task": {"goal": "查询订单", "status": "running"},
}
print(checksum(summary, 96, list(range(1, 97))))
预期输出是一个 64 位十六进制字符串。具体字符串取决于输入内容;它只能说明“当前内容与保存时内容是否一致”,不能说明“内容是否真实”。
校验和的三个边界
校验和不能发现模型语义错误
模型把“失败”写成“成功”,只要该错误摘要被重新计算校验和,校验和仍然有效。
校验和不能替代来源
必须同时保存事件范围、事件 ID 或事件哈希,否则只校验摘要文本,无法知道摘要覆盖了哪些历史。
普通哈希不能提供防篡改证明
如果攻击者可以同时修改摘要和校验和,SHA-256 只能检测普通数据损坏,不能证明数据未被有权限的攻击者修改。需要防篡改时,应使用带密钥的 HMAC、数据库审计链或外部不可变日志。
十、用哈希链检测事件缺失和重排
单个摘要校验和只能验证一个快照。对于连续事件流,可以使用哈希链:
其中 是固定初始值。
如果事件 82 被删除,或者事件 85 与 86 对调,后续哈希都会变化:
h80 → event81 → h81 → event82 → h82 → ... → h96
检查点保存:
{
"cursor": 96,
"event_chain_hash": "…",
"summary_checksum": "…"
}
恢复时重新计算事件链。如果链断裂,系统不能安全地把当前摘要当作完整状态,而应进入降级流程:
- 查找最近一个链完整的检查点;
- 从该检查点重新读取原始事件;
- 重新生成摘要;
- 对未确认的副作用执行状态查询;
- 必要时要求人工介入。
十一、恢复的关键不是“重新摘要”,而是找到安全边界
典型故障发生在摘要提交中间:
1. 读取 S7 和事件 81–96
2. 生成候选 S8
3. 写入摘要成功
4. 写入 cursor 失败
此时系统可能出现:
summary = S8
cursor = 80
如果重试时再次把 81–96 合并进 S8,可能重复计算或重复表达状态。
因此,摘要和游标必须原子提交,或者使用可检测的两阶段状态:
PREPARED:
candidate_summary = S8
candidate_cursor = 96
candidate_checksum = h8
COMMITTED:
summary = S8
cursor = 96
checksum = h8
恢复逻辑:
def recover(snapshot, events):
if snapshot.phase == "COMMITTED":
verify(snapshot, events)
return snapshot
if snapshot.phase == "PREPARED":
if verify_candidate(snapshot, events):
commit(snapshot)
return snapshot
else:
discard_candidate(snapshot)
return rebuild_from_previous_checkpoint(snapshot, events)
raise RuntimeError("unknown snapshot phase")
这里的 verify_candidate 不仅检查候选摘要格式,还要检查:
- 候选游标是否连续;
- 事件链哈希是否匹配;
- 摘要引用的事件是否存在;
- 工具副作用是否已确认;
- schema 版本是否兼容。
十二、摘要恢复与副作用恢复必须分开
这是 Agent 系统中最容易被忽视的边界。
假设 Agent 在事件 120 调用了退款接口,随后进程崩溃:
tool_call: refund(order_id=10086, idempotency_key=k1)
network: timeout
process: crashed
恢复时不能因为没有工具结果就重新发起一个新请求。正确流程是:
1. 使用 idempotency_key=k1 查询退款接口状态;
2. 如果已成功,记录 succeeded;
3. 如果明确失败,记录 failed;
4. 如果仍未知,暂停并要求人工或业务系统确认;
5. 只有确认服务端未接受时,才允许重试。
摘要只负责表达:
refund_status = unknown
它不负责证明退款是否发生。证明必须来自工具服务、业务数据库或带幂等语义的外部系统。
十三、并发摘要必须使用版本控制
当同一会话存在并发请求:
请求 A:读取 S7,处理事件 81–90
请求 B:读取 S7,处理事件 81–95
如果 A 和 B 都成功写入,后写入者可能覆盖前者,导致事件 91–95 的状态消失。
可以使用乐观并发控制:
UPDATE agent_context
SET
summary_json = :new_summary,
cursor = :new_cursor,
checksum = :new_checksum,
version = version + 1
WHERE
conversation_id = :conversation_id
AND version = :expected_version
AND cursor = :expected_cursor;
如果更新行数为 0,说明版本已经变化,当前摘要不能直接提交。系统应:
- 重新读取最新检查点;
- 将未折叠事件重新计算;
- 再次生成或合并;
- 重试有限次数;
- 超过次数后转入串行队列或人工诊断。
当两个分支代表用户明确选择的不同方案时,不应强行合并。应把它们记录为不同分支,直到用户确认主线。
十四、OpenAI 会话状态与本地摘要的关系
OpenAI 的 Responses API 支持几种会话状态管理方式:
- 手动把历史消息和工具项传回请求;
- 使用
conversation标识持久化会话; - 使用
previous_response_id将响应串成线程。
官方文档说明,Conversations API 可以持久化消息、工具调用、工具输出等项目,并支持跨会话、设备或任务继续使用;previous_response_id 则用于把响应串成线程。(developers.openai.com)
这解决的是“服务端如何保存或关联会话项”,不等于解决了“业务上哪些内容应该进入模型上下文”。即使使用 previous_response_id,历史输入 Token 仍会计入输入计费;而上下文窗口仍然受单次请求的 Token 限制。(developers.openai.com)
因此,常见分层是:
业务事件库
↓
本地摘要与检查点
↓
选择当前模型上下文
↓
Responses API conversation / previous_response_id
服务端会话状态可以减少应用层重复拼接,但不能替代:
- 业务事实的权威来源;
- 工具副作用的确认;
- 摘要 schema;
- 事件游标;
- 本地校验和;
- 故障恢复策略。
OpenAI 文档还将 compaction 作为上下文管理能力,并提供服务端 compaction 与显式 compact endpoint 的方向;具体参数和可用范围应以目标 API 版本的官方文档为准,不能把服务端自动压缩当成业务状态一致性保证。(developers.openai.com)
十五、LangGraph 中的检查点、摘要和长期记忆
LangGraph 的持久化层将短期线程状态交给 checkpointer,将跨线程的长期数据交给 store。官方快速示例通过 thread_id 访问同一线程的图状态。(docs.langchain.com)
一个简化的结构如下:
from typing import TypedDict
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.graph import StateGraph, START, END
class State(TypedDict, total=False):
messages: list[dict]
context_summary: dict
cursor: int
def summarize_if_needed(state: State) -> State:
messages = state.get("messages", [])
summary = state.get("context_summary", {
"goal": None,
"facts": [],
"pending_actions": [],
})
# 示例:真实系统应按 Token、工具边界和风险事件触发。
if len(messages) <= 20:
return state
old = messages[:-8]
recent = messages[-8:]
candidate = {
"goal": summary["goal"],
"facts": summary["facts"],
"pending_actions": summary["pending_actions"],
"folded_message_count": len(old),
}
return {
**state,
"messages": recent,
"context_summary": candidate,
"cursor": state.get("cursor", 0) + len(old),
}
builder = StateGraph(State)
builder.add_node("summarize", summarize_if_needed)
builder.add_edge(START, "summarize")
builder.add_edge("summarize", END)
graph = builder.compile(checkpointer=InMemorySaver())
result = graph.invoke(
{
"messages": [
{"role": "user", "content": "我的订单号是 10086"},
{"role": "assistant", "content": "已记录订单号"},
],
"cursor": 0,
},
{"configurable": {"thread_id": "order-10086"}},
)
print(result)
这个示例展示的是生命周期,不是完整摘要器:
- 通过
thread_id绑定线程; - 读取当前图状态;
- 根据条件生成候选摘要;
- 保留最近原文;
- 将状态交给 checkpointer。
InMemorySaver 适合演示和测试,因为进程重启会丢失内存中的检查点;生产环境应使用持久化 checkpointer。官方文档列出了 PostgreSQL 和 SQLite 等实现,并提醒长期对话的检查点会无限增长,需要设置保留策略或定期清理。(docs.langchain.com)
如果子图产生了需要跨线程共享的用户事实,不应只依赖子图自己的 checkpoint namespace。官方文档指出,子图更新的状态可能不会立即被父图看到;跨图边界的数据应考虑使用共享 Store 或显式写入父图检查点。(docs.langchain.com)
十六、一个完整的滚动摘要流程
下面的时序可以概括生产系统中的主路径:
sequenceDiagram
participant U as 用户
participant A as Agent Orchestrator
participant C as Context Builder
participant S as Summary Service
participant E as Event Store
participant P as Checkpoint Store
participant T as Tool Service
U->>A: 新消息
A->>E: 写入 user_event
A->>P: 读取最新 checkpoint
A->>C: 构造摘要 + 增量事件 + 最近原文
C->>A: 当前模型上下文
A->>T: 工具调用
T-->>A: 工具结果
A->>E: 写入 tool_call/tool_result
A->>A: 判断是否触发摘要
alt 需要摘要
A->>S: 旧摘要 + 未折叠事件
S-->>A: 候选摘要
A->>A: Schema、来源、状态、哈希校验
A->>P: CAS/原子提交新摘要和游标
else 暂不摘要
A->>P: 保存增量状态
end
A-->>U: 响应
关键路径不是“调用摘要模型”,而是:
事件先落盘
→ 读取版本化检查点
→ 生成候选摘要
→ 校验候选摘要
→ 原子提交摘要、游标和校验和
如果摘要模型调用成功但提交失败,候选摘要只是临时对象;如果工具调用成功但事件落盘失败,则必须优先处理事件可见性问题,不能继续把状态当成已持久化。
十七、常见错误及其失败表现
把摘要当作唯一事实源
失败表现: 用户纠正一个早期事实后,摘要仍然沿用旧值。
原因: 摘要被覆盖写入,却没有保留事件来源和事实版本。
诊断:
检查摘要字段的 source_event_ids
检查事件游标是否覆盖纠正事件
检查事实是否有 active/expired 状态
只摘要自然语言,不摘要工具状态
失败表现: Agent 重复执行已经成功的写操作。
原因: 摘要保留了任务目标,却删除了幂等键、请求 ID 和工具结果。
修复: 将工具调用视为状态转移,而不是普通聊天文本。
使用超时作为失败结论
失败表现: 支付、退款、创建资源等动作发生重复副作用。
原因: 网络层失败被错误映射为业务层失败。
修复: 引入 unknown,恢复时查询外部系统状态。
摘要和检查点分别提交
失败表现: 摘要显示已折叠 100 条消息,但游标仍停在 80;重启后事件重复或丢失。
原因: 摘要、游标和校验和不是同一个原子版本。
修复: 使用事务、CAS 或 prepared/committed 两阶段状态。
用内存检查点验证生产恢复
失败表现: 单机测试正常,服务重启、扩容或故障转移后会话消失。
原因: 内存保存器只提供进程生命周期内的连续性。LangGraph 官方文档明确指出,MemorySaver 和 InMemorySaver 重启后不会保留检查点。(docs.langchain.com)
十八、如何评估摘要是否真的保真
不要只测试摘要文本是否“读起来像原文”。应构造任务级评估:
事实恢复测试
给出摘要后,要求 Agent 回答:
订单号是什么?
用户授权修改哪些字段?
最后一次工具调用是否成功?
当前还缺什么确认?
与原始事件流中的标准答案比较。
工具参数测试
让 Agent 继续执行:
根据当前状态,下一次工具调用的参数是什么?
检查:
- ID 是否正确;
- 日期、金额和单位是否正确;
- 是否错误重复执行;
- 是否在缺少授权时继续执行。
故障恢复测试
在以下位置强制杀进程:
摘要生成前
摘要生成后、提交前
摘要提交后、响应返回前
工具请求发送后、结果落盘前
恢复后检查:
- 是否丢失事件;
- 是否重复副作用;
- 是否能够定位最后一个安全检查点;
- 是否把未知状态误判为失败或成功。
反事实测试
将一个关键事件删除、替换或重排,再检查哈希链、游标和摘要一致性检查能否发现异常。
结语:摘要是可验证的状态压缩
Agent 上下文摘要的核心不是“让提示词变短”,而是建立一套可恢复的状态压缩协议:
原始事件流:保存事实
摘要:压缩任务相关状态
最近原文:保留局部细节
检查点:保存恢复位置
校验和:检测意外变化
工具查询:确认外部副作用
长期记忆:保存跨线程事实
一个合格的摘要系统至少应满足:
如果摘要只追求压缩率,它会成为新的隐性错误源;如果摘要带有事件游标、状态枚举、来源引用、版本控制和恢复路径,它才真正成为 Agent 工程体系中的上下文基础设施。
系列导航与关联阅读
- 系列入口:Agent 工程完整路线:从运行循环、记忆与协议到安全、评测和生产交付
- 上一篇:Agent 短期记忆:消息历史、工具轨迹、Token 窗口和裁剪
- 下一篇:Agent 长期记忆:写入策略、检索、更新、冲突和遗忘
官方资料
本文依据 Agent、模型、协议与框架官方资料重新梳理;正文、示例与生产清单由 WR BLOG 编写。

评论
0 条讨论