RAG23-RAG28

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‑8
    • csv_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)

三、运行注意事项

  1. 目录结构:

    P3_LangChainRAG开发
    ├─ data
    │ └─ stu.csv
    └─ 22CSVLoader的使用.py

  2. 安装依赖

    pip install langchain langchain-community langchain-core

  3. stu.csv 示例内容

    name,age,gender
    王梓涵,25,男
    刘若曦,22,女
    陈俊宇,20,男
    赵思瑶,28,女
    黄浩然,15,男
    林雨桐,20,女
    周博文,20,男
    吴诗琪,24,女
    马子轩,22,男
    孙悦然,27,女

  4. 输出效果:每一行 csv 生成一个 Document 对象,metadata包含source文件路径和row行号。

四、面试简答速记

  1. Document 有哪两个核心属性?
  • page_content:文档正文;metadata:元数据字典。
  1. load()lazy_load()区别?
  • load():一次性加载全部文档返回列表,小文件;大文件会内存溢出。
  • lazy_load():生成器流式迭代读取,边读边处理,适合大文件,避免内存爆掉。
  1. 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 文件格式

  1. 普通对象 stu.json

    {
    "name": "周杰伦",
    "age": 11,
    "hobby": ["唱", "跳", "RAP"],
    "other": {
    "addr": "深圳",
    "tel": "12332112321"
    }
    }

  2. 数组 JSON stus.json :外层[]包裹,内部多个对象

    [
    {"name": "周杰伦", "age": 11, "gender": "男"},
    {"name": "蔡依临", "age": 12, "gender": "女"},
    {"name": "王力宏", "age": 11, "gender": "男"}
    ]

  3. JsonLines 文件 stu_json_lines.jsonjson_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_contentTrue:把抽取内容转字符串放到page_contentFalsepage_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)

四、重点总结

  1. RAG25必须安装依赖 pip install jq,否则报错
  2. jq_schema是核心,控制提取哪些内容;.代表根,.[]遍历数组
  3. json_lines=True专门处理每行一个独立 JSON的文件,不要用于普通数组 json
  4. text_content=Falsepage_content是字典;True会自动序列化为字符串,RAG 场景一般保持默认 True
  5. load()返回List[Document],每个 Document 包含page_contentmetadata(会自带 source、seq_num 元数据)

五、常见坑

  1. jq 语法写错,返回空文档;注意数组下标从 0 开始
  2. 混淆普通数组 json 和 json‑lines 文件,数组 json 不要设置json_lines=True
  3. 文件路径错误,相对路径注意 py 文件执行位置
  4. 没有安装 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官方默认推荐的分割器,开箱即用效果最好。

核心特点

  1. 递归按照separators分隔符列表依次切割,优先按换行、句号等自然语义切割,兼顾上下文完整性 + 控制分片大小
  2. chunk_size:每个分片最大字符数
  3. chunk_overlap:分片之间重叠字符,解决被切到句子中间丢失上下文的问题。
  4. separators:分割优先级列表,优先用前面符号切割,切完还超长度就用下一个符号。
  5. length_function=len:使用 len 函数统计字符长度。
  6. 方法:.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)

知识点总结

  1. TextLoader :只负责读取 txt,输出单个 Document 对象的列表,不做分片。
  2. RecursiveCharacterTextSplitter :RAG 开发最常用分片工具
    • 按自然语义递归切割,不是暴力按字符截断
    • chunk_overlap重叠很关键:保证语义不会因为切割丢失
    • separators 有优先级,优先用列表靠前的符号分割
  3. 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 的访问密码

两种加载方法

  1. .load():一次性全部加载,返回List[Document],小文件用;大 PDF 会占用较多内存。
  2. .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']}")

重点总结

  1. mode="page":一页对应一个 Document,metadata 自带页码,方便溯源;single模式全部合并成 1 个 Document。
  2. .load():一次性加载;.lazy_load()惰性迭代读取,大 PDF 优先用 lazy_load,降低内存占用
  3. 局限:只能解析可复制文本的 PDF;扫描件、图片型 PDF 无法提取文字,此时要用 PyMuPDFLoader / UnstructuredLoader 或者 OCR 工具
  4. 项目流程: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 向量存储完整流程

分为两大阶段

  1. 索引 / 存储阶段(把文档入库) 原始文档 → Embedding嵌入模型 → 嵌入向量 → 存入向量数据库
  2. 查询 / 检索阶段(用户提问) 用户查询文本 → Embedding嵌入模型 → 查询向量 → 相似度搜索 → 返回Top‑k最相似文档

通俗理解: 把一堆文档变成一堆数字数组(向量)存起来;用户提问,也转成向量,计算和库里哪个向量最接近,把对应的原文拿出来给大模型参考。

二、LangChain 向量存储统一 3 个接口(所有向量库都有这 3 套方法)

  1. add_documents(documents, ids):新增文档,指定 id
  2. delete(ids=[...]):根据 id 删除文档
  3. 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 重点注意

  1. 持久化:关闭程序,向量还保存在磁盘,再次运行代码不需要重新 add_documents,直接 new Chroma 读取文件夹就可以检索。
  2. 如果重复执行add_documents,会重复插入相同文档,产生重复向量。

五、内存版 vs Chroma 对比表

表格

对比项 InMemoryVectorStore(内存) Chroma(磁盘持久化)
数据存储位置 内存 本地 sqlite 磁盘文件
程序重启 数据全部丢失 数据保留
适用场景 课程练习、demo 快速测试 本地 RAG 项目开发
接口方法 add_documents / delete / similarity_search 完全一模一样的 API

