Skip to content

Enforce Structured Output Using Tool Use and JSON Schemas

This module is about making Claude return machine-readable structured data reliably.

The exam’s core idea:

If you need guaranteed structured output: 
- Do not just ask Claude to “return JSON”.
- Use tool use with a JSON schema.
- Configure tool_choice so Claude must call the tool.

Current Anthropic docs describe structured outputs as schema-constrained responses that avoid malformed JSON, missing required fields, inconsistent data types, and schema violations.

They also distinguish JSON outputs for final response formatting from strict tool use for validated tool inputs. Focus on the tool-use path: define an extraction tool with an input_schema, then read the structured data from Claude’s tool_use.input. (Claude)


1. Core Mental Model

There are three levels of reliability:

Approach Reliability Problem
“Return JSON” in prose Low to medium Claude may add text, malformed JSON, wrong fields
JSON schema in prompt only Medium Better, but still not guaranteed
Tool use with JSON schema + forced/required tool call Highest Output is emitted as structured tool input

Exam shortcut:

Need parseable structured output? Use tool_use.
Need a tool call no matter what? Use tool_choice.
Need exact schema compliance? Use strict schema/tool configuration.
Need semantic correctness? Add validation and prompt rules.

Anthropic’s tool docs say that with user-defined tools, you provide a tool with an input_schema, Claude returns a tool_use block, and your application reads the structured input object from that block. (Claude Platform Docs)


2. Why Tool Use Beats “Return JSON”

Weak prompt:

Extract the invoice data and return JSON.

Possible problems:

Claude adds a preamble.
JSON has trailing commas.
Field names vary.
Dates are inconsistently formatted.
Required fields are missing.
Numbers are strings.

2.1 Stronger Approach

Define a tool:

extract_invoice

With a JSON schema for the fields you need. Then force Claude to call that tool.

Result:

{
  "type": "tool_use",
  "name": "extract_invoice",
  "input": {
    "invoice_number": "INV-1042",
    "invoice_date": "2026-06-01",
    "currency": "USD",
    "total_amount": 1299.5
  }
}

You parse JSON input, not free-form assistant text.

Anthropic’s docs explicitly warn that without structured outputs, even careful prompting can produce invalid JSON syntax, missing required fields, inconsistent data types, and schema violations. Structured outputs address these through constrained decoding. (Claude)


3. Extracting Data from the tool_use Response

With tool use, the structured result is inside the assistant response content block.

for block in response.content:
    if block.type == "tool_use" and block.name == "extract_invoice":
        invoice_data = block.input
        break

if invoice_data is None:
    raise RuntimeError("Claude did not call extract_invoice")

print(invoice_data)

Heuristic:

Do not parse assistant prose. Parse the tool_use.input.

Anthropic’s tool docs show that model responses with tools include tool_use blocks containing a name and an input object. (Claude Platform Docs)


4. tool_choice: auto Vs any Vs tool

This is one of the highest-yield parts.

tool_choice Meaning Use
{"type": "auto"} Claude may call a tool or may answer in text Use when tool use is optional
{"type": "any"} Claude must call one of the provided tools Use when structured output is required but document type is unknown
{"type": "tool", "name": "extract_metadata"} Claude must call that specific tool Use when a specific extraction must happen
{"type": "none"} Claude cannot call tools Not central to this exam task

Anthropic’s docs define these exact tool-choice modes and note that auto is the default when tools are provided, while any requires one of the tools and tool forces a specific named tool. (Claude Platform Docs)

4.1 auto: Model May Return Text

tool_choice = {"type": "auto"}

Use when:

Tool use is optional.
A normal text answer is acceptable.
Claude should decide whether extraction is needed.

Risk:

Claude may answer in text instead of calling the extraction tool.

For that, do not use auto.

4.2 any: Must Call One of Several Tools

Use any when you have multiple extraction schemas and the document type is unknown.

Example:

tools = [extract_invoice_tool, extract_receipt_tool, extract_contract_tool]

tool_choice={"type": "any"},

Why this is correct:

The application requires structured output.
The document type is unknown.
Claude must choose one extraction schema.

Heuristic:

tool_choice: "any" guarantees a tool call, not the correct tool.

So the available tools must be scoped carefully and have strong descriptions.

4.3 Forced Tool Selection

Use forced tool selection when a specific step must run.

tool_choice = {"type": "tool", "name": "extract_metadata"}

Example workflow:

1. Force extract_metadata.
2. Read metadata from tool_use.input.
3. Then run enrichment or classification using the extracted metadata.

Use forced selection when:

A required first step must happen.
A pipeline needs a specific schema.
Downstream logic depends on that exact extraction.
You do not want Claude to choose another tool or answer in prose.

5. Strict Schema Compliance Does Not Guarantee Semantic Correctness

This is the most important conceptual trap. Strict schemas eliminate syntax and schema errors such as:

- Malformed JSON
- Missing required fields
- Wrong type
- Extra fields when additionalProperties is false
- Invalid enum value

But they do not guarantee semantic correctness. Meaning, schema-valid but semantically wrong output:

{
  "subtotal": 100.00,
  "tax": 8.00,
  "shipping": 5.00,
  "total": 200.00
}

This is valid JSON and matches the schema, but:

100 + 8 + 5 != 200

Other semantic errors:

