[LangChain] 10. 条件路由

所谓条件路由(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 是一个类,因此使用的时候需要实例化:

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-

posted @ 2025-11-04 15:08  Zhentiw  阅读(12)  评论(0)    收藏  举报