Mình nhớ lần đầu thử gọi API của một LLM, cứ tưởng vậy là có chatbot — nhưng nó cứ quên sạch những gì vừa nói trước đó. Người dùng gõ “tên tôi là Nam”, rồi hỏi tiếp “vậy tên tôi là gì?” — bot trả lời như chưa từng biết. Lúc đó mình mới hiểu: gọi được API chỉ là bước đầu, cái khó hơn là làm cho chatbot thực sự nhớ ngữ cảnh hội thoại.
Bài này đi thẳng vào cái vấn đề đó — xây chatbot Python với LLM thực sự có trí nhớ, từ kiến trúc đến code chạy được ngay.
Vấn đề: Tại sao chatbot cứ “mất trí nhớ”?
Hầu hết LLM API đều stateless — mỗi lần gọi, model không biết gì về cuộc trò chuyện trước. Bạn phải tự gửi lại toàn bộ lịch sử hội thoại mỗi lần request. Nghe rắc rối, nhưng đây là thiết kế có chủ đích: bạn kiểm soát hoàn toàn ngữ cảnh model nhìn thấy.
Nếu chỉ gọi API theo kiểu “một câu hỏi — một câu trả lời” mà không kèm history, bạn đang xây một cái máy trả lời câu hỏi đơn lẻ, không phải chatbot. Đó là lý do chatbot tự động viết code, trợ lý hỗ trợ khách hàng… đều phải giải quyết bài toán quản lý hội thoại này.
Khái niệm cốt lõi cần nắm
Conversation History là gì?
Về cơ bản, đây chỉ là một list Python. Mỗi phần tử là một message với role (user/assistant) và content. Bạn gửi toàn bộ list lên API — model đọc hết rồi mới trả lời.
conversation_history = [
{"role": "user", "content": "Tên tôi là Nam"},
{"role": "assistant", "content": "Chào Nam! Mình có thể giúp gì?"},
{"role": "user", "content": "Vậy tên tôi là gì?"},
]
# Gửi cả list → model biết context → trả lời đúng "Tên bạn là Nam"
System Prompt
System prompt là message đặc biệt với role: "system", đặt ở đầu conversation. Nói đơn giản: đây là chỗ bạn nói với model “mày là ai, làm việc gì, được phép gì”. Muốn bot trả lời ngắn hay chi tiết, bằng tiếng Việt hay tiếng Anh, chỉ trả lời đúng chủ đề hay linh hoạt — tất cả định nghĩa ở đây.
Context Window
Mỗi model có giới hạn token đầu vào — Claude Haiku 200K, GPT-4o 128K. Thực tế, 20 tin nhắn qua lại (~3,000–5,000 token) cộng thêm vào mỗi request: nhỏ với 1 user, nhưng khi có 1,000 user thì tính lại. Hội thoại quá dài còn có thể gây lỗi context_length_exceeded — bạn cần tự trim history.
Thực hành: Xây chatbot Python có bộ nhớ hội thoại
Cài đặt thư viện
Mình dùng Anthropic SDK vì API design rõ ràng, dễ hiểu kiến trúc message. Nếu bạn dùng OpenAI hay Gemini, cấu trúc tương tự — chỉ khác tên hàm.
pip install anthropic python-dotenv
Tạo file .env để lưu API key:
ANTHROPIC_API_KEY=sk-ant-api03-your-key-here
Chatbot cơ bản với conversation memory
Tạo file chatbot.py:
import os
from dotenv import load_dotenv
import anthropic
load_dotenv()
client = anthropic.Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))
SYSTEM_PROMPT = """Bạn là trợ lý IT thân thiện, chuyên giải thích kỹ thuật
bằng tiếng Việt dễ hiểu. Trả lời ngắn gọn, đúng trọng tâm."""
def chat(history: list, user_message: str) -> str:
history.append({"role": "user", "content": user_message})
response = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=1024,
system=SYSTEM_PROMPT,
messages=history
)
reply = response.content[0].text
history.append({"role": "assistant", "content": reply})
return reply
def main():
print("Chatbot IT — gõ 'quit' để thoát\n")
history = []
while True:
user_input = input("Bạn: ").strip()
if not user_input:
continue
if user_input.lower() in ("quit", "exit", "q"):
print("Tạm biệt!")
break
print(f"Bot: {chat(history, user_input)}\n")
if __name__ == "__main__":
main()
Chạy thử:
python chatbot.py
Chatbot IT — gõ 'quit' để thoát
Bạn: Docker là gì?
Bot: Docker là nền tảng container hóa ứng dụng...
Bạn: Cho ví dụ cụ thể hơn
Bot: (Tiếp tục từ câu hỏi về Docker, không bị mất context)
Thêm giới hạn độ dài hội thoại
Hội thoại dài ngốn nhiều token và tốn tiền. Giải pháp đơn giản: chỉ giữ N cặp message gần nhất.
MAX_HISTORY_PAIRS = 10 # Giữ 10 cặp hỏi-đáp gần nhất
def trim_history(history: list) -> list:
max_messages = MAX_HISTORY_PAIRS * 2 # 1 cặp = 1 user + 1 assistant
return history[-max_messages:] if len(history) > max_messages else history
def chat(history: list, user_message: str) -> str:
history.append({"role": "user", "content": user_message})
response = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=1024,
system=SYSTEM_PROMPT,
messages=trim_history(history) # Gửi history đã trim
)
reply = response.content[0].text
history.append({"role": "assistant", "content": reply})
return reply
Lưu và tải lại hội thoại giữa các phiên
Đây là cái hay bị bỏ qua nhất khi tự build bot — đóng terminal là mất hết context. Người dùng debug cả buổi, xây được ngữ cảnh dài, mở lại phải bắt đầu từ đầu. Lưu history ra JSON giải quyết gọn:
import json
from pathlib import Path
HISTORY_FILE = Path("chat_history.json")
def save_history(history: list):
HISTORY_FILE.write_text(
json.dumps(history, ensure_ascii=False, indent=2),
encoding="utf-8"
)
def load_history() -> list:
if HISTORY_FILE.exists():
return json.loads(HISTORY_FILE.read_text(encoding="utf-8"))
return []
Code hoàn chỉnh (~60 dòng)
Gộp tất cả lại thành chatbot.py đầy đủ:
import os, json
from pathlib import Path
from dotenv import load_dotenv
import anthropic
load_dotenv()
client = anthropic.Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))
HISTORY_FILE = Path("chat_history.json")
MAX_HISTORY_PAIRS = 10
SYSTEM_PROMPT = """Bạn là trợ lý IT thân thiện, giải thích kỹ thuật bằng tiếng Việt
dễ hiểu. Trả lời ngắn gọn, đúng trọng tâm, dùng ví dụ thực tế khi cần."""
def trim_history(history: list) -> list:
max_msg = MAX_HISTORY_PAIRS * 2
return history[-max_msg:] if len(history) > max_msg else history
def chat(history: list, user_message: str) -> str:
history.append({"role": "user", "content": user_message})
response = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=1024,
system=SYSTEM_PROMPT,
messages=trim_history(history)
)
reply = response.content[0].text
history.append({"role": "assistant", "content": reply})
return reply
def save_history(history: list):
HISTORY_FILE.write_text(
json.dumps(history, ensure_ascii=False, indent=2), encoding="utf-8"
)
def load_history() -> list:
return (
json.loads(HISTORY_FILE.read_text(encoding="utf-8"))
if HISTORY_FILE.exists() else []
)
def main():
print("Chatbot IT — 'quit' thoát | 'clear' xóa lịch sử\n")
history = load_history()
if history:
print(f"(Tải {len(history) // 2} cặp hội thoại cũ)\n")
while True:
user_input = input("Bạn: ").strip()
if not user_input:
continue
if user_input.lower() in ("quit", "exit", "q"):
save_history(history)
print("Đã lưu lịch sử. Tạm biệt!")
break
if user_input.lower() == "clear":
history.clear()
print("Đã xóa lịch sử hội thoại.\n")
continue
print(f"Bot: {chat(history, user_input)}\n")
if __name__ == "__main__":
main()
Kiểm tra kết quả
Chạy lần đầu, trò chuyện, rồi gõ quit. Chạy lại — bot nhớ toàn bộ hội thoại trước:
python chatbot.py
# (Tải 5 cặp hội thoại cũ)
# Bạn: Hồi nãy mình hỏi gì vậy?
# Bot: Bạn hỏi về Docker và cách dùng volume...
Kết luận
Nhiều người dừng lại ở bước “gọi được API là xong” — nhưng thứ làm chatbot thực sự dùng được chính là quản lý conversation history đúng cách. Chỉ ~60 dòng Python là đủ để có bot có trí nhớ, lưu/tải hội thoại, và không bị vượt context window.
Từ đây, hướng mở rộng khá tự nhiên: thêm tool use để bot thực sự gọi API hay chạy code, RAG để đọc tài liệu nội bộ của bạn, hoặc wrap toàn bộ thành REST API với FastAPI. Nhưng trước khi đi xa hơn — hiểu rõ cái gốc là stateless API và history tự quản lý sẽ giúp bạn debug nhanh hơn nhiều khi bot hoạt động sai.

