跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像AI 生图工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
PythonAI算法

LangChain RAG 进阶:路由、查询构建与索引检索策略

LangChain RAG 进阶教程涵盖路由机制、结构化查询、多表示索引及重排序技术。通过逻辑与语义路由区分问题类型,利用元数据过滤器优化检索精度,采用多向量存储提升上下文关联度,并结合 RAG-Fusion 与 Cohere 重排序增强结果相关性。提供完整的 Python 代码示例与最佳实践建议,帮助开发者构建高效准确的检索增强生成系统。

292440837发布于 2025/2/7更新于 2026/7/2454 浏览
LangChain RAG 进阶:路由、查询构建与索引检索策略

LangChain RAG 进阶:路由、查询构建与索引检索策略

在构建检索增强生成(RAG)系统时,仅仅依赖简单的向量相似度搜索往往难以满足复杂场景的需求。本文深入探讨 LangChain 框架下的高级检索技术,涵盖路由机制、结构化查询、多表示索引以及重排序策略,旨在帮助开发者提升系统的准确性与效率。

路由 Routing

完成 Query Translation 之后进入 Routing 阶段。Routing 的核心意义在于根据不同的问题类型采取不同的处理策略。例如,涉及关系型数据库的问题应走 NL2SQL 路径,而涉及知识库的问题则走向量数据库查询路径。

逻辑和语义路由 Logical and Semantic routing

使用函数调用进行分类是实现路由的有效方式。通过定义结构化输出模型,LLM 可以将用户问题分类到特定的数据源。

from typing import Literal
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.pydantic_v1 import BaseModel, Field
from langchain_openai import ChatOpenAI

# Data model
class RouteQuery(BaseModel):
    """Route a user query to the most relevant datasource."""
    datasource: Literal["python_docs", "js_docs", "golang_docs"] = Field(
        ..., description="Given a user question choose which datasource would be most relevant for answering their question",
    )

# LLM with function call 
llm = ChatOpenAI(model="gpt-3.5-turbo-0125", temperature=0)
structured_llm = llm.with_structured_output(RouteQuery)

# Prompt 
system = """You are an expert at routing a user question to the appropriate data source.
Based on the programming language the question is referring to, route it to the relevant data source."""

prompt = ChatPromptTemplate.from_messages([
    ("system", system),
    ("human", "{question}"),
])

# Define router 
router = prompt | structured_llm

注意:我们使用函数调用来产生结构化输出,确保路由决策的稳定性。

question = """Why doesn't the following code work:
from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_messages(["human", "speak in {language}"])
prompt.invoke("french")
"""

result = router.invoke({"question": question})
# result.datasource -> 'python_docs'

def choose_route(result):
    if "python_docs" in result.datasource.lower():
        return "chain for python_docs"
    elif "js_docs" in result.datasource.lower():
        return "chain for js_docs"
    else:
        return "golang_docs"

from langchain_core.runnables import RunnableLambda

full_chain = router | RunnableLambda(choose_route)
full_chain.invoke({"question": question})
# 'chain for python_docs'

语义路由 Semantic routing

除了基于规则的逻辑路由,还可以利用语义相似度进行路由。通过将预设的提示词模板嵌入为向量,计算用户查询与模板向量的余弦相似度,从而选择最匹配的提示词。

from langchain.utils.math import cosine_similarity
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_core.runnables import RunnableLambda, RunnablePassthrough
from langchain_openai import ChatOpenAI, OpenAIEmbeddings

# Two prompts
physics_template = """You are a very smart physics professor. \You are great at answering questions about physics in a concise and easy to understand manner. \When you don't know the answer to a question you admit that you don't know.
Here is a question:
{query}"""

math_template = """You are a very good mathematician. You are great at answering math questions. \You are so good because you are able to break down hard problems into their component parts, \answer the component parts, and then put them together to answer the broader question.
Here is a question:
{query}"""

# Embed prompts
embeddings = OpenAIEmbeddings()
prompt_templates = [physics_template, math_template]
prompt_embeddings = embeddings.embed_documents(prompt_templates)

