Lesson 4.4
Structured Output: JSON Mode, Tools and Schemas
The three ways to get machine-readable output from Claude, what each guarantees, and how to design schemas that work.
Applications need output they can parse. Historically that meant asking nicely for JSON and writing defensive code around the result. The API now offers guaranteed schema conformance through structured outputs, alongside strict tool use for tool arguments. The exam tests which mechanism gives which guarantee, the schema rules, and the failure modes that remain even with guarantees.
Three approaches
| Approach | Guarantee | Typical use |
|---|---|---|
| Prompt for JSON ("respond only with JSON matching...") | None; usually works, sometimes wrapped in prose or code fences | Prototypes, models without structured outputs |
| Tool use as an extractor (one tool whose schema is your output) | Schema-valid arguments when strict: true; historically forced with tool_choice | Legacy pattern; forced tool_choice is rejected by newer models |
Structured outputs (output_config.format) | The response text is valid JSON matching your schema | Any production extraction or classification |
Only the third turns "parse and hope" into "parse". Use it whenever the model supports it.
Structured outputs
schema = {
"type": "object",
"properties": {
"sentiment": {"type": "string", "enum": ["positive", "neutral", "negative"]},
"topics": {"type": "array", "items": {"type": "string"}},
"needs_followup": {"type": "boolean"},
},
"required": ["sentiment", "topics", "needs_followup"],
"additionalProperties": False,
}
response = client.messages.create(
model=MODEL,
max_tokens=1024,
output_config={"format": {"type": "json_schema", "schema": schema}},
messages=[{"role": "user", "content": review_text}],
)
data = json.loads(response.content[0].text)
The older top-level output_format parameter is deprecated in favour of output_config.format. The SDKs add a parse() helper (client.messages.parse in Python, with a Pydantic model; the TypeScript equivalent with Zod) that generates the schema from your type, sends the request and returns a typed object, which removes the hand-written schema and the json.loads call.
Schema rules
Structured outputs support a subset of JSON Schema and apply some rules of their own:
- Objects must set
additionalProperties: falseand list every property inrequired(optional fields are expressed as a union withnull). - Supported keywords include
type,enum,properties,items,anyOf,constand$refwith definitions; many value constraints (numeric ranges, string length,format) are not enforced by the grammar and should be validated by you or rewritten as descriptions. - Keep schemas as small as the task allows. Deep nesting and many optional branches make generation slower and less accurate.
- The first request with a new schema pays a compilation cost; subsequent requests with the same schema reuse it.
Check the structured outputs page in the documentation for the current list of supported keywords and limits.
Strict tool use
The same guarantee applies to tool arguments: set strict: true on a tool definition and tool_use.input will validate against input_schema, subject to the same schema rules. This is the right mechanism when the structured data is an action (call this function with these arguments) rather than an answer. Both mechanisms can be active in one request: strict tools for the calls and output_config.format for the final response.
What guarantees do not cover
Schema conformance says nothing about truth. A valid {"invoice_total": 0} is still wrong if the invoice said 1,250. Keep semantic validation (ranges, cross-field consistency, reconciliation against source data) in your code. Three other cases break parsing even with structured outputs:
stop_reason: "max_tokens": the JSON was cut off. Raisemax_tokensand retry.stop_reason: "refusal": the response is a refusal, not JSON. Checkstop_reasonbefore parsing.- Incompatible features: structured outputs cannot be combined with citations, and a request mixing them returns a 400.
Prompt design still matters
The schema constrains shape; the prompt still determines content. Describe each field's meaning in the prompt or in the schema's description properties, give examples of difficult inputs, and state how to handle missing information (the schema can make a field nullable, but the prompt tells the model when to use null). Enumerations should be explained, not just listed.
Choosing a design
- Classification or extraction into a known shape: structured outputs, with enums for closed sets.
- The model should decide between several actions: strict tools, one per action.
- Free-text answer with a small structured envelope (answer plus confidence plus sources): structured outputs with a
textfield; or XML tags and a stop sequence when the model lacks structured outputs. - Multiple records from a long document: a schema with an array, but watch
max_tokens; chunk the input if records are numerous.
Exam focus
- Prompt-only JSON has no guarantee; strict tools guarantee valid arguments; structured outputs (
output_config.formatwithjson_schema) guarantee the response matches the schema. - Forcing a tool call via
tool_choiceto obtain JSON is a legacy pattern that newer models reject; use structured outputs. - Schemas need
additionalProperties: falseand all properties inrequired; optional means nullable. messages.parse()builds the schema from a Pydantic or Zod type and returns a typed object.- Schema conformance does not guarantee correctness; semantic validation stays in your code.
- Parsing still fails on
max_tokenstruncation andrefusal; structured outputs are incompatible with citations.
Check yourself
5 questions
1.A service needs Claude's response to be valid JSON that always matches a given schema, without post-hoc repair code. Which approach provides that guarantee?
2.Which two features make up Anthropic's structured outputs capability?
3.A structured-outputs request fails because the schema uses numeric minimum and maximum constraints and a recursive $ref. Why?
4.A request that combines output_config.format with document blocks that have citations enabled returns a 400 error. Why?
5.A structured-outputs response comes back with stop_reason max_tokens. How should the application treat the output?
