Claude API 結構化輸出(Structured Outputs)

為什麼需要結構化輸出?

Claude API 總算是推出 Structured Outputs了,這個功能可以讓 Claude 的回應準確的結構化 JSON輸出。

以前在用 Claude API 的時後雖然可以要求它輸出 JSON 格式,但經常會遇到以下這些問題:

  • JSON 格式錯誤導制解析失敗
  • 缺少必要欄位資訊
  • 資料類型不一致
  • 需要寫一堆錯誤處理和重試的邏輯
  • 期待輸出完全正確的 JSON像在『擲筊』,十次總會 GG的一次

現在有了結構化輸出的導入,我們應該可以 100%預期 Claude API的輸出會『絕對』的遵循我們下的結構輸出指引(?)

兩種使用模式

目前 Claude的結構化輸出提供了兩種模式:

  • JSON 輸出模式:適合需要特定格式回應得情境,像是資料萃取、產生報告、API 回應格式化等等。
  • 嚴格工具模式:適合需要驗證工具參數和名稱的場景,特別是在建構 Agent 工作流程或是複雜工具調用的時後。

實戰:從印刷品質異常報告提取8D資料

假設你想要從戴伊爾斯印刷工業的品質異常報告中提取 8D Report 的關鍵訊息,那個可以這樣做:

<?php
require 'vendor/autoload.php';

use GuzzleHttp\Client;

$client = new Client([
    'base_uri' => 'https://api.anthropic.com',
    'headers' => [
        'Content-Type' => 'application/json',
        'x-api-key' => getenv('ANTHROPIC_API_KEY'),
        'anthropic-version' => '2023-06-01',
        'anthropic-beta' => 'structured-outputs-2025-11-13'
    ]
]);

$response = $client->post('/v1/messages', [
    'json' => [
        'model' => 'claude-sonnet-4-5',
        'max_tokens' => 1024,
        'messages' => [
            [
                'role' => 'user',
                'content' => '提取這份異常報告的8D資訊:客戶名稱為戴伊爾斯印刷工業,產品是食品外盒印刷,發現批號 PO-2025-1156 出現嚴重色差問題,影響數量 5000張,那個發現日期是2025/11/10,負責工程師是 Sean Liu,初步判斷是油墨供應商批次問題'
            ]
        ],
        'output_format' => [
            'type' => 'json_schema',
            'schema' => [
                'type' => 'object',
                'properties' => [
                    'report_id' => ['type' => 'string'],
                    'customer_name' => ['type' => 'string'],
                    'product_type' => ['type' => 'string'],
                    'defect_description' => ['type' => 'string'],
                    'affected_quantity' => ['type' => 'integer'],
                    'discovery_date' => ['type' => 'string'],
                    'responsible_engineer' => ['type' => 'string'],
                    'root_cause_preliminary' => ['type' => 'string']
                ],
                'required' => ['customer_name', 'product_type', 'defect_description', 'affected_quantity'],
                'additionalProperties' => false
            ]
        ]
    ]
]);

$body = json_decode($response->getBody(), true);
$result = json_decode($body['content'][0]['text'], true);

echo "報告編號:" . $result['report_id'] . "\n";
echo "客戶名稱:" . $result['customer_name'] . "\n";
echo "產品類型:" . $result['product_type'] . "\n";
echo "缺陷描述:" . $result['defect_description'] . "\n";
echo "影響數量:" . $result['affected_quantity'] . " 張\n";

實測回傳的結果會是完全符合架構地的 JSON:

{
  "report_id": "PO-2025-1156",
  "customer_name": "戴伊爾斯印刷工業",
  "product_type": "食品外盒印刷",
  "defect_description": "嚴重色差問題",
  "affected_quantity": 5000,
  "discovery_date": "2025/11/10",
  "responsible_engineer": "Sean Liu",
  "root_cause_preliminary": "油墨供應商批次問題"
}

Strict tool use 範例:8D報告查詢系統

如果你在建構需要 Tool Calling的應用,可以使用Strict tool use確保參數正確:

<?php
require 'vendor/autoload.php';

use GuzzleHttp\Client;

