在这里插入图片描述

Milvus 混合检索 实战:稠密+稀疏向量检索与 Rerank 重排全流程解析

前言

在企业级RAG知识库落地过程中,仅使用单一稠密向量检索或传统关键词检索,都会出现召回偏差问题:纯稠密向量擅长语义理解,但专业名词、专属术语召回失效;纯关键词检索能精准匹配字面词汇,却无法理解同义、引申语义。

一、为什么需要混合检索

1.1 单一路径检索存在的问题

  1. 仅稠密向量检索(Dense Search)
    优势:基于向量余弦相似度理解文本语义,支持同义词、转述、模糊语义匹配;
    缺陷:对专业术语、编号、专有名词、缩写识别弱,当用户输入精确关键词时,容易漏召回。
  2. 仅稀疏向量检索(Sparse Search,等价BM25)
    优势:基于词权重精确匹配字面关键词,专有名词召回稳定;
    缺陷:无语义能力,无法识别近义词,同义内容会被完全忽略。
  3. 单一检索通用痛点
    召回结果分数值域不统一、排序逻辑单一,无法平衡「语义相似度」和「关键词匹配度」,最终送入大模型的上下文质量较差,问答准确率下降。

1.2 稠密检索工作原理

稠密向量检索依靠嵌入模型(BGE-M3/BGE-Large)将整段文本压缩为固定长度浮点数组(本文1024维),通过余弦距离COSINE计算向量之间的空间相似度。
文本语义越接近,向量在高维空间距离越近,检索时返回空间距离最近的Top-K文本块,核心解决「语义相似匹配」。

1.3 稀疏检索工作原理

稀疏向量由BGE-M3输出词权重字典 {单词下标:权重值},本质是增强版TF-IDF/BM25,使用内积IP计算关键词重合度。
文本命中查询关键词越多、关键词权重越高,匹配分数越高,核心解决「字面关键词精确匹配」。

1.4 Hybrid Search 整体思想

两路并行召回,结果融合重排

  1. 同一用户查询文本,同时生成稠密查询向量、稀疏查询向量;
  2. 分别执行稠密语义检索、稀疏关键词检索,得到两份独立召回列表;
  3. 通过归一化统一两路分数值域,使用加权融合 / RRF倒数排名融合完成重排;
  4. 输出兼顾语义相似、关键词匹配的最优文本片段作为RAG上下文。

二、Milvus 混合检索整体架构

2.1 整体流程图

多路召回统一融合架构:

用户Query → BGE-M3向量化 → 稠密查询向量 + 稀疏查询向量
    ↓                    ↓
稠密向量检索(ANN HNSW)    稀疏向量检索(倒排索引)
    ↓                    ↓
稠密召回列表              稀疏召回列表
    ↘                    ↙
        分数归一化 + Rerank重排(加权 / RRF)
              ↓
        最终融合Top-K结果 → 送入LLM生成答案

拓展多路场景:可接入ES关键词检索、MongoDB标量过滤,全部通过RRF统一融合。

2.2 数据流转流程

  1. 离线阶段:本地文档加载 → 文本分块Chunk → BGE-M3批量生成稠密/稀疏双向量 → Milvus批量入库;
  2. 在线查询阶段:用户提问 → 双向量生成 → 两路并行检索 → 重排融合 → 返回高相关文本块。

2.3 Hybrid Search 执行流程

  1. 对用户查询执行编码,获取dense query vector、sparse query vector;
  2. 分别调用Milvus search 接口,执行稠密、稀疏两路召回,各自取Top-N(通常取10~20,扩大召回池);
  3. 对两路原始分数做值域归一化,消除COSINE、IP度量单位差异
  4. 执行重排算法计算全局综合分数,重新排序;
  5. 截取融合后Top-K片段输出给上层业务。

2.4 Recall 与 Rerank 的关系

  1. Recall(召回):粗筛选阶段,尽可能多召回潜在相关文本,追求高召回率,允许混入低相关内容;
    混合检索两路Recall就是为了扩大候选池,避免高价值文本被提前过滤。
  2. Rerank(重排):精细排序阶段,对召回池内所有文本重新打分排序,追求高精准率
    作用:平衡两路检索的打分逻辑,剔除无关文本,把最贴合用户问题的内容排在前列。
  3. 核心关系:Recall负责「不漏」Rerank负责「选优」,二者搭配是RAG高准确率的核心标准架构。

