Bối cảnh & Lý do: Chấm dứt ác mộng Regex
Tôi nhớ lần đầu tiên thử xây dựng một pipeline trích xuất dữ liệu tự động bằng GPT-3.5. Tôi đã yêu cầu mô hình trả về danh sách người dùng dưới định dạng JSON. Nó hoạt động tốt trong 90% trường hợp. Nhưng 10% còn lại? Đó là một thảm họa. Thỉnh thoảng mô hình sẽ thêm tiền tố “Chắc chắn rồi, đây là dữ liệu của bạn:” hoặc bọc JSON trong các khối code markdown mà trình phân tách (parser) của tôi không lường trước được. Log hệ thống của tôi tràn ngập lỗi JSONDecodeError.
Nếu bạn đang xây dựng một AI pipeline nơi đầu ra của một LLM đóng vai trò là đầu vào cho một dịch vụ khác hoặc cơ sở dữ liệu, bạn không thể chấp nhận kiểu định dạng “ngẫu hứng”. Bạn cần dữ liệu mang tính xác định (deterministic) và tuân thủ nghiêm ngặt schema. Theo kinh nghiệm thực tế của tôi, đây là một trong những kỹ năng thiết yếu cần nắm vững nếu bạn muốn chuyển từ một bản thử nghiệm sang một hệ thống agentic sẵn sàng cho production.
Cả OpenAI và Anthropic đều đã giới thiệu các tính năng để giải quyết vấn đề này. OpenAI có “Structured Outputs” (JSON Schema), và Claude sử dụng “Tool Use” (Function Calling) để đạt được kết quả tương tự. Việc chuyển đổi từ thao tác xử lý chuỗi (string manipulation) sang các đối tượng có cấu trúc là điều tạo nên sự khác biệt giữa một dự án sở thích và một ứng dụng doanh nghiệp bền bỉ.
Cài đặt: Thiết lập môi trường
Trước khi xem xét cấu hình, chúng ta cần các thư viện phù hợp. Tôi ưu tiên sử dụng các SDK chính thức thay vì các yêu cầu HTTP thô vì chúng xử lý các mã nguồn mẫu (boilerplate) cho header và logic thử lại (retry) hiệu quả hơn.
Tạo một môi trường ảo mới và cài đặt các gói cần thiết:
bash
pip install openai anthropic pydantic python-dotenv
Tôi cũng khuyên bạn nên sử dụng Pydantic. Đây là tiêu chuẩn công nghiệp để xác thực dữ liệu trong Python. Bằng cách định nghĩa đầu ra mong đợi dưới dạng một model Pydantic, chúng ta có thể thu hẹp khoảng cách giữa phản hồi thô của LLM và cấu trúc dữ liệu của ứng dụng.
Tạo một tệp .env trong thư mục gốc của dự án để lưu trữ các API key một cách an toàn:
OPENAI_API_KEY=your_openai_key_here
ANTHROPIC_API_KEY=your_anthropic_key_here
Cấu hình: Triển khai Structured Outputs
Chi tiết triển khai có chút khác biệt giữa OpenAI và Claude. OpenAI cho phép thực thi schema nghiêm ngặt, trong khi Claude dựa trên khả năng tuân thủ định nghĩa công cụ (tool definition) tuyệt vời của mình.
1. OpenAI Structured Outputs (JSON Schema)
OpenAI gần đây đã giới thiệu chế độ strict cho các JSON schema. Khi được kích hoạt, mô hình được đảm bảo sẽ tuân thủ chính xác schema. Điều này đạt được bằng cách ràng buộc quá trình lấy mẫu (sampling) ở cấp độ token.
python
import os
from openai import OpenAI
from pydantic import BaseModel
from dotenv import load_dotenv
load_dotenv()
client = OpenAI()
# Định nghĩa cấu trúc bạn muốn
class InventoryUpdate(BaseModel):
item_name: str
quantity: int
category: str
tags: list[str]
# Gọi API
response = client.beta.chat.completions.parse(
model="gpt-4o-2024-08-06",
messages=[
{"role": "system", "content": "Trích xuất chi tiết kho hàng từ văn bản của người dùng."},
{"role": "user", "content": "Chúng tôi vừa nhận được 50 đơn vị dây đồng cao cấp cho bộ phận điện. Hãy gắn thẻ là 'khẩn cấp' và 'công nghiệp'."}
],
response_format=InventoryUpdate, # Đây là dòng lệnh quan trọng
)
# Truy cập dữ liệu dưới dạng đối tượng Python
structured_data = response.choices[0].message.parsed
print(f"Sản phẩm: {structured_data.item_name}, Số lượng: {structured_data.quantity}")
Lưu ý phương thức .parse(). Nó tự động xử lý việc chuyển đổi từ chuỗi JSON sang đối tượng Pydantic. Nếu mô hình không tuân thủ schema (điều gần như không thể xảy ra ở chế độ strict), nó sẽ ném ra lỗi xác thực trong quá trình gọi.
2. Claude (Anthropic) Tool Use cho dữ liệu có cấu trúc
Anthropic không có cờ “JSON Mode” cụ thể như OpenAI, nhưng việc triển khai “Tool Use” của họ vô cùng mạnh mẽ. Bằng cách định nghĩa một công cụ và buộc mô hình phải sử dụng nó (sử dụng tool_choice), chúng ta có thể trích xuất dữ liệu có cấu trúc hoàn hảo.
python
import anthropic
client = anthropic.Anthropic()
# Định nghĩa công cụ (đóng vai trò là schema của chúng ta)
tools = [
{
"name": "record_inventory",
"description": "Ghi lại các cập nhật kho hàng vào cơ sở dữ liệu.",
"input_schema": {
"type": "object",
"properties": {
"item_name": {"type": "string"},
"quantity": {"type": "integer"},
"category": {"type": "string"},
"tags": {"type": "array", "items": {"type": "string"}}
},
"required": ["item_name", "quantity", "category", "tags"]
}
}
]
message = client.messages.create(
model="claude-3-5-sonnet-20240620",
max_tokens=1024,
tools=tools,
tool_choice={"type": "tool", "name": "record_inventory"}, # Buộc Claude phải sử dụng công cụ
messages=[{"role": "user", "content": "Thêm 10 tấm pin mặt trời vào phần năng lượng. Gắn nhãn là 'năng lượng tái tạo'."}]
)
# Trích xuất đầu vào của tool use
for content in message.content:
if content.type == 'tool_use':
print(content.input)
Bằng cách sử dụng tool_choice, Claude thậm chí sẽ không cố gắng trò chuyện với bạn; nó sẽ nhảy thẳng vào việc tạo các đối số JSON cho hàm mà bạn đã định nghĩa.
Xác thực & Giám sát: Đảm bảo tính ổn định của Pipeline
Ngay cả với những tính năng gốc này, mọi thứ vẫn có thể sai sót. Timeout mạng, giới hạn tốc độ (rate limit) hoặc tràn cửa sổ ngữ cảnh (context window) vẫn có thể làm hỏng pipeline của bạn. Tôi tuân theo quy trình xác thực ba bước cho mọi dịch vụ AI production mà tôi quản lý.
1. Xác thực Schema (Lưới bảo hiểm)
Đừng bao giờ tin tưởng mù quáng vào đầu ra của API. Luôn chuyển kết quả qua một trình xác thực Pydantic. Nếu bạn đang sử dụng .parse() của OpenAI, tính năng này đã được tích hợp sẵn. Đối với Claude hoặc các chế độ JSON tiêu chuẩn, bạn nên thực hiện thủ công:
python
try:
# Giả sử 'data' là dictionary từ API
validated_obj = InventoryUpdate(**data)
except Exception as e:
# Ghi lại log thất bại và có thể kích hoạt thử lại với temperature thấp hơn
print(f"Xác thực thất bại: {e}")
2. Xử lý đầu ra bị cắt xén
Một vấn đề phổ biến là finish_reason. Nếu mô hình dừng lại vì đạt giới hạn max_tokens, JSON của bạn sẽ không đầy đủ và không hợp lệ. Tôi luôn kiểm tra lý do dừng trước khi cố gắng phân tách.
- OpenAI: Kiểm tra
choice.finish_reason == "stop". Nếu là"length", JSON của bạn đã bị cắt ngang. - Claude: Kiểm tra
message.stop_reason == "end_turn"hoặc"tool_use".
3. Giám sát chi phí và độ trễ
Đầu ra có cấu trúc thường yêu cầu nhiều token hơn một chút vì mô hình có thể cần lặp lại các key và tuân thủ định dạng cụ thể. Tôi sử dụng middleware hoặc các decorator đơn giản để log lượng token sử dụng cho mỗi yêu cầu. Nếu bạn nhận thấy lượng token tăng đột biến cho các tác vụ trích xuất đơn giản, schema của bạn có thể quá phức tạp, hoặc bạn đang cung cấp quá nhiều ví dụ trong system prompt.
Theo kinh nghiệm của tôi, sự kết hợp giữa Structured Output của GPT-4o cho việc nhập dữ liệu nghiêm ngặt và Tool Use của Claude 3.5 Sonnet cho các suy luận phức tạp cung cấp nền tảng ổn định nhất cho các AI agent hiện đại. Bằng cách buộc các mô hình giao tiếp bằng JSON, chúng ta ngừng coi chúng như những chatbot và bắt đầu coi chúng như những công cụ tính toán mạnh mẽ thực sự.

