0Pricing
Claude Architect · Lesson

tool_use for Guaranteed Structure

Eliminate syntax errors and enforce required fields.

tool_use for Guaranteed Structure is a free Claude Architect lesson on CoddyKit — lesson 1 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Claude Architect learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Why Free Text Fails

You asked Claude for JSON and parsed the reply with json.loads(). It worked 95% of the time. The other 5%? A stray markdown fence, a trailing comma, a chatty preamble like "Here's the JSON you requested:" — and your pipeline throws.

For a Claude Certified Architect, that 5% is the whole problem. Production extraction, classification, and routing flows can't depend on the model happening to format text correctly. This lesson shows how tool_use plus a JSON Schema turns a probabilistic format into a guaranteed one — eliminating syntax errors and enforcing required fields.

The Core Idea

A tool isn't only for taking actions. A tool definition is also a typed output contract. When you declare a tool with an input_schema, you're telling Claude exactly what shape its arguments must take — and the API validates the tool call against that schema.

So instead of asking for JSON in prose and hoping, you define a tool whose parameters are the structure you want, then force Claude to call it. The model fills in the fields; the schema guarantees the shape. Per the fact sheet: tool_use + JSON Schema eliminates syntax errors and enforces required fields.

Defining the Schema Tool

Here's an extraction tool. Note the three pillars of a good tool definition: a clear name, a descriptive description, and a precise input_schema with per-property descriptions and a required list.

The schema below extracts a support ticket. We require only the fields that are always present — more on that decision soon.

ticket_tool = {
    "name": "record_ticket",
    "description": "Record a structured support ticket extracted from the user message.",
    "input_schema": {
        "type": "object",
        "properties": {
            "summary": {
                "type": "string",
                "description": "One-line summary of the issue",
            },
            "priority": {
                "type": "string",
                "enum": ["low", "medium", "high", "urgent"],
                "description": "Triage priority",
            },
        },
        "required": ["summary", "priority"],
    },
}

tool_choice: Forcing the Structure

Declaring the tool isn't enough — with the default, Claude might answer in text instead. The tool_choice parameter controls that decision:

  • {"type": "auto"} — Claude picks text or a tool (the default).
  • {"type": "any"} — Claude must call some tool. This is what guarantees structured output.
  • {"type": "tool", "name": "X"} — force one specific tool by name.

When you have exactly one schema tool and want a guaranteed call to it, force it by name. This removes the "answer in prose" escape hatch entirely.

response = client.messages.create(
    model="claude-opus-4-8",
    max_tokens=1024,
    tools=[ticket_tool],
    tool_choice={"type": "tool", "name": "record_ticket"},
    messages=[{"role": "user", "content": ticket_text}],
)

Reading the Tool Input Back

When Claude calls the tool, the response contains a tool_use content block. The structured data lives in block.input — already parsed into a Python dict by the SDK, conforming to your schema.

One discipline from the fact sheet: always parse with the SDK's parsed object, never raw-string-match the serialized input. Recent models may escape Unicode or forward slashes differently, so treat block.input as structured data, not text.

for block in response.content:
    if block.type == "tool_use":
        ticket = block.input  # dict, matches the schema
        print(ticket["summary"], ticket["priority"])
        # Do NOT json.loads a re-serialized string here —
        # block.input is already the structured object.

The required Trap

This is the single most tested decision in this lesson. The rule from the fact sheet is blunt:

Mark a field required ONLY if it is always present. NEVER require a field that may be absent — the model will fabricate a value to satisfy the schema.

If customer_id sometimes isn't in the source text but you list it under required, Claude won't return an empty result — it will invent a plausible-looking ID to make the tool call valid. That's a silent data-integrity bug. Required means "guaranteed by the source," not "nice to have."

Optional Fields Done Right

Fields that may be absent simply stay out of the required array. Claude omits them when the source lacks the data, instead of hallucinating.

Here customer_id and attachments are optional; only summary and priority — which we can always derive — are required.

"properties": {
    "summary": {"type": "string"},
    "priority": {"type": "string",
                 "enum": ["low", "medium", "high", "urgent"]},
    "customer_id": {"type": "string",
                    "description": "Only if explicitly stated"},
    "attachments": {"type": "array", "items": {"type": "string"}},
},
"required": ["summary", "priority"]  # NOT customer_id / attachments

