Structured output and tools Required
Make tools that change things safe to repeat
Give every tool that creates or changes data an idempotency key, so a repeated call has no extra effect.
How to do it
Models and retry logic both repeat calls. A model that repeats itself sends a new tool call ID, so the ID alone is not enough. Derive the key from the conversation and the thing the call acts on, such as the order line. Store the key with the result and return the stored result on a repeat. The idempotency keys rule shows the server side. Tools that pay, send or delete also need a person's approval.
Example
async function createReturn(args: ReturnArgs, ctx: ToolContext) {
const key = `${ctx.conversationId}:${args.orderNumber}:${args.lineId}`;
return idempotent(key, () => returns.create({ ...args, customerId: ctx.user.id }));
}
def create_return(args: ReturnArgs, ctx: ToolContext) -> dict:
key = f"{ctx.conversation_id}:{args.order_number}:{args.line_id}"
return idempotent(key, lambda: returns.create(**args.model_dump(), customer_id=ctx.user.id))
How to check
A test calls create_return twice for the same order line, with two different tool call IDs. One return exists, and both calls get the same result.
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/idempotent-tools.