从BM25到RRF向量融合:agent-memory本地排序检索的代码实现原理

发布时间:2026/10/7 8:10:26

从BM25到RRF向量融合:agent-memory本地排序检索的代码实现原理 从BM25到RRF向量融合agent-memory本地排序检索的代码实现原理【免费下载链接】agent-memoryLong-term memory runtime for AI agents — plain Markdown as the source of truth, local ranked retrieval, and an independent sleep-time Manage layer. Claude Code and Codex share one store. No API key.项目地址: https://gitcode.com/gh_mirrors/age/agent-memoryagent-memory 是一个面向 AI Agent 的本地长期记忆运行时以纯 Markdown 文件为唯一事实来源用 SQLite 建立可重建的索引在不联网、不要 API key 的前提下完成BM25 关键词检索与可选的向量融合排序RRF。本文将带你读懂它从 BM25 到 RRF 向量融合的完整排序链路——这也是大多数本地检索引擎的核心难题。1️⃣ 先搞懂问题为什么记忆检索要两条腿走路BM25 是最经典的词频-逆文档频率排序算法擅长精确词命中向量检索则擅长语义相近但用词不同。只用 BM25会漏掉同义表达只用向量则慢且对专有名词不敏感。agent-memory 的取舍很务实见 README.mdBM25 是基线低延迟、纯本地、默认开启向量是可选插件装上才启用且两路结果用RRFReciprocal Rank Fusion倒数排名融合合并任何一条路失效都有兜底最坏情况 agent 还能lsgrep直接翻目录树。整条读路径不调用任何模型、不跨网络索引本身是删了也能重建的缓存database.py 第一行注释就写明了这一点。2️⃣ 第一路BM25 检索FTS5 分块分块给索引一个检索面但召回单位仍是整文件记忆写入时chunking.py 会把每条记忆切分一段abstract 块一句话摘要正文按 Markdown 标题切分成若干body 块每块带 heading anchor 锚点。注意一个精妙的设计分块只为索引服务召回返回的单位始终是整个 Markdown 文件。agent 拿到的是路径和锚点再按需mem read name --level outline逐层深入——每深入一层成本递增一个数量级每一层都是可以停下的地方。FTS5 倒排索引与bm25()排名索引建在 SQLite 的 FTS5 虚拟表上database.py#L45-L62chunks表存当前有效记忆的块默认检索面history表存已失效记忆的块只有--as-of时间旅行查询才会读它。查询时search_index.py 先把查询拆成小写字母数字 token用OR连接逐词精确匹配再交给 FTS5 内置的bm25()函数打分SELECT name, kind, anchor, heading, bm25(chunks) AS rank FROM chunks WHERE chunks MATCH ? ORDER BY rank LIMIT ?BM25 分数越小负得越多表示越相关代码里取relevance -rank翻转成越大越相关返回最多pool条候选search_index.py#L87-L126。3️⃣ 第二路本地向量召回可选在 store 的config.toml中打开vector_enabled true并安装可选依赖后vector_index.py 登场嵌入模型默认BAAI/bge-small-en-v1.5通过 FastEmbed/ONNX 在本地跑不依赖任何云端 APIembeddings.py存储每个块的向量以BLOB形式存进 SQLite 的vector_chunks表随文件内容哈希增量更新检索暴力精确余弦brute-force cosine——逐块算 query 向量与块向量的余弦相似度降序取前pool条vector_index.py#L75-L96。没有向量数据库、没有近似最近邻ANN黑盒数据量是记忆条数级别精确搜索足够快且可解释、可重建。4️⃣ 核心RRF 倒数排名融合怎么合并两路结果两路各自返回排序后的候选列表但 BM25 分数和余弦分数的量纲完全不同不能直接相加。RRF 的妙处在于它只看名次不看分数每个候选的融合分 它出现在各路结果中的1 / (K 名次)之和其中K 60。实现见 recall.py 的fuse_candidatesK 60经验常数让头部名次1、2、3…之间的差距远大于尾部同时避免第 1 名一家独大——这正是 RRF 论文选择的值按块身份去重以(name, kind, anchor, heading)四元组为键同一块在单路结果里重复出现只计一次归一化到 0..1两路都命中第 1 名时理论满分是2 / (601)用它作除数融合分relevance就落在稳定的 0~1 区间可和后继的权重、新鲜度因子相乘稳定排序分数相同时按身份元组排序结果可复现。一句话总结 RRF两个评委各排各的名次名次靠前的加成大两路都靠前的是真答案。5️⃣ 最终排序相关性 × 权重 × 新鲜度融合完只是候选池。Recall 主流程 还做三步① 资格过滤先于相关性。作用域scope目录前缀、有效期valid_from/invalid_at先筛一遍——该不该出现比有多相关更优先。② 摘要块加权。每份文件取得分最高的块但命中 abstract 块的相关性乘2.0命中 body 块乘1.0config.py#L51-L52——一句话摘要命中的记忆通常更值得展示。③ 新鲜度指数衰减。最终分 relevance × weight × recency其中recency 0.5 ^ (距今天数 / 180)半衰期 180 天更新半年前的记忆新鲜度剩一半再久也保底 0.25recall.py#L216-L221。这样旧但重要的记忆不会被永久沉底而刚更新的内容会浮上来。取前limit条默认 8并把命中写入访问日志——日志是后续 Manage 层按价值遗忘的依据。6️⃣ 实测数据召回率与延迟的权衡README 给出了固定 120 条查询验收集上的量化结果指标仅 BM25BM25 向量 RRFRecall579.0%86.6%7.6pp中位检索延迟5.1ms139.2ms代价很直白向量嵌入 暴力余弦把中位延迟抬高了约 27 倍换来 7.6 个百分点的头部召回率。因此 agent-memory 把向量检索定位为可选增强而非默认——小库低延迟用 BM25 就够库大、查询口语化再打开。7️⃣ 如何动手开启 RRF 向量融合三步即可体验完整链路完整说明见 README.md 的 Optional vector recall index 一节uv sync --extra vector安装嵌入依赖在 store 的config.toml中设置[index] vector_enabled truemem --store /path/to/store rebuild重建索引再mem recall 你的问题。想只走最快的 BM25 基线保持vector_enabled false即可读路径零额外开销。8️⃣ 小结agent-memory 的检索栈可以浓缩为四句话BM25/FTS5 打底倒排索引 bm25()排名毫秒级、纯本地向量是插件FastEmbed 本地嵌入 SQLite BLOB 精确余弦没有黑盒RRF 只融合名次K60、按块身份去重、归一化到 0~1量纲问题自然消失业务规则最后出场资格 → 摘要加权 → 180 天半衰期新鲜度分数 相关性 × 权重 × 新鲜度。所有索引都是可rm -rf的缓存Markdown 文件才是记忆本身——这套从 BM25 到 RRF 向量融合的实现正是既要检索精度、又保持可读可迁移的一个务实范本。【免费下载链接】agent-memoryLong-term memory runtime for AI agents — plain Markdown as the source of truth, local ranked retrieval, and an independent sleep-time Manage layer. Claude Code and Codex share one store. No API key.项目地址: https://gitcode.com/gh_mirrors/age/agent-memory创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
延伸阅读

