Functions: give your agent tools to call
Let a voice agent invoke your APIs mid-call — book a meeting, look up an order, transfer to a human. The function-calling protocol, parameter schemas, and the patterns that keep latency low.
Updated May 6, 2026
Functions are how a voice agent does anything beyond talking — book a meeting, look up an order, fire a webhook, transfer to a human. You define the tool; the LLM decides when to call it; the platform handles the I/O.
The mental model
A function is just an HTTP endpoint plus a JSON Schema describing its parameters. The LLM reads the schema, the user's intent, and decides when calling makes sense:
Caller: "What's the status of order 4521?"
LLM: decides to call get_order_status({"order_id": "4521"})
Platform: POSTs to https://your-api.com/order-status with the args
Your API: returns {"status": "shipped", "eta": "2026-05-08"}
Platform: hands the result back to the LLM
LLM: "Your order is shipped, expected to arrive May 8."
The agent narrates a filler line ("let me check that for you") while the HTTP call is in flight, so the silence doesn't feel like a hang.
Defining a function
Functions live inside the agent's response_engine.functions array, so you
define them by updating the agent:
curl -X PATCH https://api.call2me.app/v1/agents/agent_abc123 \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"response_engine": {
"type": "call2me-llm",
"functions": [
{
"name": "get_order_status",
"description": "Look up the shipping status of an e-commerce order by ID.",
"url": "https://your-api.com/order-status",
"method": "POST",
"parameters": {
"order_id": {
"type": "string",
"description": "The order number, digits only",
"required": true
}
},
"headers": {
"Authorization": "Bearer your-own-token"
}
}
]
}
}'
Or in the dashboard: Agents → Edit → Functions → New Function.
What each field does
| Field | Purpose |
|---|---|
name | Internal identifier; what the LLM uses to call it. Must match ^[a-zA-Z0-9_-]{1,64}$. |
description | The LLM reads this to decide when to call. Be explicit. Capped at 1024 characters. |
url | Your endpoint (HTTPS only, public address — private/internal IPs are rejected) |
method | GET, POST, PUT, or PATCH. Defaults to POST. |
parameters | A flat map of parameter name → {type, description, required, enum}. Not JSON Schema — the platform builds the schema for you. |
headers | Optional headers sent with your request, for your own auth. Host, Content-Length, and User-Agent are ignored. |
Note that parameters is a plain object keyed by parameter name, and each
parameter marks itself required. Wrapping it in a JSON Schema
({"type": "object", "properties": {...}}) will not work — the platform
would read type and properties as parameter names.
Limits worth knowing
These are enforced by the platform, not configurable per function:
- 8 second timeout. Slower than that, the agent tells the caller the lookup failed and moves on.
- 10 function calls per conversation. A guard against LLM loops.
- 1500 characters of response. Anything longer is truncated before reaching the LLM. Return a summary, not a full record dump.
There is no per-function filler-phrase field. To control what the agent says while waiting, instruct it in the system prompt — for example, "when you call get_order_status, first tell the caller you're checking."
What your endpoint receives
POST https://your-api.com/order-status
Content-Type: application/json
User-Agent: Call2Me-VoiceAgent/1.0
Accept: application/json, text/plain, */*
{
"order_id": "4521"
}
Only the arguments the LLM filled in are sent — plus any headers you
configured. If you need the call or agent ID on your side, declare it as a
parameter and tell the agent to pass it, or subscribe to
webhooks where the full call context is included.
For a GET function the arguments go in the query string instead of the
body.
Your endpoint returns a JSON object. Whatever you return becomes available to the LLM:
{
"status": "shipped",
"carrier": "UPS",
"tracking_number": "1Z999AA10123456784",
"eta": "2026-05-08"
}
The LLM reads the result, summarizes it for the caller, and continues the conversation.
Built-in functions
Some functions are provided by the platform and don't require an endpoint:
| Function | What it does |
|---|---|
end_call | Speak a closing line and hang up |
transfer_call | Hand the active call off to a department or number |
hold_call | Put the caller on hold with music while something is checked |
dtmf_input | Collect keypad digits — a PIN, an extension, a menu choice |
These are enabled by name, not defined with a schema. Turn them on in the
dashboard, or via response_engine.builtin_functions:
{
"response_engine": {
"type": "call2me-llm",
"builtin_functions": ["end_call", "transfer_call", "dtmf_input"]
}
}
A built-in that isn't in this list is inert — if the LLM tries to call it,
it gets Function not enabled back. For end_call, the goodbye line comes
from the agent's end_call_message if you set one; otherwise the platform
uses a default in the agent's language.
Sending an SMS mid-call is not a built-in. Use the SMS API from a custom function, or configure a post-call notification.
Latency budget
Phone conversations are unforgiving. Aim for:
- < 500ms — feels instant, no filler needed
- 500ms–1.5s — a filler line from the system prompt bridges it cleanly
- 1.5s–4s — feels slow; consider a longer filler with confidence
- > 4s — fix the underlying API or pre-fetch ahead of the call
For inbound calls where you know the caller's identity from the number,
pre-fetch their data into dynamic_variables instead of calling
mid-conversation.
Error handling
The platform never raises an exception into the conversation. Whatever goes wrong, the LLM receives a plain-text result it can act on:
Error: webhook timed out after 8.0s — please try a different option
Error: webhook unreachable
Webhook returned status 503: Service Unavailable
Error: called too quickly — please wait a moment
Non-2xx responses pass your response body through (first 200 characters),
so a useful error message from your API reaches the model — a 409 reading
"That slot was just taken" lets the agent offer another time instead of
apologizing vaguely.
Your agent prompt should know what to do:
"If a function call fails, apologize briefly and offer to transfer the caller to a human."
This is the pattern that keeps a single backend hiccup from torpedoing the conversation.
What's next
Frequently asked
Q.What's a function in voice AI?
A callable tool the agent can invoke during a conversation. You define name, description, and a parameter map; the LLM decides when to call it; the platform executes the HTTP call to your endpoint and returns the result back into the conversation.
Q.How fast does a function call have to return?
Under 500ms is ideal — instruct the agent in its system prompt to say a brief filler ('let me check that') while the call is in flight. Above 1.5s you'll start to feel awkward pauses, and the platform gives up at 8 seconds.
Q.Can a function transfer the call to a human?
Yes. transfer_call is a built-in function — enable it in the agent's builtin_functions list and configure the destination on the agent. You don't have to implement the carrier side.
Q.What if my function endpoint fails?
The platform hands the LLM a plain-text error instead of raising, and passes through the first 200 characters of your response body. Your agent prompt should tell the model what to do on failure — usually 'apologize and offer to transfer to a human' or 'try again once.'