[LangChain]Semantic search

0 Useful Docs

1 Setup

1
pip install langchain-community pypdf
CMAKE

2 Documents and Document Loaders

LangChain实现了Document abstraction,旨在表示文本单元及其相关元数据。它包含三个属性:

  • page_content: 字符串形式的内容;
  • metadata: 包含任意元数据的字典;
  • id: (可选)文档的字符串标识符。

metadata属性可以捕捉关于文档来源、与其他文档的关系及其他信息。需要注意的是,单独的Document对象通常代表较大文档中的一个chunk。

LangChain ecosystem实现了与数百种常见数据源集成的文档加载器。

API Reference: Document

Loading documents

在LangChain仓库中here,有一个样本PDF——这是耐克公司2023年的10-K报表。可以查阅LangChain文档来了解可用的PDF文档加载器。这里选择使用相对轻量级的PyPDFLoader。

1
2
3
4
5
6
7
8
from langchain_community.document_loaders import PyPDFLoader

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

docs = loader.load()

print(len(docs))
PYTHON

PyPDFLoader为每个PDF页面加载一个Document对象。对于每一个对象,可以访问:

  • 页面的字符串内容;
  • 包含文件名和页码的元数据。

以下是如何访问并打印第一个文档对象的前200个字符的内容以及其元数据的示例代码:

1
2
3
4
5
# 打印第一个文档对象的前200个字符
print(f"{docs[0].page_content[:200]}\n")

# 打印第一个文档对象的元数据
print(docs[0].metadata)
PYTHON

splitting

在信息检索和下游问答任务中,将页面作为单独的表示单位可能过于粗糙。最终目标是检索能够回答输入查询的文档对象,进一步拆分PDF文件有助于确保文档相关部分的意义不会被周围文本“冲淡”。

为此,可以使用文本分割器。这里采用基于字符的简单分割方法。具体来说,将文档分割成1000个字符的块,并且相邻块之间有200个字符的重叠。这种重叠有助于减少将陈述与其重要上下文分离的可能性。

使用RecursiveCharacterTextSplitter,它会递归地使用常见分隔符(如换行符)分割文档,直到每个块达到合适的大小。对于通用文本用例,推荐使用此文本分割器。

通过设置add_start_index=True,可以保留每个分割后Document对象在其原始文档中的起始字符索引,并将其作为元数据属性”start_index”保存。

参考here以获取更多关于处理PDF文件的详细信息,包括如何从特定部分和图像中提取文本。

API Reference: RecursiveCharacterTextSplitter

1
2
3
4
5
6
7
8
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)
PYTHON

3 Embeddings

向量搜索是一种常见的存储和搜索非结构化数据(如非结构化文本)的方法。其核心思想是存储与文本关联的数字向量。给定一个查询,我们可以将其嵌入为相同维度的向量,并使用向量相似度指标(如余弦相似度)来识别相关的文本。

LangChain 支持来自数十个供应商的嵌入模型。这些模型定义了如何将文本转换为数字向量。

这里选择HuggingFace模型。

1
2
3
4
5
6
7
8
9
10
from langchain_huggingface import HuggingFaceEmbeddings

embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-mpnet-base-v2")

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"Generated vectors of length {len(vector_1)}\n")
print(vector_1[:10])
PYTHON

4 Vector stores

LangChain的VectorStore对象包含了向存储中添加文本和Document对象的方法,并支持使用多种相似度度量进行查询。这些对象通常与embedding模型一起初始化,以确定文本数据如何转换为数值向量。

LangChain提供了一系列基于不同向量存储技术的integrations 。一些向量存储由供应商托管(例如,不同的云服务提供商),并需要特定的凭据才能访问;有些(如Postgres)则可以在独立的基础设施上运行,既可本地部署也可通过第三方实现;还有些可以在内存中运行,适用于轻量级工作负载。

这里选择In-memory类型。

1
2
3
from langchain_core.vectorstores import InMemoryVectorStore

vector_store = InMemoryVectorStore(embeddings)
PYTHON

在实例化了我们的向量存储之后,现在可以对文档进行索引。

1
ids = vector_store.add_documents(documents=all_splits)
PYTHON

