如何用 MMR 算法提升 LangChain 检索结果的多样性
2026/9/13 11:38:48 网站建设 项目流程

如何用 MMR 算法提升 LangChain 检索结果的多样性

【免费下载链接】llm-cookbook面向开发者的 LLM 入门教程,吴恩达大模型系列课程中文版项目地址: https://gitcode.com/GitHub_Trending/ll/llm-cookbook

在 llm-cookbook 项目(吴恩达大模型系列课程中文版)的 LangChain RAG 流程中,检索是最容易出边缘问题的环节之一:当向量数据库中存在重复或语义接近的文档块时,LangChain 的相似性搜索会把它们一起返回,导致 k 个结果名额被高度重复的内容占掉。本文按课程「第五章 检索(Retrieval)」的实战代码演示:先用相似性搜索复现冗余问题,再用 MMR(Maximum marginal relevance,最大边际相关性)的max_marginal_relevance_search方法得到兼顾相关性与多样性的结果,并验证检索器层面设置search_type="mmr"的效果。

一、准备条件

按 检索章节 和 环境配置 的要求,运行示例前需要:

  • 在项目目录下创建.env文件,内容为OPENAI_API_KEY = "sk-..."(示例使用 OpenAI 嵌入,需要有效的 API key);
  • 安装lark包(本章需要):
!pip install -Uq lark
  • 读取环境变量并初始化嵌入函数:
import os import openai from dotenv import load_dotenv, find_dotenv _ = load_dotenv(find_dotenv()) # read local .env file openai.api_key = os.environ['OPENAI_API_KEY']

课程示例基于 LangChain 的 Chroma 向量数据库与OpenAIEmbeddings,导入方式与前面章节一致。如果你没有在前一章节(向量数据库与词向量)构建过课程的大型向量库,可以先用下面的小型内存数据库走通全部流程,不需要任何本地文件。

二、复现问题:相似性搜索返回重复内容

MMR 要解决的核心问题是检索冗余。课程用一个三句话的小型知识库来演示:前两句含义非常接近(都在描述毒鹅膏菌及其大型子实体),第三句讲的是它的毒性。

from langchain.vectorstores import Chroma from langchain.embeddings.openai import OpenAIEmbeddings embedding = OpenAIEmbeddings() texts_chinese = [ """毒鹅膏菌(Amanita phalloides)具有大型且引人注目的地上(epigeous)子实体(basidiocarp)""", """一种具有大型子实体的蘑菇是毒鹅膏菌(Amanita phalloides)。某些品种全白。""", """A. phalloides,又名死亡帽,是已知所有蘑菇中最有毒的一种。""", ] smalldb_chinese = Chroma.from_texts(texts_chinese, embedding=embedding) question_chinese = "告诉我关于具有大型子实体的全白色蘑菇的信息"

对这个提问执行相似性搜索,k=2只返回两个最相关的文档:

smalldb_chinese.similarity_search(question_chinese, k=2)

示例结果:

[Document(page_content='一种具有大型子实体的蘑菇是毒鹅膏菌(Amanita phalloides)。某些品种全白。', metadata={}), Document(page_content='毒鹅膏菌(Amanita phalloides)具有大型且引人注目的地上(epigeous)子实体(basidiocarp)', metadata={})]

返回的正是第一句和第二句——两条含义几乎相同的文本,而真正不同的第三句(毒性信息)没有进入结果集。这就是相似性搜索失败的场景:它按语义相似度取 top-k,但不强制结果之间的多样性。

三、运行 MMR 检索并验证结果

对同一个问题和同一个数据库,改用max_marginal_relevance_search运行 MMR 算法:

smalldb_chinese.max_marginal_relevance_search(question_chinese, k=2, fetch_k=3)

示例结果:

[Document(page_content='一种具有大型子实体的蘑菇是毒鹅膏菌(Amanita phalloides)。某些品种全白。', metadata={}), Document(page_content='A. phalloides,又名死亡帽,是已知所有蘑菇中最有毒的一种。', metadata={})]

两个参数按 检索章节 的说明使用:

  • k=2:最终返回的文档数量;
  • fetch_k=3:最初从数据库中取出的候选文档数量(本例取出全部 3 句),MMR 再从中筛选出k个最不同的文档。

对比两条路径可以看到:相似性搜索返回了第一、二句(含义重复),MMR 返回了第一句和第三句,把信息量不同的毒性描述带了进来。课程指出,尽管第三句与问题的直接相关性稍弱,但这样的结果更合理——重复的语义只保留一句,空出来的名额给不同语义的文档。

MMR 的基本思想是同时考量查询与文档的相关度文档之间的相似度:计算每个候选文档与查询的相关度,并减去与已选入结果集文档的相似度,这样更不相似的文档得分更高,从而在相关性和多样性之间取得平衡。

四、在课程大型向量库上验证

以上小型例子之外,检索章节还在前面课程构建的 Matplotlib 讲义向量库上验证了 MMR。该库的持久化路径是docs/chroma/matplotlib/(相对课程目录),构建过程见 向量数据库与词向量章节——加载 3 份讲义 PDF(其中《第一回》被故意加载两次以制造重复数据)、分割后用Chroma.from_documents建库并persist()。注意:仓库中 docs/chroma/matplotlib/ 已附带课程建好的库文件,加载前先确认persist_directory相对你的运行目录解析正确。

