Sổ tay kỹ thuật

Lấy JSON có cấu trúc từ Claude (tool use + pydantic)

how-to#claude-api#python#json· cập nhật 2026-11-06

Dùng khi cần Claude trả dữ liệu đúng schema (phân loại alarm, trích xuất từ syslog, parse output show) để code xử lý tiếp, có validate bằng pydantic.

Sửa trang này
Mục lục

Tóm tắt: Định nghĩa model pydantic, rồi lấy Model.model_json_schema() làm input_schema của một tool "giả". Ép model gọi tool đó bằng tool_choice={"type": "tool", "name": ...}, sau đó đọc block.input và kiểm tra lại bằng Model.model_validate(). Validate lỗi thì gửi lỗi về dưới dạng tool_result có is_error: true để model sửa. Cách thứ hai là structured outputs (output_config.format / client.messages.parse). Kiến thức tool use cơ bản xem Claude API tool use.

Luồng hoạt động

flowchart LR A["Pydantic model"] -->|"model_json_schema()"| B["tool.input_schema"] B --> C["messages.create + tool_choice ép tool"] C --> D["block tool_use: input (dict)"] D --> E{"model_validate"} E -->|"OK"| F["Object Python"] E -->|"ValidationError"| G["tool_result is_error"] G --> C

Cấu hình

Cách 1: tool use + pydantic (chạy trên mọi model hỗ trợ tool)

uv add anthropic pydantic
extract.py
from typing import Literal
 
import anthropic
from pydantic import BaseModel, Field, ValidationError
 
client = anthropic.Anthropic()
MODEL = "claude-sonnet-5-5"
 
 
class Interface(BaseModel):
    name: str = Field(description="Tên interface, vd HundredGigE0/0/0/1")
    oper_status: Literal["up", "down"]
    description: str | None = None
 
 
class LinkDownReport(BaseModel):
    device: str = Field(description="Hostname thiết bị")
    severity: Literal["critical", "major", "minor"]
    interfaces: list[Interface]
    summary: str = Field(description="Một câu tóm tắt cho NOC")
 
 
TOOL = {
    "name": "report_link_down",
    "description": "Ghi nhận kết quả phân tích log link down. Luôn gọi tool này để trả kết quả.",
    "input_schema": LinkDownReport.model_json_schema(),
}
 
 
def extract(log_text: str, max_attempts: int = 2) -> LinkDownReport:
    messages = [{"role": "user", "content": f"Phân tích syslog sau:\n\n{log_text}"}]
    for _ in range(max_attempts):
        resp = client.messages.create(
            model=MODEL,
            max_tokens=1024,
            tools=[TOOL],
            tool_choice={"type": "tool", "name": "report_link_down"},  # ép trả qua tool
            messages=messages,
        )
        block = next(b for b in resp.content if b.type == "tool_use")
        try:
            return LinkDownReport.model_validate(block.input)
        except ValidationError as e:
            # trả lỗi validate cho model sửa ở lượt sau
            messages.append({"role": "assistant", "content": resp.content})
            messages.append({"role": "user", "content": [{
                "type": "tool_result", "tool_use_id": block.id, "is_error": True,
                "content": f"JSON không hợp lệ, sửa và gọi lại tool:\n{e}",
            }]})
    raise RuntimeError("Model không trả JSON hợp lệ sau nhiều lần thử")
 
 
if __name__ == "__main__":
    log = "RP/0/RP0/CPU0:Nov  6 10:01:02 pe1-hni ifmgr: Interface HundredGigE0/0/0/1, changed state to Down"
    print(extract(log).model_dump_json(indent=2))

Những điểm cần nhớ:

  • tool_choice kiểu tool bắt buộc model gọi đúng tool đó, nên response không có đoạn văn "Đây là kết quả...". Vì vậy stop_reason sẽ là tool_use.
  • block.input đã là dict, không cần json.loads.
  • Schema là gợi ý mạnh, không phải bảo đảm: vẫn phải model_validate. Lỗi hay gặp là sai enum, thiếu field bắt buộc, hoặc kiểu số trả về dạng chuỗi.
  • Field(description=...) đi vào schema. Mô tả càng cụ thể (đơn vị, ví dụ) thì kết quả càng đúng.

Cách 2: structured outputs (output_config.format)

SDK Python hiện hành (đã kiểm tra anthropic 0.109) có client.messages.parse(). Hàm này nhận class pydantic, tự chuyển thành output_config={"format": {"type": "json_schema", "schema": ...}}, và trả object đã parse trong parsed_output:

resp = client.messages.parse(
    model="claude-sonnet-5-5",
    max_tokens=1024,
    messages=[{"role": "user", "content": f"Phân tích syslog sau:\n\n{log}"}],
    output_format=LinkDownReport,
)
report = resp.parsed_output        # LinkDownReport hoặc None

Ngoài ra có thể đặt "strict": True trong định nghĩa tool để input của tool tuân thủ schema chặt hơn. Hai tính năng này hỗ trợ một tập con của JSON Schema và một số model nhất định. Kiểm tra docs trước khi dùng schema phức tạp ($ref lồng sâu, pattern, minLength...).

Tiêu chí Tool use + validate output_config.format / parse
Tương thích model/SDK cũ Cao Cần model và SDK mới
Bảo đảm đúng schema Không, phải validate và retry Chặt hơn, vẫn nên validate
Kết hợp với tool khác trong agent loop Tự nhiên (thêm một tool "report") Output cuối là JSON, tool khác vẫn gọi bình thường
Hỗ trợ JSON Schema Rộng Một tập con

Lỗi thường gặp

Triệu chứng Nguyên nhân Cách xử lý
StopIteration khi tìm block tool_use Không ép tool_choice, model trả lời bằng text Dùng tool_choice={"type": "tool", "name": ...}
stop_reason == "max_tokens", JSON thiếu field max_tokens nhỏ so với output (list dài) Tăng max_tokens, giới hạn số phần tử trong prompt/schema
ValidationError ở field Literal/enum Model dùng từ đồng nghĩa ("Major", "high") Ghi rõ giá trị hợp lệ trong description, gửi lỗi về để model sửa
400 khi dùng tool_choice ép tool cùng extended thinking Thinking không đi cùng tool_choice kiểu tool/any Tắt thinking cho bước trích xuất, hoặc dùng auto + prompt
400 báo schema không hỗ trợ (structured outputs) Dùng keyword JSON Schema ngoài tập được hỗ trợ Đơn giản hoá schema, chuyển ràng buộc phức tạp sang validator pydantic
Field tự do (summary) bịa thông tin không có trong log Prompt không giới hạn nguồn Yêu cầu "chỉ dựa vào log", thêm field evidence trích nguyên dòng log

Tham khảo