目录
[2.文档加载器(Document loaders)](#2.文档加载器(Document loaders))
[3.文本分割器(Text splitters)](#3.文本分割器(Text splitters))
[Embeddings 嵌入模型类](#Embeddings 嵌入模型类)
[5.向量存储(Vector Stores)](#5.向量存储(Vector Stores))
1.**检索增强生成(**RAG)
RAG介绍
概念:RAG(Retrieval-Augmented Generation,检索增强生成) 是当下 LLM 应用开发的核心技术范式,核心解决 LLM 知识陈旧、幻觉、无法引用外部数据 的问题 ------ 简单说就是「先检索外部知识库中的相关信息,再让 LLM 基于这些信息生成回答」,而不是让 LLM 凭空「编造」内容。
为了更好的理解 RAG,我们先用AI 搜索来引出 RAG。
对于【AI 大模型】来说,它最擅长的是语义理解和文本总结,最不擅长的就是获取实时的信息。因为大模型的训练数据是有截止日期的!
对于【搜索引擎】来说,它最擅长的就是获取实时的信息,缺点是信息分散,每次都需要人为进行总结。
大模型与搜索引擎的结合,就是给 AI 配备了一个活字典,让 AI 可以随时进行查阅。
下图展示了一个最简单的 AI 搜索工作流程,搜索引擎在这里充当知识库,结合我们的查询语句,大模型便可以从知识库中获取相应的查询结果:

首先,先来思考一个问题:搜索引擎可以帮我们解决实时数据的获取,但获取到的数据也是受限的。它只能获取到
公开在网络中的数据,而无法获取到一些本地数据,或企业内部的私有数据等,此时该如何?
答案是使用 RAG(检索增强生成)技术!当用户向 LLM 提问时,系统首先在知识库(如公司内部文档)中进行语
义搜索,找到最相关的内容,然后将这些内容和问题一起交给 LLM 来生成答案。与 AI 搜索类比,本质是知识库改
变了,从搜索引擎线上搜索改为了本地或私有知识库中搜索。
RAG流程
RAG 的流程分为【离线数据处理】和【在线检索】两个过程。
上面提到,RAG 知识库可以是本地文档、公司内部文档等一些私有化数据。但这些私有数据或文档实际上并不能很
好地被直接进行检索访问。因此需要将这些私有化数据构建成可以被检索的知识库,这就是离线数据处理要干的事
情。经过离线数据后,知识则会按照某种格式以及排列方式存储在知识库中,等待被使用。
而在线检索则是我们依赖知识库查询,通过大模型生成结果的过程。
过程如下图所示:

上图的每个部分都对应一个组件:
-
文档加载 (Document Loading):加载多种不同来源加载文档。LangChain 提供了 100 多种不同的文档加载器,包括 PDF 在内的非结构化的数据、SQL 在内的结构化的数据,以及 Python、Java之类的代码等。
-
文本分割 (Splitting):文本分割器把 Documents 切分为指定大小的块。
-
存储 (Storage):存储涉及到两个环节,分别是:
-
将切分好的文档块进行嵌入(Embedding),即将文档块转换成向量的形式。
-
将 Embedding 后的向量数据,存储到向量数据库中。
-
-
检索 (Retrieval):数据存入向量数据库后。当我们需要进行数据检索时,会通过某种检索算法找到与输入问题相似的文档块。
-
输出 (Output):把问题以及检索出来的文档块一起提交给 LLM,LLM 会通过问题和检索出来的提示一起来生成更加合理的答案。
2.文档加载器(Document loaders)

要想实现 RAG,首先就需要从源中获取数据,即加载数据或文档。这是通过 LangChain 的文档加载器完成的。
LangChain 文档加载器可以将各种数据源加载成一系列的文档对象 Document 。
class langchain_core.documents.base.Document 用于存储一段文本和相关元数据的类,我们可以直接定义
LangChain 文档列表,如下所示:
#手动定义文档
#通常单个Document表示文档的一块/页
documments =[
Document(
#内容字符串
page_content="LangChain框架学习",
#元数据字典
#元数据可以捕获有关文档源,与其他文档的关系的信息
metadata={"source":"ai-docs"}
),
Document(
#内容字符串
page_content="Agent学习",
#元数据字典
# 元数据可以捕获有关文档源,与其他文档的关系的信息
metadata={"source":"ai-docs"}
)
]
这里我们定义了一个 documents 文档列表,其内包含了两个 Document 文档对象。通常,单个Document对象表示较大文档的一个块/页。每个 Document 对象,包含了以下参数:
-
id :可选的文档标识符。理想情况下,这应该在整个文档集合中是唯一的,并格式化为UUID,但不会强制执行。
-
page_content :字符串文本。
-
metadata :与内容关联的任意元数据。类型为 dict [Optional]
加载PDF文档
将本地的 PDF 文档加载到 LangChain 中,其实就是将 PDF 文档转换为一个个 Document 对象。这时就需要我们
使用 PyPDFLoader 文档加载器完成这一功能。
class langchain_community.document_loaders.pdf.PyPDFLoader 类,有以下关键函数:
-
init() 初始化函数,入参 file_path ,表示要加载的 PDF 文件的路径。
-
load() → list[Document] :将数据加载到文档对象中。返回文档对象列表。
#文档加载器
from langchain_community.document_loaders import PyPDFLoader
from langchain_core.documents import Document
from sqlalchemy.testing.suite.test_reflection import metadata#文档加载器(PDF)
loader = PyPDFLoader(file_path="Docs/PDF/LangChain------AI应用开发框架.pdf")
#加载后的文档按页进行拆分的,这是文档加载器的默认行为
#也可以按其他方式进行拆分,这时就需要用到拆分器组件
docs = loader.load()print(f"PDF文件的总页数:\n{len(docs)}\n")
print(f"第二页文本的前200个字符是:\n{docs[1].page_content[:200]}\n")
print(f"第二页的元数据信息:{docs[1].metadata}")

加载Markdown文档
将本地的 Markdown 文档加载到 LangChain 中,需要我们使用UnstructuredMarkdownLoader 文档加载器完成这一功能。
Class langchain_community.document_loaders.markdown.UnstructuredMarkdownLoader 类,有以下关键函数:
init() 初始化函数,所需参数:
-
file_path :表示要加载的 Markdown 文件的路径。
-
mode :加载文件时要使用的模式。可以是 single 或 elements。默认为 single。
-
single:文档将作为单个 Document 对象返回
-
elements:会将文档拆分为 Title 和 NarrativeText 等不同类型的元素。
load() → list[Document] :将数据加载到文档对象中。返回文档对象列表。
LangChain 实现的 UnstructuredMarkdownLoader 需要依赖 Unstructured 包。因此在使用前我们需要先安装它:
pip install "unstructured[md]" nltk
示例代码:
from langchain_community.document_loaders import UnstructuredMarkdownLoader
from langchain_core.documents import Document
markdown_path = "Docs/PDF/LangChain------AI应用开发框架.md"
# single 模式,加载后,默认只有⼀个 Document 对象
loader = UnstructuredMarkdownLoader(
markdown_path,
mode="single"
)
data = loader.load()
assert len(data) == 1
assert isinstance(data[0], Document)
print(data[0].page_content[:200])
print(data[0].metadata)
from langchain_community.document_loaders import UnstructuredMarkdownLoader
from langchain_core.documents import Document
markdown_path = "Docs/PDF/LangChain------AI应用开发框架.md"
#element模式下,会将文档拆分成多个子文档
loader = UnstructuredMarkdownLoader(markdown_path, mode="elements")
data = loader.load()
print(f"问:文档个数为:\n{len(data)}\n")
print("问:前三个文档数据:")
for document in data[:3]:
print(f"{document}\n")
3.文本分割器(Text splitters)

我们已经知道可以通过文档加载器完成各种数据源的加载,将其转换为文档对象 Document 。那么接下来要做的就是文档拆分。
文档拆分通常是将大文本分解为更小的、易于管理的块。这对于索引数据并将其传递到模型中都很有用。因为,大块更难搜索并且不适合模型的有限上下文窗口。拆分可以提高搜索结果的粒度,从而可以更精确地将查询与相关文档部分进行匹配。
LangChain 的文本分割器便能将大型文档分解为更小的块。
基于字符长度拆分
根据给定的字符序列进行拆分,拆分的块长度则按字符数来衡量。
#文本分割器
#根据文档的长度进行拆分
from langchain_community.document_loaders import PyPDFLoader
from langchain_text_splitters import CharacterTextSplitter
from sympy import false
#文档加载器(PDF)
loader = PyPDFLoader(file_path="Docs/PDF/LangChain------AI应用开发框架.pdf")
documents = loader.load()
#文本分割器
text_splitter = CharacterTextSplitter(
separator="\n\n", #文本分隔符:遇到\n\n时进行拆分
chunk_size=300, #块大小(拆分后文档的大小):只是一个参考标准,如果文档的语义不完整,真实的块大小可能会超出这个大小
chunk_overlap=30, #重叠度大小:每个块之间的重叠大小
length_function=len, #使用测量长度的函数
is_separator_regex=false,#设定的separator是否是正则表达式
)
#分割文档
docs = text_splitter.split_documents(documents)
#打印10个文档
for doc in docs[1:11]:
print("="*30)
print(doc)
print("="*30)

基于Token长度拆分
LLM 大模型实际上并不是直接接收字符串,而是需要先做token 切分编码。这里我们可以借助 【 tiktoken 分词器】来进行 token 的切分编码。
LLM 本质是「数字模型」,只能处理数字,无法直接理解文字。Token 切分编码做两件事:
-
Token 切分(Tokenization):把连续的文本拆成「语义最小单元」(不是简单按字 / 词拆分);
-
Token 编码(Encoding):给每个 Token 分配唯一的数字 ID,让模型能计算和处理。
先看个例子:给定一个文本字符串 "my name is LiHua!" ,使用tiktoken 分词器进行切分编码,会得到什么结果:
import tiktoken
# 定于cl100k_base编码方式的分词器
enc = tiktoken.get_encoding("cl100k_base")
# 进⾏切分编码
enc_output = enc.encode("my name is LiHua!")
# 打印结果
print(f"编码后的token:{str(enc_output)}")
for token in enc_output:
print(f"将token: {str(token)} 变成⽂本:{str(enc.decode_single_token_bytes(token))}")

解释: cl100k_base 是 tiktoken 分词器中的一种编码方式。 gpt-4 、 gpt-3.5-turbo 等都采用这种切分编码方式。
可以看到采用切分编码 cl100k_base ,拆解后的文本字符串为 ["my", "name", "is", "Li", "H", "ua", "!"] 。token
编码表示为 [2465, 836, 374, 14851, 39, 4381,0] 。
在LangChain中,我们可以使用 CharacterTextSplitter 分割器的 .from_tiktoken_encoder() 方法来定义根据 tiktoken 分词器拆分文本的分割器,代码如下所示:
#文本分割器
#基于Token长度进行拆分
from langchain_community.document_loaders import PyPDFLoader
from langchain_text_splitters import CharacterTextSplitter
from sympy import false
#文档加载器(PDF)
loader = PyPDFLoader(file_path="Docs/PDF/LangChain------AI应用开发框架.pdf")
documents = loader.load()
#文本分割器
text_splitter = CharacterTextSplitter.from_tiktoken_encoder(
encoding_name="cl100k_base", #cl100k_base是tiktoken分词器的一种编码方式
chunk_size=300, #块大小(拆分后文档的大小):只是一个参考标准,如果文档的语义不完整,真实的块大小可能会超出这个大小
chunk_overlap=30, #重叠度大小:每个块之间的重叠大小
)
#分割文档
docs = text_splitter.split_documents(documents)
#打印10个文档
for doc in docs[1:11]:
print("="*30)
print(doc)
print("="*30)
强制长度拆分
如果我们就想要求任何块都不能超过指定大小,可以使用RecursiveCharacterTextSplitter类 或
RecursiveCharacterTextSplitter.from_tiktoken_encoder 方法,它会严格遵守对块大小的硬约束。下面展示一下用法:
#文本分割器
#强制指定长度进行分割
from langchain_community.document_loaders import PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from sympy import false
#文档加载器(PDF)
loader = PyPDFLoader(file_path="Docs/PDF/LangChain------AI应用开发框架.pdf")
documents = loader.load()
#文本分割器(递归进行分割)
#分割的结果严格按照指定块大小
text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
encoding_name="cl100k_base", #cl100k_base是tiktoken分词器的一种编码方式
chunk_size=300, #块大小(拆分后文档的大小):只是一个参考标准,如果文档的语义不完整,真实的块大小可能会超出这个大小
chunk_overlap=30, #重叠度大小:每个块之间的重叠大小
)
#分割文档
docs = text_splitter.split_documents(documents)
#打印10个文档
for doc in docs[1:11]:
print("="*30)
print(doc)
print("="*30)
Python代码拆分
若对于代码等特殊文本,可以尝试使用 LangChain 提供的不同的分割器(如PythonCodeTextSplitter 、 HTMLHeaderTextSplitter 等)效果会更好,它会理解代码的语法结构。
#分割器
#拆分python代码
from langchain_text_splitters import PythonCodeTextSplitter
# 字符串⽂档
PYTHON_CODE = """
def hello_world():
print("Hello, World!")
def hello_python():
print("Hello, Python!")
"""
#定义拆分器
python_splitter = PythonCodeTextSplitter(
chunk_size=50,
chunk_overlap=0,
)
#创建(分割)文档
python_docs = python_splitter.create_documents([PYTHON_CODE])
for doc in python_docs:
print("*"*30)
print(doc)

4.嵌入式模型
计算机天生擅长处理数字,但不理解文字、图片的含义。嵌入(Embedding)的核心思想就是将人类世界的符号(如单词、句子、产品、用户、图片)转换为计算机能够理解的数值形式(即向量,本质上是一个数字列表),并且要求这种转换能够保留原始符号的语义和关系。
我们可以把它想象成一个翻译过程,把人类语言"翻译"成计算机的"数学语言"。

说明:我们之前一直用的大语言模型是生成式模型。它理解输入并生成新的文本(回答问题、写文章)。它内部实际上也使用嵌入技术来理解输入,但最终目标是"创造"。
而嵌入模型(Embedding Models)是表示型模型。它的目标不是生成文本,而是为输入的文本创建一个最佳的、富含语义的数值表示(向量)。如 OpenAI 的 "text-embedding-3-large" 嵌入模型;Google 的 "gemini-embedding-001" 嵌⼊模型;阿里的 "Qwen3-Embedding-8B" 嵌入模型等。
什么是向量?
首先我们要知道,嵌入的结果是就是一个向量,它本质上是一个数字列表(一维数组)。
例如:[0.023, 0.487, -0.129, ..., 0.325] 。对于向量来说,有两个关键概念需要了解:
向量维度:嵌入结果得到的列表长度是固定的,称为向量的"维度"。例如,OpenAI 的 text-embedding-ada-002 模型会生成一个 1536 维的向量, text-embedding-3-large 模型会生成一个3072 维的向量。维度越高,通常能捕捉更细微的语义信息,但也需要更多的计算和存储资源。
向量空间 :想象一个无限延伸的、拥有无数个维度的宇宙,这个宇宙就是一个向量空间。
这有点抽象,可以想象一下:在三维世界里,一个点可以用 (x, y, z) 坐标表示,例如 (2, 5, -1) 。在机器学习的高维向量空间中,一个点可能是 (0.1, 0.7, -0.2, 0.4, ..., 0.02) ,一个有几百或几千个数字的坐标。在这个空间里 ,每个点(即每个向量)都能代表一个概念。例如在嵌入模型中,一个点可以代表一个单词、一句话、一张图片、一个用户、一部电影等。
到这里,向量空间的威力就能体现出来:我们可以用数学来度量语义。可以通过计算两个向量之间的"距离"或"相似度"来实现这一点。
-
欧氏距离(Euclidean Distance):就是我们高中集合学的两点之间的直线距离。距离越短,相似度越高。
-
余弦相似度(Cosine Similarity):它忽略向量的绝对长度(大小),只关注两个向量在方向上的差异。在文本和语义的世界里,"方向"代表"含义",而"长度"往往只代表"文本的长度"或"词汇的多少"。换句话说,余弦相似度关注的是 "你们是否指向同⼀个方向" / "你们是否代表同一个含义"。
嵌入式向量应用场景
语义搜索(Semantic Search)
传统搜索依赖关键词匹配(搜 "苹果" ,只能找到包含 "苹果" 这个词的文档)。语义搜索则能将查询(如 "一种红
色的水果" )和文档库中的所有文档都转换为向量。然后计算查询向量与所有文档向量的相似度,返回最相似的文
档。这样即使文档里没有 "红色" 和 "水果" 这些词,但只要它是关于 "苹果" 的,就能被找到。
检索增强生成(Retrieval-Augmented Generation, RAG)
这是当前大语言模型应用的核心模式。当用户向 LLM 提问时,系统首先使用嵌入模型在知识库(如公司内部文
档)中进行语义搜索,找到最相关的内容,然后将这些内容和问题一起交给 LLM 来⽣成答案。这极大地提高了答
案的准确性和时效性。
推荐系统(Recommendation Systems)
将用户(根据其历史行为、偏好)和物品(商品、电影、新闻)都转换为向量。喜欢相似物品的用户,其向量会接
近;相似的物品,其向量也会接近。通过计算用户和物品向量的相似度,就可以进行精准推荐。
异常检测(Anomaly Detection)
正常数据的向量通常会聚集在一起。如果一个新数据的向量远离大多数向量的聚集区,它就可能是一个异常点(如
垃圾邮件、欺诈交易)。
Embeddings 嵌入模型类
在 LangChain 中,有很多的嵌入模型提供方,使用不同的模型提供方,需要安装为其各自包,例如:
-
OpenAI: pip install -U langchain-openai
-
Ollama: pip install -U langchain-ollama
-
Google Gemini: pip install -U langchain-google-genai
官网:https://python.langchain.com/docs/integrations/text_embedding/
在 LangChain 框架中基础 Embeddings 类(OpenAIEmbeddings 继承了它)设计了两个核心方法来处理文本嵌入。
-
.embed_documents() : 用于处理文档 Documents 。它的输入是多个文本 。例如要将一个知识库里的所有段落都转换成向量后存入数据库,就会使用这个方法。
- 它返回一个【二维列表】List[List[float]] 。外层列表的每个元素对应一个输入文档,内层列表则是该文档的向量表示。
-
.embed_query() : 用于处理查询 Query 。它的输入是单个文本(一个字符串,str)。例如,当用户提出一个问题时,需要将这个问题转换成向量,以便在数据库中搜索相似的文档段落,就会使用这个方法。
- 它返回一个【一维列表】,里面是浮点数( List[float] ),代表单个查询文本的向量。
定义 OpenAIEmbeddings 嵌入模型类与定义聊天模型类似,如下所示**(嵌入单个查询)**:
#嵌入式模型
from langchain_openai import OpenAIEmbeddings
embedding_model = OpenAIEmbeddings(
model="text-embedding-3-large",
)
query_vector = embedding_model.embed_query("你好")
print(f"text-embedding-3-large 维度:{len(query_vector)}") #3072
print(f"前五个向量的值:{query_vector[:5]}")
嵌入文档列表:embed_documents 的语义是 "索引"。它的目的是预处理大量文本,为它们创建向量表示,以便后续被搜索。这一般是⼀个离线、批量处理的过程。代码如下:
#嵌入式模型-文档表示
from langchain_openai import OpenAIEmbeddings
from langchain_community.document_loaders import UnstructuredMarkdownLoader
from langchain_text_splitters import CharacterTextSplitter
#嵌入式模型
embedding_model = OpenAIEmbeddings(
model="text-embedding-3-large",
)
markdown_path = "Docs/PDF/LangChain------AI应用开发框架.md"
# single 模式,加载后,默认只有⼀个 Document 对象
loader = UnstructuredMarkdownLoader(
markdown_path,
mode="single"
)
data = loader.load()
#使用分词器拆分为文档
#文本分割器
text_splitter = CharacterTextSplitter.from_tiktoken_encoder(
encoding_name="cl100k_base", #cl100k_base是tiktoken分词器的一种编码方式
chunk_size=300, #块大小(拆分后文档的大小):只是一个参考标准,如果文档的语义不完整,真实的块大小可能会超出这个大小
chunk_overlap=30, #重叠度大小:每个块之间的重叠大小
)
#对文档进行分割,返回文档列表
docs = text_splitter.split_documents(data)
#将分割后的文档列表转化为字符串列表
texts = [doc.page_content for doc in docs ]
#将字符串列表转化为向量
doc_vector = embedding_model.embed_documents(texts)
print(f"文档数量:{len(docs)},转化后的向量列表数量:{len(doc_vector)}")
print(f"第一个向量的维度:{len(doc_vector[0])}")
print(f"第一个向量前五个维度的值:{doc_vector[0][:5]}")
5.向量存储(Vector Stores)
向量数据库介绍
在 LangChain 中,实际并不需要我们直接手动调用嵌入模型去生成向量,然后手动去比较向量。在我们之前提供的 RAG 知识地图中,存在一个 Vector Stores 向量存储,如下图所示:

实际上,向量是被管理在专门的向量存储介质中,如向量数据库。向量存储的核心任务是解决一个传统数据库(如MySQL)不擅长的问题:基于内容的相似性搜索(Similarity Search),而不是基于精确匹配的查询。
向量数据库的灵魂:专门的索引
它不会使用暴力搜索,而是会预先为所有向量构建一种特殊的索引结构。
常见的方法有近似最近邻(ANN)搜索:为了追求极致的速度,它愿意牺牲一点点精度。它不会保证找到绝对最相似的向量(即最近邻),但能以极高的概率找到非常相似的向量。通过聚类、分层、压缩等算法技术,将搜索范围从"整个数据库"缩小到"几个最可能的候选集"。
内存存储
使用 LangChain 的 InMemoryVectorStore 来实现向量的内存存储。
-
添加文档
-
我们可以使用 add_documents 方法,向内存存储中去添加文档。要注意的是,该方法会为添加的文档编排索引,索引列表随着该方法返回。
-
编制索引: 用于从源中摄取数据并为其编制索引。
-
检索和生成 :接受用户查询并从索引中检索相关数据,然后将其传递给模型。
#内存向量存储
from langchain_community.document_loaders import UnstructuredMarkdownLoader
from langchain_core.documents import Document
from langchain_core.vectorstores import InMemoryVectorStore
from langchain_openai import OpenAIEmbeddings
from langchain_text_splitters import CharacterTextSplitter#定义嵌入式模型
embedding_model = OpenAIEmbeddings(
model="text-embedding-3-large",
)#定义内存向量数据库
vector_store = InMemoryVectorStore(embedding=embedding_model)#加载文档
markdown_path = "Docs/PDF/LangChain------AI应用开发框架.md"single 模式,加载后,默认只有⼀个 Document 对象
loader = UnstructuredMarkdownLoader(
markdown_path,
mode="single"
)
data = loader.load()#使用分词器拆分为文档
#文本分割器
text_splitter = CharacterTextSplitter.from_tiktoken_encoder(
encoding_name="cl100k_base", #cl100k_base是tiktoken分词器的一种编码方式
chunk_size=300, #块大小(拆分后文档的大小):只是一个参考标准,如果文档的语义不完整,真实的块大小可能会超出这个大小
chunk_overlap=30, #重叠度大小:每个块之间的重叠大小
)#对文档进行分割,返回文档列表
docs = text_splitter.split_documents(data)#将文档列表导入内存向量数据库中
#这一步会给文档列表编排索引,并返回索引
ids = vector_store.add_documents(docs)print(f"共拆分了{len(docs)}个文档,共编排了{len(ids)}个索引")
print(f"前3个索引为:{ids[:3]}")#对内存向量数据库的CURD
#根据索引获取文档
doc_2 = vector_store.get_by_ids(ids[:2])
#删除文档
vector_store.delete(ids[:2])
def my_filter(doc:Document)->bool :
return doc.metadata["source"]=="Docs/PDF/LangChain------AI应用开发框架.md"#从内存向量存储中进行搜索
#similarity_search:使用余弦相似度来进行语义匹配
#k=2:检索出的文档的个数
#filter:过滤器,返回值类型是bool值
search_docs = vector_store.similarity_search(
query="提示词模板",
k=2,
filter=my_filter
)for doc in search_docs:
print("*"*30)
print(doc.page_content)
-
Redis向量存储
RediSearch 是 Redis 官方推出的「实时全文检索引擎」,核心是给 Redis 增加了「结构化检索 + 向量检索」能力 。
环境配置
使用 Redis 来存储向量,首先需要将相关环境配置好。
-
第一步:启动 Redis 服务端,使用 Docker 启动 Redis 实例。
docker run -d -p 6379:6379 -it redis:latest
第二步:安装 Redis 客户端包,以便将来定义客户端,以及运行搜索和查询命令。
由于我们使用 Python 进行开发,则选择 redis-py 库完成客户端定义。要安装 redis-py,只需:
pip install redis
第三步:在 LangChain 中想要使用 Redis 向量库,需要安装 langchain-redis 包。
pip install -qU langchain-redis
第四步:测试连接(Ping)
import redis
redis_url = "redis://localhost:6379"
# 定义Redis客⼾端
redis_client = redis.from_url(redis_url)
# Ping
print(redis_client.ping())
若输出 True ,则表示连接测试成功。
基本操作
LangChain 中使用 RedisVectorStore 初始化 Redis 向量存储。由于 Redis 需要相关配置,如连接 URL 等,因此 LangChain 提供了 RedisConfig 配置类供我们使用。如下所示:
class langchain_redis.vectorstores.RedisVectorStore 类,其初始化参数如下:
-
embeddings :用于此存储的 Embeddings 实例。
-
config :可选的 RedisConfig 对象。
class langchain_redis.config.RedisConfig 配置类,其关键配置参数如下:
-
Index_name :Redis 中索引的名称。默认为生成的 ULID 唯一标识符。
-
key_prefix :Redis Key 的前缀。如果未设置,则默认为 index_name。
-
redis_url :Redis 实例的 URL。默认为"redis://localhost:6379"。
-
metadata_schema :元数据字段的 schema。设置该字段对于将来的元数据过滤有帮助。
from langchain_community.document_loaders import UnstructuredMarkdownLoader
from langchain_openai import OpenAIEmbeddings
from langchain_redis import RedisConfig, RedisVectorStore
from langchain_text_splitters import CharacterTextSplitter#定义嵌入式模型
embeddings = OpenAIEmbeddings(model="text-embedding-3-large")#配置redis客户端
redis_url = "redis://127.0.0.1:6379"
config = RedisConfig(
index_name="qa",
redis_url=redis_url,
metadata_schema=[
{"name": "category", "type": "tag"},
{"name": "num", "type": "numeric"},
],
)Redis 存储初始化
vector_store = RedisVectorStore(embeddings, config=config)
⽣成分割器
text_splitter = CharacterTextSplitter.from_tiktoken_encoder(
encoding_name="cl100k_base", chunk_size=200, chunk_overlap=50
)加载⽂档
data = UnstructuredMarkdownLoader(
"../Docs/Markdown/脚⼿架级微服务租房平台Q&A.md", category="QA").load()分割⽂档
documents = text_splitter.split_documents(data)
为⽂档添加元数据
for i, doc in enumerate(documents, start=1):
doc.metadata["category"] = "QA"
doc.metadata["num"] = i
ids = vector_store.add_documents(documents=documents)
print(f"共编排了{len(ids)}个⽂档索引")
print(f"前3个⽂档的索引是:{ids[:3]}")
Pinecone向量存储
Pinecone 是一个全托管的向量数据库平台,即负责所有后端维护、扩展、更新和监控,让用户专注于应用开发,无需担心数据库管理。
Pinecone 地址:https://www.pinecone.io/
环境设置
首次使用需注册新用户,选注册成功会生成一个默认的 API Key。注意保存好你的 key,也可以创建新的 API key,设置 PINECONE_API_KEY ,将 Key 添加进 环境变量 。
更新包
pip install -qU pinecone langchain-pinecone
基本操作
LangChain 中使用PineconeVectorStore 类初始化 Pinecone 向量库。我们需要:
-
创建索引,参考https://docs.pinecone.io/guides/index-data/create-an-index#bring-your-own-vectors。
-
使用索引来初始化 PineconeVectorStore 。
class langchain_redis.vectorstores.PineconeVectorStore 类,其初始化参数如下:
-
embeddings :用于此存储的 Embeddings 实例。
-
index :Pinecone 索引
如下所示:
#pinecone向量数据库
from langchain_community.document_loaders import UnstructuredMarkdownLoader
from langchain_openai import OpenAIEmbeddings
from langchain_pinecone import PineconeVectorStore
from langchain_text_splitters import CharacterTextSplitter
from pinecone import Pinecone, ServerlessSpec
# 建⽴索引
pc = Pinecone()
index_name = "qa"
#如果索引不存在,则创建
if not pc.has_index(index_name):
pc.create_index(
name=index_name,# 索引名称
dimension=3072,# 尺⼨,表⽰向量维度,需要和嵌⼊模型维度⼀致
metric="cosine",# 度量⽅式,cosine 表⽰余弦相似度
spec=ServerlessSpec(
cloud="aws",# 亚⻢逊云
region="us-east-1" # 区域
),
)
#获取索引
index = pc.Index(index_name)
#定义嵌入式模型
embeddings = OpenAIEmbeddings(model="text-embedding-3-large")
#定义pinecone向量
vector_store = PineconeVectorStore(embedding=embeddings,index=index)
#加载文档
markdown_path = "Docs/PDF/LangChain------AI应用开发框架.md"
# single 模式,加载后,默认只有⼀个 Document 对象
loader = UnstructuredMarkdownLoader(
markdown_path,
mode="single"
)
data = loader.load()
#使用分词器拆分为文档
#文本分割器
text_splitter = CharacterTextSplitter.from_tiktoken_encoder(
encoding_name="cl100k_base", #cl100k_base是tiktoken分词器的一种编码方式
chunk_size=300, #块大小(拆分后文档的大小):只是一个参考标准,如果文档的语义不完整,真实的块大小可能会超出这个大小
chunk_overlap=30, #重叠度大小:每个块之间的重叠大小
)
#对文档进行分割,返回文档列表
docs = text_splitter.split_documents(data)
#将文档列表导入内存向量数据库中
#这一步会给文档列表编排索引,并返回索引
ids = vector_store.add_documents(docs)
print(f"共拆分了{len(docs)}个文档,共编排了{len(ids)}个索引")
print(f"前3个索引为:{ids[:3]}")
# # 全量删除
# vector_store.delete(delete_all=True)
# # 删除指定id的⽂档列表
# delete_ids = []
# vector_store.delete(ids=delete_ids)
#向量搜索
search_docs = vector_store.similarity_search(
query="数据库表怎么设计的?",
k=2,
filter={"category": "QA"},
)
for doc in search_docs:
print("*" * 30)
print(f"Content: {doc.page_content[:100]}...")
print(f"Metadata: {doc.metadata}")
6.检索器(Retrievers)
检索系统(Information Retrieval System, IR System)是一个为了满足用户信息需求,从大规模、非结构化的数据集合中,自动、⾼效地查找、排序并返回相关信息的计算机系统。
它的核心任务是:在正确的时间,以正确的方式,将正确的信息传递给正确的人。最常见的例子就是:搜索引擎(如Google、百度)。
随着大型语言模型的流行,检索系统已成为人工智能应用(例如 RAG)的重要组成部分。且存在多种【不同类型】的检索系统,包括:
- 关系数据库
关系数据库 是许多应用程序中使用的结构化数据存储的基本类型。数据存储在行(记录)和列(属性)中,可以通过 SQL ( 结构化查询语言 )进行高效的查询和作。关系数据库擅长维护数据完整性、支持复杂查询以及处理不同数据实体之间的关系。
- 词法搜索索引
许多 搜索引擎 基于将查询中的单词与每个文档中的单词进行匹配,这种方法称为词法检索。即一个单词经常出现在用户的查询和特定文档中,那么这个文档可能是一个很好的匹配。这通常使用【倒排索引】实现。
- 向量数据库
向量存储不使用字频,而是使用【嵌入模型】将文档转换为高维向量表示。这允许使用 余弦相似度 等数学运算对嵌入向量进行有效的相似性搜索。
使用向量数据库作为检索器
检索器是检索系统中的一个核心组件,它接收来自用户接口的查询(Query),检索出包含查询关键词的候选文档集合。

我们可以使用上面提到的任何检索系统实现方式创建检索器!如关系数据库、向量数据库等。由于其重要性和多样性,LangChain 提供了一个统一的接口来与不同类型的检索系统进行交互。LangChain的检索器接口非常简单:
-
输入:查询字符串
-
输出:文档列表(标准化的 LangChain 文档对象 Document)
基本使用
向量存储是索引和检索非结构化数据的一种强大而有效的方法。可以通过调用 向量数据库的as_retriever 方法,将向量存储用作检索器。在这里我们使用 Redis 向量存储。
#检索器
from typing import List
from langchain_core.documents import Document
from langchain_core.runnables import chain
from langchain_openai import OpenAIEmbeddings
from langchain_redis import RedisConfig, RedisVectorStore
#定义嵌入式模型
embeddings = OpenAIEmbeddings(model="text-embedding-3-large")
#配置redis客户端
redis_url = "redis://127.0.0.1:6379"
config = RedisConfig(
index_name="qa",
redis_url=redis_url,
metadata_schema=[
{"name": "category", "type": "tag"},
{"name": "num", "type": "numeric"},
],
)
# Redis 向量数据库存储初始化
vector_store = RedisVectorStore(embeddings, config=config)
#定义检索器
retrieval = vector_store.as_retriever()
#检索器为Runnable对象
search_docs = retrieval.invoke("LangChain框架")
for doc in search_docs:
print("*"*30)
print(doc)
使用@Chain自定义"检索器"
除了使用 as_retriever 方法,我们还可以自行创建一个"检索器"。回想一下检索器的特点:
-
LangChain 检索器是一个 Runnable 的对象
-
LangChain 检索器输入为查询字符串,输出为文档列表(标准化的 LangChain 文档对象Document)
from langchain_core.runnables import chain
from typing import List
from langchain_core.documents import Document@chain
def retriever(query: str) -> List[Document]:
return vector_store.similarity_search(query, k=2)docs = retriever.invoke("数据库表怎么设计的?")
for doc in docs:
print("*" * 30)
print(doc.page_content[:30])
7.搭建RAG链
RAG 是当前大预言模型应用的核心模式。当用户向 LLM 提问时,系统首先使用嵌入模型在知识库中进行语义搜索,找到最相关的内容,然后将这些内容和问题一起交给 LLM 来生成答案。这极大地提高了答案的准确性和时效性。
由于 LangChain 检索器是一个 Runnable 的对象,我们便可以方便的使用链完成相关的调用。下面我们来完成一个最简单的 RAG 案例,将会完成:
-
根据 Query 搜索最相关的 4 篇文档。
-
将相关的文档转换为字符串,以便后续发送给聊天模型。
-
将 Query 与文档字符串发送给聊天模型。
-
聊天模型依据输出解析器格式输出内容。
#搭建RAG链
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_redis import RedisConfig, RedisVectorStore
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough定义聊天模型
model = ChatOpenAI(model="gpt-4o-mini")
定义嵌⼊模型
embeddings = OpenAIEmbeddings(model="text-embedding-3-large")
配置 Redis 客⼾端
redis_url = "redis://127.0.0.1:6379"
config = RedisConfig(
index_name="qa",
redis_url=redis_url,
metadata_schema=[
{"name": "category", "type": "tag"},
{"name": "num", "type": "numeric"},
],
)定义 Redis 向量存储
vector_store = RedisVectorStore(embeddings, config=config)
⽣成检索器
retriever = vector_store.as_retriever()
定义提⽰词模板
prompt = ChatPromptTemplate.from_messages(
[
(
"human",
"""你是负责回答问题的助⼿。使⽤以下检索到的上下⽂⽚段来回答问题。如果你不知
道答案,就说你不知道。最多只⽤三句话,回答要简明扼要。
Question: {question}
Context: {context}
Answer:""",
),
]
)将⽂档转换为字符串
def format_docs(docs):
return "\n\n".join(doc.page_content for doc in docs)定义链
rag_chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| prompt
| model
| StrOutputParser()
)执⾏链,流式输出
for chunk in rag_chain.stream("数据库表怎么设计的?"):
print(chunk, end="|", flush=True)
RunnablePassthrough 是一个"伪"Runnable,它的主要作用是在链(Chain)中透明地传递输入数据,而不做任何修改。
当我们需要将原始输入和另一个处理过程的输出一起传递给下一个步骤时,就需要RunnablePassthrough 。就例如代码中,我们需要将【Query】与【通过检索出来的文档转换的字符串】同时发送给提示词模板。