news 2026/5/8 16:35:42

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AI记忆系统分析m-flow(二)

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 向量数据库最小是怎么用的

如果不讲任何厂商细节,向量数据库最小就是四步:

  1. 选一段要检索的文本
  2. 把它变成 embedding 向量
  3. 连同id / text / payload一起存进某个 collection
  4. 搜索时把 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 图数据库最小是怎么用的

如果不讲任何厂商细节,图数据库最小也是四步:

  1. 建节点
  2. 建边
  3. 给边起关系名并带上属性
  4. 查询邻居、triplet 或局部子图

在这个项目里,一个极简图大概像这样:

Episode(E1: API 流式返回改造) --has_facet--> Facet(F2: 变更边界) --has_point--> FacetPoint(P1: 鉴权中间件不能改) Episode(E1) --involves_entity--> Entity(EN1: API)

当向量库先命中P1F2后,图数据库就可以继续回答:

  • P1连着哪个Facet
  • 这个Facet连着哪个Episode
  • 这个Episode还连着哪些Entity

你可以把图库理解成一个特别擅长回答下面这类问题的系统:

给你一个点 把它周围怎么连的也找出来

图数据库擅长的是:

  • 父子归属
  • 多跳关系
  • 局部结构恢复
  • 路径解释

图数据库不擅长的是:

  • 直接拿一句自然语言做高质量语义相似检索
  • 靠“文本像不像”在整图上做第一轮粗召回

换句话说,图数据库很擅长回答:

这个点属于谁 它和谁相连 从它走几跳能回到哪个 Episode

0.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 search

2. 在这个项目里,图数据库到底存什么

2.1 图数据库存的是“结构化记忆图”

对 episodic memory 来说,图数据库里保存的不是孤立文本,而是节点和边。

最核心的节点有四类:

  • Episode
  • Facet
  • FacetPoint
  • Entity

最核心的边有几类:

  • Episode --has_facet--> Facet
  • Facet --has_point--> FacetPoint
  • Episode --involves_entity--> Entity
  • Facet --involves_entity--> Entity

可以先把它想成这样:

Episode ├─ Facet │ └─ FacetPoint ├─ Entity └─ Chunk / 其他证据边

2.2 图数据库关心的是“谁和谁连着”

图数据库最重要的职责不是文本相似,而是结构关系。

例如:

Episode: API 流式返回改造 ├─ Facet: 实施方案 ├─ Facet: 变更边界 │ └─ FacetPoint: 鉴权中间件不能改 ├─ Entity: API └─ Entity: /chat/completions

在图数据库视角里,关键不是这些节点各自长什么样,而是:

  • 变更边界属于哪个 Episode
  • 鉴权中间件不能改属于哪个 Facet
  • API同时挂在哪些 Episode 或 Facet 上

2.3 边在这个系统里也有语义

这点很关键。

m-flow 里的边不是完全“哑”的关系线。边上通常还带有:

  • relationship_name
  • edge_text

这意味着系统不只知道:

Episode 和 Facet 连着

还知道这条连接的关系文本是什么。

后面你会看到,这些边文本也会进入向量索引,参与检索和打分。

2.4 这个项目实际上怎么使用图数据库

如果你没用过图库,这里最重要的是分清:

  • 本项目不是把图数据库当“全文搜索引擎”来用
  • 而是把图数据库当“结构恢复器”来用

对 episodic retrieval 来说,图数据库最常做的是这几类事:

  1. 写入节点和边
    • 例如写入Episode / Facet / FacetPoint / Entity
    • 再写入has_facet / has_point / involves_entity
  2. 按 id 取节点、取边、取邻居
  3. relevant_ids投影一个局部子图
  4. 把这个局部子图物化成内存里的MemoryGraph

这意味着图库在这条链路里的使用方式不是:

拿一句 query -> 直接让图库在整库里做模糊语义匹配

而更像是:

向量库先告诉我哪些 id 值得看 -> 图库把这些 id 周围的结构拉出来 -> 程序在这块局部图上继续算

所以对这篇文章来说,你可以把图库理解成:

一个负责保存结构、恢复邻接关系、支持局部投影的后端

