Context上下文工程

Context上下文

一. Context、Memory、Prompt与Token的区别

Token:模型处理信息的单位

Token 是文本经过分词器处理后得到的基本单位,可能是一个字、一个词的一部分或标点符号。

复制代码
原始文本
   ↓
Tokenizer分词
   ↓
Token序列
   ↓
模型处理

注意:

  • Token 数不等于字符数。
  • 同一段文本使用不同分词器,Token 数可能不同。
  • 中文、英文、代码的 Token 与字符比例不同。
  • 工程中应使用对应分词器计数,不能固定认为一个 Token 等于几个汉字。

模型的上下文容量通常以 Token 衡量,调用时还需要为输出预留空间。

Prompt:给模型的指令和输入

Prompt 用于告诉模型"要做什么、如何做"。

它不仅是用户的问题,还可以包含:

  • 系统指令
  • 用户任务
  • 示例
  • 输出格式
  • 参考资料

例如:

复制代码
系统指令:
你是一名Java开发助手,请简洁回答。

用户问题:
HashMap和ConcurrentHashMap有什么区别?

输出要求:
使用表格比较。

实际使用中,"Prompt"也常泛指发送给模型的整个输入,因此它与 Context 的概念存在重叠。

Context:本次调用中模型能看到的信息

Context 是模型生成当前回答时能够使用的输入信息。

通常包括:

复制代码
系统指令
当前用户问题
选入的历史消息
工具定义
工具调用结果
RAG检索资料
检索到的记忆
当前任务状态

关键点:只有实际传入当前调用的信息,模型才能使用。

复制代码
数据库里保存了信息
        ↓
没有读取并加入请求
        ↓
模型本次看不到这些信息

上下文可以按用途分类:(重点)

分类 内容
Instructions:指令 角色、任务、输出要求
Knowledge:知识 参考资料、检索到的记忆
Tools:工具 工具名称、参数、能力描述
State:状态 历史消息、执行结果、计划进度

这些分类不要求严格互斥。

Memory:保存和复用信息的机制

Memory 是应用为了跨调用保留信息而实现的存储、更新和读取机制。

普通模型调用不会自动记住上一次请求,需要应用把相关信息重新带入上下文。

复制代码
之前的对话或任务
        ↓
保存到Memory
        ↓
后续请求读取相关信息
        ↓
加入当前Context
        ↓
模型利用这些信息回答

短期记忆

短期记忆主要服务于当前会话或任务,例如:

  • 最近几轮对话

  • 当前计划

  • 已收集的用户需求

  • 最近的工具结果

    用户:我准备去北京。
    用户:玩5天。
    用户:预算5000元。

    短期记忆保留这些信息

    模型生成旅行方案

短期记忆不一定保存在内存中,也可以持久化到数据库。它是否保留,与存储方式及清理策略有关。

长期记忆

长期记忆用于跨会话复用信息,例如:

  • 用户偏好

  • 长期目标

  • 历史任务经验

  • 稳定的个人信息

    本次会话:
    用户表示喜欢安静、少走路的旅行方式。

    提取偏好并保存

    下次规划旅行时检索偏好

    加入当前上下文

长期记忆通常保存在数据库、文件或向量存储中,但不需要每次全部加载。

Context和Memory的区别

复制代码
Memory:
保存了哪些信息,以及如何读取和更新。

Context:
本次模型调用实际看到了哪些信息。

例如,系统保存了1000条历史消息,但当前只发送最近10条:

复制代码
Memory:1000条历史消息
Context:最近10条 + 当前问题 + 系统指令等

所以:

  • Memory 不等于 Context。
  • Memory 中被选取并传入模型的内容,才成为当前 Context 的一部分。
  • Context 也包含工具定义、当前问题等不一定来自 Memory 的内容。

四者之间的关系

复制代码
Prompt中的指令与问题
        +
Memory中选取的信息
        +
RAG资料、工具定义和执行状态
        ↓
构成本次Context
        ↓
编码为Token序列
        ↓
模型生成回答
        ↓
按需要更新Memory

对比总结

概念 关注点 示例
Token 信息如何编码、占用多少容量 一段文本被分成多少Token
Prompt 告诉模型做什么 "请总结这份报告"
Context 本次模型能看到什么 问题、历史、资料、工具结果
Memory 哪些信息被保存和复用 历史对话、用户偏好

重点总结

  1. Token 是模型处理文本的单位,不能与字符数直接画等号。

  2. Prompt 不只是用户问题,也包括系统指令、示例和输出要求。

  3. Context 是当前调用实际提供给模型的信息,受上下文容量限制。

  4. Memory 负责跨调用保存和复用信息;只有加载到当前请求中的部分才进入 Context。

  5. 短期、长期描述的是记忆的用途和保留范围,不等于内存存储、数据库存储。

  6. 可以这样记:

    Token:信息占多少容量。
    Prompt:要求模型做什么。
    Context:模型本次看到什么。
    Memory:系统保存并复用什么。

二. 上下文太长会带来什么问题?

上下文为什么会膨胀

随着对话和 Agent 执行持续进行,上下文会不断累积:

复制代码
系统指令
用户问题
历史消息
工具定义
工具调用结果
RAG检索资料
任务计划与执行状态

如果没有筛选和清理,上下文可能变得过长、重复或混乱。

上下文过长的影响

  • 输入 Token 增多,调用成本和处理耗时可能增加。

  • 超出上下文窗口,导致请求失败或部分内容被截断。

  • 无关内容干扰任务,重要信息容易被忽略。

  • 历史错误、过时状态和冲突信息影响后续判断。

    上下文更多 ≠ 回答更准确

关键是提供与当前任务相关、准确且必要的信息。

Lost in the Middle:中间信息被忽略

《Lost in the Middle》研究指出,在其测试任务和模型中,关键信息的位置会影响模型表现:

复制代码
上下文开头 → 较容易被利用
上下文中间 → 较容易被忽略
上下文结尾 → 较容易被利用

即使模型能够接收长上下文,也不代表它能同样有效地利用每个位置的信息。

这里的"中间迷失"主要指中间位置的信息利用不足,不等同于所有情况下都会忘记最初目标。

具体表现取决于模型、任务和输入内容,不能把某个 Token 数作为统一的性能下降阈值。

Context Poisoning:上下文中毒

错误信息进入上下文后,被后续步骤当作事实使用,导致错误持续传播。

复制代码
模型生成错误结论
      ↓
错误结论被保存到上下文
      ↓
后续步骤将其当作事实
      ↓
错误不断放大

例如:

复制代码
工具实际结果:
订单未支付。

错误摘要:
订单已支付。

后续Agent:
根据"已支付"安排发货。

核心问题:错误内容污染了后续判断的依据。

Context Distraction:上下文分散

上下文包含大量无关信息,干扰模型对当前目标的关注,导致答非所问或偏离任务。

复制代码
当前任务:总结文章
      +
