Mở đầu — vì sao bài này quan trọng
Nếu bạn từng thử build một chatbot chỉ bằng cách gọi thẳng API của OpenAI hay Claude từ frontend, bạn sẽ nhanh chóng nhận ra một sự thật đau đớn: cái demo chạy đẹp trong 10 phút đầu tiên hoàn toàn không phải là một sản phẩm. Nó không nhớ được cuộc hội thoại trước, nó lộ API key ra ngoài trình duyệt, nó tốn tiền không kiểm soát được, và khi có 500 người dùng cùng lúc thì nó sập.
Chatbot là dạng sản phẩm AI phổ biến nhất mà bạn sẽ được yêu cầu xây dựng — từ chăm sóc khách hàng, trợ lý nội bộ, đến tư vấn bán hàng. Nhưng chính vì "ai cũng làm được cái demo", nên phần lớn team dừng lại ở mức demo và không bao giờ đưa được lên production một cách ổn định. Khoảng cách giữa demo và sản phẩm nằm ở kiến trúc (architecture) — cách bạn tổ chức các thành phần để chúng phối hợp với nhau một cách bền vững, an toàn và tiết kiệm chi phí.
Bài này tập trung hoàn toàn vào kiến trúc của một sản phẩm chatbot: các thành phần cốt lõi, chúng nói chuyện với nhau ra sao, dữ liệu chảy như thế nào từ lúc người dùng gõ một câu cho tới lúc nhận về câu trả lời. Chúng ta sẽ không đi sâu vào RAG (Bài 7), fine-tuning (Bài 8) hay guardrails (Bài 10) — đó là các mảnh ghép riêng — mà tập trung vào bức tranh tổng thể để bạn biết đặt các mảnh ghép đó vào đâu.
Khái niệm cốt lõi
Một sản phẩm chatbot production thường gồm bốn tầng chính, cộng thêm vài thành phần hỗ trợ mà thiếu chúng thì sản phẩm không thể vận hành thật.
1. Frontend — nơi người dùng gặp chatbot
Frontend là điểm chạm. Nó có thể xuất hiện dưới nhiều hình thức:
- Web chat: cửa sổ chat trên website (kiểu Intercom, Tawf.to).
- Mobile: tích hợp trong app iOS/Android.
- Voice: người dùng nói, hệ thống chuyển speech-to-text rồi text-to-speech.
- Embed / widget: một đoạn script nhúng vào website bất kỳ.
- Kênh có sẵn: Zalo OA, Messenger, WhatsApp, Telegram — đặc biệt quan trọng ở Việt Nam vì Zalo là nơi khách hàng đã ở sẵn.
2. Backend API — bộ não điều phối
Đây là thành phần quan trọng nhất nhưng hay bị bỏ qua. Backend đứng giữa frontend và LLM, đảm nhận những việc mà LLM không tự làm được:
- Xác thực và phân quyền (authentication): biết ai đang chat, họ có quyền gì.
- Rate limiting và quota: chặn người dùng spam, giới hạn số token mỗi user để kiểm soát chi phí.
- Orchestration: dựng prompt hoàn chỉnh bằng cách ghép system prompt + lịch sử hội thoại + ngữ cảnh liên quan, rồi gọi LLM.
- Giữ bí mật API key: key nằm trong biến môi trường ở server, không bao giờ lộ ra client.
- Logging và ghi nhận: lưu lại request/response để debug và cải thiện sau này.
3. LLM — động cơ ngôn ngữ
Đây là "bộ não" sinh ra câu trả lời. Bạn có hai nhóm lựa chọn chính:
- Proprietary (API thương mại): GPT của OpenAI, Claude của Anthropic, Gemini của Google. Ưu điểm: mạnh nhất, dễ tích hợp, không cần lo hạ tầng. Nhược điểm: trả tiền theo token, dữ liệu đi ra ngoài, phụ thuộc nhà cung cấp.
- Open / self-host: Llama, Qwen, hoặc các model Việt như của VinAI. Ưu điểm: kiểm soát dữ liệu, chi phí cố định ở quy mô lớn, tùy biến sâu. Nhược điểm: cần đội ngũ vận hành GPU, chất lượng thường thấp hơn ở tác vụ khó.
4. Context store — trí nhớ của chatbot
LLM về bản chất là stateless — mỗi lần gọi, nó không nhớ gì về lần trước. Cảm giác "chatbot nhớ được cuộc trò chuyện" hoàn toàn là ảo giác do bạn tạo ra: mỗi lần gọi, backend phải gửi lại toàn bộ (hoặc một phần) lịch sử hội thoại. Context store là nơi lưu trữ trí nhớ đó, thường gồm ba loại:
- Session memory: lịch sử hội thoại của phiên hiện tại. Thường lưu ở Redis hoặc database, gắn với
conversation_id. - Long-term memory / user profile: thông tin bền vững về người dùng (tên, gói dịch vụ, lịch sử đơn hàng), lưu ở database quan hệ như PostgreSQL/MySQL.
- Knowledge context: kiến thức chuyên môn để chatbot trả lời đúng — đây chính là nơi RAG và vector database đi vào (chi tiết ở Bài 7 và Bài 14).
Luồng dữ liệu tổng thể
Ghép lại, một request đi qua các bước: người dùng gõ ở frontend → gửi tới backend API kèm conversation_id → backend xác thực, lấy lịch sử từ context store, lắp ghép prompt → gọi LLM → nhận kết quả (dạng stream) → backend lưu lại vào context store và stream về frontend. Hiểu rõ luồng này là hiểu 80% kiến trúc chatbot.
Tình huống thực tế
Ví dụ 1 — Chatbot chăm sóc khách hàng của một sàn TMĐT Việt Nam
Một công ty giả định tên ShopViet (sàn thương mại điện tử tầm trung, khoảng 50.000 đơn/tháng) muốn làm chatbot trả lời câu hỏi "đơn hàng của tôi tới đâu rồi?". Team đầu tiên làm phiên bản chỉ gọi thẳng GPT-4 với prompt "Bạn là trợ lý ShopViet". Kết quả: chatbot bịa đặt trạng thái đơn hàng vì nó không hề biết dữ liệu thật.
Họ thiết kế lại kiến trúc: frontend là widget trên Zalo OA (vì khách hàng đã ở đó); backend nhận user_id và conversation_id; khi phát hiện câu hỏi về đơn hàng, backend gọi API nội bộ của hệ thống order để lấy trạng thái thật, đưa dữ liệu đó vào context, rồi mới gọi LLM diễn đạt thành câu trả lời tự nhiên. Context store dùng Redis cho session (giữ 10 lượt gần nhất) và PostgreSQL cho profile khách.
Bài học: LLM giỏi diễn đạt nhưng không biết dữ liệu của bạn. Kiến trúc tốt là để backend lấy dữ liệu thật rồi "mớm" cho LLM, chứ không để LLM tự bịa. Đây là ranh giới giữa một chatbot "nói hay" và một chatbot "trả lời đúng".
Ví dụ 2 — Chi phí token bùng nổ ở một startup edtech
Một startup edtech ở TP.HCM làm trợ lý học tiếng Anh. Ban đầu họ lưu toàn bộ lịch sử hội thoại và gửi lại hết cho model mỗi lượt. Với người dùng trò chuyện dài, mỗi lượt cuối có thể mang theo 8.000–10.000 token lịch sử. Hóa đơn API tăng vọt: một số user "nặng" tiêu tốn gấp 20 lần user trung bình, khiến chi phí biến động khó dự đoán.
Họ sửa bằng cách đưa context management vào backend: giữ nguyên 6 lượt gần nhất, và tóm tắt các lượt cũ hơn thành một đoạn "summary" ngắn gọn lưu trong session. Đồng thời đặt hard cap: mỗi phiên tối đa N token, vượt thì tự tạo summary mới. Kết quả, chi phí token trên mỗi phiên giảm khoảng 60% mà chất lượng hội thoại gần như không đổi.
Bài học: Context store không phải là "lưu càng nhiều càng tốt". Kiến trúc trí nhớ phải chủ động cắt và tóm tắt. Chi phí của chatbot nằm trực tiếp trong cách bạn thiết kế tầng context.
Ví dụ 3 — Ngân hàng và bài toán không thể để dữ liệu ra ngoài
Một ngân hàng cỡ vừa muốn làm trợ lý nội bộ cho nhân viên tra cứu quy trình. Vì lý do tuân thủ (compliance) và Nghị định 13 về bảo vệ dữ liệu cá nhân, họ không được gửi dữ liệu nhạy cảm ra API nước ngoài. Kiến trúc của họ tách làm hai: câu hỏi chung, không nhạy cảm thì route sang một API thương mại; câu hỏi chứa dữ liệu nội bộ thì route sang một model open-source (Llama) self-host trong data center của ngân hàng.
Điểm hay là backend của họ có một lớp router đứng trước LLM, phân loại request và chọn model phù hợp — nhờ vậy interface với LLM được trừu tượng hóa hoàn toàn.
Bài học: Với các ngành bị quản lý chặt, kiến trúc phải cho phép chọn nơi xử lý dữ liệu. Việc trừu tượng hóa tầng LLM từ đầu giúp bạn có được sự linh hoạt này mà không phải đập đi xây lại.
Hướng dẫn từng bước
Đây là trình tự dựng kiến trúc một chatbot production từ con số 0:
- Xác định kênh frontend trước. Khách hàng của bạn ở đâu? Nếu là người tiêu dùng Việt Nam, ưu tiên Zalo/Messenger hơn là web chat tự xây. Điều này ảnh hưởng lớn tới tỷ lệ tiếp cận.
- Dựng backend làm proxy ngay từ đầu. Đừng bao giờ gọi LLM từ frontend. Tạo một endpoint duy nhất, ví dụ
POST /chat, nhận{ conversation_id, message }và trả về stream. Toàn bộ logic nằm ở đây.
- Thiết kế schema cho context store. Tối thiểu cần hai bảng:
conversations(id, user_id, created_at) vàmessages(id, conversation_id, role, content, tokens, created_at). Thêm Redis cho session cache nếu cần tốc độ.
- Trừu tượng hóa lớp LLM. Viết một interface kiểu
generate(messages, options)và cài đặt cho từng provider. Hôm nay dùng Claude, mai muốn thử Gemini chỉ cần đổi cấu hình.
- Xây pipeline lắp ghép prompt. Mỗi lượt: lấy system prompt + tóm tắt/lịch sử từ context store + tin nhắn mới → đóng gói thành mảng messages → gọi LLM.
- Bật streaming đầu-cuối. Backend nhận stream từ LLM và forward về frontend qua Server-Sent Events (SSE) hoặc WebSocket. Trải nghiệm "chữ hiện dần" tạo cảm giác nhanh dù tổng thời gian không đổi.
- Thêm rate limit và quota. Giới hạn theo user để tránh lạm dụng và kiểm soát chi phí. Đây là hàng rào chi phí đầu tiên.
- Logging mọi thứ. Lưu request, response, số token, độ trễ. Không có log thì bạn không thể debug, không thể tính chi phí, và không thể cải thiện.
Lỗi thường gặp & mẹo
- Gọi LLM trực tiếp từ frontend. Đây là lỗi số một, dẫn tới lộ API key và không kiểm soát được chi phí. Luôn có backend đứng giữa.
- Nghĩ rằng LLM tự nhớ hội thoại. Nó stateless. Nếu chatbot "quên" là do backend của bạn không gửi lại lịch sử. Trí nhớ là trách nhiệm của bạn, không phải của model.
- Nhồi toàn bộ lịch sử vào mỗi request. Vừa tốn tiền vừa dễ vượt context window. Dùng truncation + summarization ngay từ sớm.
- Không tách
conversation_id. Nếu trộn lẫn hội thoại của các user, chatbot sẽ trả lời sai ngữ cảnh. Mọi thứ phải gắn với một session ID rõ ràng.
- Bỏ qua streaming. Người dùng đợi 8 giây nhìn màn hình trống sẽ tưởng hệ thống treo. Stream giúp cảm nhận độ trễ (perceived latency) giảm mạnh.
- Mẹo — tách "diễn đạt" khỏi "dữ liệu". Để backend lấy dữ liệu thật (đơn hàng, số dư, quy trình) rồi mới đưa cho LLM diễn đạt. Đừng bao giờ để LLM tự bịa dữ liệu.
- Mẹo — thiết kế idempotent và có timeout. LLM đôi khi chậm hoặc lỗi. Đặt timeout, retry hợp lý, và có câu trả lời dự phòng khi model không phản hồi.
Bài tập thực hành
- Vẽ sơ đồ kiến trúc. Chọn một use case cụ thể (ví dụ: chatbot tư vấn đặt lịch spa qua Zalo). Vẽ đầy đủ bốn tầng: frontend, backend, LLM, context store, và mũi tên thể hiện luồng dữ liệu của một request từ đầu đến cuối.
- Thiết kế schema context store. Viết ra định nghĩa bảng cho
conversationsvàmessages. Bổ sung: bạn sẽ lưu summary của các lượt cũ ở đâu, và tóm tắt khi nào?
- Viết chiến lược context management. Với ngân sách tối đa 4.000 token cho phần lịch sử mỗi request, mô tả bằng lời thuật toán bạn dùng để quyết định giữ lượt nào, tóm tắt lượt nào, bỏ lượt nào.
- Tình huống trừu tượng hóa LLM. Giả sử hôm nay dùng GPT-4, tháng sau sếp yêu cầu chuyển sang Llama self-host vì lý do dữ liệu. Liệt kê những phần trong kiến trúc của bạn cần thay đổi, và những phần không đổi. Nếu danh sách "cần thay đổi" quá dài, kiến trúc của bạn đang bị coupling quá chặt.
Tóm tắt
Kiến trúc chatbot production xoay quanh bốn thành phần cốt lõi: frontend (điểm chạm, xử lý streaming, không giữ API key), backend API (bộ não điều phối: xác thực, rate limit, lắp ghép prompt, giữ bí mật key), LLM (động cơ ngôn ngữ, nên trừu tượng hóa để dễ thay), và context store (trí nhớ, vì bản thân LLM stateless).
Ba nguyên tắc cần khắc cốt: (1) luôn có backend đứng giữa, không bao giờ gọi LLM từ client; (2) trí nhớ và ngữ cảnh là trách nhiệm kiến trúc của bạn, phải chủ động quản lý để kiểm soát cả chất lượng lẫn chi phí; (3) tách phần "lấy dữ liệu thật" khỏi phần "diễn đạt" để chatbot trả lời đúng chứ không chỉ nói hay. Nắm chắc luồng dữ liệu từ frontend → backend → context store → LLM → stream ngược lại, bạn đã có nền tảng vững để đặt các mảnh ghép chuyên sâu như RAG, guardrails hay MLOps vào đúng vị trí trong các bài tiếp theo.