Curriculum/Tools & Function Calling
JSON Schema for Tools
A tool is an API. JSON Schema is how you describe arguments the model may fill — and you must still validate.
Vendors call it function calling, tools, or structured outputs. Underneath, you hand the model a list of functions, each with:
name— a stable identifier, like a URL pathdescription— when to use it, when not toparameters— a JSON Schema object: types, required fields, enums, ranges
The schema is the type system of the agent. Weak schema, weak agent. The loop is a client of that type system. It should not invent argument shapes the schema did not name.
A tool is an API with a chaotic client. The client is fluent, overconfident, and will call you in a loop. JSON Schema is how you tell that client what a legal call looks like. It is also how you reject illegal calls. Those are two jobs. Vendor “guarantees” cover the first poorly and the second not at all.
Validate arguments before the world moves.
Schema, then call, then resultWhat to put in a schema
Be specific:
type: string, integer, number, boolean, object, arrayrequired: every field you actually needenumfor closed sets (low,medium,high) not free-text “priority”minimum/maximumfor amountsdescriptionon each property, not only on the function — models read thoseadditionalProperties: falseso extra keys fail (next lesson makes this a hill to die on)
Do not add a kitchen-sink options object “for later.” Extra bags are how you get SQL in a field named metadata. If you do not know the field, it does not exist. Add it in a versioned change with fixtures, not as a leftover dictionary.
Integers are not numbers. JSON Schema distinguishes them. Models emit 17 and "17" and 17.0. Your validator should say what you will accept. Money is integer cents, not a float dollars field. Floats are how you refund $39.999999.
Strings need more than type: string. Empty string is not an invoice id. minLength, a pattern if you have a real one (^INV-), and a description that says “stable id from get_invoice, do not guess.” Patterns are easy to get wrong. Prefer prefixes and enums over heroic regex.
Arrays need items with a type. Nested objects need their own required lists. An untyped array of objects is a bag with extra steps. If the list is “up to 10 ticket ids,” say maxItems. Models love to pass 200 ids because the user pasted a spreadsheet.
Schema is for the model and for you
The model uses the schema to write arguments. You use it to reject arguments. Never execute before validate. Vendors sometimes “guarantee” schema; still validate. Guarantees fail, proxies strip features, constrained decoding is incomplete, and the next model will not send you a card.
Validation is a function from args to a list of errors. Empty list means ok. Non-empty means you return a structured error observation and you do not call the world. That error is the next prompt. It should name the field. It should not name your stack frames.
Coercion is a later lesson. Schema comes first: if amount_cents is not an integer in range, fail. Do not “helpfully” parse "four thousand". Do not execute a refund of -1 because JSON parsed.
Keep the schema in git next to the handler. Generate the vendor payload from that object. If a human retypes the schema into the OpenAI dashboard, you will drift. Drift is extra keys and missing required fields in production only.
Versioning and required fields
Renaming a parameter is a breaking change for every prompt, every eval, and every stored trace. Prefer add-and-deprecate. get_job_v2 is allowed. Silent rename of job_id to id is how Tuesday’s agent dies.
Required fields are the ones the handler will actually read. If reason is optional, do not put it in required. If the handler refunds without a reason, either make it required or log reason: null. Optional fields the model always invents are noise. Optional fields you need for audit should be required.
Defaults in schema are a trap. If the model omits limit, your validator may insert 10. That is fine if you document it. It is not fine if the default is delete: true. Defaults that mutate the world belong nowhere.
A classroom validator
The live box is not a full JSON Schema engine. It is the subset you must never skip: required keys, declared properties, types, enum, min, max. Boolean is not an integer — True is not 1 for an id. Unknown fields error. Bad enum errors. Missing id errors. That is the contract working.
Three samples: a legal refund, a reason not in the enum, a body with only amount_cents. Print the error lists. If you add sql to the first sample, you should see unknown field — unless you forgot to check extra keys. Do not forget.
Run to execute this in your browser. Nothing is sent to a server.
What printed: the first sample is ok. The second dies on enum. The third is missing invoice_id. The fourth is below minimum. None of those calls should reach Stripe. The validator is the gate. Confidence is not a type.
What goes wrong
A schema that is only type: object with no properties. A vendor toggle “strict mode” that you never re-check in your process. A metadata bag. A float for money. Copy-paste of another tool’s schema with the wrong required list. All of these execute calls you cannot explain in the log.
Teams also skip descriptions on properties. Then the model puts an email in user_id. The type is string, so it passes. The description was the policy. Put it on the field. Still validate the prefix in code if you care.
How to test a schema
Keep three fixtures per tool: valid call, missing required field, extra key. Re-run them in CI when someone “just adds a parameter.” Add enum-miss and min/max as soon as those keywords exist. You do not need an LLM. You need a dict and validate.
How agents use this
Treat the schema as a unit-tested artifact. The loop sends whatever the model emitted. The runtime either runs the function or returns errors. If you cannot point at the schema file in git, the agent does not have a type system. It has a hope.
Generate tool docs for the prompt from the same object. When a field is added, the prompt and the validator change together. When a tool is disabled, both disappear. Schema is not a comment in Notion.
Watch out:Never execute before validate. A parsed JSON object can still be nuts.
Check your understanding