Tools and function calling

Describing tools so the model can use them, validating arguments, and making writes safe.

A tool is a described function

The model does not run your code. It emits a request naming a tool and arguments; your code executes it. Everything the model knows about the tool comes from the schema and the description, so those are part of your prompt.

{
  "name": "lookup_order",
  "description": "Find one order by its id. Use this before answering anything about order status.",
  "parameters": {
    "type": "object",
    "properties": {
      "order_id": { "type": "string", "description": "Order id, e.g. 'A-1042'" }
    },
    "required": ["order_id"]
  }
}
  • Say when to use it, not just what it does — that sentence drives selection.
  • Constrain types and enums so invalid calls are impossible to express.
  • Keep the number of tools small and their names unmistakable.
  • Return structured results with stable keys; the model reads them like a document.

Read tools and write tools

ClassExamplesPolicy
ReadSearch, fetch, query, listFree to run; still validate and rate-limit
Write (reversible)Create draft, add labelAllow, but log and make undo possible
Write (irreversible)Send email, charge card, deleteRequire confirmation or human approval
PrivilegedShell, arbitrary SQL, file writeAvoid; if unavoidable, sandbox and scope
def send_email(to, subject, body):
    if not re.fullmatch(r"[^@\s]+@[^@\s]+", to or ""):
        return {"error": "invalid recipient"}
    if len(body) > 5000:
        return {"error": "body too long"}
    # irreversible actions go through approval, never straight through
    return {"status": "queued_for_approval", "to": to}
⚠️
Never pass model-generated text straight into a shell, SQL string, filesystem path or URL. That is how prompt injection turns into remote code execution. Parameterise, allow-list, and treat every argument as untrusted input from a stranger.

Prompt injection

Anything an agent reads — a web page, an email, a file, a search result — can contain instructions aimed at the model. That is indirect prompt injection, and it is the defining security problem of tool-using agents.

  • Assume fetched content is hostile. Never let it authorise actions.
  • Keep the security decision outside the model: allow-lists, scopes, approvals enforced in code.
  • Separate "data" from "instructions" in the prompt structure, and say explicitly that fetched content is data only.
  • Log the provenance of every action so an incident can be traced to a document.
💡
A useful framing: the model is a user interface for your tools, and the interface is untrustworthy. Your authorisation layer must be correct even if the model is fully compromised.

FAQ

What should a tool return on failure?
A small structured object such as {"error": "not found"} with a stable key, plus enough context to act on. Errors the model can read are recoverable; exceptions are not.
Should tools be async?
Yes for I/O-bound work. Run independent calls concurrently, but keep write operations serialised so ordering stays predictable.

The agent loop Memory and retrieval

Last refreshed 2026-09-18.