Structured output and tools Required
Design tools like a small public API
Give each tool one clear job, a verb-noun name, a description of when to use it and a strict input schema. Validate every argument in code before the tool runs.
How to do it
Expose few tools, ideally 20 or fewer per request, because models choose worse among many. Name them like find_order and create_return, never do_action. Describe in one or two sentences what the tool does and when not to use it. Use enums and formats instead of free text. Set strict: true on each tool definition, as the OpenAI and Anthropic APIs both allow. Check the user's rights inside the tool, as least power requires.
Example
export const findOrder = {
name: 'find_order',
description: 'Look up one of the signed-in customer\'s orders by its number. Do not use it to search by product name.',
input: z.object({ orderNumber: z.string().regex(/^[A-Z]-\d{4,}$/) }).strict(),
run: async ({ orderNumber }: { orderNumber: string }, user: User) => orders.findForCustomer(orderNumber, user.id),
};
class FindOrderInput(BaseModel):
model_config = {"extra": "forbid"}
order_number: str = Field(pattern=r"^[A-Z]-\d{4,}$")
def find_order(raw_args: dict, user: User) -> dict:
args = FindOrderInput.model_validate(raw_args)
return orders.find_for_customer(args.order_number, user.id)
find_order.description = "Look up one of the signed-in customer's orders by its number. Do not use it to search by product name."
How to check
A test loads every tool definition. Each has a name matching ^[a-z]+_[a-z_]+$, a description of at least 40 characters, additionalProperties: false and no untyped parameter. A call with a wrong argument type is rejected before the tool body runs.
For agents: if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in Based on the Agent Wranglers ai features playbook: https://agentwranglers.se/playbook/ai-features/structured-output-and-tools/tool-design.