Guide 19

Function calling for bots: tool schemas, validation, confirmation and safe execution

Function calling turns a chatbot into something that acts. The model proposes a call; your code decides whether it happens.

Guide2 min readUpdated

The loop

  1. You send the conversation plus a list of tools, each with a name, a description and a JSON Schema for its parameters.
  2. The model either answers in text or returns one or more tool calls with arguments.
  3. Your code validates and executes each call, then sends the results back as tool messages.
  4. The model uses the results to answer, or calls more tools.

The model never executes anything. That is your code's job, and therefore your responsibility.

Writing good tools

  • Names and descriptions are prompts. Say what the tool does, when to use it and when not to.
  • Prefer few, specific tools over one generic call_api(url, body).
  • Use enums, formats and required fields in the schema. Keep parameters flat where possible.
  • Return compact, structured results. Large raw API responses waste context and confuse the model.

The OpenAPI to bot scaffolder generates tool definitions from an existing API spec as a starting point.

Validate everything

Arguments are model output, so treat them like user input:

  • Validate against the schema (Ajv in JavaScript, Pydantic in Python) and reject unknown fields.
  • Check authorization with the end user's identity, not the bot's: "can this user cancel this order?"
  • Clamp numbers, limit list sizes and never pass arguments into shell commands, SQL strings or file paths without strict handling.

Confirm side effects

Separate read tools from write tools. For anything that sends money, deletes data, messages other people or is hard to undo:

  • Return a pending action to the user with a summary and a confirm button (an inline keyboard in Telegram, a button in Discord or Slack).
  • Execute only when the user confirms, using an idempotency key so a double tap does not run twice.

Errors and limits

  • Return errors as tool results the model can act on: {"error": "order_not_found", "hint": "ask the user for the order number"}.
  • Cap tool calls per turn (for example 5 to 10) and total time per turn. Stop and apologize instead of looping.
  • Apply timeouts to every external call and use backoff for retries.
  • Run independent parallel calls concurrently, but keep writes sequential unless they are truly independent.

Observability and testing

  • Log each tool name, validated arguments (with secrets redacted), duration and outcome, linked to a conversation ID.
  • Build test cases: "user asks X, expected tool Y with arguments Z" and "user asks something out of scope, expected no tool call". Re-run them on every model or prompt change. See testing bots.
  • Treat text inside tool results as untrusted; web pages and emails can carry injected instructions. See prompt injection defense.

Check your understanding

1. Who executes a tool call?
2. What should happen before a tool deletes data?