Skip to main content
本教程将帮助您熟悉 LangChain 的文档加载器嵌入模型向量存储 抽象层。这些抽象层旨在支持从(向量)数据库及其他来源检索数据,以便与大语言模型(LLM)工作流集成。对于需要在模型推理过程中获取待分析数据的应用(例如检索增强生成 RAG),它们至关重要。 在本教程中,我们将基于一份 PDF 文档构建一个搜索引擎,用于检索与输入查询相似的 PDF 段落。此外,本指南还将在该搜索引擎基础上实现一个最小化的 RAG 应用。

核心概念

本指南聚焦于文本数据的检索,涵盖以下核心概念:
  • 文档与文档加载器;
  • 文本分割器;
  • 嵌入模型;
  • 向量存储与检索器。

环境设置

安装依赖

本教程需要安装 langchain-communitypypdf 包:
pip install langchain-community pypdf
更多详情请参阅我们的 安装指南

LangSmith

使用 LangChain 构建的许多应用会包含多个步骤及多次 LLM 调用。随着应用日益复杂,能够检查链或代理内部具体发生了什么变得至关重要。最佳方式是使用 LangSmith 注册上述链接后,请设置环境变量以开始记录追踪信息:
export LANGSMITH_TRACING="true"
export LANGSMITH_API_KEY="..."
或者,在笔记本环境中可这样设置:
import getpass
import os

os.environ["LANGSMITH_TRACING"] = "true"
os.environ["LANGSMITH_API_KEY"] = getpass.getpass()

文档与文档加载器

LangChain 实现了 Document 抽象类,用于表示一段文本及其关联元数据,包含三个属性:
  • page_content:代表内容的字符串;
  • metadata:包含任意元数据的字典;
  • id:(可选)文档的字符串标识符。
metadata 属性可用于捕获文档来源、与其他文档的关系等信息。注意,单个 Document 对象通常代表较大文档的一个片段。 我们可以按需生成示例文档:
from langchain_core.documents import Document

documents = [
    Document(
        page_content="狗是极好的伴侣,以其忠诚和友善著称。",
        metadata={"source": "mammal-pets-doc"},
    ),
    Document(
        page_content="猫是独立的宠物,常常喜欢独处。",
        metadata={"source": "mammal-pets-doc"},
    ),
]
然而,LangChain 生态系统实现了文档加载器可对接数百种常见数据源,便于将这些数据源的数据整合到您的 AI 应用中。

加载文档

让我们将一份 PDF 加载为一系列 Document 对象。LangChain 仓库中有一个示例 PDF 在此处 —— 耐克公司 2023 年的 10-K 文件。我们可以查阅 LangChain 文档中的 可用 PDF 文档加载器
from langchain_community.document_loaders import PyPDFLoader

file_path = "../example_data/nke-10k-2023.pdf"
loader = PyPDFLoader(file_path)

docs = loader.load()

print(len(docs))
107
PyPDFLoader 为每页 PDF 创建一个 Document 对象。每个对象均可轻松访问:
  • 页面的字符串内容;
  • 包含文件名和页码的元数据。
print(f"{docs[0].page_content[:200]}\n")
print(docs[0].metadata)
目录
美利坚合众国
证券交易委员会
华盛顿特区 20549
表格 10-K
(标记一项)
☑ 根据 1934 年《证券交易法》第 13 或 15(D) 条提交的年度报告
FO

{'source': '../example_data/nke-10k-2023.pdf', 'page': 0}

文本分割

无论是为了信息检索还是后续问答任务,以整页作为粒度可能过于粗糙。我们的最终目标是检索能回答输入查询的 Document 对象,进一步分割 PDF 有助于确保相关段落的含义不会被周围文本“稀释”。 为此,我们可以使用文本分割器。这里我们使用一个基于字符的简单文本分割器,将文档分割为 1000 字符的块,并在块间保留 200 字符的重叠部分。重叠有助于避免将语句与其重要上下文分离。我们使用 RecursiveCharacterTextSplitter,它会递归地使用常见分隔符(如换行符)分割文档,直到每个块达到合适大小。这是通用文本场景推荐的文本分割器。 我们设置 add_start_index=True,以便在初始文档中每个分割文档的起始字符索引作为元数据属性 “start_index” 保留下来。
from langchain_text_splitters import RecursiveCharacterTextSplitter

