news 2026/9/26 8:29:09

LangChain.js Agent 长期记忆实战:用 Milvus 向量数据库构建可检索记忆库

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LangChain.js Agent 长期记忆实战:用 Milvus 向量数据库构建可检索记忆库

1. 为什么 Agent 需要长期记忆:从上下文窗口到可检索记忆库

做过 LangChain.js Agent 的人多半踩过同一个坑:对话轮次一多,模型就开始“失忆”。你明明在第三轮告诉过它项目用的是 PostgreSQL,到第十五轮它又建议你装 MySQL。这不是模型笨,而是它的上下文窗口装不下那么多历史消息,早期的对话被挤出去了。

最直觉的解法是把所有历史消息都塞进 prompt。但这条路很快会撞墙:一是 token 成本随轮次线性上涨,二是主流模型的有效注意力在超长上下文里会衰减,塞得越多反而越容易抓错重点。我实测过一个客服场景,把 40 轮对话全量拼接后,模型对第 5 轮关键信息的召回率反而比只给最近 10 轮更低。

所以真正靠谱的思路是把记忆从“上下文”里搬出来,变成“可检索的外部存储”。这就是 Agent Memory 的核心:短期记忆仍然放在对话历史里,长期记忆则写进一个向量库,需要的时候按语义相似度检索回来,只把最相关的几条注入 prompt。

LangChain.js 里做这件事的组件链条很清晰:Embeddings负责把文本转成向量,VectorStore负责存和查,Memory负责把检索结果接回对话流程。上篇我们聊了 Memory 的抽象和短期记忆的接法,这篇专攻下半场——用 Milvus 把长期记忆真正落地。

Milvus 在这里的角色是向量数据库。它和 Chroma、Qdrant 属于同一类工具,区别在于 Milvus 是为大规模、高并发、分布式场景设计的,单机也能跑,但它的架构天生能横向扩展。如果你的 Agent 记忆量会从几千条涨到几百万条,或者你要做多租户隔离,Milvus 的 collection、partition、index 这套机制会比轻量方案更从容。

这篇文章适合两类人:一是已经用 LangChain.js 搭过 Agent、想让记忆真正“记得住”的开发者;二是正在做向量库选型、想搞清楚 Milvus 在 Agent 场景里怎么用的架构同学。下面我会从整体设计讲到具体代码,再到踩坑排查,尽量把每一步的“为什么”说清楚。

2. 整体设计:Milvus 长期记忆的架构与选型考量

2.1 记忆分层:短期、长期、工作记忆各管什么

在动手写代码前,先把记忆分层想清楚,否则很容易把 Milvus 当成万能垃圾桶,什么都往里塞。

我的做法是分三层:

  • 短期记忆:最近 N 轮对话原文,直接放在messages数组里,随对话滚动。它保证 Agent 对当前话题的连贯性,比如“它”“那个方案”这类指代能对上。
  • 长期记忆:把每轮对话或每个事实抽取成一条记录,embedding 后写进 Milvus。它解决的是跨会话、跨时间的记忆,比如“用户三个月前说过偏好深色主题”。
  • 工作记忆:当前任务相关的临时状态,比如正在处理的订单号、中间计算结果。这部分我一般放在内存或 Redis,任务结束就清掉,不进 Milvus。

为什么长期记忆要单独抽出来?因为它的写入和读取频率、生命周期和短期记忆完全不同。短期记忆每轮都变,长期记忆是“写一次、查多次”,而且查询是语义检索而非顺序读取。把两者混在一起,既浪费 token 又拖慢检索。

2.2 为什么是 Milvus:和 Chroma、Qdrant 的横向对比

选型这块我踩过不少坑,直接上对比表更清楚。

