AI & AUTOMATION

CopilotKit: Thiết kế Agentic UI và Full-stack Runtime Loop

Tóm tắt nhanh
CopilotKit là framework mã nguồn mở full-stack nhúng Generative UI, đồng bộ trạng thái hai chiều và quy trình human-in-the-loop vào ứng dụng web và di động. Bằng cách phân tách giao diện client khỏi backend agent qua giao thức AG-UI và chuẩn A2UI 1.0, CopilotKit biến AI từ khung chat thụ động thành các bề mặt tương tác phần mềm trực tiếp.

Tích hợp AI truyền thống vào phần mềm chủ yếu dựa vào khung chat nổi: người dùng nhập lệnh, mô hình xử lý qua API stateless và phản hồi Markdown thô.

Mô hình này bộc lộ ba điểm nghẽn trong ứng dụng tương tác:

  • Thiếu ngữ cảnh view: Mô hình không thể đọc trạng thái giao diện client, bản ghi đang chọn hoặc form chưa lưu nếu thiếu serialize prompt.
  • Thiếu đột biến hai chiều: Trợ lý không thể trực tiếp kích hoạt state update, sửa input hay làm nổi bật component xung đột.
  • Thiếu cơ chế ngắt quãng: Đồ thị agent không thể tạm dừng tất định để chờ con người phê duyệt trước tác vụ nhạy cảm.
[Khung chat truyền thống] ──(Markdown thô)──> [User xem thụ động]
                                  (Sao chép thủ công) │ 
                                                      ▼
[Ứng dụng mục tiêu] <─────────────────────── [Chỉnh sửa biểu mẫu]

========================= ĐỐI CHIẾU =========================

[Vòng lặp Agentic UI của CopilotKit]
      │
      ├── Đồng bộ trạng thái hai chiều (useAgent, phản chiếu state)
      ├── Lệnh gọi công cụ & Render Hooks (useFrontendTool)
      └── Tạm dừng chờ phê duyệt (useHumanInTheLoop)
      │
      ▼
[Các thành phần ứng dụng mục tiêu được cập nhật trực tiếp tại runtime]

CopilotKit giải quyết điểm nghẽn này bằng cách chuẩn hóa vòng lặp tương tác giữa agent và người dùng. Dự án phân phối theo giấy phép MIT License trong kho mã nguồn CopilotKit repository, tổ chức dạng monorepo Nx và pnpm trong package.json, kiểm soát chất lượng bằng oxlint, oxfmt và lefthook.

Chiều kiến trúcKhung chat truyền thốngAgentic UI của CopilotKit
Mô hình tương tácLuồng text một chiềuĐồng bộ trạng thái và thực thi hai chiều
Nhận biết trạng tháiChuỗi prompt phi trạng tháiĐăng ký hook React trực tiếp theo state
Render Generative UIVăn bản thô hoặc MarkdownComponent JSX động và đặc tả khai báo A2UI
Kích hoạt công cụChỉ chạy API phía backendKết hợp hành động DOM client và công cụ backend
Phạm vi đa kênhNhúng iframe web cục bộĐồng nhất agent qua Web, Slack và Teams

Cơ chế cốt lõi: Giao thức wire AG-UI và client hooks

CopilotKit phân tách khả năng phản ứng của giao diện client khỏi đồ thị thực thi của agent backend qua giao thức Agent-User Interaction (AG-UI), xây dựng trên @ag-ui/core, @ag-ui/client và @ag-ui/encoder.

Điều khiển theo chương trình với hook useAgent

Thay vì buộc lập trình viên tự quản lý Server-Sent Events, @copilotkit/react-core cung cấp hook useAgent trong packages/react-core/src/v2/hooks/use-agent.tsx.

Hook quản lý subscription qua enum UseAgentUpdate:

  • UseAgentUpdate.OnMessagesChanged: Kích hoạt khi lịch sử tin nhắn cập nhật hoặc stream token mới.
  • UseAgentUpdate.OnStateChanged: Kích hoạt khi đồ thị trạng thái chung của agent biến đổi.
  • UseAgentUpdate.OnRunStatusChanged: Kích hoạt theo vòng đời chạy agent (khởi tạo, hoàn tất, thất bại).