3. 在这个项目里,向量数据库到底存什么

3.1 向量数据库存的不是整张图,而是“可嵌入字段”

图数据库存的是节点和边。

向量数据库存的是这些节点或关系上的某些文本字段的 embedding 索引。

在这个项目里,collection 名字遵循:

{NodeType}_{field}

所以你会看到这些集合:

  • Episode_summary
  • Facet_search_text
  • Facet_anchor_text
  • FacetPoint_search_text
  • Entity_name
  • RelationType_relationship_name

3.2 每个 collection 实际存的是什么

可以直接按表理解:

Collection实际存的文本用途
Episode_summaryEpisode.summary从整件事角度粗召回
Facet_search_textFacet.search_text用短标题召回某个 Facet
Facet_anchor_textFacet.anchor_text用富语义文本召回某个 Facet
FacetPoint_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 bonus
  • score
    • 检索后会成为 node cost 或 edge cost 的输入

3.5 这个项目实际上怎么使用向量数据库

如果你没用过向量库,可以把它想成“多张专门做语义检索的表”。

这个项目实际用到的能力很克制,核心只有几件事:

  1. 建 collection
    • 例如Episode_summaryFacet_search_text
  2. 把某个字段写成向量记录
    • 一条记录至少要能回出id / text / payload
  3. query_textquery_vector做 search
  4. 返回VectorSearchHit
    • 里面至少有id / score / payload / raw_distance / collection_name
  5. 在必要时做 batch search

所以它的工作方式更像:

把多个“可检索字段”分别建索引 搜索时每个 collection 都查一下 把命中的 id 和 distance 带回来

而不是:

把整张图整个塞进一个大向量库 然后只靠向量库做最终排序

对这篇文章最重要的一点是:

  • 向量库负责返回候选命中
  • 但不会独立决定最终哪个 Episode 胜出

真正的最终归因,还要等:

  • best_by_id
  • edge_hit_map
  • 图投影
  • bundle_scorer

4. 这个项目里图数据库和向量数据库怎么配合

这条链路可以压成下面这五步:

1. 用向量库多集合召回 2. 把命中的 node id 放回图里 3. 在图里补齐相邻节点和边 4. 对每个 Episode 计算 bundle score 5. 取分数最小的 Episode bundles 输出

把它翻成更口语一点的话就是:

向量库先说:“这些点像” 图数据库再说:“这些点其实属于这些 Episode” bundle scorer 最后说:“哪个 Episode 最能解释这些命中”

5. 一条真实检索链是怎么跑的

这一节按真实顺序讲一次。

继续沿用前面的例子,假设用户问:

API 流式返回那个任务有哪些约束?

5.1 第一步:query 预处理

系统会先得到一个PreprocessedQuery,里面最重要的是:

  • original
  • vector_query
  • keyword
  • hybrid_reason
  • use_hybrid

其中:

  • vector_query
    • 是拿去做 embedding 搜索的 query
  • keyword
    • 是后面做keyword bonus用的规则关键词

例如这句 query 里同时有中文和英文API,所以会触发:

  • hybrid_reason = mixed_lang
  • keyword = API

这里没有 LLM。

5.2 第二步:多集合向量召回

然后系统会同时查多个集合:

  • Episode_summary
  • Facet_search_text
  • Facet_anchor_text
  • FacetPoint_search_text
  • Entity_name
  • Concept_name
  • RelationType_relationship_name

注意:

  • 这一步还没有图推理
  • 只是把可能相关的节点和边文本先广撒网捞出来

假设召回结果像这样:

FacetPoint_search_text: P1 = 鉴权中间件不能改 score=0.08 Facet_search_text: F2 = 变更边界 score=0.12 Facet_anchor_text: F2 = 鉴权中间件保持不变,只改响应层和测试 score=0.10 Entity_name: EN1 = API score=0.18 Episode_summary: E1 = API 流式返回改造 score=0.36 E2 = 首页文案交付 score=0.62

5.3 第三步:先在向量结果上打 bonus

这一步仍然没有图。

系统会对这些向量结果做两类规则调整:

  • keyword bonus
  • exact 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
  • 各自的分数

它还不知道这些命中之间怎么连。

