What you need to know
Tool use as the output channel
"Respond with JSON" in a prompt usually works, and occasionally returns markdown fences, a trailing comma or a sentence of prose. The reliable approach: define an extraction tool whose input_schema is the JSON schema you want, have Claude call it, and read the structured data from the tool_use block. With strict tool use, the arguments match the schema, which removes JSON syntax errors.
extract_invoice = {
"name": "extract_invoice",
"description": "Record the fields of one invoice.",
"strict": True, # strict tool use: arguments follow the schema
"input_schema": {
"type": "object",
"properties": {
"vendor_name": {"type": "string"},
"vendor_tax_id": {"type": ["string", "null"]},
"invoice_date": {"type": "string", "description": "YYYY-MM-DD"},
"category": {"type": "string",
"enum": ["goods", "services", "utilities", "other"]},
"category_detail": {"type": ["string", "null"]},
"total": {"type": "number"}},
"required": ["vendor_name", "vendor_tax_id", "invoice_date", "category", "total"]}}
resp = client.messages.create(model=MODEL, max_tokens=1024, tools=[extract_invoice],
tool_choice={"type": "tool", "name": "extract_invoice"}, messages=msgs)
data = next(b.input for b in resp.content if b.type == "tool_use")
tool_choice decides whether a tool is called
| Setting | Behaviour |
|---|---|
auto | Claude may answer in text instead of calling a tool |
any | Claude must call a tool, and picks which: right when there are several extraction schemas and the document type is unknown |
{"type": "tool", "name": ...} | Claude must call that tool: right when one extraction (such as metadata) must run before enrichment steps |
Design the schema so the model never has to invent
- Nullable or optional fields for information a document may not contain. A required
vendor_tax_idwith no tax ID in the document pushes the model to fabricate one;"type": ["string", "null"]lets it say "not present". - Enums with an escape hatch:
"other"plus acategory_detailstring for documents that fit no category, and"unclear"for genuinely ambiguous cases. - Normalisation rules in the prompt ("dates as YYYY-MM-DD; amounts as numbers without currency symbols") alongside the schema, because sources are inconsistent.
Structure is not correctness
A schema guarantees the output parses and has the right types. It does not guarantee the values are right: line items that do not sum to the total, a vendor name in the address field, a date from the wrong part of the page. Those are semantic errors, and they need validation (lesson 4.4).
In the exam
Scenario 6 (extraction) carries most of these: parse failures from "respond with JSON", fabricated values in required fields, unknown document types, extensible categories, and valid JSON with wrong numbers.
Check your understanding
A pipeline asks for JSON in the prompt, and 4% of responses fail to parse. What is the most reliable fix?
Show answer
Answer: C. Tool use with a JSON schema returns structured arguments that match the schema, removing syntax errors at the source rather than patching them afterwards.
A required vendor_tax_id is fabricated on invoices that have no tax ID. What should change?
Show answer
Answer: A. A required field forces a value. Allowing null removes the pressure to invent one while keeping the field for invoices that have it.
Your extractions always match the schema, but line items sometimes do not add up to the total. Which TWO statements are true? Select TWO.
Show answer
Answer: B, C. Schema compliance and semantic correctness are different things. Catching arithmetic errors needs a validation step that compares values.
Apply it: Bayview's extraction tools
Design Bayview's structured output for invoices and receipts.
- Write the invoice tool schema with at least eight fields, marking which are nullable and why.
- Design the category field so the 5% of unexpected documents still extract cleanly.
- Choose tool_choice when the document type is unknown and there is one tool per type.
- List two semantic errors the schema cannot catch, for lesson 4.4.
Show model answer
1. Fields
| Field | Type | Nullable? | Why |
|---|---|---|---|
| vendor_name | string | no | Every invoice has a sender |
| vendor_tax_id | string | yes | Absent on about 15% of invoices |
| invoice_number | string | yes | Some receipts-as-invoices lack one |
| invoice_date | string, YYYY-MM-DD | no | Needed for posting; normalised by prompt rules |
| due_date | string | yes | Often missing |
| currency | enum of ISO codes plus "unclear" | no | |
| line_items | array of {description, quantity, unit_price, amount} | no (may be empty) | |
| stated_total | number | no | |
| calculated_total | number | no | Sum of line items, for checking |
2. Category. enum: ["invoice", "receipt", "contract", "credit_note", "statement", "other"] plus category_detail (nullable string) that must be filled when other is chosen, for example "cover letter".
3. tool_choice. any: a tool must be called, and Claude picks the schema that fits the document.
4. Semantic errors. calculated_total differs from stated_total; the vendor's address extracted into vendor_name.