[LangGraph] 循环

1. 快速上手

实现循环的图

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

// 定义状态的Schema
const Schema = z.object({
  count: z.number(), // 计数器
});

// 根据Schema生成对应的ts类型
type TState = z.infer<typeof Schema>;

// 辅助函数
function sleep(ms: number) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

// 构建图 - 返回编译后的图实例
function buildGraph(maxCount: number) {
  // 节点
  async function increment(state: TState) {
    const next = state.count + 1;
    await sleep(1500);
    return {
      count: next,
    };
  }

  // 条件函数
  function routeFunc(state: TState) {
    // 判断是否还没有到达最大值
    return state.count < maxCount ? "true" : "false";
  }

  return new StateGraph(Schema)
    .addNode("increment", increment)
    .addEdge(START, "increment")
    .addConditionalEdges("increment", routeFunc, {
      true: "increment",
      false: END,
    })
    .compile();
}

// 主方法
async function main() {
  // 提取用户在终端输入的参数
  // --max 最大值
  // --start 起始值
  // node index.ts --max 10 --start 5
  const argv = globalThis.process.argv.slice(2);

  const args: Record<string, string> = {};
  for (let i = 0; i < argv.length; i += 2) {
    args[argv[i].replace(/^--/, "")] = argv[i + 1];
  }

  const max = Number(args.max ?? 5); // 提取出来 max 值,如果没有 max,取默认值 5
  const start = Number(args.start ?? 0); // 提取出来 start 值,如果没有 start,取默认值 0

  const graph = buildGraph(max);

  console.log(`开始执行:起始值count为${start},最大值为${max}`);

  const stream = await graph.stream({
    count: start,
  });

  for await (const update of stream) {
    console.log(`每一次的更新:`);
    console.log(update);
    console.log("\n");
  }

  console.log("循环结束");
}

main();

2. 订单状态轮询

场景需求

构建一个流程用来轮询订单状态,直到:

  1. 订单处理成功(正常结束)
  2. 尝试次数超过最大值(超时结束)
  3. 中途发生网络异常、第三方服务异常等偶发错误,但流程仍能继续

流程图

flowchart TD START([START]) END([END]) START --> fetchStatus subgraph 查询订单状态 fetchStatus[fetchStatus<br/>查询订单状态] end fetchStatus --> shouldContinue subgraph 继续判断 shouldContinue{shouldContinue<br/>是否继续轮询?} end shouldContinue -->|处理成功| END shouldContinue -->|超出最大次数| END shouldContinue -->|继续重试| waitNext subgraph 等待后继续轮询 waitNext[waitNext<br/>等待 2 秒后重试] end waitNext --> fetchStatus
// 订单状态轮询
import { StateGraph, START, END } from "@langchain/langgraph";
import { z } from "zod/v4";

// 订单状态枚举
export const OrderStatus = {
  PENDING: 0,
  PROCESSING: 1,
  SUCCESS: 2,
  FAIL: 3,
  NETWORK_ERR: 4,
  INTERNAL_ERR: 5,
} as const;

// 订单状态类型
export type OrderStatus = (typeof OrderStatus)[keyof typeof OrderStatus];

// 订单状态描述映射
const StatusMap: Record<OrderStatus, string> = {
  [OrderStatus.PENDING]: "待处理",
  [OrderStatus.PROCESSING]: "处理中",
  [OrderStatus.SUCCESS]: "处理成功",
  [OrderStatus.FAIL]: "处理失败",
  [OrderStatus.NETWORK_ERR]: "网络错误",
  [OrderStatus.INTERNAL_ERR]: "内部错误",
};

// 图的Schema
const Schema = z.object({
  orderId: z.string().describe("订单的id"),
  status: z.enum(OrderStatus).describe("订单状态"),
  attempt: z.number().describe("轮询的次数"),
});

// 根据Schema生成 ts 类型
type TState = z.infer<typeof Schema>;

// 工具 sleep
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));

// 模拟外部查询
async function mockFetchOrder(
  orderId: string,
  attempt: number,
): Promise<OrderStatus> {
  // 日志:这是第几次轮询
  console.log(`当前是第${attempt}次查询`);

  await sleep(2000); // 模拟异步请求等待一段时间

  // 更新状态
  if (Math.random() < 0.1) return OrderStatus.NETWORK_ERR; // 一定的几率,网络出错
  if (Math.random() < 0.1) return OrderStatus.FAIL; // 一定的几率,其它错误

  if (attempt < 3) return OrderStatus.PENDING;
  if (attempt === 3) return OrderStatus.PROCESSING;
  if (attempt === 4) return OrderStatus.SUCCESS;

  return OrderStatus.INTERNAL_ERR;
}

function buildGraph(maxRetries: number) {
  async function fetchStatus(state: TState) {
    const status = await mockFetchOrder(state.orderId, state.attempt);

    console.log(`订单${state.orderId}的状态是:${StatusMap[status]}`);

    const isFinal =
      status === OrderStatus.SUCCESS ||
      status === OrderStatus.FAIL ||
      status === OrderStatus.NETWORK_ERR ||
      status === OrderStatus.INTERNAL_ERR;

    return { status, attempt: isFinal ? state.attempt : state.attempt + 1 };
  }

  async function waitNext() {
    console.log(`等待2秒后继续轮询`);
    await sleep(2000);
    return {};
  }

  function routeFunc(state: TState) {
    if (
      state.status === OrderStatus.SUCCESS ||
      state.status === OrderStatus.FAIL ||
      state.status === OrderStatus.NETWORK_ERR ||
      state.status === OrderStatus.INTERNAL_ERR
    ) {
      return "done";
    }

    if (state.attempt >= maxRetries) {
      return "timeout";
    }

    return "retry";
  }

  return new StateGraph(Schema)
    .addNode("fetchStatus", fetchStatus)
    .addNode("waitNext", waitNext)
    .addEdge(START, "fetchStatus")
    .addConditionalEdges("fetchStatus", routeFunc, {
      done: END,
      timeout: END,
      retry: "waitNext",
    })
    .addEdge("waitNext", "fetchStatus")
    .compile();
}

async function main() {
  const orderId = "1234567890";
  const maxRetries = 5;

  const graph = buildGraph(maxRetries);

  const result = await graph.stream({
    orderId,
    status: OrderStatus.PENDING,
    attempt: 1,
  });

  for await (const chunk of result) {
    console.log(chunk);
  }
}

main();

posted @ 2026-02-11 15:16  Zhentiw  阅读(7)  评论(0)    收藏  举报