Hãy tưởng tượng bạn đang trò chuyện với một người bạn có chứng mất trí nhớ từng giây — mỗi khi bạn dứt lời, họ reset hoàn toàn. Bạn hỏi:
- Giải thích tại sao Claude API stateless — không tự nhớ conversation
- Tự quản lý message history trong code Python
- Viết 3 helper function tái sử dụng: add_user_message, add_assistant_message, chat
- Nhận diện và tránh các lỗi phổ biến với multi-turn (role alternating, history bloat)
Tại sao Claude lại stateless?
3 lý do thiết kế
1. Scalability
Stateful system cần lưu state cho từng user — scale ra triệu user = khó khăn ngàn lần. Stateless = mỗi request độc lập, có thể phân phối đến bất kỳ server nào.
2. Privacy
Nếu Anthropic tự lưu history, họ phải lưu data của bạn. Stateless = data về user ở server của bạn, Anthropic chỉ thấy trong 1 request rồi xóa.
3. Flexibility
Bạn tự quyết định gửi history gì:
Stateful system không cho bạn control này.
Trade-off
Vấn đề cuối — token cost — là chi phí bạn phải trả. Bài 6.47-6.49 (Prompt caching) sẽ giải quyết phần lớn.
- Full history? (đắt)
- Chỉ 10 turn gần nhất? (tiết kiệm)
- Tóm tắt phần cũ + chi tiết phần mới? (hybrid)
┌────────────────────────────────────┐ │ STATELESS (Anthropic API) │ ├────────────────────────────────────┤ │ ✅ Scale dễ │ │ ✅ Privacy rõ ràng │ │ ✅ Flexibility cao │ │ ❌ Dev phải tự quản history │ │ ❌ Token cost dồn lên user lâu │ └────────────────────────────────────┘
Cách multi-turn hoạt động
Nguyên tắc:
- Sau mỗi response từ Claude, append vào messages với role=assistant
- Khi user gửi tiếp, append với role=user
- Gửi TOÀN BỘ list qua mỗi request
┌──────────────────────────────────────────────────────┐
│ │
│ Turn 1: │
│ messages = [ │
│ { role: "user", content: "What is 2+2?" } │
│ ] │
│ ── gửi lên Anthropic ── │
│ ◀── "2+2 = 4" │
│ │
│ Turn 2: │
│ messages = [ │
│ { role: "user", content: "What is 2+2?" }, │
│ { role: "assistant", content: "2+2 = 4" }, │
│ { role: "user", content: "What about 3+3?"}│
│ ] │
│ ── gửi lên Anthropic ── │
│ ◀── "3+3 = 6" │
│ │
│ Turn 3: (tiếp tục append) │
│ ... │
│ │
└──────────────────────────────────────────────────────┘3 helper function chuẩn
Viết 3 function sau ở đầu notebook, dùng lại cho mọi bài sau:
Cách dùng
from dotenv import load_dotenv
load_dotenv()
from anthropic import Anthropic
client = Anthropic()
model = "claude-sonnet-5-20260205"
def add_user_message(messages: list, text: str):
"""Append user message vào list."""
messages.append({"role": "user", "content": text})
def add_assistant_message(messages: list, text: str):
"""Append assistant message vào list."""
messages.append({"role": "assistant", "content": text})
def chat(messages: list) -> str:
"""Gửi messages lên Claude, trả về text response."""
msg = client.messages.create(
model=model,
max_tokens=1000,
messages=messages,
)
return msg.content[0].textCách dùng
Output dự kiến:
messages = []
# Turn 1
add_user_message(messages, "Define quantum computing in one sentence")
answer = chat(messages)
print("Claude:", answer)
add_assistant_message(messages, answer)
# Turn 2 — Claude BIẾT context nhờ history
add_user_message(messages, "Write another sentence")
answer = chat(messages)
print("Claude:", answer)
add_assistant_message(messages, answer)
# In để check
print(f"\nTotal turns: {len(messages)}")3 helper function chuẩn (tiếp)
Claude hiểu "another sentence" nghĩa là "về quantum computing" — vì nó thấy full context trong messages.
Claude: Quantum computing is a form of computation...
Claude: Unlike classical computers that use bits...
Total turns: 4Pattern nâng cao: Interactive loop
Chatbot thật cần vòng lặp đọc input → gửi → in response → lặp.
Chạy trong terminal hoặc Jupyter. Gõ câu hỏi, nhấn Enter, Claude trả lời, gõ tiếp — context được giữ.
def chat_loop():
messages = []
print("Chat with Claude. Type 'quit' to exit.\n")
while True:
user_input = input("You: ")
if user_input.lower() in ("quit", "exit"):
break
add_user_message(messages, user_input)
answer = chat(messages)
add_assistant_message(messages, answer)
print(f"Claude: {answer}\n")
chat_loop()Vấn đề: History dài vô tận
Sau 50 turn, messages có 100 element (50 user + 50 assistant). Mỗi request gửi toàn bộ:
Chi phí scale O(n²) với số turn — không sustainable.
3 chiến lược quản lý history
Chiến lược 1: Sliding window
Chỉ giữ N turn gần nhất:
Turn 1: input_tokens = 10
Turn 2: input_tokens = 30
Turn 10: input_tokens = 500
Turn 50: input_tokens = 5000
Turn 100: input_tokens = 150003 chiến lược quản lý history
Đơn giản, mất context xa. Phù hợp chatbot casual.
Chiến lược 2: Summarization
Mỗi 20 turn, yêu cầu Claude tóm tắt phần cũ:
def trim_history(messages: list, max_turns: int = 20):
"""Giữ max_turns × 2 messages gần nhất."""
max_messages = max_turns * 2
if len(messages) > max_messages:
messages[:] = messages[-max_messages:]Vấn đề: History dài vô tận (tiếp)
Giữ context xa, tốn thêm API call. Phù hợp chatbot nghiêm túc.
Chiến lược 3: Prompt caching
Bật prompt caching cho system prompt + static context → chỉ tính tiền incremental. Chi tiết ở bài 6.47-6.49.
Production-grade. Dùng khi traffic lớn.
def summarize_history(messages: list):
"""Nén history cũ thành 1 system-like message."""
if len(messages) < 40:
return # Chưa cần
old_messages = messages[:20]
# Gọi Claude tóm tắt
summary = chat(old_messages + [{
"role": "user",
"content": "Summarize the above conversation in 200 words."
}])
# Replace 20 cũ bằng 1 summary message
messages[:20] = [{
"role": "user",
"content": f"[Context tóm tắt từ conversation cũ: {summary}]"
}, {
"role": "assistant",
"content": "Understood, I'll continue with this context."
}]Ví dụ thực chiến: Tutor AI toán
Tình huống
Bạn xây AI tutor cho học sinh cấp 2 giải toán. Học sinh hỏi, AI gợi ý từng bước.
Code
Output dự kiến
messages = []
system = None # sẽ học ở bài 6.10
# Turn 1
add_user_message(messages, "Giúp tôi giải 5x + 2 = 3")
ans = chat(messages)
print(f"Tutor: {ans}\n")
add_assistant_message(messages, ans)
# Turn 2 — học sinh tiếp theo step
add_user_message(messages, "Tôi trừ 2 cả 2 vế → 5x = 1")
ans = chat(messages)
print(f"Tutor: {ans}\n")
add_assistant_message(messages, ans)
# Turn 3
add_user_message(messages, "Chia cả 2 cho 5")
ans = chat(messages)
print(f"Tutor: {ans}\n")Output dự kiến
Tutor hiểu progression vì có full history. Nếu không có history, turn 2 "Tôi trừ 2 cả 2 vế" → Claude không biết đang nói về phương trình gì.
Tutor: Gợi ý: để giải 5x + 2 = 3, bạn cần isolate x.
Bước đầu tiên có thể làm gì để loại bỏ số 2?
Tutor: Chính xác! Bây giờ bạn có 5x = 1.
Làm sao để lấy chỉ riêng x?
Tutor: Đúng rồi! x = 1/5 = 0.2. Bạn đã giải được!Case studies theo ngành
💼 Sales — Discovery call assistant
Tình huống: Sales rep chat với AI trước call để brainstorm câu hỏi discovery.
Setup: Multi-turn với history. Rep mô tả company, AI gợi ý questions. Rep refine, AI adjust.
Kết quả: 30 phút prep → 10 phút, với question bank tùy chỉnh theo prospect.
🎧 Customer Support — Contextual resolution
Tình huống: Customer báo issue, AI hỏi clarify, customer trả lời, AI gợi fix.
Key: Mỗi turn AI cần nhớ issue gốc + clarifications. Không có history → hỏi lại vô nghĩa.
📝 Content — Iterative writing
Tình huống: Writer feedback "phần 2 viết lại theo hướng casual hơn", AI làm, writer ok phần 2 nhưng muốn fix phần 3.
Key: AI cần nhớ bản nháp hiện tại + feedback trước đó. Multi-turn cứu.
Anti-patterns với multi-turn
❌ Quên append assistant message
Hiện tượng:
Fix: Luôn có cặp add_user_message + chat + add_assistant_message.
❌ Thêm role khác ngoài user/assistant
messages = []
add_user_message(messages, "Hi")
answer = chat(messages)
# ← QUÊN add_assistant_message!
add_user_message(messages, "Remember what I said?")
# messages = [user1, user2] → 2 user liên tiếp → API error❌ Thêm role khác ngoài user/assistant
Fix: System prompt đi qua tham số system riêng (bài 6.10), không vào messages.
❌ Gửi empty string làm content
# ❌ Không có role "system" trong messages
messages.append({"role": "system", "content": "..."})❌ Gửi empty string làm content
Fix: Validate input trước khi append.
❌ Không trim history, bill blowup
Hiện tượng: Chatbot public, user chat 200 turn → 1 user đốt $5.
Fix: Trim history, hoặc dùng caching.
❌ Append response mà chưa check stop_reason
# ❌ Không hợp lệ
messages.append({"role": "user", "content": ""})❌ Append response mà chưa check stop_reason
Fix: Skip append nếu truncated, hoặc retry với max_tokens lớn hơn.
msg = client.messages.create(...)
if msg.stop_reason == "max_tokens":
# Response bị cắt — append vào history sẽ misleading
pass
add_assistant_message(messages, msg.content[0].text)Áp dụng ngay
Bài tập 1: Xây chatbot 5-turn (20 phút)
Trong notebook mới 02_chat.ipynb:
Bài tập 2: Implement sliding window (15 phút)
Mở rộng helper chat():
Test bằng cách chat 30 turn, verify Claude "quên" turn 1-10 nhưng nhớ turn 20-30.
- Define 3 helper functions (copy từ bài)
- Tạo messages rỗng
- Làm 5 turn với Claude về chủ đề yêu thích (ví dụ: bóng đá, nấu ăn)
- In messages cuối để xem đầy đủ history
- Tính tổng input_tokens và output_tokens qua 5 turn
def chat(messages: list, max_history: int = 20) -> str:
"""Chat với sliding window cho history."""
# Giữ max_history turns gần nhất (mỗi turn = 2 messages)
if len(messages) > max_history * 2:
trimmed = messages[-max_history * 2:]
else:
trimmed = messages
msg = client.messages.create(
model=model,
max_tokens=1000,
messages=trimmed,
)
return msg.content[0].textTóm tắt bài học
🎯 Claude API stateless — bạn tự quản lý history, gửi toàn bộ qua mỗi request.
🎯 3 helper function đủ cho 95% chatbot: add_user_message, add_assistant_message, chat.
🎯 Role phải alternating user ↔ assistant. Quên append assistant là bug phổ biến #1.
🎯 History dài → cost scale O(n²). Chiến lược: sliding window, summarization, caching.
🎯 Multi-turn = foundation cho mọi bài sau: streaming, tool use, RAG, agents. Không skip.
- Conversation history best practices
- Managing long conversations