大量无关聊天和历史记录
      ↓
模型关注了无关内容
      ↓
没有完成总结任务

核心问题:信息太杂,分散了对当前任务的注意力。

Context Confusion:上下文混乱

上下文中的工具、选项或说明过多、相似度过高,导致模型混淆它们的用途,选错执行方式。

例如:

复制代码
query_order
query_order_history
query_archived_order
query_refund_order
query_test_order

如果工具说明不清晰,模型可能无法区分应该调用哪一个。

复制代码
工具过多、职责重叠
        ↓
难以判断适用范围
        ↓
选错工具或传错参数

核心问题:难以区分信息、工具或选项的用途。

Context Clash:上下文冲突

上下文同时存在互相矛盾的信息,模型无法正确判断应以哪一条为准。

例如:

复制代码
旧计划:
用户准备去北京。

新要求:
用户已经改为去上海。

上下文没有明确更新:
北京和上海两份计划同时保留。

模型可能混用两个城市的行程。

核心问题:新旧信息或不同来源的信息相互矛盾。

四类问题对比

问题 核心表现 示例
上下文中毒 错误信息被当作事实 将"未支付"记成"已支付"
上下文分散 无关信息干扰目标 总结文章时转而讨论闲聊内容
上下文混乱 混淆工具和信息用途 多个相似工具中选错一个
上下文冲突 矛盾信息同时存在 旧目的地与新目的地混用

这些问题可能同时出现,也不是只有长上下文才会发生;上下文累积会增加管理难度。

常见处理方式

问题 处理思路
上下文过长 裁剪历史、按需检索、压缩冗长结果
中间信息被忽略 合理组织内容,突出当前目标和关键证据
上下文中毒 核验关键结果,区分事实、推测和模型结论
上下文分散 删除与当前任务无关的信息
上下文混乱 按需加载工具,明确工具职责和适用范围
上下文冲突 更新任务状态,明确最新信息和来源

压缩上下文时也要检查摘要质量,避免把错误结论保留下来。

重点总结

  1. 上下文越长不代表效果越好,相关性和准确性更重要。

  2. 模型支持的最大上下文长度,不等于有效利用信息的能力。

  3. 四类典型问题:

    中毒:错误信息被相信。
    分散:无关信息让模型偏题。
    混乱:工具和信息难以区分。
    冲突:相互矛盾的信息同时存在。

  4. 上下文管理的目标:

    保留关键事实
    删除无关内容
    更新过时状态
    减少重复信息
    按需提供工具
    明确当前目标

三. 什么是上下文工程?

核心概念

上下文工程(Context Engineering)是管理模型每次调用时能够看到的信息,让上下文保持相关、准确、充分,并控制 Token 开销。

上下文不仅包含提示词,还包括:

复制代码
历史消息
工具定义
工具执行结果
任务计划
记忆
RAG检索资料

因此,除了优化提示词,还需要决定哪些信息加载、保留、压缩或移出当前上下文。

与提示词工程的区别

类型 关注点 示例
提示词工程 如何表达指令和要求 指定角色、示例、输出格式
上下文工程 本次调用应提供哪些信息 选择历史、检索资料、筛选工具
复制代码
提示词工程:和模型怎么说。

上下文工程:让模型看到什么。

提示词工程是上下文工程的一部分,两者可以共同使用。

上下文窗口与工作内存

可以将上下文窗口类比为工作内存:

复制代码
外部存储:保存大量资料和历史信息
    ↓ 按需加载
上下文窗口:当前任务所需的信息
    ↓
模型处理

上下文工程负责管理:

  • 哪些信息现在需要加载。
  • 哪些信息暂时保存在外部。
  • 哪些内容需要压缩。
  • 哪些任务应该拆开处理。

四种主要方法

方法 作用
Write:写入 将重要信息保存到上下文之外
Select:选择 按需加载与当前任务相关的信息
Compress:压缩 减少输入体积,保留关键内容
Isolate:隔离 将不同任务的上下文分开管理

它们可以组合使用,不是必须依次执行的四个阶段。

Write:写入

将重要信息保存到文件、状态存储或数据库中,方便后续使用。

复制代码
执行中产生重要信息
        ↓
保存到外部存储
        ↓
后续需要时再读取

常见方式:

  • Scratchpad:任务草稿板。
  • Long-Term Memory:长期记忆。
Scratchpad:草稿板

保存工作过程中的临时信息,例如:

复制代码
当前目标
任务计划
已完成步骤
待办事项
关键发现
失败原因

示例:

复制代码
目标:分析接口变慢的原因。

已完成:
1. 检查网络,无明显异常。
2. 发现数据库存在慢查询。

下一步:
检查慢查询SQL的执行计划。

草稿板不必保存全部执行过程,主要记录继续任务所需的信息。

Checkpoint:检查点

检查点用于保存图的执行状态,支持恢复、中断续跑等操作。草稿板可以作为状态的一部分被保存。

复制代码
Checkpoint:保存执行状态。
Scratchpad:记录任务过程中的关键笔记。

两者不是同一个概念。

thread_idconversation_id 都可以用于关联会话,但关联的数据不同:

复制代码
thread_id:
通常用于关联图执行状态和检查点。

conversation_id:
通常用于关联聊天消息。
Long-Term Memory:长期记忆

保存跨会话仍有价值的信息,例如:

  • 用户偏好

  • 稳定的业务知识

  • 历史任务经验

  • 长期目标

    本次发现重要信息

    筛选并保存长期记忆

    下次任务按需读取

写入外部存储后,如果仍将全部内容发送给模型,并不会减少当前上下文占用。

Select:选择

从已保存的信息中,选择当前任务需要的部分加入上下文。

典型做法:

  • 检索相关的长期记忆。

  • 召回相关文档。

  • 加载适用的规则文件。

  • 筛选本次需要的工具。

  • 读取必要的任务状态。

    大量外部信息

    根据当前任务筛选

    只加载相关内容

    加入当前Context

例如:

复制代码
系统共有100个工具。

当前任务:查询订单。
      ↓
只提供订单查询、物流查询等相关工具。

选择不仅要考虑相似度,也要考虑信息来源、时效性和适用范围。

Compress:压缩

减少上下文中的 Token,同时尽量保留完成任务所需的信息。

主要方式:

方法 处理方式
Summarization:摘要 将长内容概括成简短记录
Trimming:裁剪 按规则删除部分内容
Pruning:按相关性删减 移除对当前任务没有帮助的内容
摘要
复制代码
几十轮对话
    ↓
提取目标、约束、进展和关键结论
    ↓
生成精简摘要

摘要应重点保留:

复制代码
用户目标
明确约束
已确认事实
关键结果
未解决问题
下一步行动

摘要本身可能遗漏或误写信息,因此不能仅追求字数少。

裁剪

按固定规则保留部分消息:

复制代码
保留系统指令
保留最近若干轮对话
删除过时或重复内容

