Mục lục
- Mục tiêu bài học
- Output format là gì — control cấu trúc trả lời
- Vì sao cần specify format
- Các format phổ biến
- Specify format ngay trong prompt
- JSON output — mô tả schema bằng văn bản
- JSON Mode — OpenAI
- Structured Output — OpenAI 2024+
- Anthropic — prefill `{` để force JSON
- Tool / Function calling như đường thay thế
- XML output — phong cách Anthropic
- Few-shot example cho format
- Constraint cụ thể về output
- "Sure, here is..." — preamble phá parser
- Markdown table và CSV
- Schema-first approach
- Pydantic + LLM
- Instructor library
- LangChain output parsers
- Failure mode thường gặp
- Robust parsing
- Khi không định trước format
- Format và streaming
- Cost — JSON tốn token hơn plain text
- Code Python tổng hợp
- Bài tập
Mục tiêu bài học
Sau bài này, bạn cần trả lời được:
- Vì sao app gọi LLM gần như luôn phải specify format, không thể để model tự chọn.
- Các format phổ biến và khi nào chọn cái nào (JSON cho data, XML cho Anthropic, table cho hiển thị, plain text cho streaming).
- Cơ chế JSON Mode và Structured Output của OpenAI khác nhau ở chỗ nào.
- Anthropic không có JSON mode native — workaround bằng prefill `{` hoặc tool use.
- Pydantic + Instructor là pattern chuẩn 2024-2026 để bind schema vào LLM call và auto-retry khi parse fail.
- Failure mode hay gặp (markdown wrapper, trailing comma, hallucinated field) và cách viết parser chịu lỗi.
Bài 23 đã nói cách viết task description (yêu cầu LLM "làm gì"); bài này tiếp tục với phần "trả về như thế nào". Bài 29 (Structured Output) sẽ deep-dive Pydantic schema, response_format JSON Schema và validate workflow.
Output format là gì — control cấu trúc trả lời
Output format là phần prompt nói rõ response phải trông như thế nào: là một câu, một list, một JSON object, một bảng Markdown, một XML block, hay code Python. Không có hướng dẫn, LLM thường trả prose tự nhiên kèm preamble ("Sure, here is..."), thậm chí thêm chú thích Markdown, footnote — phá hầu hết parser.
Một prompt thường có ba phần (xem bài 22, 23):
- Context: background, data input.
- Task: yêu cầu cụ thể.
- Output format: cấu trúc trả về.
Trong app production, output format gần như luôn xuất hiện. Chatbot UI thuần text có thể bỏ qua, nhưng bất cứ khi nào downstream cần parse — database insert, function call, UI render component — format phải được pin chặt.
Vì sao cần specify format
Ba lý do chính:
- Downstream parse: app gọi
json.loadstrên response. Nếu model trả "Đây là JSON bạn cần:{...}", parser raise ngayJSONDecodeError. - Consistency cho dữ liệu: cùng một câu hỏi, gọi 1000 lần — nếu mỗi lần model viết theo phong cách khác, không thể aggregate. Specify format đảm bảo schema giống nhau giữa các call.
- Cắt phần thừa: không có ràng buộc, model có xu hướng "lảm nhảm" mở đầu, thêm disclaimer, footnote. Format chặt buộc model đi thẳng vào kết quả.
Ví dụ thực tế: app extract tên + email + công ty từ một đoạn email. Không có format, response có thể là "Người gửi tên là Alice, email [email protected], công ty Acme Corp." — phải dùng regex parse. Với format JSON, response là {"name": "Alice", "email": "[email protected]", "company": "Acme Corp"} — một dòng json.loads.
Các format phổ biến
Bảng so sánh nhanh:
Format Use case chính Parse khó/dễ Token cost
─────────────────────────────────────────────────────────────────────────────
Plain text Chat, summary, free-form Khó (regex) Thấp
List Bullet point hiển thị cho user TB Thấp
Numbered list Step-by-step, ordered TB Thấp
Markdown table Hiển thị dạng bảng cho người đọc TB TB
JSON Data structure cho downstream app Dễ Cao
XML Anthropic prefer, dễ nested Dễ Cao
CSV Bulk export, spreadsheet Dễ Thấp
YAML Config, readable structure TB TB
Code block Code generation, command Dễ (fenced) TB
Lựa chọn format phụ thuộc downstream:
- App backend cần object → JSON.
- Pipeline Anthropic-heavy, cần nested rõ ràng → XML.
- Bulk data, hàng triệu row → CSV.
- Người đọc trực tiếp (chatbot UI) → Markdown (table, list).
- Streaming UI → plain text hoặc Markdown.
Specify format ngay trong prompt
Cách đơn giản nhất: viết yêu cầu format bằng câu thường, đặt cuối prompt (phần cuối ít bị "lost in the middle").
Trả lời dưới dạng JSON với keys: name, age, city.
─────────────────────────────────────────────────
Output as Markdown table với 3 cột: Tên, Tuổi, Thành phố.
─────────────────────────────────────────────────
List 5 bullet point, mỗi point ≤ 20 từ.
Vài quy tắc đã tổng kết từ thực nghiệm cộng đồng (Anthropic / OpenAI cookbooks):
- Đặt yêu cầu format ở cuối prompt; model có xu hướng theo chỉ thị cuối hơn chỉ thị giữa.
- Liệt kê tên field rõ ràng, không nói chung chung "trả về thông tin liên quan".
- Nói rõ "ONLY" / "CHỈ" khi cần raw output: "Output ONLY the JSON, no preamble, no markdown wrapper".
- Cung cấp một ví dụ ngắn (few-shot) khi schema lạ — sẽ nói rõ ở bước 12.
JSON output — mô tả schema bằng văn bản
Pattern cơ bản nhất, hoạt động với mọi model (kể cả SLM local):
Trả lời CHỈ một JSON object, không thêm preamble, không markdown,
với cấu trúc chính xác sau:
{
"summary": "tóm tắt ngắn ≤ 50 từ",
"key_points": ["point 1", "point 2", "point 3"],
"sentiment": "positive | negative | neutral"
}
Hai chi tiết quan trọng:
- Viết literal cấu trúc JSON kèm comment ngắn cho từng field — model hiểu rõ hơn là mô tả prose ("một field tên summary kiểu string...").
- Liệt kê các giá trị hợp lệ cho enum (
"positive | negative | neutral") — giảm hallucinated value ("slightly negative", "mixed"...).
Pattern này không guarantee — model có thể vẫn chèn ```json ... ``` wrapper. Vì vậy parser phía client phải robust (xem bước 21).
JSON Mode — OpenAI
OpenAI ra mắt JSON Mode tại DevDay 2023 (model gpt-4-1106-preview trở đi). Khi bật, API guarantee output là valid JSON:
from openai import OpenAI
client = OpenAI()
resp = client.chat.completions.create(
model="gpt-4o-mini",
response_format={"type": "json_object"},
messages=[
{"role": "system",
"content": "Bạn trả lời CHỈ JSON với keys: summary, sentiment."},
{"role": "user", "content": "Sản phẩm này rất tốt, giao hàng nhanh."},
],
)
print(resp.choices[0].message.content)
# {"summary": "Sản phẩm tốt, giao nhanh", "sentiment": "positive"}
Lưu ý hai điểm:
- JSON Mode chỉ đảm bảo cú pháp hợp lệ — không đảm bảo schema. Field có thể thiếu, type có thể sai. Vẫn cần Pydantic validate.
- Bắt buộc nhắc "JSON" trong system hoặc user message — nếu không, API trả lỗi.
Structured Output — OpenAI 2024+
Tháng 08/2024 OpenAI ra mắt Structured Output: thay vì chỉ guarantee JSON valid, API guarantee output match JSON Schema cụ thể.
from openai import OpenAI
from pydantic import BaseModel
class Review(BaseModel):
summary: str
sentiment: str # "positive" | "negative" | "neutral"
client = OpenAI()
resp = client.beta.chat.completions.parse(
model="gpt-4o-2024-08-06",
response_format=Review,
messages=[
{"role": "user", "content": "Sản phẩm tốt, giao hàng nhanh."},
],
)
review: Review = resp.choices[0].message.parsed
print(review.sentiment) # positive
Cơ chế: OpenAI compile JSON Schema thành constrained decoding phía server — chỉ những token thoả schema mới được sample. Output 100% valid theo schema, kể cả nested object, enum, array length.
Giới hạn (theo doc OpenAI 2024-2026):
- Chỉ subset của JSON Schema (no
oneOfphức tạp, no recursive schema sâu > 5 cấp). - Lần đầu dùng schema mới có latency cao hơn (compile time). Cache phía OpenAI khi tái sử dụng.
- Available cho
gpt-4o,gpt-4o-mini,o1,o3và một số fine-tuned variant.
Bài 29 sẽ deep-dive: cách viết Pydantic model phức tạp, enum, optional field, validate response, retry pattern khi model "từ chối" (refuse).
Anthropic — prefill `{` để force JSON
Đến cuối 2025, Anthropic chưa có JSON mode native như OpenAI. Cách chính thức (Anthropic cookbook) để force JSON là prefill assistant message: bắt đầu reply phía model bằng ký tự {.
import anthropic
import json
client = anthropic.Anthropic()
resp = client.messages.create(
model="claude-opus-4",
max_tokens=512,
messages=[
{"role": "user", "content":
"Phân loại sentiment đoạn này: 'Sản phẩm tốt, giao nhanh.'\n"
"Trả lời JSON với keys: summary, sentiment."},
{"role": "assistant", "content": "{"}, # prefill
],
)
raw = "{" + resp.content[0].text # phải nối lại ký tự đã prefill
data = json.loads(raw)
print(data["sentiment"])
Cơ chế: Claude tiếp tục sinh từ token đã có. Khi prefix là {, xác suất token kế tiếp là " (mở key) cực cao — model thực tế luôn tiếp tục với JSON hợp lệ. Stop sequence có thể đặt là }\n\n để cắt phần thừa sau JSON.
Hai workaround khác phổ biến:
- Tool use: định nghĩa một "fake tool" với JSON Schema và force
tool_choice. Model fill tool arguments theo schema — đây thực ra là cách Anthropic khuyến nghị cho structured output (xem bước 10). - Regex / Pydantic post-process: cho model trả về tự do, parse bằng regex hoặc gọi
Result.model_validate_json, retry nếu fail.
Tool / Function calling như đường thay thế
Cả OpenAI và Anthropic đều có tool / function calling: app định nghĩa tool kèm JSON Schema cho arguments, model phải fill arguments theo schema. Output structured tự động — không cần parser thủ công.
Pattern: nếu chỉ cần extract data có cấu trúc (không thực thi action), define một "fake tool" và force model dùng nó.
tools = [{
"name": "save_review",
"description": "Lưu kết quả phân tích review",
"input_schema": {
"type": "object",
"properties": {
"summary": {"type": "string"},
"sentiment": {"type": "string",
"enum": ["positive", "negative", "neutral"]},
},
"required": ["summary", "sentiment"],
},
}]
resp = client.messages.create(
model="claude-opus-4",
max_tokens=512,
tools=tools,
tool_choice={"type": "tool", "name": "save_review"},
messages=[{"role": "user", "content": "Phân tích: 'Tốt, nhanh.'"}],
)
data = resp.content[0].input # đã là dict đúng schema
Cách này có hai ưu điểm so với prefill `{`:
- Schema được validate phía Anthropic — output guarantee đúng type.
- Không cần parse string; SDK trả về
dictluôn.
Module 7 (Bài 42+) sẽ đi sâu function calling — workflow đầy đủ, tool choice, parallel calls, error handling. Ở đây chỉ ghi nhận: tool use là "structured output không tên" được dùng làm pattern chính trong Anthropic và Vertex AI 2024-2026.
XML output — phong cách Anthropic
Anthropic doc khuyến nghị XML cho prompt phức tạp vì Claude được train heavy với XML-like format. Nested rõ ràng, dễ debug hơn JSON đa cấp.
Trả lời theo cấu trúc XML sau:
<analysis>
<summary>tóm tắt ngắn</summary>
<sentiment>positive | negative | neutral</sentiment>
<key_points>
<point>point 1</point>
<point>point 2</point>
</key_points>
</analysis>
Parse bằng lxml hoặc regex đơn giản:
import re
def extract_tag(text: str, tag: str) -> str:
m = re.search(rf"<{tag}>(.*?)</{tag}>", text, re.DOTALL)
return m.group(1).strip() if m else ""
summary = extract_tag(response, "summary")
sentiment = extract_tag(response, "sentiment")
So với JSON, XML tốn nhiều token hơn (mỗi tag mở + đóng). Đổi lại: model hiếm khi escape sai dấu nháy, ít hallucinated field hơn (cấu trúc explicit). Dùng XML khi prompt đã nhiều XML sẵn (system prompt, examples) để giữ phong cách nhất quán.
Few-shot example cho format
Khi schema lạ hoặc field nhiều, mô tả prose ít hiệu quả hơn cho 2-3 ví dụ. Model học format từ pattern.
Phân tích review sản phẩm theo format sau.
Ví dụ:
Input: "Tôi mua điện thoại tuần trước. Pin tốt nhưng camera mờ."
Output: {"pros": ["Pin tốt"], "cons": ["Camera mờ"], "rating": 3}
Input: "Đóng gói cẩn thận, giao 2 ngày, sản phẩm chính hãng."
Output: {"pros": ["Đóng gói cẩn thận", "Giao 2 ngày", "Chính hãng"],
"cons": [], "rating": 5}
Bây giờ phân tích:
Input: "Giá ổn, dùng được. Có vài lỗi nhỏ ở chỗ nắp pin."
Output:
Bài 26 sẽ đi sâu few-shot prompting (số lượng example, order, diversity). Ở đây chỉ cần nhớ: nếu format mới và phức tạp, few-shot thường ổn định hơn chỉ mô tả schema bằng văn bản.
Constraint cụ thể về output
Constraint càng cụ thể, output càng dễ kiểm soát:
- "Output CHỈ một từ: positive hoặc negative." — classification 2 class, no preamble.
- "Trả lời tối đa 50 từ." — giới hạn độ dài để fit UI.
- "KHÔNG có Markdown, chỉ plain text." — tránh
**bold**,#khi save vào DB. - "KHÔNG explain, không preamble, output ngay kết quả." — bỏ phần "Sure, here is...".
- "Mỗi bullet bắt đầu bằng dấu gạch
-, không phải*hay•." — chốt ký tự cụ thể.
Một quy tắc rút từ thực nghiệm: viết constraint dưới dạng positive ("OUTPUT a JSON object") hiệu quả hơn negative ("DO NOT output text") — model thường tập trung vào động từ chính, không quan trọng có "not" hay không.
"Sure, here is..." — preamble phá parser
LLM được train với RLHF để "helpful", kết quả là rất hay mở đầu bằng "Sure, here is the JSON you requested:" rồi mới đến nội dung. Với parser strict, đây là nguyên nhân #1 gây fail.
Ba cách giảm:
- Câu lệnh rõ: thêm dòng cuối prompt: "Output ONLY the JSON object. No preamble, no explanation, no markdown wrapper."
- Prefill (Anthropic): assistant message bắt đầu bằng
{hoặc<— model không thể chèn preamble nữa. - Robust parser: skip mọi ký tự trước
{đầu tiên, parse từ đó (xem bước 21).
Trong các model 2024-2026, GPT-4o-mini và Claude Haiku có xu hướng preamble nhiều hơn model lớn. Lý do: model nhỏ "vâng lời" instruction format kém hơn, nhất là khi prompt dài.
Markdown table và CSV
Markdown table cho hiển thị (render được trên hầu hết chatbot UI):
| Tên | Tuổi | Thành phố |
|-------|------|-----------|
| Alice | 30 | Hà Nội |
| Bob | 25 | TP.HCM |
Parse table về list-of-dict bằng pandas:
import pandas as pd
import io
df = pd.read_csv(
io.StringIO(table_text),
sep="|",
skipinitialspace=True,
).dropna(axis=1, how="all")
df.columns = [c.strip() for c in df.columns]
df = df[~df.iloc[:, 0].str.contains("---", na=False)]
CSV gọn hơn về token, phù hợp khi cần bulk:
name,age,city
Alice,30,Hà Nội
Bob,25,TP.HCM
Lưu ý CSV: nhắc rõ delimiter (, hay ;), quote khi value chứa dấu phẩy, và không thêm header markdown. Model có xu hướng wrap CSV trong code fence — bỏ qua bằng cùng regex như JSON.
Schema-first approach
Pattern recommend cho production: schema-first:
- Define schema (Pydantic / JSON Schema / TypeScript interface) trước khi viết prompt.
- Pass schema vào prompt (bằng
model_json_schema()hoặc literal mô tả). - Sau khi nhận response, validate bằng cùng schema.
- Nếu validate fail → retry với error message làm hint.
Cách này biến "format" từ chỉ thị mềm thành một contract — vi phạm là phát hiện ngay, có thể fix tự động (retry, fallback).
Pydantic + LLM
Pydantic v2 là chuẩn de facto cho schema Python 2024-2026:
from pydantic import BaseModel, Field
from typing import Literal
class Result(BaseModel):
summary: str = Field(description="Tóm tắt ≤ 50 từ")
sentiment: Literal["positive", "negative", "neutral"]
confidence: float = Field(ge=0.0, le=1.0)
# Dump schema để chèn vào prompt
import json
schema_str = json.dumps(Result.model_json_schema(), indent=2,
ensure_ascii=False)
prompt = f"""Phân tích review.
Trả về JSON đúng schema:
{schema_str}
Review: 'Sản phẩm tốt, giao hàng nhanh.'"""
# Sau khi gọi LLM, parse + validate
response_text = call_llm(prompt)
result = Result.model_validate_json(response_text)
print(result.sentiment, result.confidence)
Hai lợi ích so với json.loads thuần:
- Type coercion: Pydantic tự ép
"3"→3,"true"→Truekhi field là int/bool. - Validation: nếu sentiment là "mixed" (không trong Literal), Pydantic raise
ValidationError— bắt được fail sớm.
Instructor library
Instructor (Jason Liu, 2023) là wrapper combine OpenAI / Anthropic / Gemini với Pydantic. Auto inject schema vào prompt, validate response, retry khi parse fail.
import instructor
from openai import OpenAI
from pydantic import BaseModel
class Review(BaseModel):
summary: str
sentiment: str
client = instructor.from_openai(OpenAI())
review: Review = client.chat.completions.create(
model="gpt-4o-mini",
response_model=Review,
max_retries=3,
messages=[{"role": "user", "content": "Sản phẩm tốt, giao nhanh."}],
)
print(review.sentiment)
Cơ chế: Instructor dùng function calling (hoặc Structured Output khi available) để bind schema, gọi model_validate, và nếu fail thì append ValidationError vào history rồi gọi lại — model "tự sửa" theo error message. max_retries điều chỉnh số vòng.
Support Anthropic, Mistral, Cohere, Groq, Ollama (local), Vertex AI — interface giống nhau, tiện cho code đa provider.
LangChain output parsers
LangChain (v0.3, 2024) cung cấp một họ OutputParser — foundation cho complex chain.
from langchain_core.output_parsers import PydanticOutputParser
from langchain_core.prompts import PromptTemplate
from pydantic import BaseModel
class Result(BaseModel):
summary: str
sentiment: str
parser = PydanticOutputParser(pydantic_object=Result)
prompt = PromptTemplate(
template="Phân tích: {text}\n{format_instructions}",
input_variables=["text"],
partial_variables={"format_instructions": parser.get_format_instructions()},
)
# parser.get_format_instructions() đã chứa mô tả schema dạng văn bản
chain = prompt | llm | parser
result: Result = chain.invoke({"text": "Sản phẩm tốt, giao nhanh."})
Bên cạnh PydanticOutputParser còn có:
- StructuredOutputParser: cho schema đơn giản dạng dict, không cần Pydantic.
- OutputFixingParser: tự retry và sửa khi parse fail (tương tự Instructor).
- JsonOutputParser: parse JSON với streaming-friendly mode.
So sánh ngắn: LangChain phù hợp khi đã dùng LangChain ecosystem (chain, agent, memory). Instructor gọn hơn cho use case "chỉ cần structured output, không cần chain". Cả hai đều build trên Pydantic.
Failure mode thường gặp
Khi không dùng JSON Mode / Structured Output, các fail sau xuất hiện thường xuyên:
- Markdown wrapper:
```json\n{...}\n```thay vì raw JSON. Hay gặp với GPT-3.5, Claude Haiku. - Preamble: "Sure, here is the JSON:
{...}" — phájson.loads. - Trailing comma:
{"a": 1, "b": 2,}— JSON spec không cho, nhưng JavaScript / Python dict cho — model nhầm. - Unescaped quote:
{"text": "Anh nói "xin chào""}— dấu nháy trong string không escape. - Wrong type:
{"age": "30"}thay vì{"age": 30}— string thay vì int. Pydantic coerce được phần lớn. - Hallucinated field: model thêm field không yêu cầu (
"confidence": 0.9) hoặc đổi tên field ("emotion"thay vì"sentiment"). - Thiếu field required: bỏ qua field optional thì không sao, nhưng đôi khi bỏ luôn cả required.
- Single quote thay double quote:
{'a': 1}— Python literal valid, JSON không valid. - Comment trong JSON:
{"a": 1, // bình luận}— model học từ code và copy.
Tỉ lệ fail giảm dần khi model mạnh hơn nhưng không bao giờ về 0 ở chế độ free-text. Đó là lý do nên dùng JSON Mode / Structured Output / tool use khi available.
Robust parsing
Một parser chịu được hầu hết failure mode ở bước 20:
import json
import re
def extract_json(text: str) -> dict:
"""Parse JSON từ response LLM, chịu markdown wrapper + preamble."""
text = text.strip()
# 1) Bỏ markdown fence ```json ... ``` hoặc ``` ... ```
fence = re.match(r"^```(?:json)?\s*(.*?)\s*```$", text, re.DOTALL)
if fence:
text = fence.group(1).strip()
# 2) Cắt phần trước { đầu tiên và sau } cuối cùng
start = text.find("{")
end = text.rfind("}")
if start == -1 or end == -1 or end < start:
raise ValueError(f"No JSON object found in: {text[:100]}")
text = text[start : end + 1]
# 3) Bỏ trailing comma trước } hoặc ]
text = re.sub(r",\s*([}\]])", r"\1", text)
return json.loads(text)
Parser này không sửa được unescaped quote hay single quote — những trường hợp đó nên retry với prompt nhắc lại format. Pattern retry:
def call_with_retry(prompt: str, max_retries: int = 2) -> dict:
last_err = None
for i in range(max_retries + 1):
text = call_llm(prompt)
try:
return extract_json(text)
except (ValueError, json.JSONDecodeError) as e:
last_err = e
prompt += f"\n\nLần trước parse fail: {e}. Hãy trả CHỈ JSON hợp lệ."
raise RuntimeError(f"Failed after {max_retries} retries: {last_err}")
Khi không định trước format
Không phải lúc nào cũng nên ép format. Chatbot tự nhiên, creative writing, brainstorming — free text phù hợp hơn. Hai pattern khi cần extract sau:
- Regex post-process: pattern đơn giản (email, số điện thoại, URL) regex đủ.
- Second LLM call: cho LLM thứ hai (model rẻ hơn, ví dụ
gpt-4o-mini) đọc output và extract sang JSON. Tốn thêm 1 call nhưng giữ trải nghiệm tự nhiên ở call đầu.
Pattern "two-call extraction" hữu ích khi LLM chính cần creative (kể chuyện, viết blog) và app vẫn muốn metadata (sentiment, topic, length). Call 1 sinh content tự do, call 2 extract.
Format và streaming
Streaming response (bài 33) — render từng token ngay khi đến — không hợp với mọi format:
- Plain text: stream tốt, hiển thị tự nhiên.
- Markdown: stream tốt nếu UI render incremental (bold/italic có thể chớp do tag chưa đóng).
- JSON: khó stream — partial JSON không parse được. Chỉ render được khi đủ một field nguyên (key + value đóng).
- XML: tương tự JSON, cần đợi tag đóng.
Cách workaround cho JSON streaming: dùng library như partial-json-parser (TypeScript) hoặc json-stream (Python) để parse incrementally. OpenAI Structured Output có support stream=True với partial JSON.
Quy tắc đơn giản: nếu UX cần streaming, ưu tiên Markdown. Nếu cần structured, chấp nhận latency cao hơn nhưng có guarantee schema.
Cost — JSON tốn token hơn plain text
JSON tốn nhiều token hơn plain text cho cùng nội dung vì có key, dấu nháy, brace, comma. Một benchmark thô với tiktoken cl100k:
Nội dung: "Alice, 30 tuổi, Hà Nội."
Plain text: 9 token
CSV (1 dòng): 11 token
JSON: 22 token (~2.4× plain text)
XML: 28 token (~3× plain text)
YAML: 14 token
Trên 100K request/ngày, chênh lệch 10-20 token/request thành 1-2M token/ngày — không nhỏ. Trade-off:
- Khi cần parse strict, downstream phụ thuộc structure → JSON / XML, chấp nhận cost.
- Khi chỉ cần extract đơn giản → CSV hoặc plain text + regex.
- Khi field nhiều mà chỉ một số cần parse → mixed: prose tự nhiên + một dòng
METADATA: ...cuối.
Bài 34 (Token cost) sẽ đi vào chi tiết tối ưu chi phí; ở đây chỉ cần ý thức: format quyết định cả correctness lẫn cost.
Code Python tổng hợp
Gom các pattern đã nói thành 4 mini-example chạy được:
(a) JSON output + Pydantic validate:
from openai import OpenAI
from pydantic import BaseModel, Field
from typing import Literal
import json
class Review(BaseModel):
summary: str = Field(description="≤ 50 từ")
sentiment: Literal["positive", "negative", "neutral"]
client = OpenAI()
schema = json.dumps(Review.model_json_schema(), ensure_ascii=False)
resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{
"role": "user",
"content": f"Phân tích: 'Sản phẩm tốt, giao nhanh.'\n"
f"Trả CHỈ JSON đúng schema:\n{schema}",
}],
)
review = Review.model_validate_json(resp.choices[0].message.content)
print(review.sentiment)
(b) JSON Mode OpenAI:
resp = client.chat.completions.create(
model="gpt-4o-mini",
response_format={"type": "json_object"},
messages=[
{"role": "system",
"content": "Trả JSON với keys: summary, sentiment."},
{"role": "user", "content": "Sản phẩm tốt, giao nhanh."},
],
)
data = json.loads(resp.choices[0].message.content)
(c) Anthropic prefill `{` trick:
import anthropic
aclient = anthropic.Anthropic()
resp = aclient.messages.create(
model="claude-opus-4",
max_tokens=256,
messages=[
{"role": "user", "content":
"Phân loại sentiment 'Sản phẩm tốt'. "
"Trả JSON keys: summary, sentiment."},
{"role": "assistant", "content": "{"},
],
stop_sequences=["}\n\n"],
)
data = json.loads("{" + resp.content[0].text + "}")
(d) Instructor — schema-bound + auto-retry:
import instructor
from openai import OpenAI
iclient = instructor.from_openai(OpenAI())
review = iclient.chat.completions.create(
model="gpt-4o-mini",
response_model=Review,
max_retries=3,
messages=[{"role": "user", "content": "Sản phẩm tốt, giao nhanh."}],
)
print(review.sentiment)
Bài tập
- Viết một Pydantic model
ProductReviewvới 3 field:pros(list of str),cons(list of str),rating(int 1-5). Dùng pattern (a) ở bước 25, request một model trả JSON theo schema. - Lấy 5 review thật từ Tiki / Shopee, chạy prompt ở bài 1. Đếm: bao nhiêu lần output parse được bằng
json.loadstrực tiếp? Bao nhiêu lần cầnextract_jsonở bước 21? Bao nhiêu lần fail hoàn toàn? - Convert code bài 1 sang Instructor (
response_model=ProductReview,max_retries=3). So sánh số lần fail với bài 2. - Cùng input "Phân tích sentiment 100 review", thử 2 prompt: (i) trả JSON 3 field, (ii) trả CSV 3 cột. Đếm tổng input + output token cho mỗi cách. Tính chênh lệch cost với giá
gpt-4o-mini$0.15/1M input và $0.60/1M output. - (Tùy chọn) Implement prefill `{` trick với Anthropic SDK. Test 10 input, kiểm tra rằng response sau prefill luôn là JSON parse được sau khi nối lại ký tự
{đã prefill.
- OpenAI — Structured Outputs
- OpenAI — JSON Mode
- OpenAI — Introducing Structured Outputs (08/2024)
- Anthropic — Increasing output consistency
- Anthropic — Prefill Claude's response
- Anthropic — Use XML tags in prompts
- Anthropic — Tool use for structured output
- Pydantic v2 documentation
- Instructor — Structured outputs for LLMs
- LangChain — Output parsers
- LangChain — OutputFixingParser
- JSON Schema specification
- Instructor — GitHub repository
