RAG 进阶检索学习笔记

RAG 进阶检索学习笔记


一、函数回调规范化输出

是什么?

通过 with_structured_output() 让 LLM 的返回结果强制符合指定的 Pydantic 模型结构,而不是返回普通字符串。

有什么用?

LLM 输出默认是自由文本,格式不稳定,难以程序化处理。绑定结构化输出后:

  • 返回结果直接是 Python 对象,可以直接访问字段
  • 字段类型和取值范围有约束,不会出现意外格式
  • 常用于:意图分类、路由分发、信息抽取等任务

示例代码

python

python 复制代码
from typing import Literal

import dotenv
from pydantic import BaseModel, Field
from langchain_openai import ChatOpenAI

dotenv.load_dotenv()


class RouteQuery(BaseModel):
    """将用户查询映射到对应的数据源上"""
    datasource: Literal["python_docs", "js_docs", "golang_docs"] = Field(
        description="根据用户的问题,选择哪个数据源最相关以回答用户的问题"
    )


# 1. 创建绑定结构化输出的大语言模型
llm = ChatOpenAI(model="moonshot-v1-8k", temperature=0)
structured_llm = llm.with_structured_output(RouteQuery)

# 2. 提问并获取结构化结果
question = """为什么下面的代码不工作了,请帮我检查下:

var a = "123"
"""
res: RouteQuery = structured_llm.invoke(question)

print(res)             # datasource='js_docs'
print(type(res))       # <class 'RouteQuery'>
print(res.datasource)  # js_docs

关键知识点

知识点 说明
BaseModel Pydantic 基类,定义结构化输出的字段和类型
Literal[...] 限制字段只能取指定枚举值,LLM 输出会被约束在这几个选项内
Field(description=...) 字段描述,LLM 会参考此描述决定填什么值
with_structured_output() LangChain 方法,让 LLM 输出直接映射为指定 Pydantic 对象
temperature=0 输出确定稳定,适合分类、路由等任务

二、混合检索(Ensemble Retriever)

是什么?

混合检索将两种不同类型的检索器结合,通过加权融合各自的召回结果:

  • BM25:基于关键词的传统检索,擅长精确词匹配
  • FAISS:基于向量的语义检索,擅长理解语义相似性
  • EnsembleRetriever:集成以上两者,取长补短

有什么用?

单一检索方式各有盲区:BM25 无法理解语义,向量检索可能漏掉关键词精确匹配的文档。混合检索能同时覆盖两种场景,提升整体召回率和准确性。

示例代码

python

ini 复制代码
import dotenv
from langchain_classic.retrievers import EnsembleRetriever
from langchain_community.retrievers import BM25Retriever
from langchain_community.vectorstores import FAISS
from langchain_core.documents import Document
from langchain_community.embeddings import QianfanEmbeddingsEndpoint

dotenv.load_dotenv()

# 1. 创建文档列表
documents = [
    Document(page_content="笨笨是一只很喜欢睡觉的猫咪", metadata={"page": 1}),
    Document(page_content="我喜欢在夜晚听音乐,这让我感到放松。", metadata={"page": 2}),
    Document(page_content="猫咪在窗台上打盹,看起来非常可爱。", metadata={"page": 3}),
    Document(page_content="我的狗喜欢追逐球,看起来非常开心。", metadata={"page": 10}),
    # ... 更多文档
]

# 2. 构建 BM25 关键词检索器
bm25_retriever = BM25Retriever.from_documents(documents)
bm25_retriever.k = 4  # 召回 top4

# 3. 创建 FAISS 向量检索器
faiss_db = FAISS.from_documents(documents, embedding=QianfanEmbeddingsEndpoint(model='embedding-v1'))
faiss_retriever = faiss_db.as_retriever(search_kwargs={"k": 4})

# 4. 初始化集成检索器(各占 50% 权重)
ensemble_retriever = EnsembleRetriever(
    retrievers=[bm25_retriever, faiss_retriever],
    weights=[0.5, 0.5],
)

# 5. 执行检索
docs = ensemble_retriever.invoke("除了猫,你养了什么宠物呢?")
print(docs)
print(len(docs))

关键知识点

知识点 说明
BM25Retriever 关键词检索,基于词频统计,不依赖向量模型
FAISS Facebook 开源的高效向量检索库,支持本地运行
EnsembleRetriever 集成多个检索器,通过 RRF 算法融合排序结果
weights=[0.5, 0.5] 控制两个检索器的影响权重,可按需调整
.k = 4 每个检索器各自召回的文档数量

