使用一个长上下文重排序器对检索到的文档进行进一步的处理和排序,优化输出顺序。
优化前的检索内容,顺序混乱:
优化后的检索内容,按顺序排列:
使用 LongContextReorder 对检索得到的文档进行重新排序。这一步的目的是在有较长上下文时,优化文档的顺序,使得后续生成回答或处理时能更好地利用整体信息,而不是单纯按照检索的相关性顺序。
示例代码如下:
from langchain_community.vectorstores import Chroma
from langchain_community.document_transformers import (
LongContextReorder,
)
from langchain_community.embeddings import HuggingFaceEmbeddings
# Load blog
import bs4
from langchain_community.document_loaders import WebBaseLoader
# 设置一个常见的浏览器 User-Agent 字符串
os.environ["USER_AGENT"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36"
loader = WebBaseLoader(
web_paths=("https://www.yixue.com/%E6%80%8E%E6%A0%B7%E7%9C%8B%E8%A1%80%E5%B8%B8%E8%A7%84%E5%8C%96%E9%AA%8C%E5%8D%95",),
bs_kwargs=dict(
parse_only=bs4.SoupStrainer(
class_=("mw-body-content")
)
),
)
blog_docs = loader.load()
# Split
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
chunk_size=300,
chunk_overlap=50)
# Make splits
splits = text_splitter.split_documents(blog_docs)
# Get embeddings.
embeddings = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
# Create a retriever
retriever = Chroma.from_documents(splits, embedding=embeddings).as_retriever(
search_kwargs={"k": 10}
)
question = "血红蛋白(Hb)测定人体正常范围是多少?"
# Get relevant documents ordered by relevance score
docs = retriever.invoke(query)
reordering = LongContextReorder()
reordered_docs = reordering.transform_documents(docs)
下面我来详细解释这段代码的作用和每个部分的含义,并用通俗的语言和例子说明。
1. 背景和总体思路
整体流程包括:
- 加载网页内容:从指定的 URL 下载博客页面。
- 文本拆分:将加载的长文档拆分成更小的片段,便于后续处理。
- 生成嵌入向量:利用预训练的语言模型将每个文本片段转换为数值向量,表示其语义信息。
- 构建向量数据库:把所有文本片段的嵌入向量存储在向量数据库(这里使用 Chroma)中,以便快速检索。
- 检索相关文档:根据用户的查询(例如“什么是任务分解”)从向量数据库中找出最相关的文本片段。
- 文档重排序:使用一个长上下文重排序器对检索到的文档进行进一步的处理和排序,优化输出顺序。
2. 代码详细解释
a. 导入所需的模块
from langchain_community.vectorstores import Chroma
from langchain_community.document_transformers import LongContextReorder
from langchain_community.embeddings import HuggingFaceEmbeddings
这些导入模块提供了:
- Chroma:一个向量数据库,用于存储和检索文本嵌入。
- LongContextReorder:用于重新排序文档,使得在长文本处理时能够更好地利用上下文。
- HuggingFaceEmbeddings:利用 HuggingFace 提供的预训练模型生成文本的嵌入向量。
b. 加载网页内容
import bs4
from langchain_community.document_loaders import WebBaseLoader
# 设置 User-Agent,模拟常见浏览器
os.environ["USER_AGENT"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36"
loader = WebBaseLoader(
web_paths=("https://www.yixue.com/%E6%80%8E%E6%A0%B7%E7%9C%8B%E8%A1%80%E5%B8%B8%E8%A7%84%E5%8C%96%E9%AA%8C%E5%8D%95",),
bs_kwargs=dict(
parse_only=bs4.SoupStrainer(
class_=("mw-body-content")
)
),
)
blog_docs = loader.load()
这里使用 WebBaseLoader
来加载一个博客页面,传入了目标 URL。使用 BeautifulSoup 的 SoupStrainer
只解析页面中类名为 "mw-body-content"
的部分(这样可以过滤掉无关内容,只保留主要文章内容)。设置 User-Agent 主要是为了让网站认为这是来自真实浏览器的请求,防止被屏蔽。
举例说明:假设你要加载一篇博客文章,但网页中有大量广告、侧边栏等噪音信息。利用 SoupStrainer
只选取文章主要内容,能保证后续处理的文本质量更高。
c. 文本拆分
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
chunk_size=300,
chunk_overlap=50)
splits = text_splitter.split_documents(blog_docs)
这里的作用是将加载到的长文档拆分成多个较小的文本块:
- chunk_size=300:每个块大约300个字符(或token)。
- chunk_overlap=50:相邻文本块之间有50个字符的重叠,确保上下文连贯,不会因为拆分而丢失关键信息。
举例说明:如果一篇文章有3000个字符,拆分后可能生成大约10个文本块,每个块之间有一部分内容重复,这样在检索时就能更好地捕捉到上下文信息。
d. 生成文本嵌入
embeddings = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
使用 HuggingFace 提供的预训练模型 “all-MiniLM-L6-v2” 将每个文本块转换为一个高维向量,这个向量代表了文本的语义信息,便于后续比较文本之间的相似度。
举例说明:假设有两个文本块分别描述“如何进行任务分解”和“任务分解的步骤”,经过嵌入后,这两个文本的向量在空间中距离较近,因为它们主题相似。
e. 构建向量数据库和检索器
retriever = Chroma.from_documents(splits, embedding=embeddings).as_retriever(
search_kwargs={"k": 10}
)
question = "血红蛋白(Hb)测定人体正常范围是多少?"
docs = retriever.invoke(query)
- Chroma.from_documents:将之前拆分的文本块和生成的嵌入存储到一个 Chroma 向量数据库中。
- as_retriever(search_kwargs={“k”: 10}):构造一个检索器,设置查询时返回最相关的10个文本块。
- retriever.invoke(query):根据输入的查询“什么是任务分解”检索出相关的文本块。
举例说明:当用户查询“血红蛋白(Hb)测定人体正常范围是多少”时,系统会自动搜索数据库中与该查询最匹配的文本块,并返回这10个最相关的内容。这样用户能快速得到与问题高度相关的答案片段。
f. 文档重排序
reordering = LongContextReorder()
reordered_docs = reordering.transform_documents(docs)
最后使用 LongContextReorder
对检索得到的文档进行重新排序。这一步的目的是在有较长上下文时,优化文档的顺序,使得后续生成回答或处理时能更好地利用整体信息,而不是单纯按照检索的相关性顺序。
举例说明:假如检索出来的10个文本块中有些虽然相关但顺序零散(可看前面给的输出效果对比图),通过重排序可以把那些逻辑连续、信息衔接紧密的片段放在一起,形成更连贯的答案。
总结
整段代码展示了如何从一个网页中加载内容,经过文本拆分、生成嵌入向量,再通过向量数据库进行语义检索,最后对检索结果进行重排序,最终实现一个高效的文档问答系统。
- 加载网页:提取文章核心内容。
- 拆分文本:避免单一长文本处理困难,保证上下文完整。
- 生成嵌入:将文本转换为能量化语义的向量。
- 向量检索:根据查询找出最相关的文本片段。
- 重排序:优化片段顺序,形成连贯的回答。
这种方法在知识问答、搜索引擎、文档摘要等应用场景中非常实用。通过将长文档拆分和向量化,可以快速准确地找到用户所需的信息。