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

6.14 · Structured data — Ép Claude trả JSON/code sạch

Bài viếtTrung cấp20 phút đọc

Bạn build web app tạo AWS EventBridge rule. Flow:

Sau bài này, bạn sẽ
  1. Giải thích vì sao Claude mặc định hay wrap output trong markdown + giải thích
  2. Dùng kỹ thuật assistant prefill + stop sequence để lấy output sạch
  3. Áp dụng pattern cho JSON, Python code, CSV, bulleted list
  4. Parse và validate output structured trong Python
  5. Handle edge case khi output không đúng format

Mở đầu: Khi "helpful" trở thành problem

  1. User gõ "Tạo rule cho EC2 state change"
  2. Nhấn Generate
  3. App hiển thị JSON, user copy-paste

Claude được prompt:

Generate an EventBridge rule as JSON for EC2 state change.

Claude trả về:

Sure! Here's a JSON rule for EC2 state changes:

{ "source": ["aws.ec2"], "detail-type": ["EC2 Instance State-change Notification"] }


This rule captures all state changes for EC2 instances.

User copy toàn bộ → lỗi parse. Họ phải select manual phần giữa. UX tệ.

Vấn đề: Claude được train để helpful — thêm context, explanation, markdown. Tốt cho chat, tệ cho app tạo structured data.

Bài này dạy trick thông minh để Claude output chỉ content thô.

Kỹ thuật: Assistant message prefill

Insight

Claude trong Anthropic API có thể nhận assistant message như "Claude đã bắt đầu viết, bây giờ tiếp tục":

messages = [
    {"role": "user", "content": "Generate EventBridge rule"},
    {"role": "assistant", "content": "```json\n"}  # ← Prefill!
]

Claude "nghĩ" mình đã bắt đầu code block JSON, và tiếp tục viết JSON (không nói chuyện khác).

Kết hợp với stop sequence

Thêm stop sequence để dừng khi Claude thử đóng code block:

msg = client.messages.create(
    model=model,
    max_tokens=1000,
    messages=messages,
    stop_sequences=["```"]
)

Flow:

  1. User: "Generate EventBridge rule"
  2. Assistant (prefill): ` `json\n `
  3. Claude tiếp tục: {... JSON content ...}\n
  4. Claude định đóng ` ` ` → gặp stop sequence, dừng ngay

Output = pure JSON, không preamble không postamble.

Code hoàn chỉnh

def generate_json(user_prompt: str) -> dict:
    """Generate JSON từ user prompt, trả về dict."""
    messages = [
        {"role": "user", "content": user_prompt},
        {"role": "assistant", "content": "```json\n"}
    ]
    
    msg = client.messages.create(
        model=model,
        max_tokens=1000,
        messages=messages,
        stop_sequences=["```"]
    )
    
    raw_text = msg.content[0].text.strip()
    
    import json
    return json.loads(raw_text)


result = generate_json("Generate EventBridge rule for EC2 state change")
print(result)

Áp dụng cho các format khác

Pattern chung: prefill opener → Claude viết content → stop sequence at closer.

Python code

messages = [
    {"role": "user", "content": "Write a Python function to calculate fibonacci"},
    {"role": "assistant", "content": "```python\n"}
]
stop_sequences = ["```"]

Bulleted list (no explanation)

messages = [
    {"role": "user", "content": "List 5 popular JavaScript frameworks"},
    {"role": "assistant", "content": "- "}  # prefill để Claude tiếp tục bullet
]

Output:

React
- Vue
- Angular
- Svelte
- Ember

(Ghi chú: cần re-prepend "- " vào dòng đầu khi parse)

CSV

messages = [
    {"role": "user", "content": "Create CSV of 5 countries with capital, population"},
    {"role": "assistant", "content": "country,capital,population\n"}
]

Claude sẽ tiếp tục viết data rows, không có explanation.

XML

messages = [
    {"role": "user", "content": "Generate product description"},
    {"role": "assistant", "content": "<product>\n  "}
]
stop_sequences = ["</product>"]

Pattern production: Retry + validate

Real-world, cần handle case Claude output không đúng JSON:

import json
from typing import Optional

def generate_json_robust(user_prompt: str, max_retries: int = 2) -> Optional[dict]:
    """Generate JSON với retry khi parse fail."""
    for attempt in range(max_retries + 1):
        messages = [
            {"role": "user", "content": user_prompt},
            {"role": "assistant", "content": "```json\n"}
        ]
        
        msg = client.messages.create(
            model=model,
            max_tokens=1000,
            messages=messages,
            stop_sequences=["```"],
            temperature=0  # ← deterministic cho parsing
        )
        
        raw = msg.content[0].text.strip()
        
        try:
            return json.loads(raw)
        except json.JSONDecodeError as e:
            if attempt == max_retries:
                raise
            # Retry với hint về format
            print(f"Retry {attempt + 1}: JSON parse failed: {e}")
    
    return None

Validation schema

Dùng Pydantic để validate output structure:

from pydantic import BaseModel
from typing import List

class EventBridgeRule(BaseModel):
    source: List[str]
    detail_type: List[str]
    detail: dict = {}

def generate_rule(user_prompt: str) -> EventBridgeRule:
    raw = generate_json_robust(user_prompt)
    return EventBridgeRule(**raw)

