对应 LangChain 中的 Retrieval 内容
虽然前面通过 store | checkpoint 保证了模型具备上下文的能力。但仍然存在 消息滞后、知识缺失 的问题,因为模型的训练数据都是公开数据并且时效性不是当前;从而导致在回答问题时出现 幻觉
RAG 就是将模型触及不到知识作为模型的知识库每次回答问题时去检索出来,然后再回答给用户,从而在某些特定领域让模型的回答更加靠谱。
什么是 RAG
RAG(Retrieval-Augmented Generation,检索增强生成)

- 解析语料内容,将非结构化的数据转换为结构化数据 Loader
- 通过语义或者字符等手段对结构化数据进行拆分 Chunk
- 通过向量模型,将拆分后的数据解析转换成向量存入数据库中 Embedding
- 用户的输入也转为向量,做数据的相似度检索,将检索出来的数据作为上下文结合用户的输入提交给模型 Retrieval
- 模型根据上下文生成回答内容 Generation
最典型的莫过于客服系统,包括飞书开放文档的智能助手等
RAG 优点就是提高问答的可靠性和业务数据的私密性;缺点就是上下文会比较长时间比较慢,因为每次都会查都会查库,并且携带的上下午比较多
Document Loader 文档加载器
实际业务中文档的形式五花八门,LangChain 也提供了多种加载方案 加载器
需要提前安装 uv add langchain-community
py
from langchain_community.document_loaders import (
TextLoader, # 文本
CSVLoader, # csv
PyPDFLoader, # pdf
JSONLoader, # json
UnstructuredWordDocumentLoader, # word
UnstructuredMarkdownLoader, # markdown
UnstructuredHTMLLoader, # html
DirectoryLoader, # 配合 PythonLoader 处理目录
PythonLoader
)
所有的 Loader 都继承至 BaseLoader 都实现了 load | lazy_load 方法加载文档最终返回一个 list[Docutment] 对象包含 metadata | page_content 属性。
TextLoader
py
from langchain_community import TextLoader
loader = TextLoader(file_path=f"{assets_dir}/01.txt", encoding="utf-8")
rprint(loader.load())
# [
# Document(
# metadata={'source': '/Users/a1234/Documents/at-guigu-ai/assets/01.txt'},
# page_content='Ant Design 设计规范和技术上支持灵活的样式定制,以满足业务和品牌上多样化的视觉需求,包括但不限于全局样式(主色、圆角、边框)和指定组件的视觉定制。\n\n自从 5.0版本以来,我们提供了一套全新的定制主题方案。不同于 4.x 版本的 less 和 CSS 变量,有了 CSS-in-JS 的加持后,动态主题的能力也得到了加强,包括但不限于:\n\n1. 支持动态切换主题;\n2. 支持同时存在多个主题;\n3. 支持针对某个/某些组件修改主题变量;\n4. ...'
# )
# ]
CSVLoader
每列数据作为一个 Document 输出
py
from langchain_community import CSVLoader
loader = CSVLoader(file_path=f"{assets_dir}/01.csv")
rprint(loader.load())
# [
# Document(metadata={'source': '.../assets/01.csv', 'row': 0}, page_content='标题1: 1行1列\n标题2: 1行2列'),
# Document(metadata={'source': '.../assets/01.csv', 'row': 1}, page_content='标题1: 2行1列\n标题2: 2行2列'),
# Document(metadata={'source': '.../assets/01.csv', 'row': 2}, page_content='标题1: 3行1列\n标题2: 3行2列'),
# Document(metadata={'source': '.../assets/01.csv', 'row': 3}, page_content='标题1: 4行1列\n标题2: 4行2列')
# ]
JSONLoader
接收一个 json_schema 参数加载数据中指定数据,通过 jq 实现
py
from langchain_community import JSONLoader
loader = JSONLoader(
file_path=f"{assets_dir}/01.json",
jq_schema=".", # . 表示提取所有
text_content=False # 是否提取字符串默认 True, 只有提取某个字段时才不需要显示指定
)
rprint(loader.load())
# .data 表示提取根对象下的 data 属性引用类型或者值类型
# .list[] 表示提取根对象下的 list 列表包含内容的所有属性
# .list[].content 表示提取 list 列表中的所有 content 属性
PyPDFLoader
依赖 pypdf 实现,常用参数有
mode: single 或 page 决定加载出来文档的个数默认 pageextraction_mode: plain 提取文本也是默认值; layout 布局感知会插入大量的空格password加密 pdf 的密码
py
from langchain_community import PyPDFLoader
loader = PyPDFLoader(f"{assets_dir}/xxx.pdf", mode="single")
rprint(loader.load())
UnstructuredWordDocumentLoader
提取非结构化数据依赖 unstructured 常用参数有
mode: single 或 elements,决定按标题加载还是整个一块且,默认 singlestrategy: fast 快速模型不纠结布局,hi_res 高速模型
还需要安装 python-docx
如果运行仍然报错,参考信息继续安装内容,总结处理起来很麻烦
bash
FileNotFoundError: soffice command was not found. Please install libreoffice on your system and try again.
- Install instructions: https://www.libreoffice.org/get-help/install-howto/
- Mac: https://formulae.brew.sh/cask/libreoffice
- Debian: https://wiki.debian.org/LibreOffice
py
from langchain_community import UnstructuredWordDocumentLoader
loader = UnstructuredWordDocumentLoader(
f"{assets_dir}/01.doc",
mode="elements",
strategy="fast"
)
rprint(loader.load())
UnstructuredMarkdownLoader
依赖 markdown
py
from langchain_community import UnstructuredMarkdownLoader
loader = UnstructuredMarkdownLoader(
f"{assets_dir}/01.md",
mode="single" # elements 标题
)
rprint(loader.load())
UnstructuredHTMLLoader
py
from langchain_community import UnstructuredHTMLLoader
loader = UnstructuredHTMLLoader(
f"{assets_dir}/01.html",
mode="single" # elements | paged 拥有更丰富的 metadata 包含 filename type 等
)
rprint(loader.load())
DirectoryLoader、PythonLoader
py
from langchain_community import DirectoryLoader, PythonLoader
loader = DirectoryLoader(
f"{assets_dir}",
glob="*", # 提取的正则表达式
use_multithreading=True, # 是否开启多线程
show_progress=True, # 是否在控制台显示进度
loader_cls=PythonLoader # 指定加载器, PythonLoader 本质就是读文件可以切换其他
)
rprint(loader.load())
Text Splitter 文本切割
太大的 Document 会影响模型的判断增加不必要的消耗,根据策略将语料进一步拆分为一个个的 Chunk 可以减少内容噪音和减少 token 消耗。
- 根据句子 按照自然语句的边界切分,保证语义的完整性
- 固定字符数 可能会把语义给搞坏掉
- 固定字符数 + 重叠窗口 切分的前后各多保留一些内容,避免切分关键内容,确保信息的连贯性
- 递归字符 通过递归字符的方式,动态确认切分点,可以根据文档的复杂度和信息密度调整 chunk 的大小
- 根据语义内容 保证相关内容的语义和完整性,不过需要额外引入一个模型去切分
4、5切分方案比较推荐,5 不能保证 chunk 的大小,切效率不高。 切分方案在线测试
LangChain 切分方案实例,切分类都基于 TextSplitter 实现,需要额外安装 uv add langchain-text-splitters
常用参数
chunk_size默认 4000,切分的大小chunk_overlap默认 200,chunk 之间的重叠的字符数length_function默认len函数 测量给定块长度的函数keep_separator默认 false,可选bool | 'start' | 'end'是否保留分隔符及应该放在哪个位置add_start_index默认 false 是否在元数据中包含索引strip_whitespace默认 true 是否去除开头和结尾的空格

