之前写过逻辑路由,靠 LLM 判断该走哪条链,但这样每次都要多消耗一次 LLM 调用。
语义路由不一样,直接拿向量余弦相似度来判断,不走 LLM,快还省钱。
思路
先把所有 prompt 模板提前算好向量存着,用户问问题的时候,把问题也转成向量,跟模板们挨个算一下相似度,哪个分最高用哪个。
模板预处理(启动时执行一次):
[物理模板, 数学模板] → embed_documents() → 向量列表
每次提问:
query → embed_query() → cosine_similarity() → argmax() → 选模板 → LLM
代码
先定义两个不同领域的模板:
python
physics_template = """你是一位非常聪明的物理教授。
你擅长以简洁易懂的方式回答物理问题。
当你不知道问题的答案时,你会坦率承认自己不知道。
这是一个问题:{query}"""
math_template = """你是一位非常优秀的数学家。你擅长回答数学问题。
你之所以如此优秀,是因为你能将复杂的问题分解成多个小步骤,
并且回答这些小步骤,然后将它们整合在一起回答更广泛的问题。
这是一个问题:{query}"""
模板向量只需要算一次,放在外面别放函数里,不然每次调用都重新算,浪费:
python
embeddings = QianfanEmbeddingsEndpoint()
prompt_templates = [physics_template, math_template]
prompt_embeddings = embeddings.embed_documents(prompt_templates) # 提前算好
路由函数,核心就三步:
python
def prompt_router(input) -> ChatPromptTemplate:
# 1. 算 query 的向量
query_embedding = embeddings.embed_query(input["query"])
# 2. 跟模板们算相似度,取最高的
similarity = cosine_similarity([query_embedding], prompt_embeddings)[0]
most_similar = prompt_templates[similarity.argmax()]
print("使用数学模板" if most_similar == math_template else "使用物理模板")
return ChatPromptTemplate.from_template(most_similar)
最后串成链,注意这里用 RunnableLambda 把普通函数包一下才能塞进 LCEL:
python
chain = (
{"query": RunnablePassthrough()}
| RunnableLambda(prompt_router)
| ChatOpenAI(model="moonshot-v1-8k")
| StrOutputParser()
)
print(chain.invoke("黑洞是什么?"))
print(chain.invoke("能介绍下余弦计算公式么?"))
和逻辑路由的区别
| 逻辑路由 | 语义路由 | |
|---|---|---|
| 怎么判断 | LLM 理解后输出分类 | 向量余弦相似度 |
| 多一次 LLM 调用 | ✅ | ❌ |
| 速度 | 慢 | 快 |
| 适合场景 | 路由逻辑复杂 | 分类明确、追求低延迟 |
类别少语义差异明显的时候用语义路由,逻辑复杂的时候再上 LLM 判断。