处理工具调用消息时,应保持调用请求与对应结果的完整关联。

按相关性删减

根据当前任务,删除无关内容。

复制代码
当前任务:排查数据库性能。

保留:
慢查询、执行计划、数据库指标。

移除:
无关聊天、重复日志和其他任务记录。

Isolate:隔离

将任务拆分,让不同执行单元只接收自己需要的上下文,减少信息混杂。

多智能体隔离
复制代码
主Agent
   ├── 数据Agent:只处理数据分析
   ├── 文档Agent:只处理资料检索
   └── 写作Agent:只处理结果表达
              ↓
         返回必要结果
              ↓
         主Agent汇总

关键是给子 Agent 精简输入,并让它返回有用结果;如果每个 Agent 都接收完整历史,隔离收益就会降低。

执行环境隔离

将大文件、完整数据集等保留在执行环境中,通过代码处理,只向模型返回必要结果。

复制代码
完整数据集保存在文件中
          ↓
代码读取并计算
          ↓
返回统计结果或少量样本
          ↓
模型继续分析

减少上下文占用依靠的是"外部处理、精简返回",并非使用沙盒就会自动减少 Token。

结构化状态

将信息分成不同字段,分别管理:

复制代码
{
  "messages": [],
  "plan": [],
  "search_results": [],
  "file_path": "/data/report.csv"
}

构建模型请求时只选择必要字段。

复制代码
State保存的信息
      ≠
全部发送给模型的信息

仅定义状态字段不会自动实现隔离,还需要控制请求构造逻辑。

渐进式披露

渐进式披露是先提供少量概览,模型确定需要后,再加载详细内容。

复制代码
先提供能力名称和简介
          ↓
判断是否与任务相关
          ↓
加载详细说明
          ↓
需要时继续读取参考文件

例如技能文档:

复制代码
第一层:技能名称、用途
第二层:具体操作说明
第三层:参考资料、脚本和模板

这样可以避免一开始把所有说明和资料塞入上下文。

四种方法如何配合

以长时间故障排查为例:

复制代码
Write:
把排查进展写入任务笔记。

Select:
只读取当前问题相关的日志和工具。

Compress:
把大量日志概括成关键异常。

Isolate:
让独立Agent分析某个子系统。

渐进式披露:
先读取日志概览,再按需查看详细记录。

重点总结

  1. 上下文工程管理的是模型本次调用实际接收的信息。

  2. 提示词工程关注指令表达,是上下文工程的一部分。

  3. 四种主要方法:

    Write:存到外部。
    Select:按需取用。
    Compress:精简内容。
    Isolate:分开处理。

  4. 外部存储和执行状态不等于模型上下文,只有实际传入请求的内容才会被模型看到。

  5. 检查点负责保存执行状态,草稿板负责记录任务笔记。

  6. 渐进式披露是先加载概览,再按需展开详细信息。

  7. 目标不是让上下文越短越好,而是让它包含完成当前任务所需的准确信息。

四. Manus的上下文工程实践

核心思路

Manus 的实践重点是管理 Agent 执行过程中不断增长的上下文,让模型获得必要信息,同时控制延迟和成本。

主要包括:

复制代码
提高缓存复用
限制可选工具
使用文件系统保存大内容
持续提醒任务目标
保留有价值的失败记录

围绕 KV Cache 设计上下文

模型生成文本时,会计算 Token 对应的 Query、Key、Value。

KV Cache 保存已经处理过的历史 Token 的 Key 和 Value,后续生成时可以复用。

复制代码
历史Token
   ↓
计算并缓存K、V
   ↓
生成新Token时复用历史K、V
   ↓
减少重复计算

注意:新 Token 仍需要计算自己的 Q、K、V,并关注历史内容。不能简单理解为整个推理过程从 O(n²) 变成了 O(n)

在支持前缀缓存的服务中,多次请求还可以复用相同前缀的计算结果。

复制代码
第1次请求:
系统指令 + 工具定义 + 用户问题

第2次请求:
系统指令 + 工具定义 + 用户问题 + 新增执行结果
└──────── 可复用的相同前缀 ────────┘

如何提高缓存命中率

  • 保持系统提示词和工具定义稳定。
  • 将变化频繁的信息放在稳定内容之后。
  • 使用确定性的序列化方式,避免字段顺序反复变化。
  • 尽量追加新消息,避免频繁改写前面的历史内容。
  • 根据实际服务的缓存机制配置路由和缓存边界。

例如:

复制代码
不利于前缀复用:
系统提示词开头每次插入新的时间戳。

更有利于复用:
稳定系统指令 + 稳定工具定义 + 当前时间和任务信息。

JSON 字段排序只是保证序列化稳定的一种方法,关键是相同内容应产生一致的 Token 序列。

前缀发生变化时,变化位置之前的部分仍可能复用,不一定全部缓存都失效。

工具遮蔽:保留定义,限制选择

动态删除或调整工具列表,可能改变请求前缀,影响缓存复用。

这类方案选择保留稳定的工具定义,再限制当前步骤允许调用的工具。

复制代码
完整工具定义保持稳定
          ↓
当前步骤只允许浏览器工具
          ↓
模型从允许的工具中选择

工具可以使用统一前缀命名:

复制代码
browser_search
browser_navigate

file_read
file_write

shell_run
shell_check

这样方便按工具类别进行限制。

实现方式取决于模型和推理服务,可以涉及工具选择约束、受约束解码或响应预填充。

需要注意:

  • 仅在提示词中要求"只用某类工具",不等于强制约束。
  • 普通 JSON 文本前缀不一定能触发原生工具调用。
  • 缓存边界和特殊 Token 不是所有服务通用的标准。
  • 工具定义依然占用上下文,缓存不会让它们免费或消失。

文件系统作为外部上下文

网页、PDF和日志等内容可能非常大,直接加入上下文会增加开销和干扰。

可以将完整内容保存在文件中,上下文只保留摘要和文件位置。

复制代码
读取大量网页或日志
        ↓
保存到文件
        ↓
上下文保留摘要、路径和关键发现
        ↓
需要细节时再读取对应内容

例如:

复制代码
搜索结果已保存到:/workspace/results.html
关键发现:资料主要涉及缓存设计和工具调度。
需要时读取相关章节。

与直接删除内容相比,这种方式保留了重新获取细节的能力。

但"可恢复"需要满足条件:文件仍然存在、路径有效,并且 Agent 有读取能力。仅保留一个可能失效的远程 URL,不保证信息可恢复。

通过任务清单提醒目标

长任务执行过程中,模型可能逐渐偏离最初目标。

可以维护 todo.md,记录目标、进展和下一步行动,并在关键阶段将当前清单加入上下文。

复制代码
# 任务目标
分析接口响应变慢的原因。

## 已完成
- [x] 检查网络连接
- [x] 收集接口耗时日志

## 待处理
- [ ] 分析慢查询
- [ ] 检查索引
- [ ] 整理诊断报告

