Structured Output:結構化輸出完整指南

在前面的系列文章中,我們學習了如何使用 Anthropic Messages API 進行對話,也掌握了 Tool Use 讓 Claude 呼叫外部工具的能力。但在實際開發中,你一定遇過這樣的困境:Claude 回傳的 JSON 格式不穩定、缺少必要欄位、甚至回傳了無法解析的文字。Structured Output(結構化輸出)正是 Anthropic 提供的解決方案——它能保證 Claude 的回應完全符合你定義的 JSON Schema,徹底消除解析錯誤。

為什麼需要 Structured Output?

在沒有結構化輸出的情況下,即使你在 Prompt 中明確要求 Claude 回傳 JSON 格式,仍然可能遭遇以下問題:

  • JSON 語法錯誤:回傳的內容夾帶了說明文字,導致 JSON.parse() 失敗
  • 欄位缺失:必要欄位有時出現、有時不出現,後續程式邏輯崩潰
  • 型別不一致:預期數字卻收到字串,或陣列變成物件
  • 需要重試機制:為了處理格式問題,不得不加入重試邏輯,增加延遲和成本

Structured Output 透過受限解碼(Constrained Decoding)技術,在生成階段就強制 Claude 的輸出遵循指定的 Schema。這意味著:回應永遠是合法的 JSON、欄位型別保證正確、不需要重試機制。

兩種結構化輸出方式

Anthropic 提供了兩種互補的結構化輸出功能,可以獨立使用或在同一個請求中組合使用:

功能參數用途說明
JSON Modeoutput_config.format控制 Claude 的回應格式讓 Claude 的最終回應符合你定義的 JSON Schema
Strict Tool Usestrict: true保證工具輸入參數的格式確保 Claude 呼叫工具時傳入的參數符合 Schema 驗證

JSON Mode:output_config.format

JSON Mode 是最常用的結構化輸出方式,它讓 Claude 的回應直接以符合你指定 Schema 的 JSON 格式輸出。適用場景包括:資料擷取、分類任務、格式化 API 回應、產生結構化報告等。

基本用法

使用 JSON Mode 的核心是在 API 請求中加入 output_config.format 參數,指定 type: "json_schema" 並附上你的 Schema 定義:

import anthropic

client = anthropic.Anthropic()

response = client.messages.create(
    model="claude-sonnet-4-5-20250514",
    max_tokens=1024,
    messages=[
        {
            "role": "user",
            "content": "從這封信中擷取重要資訊:王小明 (wang@example.com) 對企業方案有興趣,想安排下週二下午兩點的展示會議。",
        }
    ],
    output_config={
        "format": {
            "type": "json_schema",
            "schema": {
                "type": "object",
                "properties": {
                    "name": {"type": "string"},
                    "email": {"type": "string"},
                    "plan_interest": {"type": "string"},
                    "demo_requested": {"type": "boolean"},
                },
                "required": ["name", "email", "plan_interest", "demo_requested"],
                "additionalProperties": False,
            },
        }
    },
)
print(response.content[0].text)

回應會是合法的 JSON,直接在 response.content[0].text 中取得:

{
  "name": "王小明",
  "email": "wang@example.com",
  "plan_interest": "企業方案",
  "demo_requested": true
}

使用 SDK 輔助工具

各語言的 SDK 提供了更方便的方式定義 Schema,不需要手寫 JSON Schema。以下是 Python(Pydantic)和 TypeScript(Zod)的範例:

from pydantic import BaseModel
from anthropic import Anthropic


class ContactInfo(BaseModel):
    name: str
    email: str
    plan_interest: str
    demo_requested: bool


client = Anthropic()

response = client.messages.parse(
    model="claude-sonnet-4-5-20250514",
    max_tokens=1024,
    messages=[
        {
            "role": "user",
            "content": "從這封信中擷取重要資訊:王小明...",
        }
    ],
    output_format=ContactInfo,
)

# 直接取得型別安全的 Python 物件
contact = response.parsed_output
print(contact.name, contact.email)
import Anthropic from "@anthropic-ai/sdk";
import { z } from "zod";
import { zodOutputFormat } from "@anthropic-ai/sdk/helpers/zod";

const ContactInfoSchema = z.object({
  name: z.string(),
  email: z.string(),
  plan_interest: z.string(),
  demo_requested: z.boolean(),
});

const client = new Anthropic();

const response = await client.messages.parse({
  model: "claude-sonnet-4-5-20250514",
  max_tokens: 1024,
  messages: [
    {
      role: "user",
      content: "從這封信中擷取重要資訊:王小明...",
    },
  ],
  output_config: { format: zodOutputFormat(ContactInfoSchema) },
});

// 自動解析並驗證,取得型別安全的物件
console.log(response.parsed_output);

Strict Tool Use:保證工具參數格式

