https://python.langchain.com.cn/docs/expression_language/cookbook/embedding_router

LangChain 嵌入路由(Embedding Router)详解

本文档的核心是实现基于语义相似性的动态路由:通过将输入文本与多个预设模板的语义相似度比较,自动选择最匹配的处理逻辑(如不同领域的提示模板)。核心依赖“嵌入(Embedding)”和“余弦相似性(Cosine Similarity)”实现语义匹配,而非关键词匹配。

一、核心概念(无比喻,直接定义)

  1. 嵌入(Embedding)
    一种将文本(如问题、模板)转换为固定长度高维向量的技术。语义越相近的文本,转换后的向量在向量空间中的距离越近。

  2. 余弦相似性(Cosine Similarity)
    一种计算两个向量夹角余弦值的算法,结果范围为[-1, 1]。值越接近1,表明两个向量(对应文本)的语义相似度越高;越接近-1,相似度越低。

  3. 路由(Routing)
    根据输入文本与预设模板的语义相似度,选择对应模板进行处理的过程。本文档中,路由的目标是为输入问题匹配最相关的领域提示模板。

二、实现步骤(代码+作用详解)

步骤1:安装依赖库

需安装处理嵌入、向量计算和模型调用的库:

# 安装核心库:langchain-core(基础组件)、langchain(工具函数)、langchain-openai(嵌入和模型)
%pip install --upgrade --quiet langchain-core langchain langchain-openai

步骤2:定义领域提示模板

预设多个领域的提示模板,每个模板定义该领域的处理规则(如回答风格、专业方向)。本文档示例包含“物理”和“数学”两个领域:

from langchain_core.prompts import PromptTemplate

# 物理领域模板:定义物理问题的回答规则(简洁、易懂)
physics_template = """You are a physics professor. Answer physics questions concisely. \
Admit if you don't know.
Question: {query}"""

# 数学领域模板:定义数学问题的回答规则(拆分问题、分步解答)
math_template = """You are a mathematician. Answer math questions by breaking them down. \
Explain each step clearly.
Question: {query}"""

# 存储所有模板,用于后续生成嵌入
prompt_templates = [physics_template, math_template]

步骤3:生成嵌入向量

将预设的提示模板转换为嵌入向量(提前计算,避免重复消耗资源):

from langchain_openai import OpenAIEmbeddings

# 初始化嵌入模型(用于将文本转换为向量)
embeddings = OpenAIEmbeddings()

# 为所有提示模板生成嵌入向量(返回一个向量列表,顺序与模板列表一致)
# 即 prompt_embeddings[0] 对应 physics_template 的向量,prompt_embeddings[1] 对应 math_template 的向量
prompt_embeddings = embeddings.embed_documents(prompt_templates)

步骤4:实现路由逻辑(核心函数)

定义函数,接收用户输入的问题,通过以下步骤选择最匹配的模板:

  1. 将问题转换为嵌入向量;
  2. 计算问题向量与各模板向量的余弦相似性;
  3. 选择相似度最高的模板。
from langchain.utils.math import cosine_similarity

def prompt_router(input_data):
    # 提取用户输入的问题(input_data 为字典,键 "query" 对应问题文本)
    query = input_data["query"]
    
    # 1. 将问题转换为嵌入向量
    query_embedding = embeddings.embed_query(query)
    
    # 2. 计算问题向量与各模板向量的余弦相似性
    # cosine_similarity 接收两个二维列表,返回相似度矩阵;[0] 取第一行(问题与各模板的相似度)
    similarities = cosine_similarity([query_embedding], prompt_embeddings)[0]
    
    # 3. 找到相似度最高的模板索引(argmax() 返回最大值的位置)
    most_similar_idx = similarities.argmax()
    selected_template = prompt_templates[most_similar_idx]
    
    # 打印选中的领域(用于调试,确认路由结果)
    if selected_template == math_template:
        print("Using MATH template")
    else:
        print("Using PHYSICS template")
    
    # 将选中的模板转换为 PromptTemplate 对象,用于后续生成回答
    return PromptTemplate.from_template(selected_template)

步骤5:构建完整处理链

通过 LangChain 表达式语言(LCEL)将“输入处理→路由选择→模型调用→结果解析”串联为完整链:

from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnableLambda, RunnablePassthrough
from langchain_openai import ChatOpenAI

# 初始化大语言模型(用于根据选中的模板生成回答)
model = ChatOpenAI()

# 构建链:
chain = (
    {"query": RunnablePassthrough()}  # 传递用户输入的问题(键 "query" 与路由函数匹配)
    | RunnableLambda(prompt_router)   # 调用路由函数,选择匹配的模板
    | model                           # 模型根据选中的模板生成回答
    | StrOutputParser()               # 将模型输出转换为字符串(去除原始消息格式)
)

步骤6:测试路由效果

输入不同领域的问题,验证路由是否正确选择模板:

# 测试1:物理问题(预期选择物理模板)
print(chain.invoke("What is a black hole?"))
# 输出:先打印 "Using PHYSICS template",再输出物理领域的回答

# 测试2:数学问题(预期选择数学模板)
print(chain.invoke("What is a path integral?"))
# 输出:先打印 "Using MATH template",再输出数学领域的回答

三、核心逻辑总结

  1. 语义匹配是核心:通过嵌入向量和余弦相似性实现“输入问题→领域模板”的语义匹配,而非依赖关键词,因此对同义不同词的问题也能准确路由。
  2. 路由函数是关键prompt_router 函数封装了“向量计算→模板选择”的逻辑,是整个路由过程的核心控制器。
  3. 链的串联逻辑:通过 RunnablePassthrough 传递输入、RunnableLambda 调用路由函数、模型生成回答、解析器处理输出,形成闭环流程。

关键注意点

  • 模板设计需有区分度:不同领域的模板语义差异需足够明显,否则余弦相似性计算可能出现误判。
  • 嵌入模型一致性:生成模板向量和问题向量必须使用同一嵌入模型,否则向量空间不兼容,无法准确计算相似度。

按上述步骤运行代码,可直观观察路由效果,理解“语义相似性如何驱动动态选择”的核心机制。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