常用方法
py
def split_text(self, text: str) -> list[str]:
def create_documents(
self, texts: list[str], metadatas: list[dict[Any, Any]] | None = None
) -> list[Document]:
def split_documents(self, documents: Iterable[Document]) -> list[Document]:
# 实际上内部的实现,也是自己在调用
# split_documents -> create_documents -> split_text
CharacterTextSplitter 字符切分
separator 存在优先原则,当指定分隔符时,会优先考虑分隔符切割其实才是 chunk_size 避免句子中间硬性切断,chunk_size 过小会导致 chunk_overlap 失效
额外参数 separator 分隔符,默认 \n\n
py
from langchain_text_splitters import CharacterTextSplitter
loader = TextLoader(file_path=f"{assets_dir}/01.txt", encoding="utf-8")
splitter = CharacterTextSplitter(
chunk_size=50, # 50 字符切割
chunk_overlap=5, # chunk 之间的重复字符数
separator="" # 空表示不使用分隔符
)
texts = splitter.split_text(loader.load()[0].page_content)
rprint(texts)
# [
# 'Ant Design 设计规范和技术上支',
# '和技术上支持灵活的样式定制,以满足业务和',
# '满足业务和品牌上多样化的视觉需求,包括但',
# '求,包括但不限于全局样式(主色、圆角、边',
# '、圆角、边框)和指定组件的视觉定制。',
# ]
RecursiveCharacterTextSplitter 递归字符切分
遵循先拆分后合并的方案
拆分 先按照分隔符列表的顺序,从第一个分隔符开始切分,切分后如果大于 chunk_size 就继续按照第二个分隔符继续拆分,直到所有内容都小于 chunk_size
合并 遍历 chunk_list,从 当前 chunk + 1 开始进行合并
- 无 overlap
- 如果
当前块大小 + 下一个碎片大小 ≤ chunk_size → 合并,继续 - 如果
当前块大小 + 下一个碎片大小 > chunk_size → 保存当前块,以下一个碎片作为新块的起点,继续遍历
- 如果
- 有 overlap 逻辑同上,但 "保存当前块" 时
- 从当前块的末尾取出
chunk_overlap长度的内容,新块 = overlap 内容 + 下一个碎片 - 如果
新块大小 > chunk_size→ 递归切分下一个碎片继续遍历
- 从当前块的末尾取出
常用参数
separator默认值["\n\n", "\n", " ", ""]keep_separator默认 True 是否保留句尾标点(如 ......),避免切割后丢失语气和逻辑
py
from langchain_text_splitters import RecursiveCharacterTextSplitter
loader = TextLoader(file_path=f"{assets_dir}/01.txt", encoding="utf-8")
splitter = RecursiveCharacterTextSplitter(
chunk_size=20,
chunk_overlap=10,
separators=["...", "\n\n", "\n", ""],
keep_separator=True
)
texts = splitter.split_text(loader.load()[0].page_content)
代码切分
py
from langchain_text_splitters import RecursiveCharacterTextSplitter, Language
# Language 支持的语言种类
# ['cpp', 'go', 'java', 'kotlin', 'js', 'ts', 'php', 'proto', 'python', 'rst', 'ruby', 'rust', 'scala', 'swift',
# 'markdown', 'latex', 'html', 'sol', 'csharp', 'cobol', 'c', 'lua', 'perl', 'haskell', 'elixir', 'powershell']
RecursiveCharacterTextSplitter.from_language(
language=Language.PYTHON,
chunk_size=50,
chunk_overlap=0
)
TokenTextSplitter
encoding_name 是指定 token 编码器,将文本切分为 token 序列 tiktoken -> model.py 中有很多类型支持
py
from langchain_text_splitters import TokenTextSplitter
docs = TextLoader(file_path=f"{assets_dir}/01.txt", encoding="utf-8").load()
splitter = TokenTextSplitter(encoding_name="o200k_base", chunk_size=20, chunk_overlap=0)
rprint(splitter.split_documents(docs))
CharacterTextSplitter 也支持使用 token 切分
py
splitter = CharacterTextSplitter.from_tiktoken_encoder(
encoding_name="o200k_base",
chunk_size=20,
chunk_overlap=0,
separator="。"
)
splitter.split_documents(docs)
SemanticChunker 语义切割
需要安装 uv add langchain-experimental 需要使用向量模型将文本转换为向量,当发现前后语义差异过大时就切分
py
from langchain_experimental.text_splitter import SemanticChunker
from langchain.embeddings import init_embeddings
embed_model = init_embeddings("ollama:qwen3-embedding:0.6b")
docs = TextLoader(file_path=f"{assets_dir}/01.txt", encoding="utf-8").load()
splitter = SemanticChunker(
# 向量模型
embeddings=embed_model,
# 断点阈值类型 "percentile" 默认值, "standard_deviation", "interquartile", "gradient"
# 百分位数,标准差,四分位距,梯度 每个默认值对应的有 breakpoint_threshold_amount 可以不指定这个参数
breakpoint_threshold_type="percentile",
breakpoint_threshold_amount=65.0,
sentence_split_regex=r"(?<=[。?!])\s+" # 匹配到正则规则时,先按照正则进行拆分
)
splitter.split_documents(docs)
HTMLHeaderTextSplitter
py
from langchain_text_splitters import HTMLHeaderTextSplitter
docs = TextLoader(file_path=f"{assets_dir}/01.html", encoding="utf-8").load()
splitter = HTMLHeaderTextSplitter(headers_to_split_on=[("h1", "切分的依据会放进 metadata 中"),])
splitter.split_text(docs[0].page_content)
MarkdownTextSplitter
py
from langchain_text_splitters import MarkdownTextSplitter
docs = TextLoader(file_path=f"{assets_dir}/01.md", encoding="utf-8").load()
splitter = MarkdownTextSplitter(chunk_size=50, chunk_overlap=0)
splitter.split_documents(docs)
向量模型 init_embeddings
通过向量模型将文本等内容转换为向量;不同的模型有不同的维度,比如 qwen3-embeddings 最大支持 4096,可以理解拆分的维度越高,匹配时得到的信息越精准
定义模型的方式和 init_chat_model 一致
py
from langchain.embeddings import init_embeddings
# 因为下载了 langchain-ollama 所以不用手动加载环境变量
# api_key, base_url 如果第三方模型仍然需要传递
embed_model = init_embeddings(
"ollama:qwen3-embedding:0.6b",
dimensions=1024 # 指定维度
)
提供两个方法 embed_query | embed_documents 分别是字符串向量化和列表字符串向量化,返回小数列表。
py
text = "What was the name mentioned in the conversation?"
vector = embed_model.embed_query(text)
# 1024 [-0.02478226, -0.063406475, -0.0063675, -0.038862742, -0.044847116]
print(len(vector), vector[:5])
text = ["What was the name mentioned in the conversation?"]
vectors = embed_model.embed_documents(text)
# 1024 [-0.02478226, -0.063406475, -0.0063675, -0.038862742, -0.044847116]
print(len(vectors[0]), vectors[0][:5])
向量数据库 Milvus
世面很多支持向量的数据库 FAISS | Chroma | Milvus | Pgvector | Redis | Elasticsearch | Pinecone
Milvus 比较纯粹,专门为了向量存储设计云原生数据库,轻量级高并发都能用 官网
个人测试可以使用 lite 版本 无需安装多余的服务 uv add "pymilvus[milvus-lite]" 类似于 SQLLite
与关系型数据库不同的是 Database -> Collection -> Partition -> Entity
Collection集合,与 mongoDB 类似也可以看做是 tablePartition分区,Collection的子集,不是必须手动创建,每个Collection都有默认Entity数据,可以看做 table 中的 row
DDL 数据库操作
初始化及数据库的操作
list_databases列出所有的数据库,默认会有一个['default']create_database创建数据库drop_database删除数据库use_database切换数据库
py
from pymilvus import MilvusClient
# uri: str = "http://localhost:19530",
# user: str = "",
# password: str = "",
# db_name: str = "",
# token: str = "",
client = MilvusClient("./milvus-demo.db") # 生成 milvus_demo.db 文件夹
db_name = "rag_vector"
if db_name not in client.list_databases():
client.create_database(db_name=db_name)
# 数据库存在 collection 则无法直接删除,需要先删除 collection
client.drop_database(db_name=db_name)
client.use_database(db_name=db_name)
集合操作
list_collections列表所有集合create_collection创建集合has_collection是否包含某个集合drop_collection删除集合describe_collection查看集合元数据get_collection_stats获取集合统计信息load_collection加载集合到内容,不然无法做后续的 curd
py
col_name = "docs"
# 列出所有集合
if col_name not in client.list_collections():
# 创建集合
client.create_collection(
collection_name=col_name, # 集合名称
dimension=1024, # 嵌入向量维度,注意要和模型的维度保持一致
metric_type="COSINE" # 向量相似度计算方式,默认余弦 COSINE
)
# 如果存在集合就删除 has_collection, drop_collection
if client.has_collection(collection_name=col_name):
client.drop_collection(collection_name=col_name)
# 查看 collection 元数据
metadata = client.describe_collection(collection_name=col_name)
# {
# 'collection_name': 'docs',
# 'auto_id': False,
# 'num_shards': 1,
# 'description': '',
# 'fields': [
# id 主键,vector 数据嵌入向量
# {'field_id': 0, 'name': 'id', 'description': '', 'type': <DataType.INT64: 5>, 'params': {}, 'is_primary': True},
# {'field_id': 0, 'name': 'vector', 'description': '', 'type': <DataType.FLOAT_VECTOR: 101>, 'params': {'dim': 1024}}
# ],
# 'functions': [],
# 'aliases': [],
# 'collection_id': 0,
# 'consistency_level': 0,
# 'consistency_level_name': 'Strong',
# 'properties': {},
# 'num_partitions': 1,
# 'enable_dynamic_field': True, 支持除 id 和 vector 外动态字段
# 'enable_namespace': False,
# 'schema_version': 0
# }
DML 数据库操作
创建数据有 insert | upsert 后者再插入同样数据时会先删除旧的再插入新的
py
texts = [
"LangChain 是一个用于构建 LLM 应用的开发框架。",
"RAG 的核心是先检索相关知识,再让大模型生成答案。",
"Milvus 全球领先的开源向量数据库项目之一"
]
# 使用模型转向量
vectors = embed_model.embed_documents(texts)
data = [
{
"id": i, # 主键 id
"vector": vectors[i], # 向量数据
"text": texts[i] # 自定义数据
} for i in range(len(texts))
]
client.upsert(collection_name=col_name, data=data)
# 保证数据落盘
client.flush(collection_name=col_name)
# 获取 collection 的统计信息 output: {'row_count': 3}
stats = client.get_collection_stats(collection_name=col_name)
DQL 数据库操作
查询可以使用 get | search | query | query_iterator,共同支持 output_fields 参数
get用于ids查询query支持ids | filterquery_iterator支持filter | limit | batch_sizesearch支持filter | limit | data
py
# 查询之前需要先 load 一下
client.load_collection(collection_name=col_name)
# ===== query 精确查询 =====
result: list[dict] = client.query(
collection_name=col_name,
filter="id == 1", # 过滤条件 空表示所有,支持表达式
output_fields=["*"], # 输出的字段 * 表示所有
ids=["1"], # 主键 id 查找
)
for i in result:
print(i["id"], i["vector"][:2], i["text"])
# 0 [0.025912750512361526, 0.05833195894956589] LangChain 是一个用于构建 LLM 应用的开发框架。
# 1 [-0.060063816606998444, -0.03789394721388817] RAG 的核心是先检索相关知识,再让大模型生成答案。
# 2 [-0.04220530390739441, -0.013182224705815315] Milvus 全球领先的开源向量数据库项目之一
# ===== query_iterator 迭代器返回 =====
result: QueryIterator = client.query_iterator(
collection_name=col_name,
output_fields=["text"], # 查 text 字段
limit=2,
batch_size=1000,
filter=""
)
print(result.next())
# [{'id': 0, 'text': 'LangChain 是一个用于构建 LLM 应用的开发框架。'}, {'id': 1, 'text': 'RAG 的核心是先检索相关知识,再让大模型生成答案。'}]
# ===== get 按 id 查 =====
result: list[dict] = client.get(
collection_name=col_name,
output_fields=["*"],
ids=["1"],
)
# ===== search 按向量相似度查 =====
data: list[float] = embed_model.embed_query("什么是向量数据库?")
result: list[list[dict]] = client.search( # 返回二维数组
collection_name=col_name,
data=[data], # 查询数据 传递二维数组
output_fields=["text"],
limit=2,
filter=""
)
# [[
# {
# 'id': 2,
# 'distance': 0.7010695934295654, 向量相似度
# 'entity': {'id': 2, 'text': 'Milvus 全球领先的开源向量数据库项目之一'}
# },
# {
# 'id': 1,
# 'distance': 0.46880030632019043,
# 'entity': {'id': 1, 'text': 'RAG 的核心是先检索相关知识,再让大模型生成答案。'}
# }
# ]]
删除使用 delete 方法,支持 ids | filter 参数
py
client.delete(
collection_name=col_name,
ids=["1"]
)
client.query(collection_name=col_name, output_fields=["text"])