三、Milvus 双向量存储实现

Collection Schema

创建同时容纳稠密向量、稀疏向量、原文、元数据的集合结构,开启自增主键无需手动维护ID:

def build_mixed_schema():
    schema = MilvusClient.create_schema(auto_id=True, description="稠密稀疏混合知识库")
    schema.add_field(field_name="id", datatype=DataType.INT64, is_primary=True)
    # 稠密语义向量 1024维
    schema.add_field(field_name="vector", datatype=DataType.FLOAT_VECTOR, dim=1024)
    # 稀疏关键词权重向量
    schema.add_field(field_name="sparse_vector", datatype=DataType.SPARSE_FLOAT_VECTOR)
    # 存储文本块原文
    schema.add_field(field_name="text", datatype=DataType.VARCHAR, max_length=2000)
    # JSON元数据:文件路径、页码、文档名称,支持标量过滤
    schema.add_field(field_name="metadata", datatype=DataType.JSON)
    return schema

Dense Vector 字段说明

  • 字段名:vector
  • 类型:FLOAT_VECTOR,dim=1024,与BGE-M3输出维度严格对齐
  • 度量方式:COSINE余弦相似度,值域[-1,1],越接近1语义越相似
  • 索引:HNSW近似检索索引,平衡检索速度与精度

Sparse Vector 字段说明

  • 字段名:sparse_vector
  • 类型:SPARSE_FLOAT_VECTOR,存储{词索引:权重}字典
  • 度量方式:IP内积,值域[0,+∞),数值越大关键词匹配度越高
  • 索引:SPARSE_INVERTED_INDEX稀疏倒排索引,专为关键词检索优化

Index 索引配置

创建集合时同步构建两套向量索引,无需后续单独建索引:

def build_mixed_index():
    index_params = MilvusClient.prepare_index_params()
    # 稠密向量HNSW索引
    index_params.add_index(
        field_name="vector",
        index_type="HNSW",
        metric_type="COSINE",
        params={"M": 16, "efConstruction": 200}
    )
    # 稀疏向量倒排索引
    index_params.add_index(
        field_name="sparse_vector",
        index_type="SPARSE_INVERTED_INDEX",
        metric_type="IP",
        params={"drop_ratio_build": 0.2}
    )
    return index_params

四、文档加载与向量化

文档加载

基于LangChain加载Word/PDF/TXT等文档,示例以Word文档为例:

from langchain_community.document_loaders import UnstructuredWordDocumentLoader
def load_document(file_path: str):
    loader = UnstructuredWordDocumentLoader(file_path, mode="single")
    return loader.load()

Chunk 切分

使用递归字符分割器,控制单段文本长度、重叠字符,避免语义断裂:

from langchain_text_splitters import RecursiveCharacterTextSplitter
def split_chunk(docs):
    splitter = RecursiveCharacterTextSplitter(
        chunk_size=500,
        chunk_overlap=50,
        separators=["\n\n", "\n", "。", ","]
    )
    return splitter.split_documents(docs)

BGE-M3 编码

BGE-M3一站式输出稠密、稀疏两套向量,无需分开模型推理:

from FlagEmbedding import BGEM3FlagModel
model = BGEM3FlagModel("BAAI/bge-m3")
encode_res = model.encode(text_list, return_dense=True, return_sparse=True)
# 稠密向量列表
dense_vecs = encode_res["dense_vecs"]
# 稀疏词权重列表
sparse_vecs = encode_res["lexical_weights"]

批量写入

循环配对文本块、稠密向量、稀疏向量,组装匹配Schema的字典列表批量插入Milvus:

insert_list = []
for doc, dense, sparse in zip(chunks, dense_vecs, sparse_vecs):
    insert_list.append({
        "vector": dense,
        "sparse_vector": sparse,
        "text": doc.page_content,
        "metadata": doc.metadata
    })
client.insert(collection_name=coll_name, data=insert_list)
client.flush(coll_name) # 强制落盘持久化

五、Hybrid Search 两路召回

Dense Search 稠密检索函数

def dense_search(client, coll_name, query_dense, top_n=10):
    return client.search(
        collection_name=coll_name,
        data=[query_dense],
        anns_field="vector",
        limit=top_n,
        search_params={"metric_type": "COSINE"},
        output_fields=["text", "metadata"]
    )[0]

