链载Ai

标题: 别只顾着卷检索了!真正决定RAG上限的,是这四个“后处理”工程 [打印本页]

作者: 链载Ai    时间: 昨天 22:02
标题: 别只顾着卷检索了!真正决定RAG上限的,是这四个“后处理”工程
引言
在上一篇文章中告别“搜不到、搜不准”:用这套查询优化,让你的RAG检索召回率飙升,我们探讨了多种提升RAG系统检索阶段性能的策略,包括索引优化、查询转换、混合搜索及QA对生成。这些方法旨在从源头提高信息检索的召回率与准确性。


获得了初步的检索结果后,本篇文章将聚焦于后续的关键环节,即如何将这些信息转化为高质量、可靠的最终答案。内容将围绕以下几个核心主题展开:
接下来,我们将逐一深入分析。

一:检索结果后处理:提升上下文的信噪比
核心痛点:初步检索返回的Top-K文档,虽然大体相关,但依然包含噪音,且并非所有文档都同等重要。直接将它们全部喂给LLM,既浪费Token,也可能干扰模型的最终判断。

1.1 重排序:将最相关的推向前列
#示例:使用Flashrank进行重排序fromlangchain.retrievers.document_compressorsimportFlashrankRerankfromlangchain.retrieversimportContextualCompressionRetriever#1.定义一个重排序压缩器#top_n是重排序后返回多少个文档rerank_compressor=FlashrankRerank(model="miniReranker_arabic_v1",top_n=3)#2.创建一个ContextualCompressionRetriever,使用Flashrank作为压缩器rerank_retriever=ContextualCompressionRetriever(base_compressor=rerank_compressor,base_retriever=vectorstore.as_retriever(search_kwargs={"k":5})#先检索5个再重排)#3.使用print("\n---Reranking(Flashrank)示例---")query_rerank="LangChain的最新功能是什么?"retrieved_reranked_docs=rerank_retriever.invoke(query_rerank)print(f"对查询'{query_rerank}'的重排序检索结果({len(retrieved_reranked_docs)}个文档):")fori,docinenumerate(retrieved_reranked_docs):print(f"文档{i+1}(分数:{doc.metadata.get('relevance_score','N/A')}):\n{doc.page_content[:100]}...")print("-"*30)

1.2 上下文压缩:聚焦核心,降低成本
fromlangchain.retrieversimportContextualCompressionRetrieverfromlangchain.retrievers.document_compressorsimportLLMChainExtractorfromlangchain_openaiimportChatOpenAI#1.定义一个基础检索器(先多检索一些,再压缩)base_retriever_for_comp=vectorstore.as_retriever(search_kwargs={"k":5})#2.定义一个LLMChainExtractor(压缩器)compressor=LLMChainExtractor.from_llm(llm=llm)#3.创建ContextualCompressionRetrievercompression_retriever=ContextualCompressionRetriever(base_compressor=compressor,base_retriever=base_retriever_for_comp)#4.使用query_comp="LangChain的调试工具叫什么?它的主要作用是什么?"retrieved_compressed_docs=compression_retriever.invoke(query_comp)print(f"对查询'{query_comp}'的ContextualCompressionRetriever检索结果:")fori,docinenumerate(retrieved_compressed_docs)riginal_len=len(doc.metadata.get('original_content',doc.page_content))compressed_len=len(doc.page_content)print(f"文档{i+1}(原始长度:{original_len},压缩后长度:{compressed_len}):")print(doc.page_content)print("-"*30)

1.3 拐点法则:动态决定上下文数量
这是一个与重排序紧密配合的高级技巧。
fromtypingimportList,Tupleimportnumpyasnpfromlangchain_core.documentsimportDocumentdeffind_elbow_point(scores: np.ndarray) ->int: """  使用点到直线最大距离的纯几何方法。  返回的是拐点在原始列表中的索引。  """  n_points =len(scores) ifn_points <3:   returnn_points -1# 返回最后一个点的索引 # 创建点坐标 (x, y),x是索引,y是分数  points = np.column_stack((np.arange(n_points), scores)) # 获取第一个点和最后一个点  first_point = points[0]  last_point = points[-1] # 计算每个点到首末点连线的垂直距离 # 使用向量射影的方法  line_vec = last_point - first_point  line_vec_normalized = line_vec / np.linalg.norm(line_vec)
vec_from_first = points - first_point
# scalar_product 是每个点向量在直线方向上的投影长度 scalar_product = np.dot(vec_from_first, line_vec_normalized)
# vec_parallel 是投影向量 vec_parallel = np.outer(scalar_product, line_vec_normalized)
# vec_perpendicular 是垂直向量,它的模长就是距离 vec_perpendicular = vec_from_first - vec_parallel
dist_to_line = np.linalg.norm(vec_perpendicular, axis=1) # 找到距离最大的点的索引 elbow_index = np.argmax(dist_to_line) returnelbow_indexdeftruncate_with_elbow_method_final( reranked_docsist[Tuple[float, Document]]) ->List[Document]: ifnotreranked_docsorlen(reranked_docs) <3: print("文档数量不足3个,无法进行拐点检测,返回所有文档。") return[docfor_, docinreranked_docs] scores = np.array([scoreforscore, _inreranked_docs]) docs = [docfor_, docinreranked_docs]
# 调用我们验证过有效的拐点检测函数 elbow_index = find_elbow_point(scores)
# 我们需要包含拐点本身,所以截取到 elbow_index + 1 num_docs_to_keep = elbow_index +1 final_docs = docs[:num_docs_to_keep]
print(f"检测到分数拐点在第{elbow_index +1}位。截断后返回{len(final_docs)}个文档。") returnfinal_docsprint("\n--- 拐点检测示例 ---")# 假设 reranked_docs 是你的输入数据reranked_docs = [ (0.98,"文档1"), (0.95,"文档2"), (0.92,"文档3"), (0.75,"文档4"), (0.5,"文档5"), (0.48,"文档6")]final_documents = truncate_with_elbow_method_final(reranked_docs)print(final_documents)# 输出前三个文档

二:架构优化:用查询路由实现智能分发
核心痛点:真实场景,我们面对的问题多种多样,试图用“一套万金油”的RAG链路来解决所有问题,往往效率低下且效果不佳。


三:生成端控制:Prompt工程的最佳实践
核心痛点:即使我们提供了完美的上下文,一个模糊、无约束的Prompt也会让LLM“自由发挥”,导致回答偏离、甚至再次产生幻觉。

一个强大的RAG Prompt,至少应包含以下要素:
  1. 清晰的角色设定:“你是一名专业的[领域]知识库助手...”
  2. 严格的约束与底线:“请只根据提供的上下文回答。如果信息不足,请明确回答‘根据现有信息,我无法回答’。严禁使用你的内部知识或进行任何形式的编造。”
  3. 强制引用与溯源:“在你的回答结尾,必须以列表形式注明答案所参考的所有上下文文档来源...”
  4. 结构化输出要求: 要求LLM以JSON等固定格式输出,便于程序解析和后续处理。在LangChain中,使用.with_structured_output()是最可靠的方法。

四:系统性“幻觉”防范:构建AI的“事实护栏”
“幻觉”是RAG的天敌。防范它,绝不是单点技术,而是一个贯穿始终的系统工程。


本篇文章聚焦于检索之后的关键步骤。我们探讨了如何通过结果精炼、架构优化生成控制,将初步的检索结果,系统性地转化为高质量、可信赖的最终答案。掌握这些技巧,是实现RAG系统从“能用”到“可靠”的质变核心。






欢迎光临 链载Ai (https://www.lianzai.com/) Powered by Discuz! X3.5