Rubric
Contents — domains, guide and mocks

Output handling

CCDV-F 6.314 min read · checked 21 September 2026

Task statementOutput Handling (2.6%) — structured output patterns, response validation, defensive parsing, and scepticism toward confident output

Everything between the model and your database

  1. Constrainschema or tool defines the shape
  2. Parse defensivelycheck stop_reason, then decode
  3. Validateranges, references, business rules
  4. Act or escalatecommit, retry, or hand to a human

invalid · retry once with the error, then escalate

Most production bugs in this area come from deleting one of the middle two steps because the happy path worked in testing.

Structured output patterns

There are three ways to get machine-readable output from Claude, and they are not interchangeable. Knowing which one a scenario calls for is most of this task statement.

Ask and parse. You describe the format in the prompt — ideally with examples, as 6.2 covers — and parse whatever comes back. It works everywhere, needs no special parameters, and it is the only option when the shape is loose. It is also the one that occasionally returns “Here's the JSON you asked for:” in front of the JSON, which is why the other two exist.

Structured outputs. You pass a JSON Schema and the API constrains generation so the final response text conforms to it. The current parameter is output_config with a format object of type json_schema carrying your schema. It was previously a top-level output_format parameter, and the old beta header is still accepted during transition, but new code uses output_config.format. This is the pattern for a final answer that your code consumes — extraction, classification, anything that lands in a database.

Strict tool use. You set strict: true on a tool definition, and the tool's input_schema is enforced the same way. This is for the arguments of a function you are about to run, rather than for the answer to the user. The two can be combined in one request, and they solve different halves of the problem: one makes Claude's reply parseable, the other makes its function calls safe to execute. Tool design proper is 8.1.

A schema-constrained extractionpython
schema = {
    "type": "object",
    "properties": {
        "invoice_number": {"type": "string"},
        "total": {"type": "number"},
        "currency": {"type": "string", "enum": ["GBP", "EUR", "USD"]},
        "due_date": {"type": "string", "format": "date"},
    },
    "required": ["invoice_number", "total", "currency"],
    "additionalProperties": False,   # no surprise keys
}

resp = client.messages.create(
    model=MODEL_ID,
    max_tokens=1024,
    messages=[{"role": "user", "content": invoice_text}],
    output_config={"format": {"type": "json_schema", "schema": schema}},
)

# Shape is guaranteed. Truth is not — see validation below.
data = json.loads(resp.content[0].text)

The supported schema vocabulary is a deliberate subset, and the gaps are where teams get hurt. Types, properties, required, additionalProperties: false, items, minItems and maxItems, enums and consts, anyOf and oneOf, nested objects, and string formats such as date, date-time, email, uri and uuid are supported. minimum, maximum, minLength and maxLength are not enforced — the SDKs turn them into description text, which is advice to the model rather than a constraint. Neither are pattern, $ref, allOf, or recursive schemas. There are also complexity limits, including a cap on strict tools per request, and an over-complex schema is rejected with a 400 rather than silently downgraded.

What the schema buys you, and what it does not

Guaranteed by the schema

  • Valid JSON, no preamble or trailing prose
  • Required keys present, no unexpected keys
  • Each value of the declared type
  • Enum values drawn from your list

Still your job

  • Numeric ranges — minimum is not enforced
  • Patterns and lengths — pattern is unsupported
  • Cross-field logic: does the total match the lines?
  • Whether the facts are actually in the source
Read the right-hand column as your validation to-do list. Everything there is your code's job, whatever the schema says.

Defensive parsing

Defensive parsing starts before the parse. A response is not a string: content is a list of blocks, and a turn can carry thinking, text and a tool request together, so indexing content[0] and assuming text is a bug waiting for the day thinking is switched on. And stop_reason decides whether the body is worth parsing at all. Two values in particular invalidate a structured response even though the request returned 200: max_tokens means the output was cut off mid-object, and refusal means the model declined and the reply does not follow your schema. In both cases tokens were charged and a naive parse throws — or worse, half-succeeds.

What arrivesWhyHandle it by
Truncated JSONstop_reason is max_tokensRetry with a higher limit; never parse the fragment
Prose, not schemastop_reason is refusalRoute to a human; do not retry identically
"Fraud" where you expected "fraud"Enum casing can varyCompare case-insensitively
Valid JSON, impossible valueRanges are not enforcedRange-check after parsing
Well-formed JSON, unknown idThe model filled a required fieldCheck references against your own data
A partial object mid-streamStreaming deltas are fragmentsAccumulate, then parse at the end (2.3)

