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, and 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. For this exam task, 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.

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 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. Defining an extraction tool

Here is an exam-style extraction tool.

tools = [
    {
        "name": "extract_invoice",
        "description": (
            "Extract normalized invoice metadata from the provided invoice text. "
            "Use this tool only for invoice-like documents. "
            "Return null for fields not present in the source document; do not infer or fabricate missing values. "
            "Normalize dates to YYYY-MM-DD and currency to ISO 4217 codes when the source provides enough evidence."
        ),
        "strict": True,
        "input_schema": {
            "type": "object",
            "properties": {
                "document_type": {
                    "type": "string",
                    "enum": ["invoice", "receipt", "quote", "other", "unclear"],
                    "description": "The best classification of the document."
                },
                "document_type_other_detail": {
                    "type": ["string", "null"],
                    "description": "Required only when document_type is 'other'; otherwise null."
                },
                "invoice_number": {
                    "type": ["string", "null"],
                    "description": "Invoice identifier exactly as shown in the source, or null if absent."
                },
                "invoice_date": {
                    "type": ["string", "null"],
                    "description": "Invoice date normalized to YYYY-MM-DD, or null if absent."
                },
                "currency": {
                    "type": ["string", "null"],
                    "description": "ISO 4217 currency code such as USD, EUR, INR, or null if absent."
                },
                "total_amount": {
                    "type": ["number", "null"],
                    "description": "Final invoice total as a number, or null if absent."
                },
                "evidence": {
                    "type": "string",
                    "description": "Short source quote supporting the extracted values."
                }
            },
            "required": [
                "document_type",
                "document_type_other_detail",
                "invoice_number",
                "invoice_date",
                "currency",
                "total_amount",
                "evidence"
            ],
            "additionalProperties": False
        }
    }
]

Key design choices:

1. strict: true enforces schema-compliant tool input.
2. Nullable fields prevent fabrication when the source lacks data.
3. enum fields constrain categories.
4. "unclear" handles ambiguity.
5. "other" + detail field keeps categories extensible.
6. evidence helps semantic validation.
7. additionalProperties: false prevents random extra fields.

Anthropic’s strict tool-use docs say strict: true guarantees the tool input follows the JSON Schema and gives correctly typed arguments; the docs also recommend additionalProperties: false in strict schemas. (Claude)


4. Extracting data from the tool_use response

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

response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    tools=tools,
    tool_choice={"type": "tool", "name": "extract_invoice"},
    messages=[
        {
            "role": "user",
            "content": "Extract invoice metadata from this document:\n\n" + invoice_text
        }
    ],
)

invoice_data = None

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)

Exam point:

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)


5. tool_choice: auto vs any vs forced tool

This is one of the highest-yield parts of D4.3.

tool_choice Meaning Exam 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)


5.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.

Bad for exam scenario:

CI pipeline requires parseable structured findings.

For that, do not use auto.


5.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]

response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    tools=tools,
    tool_choice={"type": "any"},
    messages=[
        {
            "role": "user",
            "content": "Extract structured data from this document:\n\n" + document_text
        }
    ],
)

Why this is correct:

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

Exam trap:

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

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


5.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.

Code:

response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    tools=[extract_metadata_tool],
    tool_choice={"type": "tool", "name": "extract_metadata"},
    messages=[
        {
            "role": "user",
            "content": "Extract metadata before any enrichment:\n\n" + document_text
        }
    ],
)

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.

6. 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 primitive type
Extra fields when additionalProperties is false
Invalid enum value

But they do not guarantee semantic correctness.

Example 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)

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, for example by using a separate extraction tool per document section and merging the results. 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.

Exam answer pattern

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.

7. Semantic validation pattern

For financial extraction:

def validate_invoice(data: dict) -> list[str]:
    errors = []

    subtotal = data.get("subtotal")
    tax = data.get("tax")
    shipping = data.get("shipping")
    total = data.get("total")

    if None not in (subtotal, tax, shipping, total):
        expected_total = round(subtotal + tax + shipping, 2)
        if round(total, 2) != expected_total:
            errors.append(
                f"Total mismatch: subtotal + tax + shipping = {expected_total}, but total = {total}"
            )

    return errors

Prompt rule:

Extract only values supported by source evidence. If a value is ambiguous, set the field to null or use "unclear" rather than guessing.

Schema field:

"evidence": {
  "type": "string",
  "description": "Source quote supporting the extracted values."
}

The schema enforces structure. The prompt and validator enforce meaning.


8. Required vs optional vs nullable fields

The exam specifically calls out optional/nullable fields.

Bad schema

