课程大纲 › 领域 4:提示工程与结构化输出
4.3 用工具调用与 JSON Schema 强制结构化输出
タスクステートメント4.3:ツール使用とJSONスキーマを用いて構造化出力を強制する
官方大纲原文(日文):知识 4 条 · 技能 6 条
需要掌握的知识
- スキーマに準拠した構造化出力を保証し、JSON構文エラーを排除する最も信頼性の高いアプローチとしての、JSONスキーマを用いたツール使用(tool_use)
- tool_choice: "auto"(モデルはツールを呼び出さずにテキストを返す場合がある)、"any"(モデルは必ずツールを呼び出すが、どのツールかは選択できる)、強制的なツール選択(モデルは指定された名前の特定のツールを必ず呼び出す)の違い
- ツール使用による厳密なJSONスキーマは構文エラーを排除するが、意味的なエラー(例:合計と一致しない明細項目、誤ったフィールドに入った値)は防げないこと
- スキーマ設計上の考慮事項:必須フィールドと任意フィールド、拡張可能なカテゴリのための"other"+詳細文字列パターンを伴うenumフィールド
需要能做到的事
- JSONスキーマを入力パラメータとする抽出ツールを定義し、tool_useレスポンスから構造化データを抽出する
- 複数の抽出スキーマが存在し文書の種類が不明な場合に、tool_choice: "any"を設定して構造化出力を保証する
- tool_choice: {"type": "tool", "name": "extract_metadata"}で特定のツールを強制し、エンリッチメントのステップの前に特定の抽出が確実に実行されるようにする
- ソース文書に情報が含まれていない可能性がある場合にスキーマのフィールドを任意(null許容)として設計し、モデルが必須フィールドを満たすために値を捏造するのを防ぐ
- 曖昧なケース用の"unclear"などのenum値や、拡張可能な分類のための"other"+詳細フィールドを追加する
- 一貫しないソースの書式に対処するために、厳密な出力スキーマとあわせて形式の正規化ルールをプロンプトに含める
课程讲解 · AI 草稿,未经人工审核 以官方文档为准
这一条考什么
这一条考你能不能用 tool use + JSON Schema 让 Claude 稳定地输出结构化数据,而不是靠"请只输出 JSON"这类提示词。你要分清 tool_choice 的 auto、any 和强制指定某个工具这三种模式分别在什么场景用,并且知道 schema 能消除语法错误,却挡不住语义错误。另外还要会设计 schema:哪些字段设为可选或可为 null,什么时候加 "other" / "unclear" 这类枚举值。
核心概念
为什么 tool use 是最可靠的结构化输出方式
如果让模型在普通文本里"输出 JSON",常见的问题有:JSON 前后夹着解释文字、少了括号、多了尾逗号、字段名拼错。Prefill 一个 {、用正则从文本里抠 JSON、解析失败就重试,这些办法都只是在补洞。
更可靠的做法是把目标结构定义成一个工具的 input_schema。Claude 会以 tool_use 内容块的形式"调用"这个工具,input 字段就是一个已经解析好的 JSON 对象,SDK 直接返回 dict,应用这边不用再解析文本。如果在工具定义上再开启 strict: true(Structured Outputs),API 会保证 input 严格符合 schema,类型和必填字段都不会出错。
这个"工具"通常并不需要真的去执行,它只是一份数据契约。拿到 tool_use.input 之后,抽取就完成了。
extract_invoice = {
"name": "extract_invoice",
"description": "从发票文本中抽取结构化字段。文档中不存在的信息必须填 null,不要猜测。",
"input_schema": {
"type": "object",
"properties": {
"invoice_number": {"type": "string"},
"invoice_date": {"type": ["string", "null"], "description": "ISO 8601 日期 YYYY-MM-DD"},
"vendor_name": {"type": "string"},
"po_number": {"type": ["string", "null"]},
"category": {
"type": "string",
"enum": ["hardware", "software", "services", "other", "unclear"]
},
"category_detail": {"type": ["string", "null"], "description": "category 为 other 时填写具体类别"},
"stated_total": {"type": "number"}
},
"required": ["invoice_number", "vendor_name", "category", "stated_total",
"invoice_date", "po_number", "category_detail"]
}
}tool_choice 的三种模式
{"type": "auto"}(默认):模型自己决定调不调工具,也可能只回一段文本。用在对话型 Agent 上没问题,但如果你的流水线必须拿到结构化结果,auto就不能保证。{"type": "any"}:模型必须调用某个工具,但可以从提供的多个工具里自己挑。典型场景是有好几种抽取 schema(发票、合同、收据),事先又不知道文档属于哪类。这样设置既保证一定有结构化输出,又让模型根据内容选对 schema。{"type": "tool", "name": "extract_metadata"}:强制调用指定的那个工具。适合流水线里有严格先后顺序的情况,比如必须先跑extract_metadata,后面的 enrichment 步骤才能继续。- 还有一种
{"type": "none"},禁止调用工具。
注意:用 any 或强制指定工具时,API 会预填 assistant 回合直接进入工具调用,所以模型在 tool_use 之前不会先输出自然语言的推理。另外这两种模式和 extended thinking 不能同时用。
schema 只能消除语法错误,挡不住语义错误
严格 schema 能保证"类型对、必填字段都在、JSON 合法",但下面这些错误它管不了:
- 明细行金额加起来和
stated_total不相等; - 把收货地址填到了账单地址字段里;
- 把文档里根本没写的值硬编出来;
- 日期格式不一致,比如 "03/04/2024" 到底是几月几号。
这类语义问题要靠提示词里的规则、few-shot 示例和下游的业务校验(见 4.4)来解决。考试里只要题干出现"JSON 已经合法,但值不对",答案就不是"换更严格的 schema"。
schema 设计:必填 / 可选、可为 null、枚举的扩展位
- 必填字段会诱导模型编造值。 文档里可能没有的信息(PO 号、到期日)如果设成必填且不允许 null,模型为了满足约束只能编一个出来。正确做法是允许
null("type": ["string", "null"]),并且在描述或提示词里写明"文档中没有就填 null"。 - `"other"` + 详情字段。 分类不是封闭集合时,在枚举里加
"other",再配一个category_detail字符串,这样新类别不会被硬塞进最接近的旧类别,也方便以后扩充枚举。 - `"unclear"`。 用于文档本身含糊、模型判断不了的情况。比起逼模型二选一,这样做更诚实,下游也能把这类结果路由给人工审核。
- 在提示词里写格式规范化规则。 来源格式五花八门时("$1,200"、"1200.00 USD"、"一千二百元"),schema 只规定
number,转换规则写在提示词里,例如"金额统一转为不带千分位的数字;日期统一为 YYYY-MM-DD;无法确定日月顺序时填 null"。
正确做法与反模式
- 正确:把输出结构定义成工具的
input_schema,从响应的tool_use块里读input。反模式:在提示词里要求"只输出合法 JSON",再用正则或重试兜底。 - 正确:文档类型未知、有多套 schema 时用
tool_choice: {"type": "any"}。反模式:用auto,结果模型有时只回一段文字说明。 - 正确:某一步必须先执行时,用
{"type": "tool", "name": "..."}强制。反模式:靠提示词里"请先调用 extract_metadata"来祈祷顺序正确。 - 正确:可能缺失的字段允许 null,并明确告诉模型缺失时填 null。反模式:全部设为必填,导致模型捏造。
- 正确:枚举里提供
"other"+ 详情字段和"unclear"。反模式:封闭枚举强迫模型把新类别归到最像的旧值上。 - 正确:严格 schema 加上提示词里的规范化规则,再配下游语义校验。反模式:以为 schema 合法就代表数据正确。
常见陷阱
- "Prefill `{`"或"加强 JSON 指令 + 重试":看起来能降低格式错误率,但都没有 schema 级的保证。只要选项里有"定义带 schema 的工具",一般选它。
- 把可选字段改成必填"以保证完整性":这正是模型捏造值的根源。题干里出现"模型为缺失信息编造了值"时,答案是改成可选或允许 null,再配合提示词说明。
- 用 `auto` 期望一定拿到结构化输出:
auto允许纯文本回答。题干强调"必须保证结构化输出"、"文档类型未知"时选any;强调"某个抽取必须先执行"时选强制指定工具。 - "schema 更严格就能解决值不对的问题":明细之和不等于总额、字段错位属于语义错误,schema 管不了,要靠校验和反馈循环。
- "temperature 设为 0 / 换更大的模型":这类选项通常不针对根因。格式不一致问题,答案多半是规范化规则或 few-shot 示例。
- 不断往封闭枚举里加新值:维护成本高,而且总会漏掉新值。开放域的分类用
"other"+ 详情字段,或者改用自由字符串再在后处理里归一化。
实操练习
目标:对比 auto 和 any 的差别,并验证可为 null 的字段能阻止模型捏造值。
- 安装 SDK:
pip install anthropic,并设置ANTHROPIC_API_KEY。 - 准备两份 schema(发票、合同),以及一段不含 PO 号的发票文本。
- 分别用
tool_choice为auto和any各跑 5 次,统计拿到tool_use的次数。 - 检查
po_number是否为None。
import anthropic
client = anthropic.Anthropic()
invoice_tool = {
"name": "extract_invoice",
"description": "抽取发票字段。文档没有的信息填 null,不要猜测。",
"input_schema": {
"type": "object",
"properties": {
"invoice_number": {"type": "string"},
"po_number": {"type": ["string", "null"]},
"total": {"type": "number"},
"category": {"type": "string", "enum": ["hardware", "software", "services", "other", "unclear"]},
"category_detail": {"type": ["string", "null"]},
},
"required": ["invoice_number", "po_number", "total", "category", "category_detail"],
},
}
contract_tool = {
"name": "extract_contract",
"description": "抽取合同的当事方与生效日期。",
"input_schema": {
"type": "object",
"properties": {
"parties": {"type": "array", "items": {"type": "string"}},
"effective_date": {"type": ["string", "null"]},
},
"required": ["parties", "effective_date"],
},
}
doc = "INVOICE #A-1029\nVendor: Acme Ltd\nItem: 3D printer filament x 20\nTotal: $1,240.00"
system = "金额统一转为数字(去掉货币符号与千分位)。日期统一为 YYYY-MM-DD。"
for mode in ({"type": "auto"}, {"type": "any"}):
hits = 0
for _ in range(5):
resp = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
system=system,
tools=[invoice_tool, contract_tool],
tool_choice=mode,
messages=[{"role": "user", "content": f"请处理这份文档:\n{doc}"}],
)
blocks = [b for b in resp.content if b.type == "tool_use"]
if blocks:
hits += 1
print(mode["type"], blocks[0].name, blocks[0].input)
print(mode["type"], "tool_use 次数:", hits, "/ 5", "stop_reason 示例:", resp.stop_reason)预期结果:any 模式下 5 次都返回 tool_use,stop_reason 是 "tool_use",并且选中的是 extract_invoice;po_number 为 None;total 为 1240.0。再把 tool_choice 改成 {"type": "tool", "name": "extract_contract"},观察即使文档内容不匹配,模型也会被迫调用这个工具,体会强制模式的适用边界。
自测清单
- 为什么说 tool use + JSON Schema 比"提示词要求 JSON + 正则解析"更可靠?它到底保证了什么?
auto、any、{"type":"tool","name":...}分别适合什么场景?文档类型未知时选哪个?- 举出两个严格 schema 防不住的语义错误,说说应该用什么手段处理。
- 什么样的字段应该允许 null?把它设成必填会带来什么后果?
"other"+ 详情字段和"unclear"各解决什么问题?- 来源格式不统一时,规范化规则应该写在 schema 里还是提示词里?为什么?
对应题目(9)
题目归类由 AI 判断,仅供参考。
练习这 9 道题