Then the parse itself. Decode inside a try, treat a decode failure as an expected outcome rather than an exception to log and forget, and validate the decoded object against your own model — a Pydantic model, a Zod schema, a hand-written check — before anything downstream touches it. The retry worth having is a narrow one: send the response back with the specific validation error and ask for a correction, once. An unbounded retry loop against a model that keeps making the same mistake is how a cheap request becomes an expensive one.

The parse that survives contact with productionpython
if resp.stop_reason == "max_tokens":
    raise Truncated("output was cut off; raise max_tokens and retry")
if resp.stop_reason == "refusal":
    return escalate_to_human(resp)

text = "".join(b.text for b in resp.content if b.type == "text")

try:
    data = Invoice.model_validate_json(text)   # types + your own rules
except ValidationError as e:
    return retry_once_with_error(text, e)      # one correction, then stop

# Rules a schema cannot express, checked here and only here:
assert data.total >= 0
assert data.currency.upper() in ALLOWED_CURRENCIES
assert supplier_exists(data.supplier_id)

Scepticism toward confident output

The phrase the exam guide uses is worth taking literally. A language model's fluency is unrelated to its accuracy: it produces the same measured, well-organised prose whether the document said what it claims or not. There is no tremor in the voice. That means confidence conveys no information, and any design that uses tone as a quality signal — including a human reviewer skimming because it reads well — has no quality signal at all.

The documentation's own hallucination-reduction techniques are the practical response, and each of them is a design decision rather than a wording trick. Allow the model to say it does not know, explicitly, so that admitting uncertainty is a permitted answer rather than a failure it must avoid. Ground answers in direct quotes: for long documents, have it extract the relevant passages word for word before doing the task. Require citations so each claim can be traced, and instruct it to retract any claim it cannot support with a quote. Restrict it to the provided material, stating that its general knowledge is not to be used. For higher stakes, chain-of-thought verification exposes the reasoning, and best-of-N runs the same prompt several times — disagreement between runs is a usable signal that something is being invented. The page ends with the caveat that matters most: these techniques significantly reduce hallucinations but do not eliminate them, so critical information must still be validated.

Checking a summary before it goes to the customer

  • Passes: Output parses and matches the schemaautomatic
  • Passes: Every figure appears in the source documentquote check
  • Fails: Policy numbers exist in our recordsone is invented
  • Check: Model was allowed to answer “unknown”prompt forces a value
  • Missing: Reviewer sees the cited passagesUI shows the summary only
Three of these are automatic and two need a person. The design question is not “is the model trustworthy” but “which checks are cheap enough to run every time”.

Design scepticism into the product, not just the prompt. Surface the citations next to the claim so a reviewer can check in two seconds rather than two minutes. Make “unknown” a first-class value your schema and your UI both accept. Route low-confidence or check-failing cases to a person instead of to a database. And keep the reviewer's job small: a human asked to verify a hundred fluent paragraphs a day will approve them, whatever they were told.

Traps the wrong answers are built from

Tempting but wrongDo this instead
Calling json.loads on content[0].text and catching nothingCheck stop_reason first, collect text blocks explicitly, and treat a decode failure as an expected branch.
Trusting minimum, maximum or pattern in a schema to enforce a ruleThose keywords are not enforced; re-check ranges, lengths and formats in your own code after parsing.
Treating a 200 response as a successful answerA refusal or a max_tokens truncation returns 200 and is charged; both need handling before the body is used.
Retrying a failed parse in an unbounded loopRetry once with the specific validation error attached, then escalate to a human or a fallback path.
Forcing a value for every field so the output is always completeMake “unknown” or “not stated” a legal value, so the model can decline instead of inventing.

You should now be able to

  • Choose between prompt-and-parse, schema-constrained output and strict tool use for a given requirement.
  • Write a schema within the supported subset and name the keywords that are not enforced.
  • Gate parsing on stop_reason and handle refusal, truncation and enum casing.
  • Layer business-rule validation on top of schema conformance, and design the escalation path.
  • Apply grounding techniques — quotes, citations, source restriction, permitted uncertainty — to reduce invented content.
  • Explain why fluent, confident output is not evidence of correctness, and design the review step accordingly.

Practice questions

