Bạn build web app tạo AWS EventBridge rule. Flow:
- Giải thích vì sao Claude mặc định hay wrap output trong markdown + giải thích
- Dùng kỹ thuật assistant prefill + stop sequence để lấy output sạch
- Áp dụng pattern cho JSON, Python code, CSV, bulleted list
- Parse và validate output structured trong Python
- Handle edge case khi output không đúng format
Mở đầu: Khi "helpful" trở thành problem
- User gõ "Tạo rule cho EC2 state change"
- Nhấn Generate
- 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:
- User: "Generate EventBridge rule"
- Assistant (prefill): `
`json\n` - Claude tiếp tục:
{... JSON content ...}\n - 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 để:
- Log attempt number + error mỗi lần retry
- Sau 2 lần fail, gửi user prompt kèm lỗi JSON (hint cho Claude fix)
- 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
- 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).