✨LangChain 设计思想:两套向量存储对外 API 完全统一,你写好业务代码,想切换存储,只需要改初始化那一行,后面增删查代码不用改动。

六、常见看不懂的知识点通俗解释

  1. 什么是 Embedding 向量? 一段文字,交给嵌入模型,输出一串浮点数数字数组,语义越接近的文本,向量数字越接近。相似度搜索本质在计算向量之间的距离。
  2. k 参数是什么? similarity_search(xxx,k=3),k 就是我要从向量库里,拿出语义匹配度最高的 k 条文档给大模型做参考。k 不能太大,会把无关内容带进来。
  3. ids 参数干嘛用? 每一条文档给一个唯一字符串 id,后续想删除哪条数据,直接传 id 列表,就可以精准删除对应向量和文本。
  4. Document 对象是什么? LangChain 封装文档对象:page_content存正文,metadata存附加信息(来源、行号),所有 loader 加载完文件输出都是list[Document]
  5. CSVLoader 参数source_column csv 文件有一列叫 source,这一列的值自动保存到 Document 的 metadata,用来标记这条数据来自哪里。

七、踩坑清单

  1. InMemoryVectorStore:跑完程序数据就没了,不要拿来做正式项目。
  2. Chroma 如果重复运行 add_documents,会重复导入,产生重复向量。
  3. DashScopeEmbeddings 需要配置环境变量DASHSCOPE_API_KEY,否则向量化会报错。
  4. csv 文件必须保存 utf‑8 编码,否则 CSVLoader 读取中文乱码。

八、面试简答(巩固)

Q:InMemoryVectorStore 和 Chroma 有什么区别? A:InMemoryVectorStore 数据存内存,程序退出丢失,适合练习;Chroma 持久化到本地 sqlite 磁盘,重启数据保留。LangChain 提供统一接口,增删检索代码完全一致。
Q:similarity_search 的 k 参数含义? A:k 代表返回相似度最高的文档数量。
Q:向量存储在 RAG 中起到什么作用? A:存储文档对应的 embedding 向量;用户提问时把提问文本转为向量,做相似度检索,召回相关原文,交给大模型参考生成答案。

RAG28、课程:向量检索构建提示词

课程核心理解(通俗大白话)

这就是极简版 RAG 完整流程

  1. 先把一些参考文档放到内存向量库 InMemoryVectorStore(内存版,程序关掉数据就消失,不用磁盘文件,适合上课练习)
  2. 用户输入问题 怎么减肥?
  3. 向量库做相似度检索,拿出最相关的 2 条参考文本
  4. 把检索出来的参考资料塞到 Prompt 模板的{context}占位符,用户问题塞到{input}占位符
  5. 交给大模型:你只能参考我给你的资料来回答,不要瞎编
  6. 通过 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 在这里完整执行步骤

  1. 向向量库写入知识库文本
  2. 用户提问
  3. similarity_search检索获取相关文档片段
  4. 循环遍历检索结果,拼接成reference_text参考上下文
  5. 提示词模板有两个占位符
    • {context}:放向量库检索出来的参考资料
    • {input}:放用户真实问题
  6. LCEL 链路:prompt | print_prompt | model | StrOutputParser()
    • prompt:填充占位符生成完整消息
    • print_prompt自定义中间函数:打印中间 prompt(调试神器,看你到底传给模型什么文字)
    • model:通义千问大模型
    • StrOutputParser():把 ChatMessage 对象提取成普通字符串
  7. 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}

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. 常见踩坑点

  1. 忘记配置DASHSCOPE_API_KEY环境变量,运行 Embedding 直接报错
  2. similarity_search(k=2) k 不能大于向量库里面文档总条数
  3. invoke 字典 key 名必须和模板占位符名字完全一致,大小写敏感
  4. InMemoryVectorStore 重启代码,必须重新执行add_texts添加数据,内存清空了

6. 本节课 RAG 核心逻辑(简答题,面试常考)

  1. 向量存储检索匹配相关参考信息
  2. 将用户提问、检索出来的参考资料一起填入提示词模板
  3. 将组装完整提示词交给大模型得到回答

核心思想:不修改模型权重,外部给资料,让模型基于给的资料回答

运行前确认

  1. 环境变量配置好 DASHSCOPE_API_KEY

  2. 已经安装依赖包

    pip install langchain langchain-community langchain-core dashscope

相关推荐
武子康1 小时前
单卡 A6000 跑 27B FP8:我把“能跑”拆成了五组证据
人工智能·llm·agent
网络工程小王2 小时前
【LLM开发实验】 QLoRA实现原理及实战
人工智能·深度学习·机器学习·llm·qlora
苹果二3 小时前
【案例说明】融合知识图谱、大语言模型和 AI Agent 完成能源领域知识工程工作
llm·知识图谱·aiagent·能源领域
liulilittle3 小时前
长上下文的成本结构与「甜点区间」——从推理引擎的物理约束看 200K/256K/400K
c++·人工智能·ai·llm·注意力·qkv
Geek-Chow3 小时前
12 开源 vs 闭源:同一份权重,两种交付
人工智能·llm·大语言模型
Roadinforest5 小时前
Claude Code 架构深度解析:从 Agent Loop 到 Tool、MCP 与 Context
ai·架构·llm·agent·anthropic·claudecode
卷无止境5 小时前
寻找不花钱的AI算力,看这一篇就够了
llm
tachibana26 小时前
把RAGAS跑起来
数据库·人工智能·ai·架构·大模型·llm·rag
武子康6 小时前
DeepSeek Harness 为什么不用 messages 数组保存一切
人工智能·llm·agent