# Route question to prompt 
def prompt_router(input):
    # Embed question
    query_embedding = embeddings.embed_query(input["query"])
    # Compute similarity
    similarity = cosine_similarity([query_embedding], prompt_embeddings)[0]
    most_similar = prompt_templates[similarity.argmax()]
    # Chosen prompt 
    print("Using MATH" if most_similar == math_template else "Using PHYSICS")
    return PromptTemplate.from_template(most_similar)

chain = (
    {"query": RunnablePassthrough()}
    | RunnableLambda(prompt_router)
    | ChatOpenAI()
    | StrOutputParser()
)

print(chain.invoke("What's a black hole"))

输出示例显示系统正确识别了物理相关问题并选择了相应的提示词。

结构化查询 Query Construction

许多矢量存储包含元数据字段,这使得可以根据元数据过滤特定块成为可能。将自然语言转换为结构化搜索查询是提升检索精度的关键步骤。

元数据过滤器的查询结构 Query structuring for metadata filters

假设我们有一个包含视频教程的数据库,每个文档都有标题、描述、发布时间、观看次数等元数据。我们可以定义一个 Pydantic 模型来描述查询架构。

import datetime
from typing import Literal, Optional, Tuple
from langchain_core.pydantic_v1 import BaseModel, Field

class TutorialSearch(BaseModel):
    """Search over a database of tutorial videos about a software library."""
    content_search: str = Field(..., description="Similarity search query applied to video transcripts.")
    title_search: str = Field(..., description=("Alternate version of the content search query to apply to video titles. Should be succinct and only include key words that could be in a video title."))
    min_view_count: Optional[int] = Field(None, description="Minimum view count filter, inclusive. Only use if explicitly specified.")
    max_view_count: Optional[int] = Field(None, description="Maximum view count filter, exclusive. Only use if explicitly specified.")
    earliest_publish_date: Optional[datetime.date] = Field(None, description="Earliest publish date filter, inclusive. Only use if explicitly specified.")
    latest_publish_date: Optional[datetime.date] = Field(None, description="Latest publish date filter, exclusive. Only use if explicitly specified.")
    min_length_sec: Optional[int] = Field(None, description="Minimum video length in seconds, inclusive. Only use if explicitly specified.")
    max_length_sec: Optional[int] = Field(None, description="Maximum video length in seconds, exclusive. Only use if explicitly specified.")

    def pretty_print(self) -> None:
        for field in self.__fields__:
            if getattr(self, field) is not None and getattr(self, field) != getattr(self.__fields__[field], "default", None):
                print(f"{field}: {getattr(self, field)}")

from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI

system = """You are an expert at converting user questions into database queries. \You have access to a database of tutorial videos about a software library for building LLM-powered applications. \Given a question, return a database query optimized to retrieve the most relevant results.
If there are acronyms or words you are not familiar with, do not try to rephrase them."""
prompt = ChatPromptTemplate.from_messages([
    ("system", system),
    ("human", "{question}"),
])
llm = ChatOpenAI(model="gpt-3.5-turbo-0125", temperature=0)
structured_llm = llm.with_structured_output(TutorialSearch)
query_analyzer = prompt | structured_llm

# Example usage
query_analyzer.invoke({"question": "rag from scratch"}).pretty_print()

通过这种方式,LLM 能够理解用户的意图并将其转化为具体的过滤条件,如时间范围、长度限制等,从而实现更精准的检索。

索引 Indexing

高效的索引策略对于 RAG 的性能至关重要。传统的单向量索引可能无法捕捉文档的多层次语义信息。

Multi-representation Indexing

多表示索引允许我们将父文档(长文本)与其子文档(摘要或切片)分别索引。检索时先匹配子文档(摘要),再根据 ID 获取完整的父文档内容。这种方法结合了摘要的紧凑性和原文的完整性。

from langchain_community.document_loaders import WebBaseLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter

loader = WebBaseLoader("https://lilianweng.github.io/posts/2023-06-23-agent/")
docs = loader.load()

loader = WebBaseLoader("https://lilianweng.github.io/posts/2024-02-05-human-data-quality/")
docs.extend(loader.load())

import uuid
from langchain_core.documents import Document
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI

chain = (
    {"doc": lambda x: x.page_content}
    | ChatPromptTemplate.from_template("Summarize the following document:\n\n{doc}")
    | ChatOpenAI(model="gpt-3.5-turbo",max_retries=0)
    | StrOutputParser()
)

