- choose a model that calls tools, and pin it
- write tool definitions the model uses well
- validate every argument before you act
- bound the tool loop and make side effects safe
- get structured output you can rely on
- treat tool results and connected servers as untrusted
Choose a model that calls tools, and pin it
Checksupports_tools in the model catalog before you send tools. A request with tools to a model that doesn’t support them is refused with a 400 that says to remove the tools parameter or choose another model. Most chat models qualify; image-generation models and some open-weight models don’t.
Pin a model for anything that calls tools. On guarded Completions, model_family and auto-routing can pick a different model per request, and models differ in how they choose tools and format arguments. The Tool Use guide covers how each routing mode handles tools.
Write tool definitions for the model
The model decides which tool to call, and with what, from your definitions alone.- Name the action:
get_service_times, nottool1orapi_call. - Say when to use it in the description, in a sentence or two, and when not to.
- Make parameters specific. Use
enumfor fixed sets, mark what’srequired, give formats in descriptions (“ISO 8601 date, for example 2026-11-15”), and keep nesting shallow. - Offer few tools per request. A handful of clearly different tools works better than dozens of overlapping ones. Send only the tools that make sense for the current step.
- Keep definitions stable. Identical tool definitions in the same order help prompt caching; see Controlling Cost.
- Use the nested schema on Responses too. Define tools as
{"type": "function", "function": {...}}on both APIs; see Defining tools.
Validate every argument before you act
arguments is a JSON string written by the model. Treat it like input from a form on the internet: parse it, check it against the schema you sent, and check the values make sense for this user, before you run anything. When a check fails, don’t crash and don’t guess: return the error to the model as the tool’s result, so it can correct the call.
Bound the loop and guard side effects
A tool loop runs until the model answers in text. Without a limit, a confused model can call tools indefinitely, and every turn resends the whole conversation and bills it again.- Cap the steps. Stop after a fixed number of model turns, such as 8, and tell the user the task didn’t finish.
- Run every call the model asks for. A turn can contain several tool calls. Return one result per call, matched by its ID.
- Separate reads from writes. Run read-only tools freely. For tools that change something (send a message, charge a card, update a record), ask the user to confirm first, or at least log them and limit how often they can run.
- Make writes idempotent. Give each side-effecting call an idempotency key derived from the conversation and the call ID, and have your tool ignore a key it has already processed.
- Never auto-retry a request after a side effect has run. If the model’s request fails after your tool has already acted, resending the whole turn can make the model call it again. See Tool Calls and Side Effects.
/ai/v2/guarded/chat/completions:
jsonschema package (pip install jsonschema requests) to check arguments against the same schema the model saw. In TypeScript, a schema library such as Ajv or Zod does the same job as the hand-written check.
Get structured output you can rely on
When your code reads the answer, ask for a shape and then check it. There are two ways to ask:- A JSON schema in the request. On the Responses endpoints, send
text.formatwith"type": "json_schema", aname, theschemaand"strict": true; on Chat Completions, send the same inresponse_format. See Two Ways to Send a Schema on Gloo. - A tool the model must call. Define one function whose
parametersis your schema and force it withtool_choice. The arguments are your JSON. This works on both APIs and on the widest range of models; see Get JSON with a forced tool call.
- Test it on the model you pin. Support varies by model and provider. Some models don’t apply a schema sent in
response_format, and return text without an error. Some can’t be forced to call a tool, including the Claude 5.5 models: on most of these the request still runs as iftool_choicewere"auto", so the model may answer in text instead, and a few refuse a forced tool choice with a400. - Check you got what you asked for. Confirm the tool call arrived, or that the text parses, before you use it. If it didn’t, retry once with a clearer instruction, then fail gracefully.
- Validate the content, not just the shape. A schema makes the right shape likely; it doesn’t make the values right. Check ranges, IDs and dates against what you know, and allow
nullfor “not found” so the model doesn’t have to guess. - Check for cut-offs first. An answer that hit the output limit is truncated JSON. Check
finish_reasonorstatusbefore parsing; see Reasoning Models in Production. - Mention JSON when you ask for
json_object. Some models refuse{"type": "json_object"}with a400unless your messages mention JSON. A plain “Reply in JSON” in the instructions avoids it.
Treat tool results and connected servers as untrusted
A tool result is text the model reads, and it can contain instructions: a web page, an email or a document fetched by a tool can say “ignore your instructions and…”. This is prompt injection, and it applies whether the tool is your own function or a server connected through MCP.- Give tools the least access they need. A tool that reads one user’s calendar shouldn’t hold a key that reads everyone’s.
- Decide permissions in your code, not in the prompt. Check that the signed-in user may do what a tool call asks, every time.
- Keep a person in the loop for writes that are hard to undo.
- Connect only MCP servers you trust, and review what their tools can do before you let an agent use them.
Summary
- Check
supports_tools, and pin the model for anything that calls tools. - Write specific tool definitions and keep them stable.
- Parse and validate every argument; return errors to the model as tool results.
- Cap the loop, confirm and deduplicate writes, and never auto-retry after a side effect.
- Ask for a schema or force a tool, then check what came back, including whether it was cut off.
- Treat tool results and MCP servers as untrusted input.
Next steps
Tool Use guide
Tool definitions and results on Responses and Completions.
Tool Use tutorial
Build a working tool loop step by step.
Structured Output
The lesson: schemas, forced tools and why to validate.
Direct Endpoints and Untrusted Content
Prompt injection, least privilege and output checks.

