RAG from Scratch-优化-routing


路由与查询构建篇

Part 10: Logical & Semantic Routing

逻辑路由 (Logical Routing)

使用函数调用进行分类路由。

python 复制代码
from typing import Literal
from langchain_core.pydantic_v1 import BaseModel, Field
from langchain_openai import ChatOpenAI

# 1. 定义路由数据模型
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"
    )

# 2. 使用结构化输出
llm = ChatOpenAI(model="gpt-3.5-turbo-0125", temperature=0)
structured_llm = llm.with_structured_output(RouteQuery)

# 3. 创建路由 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}"),
])

router = prompt | structured_llm

# 4. 测试路由
question = """Why doesn't the following code work:
from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_messages(["human", "speak in {language}"])
"""

result = router.invoke({"question": question})
print(result.datasource)  # 输出: python_docs
python 复制代码
# 5. 根据路由结果执行不同逻辑
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)
result = full_chain.invoke({"question": question})
print(result)  # 输出: chain for python_docs

语义路由 (Semantic Routing)

基于 Embedding 相似度进行路由。

python 复制代码
from langchain.utils.math import cosine_similarity
from langchain_openai import OpenAIEmbeddings

# 1. 定义不同领域的 Prompt 模板
physics_template = """You are a very smart physics professor.
You are great at answering questions about physics in a concise manner.
Here is a question: {query}"""

math_template = """You are a very good mathematician.
You are great at answering math questions.
Here is a question: {query}"""

# 2. 对 Prompt 进行 Embedding
embeddings = OpenAIEmbeddings()
prompt_templates = [physics_template, math_template]
prompt_embeddings = embeddings.embed_documents(prompt_templates)

# 3. 路由函数
def prompt_router(input):
    query_embedding = embeddings.embed_query(input["query"])
    similarity = cosine_similarity([query_embedding], prompt_embeddings)[0]
    most_similar = prompt_templates[similarity.argmax()]
    print("Using MATH" if most_similar == math_template else "Using PHYSICS")
    return PromptTemplate.from_template(most_similar)

# 4. 构建 Chain
chain = (
    {"query": RunnablePassthrough()}
    | RunnableLambda(prompt_router)
    | ChatOpenAI()
    | StrOutputParser()
)

# 5. 测试
answer = chain.invoke("What's a black hole")
# 输出: Using PHYSICS
# A black hole is a region in space where gravity is so strong...

Part 11: Query Structuring

核心思想

将自然语言查询转换为结构化的数据库查询,支持元数据过滤。

定义查询结构

python 复制代码
import datetime
from typing import Optional
from langchain_core.pydantic_v1 import BaseModel, Field

class TutorialSearch(BaseModel):
    """Search over a database of tutorial videos."""

    content_search: str = Field(
        ...,
        description="Similarity search query applied to video transcripts.",
    )
    title_search: str = Field(
        ...,
        description="Alternate version to apply to video titles.",
    )
    min_view_count: Optional[int] = Field(
        None,
        description="Minimum view count filter, inclusive.",
    )
    max_view_count: Optional[int] = Field(
        None,
        description="Maximum view count filter, exclusive.",
    )
    earliest_publish_date: Optional[datetime.date] = Field(
        None,
        description="Earliest publish date filter, inclusive.",
    )
    latest_publish_date: Optional[datetime.date] = Field(
        None,
        description="Latest publish date filter, exclusive.",
    )
    min_length_sec: Optional[int] = Field(
        None,
        description="Minimum video length in seconds.",
    )
    max_length_sec: Optional[int] = Field(
        None,
        description="Maximum video length in seconds.",
    )

查询分析器

python 复制代码
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.
Given a question, return a database query optimized to retrieve the most relevant results."""

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

测试示例

python 复制代码
# 示例 1: 简单查询
query_analyzer.invoke({"question": "rag from scratch"})
# 输出:
# content_search: rag from scratch
# title_search: rag from scratch

# 示例 2: 带时间过滤
query_analyzer.invoke({"question": "videos on chat langchain published in 2023"})
# 输出:
# content_search: chat langchain
# title_search: chat langchain
# earliest_publish_date: 2023-01-01
# latest_publish_date: 2024-01-01

# 示例 3: 带时长过滤
query_analyzer.invoke({
    "question": "how to use multi-modal models in an agent, only videos under 5 minutes"
})
# 输出:
# content_search: multi-modal models agent
# title_search: multi-modal models agent
# max_length_sec: 300
相关推荐
程序员的那些事_1 小时前
美团王兴:别叫我兴哥,减少登味。网友:好的,兴哥
人工智能
sea12161 小时前
Flask配置MySQL连接信息的最佳实践
python·mysql·flask
XW01059991 小时前
5-6统计工龄
数据结构·python·算法
酱紫学Java1 小时前
数据安全比赛:Python 内置函数实战指南
后端·python·网络安全
廿一夏1 小时前
数据存储容器
python
流水迢迢lst1 小时前
靶场练习day14--任意文件读取
网络·安全
清空mega2 小时前
《Vue Router 与 Pinia 入门:页面跳转、动态路由、全局状态管理一篇打通》
前端·javascript·vue.js
踩着两条虫2 小时前
AI 驱动的 Vue3 应用开发平台 深入探究(十):物料系统之内置组件库
前端·vue.js