$client = new Client([
    'base_uri' => 'https://api.anthropic.com',
    'headers' => [
        'Content-Type' => 'application/json',
        'x-api-key' => getenv('ANTHROPIC_API_KEY'),
        'anthropic-version' => '2023-06-01',
        'anthropic-beta' => 'structured-outputs-2025-11-13'
    ]
]);

$response = $client->post('/v1/messages', [
    'json' => [
        'model' => 'claude-sonnet-4-5',
        'max_tokens' => 1024,
        'messages' => [
            [
                'role' => 'user',
                'content' => '幫我查詢2025年11月所有關於色差問題得8D報告'
            ]
        ],
        'tools' => [
            [
                'name' => 'query_8d_reports',
                'description' => '查詢戴伊爾斯印刷工業的8D品質異常報告',
                'strict' => true,
                'input_schema' => [
                    'type' => 'object',
                    'properties' => [
                        'defect_type' => [
                            'type' => 'string',
                            'enum' => ['色差', '套印不準', '紙張皺摺', '墨點', '鬼影', '刮痕', '其他'],
                            'description' => '缺陷類型'
                        ],
                        'date_from' => [
                            'type' => 'string',
                            'description' => '起始日期,格式 YYYY-MM-DD'
                        ],
                        'date_to' => [
                            'type' => 'string',
                            'description' => '結束日期,格式 YYYY-MM-DD'
                        ],
                        'status' => [
                            'type' => 'string',
                            'enum' => ['進行中', '已結案', '待驗證'],
                            'description' => '報告狀態'
                        ]
                    ],
                    'required' => ['defect_type'],
                    'additionalProperties' => false
                ]
            ]
        ]
    ]
]);

$body = json_decode($response->getBody(), true);

// 檢查是否有工具調用
foreach ($body['content'] as $content) {
    if ($content['type'] === 'tool_use') {
        echo "工具名稱:" . $content['name'] . "\n";
        echo "工具參數:\n";
        print_r($content['input']);
    }
}

完整 8D 報告資料萃取範例

這個範例示範如何從完整的異常報告文字中提取結構化的8D資料:

<?php
require 'vendor/autoload.php';

use GuzzleHttp\Client;

$client = new Client([
    'base_uri' => 'https://api.anthropic.com',
    'headers' => [
        'Content-Type' => 'application/json',
        'x-api-key' => getenv('ANTHROPIC_API_KEY'),
        'anthropic-version' => '2023-06-01',
        'anthropic-beta' => 'structured-outputs-2025-11-13'
    ]
]);

$report_text = <<<EOT
從以下印刷異常報告產生8D報告:

報告編號:8D-2025-089
客戶:戴伊爾斯印刷工業
產品:餅乾外包裝彩盒

D1 團隊組成:
- 組長:品管課長 Sean Liu
- 成員:生產主管 Paul、設備工程師 Vinnie、品檢員 Denise

問題描述:2025/11/12在品檢站發現該批產品出現明顯套印不準問題,
紅色與藍色圖層偏移約0.8mm,影響數量約8000張。

根本原因分析:
1. 機台校正 - 發現第3號印刷機對位系統感應器老化
2. 環境因素 - 當日車間溫濕度超出標準範圍

矯正措施:
1. 更換第3號機對位感應器(負責人:設備組長 Vinnie,完成日:2025/11/15,狀態:已完成)
2. 加強溫濕度監控系統(負責人:品管課長 Sean Liu,完成日:2025/11/20,狀態:進行中)
EOT;