执行流程:

复制代码
完成一个阶段
      ↓
更新任务清单
      ↓
读取并提醒当前目标和剩余事项
      ↓
继续下一步

仅修改文件不会自动影响模型,相关内容需要被读取或加入后续请求。

任务清单应保持简洁,避免大量重复提醒再次挤占上下文。

保留错误记录,帮助恢复

工具调用失败后,保留错误原因和已尝试的方法,可以帮助模型调整策略,避免重复犯错。

复制代码
尝试读取文件
      ↓
返回"路径不存在"
      ↓
记录失败路径
      ↓
检查实际目录
      ↓
使用正确路径重试

可以保存:

复制代码
失败动作:读取/data/report.csv
错误原因:文件不存在
已知信息:实际文件位于/workspace/report.csv
下一步:使用正确路径读取

这里的"学习"是模型利用当前上下文中的反馈调整行为,不是更新模型参数。

也不需要将完整错误堆栈反复塞入上下文,可以保留关键错误,把完整日志存到文件。

稳定缓存与动态上下文的平衡

保持前缀稳定有利于缓存,清理历史有利于上下文质量,两者需要权衡。

复制代码
短期执行:
追加新消息,尽量复用缓存。

历史过长:
压缩或重建上下文,接受必要的缓存失效。

不能为了缓存一直保留过时、错误或无关的信息。

重点总结

  1. 保持请求前缀稳定,有利于复用缓存、减少重复计算。
  2. 动态信息尽量放在后面;修改历史前缀可能影响后续缓存复用。
  3. 工具遮蔽是在保留工具定义的同时限制当前可选工具,具体实现依赖服务能力。
  4. 大内容保存到文件,上下文保留摘要和位置,需要时再读取。
  5. 通过简洁的任务清单持续提醒目标、进度和下一步。
  6. 保留关键失败信息,帮助模型恢复和调整策略,而不是反复重复错误。
  7. 对应上下文工程方法:
实践 主要作用
文件保存完整内容 Write:外部写入
按需读取文件 Select:选择信息
用摘要和路径替代全文 Compress:减少上下文
提醒当前任务清单 保持目标聚焦
保留关键错误记录 支持执行恢复

最终目标是让 Agent 每一步都获得足够、相关、可信的信息,并减少不必要的计算和上下文占用。

五. 什么是长期记忆?

Agent 的记忆

Agent 的记忆是存储、读取和利用历史信息的机制,可以包括:

  • 历史对话
  • 工具调用结果
  • 任务状态
  • 用户偏好
  • 事实和经验

模型使用这些信息时,需要将相关内容加载到当前上下文,而不是把全部历史一次性传入。

复制代码
历史信息
   ↓
保存为记忆
   ↓
当前任务按需读取
   ↓
加入模型上下文
   ↓
生成回答

短期记忆

短期记忆主要服务于当前会话或任务,保存最近的消息和执行状态。

例如:

复制代码
用户:我准备去北京。
用户:玩5天,预算5000元。
用户:帮我安排一下。

模型结合当前会话的信息生成行程。

短期记忆可以保存在:

  • 内存
  • Redis
  • 关系型数据库
  • 执行检查点

存储位置不决定它属于短期还是长期记忆。

长期记忆

长期记忆保存具有持续价值的信息,供后续不同会话或任务按需使用。

例如:

复制代码
一年前:
用户说自己喜欢吃香菜。
       ↓
提取并保存饮食偏好。
       ↓
一年后:
用户要求推荐餐馆。
       ↓
读取相关偏好,辅助推荐。

不需要把这一年的全部对话传给模型,只需要提取与当前任务有关的信息。

长期记忆的作用

作用 示例
个性化服务 用户喜欢清淡饮食、偏好简洁回答
知识积累 保存业务事实、项目约定
经验复用 记录某类故障的有效处理方式
补充上下文 从历史中召回当前任务需要的信息

长期记忆不仅是用户画像,也可以保存任务经验、操作流程和业务知识。

长期记忆与持久化记忆

这两个概念关注的角度不同:

复制代码
持久化:
数据能否在进程退出或重启后保留。

长期记忆:
信息是否用于跨会话、跨任务的长期复用。

例如:

复制代码
将某个会话的消息保存到数据库
        ↓
重启后继续原会话
        ↓
持久化的会话记忆
从不同会话中提取用户偏好
        ↓
保存为用户画像
        ↓
在新的会话中按需使用
        ↓
长期记忆

长期记忆通常需要持久化存储,但仅把聊天记录存进数据库,不代表已经实现了长期记忆管理

短期记忆与长期记忆对比

对比项 短期记忆 长期记忆
主要范围 当前会话或任务 跨会话、跨任务
常见内容 最近消息、当前进度 偏好、事实、经验
使用方式 最近消息、摘要、任务状态 根据任务检索或精确读取
管理重点 保持当前任务连贯 提取、更新、筛选和复用
是否可以持久化 可以 通常需要

为什么不能携带全部历史

复制代码
用户历史不断增加
        ↓
全部加入上下文
        ↓
Token开销增加、可能超出窗口
        ↓
无关信息干扰当前任务

因此,长期记忆需要两个关键能力:

复制代码
写入时:判断什么值得记住。
读取时:判断本次需要什么。

实现方式一:向量存储与检索

将记忆条目或摘要转换成向量,查询时召回语义相关的内容。

复制代码
历史对话
   ↓
提取有价值的记忆
   ↓
Embedding向量化
   ↓
保存到向量数据库
   ↓
根据当前问题检索相关记忆

适合:

  • 历史经验
  • 对话摘要
  • 语义相关的事实
  • 不容易用固定字段描述的信息

检索时还需要限制用户、权限和适用范围,避免召回其他用户的记忆。

实现方式二:结构化数据库

将用户偏好或明确事实保存为字段,方便精确查询和更新。

复制代码
{
  "userId": "user-001",
  "preferences": {
    "likesCoriander": true,
    "answerStyle": "concise",
    "preferredLanguage": "zh-CN"
  },
  "updatedAt": "2026-09-14"
}

适合:

  • 用户画像
  • 偏好设置
  • 身份信息
  • 明确的时间、状态和属性

例如:

复制代码
当前任务:生成回答
        ↓
根据userId读取answerStyle
        ↓
发现偏好为concise
        ↓
提供简洁回答

实现方式三:图数据库

使用实体和关系表达记忆,适合关联信息较多的场景。

复制代码
用户A ── 任职于 ── 公司B
用户A ── 参与 ── 项目C
项目C ── 使用 ── 技术D

适合:

  • 人物关系
  • 项目关系
  • 组织结构
  • 多实体关联查询

实现方式四:混合存储

根据记忆类型选择不同存储方式。

复制代码
用户ID、明确偏好 → 结构化数据库
历史摘要、任务经验 → 向量数据库
人物、项目关联 → 图数据库