rule = generate_rule("EC2 state change")

Ví dụ thực chiến: Extract dữ liệu từ CV

Tình huống

HR manager nhận 200 CV. Cần extract: tên, email, experience, skills → file Excel.

Setup

import json

def extract_cv(cv_text: str) -> dict:
    messages = [
        {"role": "user", "content": f"""Extract structured data from this CV:

<cv>
{cv_text}
</cv>

Return JSON with fields:
- name: string
- email: string
- years_experience: integer
- skills: list of strings
- current_role: string"""},
        {"role": "assistant", "content": "```json\n"}
    ]
    
    msg = client.messages.create(
        model="claude-haiku-4-5",  # Haiku đủ cho task này
        max_tokens=500,
        messages=messages,
        stop_sequences=["```"],
        temperature=0
    )
    
    return json.loads(msg.content[0].text.strip())


cvs = load_cvs_from_folder("cvs/")
results = [extract_cv(cv) for cv in cvs]

import pandas as pd
pd.DataFrame(results).to_excel("cv_data.xlsx")

Kết quả

200 CV extract trong 10 phút thay vì 2 giờ làm tay. Chi phí: ~$0.50 tổng. Output pandas DataFrame sẵn để analyze.

Case studies theo ngành

Developer — API response generator

Tool
Tool cho dev test API. Input: schema mong muốn. Output: mock JSON.
Prefill
` `json\n `. Dev copy-paste ngay vào Postman.

Data — CSV cleaner

Task
User paste CSV bị lỗi quote, prompt Claude fix.
Prefill
header row. Claude tiếp tục với data rows sạch.

Legal — Clause extraction

Task
Trích các điều khoản của contract vào structured format.
Prefill
<clauses>\n<clause>. Claude output XML structured.

Support — Ticket classification

Task
Input ticket → output JSON {category, priority, action}.
Prefill
` `json\n `. Parser tự extract, push vào DB.

Anti-patterns

Quên stop sequence

messages = [
    {"role": "user", "content": "Gen JSON"},
    {"role": "assistant", "content": "```json\n"}
]

Fix: Luôn kèm stop_sequences với chuỗi kết là ba dấu backtick (xem ví dụ dưới).

Prefill quá cụ thể

{"role": "assistant", "content": "```json\n{\"source\": "}
Vấn đề
Ép Claude phải tiếp tục từ "source" — có thể không phù hợp với mọi input.
Fix
Chỉ prefill wrapper (``json\n``), để Claude tự generate structure.

Không handle JSON parse fail

result = json.loads(msg.content[0].text.strip())  # ← crash nếu malformed

Fix: Try-except + retry với hint về format.

Dùng temperature cao cho parsing

temperature=1  # ← random, output không reliable cho parse

Fix: temperature=0 cho tác vụ cần deterministic.

Prefill nhưng assistant thấy mất context

messages = [
    {"role": "user", "content": "Topic A"},
    {"role": "assistant", "content": "Response A"},
    {"role": "user", "content": "Now give JSON"},
    {"role": "assistant", "content": "```json\n"}  ← OK
]

OK. Nhưng nếu prefill không ở cuối → Claude confused.

Fix: Prefill luôn là message cuối cùng.

Áp dụng ngay

Bài tập 1: Generate 3 loại structured data (25 phút)

Trong notebook, tạo 3 function:

def generate_product_json(description: str) -> dict:
    """Return JSON {name, price, features}"""
    # ...

def generate_sql_query(natural_lang: str) -> str:
    """Return clean SQL string"""
    # ...

def generate_csv(topic: str, num_rows: int) -> str:
    """Return CSV string"""
    # ...

Test mỗi function với 3 input. Verify output parse được không lỗi.

Bài tập 2: Retry logic (15 phút)

Mở rộng generate_json_robust để:

  1. Log attempt number + error mỗi lần retry
  2. Sau 2 lần fail, gửi user prompt kèm lỗi JSON (hint cho Claude fix)
  3. Max 3 attempts, raise exception nếu vẫn fail
def generate_json_v2(user_prompt: str, max_retries: int = 3) -> dict:
    # ...

Mẹo nâng cao

Mẹo 1: JSON schema trong prompt

user_prompt = f"""Extract data from: {text}

Return JSON matching this exact schema:
{{
  "name": string,
  "age": integer,
  "emails": [string]
}}"""

Claude follow schema tốt hơn.

Mẹo 2: Structured outputs (future)

Anthropic có thể thêm feature response_format tương tự OpenAI — force schema ở API level. Check docs cập nhật.

Mẹo 3: Dùng tool_use cho structured output

Thay vì prefill, define 1 tool với schema. Claude buộc phải call tool với args theo schema. Output 100% đúng format. Chi tiết ở Module 5.

Tóm tắt bài học

Những điều cốt lõi
  • Claude mặc định wrap output trong markdown + explanation — tốt cho chat, tệ cho app.
  • Prefill + stop sequence là pattern chuẩn để lấy raw structured output.
  • Áp dụng cho JSON, code, CSV, XML — chỉ đổi prefill content và stop sequence.
  • Production phải có validation + retry + temp=0 để reliable.
  • Tool use là pattern stronger cho structured output (Module 5).

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.