text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000, chunk_overlap=200, add_start_index=True
)
all_splits = text_splitter.split_documents(docs)

len(all_splits)
514

嵌入模型

向量搜索是存储和检索非结构化数据(如非结构化文本)的常用方法。其核心思想是将文本关联的数值向量存储起来。给定一个查询,我们可以将其嵌入为相同维度的向量,并使用向量相似度度量(如余弦相似度)识别相关文本。 LangChain 支持来自数十家提供商的嵌入模型。这些模型规定了如何将文本转换为数值向量。让我们选择一个模型:
  • OpenAI
  • Azure
  • Google Gemini
  • Google Vertex
  • AWS
  • HuggingFace
  • Ollama
  • Cohere
  • MistralAI
  • Nomic
  • NVIDIA
  • Voyage AI
  • IBM watsonx
  • Fake
pip install -U "langchain-openai"
import getpass
import os

if not os.environ.get("OPENAI_API_KEY"):
os.environ["OPENAI_API_KEY"] = getpass.getpass("Enter API key for OpenAI: ")

from langchain_openai import OpenAIEmbeddings

embeddings = OpenAIEmbeddings(model="text-embedding-3-large")
vector_1 = embeddings.embed_query(all_splits[0].page_content)
vector_2 = embeddings.embed_query(all_splits[1].page_content)

assert len(vector_1) == len(vector_2)
print(f"生成的向量长度为 {len(vector_1)}\n")
print(vector_1[:10])
生成的向量长度为 1536

[-0.008586574345827103, -0.03341241180896759, -0.008936782367527485, -0.0036674530711025, 0.010564599186182022, 0.009598285891115665, -0.028587326407432556, -0.015824200585484505, 0.0030416189692914486, -0.012899317778646946]
有了生成文本嵌入的模型后,下一步便是将它们存储在一个支持高效相似度搜索的特殊数据结构中。

向量存储

LangChain 的 VectorStore 对象包含向存储中添加文本和 Document 对象的方法,以及使用各种相似度度量进行查询的方法。它们通常使用嵌入模型初始化,这些模型决定了文本数据如何转换为数值向量。 LangChain 提供了一系列与不同向量存储技术的集成。一些向量存储由提供商托管(如各类云服务商),需要特定凭证才能使用;一些(如 Postgres)可在本地或通过第三方运行的独立基础设施上运行;还有一些可内存运行以处理轻量级负载。让我们选择一个向量存储:
  • In-memory
  • AstraDB
  • Chroma
  • FAISS
  • Milvus
  • MongoDB
  • PGVector
  • PGVectorStore
  • Pinecone
  • Qdrant
pip install -U "langchain-core"
from langchain_core.vectorstores import InMemoryVectorStore

vector_store = InMemoryVectorStore(embeddings)
实例化向量存储后,我们现在可以对文档建立索引。
ids = vector_store.add_documents(documents=all_splits)
请注意,大多数向量存储实现允许您连接到现有的向量存储——例如,通过提供客户端、索引名称或其他信息。有关更多详细信息,请参阅特定集成的文档。 一旦我们实例化了一个包含文档的 VectorStore,就可以对其进行查询。VectorStore 包含以下查询方法:
  • 同步和异步查询;
  • 通过字符串查询和通过向量查询;
  • 返回和不返回相似度分数;
  • 通过相似度和最大边际相关性(在检索结果的相关性与多样性之间取得平衡)。
这些方法的输出通常包含一个 Document 对象列表。

使用示例

嵌入模型通常将文本表示为“稠密”向量,使得语义相似的文本在几何空间中彼此接近。这使我们只需传入一个问题即可检索相关信息,而无需了解文档中使用的任何特定关键词。 根据与字符串查询的相似度返回文档:
results = vector_store.similarity_search(
    "耐克在美国有多少个配送中心?"
)

