How To · LLM TechniqueIntermediate

How to Build an AI Agent with Function Calling

How to build an AI agent with tools: function-calling schemas, permissions, retries, write confirmations, and tracing that survives production traffic in 2026.

Tool schemasAllowlist gatewayIdempotent executorsWrite confirmationsOpenTelemetry traces15 min · 6 steps · Updated 2026-08-25
The short answer

To build an AI agent with function calling, register each tool as a JSON Schema the model can select, execute those calls only through a server-side allowlist that enforces the user’s permissions, retry reads and confirm writes, and trace every invocation. Tool use is not “the model runs code”; it is a controlled loop: plan, call, observe, stop — with the application owning side effects.

What you’ll build

  • A versioned tool registry with JSON Schema arguments, side-effect flags, and owners
  • Server-side allowlists so the model cannot invent a function or escalate scopes
  • Retries with backoff for reads, and human confirmation for any write or irreversible call
  • Idempotent executors so Slack retries and agent loops do not double-charge or double-create
  • Traces from user message to each tool call to the final answer, used in weekly evals

Before you start

  • 01An LLM endpoint that supports tool / function calling with JSON arguments
  • 02A catalog of 3–10 real APIs or MCP tools with owners, SLAs, and auth
  • 03A place to store per-user identity and scopes from your IdP
  • 04Idempotency keys and a dead-letter queue for failed tool calls
  • 05A trace backend that can store spans without raw secrets or unconstrained PII

Key takeaways

  • 01

    Function calling is a schema and a gateway, not a prompt that lists APIs in prose.

  • 02

    The executor must enforce allowlists, argument validation, and query-time user scopes; the model cannot be trusted to police itself.

  • 03

    Mark every tool as read or write; retries, idempotency, and confirmation rules differ completely between the two.

  • 04

    Cap the loop (steps, time, tokens) and require a terminal action — answer, refuse, or escalate — so agents cannot spin.

  • 05

    Traces of message → tool name → args → result are the eval dataset; without them you cannot measure task success.

01

What function calling (tool use) actually is

Function calling is an API contract: you send the model a list of tools with JSON Schema parameters; it may return a structured call instead of (or before) a user-visible answer; your code runs the call and returns a result; the model continues. That loop is how you build an AI agent with tools rather than a chatbot that only talks.

The model never gets a raw shell. If your design requires arbitrary code or URLs, you have left function calling and entered remote execution. Stay on named tools with validated arguments. MCP servers and native provider tools are both valid transports; the security model — registry, auth, audit — must be yours either way.

02

Tool schemas that models can actually use

Vague descriptions produce vague arguments. Name tools after user jobs (`get_invoice`, `create_jira_ticket`), not after internal microservices. Constrain strings with enums and formats. Keep arity small; a 30-field blob is how you get missing required keys. Include examples in the description only if they match production — stale examples become copied mistakes.

Version schemas (`get_invoice@v2`) and pin what the serving prompt includes. A silent field rename is an outage. When two tools overlap, the model will pick at random; merge them or write a router tool with a single enum. Test schemas with a golden set of user utterances → expected tool + args before you test prose quality.

03

Permissions, allowlists, and the execution gateway

Treat every tool call as an untrusted request that happens to be JSON. The gateway allowlists names, validates arguments against schema, injects the user identity from the session (never from model-supplied user IDs), and checks scopes the way your API gateway already does. Query-time ACLs apply to tools that read documents or rows: the executor filters, the model does not.

Log denied calls. A burst of denied `delete_user` is either an injection test or a mis-prompted agent; both deserve a ticket. Do not return stack traces to the model. Return a short, structured error (`permission_denied`, `not_found`, `validation_error`) so the model can recover without leaking internals.

04

Retries, idempotency, and confirmation for writes

Reads and writes are different products. Reads may retry. Writes need an idempotency key stored before the HTTP call, a confirmation payload for irreversible actions, and a status API so a timeout can be resolved. If the user already confirmed, a retry must not pop a second confirmation that creates a second object.

Confirmation copy should show the tool, the arguments that matter (amount, destination, resource id), and a timeout. Policy can auto-approve low-risk writes (draft a ticket in a sandbox project) and require a human for money, access, or production mutations. Shadow mode should simulate writes even when confirmation is on, so evals can score intent without mutating prod.

05

Tracing and evaluating tool-use agents

A trace is the unit of eval. Store the user message, the tools offered (by version), each call’s name and a hash of arguments, latency, error, and the final answer. Redact secrets. From those traces you can score: right tool selected, arguments valid, task completed, extra calls, unauthorized attempts.

Build a golden set of 50+ multi-step jobs with expected tool sequences — not only expected prose. CI should fail when tool-selection accuracy or task success drops. Provider-specific function-calling formats change; keep a thin adapter so Anthropic, OpenAI, Google, or open-source tool use can be swapped without rewriting executors. No token markup: the client pays the model provider.

  • Registry: name, schema version, owner, read/write, required scopes
  • Gateway: allowlist, validate, bind user identity, deny unknown tools
  • Writes: idempotency key, confirmation, no blind retries
  • Evals: golden sequences, traces, shadow mode before cutover
06

What fails in production

The usual failures are schema drift, timeout-plus-replay duplicates, prompt injection via tool results (treat returned text as data), and loops that re-call a failing tool. Cap retries per tool per trace. If a tool fails twice with the same error, the model must stop and tell the user.