更多相关文章

2026/10/7 8:10:26

蓝牙耳机哪个性价比高?2026十大高性价比蓝牙耳机推荐

​蓝牙耳机哪个性价比高?相比过去更加注重音质的耳机市场,如今用户开始关注佩戴是否轻松、使用是否方便。开放式耳机正是顺应这一趋势出现的产品类型。不过不同品牌对于产品定位有所区别,最终体验也各有不同。本次我们测试了2026十大高性价比…

2026/10/7 8:45:28

Redhawk-SC输入件配置:构建芯片供电数字孪生体的核心实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 8:45:28

AI工业控制系统搭建指南:架构决策、选型与工程化落地

1. 从"AI工业控制系统"这个词说起:它到底在解决什么问题先把概念掰开。工业控制系统,也就是常说的ICS,核心职责是把传感器采集到的温度、压力、流量、位置这些物理量读进来,经过逻辑判断,再输出控制指令给执…

2026/10/7 8:45:28

从能聊到能办:Agent-Reach打通大模型工具调用最后一公里

最近我一直在鼓捣一个叫 Agent-Reach 的项目,说实话这个名字一开始就是我随手敲出来的代号,后来越做越觉得贴切——Reach,够得着。现在圈子里做个 Agent demo 很容易:让大模型接上对话窗口,能写诗、能编故事、能给你规…

2026/10/7 8:45:28

从连接到观测:Agent-Reach如何构建大模型智能体的触达层

“Agent-Reach”这个名字,我第一次看到是在一个技术社群的讨论帖里。当时大家正为一个老大难问题吵得不可开交——LLM(大语言模型)驱动的智能体在真实业务场景里,如何稳定地触达各种外部系统和工具,而不是像个没头苍蝇…

2026/10/7 8:45:28

Java银行排号系统源码解析:从通信模型到并发取号实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/5 6:32:56

Jev+Agent接管浏览器:browser-use实战与jev-ultrafast性能优化

1. 从“Jev”说起:为什么我要把Agent接进浏览器“Jev”这个词最近在圈子里出现的频率越来越高,很多人第一次听到会以为是某个新模型的名字,其实它更像是一种思路——把Jev模型的能力当作底座,通过Agent的方式去接管浏览器&#xf…

2026/10/7 8:18:33

多智能体集群实战:DeepAgents编排、MCP与A2A协议及Skills体系

1. 从"单兵作战"到"集群协同":多智能体编排到底在解决什么问题如果你最近在折腾 Agent 相关的东西,大概率会有一种感觉:单个 Agent 能做的事情,其实很快就摸到天花板了。你给它一个提示词,挂几个工…

2026/10/6 17:46:51

无源低通滤波器设计实战:从RC到LC,手把手教你避开那些坑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:05:03

ESP32免重刷固件:浏览器直接修改NVS键值实现WiFi配置更新

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:05:03

SAP HANA查询结果导出CSV:避开乱码、性能与权限的实用指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:05:03

数字后端Placement阶段Density与Congestion控制实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