所以接下来会去图数据库里做两阶段投影:

  1. 先按命中的 relevant ids 投一个局部子图
  2. 再把相邻节点扩一跳,拿到更完整的局部结构

这里还有一个很容易忽略的点:

  • 图数据库里的原始数据不会被bundle_scorer直接逐条扫描
  • 程序会先把这一小块相关结构投影成一个内存里的MemoryGraph
  • 后面的关系整理、路径计算、bundle scoring 都发生在这个局部投影上

这一层可以把它理解成:

先用向量库找到“哪些点像” 再用图数据库把这些点周围的关系网拉出来

5.7 第七步:构建 RelationshipIndex

子图拿回来后,程序不会直接在原始图对象上瞎搜。

它会先整理出几张关系表:

  • facets_by_episode
  • points_by_facet
  • entities_by_episode
  • entities_by_facet
  • 以及各种边的 lookup 表

这样后面的bundle_scorer就可以非常快地做路径计算。


6. bundle_scorer 到底在做什么

6.1 最短理解

bundle_scorer并不是在做“高级语义理解”。

它做的是一个很工程化的事情:

对每个 Episode,穷举几条允许的图路径,算出总代价,取最小值。

这个最小值,就是这个 Episode 的bundle score

6.2 EpisodeBundle 是什么

最终每个候选 Episode 会得到一个EpisodeBundle,里面最重要的是:

  • episode_id
  • score
  • best_path
  • best_support_id
  • best_facet_id
  • best_point_id
  • best_entity_id

你可以把它理解成:

这个 Episode 为什么会被召回 它是通过哪条最佳路径赢出来的 最佳证据落在 Facet、Point 还是 Entity 上

6.3 它允许哪些路径

当前实现允许的主路径有五种:

  • direct_episode
    • 直接命中 Episode
  • facet
    • 直接命中 Facet,再回到 Episode
  • point
    • 先命中 FacetPoint,再回到 Facet,再回到 Episode
  • entity
    • 先命中 Episode 直接关联的 Entity,再回到 Episode
  • facet_entity
    • 先命中 Facet 关联的 Entity,再回到 Facet,再回到 Episode

所以它本质上是在做:

命中点 -> 沿图往上走 -> 最后落到 Episode

6.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_cost
  • hop_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.9
  • hop_cost = 0.05
  • direct_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 判断是否开启 hybrid:

  • mixed_lang
  • number
  • short_query

然后再按原因提取keyword

情况 A:mixed_lang

如果 query 同时含中文和英文,就只抽英文部分。

例如:

API 流式返回那个任务有哪些约束?

抽出来的keyword就是:

API
情况 B:number

如果 query 里有数字,就优先抽数字和单位。

例如:

40万预算那个方案后来怎么改了?

抽出来的 keyword 更接近:

40万
情况 C:short_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 只在候选内部改分数

具体规则很硬:

  1. keyword归一化
    • 转小写
    • 去掉空格和常见标点
  2. 把候选结果文本也做同样归一化
  3. 做一次 substring 判断
  4. 命中就把 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 一个具体例子

query:

API 流式返回那个任务有哪些约束?

预处理后:

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 回答:哪些命中应该再往前提一点
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/8 16:35:38

Node.js后端服务如何通过Taotoken稳定调用大模型并管理API密钥

🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 Node.js后端服务如何通过Taotoken稳定调用大模型并管理API密钥 对于Node.js后端开发者而言,在服务中集成大模型能力正变…

作者头像 李华
网站建设 2026/5/8 16:35:26

使用Python快速编写第一个调用Taotoken多模型API的脚本示例

🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 使用Python快速编写第一个调用Taotoken多模型API的脚本示例 对于初次接触大模型API的开发者而言,如何快速上手并验证不…

作者头像 李华
网站建设 2026/5/8 16:33:59

MapReduce基础编程操作

MapReduce是Hadoop生态系统的分布式计算框架,采用分而治之的设计思想,将大规模数据集拆分成多个小数据块,在集群节点上并行处理,最后汇总结果。本次实验从三个维度深入掌握MapReduce:Shell命令操作、YARN Web界面监控和…

作者头像 李华