Line items do not sum to total.
Invoice date is actually due date.
Customer name appears in vendor field.
Currency inferred incorrectly.
Ambiguous category forced into the wrong enum.
Missing field fabricated to satisfy a required field.

Current docs state that strict/structured outputs guarantee schema compliance, type safety, and fewer parsing errors; they do not claim that the extracted values are semantically correct. Semantic validation remains your application’s responsibility. (Claude)

5.1 Complexity Limits of Strict Mode

Strict schemas also have practical complexity limits. Very deep nesting or very large schemas may not be fully enforceable in strict mode. The practical workaround is to split or flatten the schema rather than relying on one giant nested schema. This reinforces the point above: strict mode reliably eliminates syntax/schema errors, but it is neither a guarantee of semantic correctness nor a license to model arbitrarily complex schemas.

If the scenario says:

The JSON is valid, but the values are wrong.

Best answer is not:

Make the schema stricter only.

Better answer:

Add semantic validation, evidence fields, normalization rules, and post-processing checks.


6. Required Vs Optional Vs Nullable Fields

The exam specifically calls out optional/nullable fields.

6.1 Bad Schema

{
  "type": "object",
  "properties": {
    "termination_date": { 
        "type": "string",
        "description": "Termination date in format dd-mm-yyyy" 
    }
  },
  "required": ["termination_date"]
}

Problem:

Many contracts do not contain a termination date.
If the field is required and non-nullable, Claude may fabricate a value.

6.2 Better Schema: Required but Nullable

{
  "type": "object",
  "properties": {
    "termination_date": {
      "type": ["string", "null"],
      "description": "Termination date or null if not present."
    }
  },
  "required": ["termination_date"]
}

This keeps a stable output shape while allowing absence.

6.3 Optional Field

{
  "type": "object",
  "properties": {
    "termination_date": {
      "type": ["string", "null"],
      "description": "Termination date in format dd-mm-yyyy."
    }
  },
  "required": []
}

Use optional fields when:

The downstream consumer can handle missing keys.
The field applies only to some document types.
The schema should stay sparse.

Current Anthropic docs note schema complexity limits for optional parameters and union types like ["string", "null"], so production schemas should balance robustness with complexity.

The conceptual point is still: nullable fields prevent forced fabrication when data may be absent. (Claude)


7. Enum Design: unclear, other

Enums are good for stable categories. But too-narrow Enums create bad behavior when the source does not fit.

7.1 Bad Enum

"document_type": {
  "type": "string",
  "enum": ["invoice", "receipt", "contract"]
}

If the document type is a "quotation", Claude will be forced to choose a wrong category.

7.2 Better Enum

"document_type": {
  "type": "string",
  "enum": ["invoice", "receipt", "contract", "other", "unclear"]
}

Use:

unclear = source is ambiguous or insufficient.
other = source clearly indicates a category outside the enum.

Heuristic:

Do not force ambiguous or out-of-taxonomy inputs into a closed Enum.


8. Format Normalization Belongs in the Prompt, Not Just the Schema

A schema can say:

"invoice_date": { "type": ["string", "null"] }

But that does not by itself tell Claude how to normalize:

June 1, 2026
01/06/2026
2026.06.01
1 Jun 26

Add prompt rules:

- Normalize dates to YYYY-MM-DD.
- If the date is ambiguous between MM/DD/YYYY and DD/MM/YYYY
    - return null and set date_ambiguity_reason.
Preserve original values in evidence fields when useful.

Schema:

"invoice_date": {
  "type": ["string", "null"],
  "description": "Date normalized to YYYY-MM-DD, or null if absent."
},
"invoice_date_ambiguity_reason": {
  "type": ["string", "null"],
  "description": "When invoice_date is null due to ambiguous format."
}

Heuristic:

Schema defines the shape.
Prompt rules define normalization semantics.
Post-validation checks semantic consistency.

9. Common Traps

9.1 Trap 1: Asking for JSON Instead of Enforcing Schema

Bad:

Please return valid JSON.

Better:

Define a tool with input_schema and require tool use.

9.2 Trap 2: Using auto When Structured Output Is Mandatory

Bad:

tool_choice = {"type": "auto"}

When the pipeline requires structured output.

Better:

tool_choice = {"type": "any"}
# Or use a specific tool:
tool_choice = {"type": "tool", "name": "extract_metadata"}

9.3 Trap 3: Assuming any Means Correct Tool

Wrong:

tool_choice: any guarantees the model will choose the correct schema.

Correct:

any guarantees a tool call. Tool descriptions and scoped tool sets still determine correct selection.

9.4 Trap 4: Assuming Schema Validity Means Factual Validity

Wrong:

The JSON validates, so the extraction must be correct.

Correct:

The JSON validates syntactically and structurally. You still need evidence and semantic checks.

9.5 Trap 5: Required Non-Null Fields for Missing Source Data

Bad:

"termination_date": { "type": "string" }

When many documents do not contain a termination date, Claude might fabricate data.

Better:

"termination_date": { "type": ["string", "null"] }


9.6 Trap 6: Closed Enum with no Escape Hatch

Bad:

"category": {
  "type": "string",
  "enum": ["bug", "feature", "documentation"]
}

Better:

"category": {
  "type": "string",
  "enum": ["bug", "feature", "documentation", "other", "unclear"]
}


Test yourself on this topic Interactive questions for Task 4.3 — Structured Output with Tool Use & JSON Schemas, with instant explanations and scoring.
Start quiz →