簡介
人工智慧以自由文字形式回答 → 難以用程式碼解析。在生產中,您需要結構化輸出:JSON、表格、固定列表...供下游系統處理。
範例:「分析評論的情緒」→ AI 回答「這是一篇正面評論」(自由文本,難以解析)。結構化輸出:
{"sentiment": "positive", "score": 0.85, "keywords": ["tốt", "nhanh"]}→ 程式碼解析很簡單!
本文涵蓋:
- JSON模式——強制AI回傳JSON
- JSON Schema — 定義確切的結構
- 函數呼叫——透過工具使用的結構化輸出
- Pydantic + Instructor — 類型安全驗證
1. JSON模式-讓AI回傳JSON
1.1 基本提示
❌ Prompt kém:
"Phân tích review này và cho biết sentiment"
→ AI: "Review này có sentiment tích cực vì người dùng khen sản phẩm tốt..."
✅ Prompt tốt:
"Phân tích review này. Trả lời CHÍNH XÁC bằng JSON:
{
"sentiment": "positive" | "negative" | "neutral",
"score": 0.0-1.0,
"keywords": ["từ khóa 1", "từ khóa 2"],
"summary": "tóm tắt 1 câu"
}"
→ AI: {"sentiment": "positive", "score": 0.85, "keywords": ["tốt", "nhanh"], "summary": "..."}
1.2 OpenAI JSON模式
"""OpenAI JSON Mode — đảm bảo output là valid JSON"""
from openai import OpenAI
client = OpenAI()
response = client.chat.completions.create(
model="gpt-4o-mini",
response_format={"type": "json_object"}, # ← JSON Mode ON
messages=[
{"role": "system", "content": "Trả lời bằng JSON."},
{"role": "user", "content": """Phân tích sentiment:
Review: "Sản phẩm tốt, giao hàng nhanh, sẽ mua lại!"
JSON format:
{"sentiment": "positive|negative|neutral", "score": 0-1, "keywords": [...]}"""},
],
)
import json
result = json.loads(response.choices[0].message.content)
print(result)
# {"sentiment": "positive", "score": 0.92, "keywords": ["tốt", "nhanh", "mua lại"]}
1.3 JSON模式的問題
JSON Mode chỉ đảm bảo output là VALID JSON,
KHÔNG đảm bảo schema đúng!
Bạn yêu cầu: {"sentiment": "...", "score": ...}
AI có thể trả: {"feeling": "good", "rating": 5} ← Sai field names!
→ Cần JSON Schema để enforce cấu trúc chính xác.
2. JSON 模式 — 結構化輸出
2.1 OpenAI 結構化輸出 (2024+)
"""Structured Outputs: define schema, AI PHẢI tuân thủ"""
from openai import OpenAI
from pydantic import BaseModel
class SentimentAnalysis(BaseModel):
sentiment: str # "positive", "negative", "neutral"
score: float # 0.0 - 1.0
keywords: list[str]
summary: str
client = OpenAI()
response = client.beta.chat.completions.parse(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": "Phân tích sentiment review."},
{"role": "user", "content": "Sản phẩm tốt, giao hàng nhanh!"},
],
response_format=SentimentAnalysis, # ← Schema enforcement
)
result = response.choices[0].message.parsed
print(result.sentiment) # "positive"
print(result.score) # 0.92
print(result.keywords) # ["tốt", "nhanh"]
2.2 複雜模式
"""Schema phức tạp: nested objects, enums, optional fields"""
from pydantic import BaseModel, Field
from typing import Optional
from enum import Enum
class Sentiment(str, Enum):
positive = "positive"
negative = "negative"
neutral = "neutral"
class Aspect(BaseModel):
category: str = Field(description="Khía cạnh: quality, price, delivery, service")
sentiment: Sentiment
text: str = Field(description="Đoạn text liên quan")
class ReviewAnalysis(BaseModel):
overall_sentiment: Sentiment
overall_score: float = Field(ge=0, le=1, description="0=rất tiêu cực, 1=rất tích cực")
aspects: list[Aspect] = Field(description="Phân tích từng khía cạnh")
recommendation: bool = Field(description="Có nên mua không?")
summary: str
# Output:
# {
# "overall_sentiment": "positive",
# "overall_score": 0.85,
# "aspects": [
# {"category": "quality", "sentiment": "positive", "text": "Sản phẩm tốt"},
# {"category": "delivery", "sentiment": "positive", "text": "giao hàng nhanh"},
# {"category": "price", "sentiment": "neutral", "text": "giá hợp lý"}
# ],
# "recommendation": true,
# "summary": "..."
# }
💡 練習 1: 建立一個架構,用於從履歷/履歷中提取資訊:姓名、電子郵件、技能、經驗(清單)、教育程度。使用 3 個不同的 CV 進行測試。
3. 函數調用
3.1 結構化輸出的工具使用
"""Function calling: AI "gọi function" với arguments có cấu trúc"""
from openai import OpenAI
client = OpenAI()
tools = [{
"type": "function",
"function": {
"name": "save_contact",
"description": "Lưu thông tin liên hệ",
"parameters": {
"type": "object",
"properties": {
"name": {"type": "string", "description": "Họ tên"},
"phone": {"type": "string", "description": "Số điện thoại"},
"email": {"type": "string", "description": "Email"},
"company": {"type": "string", "description": "Công ty"},
},
"required": ["name"],
},
},
}]
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content":
"Anh Minh, SĐT 0901234567, email [email protected], công ty XDev"}],
tools=tools,
tool_choice={"type": "function", "function": {"name": "save_contact"}},
)
# AI trả về structured arguments
args = json.loads(response.choices[0].message.tool_calls[0].function.arguments)
# {"name": "Minh", "phone": "0901234567", "email": "[email protected]", "company": "XDev"}
3.2 何時使用函數呼叫與 JSON 模式?
| 特點 | JSON 架構 | 函數呼叫 |
|---|---|---|
| 輸出格式 | JSON 物件 | 函數參數 |
| 架構執行 | ✅ 嚴格 | ✅ 嚴格 |
| 多個輸出 | ❌ 1 件 | ✅ 多個工具呼叫 |
| 串流媒體 | ✅ | ✅ |
| 使用案例 | 資料提取 | 行動+提取 |
4. LangChain結構化輸出
4.1 with_structed_output()
"""LangChain: structured output dễ dàng"""
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field
class ExtractedInfo(BaseModel):
"""Thông tin trích xuất từ email"""
sender: str = Field(description="Người gửi")
subject: str = Field(description="Chủ đề")
action_items: list[str] = Field(description="Việc cần làm")
priority: str = Field(description="high, medium, low")
deadline: str | None = Field(description="Deadline nếu có")
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
structured_llm = llm.with_structured_output(ExtractedInfo)
email = """Chào team,
Cần hoàn thành report Q3 trước thứ 6 tuần này.
Minh review data, Hùng viết slides.
Ưu tiên cao vì CEO cần trình bày thứ 2.
Thanks."""
result = structured_llm.invoke(f"Extract thông tin từ email:\n{email}")
print(result.action_items) # ["Hoàn thành report Q3", "Review data", "Viết slides"]
print(result.priority) # "high"
print(result.deadline) # "Thứ 6 tuần này"
4.2 講師 — 類型安全 + 重試
"""Instructor: Pydantic validation + auto retry"""
# pip install instructor
import instructor
from openai import OpenAI
from pydantic import BaseModel, field_validator
client = instructor.from_openai(OpenAI())
class UserInfo(BaseModel):
name: str
age: int
email: str
@field_validator("age")
@classmethod
def validate_age(cls, v):
if not 0 < v < 150:
raise ValueError("Age must be between 1 and 149")
return v
@field_validator("email")
@classmethod
def validate_email(cls, v):
if "@" not in v:
raise ValueError("Invalid email format")
return v
# Instructor tự retry nếu validation fail!
result = client.chat.completions.create(
model="gpt-4o-mini",
response_model=UserInfo,
max_retries=3, # Retry tối đa 3 lần nếu validation fail
messages=[{"role": "user", "content": "Minh, 30 tuổi, [email protected]"}],
)
print(result) # UserInfo(name="Minh", age=30, email="[email protected]")
💡練習 2: 使用 Instructor 建立提取管道:輸入 = 產品描述段落 → 輸出 = 模式(名稱、價格、類別、功能、評級)。新增驗證器:價格 > 0,評級 1-5。
5. 錯誤處理與邊緣狀況
5.1 重試模式
"""Retry khi JSON parse fail"""
import json
from tenacity import retry, stop_after_attempt, retry_if_exception_type
@retry(
stop=stop_after_attempt(3),
retry=retry_if_exception_type(json.JSONDecodeError),
)
def extract_json(text: str, prompt: str) -> dict:
response = client.chat.completions.create(
model="gpt-4o-mini",
response_format={"type": "json_object"},
messages=[
{"role": "system", "content": prompt},
{"role": "user", "content": text},
],
)
return json.loads(response.choices[0].message.content)
5.2 後備策略
Strategy khi structured output fail:
1. JSON Schema (strict) ← Thử đầu tiên
↓ fail
2. JSON Mode + prompt ← Fallback
↓ fail
3. Text output + regex parse ← Last resort
總結
| 概念 | 記住 |
|---|---|
| JSON 模式 | 確保有效的 JSON,而不是強制架構 |
| 結構化輸出 | 模式執行,Pydantic 模型 |
| 函數呼叫 | 工具使用格式,多次呼叫 |
| with_structed_output() | LangChain包裝器,簡單易用 |
| 導師 | Pydantic 驗證 + 自動重試 |
| 重試 | 解析失敗時堅韌重試 |
一般練習
- ✅ 完成 2 個小練習 (1, 2)
- 電子郵件分類器: 輸入 = 電子郵件 → 輸出 =
{category, priority, action_items, sentiment, response_draft}。使用 Pydantic 模式 + 驗證。使用 10 封電子郵件進行測試。 - 資料管道: 抓取20條評論→擷取結構化資料(講師)→儲存到CSV→分析。比較結構化輸出與正規表示式解析的準確性。
- 多重模型: 結構化輸出品質比較:GPT-4o-mini vs Claude vs Gemini。哪種模型最符合該架構?
下一篇文章: 程式碼產生提示工程 — 為 AI 編寫提示以產生高品質程式碼、審查程式碼、偵錯和生成測試。