[LangChain] 10. 条件路由
所谓条件路由(Conditional Routing),就是在一条 AI 推理/数据处理流水线里,先依据输入内容、上下文或运行时信号做一次判别,再把请求分发到不同的子流程(链)上执行。本质上是受控的 if/else:先“判别”,再“选择”,最后“执行”,避免“一个提示词走天下”。
在 LangChain.js 中,常见做法有两种:
- 用
RunnableBranch声明式按顺序匹配分支(可视作 if/else-if/else 的旧范式) - 用
RunnableLambda在函数里动态返回要执行的子链(官方更推荐,灵活且易组合)
路由条件既可以是规则驱动(正则、关键字、用户角色、租户权限、时间段、阈值等),也可以是模型辅助分类(先用轻量 LLM 判断“数学/SQL/闲聊/RAG”等意图);命中失败时应设置兜底分支,保证可用性。
总结一句话:条件路由让系统不再押注单一路径,而是把“该走哪条路”的选择设计为一等公民。
RunnableBranch
RunnableBranch 是 LangChain.js 中提供的条件分派器。
它把一条流程拆成多条“候选子链”,并附带各自的触发条件,运行时从左到右依次评估条件,命中第一个就执行对应子链,若都不命中则走默认分支。可以把它理解成链式的 if/else if/else。
适用场景
- 意图分流:把“数学问答 / SQL 咨询 / 常规闲聊”分到不同处理链。
- 策略切换:根据上下文选择不同 Prompt / 模型 / 温度 或不同的工具集合。
- 合规与风控:命中敏感词/权限不足 → 分流到“拒答/脱敏/人工审核”链。
- 降本增效:先走便宜路径(检索/小模型),只有命中特定条件才走贵路径(推理型大模型)。
- 容错兜底:为无法归类或条件未命中的情况提供稳定的默认输出链。
基础语法
RunnableBranch 是一个类,因此使用的时候需要实例化:
new RunnableBranch({ branches, default })
- branches:对应的类型为
Branch<Runinput, Runoutput>[],其中 Runinput 是条件,Runoutput 是被选中后的子链。 - default:无分支命中时执行的
Runnable<RunInput, RunOutput>
RunnableBranch 还有一个静态方法 RunnableBranch.from([...BranchLike[], defaultRunnableLike]),该方法接受一个数组,数组的前 N 项是 [条件, 子链] 的数组,最后一项是默认子链。
RunnableBranch.from([
[条件1, 子链1],
[条件2, 子链2],
[条件3, 子链3],
默认子链
])
练习
使用 RunnableBranch 创建子链
import { ChatOllama } from "@langchain/ollama";
import {
ChatPromptTemplate,
SystemMessagePromptTemplate,
} from "@langchain/core/prompts";
import { StringOutputParser } from "@langchain/core/output_parsers";
import {
RunnableBranch,
RunnableLambda,
RunnablePassthrough,
RunnableSequence,
} from "@langchain/core/runnables";
// 1. 模型
const model = new ChatOllama({
model: "llama3",
});
// 2. 解析器
const parser = new StringOutputParser();
// 3. 三条“领域链” —— 物理 / 数学 / 其他
const physicsChain = ChatPromptTemplate.fromTemplate(
`
你是一位物理学家,擅长使用中文回答物理相关的问题,当你不知道问题的答案时,你就回答不知道。
具体问题如下:
{input}
`
)
.pipe(model)
.pipe(parser); // 物理链
const mathChain = ChatPromptTemplate.fromTemplate(
`
你是一个数学家,擅长使用中文回答数学相关的问题,当你不知道问题的答案时,你就回答不知道。
具体问题如下:
{input}
`
)
.pipe(model)
.pipe(parser); // 数学链
const otherChain = ChatPromptTemplate.fromTemplate(
`
你是一个AI助手,你会使用中文回答以下问题。
具体问题如下:
{input}
`
)
.pipe(model)
.pipe(parser); // 其它链
// 4. 分类链:输出“数学”/“物理”/“其它”
const classifyChain = ChatPromptTemplate.fromMessages([
[
"system",
`
你是一个仅输出标签的分类器。
任务:将用户问题分类为以下三类之一:数学、物理、其它。
规则:
- 只能输出且必须输出其中一个:数学 或 物理 或 其它
- 不要输出任何解释、理由、引号、标点、前后缀或额外空行
- 若不确定,输出:其它
`.trim(),
],
[
"human",
`
问题:
{input}
现在输出分类(仅一个词):`.trim(),
],
])
.pipe(model)
.pipe(parser); // 分类链
// 5. 分派器:根据 topic 路由到对应子链
const answerBranch = RunnableBranch.from([
[
(input) => input.topic.includes("物理"),
RunnableLambda.from((x) => {
console.log("执行了物理链");
return x;
}).pipe(physicsChain),
],
[
(input) => input.topic.includes("数学"),
RunnableLambda.from((x) => {
console.log("执行了数学链");
return x;
}).pipe(mathChain),
],
otherChain,
]);
// 6. 最终组合 - 最终给外部使用的,就是这个 finalChain
const finalChain = RunnableSequence.from([
// 当前的入参数:{ input: "什么是经典力学?" } --> { input: "什么是经典力学?" : topic: "物理"}
RunnablePassthrough.assign({
topic: classifyChain,
}),
answerBranch,
]);
// 测试
const run = async () => {
console.log(await finalChain.invoke({ input: "什么是经典力学?" }));
console.log(
await finalChain.invoke({ input: "对 y = x 求导的结果是多少?" })
);
console.log(await finalChain.invoke({ input: "你好,你是谁?" }));
};
run();
RunnableLambda
不过现在 官方更加推荐使用 RunnableLambda 的方式。因为 RunnableLambda 更灵活,在函数里可以做复杂判别、动态返回任意子链,便于与外部系统结合。
练习
使用 RunnableLambda 重构上面例子
import { ChatOllama } from "@langchain/ollama";
import {
ChatPromptTemplate,
SystemMessagePromptTemplate,
} from "@langchain/core/prompts";
import { StringOutputParser } from "@langchain/core/output_parsers";
import {
RunnableBranch,
RunnableLambda,
RunnablePassthrough,
RunnableSequence,
} from "@langchain/core/runnables";
// 1. 模型
const model = new ChatOllama({
model: "llama3",
});
// 2. 解析器
const parser = new StringOutputParser();
// 3. 三条“领域链” —— 物理 / 数学 / 其他
const physicsChain = ChatPromptTemplate.fromTemplate(
`
你是一位物理学家,擅长使用中文回答物理相关的问题,当你不知道问题的答案时,你就回答不知道。
具体问题如下:
{input}
`
)
.pipe(model)
.pipe(parser); // 物理链
const mathChain = ChatPromptTemplate.fromTemplate(
`
你是一个数学家,擅长使用中文回答数学相关的问题,当你不知道问题的答案时,你就回答不知道。
具体问题如下:
{input}
`
)
.pipe(model)
.pipe(parser); // 数学链
const otherChain = ChatPromptTemplate.fromTemplate(
`
你是一个AI助手,你会使用中文回答以下问题。
具体问题如下:
{input}
`
)
.pipe(model)
.pipe(parser); // 其它链
// 4. 分类链:输出“数学”/“物理”/“其它”
const classifyChain = ChatPromptTemplate.fromMessages([
[
"system",
`
你是一个仅输出标签的分类器。
任务:将用户问题分类为以下三类之一:数学、物理、其它。
规则:
- 只能输出且必须输出其中一个:数学 或 物理 或 其它
- 不要输出任何解释、理由、引号、标点、前后缀或额外空行
- 若不确定,输出:其它
`.trim(),
],
[
"human",
`
问题:
{input}
现在输出分类(仅一个词):`.trim(),
],
])
.pipe(model)
.pipe(parser); // 分类链
// 5. Router
const router = RunnableLambda.from(async (input) => {
// input: { input: "什么是经典力学?" }
const topic = await classifyChain.invoke({ input });
console.log(`topic: ${topic}`);
const chosenChain =
topic === "物理" ? physicsChain : topic === "数学" ? mathChain : otherChain;
return await chosenChain.invoke({ input });
});
// 6. 最终组合 - 最终给外部使用的,就是这个 finalChain
const finalChain = RunnableSequence.from([
RunnableLambda.from((x) => x),
router,
]);
// 测试
const run = async () => {
console.log(await finalChain.invoke({ input: "什么是经典力学?" }));
console.log(
await finalChain.invoke({ input: "对 y = x 求导的结果是多少?" })
);
console.log(await finalChain.invoke({ input: "你好,你是谁?" }));
};
run();
-EOF-

浙公网安备 33010602011771号