# Kiến trúc AI Agent — Nâng cấp Dual-Agent (Ver 2)

Hệ thống chatbot Canifa đã thực hiện một bước chuyển mình quan trọng từ kiến trúc **Single Agent** (một AI duy nhất xử lý mọi việc) sang kiến trúc **Dual-Agent** (hai AI chuyên biệt phối hợp).

## 1. Vấn đề của phiên bản cũ

Hãy hình dung bro thuê **một nhân viên duy nhất** vừa phải tiếp khách, vừa chạy vào kho tìm hàng, vừa tư vấn phong cách, vừa viết hóa đơn — tất cả cùng một lúc. Kết quả? Nhân viên đó sẽ hay nhầm lẫn, chậm chạp và nói bậy.

Đó chính xác là vấn đề của **Ver 1 (Single Agent)**:

> Khách hỏi: *"Tìm cho mình set đồ đi tiệc sang trọng nhé"*
>
> Con AI Ver 1 phải đồng thời: hiểu ý khách → tìm trong database → lọc hàng còn tồn → viết câu tư vấn đẹp → giữ đúng giọng điệu Canifa
>
> ❌ Kết quả thực tế: AI đôi khi bỏ sót filter "còn hàng", gọi sai Tool tìm kiếm, hoặc trả lời cứng nhắc không ra Canifa chút nào.

### Sơ đồ kiến trúc Ver 1 (Cũ)
![Ver 1 Architecture](data/29-new-agent_architect/v1_architecture.svg)

---

## 2. Giải pháp: Chia việc cho 2 AI chuyên biệt

Ver 2 hoạt động giống như **bộ đôi nhân viên phối hợp**:

| Thành phần | Agent 1 — Classifier | Agent 2 — Stylist |
|---|---|---|
| **Vai trò** | "Thư ký thông minh" — chỉ lo phân tích và gọi Tool | "Chuyên viên tư vấn" — chỉ lo viết câu trả lời đẹp |
| **Làm gì** | Đọc câu hỏi → tách ra các tiêu chí tìm kiếm → gọi đúng Tool | Nhận kết quả từ Tool → viết tư vấn đúng văn phong Canifa |
| **Model dùng** | GPT-4o-mini / Gemini Flash (nhanh, rẻ) | Claude Sonnet (thông minh, viết hay) |
| **Output** | JSON chuẩn, không được phép viết tự do | Câu trả lời hoàn chỉnh cho khách |

---

## 3. Luồng xử lý thực tế — ví dụ cụ thể

> **Khách nhắn:** *"Cho mình xem váy dự tiệc màu trắng size M còn không?"*

**Bước 1 — Agent 1 (Classifier) xử lý:**
Đọc câu hỏi và xuất ra JSON cứng, không thêm bớt:

```json
{
  "intent": "product_search",
  "category": "váy",
  "occasion": ["dự tiệc"],
  "color": ["trắng"],
  "size": ["M"],
  "gender": "female",
  "tools_to_call": ["lead_search", "stock_check"]
}
```
> ✅ Agent 1 **không được phép** trả lời kiểu *"Dạ em tìm ngay cho chị nhé!"* — nó chỉ xuất JSON, không hơn không kém.

**Bước 2 — Tool Layer thực thi:**
Hệ thống nhận JSON trên và chạy song song:
- `lead_search` → Tìm các SKU váy trắng, dự tiệc trong database
- `stock_check` → Lọc ra những cái còn size M
- *(Nếu có intent "phối đồ" thì gọi thêm `styling_engine`)*

Kết quả trả về là danh sách sản phẩm thực tế, có SKU, giá, ảnh.

**Bước 3 — Agent 2 (Stylist) viết câu trả lời:**
Nhận vào: câu hỏi gốc của khách + kết quả từ Tool → viết tư vấn:
> *"Chị ơi, Canifa hiện có 3 mẫu váy trắng dự tiệc còn size M cho chị lựa đây ạ! Em thấy mẫu VM2301 dáng midi cut vai chéo đang hot lắm, vừa sang vừa phù hợp tiệc tối. Chị muốn em gợi ý thêm cách phối phụ kiện không ạ? 🤍"*

