Trong phần trước, chúng ta đã thống nhất việc loại bỏ Chatbot để tiến tới Generative UI. Nhưng để AI có thể “đẻ” ra được các UI Component ngay trên màn hình người dùng, Frontend và Backend không thể chỉ giao tiếp qua các API stateless thông thường. Chúng cần chia sẻ một trạng thái (State) chung.

Vấn đề là: Bộ não của AI và Trình duyệt của người dùng nói hai ngôn ngữ hoàn toàn khác nhau.

2.1. Phân định rạch ròi: AIState và UIState

Khi xây dựng một hệ thống Agentic có Giao diện, nguyên tắc sống còn đầu tiên là phải tách biệt tuyệt đối AIStateUIState.

AIState (Não bộ của Backend)

  • Bản chất: Là một mảng (array) chứa toàn bộ lịch sử hội thoại, các tool calls, và context của Agent.
  • Định dạng: JSON thuần túy (Serializable). Đây là thứ sẽ được gửi thẳng vào mồm của LLM (ví dụ: OpenAI API) và lưu trữ dưới Database (PostgreSQL/Redis).
  • Ví dụ: [{"role": "user", "content": "Mua vé đi Hà Nội"}, {"role": "assistant", "tool_calls": [{"name": "book_flight", "arguments": "{"dest": "HAN"}"}]}]

UIState (Hiển thị của Frontend)

  • Bản chất: Là danh sách các React/Svelte/Vue Components đang được render trên màn hình tại thời điểm hiện tại.
  • Định dạng: Các Object chứa Functions, DOM Nodes, Event Listeners (Non-serializable). Bạn không thể lưu UIState vào Database.
  • Ví dụ: [<UserMessage text="Mua vé đi Hà Nội" />, <FlightBookingWidget dest="HAN" onConfirm={handleConfirm} />]

Bài toán cốt lõi của Generative UI chính là làm sao ánh xạ (map) một chuỗi JSON (AIState) thành một danh sách các Component (UIState) một cách an toàn và theo thời gian thực.

2.2. Hai trường phái kiến trúc: Next.js (RSC) vs Framework-Agnostic (Astro)

Hiện tại, thế giới Frontend chia làm hai nửa trong cách xử lý bài toán mapping này.

Trường phái 1: Next.js và React Server Components (RSC)

Đây là cách tiếp cận được Vercel (qua Vercel AI SDK) quảng bá rầm rộ nhất.

  • Cách hoạt động: Việc mapping từ AIState sang UIState diễn ra hoàn toàn trên Server. Server chạy LLM, nhận JSON về, và lập tức render ra React Component. Sau đó, server “stream” (truyền) thẳng Component đó xuống Client thông qua RSC payload.
  • Ưu điểm: DX (Developer Experience) cực tốt. Bạn code frontend và backend chung một chỗ.
  • Nhược điểm (Tử huyệt Enterprise): Bị khóa chặt (Vendor lock-in) vào Next.js và React. Nếu hệ thống lõi của bạn đang dùng Vue, Svelte, Angular, hoặc chạy trên Astro, Java Spring Boot ở Backend, mô hình này phá sản hoàn toàn.

Trường phái 2: Framework-Agnostic với chuẩn A2UI (Sự lựa chọn của Enterprise)

Để hệ thống không bị lỗi thời (Future-proof) và dễ dàng tích hợp vào các dự án Legacy, chúng ta bắt buộc phải đẩy logic mapping xuống Client (hoặc một Orchestrator trung gian như Astro).

  • Cách hoạt động:
    1. Backend Agent (chạy Python/Golang/Node) gọi LLM và trả về một cấu trúc JSON chuẩn hóa (như chuẩn A2UI - Agent to User Interface).
    2. JSON này chỉ chứa: Component_Name (ví dụ: flight_widget) và Props_Data.
    3. Frontend (Astro) đóng vai trò là Orchestrator. Nó nhận JSON này, tìm trong Component Registry của mình, bốc Svelte/Vue Component tương ứng ra, bơm Data vào và render lên màn hình.
  • Ưu điểm: Backend Agent có thể viết bằng bất kỳ ngôn ngữ nào. Frontend có thể dùng Astro để mix cả React, Vue, Svelte trên cùng một trang (Islands Architecture). Bảo mật tuyệt đối vì AI không bao giờ được chạm vào mã HTML/JS, nó chỉ được trả về Data.

2.3. Giao thức Đồng bộ: SSE vs WebSockets

Khi Frontend đã biết cách render, câu hỏi tiếp theo là: Dùng đường truyền nào để Frontend nhận tín hiệu từ Agent?

Server-Sent Events (SSE) - Phù hợp cho Token Streaming

Nếu UI của bạn chỉ cần hiển thị chữ đang gõ ra từng chữ một (ChatGPT-style) hoặc hiện trạng thái đơn giản (“Đang tìm kiếm…”), SSE là quá đủ.

  • Ưu điểm: Dùng HTTP tiêu chuẩn, đi qua Load Balancers/Firewalls dễ dàng, trình duyệt tự động reconnect.
  • Nhược điểm: Là giao tiếp một chiều (Unidirectional). Server gửi dữ liệu xuống Client. Client muốn gửi dữ liệu lên phải gọi một HTTP POST request khác.

WebSockets - Bắt buộc cho Interactive Agents

Hệ thống Agentic phức tạp đòi hỏi sự tương tác hai chiều (Bi-directional) liên tục.

  • Kịch bản: Agent A sinh ra form thanh toán trên màn hình (Server $\rightarrow$ Client). Người dùng sửa số tiền và bấm “Xác nhận”. Tín hiệu này phải lập tức được gửi ngược về cho Agent A để nó cập nhật Context và đi tiếp (Client $\rightarrow$ Server).
  • Trong Generative UI thực thụ, trạng thái UI thay đổi từng mili-giây khi người dùng tương tác với Component do AI sinh ra. Việc dùng HTTP POST cho mỗi tương tác sẽ gây ra độ trễ (latency) không thể chấp nhận được.
  • Khuyến nghị: Sử dụng WebSockets (hoặc WebRTC Data Channels cho các app realtime cực nặng) để quản lý UIStateAIState.

⚠️ Lưu ý kiến trúc (Vận hành & Phục hồi): Khi dùng WebSockets, bạn sẽ phải đối phó với 2 bài toán hạ tầng lớn:

  1. Sticky Sessions: Load Balancer phải điều hướng connection của Client trúng đúng vào Pod/Container đang chạy Agent đó trong cụm Kubernetes.
  2. State Recovery (Phục hồi trạng thái): WebSockets cực kỳ dễ rớt kết nối trên môi trường mạng thực tế (như 4G/Mobile). Khi Frontend reconnect thành công, bắt buộc phải có cơ chế tự động “kéo” (sync) lại trạng thái hiện tại từ AIState của Backend để khôi phục lại UIState, tránh việc Component bị “đóng băng” do mất tín hiệu ngầm.

🔗 Bước tiếp theo: Ở phần này, chúng ta đã nhắc đến “Component Registry” — trái tim của kiến trúc Framework-Agnostic. Làm thế nào để Backend Agent biết Frontend có những Component gì để gọi? Đón xem Phần 3 — Component Registry & Cầu nối MCP tới Frontend.