Structured output: lấy dữ liệu đúng khuôn từ LLM

Bạn cần biến tin nhắn đặt hàng của khách thành dữ liệu để lưu vào database. Dặn mô hình “hãy trả lời bằng JSON” thường vẫn chạy được, cho đến hôm nó chèn thêm một câu chào và json.loads() báo lỗi. Structured output giới hạn câu trả lời theo đúng một lược đồ (schema) cho trước, nên lần nào kết quả cũng parse được.

Định nghĩa khuôn bằng Pydantic

Nếu chưa quen Pydantic, xem bài Pydantic: kiểm tra và chuẩn hóa dữ liệu trước.

import anthropic
from pydantic import BaseModel, Field

class MatHang(BaseModel):
    ten: str
    so_luong: int
    don_gia: int = Field(description="Giá một đơn vị, tính bằng đồng")

class DonHang(BaseModel):
    ten_khach: str
    so_dien_thoai: str | None
    dia_chi_giao: str
    mat_hang: list[MatHang]

description trong Field được gửi kèm schema, nên mô hình hiểu “150k” phải đổi thành 150000. Trường không chắc có trong tin nhắn thì để None được, đỡ ép mô hình phải bịa ra.

Gọi messages.parse()

client = anthropic.Anthropic()

def trich_don_hang(tin_nhan: str) -> DonHang:
    ph = client.messages.parse(
        model="claude-opus-5-5",
        max_tokens=4096,
        system="Trích xuất đơn hàng từ tin nhắn của khách. Quy mọi giá về đồng.",
        messages=[{"role": "user", "content": tin_nhan}],
        output_format=DonHang,
    )
    if ph.parsed_output is None:
        raise ValueError(f"Không trích xuất được, stop_reason={ph.stop_reason}")
    return ph.parsed_output

if __name__ == "__main__":
    don = trich_don_hang("""Chào shop, mình là Lan, lấy giúp 2 áo thun trắng giá 150k
với 1 quần jean 420k nhé. Giao về 12 Trần Phú, Hà Đông. Sđt 0912 345 678.""")
    tong = sum(m.so_luong * m.don_gia for m in don.mat_hang)
    print(don.ten_khach, don.so_dien_thoai, f"{tong:,} đồng")

Hai đoạn code trên ghép lại thành tệp don_hang.py, sẽ dùng lại ở bài kiểm thử.

parsed_output là một đối tượng DonHang đã được kiểm tra kiểu. Bạn dùng thẳng don.mat_hang[0].don_gia, không cần đụng tới chuỗi JSON. Vẫn nên kiểm tra None: câu trả lời có thể bị cắt khi chạm max_tokens.

Không dùng Pydantic: truyền schema trực tiếp

import json

ph = client.messages.create(
    model="claude-opus-5-5",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Đánh giá: 'Giao hàng chậm nhưng áo rất đẹp'"}],
    output_config={"format": {"type": "json_schema", "schema": {
        "type": "object",
        "properties": {
            "cam_xuc": {"type": "string", "enum": ["tich_cuc", "trung_tinh", "tieu_cuc"]},
            "ly_do": {"type": "string"},
        },
        "required": ["cam_xuc", "ly_do"],
        "additionalProperties": False,
    }}},
)
ket_qua = json.loads(next(k.text for k in ph.content if k.type == "text"))

Dùng enum cho bài toán phân loại là cách gọn nhất: mô hình chỉ được chọn một trong các nhãn bạn cho phép. Schema viết tay phải có "additionalProperties": False.

Đúng khuôn chưa chắc đã đúng nội dung

  • Schema đảm bảo so_luong là số nguyên, nhưng không đảm bảo con số đó đúng. Vẫn cần kiểm tra theo quy tắc nghiệp vụ, ví dụ số điện thoại có đủ 10 số không, tổng tiền có hợp lý không.
  • Giữ schema nhỏ và rõ. Mỗi trường nên mang một ý, đặt tên dễ hiểu, kèm mô tả khi đơn vị hoặc định dạng dễ gây nhầm.
  • Bài sau về kiểm thử sẽ dùng chính hàm trích xuất này để đo xem prompt có làm tốt hay không.
Lên đầu trang