Sparse Search 稀疏检索函数

def sparse_search(client, coll_name, query_sparse, top_n=10):
    return client.search(
        collection_name=coll_name,
        data=[query_sparse],
        anns_field="sparse_vector",
        limit=top_n,
        search_params={"metric_type": "IP"},
        output_fields=["text", "metadata"]
    )[0]

Score 说明

  1. 稠密COSINE分数:范围[-1, 1],1代表完全同义,-1代表语义完全无关;
  2. 稀疏IP内积分数:范围[0, +∞),数值无上限,关键词命中越多分数越高;
  3. 核心冲突:两路分数值域、度量逻辑完全不同,无法直接相加比较,必须归一化处理。

查询流程图

用户Query文本
    ↓
BGE-M3编码 → query_dense / query_sparse
    ↓                ↓
dense_search        sparse_search
    ↓                ↓
dense_hits(id,cos分数,文本)  sparse_hits(id,ip分数,文本)
    ↘                ↙
        归一化统一分数区间
              ↓
          Rerank重排计算

六、Hybrid Search Rerank 重排(整章重写)

为什么需要 Rerank

  1. 两路检索分数值域不一致,原始分数不能直接融合;
  2. 单一检索排序逻辑片面,稠密只看语义、稀疏只看关键词;
  3. 合并候选池后需要统一标准打分,优先输出同时满足「语义相似+关键词匹配」的文本。

Score 为什么不能直接比较

  • 稠密COSINE最大1,稀疏IP可能达到几十,数值量级差距巨大;
  • 二者度量逻辑不同:一个衡量空间相似度,一个衡量词重合度,原始数值无对比意义;
  • 解决方案:将两路分数统一映射到 [0,1] 区间,完成归一化。

Rerank 整体执行流程

  1. 收集稠密、稀疏两路召回的全部文本ID,合并候选池;
  2. 归一化两路原始分数至0~1区间;
  3. 选择重排算法(加权融合 / RRF)计算每条文本综合得分;
  4. 按综合得分降序排序,截取Top-K作为最终检索结果。

Score Normalize 分数归一化

def normalize_score(hits, metric_type: str) -> dict[int, float]:
    if not hits:
        return {}
    raw_scores = [hit["distance"] for hit in hits]
    min_s, max_s = min(raw_scores), max(raw_scores)
    id_norm_map = {}
    for hit in hits:
        raw = hit["distance"]
        if metric_type == "COSINE":
            # cos [-1,1] → [0,1]
            norm = (raw + 1) / 2
        else:
            # IP [0,∞) 最小最大缩放至0~1
            norm = (raw - min_s) / (max_s - min_s) if max_s != min_s else 0.0
        id_norm_map[hit["id"]] = norm
    return id_norm_map

Weighted Rerank 加权融合重排

原理

人工分配稠密、稀疏权重,自由偏向语义或关键词:
总分数 = 归一化稠密分 × dense_weight + 归一化稀疏分 × sparse_weight
通用配置:语义优先0.7:0.3;专业文档关键词优先0.5:0.5。

def weighted_rerank(dense_hits, sparse_hits, dense_w=0.7, sparse_w=0.3, top_k=5):
    # 归一化两路分数
    dense_norm = normalize_score(dense_hits, "COSINE")
    sparse_norm = normalize_score(sparse_hits, "IP")
    # 缓存文本元数据
    id_entity = {}
    for hit in dense_hits:
        id_entity[hit["id"]] = hit["entity"]
    for hit in sparse_hits:
        if hit["id"] not in id_entity:
            id_entity[hit["id"]] = hit["entity"]
    # 合并所有文档ID
    all_ids = set(dense_norm.keys()) | set(sparse_norm.keys())
    total_score = {}
    for idx in all_ids:
        d_score = dense_norm.get(idx, 0.0)
        s_score = sparse_norm.get(idx, 0.0)
        total = d_score * dense_w + s_score * sparse_w
        total_score[idx] = round(total, 4)
    # 排序截取topK
    sorted_res = sorted(total_score.items(), key=lambda x: x[1], reverse=True)[:top_k]
    # 组装返回结果
    final = []
    for doc_id, score in sorted_res:
        final.append({
            "id": doc_id,
            "total_score": score,
            "text": id_entity[doc_id]["text"],
            "metadata": id_entity[doc_id]["metadata"]
        })
    return final

