3. Điều hướng luồng - Edges & Control Flow

Nam

Nam Hoang / Sep 03, 2025

6 min read

Trong bài học trước, chúng ta đã xây dựng một graph đơn giản chỉ với một Node duy nhất. Tuy nhiên, sức mạnh thực sự của LangGraph nằm ở khả năng kết nối nhiều Node lại với nhau để tạo thành một quy trình làm việc hoàn chỉnh. Thành phần đảm nhận vai trò này chính là Edges (Cạnh).

I. Khái niệm về Edges (Cạnh)

Nếu Nodes là các "nhân viên" thực hiện công việc, thì Edges chính là các "sợi dây" kết nối. Chúng định nghĩa Control Flow (luồng điều khiển) của ứng dụng.

Có một quy tắc vàng cần nhớ: Edges truyền quyền điều khiển, không truyền dữ liệu.

  • Dữ liệu luôn nằm trong State dùng chung và được lưu trữ bởi runtime.
  • Edge chỉ đơn giản bảo LangGraph rằng: "Sau khi Node A xong, hãy chuyển State này cho Node B xử lý tiếp".

II. Luồng thực thi tuần tự (Serial Flow)

Đây là dạng cơ bản nhất của Edge, nơi các Node chạy lần lượt theo một thứ tự xác định (A -> B -> C). Mỗi Node sẽ nhận State từ Node trước đó, thực hiện thay đổi và chuyển tiếp kết quả.

III. Mã nguồn: 03-edges-control-flow.ts

Dưới đây là mã nguồn minh họa một quy trình gồm 2 bước: Bước 1 nhận dữ liệu, Bước 2 xử lý thêm dữ liệu đó.

// path: 03-edges-control-flow.ts
import { StateGraph, START, END } from '@langchain/langgraph';
import { generateImage } from '@workspace/util-langchain';
import z from 'zod';

// 1. Define State
const StateDefinition = z.object({
  nlist: z.array(z.string()),
});
type State = z.infer<typeof StateDefinition>;

// 2. Define Nodes
function step1(state: State) {
  console.log('--- Executing Step 1 ---');
  // Add a new string to the array
  return { nlist: [...state.nlist, 'Data from Step 1'] };
}

function step2(state: State) {
  console.log('--- Executing Step 2 ---');
  // step2 sees the changes made by step1 in the state
  return { nlist: [...state.nlist, 'Data from Step 2'] };
}

// 3. Build Graph with Serial Edges
export const serialGraph = new StateGraph(StateDefinition)
  .addNode('node1', step1)
  .addNode('node2', step2)

  // Define the navigation flow
  .addEdge(START, 'node1') // Start -> node1
  .addEdge('node1', 'node2') // node1 -> node2
  .addEdge('node2', END) // node2 -> End
  .compile();

// 4. Execution
async function run() {
  // Generate diagram
  await generateImage(serialGraph, 'graph-ignore/scripts-03-serial-flow.jpg');

  console.log('\n=== L3: Edges and Control Flow Example ===\n');

  const initialState: State = { nlist: ['Initial Value'] };
  const result = await serialGraph.invoke(initialState);

  console.log('\n--- FINAL RESULT ---');
  console.log(result.nlist);
  // Expected: ["Initial Value", "Data from Step 1", "Data from Step 2"]
}

run();

IV. Giải thích mục đích của Code

  1. Tính liên tục: Bạn có thể thấy step2 không hề nhận tham số trực tiếp từ step1. Nó nhận toàn bộ state từ runtime. Vì step1 đã cập nhật mảng nlist, nên step2 sẽ nhìn thấy những thay đổi đó.
  2. Định danh Node: Khi dùng .addNode('node1', step1), chúng ta đặt tên định danh là 'node1'. Tên này được dùng trong hàm .addEdge() để thiết lập đường đi.
  3. Tách biệt logic: Mỗi Node chỉ tập trung vào một nhiệm vụ nhỏ. Điều này giúp code dễ đọc, dễ bảo trì và dễ kiểm thử.

Sơ đồ hoạt động:

Sơ đồ Serial Flow

V. Thử nghiệm: Lab 02

Để hiểu rõ hơn về cách luồng di chuyển:

  1. Tạo file 03-edges-control-flow.ts và chạy bằng lệnh tsx.
  2. Thử thêm một nút thứ 3 (node3) vào quy trình:
    • Viết thêm hàm step3.
    • Dùng .addNode để thêm vào graph.
    • Sửa lại các Edge: node1 -> node2 -> node3 -> END.
  3. Chạy lại và quan sát mảng kết quả cuối cùng.

VI. Tổng kết

  • Edges điều phối thứ tự thực hiện công việc giữa các nút.
  • Trong luồng Tuần tự, các nút xếp hàng chờ đến lượt mình xử lý State.
  • Việc chia nhỏ quy trình thành nhiều Node giúp hệ thống minh bạch và dễ kiểm soát lỗi.

Ở bài học tiếp theo, chúng ta sẽ học cách tối ưu tốc độ bằng cách cho nhiều Node chạy cùng một lúc với Parallel Execution và cách xử lý xung đột dữ liệu bằng Reducers!

👉 Bài tiếp theo: 4. Xử lý song song & Reducers