Chatbot “dở” vì chọn sai cách, không phải vì code tệ
Mình từng mất gần 2 tuần xây chatbot hỗ trợ khách hàng bằng if/else thuần. Kết quả? Bot hiểu đúng câu gõ chính xác theo template, còn lại thì câm lặng hoàn toàn. Khách gõ “mk quên rồi” thay vì “tôi quên mật khẩu” là xong — bot không hiểu gì hết.
Vấn đề không phải code tệ — mà là chọn sai kiến trúc ngay từ đầu. Thực ra chỉ có 3 hướng xây chatbot, và ranh giới giữa chúng rõ ràng hơn mình nghĩ ban đầu.
3 cách xây chatbot: So sánh thực tế
Cách 1: Rule-based (if/else, regex)
Cách cổ điển nhất — và đừng vội đánh giá thấp nó. Bot đọc input, so khớp pattern, trả về response cố định. Kiểu này vẫn đang chạy trong production ở nhiều hệ thống FAQ, IVR điện thoại, và chatbot ngân hàng.
def simple_bot(user_input):
msg = user_input.lower()
if "mật khẩu" in msg or "pass" in msg:
return "Vào mục 'Quên mật khẩu' để reset nhé."
elif "giờ mở cửa" in msg:
return "Mở cửa 8h–17h, thứ 2 đến thứ 6."
else:
return "Xin lỗi, mình chưa hiểu câu hỏi này."
Ưu điểm:
- Không cần API, không tốn tiền, chạy offline hoàn toàn
- Response 100% kiểm soát được — quan trọng với use case pháp lý hoặc y tế
- Deploy đơn giản, zero dependency bên ngoài
Nhược điểm:
- Cần viết rule cho mọi biến thể câu hỏi — không thực tế khi có hàng trăm câu
- Không xử lý được tiếng lóng, viết tắt, typo
- Rule base phình to thì bảo trì là ác mộng
Cách 2: Intent classification + ML truyền thống
Train một model nhỏ (Naive Bayes, SVM, hoặc BERT nhỏ) để phân loại intent từ câu hỏi, sau đó map intent sang response cố định. Mình từng thử với scikit-learn + TF-IDF cho chatbot 12 intent — đạt ~80% accuracy với khoảng 200 câu training mỗi intent. Không tệ, nhưng phần chuẩn bị data ngốn cả tuần.
Ưu điểm:
- Linh hoạt hơn rule-based — chịu được biến thể ngôn ngữ nhất định
- Chạy local, không phụ thuộc API ngoài sau khi train xong
Nhược điểm:
- Cần data train có gán nhãn — ít nhất vài trăm câu mỗi intent
- Vẫn không generate response linh hoạt, chỉ chọn từ danh sách cố định
- Setup pipeline phức tạp hơn đáng kể so với 2 cách còn lại
Cách 3: LLM-based (Claude, GPT, Gemini…)
Gửi message của user lên LLM, nhận response sinh tự nhiên. Bot “hiểu” ngữ cảnh, viết tắt, typo và giữ được lịch sử hội thoại qua nhiều lượt.
Ưu điểm:
- Hiểu ngôn ngữ tự nhiên — kể cả “mk quên pass rồi”
- Code rất ít — tập trung vào system prompt thay vì viết logic
- Dễ thay đổi behavior mà không cần re-train hay sửa rule
Nhược điểm:
- Tốn tiền theo token — cần tính toán cost trước khi scale
- Phụ thuộc kết nối internet và service bên ngoài
- Response không hoàn toàn deterministic — đôi khi “sáng tạo” quá mức cần thiết
Chọn cách nào cho dự án của bạn?
Sau khi đã thử cả 3 trên dự án thật, mình rút ra nguyên tắc chọn khá rõ ràng:
- Rule-based: Bot nội bộ với dưới 20 loại câu hỏi, cần 100% predictable — bot điều hướng menu, FAQ cứng, hoặc môi trường airgapped.
- ML truyền thống: Bạn đã có data gán nhãn, cần chạy offline, budget eo hẹp nhưng yêu cầu cao hơn if/else.
- LLM: Hầu hết trường hợp còn lại — đặc biệt khi cần conversation tự nhiên, thời gian build ngắn, hoặc câu hỏi không thể predict hết.
Mình đã áp dụng LLM-based trên production cho chatbot support nội bộ — nhân viên hỏi đủ kiểu, không theo pattern nào cả. Kết quả ổn định hơn mong đợi, và không tốn thời gian maintain rule.
Xây chatbot LLM với Python: Từng bước thực tế
Trong ví dụ này mình dùng Anthropic Claude API. Bạn có thể thay bằng OpenAI hoặc Gemini với cú pháp tương tự — concept hoàn toàn giống nhau.
Bước 1: Cài đặt thư viện
pip install anthropic
Bước 2: Chatbot cơ bản (single-turn)
import anthropic
client = anthropic.Anthropic(api_key="your-api-key")
def chat(user_message: str) -> str:
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
system="Bạn là trợ lý hỗ trợ khách hàng của shop thời trang XYZ. Trả lời ngắn gọn, thân thiện.",
messages=[
{"role": "user", "content": user_message}
]
)
return response.content[0].text
# Test thử
print(chat("mk quên pass rồi phải làm sao"))
# → "Bạn click vào 'Quên mật khẩu' ở trang đăng nhập, nhập email là xong nhé!"
Bước 3: Thêm lịch sử hội thoại (multi-turn)
Chatbot thực tế cần nhớ ngữ cảnh — người dùng không muốn giải thích lại từ đầu mỗi lượt. Trick ở đây là truyền toàn bộ lịch sử vào mỗi API call:
import anthropic
import os
client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
SYSTEM_PROMPT = """Bạn là trợ lý IT nội bộ của công ty.
Chỉ trả lời các câu hỏi về IT, phần mềm, và quy trình công ty.
Nếu không biết, hãy nói thật thay vì đoán mò."""
def run_chatbot():
conversation_history = []
print("Chatbot IT nội bộ. Gõ 'quit' để thoát.\n")
while True:
user_input = input("Bạn: ").strip()
if user_input.lower() in ["quit", "exit", "thoát"]:
print("Bot: Tạm biệt!")
break
if not user_input:
continue
conversation_history.append({"role": "user", "content": user_input})
try:
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
system=SYSTEM_PROMPT,
messages=conversation_history
)
bot_reply = response.content[0].text
except anthropic.APIConnectionError:
bot_reply = "Đang gặp sự cố kết nối. Thử lại sau nhé."
except anthropic.RateLimitError:
bot_reply = "Bot đang quá tải, chờ vài giây rồi thử lại."
conversation_history.append({"role": "assistant", "content": bot_reply})
print(f"Bot: {bot_reply}\n")
if __name__ == "__main__":
run_chatbot()
Bước 4: Giới hạn lịch sử để tránh token phình to
Lịch sử hội thoại dài = nhiều token = tốn tiền. Thực tế chỉ cần giữ N lượt gần nhất là đủ ngữ cảnh:
MAX_TURNS = 10 # Giữ 10 lượt gần nhất (mỗi lượt = 1 user + 1 assistant)
def trim_history(history: list) -> list:
if len(history) > MAX_TURNS * 2:
return history[-(MAX_TURNS * 2):]
return history
# Dùng trước khi gọi API:
conversation_history = trim_history(conversation_history)
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
system=SYSTEM_PROMPT,
messages=conversation_history
)
Ước tính chi phí trước khi đưa vào production
Với Claude Sonnet, giá khoảng $3/1M input token và $15/1M output token. Một cuộc hội thoại 10 lượt, mỗi lượt ~200 token input + 150 token output ≈ $0.003 — khá rẻ cho bot nội bộ ít người dùng.
Muốn cắt giảm cost hơn nữa? Dùng Claude Haiku thay Sonnet — rẻ hơn ~10 lần, phù hợp với chatbot FAQ đơn giản không cần reasoning phức tạp.
Kết luận nhanh
Với chatbot nội bộ hoặc prototype cần ra nhanh, LLM là lựa chọn thực tế nhất — code ít, xử lý ngôn ngữ tự nhiên tốt, không cần data training. Rule-based vẫn có chỗ đứng khi cần 100% deterministic hoặc chạy hoàn toàn offline.
Code trên chạy được ngay — thêm API key vào biến môi trường là xong. Bước tiếp nếu muốn nâng cấp: tích hợp RAG để bot trả lời dựa trên tài liệu nội bộ. Hoặc bọc trong FastAPI, expose qua HTTP, nhúng vào web app — thêm khoảng 50 dòng code là đủ.