Nhằm tránh giật lag khi stream token, useAgent áp dụng cơ chế leading-plus-trailing qua thuộc tính throttleMs: sự kiện đầu tiên kích hoạt re-render ngay, các cập nhật trung gian trong khoảng thời gian được gộp lại, và timer trailing đảm bảo trạng thái cuối được vẽ khi chu kỳ kết thúc. Các callback vòng đời chạy thực thi bỏ qua throttle để đảm bảo phản hồi tức thì.

import { useAgent, UseAgentUpdate } from "@copilotkit/react-core";

export function AgentWorkflowMonitor({ agentId }: { agentId: string }) {
  const { agent } = useAgent({
    agentId,
    updates: [
      UseAgentUpdate.OnStateChanged,
      UseAgentUpdate.OnRunStatusChanged,
    ],
    throttleMs: 150, // Gộp token stream để giảm re-render dư thừa trong React
  });

  return (
    <div className="rounded-lg border border-slate-700 p-4">
      <h3 className="font-semibold text-slate-200">Giai đoạn: {agent.state.phase}</h3>
      <p className="text-sm text-slate-400">Trạng thái: {agent.runStatus}</p>
      <button
        onClick={() => agent.setState({ phase: "validation_pending" })}
        className="mt-3 rounded bg-indigo-600 px-3 py-1 text-sm text-white"
      >
        Cập nhật giai đoạn
      </button>
    </div>
  );
}

Render Generative UI động với useFrontendTool

Bên cạnh công cụ chạy trên server, frontend thường cần các công cụ thực thi trong trình duyệt và render component tùy chỉnh ngay trong luồng chat.

CopilotKit hiện thực cơ chế này qua useFrontendTool trong packages/react-core/src/v2/hooks/use-frontend-tool.tsx. Khi component mount, copilotkit.addTool(tool) đăng ký hành động thực thi.

Nếu công cụ có khai báo render, CopilotKit lưu renderer vào addHookRenderToolCall. Khi component unmount, hệ thống gỡ bỏ công cụ thực thi khỏi registry của agent nhưng chủ ý giữ lại render hook. Nhờ đó, các thẻ UI trong lịch sử chat vẫn tiếp tục hiển thị và tương tác bình thường khi người dùng chuyển view.

Với quy trình cần người dùng xác nhận, packages/react-core/src/v2/hooks/use-human-in-the-loop.tsx cung cấp useHumanInTheLoop. Hook giữ Promise ở trạng thái chờ cho đến khi người dùng thao tác qua respond(), đồng thời hủy listener AbortSignal khi hoàn tất hoặc hủy phiên nhằm chống rò rỉ bộ nhớ.

Giao diện khai báo Generative UI và sandbox MCP Apps

Ngoài callback render React, CopilotKit hỗ trợ chuẩn Generative UI khai báo và cơ chế sandbox cho công cụ ngoại vi.

Bề mặt giao diện khai báo với A2UI 1.0

Để tránh khóa chặt framework và tương thích đa nền tảng (React, Angular, Vue), CopilotKit áp dụng đặc tả A2UI 1.0. Triển khai qua @copilotkit/a2ui-renderer và @ag-ui/a2ui-middleware, framework quy định DEFAULT_SURFACE_ID = "default" và điểm vào bắt buộc ROOT_COMPONENT_ID = "root" trong packages/a2ui-renderer/src/a2ui-types.ts.

Phía server dùng createSurfaceWriter trong packages/runtime/src/v2/runtime/generative-ui/surface-writer.ts phát sự kiện ACTIVITY_DELTA (generative-ui-surface). Server stream các thao tác JSON Patch nguyên tử để client kiểm tra với catalog component trước khi gắn vào DOM.

Thực thi an toàn cho MCP Apps

Khi agent gọi công cụ từ máy chủ Model Context Protocol (MCP) trả về widget đồ họa, việc nhúng mã web bên thứ ba tiềm ẩn rủi ro bảo mật lớn.

