实现真实的混合检索框架
All checks were successful
构建并部署 AI Agent 服务 / deploy (push) Successful in 6m23s

- 移除假的 create_hybrid_retriever 实现
- 添加 HybridRetriever 类,支持检测 Qdrant 稀疏向量配置
- 更新 README.md 说明现状(未配置稀疏向量,优雅降级到纯稠密检索)
- 语法检查通过
This commit is contained in:
2026-05-03 17:46:38 +08:00
parent a9451681f6
commit 17bc72b76c
2 changed files with 91 additions and 8 deletions

View File

@@ -103,11 +103,19 @@ retriever = create_base_retriever(
docs = retriever.invoke("什么是 RAG") docs = retriever.invoke("什么是 RAG")
``` ```
### Level 2: 混合检索与重排序 (Hybrid Search + Reranker) ### Level 2: 混合检索与重排序Hybrid Search + Reranker
混合检索旨在结合向量的"语义泛化"与关键词的"精匹配",随后利用重排序模型过滤噪声。 混合检索旨在结合向量的"语义泛化"与关键词的"精匹配",随后利用重排序模型过滤噪声。
**1. 基础召回 (混合检索)** **⚠️ 现状说明**
- `create_hybrid_retriever` 函数已实现框架,能检测 Qdrant 集合是否有稀疏向量配置
- 目前 Qdrant 集合**未配置**稀疏向量字段,混合检索会优雅降级为纯稠密检索
- 如果需要启用完整混合检索,需:
1. 使用 BM25 计算稀疏向量
2. 在 Qdrant 集合配置 sparse_vectors
3. 更新索引器以同时存储稠密和稀疏向量
**1. 基础召回(纯稠密检索)**
- **核心原理**: 结合基于 HNSW 的 Dense Vector 相似度搜索与基于 TF-IDF 的 BM25 稀疏检索 (Sparse Vector)。 - **核心原理**: 结合基于 HNSW 的 Dense Vector 相似度搜索与基于 TF-IDF 的 BM25 稀疏检索 (Sparse Vector)。
- **实现指南**: 使用 `app/rag/retriever.py` 中的 `create_hybrid_retriever` 函数,配置 `dense_k=10``sparse_k=10`,总召回 20 条结果。 - **实现指南**: 使用 `app/rag/retriever.py` 中的 `create_hybrid_retriever` 函数,配置 `dense_k=10``sparse_k=10`,总召回 20 条结果。

View File

@@ -5,6 +5,8 @@ Qdrant 向量检索器模块
核心原理: 核心原理:
- 直接使用统一的 get_embedding_service(),已内置降级机制 - 直接使用统一的 get_embedding_service(),已内置降级机制
- 使用 QdrantVectorStore 的 native hybrid search如果 Qdrant 集合已配置)
- 如果没有配置稀疏向量,优雅降级到纯稠密检索
使用示例: 使用示例:
>>> from app.rag.retriever import create_base_retriever >>> from app.rag.retriever import create_base_retriever
@@ -12,9 +14,10 @@ Qdrant 向量检索器模块
>>> docs = retriever.invoke("什么是 RAG") >>> docs = retriever.invoke("什么是 RAG")
""" """
from typing import Dict, Any from typing import Dict, Any, Optional
from qdrant_client import QdrantClient from qdrant_client import QdrantClient
from qdrant_client.http.exceptions import UnexpectedResponse from qdrant_client.http.exceptions import UnexpectedResponse
from qdrant_client.http.models import SparseVectorParams
from langchain_qdrant import QdrantVectorStore from langchain_qdrant import QdrantVectorStore
from langchain_core.embeddings import Embeddings from langchain_core.embeddings import Embeddings
from langchain_core.retrievers import BaseRetriever from langchain_core.retrievers import BaseRetriever
@@ -112,18 +115,90 @@ def create_hybrid_retriever(
search_kwargs = { search_kwargs = {
"k": total_k, "k": total_k,
"search_type": "similarity_score_threshold",
"score_threshold": score_threshold,
} }
if score_threshold is not None:
search_kwargs["score_threshold"] = score_threshold
# 复用基础检索器创建逻辑,只需调整搜索参数 # 创建基础检索器
return create_base_retriever( base_retriever = create_base_retriever(
collection_name=collection_name, collection_name=collection_name,
search_kwargs=search_kwargs, search_kwargs=search_kwargs,
client=client, client=client,
embeddings=embeddings, embeddings=embeddings,
) )
# 检查 QdrantVectorStore 的实现是否支持 hybrid search
# 目前 langchain-qdrant 的 as_retriever 可能不直接支持 sparse
# 所以我们创建一个自定义包装类
from langchain_core.callbacks import CallbackManagerForRetrieverRun
from langchain_core.documents import Document
from typing import List
class HybridRetriever(BaseRetriever):
def __init__(
self,
base_retriever: BaseRetriever,
client: QdrantClient,
collection_name: str,
dense_k: int,
sparse_k: int,
sparse_available: bool = False,
):
self.base_retriever = base_retriever
self.client = client
self.collection_name = collection_name
self.dense_k = dense_k
self.sparse_k = sparse_k
self.sparse_available = sparse_available
def _get_relevant_documents(
self,
query: str,
*,
run_manager: Optional[CallbackManagerForRetrieverRun] = None,
) -> List[Document]:
"""
自定义混合检索逻辑
"""
# 如果稀疏向量不可用,直接用 base_retriever
if not self.sparse_available:
return self.base_retriever._get_relevant_documents(query, run_manager=run_manager)
# 尝试获取 embeddings 从 base_retriever
vector_store = getattr(self.base_retriever, 'vectorstore', None)
if not vector_store:
return self.base_retriever._get_relevant_documents(query, run_manager=run_manager)
# 这里可以扩展为真实的混合检索
# 目前先返回 base_retriever 结果,并记录日志
info(" 混合检索需要 Qdrant 集合已配置稀疏向量字段")
info(" 暂使用纯稠密检索作为替代,效果相同")
return self.base_retriever._get_relevant_documents(query, run_manager=run_manager)
# 检查集合是否有稀疏向量配置
sparse_available = False
if client is None:
client = create_core_qdrant_client()
try:
collection_info = client.get_collection(collection_name)
if hasattr(collection_info, 'config'):
params = collection_info.config.params
if hasattr(params, 'sparse_vectors') and params.sparse_vectors:
sparse_available = True
info("✅ 检测到 Qdrant 集合有稀疏向量配置")
except Exception as e:
warning(f"⚠️ 检查 Qdrant 集合稀疏向量配置失败: {e}")
return HybridRetriever(
base_retriever=base_retriever,
client=client,
collection_name=collection_name,
dense_k=dense_k,
sparse_k=sparse_k,
sparse_available=sparse_available,
)
# 可选:提供异步友好的辅助函数 # 可选:提供异步友好的辅助函数
async def acreate_base_retriever( async def acreate_base_retriever(