Structured Output and Tool Calling · Contracts · lesson 2 of 6
A schema is a contract
about 18 minutes · free · runs in your browser
Step 1 of 2
Valid JSON is not the same as the right JSON
json.loads succeeding proves the text was JSON. It proves nothing about the fields.
{} parses. {"sentiment": null} parses. {"word_count": "seven"} parses, and then
fails much later inside an arithmetic expression that had no idea a string was possible.
A schema states what you require: which keys, of which types. In production you would write it once as a Pydantic model and let it do the checking —
class Review(BaseModel):
topic: str
sentiment: str
word_count: int
— and the useful thing to understand is what that class does, because it is what you would otherwise write by hand: for each declared field, is it present, and is it the type promised.
Your turn: write check(payload, schema) where schema maps a field name to a
type. Return the payload when it satisfies the schema; raise ValueError naming the
first field that does not. Naming the field matters — "validation failed" in a log at 2am
is a message that has to be debugged rather than read.
You start from this, and edit it in the browser:
SCHEMA = {"topic": str, "sentiment": str, "word_count": int}
def check(payload, schema):
"""Return payload if it satisfies schema, else raise ValueError naming the field."""
return payload
Step 2 of 2
The whole boundary in one function
Put the two halves together and you have the shape every production LLM call eventually takes: ask with an instruction, parse, validate, and only then hand the result to the rest of the program.
Everything before the return is the boundary. Everything after it can assume the data is what it says it is — which is the entire reason the boundary exists.
Your turn: write extract(question) that asks for JSON, parses it, checks it against
SCHEMA, and returns the payload. Any failure should raise; the caller has no way to
handle a half-valid dictionary, and pretending otherwise pushes the problem somewhere
worse.
You start from this, and edit it in the browser:
import json
import fake_llm
SCHEMA = {"topic": str, "sentiment": str, "word_count": int}
RULE = "Reply with JSON only. No preamble."
def check(payload, schema):
for field, expected in schema.items():
if field not in payload:
raise ValueError("missing field: " + field)
if not isinstance(payload[field], expected):
ValueError( + field)
payload
():
{}