$schema = [
    'type' => 'object',
    'properties' => [
        'report_id' => ['type' => 'string'],
        'customer_name' => ['type' => 'string'],
        'product_name' => ['type' => 'string'],
        'd1_team' => [
            'type' => 'object',
            'properties' => [
                'leader' => ['type' => 'string'],
                'members' => [
                    'type' => 'array',
                    'items' => ['type' => 'string']
                ]
            ]
        ],
        'd2_problem' => [
            'type' => 'object',
            'properties' => [
                'defect_type' => ['type' => 'string'],
                'defect_detail' => ['type' => 'string'],
                'affected_quantity' => ['type' => 'integer'],
                'discovery_date' => ['type' => 'string'],
                'discovery_location' => ['type' => 'string']
            ],
            'required' => ['defect_type', 'affected_quantity']
        ],
        'd4_root_causes' => [
            'type' => 'array',
            'items' => [
                'type' => 'object',
                'properties' => [
                    'category' => ['type' => 'string'],
                    'description' => ['type' => 'string'],
                    'verification' => ['type' => 'string']
                ]
            ]
        ],
        'd5_corrective_actions' => [
            'type' => 'array',
            'items' => [
                'type' => 'object',
                'properties' => [
                    'action' => ['type' => 'string'],
                    'responsible_person' => ['type' => 'string'],
                    'target_date' => ['type' => 'string'],
                    'status' => ['type' => 'string']
                ]
            ]
        ],
        'status' => ['type' => 'string']
    ],
    'required' => ['report_id', 'customer_name', 'd2_problem'],
    'additionalProperties' => false
];

$response = $client->post('/v1/messages', [
    'json' => [
        'model' => 'claude-sonnet-4-5',
        'max_tokens' => 2048,
        'messages' => [
            [
                'role' => 'user',
                'content' => $report_text
            ]
        ],
        'output_format' => [
            'type' => 'json_schema',
            'schema' => $schema
        ]
    ]
]);

$body = json_decode($response->getBody(), true);
$report = json_decode($body['content'][0]['text'], true);

echo "=== 8D 報告資訊 ===\n\n";
echo "報告編號:" . $report['report_id'] . "\n";
echo "客戶:" . $report['customer_name'] . "\n";
echo "產品:" . $report['product_name'] . "\n\n";

echo "D1 團隊組成:\n";
echo "組長:" . $report['d1_team']['leader'] . "\n";
echo "成員:" . implode('、', $report['d1_team']['members']) . "\n\n";

echo "D2 問題描述:\n";
echo "缺陷類型:" . $report['d2_problem']['defect_type'] . "\n";
echo "詳細說明:" . $report['d2_problem']['defect_detail'] . "\n";
echo "影響數量:" . $report['d2_problem']['affected_quantity'] . " 張\n\n";

echo "D4 根本原因:\n";
foreach ($report['d4_root_causes'] as $idx => $cause) {
    echo ($idx + 1) . ". {$cause['category']}: {$cause['description']}\n";
}
echo "\n";

echo "D5 矯正措施:\n";
foreach ($report['d5_corrective_actions'] as $idx => $action) {
    echo ($idx + 1) . ". {$action['action']}\n";
    echo "   負責人:{$action['responsible_person']}\n";
    echo "   完成日:{$action['target_date']}\n";
    echo "   狀態:{$action['status']}\n\n";
}

常見應用場景

資料萃取:從PDF掃描件提取8D資訊

從非結構化的手寫或掃描的異常報告中萃取結構化資料。系統會自動確保所有必要資料段都有值而且類型正確:

<?php
require 'vendor/autoload.php';

use GuzzleHttp\Client;

$client = new Client([
    'base_uri' => 'https://api.anthropic.com',
    'headers' => [
        'Content-Type' => 'application/json',
        'x-api-key' => getenv('ANTHROPIC_API_KEY'),
        'anthropic-version' => '2023-06-01',
        'anthropic-beta' => 'structured-outputs-2025-11-13'
    ]
]);

$scanned_text = <<<EOT
異常報告單
公司:戴伊爾斯印刷工業
報告編號: QA-2025-1123
機台: 第5號印刷機
操作員: Posen
班別: 早班
缺陷類型: 墨點
不良數量: 350
矯正措施:
1. 清潔墨槽
2. 更換過濾網
3. 調整油墨粘度
EOT;

$schema = [
    'type' => 'object',
    'properties' => [
        'company_name' => ['type' => 'string'],
        'report_number' => ['type' => 'string'],
        'defect_category' => ['type' => 'string'],
        'machine_number' => ['type' => 'string'],
        'operator_name' => ['type' => 'string'],
        'shift' => ['type' => 'string'],
        'defect_count' => ['type' => 'integer'],
        'corrective_actions' => [
            'type' => 'array',
            'items' => ['type' => 'string']
        ]
    ],
    'required' => ['report_number', 'defect_category', 'defect_count'],
    'additionalProperties' => false
];

