How voice agents take actions mid-call: function calling, explained

Function calling lets a voice agent request a structured operation—such as checking availability, creating a ticket, or transferring a call—while the call is still in progress. The agent chooses a named function and proposes typed arguments; an orchestration layer validates permissions and data, executes the operation against a trusted system, and returns a structured result that the agent can explain to the caller. The agent does not directly run arbitrary code or gain automatic access to a database.

A function call is a request, not execution

In function calling, the application exposes a catalog of allowed operations to the agent. Each definition normally includes a stable name, a plain-language description, and an argument schema. The schema might say that check_availability requires a service identifier, start time, end time, and time zone, with each field constrained to a particular type or format.

When the conversation reaches the right point, the reasoning component produces a structured request instead of ordinary response text:

{
  "name": "check_availability",
  "arguments": {
    "service_id": "initial-consultation",
    "starts_after": "2026-08-24T09:00:00-07:00",
    "ends_before": "2026-08-24T12:00:00-07:00",
    "time_zone": "America/Los_Angeles"
  }
}

The orchestrator parses that request and decides whether it is valid and authorized. Only then does application code call a calendar, CRM, dispatch system, or internal service. The result returns through the orchestrator as data. The agent uses it to continue the call.

Calling this a “function” can be misleading. The implementation may be an in-process method, a queue-backed workflow, a REST API, or a remote tool exposed through the Model Context Protocol. The conversational contract stays the same: a controlled name and schema in, a bounded result out.

The end-to-end sequence on a live call

A typical action crosses several asynchronous systems:

  1. The caller speaks. Audio is recognized into a partial and then final transcript.
  2. Dialogue state is assembled. The agent receives the relevant conversation, collected fields, tool definitions, and policy instructions.
  3. The agent requests a tool. It emits a function name plus candidate arguments.
  4. The orchestrator validates. It checks schema, authorization, call state, confirmation requirements, and business rules.
  5. The executor performs the operation. It authenticates to the target system with server-held credentials and applies timeouts and retry policy.
  6. A structured result returns. The result distinguishes success, no match, validation failure, conflict, timeout, and system error.
  7. Dialogue resumes. The agent turns the result into a concise spoken response or asks for missing information.

This sequence is part of dialogue management, not a side channel. The call may need to stay silent, play a brief acknowledgement, collect more speech, or permit interruption while the operation is outstanding. The action and the speaking turn must share a state machine.

Tool schemas are part of the product interface

A good schema reduces conversational ambiguity before execution. Broad tools such as manage_customer with a free-form request field push critical interpretation into backend code. Narrow tools such as find_customer, update_contact_email, and create_support_case make permissions, validation, and confirmation easier to reason about.

Useful schema design follows several principles:

  • Use domain types, not prose. Represent a timestamp, currency, phone number, or enumerated status in a canonical form.
  • Make required fields genuinely required. If an operation cannot succeed without a postal code, do not let the schema imply it is optional.
  • Separate lookup from mutation. Reading appointment availability and booking an appointment have different risk and confirmation needs.
  • Return stable identifiers. Human-readable names can collide; IDs let later calls refer to the same record reliably.
  • Bound output size. A live call rarely needs hundreds of search results. Return a small, ranked set plus metadata that says whether more exist.
  • Describe errors structurally. slot_unavailable gives the agent a recovery path; an opaque server string does not.

Collecting arguments without guessing

The agent usually builds arguments over several turns. A caller might say, “I need a repair visit Tuesday morning,” then supply a ZIP code, name, and contact number in response to follow-up questions. The multi-turn conversation state should distinguish three categories:

  • Values the caller stated and the system parsed confidently.
  • Values inferred from trusted context, such as the authenticated account's time zone.
  • Values still missing or ambiguous.

Those categories should not collapse into one object. An agent that guesses a date, location, or identity can produce a schema-valid but incorrect action. Critical inferred values should be repeated back or explicitly confirmed.

Normalization belongs before execution. “Next Friday morning” must become a date range in a named time zone. “My usual location” must resolve to a specific record. If more than one interpretation remains, the orchestrator should return a validation result that prompts a targeted question rather than allowing the backend to choose silently.

Read, write, and irreversible actions need different controls

Not every tool call carries the same risk. A practical policy divides operations into tiers.

Read-only operations retrieve availability, order status, hours, or account facts. They still require authorization and data minimization, but usually do not need verbal confirmation for every request.

Reversible writes create a draft ticket, hold a slot, or update a preference that can be changed later. The agent should summarize the important fields before committing when a mistake would inconvenience the caller.

Consequential or difficult-to-reverse writes cancel service, submit an application, disclose protected data, or trigger an external side effect. These require explicit confirmation, stricter identity checks, and often a human approval path.

Confirmation must bind to a specific proposed action. Asking “Is that okay?” before the final date or amount is known does not authorize the later operation. Store the exact fields presented to the caller and the turn in which consent was given.

Business authorization also belongs outside the agent. The backend decides whether this caller may access this customer record, whether the requested slot is still valid, and whether policy permits the change. Prompt instructions are guidance; they are not an access-control system.

Idempotency and concurrency prevent duplicate actions

Live-call infrastructure retries. A network response can be lost after the target system has already committed a booking. If the orchestrator repeats the request without an idempotency key, the caller may receive two appointments even though the agent made one logical request.