维度MilvusChromaQdrant
部署复杂度中,单机可用 Docker,集群需组件低,嵌入式即可低到中,单二进制
数据规模亿级,分布式扩展十万级较舒适千万级
索引类型非常丰富,IVF、HNSW、DiskANN 等有限较丰富
多租户/分区原生 partition、database弱支持 payload 过滤
生态与 LangChain.js 集成官方支持官方支持官方支持
适合场景大规模、生产级、多租户原型、本地小数据中小规模、过滤需求强

我选 Milvus 的理由很实际:一是记忆量预期会增长,不想中途换库;二是 Milvus 的 partition 机制天然适合按用户或会话隔离记忆;三是它的索引调优空间大,后期召回率不达标时有牌可打。如果你的项目就是本地跑个 demo,Chroma 更省事;如果过滤条件特别复杂,Qdrant 的 payload 过滤更顺手。

注意:Milvus 单机版(Standalone)用 Docker 起最省心,但要注意它依赖 etcd 和 MinIO,docker-compose一把起比单独装三个组件靠谱得多。

2.3 数据模型设计:一条记忆记录该存哪些字段

很多人一上来只存text和vector,后面想做过滤、做去重、做时间衰减时才发现字段不够。我建议一条记忆记录至少包含这些字段:

  • id:主键,用 UUID 或自增。
  • vector:embedding 向量,维度要和模型对齐。
  • text:原始文本,检索回来直接注入 prompt。
  • userId:多用户隔离的关键,配合 partition 或标量过滤。
  • sessionId:会话标识,方便按会话清理。
  • type:记忆类型,比如fact、preference、summary。
  • createdAt:时间戳,用于时间衰减排序或过期清理。
  • metadata:JSON 字符串,放扩展信息。

为什么type和createdAt重要?因为检索时你往往不想把所有记忆一视同仁。用户偏好类记忆应该长期保留,临时事实类记忆可以设过期。有了这两个字段,排序和过滤才有依据。

3. 环境搭建:Milvus 本地部署与 LangChain.js 依赖接入

3.1 用 Docker Compose 起一个 Milvus Standalone

Windows 和 macOS 上我都建议用 Docker Desktop 跑 Milvus Standalone。官方提供了docker-compose.yml,核心是三个服务:milvus、etcd、minio。etcd 存元数据,minio 存向量和标量数据的底层文件,milvus 本体负责协调和查询。

拉取配置后直接:

docker compose up -d

起来后用docker ps确认三个容器都在跑。Milvus 默认暴露19530端口给 SDK 连接,9091是健康检查端口。你可以访问http://localhost:9091/healthz看是否返回健康状态。

注意:Windows 上如果 Docker 用的是 WSL2 后端,内存建议给到 8GB 以上,Milvus 加 etcd 加 MinIO 一起跑,4GB 很容易 OOM。我第一次在 4GB 的 WSL 里跑,milvus 容器反复重启,查日志才发现是被系统杀掉了。

3.2 安装 LangChain.js 与 Milvus SDK

Node 侧需要装这几个包:

npm install @langchain/core @langchain/milvus @langchain/openai

@langchain/milvus是官方集成包,封装了Milvus这个 VectorStore 类。Embedding 我这边用 OpenAI 的text-embedding-3-small,维度 1536,性价比高。如果你要本地跑 embedding,可以换成@langchain/community里的 HuggingFace 或 Ollama 集成,但要注意维度变化后 collection 的dimension必须同步改。

这里有个容易忽略的点:Embedding 模型一旦选定,维度就固定了,collection 建好后不能改维度。换模型等于重建 collection,所以选型时要想清楚。我一般会在项目里把 embedding 模型名和维度写成常量,避免两处不一致。

3.3 连接参数与常见配置项说明

初始化 Milvus 客户端时,几个参数值得说明:

  • url:http://localhost:19530,注意是 http 不是 https(本地)。
  • collectionName:集合名,建议带业务前缀,比如agent_memory。
  • vectorField:向量字段名,默认vector。
  • primaryField:主键字段名,默认id。
  • textField:文本字段名,默认text。

