14. Human-in-the-loop (Interrupts)

Nam

Nam Hoang / Feb 14, 2026

8 min read

Trong Bài 1, chúng ta đã xác định Non-Determinism (tính không xác định) là một thách thức lớn của AI. Dù mô hình thông minh đến đâu, rủi ro về "ảo giác" (hallucination) vẫn luôn tồn tại. Bài học này sẽ hướng dẫn bạn thiết lập cơ chế Human-in-the-loop (HITL) sử dụng Interrupts để đảm bảo Agent không bao giờ tự ý thực hiện các hành động nguy hiểm mà không có sự đồng ý của con người.

I. Tại sao cần Interrupts?

Interrupt (Ngắt) đóng vai trò như một "chốt chặn an toàn". Thay vì để Agent tự động gửi email cho khách hàng hoặc thực hiện thanh toán, chúng ta cấu hình để hệ thống tự động dừng lại, lưu trạng thái và gửi thông báo chờ phê duyệt. Điều này đặc biệt quan trọng trong các ứng dụng doanh nghiệp có độ rủi ro cao.

II. Các điều kiện cần thiết để dừng Agent

Để cơ chế Interrupt hoạt động bền bỉ trong hệ thống chuyên nghiệp của chúng ta, bạn cần:

  1. Checkpointer (Bài 7): Agent phải lưu lại "hiện trường" vào bộ nhớ. Nếu không có nơi lưu trữ, Agent sẽ không thể "nhớ" mình đang đứng ở đâu sau khi bạn nhấn nút duyệt.
  2. Thread ID: Để định danh chính xác phiên làm việc nào đang bị tạm dừng.
  3. Hệ thống Tiện ích: Sử dụng util-langchain để khởi tạo Model Gemini 2.5 Flash mạnh mẽ và trực quan hóa sơ đồ dừng.

III. Mã nguồn: 14-pausing-with-interrupts.ts

Chúng ta sẽ tạo một Agent có công cụ gửi email nhạy cảm. Chúng ta sử dụng humanInTheLoopMiddleware để bắt Agent phải "xin phép" trước khi chạy tool.

// apps/langchain/scripts/14-pausing-with-interrupts.ts
// pnpm --filter=ai-notes-langchain run tsx scripts/14-pausing-with-interrupts.ts

import './env'; // Load API keys
import { createGeminiModel, generateImage } from '@workspace/util-langchain';
import { createAgent, tool, humanInTheLoopMiddleware } from 'langchain';
import { MemorySaver } from '@langchain/langgraph';
import { z } from 'zod';

// 1. Define a sensitive Tool
const sendEmailTool = tool(
  async ({ to, body }) => {
    // In this lesson, this function will NOT be called until we provide permission in the next lesson
    return `SUCCESS: Email sent to ${to} with content: "${body}"`;
  },
  {
    name: 'send_email',
    description:
      'Send an email to a customer. This action is highly sensitive.',
    schema: z.object({
      to: z.string().email(),
      body: z.string(),
    }),
  },
);

async function main() {
  const model = createGeminiModel();
  const checkpointer = new MemorySaver();

  // 2. Initialize Agent with Human-in-the-loop Middleware
  const agent = createAgent({
    model,
    tools: [sendEmailTool],
    checkpointer, // MANDATORY for pausing/resuming
    middleware: [
      humanInTheLoopMiddleware({
        interruptOn: {
          // Whenever the AI wants to call 'send_email', it will trigger an INTERRUPT
          send_email: true,
        },
      }),
    ],
  });

  // 3. Generate visual diagram
  await generateImage(agent.graph, 'graph-ignore/scripts-14-interrupt.jpg');

  const config = { configurable: { thread_id: 'urgent-task-001' } };

  console.log('--- Agent processing user request... ---');

  // 4. Run the Agent with a request that triggers the sensitive tool
  const result = await agent.invoke(
    {
      messages: [
        {
          role: 'user',
          content: 'Please send a welcome email to Nam at nam@example.com.',
        },
      ],
    },
    config,
  );

  // 5. Check if the Agent was interrupted
  if (result.__interrupt__ && result.__interrupt__.length > 0) {
    console.log('\n🛑 AGENT EXECUTION PAUSED!');

    const interruptData = result.__interrupt__[0].value;
    const actionRequest = interruptData.actionRequests[0];

    console.log('Action awaiting approval:');
    console.log(`- Tool: ${actionRequest.name}`);
    console.log(`- Proposed Arguments:`, actionRequest.arguments);

    console.log('\n[NOTE]: The Agent is now standing by.');
    console.log('The entire context is saved in the Checkpointer.');
    console.log('We will resume this specific thread in the next lesson.');
  } else {
    console.log('Agent completed without interruption.');
  }
}

main().catch(console.error);

IV. Phân tích kỹ thuật

  • interruptOn: Đây là nơi bạn khai báo danh sách các Tool "vùng cấm". Bất cứ khi nào Agent suy luận rằng cần dùng tool này, LangChain sẽ ném ra một tín hiệu ngắt thay vì thực thi code JavaScript của tool.
  • result.__interrupt__: Khi Agent bị tạm dừng, thuộc tính này sẽ xuất hiện trong kết quả trả về của invoke. Nó chứa chi tiết về việc AI định làm gì, giúp bạn có thể hiển thị lên giao diện UI cho người dùng xem xét.
  • Trạng thái "Treo": Khi bị interrupt, luồng code thực thi tool thực tế chưa hề được chạy. Hệ thống đang ở trạng thái chờ lệnh resume.

Sơ đồ hoạt động:

Nhờ sơ đồ, bạn có thể thấy Agent dừng lại ngay sau bước suy luận (agent node) và trước khi bước vào thực thi công cụ (tools node):

Sơ đồ Interrupt Agent

V. Tổng kết

  • Interrupts là cơ chế bảo vệ tối thượng để xử lý các thách thức về an toàn AI trong thực tế.
  • Checkpointer là thành phần không thể thiếu để duy trì "ký ức" của Agent trong khi chờ đợi con người.
  • Việc nạp env.ts và dùng util-langchain giúp bạn quản lý các phiên làm việc (Threads) bị tạm dừng một cách khoa học.

Ở trạng thái hiện tại, Agent đang đứng đợi. Trong bài học tiếp theo, chúng ta sẽ học cách gửi các đối tượng Command để "đánh thức" Agent và ra lệnh cho nó Duyệt, Sửa hoặc Hủy bỏ hành động!

👉 Bài tiếp theo: 15. Xử lý quyết định của con người (Command Resume)