CHAPTER 04 / Agent 工程
让记忆保留证据,而不是固化错误
区分工作状态、事实和经验,为持久记忆加入来源、范围与失效规则。
- 区分当前上下文与跨任务持久记忆
- 设计可追溯且能失效的事实记录
- 避免把模型推测或过期结论当成已确认事实
先决定需要记住什么
Repo 助手第二天再回答超时问题,若每次都遍历仓库,速度会很慢;若永远沿用昨天的答案,又可能出错。记忆解决的是跨轮次或跨任务的信息保存,不会自动让信息保持真实。当前上下文是这次模型调用可见的材料,持久记忆则存放在文件或数据库中,需要检索后才能进入上下文。
把记忆分成几类会更容易治理。工作状态记录任务做到哪里;事实记录配置值和来源;经验记录某种查找方式在过去是否有效;用户偏好记录得到授权保存的长期选择。这些类别的有效期不同。“先查 config 目录”可能长期有用,“生产超时 30 秒”则可能随一次提交立刻过期。
一条事实要携带它成立的条件
建议事实记录包含 statement、source、source_version、observed_at、scope 和 status。scope 可以是仓库、分支与环境,避免把测试配置用于生产。status 区分待验证、已核验和失效。模型对自己“有信心”不能替代证据;如果来源只有另一个模型的总结,就应继续追溯原文。
以“超时 30 秒”为例,更完整的记录是“demo 仓库的 prod.cfg 第 2 行,在内容哈希 H 的版本中定义 timeout=30”。这仍不保证运行时没有环境变量覆盖,因此结论应写到证据支持的范围。记录越精确,后续越容易判断是否还适用,而不是把一条局部观察变成项目全局规则。
写入、检索、核验、更新、删除
记忆生命周期至少有五步。先筛选值得长期保留的内容;检索时限制任务范围;使用前核验来源;发现变化后更新或失效;不再需要时删除。每次工具返回都写入记忆会积累大量噪声,通常只保存稳定的定位线索、关键决策与有来源的结果。
更新时不要简单叠加互相矛盾的事实。可以用同一实体与属性作为键,保留版本历史,把旧记录标为 superseded。对没有可读取版本的外部资料可以用过期时间触发复核,但“尚未到期”只能表示暂未要求重查,不能证明事实从未变化。
工作状态记忆还需要记录动作与确认结果的区别。比如“准备写入补丁”只能标记计划,“补丁写入且测试通过”才是已完成状态。进程重启时先核对真实文件与测试结果,再继续下一个步骤。若把计划当成完成事实保存,恢复任务会跳过必要工作;若把已执行动作当成未执行,则可能重复产生副作用。
这也是记忆与日志分开保存的理由。日志保留发生过的事件,状态则是当前认可的结论。可以从日志重新计算状态,但不应把每一条历史记录都直接作为今天的事实送给模型。
动手:让来源变化自动使记忆失效
下面用 Python 标准库 sqlite3 存储一条事实,并用 SHA-256 比较来源内容。无需 API。演示数据库位于内存中;改成自己的数据库文件路径后,记录才会跨进程保存。示例不使用向量数据库,因为核心问题是证据有效性。
import hashlib
import sqlite3
sources = {"prod.cfg": "timeout=30"}
def digest(text):
return hashlib.sha256(text.encode("utf-8")).hexdigest()
conn = sqlite3.connect(":memory:")
conn.execute("""CREATE TABLE facts (
scope TEXT, statement TEXT, source TEXT, source_hash TEXT
)""")
conn.execute("INSERT INTO facts VALUES (?, ?, ?, ?)",
("demo/prod", "配置定义 timeout=30", "prod.cfg",
digest(sources["prod.cfg"])))
conn.commit()
def recall(scope):
valid = []
rows = conn.execute("SELECT statement, source, source_hash FROM facts "
"WHERE scope = ?", (scope,))
for statement, source, stored_hash in rows:
current = sources.get(source)
if current is not None and digest(current) == stored_hash:
valid.append({"statement": statement, "source": source})
return valid
print("修改前:", recall("demo/prod"))
sources["prod.cfg"] = "timeout=45"
print("修改后:", recall("demo/prod"))
assert recall("demo/prod") == []
assert recall("other/prod") == []
conn.close()修改前能召回 30 秒,来源改成 45 后旧记录不再被返回。下一步应重新读取并形成新事实,而不是让模型用旧事实猜测新值。真实文件的整体哈希可能因无关排版变化失效,这是较保守的取舍;更精细的片段版本能减少重查,但实现复杂度更高。
相关记忆不等于可信记忆
向量相似度回答“这段话是否与问题相近”,不会回答“是否当前有效、是否属于同一用户、是否来源可靠”。先按权限和任务范围过滤,再做关键词或向量检索,最后检查版本与证据。对于小型仓库,明确路径和关键词往往已经够用,先建立可解释基线再增加检索复杂度。
MemGPT 研究了在不同存储层之间移动信息,以扩展有限上下文的可用范围。这里借用分层管理的思路;它并不意味着把内容写入数据库就能得到无限、无误的记忆。阅读 MemGPT 原论文时可重点关注信息何时进入、何时离开当前上下文。
用变化场景验证记忆带来的收益
至少测四类任务:原资料不变、配置修改、文件删除、环境切换。前者测试重复任务是否更快,后三者测试系统能否拒绝沿用错误证据。还要检查失败经验的适用范围,例如一次搜索无结果不应永久阻止以后搜索同一路径,因为仓库可能新增文件。
长期记忆会增加运维成本。要明确谁能查看、修改与删除记录,日志避免保存不必要的敏感内容。对个人学习项目可以先只保存文件定位和任务状态,把具体事实每次重新验证。更少的记忆有时更可靠,是否值得持久化取决于重复收益与维护负担。
LEARN BY DOING
给 Repo 助手建立可失效的事实记忆
- 运行 SQLite 示例,验证来源改变后不会返回旧事实。
- 增加 observed_at、status 和环境字段,并为事实设计稳定 id。
- 模拟删除来源、切换环境和配置更新;记录命中、失效与重新读取次数。
验收标准
- 未变资料可命中;已改或已删除来源不会作为有效证据返回。
- 测试环境事实不会进入生产任务结果。
- 更新后新旧事实可区分,最终回答能追溯到当前来源与版本。
合上书,检查一下
先作答,再看解释。答错的地方,正好是值得再读一遍的地方。
配合本章看视频
下面按本站章节主题选配。为什么没有采用原课录播 →
CMU AI Agents 2026: 4. Memory and Skills for Agents ↗
区分历史轨迹、事实和可复用技能,讨论记忆的更新、检索和维护,贴合本站避免把错误长期保存的学习目标。
带着问题看 · 记忆与技能的区别;MemGPT 的外部记忆;冲突时新增、更新、删除或不改;技能如何验证、修订和淘汰。
查看来源与观看说明 →🗃️ 上下文工程:优化 Agent 效能的关键技术 🔑 ↗
作者以 LangChain 官方上下文工程文章为资料入口,适合配合本平台的上下文预算与记忆章节学习;其身份是中文讲解,非 LangChain 官方授课或 CS2680 课堂录像。
带着问题看 · 结合作者所引官方文章,比较写入外部状态、按需选择、压缩与隔离四种处理方式;把同一段工具返回分别放入上下文、摘要和外部存储,观察信息损失。
查看来源与观看说明 →