这些字段名要和你在 Milvus 里建的 schema 对上,否则插入时会报字段不存在。LangChain 的 Milvus 集成在首次插入时会自动建 collection,但自动建的 schema 字段有限,如果你需要userId、type这些自定义字段,最好手动建 collection,把 schema 定义清楚。

4. 核心实现:把对话写进 Milvus 并检索回来

4.1 Embedding 生成:模型选择与批量处理

Embedding 是整条链路的入口,它的质量直接决定检索效果。我对比过几个常用模型:text-embedding-3-small便宜且够用,text-embedding-3-large召回更准但成本高,本地模型如bge-m3在多语言场景表现不错。

批量处理是个关键优化点。如果你一轮对话产生 10 条记忆,逐条调 embedding 接口会有 10 次网络往返。LangChain 的embedDocuments支持批量,一次传数组,内部会合并请求。我实测批量 100 条比逐条快 5 倍以上。

import { OpenAIEmbeddings } from "@langchain/openai"; const embeddings = new OpenAIEmbeddings({ model: "text-embedding-3-small", dimensions: 1536, }); const vectors = await embeddings.embedDocuments(texts);

注意:批量不是越大越好。OpenAI 单次请求有 token 上限,文本太长时要先切分。我一般把单条记忆控制在 500 字以内,超长的先做摘要再入库。

4.2 写入记忆:addDocuments 与自定义字段

写入时用addDocuments,每条Document的metadata会作为标量字段存进去。

import { Milvus } from "@langchain/milvus"; import { Document } from "@langchain/core/documents"; const store = new Milvus(embeddings, { url: "http://localhost:19530", collectionName: "agent_memory", }); const docs = [ new Document({ pageContent: "用户偏好深色主题,代码字体用 JetBrains Mono", metadata: { userId: "u_1001", sessionId: "s_abc", type: "preference", createdAt: Date.now(), }, }), ]; await store.addDocuments(docs);

这里有个坑:metadata 里的值类型要统一。Milvus 对标量字段类型敏感,createdAt如果一会儿是数字一会儿是字符串,插入会失败。我统一用数字时间戳,排序和过滤都方便。

4.3 检索记忆:相似度搜索与过滤条件组合

检索是长期记忆的价值所在。基础用法是similaritySearch,传查询文本和 topK:

const results = await store.similaritySearch("用户喜欢什么主题", 5, { userId: "u_1001", });

第三个参数是过滤条件,这里按userId过滤,保证只召回当前用户的记忆。这一步在多用户场景里至关重要,否则会把别人的记忆检索出来,既错又危险。

过滤条件支持组合,比如只要偏好类且最近 30 天的记忆:

const filter = { userId: "u_1001", type: "preference", createdAt: { $gt: Date.now() - 30 * 24 * 3600 * 1000 }, };

Milvus 的标量过滤语法和 MongoDB 类似,$gt、$in、$and都支持。用好过滤能大幅缩小检索范围,提升速度和准确率。

4.4 接回 Agent:把检索结果注入对话上下文

检索回来的Document不能直接丢给模型,要格式化成一段“已知信息”再拼进 system prompt 或作为一条 system 消息。我的做法是:

const memoryText = results .map((doc, i) => `[记忆${i + 1}] ${doc.pageContent}`) .join("\n"); const systemPrompt = `你是一个助手。以下是关于用户的已知信息,回答时请参考:\n${memoryText}`;

为什么要加编号和“记忆”前缀?因为模型对结构化信息的利用效率更高,明确告诉它这是背景知识,它会更倾向于引用而不是忽略。实测加前缀后,模型引用记忆的准确率有明显提升。

5. 性能与效果调优:索引、召回率与成本控制

5.1 索引类型选择:HNSW 与 IVF 的取舍

Milvus 建 collection 时如果不指定索引,默认可能用 FLAT(暴力搜索),数据量一大就慢。生产环境一定要建索引。

