LangChain 0.2检索器获取文档相似度分数方法及相关疑问
LangChain检索器相似度分数获取及相关疑问解答
一、FAISS检索器获取相似度分数的方案
你当前用retriever.invoke(query)只能拿到Document列表,无法直接获取分数。可以通过两种方式解决:
- 直接调用VectorStore底层方法
绕开Retriever封装,直接使用FAISS的similarity_search_with_score方法,它会返回(Document, 相似度分数)的元组列表:vectorstore = FAISS.from_documents(docs, embeddings_model) results_with_score = vectorstore.similarity_search_with_score(query, k=4) # 遍历结果 for doc, score in results_with_score: print(f"文档内容:{doc.page_content}\n分数:{score}\n") - 自定义Retriever临时添加分数(而非永久元数据)
官方方案将分数写入文档元数据确实不合理——分数是针对当前查询的临时结果,不该作为文档的永久属性。你可以自定义包装器,仅在本次检索时将分数附加到文档的临时属性(不修改元数据):from langchain_core.retrievers import BaseRetriever from langchain_core.callbacks import CallbackManagerForRetrieverRun from typing import List class FAISSScoreRetriever(BaseRetriever): vectorstore: FAISS k: int def _get_relevant_documents( self, query: str, *, run_manager: CallbackManagerForRetrieverRun ) -> List[Document]: docs_with_score = self.vectorstore.similarity_search_with_score(query, k=self.k) # 给每个Document添加临时的score属性,不修改元数据 for doc, score in docs_with_score: doc.score = score return [doc for doc, _ in docs_with_score] # 使用自定义检索器 semantic_retriever = FAISSScoreRetriever(vectorstore=vectorstore, k=4) results = semantic_retriever.invoke(query) for doc in results: print(f"文档内容:{doc.page_content}\n分数:{doc.score}\n")
二、官方方案将分数写入元数据的合理性说明
官方这么设计,主要是为了兼容LangChain的Document标准结构——很多下游组件(比如链、输出格式化)只识别Document对象的metadata字段。但这种方式确实存在弊端:分数是查询相关的临时值,写入元数据会污染文档原始信息,还可能在多次检索时覆盖旧分数。你选择临时存储分数的方案,更符合数据逻辑。
三、LangChain 0.2中invoke与similarity_search_with_score的区别
invoke:是BaseRetriever接口的标准统一方法,所有检索器(包括FAISS、BM25、Ensemble)都实现了该方法。返回值是List[Document],专注于提供标准化检索结果,适配LangChain生态(比如和LLM链无缝集成),可能包含检索器的额外逻辑(比如过滤、重排、阈值筛选)。similarity_search_with_score:是VectorStore类的底层方法,仅针对向量存储实现。直接返回List[(Document, float)]元组,给出原始相似度分数,没有额外封装,适合需要直接获取底层检索结果的场景。
简单说:invoke是上层标准化接口,similarity_search_with_score是底层向量检索的原始方法。
四、BM25Retriever获取相似度分数
BM25Retriever默认的invoke方法只返回Document列表,要获取分数可以使用它的get_relevant_documents_with_score方法(LangChain 0.2已支持):
from langchain.retrievers import BM25Retriever bm25_retriever = BM25Retriever.from_documents(docs, k=4) results_with_score = bm25_retriever.get_relevant_documents_with_score(query) for doc, score in results_with_score: print(f"文档内容:{doc.page_content}\nBM25分数:{score}\n")
如果需要通过invoke返回带分数的结果,同样可以自定义包装器,给Document添加临时的bm25_score属性。
五、EnsembleRetriever获取相似度分数
EnsembleRetriever默认invoke返回合并后的Document列表,会丢失原始分数。要获取分数,需要自定义处理逻辑:
- 分别获取每个子检索器的带分数结果;
- 合并结果时保留每个文档的来源检索器和对应分数;
- 自定义包装器返回带分数的Document或自定义结构。
示例代码:
from langchain.retrievers import BM25Retriever from langchain_community.vectorstores import FAISS # 初始化两个子检索器 bm25_retriever = BM25Retriever.from_documents(docs, k=4) faiss_retriever = FAISSScoreRetriever(vectorstore=FAISS.from_documents(docs, embeddings_model), k=4) # 自定义EnsembleRetriever的分数获取逻辑 class EnsembleScoreRetriever(BaseRetriever): faiss_retriever: FAISSScoreRetriever bm25_retriever: BM25Retriever def _get_relevant_documents( self, query: str, *, run_manager: CallbackManagerForRetrieverRun ) -> List[Document]: # 获取两个检索器的带分数结果 faiss_results = self.faiss_retriever.invoke(query) bm25_results_with_score = self.bm25_retriever.get_relevant_documents_with_score(query) # 给BM25结果添加临时属性 for doc, score in bm25_results_with_score: doc.bm25_score = score doc.faiss_score = None # 给FAISS结果添加标记 for doc in faiss_results: doc.faiss_score = doc.score doc.bm25_score = None # 合并并去重结果 combined_results = faiss_results + [doc for doc, _ in bm25_results_with_score] seen_content = set() unique_results = [] for doc in combined_results: if doc.page_content not in seen_content: seen_content.add(doc.page_content) unique_results.append(doc) return unique_results # 使用自定义组合检索器 ensemble_retriever = EnsembleScoreRetriever( faiss_retriever=faiss_retriever, bm25_retriever=bm25_retriever ) results = ensemble_retriever.invoke(query) for doc in results: print(f"文档内容:{doc.page_content}\nFAISS分数:{doc.faiss_score}\nBM25分数:{doc.bm25_score}\n")
内容的提问来源于stack exchange,提问作者Dev_Man
相关产品推荐
相关产品推荐