summaries = chain.batch(docs, {"max_concurrency": 5})

from langchain.storage import InMemoryByteStore
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
from langchain.retrievers.multi_vector import MultiVectorRetriever

# The vectorstore to use to index the child chunks
vectorstore = Chroma(collection_name="summaries",
                     embedding_function=OpenAIEmbeddings())

# The storage layer for the parent documents
store = InMemoryByteStore()
id_key = "doc_id"

# The retriever
retriever = MultiVectorRetriever(
    vectorstore=vectorstore,
    byte_store=store,
    id_key=id_key,
)
doc_ids = [str(uuid.uuid4()) for _ in docs]

# Docs linked to summaries
summary_docs = [
    Document(page_content=s, metadata={id_key: doc_ids[i]})
    for i, s in enumerate(summaries)
]

# Add
retriever.vectorstore.add_documents(summary_docs)
retriever.docstore.mset(list(zip(doc_ids, docs)))

query = "Memory in agents"
sub_docs = vectorstore.similarity_search(query,k=1)
sub_docs[0]

检索结果首先返回摘要,随后可以通过 retriever.get_relevant_documents 获取完整的原始文档内容。

ColBERT

ColBERT 是一种基于延迟交互的检索模型,它为段落中的每个标记生成受上下文影响的向量。相比传统向量检索,ColBERT 能更好地捕捉细粒度的语义匹配。

from ragatouille import RAGPretrainedModel
RAG = RAGPretrainedModel.from_pretrained("colbert-ir/colbertv2.0")

import requests

def get_wikipedia_page(title: str):
    URL = "https://en.wikipedia.org/w/api.php"
    params = {
        "action": "query",
        "format": "json",
        "titles": title,
        "prop": "extracts",
        "explaintext": True,
    }
    headers = {"User-Agent": "RAGatouille_tutorial/0.0.1"}
    response = requests.get(URL, params=params, headers=headers)
    data = response.json()
    page = next(iter(data["query"]["pages"].values()))
    return page["extract"] if "extract" in page else None

full_document = get_wikipedia_page("Hayao_Miyazaki")

RAG.index(
    collection=[full_document],
    index_name="Miyazaki-123",
    max_document_length=180,
    split_documents=True,
)

results = RAG.search(query="What animation studio did Miyazaki found?", k=3)
retriever = RAG.as_langchain_retriever(k=3)
retriever.invoke("What animation studio did Miyazaki found?")

Retrieval

检索阶段的优化直接影响最终回答的质量。除了基础检索,重排序(Re-ranking)技术可以显著提升结果的相关性。

Re-ranking

重排序通常分为两阶段:粗排(召回)和精排(重排序)。RAG-Fusion 是一种无需额外训练的重排序方法,它通过生成多个变体查询并合并结果来实现。

import bs4
from langchain_community.document_loaders import WebBaseLoader
loader = WebBaseLoader(
    web_paths=("https://lilianweng.github.io/posts/2023-06-23-agent/",),
    bs_kwargs=dict(
        parse_only=bs4.SoupStrainer(
            class_=("post-content", "post-title", "post-header")
        )
    ),
)
blog_docs = loader.load()

from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
    chunk_size=300, 
    chunk_overlap=50)

splits = text_splitter.split_documents(blog_docs)

from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
vectorstore = Chroma.from_documents(documents=splits, 
                                    embedding=OpenAIEmbeddings())

retriever = vectorstore.as_retriever()

from langchain.prompts import ChatPromptTemplate

template = """You are a helpful assistant that generates multiple search queries based on a single input query. \nGenerate multiple search queries related to: {question} \nOutput (4 queries):"""
prompt_rag_fusion = ChatPromptTemplate.from_template(template)

from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI

generate_queries = (
    prompt_rag_fusion 
    | ChatOpenAI(temperature=0)
    | StrOutputParser() 
    | (lambda x: x.split("\n"))
)

from langchain.load import dumps, loads

def reciprocal_rank_fusion(results: list[list], k=60):
    fused_scores = {}
    for docs in results:
        for rank, doc in enumerate(docs):
            doc_str = dumps(doc)
            if doc_str not in fused_scores:
                fused_scores[doc_str] = 0
            previous_score = fused_scores[doc_str]
            fused_scores[doc_str] += 1 / (rank + k)
    reranked_results = [
        (loads(doc), score)
        for doc, score in sorted(fused_scores.items(), key=lambda x: x[1], reverse=True)
    ]
    return reranked_results