RRF 倒数排名融合重排

原理

无需手动设置权重,依靠检索名次自动计算综合分数,公式:
RRFscore(d)=∑i=1N1k+ranki(d)RRF_{score}(d) = \sum_{i=1}^N \frac{1}{k + rank_i(d)}RRFscore(d)=i=1Nk+ranki(d)1

  • k:平滑系数,行业标准固定60;
  • rank_i(d):文档d在第i路检索中的排名;
  • 文档在越多检索队列排名靠前,RRF分数越高。
def rrf_rerank(dense_hits, sparse_hits, smooth_k=60, top_k=5):
    # 构建id-排名映射
    dense_rank = {hit["id"]: i+1 for i, hit in enumerate(dense_hits)}
    sparse_rank = {hit["id"]: i+1 for i, hit in enumerate(sparse_hits)}
    # 缓存文本信息
    id_entity = {}
    for hit in dense_hits:
        id_entity[hit["id"]] = hit["entity"]
    for hit in sparse_hits:
        if hit["id"] not in id_entity:
            id_entity[hit["id"]] = hit["entity"]
    all_ids = set(dense_rank.keys()) | set(sparse_rank.keys())
    rrf_score = {}
    for idx in all_ids:
        score = 0.0
        if idx in dense_rank:
            score += 1 / (smooth_k + dense_rank[idx])
        if idx in sparse_rank:
            score += 1 / (smooth_k + sparse_rank[idx])
        rrf_score[idx] = round(score, 5)
    # 排序截取topK
    sorted_res = sorted(rrf_score.items(), key=lambda x: x[1], reverse=True)[:top_k]
    final = []
    for doc_id, score in sorted_res:
        final.append({
            "id": doc_id,
            "rrf_score": score,
            "text": id_entity[doc_id]["text"],
            "metadata": id_entity[doc_id]["metadata"]
        })
    return final

两种算法流程图

  1. 加权融合流程
    两路原始分数 → 归一化到0~1 → 自定义权重相乘求和 → 全局排序输出TopK
  2. RRF融合流程
    两路召回结果提取排名 → 套用1/(k+rank)公式累加 → 全局排序输出TopK

两种算法对比

重排算法 优点 缺点 适用业务场景
Weighted加权融合 权重可控,可手动偏向语义/关键词,调试灵活 需要反复调参,多数据源扩展繁琐 业务稳定、知识库类型固定、有调参经验
RRF倒数排名融合 零参数自动融合,天然支持多路数据源(向量库+ES+SQL),鲁棒性强 无法人工干预偏向语义或关键词 通用RAG、快速上线、多库混合召回场景

七、完整代码实战

完整整合代码(分层结构+详细注释+流程说明)

"""
Milvus稠密稀疏混合检索 + 加权/RRF双重排完整实战
环境依赖:
pip install pymilvus flagembedding langchain unstructured-documents torch
"""
from pymilvus import MilvusClient, DataType
from FlagEmbedding import BGEM3FlagModel
from langchain_community.document_loaders import UnstructuredWordDocumentLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from typing import List, Dict, Set

# ===================== 1. Milvus 客户端、Schema、索引工具 =====================
def get_milvus_client(uri: str = "http://localhost:19530") -> MilvusClient:
    """初始化Milvus本地客户端"""
    return MilvusClient(uri=uri, token="")

def build_mixed_schema():
    """构建双向量混合存储表结构"""
    schema = MilvusClient.create_schema(auto_id=True, description="稠密稀疏混合知识库")
    schema.add_field(field_name="id", datatype=DataType.INT64, is_primary=True)
    schema.add_field(field_name="vector", datatype=DataType.FLOAT_VECTOR, dim=1024)
    schema.add_field(field_name="sparse_vector", datatype=DataType.SPARSE_FLOAT_VECTOR)
    schema.add_field(field_name="text", datatype=DataType.VARCHAR, max_length=2000)
    schema.add_field(field_name="metadata", datatype=DataType.JSON)
    return schema

