Tool calling
Ask a language model what a ride across town costs, with nothing else to go on, and it will give you a number. The number will sound right. That doesn’t make it the fare you charge. A model without tools has no way to tell the difference: it will invent an answer rather than admit it doesn’t know.
Tool calling, also called function calling, closes that gap. A tool can look up or check something, such as the fare to an address or whether a driver is free, and a tool can do something, such as book the pickup, text the receipt, or transfer the call. Either way, the result goes back to the agent as context it can act on and talk about. On SignalWire, tool calls travel over SWAIG, the SignalWire AI Gateway, and the tools you define are SWAIG functions.
SWAIG functions belong to the agent definition, not to a communication channel. The same functions can serve voice calls and text conversations. Channel-specific actions, such as transferring a call or playing audio, apply only when the active channel supports them.
This guide follows one agent the whole way through: a dispatcher for a taxi company that quotes fares and books pickups.
The agent is the front end
Think about what you would ask of a new person on your dispatch desk. You would want them to listen well, get the caller to the point, and stay pleasant with someone who is running late. You would not ask them to memorize the fare table, or which drivers are free this afternoon, or how the airport surcharge is calculated. You would point them at the dispatch system and expect them to look it up, every time, because that is where the answer actually lives.
An AI agent earns its keep the same way. Let it run the conversation, and leave prices, availability, and formulas in the code that owns them. Tool calling is how you point the agent at that code and tell it when to go looking.
A prompt is a suggestion, and code is a constraint: “never give discounts” holds most of the time, while a pricing rule in your handler holds every time. Anything that must be exact, current, or enforced belongs behind a SWAIG function:
The split also keeps rate tables, prices, and policies out of the prompt, where they go stale and get ignored. Best practices and prompt engineering cover the prompt side. This guide covers the functions: how they work, how to build one, and how to keep them reliable on real calls.
How a SWAIG function works
A SWAIG function is a named capability you hand to the agent: a name, a description, and a JSON Schema describing its parameters. Each SWAIG request is one HTTP POST carrying a JSON body, answered by one JSON reply. Nothing stays open between requests, and nothing has to be installed on your server.
SWAIG function descriptions are prompt engineering. “Quote the fare from the caller’s pickup address to their destination” tells the agent what the function does and what it needs before calling it. When an agent picks the wrong function or calls it too early, the description is usually the first thing to fix.
The round trip goes from the caller, through the agent, to your server and back:
The request and the reply
SignalWire POSTs the request to the function’s web_hook_url.
Every request names the function, carries the arguments the agent extracted, and identifies itself
with "content_type": "text/swaig"; the fields around those describe the call and the session it
belongs to. If web_hook_url carries credentials in username:password@host form, they arrive as
HTTP basic authentication.
The AI SWAIG tool webhook documents every field, and the
SWAIG.functions reference documents the same fields next to the configuration
that declares them.
A minimal reply:
The response is written to the AI, not to the caller.
It can carry data, and it can carry instructions about what the agent should do next.
A reply can also carry action, which changes what the conversation does next. See
steering the conversation.
Where the code lives
Any endpoint that can host HTTP, accept JSON, and return JSON can serve a SWAIG function, which leaves you two ways to host yours:
- Your own webhook: a route you write and host, reachable over HTTPS from the public internet —
a Flask view, an Express handler, a serverless function behind its own URL. It reads the POST body,
runs whatever your business does, and returns the JSON reply. You name it in
web_hook_url, and nothing SignalWire-specific has to be installed for it to work. - The Server SDK: define the function and its handler in one class built on the SDK’s Agents
namespace (
AgentBase), and the SDK serves the endpoint for you. See SWAIG functions in the Server SDK.
If the function is a straightforward REST call, DataMap describes the request and response mapping declaratively and SignalWire executes it server-side, with no server of yours at all. See DataMap in the Server SDK or in SWML.
Declaring your functions
Wherever the code lives, the agent needs each function’s name, description, and parameters before it can call anything. There are two ways to hand it that declaration: write it into the agent, or point the agent at a URL and let SignalWire fetch it while the agent loads.
Declaring inline
An inline declaration lives with the rest of the agent’s configuration, so SignalWire has every
signature the moment the agent loads and asks your server nothing. In SWML, each function is an entry
in SWAIG.functions. With the Server SDK, each is a define_tool call, which
declares the function and registers the handler that runs it in one step.
Declare inline when the functions belong to one application. The whole contract is readable where the agent is configured, and there is no second endpoint to keep available.
The dispatch agent at the end of this guide declares its functions this way, in both forms.
Declaring remotely
A remote declaration leaves the agent holding only a URL. A SWAIG.includes entry
names that URL and the functions you want from it, and while the agent loads, SignalWire POSTs a
signature request there. Your server answers with the full definitions, so the
agent learns the signatures from the same server that implements them.
The point is that the asking happens up front rather than mid-call. The agent finishes loading knowing exactly what it can do, the same as if you had written the definitions in by hand — the list just arrived from somewhere else.
That matters once one toolset serves more than one agent. Say the taxi company runs three: a main
line, a corporate-accounts line, and an after-hours line, all of which quote and book. Declared
inline, the same definitions are pasted into three configurations, and adding a passenger_count
parameter means editing all three and redeploying each. Declared remotely, each agent names the
dispatch server’s URL, the parameter is added once where the handler already lives, and every agent
picks it up the next time it loads.
Your server replies to the signature request with an array of definitions — the same fields an inline
declaration carries, plus the web_hook_url each function should be called on:
A definition registers only when it has all three of function, description, and a web_hook_url;
one that omits any of them is skipped without an error, so check that a function you expected is
actually being offered. A shared web_hook_url can go in a defaults object returned alongside the
functions, and a definition that carries a data_map needs no URL at all.
For every field an entry accepts, see SWAIG.includes in SWML and
add_function_include in the Server SDK.
From there the two paths converge. A remotely declared function receives the same request, returns the
same response and action, and steers the call the same way; nothing downstream of the declaration
can tell them apart.
Steering the conversation
Alongside response, your code can return actions: instructions the platform executes during
the conversation. Some actions operate on either channel, while others require a live voice call:
- Update conversation state (
global_data) that later functions and the prompt can use. - Move the conversation to a different step or context, changing which functions are exposed.
- Send an SMS, such as a confirmation, a link, or a receipt.
- Transfer the call to a human, a queue, or another agent during a voice call.
- Play audio or execute calling SWML during a voice call.
Your backend decides, and the function result tells the agent what to do about it. When your code books the ride, it can return the pickup time for the agent to read out and an action that texts the caller the driver’s name and plate.
Every object action accepts is documented in the SWAIG.functions reference.
For the code side, see FunctionResult actions in the Server SDK, and the SWML
guides on switching context and toggling functions.
Reliability patterns
The failure modes below show up once real callers arrive.
Validate in code
Every argument the AI fills in was extracted from spoken, imperfect audio, so treat it as user input.
Normalize and verify it in your handler: geocode the address, check the account number’s format,
ask “Portland, Oregon or Portland, Maine?” when it matters.
When validation fails, return a response that tells the AI how to recover,
such as “No match for that address. Ask the caller to repeat it, street first.”
Keep validated state in global_data
Once your code has verified something, don’t make the AI carry it.
Write it to global_data, the conversation state that lives with the session,
and let downstream functions take no arguments at all, reading the validated state instead.
The dispatcher does this with a pair of handlers on the agent class. validate_trip takes the two
addresses as the caller said them, geocodes both, and writes the results to global_data.
get_quote declares no parameters at all and prices the trip from what is already there:
A function with no arguments has no arguments to get wrong.
The quote is computed from addresses your code validated, so a creative caller can’t talk the agent
into a different pickup or a better price.
The dispatch agent at the end of this guide wires both functions into a
complete Server SDK application and shows the equivalent SWML agent definition.
See state management for the full global_data lifecycle.
Scope functions to each step
An agent with every function available at every moment will eventually call one at the wrong time,
booking before it quotes or charging before it confirms.
Structure multi-stage conversations into steps, and scope which functions are active in each one.
The dispatcher can’t call book_ride before get_quote if book_ride doesn’t exist yet.
For more on scoping functions to a step, see contexts and workflows in the
Server SDK, or toggling functions in SWML.
Cover the wait on voice calls
Most lookups take a noticeable moment, and callers hear silence as a dropped call.
Give every function that leaves the conversation a filler phrase (“Let me work that out…”)
or hold audio, so the caller hears a working agent instead of dead air.
Fillers play asynchronously; when your endpoint is fast, the caller may never hear them at all.
Configure per-function fillers, as in the dispatch agent below, and agent-wide
function_fillers.
Read the post-prompt report
Define a post_prompt and set a post_prompt_url, and the platform delivers a report
after each conversation ends: the summary, the full conversation log, and every function call with
its timing.
Study the turns around each function call. A mis-picked function or a guessed argument usually traces
back to the function’s description, which you can revise like any other interface copy.
The open-source post prompt viewer
inspects these reports, with the transcript, telemetry, and latency in one place.
In the Server SDK, see post-prompt data.
A dispatch agent
Everything above comes together in one agent. A fare depends on two addresses, the current rate card,
and how busy the fleet is, so no prompt could hold it. The examples below confirm the addresses in
code before anything is priced, keep them in global_data so the quote itself takes no arguments,
cover both waits with fillers, and declare both functions inline.
In SWML, web_hook_url names the server you run; with the Server SDK, the class is the server,
and the SDK hosts the endpoint for you.
The Server SDK tab is a complete local application. The SWML tab is the equivalent agent definition;
replace its example webhook URLs with public endpoints that implement validate_trip and
get_quote before you run it.
For the SDK side, see SWAIG functions in the Server SDK and
SWAIG request handling.
For SWML, see the SWAIG guide and the SWAIG.functions reference.
geocode and price_ride stand in for the services you already run: an address lookup, a distance
API, a rate table, a surge multiplier. Swap them out and nothing else changes.
When an address doesn’t resolve, the agent asks for it again, because that is what your code returned.
You can exercise a function before any call.
The SDK’s swaig-test CLI loads the agent file,
executes a function with arguments you supply, and prints the exact response the agent would receive:
To hear it on a real call, run the Python file and point a phone number at your agent (the Server SDK quickstart walks through it), or paste the SWML into your Dashboard as shown in the AI quickstart.