索引类型特点适用场景
FLAT精确,无近似误差数据量小,追求 100% 召回
IVF_FLAT倒排+聚类,速度快百万级,可接受少量误差
HNSW图索引,召回和速度平衡好千万级,低延迟要求
DiskANN磁盘索引,省内存超大规模,内存受限

Agent 记忆场景我一般选 HNSW,因为记忆量通常不会到亿级,但延迟要求高,HNSW 的查询延迟稳定。建索引时M和efConstruction两个参数影响召回率和构建时间,M越大召回越高但内存占用越大,我一般从M=16起步。

5.2 召回率调优:topK、阈值与重排序

topK 不是越大越好。topK 太大,注入 prompt 的记忆太多,既费 token 又可能引入噪声;太小则可能漏掉关键记忆。我的经验值是 3 到 8 条,具体看记忆密度。

另一个手段是相似度阈值过滤。Milvus 返回的 score 是距离,不同索引类型含义不同(L2 是越小越相似,IP 是越大越相似)。我一般会设一个阈值,把明显不相关的记忆丢掉。如果对精度要求高,可以在检索后加一层重排序(rerank),用交叉编码器对候选记忆重新打分,但会增加延迟,按需使用。

5.3 成本控制:减少无效 embedding 与缓存策略

Embedding 调用是主要成本来源。几个省钱技巧:

  • 去重:写入前先查一下是否已有相似记忆,避免重复 embedding。可以用一个短文本哈希做粗筛。
  • 缓存:相同查询文本的 embedding 结果缓存起来,重复查询直接命中。
  • 摘要代替原文:长对话先摘要再入库,减少 token 消耗。
  • 批量:前面提过的批量 embedding。

我实测过一个场景,加了去重和缓存后,embedding 调用量下降了约 40%。

6. 常见问题与排查技巧实录

6.1 连接失败与超时排查

最常见的是连不上 Milvus。排查顺序:

  1. docker ps看容器是否都在跑。
  2. 访问http://localhost:9091/healthz看健康状态。
  3. 检查端口是否被占用,19530是否映射正确。
  4. 看 milvus 容器日志,docker logs <container>,常见是 etcd 或 MinIO 没起来。

注意:如果 milvus 容器反复重启,八成是内存不够被系统杀了,先加内存再排查其他。

6.2 维度不匹配与字段类型错误

报错dimension mismatch说明 embedding 维度和你建 collection 时的dimension不一致。换过 embedding 模型后必须重建 collection。字段类型错误通常是 metadata 里混了类型,统一成数字或字符串即可。

6.3 检索结果不相关的调优思路

检索不准,先看三件事:embedding 模型是否适合你的语言和领域;文本是否切分合理;过滤条件是否过严或过松。我遇到过一次召回全是不相关记忆,最后发现是文本切分把一句话切断了,语义被打散。调整切分策略后恢复正常。

6.4 常见问题速查表

现象可能原因解决方向
连接超时容器未起或端口不通查容器状态和端口映射
维度不匹配embedding 模型换了重建 collection
插入失败metadata 类型不一致统一字段类型
检索不准切分或模型问题调整切分,换模型
查询变慢未建索引建 HNSW 索引
内存暴涨索引参数过大调小 M 或换 DiskANN

6.5 几个我踩过的坑

第一个坑是忘记按 userId 过滤,测试时单用户没发现,上线多用户后记忆串了。第二个坑是collection 自动建 schema 字段不够,后来改成手动建。第三个坑是时间戳用字符串,导致范围过滤失效,改成数字后正常。这些坑都不难,但没人提醒的话真会卡半天。

7. 记忆的维护:过期清理与增量更新

长期记忆不是只写不删。用户偏好会变,临时事实会过期,记忆库需要维护机制。

我的做法是给记忆加createdAt和type,定期跑一个清理任务:把type为temporary且超过 7 天的记忆删掉;对preference类记忆,如果同一用户同一主题有新记忆写入,把旧的标记为失效或直接删除。

