资讯动态

AI记忆系统分析m-flow(二)

发布时间:2026/8/15 1:35:17 来源:尧图企业网站定制
m-flow Episodic Retrieval图数据库、向量数据库、Bundle Scoring 与 Keyword Bonus这篇文章只讲episodic retrieval。目标不是泛泛介绍“什么是图数据库”或“什么是向量数据库”而是回答下面几个更具体的问题在 m-flow 里图数据库到底存什么在 m-flow 里向量数据库到底存什么检索时这两个库各自负责哪一步bundle scoring到底在算什么keyword bonus到底怎么工作为什么它不是一条独立检索链如果只先记一句话可以记这个向量数据库负责“先把可能相关的点捞出来” 图数据库负责“把这些点放回结构里判断它们属于哪一个 Episode” bundle scoring 负责“给每个 Episode 算总代价并排序” keyword bonus 负责“在向量召回结果上做一层规则加权”还有一个非常重要的前提episodic retrieval 里的 score 本质上是 distance distance 越小越好 所以所谓 bonus很多时候其实是“把距离减小”0. 如果你没用过向量数据库和图数据库先建立最小心智模型这一节是给“没真正用过这两类库”的读者准备的。如果只先记最短版本可以记下面这三句话普通数据库擅长按字段精确查 向量数据库擅长按语义相似查 图数据库擅长按关系结构查0.1 三类数据库分别回答什么问题假设你有下面这件事Episode: API 流式返回改造 Facet: 变更边界 FacetPoint: 鉴权中间件不能改 Entity: /chat/completions如果你问的是id E1 的 Episode 是谁type Facet 的节点有几个这种更像是普通数据库擅长的问题因为它本质上是按字段过滤。如果你问的是和“API 流式返回那个任务有哪些约束”语义最像的是哪几条文本和“变更边界”意思接近的是哪些记录这种更像是向量数据库擅长的问题因为它本质上是相似度搜索。如果你问的是“鉴权中间件不能改”这个点属于哪个 Facet这个 Facet 又属于哪个 Episode“API”这个实体和哪些 Episode 相连这种更像是图数据库擅长的问题因为它本质上是在问节点之间怎么连。所以你可以先把三者理解成普通数据库按列查 向量数据库按“像不像”查 图数据库按“连不连、怎么连”查0.2 向量数据库最小是怎么用的如果不讲任何厂商细节向量数据库最小就是四步选一段要检索的文本把它变成 embedding 向量连同id / text / payload一起存进某个 collection搜索时把 query 也变成向量做最近邻检索返回 top-k 命中你可以把它理解成输入一句话 - 变成一串浮点数 - 去找“向量空间里离它最近”的记录 - 返回 id、text、score一个最小例子collection Facet_anchor_text record: id F2 text 鉴权中间件保持不变只改响应层和测试。 payload {type: Facet, field: anchor_text} query: API 流式返回那个任务有哪些约束 search result: id F2 score 0.10这里的意思不是“它完全相同”而是这条文本和 query 在语义空间里比较近所以它值得进入下一步候选向量数据库擅长的是同义表达模糊表达不同措辞但语义接近向量数据库不擅长的是判断这条命中属于哪个 Episode判断两个命中之间是不是父子关系沿着Point - Facet - Episode这种结构往回走换句话说向量数据库很擅长先回答什么像 query但它不擅长独立回答这些命中最后该归到谁头上0.3 图数据库最小是怎么用的如果不讲任何厂商细节图数据库最小也是四步建节点建边给边起关系名并带上属性查询邻居、triplet 或局部子图在这个项目里一个极简图大概像这样Episode(E1: API 流式返回改造) --has_facet-- Facet(F2: 变更边界) --has_point-- FacetPoint(P1: 鉴权中间件不能改) Episode(E1) --involves_entity-- Entity(EN1: API)当向量库先命中P1或F2后图数据库就可以继续回答P1连着哪个Facet这个Facet连着哪个Episode这个Episode还连着哪些Entity你可以把图库理解成一个特别擅长回答下面这类问题的系统给你一个点 把它周围怎么连的也找出来图数据库擅长的是父子归属多跳关系局部结构恢复路径解释图数据库不擅长的是直接拿一句自然语言做高质量语义相似检索靠“文本像不像”在整图上做第一轮粗召回换句话说图数据库很擅长回答这个点属于谁 它和谁相连 从它走几跳能回到哪个 Episode0.4 为什么 m-flow 里必须把两者一起用如果只用向量库你会得到一堆“看起来像 query”的文本命中但不知道它们是否属于同一件事如果只用图库你会得到很清楚的结构关系但不知道该先从哪几个点开始找所以 m-flow 的组合方式本质上是向量库先负责“找入口” 图库再负责“还原归属关系”也可以更口语一点向量库先撒网 图库再收网1. 为什么 m-flow 不只用向量库如果只有向量库系统能回答的问题只有一种哪一段文本和 query 最像这个能力当然有用但它不够。因为 episodic memory 要解决的问题不是“找最像的一段文本”而是这些命中的局部证据最终应该收敛到哪一个 Episode一个用户问题经常会同时碰到不同粒度的内容有时先命中一个Episode.summary有时先命中一个Facet.search_text有时先命中一个FacetPoint.search_text有时只命中一个Entity.name如果没有图结构系统只能看到一堆分散的文本命中看不到这些命中之间的归属关系。这就是 m-flow 同时使用图数据库和向量数据库的原因向量数据库负责语义相似度图数据库负责结构归属和多跳连接1.1 这套逻辑不绑定某一家数据库这个项目的 retrieval 代码不是直接写死在某个具体后端上而是走 adapter 抽象图侧有graph provider向量侧有vector provider因此从代码结构上说它支持的不是单一组合。可以先把它理解成图侧常见的是Kuzu向量侧常见的是LanceDB但 retrieval 主逻辑真正依赖的不是“Kuzu 特性”或“LanceDB 特性”而是这两个抽象能力图侧能按 id 投影局部子图向量侧能按 collection 做 embedding search2. 在这个项目里图数据库到底存什么2.1 图数据库存的是“结构化记忆图”对 episodic memory 来说图数据库里保存的不是孤立文本而是节点和边。最核心的节点有四类EpisodeFacetFacetPointEntity最核心的边有几类Episode --has_facet-- FacetFacet --has_point-- FacetPointEpisode --involves_entity-- EntityFacet --involves_entity-- Entity可以先把它想成这样Episode ├─ Facet │ └─ FacetPoint ├─ Entity └─ Chunk / 其他证据边2.2 图数据库关心的是“谁和谁连着”图数据库最重要的职责不是文本相似而是结构关系。例如Episode: API 流式返回改造 ├─ Facet: 实施方案 ├─ Facet: 变更边界 │ └─ FacetPoint: 鉴权中间件不能改 ├─ Entity: API └─ Entity: /chat/completions在图数据库视角里关键不是这些节点各自长什么样而是变更边界属于哪个 Episode鉴权中间件不能改属于哪个 FacetAPI同时挂在哪些 Episode 或 Facet 上2.3 边在这个系统里也有语义这点很关键。m-flow 里的边不是完全“哑”的关系线。边上通常还带有relationship_nameedge_text这意味着系统不只知道Episode 和 Facet 连着还知道这条连接的关系文本是什么。后面你会看到这些边文本也会进入向量索引参与检索和打分。2.4 这个项目实际上怎么使用图数据库如果你没用过图库这里最重要的是分清本项目不是把图数据库当“全文搜索引擎”来用而是把图数据库当“结构恢复器”来用对 episodic retrieval 来说图数据库最常做的是这几类事写入节点和边例如写入Episode / Facet / FacetPoint / Entity再写入has_facet / has_point / involves_entity按 id 取节点、取边、取邻居按relevant_ids投影一个局部子图把这个局部子图物化成内存里的MemoryGraph这意味着图库在这条链路里的使用方式不是拿一句 query - 直接让图库在整库里做模糊语义匹配而更像是向量库先告诉我哪些 id 值得看 - 图库把这些 id 周围的结构拉出来 - 程序在这块局部图上继续算所以对这篇文章来说你可以把图库理解成一个负责保存结构、恢复邻接关系、支持局部投影的后端3. 在这个项目里向量数据库到底存什么3.1 向量数据库存的不是整张图而是“可嵌入字段”图数据库存的是节点和边。向量数据库存的是这些节点或关系上的某些文本字段的 embedding 索引。在这个项目里collection 名字遵循{NodeType}_{field}所以你会看到这些集合Episode_summaryFacet_search_textFacet_anchor_textFacetPoint_search_textEntity_nameRelationType_relationship_name3.2 每个 collection 实际存的是什么可以直接按表理解Collection实际存的文本用途Episode_summaryEpisode.summary从整件事角度粗召回Facet_search_textFacet.search_text用短标题召回某个 FacetFacet_anchor_textFacet.anchor_text用富语义文本召回某个 FacetFacetPoint_search_textFacetPoint.search_text用细粒度断言召回Entity_nameEntity.name用实体名召回RelationType_relationship_name边的edge_text / relationship_name给路径上的边补语义分数3.3 它不是“把整条图边整条存进去”而是给边语义做索引RelationType_relationship_name容易误解。它不是说图里每一条边都原样复制成一条独立向量记录更准确地说它会把边上的语义标签拿出来做索引优先用edge_text没有时退回relationship_name检索时先查这些关系文本再按文本匹配回图边给实际路径上的边补vector_distance。所以这层的职责是不是召回终点而是给“路径本身”提供语义成本。3.4 向量库里一条记录大概长什么样你可以把每条向量记录理解成id 某个节点或关系标签的 id text 被嵌入的文本 vector embedding 向量 payload 原始字段对 episodic retrieval 来说最关键的是id后面要回图text后面要做keyword bonusscore检索后会成为 node cost 或 edge cost 的输入3.5 这个项目实际上怎么使用向量数据库如果你没用过向量库可以把它想成“多张专门做语义检索的表”。这个项目实际用到的能力很克制核心只有几件事建 collection例如Episode_summary、Facet_search_text把某个字段写成向量记录一条记录至少要能回出id / text / payload按query_text或query_vector做 search返回VectorSearchHit里面至少有id / score / payload / raw_distance / collection_name在必要时做 batch search所以它的工作方式更像把多个“可检索字段”分别建索引 搜索时每个 collection 都查一下 把命中的 id 和 distance 带回来而不是把整张图整个塞进一个大向量库 然后只靠向量库做最终排序对这篇文章最重要的一点是向量库负责返回候选命中但不会独立决定最终哪个 Episode 胜出真正的最终归因还要等best_by_idedge_hit_map图投影bundle_scorer4. 这个项目里图数据库和向量数据库怎么配合这条链路可以压成下面这五步1. 用向量库多集合召回 2. 把命中的 node id 放回图里 3. 在图里补齐相邻节点和边 4. 对每个 Episode 计算 bundle score 5. 取分数最小的 Episode bundles 输出把它翻成更口语一点的话就是向量库先说“这些点像” 图数据库再说“这些点其实属于这些 Episode” bundle scorer 最后说“哪个 Episode 最能解释这些命中”5. 一条真实检索链是怎么跑的这一节按真实顺序讲一次。继续沿用前面的例子假设用户问API 流式返回那个任务有哪些约束5.1 第一步query 预处理系统会先得到一个PreprocessedQuery里面最重要的是originalvector_querykeywordhybrid_reasonuse_hybrid其中vector_query是拿去做 embedding 搜索的 querykeyword是后面做keyword bonus用的规则关键词例如这句 query 里同时有中文和英文API所以会触发hybrid_reason mixed_langkeyword API这里没有 LLM。5.2 第二步多集合向量召回然后系统会同时查多个集合Episode_summaryFacet_search_textFacet_anchor_textFacetPoint_search_textEntity_nameConcept_nameRelationType_relationship_name注意这一步还没有图推理只是把可能相关的节点和边文本先广撒网捞出来假设召回结果像这样FacetPoint_search_text: P1 鉴权中间件不能改 score0.08 Facet_search_text: F2 变更边界 score0.12 Facet_anchor_text: F2 鉴权中间件保持不变只改响应层和测试 score0.10 Entity_name: EN1 API score0.18 Episode_summary: E1 API 流式返回改造 score0.36 E2 首页文案交付 score0.625.3 第三步先在向量结果上打 bonus这一步仍然没有图。系统会对这些向量结果做两类规则调整keyword bonusexact match bonus例如 query 里的keyword API命中某些结果文本后系统会把对应 score 再减一点。所以某个结果可能从0.18 - 0.03注意这不是“相似度加分”而是“距离减小”。5.4 第四步生成best_by_id同一个节点可能在多个集合里都命中。例如Facet F2既可能命中Facet_search_text又命中Facet_anchor_text系统会把同一节点在所有集合里的分数取最小值生成best_by_id[node_id] min(all collection scores)这样后续图计算只看这个节点目前最强的一次命中。5.5 第五步生成edge_hit_map对于边文本集合RelationType_relationship_name系统也会做类似的事情edge_hit_map[edge_text] best score之后如果图里某条边的edge_text命中了 query这条边在路径计算里就更便宜。如果没有命中就用默认罚分edge_miss_cost。5.6 第六步把这些命中投影回图数据库现在系统手里只有命中的 node ids命中的 edge texts各自的分数它还不知道这些命中之间怎么连。所以接下来会去图数据库里做两阶段投影先按命中的 relevant ids 投一个局部子图再把相邻节点扩一跳拿到更完整的局部结构这里还有一个很容易忽略的点图数据库里的原始数据不会被bundle_scorer直接逐条扫描程序会先把这一小块相关结构投影成一个内存里的MemoryGraph后面的关系整理、路径计算、bundle scoring 都发生在这个局部投影上这一层可以把它理解成先用向量库找到“哪些点像” 再用图数据库把这些点周围的关系网拉出来5.7 第七步构建 RelationshipIndex子图拿回来后程序不会直接在原始图对象上瞎搜。它会先整理出几张关系表facets_by_episodepoints_by_facetentities_by_episodeentities_by_facet以及各种边的 lookup 表这样后面的bundle_scorer就可以非常快地做路径计算。6. bundle_scorer 到底在做什么6.1 最短理解bundle_scorer并不是在做“高级语义理解”。它做的是一个很工程化的事情对每个 Episode穷举几条允许的图路径算出总代价取最小值。这个最小值就是这个 Episode 的bundle score。6.2 EpisodeBundle 是什么最终每个候选 Episode 会得到一个EpisodeBundle里面最重要的是episode_idscorebest_pathbest_support_idbest_facet_idbest_point_idbest_entity_id你可以把它理解成这个 Episode 为什么会被召回 它是通过哪条最佳路径赢出来的 最佳证据落在 Facet、Point 还是 Entity 上6.3 它允许哪些路径当前实现允许的主路径有五种direct_episode直接命中 Episodefacet直接命中 Facet再回到 Episodepoint先命中 FacetPoint再回到 Facet再回到 Episodeentity先命中 Episode 直接关联的 Entity再回到 Episodefacet_entity先命中 Facet 关联的 Entity再回到 Facet再回到 Episode所以它本质上是在做命中点 - 沿图往上走 - 最后落到 Episode6.4 Facet cost 怎么算在算 Episode 之前系统会先算每个 Facet 的最小代价。公式可以写成FacetCost(fid) min( 直接命中 Facet, 命中其下 Point 后回到 Facet, 命中其下 Entity 后回到 Facet )如果展开一点就是FacetCost(fid) min( facet_direct(fid), point_direct(pid) edge_cost(fid-pid) hop_cost, entity_direct(en) edge_cost(fid-en) hop_cost )其中facet_direct(fid)best_by_id[fid]point_direct(pid)best_by_id[pid]entity_direct(en)best_by_id[en]edge_cost如果这条边的文本命中过 query就用命中分数否则用edge_miss_costhop_cost每多跳一次就加一点固定成本6.5 Episode bundle score 怎么算然后对每个 Episode再算EpisodeScore(ep) min( direct_episode, via_facet, via_entity )展开后大致是direct_episode episode_direct(ep) direct_episode_penalty via_facet FacetCost(fid) edge_cost(ep-fid) hop_cost via_entity entity_direct(en) edge_cost(ep-en) hop_cost这里有三个最重要的超参数edge_miss_cost 0.9hop_cost 0.05direct_episode_penalty 0.3最短理解是边没命中就比较贵多跳比少跳贵直接命中 Episode 会额外吃一个罚分6.6 为什么要给 direct Episode 一个 penalty这点很多人第一次看会觉得反直觉。原因是Episode.summary通常比较长也比较泛它很容易“看起来相关”但这种相关可能不够尖锐所以系统故意做了一个偏好如果 query 能非常精确地命中某个 Point、Facet 或 Entity就优先相信那条更具体的路径而不是宽泛的 Episode summary。这就是direct_episode_penalty的作用。6.7 为什么不是把所有命中平均而是只取最小路径bundle scoring 当前采用的是这个 Episode 的最好路径是什么而不是这个 Episode 所有路径的平均分是多少这样做的意思是只要一个 Episode 有一条非常强的证据链它就应该被召回。否则一个 Episode 下挂了很多不相关 Facet 时平均值会把真正重要的那条路径淹掉。6.8 一个完整例子假设图里有Episode E1 API 流式返回改造 └─ Facet F2 变更边界 └─ Point P1 鉴权中间件不能改 Episode E2 首页文案交付当前 query 的命中结果是P1 score 0.08 F2 score 0.12 E1 score 0.36 E2 score 0.62再假设路径成本如下edge_cost(F2-P1) 0.04 edge_cost(E1-F2) 0.05 hop_cost 0.05 direct_episode_penalty 0.3那么FacetCost(F2) min( 0.12, 0.08 0.04 0.05 ) 0.12 EpisodeScore(E1) min( 0.36 0.3, 0.12 0.05 0.05 ) min(0.66, 0.22) 0.22这意味着虽然Episode.summary自己也命中了但更强的解释路径其实是Point - Facet - Episode所以E1.best_path point而且它会排得很靠前。7. keyword bonus 到底怎么工作7.1 最短理解keyword bonus不是LLM 拆 query不是 BM25不是一条单独的 keyword search也不是直接在图数据库里做过滤它的真实位置是在向量召回结果出来之后对命中的候选再做一层纯规则 rerank。7.2 它发生在 bundle_scorer 之前顺序必须分清query 预处理 - 多集合向量召回 - keyword / exact bonus 调整 score - best_by_id / edge_hit_map - 图投影 - bundle scoring所以keyword bonus不属于bundle_scorer它只是给bundle_scorer提供更好的输入分数7.3 keyword 是怎么抽出来的系统先根据 query 判断是否开启 hybridmixed_langnumbershort_query然后再按原因提取keyword情况 Amixed_lang如果 query 同时含中文和英文就只抽英文部分。例如API 流式返回那个任务有哪些约束抽出来的keyword就是API情况 Bnumber如果 query 里有数字就优先抽数字和单位。例如40万预算那个方案后来怎么改了抽出来的 keyword 更接近40万情况 Cshort_query如果问题去掉疑问词后太短就直接保留核心 query。7.4 keyword bonus 的计算方式这里最容易误解的一点是keyword bonus 不是再用 keyword 检索一次真实顺序是1. 从原始 query 里抽出 keyword 2. 仍然用 vector_query 去向量库检索 3. 向量库先返回一批候选结果 4. 再检查这些候选文本里有没有 keyword 5. 命中 keyword 的候选把 score 减去 keyword_match_bonus也就是说向量库先召回候选 keyword bonus 只在候选内部改分数具体规则很硬把keyword归一化转小写去掉空格和常见标点把候选结果文本也做同样归一化做一次 substring 判断命中就把 score 减去keyword_match_bonus当前默认值keyword_match_bonus 0.15这个值的意思是如果候选文本包含 keyword就把它的 distance 减少 0.15所以如果一个候选原始分数是0.22命中 keyword 后可能变成0.07这就是为什么前面一直强调score 是 distance bonus 其实是把 distance 往下减最短理解就是命中关键字 - score 变小 score 越小 - 越相关7.5 一个具体例子queryAPI 流式返回那个任务有哪些约束预处理后keyword API hybrid_reason mixed_lang use_hybrid True然后系统用vector_query先查向量库。假设向量库返回了三个候选候选 A: text 鉴权中间件保持不变。 score 0.22 候选 B: text API 流式返回改造的变更边界。 score 0.24 候选 C: text 首页 hero 文案交付时间。 score 0.30接着才进入keyword bonus。候选 A 归一化后keyword_norm api text_norm 鉴权中间件保持不变不包含api所以不改分score 0.22候选 B 归一化后keyword_norm api text_norm api流式返回改造的变更边界包含api所以命中 keyword bonus。于是old_score 0.24 new_score 0.24 - 0.15 0.09候选 C 归一化后keyword_norm api text_norm 首页hero文案交付时间不包含api所以不改分score 0.30最后排序会更接近候选 B: 0.09 候选 A: 0.22 候选 C: 0.30之后这个更小的分数会进入best_by_id再影响后面的 bundle scoring。7.6 为什么它只是一层 bonus不是主检索逻辑原因很简单主检索仍然靠 embedding 多集合召回keyword bonus 只是在已经召回出来的候选上做排序修正它解决的问题不是“把完全没召回的结果捞回来”而是当 query 里有特别关键的英文词、数字、短词时让包含这些精确信号的候选更靠前。8. 图数据库、向量数据库、bundle_scorer、keyword bonus 的关系这四者的关系可以压成一张图用户 query - preprocess_query - vector_query - keyword - 向量数据库多集合召回 - node hits - edge label hits - keyword bonus / exact bonus - 调整 node hit score - best_by_id / edge_hit_map - 图数据库投影局部子图 - Episode / Facet / Point / Entity / edges - build_relationship_index - bundle_scorer - 为每个 Episode 计算最小路径代价 - top bundles - output assembly - 注入回答模型也就是说向量数据库负责“召回候选”keyword bonus 负责“修正候选分数”图数据库负责“还原结构关系”bundle_scorer 负责“把局部命中收敛成 Episode 排名”9. 常见误解9.1 误解一图数据库负责检索向量数据库只是存 embedding不对。更准确地说向量数据库负责第一轮 semantic recall图数据库负责后续结构归因和多跳连接两个都参与 retrieval只是职责不同。9.2 误解二bundle_scorer 是某种 LLM 推理器不对。bundle_scorer是纯规则路径计算。它没有 prompt也不调用 LLM。9.3 误解三keyword bonus 是另一套 keyword search不对。它不是独立召回器而是向量结果上的后置加权。9.4 误解四Episode 命中了就一定最强不对。当前实现刻意压低 direct Episode 的优先级鼓励更具体的Point / Facet / Entity路径获胜。9.5 误解五edge collection 是可有可无的不对。RelationType_relationship_name的作用不是决定最终返回什么而是给路径本身补语义成本。没有这一层系统只能按“节点像不像”走无法区分这两个节点虽然都像 query但它们之间的连接是否也和 query 相关10. 最后用一句话收束如果把 episodic retrieval 压成一句工程语言可以写成m-flow 先用向量数据库在多个语义入口上广撒网召回节点和边文本 再把这些命中放回图数据库恢复结构 最后用 bundle_scorer 计算“哪一个 Episode 拥有最低成本的证据链” 而 keyword bonus 只是这条链路前半段的一个规则型分数修正器。如果继续往下展开向量库回答什么像 图数据库回答它属于谁 bundle scorer 回答谁最能解释这些命中 keyword bonus 回答哪些命中应该再往前提一点

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价