5. Conditional Edges & Router Logic

Nam

Nam Hoang / Sep 05, 2025

8 min read

Trong các bài học trước, chúng ta đã kết nối các Node bằng những "sợi dây" cố định. Tuy nhiên, một AI Agent thực sự cần khả năng thông minh hơn: nó phải tự quyết định xem bước tiếp theo nên làm gì. Đây là lúc chúng ta cần đến Conditional Edges (Cạnh có điều kiện) và Router Functions.

I. Khái niệm về Conditional Edges

Một Conditional Edge đóng vai trò như một người điều phối giao thông. Thay vì luôn đi từ A sang B, nó sẽ hỏi một hàm trung gian gọi là Router Function: "Dựa trên dữ liệu hiện có trong State, chúng ta nên rẽ vào hướng nào?".

Điều này cho phép chúng ta xây dựng các logic linh hoạt như:

  • Phân loại (Classification): Nếu khách hàng hỏi về tiền -> đi tới Node Hóa đơn. Nếu báo lỗi kỹ thuật -> đi tới Node Hỗ trợ.
  • Vòng lặp (Loops): Nếu kết quả trả về chưa đạt yêu cầu -> quay lại bước trước để thực hiện lại.
  • Kết thúc (Termination): Nếu tác vụ đã hoàn thành hoặc người dùng muốn thoát -> đi tới END.

II. Router Function: Bộ não điều hướng

Khác với các Node thông thường dùng để xử lý dữ liệu, một hàm Router:

  1. Nhận vào: Trạng thái (State) hiện tại của ứng dụng.
  2. KHÔNG cập nhật: Nó chỉ đọc dữ liệu, không thay đổi nội dung bên trong State.
  3. Trả về: Một chuỗi (string) là tên của Node tiếp theo mà Graph cần kích hoạt (hoặc hằng số END).

III. Mã nguồn: 05-conditional-edges-routing.ts

Dưới đây là ví dụ về một hệ thống điều phối hỗ trợ khách hàng. Agent sẽ phân tích lệnh của người dùng và quyết định hướng đi phù hợp.

// path: 05-conditional-edges-routing.ts
import { END, START, StateGraph } from '@langchain/langgraph';
import { registry } from '@langchain/langgraph/zod';
import { generateImage } from '@workspace/util-langchain';
import { getUserInput } from './utils/input';
import z from 'zod';

// 1. Define State with a Reducer to track history
const StateDefinition = z.object({
  history: z.array(z.string()).register(registry, {
    reducer: { fn: (left: string[], right: string[]) => left.concat(right) },
    default: () => [],
  }),
});
type State = z.infer<typeof StateDefinition>;

// 2. Define processing Nodes
function analyzerNode(state: State) {
  console.log('--- NODE: Analyzing command ---');
  return { history: ['Analyzed'] };
}

function billingNode(state: State) {
  console.log('--- NODE: Processing billing ---');
  return { history: ['Payment processed'] };
}

function supportNode(state: State) {
  console.log('--- NODE: Technical support ---');
  return { history: ['Error resolved'] };
}

// 3. Router Function: Decision logic based on keywords
function router(state: State): string {
  const lastInput = state.history[0]; // Assume the first element is the user command

  if (lastInput.toLowerCase().includes('money')) return 'billing';
  if (lastInput.toLowerCase().includes('error')) return 'support';

  return END; // Default to end if no criteria matched
}

// 4. Build the Graph
export const smartGraph = new StateGraph(StateDefinition)
  .addNode('analyzer', analyzerNode)
  .addNode('billing', billingNode)
  .addNode('support', supportNode)

  .addEdge(START, 'analyzer')

  // Setup Conditional Edges: From 'analyzer', call 'router' to decide direction
  .addConditionalEdges('analyzer', router, {
    billing: 'billing',
    support: 'support',
    [END]: END,
  })

  .addEdge('billing', END)
  .addEdge('support', END)
  .compile();

// 5. Execution
async function run() {
  await generateImage(
    smartGraph,
    'graph-ignore/scripts-05-conditional-edges-routing.jpg',
  );

  const userTask = await getUserInput(
    'How can I help you? (Example: "I have an error" or "Out of money"): ',
  );

  const result = await smartGraph.invoke({ history: [userTask] });
  console.log('\nExecution History:', result.history);
}

run();

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

  1. Tính tách biệt (Separation of Concerns): Node analyzer chỉ lo việc phân tích. Các Node billingsupport chỉ lo chuyên môn của mình. Logic rẽ nhánh nằm hoàn toàn trong hàm router. Điều này giúp bạn dễ dàng bảo trì hoặc thêm các nhánh mới.
  2. Ánh xạ (Mapping): Trong hàm addConditionalEdges, tham số thứ ba là một object ánh xạ (mapping). Nó bảo LangGraph: "Nếu hàm router trả về chuỗi 'billing', hãy thực sự kích hoạt node có tên là 'billing'".
  3. Hỗ trợ Agentic: Trong các bài học sau, thay vì dùng if-else thủ công, bạn sẽ để LLM trả về kết quả phân tích và Router sẽ dựa vào đó để điều hướng Agent một cách tự động.

Sơ đồ hoạt động:

Sơ đồ Conditional Routing

V. Thử nghiệm: Lab 05

Hãy thử khả năng điều hướng của hệ thống:

  1. Chạy file bằng lệnh tsx.
  2. Test 1: Nhập "I have an error in my app". Quan sát xem supportNode có được gọi không.
  3. Test 2: Nhập "I want to pay money". Quan sát billingNode.
  4. Test 3: Nhập "Hello". Graph sẽ kết thúc ngay sau node analyzer vì không thỏa mãn điều kiện nào trong router.

VI. Tổng kết

  • Nodes dùng để thực hiện công việc (Work).
  • Conditional Edges dùng để đưa ra quyết định (Decision).
  • Một Router Function luôn phải trả về một chuỗi hợp lệ đã được đăng ký ánh xạ trong Graph.

Chúc mừng! Bạn đã nắm vững toàn bộ các cách điều phối luồng cơ bản trong LangGraph. Trong bài học tiếp theo, chúng ta sẽ học về Design Mindset để biết cách chuyển đổi một yêu cầu nghiệp vụ phức tạp thành một cấu trúc Graph chuyên nghiệp.

👉 Bài tiếp theo: 6. Tư duy thiết kế - Thinking in LangGraph