这样可以同时支持精确查询和语义检索,不必把所有记忆都放进向量库。

实现方式五:配置或规则文件

将用户明确提出的偏好、约束和项目规范保存为文件,后续按需加载。

复制代码
# 用户偏好
- 使用中文回答。
- 解释尽量简洁。
- Java代码块标注java。

# 项目约定
- Controller按业务模块分类。
- 配置中的密钥通过环境变量读取。

这类文件可以充当持久的偏好或规则来源,但不会自动学习,需要明确的写入和更新机制。

微调与长期记忆的区别

微调会改变模型参数,可以用于学习稳定的风格、能力或领域模式。

但它不适合充当日常用户事实的主要记忆库:

  • 单条信息不容易精确读取。

  • 更新和删除成本较高。

  • 难以保证模型准确复述某个事实。

  • 不适合频繁变化的用户偏好。

    长期记忆存储:
    外部保存信息,需要时加载。

    微调:
    修改模型参数,改变模型行为。

用户画像和动态事实通常更适合使用外部存储。

长期记忆的完整流程

复制代码
用户交互
   ↓
识别值得保存的信息
   ↓
去重、核验并处理冲突
   ↓
保存或更新记忆
   ↓
新的会话或任务
   ↓
按用户、相关性和时效性读取
   ↓
加入当前上下文
   ↓
模型完成任务

例如,用户更新偏好:

复制代码
旧记忆:喜欢香菜。
新信息:现在不吃香菜了。
        ↓
更新偏好,标记旧信息失效。

不能只不断追加,而不处理新旧信息冲突。

重点总结

  1. 短期记忆服务当前会话,长期记忆支持跨会话、跨任务复用。

  2. 持久化是存储属性,不等于长期记忆。

  3. 长期记忆不需要加载全部历史,只读取当前任务相关的信息。

  4. 常见存储方式:

    向量库 → 语义检索
    结构化数据库 → 精确事实和用户画像
    图数据库 → 实体关系
    规则文件 → 明确偏好和约束

  5. 长期记忆需要管理提取、更新、去重、冲突、过期和删除。

  6. 基于记忆调整回答,不等于模型参数发生了学习或更新。

  7. 核心流程:

    提取重要信息 → 长期保存 → 按需读取 → 加入Context

六. 长期记忆实现方案:Mem0

Mem0 简介

Mem0 是为 AI 应用提供长期记忆能力的框架,可以从对话中提取用户信息、偏好和事实,保存后供后续会话检索使用。

复制代码
用户对话
   ↓
提取重要事实和偏好
   ↓
与已有记忆比较
   ↓
新增、更新、删除或保留
   ↓
保存记忆
   ↓
后续对话按需检索

它不仅保存原始聊天记录,还负责提取和维护有价值的记忆。

核心模块

模块 作用
LLM 提取事实、判断记忆是否需要更新
Embedder 将记忆和查询转换成向量
Vector Store 保存向量并检索相关记忆
用户标识与元数据 区分记忆归属、记录来源等信息

记忆提取

从对话中提取适合长期保存的信息。

复制代码
用户:
我叫Hollis,喜欢编程和游戏。

提取结果:
姓名是Hollis。
喜欢编程和游戏。

这些记忆通常是简短的自然语言条目,再通过 ID、用户标识和时间等字段进行组织。

记忆存储与检索

复制代码
记忆条目
   ↓
Embedding向量化
   ↓
关联user_id并存入向量数据库

新的会话中:

复制代码
当前问题 + user_id
        ↓
检索该用户的相关记忆
        ↓
返回记忆条目

user_id 用于限定记忆归属,查询内容用于寻找相关信息。

记忆更新

新信息可能与旧记忆重复或冲突,Mem0 可以借助模型判断如何处理。

复制代码
旧记忆:
用户住在北京。

新信息:
用户表示刚搬到上海。

处理:
更新居住地信息。

模型判断仍可能出错,因此重要信息需要保留来源,并支持查询、纠正和删除。

本例技术组合

复制代码
Mem0:记忆提取和管理
Ollama:本地运行模型
聊天模型:提取事实、判断更新
Embedding模型:生成向量
PostgreSQL + pgvector:存储和检索向量

课程中的模型准备说明和代码配置不一致。下面统一使用代码中的组合:

复制代码
聊天模型:qwen3:1.7b
Embedding模型:nomic-embed-text:v1.5
向量维度:768

实际提取效果取决于模型能力,小模型可能无法稳定生成符合要求的结果。

准备本地模型

复制代码
ollama pull qwen3:1.7b
ollama pull nomic-embed-text:v1.5

确保 Ollama 服务可以访问:

复制代码
http://localhost:11434

准备数据库

连接已经安装 pgvector 的 PostgreSQL,创建数据库:

复制代码
CREATE DATABASE mem0_memory;

切换连接到 mem0_memory 后启用扩展:

复制代码
CREATE EXTENSION IF NOT EXISTS vector;

本例假设数据库连接信息为:

复制代码
主机:127.0.0.1
端口:5433
数据库:mem0_memory
用户名:pgvector
密码:pgvector

这些是示例配置,应以实际部署为准。

创建 Python 项目

Windows PowerShell:

复制代码
uv init --no-package hello_mem0
cd hello_mem0
uv venv
.\.venv\Scripts\Activate.ps1

添加依赖:

复制代码
uv add mem0ai ollama psycopg2-binary

注意包名与导入名不同:

复制代码
安装包:mem0ai
导入名:mem0

配置并使用 Mem0

以下沿用课程中本地 Memory 的 API 写法,保存为 hello.py

python 复制代码
from mem0 import Memory


config = {
    "vector_store": {
        "provider": "pgvector",
        "config": {
            "user": "pgvector",
            "password": "pgvector",
            "host": "127.0.0.1",
            "port": 5433,
            "dbname": "mem0_memory",
            "embedding_model_dims": 768,
        },
    },
    "llm": {
        "provider": "ollama",
        "config": {
            "model": "qwen3:1.7b",
            "temperature": 0,
            "max_tokens": 2000,
            "ollama_base_url": "http://localhost:11434",
        },
    },
    "embedder": {
        "provider": "ollama",
        "config": {
            "model": "nomic-embed-text:v1.5",
            "ollama_base_url": "http://localhost:11434",
            "embedding_dims": 768,
        },
    },
}


print("初始化Memory...")
memory = Memory.from_config(config)

messages = [
    {
        "role": "user",
        "content": "Hi, I'm Hollis. I love Coding and Gaming.",
    },
    {
        "role": "assistant",
        "content": "Hey Hollis! I'll remember your interests.",
    },
]

print("添加记忆...")
add_result = memory.add(
    messages,
    user_id="Hollis666",
)
print(add_result)

print("查询记忆...")
search_result = memory.search(
    "What do you know about me?",
    user_id="Hollis666",
)
print(search_result)

三部分配置的区别

复制代码
"llm": {
    "provider": "ollama"
}