$response = $client->post('/v1/messages', [
    'json' => [
        'model' => 'claude-sonnet-4-5',
        'max_tokens' => 1024,
        'messages' => [
            [
                'role' => 'user',
                'content' => "從這份掃描的異常單提取資料:\n\n" . $scanned_text
            ]
        ],
        'output_format' => [
            'type' => 'json_schema',
            'schema' => $schema
        ]
    ]
]);

$body = json_decode($response->getBody(), true);
$result = json_decode($body['content'][0]['text'], true);

echo "公司:{$result['company_name']}\n";
echo "報告編號:{$result['report_number']}\n";
echo "缺陷類別:{$result['defect_category']}\n";
echo "機台:{$result['machine_number']}\n";
echo "操作員:{$result['operator_name']}\n";
echo "班別:{$result['shift']}\n";
echo "不良數:{$result['defect_count']}\n";
echo "矯正措施:\n";
foreach ($result['corrective_actions'] as $idx => $action) {
    echo "  " . ($idx + 1) . ". $action\n";
}

內容分類:缺陷類型自動分類

對印刷品質問題進行分類然後輸出標準化的分類結果,包含類別、嚴重程度、建議對策等:

<?php
require 'vendor/autoload.php';

use GuzzleHttp\Client;

$client = new Client([
    'base_uri' => 'https://api.anthropic.com',
    'headers' => [
        'Content-Type' => 'application/json',
        'x-api-key' => getenv('ANTHROPIC_API_KEY'),
        'anthropic-version' => '2023-06-01',
        'anthropic-beta' => 'structured-outputs-2025-11-13'
    ]
]);

$defect_description = '戴伊爾斯印刷工業大量產品出現局部掉墨現象,那個影響外觀品質,客戶已經反應多次';

$schema = [
    'type' => 'object',
    'properties' => [
        'primary_category' => [
            'type' => 'string',
            'enum' => ['色差', '套印不準', '紙張問題', '墨點', '鬼影', '刮痕', '其他']
        ],
        'severity_level' => [
            'type' => 'string',
            'enum' => ['輕微', '中等', '嚴重', '致命']
        ],
        'suggested_department' => ['type' => 'string'],
        'estimated_cost_impact' => ['type' => 'number'],
        'priority_score' => ['type' => 'integer'],
        'recommended_actions' => [
            'type' => 'array',
            'items' => ['type' => 'string']
        ]
    ],
    'required' => ['primary_category', 'severity_level', 'priority_score'],
    'additionalProperties' => false
];

$response = $client->post('/v1/messages', [
    'json' => [
        'model' => 'claude-sonnet-4-5',
        'max_tokens' => 1024,
        'messages' => [
            [
                'role' => 'user',
                'content' => "分類這個品質問題:" . $defect_description
            ]
        ],
        'output_format' => [
            'type' => 'json_schema',
            'schema' => $schema
        ]
    ]
]);

$body = json_decode($response->getBody(), true);
$classification = json_decode($body['content'][0]['text'], true);

echo "=== 品質問題分類結果 ===\n\n";
echo "缺陷類別:{$classification['primary_category']}\n";
echo "嚴重程度:{$classification['severity_level']}\n";
echo "優先分數:{$classification['priority_score']}\n";
echo "建議部門:{$classification['suggested_department']}\n";
echo "預估成本影響:NT$ " . number_format($classification['estimated_cost_impact']) . "\n\n";
echo "建議措施:\n";
foreach ($classification['recommended_actions'] as $idx => $action) {
    echo "  " . ($idx + 1) . ". $action\n";
}

API回應格式化:8D報告查詢API

產生符合要求 API 規格的回應格式確保下游系統可以正確解析:

<?php
require 'vendor/autoload.php';

use GuzzleHttp\Client;

$client = new Client([
    'base_uri' => 'https://api.anthropic.com',
    'headers' => [
        'Content-Type' => 'application/json',
        'x-api-key' => getenv('ANTHROPIC_API_KEY'),
        'anthropic-version' => '2023-06-01',
        'anthropic-beta' => 'structured-outputs-2025-11-13'
    ]
]);

$filters = [
    'company' => '戴伊爾斯印刷工業',
    'month' => '2025-11',
    'defect_type' => '色差',
    'status' => '已結案'
];

