三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

AI Agent 应用实战(4):设计记忆与上下文管理

AI Agent 应用实战(4):设计记忆与上下文管理

上一篇建立了可停止、可恢复的单 Agent 循环。本篇处理循环变长后的核心矛盾:历史不能无限塞进模型,但关键事实又不能丢。目标不是“记得越多越好”,而是在正确作用域内保存可追溯、可撤销的信息。

一、痛点:从“能演示”走向“可托管”

上下文是本次推理的工作集,记忆是可被检索的持久数据,两者不能混用。建议分三层:步骤记忆保存单轮工具结果;会话记忆保存当前任务已确认事实;长期记忆只保存经过授权、未来确实有用且带来源的事实。原始事件追加保存,摘要只是可重建的派生视图。

真正可迁移的做法,是先写任务契约再选模型:输入从哪里来,成功产物是什么,哪些动作禁止自动执行,最多允许多少步、多少时间和多少费用,失败后由谁接管。契约一旦写进代码和测试,模型升级只是实现替换,不会悄悄改变业务责任边界。

二、原理:把概率判断放进确定性边界

每条记忆应包含主体、内容、来源、创建时间、有效期和敏感级别。写入前先去重和冲突检测,读取时同时按主体权限、时间有效性与任务相关性过滤。用户纠正信息时不要偷偷覆盖旧值,而应把旧记录标为 superseded,并保留纠正链,方便解释答案为何变化。

模型输出、工具数据和记忆内容都按不可信输入处理。每个边界都执行校验、授权、裁剪和审计;所有状态变化都有明确原因。这样做看似增加一些代码,却把“偶尔答错”拆成可定位的规划错误、参数错误、工具错误或策略错误,团队才能针对性改进。

三、实现:用独立程序跑通最小闭环

下面用 SQLite 实现一个极小的事实仓库。它只保存明确事实,并通过 owner 隔离用户;同一 key 的新版本不会删除旧版本。示例展示写入、纠正和读取当前值,迁移到向量库时也应保留这些元数据约束。

importsqlite3fromdatetimeimportdatetime,timezone db=sqlite3.connect(":memory:")db.execute("""CREATE TABLE memory ( owner TEXT, key TEXT, value TEXT, source TEXT, created_at TEXT, superseded INTEGER DEFAULT 0 )""")defremember(owner:str,key:str,value:str,source:str)->None:db.execute("UPDATE memory SET superseded=1 WHERE owner=? AND key=? AND superseded=0",(owner,key))now=datetime.now(timezone.utc).isoformat()db.execute("INSERT INTO memory VALUES (?, ?, ?, ?, ?, 0)",(owner,key,value,source,now))db.commit()remember("user-7","timezone","UTC","user_claim")remember("user-7","timezone","Asia/Shanghai","user_correction")row=db.execute("SELECT value, source FROM memory WHERE owner=? AND key=? AND superseded=0",("user-7","timezone"),).fetchone()count=db.execute("SELECT COUNT(*) FROM memory WHERE owner=? AND key=?",("user-7","timezone")).fetchone()[0]print(f"current_timezone={row[0]}")print(f"version_count={count}")print(f"latest_source={row[1]}")

运行输出:

current_timezone=Asia/Shanghai version_count=2 latest_source=user_correction

送入模型前,应先构造上下文预算。系统规则和当前用户请求优先级最高;任务事实其次;历史对话只保留与当前决策有关的片段。摘要必须标注它不是原始证据,重要数字仍从源记录提取。这样能避免多轮压缩后,条件、否定词和责任主体逐渐漂移。

第二个程序补上生产中最容易遗漏的控制点。它与前一个示例完全独立,可单独保存运行,不依赖本系列其他文件;示例数据是内存假实现,因此不会访问真实账户或产生外部副作用。

fromdataclassesimportdataclass@dataclass(frozen=True)classSegment:name:strtokens:intpriority:intsegments=[Segment("system_rules",120,100),Segment("current_request",90,90),Segment("verified_facts",160,80),Segment("old_chitchat",110,10),]budget=400used=0forsegmentinsorted(segments,key=lambdaitem:item.priority,reverse=True):ifused+segment.tokens>budget:print(f"skipped={segment.name}reason=budget")continueused+=segment.tokensprint(f"included={segment.name}tokens={segment.tokens}")print(f"total_tokens={used}")

运行输出:

included=system_rules tokens=120 included=current_request tokens=90 included=verified_facts tokens=160 skipped=old_chitchat reason=budget total_tokens=370

落地时应把示例中的内存状态替换为事务型存储,把打印日志替换为结构化事件,但不要改变契约。事件至少包含 run_id、步骤、输入摘要、策略结果、耗时、错误类别和版本;密钥、完整个人信息及未经脱敏的提示不得进入日志。

四、踩坑:失败路径决定系统上限

第一类坑是把模型自行推断写成长期事实,之后不断自我强化;第二类坑是跨租户检索,语义相似却没有权限过滤;第三类坑是只保留摘要,无法核对原文。记忆写入要区分 user_claim、tool_fact 与 model_inference,默认不持久化推断;删除请求还要能定位所有派生索引并同步清理。

还要防止把确定逻辑模型化。金额计算、权限判断、枚举路由和状态转移应使用普通代码;模型适合处理分类、抽取、歧义消解与证据综合。每减少一个无必要的模型决策点,就减少一处延迟、成本和不可复现性,同时让测试更容易覆盖。

五、验证:用轨迹和门槛验收

用四组测试验收:同一用户能取回有效事实;其他用户无法命中;过期与被替代记录不会进入上下文;删除后原表和索引都不可检索。再用固定问题比较裁剪前后的答案,确认关键约束未丢且 token 明显下降。下一篇的多 Agent 协作会继续复用带来源事实,而不是互传整段对话。

发布顺序固定为离线回放、影子流量、小比例灰度和有限自治。每阶段先定义通过线和回滚条件,再看结果;不能在看到分数后移动门槛。关键样本要保存初始状态、每步动作、观察、最终产物与终止原因,模型、提示和工具契约版本也必须一起记录。

多 Agent 之间只传结构化任务包和证据引用,能显著降低角色之间的上下文污染。

参考来源

  • SQLite Python
  • LangGraph Memory
  • OWASP LLM Prompt Injection

👍 觉得有用就点个赞 + 收藏,方便回头查阅;有疑问直接在评论区留言,我看到都会回。

🚀 本文属于《AI Agent 应用实战》系列,持续更新,关注不迷路。

📌 文章里的代码都能直接跑。想要可直接 clone 的完整工程 + 配套部署脚本 / 踩坑清单?评论一声或发邮件到cj2664@qq.com,我免费发你。
如果你正好在做类似系统、或有工程化难题想找人做,也欢迎邮件聊一句——我按实际情况评估,能落地的就接单或出方案。评论和邮件都能直接找到我,不用跳别的平台。

← 返回列表