> ✅ Agent 2 **không cần biết** SQL hay Tool là gì — nó chỉ cần dữ liệu sạch và viết đẹp.

---

## 4. Sơ đồ đối chiếu kiến trúc

![Architecture Comparison](data/29-new-agent_architect/architecture_comparison.svg)

---

## 5. So sánh kết quả Ver 1 vs Ver 2

| Tình huống | Ver 1 ❌ | Ver 2 ✅ |
|---|---|---|
| **Khách hỏi váy size M** | Đôi khi quên filter size, trả về cả size L | Luôn lọc đúng size vì JSON bắt buộc có field `size` |
| **Hàng hết stock** | Vẫn có thể gợi ý sản phẩm đã hết | `stock_check` chạy bắt buộc trước khi Stylist viết |
| **Giọng văn không đều** | Lúc thân thiện lúc cứng nhắc tùy "mood" AI | Agent 2 được train riêng giọng Canifa, nhất quán 100% |
| **Muốn sửa giọng bot** | Sửa là có thể làm hỏng logic Tool | Chỉ cần sửa prompt Agent 2, Agent 1 không bị ảnh hưởng |

---

## 6. Tại sao Ver 1 bị "lười" và trả lời nhảm

Đây là vấn đề cốt lõi mà các hệ thống AI đơn lẻ thường gặp phải.

**Hiện tượng thực tế:**
*   AI trả lời: *"Dạ em không tìm thấy sản phẩm phù hợp ạ"* — trong khi kho còn đầy hàng.
*   AI trả lời: *"Chị có thể mô tả thêm không ạ"* — dù câu hỏi khách đã rất rõ ràng.

**Nguyên nhân thực sự — Cognitive Overload (Quá tải nhận thức):**
LangChain đã thực nghiệm và phát hiện: khi một single agent được giao quá nhiều tool và domain, performance sụp đổ. 
*   **GPT-4o** rơi xuống chỉ còn **2% độ chính xác** khi có từ 7 domain trở lên. 
*   **Llama-3.3-70B** thì quên luôn không gọi tool email trong mọi test case. 
*   Nói đơn giản: một khi agent có từ 15–20 tool trở lên, độ chính xác chọn đúng tool rơi xuống dưới 80% — agent bắt đầu gọi sai tool, truyền sai tham số, hoặc bỏ qua tool hoàn toàn.

> **Ví dụ thực tế:** Khách hỏi "váy trắng size M còn không"
> Con AI Ver 1 đang phải nhớ đồng thời: Persona Canifa + 5 tools (`lead_search`, `stock_check`, `styling_engine`, `size_guide`, `promotion_check`) + lịch sử chat + hướng dẫn format câu trả lời.
> → Nó "choáng", chọn đại một câu an toàn kiểu *"Em không tìm thấy ạ"* để thoát khỏi tình huống.

---

## 7. Cơ chế Guardrail — Ver 2 "bịt" lỗi này như thế nào

**Cưỡng bức output dạng JSON (Structured Output Enforcement):**
Agent 1 bị ép chỉ được phép xuất ra đúng một schema JSON cố định. Nếu không ra đúng format → hệ thống tự reject và retry, không bao giờ cho câu trả lời "lười" lọt qua.

**System Prompt của Agent 1:**
> "Bạn CHỈ được phép trả về JSON theo schema dưới đây.
> KHÔNG được viết bất kỳ câu tiếng Việt nào.
> KHÔNG được trả về text tự do.
> Nếu không đủ thông tin, điền null vào field đó."

**Kết quả:** Nghiên cứu **MSARL** xác nhận kiến trúc dual-agent với phân công vai trò rõ ràng giảm đáng kể hallucination và tăng xu hướng gọi đúng tool, cải thiện độ bền tổng thể của hệ thống.

