福生无量摸鱼天尊

agent系列:(三)context and memory

2026/03/06
9
0

context其实跟infra息息相关,好的infra能支持非常多的idea并发的去实现,而pipeline的高度耦合使得infra变得屎山中的屎山,所以模块化的context infra势在必行。我们想构建一个context playload,一个有序、分层、可度量的 typed blocks 集合,是模型单次推理的输入态。

Context Engineering:围绕 payload 的三类能力:

  • Retrieval & Generation:从外部源/内部状态获取或生成上下文块(docs、tools、memory、state)。

  • Processing:对块做清洗、裁剪、压缩、去重、排序、结构化与对齐。

  • Management:生命周期(TTL/版本)、缓存、更新策略、权限边界、跨会话迁移与审计。

再往下推一步,如何让context在这些模块中observable,可以从如下进行考虑:

  • Coverage(覆盖):关键事实/约束是否都进入 payload(召回侧)。

  • Precision(精度):进入 payload 的内容对任务是否“必要且足够”(噪声侧)。

  • Freshness(新鲜度):是否显式编码时间戳/版本,避免过时证据主导。

  • Traceability(可追溯):生成结论能否回溯到证据块与来源 span。

  • Controllability(可控):块的优先级/权限/可变性是否稳定可预测。

但是这些都很难量化,特别是在多模态的当下,好的infra是很难构建的,我们尝试去讨论一下。

context block

每个 block 都有统一Header,再挂载Body。

Block {
  type: enum,            // Instruction / State / Evidence / Memory / ToolIO / Trace ...
  id: string,            // 稳定标识,便于去重/引用
  priority: int,         // 布局与截断策略使用
  scope: enum,           // system | tenant | user | session | task
  mutability: enum,      // immutable | append_only | mutable
  ttl: duration?,        // 生命周期(尤其 memory/evidence)
  source: {kind, uri?, doc_id?, tool_name?},
  provenance: {span?, page?, bbox?, timestamp?, hash?},
  trust: {level, rationale?},
  token_est: int,
  body: ...
}

Context Processing

Processing基本上都是一些操作,我们可以类似一种编译器装配的设置来配置系统:

def build_payload(state, user_input, budget):
    # 1) collect candidates
    cand = []
    cand += make_instruction_blocks(state.policies, state.tool_contracts)         # immutable
    cand += make_task_state_blocks(state.plan, state.progress, state.failures)    # append-only
    cand += retrieve_memory_blocks(state.user_id, user_input)                     # gated
    cand += retrieve_evidence_blocks(user_input, state.sources)                   # with provenance
    cand += recent_tool_io_blocks(state.tool_trace)                               # structured
    cand += minimal_conversation_trace(state.dialogue)                            # trimmed

    # 2) sanitize & normalize
    cand = sanitize_by_type(cand)             # 防注入:evidence 禁止携带指令语气/越权字段
    cand = normalize(cand)

    # 3) score & select
    cand = dedup(cand, key=("type","source","semantic"))
    cand = rank(cand, objective=state.objective)

    # 4) compress with alignment
    cand = compress(cand, budget=budget, keep_alignment=True)

    # 5) layout & validate
    payload = layout(cand, ordering_policy="instruction>state>memory>evidence>toolio>trace")
    validate(payload)                         # schema/permissions/citation anchors
    return payload

Processing 的操作集合

  • Normalize:统一编码/单位/时间格式;去 HTML 噪声;表格结构化。

  • Chunk & Align:切块同时保留 span 映射(后续引用必须回链)。

  • Dedup:语义去重(同义/复述)+ 来源去重(同文多处)。

  • Rank:多目标排序(相关性×可信度×新鲜度×覆盖增益×成本)。

  • Compress:在预算内保真压缩(保留可引用锚点)。

  • Layout:布局优化(层优先级 + 依赖关系 + 读者友好)。

  • Validate:检查 schema、引用可对齐、权限不越界。

压缩

  • Fidelity:关键实体/数值/条件不丢;引用锚点不丢。

  • Cost:token 降本,推理延迟可控。

  • Explainability:压缩后仍能解释“为什么保留这些”。

Retrieval 检索

Retrieval一直是ReAct中非常难搞的一环,我认为把检索写成策略引擎而非固定流程,核心问题不是“取几段”,而是何时取、取什么、取到何时停

  • Trigger:是否检索

    • 条件例:置信度不足、需要外部事实、需要最新信息、发现冲突、引用要求。

  • Target:检索什么源

    • 文档库 / web / 工具观测(Tool results)/ 记忆库 / 代码与配置库。

  • Budget:检索多少

    • 自适应 TopK:先小 K 探测,再扩展;或按“信息增益”停止。

  • Stop:何时停止

    • stop rule = 证据覆盖达标 + 冲突可解释 + 引用可对齐 + token budget 可承受。

Pipeline

常见的Agentic RAG 的闭环都是retrieve → generate → critique → refine,我们可以把这种想法下沉到检索里,抽象成控制回路(control loop):

  • Planner:从 task state 产出查询计划(queries + hops + stop condition)。

  • Retriever/Reranker:多路召回 + 重排(相关性、可信度、时效性、去重)。

  • Critic/Verifier:检查回答是否被证据支持;是否存在冲突/注入/过时。

  • Updater:把新 evidence 写入 state(不是写入 memory,除非通过写入门控)。

这带来的好处就是信息是能够被掌控的,一旦出现检索失败,能定位到具体的位置:

  • 错误召回:相关但错误;需要可信度/来源权重与对照验证。

  • 噪声过大:召回太宽导致注意力稀释;需要压缩与布局策略(见第 3 节)。

  • 注入文档:检索到“指令伪装”的内容;需要层隔离 + 过滤规则(evidence 不得解释为 instruction)。

  • 过时内容:缺少 timestamp/版本;需要 freshness gating(新内容优先,或显式展示冲突)。

Memory

到 2025–2026 这一波,agent 的 context 基本已经被业界重新定义了。它不再只是prompt 里塞什么,而是一个受预算约束的动态工作集

  • Anthropic把它定义成有限而关键的资源

  • LangChain把常见手段总结成 write / select / compress / isolate

  • Google ADK 更进一步把 context 视为对 session、memory、artifacts 的一次“compiled view”

这三种表述其实指向同一件事:context 是运行时投影,不是原始存储本身

这件事之所以重要,是因为“长上下文”并没有真正解决问题。Lost in the Middle 说明模型对长上下文中间位置的信息利用并不稳健。Chroma 2025 的研究也指出,很多流行长上下文评测过于像 Needle-in-a-Haystack,不能代表真实 agent、总结、推理场景。换句话说,窗口变大,不等于上下文工程自动消失

那我们要怎么思考agent memory呢?我们可以分层来想:

  • 存储层存原始状态(session / memory / artifacts)

  • 逻辑层决定本轮该看什么(select / compress / scope / handoff)

  • 控制层决定何时检索、何时继续找、何时停止

  • 索引层异步更新可检索表示

  • 评估层衡量正确性、完整性、新鲜度、延迟和成本。

这个视角和 Google ADK 的storage vs presentation、Anthropic 的有限 attention budget以及 LangChain 的write / select / compress / isolate是高度一致的。

看了很多综述,大致可以吧memory进行如下的分类:

三类memory的形式

简单来说 agent memory 的实现形态归纳为:

  • Token-level memory:外部存储 + 检索/摘要/拼接(最常见、最工程化)

  • Parametric memory:把记忆写进模型参数(微调/适配/持续学习)

  • Latent memory:隐藏状态/缓存态(更接近“状态机/循环网络式”的隐式记忆)

要做“通用 memory 系统”,几乎必然以 token-level 为主干,按需求引入 parametric/latent 作为补充。

三类memory的分类

  • Working memory:当前任务的临时工作区(短期状态、计划、工具中间结果)

  • Experiential memory:经历/轨迹/案例/失败教训(长期数据,支持跨任务迁移)

  • Factual memory:事实、属性、配置、用户偏好(可沉淀的,可被明确表达或结构化)

LangGraph 的 conceptual guide 也用“人类记忆类比”强调:语义(facts)、情景(experiences)、程序(rules)都可能成为 agent 的长期记忆,并且关键问题是“何时写入(hot path vs background)”。

三个memory的操作

  • Formed(形成):从交互与环境中抽取“值得记住”的东西

  • Evolved(演化):合并、去重、纠错、更新、遗忘、压缩

  • Retrieved(检索):按任务需要把合适的信息装配回上下文

memory 系统质量主要就卡在这三步的 策略(写什么、怎么变、怎么取)与 约束(token budget、延迟、可解释、权限)。

主流Memory的框架

OS范式:MemGPT / Letta

核心思想:把 LLM 当成“有限 RAM”,把外部存储当“磁盘/慢层”,通过 paging/分层管理 在有限 context window 内实现“看起来无限”的上下文。

MemGPT 提出 virtual context management,并用层级 memory + 中断(interrupts)管理控制流;在文档分析和多会话聊天上验证能“记住、反思、随时间演化”。

Letta 对 memory 的工程抽象:

  • Memory blocks:始终可见、会被 prepend 到上下文(XML 格式),有 label/description/value/limit;相当于“固定注入的核心记忆区”(例如 persona、用户画像、长期目标)。

  • Archival memory:语义可检索的“档案记忆”(向量库);不自动注入上下文,需要 agent 通过工具调用检索;更像“慢层外存”。

  • Context hierarchy:在 Letta 中明确了 memory blocks、files、archival memory、外部数据库/工具(含 MCP)等层级化上下文来源。

  • Shared memory blocks:多 agent 共享同一块记忆,适合团队协作/一致策略。

优势

  • 看似是OS的分页机制,实际上都是做成分层记忆架构:设定机制固定注入 + 外部档案按需检索。

  • 可把“memory=prompt 工程”显式化(blocks/limits/层级),更可控。

短板/风险

  • 最明显的就是需要一整套“写入准则 + 过期/冲突处理 + 安全过滤”。

  • 强依赖“agent 会自己管理内存”的策略质量;写入/更新不好会造成 prompt 污染。

CRUD + 摘要 + 向量检索范式:Mem0

Mem0 的论文给了一套非常清晰的 两阶段 pipeline:Extraction + Update

  • Extraction:新消息对(常见是一轮 user+assistant)进入后,结合两种上下文:

    • 数据库中的会话的摘要(通常会维护一个索引表,通过摘要来索引原文)

    • 最近 N 条消息;

    • 并用 异步摘要刷新模块保证摘要不会阻塞主链路

  • Update:对每个候选事实,先做 embedding 相似检索拿到 top 相似记忆,再让 LLM 通过 function calling 决定对记忆库执行四类操作:ADD / UPDATE / DELETE / NOOP(特别关键:DELETE 处理“新信息否定旧信息”的冲突)。

  • 论文还提出图记忆增强版:用 实体-关系有向图存记忆,底层用 Neo4j,并结合 embedding 做双路检索(实体导向 + triplet 语义相似)。

这类“有明确 CRUD 语义”的 memory,比“只做向量检索”更容易做一致性与可维护性。

优势

  • 写入/更新语义清晰(ADD/UPDATE/DELETE/NOOP)非常适合做通用 memory 引擎的核心接口。

  • 摘要的异步刷新是典型生产设计:把“昂贵但必要”的 consolidation 下沉到后台。

短板

  • 抽取事实与更新决策依赖 LLM 的稳定性;需要大量约束(schema、校验、阈值、审计)。

  • 图记忆对抽取质量敏感;图越大,治理越复杂。

Knowledge Graph 范式:Zep / Graphiti

Zep 的论文把核心点说得很明确:企业场景需要从 持续对话 + 业务结构化数据中动态整合知识,而不是只检索静态文档;因此 Zep 以 Graphiti(时间感知知识图引擎)为核心,维护关系随时间变化的图。

评测与性能亮点

  • 在 DMR(MemGPT 团队提出的 Deep Memory Retrieval)上,Zep 报告超过 MemGPT(94.8% vs 93.4%)。

  • 在更强调时间推理的 LongMemEval 上,Zep 报告最高可提升 18.5% accuracy,并把响应延迟降低 90%(相对 baseline 实现)。

项目侧(工程产品化)

  • Zep 仓库将其定位为 end-to-end “context engineering platform”:实时 ingest 聊天/业务数据/事件 → 构建时间 KG → 检索并组装“关系感知”的上下文块(强调 <200ms latency)。

优势

  • 多跳关系推理时间一致性更强(适合“跨 session 信息综合”“时间线问题”“知识更新”)。

  • 更贴近企业数据治理:结构化数据接入、历史关系保留。

短板

  • 系统复杂度高:抽取、对齐、消歧、时态边失效(invalidate)、权限、扩展性、图查询成本等。

  • 图抽取错误会“结构化放大”:一旦写错关系,影响面更大。

GraphRAG:把“知识图+社区摘要层级”用于 RAG/记忆检索

GraphRAG 的官方文档给出了非常工程化的流程:

Index 阶段

  1. 把语料切成 TextUnits;

  2. 从 TextUnits 抽取实体、关系、关键主张;

  3. 用 Leiden 做层级聚类得到社区结构;

  4. 自底向上生成社区摘要(形成多层抽象)。

Query 阶段

  • Global Search:利用社区摘要回答“全局/整体性问题”;

  • Local Search:围绕特定实体向邻居扩展;

  • DRIFT Search:局部搜索叠加社区信息;

  • 也保留 Basic Search(标准 top-k 向量检索)。

GraphRAG 明确指出 baseline RAG 的痛点:难以“connect the dots”、难以对大集合做整体概念理解。

优势

  • 对“跨文档、跨主题、需要综合”的问题更强;天然提供层级摘要作为压缩记忆。

  • 适合做“组织级知识记忆”(KB + 社区概念层)。

短板

  • Index 成本高(抽取+聚类+摘要),增量更新也更复杂。

  • 摘要层可能引入“摘要漂移/信息丢失”,需要评测与校验。

层级摘要检索(RAPTOR):树化总结 + 多粒度检索(文档记忆非常实用)

RAPTOR 的核心机制:递归地 embedding → 聚类 → 摘要,构建自底向上的“摘要树”;推理时从树的不同抽象层级检索并整合信息,以提升对长文整体理解与多步推理。

优势

  • 很适合做“文档型长期记忆”的压缩与多粒度召回(局部细节 + 全局概念)。

  • 与 GraphRAG 在“层级摘要”思想上互补:RAPTOR 偏纯文本树,GraphRAG 偏图结构+社区。

短板

  • 摘要正确性是关键风险点;需要“证据回溯”(回答引用叶子块证据)来降低漂移影响。

反思/经验沉淀范式:Generative Agents、Reflexion、Meta-Policy Reflexion

这条线解决的是:仅存事实不够,agent 还需要把经历变成更高层策略/规则(更接近 experiential & procedural memory)。

Generative Agents

  • 架构:记录完整经历(memory stream),随时间综合成更高层反思(reflections),并动态检索来规划行为。

  • 其记忆检索会综合 相关性/时间新近性/重要性 来打分,把“对当下决策最有用”的记忆浮出水面。

Reflexion

  • 提出让 agent 通过自我反思把经验写入“动态记忆”,增强推理轨迹与行动选择能力。

Meta-Policy Reflexion

  • 进一步把反思沉淀成 可复用规则:引入外部 Meta-Policy Memory,把反思 insights consolidation 成结构化规则供未来复用。

优势

  • 能让 memory 不止“记住”,而是“学到”:形成可迁移的策略/规则(procedural memory)。

  • 很适合复杂任务 agent(编程、运维、投研)做长期自我改进。

短板

  • 反思与规则抽取的正确性/可控性更难;容易生成过拟合或错误规则。

  • 需要强评测与回滚机制(规则版本化、A/B、灰度)。

框架级 Memory API:LangGraph/LangChain、LlamaIndex(“把记忆系统做成可插拔基础设施”)

LangGraph / LangChain:把“短期状态 + 长期 store + 持久化”做成一等能力

  • 短期记忆(thread-scoped):LangGraph 把对话历史与状态放在 agent state 中;通过 checkpointer 将 state 持久化到数据库,使 thread 可随时恢复;并且 state 在每一步(含工具调用)后更新、每步开始读取。

  • 长期记忆(跨 session):以 store 保存 JSON 文档;每条 memory 放在自定义 namespace(类似文件夹)+ key(类似文件名)下,支持层级组织与跨 namespace 搜索/过滤。

  • LangGraph 明确提出“何时写入记忆”的两种路径:hot path(主链路写) vs background(后台异步生成)

  • Persistence 文档还说明 checkpointer 将每个 super-step 的 state 存为 checkpoint,存入 thread,从而支持 memory、time travel、fault-tolerance 等能力。