{
  "type": "object",
  "properties": {
    "termination_date": { "type": "string" }
  },
  "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.

Better schema: required but nullable

{
  "type": "object",
  "properties": {
    "termination_date": {
      "type": ["string", "null"],
      "description": "Termination date normalized to YYYY-MM-DD, or null if not present."
    }
  },
  "required": ["termination_date"]
}

This keeps a stable output shape while allowing absence.

Optional field

{
  "type": "object",
  "properties": {
    "termination_date": {
      "type": ["string", "null"]
    }
  },
  "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.

Use required nullable fields when:

The downstream consumer expects a stable key.
The source may not contain the information.
You want to prevent fabrication while preserving structure.

Exam shortcut:

If a source document may not contain the value, do not make the field required and non-nullable.
Use nullable or optional fields.

Current Anthropic docs note schema complexity limits for optional parameters and union types like ["string", "null"], so production schemas should balance robustness with complexity. For the exam, the conceptual point is still: nullable fields prevent forced fabrication when data may be absent. (Claude)


9. Enum design: unclear, other, and detail fields

Enums are good for stable categories:

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

But too-narrow enums create bad behavior when the source does not fit.

Bad enum

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

If the document is a quote, Claude must choose a wrong category.

Better enum

"document_type": {
  "type": "string",
  "enum": ["invoice", "receipt", "contract", "other", "unclear"]
},
"document_type_other_detail": {
  "type": ["string", "null"],
  "description": "If document_type is 'other', describe the actual document type; otherwise null."
}

Use:

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

Example:

{
  "document_type": "other",
  "document_type_other_detail": "quote"
}

or:

{
  "document_type": "unclear",
  "document_type_other_detail": null
}

Exam trap:

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


10. 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.
Normalize currencies to ISO 4217 codes.
Normalize percentages as numbers from 0 to 100.
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 or ambiguous."
},
"invoice_date_ambiguity_reason": {
  "type": ["string", "null"],
  "description": "Explanation when invoice_date is null due to ambiguous source format."
}

Exam shortcut:

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

11. Full example: unknown document extraction

Suppose documents may be invoices, receipts, or contracts.

Use multiple tools and tool_choice: any.

tools = [
    extract_invoice_tool,
    extract_receipt_tool,
    extract_contract_tool,
]

response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=2048,
    tools=tools,
    tool_choice={"type": "any"},
    messages=[
        {
            "role": "user",
            "content": """
Extract structured data from the document below.

Rules:
- Choose the extraction tool that best matches the document.
- If the document is ambiguous, choose the closest schema and set ambiguous fields to null or "unclear".
- Do not fabricate absent values.
- Normalize dates to YYYY-MM-DD.
- Normalize currencies to ISO 4217 codes.

Document:
""" + document_text
        }
    ],
)

Why any?

The app requires structured output.
The document type is unknown.
Claude must pick one extraction tool.

Why not auto?

Claude might answer in prose instead of calling a tool.

Why not forced extract_invoice?

The document might be a receipt or contract.

12. Full example: mandatory metadata before enrichment

Workflow:

1. Extract metadata.
2. Use metadata to decide enrichment strategy.
3. Run enrichment.

Force the metadata tool first:

metadata_response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    tools=[extract_metadata_tool],
    tool_choice={"type": "tool", "name": "extract_metadata"},
    messages=[
        {
            "role": "user",
            "content": "Extract metadata from this document before enrichment:\n\n" + document_text
        }
    ],
)

Then parse:

metadata = next(
    block.input
    for block in metadata_response.content
    if block.type == "tool_use" and block.name == "extract_metadata"
)

Then enrichment:

enrichment_response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=2048,
    tools=[enrich_invoice_tool, enrich_contract_tool],
    tool_choice={"type": "any"},
    messages=[
        {
            "role": "user",
            "content": f"Metadata: {metadata}\n\nDocument:\n{document_text}"
        }
    ],
)

Exam point:

Force a specific tool when a specific extraction must run before later steps.


13. Common exam traps

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.

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:

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

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.

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.

Trap 5: Required non-null fields for missing source data

Bad:

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

when many documents do not contain a termination date.

Better:

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

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"]
},
"category_other_detail": {
  "type": ["string", "null"]
}

14. Scenario-based practice questions

Question 1

A developer prompts Claude:

Extract this invoice as JSON.

The output sometimes includes prose before the JSON and occasionally has syntax errors.

What is the best fix?

A) Add “do not include prose” to the prompt.
B) Use tool use with a JSON schema and require Claude to call the extraction tool.
C) Increase temperature.
D) Ask Claude to retry until valid.

Answer

B. Tool use with a schema is the reliable approach. Prompt-only JSON formatting is weaker.


Question 2