Gói MCP Apps host trong packages/mcp-apps-renderer/src/sandbox.ts cô lập widget bằng kiến trúc hai tầng iframe:

  • Cấu hình Sandbox: Host dùng sandbox="allow-scripts allow-forms", không cấp allow-same-origin. Widget nhận opaque origin nên không thể truy cập cookie phiên, local storage hay DOM ứng dụng chủ.
  • Proxy chuyển tiếp: Iframe proxy bên ngoài tải qua srcdoc chuyển tiếp tin nhắn giữa ứng dụng và widget bên trong qua postMessage.
  • Kiểm tra CSP: Các domain cho phép được đối chiếu với regex CSP_HOST_SOURCE để ngăn chặn chèn ký tự làm hỏng thẻ meta hoặc header Content Security Policy.

Tầng ứng dụng thực tế: Thiết lập trên Next.js App Router

Triển khai production yêu cầu cấu hình runtime handler phía server và context provider ở client.

Cấu hình runtime handler phía server

Trong Runtime v2, createCopilotRuntimeHandler trong packages/runtime/src/v2/runtime/core/fetch-handler.ts trả về hàm Fetch chuẩn (Request) => Promise<Response>, tương thích trực tiếp với Next.js App Router, Bun, Deno và Cloudflare Workers.

Trong Next.js App Router, endpoint được cấu hình trong catch-all route handler:

// app/api/copilotkit/[[...slug]]/route.ts
import {
  CopilotRuntime,
  createCopilotRuntimeHandler,
} from "@copilotkit/runtime/v2";
import { LangGraphAgent } from "@copilotkit/runtime/langgraph";

const runtime = new CopilotRuntime({
  agents: {
    operations_copilot: new LangGraphAgent({
      deploymentUrl: process.env.LANGGRAPH_DEPLOYMENT_URL || "http://localhost:8123",
      graphId: "operations_copilot",
      langsmithApiKey: process.env.LANGSMITH_API_KEY || "",
    }),
  },
});

const handler = createCopilotRuntimeHandler({
  runtime,
  basePath: "/api/copilotkit",
  cors: true,
});

export const GET = handler;
export const POST = handler;
export const OPTIONS = handler;

Thiết lập ứng dụng client và đăng ký công cụ

Tại frontend, bọc ứng dụng bằng CopilotKit và gắn CopilotSidebar từ @copilotkit/react-ui:

// app/layout.tsx
import "@copilotkit/react-ui/styles.css";
import { CopilotKit } from "@copilotkit/react-core";
import { CopilotSidebar } from "@copilotkit/react-ui";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="vi">
      <body>
        <CopilotKit runtimeUrl="/api/copilotkit">
          {children}
          <CopilotSidebar
            defaultOpen={false}
            labels={{
              title: "Trợ lý vận hành",
              initial: "Tôi có thể hỗ trợ gì cho quy trình triển khai của bạn?",
            }}
          />
        </CopilotKit>
      </body>
    </html>
  );
}

Các component bên trong provider có thể đăng ký công cụ tương tác:

// components/DatabaseMigrator.tsx
"use client";

import { useFrontendTool } from "@copilotkit/react-core";
import { useState } from "react";

interface MigrationParams {
  clusterId: string;
  targetVersion: string;
}

export function DatabaseMigrator() {
  const [status, setStatus] = useState<string>("idle");

  useFrontendTool<MigrationParams>({
    name: "requestMigrationPlan",
    description: "Tạo và hiển thị thẻ xác nhận nâng cấp cơ sở dữ liệu",
    parameters: [
      { name: "clusterId", type: "string", description: "Mã cluster mục tiêu", required: true },
      { name: "targetVersion", type: "string", description: "Phiên bản engine cần nâng cấp", required: true },
    ],
    render: ({ status: toolStatus, args }) => {
      const { clusterId, targetVersion } = args;
      return (
        <div className="rounded border border-indigo-500/40 bg-slate-900 p-3 text-xs text-slate-200">
          <p className="font-bold text-indigo-400">Kế hoạch nâng cấp</p>
          <p>Cluster: {clusterId}</p>
          <p>Phiên bản đích: {targetVersion}</p>
          <div className="mt-2 flex gap-2">
            <button
              onClick={() => setStatus(`Đã duyệt cho cluster ${clusterId}`)}
              className="rounded bg-emerald-600 px-2 py-1 text-white hover:bg-emerald-500"
            >
              Xác nhận
            </button>
            <span className="self-center text-slate-400">Trạng thái: {toolStatus}</span>
          </div>
        </div>
      );
    },
    handler: async ({ clusterId, targetVersion }) => {
      return { scheduled: true, clusterId, targetVersion, timestamp: Date.now() };
    },
  });

  return (
    <div className="p-4">
      <p className="text-sm text-slate-300">Trạng thái Cluster: {status}</p>
    </div>
  );
}

