跳转至

多向量检索架构(Multi-Vector Retrieval)

本页系统化 Eagle-RAG 的多向量检索架构:单查询如何扇出到多个 (collection, encoder) 嵌入空间、空间内如何做 hybrid dense+sparse、跨空间如何用 RRF 秩序融合。这是 证据聚合 的上游 —— 多向量是检索期的多空间扇出,证据聚合是检索后的证据收敛。

与既有文档的关系

本页是概念正本。逐函数实现细节见 检索路由引擎;架构决策见 ADR-004 多编码器 RRF 融合;插件如何注册编码器/集合见 插件架构


1. 概念定义

Multi-vector retrieval(多向量检索)指一次查询同时检索多个嵌入空间,每个空间有自己的向量维度、编码器与模态,结果跨空间融合。区别于「单编码器、单集合、单空间」的朴素 RAG。

Eagle-RAG 的多向量形态可以一句话概括:

单查询扇出到多个 (collection, encoder) 嵌入空间 → 空间内可选 hybrid dense+sparse → 跨空间用 RRF(倒数排名融合,rank-only) 融合,永不混用跨嵌入空间的原始分数

三个关键约束(来自 ADR-004):

  1. 不同 encoder 的 raw score 不可比(维度、分布、归一化都不同)→ 跨空间只能比排名,不比分数。
  2. Hybrid sparse 是空间内增强,对同一 collection 的 dense ANN 结果做词项重叠重排,不是跨空间操作。
  3. Core 默认路由不自动查专用 collection(G4)—— 只有域插件显式路由才会扇出到域专用空间。

2. 三个层次

2.1 空间层:多 collection

每个 plugin_namespace(= 一个 Milvus Database)内有一组集合,每个集合是一个独立的嵌入空间:

集合 维度 编码器 模态 归属
eagle_text 1536 Qwen text-embedding-v4 text Core 默认
eagle_visual 2048 Qwen3-VL-Embedding-2B visual Core 默认
eagle_text_biomed 768 PubMedBERT text biomed 域
eagle_text_medcpt 768 MedCPT text biomed 域
eagle_chemical 512 MolFormer text biomed 域
eagle_medical_radiology 1024 MedImageInsight visual biomed 域
eagle_medical_pathology 1024 UNI2 visual biomed 域

代码: 集合元数据经 CollectionProfile(dim, default_encoder, hybrid_enabled, extra_output_fields) 注册到 EncoderRegistryeagle_rag/plugins/encoder_registry.py L32-39)。Core 默认集合在 eagle_rag/plugins/core_defaults.py 注册(含 hybrid_enabled=True,L106)。

2.2 编码器层:EncoderRegistry

EncoderRegistry 是 encoder↔collection 维度契约的唯一真相源

# eagle_rag/plugins/encoder_registry.py
class EncoderRegistry:
    def register(name, encoder, *, dim, modality="text") -> None: ...
    def register_collection(collection, *, dim, default_encoder=None,
                            hybrid_enabled=False, extra_output_fields=()) -> None: ...
    def validate_plan(self, collection: str, encoder_name: str) -> None:
        # rerank 编码器跳过;否则 encoder.dim 必须等于 collection.dim
        ...

关键契约validate_plan(collection, encoder) 在写入前强制 encoder.dim == collection.dim(L111-118),维度不匹配直接 ValueError。这保证「不会把 2048 维向量写进 1536 维集合」这类静默错误。

查询时EncoderRegistry.collection_profile(collection) 返回该空间的 default_encoder / hybrid_enabled / extra_output_fields,供 RetrieverOrchestrator 决定用哪个编码器编码 query、是否触发 hybrid、读哪些 output 字段。

2.3 融合层:空间内 hybrid + 跨空间 RRF

flowchart LR
    Q["query"] --> ROUTE["QueryRouteClassifier.route()"]
    ROUTE --> P1["Plan 1<br/>(eagle_text, qwen-emb-v4)"]
    ROUTE --> P2["Plan 2<br/>(eagle_text_biomed, pubmedbert)"]
    ROUTE --> P3["Plan 3<br/>(eagle_visual, qwen3-vl)"]

    P1 --> ANN1["dense ANN"]
    P2 --> ANN2["dense ANN"]
    P3 --> ANN3["dense ANN"]

    ANN1 --> HY1["hybrid_fuse_dense_sparse<br/>(alpha·dense + (1-alpha)·sparse)"]
    ANN2 --> HY2["hybrid_fuse_dense_sparse"]
    ANN3 --> HY3["(visual 无 sparse)"]

    HY1 --> RRF["merge_rrf<br/>rank-only 融合"]
    HY2 --> RRF
    HY3 --> RRF
    RRF --> DEDUP["dedupe_cross_collection<br/>source_chunk_id / (doc_id,path)"]
    DEDUP --> SUPP["RRF_POST_MERGE hook<br/>候选注入"]
    SUPP --> RR["rerank_merged<br/>RERANK_MERGED / qwen3-rerank"]
    RR --> OUT["top_n 证据"]