persist_directory_chinese = 'docs/chroma/matplotlib/' vectordb_chinese = Chroma( persist_directory=persist_directory_chinese, embedding_function=embedding ) print(vectordb_chinese._collection.count())

课程中该库的文档块数量为 27,运行后打印这个数值可以确认向量库加载成功。

先用相似性搜索复现重复块问题:

question_chinese = "Matplotlib是什么?" docs_ss_chinese = vectordb_chinese.similarity_search(question_chinese, k=3) print("docs[0]: ") print(docs_ss_chinese[0].page_content[:100]) print() print("docs[1]: ") print(docs_ss_chinese[1].page_content[:100])

示例输出(截取前 100 字符):

docs[0]: 第⼀回:Matplotlib 初相识 ⼀、认识matplotlib Matplotlib 是⼀个 Python 2D 绘图库,能够以多种硬拷⻉格式和跨平台的交互式环境⽣成出版物质量的图形,⽤来绘制各种 docs[1]: 第⼀回:Matplotlib 初相识 ⼀、认识matplotlib Matplotlib 是⼀个 Python 2D 绘图库,能够以多种硬拷⻉格式和跨平台的交互式环境⽣成出版物质量的图形,⽤来绘制各种

docs[0]docs[1]前 100 个字符完全相同——相似性搜索在索引存在重复文档时返回了重复块。换成 MMR 再查一次:

docs_mmr_chinese = vectordb_chinese.max_marginal_relevance_search(question_chinese, k=3)

验证方式与上面一致,逐条打印page_content

print(docs_mmr_chinese[0].page_content[:100]) print() print(docs_mmr_chinese[1].page_content[:100])

示例输出:

第⼀回:Matplotlib 初相识 ⼀、认识matplotlib Matplotlib 是⼀个 Python 2D 绘图库,能够以多种硬拷⻉格式和跨平台的交互式环境⽣成出版物质量的图形,⽤来绘制各种 By Datawhale 数据可视化开源⼩组 © Copyright © Copyright 2021.y轴分为左右两个,因此 tick1 对应左侧的轴; tick2 对应右侧的轴。 x轴分为上下两个

判断标准很直接:MMR 的第一个结果与相似性搜索的最高分文档相同(因为它仍然最相似),但第二个结果是内容不同的文档块,重复被过滤掉了。只要看到k个结果之间不再两两相同,就符合课程演示的预期。

五、把 MMR 设为检索器的搜索类型

如果检索结果还要经过压缩等后续处理,可以在从向量数据库创建检索器时直接把搜索类型设为 MMR,而不必逐次调用max_marginal_relevance_search。课程「结合各种技术」一节的写法是把 MMR 检索器作为ContextualCompressionRetriever的底层检索器:

from langchain.llms import OpenAI from langchain.retrievers import ContextualCompressionRetriever from langchain.retrievers.document_compressors import LLMChainExtractor llm = OpenAI(temperature=0) compressor = LLMChainExtractor.from_llm(llm) # 压缩器 compression_retriever_chinese = ContextualCompressionRetriever( base_compressor=compressor, base_retriever=vectordb_chinese.as_retriever(search_type="mmr") ) question_chinese = "Matplotlib是什么?" compressed_docs_chinese = compression_retriever_chinese.get_relevant_documents(question_chinese)

示例输出(文档节选):

Document 1: Matplotlib 是⼀个 Python 2D 绘图库,能够以多种硬拷⻉格式和跨平台的交互式环境⽣成出版物质量的图形,⽤来绘制各种静态,动态,交互式的图表。

对比 1.5 节未开启 MMR 的压缩结果(返回了两条内容相同的文档),这里返回的结果集中不再包含重复信息。也就是说,as_retriever(search_type="mmr")和直接调用max_marginal_relevance_search是同一能力的两种使用位置:前者把 MMR 固化进检索器管道,后者在单次查询时调用。

六、使用时的判断与限制

  • MMR 用部分「与问题直接相关度稍弱」换取结果多样性,这是课程示例中明确展示的行为(第三句毒性描述的相关性不如前两句,但仍被保留),选择kfetch_k时按你期望的多样性程度调整即可,课程示例为k=2, fetch_k=3
  • 课程同时指出压缩检索的底层仍是语义搜索,单独使用压缩不能消除重复,需要像上文一样显式启用 MMR。
  • MMR 只解决「结果冗余/多样性」这一种失败场景。检索章节还覆盖了另外的失败场景——问某一讲的内容却返回其他讲的结果,对应的是元数据过滤(filter)和SelfQueryRetriever的解法,属于独立任务,可按 检索章节 的 1.3、1.4 节继续。

完整可运行的示例代码在 5.检索 retrieval.ipynb,与本文步骤一一对应。

【免费下载链接】llm-cookbook面向开发者的 LLM 入门教程,吴恩达大模型系列课程中文版项目地址: https://gitcode.com/GitHub_Trending/ll/llm-cookbook

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询