RAG 进阶检索学习笔记

24 阅读5分钟

RAG 进阶检索学习笔记


一、函数回调规范化输出

是什么?

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

有什么用?

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

  • 返回结果直接是 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

关键知识点

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

二、混合检索(Ensemble Retriever)

是什么?

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

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

有什么用?

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

示例代码

python

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

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


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

是什么?

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

有什么用?

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

流程图

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

示例代码

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

关键知识点

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

总结对比

函数回调规范化输出混合检索路由分发
核心目标LLM 输出结构化提升检索召回率问题智能分流
关键组件with_structured_outputEnsembleRetrieverRouteQuery + choose_route
典型场景意图识别、信息抽取企业知识库检索多知识库 / 多 Agent 系统
依赖 LLM