---

## 8. Lợi thế kỹ thuật của Ver 2 — Gọi Tool song song

*   **Ver 1 (Tuần tự):** Gọi tool tuần tự — tool này xong mới gọi tool tiếp.
    `lead_search (800ms)` → `stock_check (400ms)` → `styling_engine (600ms)` = **Tổng: ~1800ms**.
*   **Ver 2 (Song song):** Agent 1 ra JSON một lần → Tool Layer gọi đồng thời cả 3.
    `lead_search (800ms)` ─┐
    `stock_check (400ms)` ─┼─→ **Kết quả gộp (800ms)** → Agent 2 viết (400ms)
    `styling_engine(600ms)`─┘
    **Tổng: ~1200ms**.

Multi-agent cho phép chạy song song: trong khi retrieval agent tìm kiếm knowledge base, CRM agent đồng thời lấy lịch sử khách hàng. Tổng latency chỉ bằng bước dài nhất, thay vì cộng tất cả lại.

---

## 9. Tại sao Ver 2 dễ mở rộng hơn

Multi-agent đặc biệt có giá trị khi: một agent duy nhất có quá nhiều tool và đưa ra quyết định tệ về tool nào dùng; các task yêu cầu kiến thức chuyên biệt với context sâu; hoặc khi cần áp đặt ràng buộc tuần tự chỉ mở khóa sau khi điều kiện nhất định được thỏa mãn.

**Ví dụ thực tế — thêm tính năng "gợi ý size theo body":**

| | Ver 1 | Ver 2 |
|---|---|---|
| **Muốn thêm Tool size_recommender** | Phải viết lại toàn bộ prompt → rủi ro làm hỏng giọng điệu | Chỉ thêm field `body_measurements` vào schema Agent 1 |
| **Muốn thêm Tool gọi API ngoài** | Phải train/viết lại cả con AI | Thêm 1 tool mới trong Tool Layer, Agent 1 biết khi nào gọi |
| **Muốn đổi giọng văn bot** | Nguy cơ ảnh hưởng logic Tool | Chỉ sửa System Prompt của Agent 2 |

---

## 10. Những điều cần biết thêm

**Khi Tool bị lỗi hoặc hết hàng:**
Agent 2 vẫn nhận được thông tin *"không tìm thấy sản phẩm phù hợp"* và tự động chuyển sang xin lỗi khéo + gợi ý hướng khác — thay vì im lặng hoặc bịa sản phẩm.

**Trade-off cần nhận thức rõ:**
- Mỗi tin nhắn tốn **2 lần gọi API** (Agent 1 + Agent 2).
- Chi phí tracking phức tạp hơn vì 2 model khác nhau.
- Đây là sự đánh đổi chấp nhận được so với độ tin cậy cao hơn nhiều.

---

## 📌 Tài liệu tham khảo cộng đồng

Dưới đây là các nguồn tham khảo và benchmark kỹ thuật hỗ trợ cho kiến trúc này:

*   **Reddit r/AI_Agents — "Tool Overload - Agents and MCP":** *"Một khi agent phải access 5+ tool, đặc biệt khi cần data từ nhiều tool cùng lúc, độ chính xác giảm. Giải pháp: phân tán tool ra nhiều agent chuyên biệt."*
*   **LangChain Research — "Benchmarking Single Agent Performance":** ["Cả context nhiều hơn lẫn tool nhiều hơn đều làm giảm performance agent."](https://www.langchain.com/blog/react-agent-benchmarking)
*   **MSARL Research (Arxiv):** Kiến trúc multi-agent giúp giảm đáng kể tỷ lệ hallucination.
*   **Techzine & GuruSup:** Các thực nghiệm về giới hạn cognitive load của GPT-4o và Llama.

---
*Tài liệu kỹ thuật nội bộ Canifa AI Platform.*