$schema = [
    'type' => 'object',
    'properties' => [
        'status' => ['type' => 'string'],
        'total_records' => ['type' => 'integer'],
        'data' => [
            'type' => 'array',
            'items' => [
                'type' => 'object',
                'properties' => [
                    'report_id' => ['type' => 'string'],
                    'customer' => ['type' => 'string'],
                    'defect_type' => ['type' => 'string'],
                    'status' => ['type' => 'string'],
                    'created_date' => ['type' => 'string']
                ]
            ]
        ],
        'filters_applied' => ['type' => 'object']
    ],
    'required' => ['status', 'total_records', 'data'],
    'additionalProperties' => false
];

$response = $client->post('/v1/messages', [
    'json' => [
        'model' => 'claude-sonnet-4-5',
        'max_tokens' => 2048,
        'messages' => [
            [
                'role' => 'user',
                'content' => "查詢符合以下條件的8D報告:\n" . json_encode($filters, JSON_UNESCAPED_UNICODE)
            ]
        ],
        'output_format' => [
            'type' => 'json_schema',
            'schema' => $schema
        ]
    ]
]);

$body = json_decode($response->getBody(), true);
$api_response = json_decode($body['content'][0]['text'], true);

header('Content-Type: application/json; charset=utf-8');
echo json_encode($api_response, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);

Agent工作流程:自動化8D報告處理系統

在建構 Agent 系統時嚴格工具模式可以保證工具參數類型正確不會因為參數錯誤導致整個流程失敗:

<?php
require 'vendor/autoload.php';

use GuzzleHttp\Client;

$client = new Client([
    'base_uri' => 'https://api.anthropic.com',
    'headers' => [
        'Content-Type' => 'application/json',
        'x-api-key' => getenv('ANTHROPIC_API_KEY'),
        'anthropic-version' => '2023-06-01',
        'anthropic-beta' => 'structured-outputs-2025-11-13'
    ]
]);

$user_message = '緊急:戴伊爾斯印刷工業客戶反應最新一批餅乾外盒顏色太淡,訂單號碼 PO-20251115-001,數量 12000張需要重印';

$response = $client->post('/v1/messages', [
    'json' => [
        'model' => 'claude-sonnet-4-5',
        'max_tokens' => 2048,
        'messages' => [
            [
                'role' => 'user',
                'content' => $user_message
            ]
        ],
        'tools' => [
            [
                'name' => 'create_8d_report',
                'description' => '建立新的8D品質異常報告',
                'strict' => true,
                'input_schema' => [
                    'type' => 'object',
                    'properties' => [
                        'customer_id' => ['type' => 'string'],
                        'defect_type' => [
                            'type' => 'string',
                            'enum' => ['色差', '套印不準', '紙張皺摺', '墨點', '鬼影', '刮痕']
                        ],
                        'severity' => [
                            'type' => 'integer',
                            'enum' => [1, 2, 3, 4, 5]
                        ],
                        'affected_qty' => ['type' => 'integer'],
                        'description' => ['type' => 'string']
                    ],
                    'required' => ['customer_id', 'defect_type', 'severity'],
                    'additionalProperties' => false
                ]
            ],
            [
                'name' => 'notify_quality_team',
                'description' => '通知品質團隊處理異常',
                'strict' => true,
                'input_schema' => [
                    'type' => 'object',
                    'properties' => [
                        'report_id' => ['type' => 'string'],
                        'urgency' => [
                            'type' => 'string',
                            'enum' => ['低', '中', '高', '緊急']
                        ],
                        'notify_list' => [
                            'type' => 'array',
                            'items' => ['type' => 'string']
                        ],
                        'message' => ['type' => 'string']
                    ],
                    'required' => ['report_id', 'urgency'],
                    'additionalProperties' => false
                ]
            ],
            [
                'name' => 'schedule_rework',
                'description' => '排程重工作業',
                'strict' => true,
                'input_schema' => [
                    'type' => 'object',
                    'properties' => [
                        'order_number' => ['type' => 'string'],
                        'rework_quantity' => ['type' => 'integer'],
                        'target_completion' => [
                            'type' => 'string',
                            'format' => 'date'
                        ],
                        'priority' => [
                            'type' => 'integer',
                            'enum' => [1, 2, 3]
                        ],
                        'assigned_machine' => ['type' => 'string']
                    ],
                    'required' => ['order_number', 'rework_quantity'],
                    'additionalProperties' => false
                ]
            ]
        ]
    ]
]);

