diff --git a/backend/app/rag/README.md b/backend/app/rag/README.md index 2113a15..26423d7 100644 --- a/backend/app/rag/README.md +++ b/backend/app/rag/README.md @@ -103,11 +103,19 @@ retriever = create_base_retriever( 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)。 - **实现指南**: 使用 `app/rag/retriever.py` 中的 `create_hybrid_retriever` 函数,配置 `dense_k=10` 和 `sparse_k=10`,总召回 20 条结果。 diff --git a/backend/app/rag/retriever.py b/backend/app/rag/retriever.py index 14d1e39..3c3272b 100644 --- a/backend/app/rag/retriever.py +++ b/backend/app/rag/retriever.py @@ -5,6 +5,8 @@ Qdrant 向量检索器模块 核心原理: - 直接使用统一的 get_embedding_service(),已内置降级机制 +- 使用 QdrantVectorStore 的 native hybrid search(如果 Qdrant 集合已配置) +- 如果没有配置稀疏向量,优雅降级到纯稠密检索 使用示例: >>> from app.rag.retriever import create_base_retriever @@ -12,9 +14,10 @@ Qdrant 向量检索器模块 >>> docs = retriever.invoke("什么是 RAG?") """ -from typing import Dict, Any +from typing import Dict, Any, Optional from qdrant_client import QdrantClient from qdrant_client.http.exceptions import UnexpectedResponse +from qdrant_client.http.models import SparseVectorParams from langchain_qdrant import QdrantVectorStore from langchain_core.embeddings import Embeddings from langchain_core.retrievers import BaseRetriever @@ -112,18 +115,90 @@ def create_hybrid_retriever( search_kwargs = { "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, search_kwargs=search_kwargs, client=client, 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(