15. Subgraphs - Xây dựng hệ thống Agent Modular

Nam

Nam Hoang / Sep 15, 2025

7 min read

Khi ứng dụng AI của bạn phát triển, đồ thị (Graph) của bạn sẽ ngày càng lớn và rối rắm. Một Agent có 50 node sẽ rất khó để gỡ lỗi hoặc mở rộng. Subgraphs (Graph con) là giải pháp của LangGraph để module hóa hệ thống: bạn có thể coi một Graph hoàn chỉnh như một "Node" bên trong một Graph khác.

I. Tại sao cần sử dụng Subgraphs?

  1. Đóng gói (Encapsulation): Các logic phức tạp (ví dụ: quy trình tra cứu tài liệu chuyên sâu) có thể được giấu kín bên trong một subgraph.
  2. Cô lập trạng thái (Isolated State): Subgraph có thể có Schema riêng. Điều này giúp tránh việc State chính của ứng dụng bị quá tải bởi các biến trung gian chỉ dùng cho một tác vụ nhỏ.
  3. Tái sử dụng (Reusability): Bạn có thể xây dựng một "Subgraph chuyên viết code" và nhúng nó vào nhiều Agent khác nhau trong hệ thống.
  4. Phát triển theo nhóm: Team A tập trung làm Subgraph Research, Team B làm Subgraph Writer. Miễn là đầu vào và đầu ra đúng chuẩn, chúng sẽ hoạt động hoàn hảo khi ghép lại.

II. Cách triển khai Subgraph

Có hai cách chính để nhúng một Subgraph:

  • Shared State: Graph cha và Graph con chia sẻ chung các khóa dữ liệu.
  • Isolated State: Graph con có Schema hoàn toàn khác Graph cha. Bạn gọi subgraph như một hàm bên trong Node của cha và thực hiện ánh xạ (mapping) dữ liệu qua lại.

III. Mã nguồn: 15-subgraphs-modular-design.ts

Dưới đây là ví dụ minh họa một Graph cha (Manager) điều phối một Subgraph (Researcher). Subgraph sẽ có bộ nhớ và logic xử lý riêng biệt.

// path: 15-subgraphs-modular-design.ts
import { END, MemorySaver, START, StateGraph } from '@langchain/langgraph';
import { generateImage } from '@workspace/util-langchain';
import z from 'zod';

// --- 1. DEFINE SUBGRAPH (RESEARCHER) ---
const SubgraphState = z.object({
  topic: z.string(),
  results: z.array(z.string()).default([]),
});

const researchNode = async (state: any) => {
  console.log(`--- SUBGRAPH: Researching about ${state.topic} ---`);
  // Simulate heavy research work
  return { results: ['Information A', 'Information B'] };
};

const researcher = new StateGraph(SubgraphState)
  .addNode('research', researchNode)
  .addEdge(START, 'research')
  .addEdge('research', END)
  .compile();

// --- 2. DEFINE PARENT GRAPH (MANAGER) ---
const ParentState = z.object({
  task: z.string(),
  finalDraft: z.string().optional(),
});

const managerNode = async (state: any) => {
  console.log('--- PARENT: Manager receiving request ---');

  // Call subgraph as a function to isolate data (Mapping Parent -> Subgraph)
  const researchOutput = await researcher.invoke({ topic: state.task });

  // Mapping Subgraph -> Parent
  return {
    finalDraft: `Draft based on: ${researchOutput.results.join(', ')}`,
  };
};

const workflow = new StateGraph(ParentState)
  .addNode('manager', managerNode)
  .addEdge(START, 'manager')
  .addEdge('manager', END)
  .compile({ checkpointer: new MemorySaver() });

// --- 3. EXECUTION ---
async function run() {
  // Generate diagrams for both Parent and Subgraph
  await generateImage(
    workflow,
    'graph-ignore/scripts-15-subgraphs-modular-design-workflow.jpg',
  );
  await generateImage(
    researcher,
    'graph-ignore/scripts-15-subgraphs-modular-design-researcher.jpg',
  );

  const result = await workflow.invoke(
    { task: 'The future of LangGraph' },
    { configurable: { thread_id: 'm-1' } },
  );

  console.log('\n=== FINAL RESULT ===');
  console.log(result.finalDraft);
}

run();

IV. Hình ảnh sơ đồ Graph

Việc tách nhỏ giúp sơ đồ của mỗi Graph trở nên cực kỳ đơn giản và dễ hiểu:

Sơ đồ Parent Graph (Manager):
Sơ đồ Parent Graph

Sơ đồ Subgraph (Researcher):
Sơ đồ Subgraph

V. Các điểm lưu ý kỹ thuật quan trọng

  1. Persistence: Bạn chỉ cần cung cấp checkpointer cho Graph cha. LangGraph sẽ tự động lan truyền (propagate) checkpointer đó xuống các subgraph bên dưới.
  2. Streaming: Để thấy được các bước chạy chi tiết bên trong subgraph khi sử dụng lệnh .stream(), bạn nên đặt tùy chọn subgraphs: true trong cấu hình stream.
  3. Human-in-the-loop: Nếu một subgraph bị tạm dừng bởi lệnh interrupt(), Graph cha cũng sẽ tạm dừng tại đúng node chứa subgraph đó. Khi con người resume, tín hiệu sẽ được truyền xuống đúng vị trí đang chờ trong subgraph.

VI. Tổng kết

  • Subgraphs là chìa khóa để xây dựng các hệ thống AI cấp độ doanh nghiệp (Enterprise-grade), nơi sự phức tạp cần được chia nhỏ.
  • Sử dụng Isolated State (Mapping qua hàm invoke) là cách tốt nhất để tạo ra các module độc lập hoàn toàn, dễ kiểm thử và bảo trì.

Chúc mừng bạn! Bạn đã hoàn thành toàn bộ các kỹ năng nâng cao trong khóa học. Bạn đã sẵn sàng để lắp ghép mọi thứ lại thành một ứng dụng thực tế hoàn chỉnh trong phần tiếp theo: AI Email Assistant Agent!

👉 Bài tiếp theo: 16. Project: Email Assistant - Kiến trúc & Phân loại