📐 為什麼需要 Structured Output?
AI 預設回答是「自由文字」——但當你要把 AI 嵌入程式碼,你需要的是 JSON、不是散文。
💡 一句話理解 Structured Output = 讓 AI 的輸出像資料庫一樣穩定、可預測、可解析。
真實問題
❌ 直接問 AI:「分析這段客戶回饋的情感」
AI 回答:「這段文字整體偏正面,客戶對產品品質很滿意,
但對出貨速度有些不滿...(balabala 500 字)」
→ 你的程式碼要怎麼 parse 這坨文字?正則表達式?不可能穩定。
✅ 用 Structured Output:
AI 回答:{
"sentiment": "mixed",
"score": 0.65,
"positive": ["產品品質", "客服態度"],
"negative": ["出貨速度"],
"summary": "整體正面但物流需改善"
}
→ JSON.parse() 就搞定。穩定、可預測、可自動化。
🔧 三種實現方式
方式一覽
| 方式 | 穩定度 | 複雜度 | 適合場景 |
|---|---|---|---|
| Prompt 指定格式 | ⭐⭐ | 低 | 快速原型 |
| JSON Mode | ⭐⭐⭐⭐ | 低 | 簡單結構 |
| Function Calling | ⭐⭐⭐⭐⭐ | 中 | 生產環境 |
📝 方式 1:Prompt 指定格式
最簡單但最不穩定的方式——在 Prompt 裡告訴 AI 要輸出 JSON。
response = client.chat.completions.create(
model="gpt-4o",
messages=[{
"role": "user",
"content": """分析以下客戶回饋的情感。
回饋:「產品很棒但是等了兩週才收到」
請用以下 JSON 格式回答,不要加其他文字:
{
"sentiment": "positive" | "negative" | "mixed",
"score": 0.0 到 1.0,
"positive_aspects": ["..."],
"negative_aspects": ["..."]
}"""
}]
)
import json
result = json.loads(response.choices[0].message.content)
⚠️ 風險:AI 可能回傳
"```json\n{...}\n```"或加上解釋文字,導致 JSON.parse 失敗。只適合原型驗證。
🔒 方式 2:JSON Mode
各家 API 都有「保證輸出合法 JSON」的模式。
OpenAI JSON Mode
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "你是情感分析 API。永遠用 JSON 格式回答。"},
{"role": "user", "content": "分析:「產品很棒但等了兩週才收到」"}
],
response_format={"type": "json_object"} # 保證輸出合法 JSON
)
result = json.loads(response.choices[0].message.content)
# ✅ 保證是合法 JSON
# ⚠️ 不保證欄位名稱和型別——AI 可能回傳任意結構
OpenAI Structured Outputs(最推薦)
from pydantic import BaseModel
from typing import List
# 用 Pydantic 定義你想要的 Schema
class SentimentResult(BaseModel):
sentiment: str # "positive", "negative", "mixed"
score: float # 0.0 ~ 1.0
positive_aspects: List[str]
negative_aspects: List[str]
summary: str
response = client.beta.chat.completions.parse(
model="gpt-4o",
messages=[
{"role": "system", "content": "分析客戶回饋的情感。"},
{"role": "user", "content": "產品很棒但等了兩週才收到"}
],
response_format=SentimentResult # 保證符合 Schema!
)
result = response.choices[0].message.parsed
print(result.sentiment) # "mixed"
print(result.score) # 0.65
# ✅ 型別安全、欄位保證存在、IDE 自動補全
Claude JSON 輸出
response = client.messages.create(
model="claude-sonnet-4-20260514",
max_tokens=500,
system="你是情感分析 API。只回傳 JSON,不要其他文字。",
messages=[{
"role": "user",
"content": """分析以下回饋的情感,用這個 JSON schema 回答:
{"sentiment": string, "score": number, "aspects": string[]}
回饋:「產品很棒但等了兩週」"""
}]
)
result = json.loads(response.content[0].text)
🔧 方式 3:Function Calling(最強大)
Function Calling 讓 AI 不只是「輸出 JSON」,而是主動呼叫你定義的函式。這是建構 AI Agent 的基礎。
基本概念
你定義一組工具(functions)→ AI 判斷該用哪個工具
→ AI 輸出符合 schema 的參數 → 你的程式碼執行對應邏輯
OpenAI Function Calling
# 1. 定義工具
tools = [
{
"type": "function",
"function": {
"name": "search_products",
"description": "依據條件搜尋商品",
"parameters": {
"type": "object",
"properties": {
"keyword": {
"type": "string",
"description": "搜尋關鍵字"
},
"category": {
"type": "string",
"enum": ["electronics", "clothing", "food"],
"description": "商品類別"
},
"max_price": {
"type": "number",
"description": "最高價格(台幣)"
}
},
"required": ["keyword"]
}
}
},
{
"type": "function",
"function": {
"name": "get_order_status",
"description": "查詢訂單狀態",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "訂單編號"
}
},
"required": ["order_id"]
}
}
}
]
# 2. 呼叫 API,AI 會自動選擇要用的工具
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "user", "content": "我想找 5000 元以下的無線耳機"}
],
tools=tools,
tool_choice="auto" # AI 自動決定是否使用工具
)
# 3. 處理 AI 的回應
message = response.choices[0].message
if message.tool_calls:
for call in message.tool_calls:
fn_name = call.function.name # "search_products"
fn_args = json.loads(call.function.arguments)
# {"keyword": "無線耳機", "category": "electronics", "max_price": 5000}
# 4. 執行你的實際函式
if fn_name == "search_products":
results = search_products(**fn_args) # 你的搜尋邏輯
# 5. 把結果回傳給 AI,讓它生成自然語言回覆
follow_up = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "user", "content": "我想找 5000 元以下的無線耳機"},
message, # AI 的 tool_call
{
"role": "tool",
"tool_call_id": call.id,
"content": json.dumps(results, ensure_ascii=False)
}
]
)
print(follow_up.choices[0].message.content)
Claude Tool Use
response = client.messages.create(
model="claude-sonnet-4-20260514",
max_tokens=1000,
tools=[{
"name": "search_products",
"description": "依據條件搜尋商品",
"input_schema": {
"type": "object",
"properties": {
"keyword": {"type": "string", "description": "搜尋關鍵字"},
"max_price": {"type": "number", "description": "最高價格"}
},
"required": ["keyword"]
}
}],
messages=[
{"role": "user", "content": "我想找 5000 元以下的無線耳機"}
]
)
# Claude 的 tool use 回傳在 content block 中
for block in response.content:
if block.type == "tool_use":
print(f"工具: {block.name}")
print(f"參數: {block.input}")
🏗️ 實戰:Schema 設計原則
好的 Schema 設計
# ✅ 好:欄位語意明確、有 enum 約束、有 description
{
"type": "object",
"properties": {
"urgency": {
"type": "string",
"enum": ["low", "medium", "high", "critical"],
"description": "問題的緊急程度"
},
"category": {
"type": "string",
"enum": ["billing", "technical", "shipping", "other"],
"description": "問題分類"
},
"requires_human": {
"type": "boolean",
"description": "是否需要轉接真人客服"
}
},
"required": ["urgency", "category", "requires_human"]
}
# ❌ 壞:欄位模糊、沒有約束、沒有描述
{
"type": "object",
"properties": {
"level": {"type": "string"},
"type": {"type": "string"},
"flag": {"type": "boolean"}
}
}
Schema 設計清單
- ✅ 每個欄位都有
description - ✅ 可列舉的值用
enum約束 - ✅ 標記
required欄位 - ✅ 數值欄位設定合理的
minimum/maximum - ✅ 陣列欄位設定
maxItems避免輸出過長 - ✅ 巢狀結構不要超過 3 層
⚠️ 錯誤處理
即使用了 Structured Output,還是可能出問題:
def safe_parse(response, fallback=None):
"""安全的 JSON 解析,含降級策略"""
content = response.choices[0].message.content
try:
return json.loads(content)
except json.JSONDecodeError:
# 嘗試清理(去掉 markdown code block)
cleaned = content.strip().removeprefix("```json").removesuffix("```").strip()
try:
return json.loads(cleaned)
except json.JSONDecodeError:
print(f"⚠️ JSON 解析失敗,原始回應: {content[:200]}")
return fallback
→ 學會 API 呼叫和結構化輸出後,下一步是理解 Embedding 向量嵌入 技術。
❓ FAQ
Function Calling 和 Structured Output 有什麼差別?
Structured Output 是「讓 AI 產出符合 schema 的 JSON」。Function Calling 是「讓 AI 決定要呼叫哪個函式、傳什麼參數」——是更上層的概念。Function Calling 底層依賴 Structured Output,但多了「工具選擇」的智慧。
JSON Mode 和 Structured Outputs 怎麼選?
JSON Mode 只保證輸出合法 JSON,不保證符合你的 schema。Structured Outputs(Pydantic model)保證每個欄位的名稱和型別都正確。生產環境永遠用 Structured Outputs。
Function Calling 和 MCP 什麼關係?
Function Calling 是各 API 各自的工具呼叫方式(OpenAI 一套、Claude 一套)。MCP 是統一標準——寫一次工具,所有支援 MCP 的 AI 都能用。MCP 是 Function Calling 的跨平台進化版。
AI 不遵守 Schema 怎麼辦?
用 OpenAI 的 Structured Outputs(Pydantic)幾乎不會發生。用 JSON Mode 或 Prompt 方式可能遇到。解法:1) 升級到 Structured Outputs 2) 加 JSON 解析 fallback 3) 重試機制。