$body = json_decode($response->getBody(), true);

echo "=== Claude Agent 回應 ===\n\n";

foreach ($body['content'] as $content) {
    if ($content['type'] === 'text') {
        echo $content['text'] . "\n\n";
    }
    
    if ($content['type'] === 'tool_use') {
        echo "--- 執行工具:{$content['name']} ---\n";
        echo "工具ID:{$content['id']}\n";
        echo "參數:\n";
        print_r($content['input']);
        echo "\n";
        
        // 這裡可以實際執行工具調用
        switch ($content['name']) {
            case 'create_8d_report':
                $report_id = 'RPT-' . date('Ymd') . '-' . rand(1000, 9999);
                echo "✓ 已建立8D報告,編號:$report_id\n\n";
                break;
                
            case 'notify_quality_team':
                echo "✓ 已通知品質團隊(Sean Liu 等人)\n\n";
                break;
                
            case 'schedule_rework':
                echo "✓ 重工作業已排程\n\n";
                break;
        }
    }
}

錯誤處理範例

在實際應用中那個需要妥善處理各種錯誤情況:

<?php
require 'vendor/autoload.php';

use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;

$client = new Client([
    'base_uri' => 'https://api.anthropic.com',
    'headers' => [
        'Content-Type' => 'application/json',
        'x-api-key' => getenv('ANTHROPIC_API_KEY'),
        'anthropic-version' => '2023-06-01',
        'anthropic-beta' => 'structured-outputs-2025-11-13'
    ],
    'timeout' => 60
]);

$prompt = '提取戴伊爾斯印刷工業 2025年11月的報告資料...';
$schema = [
    'type' => 'object',
    'properties' => [
        'report_id' => ['type' => 'string'],
        'customer' => ['type' => 'string']
    ],
    'required' => ['report_id'],
    'additionalProperties' => false
];

$max_retries = 3;
$retry_count = 0;

while ($retry_count < $max_retries) {
    try {
        $response = $client->post('/v1/messages', [
            'json' => [
                'model' => 'claude-sonnet-4-5',
                'max_tokens' => 2048,
                'messages' => [
                    ['role' => 'user', 'content' => $prompt]
                ],
                'output_format' => [
                    'type' => 'json_schema',
                    'schema' => $schema
                ]
            ]
        ]);
        
        $body = json_decode($response->getBody(), true);
        
        // 檢查 stop_reason
        if ($body['stop_reason'] === 'max_tokens') {
            echo "警告:回應被截斷,需要增加 max_tokens\n";
            // 可以選擇重試或處理
        }
        
        if ($body['stop_reason'] === 'refusal') {
            echo "錯誤:Claude 拒絕了請求(安全原因)\n";
            break;
        }
        
        $result = json_decode($body['content'][0]['text'], true);
        
        echo "成功提取資料:\n";
        print_r($result);
        
        break; // 成功就跳出迴圈
        
    } catch (ClientException $e) {
        $status_code = $e->getResponse()->getStatusCode();
        $error_body = json_decode($e->getResponse()->getBody(), true);
        
        if ($status_code === 400) {
            // Schema 驗證錯誤
            echo "Schema 錯誤:" . $error_body['error']['message'] . "\n";
            break; // Schema 錯誤不需要重試
        }
        
        if ($status_code === 429) {
            // Rate limit,使用指數退避重試
            $retry_count++;
            if ($retry_count < $max_retries) {
                $wait_time = pow(2, $retry_count);
                echo "達到速率限制,等待 {$wait_time} 秒後重試...\n";
                sleep($wait_time);
                continue;
            } else {
                echo "已達最大重試次數\n";
                break;
            }
        }
        
        // 其他錯誤
        echo "API 錯誤:" . $e->getMessage() . "\n";
        break;
        
    } catch (Exception $e) {
        echo "系統錯誤:" . $e->getMessage() . "\n";
        error_log("Claude API 錯誤: " . $e->getMessage());
        break;
    }
}