print(results[0])
page_content='直销业务在美国通过以下数量的零售店销售产品:
美国零售店数量
NIKE品牌工厂店 213
NIKE品牌直营店(包括仅限员工的店铺) 74
Converse店铺(包括工厂店) 82
总计 369
在美国,NIKE拥有八个重要的配送中心。详情请参阅第2项“物业”。
2023年10-K表 2' metadata={'page': 4, 'source': '../example_data/nke-10k-2023.pdf', 'start_index': 3125}
异步查询:
results = await vector_store.asimilarity_search("耐克是何时成立的?")

print(results[0])
page_content='目录
第一部分
第1项. 业务
概述
NIKE, Inc. 于1967年根据俄勒冈州法律成立。本年度10-K表(“本年报”)中,“我们”、“我们”、“我们的”、“NIKE”和“公司”指NIKE, Inc. 及其前身、子公司和附属公司,除非上下文另有说明。
我们的主要业务活动是设计、开发以及在全球范围内营销和销售运动鞋、服装、设备、配件和服务。NIKE是全球最大的运动鞋和服装销售商。我们通过NIKE直销业务(包括NIKE自有零售店和数字平台销售(也称为“NIKE品牌数字”))、零售账户以及独立分销商、被许可方和销售商的组合销售产品。' metadata={'page': 3, 'source': '../example_data/nke-10k-2023.pdf', 'start_index': 0}
返回相似度分数:
# 注意,不同提供商实现的分数不同;此处的分数
# 是一个距离度量,与相似度成反比。

results = vector_store.similarity_search_with_score("耐克2023年的收入是多少?")
doc, score = results[0]
print(f"分数: {score}\n")
print(doc)
分数: 0.23699893057346344

page_content='目录
2023财年NIKE品牌收入亮点
下表按可报告经营分部、分销渠道和主要产品线列出了NIKE品牌收入:
2023财年与2022财年比较
• 2023财年,NIKE, Inc. 收入为512亿美元,较2022财年分别增长10%(按报告数据)和16%(按汇率中性基准)。
增长主要得益于北美、欧洲、中东和非洲(“EMEA”)、APLA和大中华区收入的增长,分别贡献了约7、6、2和1个百分点。
• NIKE品牌收入占NIKE, Inc. 收入的90%以上,分别增长10%(按报告数据)和16%(按汇率中性基准)。这一增长主要得益于男装、Jordan品牌、女装和童装的批发等效基础上分别增长17%、35%、11%和10%。' metadata={'page': 35, 'source': '../example_data/nke-10k-2023.pdf', 'start_index': 0}
根据与嵌入查询的相似度返回文档:
embedding = embeddings.embed_query("2023年耐克的利润率受到什么影响?")

results = vector_store.similarity_search_by_vector(embedding)
print(results[0])
page_content='目录
毛利率
2023财年与2022财年比较
2023财年,我们的综合毛利润从2022财年的214.79亿美元增至222.92亿美元,增长4%。毛利率从2022财年的46.0%下降250个基点至2023财年的43.5%,原因如下:
* 批发等效
2023财年毛利率下降的主要原因是:
• NIKE品牌产品成本(按批发等效计算)上升,主要由于投入成本增加、入境运费和物流成本升高以及产品组合变化;
• NIKE直销业务利润率降低,因本期促销活动增加以清理库存,而上期促销活动较少(因可用库存供应较低);
• 净外汇汇率(包括对冲)的不利变动;
• 折扣店利润率降低(按批发等效计算)。
这部分被以下因素部分抵消:' metadata={'page': 36, 'source': '../example_data/nke-10k-2023.pdf', 'start_index': 0}
了解更多:

检索器

LangChain 的 VectorStore 对象并非 Runnable 的子类。LangChain 的 Retrievers 是 Runnable,因此它们实现了一组标准方法(如同步和异步的 invokebatch 操作)。虽然我们可以从向量存储构造检索器,但检索器也可以与其他非向量存储数据源(如外部 API)交互。 我们可以自己创建一个简单的版本,无需继承 Retriever。如果我们选择希望用于检索文档的方法,可以轻松创建一个可运行对象。下面我们将围绕 similarity_search 方法构建一个:
from typing import List

from langchain_core.documents import Document
from langchain_core.runnables import chain