负责理解对话、提取记忆和判断记忆变更。

复制代码
"embedder": {
    "provider": "ollama"
}

负责将文本转换成向量。

复制代码
"vector_store": {
    "provider": "pgvector"
}

负责保存向量并进行相似度检索。

复制代码
LLM:记什么、怎么更新。
Embedder:将内容转换成向量。
Vector Store:保存并查找相关记忆。

添加记忆

复制代码
result = memory.add(
    messages,
    user_id="Hollis666",
)

概念流程:

复制代码
分析对话
   ↓
提取候选事实
   ↓
查找相关旧记忆
   ↓
判断是否新增或更新
   ↓
保存结果

示意返回:

复制代码
{
  "results": [
    {
      "id": "memory-001",
      "memory": "Name is Hollis",
      "event": "ADD"
    },
    {
      "id": "memory-002",
      "memory": "Loves Coding and Gaming",
      "event": "ADD"
    }
  ]
}

实际条目和措辞由模型处理结果决定,不保证每次完全相同。

检索记忆

复制代码
results = memory.search(
    "What are my interests?",
    user_id="Hollis666",
)

示意结果:

复制代码
{
  "results": [
    {
      "id": "memory-002",
      "memory": "Loves Coding and Gaming",
      "user_id": "Hollis666",
      "score": 0.8
    }
  ]
}

检索结果是相关记忆,不是最终聊天回答。

将记忆加入模型上下文

应用需要读取检索结果,再将记忆加入聊天模型的输入。

python 复制代码
results = memory.search(
    "推荐一些适合我的休闲活动",
    user_id="Hollis666",
)

memory_text = "\n".join(
    item["memory"]
    for item in results.get("results", [])
)

prompt = f"""
请根据用户问题和相关记忆给出建议。

相关记忆:
{memory_text}

用户问题:
推荐一些适合我的休闲活动。
"""

print(prompt)

完整应用流程:

复制代码
用户提问
   ↓
Mem0检索相关记忆
   ↓
记忆 + 当前问题构成Prompt
   ↓
调用聊天模型生成回答
   ↓
按需将新的有效信息写入Mem0

用户标识的注意事项

本例使用:

复制代码
memory.search(
    "What do you know about me?",
    user_id="Hollis666",
)

课程中遇到的错误:

复制代码
At least one of 'user_id', 'agent_id', or 'run_id' must be provided.

表示该调用没有按接口要求提供记忆作用域。

不同版本、本地接口与托管服务的过滤参数可能不同,不能直接混用示例。这里保持与 add() 一致,显式传入 user_id

向量维度必须一致

本例配置:

复制代码
"embedding_model_dims": 768
"embedding_dims": 768

需要保持:

复制代码
模型实际输出维度
       =
向量存储配置维度
       =
已有数据库向量列的维度

否则可能出现:

复制代码
expected 1536 dimensions, not 768

配置中的数字不会自动把模型向量转换成对应维度。

如果已有表使用1536维,仅修改配置不一定能解决问题,需要迁移到匹配的存储结构,并重新生成向量。

运行程序

复制代码
uv run hello.py

也可以在已安装依赖的 Python 环境中执行:

复制代码
python hello.py

pip 用于管理依赖,不能使用 pip hello.py 运行脚本。

重点总结

  1. Mem0 管理的是从交互中提取的记忆,不只是完整聊天记录。

  2. 三个核心模块:

    LLM → 提取和更新记忆
    Embedder → 生成向量
    Vector Store → 保存和检索

  3. 两个主要操作:

    memory.add(messages, user_id="Hollis666")
    memory.search("当前问题", user_id="Hollis666")

  4. 保存和检索需要使用正确的用户标识;真实应用应由身份认证确定归属。

  5. 模型实际向量维度、配置维度和数据库维度必须一致。

  6. 查询到记忆后,仍需要应用将其加入聊天模型的上下文。

  7. 整体流程:

    对话 → 提取记忆 → 更新存储

    新问题 → 检索相关记忆 → 加入Prompt → 生成回答

七. Mem0的实现原理

核心定位

Mem0 是记忆管理框架,负责协调模型和存储组件,完成记忆提取、更新、保存和检索。

复制代码
应用调用Mem0
      ↓
协调LLM、Embedding和存储
      ↓
完成记忆管理

核心组件

组件 作用
LLM 提取事实,判断记忆是否需要新增、更新或删除
Embedding模型 将记忆内容和查询转换成向量
向量数据库 保存记忆文本、向量和元数据,支持语义检索
图数据库(可选) 保存实体关系,支持关联检索
SQLite历史存储 在课程所述本地实现中记录记忆变更历史

注意:向量化由 Embedding 模型完成,向量数据库负责保存和检索。

这些组件需要相应配置,但不一定都要独立部署,也可以使用本地库或托管服务。

添加记忆:add

复制代码
memory.add(
    messages,
    user_id="Hollis666",
)

默认的记忆推断流程可以理解为:

复制代码
输入对话和用户标识
        ↓
LLM提取候选事实
        ↓
Embedding生成向量,检索相关旧记忆
        ↓
LLM比较新旧信息
   ├── 新信息 → 新增
   ├── 信息变化 → 更新
   ├── 旧信息失效 → 删除
   └── 重复或无需处理 → 不变
        ↓
更新向量存储
        ↓
记录记忆变更历史

例如:

复制代码
旧记忆:
用户住在北京。

新对话:
我刚搬到上海。

处理结果:
更新用户当前居住地为上海。

模型不一定每次都判断正确,重要记忆仍需要支持纠正和删除。

图记忆:可选能力

启用图存储后,可以提取并保存实体之间的关系。

复制代码
原始信息:
爱丽丝的朋友是约翰。

图关系:
爱丽丝 ── 朋友 ──→ 约翰

适合保存:

  • 人物关系
  • 组织关系
  • 项目关联
  • 实体之间的联系

未配置图数据库时,仍然可以通过向量记忆完成主要功能。

检索记忆:search

复制代码
memory.search(
    "我住在哪里?",
    user_id="Hollis666",
)

基础流程:

复制代码
当前问题 + 用户标识
        ↓
Embedding将问题向量化
        ↓
在对应用户范围内检索相关记忆
        ↓
筛选并返回结果

如果启用了图记忆,还可以结合实体关系检索。

需要区分:

  • 基础向量检索不一定需要先调用 LLM 改写问题。
  • 图检索、重排序等能力取决于配置和实现。
  • 不能默认认为每次检索都会综合时间、重要性等全部因素。

SQLite历史与向量存储的区别

复制代码
向量存储:
当前有哪些记忆,哪些与问题相关。

历史存储:
某条记忆何时新增、更新或删除。

例如:

复制代码
当前记忆:
用户住在上海。

变更历史:
首次添加:用户住在北京。
后续更新:用户搬到上海。

记忆变更历史也不等于完整聊天记录。

检索结果如何参与回答

