回到頂部
Mason AI Lab tech article hero for Structured Output 結構化輸出指南

Structured Output 結構化輸出指南

Structured Output(結構化輸出)讓 AI 穩定回傳 JSON。完整解析 Function Calling、JSON Schema 設計與 API 串接實務。

Structured Output(結構化輸出)是把 AI 從「文字機器」變成「可整合系統」的關鍵橋樑。當你想把 AI 的回應丟進資料庫、串接 API、或接到現有的後端系統,你需要的不是一段流暢的文字,而是一個格式穩定的 JSON。本指南會帶你理解 Function Calling 與 JSON Schema 兩種主流做法,以及在實際 API 串接時的設計判斷。

📐 為什麼需要 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) 重試機制。

№ · further reading

延伸閱讀