Do not give the agent a generic `run_sql` or `run_shell` to “move faster.” If you need Text-to-SQL or computer use, those are separate designs with read-only users, sandboxes, and their own evals. Function calling done well is boring: few tools, strict schemas, loud traces.

Step-by-step build

  1. 1

    Inventory tools and side effects

    List the actions the agent must take, who owns each API, and whether the call is read, write, or irreversible. Start with a small allowlist that covers real tasks; a 40-tool catalog on day one is how you get an agent that never picks the right one.

  2. 2

    Write JSON Schema function definitions

    For each tool, publish a name, description, and JSON Schema for arguments with types, enums, and required fields. Include error shapes. Version the schema. The description is for the model; the schema is for validation — both must be accurate or the model will hallucinate arguments.

  3. 3

    Build the allowlist gateway and executors

    The model returns a function name and JSON arguments. The gateway checks the name against the registry, validates args, resolves the user, and checks scopes. Only then does the executor run. Unknown names and extra fields fail closed.

  4. 4

    Add retries, idempotency, and write confirmation

    Retry idempotent reads with backoff and jitter. For writes, require an idempotency key and a confirmation step (UI button or policy) before execution. On timeout, look up the key; do not blindly replay.

  5. 5

    Constrain the agent loop

    Cap tool rounds (typically 4–8), wall-clock time, and tokens. After each result, the model either calls another allowed tool, answers, or refuses. Kill runaway loops. Do not let the model chain into a tool that was not in the original allowlist for that task.

  6. 6

    Trace, evaluate, and shadow

    Emit a span per model call and per tool. Score task success on a golden set of multi-step jobs. Run the agent in shadow mode where writes are simulated. Cut over when success rate, confirmation hit rate, and unauthorized-attempt rate meet the bar.

Common pitfalls

The mistakes that show up in real deployments — each one costs a week if you learn it the hard way.

Letting the model name any HTTP endpoint

Open-ended “call this URL” tools turn prompt injection into remote execution. Register concrete functions with schemas and allowlists; reject unknown names in the gateway, not in the prompt.

Putting authorization only in the tool description

The model will eventually ignore “do not delete.” Check scopes in the executor against the asking user. The LLM is not your access-control layer.

Retrying writes the same way you retry reads

A timeout on createInvoice is not a signal to fire it again. Reads can retry; writes need idempotency keys and a status lookup before a second attempt.

No confirmation on side effects

Calendar deletes, payments, and ACL changes should not execute because the model was “pretty sure.” Surface a confirmation payload the user (or a policy) must approve.

Debugging from chat logs instead of traces

Without spans you cannot tell whether the schema, the model, the gateway, or the API failed. Trace every tool name, argument hash, latency, and error code from day one.

Frequently asked questions

How do you build an AI agent with tools?

Define each action as a JSON Schema function, let the model select from that list, and execute only through a server-side gateway that validates arguments and the user’s scopes. Cap the call loop, confirm writes, and trace every invocation. The application owns side effects; the model only proposes structured calls.

What is function calling in an LLM?

Function calling (tool use) is a structured output mode where the model returns a tool name and JSON arguments instead of free prose, your code runs that tool, and the result is fed back for the next step. It is how agents act. It is not arbitrary code execution unless you mistakenly expose a shell.

How do I stop an agent from calling the wrong tool?

Shrink the allowlist to the tools for that task, make names and descriptions non-overlapping, validate arguments against schema, and score tool-selection accuracy on a golden set. Server-side allowlists stop invented names. Overlapping tools and 40-item catalogs are the usual cause of wrong picks, not a missing adjective in the prompt.

Should writes require confirmation?

Yes for anything irreversible: payments, deletes, access changes, production mutations. Show the user the tool and the critical arguments, expire the approval, and execute once under an idempotency key. Low-risk writes in a sandbox can be auto-approved by policy, but default to confirm until traces show the agent is reliable.

How do you measure whether a tool-use agent works?

Score task success on frozen multi-step jobs, plus tool-selection accuracy, schema-valid argument rate, extra-call rate, and unauthorized attempts. Use traces, not chat anecdotes. Shadow mode should simulate writes. Ship only when those numbers beat the previous pin on the same golden set.

Want this built for your team?

From architecture review to a deployed, evaluated system your engineers own — we ship it with you.

FAQ

Working with us

How soon can this build work start?

Typically within a week or two of a scope being agreed. The first delivery is deliberately a small batch so you can check the output against your expectations before volume ramps.

What do you need from our team?

One process owner who knows the workflow, one engineer with access to the systems involved, and a weekly 45-minute review. No standing committee, and no requirement for an ML specialist on your side.

Who owns the output and the data?

You do. Datasets, labels, weights, evaluation suites and runbooks are yours and are handed over at the end. Your data trains your models only, with zero-retention provider settings by default.

Can you scale volume up quickly if we need it?

Yes, and the quality bar holds because the rubric and gold set are already agreed by that point. Ramping is a staffing question, not a re-scoping one, so it usually takes days rather than a new engagement.

We already have a vendor for this. Why switch?

Often you should not. The cases where teams move to us are when they cannot get a quality number out of their current vendor, or when the work is delivered as an opaque batch with no trace of how disagreements were resolved.

What happens after the engagement ends?

We stay on-call for 30 days at no extra cost, then move to an optional support retainer. Most teams also keep a quarterly evaluation review with us to catch drift early.

Copyright © 2026
ReinforcedX, Inc.
All rights reserved