Mem0 返回的是记忆条目,应用再将它们加入聊天模型的上下文。

复制代码
用户提问
   ↓
Mem0检索相关记忆
   ↓
记忆 + 当前问题
   ↓
传给聊天模型
   ↓
生成个性化回答

例如:

复制代码
相关记忆:
用户不喜欢香菜。

当前问题:
推荐一道晚餐。

模型根据偏好给出建议。

重点总结

  1. Mem0 是记忆管理框架,底层数据由配置的存储组件保存。

  2. 各组件分工:

    LLM:提取事实、判断变更。
    Embedding:生成向量。
    向量数据库:保存和语义检索。
    图数据库:保存实体关系,可选。
    历史存储:记录记忆变更。

  3. add() 的核心:

    提取新事实 → 检索旧记忆 → 比较并决策 → 更新存储

  4. search() 的核心:

    问题向量化 → 按用户范围检索 → 返回相关记忆

  5. 图检索和重排序属于可选能力,不是所有配置都会启用。

  6. 是否完全本地运行取决于模型和存储的部署方式;使用远程模型时,相关数据仍会发送到对应服务。

八. Spring AI Alibaba接入Mem0做长期记忆

整体思路

Java 应用通过 HTTP 调用 Mem0 REST API Server,由 Python 服务执行记忆提取、更新和检索。

复制代码
Spring AI ChatClient
        ↓
Mem0ChatMemoryAdvisor
        ↓
Mem0MemoryStore
        ↓
Mem0ServiceClient
        ↓ HTTP
Mem0 REST API Server
        ↓
LLM + Embedding + 向量数据库

本节按课程提供的服务和 1.1.0.0-M5 扩展版本整理,其他版本的配置与接口可能不同。

Mem0 REST API Server

REST 服务将 Mem0 的能力暴露为 HTTP 接口,Java 无需直接调用 Python 代码。

主要能力:

  • 添加记忆
  • 检索相关记忆
  • 获取已有记忆
  • 更新和删除记忆
  • 查看记忆变更历史

具体接口、参数和请求方法以部署服务的 Swagger 文档为准。

课程服务的文档地址:

复制代码
http://localhost:8888/docs

部署课程中的服务

课程使用的是修改过的部署文件,需要先取得并解压对应项目。

Windows PowerShell:

复制代码
Copy-Item .env.example .env

修改 .env

复制代码
OPENAI_API_KEY=你的模型服务APIKey

启动服务:

复制代码
docker compose up -d

课程部署同时启动 Mem0 服务及 PostgreSQL、Neo4j 等依赖,实际组成由 docker-compose.yml 决定。

OPENAI_API_KEY 是该部署的配置变量名,不代表只能使用 OpenAI 官方服务,仍需与服务端配置的地址和模型对应。

初始化服务

课程中的定制服务需要先通过 /configure 初始化 Memory,才能执行记忆操作。

复制代码
启动REST服务
      ↓
调用/configure
      ↓
创建Memory及相关组件
      ↓
执行记忆增删改查

没有初始化时,可能出现:

复制代码
AttributeError: 'NoneType' object has no attribute 'add'

初始化请求的参数以该服务 /docs 中的定义为准,不能直接假设所有 Mem0 服务都需要相同的初始化步骤。

验证记忆写入

PowerShell:

复制代码
$body = @{
    messages = @(
        @{
            role = "user"
            content = "Hi, I am Hollis. I love Coding and Gaming."
        }
    )
    user_id = "Hollis678"
} | ConvertTo-Json -Depth 10

Invoke-RestMethod `
    -Uri "http://localhost:8888/memories" `
    -Method Post `
    -ContentType "application/json; charset=utf-8" `
    -Body ([System.Text.Encoding]::UTF8.GetBytes($body))

Mem0 分析对话,提取有价值的信息并保存。

获取用户记忆

复制代码
Invoke-RestMethod `
    -Uri "http://localhost:8888/memories?user_id=Hollis678" `
    -Method Get

这里是获取用户已有记忆,不等于根据问题进行语义搜索。语义检索应使用该版本服务提供的搜索接口。

引入长期记忆依赖

复制代码
<dependency>
    <groupId>com.alibaba.cloud.ai</groupId>
    <artifactId>spring-ai-alibaba-starter-memory-long</artifactId>
    <version>1.1.0.0-M5</version>
</dependency>

课程中相关组件的职责:

组件 作用
Mem0ServiceClient 封装 HTTP 请求
Mem0MemoryStore 适配 Spring AI 的存储接口
Mem0ChatMemoryAdvisor 调用前读取记忆,调用后写入交互
自动配置类 创建相关 Bean,并配置 Mem0 服务

注意与项目中已有的 Spring AI、Spring AI Alibaba 版本保持兼容。

最小配置

如果服务端已经配置好模型和数据库,Java 端可以使用课程中的最小配置:

复制代码
spring:
  ai:
    alibaba:
      mem0:
        client:
          base-url: http://127.0.0.1:8888
          timeout-seconds: 120
        server:
          version: v1.0.0

其中 version 是该集成传给服务端的配置项,不是 Maven 依赖版本。

自定义服务端配置

也可以由 Java 配置模型、向量库和可选的图存储,然后通过 /configure 传给服务端。

以下沿用课程中的配置字段:

复制代码
spring:
  ai:
    alibaba:
      mem0:
        client:
          base-url: http://127.0.0.1:8888
          timeout-seconds: 120

        server:
          version: v1.0.0

          vector-store:
            provider: pgvector
            config:
              host: postgres
              port: 5432
              dbname: postgres
              user: postgres
              password: ${MEM0_DB_PASSWORD}
              collection-name: memories

          graph-store:
            provider: neo4j
            config:
              url: bolt://neo4j:7687
              username: neo4j
              password: ${MEM0_GRAPH_PASSWORD}

          llm:
            provider: openai
            config:
              api-key: ${MEM0_LLM_API_KEY}
              model: ${MEM0_LLM_MODEL}
              temperature: 0.2
              openai-base-url: ${MEM0_LLM_BASE_URL}

          embedder:
            provider: openai
            config:
              api-key: ${MEM0_EMBEDDING_API_KEY}
              model: ${MEM0_EMBEDDING_MODEL}
              openai-base-url: ${MEM0_EMBEDDING_BASE_URL}

需要区分两个地址视角:

复制代码
client.base-url:
Java应用访问Mem0服务的地址。

server中的数据库和模型地址:
Mem0服务访问这些依赖时使用的地址。

例如,postgresneo4j 通常是 Docker 网络内的服务名。

另外,Embedding 模型的实际输出维度需要与向量存储一致。

给 ChatClient 添加长期记忆

复制代码
import com.alibaba.cloud.ai.memory.mem0.advisor.Mem0ChatMemoryAdvisor;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

import static com.alibaba.cloud.ai.memory.mem0.advisor
        .Mem0ChatMemoryAdvisor.USER_ID;