💡 RRF(Reciprocal Rank Fusion) :EnsembleRetriever 内部使用的结果融合算法,根据各检索器的排名加权合并,而不是简单拼接。


三、基于逻辑和语义的路由分发

是什么?

路由分发是根据用户问题的内容,自动将其导向不同的处理链路(如不同的知识库、检索器或 Agent)。结合结构化输出 + LCEL,可以构建出智能的路由系统。

有什么用?

  • 多知识库场景下,不同问题应查询不同数据源
  • 避免用一个大而全的检索器处理所有问题,提升效率和准确性
  • 是构建多 Agent 系统复杂 RAG 流程的基础能力

流程图

bash 复制代码
用户问题
   ↓
RunnablePassthrough()      # 透传问题
   ↓
ChatPromptTemplate         # 构造路由 prompt(含系统指令)
   ↓
LLM + structured_output    # 输出结构化路由结果 RouteQuery
   ↓
choose_route()             # 根据 datasource 字段选择对应链路
   ↓
返回对应的检索器 / chain

示例代码

python

python 复制代码
from typing import Literal

import dotenv
from langchain_core.prompts import ChatPromptTemplate
from pydantic import BaseModel, Field
from langchain_core.runnables import RunnablePassthrough
from langchain_openai import ChatOpenAI

dotenv.load_dotenv()


class RouteQuery(BaseModel):
    """将用户查询映射到最相关的数据源"""
    datasource: Literal["python_docs", "js_docs", "golang_docs"] = Field(
        description="根据给定用户问题,选择哪个数据源最相关以回答他们的问题"
    )


def choose_route(result: RouteQuery) -> str:
    """根据路由结果选择不同的检索器"""
    if "python_docs" in result.datasource:
        return "chain in python_docs"
    elif "js_docs" in result.datasource:
        return "chain in js_docs"
    else:
        return "golang_docs"


# 1. 构建 LLM 并绑定结构化输出
llm = ChatOpenAI(model="moonshot-v1-8k", temperature=0)
structured_llm = llm.with_structured_output(RouteQuery)

# 2. 创建路由链
prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个擅长将用户问题路由到适当数据源的专家。\n请根据问题涉及的编程语言,将其路由到相关数据源"),
    ("human", "{question}")
])
router = {"question": RunnablePassthrough()} | prompt | structured_llm | choose_route

# 3. 执行路由
question = """为什么下面的代码不工作了,请帮我检查下:

from langchain_core.prompts import ChatPromptTemplate

prompt = ChatPromptTemplate.from_messages(["human", "speak in {language}"])
prompt.invoke("中文")"""

print(router.invoke(question))  # chain in python_docs

关键知识点

| 知识点 | 说明 |
|----------------------|------------------------------------------|-----------------------------|
| RouteQuery | Pydantic 模型,约束 LLM 只能输出预定义的数据源名称 |
| choose_route() | 普通函数也可以作为 LCEL 链的最后一个节点,接收上游输出 |
| ChatPromptTemplate | 支持 system + human 多角色消息,system 用于设定角色和规则 |
| `LCEL 管道符 | ` | 将 dict、prompt、llm、函数串联成完整链路 |
| 语义路由 | LLM 理解问题语义后做分类,比规则匹配更灵活 |


总结对比

函数回调规范化输出 混合检索 路由分发
核心目标 LLM 输出结构化 提升检索召回率 问题智能分流
关键组件 with_structured_output EnsembleRetriever RouteQuery + choose_route
典型场景 意图识别、信息抽取 企业知识库检索 多知识库 / 多 Agent 系统
依赖 LLM
相关推荐
Moment2 小时前
想要长期陪伴你的助理?先从部署一个 OpenClaw 开始 😍😍😍
前端·后端·github
Das1_2 小时前
【Golang 数据结构】Slice 底层机制
后端·go
得物技术2 小时前
深入剖析Spark UI界面:参数与界面详解|得物技术
大数据·后端·spark
古时的风筝2 小时前
花10 分钟时间,把终端改造成“生产力武器”:Ghostty + Yazi + Lazygit 配置全流程
前端·后端·程序员
Cache技术分享2 小时前
340. Java Stream API - 理解并行流的额外开销
前端·后端
初次攀爬者2 小时前
RocketMQ 消息可靠性保障与堆积处理
后端·消息队列·rocketmq
ygxb2 小时前
如何去创建一个规范化的Agent SKIll?
后端·ai编程·claude
JxWang052 小时前
Task01:环境搭建,初识数据库
后端
周杰伦jc2 小时前
RocketMQ 完全指南:从入门到原理到生产实战、八股面试
后端