Skip to content

七、检索

7.1 检索方式概览

Milvus支持多种检索方式:对向量字段支持精确检索(KNN)和近似近邻检索(ANN);对标量字段提供条件过滤,主要配合向量检索进行结果筛选。

向量检索的关键参数:

参数说明
查询向量 (data)待检索对象的向量表示,维度需与集合中向量字段一致
向量字段 (anns_field)指定参与检索的向量字段名称
TopK (limit)返回相似度最高的结果数量
metric_type相似度计算方式,如COSINE、L2或IP

7.2 稠密向量检索

python
def dense_vector_search_example(client, query: str, limit: int = 5):
    model = get_bge_m3_model()
    dense_vec, _ = encode_query(model, query)

    results = client.search(
        collection_name=COLLECTION_NAME,
        data=[dense_vec],
        anns_field="vector",
        limit=limit,
        search_params={"metric_type": "COSINE"},
        output_fields=["id", "text", "metadata"]
    )
    return results[0]

7.3 稀疏向量检索

python
def sparse_vector_search_example(client, query: str, limit: int = 5):
    model = get_bge_m3_model()
    _, sparse_vec = encode_query(model, query)

    results = client.search(
        collection_name=COLLECTION_NAME,
        data=[sparse_vec],
        anns_field="sparse_vector",
        limit=limit,
        search_params={"metric_type": "IP"},
        output_fields=["id", "text", "metadata"],
    )
    return results[0]

7.4 混合检索与重排序

混合检索将稠密向量(语义匹配)和稀疏向量(关键词匹配)结合,并通过重排序器(Reranker) 对两路召回结果进行融合排序。

重排序(Reranker) 是指在初步检索完成后,对候选结果进行二次排序优化。其目标不是扩大召回范围,而是在已有候选集内提升排序质量和结果相关性。

RRFRanker 的工作流程:

python
def hybrid_vector_search_example_rrf(client, query: str, limit: int = 5):
    from pymilvus import AnnSearchRequest, RRFRanker

    model = get_bge_m3_model()
    dense_vec, sparse_vec = encode_query(model, query)

    dense_req = AnnSearchRequest(
        data=[dense_vec], anns_field="vector",
        param={"metric_type": "COSINE"}, limit=limit,
    )
    sparse_req = AnnSearchRequest(
        data=[sparse_vec], anns_field="sparse_vector",
        param={"metric_type": "IP"}, limit=limit,
    )

    results = client.hybrid_search(
        collection_name=COLLECTION_NAME,
        reqs=[dense_req, sparse_req],
        ranker=RRFRanker(k=60),
        limit=limit,
        output_fields=["id", "text", "metadata"],
    )
    return results[0]

7.5 标量检索

标量检索不涉及向量相似度计算,仅基于标量字段条件进行筛选:

python
def scalar_query_examples(client, keyword: str = "大模型"):
    # 对text字段进行模糊检索
    like_res = client.query(
        collection_name=COLLECTION_NAME,
        filter=f'text like "%{keyword}%"',
        output_fields=["id", "text"],
        limit=5,
    )

    # 对metadata的JSON字段进行检索
    json_res = client.query(
        collection_name=COLLECTION_NAME,
        filter='metadata["source"] like "%sample%"',
        output_fields=["id", "metadata"],
        limit=5,
    )

7.6 完整案例

python
import os
from pymilvus import MilvusClient, AnnSearchRequest, RRFRanker
from FlagEmbedding import BGEM3FlagModel

# ==========================================
# 1. 全局配置与客户端初始化
# ==========================================
COLLECTION_NAME = "demo_collection"
# 确保这里和上一节插入数据时使用的模型路径一致
MODEL_PATH = r"D:\ai_models\huggingface_cache\bge-m3" 
MILVUS_URI = "http://localhost:19530"

print("-> 正在连接 Milvus 客户端...")
client = MilvusClient(uri=MILVUS_URI, token="")

print(f"-> 正在加载 BGE-M3 模型 (用于处理查询问题)...")
model = BGEM3FlagModel(MODEL_PATH)

