The schema is a prompt and a contract at the same time, which is why it is worth
more care than an internal function signature.
{
"name": "refund_order",
"description": "Issue a refund against one order. Call lookup_order first and confirm the order is delivered. Refunds are final and visible to the customer.",
"input_schema": {
"type": "object",
"properties": {
"order_id": {"type": "string", "pattern": "^ORD-[0-9]{8}$"},
"amount_minor": {"type": "integer", "minimum": 1},
"currency": {"type": "string", "enum": ["GBP", "EUR", "USD"]},
"reason_code": {"type": "string",
"enum": ["damaged", "late", "duplicate", "goodwill"]},
"idempotency_key": {"type": "string"}
},
"required": ["order_id", "amount_minor", "currency",
"reason_code", "idempotency_key"],
"additionalProperties": false
}
}
Four decisions in that block, each preventing a specific failure. The description
carries the preconditions and the consequence, because the model chooses tools by
reading descriptions and has no other source for "this is irreversible". A
description that only restates the name gives the model nothing to select on, and
tool confusion between two similar tools is almost always a description problem
rather than a reasoning one.
Enumerations instead of free text turn an unbounded generation into a choice.
reason_code as a string means you receive forty spellings of "broken" and can
neither report on them nor branch on them; as an enum the model must pick, and an
invalid pick is a schema error rather than bad data written to your ledger.
additionalProperties: false matters for the same reason — it rejects a
hallucinated field instead of ignoring it, which is how you find out that the
model believed in a parameter you never had.
The amount being an integer in minor units is a correctness decision, not a
formatting one. A model emitting 42.99 as a float in a field typed as a number
invites the rounding problems money always has, and an integer has no ambiguous
representation to get wrong.
The idempotency key is required rather than optional because an optional safety
parameter is an absent one. Make it derivable from the request content, so a
genuine retry collides and a genuinely different refund does not.
The cost of all this rigour is that a strict schema turns model mistakes into
errors your orchestrator must handle and feed back as observations. That is the
right trade, but the error messages are now part of the prompt, so they must
explain what to do rather than merely what failed.