LangChain 语义相似性路由
·
https://python.langchain.com.cn/docs/expression_language/cookbook/embedding_router
LangChain 嵌入路由(Embedding Router)详解
本文档的核心是实现基于语义相似性的动态路由:通过将输入文本与多个预设模板的语义相似度比较,自动选择最匹配的处理逻辑(如不同领域的提示模板)。核心依赖“嵌入(Embedding)”和“余弦相似性(Cosine Similarity)”实现语义匹配,而非关键词匹配。
一、核心概念(无比喻,直接定义)
-
嵌入(Embedding)
一种将文本(如问题、模板)转换为固定长度高维向量的技术。语义越相近的文本,转换后的向量在向量空间中的距离越近。 -
余弦相似性(Cosine Similarity)
一种计算两个向量夹角余弦值的算法,结果范围为[-1, 1]。值越接近1,表明两个向量(对应文本)的语义相似度越高;越接近-1,相似度越低。 -
路由(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:实现路由逻辑(核心函数)
定义函数,接收用户输入的问题,通过以下步骤选择最匹配的模板:
- 将问题转换为嵌入向量;
- 计算问题向量与各模板向量的余弦相似性;
- 选择相似度最高的模板。
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",再输出数学领域的回答
三、核心逻辑总结
- 语义匹配是核心:通过嵌入向量和余弦相似性实现“输入问题→领域模板”的语义匹配,而非依赖关键词,因此对同义不同词的问题也能准确路由。
- 路由函数是关键:
prompt_router函数封装了“向量计算→模板选择”的逻辑,是整个路由过程的核心控制器。 - 链的串联逻辑:通过
RunnablePassthrough传递输入、RunnableLambda调用路由函数、模型生成回答、解析器处理输出,形成闭环流程。
关键注意点
- 模板设计需有区分度:不同领域的模板语义差异需足够明显,否则余弦相似性计算可能出现误判。
- 嵌入模型一致性:生成模板向量和问题向量必须使用同一嵌入模型,否则向量空间不兼容,无法准确计算相似度。
按上述步骤运行代码,可直观观察路由效果,理解“语义相似性如何驱动动态选择”的核心机制。
更多推荐

所有评论(0)