def build_mixed_index():
    """构建稠密HNSW、稀疏倒排索引参数"""
    index_params = MilvusClient.prepare_index_params()
    index_params.add_index(
        field_name="vector",
        index_type="HNSW",
        metric_type="COSINE",
        params={"M": 16, "efConstruction": 200}
    )
    index_params.add_index(
        field_name="sparse_vector",
        index_type="SPARSE_INVERTED_INDEX",
        metric_type="IP",
        params={"drop_ratio_build": 0.2}
    )
    return index_params

def create_collection(client: MilvusClient, coll_name: str):
    """重置并创建混合向量集合"""
    if client.has_collection(coll_name):
        client.drop_collection(coll_name)
    client.create_collection(
        collection_name=coll_name,
        schema=build_mixed_schema(),
        index_params=build_mixed_index()
    )
    print(f"【集合创建完成】{coll_name},双向量索引自动构建成功")

# ===================== 2. 文档加载、分块、批量入库 =====================
def load_and_split_doc(file_path: str):
    """加载Word文档并递归切分文本块"""
    loader = UnstructuredWordDocumentLoader(file_path, mode="single")
    raw_docs = loader.load()
    splitter = RecursiveCharacterTextSplitter(
        chunk_size=500,
        chunk_overlap=50,
        separators=["\n\n", "\n", "。", ","]
    )
    chunks = splitter.split_documents(raw_docs)
    print(f"【文档切分完成】共生成 {len(chunks)} 个文本块")
    return chunks

def batch_insert_mixed_vector(client: MilvusClient, coll_name: str, chunks, embed_model: BGEM3FlagModel):
    """批量生成稠密+稀疏向量并插入Milvus"""
    text_list = [doc.page_content for doc in chunks]
    # BGE-M3批量编码,同时输出两套向量
    encode_res = embed_model.encode(text_list, return_dense=True, return_sparse=True)
    dense_vecs = encode_res["dense_vecs"]
    sparse_vecs = encode_res["lexical_weights"]
    # 组装入库数据
    insert_data = []
    for doc, dense, sparse in zip(chunks, dense_vecs, sparse_vecs):
        insert_data.append({
            "vector": dense,
            "sparse_vector": sparse,
            "text": doc.page_content,
            "metadata": doc.metadata
        })
    # 批量插入并持久化
    insert_res = client.insert(collection_name=coll_name, data=insert_data)
    client.flush(coll_name)
    print(f"【批量入库成功】插入主键ID:{insert_res['ids'][:5]}...")
    return insert_res

# ===================== 3. 两路检索工具函数 =====================
def dense_search(client: MilvusClient, coll_name: str, query_dense, top_n: int = 10):
    """稠密语义检索,返回原始命中列表"""
    return client.search(
        collection_name=coll_name,
        data=[query_dense],
        anns_field="vector",
        limit=top_n,
        search_params={"metric_type": "COSINE"},
        output_fields=["text", "metadata"]
    )[0]

def sparse_search(client: MilvusClient, coll_name: str, query_sparse, top_n: int = 10):
    """稀疏关键词检索,返回原始命中列表"""
    return client.search(
        collection_name=coll_name,
        data=[query_sparse],
        anns_field="sparse_vector",
        limit=top_n,
        search_params={"metric_type": "IP"},
        output_fields=["text", "metadata"]
    )[0]

# ===================== 4. 重排核心:归一化、加权融合、RRF融合 =====================
def normalize_score(hits: List[dict], metric_type: str) -> Dict[int, float]:
    """两路分数归一化至[0,1]区间"""
    if not hits:
        return {}
    raw_scores = [hit["distance"] for hit in hits]
    min_s, max_s = min(raw_scores), max(raw_scores)
    id_norm_map = {}
    for hit in hits:
        raw = hit["distance"]
        if metric_type == "COSINE":
            norm = (raw + 1) / 2
        else:
            norm = (raw - min_s) / (max_s - min_s) if max_s != min_s else 0.0
        id_norm_map[hit["id"]] = norm
    return id_norm_map