@chain
def retriever(query: str) -> List[Document]:
    return vector_store.similarity_search(query, k=1)


retriever.batch(
    [
        "耐克在美国有多少个配送中心?",
        "耐克是何时成立的?",
    ],
)
[[Document(metadata={'page': 4, 'source': '../example_data/nke-10k-2023.pdf', 'start_index': 3125}, page_content='直销业务在美国通过以下数量的零售店销售产品:\n美国零售店数量\nNIKE品牌工厂店 213 \nNIKE品牌直营店(包括仅限员工的店铺) 74 \nConverse店铺(包括工厂店) 82 \n总计 369 \n在美国,NIKE拥有八个重要的配送中心。详情请参阅第2项“物业”。\n2023年10-K表 2')],
 [Document(metadata={'page': 3, 'source': '../example_data/nke-10k-2023.pdf', 'start_index': 0}, page_content='目录\n第一部分\n第1项. 业务\n概述\nNIKE, Inc. 于1967年根据俄勒冈州法律成立。本年度10-K表(“本年报”)中,“我们”、“我们”、“我们的”、“NIKE”和“公司”指NIKE, Inc. 及其前身、子公司和附属公司,除非上下文另有说明。\n我们的主要业务活动是设计、开发以及在全球范围内营销和销售运动鞋、服装、设备、配件和服务。NIKE是全球最大的运动鞋和服装销售商。我们通过NIKE直销业务(包括NIKE自有零售店和数字平台销售(也称为“NIKE品牌数字”))、零售账户以及独立分销商、被许可方和销售商的组合销售产品。')]]
向量存储实现了 as_retriever 方法,用于生成一个检索器,即 VectorStoreRetriever。这些检索器包含特定的 search_typesearch_kwargs 属性,用于标识调用底层向量存储的哪些方法以及如何参数化它们。例如,我们可以用以下方式复制上述操作:
retriever = vector_store.as_retriever(
    search_type="similarity",
    search_kwargs={"k": 1},
)

retriever.batch(
    [
        "耐克在美国有多少个配送中心?",
        "耐克是何时成立的?",
    ],
)
[[Document(metadata={'page': 4, 'source': '../example_data/nke-10k-2023.pdf', 'start_index': 3125}, page_content='直销业务在美国通过以下数量的零售店销售产品:\n美国零售店数量\nNIKE品牌工厂店 213 \nNIKE品牌直营店(包括仅限员工的店铺) 74 \nConverse店铺(包括工厂店) 82 \n总计 369 \n在美国,NIKE拥有八个重要的配送中心。详情请参阅第2项“物业”。\n2023年10-K表 2')],
 [Document(metadata={'page': 3, 'source': '../example_data/nke-10k-2023.pdf', 'start_index': 0}, page_content='目录\n第一部分\n第1项. 业务\n概述\nNIKE, Inc. 于1967年根据俄勒冈州法律成立。本年度10-K表(“本年报”)中,“我们”、“我们”、“我们的”、“NIKE”和“公司”指NIKE, Inc. 及其前身、子公司和附属公司,除非上下文另有说明。\n我们的主要业务活动是设计、开发以及在全球范围内营销和销售运动鞋、服装、设备、配件和服务。NIKE是全球最大的运动鞋和服装销售商。我们通过NIKE直销业务(包括NIKE自有零售店和数字平台销售(也称为“NIKE品牌数字”))、零售账户以及独立分销商、被许可方和销售商的组合销售产品。')]]
VectorStoreRetriever 支持的搜索类型包括 "similarity"(默认)、"mmr"(最大边际相关性,如上所述)和 "similarity_score_threshold"。我们可以使用后者通过相似度分数对检索器输出的文档设置阈值。 检索器可以轻松集成到更复杂的应用中,例如检索增强生成 (RAG) 应用,将给定问题与检索到的上下文结合形成 LLM 的提示。要了解更多关于构建此类应用的信息,请查看 RAG 教程

下一步

您现在已经了解了如何基于 PDF 文档构建语义搜索引擎。 关于文档加载器的更多信息: 关于嵌入模型的更多信息: 关于向量存储的更多信息: 关于 RAG 的更多信息,请参见: