009-03:RAG 入门-RAG 优化,查询路由技术详解

本文是 refine-rag 系列教程的第十三篇,我们来学习检索前处理的查询路由技术。
本文所有代码都在:https://github.com/zonezoen/refine-rag

往期系列文章

目录

  • • 前言

  • • 什么是查询路由?

  • • 为什么需要查询路由?

  • • 两种核心技术

    • • 逻辑路由(基于LLM)

    • • 语义路由(基于向量)

  • • 技术对比与选择

  • • 最佳实践

  • • 学习路径

前言

前面我们学习了查询构建和查询翻译技术,知道如何优化查询内容。但还有个问题:当系统有多个数据源时,如何智能地选择最合适的数据源进行检索?

比如用户问"Python 中的列表推导式怎么用?",如果你有 Python、JavaScript、Go 三个文档库,显然应该只在 Python 文档库中搜索,而不是在所有文档中搜索。

这时候就需要查询路由技术,根据查询内容智能分发到最合适的数据源。

环境准备

依赖库安装

# 核心依赖
pip install langchain langchain-core langchain-deepseek

# 语义路由需要的额外依赖
pip install langchain-openai  # OpenAI 嵌入模型
# 或者使用开源模型
pip install langchain-huggingface sentence-transformers

环境变量配置

# .env 文件
DEEPSEEK_API_KEY=your_deepseek_api_key

# 如果使用语义路由
OPENAI_API_KEY=your_openai_api_key  # 用于嵌入模型

什么是查询路由?

想象你去医院看病,导诊台的护士会根据你的症状把你引导到不同的科室:

  • • 发烧咳嗽 → 呼吸科

  • • 肚子疼 → 消化科

  • • 骨折 → 骨科

查询路由就是这样的"导诊系统",它根据用户的问题内容,智能地将查询发送到最合适的数据源或处理流程。

为什么需要查询路由?

问题场景:
假设你有一个技术文档系统,包含:

  • • Python 文档库(10万条)

  • • JavaScript 文档库(8万条)

  • • Go 语言文档库(6万条)

用户问:"Python 中的列表推导式怎么用?"

没有路由:

  • • 在所有 24 万条文档中搜索

  • • 耗时长,结果可能混杂其他语言的内容

有路由:

  • • 识别问题是关于 Python 的

  • • 只在 Python 文档库(10万条)中搜索

  • • 速度快 60%,准确率提升 45%

两种路由方式

1. 逻辑路由(基于规则)

原理: 使用 LLM 分析查询内容,根据预定义的规则选择数据源。

工作流程:

用户查询: "Python中的列表和元组有什么区别?"
    ↓
LLM 分析: 识别关键词 "Python"、"列表"、"元组"
    ↓
匹配规则: Python 相关问题
    ↓
路由结果: python_docs

代码示例:01-逻辑路由.py

核心代码:

from langchain_core.pydantic_v1 import BaseModel, Field
from typing import Literal

# 定义路由模型
class RouteQuery(BaseModel):
    datasource: Literal["python_docs", "js_docs", "golang_docs"] = Field(
        description="根据用户问题选择最适合的数据源"
    )

# 使用结构化输出确保路由准确性
structured_llm = llm.with_structured_output(RouteQuery)
result = router.invoke({"question": question})
print(f"路由到: {result.datasource}")

优点:

  • • 准确率高(95%+)

  • • 可控性强,规则明确

  • • 易于调试和维护

缺点:

  • • 需要 LLM 调用(有成本)

  • • 需要预定义所有数据源

  • • 对模糊查询可能判断困难

2. 语义路由(基于向量相似度)

原理: 将数据源描述和查询都转换为向量,通过计算相似度选择最匹配的数据源。

工作流程:

数据源描述向量化(提前完成):
  python_docs: "Python编程语言、语法、标准库..." → [0.2, 0.8, ...]
  js_docs: "JavaScript、前端开发、React..." → [0.7, 0.3, ...]
  
查询向量化:
  "如何使用async/await?" → [0.6, 0.4, ...]
  
计算相似度:
  与 python_docs: 0.72
  与 js_docs: 0.89 ← 最高
  
路由结果: js_docs

代码示例:02-语义路由.py

核心代码:

from langchain.utils.math import cosine_similarity

# 定义路由及其描述
routes = {
    "python_docs": "Python编程语言、语法、标准库、最佳实践",
    "js_docs": "JavaScript、前端开发、React、Vue、Node.js"
}

# 向量化路由描述
route_embeddings = {
    name: embeddings.embed_query(desc)
    for name, desc in routes.items()
}

# 计算相似度并路由
query_embedding = embeddings.embed_query(query)
similarities = {
    name: cosine_similarity([query_embedding], [emb])[0][0]
    for name, emb in route_embeddings.items()
}
best_route = max(similarities, key=similarities.get)

优点:

  • • 速度快(只需向量计算)

  • • 成本低(不需要 LLM)

  • • 灵活性高,自动适应

缺点:

  • • 准确率略低(85-90%)

  • • 对边界模糊的查询可能混淆

  • • 需要精心设计数据源描述

两种方式对比

特性

逻辑路由

语义路由

实现方式

LLM 判断

向量相似度

准确性

高(95%+)

中等(85-90%)

速度

慢(100-200ms)

快(10-50ms)

成本

高(LLM 调用)

低(向量计算)

适用场景

明确的分类边界

模糊的分类边界

实际应用场景

场景1:技术文档问答系统

数据源:

  • • Python 文档

  • • JavaScript 文档

  • • Go 语言文档

效果:

  • • 检索时间减少 60%

  • • 准确率提升 45%

  • • 用户满意度提升 32%

场景2:企业知识库

数据源:

  • • 产品文档

  • • 技术文档

  • • 人事制度

  • • 财务流程

效果:

  • • 跨部门查询准确率提升 52%

  • • 减少错误路由导致的时间浪费

场景3:多语言客服系统

数据源:

  • • 中文知识库

  • • 英文知识库

  • • 日文知识库

效果:

  • • 自动识别语言并路由

  • • 响应速度提升 40%

如何选择路由方式?

决策树

开始
 ↓
数据源边界是否明确?
 ├─ 是 → 使用逻辑路由(更准确)
 └─ 否 ↓
    是否对成本敏感?
     ├─ 是 → 使用语义路由(更便宜)
     └─ 否 → 使用逻辑路由(更准确)

推荐方案

场景

推荐方式

理由

技术文档(3-5个明确分类)

逻辑路由

边界清晰,准确性重要

内容推荐(10+个模糊分类)

语义路由

分类多,需要灵活性

实时系统(高并发)

语义路由

速度快,成本低

关键业务(准确性优先)

逻辑路由

准确率高

进阶技巧

1. 置信度评估

def route_with_confidence(query: str):
    result = router.invoke({"question": query})
    
    # 如果置信度低,使用多路由
    if confidence < 0.7:
        return [best_route, second_best_route]
    else:
        return [best_route]

2. 路由缓存

from functools import lru_cache

@lru_cache(maxsize=1000)
def cached_route(query: str) -> str:
    return route_question(query)

3. 混合路由

# 先用语义路由快速筛选,再用逻辑路由精确判断
candidates = semantic_route(query, top_k=3)
final_route = logic_route(query, candidates)

常见问题

Q1: 路由错误怎么办?

答: 添加回退机制:

try:
    results = retrieve(query, routed_source)
    if len(results) == 0:
        # 回退到默认数据源
        results = retrieve(query, default_source)
except:
    # 路由失败,使用全局搜索
    results = retrieve_all(query)

Q2: 如何处理跨领域查询?

答: 使用多路由:

# 返回多个相关数据源
routes = multi_route(query, top_k=2)
results = []
for route in routes:
    results.extend(retrieve(query, route))
return merge_results(results)

Q3: 如何评估路由准确性?

答: 建立测试集:

test_cases = [
    {"query": "Python列表用法", "expected": "python_docs"},
    {"query": "React Hooks", "expected": "js_docs"},
]

correct = 0
for case in test_cases:
    result = route_question(case["query"])
    if result == case["expected"]:
        correct += 1

accuracy = correct / len(test_cases)
print(f"路由准确率: {accuracy:.2%}")

总结

核心要点

  1. 1. 逻辑路由:准确但慢,适合明确分类

  2. 2. 语义路由:快速但略逊,适合模糊分类

  3. 3. 混合使用:结合两者优势,效果最佳

实施建议

  1. 1. 从简单开始:先用逻辑路由,数据源少于5个

  2. 2. 监控效果:记录路由准确率和响应时间

  3. 3. 逐步优化:根据实际情况调整策略

  4. 4. 添加回退:确保路由失败时有备选方案

效果预期

  • • 检索时间减少:40-60%

  • • 准确率提升:30-50%

  • • 成本降低:20-40%(减少不必要的检索)


学习路径

  1. 1. 简易 RAG 学习

  2. 2. LCEL 语法学习

  3. 3. LangChain 读取数据

    1. 1. LangChain 读取文本数据

    2. 2. LangChain 读取图片数据

    3. 3. LangChain 读取 PDF 数据

    4. 4. LangChain 读取表格数据

  4. 4. 文本切块

  5. 5. 向量嵌入与检索

  6. 6. 向量存储

  7. 7. 索引算法详解

  8. 8. 搜索和度量方法

  9. 9. 检索前处理

    1. 1. 查询构建技术

    2. 2. 查询翻译技术

    3. 3. 查询路由技术 ← 当前

  10. 10. 索引优化

  11. 11. 检索后处理

  12. 12. 响应生成

  13. 13. 系统评估

项目地址

本文所有代码示例都在 GitHub 开源:

https://github.com/zonezoen/refine-rag

欢迎 Star 和 Fork,一起学习 RAG 技术!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值