Enums With an Escape Hatch

Enums constrain a field to a fixed set of values — great for categories, labels, and priorities. But a rigid enum forces every input into one of your predefined buckets, which breaks on edge cases you didn't anticipate.

The fact-sheet pattern for extensibility: use enums with an "other" value plus a free-text detail field. When Claude hits something outside your categories, it picks other and explains in the detail field — instead of mislabeling.

"category": {
    "type": "string",
    "enum": ["billing", "bug", "account", "other"],
    "description": "Use 'other' if none of the named categories fit",
},
"category_detail": {
    "type": "string",
    "description": "Free-text explanation, required only when category is 'other'",
}

Validate, Then Retry With Feedback

The schema guarantees syntactic shape, but you still validate semantics — totals that add up, dates in range, cross-field consistency. Use a Pydantic-style validator on block.input.

When validation fails on a format, structural, or arithmetic error, use retry-with-feedback: resend the original source, the wrong output, and the exact validation error. Critically, the fact sheet warns: retry does NOT help when the information is simply absent from the source — retrying just invites fabrication.

from pydantic import BaseModel, ValidationError

class Ticket(BaseModel):
    summary: str
    priority: str

try:
    ticket = Ticket(**block.input)
except ValidationError as e:
    # Resend: original text + wrong output + this exact error.
    # Only worth it for format/arithmetic faults, not missing data.
    retry(ticket_text, block.input, str(e))

Self-Correction for Numbers

For numeric extraction, a powerful schema trick is to make Claude expose its work so discrepancies are detectable. Per the fact sheet: extract both a calculated_total and a stated_total to detect discrepancies.

If the document says "Total: $1,200" but the line items sum to $1,150, two separate fields surface the mismatch — your validator catches it instead of trusting a single number. The schema becomes an audit instrument, not just a container.

"properties": {
    "line_items": {"type": "array", "items": {"type": "number"}},
    "calculated_total": {"type": "number",
        "description": "Sum you computed from the line items"},
    "stated_total": {"type": "number",
        "description": "Total as literally written in the document"},
},
"required": ["line_items", "calculated_total", "stated_total"]

Where This Fits in the Loop

Structured output is one stop in the larger agentic loop: request → inspect stop_reason → if tool_use, handle the tool → repeat until end_turn. A forced schema call returns stop_reason: "tool_use"; you read block.input and proceed.

Two architect-grade reminders: terminate on the stop_reason, never by parsing text for words like "done." And keep provenance — map each extracted claim back to its source (doc name, quote, date) so downstream consumers can trust and audit the structured result.

Quick Check

You're extracting invoices. The source PDFs sometimes omit a purchase-order number. A teammate proposes putting po_number in the schema's required array "so we always get one." What should you do?

Recap: Guaranteed Structure

Key takeaways for the exam and for production:

  • tool_use + JSON Schema eliminates syntax errors and enforces required fields — far more reliable than parsing prose JSON.
  • tool_choice: "any" guarantees some tool call; {"type":"tool","name":"X"} forces a specific schema tool; "auto" leaves it optional.
  • required = always present. Never require a possibly-absent field — it causes fabrication. Optional fields simply stay out of required.
  • Enums need an "other" + detail field for extensibility.
  • Read structured data from block.input; never raw-string-match the serialized tool input.
  • Retry-with-feedback fixes format/arithmetic faults — not absent information. Use calculated_total vs stated_total for self-correction, and keep provenance.

Frequently asked questions

Is the “tool_use for Guaranteed Structure” lesson free?

Yes — the full text of “tool_use for Guaranteed Structure” is free to read here on the web, and the Claude Architect course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Claude Architect course, upgrade to CoddyKit PRO.

What will I learn in “tool_use for Guaranteed Structure”?

Eliminate syntax errors and enforce required fields. You practise Claude Architect with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Claude Architect?

No prior experience is required. Claude Architect on CoddyKit is structured for beginners through advanced learners; this is — lesson 1 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “tool_use for Guaranteed Structure” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Claude Architect lesson?

Yes. Every Claude Architect lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. tool_use for Guaranteed Structure
  2. Designing a JSON Schema
  3. Required vs Optional/Nullable Fields
  4. Enums with 'other' for Extensibility
← Back to Claude Architect