[LangGraph] 从节点中调用子图

所谓子图,顾名思义,就是在一张图里面还嵌套了另外一张图。

子图常见的应用场景:

  1. 构建多智能体系统
  2. 在多个图中复用一组节点
  3. 分布式开发
    • 当希望不同团队独立开发图的不同部分时,可以将每个部分定义为一个子图。
    • 只要子图的接口(输入输出 schema)保持一致,父图就可以在无需了解子图内部细节的情况下进行构建。

在使用子图的时候,有两种使用方式:

  1. 从节点中调用子图:将子图作为一个函数来调用
  2. 将子图添加为节点

核心概念

在这种方式下:

  • 父图把输入传给子图;
  • 子图返回结果回给父图

这种模式下,子图看起来就像父图的一个“函数”,类似于平时我们写代码时调用一个函数:

const result = subProcess(input);

父流程不关心子流程内部的 node、状态、步骤。

父图与子图之间数据通过输入/输出 schema 明确传递,子图内部使用的 state,不直接暴露给父图。

示意图:

父图 NodeA → 子图(…) → 父图继续执行

快速上手

场景

父图负责处理“用户下单流程”。当用户下单时,父图会调用“风控子图”去判断订单是否有风险。

  • 子图内部有多步,例如:IP 检测 + 金额检测。
  • 父图调用子图 → 获取风控结果 → 决定是否继续下单或拒绝。

示意图

flowchart TD %% --- 父图部分 --- A[父图:下单流程<br/>Receive Order] --> B[节点:checkRisk<br/>调用子图执行风控] %% --- 调用子图 --- B --> subgraph_call[调用子图 → 风控子流程] %% --- 子图内部 --- subgraph Subgraph[子图:风控子流程] S1[Node1: 检查 IP 风险] S2[Node2: 检查金额风险] S3[Node3: 汇总并生成 riskLevel] S1 --> S2 --> S3 end subgraph_call --> S1 S3 --> C[父图继续:根据 riskLevel 判断结果<br/>生成 finalMessage]

rishsubgraph.ts

import { StateGraph, START, END } from "@langchain/langgraph";
import { z } from "zod/v4";

// 子图的状态
const RiskState = z.object({
  orderId: z.string().describe("订单ID"),
  ip: z.string().describe("下订单的IP"),
  amount: z.number().describe("订单的总金额"),
  ipRisk: z.boolean().optional().describe("IP是否存在风险"),
  amountRisk: z.boolean().optional().describe("金额是否存在风险"),
  riskLevel: z.enum(["low", "mid", "high"]).optional().describe("风险的等级"),
});

export type TRiskState = z.infer<typeof RiskState>;

// 两个子图节点:1. 检查IP  2. 检查金额
async function checkIpRisk(state: TRiskState) {
  const riskyIps = ["10.10.10.10", "123.123.123.123"]; // 这一组是有风险的IP
  const isRisk = riskyIps.includes(state.ip);

  return {
    ipRisk: isRisk,
  };
}

async function checkAmountRisk(state: TRiskState) {
  return {
    amountRisk: state.amount > 5000, // 假设订单总额大于5000就存在风险
  };
}

// 汇总节点
async function summarizeRisk(state: TRiskState) {
  const { ipRisk, amountRisk } = state;
  if (ipRisk || amountRisk) {
    return {
      riskLevel: "high",
    };
  }
  return {
    riskLevel: "low",
  };
}

// 创建子图
export const riskSubgraph = new StateGraph(RiskState)
  .addNode("checkIpRisk", checkIpRisk)
  .addNode("checkAmountRisk", checkAmountRisk)
  .addNode("summarizeRisk", summarizeRisk)
  // 边
  .addEdge(START, "checkIpRisk")
  .addEdge("checkIpRisk", "checkAmountRisk")
  .addEdge("checkAmountRisk", "summarizeRisk")
  .addEdge("summarizeRisk", END)
  .compile();

maingraph.ts

import { StateGraph, START, END } from "@langchain/langgraph";
import { z } from "zod/v4";
import { riskSubgraph } from "./risksubgraph.ts";

// 父图的状态
const OrderState = z.object({
  orderId: z.string().describe("订单的ID"),
  ip: z.string().describe("下订单的IP"),
  amount: z.number().describe("订单的金额"),
  riskLevel: z.enum(["low", "mid", "high"]).optional().describe("风险等级"),
  finalMessage: z.string().optional().describe("最终生成的消息"),
});

type TOrderState = z.infer<typeof OrderState>;

// 该节点用于检验订单是否存在风险
async function checkRisk(state: TOrderState) {
  console.log("父图开始检验订单是否存在风险");

  // 直接调用子图
  const result = await riskSubgraph.invoke({
    orderId: state.orderId,
    ip: state.ip,
    amount: state.amount,
  });

  console.log(`子图返回的结果为:`, result);

  return {
    riskLevel: result.riskLevel,
  };
}

// 根据风险的结果生成最终的信息
async function finish(state: TOrderState) {
  if (state.riskLevel === "high") {
    return {
      finalMessage: "订单被拒绝:风控风险过高。",
    };
  }

  return {
    finalMessage: "订单审核通过,允许下单。",
  };
}

export const mainGraph = new StateGraph(OrderState)
  .addNode("checkRisk", checkRisk)
  .addNode("finish", finish)
  // 边
  .addEdge(START, "checkRisk")
  .addEdge("checkRisk", "finish")
  .addEdge("finish", END)
  .compile();

index.ts

import { mainGraph } from "./maingraph.ts";

async function main() {
  const input = {
    orderId: "A10101",
    ip: "10.1.10.10",
    amount: 6000,
  };

  const result = await mainGraph.invoke(input);

  console.log("\n=====最终结果=======");
  console.log(result);
}
main();

posted @ 2026-02-23 14:53  Zhentiw  阅读(1)  评论(0)    收藏  举报