Lộ trình học›Claude API›Building with the Claude API Mở bản đồ

6.8 · Bài tập — Xây chatbot đầu tiên của bạn

Bài viếtCơ bản → Trung c53 phút đọc

Bạn đã học 7 bài đầu tiên. Kiến thức chắc về:

Sau bài này, bạn sẽ
  1. Ship một chatbot hoàn chỉnh có thể chạy end-to-end
  2. Áp dụng pattern multi-turn từ bài 6.7 vào use case thật
  3. Debug các lỗi phổ biến khi chạy chatbot (role alternating, token limit)
  4. Đo performance của chatbot: latency, token usage, cost

Mở đầu: Học bằng làm, không học bằng đọc

  • Cách gọi API (bài 6.6)
  • Multi-turn conversation (bài 6.7)
  • Helper functions chuẩn

Bài này là milestone. Bạn không đọc thêm lý thuyết — bạn ship 1 thứ chạy được. Đây là lần đầu tiên trong khóa bạn tạo ra một sản phẩm nhỏ từ kiến thức đã học.

Đề bài

Xây một chatbot "Travel Assistant" trong Jupyter notebook. Yêu cầu:

Tính năng bắt buộc

  1. Multi-turnnhớ context xuyên suốt phiên chat
  2. Interactiveuser gõ input, Claude trả lời, lặp lại đến khi user gõ "quit"
  3. Giới hạn scopeChatbot 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ự.
  4. LogIn token usage mỗi turn để theo dõi cost

Tính năng optional (điểm cộng)

  • 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

from dotenv import load_dotenv
load_dotenv()
from anthropic import Anthropic

client = Anthropic()
model = "claude-sonnet-4-6-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

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].text

Bước 3: Viết hàm main loop

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 4.6)
    cost = (total_input_tokens * 3 + total_output_tokens * 15) / 1_000_000
    print(f"Estimated cost: ${cost:.4f}")

Bước 4: Chạy

travel_chatbot()

Bước 5: Test flow

Thử các case:

  1. "Tôi muốn đi Đà Nẵng 3 ngày, có gợi ý gì?"
  2. "Nên ăn gì ở đó?" (test memory)
  3. "Ngân sách khoảng 5 triệu, đủ không?" (test context)
  4. "Giúp tôi viết Python function" (test scope limit)
  5. "Quit"

Phiên bản nâng cao (optional)

Sliding window

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].text

Validation

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 None

Case 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:

import os
print("Key loaded:", bool(os.getenv("ANTHROPIC_API_KEY")))

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.

Self-review checklist

Trước khi consider "done", check:

Functional

  • 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

Code quality

  • 3 helper function tách biệt
  • Có exception handling
  • In token usage mỗi turn
  • Thống kê cuối session

Edge cases

  • 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

Những điều cốt lõi
  • Đâ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.

Tài liệu tham khảo

Bạn đã hoàn thành bài này chưa?Tiến độ lưu trên trình duyệt của bạn.