Skip to content

条件路由 ​

所谓条件路由(Conditional Routing),就是在一条 AI 推理/数据处理流水线里,先依据输入内容、上下文或运行时信号做一次判别,再把请求分发到不同的子流程(链)上执行。本质上是受控的 if/else:先“判别”,再“选择”,最后“执行”,避免“一个提示词走天下”。

在 LangChain.js 中,常见做法有两种:

  1. 用 RunnableBranch 声明式按顺序匹配分支(可视作 if/else-if/else 的旧范式)(官方不太推荐了)
  2. 用 RunnableLambda 在函数里动态返回要执行的子链(官方更推荐,灵活且易组合)

路由条件既可以是规则驱动(正则、关键字、用户角色、租户权限、时间段、阈值等),也可以是模型辅助分类(先用轻量 LLM 判断“数学/SQL/闲聊/RAG”等意图);命中失败时应设置兜底分支,保证可用性。

总结一句话:条件路由让系统不再押注单一路径,而是把“该走哪条路”的选择设计为一等公民。

RunnableBranch ​

RunnableBranch 是 LangChain.js 中提供的条件分派器。

它把一条流程拆成多条“候选子链”,并附带各自的触发条件,运行时从左到右依次评估条件,命中第一个就执行对应子链,若都不命中则走默认分支。可以把它理解成链式的 if/else if/else。

适用场景

  • 意图分流:把“数学问答 / SQL 咨询 / 常规闲聊”分到不同处理链。
  • 策略切换:根据上下文选择不同 Prompt / 模型 / 温度 或不同的工具集合。
  • 合规与风控:命中敏感词/权限不足 → 分流到“拒答/脱敏/人工审核”链。
  • 降本增效:先走便宜路径(检索/小模型),只有命中特定条件才走贵路径(推理型大模型)。
  • 容错兜底:为无法归类或条件未命中的情况提供稳定的默认输出链。

基础语法

RunnableBranch 是一个类,因此使用的时候需要实例化:

js
new RunnableBranch({ branches, default })
  • branches:对应的类型为 Branch<Runinput, Runoutput>[],其中 Runinput 是条件,Runoutput 是被选中后的子链。
  • default:无分支命中时执行的 Runnable<RunInput, RunOutput>

RunnableBranch 还有一个静态方法 RunnableBranch.from([...BranchLike[], defaultRunnableLike]),该方法接受一个数组,数组的前 N 项是 [条件, 子链] 的数组,最后一项是默认子链。

js
RunnableBranch.from([
  [条件1, 子链1],
  [条件2, 子链2],
  [条件3, 子链3],
  默认子链
])

课堂练习

使用 RunnableBranch 创建子链

RunnableLambda ​

不过现在 官方更加推荐使用 RunnableLambda 的方式。因为 RunnableLambda 更灵活,在函数里可以做复杂判别、动态返回任意子链,便于与外部系统结合。

课堂练习

使用 RunnableLambda 重构上面例子


-EOF-

RunnableBranch ​

js
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. TODO
// 分派器:根据 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,
    }),
    // RunnablePassthrough.assign()的设计机制:当 assign()方法的参数值是一个 Runnable(可运行对象)时,
    // LangChain 会在执行过程中自动调用该 Runnable,并将其返回值作为新字段的值。
    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 ​

js
import { ChatOllama } from "@langchain/ollama";
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { StringOutputParser } from "@langchain/core/output_parsers";
import { RunnableLambda, 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); // 分类链

// 路由器
const router = RunnableLambda.from(async ({ input }) => {
    // input : 入参 - “什么是经典力学?”、“对 y = x 求导的结果是多少”
    const topic = await classifyChain.invoke({
        input,
    });
    console.log("当前的分类结果:", topic); // “当前的分类结果: 物理”

    // 根据分类结果,选择一条链条
    const chosenChain = topic.includes("数学")
        ? mathChain
        : topic.includes("物理")
          ? physicsChain
          : otherChain;

    return await chosenChain.invoke({
        input,
    });
});

// 5. 组合为完整链
const finalChain = RunnableSequence.from([RunnableLambda.from((input) => input), 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();