Bạn đã học 7 bài đầu tiên. Kiến thức chắc về: - Cách gọi API (bài 6.6) - Multi-turn conversation (bài 6.7) - Helper functions chuẩn
- Ship một chatbot hoàn chỉnh có thể chạy end-to-end
- Áp dụng pattern multi-turn từ bài 6.7 vào use case thật
- Debug các lỗi phổ biến khi chạy chatbot (role alternating, token limit)
- Đo performance của chatbot: latency, token usage, cost
Đề bài
Xây một chatbot "Travel Assistant" trong Jupyter notebook. Yêu cầu:
Tính năng bắt buộc
Tính năng optional (điểm cộng)
- Multi-turn: nhớ context xuyên suốt phiên chat
- Interactive: user gõ input, Claude trả lời, lặp lại đến khi user gõ "quit"
- Giới hạn scope: Chatbot chỉ trả lời về du lịch (xin gợi ý địa điểm, lịch trình, ẩm thực). Nếu user hỏi off-topic, Claude phải từ chối lịch sự.
- Log: In token usage mỗi turn để theo dõi cost
- Sliding window history (giữ 10 turn gần nhất)
- Xử lý edge case: input rỗng, stop_reason=max_tokens
- Thống kê cuối session: tổng token, số turn, cost ước tính
Hướng dẫn step-by-step
Bước 1: Setup boilerplate
Bước 2: Viết hàm chat với logging
from dotenv import load_dotenv
load_dotenv()
from anthropic import Anthropic
client = Anthropic()
model = "claude-sonnet-5-20260205"
def add_user_message(messages, text):
messages.append({"role": "user", "content": text})
def add_assistant_message(messages, text):
messages.append({"role": "assistant", "content": text})Bước 2: Viết hàm chat với logging
Bước 3: Viết hàm main loop
total_input_tokens = 0
total_output_tokens = 0
def chat(messages):
global total_input_tokens, total_output_tokens
msg = client.messages.create(
model=model,
max_tokens=1000,
messages=messages,
)
# Log token
total_input_tokens += msg.usage.input_tokens
total_output_tokens += msg.usage.output_tokens
print(f" [tokens: in={msg.usage.input_tokens}, out={msg.usage.output_tokens}]")
# Check truncation
if msg.stop_reason == "max_tokens":
print(" ⚠️ Response bị cắt! Cần tăng max_tokens.")
return msg.content[0].textBước 3: Viết hàm main loop
Bước 4: Chạy
def travel_chatbot():
messages = []
# Giới thiệu scope qua prompt đầu
intro = """Bạn là Travel Assistant. Chỉ trả lời câu hỏi về du lịch:
- Gợi ý địa điểm
- Lập lịch trình
- Gợi ý ẩm thực địa phương
- Mẹo du lịch
Nếu user hỏi chủ đề khác (code, finance, relationship...),
hãy lịch sự từ chối và hướng về chủ đề du lịch."""
# Cách "hack": đưa intro vào user message đầu + assistant ack
add_user_message(messages, intro)
add_assistant_message(messages,
"Chào bạn! Tôi là Travel Assistant. Hãy hỏi tôi về du lịch — điểm đến, lịch trình, ẩm thực. Bạn cần tư vấn chuyến đi nào?")
print("🧭 Travel Assistant đã sẵn sàng. Gõ 'quit' để thoát.\n")
print(messages[-1]["content"])
print()
while True:
user_input = input("You: ").strip()
if user_input.lower() in ("quit", "exit", "bye"):
break
if not user_input:
print(" (input rỗng, skip)")
continue
add_user_message(messages, user_input)
try:
answer = chat(messages)
add_assistant_message(messages, answer)
print(f"\nTravel Assistant: {answer}\n")
except Exception as e:
print(f" ❌ Lỗi: {e}")
# Remove user message cuối để giữ history hợp lệ
messages.pop()
# Thống kê cuối
print("\n" + "=" * 50)
print(f"Session ended.")
print(f"Total turns: {(len(messages) - 2) // 2}") # trừ 2 intro message
print(f"Total tokens: input={total_input_tokens}, output={total_output_tokens}")
# Cost estimate (Sonnet 5)
cost = (total_input_tokens * 3 + total_output_tokens * 15) / 1_000_000
print(f"Estimated cost: ${cost:.4f}")Bước 4: Chạy
Bước 5: Test flow
Thử các case:
- ✅ "Tôi muốn đi Đà Nẵng 3 ngày, có gợi ý gì?"
- ✅ "Nên ăn gì ở đó?" (test memory)
- ✅ "Ngân sách khoảng 5 triệu, đủ không?" (test context)
- ❌ "Giúp tôi viết Python function" (test scope limit)
- ✅ "Quit"
travel_chatbot()Phiên bản nâng cao (optional)
Sliding window
Validation
MAX_HISTORY = 20 # 10 turn × 2 messages
def chat_with_window(messages):
# Giữ 2 message intro + MAX_HISTORY messages gần nhất
if len(messages) > MAX_HISTORY + 2:
trimmed = messages[:2] + messages[-MAX_HISTORY:]
else:
trimmed = messages
msg = client.messages.create(
model=model,
max_tokens=1000,
messages=trimmed,
)
return msg.content[0].textValidation
Retry logic
def validate_messages(messages):
"""Check role alternating + first=user."""
if not messages:
return
assert messages[0]["role"] == "user", "First message must be user"
for i in range(1, len(messages)):
assert messages[i]["role"] != messages[i-1]["role"], \
f"Role not alternating at index {i}"Retry logic
import time
from anthropic import RateLimitError, APIError
def chat_with_retry(messages, max_retries=3):
for attempt in range(max_retries):
try:
return chat(messages)
except RateLimitError:
wait = 2 ** attempt # exponential backoff
print(f" Rate limited. Retry in {wait}s...")
time.sleep(wait)
except APIError as e:
if attempt == max_retries - 1:
raise
print(f" API error: {e}. Retrying...")
return NoneCase studies tương tự bạn có thể thử sau
📚 Education — Study buddy
Same pattern nhưng scope "giải thích khái niệm học thuật". Tương tác như tutor ở bài 6.7.
💼 Sales — Discovery prep
Scope: gợi ý câu hỏi discovery. Input: thông tin prospect. Output: 10 câu hỏi.
🍳 Cooking assistant
Scope: công thức nấu ăn, thay thế nguyên liệu. "Tôi có gì trong tủ lạnh → món gì?"
💪 Fitness coach
Scope: lập plan tập. Track progress qua history.
Mỗi use case đều dùng chính pattern bạn vừa học — chỉ khác intro prompt và scope.
Debug guide — Khi gặp lỗi
Lỗi 1: BadRequestError: messages: first message must use "user" role
Nguyên nhân: Có thể bạn append assistant trước user.
Fix: Kiểm tra messages[0]["role"].
Lỗi 2: BadRequestError: messages: all messages must have non-empty content
Nguyên nhân: User gõ empty string (enter không có text).
Fix: Validate user_input.strip() trước khi append.
Lỗi 3: AuthenticationError
Nguyên nhân: API key không load.
Fix:
Lỗi 4: Claude "quên" mặc dù có history
Nguyên nhân: Quên append assistant message sau mỗi turn.
Fix: Cẩn thận với flow: add_user → chat → add_assistant.
Lỗi 5: Response bị cắt
Nguyên nhân: max_tokens quá thấp.
Fix: Tăng lên 2000-4000. Check stop_reason.
import os
print("Key loaded:", bool(os.getenv("ANTHROPIC_API_KEY")))Self-review checklist
Trước khi consider "done", check:
Functional
Code quality
Edge cases
- [ ] Chatbot trả lời đúng topic (du lịch)
- [ ] Từ chối off-topic lịch sự
- [ ] Nhớ context qua nhiều turn
- [ ] Quit command hoạt động
- [ ] 3 helper function tách biệt
- [ ] Có exception handling
- [ ] In token usage mỗi turn
- [ ] Thống kê cuối session
- [ ] Empty input không crash
- [ ] Long response không bị cắt giữa chừng
- [ ] Có thể chat 20+ turn mà không lỗi
Tóm tắt bài học
🎯 Đây là milestone đầu tiên — bạn đã ship 1 chatbot production-ready ở mức sơ khai.
🎯 Pattern bạn viết có thể adapt cho mọi use case chatbot: thay intro, thay scope.
🎯 Logging token là thói quen cần duy trì — không có log = không biết cost.
🎯 Validation + retry là 2 layer bảo vệ mà hầu hết app production cần.
🎯 Sliding window đủ cho 80% chatbot casual. Summarization cần cho chatbot dài.
- anthropic-cookbook/basic_chat — nhiều ví dụ chatbot
- Rate limit handling