大数跨境

从上下文压缩到文件级持久化:彻底解决 AI Agent 的健忘症(全流程解析)(Agent架构实操九)

从上下文压缩到文件级持久化:彻底解决 AI Agent 的健忘症(全流程解析)(Agent架构实操九) 智能体AI
2026-07-03
0
导读:LLM 记忆提取的“硬伤”:为什么简单的上下文压缩救不了你的 Agent?

前言:从上下文压缩到持久化记忆

前代方案通过会话摘要解决了上下文窗口限制,但存在致命缺陷:压缩过程会导致细节丢失(如“用 Tab 不用空格”的偏好被泛化为“有代码风格偏好”),且新会话无法继承这些信息。LLM 缺乏持久状态是架构层面的问题,单纯优化压缩算法无法解决,必须引入一层不参与压缩、可跨会话保留的存储机制。

为什么选择文件系统作为记忆载体

相较于向量数据库(部署重、调试难)和结构化数据库(查询逻辑复杂、LLM 读取不自然),文件系统方案具有显著优势:

  • 零依赖与直观性:每个记忆是一个独立的 Markdown 文件,支持人工直接查看、编辑和删除。
  • 自然语言交互:LLM 生成和读取均为自然文本,无需额外的序列化层。
  • 故障排查便捷:出现问题可直接检查文件内容。

存储格式设计:采用"Markdown + YAML Frontmatter"结构。Frontmatter 存储元数据(name, description, type)用于索引检索,正文存储完整细节,实现按需读取。

---
name: user-preference-tabs
description: User prefers tabs for indentation
type: user
---

User prefers using tabs, not spaces, for indentation.
**Why:** Consistency with existing codebase conventions.
**How to apply:** Always use tabs when writing or editing files.

四种记忆类型分工:

类型 记录内容 示例
user 用户身份与偏好 “用 Tab 不用空格”
feedback 做事准则与避坑指南 “测试中不要 Mock 数据库”
project 当前项目背景与状态 "Auth 重写是合规驱动”
reference 关键资源位置 "Pipeline bug 在 Linear INGEST"

索引机制:常驻 SYSTEM Prompt

随着记忆文件增多,需建立统一目录。MEMORY.md作为索引文件,每行包含一个记忆文件的链接及简述。每次写入新记忆时自动重建该索引。

- [user-preference-tabs](user-preference-tabs.md) — User prefers tabs for indentation
- [no-mock-db](no-mock-db.md) — Never mock the database in tests
- [auth-rewrite-context](auth-rewrite-context.md) — Auth rewrite is compliance-driven

注入策略:将索引内容注入 SYSTEM Prompt。由于索引体积小且稳定,可利用 Prompt Cache 机制,只要记忆未变更,这部分 Token 无需重新计算,极大降低成本。

def build_system() -> str:
    index = read_memory_index()
    memories_section = f"\n\nMemories available:\n{index}" if index else ""
    return (
        f"You are a coding agent at {WORKDIR}."
        f"{memories_section}\n"
        "Relevant memories are injected below. Respect user preferences from memory.\n"
        "When the user says 'remember' or expresses a clear preference, extract it as a memory."
    )

Agent 仅通过索引知晓“有哪些记忆”,避免将所有内容塞入上下文造成浪费。

加载策略:按需注入 User Turn

知道有哪些记忆后,仅需在每次用户请求开始时,筛选出相关文件并将完整内容注入到当前的 User Turn,而非 SYSTEM Prompt。

设计考量:若将动态变化的记忆内容放入 SYSTEM,会导致 Prompt Cache 失效。将其置于 User Turn 可保持 SYSTEM 稳定,确保缓存命中率。

筛选逻辑:通过一次 Side-query(辅助查询),将最近几轮对话与记忆目录(名称 + 描述)发送给模型,由其返回相关文件的索引列表。若 API 调用失败,则降级为关键词匹配。

def select_relevant_memories(messages: list, max_items: int = 5) -> list[str]:
    # 1. 收集最近用户输入作为上下文
    # 2. 构建记忆目录 catalog
    # 3. 调用 LLM 进行语义匹配,返回 JSON 数组索引
    # 4. 异常处理:降级为关键词匹配 name + description
    # ... (代码逻辑略,见原文)
    return selected_files

选中的文件内容将被包裹在<relevant_memories>标签中,拼接到当前用户输入之前,确保主 Agent 能即时获取相关信息。

写入机制:隐式提取与去重合并

隐式提取

用户偏好通常散落在正常对话中,不会显式要求“记住”。提取逻辑设定在每轮对话结束(Agent 回答完毕且未调用工具)时触发。

关键细节:提取操作基于压缩前的消息快照(pre_compress)。若在压缩后提取,关键细节已丢失,会导致记忆质量下降。

# 在 agent_loop 中
if response.stop_reason != "tool_use":
    extract_memories(pre_compress)  # 从原始对话提取
    consolidate_memories()          # 定期合并

提取时会对比现有记忆列表,避免重复写入。

定期合并(Consolidation)

为防止记忆文件冗余、过时或矛盾,当文件数量达到阈值(如 10 个)时触发合并流程。LLM 负责去重、解决冲突并淘汰过时信息,返回精简后的清单覆盖旧文件。

注:生产环境需增加时间间隔、扫描节流、会话数统计及文件锁等多重门控机制,防止频繁合并。

Memory 与 Session Memory 的分工协作

本方案并未取代原有的上下文压缩机制,而是与其形成互补:

维度 Memory (长期记忆) Session Memory (会话记忆)
持久范围 跨会话 单会话内
存储位置 .memory/*.md 文件 Compact 摘要
注入位置 SYSTEM + User Turn 替换历史消息
核心作用 长期知识积累(偏好、背景) 跨压缩点的上下文续接

两者配合逻辑:会话内上下文超限触发压缩,由 Session Memory 承接关键信息;会话结束后触发提取,由 Memory 将高价值信息永久保存。

完整执行循环与时序

Agent 的标准执行流程如下:

用户输入
  ↓
load_memories()       ← 筛选相关记忆,注入 User Turn
build_system()        ← 读取 MEMORY.md 索引,构建 SYSTEM
  ↓
pre_compress snapshot ← 创建压缩前快照,供后续提取使用
  ↓
[上下文管理]: tool_result_budget / snip_compact / micro_compact
  ↓
LLM 推理
  ↓
若有工具调用 → 执行工具 → 继续循环
若无工具调用(本轮结束):
  extract_memories(pre_compress) ← 从快照提取新记忆
  consolidate_memories()         ← 若文件过多则合并
  return

此设计确保同一轮次内 SYSTEM 不重复构建,且记忆提取基于最完整的原始信息。

记忆内容的边界界定

Memory 并非日志系统,受限于索引行数(≤200)、单次加载量(≤5 条)及文件大小,其设计原则是只存“未来有用”的,不存“曾经发生”的

  • 适合存储:用户偏好、反复出现的约束、项目核心背景(目标、架构、依赖)、常用排查线索。
  • 不适合存储:完整对话记录、临时任务状态、仅对当前任务有效的中间信息(这些应交给 Session Memory 或 Transcript)。

下一步优化方向将是 System Prompt 的动态组装,使其能根据不同工具和项目约束实时生成,而非依赖硬编码字符串。

【声明】内容源于网络
0
0
智能体AI
1234
内容 467
粉丝 0
智能体AI 1234
总阅读13.4k
粉丝0
内容467