Ranh giới vòng đời runtime và bảo mật vận hành

Vận hành Agentic UI đòi hỏi những lưu ý kỹ thuật đặc thù:

Vòng đời Edge so với tiến trình dài hạn

Adapter Hono trong packages/runtime/src/v2/runtime/endpoints/hono.ts xử lý vòng đời theo môi trường:

  • Tiến trình dài hạn (Node/Express): Do quản lý process OS bền vững, hệ thống kích hoạt engine Channels ngay khi khởi tạo instance.
  • Tiến trình Edge (Hono): Khi chạy serverless hoặc route handler Next.js App Router, isolate bị đóng băng theo từng request. Kích hoạt sớm sẽ tạo nhiều listener cạnh tranh qua mỗi cold start. Vì vậy, adapter Hono nạp lười, yêu cầu host dài hạn (@hono/node-server, Bun, Deno) gọi await app.channels.ready() một lần khi khởi động, còn serverless giữ trạng thái nghỉ.

Bộ Channels SDK và ranh giới tổ chức

Gói engine @copilotkit/channels-core (v0.12.0) hỗ trợ Slack, Microsoft Teams, Discord và WhatsApp.

Trong Slack, SanitizingHttpAgent trong packages/channels-slack/src/sanitizing-http-agent.ts phân tích stream SSE qua parseSSEStream và tự ép kiểu trường nullable (như TOOL_CALL_START mang parentMessageId null từ ngắt quãng LangGraph), tránh lỗi Zod làm ngắt run.

Khi đưa agent vào kênh nội bộ doanh nghiệp:

  • Xác thực phiên: Phiên cookie web không thể tự gán cho Slack/Teams nếu thiếu liên kết danh tính.
  • Khử khuẩn dữ liệu: Thẻ Slack Block Kit và Adaptive Cards cần lọc sạch dữ liệu ngoại vi tránh prompt injection gián tiếp.
  • Quản lý token: Đăng ký state qua useAgent làm tăng dung lượng truyền tải. Nên chọn lọc slice trạng thái hẹp thay vì đẩy toàn bộ cây dữ liệu.

Continual Learning và quỹ đạo tương tác

Trong packages/learning/src/trajectory-collector.ts, CopilotKit ghi nhận tương tác full-stack: nhấp chuột, điều hướng, dữ liệu nhập và request mạng song song với bước chạy agent. Module giới hạn MAX_TRAJECTORY_EVENT_BYTES = 16 * 1024 (16 KB) và làm mờ PII ở client. Dữ liệu được chuyển về CopilotKit Intelligence để đúc kết thành các Skill có thể kiểm duyệt và tái sử dụng.

Tiêu chí lựa chọn và quyết định kỹ thuật

CopilotKit chuyển đổi mô hình tương tác AI từ hộp chat nổi thành bề mặt phần mềm tương tác hai chiều.

Trường hợp nên sử dụng CopilotKit

  1. Ứng dụng tương tác sâu: Agent cần đọc form, cập nhật bảng tính hoặc hiển thị thẻ xác nhận trực tiếp trong UI.
  2. Triển khai đa kênh thống nhất: Cần dùng chung một lõi agent trên cả web, di động và kênh chat doanh nghiệp (Slack, Teams).
  3. Quy trình chuẩn hóa AG-UI và MCP: Cần streaming frontend chuẩn hóa, render Generative UI qua A2UI và sandbox an toàn cho widget ngoại vi.

Trường hợp nên dùng giải pháp đơn giản hơn

  1. Hỏi đáp tài liệu thuần túy: Trang tài liệu hoặc wiki chỉ cần stream Markdown mà không can thiệp trạng thái ứng dụng.
  2. Tác vụ ngầm không giao diện: Worker xử lý theo lô chạy nền không cần con người phê duyệt hay phản hồi đồ họa.
Duy Nghiện
Hãy làm khán giả, đừng làm nhân vật chính :)

You may also like

Nhận thông báo qua email
Nhận thông báo cho
guest

0 Bình luận
Mới nhất
Cũ nhất Nhiều like nhất