SWAIG (SignalWire AI Gateway) is the platform's AI tool-calling system -- it connects the AI's decisions to actions like call transfers, SMS, recordings, and API calls, with native access to the media stack. This document is a reference for the swaig::FunctionResult class. These methods provide convenient abstractions for SWAIG actions, eliminating the need to manually construct action JSON objects.
Every action method returns FunctionResult& (a reference to *this), so calls chain fluently. The class is constructed and returned from a swaig::ToolHandler:
#include <signalwire/agent/agent_base.hpp>
#include <signalwire/swaig/function_result.hpp>
#include <signalwire/swaig/parameter_schema.hpp>
#include <nlohmann/json.hpp>
#include <iostream>
using json = nlohmann::json;
signalwire::agent::AgentBase agent("my-agent");
signalwire::swaig::FunctionResult result("ok");Creates a new result object with optional response text and post-processing behavior.
signalwire::swaig::FunctionResult r1("Hello, I'll help you with that");
signalwire::swaig::FunctionResult r2("Processing request...", /*post_process=*/true);Sets or updates the response text that the AI will speak.
result.set_response("I've updated your information");Controls whether the AI gets one more turn before executing actions.
result.set_post_process(true); // AI speaks response before executing actions
result.set_post_process(false); // Actions execute immediatelyExecute SWML content, with optional transfer behavior. swml_content is a json value.
// SWML as a json object
json swml = {{"version", "1.0.0"}, {"sections", {{"main", {{{"say", "Hello"}}}}}}};
result.execute_swml(swml, /*transfer=*/true);Transfer/connect the call to another destination using SWML.
result.connect("+15551234567", /*final=*/true); // Permanent transfer
result.connect("support@company.com", /*final=*/false, "+15559876543"); // Temporary transferTransfer the call to a destination and provide the response the AI speaks on return.
result.swml_transfer("+15551234567", "You are now back with the assistant.", true);Send an SMS message to a PSTN phone number using SWML.
// Simple text message
result.send_sms("+15551234567", "+15559876543", "Your order has been confirmed!");
// Media message with images (body empty, media vector populated)
result.send_sms("+15551234567", "+15559876543", "",
{"https://example.com/receipt.jpg", "https://example.com/map.png"});
// Full featured message with tags and region
result.send_sms("+15551234567", "+15559876543", "Order update",
{"https://example.com/receipt.pdf"},
{"order", "confirmation", "customer"},
"us");Parameters:
to(required): phone number in E.164 format to send tofrom(required): phone number in E.164 format to send frombody(optional): message text (required if no media)media(optional): vector of URLs to send (required if no body)tags(optional): vector of tags for UI searchingregion(optional): region to originate the message from
Process payments using the SWML pay action. The first argument is the connector URL; the remaining parameters (input method, timeout, amount, currency, prompts, etc.) all have defaults.
// Simple payment setup
result.pay("https://api.example.com/accept-payment",
/*input_method=*/"dtmf", /*status_url=*/"", /*payment_method=*/"credit-card",
/*timeout=*/10, /*max_attempts=*/3);Build custom prompts and parameters with the static helpers:
// Create payment actions
std::vector<json> welcome = {
signalwire::swaig::FunctionResult::create_payment_action("Say", "Welcome to our payment system"),
signalwire::swaig::FunctionResult::create_payment_action("Say", "Please enter your credit card number")
};
json card_prompt = signalwire::swaig::FunctionResult::create_payment_prompt("payment-card-number", welcome);
// Create payment parameters
std::vector<json> params = {
signalwire::swaig::FunctionResult::create_payment_parameter("customer_id", "12345"),
signalwire::swaig::FunctionResult::create_payment_parameter("order_id", "ORD-789")
};Static helper methods:
create_payment_action(action_type, phrase)→jsoncreate_payment_prompt(for_situation, actions, card_type = "", error_type = "")→jsoncreate_payment_parameter(name, value)→json
Start background call recording using SWML. The script continues executing while recording happens in the background.
// Simple background recording
result.record_call();
// Recording with custom settings
result.record_call("support_call_001", /*stereo=*/true, "mp3", "both");A typed overload accepts the RecordFormat and RecordDirection enums in place of the string format/direction, for call-site typo checking:
result.record_call("voicemail", false, signalwire::swaig::RecordFormat::Wav, signalwire::swaig::RecordDirection::Speak);Core parameters:
control_id(optional): identifier for this recording (use withstop_record_call)stereo: record in stereo (default: false)format:"wav","mp3", or"mp4"(default:"wav")direction:"speak","listen", or"both"(default:"both")- additional optional parameters:
terminators,beep,input_sensitivity,initial_timeout,end_silence_timeout,max_length,status_url
Stop an active background call recording. If control_id is empty, stops the most recent recording.
result.stop_record_call(); // Stop the most recent recording
result.stop_record_call("support_call_001"); // Stop a specific recording
// Chain to stop recording and provide feedback
result.stop_record_call("customer_voicemail")
.say("Thank you, your message has been recorded");Join a RELAY room (for multi-party communication and collaboration).
result.join_room("support_team_room");
result.join_room("customer_meeting_001")
.say("Welcome to the customer meeting room");Send a SIP REFER for call transfer in SIP environments.
result.sip_refer("sip:support@company.com");
result.say("Transferring your call to our specialist")
.sip_refer("sip:specialist@company.com");Join an ad-hoc audio conference. There are two overloads: a flat positional overload mirroring the SWML parameters 1:1, and an options-bag overload taking a swaig::JoinConferenceOptions struct (the C++-idiomatic way to pass the many optional parameters).
// Simple conference join
result.join_conference("my_conference");
// Options-bag overload — set only the fields you need
signalwire::swaig::JoinConferenceOptions opts;
opts.record = signalwire::swaig::ConferenceRecord::RecordFromStart;
opts.max_participants = 50;
opts.beep = signalwire::swaig::ConferenceBeep::OnEnter;
result.join_conference("customer_support_conf", opts);The closed-set fields (beep, record, trim, the callback methods) accept either the typed enum or a bare string; both produce identical SWML.
Start a background call tap. Media is streamed over WebSocket or RTP to a customer-controlled URI for real-time monitoring.
// Simple WebSocket tap
result.tap("wss://example.com/tap");
// RTP tap with custom settings
result.tap("rtp://192.168.1.100:5004", "monitoring_tap_001", "both", "PCMA", 30);A typed overload accepts the TapDirection and Codec enums. Note the tap direction set is {speak, hear, both} (hear, not record_call's listen):
result.tap("wss://monitoring.example.com/audio", "compliance_tap",
signalwire::swaig::TapDirection::Speak, signalwire::swaig::Codec::Pcmu);Stop an active tap stream. If control_id is empty, stops the last tap started.
result.stop_tap();
result.stop_tap("monitoring_tap_001");Terminate the call immediately.
result.hangup();Put the call on hold with a timeout (in seconds).
result.hold(60); // Hold for 1 minute
result.hold(600); // Hold for 10 minutesControl how the agent waits for user input. enabled and timeout are std::optional.
result.wait_for_user(true); // Wait indefinitely
result.wait_for_user(std::nullopt, 30); // Wait 30 seconds
result.wait_for_user(std::nullopt, std::nullopt, /*answer_first=*/true);
result.wait_for_user(false); // Disable waitingStop agent execution completely.
result.stop();Make the agent speak specific text immediately.
result.say("Please hold while I look that up for you");Play an audio file in the background. With wait=true, the AI suppresses attention while it plays.
result.play_background_file("hold_music.wav"); // AI tries to get attention
result.play_background_file("announcement.mp3", /*wait=*/true); // AI suppresses attentionStop the currently playing background audio.
result.stop_background_file();Set the silence timeout after speech detection for finalizing recognition.
result.set_end_of_speech_timeout(2000); // 2 seconds of silenceSet the timeout since the last speech event — better for noisy environments.
result.set_speech_event_timeout(3000); // 3 seconds since last speech eventAdd (or clear) speech-recognition hints for this turn.
result.add_dynamic_hints({{"hints", json::array({"SignalWire", "SWML"})}});
result.clear_dynamic_hints();Merge values into the global agent data.
result.update_global_data({{"user_name", "John"}, {"step", 2}});Remove global data variables by key(s). keys is a json value (a single string or an array of strings).
result.remove_global_data("temporary_data"); // Single key
result.remove_global_data({"step", "temp_value"}); // Multiple keysSet metadata scoped to the current function's meta_data_token.
result.set_metadata({{"session_id", "abc123"}, {"user_tier", "premium"}});Remove metadata from the current function's scope.
result.remove_metadata("temp_session_data"); // Single key
result.remove_metadata({"cache_key", "temp_flag"}); // Multiple keysEmit a SWML user event.
result.swml_user_event({{"event", "checkout_complete"}});Enable/disable specific SWAIG functions dynamically. The argument is a json array of toggle objects.
result.toggle_functions(json::array({
{{"function", "transfer_call"}, {"active", false}},
{{"function", "lookup_info"}, {"active", true}}
}));Control whether functions can be called on speaker timeout.
result.enable_functions_on_timeout(true);
result.enable_functions_on_timeout(false);Send full data to the LLM for this turn only, then use a smaller replacement.
result.enable_extensive_data(true);Remove or replace the tool_call + tool_result pair from the LLM's conversation history after the first send. This is useful when a function call is an implementation detail that would confuse the model if it remained visible in context. The argument is a json value: pass true to remove the pair entirely, or a string to replace it with an assistant message containing that text.
// Remove entirely — LLM won't see this function was called
signalwire::swaig::FunctionResult r1("Done.");
r1.replace_in_history(true);
// Replace with a friendly assistant message instead of tool artifacts
signalwire::swaig::FunctionResult r2("Profile saved.");
r2.replace_in_history("I've saved your profile information.");When to use:
- Functions that are implementation details (saving data, logging, internal state changes)
- Functions called frequently that would bloat conversation history
- Situations where tool artifacts confuse the model's reasoning
Update agent runtime settings with server-side validation. The argument is a json object.
// AI model settings
result.update_settings({
{"temperature", 0.7},
{"max-tokens", 2048},
{"frequency-penalty", -0.5}
});
// Speech recognition settings
result.update_settings({
{"confidence", 0.8},
{"barge-confidence", 0.7}
});Supported settings:
frequency-penalty: float (-2.0 to 2.0)presence-penalty: float (-2.0 to 2.0)max-tokens: integer (0 to 4096)top-p: float (0.0 to 1.0)confidence: float (0.0 to 1.0)barge-confidence: float (0.0 to 1.0)temperature: float (0.0 to 2.0, clamped to 1.5)
Change the agent's context/prompt during a conversation.
// Simple context switch
result.switch_context("You are now a technical support agent");
// Advanced context switch
result.switch_context("You are a billing specialist",
"The user needs help with their invoice",
/*consolidate=*/true);Navigate a multi-step / multi-context flow by name.
result.swml_change_step("collect_payment");
result.swml_change_context("billing");Queue simulated user input for testing or flow control.
result.simulate_user_input("Yes, I'd like to speak to billing");Add a single action manually (for custom actions not covered by the helper methods).
result.add_action("custom_action", {{"param", "value"}});Add multiple actions at once from a vector of JSON action objects.
result.add_actions({
{{"say", "Hello"}},
{{"hold", 300}}
});Render the result to JSON, or to a JSON string (optionally indented).
json j = result.to_json();
std::string s = result.to_string(2); // pretty-printed with 2-space indentFunctionResult also exposes low-level RELAY RPC helpers for advanced call control:
execute_rpc(method, params = {}, call_id = "", node_id = "")rpc_dial(to_number, from_number, dest_swml, device_type = "phone")rpc_ai_message(call_id, message_text, role = "system")rpc_ai_unhold(call_id)
result.rpc_ai_message("call-abc-123", "A new ticket was created for you.", "system");All action methods return *this, enabling fluent chaining:
signalwire::swaig::FunctionResult my_result =
signalwire::swaig::FunctionResult("Processing your request", /*post_process=*/true)
.update_global_data({{"status", "processing"}})
.play_background_file("processing.wav", /*wait=*/true)
.set_end_of_speech_timeout(2500);
// Transfer example
signalwire::swaig::FunctionResult transfer =
signalwire::swaig::FunctionResult("Let me transfer you to billing")
.set_metadata({{"transfer_reason", "billing_inquiry"}})
.update_global_data({{"last_action", "transfer_to_billing"}})
.connect("+15551234567", /*final=*/true);- Use
post_process=truewhen you want the AI to speak before executing actions. - Chain methods for cleaner, more readable code.
- Use the specific helper methods instead of manual
add_actionconstruction when one is available. - Validate settings —
update_settings()relies on server-side validation.
The post_data object is the JSON payload sent to SWAIG function handlers — it is delivered as the raw_data argument of a swaig::ToolHandler. Its structure differs between webhook functions and DataMap functions.
| Key | Type | Description |
|---|---|---|
app_name |
string | Name of the AI application |
function |
string | Name of the SWAIG function being called |
call_id |
string | Unique UUID of the current call session |
ai_session_id |
string | Unique UUID of the AI session |
caller_id_name |
string | Caller ID name (if available) |
caller_id_num |
string | Caller ID number (if available) |
channel_active |
boolean | Whether the channel is currently up |
channel_offhook |
boolean | Whether the channel is off-hook |
channel_ready |
boolean | Whether the AI session is ready |
argument |
object | Parsed function arguments |
argument_desc |
object | Function argument schema/description |
purpose |
string | Description of what the function does |
content_type |
string | Always "text/swaig" |
version |
string | SWAIG protocol version |
global_data |
object | Application-level global data (when set) |
conversation_id |
string | Conversation identifier (when tracking enabled) |
project_id |
string | SignalWire project ID |
space_id |
string | SignalWire space ID |
These keys are only present for traditional webhook SWAIG functions:
| Key | Type | Description | Present When |
|---|---|---|---|
meta_data_token |
string | Token for metadata access | Function has metadata token |
meta_data |
object | Function-level metadata | Function has metadata token |
SWMLVars |
object | SWML variables | swaig_post_swml_vars parameter set |
SWMLCall |
object | SWML call state | swaig_post_swml_vars parameter set |
call_log |
array | Processed conversation history | swaig_post_conversation is true |
raw_call_log |
array | Raw conversation history | swaig_post_conversation is true |
Metadata scoping: Functions sharing the same meta_data_token share access to the same metadata. If no token is specified, scope defaults to function name/URL.
Conversation history: call_log may shrink after conversation resets (consolidation), while raw_call_log preserves full history. Both include timing data (latency, utterance_latency, audio_latency).
| Key | Type | Description |
|---|---|---|
prompt_vars |
object | Template variables built from call context, SWML vars, and global_data |
args |
object | First parsed argument object for easy template access |
input |
object | Copy of entire post_data for variable expansion |
| Key | Source | Description |
|---|---|---|
call_direction |
Call direction | "inbound" or "outbound" |
caller_id_name |
Channel variable | Caller's name |
caller_id_number |
Channel variable | Caller's number |
local_date |
System time | Current date in local timezone |
local_time |
System time | Current time with timezone |
time_of_day |
Derived from hour | "morning", "afternoon", or "evening" |
supported_languages |
App config | Available languages |
default_language |
App config | Primary language |
All keys from global_data are also merged into prompt_vars, with global_data taking precedence.
| Parameter | Type | Default | Purpose |
|---|---|---|---|
swaig_allow_swml |
boolean | true | Allow functions to execute SWML actions |
swaig_allow_settings |
boolean | true | Allow functions to modify AI settings |
swaig_post_conversation |
boolean | false | Include conversation history in post_data |
swaig_set_global_data |
boolean | true | Allow functions to modify global_data |
swaig_post_swml_vars |
boolean/array | false | Include SWML variables in post_data |
DataMap processing supports template expansion with access to:
- Nested object access via dot notation:
${user.name} - Array access:
${items[0].value} - Encoding functions:
${enc:url:variable} - Built-in functions:
@{strftime %Y-%m-%d},@{expr 2+2}
- Agent Guide — general agent development guide
examples/simple_agent.cpp— basic SWAIG function usageexamples/swaig_features_agent.cpp— advancedFunctionResultaction usage (state, media, speech, context, SMS)