空间内 hybrideagle_rag/retrievers/hybrid_text_retriever.py L73-104):

def hybrid_fuse_dense_sparse(dense_nodes, query, *, alpha=0.6,
                             extra_sparse_terms=None, rrf_k=60):
    # 1. 对 dense ANN 结果按词项重叠做 sparse 排序
    sparse_nodes = sparse_rank_nodes(dense_nodes, sparse_query, extra_terms=...)
    # 2. alpha 加权融合(alpha=1 纯 dense,alpha=0 纯 sparse)
    combined = alpha * dense_score + (1 - alpha) * sparse_score

激活条件(retriever_orchestrator.py L427-434, L519-522):router.hybrid_text_enabled collection 命中 router.hybrid_text_collections、或 CollectionProfile.hybrid_enabled、或为默认 text collection。visual 集合不做 sparse(视觉无词项)。

跨空间 RRFeagle_rag/router/rerank_fusion.py L37-62):merge_rrf1/(k+rank) 加权融合多 plan 结果,空结果集不贡献幻影排名(G8)。这是跨空间的唯一合法融合方式 —— raw score 跨 encoder 不可比。

跨空间去重rerank_fusion.py L65-93):dedupe_cross_collectionsource_chunk_id(document_id, path) 折叠重复逻辑 chunk,保留高排名者(G32),并记 rrf_dedupe 审计事件。


3. 查询路由数据结构

多向量扇出的「计划」是插件可插拔的:

# eagle_rag/plugins/routing.py
@dataclass(frozen=True)
class CollectionQueryPlan:
    collection: str
    encoder: str
    top_k: int = 5

@dataclass(frozen=True)
class QueryRouteDecision:
    plans: tuple[CollectionQueryPlan, ...]   # 一次查询可含多个 plan = 多空间扇出
    retrieval_hints: dict[str, Any] = ...

稠密/稀疏查询扩写:域插件可订阅 QUERY_DENSE_EXPAND hook,返回 ExpandedQuery(dense_query, sparse_terms, intent)routing.py L37-43)。

  • biomed 示例:用 UMLS 实体识别改写 dense query(如同义词扩展),同时发 sparse_terms(如药物名、MeSH 词)增强 lexical 召回。
  • RetrieverOrchestrator._retrieve_generic_milvus(L464-479)先调 hook 拿 expanded,再编码 dense_query、把 sparse_terms 透传给 hybrid_fuse_dense_sparse

4. 关键设计契约

契约 出处 含义
RRF 是跨空间唯一合法融合 ADR-004 G8 不同 encoder 的 raw score 不可比,跨空间只比排名
Hybrid sparse 是空间内增强 hybrid_text_retriever.py 同一 collection 的 dense ANN 结果上做词项重叠,非跨空间
Core 默认不查专用 collection ADR-004 G4 Core QueryRouteClassifier 永不自动扇出到域集合,仅 eagle_text(+eagle_visual)
validate_plan 守 dim 一致 encoder_registry.py L111-118 写入前强制 encoder.dim == collection.dim
单 plan 失败 best-effort 跳过 ADR-004 G14 某空间检索异常返回 [],不拖垮整查询

与单域部署的关系:跨行业靠多实例(每 plugin_namespace 一个 Milvus Database),同 DB 内单 query 可跨多 collection(ADR-002多租户)。多向量是「单域内多空间」,不是「跨域扇出」。


5. 与多模态融合的关系

多模态融合 是多向量架构在 modality 维度的特例:eagle_text(1536)与 eagle_visual(2048)是两个不同维度的嵌入空间,文本节点与视觉瓦片经各自编码器入各自集合,查询时双空间扇出、RRF 融合、VLM 同 prompt 消费。多向量架构把这个模式泛化到「任意多个域专用编码器空间」。


6. Roadmap(未实现)

以下均为未实现的设计缺口,非当前能力

  • per-collection / per-query 自适应 hybrid alpha:当前 router.hybrid_alpha 是全局值(hybrid_fuse_dense_sparsealpha),不同 collection / 不同 query 类型(实体召回 vs 语义泛化)的理想权重不同。
  • late-interaction 多向量(ColBERT 风格 token-level 向量):当前每 chunk 一个稠密向量,无 token 级 late interaction。
  • 跨空间 score 归一化对比可视化:当前 RRF 隐藏了各空间 raw score,调试时缺乏「各空间分数分布对比」工具。
  • 多向量 ablation 评测脚手架:缺乏「关掉某空间看 QA 指标变化」的系统性 ablation 入口(可经 suppress_collections intent 部分实现,但无统一脚手架)。

潜在扩展点:CollectionProfilehybrid_alpha 字段;ExpandedQuery.intent 携带 alpha 提示;新增 MULTI_VECTOR_ABLATION 诊断 hook。


7. 参考