A pipeline requires structured output, but Claude may answer in natural language if it thinks no tool is needed.

Which tool_choice is inappropriate?

A) {"type": "auto"}
B) {"type": "any"}
C) {"type": "tool", "name": "extract_invoice"}
D) Forced specific tool

Answer

A. auto allows Claude to skip tools and return text. Use any or forced tool selection when structured output is mandatory.


Question 3

You have three extraction tools: extract_invoice, extract_receipt, and extract_contract. The document type is unknown, but the system must receive structured output.

What should you use?

A) tool_choice: {"type": "auto"}
B) tool_choice: {"type": "any"} with clear tool descriptions
C) Force extract_invoice every time
D) No tools; ask for JSON

Answer

B. any guarantees one tool call while allowing Claude to choose among scoped extraction schemas.


Question 4

A workflow must always extract metadata before running enrichment.

What should you use first?

A) tool_choice: {"type": "tool", "name": "extract_metadata"}
B) tool_choice: {"type": "auto"}
C) No tool choice
D) A prose instruction only

Answer

A. Forced tool selection ensures the required extraction step happens first.


Question 5

A strict schema returns:

{
  "subtotal": 100,
  "tax": 10,
  "total": 200
}

The JSON validates, but the total is wrong.

What does this show?

A) Strict schemas prevent semantic errors.
B) Strict schemas eliminate syntax/schema errors but not semantic inconsistency.
C) JSON schemas are useless.
D) The model did not use a tool.

Answer

B. Schema compliance does not guarantee that values are logically correct.


Question 6

A contract extraction schema requires:

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

Many contracts do not include a termination date, and Claude fabricates one.

What is the best schema change?

A) Make termination_date nullable or optional.
B) Remove the schema.
C) Add more required fields.
D) Force every contract to have a date.

Answer

A. If the source may not contain a field, the schema must allow absence. Nullable fields prevent fabrication pressure.


Question 7

A classifier enum is:

["invoice", "receipt", "contract"]

Documents sometimes include quotes and purchase orders, causing misclassification.

What is the best design?

A) Keep the enum closed and force the nearest value.
B) Add other, unclear, and an other_detail field.
C) Remove all enum constraints.
D) Make every document a contract.

Answer

B. other handles known out-of-taxonomy cases; unclear handles ambiguity.


Question 8

A schema requires invoice_date as a string, but source documents use inconsistent date formats.

What else should you add?

A) Format normalization rules in the prompt, such as “normalize dates to YYYY-MM-DD; return null if ambiguous.”
B) Nothing; the schema is enough.
C) A broader enum.
D) A random default date.

Answer

A. The schema enforces structure; prompt rules define normalization behavior.


Question 9

Claude returns a valid tool_use block. Where should the application read the extracted data?

A) From assistant prose before the tool call.
B) From tool_use.input.
C) From the system prompt.
D) From the tool description.

Answer

B. The structured extraction is in the input object of the tool_use block.


Question 10

Which statement is correct?

A) tool_choice: "any" forces a specific named tool.
B) tool_choice: "auto" guarantees structured output.
C) Forced tool selection guarantees a specific tool call.
D) Strict schemas guarantee semantic correctness.

Answer

C. Forced tool selection requires Claude to call the named tool. any requires some tool, not a specific tool; auto may skip tools; strict schemas do not guarantee semantic correctness.


15. Final D4.3 checklist

Memorize this:

1. Prompt-only JSON is not the most reliable structured-output method.
2. Tool use with JSON schema is the exam’s preferred reliable method.
3. Read structured data from tool_use.input.
4. Use strict schema/tool configuration for schema-compliant inputs.
5. auto means Claude may call a tool or may answer directly.
6. any means Claude must call one provided tool.
7. forced tool means Claude must call the named tool.
8. Use any when structured output is required but document type is unknown.
9. Force a specific tool when a required extraction step must happen first.
10. Strict schemas eliminate syntax/schema errors, not semantic errors.
11. Validate semantic consistency separately.
12. Required non-null fields can cause fabrication when source data is absent.
13. Use nullable or optional fields for possibly missing source data.
14. Add unclear for ambiguity.
15. Add other + detail field for extensible categories.
16. Include normalization rules in prompts alongside schemas.
17. Use evidence/source fields to support validation.
18. Keep tool sets scoped so any does not choose among unrelated tools.

D4.3 mental model

Schema = shape guarantee.
tool_choice = call guarantee.
strict tool use = schema-compliant tool input.
Prompt rules = normalization and extraction semantics.
Validation = catches semantic errors.

The exam’s favorite distinction is:

Tool use with JSON schemas can guarantee structured, parseable output, but it cannot guarantee that the extracted values are semantically correct.