Structured Output
Tool use with a JSON schema is the reliable way to guarantee syntactically valid, schema-shaped output from Claude — but it prevents syntax errors, not semantic ones.
When a downstream system needs machine-readable output, prompting Claude to *return JSON* is fragile — the model can still emit malformed or drifting structures. The reliable approach is tool use: you define a tool whose input_schema is the shape you want, and Claude returns a tool_use block whose input conforms to that schema. This eliminates JSON syntax errors entirely.
tool_use with a JSON schema > prompt-based 'please return JSON'. The first is guaranteed schema-shaped; the second can produce malformed JSON.
Asking for JSON in the prompt and parsing the reply is fragile — Claude can wrap it in prose, drift the shape, or emit malformed JSON.
messages=[{"role": "user",
"content": "Extract the invoice as JSON."}]
# reply may include prose -> json.loads() throwsForce a named tool whose input_schema is your target shape; Claude returns a tool_use block that is always schema-valid.
tools=[invoice_tool],
tool_choice={"type": "tool",
"name": "extract_invoice"}
# msg.content -> tool_use.input conforms to schemaThe three tool_choice modes
| Mode | Behaviour | When to use |
|---|---|---|
| "auto" | Model decides whether to call a tool or reply with text | Conversational replies are acceptable |
| "any" | Model must call a tool but picks which one | Several schemas, unknown document type |
| {"type":"tool","name":"..."} | Model must call one specific named tool | Mandatory step needing maximum control |
For guaranteed extraction into a single known schema, force the specific tool. Use "any" when the input could match one of several schemas and you want the model to select. Reserve "auto" for cases where a plain text answer is a valid outcome.
import anthropic
client = anthropic.Anthropic()
invoice_tool = {
"name": "extract_invoice",
"description": "Extract structured fields from an invoice.",
"input_schema": {
"type": "object",
"properties": {
"invoice_number": {"type": "string"},
"issue_date": {"type": "string", "description": "ISO 8601 (YYYY-MM-DD)"},
"total": {"type": "number", "description": "Decimal amount, no currency symbol"},
"currency": {"type": "string", "enum": ["USD", "EUR", "GBP", "unclear"]},
"department": {"type": ["string", "null"], "description": "null if absent"}
},
"required": ["invoice_number", "total"]
}
}
msg = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
tools=[invoice_tool],
tool_choice={"type": "tool", "name": "extract_invoice"},
messages=[{"role": "user", "content": document_text}],
)
data = next(b.input for b in msg.content if b.type == "tool_use")Syntax versus semantic errors
A schema constrains structure — field names, types, required keys — so the output is always parseable. It does not guarantee the values are correct. Semantic errors slip through: line items that do not sum to the stated total, a value placed in the wrong field, or a fabricated number for information that was never in the source. Catching those requires external validation logic, covered in the next lesson.
Do not assume tool_use prevents all extraction errors — it stops malformed JSON, not wrong values. And do not confuse tool_choice 'auto' (may reply in text) with 'any' (must call some tool).
Schema design that prevents fabrication
- Optional/nullable fields — make a field nullable when the source may omit it, so the model reports absence instead of inventing a plausible value.
- An 'unclear' enum value — give genuinely ambiguous categoricals an explicit escape hatch instead of forcing a guess.
- 'other' plus a detail string — pair an 'other' enum with a freeform field to capture edge cases without distorting the taxonomy.
- Format normalization in the prompt — state format expectations (ISO 8601 dates, decimal currency) alongside the schema, since the schema alone cannot enforce them.
Making every field required does not improve completeness — it pushes the model to fabricate values. Optional and nullable fields are how you get honest 'not present' answers.
How the exam will try to trick you
The distractors below look right under time pressure — learn the tell.
- The trap
Assume
tool_usewith a JSON schema prevents all extraction errors.Correct answertool_useeliminates JSON syntax errors only — semantic errors (wrong sums, misplaced or fabricated values) still slip through.Why: A schema constrains structure, not the correctness of the values placed in it.
- The trap
Use
tool_choice: "auto"when you need guaranteed structured output.Correct answerUse
"any"(some tool) or a named tool to force a call; reserve"auto"for when a plain text reply is acceptable.Why:
"auto"lets the model answer in text instead of calling a tool, so structured output is not guaranteed. - The trap
Make every schema field required to guarantee complete extraction.
Correct answerMake fields optional or nullable when the source may omit them.
Why: Required fields pressure the model to fabricate plausible values for information that is genuinely absent.
Key takeaways
- Tool use with a JSON schema guarantees schema-shaped output and eliminates JSON syntax errors.
- tool_choice 'auto' allows text; 'any' forces some tool; a named tool forces exactly that one.
- Schemas stop syntax errors but never semantic errors like wrong sums or misplaced values.
- Make fields optional/nullable so Claude reports absence instead of fabricating values.
- Add 'unclear' and 'other + detail' options for ambiguous or edge-case categoricals.
- Put format rules (ISO dates, decimal currency) in the prompt; the schema can't enforce them.
Frequently asked questions
How do you force JSON output from Claude?+
Define a tool whose input_schema is your target JSON shape and set tool_choice to that named tool. Claude returns a tool_use block conforming to the schema, which eliminates malformed JSON far more reliably than asking for JSON in the prompt.
Does tool use guarantee the extracted values are correct?+
No. Tool use guarantees the structure is valid and parseable, but semantic errors — wrong totals, misplaced values, or fabricated fields — still occur. You need external validation and retry logic to catch those.