# ==========================================
# 2. 辅助函数:将用户提问转换为向量
# ==========================================
def encode_query(query_text: str):
    """使用 BGE-M3 模型同时生成查询的稠密向量和稀疏向量"""
    vectors = model.encode(
        [query_text], 
        return_dense=True, 
        return_sparse=True
    )
    # 提取列表中的第一个(也是唯一一个)元素的向量
    dense_vec = vectors["dense_vecs"][0]
    sparse_vec = vectors["lexical_weights"][0]
    return dense_vec, sparse_vec

# ==========================================
# 3. 混合检索与重排序 (Hybrid Search + RRF)
# ==========================================
def hybrid_vector_search_example_rrf(query: str, limit: int = 3):
    print(f"\n[{' 混合检索测试 ':=^40}]")
    print(f"用户提问: {query}")
    
    # 1. 对查询语句进行向量化
    dense_vec, sparse_vec = encode_query(query)

    # 2. 构建稠密向量检索请求 (语义检索)
    # 注意:这里的 metric_type 必须与你创建索引时保持一致 (你之前代码里建的是 COSINE)
    dense_req = AnnSearchRequest(
        data=[dense_vec], 
        anns_field="vector",
        param={"metric_type": "COSINE"}, 
        limit=limit,
    )
    
    # 3. 构建稀疏向量检索请求 (关键词检索)
    # 稀疏向量的度量方式通常使用 IP (内积)
    sparse_req = AnnSearchRequest(
        data=[sparse_vec], 
        anns_field="sparse_vector",
        param={"metric_type": "IP"}, 
        limit=limit,
    )

    # 4. 执行混合检索,并使用 RRFRanker 重新排序
    results = client.hybrid_search(
        collection_name=COLLECTION_NAME,
        reqs=[dense_req, sparse_req],
        ranker=RRFRanker(k=60), # k 决定了排名权重的衰减速度,一般设为 60
        limit=limit,
        output_fields=["id", "text", "metadata"], # 告诉 Milvus 返回哪些原始字段
    )
    
    # 5. 打印结果
    print(f"\n--- 混合检索找到了 {len(results[0])} 条相关片段 ---")
    for i, hit in enumerate(results[0]):
        print(f"\nTop {i+1} (匹配分数/RRF Score: {hit['distance']:.4f})")
        print(f"ID: {hit['id']}")
        print(f"来源文件: {hit['entity'].get('metadata', {}).get('source', '未知')}")
        print(f"文本内容: {hit['entity'].get('text', '')}")


# ==========================================
# 4. 标量检索 (精准过滤)
# ==========================================
def scalar_query_examples(keyword: str):
    print(f"\n[{' 标量检索测试 ':=^40}]")
    print(f"正在全文检索包含关键词 '{keyword}' 的片段...")
    
    # 对 text 字段进行模糊匹配 (类似 SQL 的 LIKE)
    like_res = client.query(
        collection_name=COLLECTION_NAME,
        filter=f'text like "%{keyword}%"',
        output_fields=["id", "text", "metadata"],
        limit=3,
    )
    
    print(f"\n--- 标量检索找到了 {len(like_res)} 条结果 ---")
    for i, res in enumerate(like_res):
        print(f"\n结果 {i+1} (ID: {res['id']})")
        print(f"文本内容: {res['text']}")


# ==========================================
# 主运行入口
# ==========================================
if __name__ == "__main__":
    try:
        # 你可以根据你导入的 sample.docx 的实际内容,修改下面的提问词
        test_query = "什么是大语言模型?" 
        
        # 执行混合检索测试
        hybrid_vector_search_example_rrf(query=test_query, limit=3)
        
        # 执行标量检索测试
        test_keyword = "模型"
        scalar_query_examples(keyword=test_keyword)
        
    except Exception as e:
        print(f"\n 检索过程中发生错误: {e}")

检索到了相关文档片段,最后一步就是把它们交给大模型,生成最终答案。