需要注意的是,大多数向量存储实现允许连接到现有的向量存储——例如,通过提供客户端、索引名称或其他信息进行连接。有关特定integration的更多详情,请参阅相关文档。

一旦我们实例化了一个包含文档的VectorStore,就可以对其进行查询。VectorStore提供了多种查询方法:

  • 同步与异步查询;
  • 通过字符串查询和向量查询;
  • 支持返回或不返回相似度分数;
  • 按相似度以及maximum marginal relevance(在检索结果的相关性和多样性之间取得平衡)进行查询。

这些方法通常在其输出中包含一个Document对象列表。

Usage

嵌入通常将文本表示为“密集”向量,使得具有相似意义的文本在几何空间中彼此接近。

下面的代码示例展示了如何根据与字符串查询的相似度来检索文档:

1
2
3
4
5
# 根据相似度搜索返回结果
results = vector_store.similarity_search("How many distribution centers does Nike have in the US?")

# 输出第一个结果
print(results[0])
PYTHON

异步查询:

1
2
3
results = await vector_store.asimilarity_search("When was Nike incorporated?")

print(results[0])
PYTHON

返回分数:

1
2
3
4
5
6
# Note that providers implement different scores;

results = vector_store.similarity_search_with_score("What was Nike's revenue in 2023?")
doc, score = results[0]
print(f"Score: {score}\n")
print(doc)
PYTHON

根据向量查询:

1
2
3
4
embedding = embeddings.embed_query("How were Nike's margins impacted in 2023?")

results = vector_store.similarity_search_by_vector(embedding)
print(results[0])
PYTHON

更多信息:

5 Retrievers

LangChain的VectorStore对象并不继承自Runnable接口,而LangChain的检索器(Retrievers)是实现了Runnable接口的,因此它们支持一系列标准方法,如同步和异步 invokebatch 操作。我们可以从vector stores构建检索器,检索器也支持与非向量存储的数据源接口交互,比如外部API。

即便不直接继承Retriever类,我们也能创建一个简单的版本。只要定义了用于检索文档的方法,就可以轻松创建一个可运行的对象。下面的例子将围绕similarity_search方法来构建:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from typing import List
from langchain_core.documents import Document
from langchain_core.runnables import chain

# 定义一个使用similarity_search方法的检索器
@chain
def retriever(query: str) -> List[Document]:
return vector_store.similarity_search(query, k=1)

# 对多个查询执行批处理操作
retriever.batch([
"How many distribution centers does Nike have in the US?",
"When was Nike incorporated?",
])
PYTHON

API Reference: Document | chain

向量存储(VectorStore)实现了as_retriever方法,可以生成一个专门的VectorStoreRetriever。这类检索器包含特定的search_typesearch_kwargs属性,用于指定调用底层向量存储的哪些方法及其参数化方式。

1
2
3
4
5
6
7
8
9
10
11
# 创建一个VectorStoreRetriever,设置搜索类型为相似性搜索,并指定返回最相关的1个结果
retriever = vector_store.as_retriever(
search_type="similarity",
search_kwargs={"k": 1},
)

# 对多个查询执行批处理操作
retriever.batch([
"How many distribution centers does Nike have in the US?",
"When was Nike incorporated?",
])
PYTHON

VectorStoreRetriever支持多种搜索类型,包括默认的”similarity”(相似性)、”mmr”(最大边际相关性)以及”similarity_score_threshold”。我们可以使用”similarity_score_threshold”根据相似度分数对检索器输出的文档进行阈值设定,从而控制返回结果的相关性程度。

检索器可以轻松集成到更复杂的应用程序中,例如结合特定问题与检索上下文以生成提示给语言模型(LLM)的 retrieval-augmented generation (RAG)应用。

6 更多信息

Retrieval strategies can be rich and complex. For example:

The retrievers section of the how-to guides covers these and other built-in retrieval strategies.

It is also straightforward to extend the BaseRetriever class in order to implement custom retrievers. See our how-to guide here.

For more on document loaders:

For more on embeddings:

For more on vector stores:

For more on RAG, see:


[LangChain]Semantic search
https://erlsrnby04.github.io/2025/03/29/LangChain-Semantic-search/
作者
ErlsrnBy04
发布于
2025年3月29日
许可协议