RAG23LangChain 文档加载器 DocumentLoader & CSVLoader 笔记
一、核心概念
1. 文档加载器
- 作用:提供标准接口 ,读取 PDF/CSV/JSON 等不同来源数据,统一转为 LangChain 的
Document对象,做到一致化处理。 - 所有加载器(内置 / 自定义)都继承实现
BaseLoader接口。
2. Document 类(文档统一载体)
所有文档加载器最终返回
Document实例
from langchain_core.documents import Document
doc = Document(
page_content="Hello, world!", # ✅文档正文内容
metadata={"source": "https://example.com"} # ✅元数据,字典格式,记录来源、行号等信息
)
page_content:文档文本内容metadata:元数据,记录来源文件、行号、页码等附加信息
3. BaseLoader 的两个核心方法
表格
| 方法 | 说明 | 适用场景 |
|---|---|---|
load() |
一次性全部加载,返回 List[Document] |
小文件;全部读到内存 |
lazy_load() |
延迟流式加载,返回生成器,for 循环迭代获取 | 超大文件,防止内存溢出 |
所有加载器都具备这两个方法。
4. CSVLoader
专门用来读取 csv 文件,每一行 csv 生成一个Document对象。
- 每一行 csv → 一个 Document 实例
page_content:该行各个字段拼接文本metadata:记录文件 source、row 行号- 关键参数:
file_path:文件路径encoding:文件编码,一般utf‑8csv_args:字典,底层传给 python csv 库,可配置分隔符delimiter、引号quotechar、无表头时指定fieldnames字段名
⚠️
fieldnames:无表头 csv 才使用;如果 csv 自带表头,不要配置,否则会把表头当成数据行。
二、完整可运行代码
示例 1:基础 CSVLoader load () 一次性加载
from langchain_community.document_loaders.csv_loader import CSVLoader
# 1. 创建加载器
loader = CSVLoader(
file_path="./data/stu.csv",
encoding="utf-8"
)
# 2. load()一次性全部加载到内存,返回Document列表
documents = loader.load()
# 遍历打印,查看每一个Document对象
for doc in documents:
print(type(doc))
print("page_content:\n", doc.page_content)
print("metadata:", doc.metadata)
print("-" * 50)
示例 2:自定义 csv 解析参数 csv_args
from langchain_community.document_loaders.csv_loader import CSVLoader
loader = CSVLoader(
file_path="./data/stu.csv",
encoding="utf-8",
csv_args={
"delimiter": ",", # 指定字段分隔符
"quotechar": '"', # 字符串包裹引号
# "fieldnames": ["name","age","gender"] # 无表头csv才打开这个配置
}
)
documents = loader.load()
for d in documents:
print(d)
示例 3:lazy_load () 流式大文件加载(生成器,节省内存)
from langchain_community.document_loaders.csv_loader import CSVLoader
loader = CSVLoader(
file_path="./data/stu.csv",
encoding="utf-8"
)
# lazy_load 返回生成器,不会一次性全部载入内存,适合超大csv
for doc in loader.lazy_load():
print(doc.page_content)
print(doc.metadata)
print("="*40)
三、运行注意事项
-
目录结构:
P3_LangChainRAG开发
├─ data
│ └─ stu.csv
└─ 22CSVLoader的使用.py -
安装依赖
pip install langchain langchain-community langchain-core
-
stu.csv 示例内容
name,age,gender
王梓涵,25,男
刘若曦,22,女
陈俊宇,20,男
赵思瑶,28,女
黄浩然,15,男
林雨桐,20,女
周博文,20,男
吴诗琪,24,女
马子轩,22,男
孙悦然,27,女 -
输出效果:每一行 csv 生成一个 Document 对象,
metadata包含source文件路径和row行号。
四、面试简答速记
- Document 有哪两个核心属性?
page_content:文档正文;metadata:元数据字典。
load()和lazy_load()区别?
load():一次性加载全部文档返回列表,小文件;大文件会内存溢出。lazy_load():生成器流式迭代读取,边读边处理,适合大文件,避免内存爆掉。
- CSVLoader 读取 csv,一行 csv 对应几个 Document? 一行 csv 生成一个 Document 对象。
官方文档地址:https://docs.langchain.com/oss/python/integrations/document_loaders
RAG24、LangChain JSONLoader 笔记
一、概述
JSONLoader:把 JSON 文件加载为 LangChain 的Document文档对象,用于 RAG 知识库构建。
⚠️ 依赖第三方库 jq,需要手动安装
pip install jq
底层依靠 jq 做 JSON 解析,通过jq_schema语法抽取 JSON 里面的数据。
jq_schema 抽取语法规则
表格
| 语法 | 含义 |
|---|---|
. |
代表 JSON 根对象 |
[] |
代表数组 |
.name |
抽取根下 name 字段 |
.hobby[1] |
抽取 hobby 数组的第 2 个元素(下标从 0 开始) |
.other.addr |
嵌套对象取值,取 other 里面 addr |
.[] |
遍历数组,取出数组中每一个字典对象 |
.[].name |
遍历数组,取出数组每个对象的 name 字段 |
三种 JSON 文件格式
-
普通对象 stu.json
{
"name": "周杰伦",
"age": 11,
"hobby": ["唱", "跳", "RAP"],
"other": {
"addr": "深圳",
"tel": "12332112321"
}
} -
数组 JSON stus.json :外层
[]包裹,内部多个对象[
{"name": "周杰伦", "age": 11, "gender": "男"},
{"name": "蔡依临", "age": 12, "gender": "女"},
{"name": "王力宏", "age": 11, "gender": "男"}
] -
JsonLines 文件 stu_json_lines.json :
json_lines=True,每行一个独立 json 对象,整体没有外层数组{"name": "周杰伦", "age": 11, "gender": "男"}
{"name": "蔡依临", "age": 12, "gender": "女"}
{"name": "王力宏", "age": 11, "gender": "男"}
二、JSONLoader 构造参数
from langchain_community.document_loaders import JSONLoader
loader = JSONLoader(
file_path="xxx.json", # 必填,json文件路径
jq_schema=".", # 必填,jq抽取表达式
text_content=True, # 可选,默认True:抽取结果转为字符串作为page_content;False保留dict对象
json_lines=False # 可选,默认False;True代表文件是JsonLines格式(每行一个json)
)
file_path:文件路径,必填jq_schema:jq 抽取语法,必填text_content:True:把抽取内容转字符串放到page_content;False则page_content为字典对象json_lines:是否是 JsonLines 文件,每行一条 json,默认 False
调用loader.load()返回List[Document]。
三、完整可运行代码
文件目录:
./data/下放三个 json 文件
from langchain_community.document_loaders import JSONLoader
print("====案例1:读取普通对象stu.json,抽取name字段====")
loader1 = JSONLoader(
file_path="./data/stu.json",
jq_schema=".name"
)
docs1 = loader1.load()
for doc in docs1:
print(doc.page_content)
print(doc.metadata)
print("\n====案例2:读取stu.json,抽取hobby数组第二个元素====")
loader2 = JSONLoader(
file_path="./data/stu.json",
jq_schema=".hobby[1]"
)
docs2 = loader2.load()
print(docs2[0].page_content)
print("\n====案例3:读取数组 stus.json,遍历数组取出全部对象====")
loader3 = JSONLoader(
file_path="./data/stus.json",
jq_schema=".[]"
)
docs3 = loader3.load()
for idx, doc in enumerate(docs3):
print(f"{idx+1}: {doc.page_content}")
print("\n====案例4:读取数组 stus.json,只抽取所有name====")
loader4 = JSONLoader(
file_path="./data/stus.json",
jq_schema=".[].name"
)
docs4 = loader4.load()
for doc in docs4:
print(doc.page_content)
print("\n====案例5:读取JsonLines格式 stu_json_lines.json====")
loader5 = JSONLoader(
file_path="./data/stu_json_lines.json",
jq_schema=".",
json_lines=True
)
docs5 = loader5.load()
for doc in docs5:
print(doc.page_content)
四、重点总结
- RAG25必须安装依赖
pip install jq,否则报错 jq_schema是核心,控制提取哪些内容;.代表根,.[]遍历数组json_lines=True专门处理每行一个独立 JSON的文件,不要用于普通数组 jsontext_content=False:page_content是字典;True会自动序列化为字符串,RAG 场景一般保持默认 Trueload()返回List[Document],每个 Document 包含page_content和metadata(会自带 source、seq_num 元数据)
五、常见坑
- jq 语法写错,返回空文档;注意数组下标从 0 开始
- 混淆普通数组 json 和 json‑lines 文件,数组 json 不要设置
json_lines=True - 文件路径错误,相对路径注意 py 文件执行位置
- 没有安装 jq 库直接运行,直接抛出异常
RAG25、课程笔记:TextLoader 和 RecursiveCharacterTextSplitter
1、TextLoader 文本加载器
作用
读取.txt文本文件,把整个文件全部内容封装成 1 个 Document 对象 ,返回 [Document],所以 len(docs)=1。
导入
from langchain_community.document_loaders import TextLoader
- 参数
- 文件路径:
file_path encoding:文件编码,中文 txt 一般填utf-8,防止乱码
- 文件路径:
- 方法
.load():加载文档,返回 Document 列表
注意:TextLoader 不会做文本切分,不管 txt 多大,全部塞进一个 Document,大文件必须配合文本分割器做分片。
2、RecursiveCharacterTextSplitter 递归字符文本分割器
LangChain官方默认推荐的分割器,开箱即用效果最好。
核心特点
- 递归按照
separators分隔符列表依次切割,优先按换行、句号等自然语义切割,兼顾上下文完整性 + 控制分片大小。 chunk_size:每个分片最大字符数chunk_overlap:分片之间重叠字符,解决被切到句子中间丢失上下文的问题。separators:分割优先级列表,优先用前面符号切割,切完还超长度就用下一个符号。length_function=len:使用 len 函数统计字符长度。- 方法:
.split_documents(docs),对 loader 加载出来的 Document 列表做分片,返回多个小 Document。
依赖安装
pip install langchain-text-splitters
完整可运行代码
from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
# 1. 加载txt文本文件
loader = TextLoader(
file_path="./data/Python基础语法.txt",
encoding="utf-8"
)
# 得到完整文档,只有1个Document
docs = loader.load()
print(f"加载后原始文档数量: {len(docs)}")
# 2. 创建递归字符分割器
splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # 每一块最大字符数
chunk_overlap=50, # 块与块重叠字符
# 分割优先级:优先双换行,其次单换行,再句号、感叹号等
separators=["\n\n", "\n", "。", "!", "?", ".", "!", "?", " ", ""],
length_function=len # 使用len统计字符长度
)
# 3. 执行文档切分
split_docs = splitter.split_documents(docs)
print(f"切分之后分片数量:{len(split_docs)}")
# 遍历打印每一个分片
for idx, doc in enumerate(split_docs):
print("=" * 30 + f" 分片{idx+1} " + "=" * 30)
print(doc.page_content)
print("=" * 70)
知识点总结
- TextLoader :只负责读取 txt,输出单个 Document 对象的列表,不做分片。
- RecursiveCharacterTextSplitter :RAG 开发最常用分片工具
- 按自然语义递归切割,不是暴力按字符截断
chunk_overlap重叠很关键:保证语义不会因为切割丢失- separators 有优先级,优先用列表靠前的符号分割
- RAG 标准流水线:
Loader加载文档 → TextSplitter切分文档 → 向量化存入向量库。
面试高频问题
Q:为什么要设置 chunk_overlap? A:防止一个完整语义句子被切分到两个 chunk,检索的时候上下文断裂;分片之间保留一部分重叠文本,保证语义连续性。
Q:RecursiveCharacterTextSplitter 和普通 CharacterTextSplitter 区别? A:普通分割器只用一个分隔符 切割;递归分割器是一组分隔符递归尝试,优先用大粒度分隔符,保证尽可能在语义边界切分,效果更好。
RAG26、课程笔记:PyPDFLoader PDF 文档加载器
1、简介
PyPDFLoader:LangChain 社区提供的 PDF 文件加载器,底层依赖pypdf库,只提取 PDF 里面的文本内容,不能解析图片、扫描版 PDF。
安装依赖
pip install pypdf
导入
from langchain_community.document_loaders import PyPDFLoader
构造参数
表格
| 参数 | 说明 |
|---|---|
file_path |
必填,pdf 文件路径 |
mode |
读取模式:page(默认):每一页生成 1 个 Document 对象 single:把整个 PDF 全部内容合并为1 个 Document |
password |
可选,加密 PDF 的访问密码 |
两种加载方法
.load():一次性全部加载,返回List[Document],小文件用;大 PDF 会占用较多内存。.lazy_load():惰性加载,迭代器 ,一页一页读取,不会一次性把全部内容载入内存,处理大 PDF 推荐使用。
Document 对象的
metadata元数据会自动携带页码信息{"page": 页码}。
完整可运行代码
from langchain_community.document_loaders import PyPDFLoader
# 1.实例化加载器
loader = PyPDFLoader(
file_path="./data/pdf1.pdf",
mode="page", # 默认page模式:一页一个Document
# password="" # 如果pdf加密,填写密码
)
# ----------------方式1:load() 一次性全部加载----------------
docs = loader.load()
print(f"总页数/文档数量:{len(docs)}")
for idx, doc in enumerate(docs):
print("=" * 40 + f" 第{doc.metadata['page']+1}页 " + "="*40)
print(doc.page_content)
# ----------------方式2:lazy_load() 惰性加载(大文件推荐)----------------
i = 0
for doc in loader.lazy_load():
i += 1
print("=" * 30, i)
print(f"页码元数据:{doc.metadata}")
print(doc.page_content)
结合 RecursiveCharacterTextSplitter 完整 RAG 流水线(PDF → 分片)
PDF 一页的文本依然很长,实际 RAG 项目中,PDF 加载完成后,仍然要交给分割器做语义分片
from langchain_community.document_loaders import PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
# 1.加载PDF
loader = PyPDFLoader(file_path="./data/pdf1.pdf")
pdf_docs = loader.load()
# 2.初始化分割器
splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50,
separators=["\n\n", "\n", "。", "!", "?", ".", "!", "?", " ", ""],
length_function=len
)
# 3.对PDF文档做切分
split_docs = splitter.split_documents(pdf_docs)
print(f"原始pdf页数:{len(pdf_docs)},切分后分片总数:{len(split_docs)}")
# 查看分片
for chunk in split_docs[:3]:
print("-"*50)
print(chunk.page_content)
print(f"来源页码:{chunk.metadata['page']}")
重点总结
mode="page":一页对应一个 Document,metadata 自带页码,方便溯源;single模式全部合并成 1 个 Document。.load():一次性加载;.lazy_load()惰性迭代读取,大 PDF 优先用 lazy_load,降低内存占用。- 局限:只能解析可复制文本的 PDF;扫描件、图片型 PDF 无法提取文字,此时要用 PyMuPDFLoader / UnstructuredLoader 或者 OCR 工具。
- 项目流程:
PyPDFLoader加载PDF → RecursiveCharacterTextSplitter分片 → 向量化存入向量库。
面试小问题
Q:
load()和lazy_load()的区别? A:
load():把所有文档全部读到内存,返回完整列表,小文件方便;大 PDF 内存开销大。lazy_load()返回迭代器,按需一页一页读取,不会一次性加载全部内容,内存友好,适合大体积 PDF 文件。
Q:PyPDFLoader 可以处理扫描版 PDF 吗? A:不行。扫描 PDF 本质是图片,没有文本流,PyPDF 拿不到文字,需要 OCR 识别方案。
RAG27、Vector stores 向量存储 课程笔记
这节课是 RAG 的核心,向量存储就是用来保存文本转出来的向量,做相似度检索。分为内存版(InMemoryVectorStore) 和持久化磁盘版(Chroma)
一、RAG 向量存储完整流程
分为两大阶段
- 索引 / 存储阶段(把文档入库)
原始文档 → Embedding嵌入模型 → 嵌入向量 → 存入向量数据库 - 查询 / 检索阶段(用户提问)
用户查询文本 → Embedding嵌入模型 → 查询向量 → 相似度搜索 → 返回Top‑k最相似文档
通俗理解: 把一堆文档变成一堆数字数组(向量)存起来;用户提问,也转成向量,计算和库里哪个向量最接近,把对应的原文拿出来给大模型参考。
二、LangChain 向量存储统一 3 个接口(所有向量库都有这 3 套方法)
add_documents(documents, ids):新增文档,指定 iddelete(ids=[...]):根据 id 删除文档similarity_search(query, k=N):相似度检索,k代表返回几条最相似的结果
k=4:就是取匹配度最高的 4 条文档
三、第一种:内存向量存储 InMemoryVectorStore
✅特点:数据放在内存,程序关闭数据全部丢失,只适合练习、测试,不能生产环境使用。
完整可运行代码
from langchain_core.vectorstores import InMemoryVectorStore
from langchain_community.embeddings import DashScopeEmbeddings
from langchain_community.document_loaders import CSVLoader
# 1.初始化内存向量存储,指定阿里百炼的嵌入模型
vector_store = InMemoryVectorStore(embedding=DashScopeEmbeddings())
# 2.加载csv文档 info.csv
loader = CSVLoader(
file_path="./data/info.csv",
encoding="utf-8",
source_column="source" # metadata里标记数据来源
)
documents = loader.load()
# 3.把文档写入向量库,手动生成id
vector_store.add_documents(
documents=documents,
ids=["id"+str(i) for i in range(1, len(documents)+1)]
)
# 4.根据id删除文档
vector_store.delete(ids=["id1","id2"])
# 5.相似度检索 k=3 返回3条结果
result = vector_store.similarity_search(
query="Python是不是简单易学",
k=3
)
# 打印检索结果
for doc in result:
print("page_content:", doc.page_content)
print("metadata:", doc.metadata)
print("-"*50)
运行输出解读
返回的是list[Document]对象
page_content:原始文本内容metadata:元数据,记录来源、行号 row、文档 id 等信息- exit code 0:代表代码无报错执行成功。
内存版坑点
程序一旦停止运行,内存释放,所有向量全部消失。重启代码,需要重新加载文档、重新向量化。
四、第二种:Chroma 外部持久化向量存储
✅特点:向量数据保存到本地磁盘文件(sqlite 数据库),程序关闭数据不会丢,下次启动可以直接读取,适合做项目开发。
需要先安装依赖
pip install langchain-chroma chromadb
完整可运行代码
from langchain_chroma import Chroma
from langchain_community.embeddings import DashScopeEmbeddings
from langchain_community.document_loaders import CSVLoader
# 1.初始化Chroma向量库
vector_store = Chroma(
collection_name="test", # 集合名,相当于表名
embedding_function=DashScopeEmbeddings(), # 嵌入模型
persist_directory="./chroma_db" # 磁盘文件夹,数据保存在这里
)
# 2.加载csv文档
loader = CSVLoader(
file_path="./data/info.csv",
encoding="utf-8",
source_column="source"
)
documents = loader.load()
# 3.写入向量库
vector_store.add_documents(
documents=documents,
ids=["id"+str(i) for i in range(1, len(documents)+1)]
)
# 4.删除文档
vector_store.delete(ids=["id1","id2"])
# 5.相似度检索
result = vector_store.similarity_search(
query="Python是不是简单易学",
k=3
)
for doc in result:
print(doc.page_content)
print(doc.metadata)
print("-"*50)
Chroma 底层原理
运行之后项目目录生成chroma_db文件夹,里面是chroma.sqlite3数据库文件。
- embeddings 表:存放二进制格式向量数组
- segments 表:存放原始文本片段
可以直接用数据库工具打开 sqlite 文件查看底层表结构。
Chroma 重点注意
- 持久化:关闭程序,向量还保存在磁盘,再次运行代码不需要重新 add_documents,直接 new Chroma 读取文件夹就可以检索。
- 如果重复执行
add_documents,会重复插入相同文档,产生重复向量。
五、内存版 vs Chroma 对比表
表格
| 对比项 | InMemoryVectorStore(内存) | Chroma(磁盘持久化) |
|---|---|---|
| 数据存储位置 | 内存 | 本地 sqlite 磁盘文件 |
| 程序重启 | 数据全部丢失 | 数据保留 |
| 适用场景 | 课程练习、demo 快速测试 | 本地 RAG 项目开发 |
| 接口方法 | add_documents / delete / similarity_search | 完全一模一样的 API |
✨LangChain 设计思想:两套向量存储对外 API 完全统一,你写好业务代码,想切换存储,只需要改初始化那一行,后面增删查代码不用改动。
六、常见看不懂的知识点通俗解释
- 什么是 Embedding 向量? 一段文字,交给嵌入模型,输出一串浮点数数字数组,语义越接近的文本,向量数字越接近。相似度搜索本质在计算向量之间的距离。
- k 参数是什么?
similarity_search(xxx,k=3),k 就是我要从向量库里,拿出语义匹配度最高的 k 条文档给大模型做参考。k 不能太大,会把无关内容带进来。 - ids 参数干嘛用? 每一条文档给一个唯一字符串 id,后续想删除哪条数据,直接传 id 列表,就可以精准删除对应向量和文本。
- Document 对象是什么? LangChain 封装文档对象:
page_content存正文,metadata存附加信息(来源、行号),所有 loader 加载完文件输出都是list[Document]。 - CSVLoader 参数
source_columncsv 文件有一列叫 source,这一列的值自动保存到 Document 的 metadata,用来标记这条数据来自哪里。
七、踩坑清单
- InMemoryVectorStore:跑完程序数据就没了,不要拿来做正式项目。
- Chroma 如果重复运行 add_documents,会重复导入,产生重复向量。
- DashScopeEmbeddings 需要配置环境变量
DASHSCOPE_API_KEY,否则向量化会报错。 - csv 文件必须保存 utf‑8 编码,否则 CSVLoader 读取中文乱码。
八、面试简答(巩固)
Q:InMemoryVectorStore 和 Chroma 有什么区别? A:InMemoryVectorStore 数据存内存,程序退出丢失,适合练习;Chroma 持久化到本地 sqlite 磁盘,重启数据保留。LangChain 提供统一接口,增删检索代码完全一致。
Q:similarity_search 的 k 参数含义? A:k 代表返回相似度最高的文档数量。
Q:向量存储在 RAG 中起到什么作用? A:存储文档对应的 embedding 向量;用户提问时把提问文本转为向量,做相似度检索,召回相关原文,交给大模型参考生成答案。
RAG28、课程:向量检索构建提示词
课程核心理解(通俗大白话)
这就是极简版 RAG 完整流程
- 先把一些参考文档放到内存向量库
InMemoryVectorStore(内存版,程序关掉数据就消失,不用磁盘文件,适合上课练习) - 用户输入问题
怎么减肥? - 向量库做相似度检索,拿出最相关的 2 条参考文本
- 把检索出来的参考资料塞到 Prompt 模板的
{context}占位符,用户问题塞到{input}占位符 - 交给大模型:你只能参考我给你的资料来回答,不要瞎编
- 通过 LangChain 的管道符
|组装 chain 链路,执行拿到回答
重点:
InMemoryVectorStore内存向量库,不会保存到硬盘,程序关闭所有数据清空,只适合学习测试;生产用 Chroma/Milvus。
完整可直接运行代码 28 向量检索构建提示词.py
"""
提示词:用户的提问 + 向量库中检索到的参考资料
极简RAG演示 InMemoryVectorStore内存向量库
"""
from langchain_community.chat_models import ChatTongyi
from langchain_core.vectorstores import InMemoryVectorStore
from langchain_community.embeddings import DashScopeEmbeddings
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
# 1.初始化大模型
model = ChatTongyi(model="qwen3-max")
# 2.构建提示词模板
prompt = ChatPromptTemplate.from_messages(
[
("system", "以我提供的已知参考资料为主,简洁和专业的回答用户问题。参考资料:{context}。"),
("user", "用户提问: {input}")
]
)
# 3.初始化内存向量库,指定阿里embedding向量化模型
vector_store = InMemoryVectorStore(embedding=DashScopeEmbeddings(model="text-embedding-v4"))
# 4.add_texts:把参考文本列表存入向量库(内部自动做embedding)
vector_store.add_texts([
"减肥就是要少吃多练",
"在减脂期间吃东西很重要,清淡少油控制卡路里摄入并运动起来",
"跑步是很好的运动哦"
])
# 用户问题
input_text = "怎么减肥?"
# 5.相似度检索 k=2,取出最匹配的2条文档
result = vector_store.similarity_search(input_text, k=2)
# 6.循环拼接检索出来的文档内容,组装参考上下文
reference_text = "["
for doc in result:
# doc.page_content 就是原始文本内容
reference_text += doc.page_content
reference_text += "]"
# 小工具函数:打印中间生成的prompt,方便调试看传给大模型的完整消息
def print_prompt(prompt_value):
print(prompt_value.to_string())
print("="*20)
return prompt_value
# 7.使用LCEL管道 | 组装执行链
chain = prompt | print_prompt | model | StrOutputParser()
# 8.invoke传入参数字典,填充模板里面两个占位符 context、input
res = chain.invoke({"input": input_text, "context": reference_text})
print("大模型最终回答:")
print(res)
📝课程笔记
1. InMemoryVectorStore 内存向量库
- 特点:全部数据在内存,程序退出全部丢失,不会生成本地文件
- 适合:课堂快速实验,不用管理 chroma_db 文件夹
- 方法:
add_texts(字符串列表):批量存入文本,内部自动调用 Embedding 转为向量similarity_search(query, k=N):相似度检索,返回 Top‑k 最相似的 Document 对象
- 返回值 result 是 Document 对象列表:
doc.page_content:原始文本内容doc.metadata:元数据信息
2. RAG 在这里完整执行步骤
- 向向量库写入知识库文本
- 用户提问
similarity_search检索获取相关文档片段- 循环遍历检索结果,拼接成
reference_text参考上下文 - 提示词模板有两个占位符
{context}:放向量库检索出来的参考资料{input}:放用户真实问题
- LCEL 链路:
prompt | print_prompt | model | StrOutputParser()prompt:填充占位符生成完整消息print_prompt自定义中间函数:打印中间 prompt(调试神器,看你到底传给模型什么文字)model:通义千问大模型StrOutputParser():把 ChatMessage 对象提取成普通字符串
chain.invoke({"input":xxx, "context":xxx})传入字典给两个占位符赋值
3. 容易看不懂的地方拆解
① reference_text = "[" 循环拼接
reference_text = "["
for doc in result:
reference_text += doc.page_content
reference_text += "]"
通俗讲:把检索出来 2 段文字拼在一起,外面包[ ],作为完整参考资料丢进 prompt 的{context}。
② print_prompt自定义函数为什么可以放进管道|
LCEL 管道
|支持放入普通函数,输入是上一步输出,return 给下一级。 作用:调试!运行时控制台打印出完整发给大模型的 prompt 字符串,方便排查为什么模型回答不对。
③ invoke 传参字典
chain.invoke({"input": input_text, "context": reference_text})
模板里面有几个占位符,invoke 字典就要传几个 key。模板写了{context}和{input},字典就必须带上这两个 key。
4. 和 Chroma 向量库区别对比
表格
| 项目 | InMemoryVectorStore | Chroma |
|---|---|---|
| 存储位置 | 内存 | 本地磁盘文件夹 persist_directory |
| 持久化 | ❌关闭程序数据消失 | ✅保存到磁盘,下次运行还在 |
| 适用场景 | 上课快速 demo | 真实 RAG 项目开发 |
5. 常见踩坑点
- 忘记配置
DASHSCOPE_API_KEY环境变量,运行 Embedding 直接报错 similarity_search(k=2)k 不能大于向量库里面文档总条数- invoke 字典 key 名必须和模板占位符名字完全一致,大小写敏感
- InMemoryVectorStore 重启代码,必须重新执行
add_texts添加数据,内存清空了
6. 本节课 RAG 核心逻辑(简答题,面试常考)
- 向量存储检索匹配相关参考信息
- 将用户提问、检索出来的参考资料一起填入提示词模板
- 将组装完整提示词交给大模型得到回答
核心思想:不修改模型权重,外部给资料,让模型基于给的资料回答。
运行前确认
-
环境变量配置好
DASHSCOPE_API_KEY -
已经安装依赖包
pip install langchain langchain-community langchain-core dashscope