LlamaIndex:提供可定制 memory.put/get 与多种 memory 组合

  • 文档明确:agent 运行时会调用 memory.put() 存信息、memory.get() 取信息。

  • 其(旧)memory types 示例很典型:

    • ChatMemoryBuffer(token 限制内的最近消息)

    • ChatSummaryMemoryBuffer(超限时周期性总结)

    • VectorMemory(向量库相似检索,不保证顺序)

    • SimpleComposableMemory(组合多种 memory,例如 Vector + Buffer)

优势

  • 作为“通用 memory infra”的骨架很合适:状态持久化、namespace、多租户、可插拔 store、可组合 memory。

  • 易与其它 memory layer(Mem0/Zep/自研)集成。

短板

  • 框架提供的是能力而非策略:真正难的是“写什么/怎么更新/怎么检索/怎么拼 prompt”。

常见的memory bench

  • LongMemEval(ICLR 2025):评估五类长期记忆能力:信息抽取、多 session 推理、时间推理、知识更新、以及“应当拒答/弃权(abstention)”。

    • 同时提出将长期记忆设计拆成 indexing / retrieval / reading 三阶段,并给出会话分解、key 扩展、时间感知 query 扩展等优化。

  • LoCoMo(2024)非常长的多 session 对话数据集(每段对话约 300 turns、平均 9k tokens、最多 35 个 session),任务覆盖 QA、事件总结、多模态对话生成,用于检验“超长时间跨度的一致性与因果/时间动态理解”。

  • MemBench(ACL 2025 Findings,2506.21605):强调两点:

    1. 多场景:参与式(参与对话)+ 观察式(第三方记录)

    2. 多层级记忆:factual + reflective(例如“用户口味偏好”这类隐含反思性信息)并提出多指标评估:accuracy、recall、capacity、temporal efficiency(时间效率)。

  • DMR(Deep Memory Retrieval):MemGPT 团队使用的评测集,Zep 论文也以此对比并报告优于 MemGPT。

至此,我们可以尝试去回答一些问题了:

怎么让 agent 高效知道自己需要什么 context?

最核心的答案不是“让 agent 知道所有相关上下文”,而是让它知道自己还缺什么证据

  • Self-RAG 的关键就是让模型在生成过程中决定是否需要检索

  • MemR³ 进一步把这个控制显式化成 retrieve / reflect / answer 三种动作,并维护 evidence-gap tracker;

  • Google ADK 则把记忆读取分成 reactive recall 和 proactive recall。

你会发现,这三条线的共同点都是:把检索从一次性数据库查询,变成闭环控制问题

对长链路 agent,我认为今天最有效的方法不是单纯 query-aware,而是gap-aware + plan-aware

  • PAACE 2025/2026 的核心贡献,就是把 context shaping 从当前 query 相关性提升到下一步任务、计划结构、依赖保真的层面

  • ACE 则把 context 看成会不断积累、反思、整理的 playbook,而不是反复重写、越来越短的摘要

对于多步 agent,这个方向比单纯压缩历史消息更接近真正可扩展的做法。

落到工程上建议这么做:把 memory 明确分成 procedural / semantic / episodic / artifacts 四类。

  • procedural 是长期固定规则,应该小而稳定地 pin 住;

  • semantic 是事实类记忆;

  • episodic 是成功/失败轨迹;

  • artifacts 是大文件、大日志、JSON、PDF 这类不该常驻窗口的重对象。

LangGraph 文档明确用 semantic / episodic / procedural 区分长期记忆,Google ADK 则把 artifacts 单独抽出来,Letta 的 memory blocks 说明“总在上下文里”的块是有价值的,但它们更适合放小而关键的协调信息,而不是大块原始材料。

所以这类问题里,我最推荐的落地组合是:

  • 小而稳定的基座上下文:架构不需要很复杂

  • typed memory:给 agent 一小块固定规则、当前目标、最近状态

  • proactive preload:再用轻量预取把“很可能需要”的记忆放进来

  • reactive retrieve/reflect :一旦发现证据缺口,再显式检索

  • artifact handle:大对象只传句柄和摘要,不把原文常驻

还有一个很实用的点:记忆写入要分热路径和异步路径

  • LangGraph/LangMem 明确区分了 hot path 和记忆后台抽取;

  • Mem0 的 add/search/update 也是围绕“先抽取事实、做冲突处理,再落到向量/图存储”来设计的。

  • 经验上,显式、确定、高价值的记忆写热路径;

  • 广泛、低风险、可延迟的抽取写异步路径。

这样既不拖慢主链路,也不会漏掉长期价值。