增量更新用addDocuments追加即可,Milvus 支持持续写入。删除用delete按主键或过滤条件删。注意删除是软删还是硬删取决于配置,Milvus 默认删除后数据在 compaction 后才真正释放空间。

提示:清理任务建议放在低峰期跑,避免和在线检索抢资源。

8. 从单机到生产:扩展性与多租户隔离

单机 Milvus 够用很久,但数据量和并发上来后要考虑扩展。Milvus 的分布式版把查询、写入、索引分开部署,能独立扩缩容。多租户隔离有两个层次:轻量级用标量字段userId过滤,重量级用 partition 或 database 物理隔离。我一般先用字段过滤,等单 collection 数据量到千万级再考虑 partition。

partition 的好处是查询时只扫对应分区,速度快。但 partition 数量不宜过多,Milvus 对 partition 数有上限,按用户分要谨慎,按租户或按时间分更合理。

9. 写在最后的一点个人体会

这套 Milvus 长期记忆方案我在两个项目里跑过,一个客服 Agent,一个个人助理。客服场景记忆量大、并发高,Milvus 的索引和分区帮了大忙;个人助理场景数据量小,其实 Chroma 也够,但用 Milvus 让我不用中途换库。

如果让我给刚上手的人一句建议:先把数据模型和过滤字段设计好,再写代码。我见过太多人急着调 embedding,结果字段不够用,回头改 schema 重建 collection,前面的数据全白写。记忆这件事,结构比模型更重要。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/26 8:29:05

SQL Server实验大作业实战:从建库建表到报告避坑全流程

简介&#xff1a;面向软件工程本科生的数据库SQL Server实验大作业&#xff0c;以小区物业收费管理系统为业务背景&#xff0c;完整覆盖从需求分析、E-R图设计到建表、查询、视图、索引、授权及用户操作等全流程。资源共16个文件&#xff0c;包含13个sql脚本、1份docx实验报告、…

作者头像 李华
网站建设 2026/9/26 8:27:57

Atlas 300V 24G:AI推理加速卡如何高效部署YOLO

最近后台和社群里好几个人在问同一个问题&#xff1a;“atlas 300v 24g 是运算加速卡吗&#xff1f;”旁边跟着的另一条热搜是“atlas部署yolo”。这两条串在一起&#xff0c;我大概能猜出大家的处境&#xff1a;要么是刚拿到一块 Atlas 300V 的卡准备上手&#xff0c;要么是在…

作者头像 李华
网站建设 2026/9/26 8:26:43

多智能体沉浸式教学系统OpenMAIC:架构拆解与部署实践

1. 项目概述与核心价值最近清源开源社区又放出一个重磅项目&#xff1a;OpenMAIC&#xff0c;一个多智能体沉浸式教学系统&#xff0c;在 GitHub 上已经冲到 36,000 星。老实说&#xff0c;教育领域的 AI 开源项目能拿到这个量级的关注度&#xff0c;本身就很能说明问题。我第一…

作者头像 李华
网站建设 2026/9/26 8:26:30

Codex 实战:AGENTS.md 与 Skills 配置指南

1. 这次 Codex 更新到底改了什么 1.1 从"能写代码"到"能干活"的分水岭 Codex 这次放出来的东西&#xff0c;圈子里讨论度最高的不是模型本身跑分涨了多少&#xff0c;而是它把 AGENTS.md 和 Skills 这两套机制真正打通了。我第一时间把手上几个项目迁…

作者头像 李华
网站建设 2026/9/26 8:26:22

关天智创在线测厚仪产品稳定性怎么样,规模实力如何

在锂电池工厂的深夜产线上&#xff0c;质检员手中的卡尺反复开合&#xff0c;记下一组组厚度数据。软包电芯经过热压、化成后微微鼓胀&#xff0c;厚度的波动藏在几微米之间&#xff0c;肉眼无法分辨&#xff0c;人工抽检却只能覆盖冰山一角。数据少、可信度低&#xff0c;良品…

作者头像 李华