Langchain的学习(三)

目录

核心组件(下)

输出解析器

Pydantic

步骤拆解

[1. 定义 Pydantic 模型(定格式)](#1. 定义 Pydantic 模型(定格式))

[2. 创建解析器(绑定模型)](#2. 创建解析器(绑定模型))

[3. 注入提示模板(教模型怎么输出)](#3. 注入提示模板(教模型怎么输出))

[4. 链式调用(一键结构化)](#4. 链式调用(一键结构化))

运行示例

总结

JSON

参数与用法

其他常见输出解析器

特点

文档加载器

RAG

概念

Document文档类

[1. 代码样例](#1. 代码样例)

[2. 属性](#2. 属性)

[3. 关键理解](#3. 关键理解)

[4. 代码扩展:更完整的 metadata](#4. 代码扩展:更完整的 metadata)

[5. 直观比喻](#5. 直观比喻)

[✅ 总结](#✅ 总结)

加载PDF文档

[1. 流程](#1. 流程)

[2. 特性](#2. 特性)

[3. 常见问题与局限](#3. 常见问题与局限)

[✅ 总结](#✅ 总结)

加载Markdown文件

[1. 代码与两种模式](#1. 代码与两种模式)

[2. elements 模式的核心特性](#2. elements 模式的核心特性)

[3. 代码解读](#3. 代码解读)

[4. 支持的元素类型(category 取值)](#4. 支持的元素类型(category 取值))

[5. 与 PyPDFLoader 的对比](#5. 与 PyPDFLoader 的对比)

[✅ 总结](#✅ 总结)

文档分割器

概念

基于字符串长度拆分

参数

工作流程

适用场景

总结

基于token长度拆分

参数解析

关键概念

[为什么用 Token 拆分?](#为什么用 Token 拆分?)

总结

强制长度拆分

[1. 写法一:基于 Token 计数(适配模型)](#1. 写法一:基于 Token 计数(适配模型))

[2. 写法二:基于字符计数(简单直观)](#2. 写法二:基于字符计数(简单直观))

[3. 共同点](#3. 共同点)

[4. 参数对比](#4. 参数对比)

总结

文本向量

嵌入模型

[1. 流程总结](#1. 流程总结)

[2. 每步作用](#2. 每步作用)

[3. 目的](#3. 目的)

向量存储

概念

向量存储是什么?

为什么它能搜得快?

不止是存储:完整的数据管理能力

[向量存储在 RAG 中的位置](#向量存储在 RAG 中的位置)

在线检索

常见向量数据库选型

总结

内存向量存储

CRUD

检索

Redis向量存储

CRUD

检索

Pinecone向量存储

检索器


核心组件(下)

输出解析器

Pydantic

python 复制代码
from typing import Optional
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import PydanticOutputParser
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field

# 1. 定义聊天模型
model = ChatOpenAI(model="gpt-4o-mini")

# 2. 定义 Pydantic 输出结构(约束模型输出格式)
class Joke(BaseModel):
    """给用户讲的一个笑话"""
    setup: str = Field(description="这个笑话的开头")
    punchline: str = Field(description="这个笑话的妙语")
    rating: Optional[int] = Field(default=None, description="从1-10分,给这个笑话评分")

# 3. 定义 Pydantic 解析器
parser = PydanticOutputParser(pydantic_object=Joke)

# 4. 构建提示模板(自动注入格式要求)
prompt = PromptTemplate(
    template="回复用户问题。\n返回结构说明:{format_instructions}\n用户问题:{query}\n",
    partial_variables={"format_instructions": parser.get_format_instructions()},
    input_variables=["query"],
)

# 5. 构建链:提示模板 → 模型 → 解析器
chain = prompt | model | parser

# 6. 调用链并获取结构化结果
result = chain.invoke({"query": "讲一个关于跳舞的笑话"})
print(result)

PydanticOutputParser 是 LangChain 中用于将模型输出解析为 Pydantic 模型对象的工具,核心作用是:

  • 强制模型输出严格结构化的 JSON,和定义的 Pydantic 类完全对齐。
  • 自动把模型返回的 JSON 字符串转成可直接操作的 Python 对象,避免手动解析 JSON。
  • Field(description="...") 给模型明确字段含义,减少输出歧义。

步骤拆解
1. 定义 Pydantic 模型(定格式)
2. 创建解析器(绑定模型)
python 复制代码
parser = PydanticOutputParser(pydantic_object=Joke)
  • 解析器和 Joke 类绑定,知道要把输出解析成这个结构。
  • parser.get_format_instructions():自动生成一段提示词,告诉模型必须返回什么样的 JSON 结构
3. 注入提示模板(教模型怎么输出)
python 复制代码
prompt = PromptTemplate(
    template="回复用户问题。\n返回结构说明:{format_instructions}\n用户问题:{query}\n",
    partial_variables={"format_instructions": parser.get_format_instructions()},
    input_variables=["query"],
)
  • parser.get_format_instructions() 注入提示词,让模型在生成前就知道输出格式。
  • 比如生成的提示会包含:"返回一个JSON对象,包含setup、punchline、rating字段..."
4. 链式调用(一键结构化)
python 复制代码
chain = prompt | model | parser
result = chain.invoke({"query": "讲一个关于跳舞的笑话"})
  • 流程:用户问题 → 提示模板 → 模型生成JSON → 解析器转成Joke对象
  • 最终 result 是一个 Joke 实例,可以直接用 result.setupresult.punchline 访问字段。

运行示例

调用后模型会返回类似 JSON:

python 复制代码
{
  "setup": "为什么跳舞的人不喜欢下雨?",
  "punchline": "因为怕踩到雷(蕾)!",
  "rating": 8
}

解析后 result 就是:

python 复制代码
Joke(
    setup="为什么跳舞的人不喜欢下雨?",
    punchline="因为怕踩到雷(蕾)!",
    rating=8
)

可以直接 print(result.setup) 拿到笑话开头。


总结

格式严格:模型必须按你定义的结构输出,不会乱答。

自动解析:不用手动写 JSON 解析代码,直接用对象访问字段。

语义清晰Field(description) 让模型理解每个字段的用途,减少歧义。

类型安全 :Pydantic 会自动校验类型(比如 rating 必须是 int 或 None),避免格式错误。


with_structured_output 的对比

方式 实现 优点 缺点
PydanticOutputParser 手动写提示 + 解析器 兼容性好,支持所有模型 代码稍多
model.with_structured_output 模型原生结构化输出 代码极简,效果更稳 依赖模型对函数调用 / 结构化输出的支持

JSON

JsonOutputParser 是 LangChain 中专门用于解析 JSON 格式输出的工具,核心作用是将模型返回的 JSON 字符串转换为可操作的 Python 对象,并可通过 Pydantic 进行格式校验。

参数与用法
  • pydantic_object:可选,传入 Pydantic 模型后,解析器会自动验证输出是否符合结构;若为空则仅做 JSON 解析,不校验格式。
  • invoke():将模型输出的 JSON 字符串转换为 Python 字典或 Pydantic 对象。
  • get_format_instructions()关键方法,自动生成格式指令字符串,嵌入提示词中,明确告诉 LLM 必须返回指定结构的 JSON,避免格式混乱。

其他常见输出解析器

除了 JSON 解析器,LangChain 还提供了丰富的结构化输出解析器,覆盖不同格式场景:

解析器类型 类名 适用场景
XML 解析器 XMLOutputParser 需要输出 XML 格式的场景
YAML 解析器 YamlOutputParser 适合配置、层级化数据的 YAML 格式
CSV 解析器 CommaSeparatedListOutputParser 输出逗号分隔的列表 / 表格数据
枚举解析器 EnumOutputParser 限定输出为预定义的枚举值(如分类标签)
日期解析器 DatetimeOutputParser 强制输出为标准日期 / 时间格式
自定义解析器 自定义类 特殊格式需求,可继承基类实现自定义解析逻辑

特点
  1. 格式可控:通过提示词指令 + 解析器校验,保证模型输出严格符合目标格式,避免自由文本。
  2. 自动解析:无需手动写 JSON/XML 解析代码,直接拿到可操作的 Python 对象。
  3. 灵活扩展:支持多种格式,且可自定义解析器,适配复杂业务需求。
  4. 类型安全:结合 Pydantic 可实现字段类型、必填项等校验,提升数据可靠性。

文档加载器

RAG

RAG(Retrieval-Augmented Generation,检索增强生成)是一种 ** 让大模型 "读外部知识库再回答"** 的技术,解决模型 "知识陈旧、信息不准、无法回答私有数据" 的问题。


概念

1. RAG的流程拆解

RAG 的核心逻辑:

  1. 用户提问:比如「我们今年新增加的带薪育儿假政策具体是怎样的?」
  2. 检索知识库 :系统从内部知识库(福利更新备忘录、最新人事制度文档等)里,找出和问题最相关的文档片段
  3. LLM 生成答案 :把检索到的文档片段作为「参考资料」,和用户问题一起喂给大模型,让模型基于这些资料总结出直接答案,而不是只返回文档链接。

2. 解决的痛点

  • 传统检索:只返回相关文档,需要用户自己阅读总结,效率低。
  • 纯大模型:知识截止到训练时间,**无法回答新政策、内部文档这类私有信息,**容易 "胡说八道"(幻觉)。
  • RAG:既利用知识库的准确信息,又用大模型的语言能力生成自然答案,做到 "有据可查、直接可读"。

3. 核心流程(标准 RAG pipeline)

  1. 知识库构建 :把文档(本地PDF/Word/ 文本)切分成小块(以4主题,功能等不同特性进行切分) → 转成向量(调用嵌入模型将其转换成向量) → 存入向量数据库。
  2. 检索阶段 :用户问题转成向量 → 在向量库中相似度检索,找出最相关的文档片段。
  3. 生成阶段:将「问题 + 检索到的参考片段」拼到提示词里 → 大模型基于这些资料生成最终答案。

4. 特点

知识更新快:不需要重新训练模型,只要往知识库加新文档,就能回答新问题(比如新政策)。

减少幻觉:答案基于真实文档,能标注引用来源,更可信。

适配私有数据:企业内部文档、个人笔记都能作为知识库,不需要公开训练。

成本更低:相比微调大模型,RAG 实现简单、成本更低。


5. 比喻

如果把大模型比作「学生」:

  • 纯大模型:闭卷考试,只能靠脑子里已有的知识答题,容易记错。
  • RAG:开卷考试,允许你翻指定的参考书,然后用自己的话总结答案,既准确又流畅。

Document文档类

Document 是 LangChain 中承载文本内容与元数据的最小单位,在 RAG 流程里,它就是被切分、向量化、检索的 "文档片段"。


1. 代码样例
python 复制代码
from langchain_core.documents import Document

# 手动定义的文档列表
documents = [
    # 单个 Document 代表大文档的某个块/某一页
    Document(
        page_content="狗是忠实的伙伴",  # 核心:文本内容
        metadata={"source": "pets-doc"},  # 附加:元数据(来源、关系、属性等)
    ),
    Document(
        page_content="猫是很好的的宠物",
        metadata={"source": "pets-doc"},
    ),
]
2. 属性
属性名 作用 你的代码示例
page_content 必填 ,存储文档的纯文本内容,是后续向量化和检索的主体 "狗是忠实的伙伴"
metadata 可选 ,字典格式,存储附加信息(来源、作者、页码、时间等) {"source": "pets-doc"}

3. 关键理解
  • 最小粒度 :一个 Document 通常是大文档(如 PDF / 长文)切分后的片段,而不是整个文件。
  • 元数据的价值
    • 标注来源:方便后续引用溯源(比如告诉用户 "答案来自 pets-doc 文档")。
    • 过滤检索:可以在检索时根据 metadata 筛选(比如只查来自 "pets-doc" 的内容)。
  • RAG 中的角色
    1. 文档加载 → 切分成多个 Document
    2. 向量化 → 把 page_content 转成向量
    3. 检索 → 找到和问题最相似的 Document 片段
    4. 生成 → 把 page_content 喂给 LLM 做参考

4. 代码扩展:更完整的 metadata
python 复制代码
Document(
    page_content="狗是忠实的伙伴,平均寿命约12-15年",
    metadata={
        "source": "pets-doc",
        "author": "pet-expert",
        "page": 5,
        "category": "dog",
        "created_at": "2025-05-22"
    }
)

这些元数据可以在检索时做更精细的控制,比如只返回 2025 年之后的文档。


5. 直观比喻

如果把知识库比作一本书:

  • 整本书 = 原始文档(PDF/Word)
  • 每一页 / 每一段 = 一个 Document
  • page_content = 这一页的文字
  • metadata = 这一页的页码、章节、作者等信息

✅ 总结

Document 是 LangChain 里文本的标准包装

  • page_content 存内容
  • metadata 存上下文信息
  • 是 RAG 流程里 "切分 → 入库 → 检索 → 生成" 的核心数据单元

加载PDF文档

1. 流程
python 复制代码
from langchain_community.document_loaders import PyPDFLoader

# 1. 初始化加载器:指定 PDF 路径
loader = PyPDFLoader(file_path="你的pdf文档路径")

# 2. 执行加载:按页拆分成 Document 列表
docs = loader.load()
  • loader.load() :遍历 PDF 每一页,一页生成一个 Document ,最终 docs 的长度 = PDF 总页数。
  • 你的代码里 len(docs) 就是总页数,比如 32 页就会得到 32 个 Document

2. 特性
  • 按页拆分PyPDFLoader 只做物理分页拆分,不会按语义 / 字数拆分。

  • 文本提取 :只提取纯文本内容,图片、图表、公式等非文本内容会被忽略(需要多模态 Loader 才能处理图片)。

  • 元数据自动填充 :每个 Documentmetadata 会自动包含:

    • source: PDF 文件路径
    • page: 当前页码(从 0 开始计数)
    python 复制代码
    # 示例输出
    print(docs[0].metadata)  # {'source': 'xxx.pdf', 'page': 0}

3. 常见问题与局限
  • 图片 / 图表去哪了? PyPDFLoader纯文本加载器 ,无法提取图片、矢量图、扫描版 PDF 的文字。如果需要处理图片,需要用:
    • 多模态加载器(如 AmazonTextractPDFLoaderPyMuPDFLoader 配合 OCR)
    • 先把 PDF 转图片,再用视觉模型提取文字
  • 跨页语义被切断 一个完整知识点如果跨了 2~3 页,按页拆分后会被切成多个 Document,需要后续用文档拆分器做细粒度语义切割,才能保证 RAG 检索的完整性。
  • 长页问题 单页文本过长时(比如几千字),需要用 RecursiveCharacterTextSplitter 再切分成小块,才能适配模型上下文窗口和向量检索。

✅ 总结

PyPDFLoader 是 PDF 文本加载的基础工具

  • 核心:按页拆成 Document,文本提取 + 元数据自动管理
  • 局限:只处理纯文本,不处理图片,粒度粗(整页)
  • 定位:RAG 流程的第一步,后续必须配合文档拆分器才能用于生产环境

加载Markdown文件

UnstructuredMarkdownLoader 是专门加载 Markdown 文件 的加载器,最大特点是能按 Markdown 结构(标题、段落、列表等)智能拆分,比纯文本加载更贴合文档语义。


1. 代码与两种模式
python 复制代码
from langchain_community.document_loaders import UnstructuredMarkdownLoader

# 初始化加载器
md_loader = UnstructuredMarkdownLoader(
    file_path="test.md",
    mode="elements",  # 关键:拆分模式
    # mode="single"   # 默认:整个文档加载为 1 个 Document
)

# 执行加载 → 拆分后的 Document 列表
docs = md_loader.load()
模式 行为 适用场景
single(默认) 把整个 MD 文件读成 1 个 Document,不拆分 简单文档、不需要结构化拆分
elements 按 MD 元素拆成多个 Document,保留结构信息 复杂文档、需要按标题 / 段落 / 列表精细管理

2. elements 模式的核心特性

mode="elements",这是最强大的模式:

  • 按 Markdown 元素拆分 :标题、段落、列表、表格、图片等都会被拆成独立 Document
  • 保留结构关系 :通过 parent_id/element_id 记录元素层级(比如子标题的 parent_id 指向父标题)。
  • 元数据更丰富 :每个 Documentmetadata 包含:
    • source: 文件路径
    • category: 元素类型(Title/NarrativeText/ListItem/Table/Image 等)
    • element_id: 元素唯一 ID
    • parent_id: 父元素 ID(用于还原文档结构)
python 复制代码
# 示例元数据输出
print(docs[0].metadata)
# 输出类似:
# {
#   'source': '../Docs/markdown/xxx.md',
#   'category': 'Title',
#   'element_id': '3a0670f9bfd58576e430ef11def41593',
#   'parent_id': None
# }

3. 代码解读
python 复制代码
# 打印拆分后的 Document 总数(等于 MD 元素数量)
print(f"MD文档总数:\n{len(docs)}\n")

# 查看第一个 Document(通常是一级标题)
print(f"第一个文档的内容是:\n{docs[0].page_content}\n")
print(f"第一个文档的元数据字典是:\n{docs[0].metadata}\n")

# 查看第二个 Document(通常是二级标题,parent_id 指向一级标题)
print(f"第二个文档的内容是:\n{docs[1].page_content}\n")
print(f"第二个文档的元数据字典是:\n{docs[1].metadata}\n")

# 查看第三个 Document(通常是段落文本,category=UncategorizedText/NarrativeText)
print(f"第三个文档的内容是:\n{docs[2].page_content}\n")
print(f"第三个文档的元数据字典是:\n{docs[2].metadata}\n")

# 统计所有元素类型
print(f"当前MD文档的所有分类:{set(doc.metadata['category'] for doc in docs)}")

4. 支持的元素类型(category 取值)

UnstructuredMarkdownLoader 能识别:

  • Title: 标题(#、##、###...)
  • NarrativeText: 叙事性文本(普通段落)
  • ListItem: 列表项(-、*、1. 等)
  • Table: 表格
  • Image: 图片
  • UncategorizedText: 未分类文本

这些类型让我们可以精准过滤内容,比如只检索标题和段落,忽略表格 / 图片。


5. 与 PyPDFLoader 的对比
维度 PyPDFLoader UnstructuredMarkdownLoader (elements 模式)
拆分依据 物理页码 Markdown 语义元素(标题 / 段落 / 列表等)
粒度 整页 细粒度元素(一个标题 / 一段文字 = 一个 Document)
元数据 页码 + 来源 元素类型 + ID + 层级关系
适用场景 PDF 纯文本提取 Markdown 结构化文档,需要保留语义层级

✅ 总结

UnstructuredMarkdownLoader 是加载 Markdown 的专业工具

  • 核心:mode="elements"按语义结构拆分,保留文档层级和元素类型。
  • 优势:比纯文本加载更智能,适合复杂 MD 文档(如技术文档、Q&A),方便后续检索和过滤。
  • 定位:RAG 流程中处理结构化 Markdown 的首选。

文档分割器

概念

核心作用:把长文档切成语义完整、大小合适的小块(Chunk),适配模型上下文和向量检索。


要点

  1. 为什么要切
    • 模型有 Token 上限,超长文本塞不下
    • 太长会稀释语义,检索不准;太短会丢上下文
  2. 拆分原则
    • 优先在自然边界(段落、换行、标点)处切,保证语义完整
    • chunk_size 控制块大小,chunk_overlap 保留相邻块上下文
  3. 最常用实现RecursiveCharacterTextSplitter:按「段落→换行→空格→字符」递归拆分,通用且效果最好。
  4. 典型参数
    • chunk_size=1000:每块约 1000 字符
    • chunk_overlap=200:相邻块重叠 200 字符,避免语义断裂

总结

文档分割器 = 长文本 → 小块(语义完整 + 长短合适),是 RAG 必用的 "切块工具"。

基于字符串长度拆分

指定分隔符把长文本切成固定大小的块,是最基础的「字符串切割」分割器。


参数
python 复制代码
text_splitter = CharacterTextSplitter(
    separator="\n\n",        # 分隔符:优先按双换行(段落)切
    chunk_size=400,          # 每块最大约400字符
    chunk_overlap=50,        # 相邻块重叠50字符,保证语义连贯
    length_function=len,      # 长度计算:按字符数
    is_separator_regex=False, # 分隔符是否为正则表达式
)
  • separator="\n\n" :先在段落分隔处 拆分,尽量不拆碎完整段落;如果拆完还超过 chunk_size,会继续按默认优先级(\n\n\n → 空格 → 字符)递归切。
  • chunk_size=400 :目标块大小,允许超过(为了保证句子 / 段落完整)。
  • chunk_overlap=50:相邻块共享 50 字符,避免跨块语义断裂。
  • length_function=len:默认按字符计数,也可以换成 Token 计数。

工作流程
  1. 把整个文档按 separator="\n\n" 拆成段落。
  2. 若某段落长度 ≤400 → 直接作为一个块。
  3. 若某段落长度 >400 → 继续按 \n → 空格 → 字符逐级拆分,直到块大小 ≤400。
  4. 相邻块重叠 50 字符,保证上下文不丢失。

适用场景
  • 简单文本、Markdown 等有明显段落分隔的文档。
  • 不需要复杂语义拆分,追求高效、直观的切块。

总结

CharacterTextSplitter = 按分隔符 + 块大小的「粗暴但好用」字符串切割,是 RAG 入门最易理解的分割器。

基于token长度拆分

CharacterTextSplitter.from_tiktoken_encoder按 Token 计数来切分文档,比按字符计数更贴合模型上下文限制。


参数解析
python 复制代码
text_splitter = CharacterTextSplitter.from_tiktoken_encoder(
    encoding_name="cl100k_base",  # tiktoken 编码方式(对应 GPT-3.5/4 等模型)
    chunk_size=400,               # 每块最大 400 Token
    chunk_overlap=50,              # 相邻块重叠 50 Token
)
关键概念
  • Token:模型处理文本的最小单位(1 个 Token ≈ 0.75 个英文单词,中文更接近 1:1)。
  • encoding_name="cl100k_base":OpenAI 模型用的分词编码,和模型上下文窗口完全对齐。
  • chunk_size=400 :块大小以 Token 数为单位,而非字符数,更精准控制模型输入长度。
  • 拆分逻辑 :和普通 CharacterTextSplitter 一样按分隔符切,但长度判断用 tiktoken 分词计数,保证块大小不超过模型 Token 限制。

为什么用 Token 拆分?
  • 精准适配模型:模型上下文是按 Token 算的(比如 4k/8k/32k Token),按字符切容易超限制。
  • 避免截断语义:按 Token 切更贴合模型实际输入,不会出现「字符数够但 Token 超了」的问题。
  • 兼容性更好:不同语言(中英混合)下,Token 计数比字符计数更稳定。

总结

Token 拆分 = 用 tiktoken 按模型原生 Token 计数切分,让块大小严格匹配模型上下文,是生产环境更可靠的拆分方式。

强制长度拆分

RecursiveCharacterTextSplitter是 LangChain 最通用的递归语义分割器 ,核心是「按自然边界优先拆分,保证语义完整」,两种写法只是计数方式不同


1. 写法一:基于 Token 计数(适配模型)
python 复制代码
text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
    encoding_name="cl100k_base",  # 对齐 GPT 模型的分词规则
    chunk_size=100,               # 每块最大 100 Token
    chunk_overlap=0,              # 块之间无重叠
)
  • 计数方式 :用 tiktokenToken 数 计算块大小,和模型上下文窗口完全对齐。
  • 优势:精准控制输入长度,不会出现「字符数够但 Token 超了」的问题,适合生产环境。
  • 适用场景:对接 OpenAI 系列模型(GPT-3.5/4、text-embedding-3 等)。

2. 写法二:基于字符计数(简单直观)
python 复制代码
text_splitter = RecursiveCharacterTextSplitter(
    separators=["\n\n", "\n", " "],  # 分割符优先级:段落 → 换行 → 空格
    chunk_size=100,                 # 每块最大 100 字符
    chunk_overlap=0,                # 块之间无重叠
    length_function=len,             # 长度计算:按字符数
    is_separator_regex=False,        # 分隔符不是正则表达式
)
  • 计数方式 :用 len()字符数 计算块大小,逻辑简单、易理解。
  • 优势 :开箱即用,不需要额外依赖 tiktoken,适合调试 / 简单场景。
  • 局限:字符数和 Token 数不完全等价,可能和模型上下文窗口有偏差。

3. 共同点

两种写法的拆分逻辑完全一致,都是递归尝试按分隔符切割:

  1. 先按 \n\n(段落)切,若块大小 ≤ 限制 → 保留。
  2. 若仍超长 → 按 \n(换行)切。
  3. 若仍超长 → 按空格切。
  4. 最后才按单个字符切。
  • 目的:最大程度保留句子 / 段落的完整性,避免语义断裂。

4. 参数对比
参数 Token 版 字符版
chunk_size 单位:Token 单位:字符
chunk_overlap 单位:Token 单位:字符
length_function 内置 tiktoken 计数 显式指定 len
依赖 需要 tiktoken 无额外依赖
精度 高(完全对齐模型) 低(字符 ≠ Token)

总结
  • 字符版:简单直观,适合入门 / 调试。
  • Token 版:精准适配模型,是生产环境的首选。
  • 两者核心都是递归按语义边界拆分,只是「长度计算方式」不同。

文本向量

嵌入模型

嵌入(Embedding)的核心思想是将人类世界的符号(如单词、句子、产品、用户、图片)转换为计算机能够理解的数值形式(即向量,本质上是一个数字列表),并且要求这种转换能够保留原始符号的语义和关系。

  • 嵌入模型 :属于表示型模型,目标不是生成文本,而是为输入的文本创建一个最佳的、富含语义的数值表示(向量)。
  • 典型代表:OpenAI 的 text-embedding-3-large、Google 的 gemini-embedding-001、阿里的 Qwen3-Embedding-8B 等。
  • 与生成式模型的区别:大语言模型(生成式)以 "创造新文本" 为目标,内部会用嵌入理解输入;而嵌入模型专注于 "生成语义向量",本身不生成新内容。

简单定义:

python 复制代码
from langchain_openai import OpenAIEmbeddings

# 定义嵌入模型
embeddings = OpenAIEmbeddings(
    model="text-embedding-3-large",  # 指定嵌入模型版本
)

调用演示:

python 复制代码
from langchain_community.document_loaders import UnstructuredMarkdownLoader
from langchain_text_splitters import CharacterTextSplitter
from langchain_openai import OpenAIEmbeddings

# 1. 加载 Markdown(single 模式 = 1 个大 Document)
loader = UnstructuredMarkdownLoader("test.md")
data = loader.load()

# 2. 按 Token 拆分文档
text_splitter = CharacterTextSplitter.from_tiktoken_encoder(
    encoding_name="cl100k_base",
    chunk_size=400,
    chunk_overlap=50,
)
docs = text_splitter.split_documents(data)

# 3. 初始化嵌入模型
embeddings = OpenAIEmbeddings(model="text-embedding-3-large")

# 4. 把文档转成向量
texts = [doc.page_content for doc in docs]
docs_vector = embeddings.embed_documents(texts)

# 5. 查看结果
print(f"文档数量:{len(docs)},向量数量:{len(docs_vector)}")
print(f"第一个文档向量维度:{len(docs_vector[0])}")
print(f"第一个文档向量前五个值:{docs_vector[0][:5]}")
1. 流程总结

Markdown 文件 → 加载成大文档 → 切成小块 → 转成语义向量

2. 每步作用
  • 加载 :把整个 MD 文件读成 1 个 Document
  • 拆分 :按 cl100k_base Token 切成 400 Token 左右的小块,保证语义完整。
  • 嵌入 :调用 OpenAI 模型,把每块文本变成高维向量(比如 3072 维),计算机能通过向量计算相似度。
  • 输出:验证「文档数 = 向量数」,并查看向量维度和内容。
3. 目的

为后续向量数据库入库检索做准备:向量才能让计算机知道「哪段文本和用户问题最像」。

向量存储

在大模型应用爆发的今天,RAG(检索增强生成) 已经成为解决「知识陈旧、幻觉」问题的核心方案。而向量存储(Vector Store),正是 RAG 流程中承上启下的关键组件 ------ 它让计算机能像人一样「理解语义」,实现高效的相似性检索。

概念

向量存储是什么?

传统数据库(如 MySQL)擅长精确匹配查询(比如「找名字叫张三的用户」),但无法回答「找和这段文本语义最像的内容」这类问题。

向量存储的核心使命,就是解决这个难题:

专门用于高效存储、管理和检索高维向量数据 ,实现基于内容的相似性搜索(Similarity Search)

简单来说:

  • 文本 / 图片 / 音频 → 嵌入模型 → 高维向量(比如 1536/3072 维的数字列表)
  • 向量存储 → 把这些向量管起来,让你能快速找到「和查询向量最像」的那批数据

为什么它能搜得快?

面对百万甚至上亿级别的向量数据,暴力遍历对比显然不现实。向量数据库靠两大核心技术实现了高效检索:

1. 专门的索引:近似最近邻(ANN)搜索

这是向量数据库的「灵魂」。它不会遍历所有数据,而是预先构建特殊索引,把「全库搜索」缩小到「几个最可能的候选集」:

  • 核心思想:牺牲一点点精度,换取极致速度。不保证找到绝对最相似的向量,但能以极高概率找到非常相似的向量。
  • 类比:就像在图书馆找书,你不会从 A 到 Z 遍历所有书架,而是先按分类(文学 / 历史 / 科技)锁定大概区域,再在这个区域里仔细找,速度快得多。
  • 常见索引技术
    • 基于聚类:IVF(倒排文件)、K-Means
    • 基于图结构:HNSW(分层导航小世界,工业界最常用)

2. 硬件加速:SIMD 与并行计算

向量数据库充分利用 CPU 的 SIMD(单指令多数据流) 指令集和 GPU 并行计算能力:

  • 传统 SISD 指令:一条指令只能处理一组数据
  • SIMD 指令:一条指令能同时处理多组数据,让大规模向量计算速度极快

这让高维向量之间的相似度计算(如余弦相似度)能在毫秒级完成。


不止是存储:完整的数据管理能力

现代向量数据库早已不是「只存向量」的简单工具,它提供了完整的企业级数据管理功能:

  1. CRUD 操作:支持增删改查,可动态更新向量数据,适配业务迭代。
  2. 元数据过滤:除了向量本身,还能管理文档元数据(创建时间、作者、类别等)。比如先过滤「2024 年以后的科技类文档」,再在这个子集里做相似性搜索,大幅提升准确性和效率。
  3. 可扩展性与持久化:支持分布式部署,轻松处理海量数据;同时保证数据持久化,不会像纯内存方案那样断电丢失。
  4. 便捷集成:提供 gRPC、RESTful 等友好 API,能和 LangChain、LlamaIndex 等大模型框架无缝集成,开发者无需关心底层细节。

向量存储在 RAG 中的位置

从 RAG 全流程看,向量存储是「离线处理」和「在线检索」的桥梁:

离线数据处理

  1. 文档加载:读取 PDF/Markdown/ 数据库等原始数据 → 生成 Document
  2. 文档拆分:把长文档切成适合模型的小块(Chunk)
  3. 向量入库:小块文本 → 嵌入模型 → 向量 → 存入向量存储
在线检索
  1. 问题向量化:用户问题 → 嵌入模型 → 查询向量
  2. 相似性搜索:查询向量 → 向量存储 → 找到最相似的文档小块
  3. 生成答案:相似文档 + 用户问题 → 大模型 → 最终回答

常见向量数据库选型

目前主流的向量数据库有很多,适配不同场景:

  • 轻量开源:Chroma(内存级,适合入门 / 原型)、FAISS(Facebook 开源,高性能)
  • 云原生 / 企业级:Pinecone、Weaviate、Qdrant、Milvus(支持分布式、高可用、元数据过滤)
  • 多模态支持:Milvus、Weaviate 等,可同时处理文本、图片、音频向量

LangChain 已经和这些库深度集成,你只需要几行代码就能完成向量存储的接入,无需手动处理向量生成、存储和比较的复杂性。


总结

向量存储是大模型时代的「语义搜索引擎」:

  • 它让计算机能理解语义,实现基于内容的相似性检索
  • 它靠 ANN 索引 + 硬件加速 解决海量高维向量的高效检索问题
  • 它是 RAG 流程的核心,让大模型能「读外部知识库」,从根本上缓解幻觉和知识陈旧问题

如果我们正在开发大模型应用,向量存储一定是你绕不开的关键组件 ------ 它让你的模型不仅能「生成」,更能「准确地生成」。

内存向量存储
CRUD
python 复制代码
from langchain_community.document_loaders import UnstructuredMarkdownLoader
from langchain_text_splitters import CharacterTextSplitter
from langchain_openai import OpenAIEmbeddings
from langchain_chroma import Chroma  # 以 Chroma 为例

# 1. 加载 Markdown 文档
loader = UnstructuredMarkdownLoader("test.md")
data = loader.load()

# 2. 按 Token 拆分文档
text_splitter = CharacterTextSplitter.from_tiktoken_encoder(
    encoding_name="cl100k_base",
    chunk_size=400,
    chunk_overlap=50,
)
docs = text_splitter.split_documents(data)

# 3. 初始化嵌入模型 + 向量存储
embeddings = OpenAIEmbeddings(model="text-embedding-3-large")
vector_store = Chroma(embedding_function=embeddings)  # 内存版向量库

# 4. 批量添加文档到向量库(自动生成向量+索引)
ids = vector_store.add_documents(docs)
print(f"共有{len(docs)}个文档,编排了{len(ids)}个索引")
print(f"前三个文档的索引:{ids[:3]}")

# 5. 根据 ID 获取文档
doc_2 = vector_store.get_by_ids(ids[:2])
print(doc_2)

# 6. 删除指定 ID 的文档
vector_store.delete(ids=ids[:2])

# 7. 根据 ID 获取文档
doc_3 = vector_store.get_by_ids(ids[:3])
print(doc_3)
检索
python 复制代码
from langchain_community.document_loaders import UnstructuredMarkdownLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_community.vectorstores import Chroma
from langchain_core.documents import Document

# 1. 加载 Markdown 文档
loader = UnstructuredMarkdownLoader("test.md")
data = loader.load()

# 2. 按 Token 切分文档
text_splitter = RecursiveCharacterTextSplitter(
    encoding_name="cl100k_base",
    chunk_size=400,
    chunk_overlap=50
)
split_docs = text_splitter.split_documents(data)

# 3. 初始化向量库(以 Chroma 为例)
embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
vector_store = Chroma.from_documents(documents=split_docs, embedding=embeddings)

# ------------------- 基础相似检索 -------------------
print("=== 基础相似检索结果 ===")
search_docs = vector_store.similarity_search(query="项目介绍", k=2)
for doc in search_docs:
    print("*" * 30)
    print(doc)

# ------------------- 元数据过滤检索 -------------------
# 定义过滤函数:匹配指定来源文件
def _filter_function(doc: Document) -> bool:
    return doc.metadata.get("source") == "test.md"

print("\n=== 元数据过滤检索结果 ===")
search_docs = vector_store.similarity_search(
    query="项目介绍",
    k=2,
    filter=_filter_function
)
for doc in search_docs:
    print("*" * 30)
    print(doc)

执行流程是:

  1. 先去向量数据库 查出 相似度最高的 k 条数据(比如 k=2)
  2. 回到本地 Python 内存 把这 2 条数据一条一条拿出来遍历
  3. 挨个比对你的条件 source == "test.md"
  4. 符合就留下,不符合就丢掉
Redis向量存储
python 复制代码
from langchain_openai import OpenAIEmbeddings
from langchain_redis import RedisVectorStore, RedisConfig

# 嵌入模型
embeddings = OpenAIEmbeddings(model="text-embedding-3-large")

# Redis 配置
config = RedisConfig(
    index_name="qa",  # 定义索引名
    redis_url="redis://192.168.100.238:6379",
    metadata_schema=[
        {"name": "category", "type": "tag"},  # 添加索引字段: 分类(标签类型)
        {"name": "num", "type": "numeric"}    # 添加索引字段: 编号(数值类型)
    ]
)

# 初始化 Redis 向量存储实例
vector_store = RedisVectorStore(
    embeddings=embeddings,
    config=config
)

# CRUD、检索(后续操作入口)
  • RedisVectorStore:LangChain 对接 Redis 向量数据库的存储类,负责向量的增删查改。
  • RedisConfig:Redis 向量库的配置类,用于定义索引、连接地址和元数据 schema。

Redis 向量库配置

python 复制代码
config = RedisConfig(
    index_name="qa",  # 定义索引名
    redis_url="redis://192.168.100.238:6379",
    metadata_schema=[
        {"name": "category", "type": "tag"},  # 标签类型,用于精确匹配过滤
        {"name": "num", "type": "numeric"}    # 数值类型,用于范围查询过滤
    ]
)
  • index_name="qa":Redis 中向量索引的名称,用于区分不同业务的向量数据。
  • redis_url:Redis 服务连接地址(IP + 端口),这里是 192.168.100.238:6379
  • metadata_schema:定义元数据字段的索引类型,用于数据库层面的过滤检索
    • categorytag 类型 → 适合做标签匹配(如 category="技术")。
    • numnumeric 类型 → 适合做数值范围查询(如 num > 10)。

初始化向量存储实例

python 复制代码
vector_store = RedisVectorStore(
    embeddings=embeddings,
    config=config
)
  • 作用:将嵌入模型和 Redis 配置绑定,生成可直接操作的向量存储对象。
  • 后续可通过 vector_store 执行:
    • 添加文档:vector_store.add_documents(docs)
    • 相似检索:vector_store.similarity_search(query, k=3, filter={"category": "技术"})
    • 删除数据:vector_store.delete(ids=[...])

使用提示

  1. 元数据过滤(数据库层面) 你定义的 metadata_schema 支持在检索时直接传过滤条件,例如:

    复制代码
    vector_store.similarity_search(
        query="什么是向量数据库",
        k=2,
        filter={"category": "技术", "num": {"$gt": 5}}
    )

    这会先在 Redis 中过滤符合条件的文档,再做语义相似度计算,效率远高于本地过滤。

  2. 业务场景 这段代码是典型的 RAG(检索增强生成) 基础设施,适合做:

    • 知识库问答
    • 文档语义检索
    • 带分类 / 编号筛选的精准搜索
CRUD
python 复制代码
from langchain_openai import OpenAIEmbeddings
from langchain_redis import RedisVectorStore, RedisConfig
from langchain_community.document_loaders import UnstructuredMarkdownLoader
from langchain_text_splitters import CharacterTextSplitter

# ---------------------- 1. 嵌入模型 & Redis 配置 ----------------------
embeddings = OpenAIEmbeddings(model="text-embedding-3-large")

config = RedisConfig(
    index_name="qa",
    redis_url="redis://ip+port",
    metadata_schema=[
        {"name": "category", "type": "tag"},
        {"name": "num", "type": "numeric"}
    ]
)

# 初始化 Redis 向量存储
vector_store = RedisVectorStore(
    embeddings=embeddings,
    config=config,
)

# ---------------------- 2. 加载文档 & 分块 ----------------------
loader = UnstructuredMarkdownLoader("路径")
data = loader.load()

text_splitter = CharacterTextSplitter.from_tiktoken_encoder(
    encoding_name="cl100k_base",
    chunk_size=400,
    chunk_overlap=50,
)
docs = text_splitter.split_documents(data)

# 标注元数据
for i, doc in enumerate(docs, start=1):
    doc.metadata["category"] = "QA"
    doc.metadata["num"] = i

# ---------------------- 3. 添加文档到 Redis ----------------------
ids = vector_store.add_documents(documents=docs)
print(f"编制了{len(ids)}个索引")
print(f"前三个索引是: {ids[:3]}")

# ---------------------- 4. CRUD 操作 ----------------------
# 查:根据 ID 获取
print(vector_store.get_by_ids(["01K85F9HDK4H98QK221YA2ZQ7E"]))

# 删:根据 ID 删除
vector_store.delete(["01K85F9HDK4H98QK221YA2ZQ7E"])
print(vector_store.get_by_ids(["01K85F9HDK4H98QK221YA2ZQ7E"]))

# 底层删除(Redis 原生 drop keys)
vector_store.index.drop_keys(["qa:01K85F9HDK4H98QK221YA2ZQ7E"])

# 全量删除(清空索引)
vector_store.index.delete(drop=True)

流程讲解

  1. 环境初始化

    • OpenAIEmbeddings 生成文本向量
    • RedisConfig 配置 Redis 连接和元数据索引结构
    • 初始化 RedisVectorStore 实例
  2. 文档处理

    • UnstructuredMarkdownLoader 读取本地 Markdown 文件
    • CharacterTextSplitter 按 token 切分长文档(chunk_size=400,重叠 50 token)
    • 给每个文档块添加 categorynum 元数据,方便检索过滤
  3. 数据入库

    • add_documents 将文档向量化后存入 Redis,生成唯一索引 ID
  4. CRUD 操作

    • get_by_ids 按 ID 读取文档
    • delete 按 ID 删除;index.drop_keys 底层删除;index.delete(drop=True) 清空全量数据

关键说明

  • 这是标准 Redis 向量存储 方案,使用 langchain-redis 库对接 Redis
  • 元数据 category(标签类型)和 num(数值类型)会在 Redis 中建立索引,支持按分类 / 编号过滤检索
  • 日志里的 chunk size > 400 是正常提示:为保证段落完整,分词器会自动超出设定大小
检索
python 复制代码
from langchain_redis import RedisVectorStore, RedisConfig, Tag, Num  # 注意导入过滤类
from langchain_openai import OpenAIEmbeddings

# ---------------------- 1. 基础配置(复用之前的初始化) ----------------------
embeddings = OpenAIEmbeddings(model="text-embedding-3-large")
config = RedisConfig(
    index_name="qa",
    redis_url="redis://ip+port",
    metadata_schema=[
        {"name": "category", "type": "tag"},
        {"name": "num", "type": "numeric"}
    ]
)
vector_store = RedisVectorStore(embeddings=embeddings, config=config)

# ---------------------- 2. 基础相似度检索 ----------------------
# 普通相似度检索(不带分数)
search_docs = vector_store.similarity_search(
    query="项目介绍",
    k=2  # 返回最相似的2条结果
)

# 带相似度分数的检索(分数越低,相似度越高)
search_docs_results = vector_store.similarity_search_with_score(
    query="项目介绍",
    k=2
)

# 遍历并打印带分数的结果
for doc, score in search_docs_results:
    print("*" * 30)
    print(f"文档分数: {score}")
    print(f"文档内容: {doc.page_content}")
    print(f"文档元数据: {doc.metadata}")

# ---------------------- 3. 带元数据过滤的检索 ----------------------
# 定义过滤条件:category == "QA" 且 num > 6
filter_condition = (Tag("category") == "QA") & (Num("num") > 6)

# 带过滤的相似度检索(带分数)
search_docs_results = vector_store.similarity_search_with_score(
    query="项目介绍",
    k=2,
    filter=filter_condition
)

# 遍历并打印结果
for doc, score in search_docs_results:
    print("*" * 30)
    print(f"文档分数: {score}")
    print(f"文档内容: {doc.page_content}")
    print(f"文档元数据: {doc.metadata}")

# ---------------------- 4. MMR 多样性检索 ----------------------
# MMR(最大边际相关性)检索:先召回10条,再重排输出2条,保证结果多样性
search_docs = vector_store.max_marginal_relevance_search(
    query="项目介绍",
    k=2,          # 最终返回2条
    filter=filter_condition,
    fetch_k=10    # 先从库中召回10条候选,再做多样性重排
)

# 遍历并打印MMR结果
for doc in search_docs:
    print("*" * 30)
    print(f"文档内容: {doc.page_content}")
    print(f"文档元数据: {doc.metadata}")

流程讲解

1. 基础检索

  • similarity_search :直接返回最相似的文档,不带分数
  • similarity_search_with_score :返回文档 + 相似度分数,分数越低表示和查询越相似

2. 元数据过滤检索

  • Tag() 处理标签类型字段(如 category),用 Num() 处理数值类型字段(如 num)。
  • 过滤条件支持 &(且)、|(或)、~(非)逻辑组合,实现精准筛选。
  • 示例:(Tag("category") == "QA") & (Num("num") > 6) 只检索分类为 QA 且编号大于 6 的文档。

3. MMR 多样性检索

  • max_marginal_relevance_search :在保证相关性的同时,最大化结果多样性,避免返回内容高度相似的文档。
  • fetch_k:先从向量库中召回 fetch_k 条候选文档,再从中选出 k 条最具多样性的结果。
  • 适合需要结果覆盖面更广的场景(如问答系统、推荐系统)。

注意点

  1. 过滤类导入 :必须导入 TagNum,否则无法构建过滤条件。
  2. 元数据类型匹配 :过滤字段类型必须和 RedisConfig 中定义的 metadata_schema 一致(tag/numeric)。
  3. 分数含义 :Redis 向量检索的分数是距离值,值越小表示向量越接近,相似度越高。
  4. MMR 适用场景:当你希望检索结果既相关又不重复时(比如避免多条内容讲同一个知识点),使用 MMR 更合适。
Pinecone向量存储
python 复制代码
from langchain_pinecone import PineconeVectorStore
from pinecone import Pinecone, ServerlessSpec
from langchain_openai import OpenAIEmbeddings
from langchain_community.document_loaders import UnstructuredMarkdownLoader
from langchain_text_splitters import CharacterTextSplitter

# ---------------------- 1. Pinecone 初始化 & 创建索引 ----------------------
pc = Pinecone()  # 自动读取环境变量 PINECONE_API_KEY
index_name = "standard-dense-py"

# 如果索引不存在则创建
if not pc.has_index(index_name):
    pc.create_index(
        name=index_name,
        vector_type="dense",
        dimension=1536,  # 与 text-embedding-3-large 向量维度一致
        metric="cosine",
        spec=ServerlessSpec(
            cloud="aws",
            region="us-east-1"
        ),
        deletion_protection="disabled",
        tags={
            "environment": "development"
        }
    )

# 获取已存在的索引
index = pc.Index(index_name)

# ---------------------- 2. 嵌入模型 & 向量库初始化 ----------------------
embeddings = OpenAIEmbeddings(model="text-embedding-3-large")
vector_store = PineconeVectorStore(
    embedding=embeddings,
    index=index
)

# ---------------------- 3. 文档加载 & 分块 ----------------------
loader = UnstructuredMarkdownLoader("路径")
data = loader.load()

text_splitter = CharacterTextSplitter.from_tiktoken_encoder(
    encoding_name="cl100k_base",
    chunk_size=400,
    chunk_overlap=50,
)
docs = text_splitter.split_documents(data)

# 标注元数据
for i, doc in enumerate(docs, start=1):
    doc.metadata["category"] = "QA"
    doc.metadata["num"] = i

# ---------------------- 4. 数据入库 ----------------------
ids = vector_store.add_documents(documents=docs)
print(f"编制了{len(ids)}个索引")
print(f"前三个索引是: {ids[:3]}")

# ---------------------- 5. CRUD 操作 ----------------------
# 全量删除
vector_store.delete(delete_all=True)

# 删除指定 ID 的文档
delete_ids = []  # 填入要删除的 ID 列表
vector_store.delete(ids=delete_ids)

# ---------------------- 6. 相似度检索 ----------------------
search_docs = vector_store.similarity_search(query="项目介绍", k=2)

# 遍历并打印检索结果
for doc in search_docs:
    print("*" * 30)
    print(f"文档内容: {doc.page_content}")
    print(f"文档元数据: {doc.metadata}")

流程讲解

1. Pinecone 环境初始化

  • Pinecone() :创建客户端,自动读取 PINECONE_API_KEY 环境变量。
  • create_index():创建无服务器向量索引,指定向量维度(需与嵌入模型匹配)、距离度量(cosine)和云服务商配置。
  • Index(index_name):获取已创建的索引实例,用于后续数据操作。

2. 文档处理与入库

  • 文档加载UnstructuredMarkdownLoader 读取本地 Markdown 文件。
  • 文本分块CharacterTextSplitter 按 token 切分长文档,保证段落完整性。
  • 元数据标注 :给每个文档块添加 category(分类)和 num(编号),方便后续检索过滤。
  • 数据入库add_documents 将文档向量化后存入 Pinecone,生成唯一索引 ID。

3. CRUD 操作

  • 新增add_documents → 向向量库添加文档。
  • 删除
    • delete(delete_all=True) → 清空向量库所有数据。
    • delete(ids=delete_ids) → 删除指定 ID 的文档。
  • 查询similarity_search → 按语义相似度检索文档,返回最相似的 k 条结果。

注意点

  1. 维度匹配dimension=1536 必须与 text-embedding-3-large 模型的输出维度一致,否则会报错。
  2. 环境变量 :需提前设置 PINECONE_API_KEYPINECONE_ENVIRONMENT 环境变量。
  3. 无服务器架构ServerlessSpec 是 Pinecone 的无服务器方案,按需计费,适合开发测试。
  4. 检索特性:Pinecone 原生支持高并发向量检索,适合生产环境部署。

检索器

在 RAG(检索增强生成)应用中,** 检索器(Retriever)** 的核心作用是:接收用户查询文本,从向量数据库中召回最相关的文档片段,为后续大模型生成回答提供上下文支撑。

LangChain 提供了两种主流的检索器实现方式,本文将结合 Redis 向量库的实践代码,做极简对比说明。


两种实现

1. 官方封装:as_retriever() 开箱即用

python 复制代码
# 官方标准写法,LangChain 向量库通用接口
retriever = vector_store.as_retriever(search_kwargs={"k": 2})
  • 核心逻辑 :LangChain 为所有向量库(Redis、Chroma、FAISS 等)统一封装了 as_retriever() 方法,直接生成符合 Runnable 规范的检索器。
  • 调用方式 :和所有 LangChain 组件一致,使用 retriever.invoke("你的查询词") 触发检索,返回 List[Document]
  • 适用场景:简单检索需求,无需自定义逻辑,追求快速开发。

2. 自定义实现:@chain 装饰器手动包装

python 复制代码
from langchain_core.runnables import chain
from langchain_core.documents import Document
from typing import List

@chain
def retriever(query: str) -> List[Document]:
    return vector_store.similarity_search(query=query, k=2)
  • 核心逻辑 :用 @chain 装饰器,把普通的向量库相似度搜索方法,包装成 LangChain 可识别的 Runnable 组件。
  • 调用方式 :同样使用 retriever.invoke("你的查询词"),和官方检索器接口完全兼容。
  • 适用场景:需要在检索前后加入自定义逻辑,比如元数据过滤、多数据源合并、结果改写等。

区别

维度 as_retriever() @chain 自定义实现
代码量 一行代码,开箱即用 需定义函数,多写几行代码
灵活性 固定封装,仅支持基础检索参数(k、过滤条件等) 可在函数内任意扩展逻辑
兼容性 所有向量库通用,符合 LangChain 规范 同样兼容 LangChain 链,可无缝替换官方实现
适用场景 快速开发、无自定义需求的基础 RAG 复杂检索逻辑、多步骤处理的定制化场景

两种方式的底层执行流程完全一致:

  1. 调用 retriever.invoke("项目介绍"),传入用户查询;
  2. 检索器接收查询,调用向量库的相似度搜索接口;
  3. 向量库将查询文本转为向量,匹配库中余弦相似度最高的前 2 条文档;
  4. 返回 List[Document] 对象,循环打印 doc.page_content 即可获取检索结果。

总结

  • 追求简单高效,直接用 vector_store.as_retriever()
  • 需要定制化逻辑,用 @chain 包装自定义函数,实现和官方检索器完全一致的调用体验。

两种方式在 LangChain 链中可以无缝替换,根据你的业务需求选择即可。

相关推荐
愚者Pro2 小时前
Flutter Widget组件学习(专为 Uniapp 转 Flutter 定制)
vue.js·学习·flutter·uni-app
yzx9910133 小时前
从焦虑到掌控:关于学习AI工具的深度思考
人工智能·学习
Bechamz4 小时前
大数据开发学习Day42
大数据·学习
zhangrelay4 小时前
ROS 2 Lyrical Luth启程-Ubuntu26.04-
linux·笔记·学习·ubuntu
锦鲤52144 小时前
机器学习学习笔记
笔记·学习·机器学习
minglie14 小时前
utf8转utf16
学习
三品吉他手会点灯5 小时前
STM32F103 学习笔记-22-DMA(第1节)-DMA功能框图讲解和DMA初始化结构体讲解
笔记·stm32·单片机·嵌入式硬件·学习
咸甜适中5 小时前
rust语言学习笔记Trait(十一)Deref、DerefMut(解引用)
笔记·学习·rust
这是谁的博客?6 小时前
LangChain 框架深度解析:从 LCEL 到 Agent 架构的核心原理
ai·架构·langchain·llm·agent·架构设计
AI小技巧6 小时前
商务英语在线学习哪家好?主流平台深度测评与避坑指南
学习