question = "What is task decomposition for LLM agents?"
retrieval_chain_rag_fusion = generate_queries | retriever.map() | reciprocal_rank_fusion
docs = retrieval_chain_rag_fusion.invoke({"question": question})
len(docs)

此外,也可以使用专门的 Rerank 模型(如 Cohere Rerank)对初步检索结果进行重新打分。

from langchain_community.llms import Cohere
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import CohereRerank

retriever = vectorstore.as_retriever(search_kwargs={"k": 10})
compressor = CohereRerank()
compression_retriever = ContextualCompressionRetriever(
    base_compressor=compressor, base_retriever=retriever
)
compressed_docs = compression_retriever.get_relevant_documents(question)

总结与最佳实践

本文详细介绍了 LangChain RAG 系统中的高级组件。在实际应用中,建议遵循以下原则:

  1. 路由优先:当存在多种异构数据源时,先通过路由机制分流,避免无效检索。
  2. 结构化查询:充分利用元数据过滤能力,将自然语言约束转化为精确的数据库查询条件。
  3. 多层次索引:采用多表示索引策略,平衡检索速度与上下文信息的完整性。
  4. 重排序优化:在召回后引入重排序步骤,特别是使用 RAG-Fusion 或专用 Rerank 模型,可显著提升 Top-K 结果的相关性。

通过组合上述技术,开发者可以构建出更加鲁棒、准确且高效的 RAG 应用,满足企业级复杂场景的需求。

目录

  1. LangChain RAG 进阶:路由、查询构建与索引检索策略
  2. 路由 Routing
  3. 逻辑和语义路由 Logical and Semantic routing
  4. Data model
  5. LLM with function call
  6. Prompt
  7. Define router
  8. result.datasource -> 'python_docs'
  9. 'chain for python_docs'
  10. 语义路由 Semantic routing
  11. Two prompts
  12. Embed prompts
  13. Route question to prompt
  14. 结构化查询 Query Construction
  15. 元数据过滤器的查询结构 Query structuring for metadata filters
  16. Example usage
  17. 索引 Indexing
  18. Multi-representation Indexing
  19. The vectorstore to use to index the child chunks
  20. The storage layer for the parent documents
  21. The retriever
  22. Docs linked to summaries
  23. Add
  24. ColBERT
  25. Retrieval
  26. Re-ranking
  27. 总结与最佳实践
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 深入解析 Java 线程池的开源扩展方案
  • Whisper.cpp 模型选型实战:性能与准确率实测
  • 链表算法实战:排队顺序与单向链表实现
  • 从麦克斯韦到无人机:有感 FOC 与无感 FOC 的深度解析
  • Transformer 与大模型应用开发核心指南
  • Llama 3.1 开源发布:LLM 新里程碑与部署指南
  • OpenClaw 部署实战:本地 AI 数字员工搭建与安全配置
  • Linux 基础指令与权限管理实战指南
  • 2022 信奥赛 C++ 提高组 CSP-S 复赛真题及题解:星战
  • 青龙面板结合内网穿透实现定时任务自动化及远程监控
  • Python 基于大数据的 B 站热门视频数据分析与研究系统
  • 冷启动数据与多阶段训练在 DeepSeek 中的作用
  • OpenClaw 汉化中文版部署指南:npm/Docker/脚本三种安装方式及常见问题解决
  • 金融数据分析常用工具:Python、R 与 SQL 对比
  • 基于 Leaflet Trackplayer 的 WebGIS 高速轨迹可视化实战
  • 字节开源 DeerFlow 2.0:从深度研究到 Super Agent 基础设施
  • Go2 机器人 ROS2 仿真环境搭建指南
  • Python 系统学习流程图与进阶指南
  • 基于 Azure 和 OpenAI 构建智能语音客服系统
  • Visual C++ Redistributable 运行库安装与故障排查指南

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • RSA密钥对生成器

    生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online

  • Mermaid 预览与可视化编辑

    基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online

  • 随机西班牙地址生成器

    随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • curl 转代码

    解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online