@RestController
@RequestMapping("/longTermMemory")
public class LongTermMemoryController {

    private final ChatClient chatClient;

    public LongTermMemoryController(
            ChatModel chatModel,
            @Qualifier("mem0MemoryStore") VectorStore memoryStore) {

        Mem0ChatMemoryAdvisor advisor =
                Mem0ChatMemoryAdvisor.builder(memoryStore)
                        .build();

        this.chatClient = ChatClient.builder(chatModel)
                .defaultAdvisors(advisor)
                .build();
    }

    @GetMapping("/chat")
    public String chat(
            @RequestParam String message,
            @RequestParam String userId) {

        return chatClient.prompt(message)
                .advisors(spec ->
                        spec.params(Map.of(USER_ID, userId))
                )
                .call()
                .content();
    }
}

这里的三个关键步骤:

复制代码
创建Mem0ChatMemoryAdvisor
        ↓
注册到ChatClient
        ↓
每次调用传入USER_ID

如果项目存在多个 ChatModel,需要再通过 @Qualifier 指定聊天模型。

Advisor 的执行流程

复制代码
用户问题 + userId
        ↓
Advisor调用Mem0检索相关记忆
        ↓
将记忆加入当前Prompt
        ↓
调用聊天模型
        ↓
获得回答
        ↓
Advisor将本次交互提交给Mem0
        ↓
Mem0提取并更新记忆

长期记忆和当前上下文的关系:

复制代码
Mem0保存的记忆
        ↓
本次检索出的相关部分
        ↓
加入ChatClient请求
        ↓
模型才能使用

调用示例

第一次告诉模型:

复制代码
GET /longTermMemory/chat?userId=hollis666&message=我元旦去了三亚

后续再次使用同一个用户标识:

复制代码
GET /longTermMemory/chat?userId=hollis666&message=我元旦去哪里了?

模型收到的上下文可以理解为:

复制代码
用户问题:
我元旦去哪里了?

相关长期记忆:
用户元旦去了三亚。

Java 应用重启后,只要 Mem0 后端数据仍保留,就可以继续检索。

但"重启后还能读取"只能证明数据持久化;长期记忆的关键还在于跨会话提取和按需复用。

用户记忆不等于确定偏好

如果只保存:

复制代码
用户元旦去过三亚。

不能直接断言:

复制代码
用户冬天总是喜欢去三亚。

一次旅行经历是事实,不一定代表长期偏好。模型应区分记录中的事实与基于事实做出的推测。

Mem0 不可用对启动的影响

按课程中的自动配置实现,启动时会创建客户端并调用 /configure。如果 Mem0 不可用,可能导致初始化失败。

复制代码
Spring Boot启动
      ↓
创建Mem0相关Bean
      ↓
调用/configure
      ↓
连接失败
      ↓
应用启动受到影响

仅在 Controller 上添加 @Lazy,不能阻止其他自动配置 Bean 提前初始化。

延迟初始化方案

课程方案分为三步:

  1. 排除原有自动配置。
  2. 自定义懒加载配置,创建所需 Bean。
  3. 将使用这些 Bean 的 Controller 设为懒加载。

排除自动配置:

复制代码
@SpringBootApplication(exclude = {
    com.alibaba.cloud.ai.autoconfigure.memory
            .Mem0ChatMemoryAutoConfiguration.class
})
public class SpringAiApplication {

    public static void main(String[] args) {
        SpringApplication.run(
                SpringAiApplication.class,
                args
        );
    }
}

自定义懒加载配置,相关类型来自课程版本的 Mem0 扩展:

复制代码
@Lazy
@Configuration
@EnableConfigurationProperties(Mem0ChatMemoryProperties.class)
public class Mem0LazyConfiguration {

    @Bean
    public Mem0ServiceClient mem0ServiceClient(
            Mem0ChatMemoryProperties properties,
            ResourceLoader resourceLoader) {

        Mem0ServiceClient client = new Mem0ServiceClient(
                properties.getClient(),
                properties.getServer(),
                resourceLoader
        );

        client.configure(properties.getServer());

        return client;
    }

    @Bean
    public VectorStore mem0MemoryStore(
            Mem0ServiceClient mem0ServiceClient) {

        return Mem0MemoryStore.builder(mem0ServiceClient)
                .build();
    }
}

Controller 添加:

复制代码
@Lazy
@RestController
@RequestMapping("/longTermMemory")
public class LongTermMemoryController {
    // 使用前面的Controller实现
}

执行效果:

复制代码
应用启动
   ↓
暂不初始化Mem0相关Bean
   ↓
首次需要长期记忆功能
   ↓
初始化客户端和存储
   ↓
调用Mem0服务

懒加载只是推迟初始化;首次访问时服务仍不可用,调用依然会失败。如果其他非懒加载 Bean 提前依赖这些组件,也可能触发初始化。

重点总结

  1. Java 通过 Mem0 REST API Server 使用 Python 侧的记忆管理能力。

  2. 核心调用链:

    ChatClient

    Mem0ChatMemoryAdvisor

    Mem0MemoryStore

    Mem0ServiceClient

    Mem0 REST API

  3. Advisor 在调用前检索记忆、补充上下文,调用后提交交互供 Mem0 提取和更新。

  4. USER_ID 用于关联用户记忆,实际业务中应从登录身份取得。

  5. 课程中的 /configure、配置字段和自动初始化行为与具体服务、扩展版本有关。

  6. Java 访问 Mem0 的地址,与 Mem0 访问数据库和模型的地址,需要分别配置。

  7. 延迟初始化需要同时处理自动配置、相关 Bean 和使用方;仅给 Controller 加 @Lazy 不一定有效。

  8. 最终流程:

    读取相关记忆 → 加入Prompt → 生成回答 → 提交新交互 → 更新记忆

相关推荐
百里香酚兰3 小时前
【Unity学习笔记】如何把粉色Prefab还原
笔记·学习·unity
梦影_3 小时前
Langchain简单快速上手教程(五)——聊天模型之流式传输
java·数据库·人工智能·python·langchain
索端阳4 小时前
ARM Day02 学习总结:ARM汇编基础核心知识点梳理
汇编·学习·arm
ai2work4 小时前
17 · composer 进阶
python
L@ncor4 小时前
第二章 智能体发展史 · 学习笔记
人工智能·python
asdzx674 小时前
Python 实战:基于 Spire.PDF 为 PDF 文档添加自定义文本
python·pdf
猫头虎4 小时前
FDE 是什么岗位?Forward Deployed Engineer 岗位标准、能力模型、交付物规范与冲突处置规则全解析
人工智能·开源·开源软件·ai编程·ai写作·gpu算力·agi
石小石Orz4 小时前
民间AI排行榜单新鲜出炉,Fable 5.1仅排第三
前端·后端·ai编程
m0_547486664 小时前
《Python数据分析与可视化项目教程》全套PPT课件2026
python·数据分析·数据可视化