def weighted_rerank(dense_hits, sparse_hits, dense_w=0.7, sparse_w=0.3, top_k=5):
    """加权融合重排,支持自定义语义/关键词权重"""
    dense_norm = normalize_score(dense_hits, "COSINE")
    sparse_norm = normalize_score(sparse_hits, "IP")
    # 缓存文本与元数据
    entity_cache = {}
    for hit in dense_hits:
        entity_cache[hit["id"]] = hit["entity"]
    for hit in sparse_hits:
        if hit["id"] not in entity_cache:
            entity_cache[hit["id"]] = hit["entity"]
    all_doc_ids = set(dense_norm.keys()) | set(sparse_norm.keys())
    score_map = {}
    for idx in all_doc_ids:
        d = dense_norm.get(idx, 0.0)
        s = sparse_norm.get(idx, 0.0)
        total = d * dense_w + s * sparse_w
        score_map[idx] = round(total, 4)
    # 降序排序截取topK
    sorted_list = sorted(score_map.items(), key=lambda x: x[1], reverse=True)[:top_k]
    result = []
    for doc_id, score in sorted_list:
        result.append({
            "id": doc_id,
            "total_weight_score": score,
            "text": entity_cache[doc_id]["text"],
            "metadata": entity_cache[doc_id]["metadata"]
        })
    return result

def rrf_rerank(dense_hits, sparse_hits, smooth_k=60, top_k=5):
    """RRF倒数排名融合重排,无权重自动平衡两路结果"""
    dense_rank_map = {hit["id"]: i+1 for i, hit in enumerate(dense_hits)}
    sparse_rank_map = {hit["id"]: i+1 for i, hit in enumerate(sparse_hits)}
    entity_cache = {}
    for hit in dense_hits:
        entity_cache[hit["id"]] = hit["entity"]
    for hit in sparse_hits:
        if hit["id"] not in entity_cache:
            entity_cache[hit["id"]] = hit["entity"]
    all_doc_ids = set(dense_rank_map.keys()) | set(sparse_rank_map.keys())
    rrf_score_map = {}
    for idx in all_doc_ids:
        s = 0.0
        if idx in dense_rank_map:
            s += 1 / (smooth_k + dense_rank_map[idx])
        if idx in sparse_rank_map:
            s += 1 / (smooth_k + sparse_rank_map[idx])
        rrf_score_map[idx] = round(s, 5)
    sorted_list = sorted(rrf_score_map.items(), key=lambda x: x[1], reverse=True)[:top_k]
    result = []
    for doc_id, score in sorted_list:
        result.append({
            "id": doc_id,
            "rrf_score": score,
            "text": entity_cache[doc_id]["text"],
            "metadata": entity_cache[doc_id]["metadata"]
        })
    return result

# ===================== 主执行流程 =====================
if __name__ == "__main__":
    # 1. 初始化全局资源
    COLL_NAME = "mixed_knowledge_base"
    milvus_client = get_milvus_client()
    print("加载BGE-M3嵌入模型中...")
    embed_model = BGEM3FlagModel("BAAI/bge-m3")

    # 2. 创建混合向量集合
    create_collection(milvus_client, COLL_NAME)

    # 3. 文档处理+批量入库(替换为你的文档路径)
    chunks = load_and_split_doc("assets/sample.docx")
    batch_insert_mixed_vector(milvus_client, COLL_NAME, chunks, embed_model)

    # 4. 用户查询,生成双向量
    user_query = "Milvus混合检索如何同时兼顾语义和关键词匹配?"
    query_encode = embed_model.encode([user_query], return_dense=True, return_sparse=True)
    q_dense = query_encode["dense_vecs"][0]
    q_sparse = query_encode["lexical_weights"][0]

    # 5. 两路并行召回,各取Top10扩大候选池
    dense_top10 = dense_search(milvus_client, COLL_NAME, q_dense, top_n=10)
    sparse_top10 = sparse_search(milvus_client, COLL_NAME, q_sparse, top_n=10)

    # 6. 方案1:加权融合重排结果
    print("\n========== 【加权融合重排结果 dense:0.7 sparse:0.3】 ==========")
    weighted_res = weighted_rerank(dense_top10, sparse_top10, dense_w=0.7, sparse_w=0.3, top_k=5)
    for idx, item in enumerate(weighted_res):
        print(f"{idx+1} 综合分数:{item['total_weight_score']}")
        print(f"文本片段:{item['text'][:150]}...\n")

    # 7. 方案2:RRF倒数排名融合重排结果
    print("\n========== 【RRF自动融合重排结果】 ==========")
    rrf_res = rrf_rerank(dense_top10, sparse_top10, top_k=5)
    for idx, item in enumerate(rrf_res):
        print(f"{idx+1} RRF分数:{item['rrf_score']}")
        print(f"文本片段:{item['text'][:150]}...\n")
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