Generate an idempotency key from the call and action attempt, persist it before execution, and send it to downstream systems that support deduplication. If the same attempt arrives again, return the original result rather than performing the write twice.

Concurrency creates another class of races. Availability can disappear between lookup and booking. The caller can correct a date while the previous request is in flight. An inbound call can end while a slow tool still runs. The orchestration state should therefore track an action ID, the conversation revision that created it, whether it is cancelable, and whether its result is still relevant.

For reservations, prefer an atomic backend operation or an explicit hold-and-confirm protocol. “The slot appeared in search” is not proof that a later write will succeed. A conflict result should return alternatives and let the conversation recover.

Designing the spoken wait state

Function latency is experienced as silence unless the call flow handles it. The agent can acknowledge the action—“I’ll check that now”—before waiting, but acknowledgements should not imply success. “I’ve booked it” is wrong until the write commits.

For a fast operation, one brief acknowledgement may cover the wait. For a slower operation, the dialogue layer needs a policy: play neutral progress audio, allow the caller to keep speaking, cancel on request, or route to a human. Repeated filler can be more frustrating than a transparent limitation.

Barge-in complicates the state. If the caller says “Actually, make that Thursday” while Tuesday's lookup runs, the system must decide whether to cancel, ignore the stale result, or use it only as context. Tagging tool requests with conversation revisions prevents an old response from overwriting the corrected state.

Failure modes should be designed, not improvised

A tool result should separate failures the conversation can resolve from failures it cannot.

  • Missing or invalid arguments: Ask for the specific field again.
  • No matching record: Confirm spelling or request another identifier without revealing unrelated records.
  • Business conflict: Explain that the requested option is unavailable and offer valid alternatives.
  • Authentication or authorization failure: Do not retry with invented credentials; move to verification or a protected handoff.
  • Timeout: State that the system could not confirm the result. Never describe an unknown write as failed or successful until its status is reconciled.
  • Rate or capacity limit: Back off according to policy and avoid tight retries inside the call.
  • Internal error: Use a safe message, preserve an audit record, and choose a fallback.

Retries are appropriate only when the operation is idempotent and the error is plausibly transient. Validation errors do not improve with retrying. Unknown outcomes for non-idempotent writes require a status check, not another create request.

When the action cannot safely complete, a human handoff should carry the collected fields, attempted function, and exact outcome. The human should not have to reconstruct the failure from the entire transcript.

Security boundaries for tool execution

Keep credentials in the executor, never in the prompt, transcript, or model-visible tool result. Give each tool the narrowest scope it needs. An availability lookup should not inherit permission to delete calendar events because both use the same upstream system.

Validate every argument after generation. Enforce allowed destinations, record ownership, string lengths, enumerations, date bounds, and network egress rules in deterministic code. Treat caller speech and retrieved text as untrusted input; neither should be able to redefine tool policy.

Minimize tool results before returning them to the conversation. A customer lookup may find a full internal record, but the agent should receive only fields needed for the current task. Redact secrets and avoid placing sensitive backend errors into spoken responses or transcripts.

Audit events should record the call ID, tool version, validated arguments, authorization decision, idempotency key, execution timing, outcome, and the user confirmation associated with consequential writes. Logs need their own access and retention controls.

Function calls and webhooks solve opposite directions

A function call is usually a synchronous or bounded request from the live conversation to another system: “check this now and give me a result I can use on the call.” A webhook usually carries an event from the call platform to a subscriber: “this call completed” or “this state changed.”

They can work together. A mid-call function can create a case and return its ID immediately; a webhook can later notify another system that the call ended and include final metadata. The function path affects the current turn and needs a tight timeout. The webhook path can be asynchronous and needs durable delivery, signature verification, and replay handling. See how webhooks carry call events for that lifecycle.

Testing function calling on calls

Start with contract tests for every schema and executor. Then test conversations that omit fields, correct earlier answers, interrupt during execution, deny confirmation, repeat the same request, and end before a result returns. Inject timeouts, malformed outputs, authorization failures, duplicate deliveries, and ambiguous dates.

Assertions should cover more than spoken wording. Verify the exact validated arguments, number of backend writes, idempotency behavior, confirmation evidence, structured result, and final call state. For consequential tools, a test that merely hears “done” proves almost nothing; the authoritative target system must show one correct change.

Deploy new tool versions gradually. Keep old contracts available for active calls, correlate each action with a version, and maintain a fast disable path for writes.

On ThunderPhone

ThunderPhone supports attaching remote MCP servers to agents by URL, synchronizing them for automatic tool discovery, and supplying static HTTP headers. The documented transport is Streamable HTTP; stdio-only local servers are not supported.

FAQ

Is function calling the same as an API call?

No. Function calling is the structured request produced by the agent. Application code validates that request and may fulfill it through an API, local function, queue, or remote tool protocol.

Can a voice agent call any function in a codebase?

It should not. Expose a small allowlist of purpose-built tools with narrow permissions and validated schemas. Arbitrary code execution is neither necessary nor safe.

What should happen if a tool times out after a write may have succeeded?

Treat the outcome as unknown. Query status using the idempotency key or operation ID before retrying, and tell the caller that confirmation is unavailable until the system reconciles the result.

When should the caller confirm an action?

Require confirmation when a write is consequential, difficult to reverse, based on uncertain input, or policy-controlled. Bind the confirmation to the exact values that will be submitted.