除了控制 Claude 的回應格式,你也可以在工具定義中加入 strict: true,確保 Claude 呼叫工具時傳入的參數一定符合 Schema。這對於建構可靠的 AI Agent 工作流程至關重要。

import anthropic

client = anthropic.Anthropic()

response = client.messages.create(
    model="claude-sonnet-4-5-20250514",
    max_tokens=1024,
    messages=[
        {"role": "user", "content": "幫我查詢台北到東京的機票"}
    ],
    tools=[
        {
            "name": "search_flights",
            "description": "搜尋可用航班",
            "strict": True,  # 啟用嚴格模式
            "input_schema": {
                "type": "object",
                "properties": {
                    "origin": {
                        "type": "string",
                        "description": "出發城市"
                    },
                    "destination": {
                        "type": "string",
                        "description": "目的地城市"
                    },
                    "date": {
                        "type": "string",
                        "format": "date",
                        "description": "出發日期 (YYYY-MM-DD)"
                    }
                },
                "required": ["origin", "destination", "date"],
                "additionalProperties": False
            }
        }
    ]
)

啟用 strict: true 後,Claude 生成的工具呼叫參數保證符合你定義的 Schema,不會出現缺少必要欄位或型別錯誤的情況。

組合使用 JSON Mode + Strict Tool Use

你可以在同一個請求中同時使用 JSON Mode 和 Strict Tool Use,讓 Claude 的工具呼叫參數和最終回應都受到 Schema 約束:

response = client.messages.create(
    model="claude-sonnet-4-5-20250514",
    max_tokens=1024,
    messages=[
        {"role": "user", "content": "幫我規劃巴黎之旅,5月15日出發"}
    ],
    # JSON Mode:控制最終回應格式
    output_config={
        "format": {
            "type": "json_schema",
            "schema": {
                "type": "object",
                "properties": {
                    "summary": {"type": "string"},
                    "next_steps": {
                        "type": "array",
                        "items": {"type": "string"}
                    }
                },
                "required": ["summary", "next_steps"],
                "additionalProperties": False
            }
        }
    },
    # Strict Tool Use:保證工具參數格式
    tools=[
        {
            "name": "search_flights",
            "description": "搜尋航班",
            "strict": True,
            "input_schema": {
                "type": "object",
                "properties": {
                    "destination": {"type": "string"},
                    "date": {"type": "string", "format": "date"}
                },
                "required": ["destination", "date"],
                "additionalProperties": False
            }
        }
    ]
)

JSON Schema 定義規則

結構化輸出使用標準 JSON Schema 格式來定義期望的輸出結構。以下是撰寫 Schema 時需要注意的規則:

支援的 Schema 功能