批次處理範例:處理多份報告

當需要一次處理大量報告時可以用迴圈批次執行:

<?php
require 'vendor/autoload.php';

use GuzzleHttp\Client;

$client = new Client([
    'base_uri' => 'https://api.anthropic.com',
    'headers' => [
        'Content-Type' => 'application/json',
        'x-api-key' => getenv('ANTHROPIC_API_KEY'),
        'anthropic-version' => '2023-06-01',
        'anthropic-beta' => 'structured-outputs-2025-11-13'
    ]
]);

$schema = [
    'type' => 'object',
    'properties' => [
        'report_id' => ['type' => 'string'],
        'customer' => ['type' => 'string'],
        'defect_type' => ['type' => 'string'],
        'affected_quantity' => ['type' => 'integer']
    ],
    'required' => ['report_id', 'customer', 'defect_type'],
    'additionalProperties' => false
];

// 假設有多份報告要處理
$report_files = [
    '異常報告-20251101.txt',
    '異常報告-20251102.txt',
    '異常報告-20251103.txt'
];

$results = [];

foreach ($report_files as $file) {
    echo "處理檔案:$file\n";
    
    $content = file_get_contents($file);
    
    try {
        $response = $client->post('/v1/messages', [
            'json' => [
                'model' => 'claude-sonnet-4-5',
                'max_tokens' => 1024,
                'messages' => [
                    [
                        'role' => 'user',
                        'content' => "從這份戴伊爾斯印刷工業的報告提取8D資料:\n\n" . $content
                    ]
                ],
                'output_format' => [
                    'type' => 'json_schema',
                    'schema' => $schema
                ]
            ]
        ]);
        
        $body = json_decode($response->getBody(), true);
        $data = json_decode($body['content'][0]['text'], true);
        
        $results[] = $data;
        
        echo "✓ 成功提取:{$data['report_id']}\n\n";
        
        // 避免觸發 rate limit
        sleep(1);
        
    } catch (Exception $e) {
        echo "✗ 錯誤:" . $e->getMessage() . "\n\n";
    }
}

// 將結果存成 JSON 檔案
file_put_contents(
    'extracted_reports_' . date('Ymd') . '.json',
    json_encode($results, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)
);

echo "處理完成!共提取 " . count($results) . " 份報告\n";

重要注意事項

效能考量

第一次執行你所設定的自定義 JSON 資料綱要由於需要進行語法解析所以會有額外的延遲,不過編譯好的JSON綱要會被 Claude快取 24小時所以之後的請求就會快很多。

Token成本

使用結構化輸出時Claude會自動收到一個說明輸出格式的系統提示所以你的輸入 token 數會稍微增加一點點。

目前在 JSON Schema 上的限制

結構化輸出支援大部分標準 JSON Schema 功能但有一些限制:

支援的功能

  • 所有基本類型:object、array、string、integer、number、boolean、null
  • enum(只支援字串、數字、布林值)
  • anyOf 和 allOf
  • $ref 和 definitions
  • 字串格式:date-time、email、uuid 等

不支援的功能

  • 遞迴架構
  • 數值限制(minimum、maximum等)
  • 字串長度限制
  • 複雜的陣列限制

如果使用了不支援的功能會收到 400錯誤

特殊情況處理

  • 拒絕回應:如果 Claude因為安全原因拒絕請求回應可能不符合架構。這種情況下 stop_reason 會是 “refusal”
  • Token限制:如果回應因為達到max_tokens而被截斷輸出可能不完整。這時後需要增加 max_tokens重試

功能相容性

可以一起使用

  • 批次處理(享50%折扣):適合大量歷史8D報告分析
  • Token計算
  • 串流輸出
  • JSON輸出和 Strict too use可以同時使用

不相容

  • Citations(引用功能)
  • Message Prefilling(訊息預填)

如何讓快取的 JSON Schema 失效?

快取機制的基本運作

Claude API 的結構化輸出使用「文法編譯」技術。當你第一次使用一個特定的 schema 時,會有額外的延遲因為系統需要編譯文法。編譯完成後,這個文法會被快取 24 小時(從最後一次使用開始計算),讓後續請求更快。

