Skip to content

Get structured (JSON) output from an LLM

Instead of asking for JSON and hoping, define the shape as a schema and get responses your code can use directly. Worked through with a customer-message classifier.

PracticalDevelopers

15 minAs of Oct 2026

By the end of this guide

  • Explain what goes wrong when you ask for JSON in the prompt, and what structured outputs prevent.
  • Define a schema as a Pydantic model and get validated results back with messages.parse.
  • Catch the content errors and exceptional responses a schema can't prevent, using a test sample and stop_reason.
Illustration of a tilted card of loose text passing through a template card of labels and empty slots into a card where values sit neatly inside curly braces, marked with a check
Illustration: AI Ping

Who this is for

  • Developers who need to pull values out of LLM answers into a database or a UI
  • Anyone whose code keeps growing error handling for json.loads failures and missing fields

What you need

  • Python with the anthropic SDK and the pydantic package
  • Steps 1–3 of "Your first LLM API call" done

Step by step

  1. Why "answer in JSON" isn't enough

    Write "answer only in JSON" in a prompt and most of the time you'll get JSON. But across hundreds or thousands of requests, the shape occasionally slips, and that one response stops your program.

    Structured outputs constrain generation so the response always follows the JSON schema you define. That largely removes the retries caused by broken JSON syntax, missing fields and wrong types.

    What can happen when you only ask for JSON in the prompt (examples)
    ProblemA response likeIn your code
    Extra text around it"Here is the classification:" followed by JSONjson.loads fails
    Code fencesJSON wrapped in ```jsonParsing it as is fails
    Missing fieldcategory but no summaryKeyError
    Wrong typeurgent as the string "yes" instead of a booleanConditions behave oddly
  2. Define the shape as a schema

    This guide takes a customer message and gets back a category, whether it's urgent and a one-sentence summary. Start by fixing each field's type and allowed values.

    In Python, a Pydantic model is the simplest way to define the schema. If you already have a schema file, you can use the JSON schema directly instead.

    Fields for the customer-message classifier (example)
    FieldTypeRule
    categoryStringOne of delivery, refund, payment, other
    urgentTrue/falsetrue when the customer names a deadline and asks for speed
    summaryStringThe message in one sentence
    Example prompt
    Write a Pydantic model for the result of classifying a customer message.
    category is one of delivery, refund, payment, other; urgent is true/false; summary is a string. All three are required.
    Example AI reply

    Fields without a default are required, so nothing else needs marking. When the SDK converts the model to a JSON schema, it also adds additionalProperties: false so no undeclared fields can slip in.

    Example code
    from typing import Literal from pydantic import BaseModel class Inquiry(BaseModel): category: Literal["delivery", "refund", "payment", "other"] urgent: bool summary: str
    Literal["delivery", ...]
    Allows only one of the listed values. In the JSON schema it becomes a list of allowed values (enum).
    urgent: bool
    A true/false value. It arrives as a real boolean, not the string "true".
    summary: str
    A string. For conditions such as a length limit, see step 5.
  3. Request it with messages.parse and read the result

    Pass the model class to messages.parse in the anthropic SDK. The SDK sends the schema as output_config.format, checks the response that comes back and returns an object, so you never parse strings yourself.

    If you write the JSON schema by hand, pass output_config={"format": {"type": "json_schema", "schema": ...}} to messages.create and read the response's text block with json.loads.

    Example prompt
    Write code that classifies the customer message below with the Inquiry model. Use messages.parse from the anthropic SDK, with the claude-opus-5-5 model.
    Message: It's been over a week since I ordered and it still says ‘preparing for shipment’. I need it for an event this Saturday, so it has to arrive by Friday.
    Example AI reply

    Pass Inquiry as output_format and the validated result arrives in parsed_output as an Inquiry object.

    Example output: delivery True Order stuck in ‘preparing for shipment’ for over a week; the customer needs it by Friday

    Example code
    import anthropic # Put the Inquiry class from step 2 here client = anthropic.Anthropic() text = "It's been over a week since I ordered and it still says ‘preparing for shipment’. I need it for an event this Saturday, so it has to arrive by Friday." response = client.messages.parse( model="claude-opus-5-5", max_tokens=1024, messages=[{"role": "user", "content": f"Classify this customer message: {text}"}], output_format=Inquiry, ) inquiry = response.parsed_output print(inquiry.category, inquiry.urgent, inquiry.summary)
    client.messages.parse(...)
    The SDK helper for structured outputs. It checks the response against the schema before returning it.
    output_format=Inquiry
    The shape you want back. The SDK turns it into a JSON schema and sends it as output_config.format.
    response.parsed_output
    The validated Inquiry object. Read it through attributes such as inquiry.category.

    Check it

    • Check that category is one of the four values and urgent is True or False. Change the message and run it several times: the shape should never change.
  4. What a schema can't guarantee

    Structured outputs protect the shape of a response, not whether its content is right. A category can differ from your team's rules, or a summary can drift from the message, so test first with a few messages whose right answers you know.

    The shape can break in exceptional cases too. A response that stops with stop_reason refusal (declined for safety) or max_tokens (cut off at the cap) may not match the schema. Check stop_reason before using a result, and retry such responses or hand them to a person.

    A test with five messages whose answers are known (example)
    Message (summary)ExpectedGotMatch
    My coupon code didn't give me the discountpaymentpayment✓
    I asked for a refund. When will it arrive?refundrefund✓
    I typed the wrong address. Can I change it?deliverydelivery✓
    My card was charged twice. Please cancel onepaymentrefund✗
    Where do I close my account?otherother✓
    5 messages4 match (80%)
    Example prompt
    In a test with five messages, it classified "My card was charged twice" as refund. By our team's rules that's a payment error, so it should be payment.
    Write short criteria for the four categories and for urgent that I can put in the system prompt. Say where borderline cases go.
    Example AI reply

    Try putting these criteria in the system prompt.

    delivery: problems getting the item to the customer, such as delays, address changes or damage

    refund: return and refund requests, and questions about refund timing

    payment: problems with paying, such as payment errors, double charges, coupons and discounts. Requests to cancel a double charge go here too.

    other: anything that fits none of the above (account details, how to use the service and so on)

    urgent: true when the customer names a date or deadline and asks for speed, otherwise false

    Check it

    • After adding the criteria, rerun the same five messages and check that every result matches.
    • Before going live, widen the sample to a few dozen past messages and measure accuracy again.
  5. Know these before you go live

    To constrain the response, structured outputs compile your schema into a grammar. That brings a few things to know about speed, cost and which schema features are supported.

    Things to know before you go live
    WhatDetailsWhat to do
    The first request is slowA new schema adds latency the first time while its grammar compiles. The grammar is then cached for 24 hours from last useMake one warm-up call right after you deploy
    Slightly more input tokensA system prompt describing the output format is added automaticallyInclude it in cost estimates
    Unsupported constraintsNumeric and length constraints such as minimum and maxLength aren't accepted by the API. SDK helpers move them into field descriptions and check the response against the original constraintState length limits in the prompt and check them in code
    Changing the schema breaks the cacheChanging output_config.format invalidates the prompt cache for that conversationDon't change the schema often

    Check it

    • If the first request with a new schema was unusually slow, grammar compilation is a likely cause. Send a second request with the same schema and see whether it's faster.

Common mistakes

  • Trusting "answer only in JSON" in the prompt and skipping parse-error handling
  • Assuming the content is right because the shape is right, and shipping without a test
  • Skipping the stop_reason check and storing refused or cut-off responses

References

Keep learning