[LangGraph] 将子图添加为节点

核心概念

子图直接“嵌入”到父图内,成为父图的一部分,与父图共享 state 的 key。

示意图:

父图节点  ===>  { 子图内部的很多节点 } ===> 父图下一个节点

看起来像父图的一个 node,但内部却是一个完整的 graph。

也正因为如此:

  • 子图与父图使用同一个 state(共享部分或全部 key)

  • 子图内部的 node 可以读写父图 state

子图不再是一个“黑盒”,而是父图的一部分

适用场景:

  • 多团队协作,把一段流程拆成一个子图
  • 想复用一段常用流程(比如全局日志链路、消息管道)
  • 子图需要访问父图的多个 state 字段,不止输入/输出

快速上手

场景

客户注册需要执行复杂的 KYC(身份核验)流程:

子图负责:

  • OCR 身份证识别
  • 人脸对比
  • 风险评分
  • 写入结果到父图的 shared state

父图负责:

  • 收集用户信息
  • 调用 KYC 子图验证
  • 根据验证结果决定是否注册成功

示意图

flowchart TD %% ---------- 父图 ---------- subgraph ParentGraph[父图:用户注册流程] P_START([START]) P1[父节点:kycFlow<br/>(子图作为复合节点)] P2[父节点:finish<br/>根据 kycPassed 生成注册结果] P_END([END]) P_START --> P1 --> P2 --> P_END end %% ---------- 子图 ---------- subgraph Subgraph[子图:KYC 验证流程(共享父图 State)] S_START([START]) S1[子节点:OCR 校验<br/>ocrPassed] S2[子节点:人脸比对<br/>faceMatch] S3[子节点:KYC 汇总<br/>kycPassed] S_END([END]) S_START --> S1 --> S2 --> S3 --> S_END end %% ---------- 连接关系 ---------- %% 说明:父图的 kycFlow 节点 == 整个子图执行 P1 -. 调用并共享 state .-> S_START S_END -. 返回更新后的 state .-> P2

index.ts

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

async function main() {
  // 模拟数据
  const input = {
    userName: "张三",
    idCardImage: "path/idcard.jpg",
    selfImage: "path/selfie.jpg",
  };

  const result = await mainGraph.invoke(input);

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

mainGraph.ts

// 父图
import { StateGraph, START, END } from "@langchain/langgraph";
import { z } from "zod/v4";
import { kycSubgraph } from "./kycSubgraph.ts";

// 父图的状态
const MainState = z.object({
  // 用户注册的基本信息
  userName: z.string().describe("用户名"),
  idCardImage: z.string().describe("用户身份证图片"),
  selfImage: z.string().describe("用户相关的图片"),

  // 各种验证
  ocrPassed: z.boolean().optional().describe("ocr验证是否通过"),
  faceMatch: z.boolean().optional().describe("人脸匹配是否通过"),
  kycPassed: z.boolean().optional().describe("kyc验证是否通过"),

  // 最终注册的结果
  registerResult: z.string().optional().describe("最终注册的结果"),
});

type TMainState = z.infer<typeof MainState>;

function finish(state: TMainState) {
  if (state.kycPassed) {
    return {
      registerResult: "注册成功",
    };
  }
  return {
    registerResult: "注册失败,KYC验证没有通过",
  };
}

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

kycSubgraph.ts

// 做身份验证的子图
import { StateGraph, START, END } from "@langchain/langgraph";
import { z } from "zod/v4";

const KycState = z.object({
  // 用户注册的基本信息
  userName: z.string().describe("用户名"),
  idCardImage: z.string().describe("用户身份证图片"),
  selfImage: z.string().describe("用户相关的图片"),

  // 各种验证
  ocrPassed: z.boolean().optional().describe("ocr验证是否通过"),
  faceMatch: z.boolean().optional().describe("人脸匹配是否通过"),
  kycPassed: z.boolean().optional().describe("kyc验证是否通过"),
});

type TKycState = z.infer<typeof KycState>;

// 检查的节点:1. OCR验证   2. 人脸比对
async function ocrCheck() {
  const randomPass = Math.random() > 0.5; // 90% 通过

  console.log("🔍 [子图] OCR 检查:", randomPass);

  return { ocrPassed: randomPass };
}

async function faceMatchCheck() {
  const randomPass = Math.random() > 0.5; // 80% 通过

  console.log("🔍 [子图] 人脸比对:", randomPass);

  return { faceMatch: randomPass };
}

// 汇总节点
async function summary(state: TKycState) {
  // 这两项验证都通过了,那就是通过了
  const passed = state.ocrPassed && state.faceMatch;

  return {
    kycPassed: passed,
  };
}

export const kycSubgraph = new StateGraph(KycState)
  .addNode("ocrCheck", ocrCheck)
  .addNode("faceMatchCheck", faceMatchCheck)
  .addNode("summary", summary)
  // 边
  .addEdge(START, "ocrCheck")
  .addEdge("ocrCheck", "faceMatchCheck")
  .addEdge("faceMatchCheck", "summary")
  .addEdge("summary", END)
  .compile();

posted @ 2026-02-24 14:38  Zhentiw  阅读(2)  评论(0)    收藏  举报