會導致快取失效的情況

根據官方文件,快取會在以下情況失效:

情況一:改變 JSON schema 結構

任何對 JSON schema 結構的改變都會讓快取失效。這包括:

  • 新增或刪除 properties(欄位)
  • 修改欄位的類型
  • 改變 required 陣列
  • 修改 enum 的選項
  • 改變巢狀物件的結構
  • 修改 additionalProperties 設定
  • 任何其他會改變 schema 結構的修改

比如說戴伊爾斯印刷工業原本的 schema 有 report_idcustomer_namedefect_type 三個欄位,後來 Sean Liu 在 2025年11月決定新增 affected_quantityseverity 兩個欄位,這樣的修改會讓快取失效,下次請求時需要重新編譯。

情況二:改變工具集合

當同時使用結構化輸出和工具使用(tool use)時,改變請求中的工具集合會讓快取失效。這包括:

  • 新增工具
  • 刪除工具
  • 修改任何工具的 input_schema
  • 改變工具的順序

例如戴伊爾斯印刷工業系統原本有 create_8d_reportnotify_quality_team 兩個工具,後來新增了 schedule_rework 工具,這個改變會讓快取失效。

情況三:改變 output_format 參數會影響 Prompt Cache

  • 如果你同時使用結構化輸出和 prompt caching 功能,改變 output_format 參數會讓該對話串的 prompt cache 失效。
  • Prompt cache 是用來快取 system prompt 或長篇 context 的功能。當你修改 output_format 中的 schema 時,prompt cache 會失效,這表示原本快取的內容需要重新處理。

不會讓快取失效的情況

只修改 name 或 description 欄位

文件明確指出:只改變 namedescription 欄位不會讓快取失效。

這表示你可以修改:

  • 工具的 name
  • 工具的 description
  • Schema properties 的 description

而不會觸發重新編譯。

例如 Sean Liu 可以把工具描述從「建立報告」改成「為戴伊爾斯印刷工業建立符合 ISO 標準的 8D 品質異常報告」,這樣的修改不會影響快取。

24 小時快取週期

  • 快取的有效期是 24 小時,從最後一次使用開始計算。
  • 這表示如果戴伊爾斯印刷工業在 2025年11月15日上午 10:00 使用了某個 schema,然後在下午 3:00 又使用一次,快取的過期時間會更新到 11月16日下午 3:00,而不是 11月16日上午 10:00。
  • 對於經常使用的 schema,只要每天都有使用,快取就會持續有效。

戴伊爾斯印刷工業實際應用案例

自動化品質報告生成

戴伊爾斯印刷工業每天需要處理 20-30 份異常報告以前需要品管人員手動整理成標準8D格式,那個現在用 Claude API 自動萃取關鍵資訊然後產生符合 ISO 標準得報告大幅減少人工作業時間。負責這個專案的 Sean Liu 表示,導入系統後那個報告處理時間從平均 30分鐘縮短到 5分鐘

歷史數據分析

透過批次處理功能一次性分析從 2025年11月開始累積的 2000+ 份8D報告就是說找出最常發生的缺陷類型、根本原因趨勢、矯正措施有效性等協助持續改善

即時品質預警系統

結合 Strict tool use建構自動化工作流程當品檢站發現異常時系統自動:

  • 提取異常資訊並分類
  • 根據嚴重程度通知相關人員(包括 Sean Liu 等品管團隊)
  • 自動建立8D報告草稿
  • 排程重工作業
  • 追蹤矯正措施執行進度

實作心得

  • 結構化輸出功能讓 Claude API的輸出變得更可靠、更容易整合到生產環境中,對於戴伊爾斯印刷工業這樣的製造業來說不管是8D報告處理、品質資料分析還是建構自動化品管系統都能確保輸出格式正確大幅減少錯誤處理得複雜度
  • 目前這個功能還在公開測試階段,支援 Claude Sonnet 4.5 和 Claude Opus 4.1,如果你想測試需要在 API 請求中加入 anthropic-beta Header參數並將值設定為 structured-outputs-2025-11-13

Leave a Comment

Your email address will not be published. Required fields are marked *