Original questions written for this lesson, in the exam’s style. Answer first, then open the reasoning — every option is explained, including why the wrong ones are tempting.

  1. Question 1

    A pricing tool asks Claude to extract a discount percentage from a contract using a JSON Schema with "type": "number", "minimum": 0 and "maximum": 100. In production a record appears with a discount of 350, which parsed and posted without error.

    What went wrong?

    1. AThe schema was not applied because the request omitted the required beta header.
    2. BNumeric bounds are not enforced; the SDK renders them as description text, so the range must be checked in code.
    3. CThe response was truncated, so the number was read from an incomplete object.
    4. Dnumber permits any numeric value including percentages above 100; the field should have been an integer.
    Show answer and reasoning
    1. AIncorrect. The beta header is no longer required for structured outputs, and a schema that was not applied would typically produce shape errors rather than one out-of-range number.
    2. BCorrect. minimum and maximum are outside the supported enforcement subset and become advice to the model, which means only application-side validation can guarantee the range.
    3. CIncorrect. Truncation yields invalid JSON that fails to parse rather than a clean record with one implausible value.
    4. DIncorrect. Changing the type to integer would still admit 350; the type was never the constraint that was supposed to catch this.
  2. Question 2

    A claims system sends long documents to Claude and parses the reply as JSON. Occasionally the job throws a decode error at the same point in the file, and the logs show the request returned HTTP 200 with usage charged.

    What is the most likely cause and the correct handling?

    1. AThe API is rate limiting the request; add exponential backoff around the call.
    2. BThe model added a preamble before the JSON; strip anything before the first brace.
    3. CThe output hit max_tokens; check stop_reason before parsing and retry with a higher limit.
    4. DThe schema is too complex to compile; simplify it until the response validates.
    Show answer and reasoning
    1. AIncorrect. Rate limiting returns a 429 rather than a charged 200 response with a partial body.
    2. BIncorrect. String surgery is fragile and a schema-constrained response would not contain a preamble at all; it also does not explain a consistent cut-off point.
    3. CCorrect. A truncated generation returns 200 with stop_reason of max_tokens, so the body is an incomplete object that must never be parsed as an answer.
    4. DIncorrect. An over-complex schema is rejected up front with a 400, not with a partially generated 200 response.
  3. Question 3

    A bank summarises customer call transcripts. The summaries are fluent and the review team approves nearly all of them. An audit finds several summaries assert commitments the customer never made.

    Which two changes most directly address this? (Select 2.)

    1. ARequire the model to quote the passages supporting each commitment, and show those quotes to the reviewer.
    2. BAllow and expect no_commitments as a legal value when the transcript contains none.
    3. CAdd a JSON Schema so the summary is returned in a structured shape.
    4. DInstruct the model to be accurate and not to fabricate details.
    5. ELower the temperature so the output is more deterministic.
    Show answer and reasoning
    1. ACorrect. Quote grounding forces the claim back to the transcript, and putting the passage in front of the reviewer turns a two-minute check into a two-second one.
    2. BCorrect. A field that must always be filled pressures the model into producing something; a permitted empty answer removes that pressure.
    3. CIncorrect. A schema fixes the shape of an invented commitment without affecting whether it was invented.
    4. DIncorrect. A general exhortation is the rhetorical fix; it provides no mechanism and no way for a reviewer to verify anything.
    5. EIncorrect. Determinism makes the same claim reproducible rather than correct, and does not ground it in the transcript.
  4. Question 4

    A team must call an internal issue_refund function with an amount and a reason code, and separately show the customer a written explanation. They want both to be reliable.

    Which approach fits best?

    1. AUse strict tool use for the refund call's arguments, and a schema-constrained response for the structured parts of the reply.
    2. BUse a JSON Schema for the whole response and have the application call the refund function from the parsed object.
    3. CPrompt for JSON containing both the arguments and the explanation, then parse and dispatch.
    4. DUse strict tool use for both, passing the customer explanation as a tool parameter.
    Show answer and reasoning
    1. ACorrect. The two mechanisms cover different halves — enforced tool arguments for what gets executed, an enforced response format for what your code consumes — and may be combined in one request.
    2. BIncorrect. This works but discards the tool loop, so the model cannot see the refund's outcome and react to a failure.
    3. CIncorrect. Prompt-and-parse leaves both halves unenforced, which is the weakest option available here.
    4. DIncorrect. Routing prose through a tool argument to gain enforcement conflates the action with the answer and constrains text that has no schema to satisfy.

Sources

Drafted with AI assistance and checked against the sources above; expert review is in progress. Spotted an error? Tell us and it gets fixed, dated and listed on how this is written.