功能說明
基本型別objectarraystringintegernumberbooleannull
enum支援字串、數字、布林值、null(不支援複雜型別)
const常數值
anyOf / allOf聯合型別與交集型別(allOf 搭配 $ref 不支援)
$ref / $def內部引用定義(不支援外部 $ref
required必填欄位
additionalProperties必須設為 false
字串格式date-timedatetimeemailuriuuidipv4ipv6
pattern支援基本正規表達式(不支援 lookahead/lookbehind)
minItems僅支援值為 0 或 1

不支援的功能

  • 遞迴 Schema:不支援自我引用的結構
  • 數值限制minimummaximummultipleOf 等不支援
  • 字串長度限制minLengthmaxLength 不支援
  • 陣列進階限制minItems 超過 1、maxItems 不支援
  • 外部引用$ref 指向外部 URL 不支援
  • additionalProperties:不能設為 true 或指定 Schema

好消息是,Python 和 TypeScript SDK 會自動轉換不支援的 Schema 特性——移除不支援的限制,並將條件資訊加入欄位描述中,讓 Claude 仍能理解你的意圖。例如 Pydantic 中的 minimum: 100 會被轉換為描述文字「Must be at least 100」。

Schema 複雜度限制

結構化輸出會將 JSON Schema 編譯成文法(Grammar),複雜的 Schema 會產生較大的文法。API 有以下明確限制:

限制項目上限值說明
Strict 工具數量20每次請求最多 20 個 strict: true 的工具
可選參數數量24所有 strict Schema 中未列入 required 的參數總和
聯合型別參數16使用 anyOf 或型別陣列的參數總和
編譯逾時180 秒Schema 編譯的最大時間限制

驗證與錯誤處理

雖然結構化輸出在絕大多數情況下保證 Schema 合規,但有兩種特殊情境需要處理:

安全拒絕(Refusal)

當 Claude 基於安全原因拒絕回應時,回傳的 stop_reason 會是 "refusal"。此時輸出可能不符合 Schema,因為拒絕訊息優先於 Schema 限制。你需要在程式中檢查 stop_reason

response = client.messages.create(
    model="claude-sonnet-4-5-20250514",
    max_tokens=1024,
    messages=[{"role": "user", "content": "..."}],
    output_config={...}
)

# 檢查是否被拒絕
if response.stop_reason == "refusal":
    print("Claude 基於安全原因拒絕了此請求")
elif response.stop_reason == "end_turn":
    # 正常回應,可以安全解析 JSON
    import json
    data = json.loads(response.content[0].text)
    print(data)

Token 上限截斷

當回應因為達到 max_tokens 限制而被截斷時,stop_reason 會是 "max_tokens"。被截斷的 JSON 可能不完整且無法解析。解決方式是增加 max_tokens 值後重試。

完整錯誤處理模式

import json
import anthropic

client = anthropic.Anthropic()

def get_structured_response(prompt, schema):
    """安全地取得結構化輸出"""
    try:
        response = client.messages.create(
            model="claude-sonnet-4-5-20250514",
            max_tokens=4096,
            messages=[{"role": "user", "content": prompt}],
            output_config={
                "format": {
                    "type": "json_schema",
                    "schema": schema
                }
            }
        )
        
        # 檢查停止原因
        if response.stop_reason == "refusal":
            return {"error": "request_refused", "message": "Claude 拒絕處理此請求"}
        
        if response.stop_reason == "max_tokens":
            return {"error": "truncated", "message": "回應被截斷,請增加 max_tokens"}
        
        # 正常解析
        return json.loads(response.content[0].text)
    
    except anthropic.APIError as e:
        return {"error": "api_error", "message": str(e)}

實際應用範例

以下是三個常見的結構化輸出應用場景,展示如何在實際專案中活用這項功能。

範例一:發票資料擷取

從非結構化文字中擷取結構化資料,是 Structured Output 最經典的應用。以下範例展示如何從發票文字中擷取關鍵欄位:

from pydantic import BaseModel
from typing import List
from anthropic import Anthropic


class LineItem(BaseModel):
    description: str
    quantity: int
    unit_price: float


class Invoice(BaseModel):
    invoice_number: str
    date: str
    total_amount: float
    line_items: List[LineItem]
    customer_name: str


client = Anthropic()

invoice_text = """
發票編號:INV-2026-0042
日期:2026年5月15日
客戶:台灣科技股份有限公司

品項:
1. 雲端伺服器租賃 x 12個月 - NT$8,000/月
2. SSL 憑證 x 1 - NT$3,500
3. 技術支援方案 x 1 - NT$15,000

總計:NT$114,500
"""

response = client.messages.parse(
    model="claude-sonnet-4-5-20250514",
    max_tokens=4096,
    output_format=Invoice,
    messages=[
        {"role": "user", "content": f"從以下發票中擷取結構化資料:\n{invoice_text}"}
    ],
)

invoice = response.parsed_output
print(f"發票號碼:{invoice.invoice_number}")
print(f"客戶:{invoice.customer_name}")
print(f"總金額:{invoice.total_amount}")
for item in invoice.line_items:
    print(f"  - {item.description}: {item.quantity} x {item.unit_price}")

範例二:客戶回饋分類

使用結構化輸出進行文字分類,可以同時取得分類標籤、信心分數和情感分析結果:

from pydantic import BaseModel
from typing import List
from anthropic import Anthropic


class FeedbackClassification(BaseModel):
    category: str         # 產品、服務、物流、價格...
    confidence: float     # 0.0 ~ 1.0
    tags: List[str]       # 相關標籤
    sentiment: str        # positive / negative / neutral
    summary: str          # 一句話摘要


client = Anthropic()

feedbacks = [
    "產品品質很好,但物流速度太慢了,等了快兩週才收到",
    "客服態度非常好,幫我快速解決了退款問題",
    "價格比競品貴了不少,希望能有更多折扣方案",
]

for feedback in feedbacks:
    response = client.messages.parse(
        model="claude-sonnet-4-5-20250514",
        max_tokens=1024,
        output_format=FeedbackClassification,
        messages=[
            {"role": "user", "content": f"分類以下客戶回饋:{feedback}"}
        ],
    )
    result = response.parsed_output
    print(f"類別:{result.category} | 情感:{result.sentiment}")
    print(f"信心度:{result.confidence} | 標籤:{', '.join(result.tags)}")
    print(f"摘要:{result.summary}")
    print("---")

範例三:結構化分析報告

生成包含多層巢狀結構的分析報告,適合用於自動化報表系統:

from pydantic import BaseModel
from typing import List, Optional
from anthropic import Anthropic


class Metric(BaseModel):
    name: str
    value: float
    unit: str
    trend: str  # up / down / stable


class Recommendation(BaseModel):
    priority: str        # high / medium / low
    action: str
    expected_impact: str


class AnalysisReport(BaseModel):
    title: str
    executive_summary: str
    key_metrics: List[Metric]
    strengths: List[str]
    weaknesses: List[str]
    recommendations: List[Recommendation]
    risk_level: str      # low / medium / high


client = Anthropic()

response = client.messages.parse(
    model="claude-sonnet-4-5-20250514",
    max_tokens=4096,
    output_format=AnalysisReport,
    messages=[
        {
            "role": "user",
            "content": """分析以下業務數據並產生報告:
            Q1 營收:NT$2,500萬(年增 15%)
            客戶留存率:87%(下降 3%)
            新客戶數:156(年增 22%)
            平均客單價:NT$12,000(持平)
            客訴率:2.1%(上升 0.5%)"""
        }
    ],
)

report = response.parsed_output
print(f"報告:{report.title}")
print(f"摘要:{report.executive_summary}")
print(f"風險等級:{report.risk_level}")

Prompt Engineering 搭配結構化輸出

雖然結構化輸出保證了格式的正確性,但回應的內容品質仍然取決於你的 Prompt。以下是幾個搭配使用的技巧:

  1. 在 Schema 中加入描述:善用 JSON Schema 的 description 欄位,告訴 Claude 每個欄位期望的內容。例如 "confidence": {"type": "number", "description": "介於 0 到 1 之間的信心分數"}
  2. 使用 enum 限制值域:對於有限選項的欄位,使用 enum 限制可選值。例如 "sentiment": {"type": "string", "enum": ["positive", "negative", "neutral"]}
  3. 在 Prompt 中提供範例:即使有 Schema 限制,在 Prompt 中附上一個期望輸出的範例,仍能顯著提升回應品質
  4. 善用 System Prompt:在 system 訊息中設定角色和任務背景,讓 Claude 更準確地理解你需要的資訊
  5. 分層設計 Schema:對於複雜輸出,使用巢狀物件和陣列來組織結構,而不是把所有欄位放在最外層

效能考量與最佳實踐

使用結構化輸出時,有幾個效能面向需要注意:

文法編譯與快取

  • 首次請求延遲:第一次使用特定 Schema 時,需要額外時間編譯文法
  • 自動快取:編譯後的文法會快取 24 小時,後續請求速度更快
  • 快取失效:修改 Schema 結構或工具集合會使快取失效(僅修改 namedescription 不會)

Token 成本

使用結構化輸出時,Claude 會自動收到一個額外的系統提示來說明輸出格式,這意味著你的輸入 Token 數會略微增加。此外,修改 output_config.format 會使 Prompt Cache 失效。

降低複雜度的策略

  1. 只對關鍵工具啟用 strict:不需要每個工具都開 strict: true,Claude 本身就有不錯的 Schema 遵循能力
  2. 減少可選參數:盡量將參數設為 required,每個可選參數都會大幅增加文法的狀態空間
  3. 簡化巢狀結構:深層巢狀加上可選欄位會使複雜度倍增,盡量扁平化
  4. 分拆請求:如果工具數量太多,考慮拆成多個請求或使用 Sub-Agent 架構

支援的模型與平台

結構化輸出目前已在以下模型和平台上正式可用(GA):

模型平台支援
Claude Opus 4.6Anthropic API、Amazon Bedrock
Claude Sonnet 4.6Anthropic API、Amazon Bedrock
Claude Sonnet 4.5Anthropic API、Amazon Bedrock
Claude Opus 4.5Anthropic API、Amazon Bedrock
Claude Haiku 4.5Anthropic API、Amazon Bedrock

在 Microsoft Foundry 上目前仍為 Beta 階段。

功能相容性

結構化輸出可以與大部分 API 功能搭配使用,但有部分例外需要注意:

功能相容性說明
Batch Processing✅ 支援可大量處理結構化輸出,享有 50% 折扣
Streaming✅ 支援可以串流接收結構化輸出
Token Counting✅ 支援可計算 Token 數量,不觸發編譯
JSON + Strict Tool Use✅ 支援可在同一請求中同時使用
Citations❌ 不相容引用功能與嚴格 JSON Schema 衝突
Message Prefilling❌ 不相容不可與 JSON Mode 同時使用

延伸閱讀

總結

Structured Output 是 Anthropic API 中非常實用的功能,它從根本上解決了 LLM 輸出格式不穩定的問題。透過 output_config.format(JSON Mode)和 strict: true(Strict Tool Use)兩種方式,你可以確保 Claude 的每一次回應都是合法、可解析、符合 Schema 的結構化資料。

在實際應用中,建議從簡單的 Schema 開始,善用 SDK 提供的 Pydantic / Zod 輔助工具,並搭配良好的 Prompt Engineering 來提升內容品質。隨著你對功能的熟悉,可以逐步處理更複雜的巢狀結構和多工具場景。

下一篇文章中,我們將探討更多 Anthropic API 的進階功能。敬請期待!