This document provides a comprehensive reference for all public APIs in the SignalWire AI Agents SDK for Java.
import com.signalwire.sdk.agent.AgentBase;
import com.signalwire.sdk.swaig.FunctionResult;
import com.signalwire.sdk.datamap.DataMap;
import com.signalwire.sdk.contexts.ContextBuilder;
AgentBase agent = AgentBase.builder().name("ref-agent").route("/agent").build();
FunctionResult result = new FunctionResult("...");
DataMap dataMap = new DataMap("ref_tool");
ContextBuilder contexts = agent.defineContexts();Add the SDK as a dependency (Maven Central coordinates com.signalwire:signalwire-sdk:4.0.0).
Gradle (build.gradle):
dependencies {
implementation 'com.signalwire:signalwire-sdk:4.0.0'
}Maven (pom.xml):
<dependency>
<groupId>com.signalwire</groupId>
<artifactId>signalwire-sdk</artifactId>
<version>4.0.0</version>
</dependency>- AgentBase Class - Core agent functionality
- FunctionResult Class - SWAIG (SignalWire AI Gateway) function response handling
- DataMap Class - Serverless API tools that execute on SignalWire's servers
- Context System - Structured workflows
- State Management - Persistent state
- Skills System - Modular capabilities
- Utility Classes - Supporting classes
The AgentBase class is the foundation for creating AI agents. It extends Service (the base class for generating SWML -- SignalWire Markup Language -- documents) and provides comprehensive functionality for building conversational AI agents.
import com.signalwire.sdk.agent.AgentBase;
Agents are constructed through the fluent AgentBase.builder() API, not a public constructor. Call build() to obtain the agent, then configure it with the instance methods below.
agent = AgentBase.builder()
.name("my-agent") // Human-readable name
.route("/") // HTTP route path (default: "/")
.host("0.0.0.0") // Host address to bind to (default: "0.0.0.0")
.port(3000) // Port number to listen on (default: 3000)
.authUser("admin") // HTTP basic auth username (optional)
.authPassword("secret") // HTTP basic auth password (optional)
.autoAnswer(true) // Automatically answer incoming calls (default: true)
.maxDuration(3600) // Max call duration in seconds
.recordCall(false) // Record calls by default (default: false)
.recordFormat("mp4") // Recording format: "mp4", "wav", "mp3"
.recordStereo(true) // Record in stereo (default: true)
.signingKey("...") // Webhook-signature signing key (optional)
.build();Builder methods:
name(String): Human-readable name for the agentroute(String): HTTP route path for the agent (default:"/")host(String): Host address to bind to (default:"0.0.0.0")port(int): Port number to listen on (default:3000)authUser(String)/authPassword(String): Username/password for HTTP basic authautoAnswer(boolean): Automatically answer incoming calls (default:true)maxDuration(int): Maximum call duration in secondsrecordCall(boolean): Record calls by default (default:false)recordFormat(String)/recordFormat(RecordFormat): Recording format:"mp4","wav","mp3"(default:"mp4")recordStereo(boolean): Record in stereo (default:true)signingKey(String): Key for inbound webhook signature validationtrustProxyForSignature(boolean): Trust proxy headers when validating signaturesenvProvider(EnvProvider): Override the environment-variable source (useful for testing)
Start the agent's HTTP server. Delegates to serve(). In serverless deployments (e.g. AWS Lambda) the base URL is auto-detected via detectServerlessBaseUrl().
Usage:
agent = AgentBase.builder().name("my-agent").port(3000).build();
agent.run(); // blocks serving on the configured host/portThe port can also be overridden at runtime with the PORT environment variable, and host/port defaults come from the builder.
Set the agent's prompt as raw text. Returns the agent for chaining.
Usage:
agent.setPromptText("You are a helpful customer service agent.");Set additional text to append after the main prompt.
Usage:
agent.setPostPrompt("Always be polite and professional.");Set Language Model parameters for the main prompt. Parameters are MERGED with any previously set (successive calls accumulate; a repeated key overwrites). Values are passed through to the SignalWire server, which validates and applies them based on the target model's capabilities.
Common Parameters:
temperature: Controls randomness. Lower = more focusedtop_p: Nucleus sampling thresholdbarge_confidence: ASR confidence to interruptpresence_penalty: Topic diversity controlfrequency_penalty: Repetition control
Note: No defaults are sent unless explicitly set. Invalid parameters for the selected model are handled/ignored by the server.
Usage:
// Configure for consistent, professional responses
agent.setPromptLlmParams(Map.of(
"temperature", 0.3,
"top_p", 0.9,
"barge_confidence", 0.7,
"presence_penalty", 0.1,
"frequency_penalty", 0.2
));Set Language Model parameters for the post-prompt. Also merges with previous values. Passed through to the SignalWire server.
Common Parameters:
temperature: Controls randomness. Lower = more focusedtop_p: Nucleus sampling thresholdpresence_penalty: Topic diversity controlfrequency_penalty: Repetition control
Note: barge_confidence is not applicable to post-prompt. No defaults are sent unless explicitly set.
Usage:
// Configure for focused summaries
agent.setPostPromptLlmParams(Map.of(
"temperature", 0.2,
"top_p", 0.9
));Add a structured section to the prompt using the Prompt Object Model. Overloads: promptAddSection(title, body) (no bullets).
Parameters:
title: Section title/headingbody: Main section content (pass""for none)bullets: List of bullet points
Usage:
// Simple section
agent.promptAddSection("Role", "You are a customer service representative.");
// Section with bullets
agent.promptAddSection(
"Guidelines",
"Follow these principles:",
List.of("Be helpful", "Stay professional", "Listen carefully"));Add bullet content to an existing prompt section.
Parameters:
title: Title of existing section to modifybullets: Bullet points to add
Usage:
// Add bullets to an existing section
agent.promptAddToSection("Process", List.of("Follow up", "Close ticket"));Add a subsection to an existing prompt section.
Parameters:
parentTitle: Title of parent sectiontitle: Subsection titlebody: Subsection content
Usage:
agent.promptAddSubsection(
"Guidelines",
"Escalation Rules",
"Escalate when the customer is angry or the issue is beyond scope.");Configure voice and language settings for the agent. Overloads:
addLanguage(String name, String code, String voice)addLanguage(String name, String code, String voice, List<String> speechFillers, List<String> functionFillers, String engine, String model)addLanguage(String name, String code, String voice, List<String> speechFillers, List<String> functionFillers, String engine, String model, Map<String, Object> params)
Parameters:
name: Human-readable language namecode: Language code (e.g.,"en-US","es-ES")voice: Voice identifier (e.g.,"rime.spore","nova.luna"). A combined"engine.voice:model"string is parsed automatically whenengine/modelare not given.speechFillers: Filler phrases during speech processingfunctionFillers: Filler phrases during function executionengine: TTS engine to usemodel: AI model to use
Usage:
// Basic language setup
agent.addLanguage("English", "en-US", "rime.spore");
// With custom fillers
agent.addLanguage(
"English",
"en-US",
"nova.luna",
List.of("Let me think...", "One moment..."),
List.of("Processing...", "Looking that up..."),
null, // engine
null); // modelSet multiple language configurations at once.
Usage:
agent.setLanguages(List.of(
Map.of("name", "English", "code", "en-US", "voice", "rime.spore"),
Map.of("name", "Spanish", "code", "es-ES", "voice", "nova.luna")));Add a single speech recognition hint.
Usage:
agent.addHint("SignalWire");Add multiple speech recognition hints.
Usage:
agent.addHints(List.of("SignalWire", "SWML", "API", "webhook", "SIP"));Add a pattern-based hint for speech recognition. Overload: addPatternHint(hint, pattern, replace) (case-sensitive).
Parameters:
hint: The hint phrasepattern: Regex pattern to matchreplace: Replacement textignoreCase: Case-insensitive matching
Usage:
agent.addPatternHint(
"phone number",
"(\\d{3})-(\\d{3})-(\\d{4})",
"($1) $2-$3",
false);Add a pronunciation rule for text-to-speech.
Parameters:
replace: Text to replacewith: Replacement pronunciationignoreCase: Case-insensitive replacement
Usage:
agent.addPronunciation("API", "A P I", false);
agent.addPronunciation("SWML", "swim-el", false);Set multiple pronunciation rules at once.
Usage:
agent.setPronunciations(List.of(
Map.of("replace", "API", "with", "A P I"),
Map.of("replace", "SWML", "with", "swim-el", "ignore_case", true)));Set a single AI parameter.
Usage:
agent.setParam("ai_model", "gpt-4.1-nano");
agent.setParam("end_of_speech_timeout", 500);Set multiple AI parameters at once.
Common Parameters:
ai_model: AI model to use ("gpt-4.1-nano","gpt-4.1-mini", etc.)end_of_speech_timeout: Milliseconds to wait for speech end (default: 1000)attention_timeout: Milliseconds before attention timeout (default: 30000)background_file_volume: Volume for background audio (-60 to 0 dB)temperature: AI creativity/randomness (0.0 to 2.0)max_tokens: Maximum response lengthtop_p: Nucleus sampling parameter (0.0 to 1.0)
Usage:
agent.setParams(Map.of(
"ai_model", "gpt-4.1-nano",
"end_of_speech_timeout", 500,
"attention_timeout", 15000,
"background_file_volume", -20,
"temperature", 0.7));Set global data available to the AI and functions.
Usage:
agent.setGlobalData(Map.of(
"company_name", "Acme Corp",
"support_hours", "9 AM - 5 PM EST",
"escalation_number", "+1-555-0123"));Update existing global data (merge with existing).
Usage:
agent.updateGlobalData(Map.of(
"current_promotion", "20% off all services",
"promotion_expires", "2024-12-31"));Define a custom SWAIG function/tool. The handler is a ToolHandler functional interface — a lambda (args, raw) -> FunctionResult, where args is a Map<String, Object> of parsed function arguments and raw is the raw request data. There is also defineTool(ToolDefinition) for a pre-built definition and defineTools(List<ToolDefinition>).
Parameters:
name: Function namedescription: Function description for the AIparameters: JSON schema for function parameters (anobjectschema withproperties/required)handler:ToolHandlerlambda executed when the tool is called
Usage:
import com.signalwire.sdk.swaig.FunctionResult;
agent.defineTool(
"get_weather",
"Get current weather for a location",
Map.of(
"type", "object",
"properties", Map.of(
"location", Map.of("type", "string", "description", "City name")),
"required", List.of("location")),
(args, raw) -> {
String location = (String) args.getOrDefault("location", "Unknown");
return new FunctionResult("The weather in " + location + " is sunny and 75F");
});For a definition that needs extra SWAIG fields (secure flag, per-language fillers), build a ToolDefinition and pass it to defineTool(ToolDefinition):
import com.signalwire.sdk.swaig.ToolDefinition;
var def = new ToolDefinition(
"get_weather",
"Get current weather for a location",
Map.of("type", "object", "properties", Map.of(
"location", Map.of("type", "string", "description", "City name")),
"required", List.of("location")),
(args, raw) -> new FunctionResult("Sunny and 75F"));
def.setSecure(true);
agent.defineTool(def);Register a pre-built SWAIG function dictionary (for example the output of a DataMap).
Usage:
// Register a DataMap tool
var weatherTool = new DataMap("get_weather")
.webhook("GET", "https://api.weather.com/...");
agent.registerSwaigFunction(weatherTool.toSwaigFunction());SignalWire AI agents support special SWAIG functions that are automatically called at specific points in the conversation lifecycle. In Java, register them like any other tool via defineTool using the reserved names startup_hook and hangup_hook.
Called when a new conversation/call begins.
Implementation:
agent.defineTool(
"startup_hook",
"Called when a new conversation starts to initialize state",
Map.of("type", "object", "properties", Map.of()),
(args, raw) -> {
Object callId = raw.get("call_id");
// Initialize session resources, load user data, etc.
return new FunctionResult("Session initialized");
});Called when a conversation/call ends.
Implementation:
agent.defineTool(
"hangup_hook",
"Called when conversation ends to clean up resources",
Map.of("type", "object", "properties", Map.of()),
(args, raw) -> {
Object callId = raw.get("call_id");
// Clean up resources, save session data, etc.
return new FunctionResult("Session ended");
});Common Use Cases:
- Loading user preferences at session start
- Initializing session-specific resources
- Logging conversation metrics
- Cleaning up temporary data
- Saving conversation summaries
Add a modular skill to the agent. Overloads: addSkill(SkillName, Map) (enum form). Pass null (or an empty map) for skills needing no configuration.
Parameters:
skillName: Name of the skill to addparams: Skill configuration parameters
Available Skills:
datetime: Current date/time informationmath: Mathematical calculationsweb_search: Google Custom Search integrationdatasphere: SignalWire DataSphere searchnative_vector_search: Remote document search
Usage:
// Simple skill (no config)
agent.addSkill("datetime", null);
agent.addSkill("math", null);
// Skill with configuration
agent.addSkill("web_search", Map.of(
"api_key", "your-google-api-key",
"search_engine_id", "your-search-engine-id",
"num_results", 3));
// Multiple instances with different tool names
agent.addSkill("web_search", Map.of(
"api_key", "your-api-key",
"search_engine_id", "general-engine",
"tool_name", "search_general"));
agent.addSkill("web_search", Map.of(
"api_key", "your-api-key",
"search_engine_id", "news-engine",
"tool_name", "search_news"));Remove a skill from the agent. Overload: removeSkill(SkillName).
Usage:
agent.removeSkill("web_search");Get the list of currently added skills.
Returns: List<String> — names of active skills.
Usage:
List<String> activeSkills = agent.listSkills();
System.out.println("Active skills: " + activeSkills);Check if a skill is currently added. Overload: hasSkill(SkillName).
Returns: boolean — true if the skill is active.
Usage:
if (agent.hasSkill("web_search")) {
System.out.println("Web search is available");
}Enable specific native SWML functions.
Available Native Functions:
transfer: Transfer callshangup: End callsplay: Play audio filesrecord: Record audiosend_sms: Send SMS messages
Usage:
agent.setNativeFunctions(List.of("transfer", "hangup", "send_sms"));Set custom filler phrases for internal/native SWAIG functions, keyed by function name → language code → filler phrases. (There is also setInternalFillers(List<Map<String, Object>>) for a list-of-maps shape.)
Available Internal Functions:
next_step: Moving between workflow steps (contexts system)change_context: Switching contexts in workflowscheck_time: Getting current timewait_for_user: Waiting for user inputwait_seconds: Pausing for specified durationget_visual_input: Processing visual data
Usage:
agent.setInternalFillersMap(Map.of(
"next_step", Map.of(
"en-US", List.of("Moving to the next step...", "Let's continue..."),
"es", List.of("Pasando al siguiente paso...", "Continuemos...")),
"check_time", Map.of(
"en-US", List.of("Let me check the time...", "Getting current time..."))));Add internal fillers for a specific function and language.
Usage:
agent.addInternalFiller("next_step", "en-US", List.of(
"Great! Let's move to the next step...",
"Perfect! Moving forward..."));Include external SWAIG functions from another service.
Parameters:
url: URL of the external SWAIG servicefunctions: The function-include configuration (function names and optional metadata)
Usage:
agent.addFunctionInclude(
"https://external-service.com/swaig",
Map.of(
"functions", List.of("external_function1", "external_function2"),
"meta_data", Map.of("service", "external", "version", "1.0")));Set multiple function includes at once.
Usage:
agent.setFunctionIncludes(List.of(
Map.of(
"url", "https://service1.com/swaig",
"functions", List.of("func1", "func2")),
Map.of(
"url", "https://service2.com/swaig",
"functions", List.of("func3"),
"meta_data", Map.of("priority", "high"))));Set the default webhook URL for SWAIG functions.
Usage:
agent.setWebHookUrl("https://myserver.com/webhook");Set the URL for post-prompt processing.
Usage:
agent.setPostPromptUrl("https://myserver.com/post-prompt");Add query parameters to be included in all SWAIG webhook URLs. Useful for preserving dynamic configuration state across SWAIG callbacks — e.g. if your dynamic config adds skills based on query parameters, pass those same parameters through to the SWAIG webhook so the same configuration is applied.
Usage:
// In a dynamic config callback, preserve configuration parameters
agent.setDynamicConfigCallback((queryParams, headers, body, cfg) -> {
String customerId = queryParams.get("customer_id");
if (customerId != null) {
cfg.addSwaigQueryParams(Map.of("customer_id", customerId));
cfg.addSkill("customer_lookup", Map.of("customer_id", customerId));
}
});Clear all SWAIG query parameters.
Usage:
agent.clearSwaigQueryParams();Enable the debug event webhook for this agent. When enabled, the AI module POSTs real-time debug events to a /debug_events endpoint on this agent during calls. Events are automatically logged via the agent's structured logger and can optionally be handled with a custom callback via onDebugEvent().
Usage:
agent.enableDebugEvents();How it works:
- Registers a
/debug_eventsPOST endpoint on the agent's HTTP server - Auto-sets
debug_webhook_urlanddebug_webhook_levelin the SWMLparamsduring rendering - The URL is built automatically using the same auth/proxy logic as other webhook URLs
- No manual URL configuration needed
Event types (level 1):
| Event label | Description |
|---|---|
session_start |
AI session started (model, TTS engine, voice, language) |
session_end |
AI session ended (reason, duration, token counts) |
barge |
User interrupted AI speech (barge type, elapsed ms) |
step_change |
Conversation step changed |
context_change |
Conversation context changed |
llm_error |
LLM error (fatal, retry, max_retries) |
voice_error |
TTS voice configuration or runtime error |
hold |
Call placed on hold or taken off hold |
filler |
Filler phrase spoken (thinking or function filler) |
consolidation |
Token consolidation triggered |
process_action |
Webhook action being processed |
gather_start |
Gather flow started |
gather_complete |
Gather flow completed |
Additional high-volume events:
| Event label | Description |
|---|---|
llm_request |
LLM API request initiated (input tokens) |
llm_response |
LLM API response received (duration, output tokens) |
conversation_add |
Entry added to conversation history |
These methods allow you to customize the SWML call flow by inserting verbs at different stages of the call lifecycle. Verb data is passed as an Object (typically a Map).
Add a verb to run before the call is answered (while still ringing).
Safe pre-answer verbs: transfer, execute, return, label, goto, request, switch, cond, if, eval, set, unset, hangup, send_sms, sleep, stop_record_call, stop_denoise, stop_tap
Usage:
// Send SMS before answering
agent.addPreAnswerVerb("send_sms", Map.of(
"to", "+15551234567",
"from", "+15559876543",
"body", "Incoming call from AI agent"));
// Set variables before answer
agent.addPreAnswerVerb("set", Map.of("call_start", "${system.timestamp}"));Configure the answer verb that connects the call.
Usage:
// Set maximum call duration to 1 hour
agent.addAnswerVerb("answer", Map.of("max_duration", 3600));Add a verb to run after the call is answered but before the AI starts.
Usage:
// Play welcome message before AI starts
agent.addPostAnswerVerb("play", Map.of(
"url", "say:Welcome to our AI assistant. This call may be recorded."));
// Add a brief pause
agent.addPostAnswerVerb("sleep", Map.of("duration", 1));Add a verb to run after the AI conversation ends.
Usage:
// Clean hangup after AI ends
agent.addPostAiVerb("hangup", Map.of());
// Transfer to human after AI conversation
agent.addPostAiVerb("transfer", Map.of("to", "+15551234567"));
// Log call completion
agent.addPostAiVerb("request", Map.of(
"url", "https://myserver.com/call-complete",
"method", "POST"));Remove all pre-answer / post-answer / post-AI verbs respectively.
Method Chaining Example:
agent.addPreAnswerVerb("set", Map.of("source", "ai_agent"))
.addAnswerVerb("answer", Map.of("max_duration", 1800))
.addPostAnswerVerb("play", Map.of("url", "say:Hello!"))
.addPostAiVerb("hangup", Map.of());Set a callback for per-request dynamic configuration. The DynamicConfigCallback functional interface receives (queryParams, headers, body, agent) and mutates the agent for that request.
Usage:
// Callback signature: configure(queryParams, bodyParams, headers, agent)
agent.setDynamicConfigCallback((queryParams, bodyParams, headers, cfg) -> {
// Configure based on request
if ("spanish".equals(queryParams.get("language"))) {
cfg.addLanguage("Spanish", "es-ES", "nova.luna");
}
// Set customer-specific data (headers map values are lists)
List<String> customerIdValues = headers.get("X-Customer-ID");
if (customerIdValues != null && !customerIdValues.isEmpty()) {
cfg.setGlobalData(Map.of("customer_id", customerIdValues.get(0)));
}
});Enable SIP-based routing for voice calls.
Usage:
agent.enableSipRouting();Register a specific SIP username for this agent.
Usage:
agent.registerSipUsername("support");
agent.registerSipUsername("sales");Inspect registered SIP usernames (Set<String>) and whether SIP routing is enabled (boolean).
Usage:
if (agent.isSipRoutingEnabled()) {
System.out.println("SIP usernames: " + agent.getSipUsernames());
}Get the agent's name. Returns String.
Get the underlying JDK HTTP server instance (com.sun.net.httpserver.HttpServer) for advanced embedding.
Render the agent's complete SWML document as a Map<String, Object> or a JSON String.
Usage:
String swmlJson = agent.renderSwmlJson("https://myserver.com");
System.out.println(swmlJson);Register a handler for conversation summaries. The callback is invoked when the AI generates a summary based on your post-prompt configuration. It receives (summary, rawData), where summary is the parsed summary map and rawData is the complete raw POST data.
Usage:
agent = AgentBase.builder().name("summary-agent").route("/agent").build();
// Configure post-prompt to request a JSON summary
agent.setPostPrompt(
"Return a JSON summary of the conversation: "
+ "{\"topic\":\"...\",\"satisfied\":true,\"follow_up_needed\":false,\"key_points\":[]}");
agent.onSummary((summary, rawData) -> {
if (summary != null) {
Object topic = summary.getOrDefault("topic", "Unknown");
Object satisfied = summary.getOrDefault("satisfied", false);
System.out.println("Call about: " + topic + ", Customer satisfied: " + satisfied);
// Save to database, send to CRM, trigger follow-up, etc.
if (Boolean.TRUE.equals(summary.get("follow_up_needed"))) {
// scheduleFollowUp(summary);
}
}
});Register a handler for debug webhook events. Requires enableDebugEvents() to be called first. The callback receives the full event payload map, which includes call_id, label, and event-specific fields.
Usage:
agent = AgentBase.builder().name("debug-agent").route("/agent").build();
agent.enableDebugEvents();
agent.onDebugEvent(data -> {
Object callId = data.get("call_id");
String label = String.valueOf(data.get("label"));
switch (label) {
case "llm_error" ->
System.out.println("LLM error on call " + callId + ": " + data.get("event"));
case "barge" ->
System.out.println("Barge after " + data.get("barge_elapsed_ms") + "ms");
case "session_end" ->
System.out.println("Call ended: " + data.get("reason")
+ ", duration: " + data.get("duration_ms") + "ms");
default -> { }
}
});Note: Even without registering a handler, all debug events are automatically logged via the agent's structured logger when
enableDebugEvents()is called.
The base dispatch entry point for tool calls. It routes to the registered ToolHandler for name and returns its FunctionResult. You typically define tools with defineTool rather than overriding this, but it can be called or wrapped for custom dispatch logic.
Returns: FunctionResult.
Basic-auth credentials come from the builder (authUser/authPassword) or environment variables (SWML_BASIC_AUTH_USER / SWML_BASIC_AUTH_PASSWORD). Comparison is timing-safe (MessageDigest.isEqual()); when no password is supplied, one is generated with SecureRandom rather than falling back to a weak default.
Retrieve the effective basic-auth credentials (from the builder or environment).
Usage:
String user = agent.getAuthUser();
String pass = agent.getAuthPassword();Validate an inbound webhook's signature against the configured signing key. Returns boolean.
Define structured workflow contexts for the agent. Returns a ContextBuilder.
Returns: ContextBuilder — builder for creating contexts and steps.
Usage:
contexts = agent.defineContexts();
contexts.addContext("greeting")
.addStep("welcome")
.setText("Welcome! How can I help?")
.setStepCriteria("User has stated their need")
.setValidSteps(List.of("next"));
contexts.addContext("main_menu")
.addStep("menu")
.setText("Choose: 1) Support 2) Sales 3) Billing")
.setFunctions(List.of("transfer_to_support", "transfer_to_sales"));This concludes the AgentBase class reference. The document continues with FunctionResult, DataMap, and other components.
The FunctionResult class is used to create structured responses from SWAIG functions. It handles both natural language responses and structured actions that the agent should execute. (This is the Java equivalent of Python's SwaigFunctionResult.)
import com.signalwire.sdk.swaig.FunctionResult;
new FunctionResult() // empty (actions only)
new FunctionResult(String response) // with a spoken response
new FunctionResult(String response, boolean postProcess) // with post-processingPost-processing Behavior:
postProcess = false(default): Execute actions immediately after AI responsepostProcess = true: Let AI respond to the user one more time, then execute actions
Usage:
// Simple response
result = new FunctionResult("The weather is sunny and 75F");
// Response with post-processing enabled
var result2 = new FunctionResult("I'll transfer you now", true);
// Empty response (actions only)
var result3 = new FunctionResult();Set or update the natural language response text.
Usage:
result = new FunctionResult();
result.setResponse("I found your order information");Enable or disable post-processing for this result.
Usage:
result = new FunctionResult("I'll help you with that");
result.setPostProcess(true); // Let AI handle follow-up questions firstAdd a structured action to execute.
Parameters:
name: Action name/type (e.g.,"play","transfer","set_global_data")data: Action data — a string, boolean,Map, orList
Usage:
// Simple action with boolean
result.addAction("hangup", true);
// Action with string data
result.addAction("play", "welcome.mp3");
// Action with object data
result.addAction("set_global_data", Map.of("customer_id", "12345", "status", "verified"));
// Action with array data
result.addAction("send_sms", List.of("+15551234567", "Your order is ready!"));Add multiple actions at once.
Usage:
result.addActions(List.of(
Map.of("play", "hold_music.mp3"),
Map.of("set_global_data", Map.of("status", "on_hold")),
Map.of("wait", 5000)));Transfer or connect the call to another destination. Overload: connect(destination, isFinal).
Parameters:
destination: Phone number, SIP address, or other destinationisFinal: Permanent transfer (true) vs temporary transfer (false)from: Override caller ID (nullable)
Transfer Types:
isFinal = true: Permanent transfer — call exits agent completelyisFinal = false: Temporary transfer — call returns to agent if the far end hangs up
Usage:
// Permanent transfer to a phone number
result.connect("+15551234567", true);
// Temporary transfer to a SIP address with custom caller ID
result.connect("support@company.com", false, "+15559876543");
// Transfer with a response
var r = new FunctionResult("Transferring you to our sales team");
r.connect("sales@company.com", true);Create a SWML-based transfer with AI response setup. aiResponse is spoken when the transfer returns.
Usage:
result.swmlTransfer(
"+15551234567",
"You've been transferred back to me. How else can I help?",
false);Perform a SIP REFER transfer.
Usage:
result.sipRefer("sip:support@company.com");End the call immediately.
Usage:
result = new FunctionResult("Thank you for calling. Goodbye!");
result.hangup();Put the call on hold. timeout is in seconds.
Usage:
result = new FunctionResult("Please hold while I look that up");
result.hold(60);Stop current audio playback or recording.
Usage:
result.stop();Add text for the AI to speak.
Usage:
result.say("Please wait while I process your request");Play an audio file in the background. Overload: playBackgroundFile(filename).
Parameters:
filename: Audio file path or URLwait: Wait for the file to finish before continuing
Usage:
// Play hold music in the background
result.playBackgroundFile("hold_music.mp3");
// Play an announcement and wait for completion
result.playBackgroundFile("important_announcement.wav", true);Stop background audio playback.
Usage:
result.stopBackgroundFile();Update existing global data (merge with existing).
Usage:
result.updateGlobalData(Map.of(
"last_interaction", "2024-01-15T10:30:00Z",
"agent_notes", "Customer satisfied with resolution"));Remove specific keys from global data. keys may be a single String or a List<String>.
Usage:
// Remove a single key
result.removeGlobalData("temporary_data");
// Remove multiple keys
result.removeGlobalData(List.of("temp1", "temp2", "cache_data"));Set metadata for the conversation.
Usage:
result.setMetadata(Map.of(
"call_type", "support",
"priority", "high",
"department", "technical"));Remove specific metadata keys (single String or List<String>).
Usage:
result.removeMetadata(List.of("temporary_flag", "debug_info"));Adjust how long to wait for speech to end.
Usage:
result.setEndOfSpeechTimeout(300); // Shorter — quick responses
result.setEndOfSpeechTimeout(2000); // Longer — thoughtful responsesSet the timeout for speech events.
Usage:
result.setSpeechEventTimeout(5000);Control whether to wait for user input. Overload: waitForUser().
Parameters:
enabled: Enable/disable waiting for the user (nullable)timeout: Timeout in milliseconds (nullable)answerFirst: Answer the call before waiting
Usage:
// Wait for user input with a 10-second timeout
result.waitForUser(true, 10000, false);
// Don't wait for the user (immediate response)
result.waitForUser(false, null, false);Enable or disable specific functions.
Usage:
result.toggleFunctions(List.of(
Map.of("name", "transfer_to_sales", "enabled", true),
Map.of("name", "end_call", "enabled", false),
Map.of("name", "escalate", "enabled", true, "timeout", 30000)));Control whether functions are enabled when a timeout occurs.
Usage:
result.enableFunctionsOnTimeout(false); // Disable functions on timeoutEnable extensive data collection.
Usage:
result.enableExtensiveData(true);Update various AI settings.
Usage:
result.updateSettings(Map.of(
"temperature", 0.8,
"max_tokens", 150,
"end_of_speech_timeout", 800));Switch conversation context or reset the conversation. Overload: switchContext(String systemPrompt).
Parameters:
systemPrompt: New system prompt (nullable)userPrompt: New user prompt (nullable)consolidate: Consolidate conversation historyfullReset: Completely reset the conversation
Usage:
// Switch to a technical support context
result.switchContext(
"You are now a technical support specialist",
"The customer needs technical help",
false,
false);
// Reset conversation completely
result.switchContext(null, null, false, true);
// Consolidate conversation history
result.switchContext(null, null, true, false);Simulate user input for testing or automation.
Usage:
result.simulateUserInput("I need help with my order");sendSms(String toNumber, String fromNumber, String body, List<String> media, List<String> tags, String region)
Send an SMS message. Overload: sendSms(toNumber, fromNumber, body, media, tags) (no region). Either body or media must be provided.
Parameters:
toNumber: Recipient phone numberfromNumber: Sender phone numberbody: SMS message text (nullable if media is provided)media: List of media URLs (nullable)tags: Message tags (nullable)region: SignalWire region (nullable)
Usage:
// Simple text message
result.sendSms(
"+15551234567",
"+15559876543",
"Your order #12345 has shipped!",
null,
null,
null);
// Message with media and tags
result.sendSms(
"+15551234567",
"+15559876543",
"Here's your receipt",
List.of("https://example.com/receipt.pdf"),
List.of("receipt", "order_12345"),
null);Start call recording. Several overloads exist, from recordCall() (all defaults) up to the full-arity form:
recordCall(String controlId, boolean stereo, String format, String direction, String terminators, boolean beep, double inputSensitivity, double initialTimeout, double endSilenceTimeout, Double maxLength, String statusUrl).
Parameters:
controlId: Unique identifier for this recording (nullable)stereo: Record in stereoformat: Recording format:"wav","mp3","mp4"direction: Recording direction:"both","inbound","outbound"terminators: DTMF keys to stop recording (nullable)beep: Play a beep before recordinginputSensitivity: Input sensitivity levelinitialTimeout/endSilenceTimeout: Timeouts in secondsmaxLength: Maximum recording length in seconds (nullable)statusUrl: Webhook URL for recording status (nullable)
Usage:
// Basic recording (defaults)
result.recordCall();
// Recording with control ID and settings
result.recordCall(
"customer_call_001", // controlId
true, // stereo
"wav", // format
"both", // direction
"#*", // terminators
true, // beep
44.0, // inputSensitivity
0.0, // initialTimeout
0.0, // endSilenceTimeout
300.0, // maxLength
null); // statusUrlStop call recording. Overload: stopRecordCall().
Usage:
result.stopRecordCall();
result.stopRecordCall("customer_call_001");Join a SignalWire room.
Usage:
result.joinRoom("support_room_1");Join a conference call. Use joinConference(String name) for the common case, or the full-arity overload for detailed settings (muted, beep, startOnEnter, endOnExit, waitUrl, maxParticipants, record, region, trim, coach, status-callback settings, etc.).
Usage:
// Basic conference join
result.joinConference("sales_meeting");Process a payment through the call. The full-arity form takes positional arguments:
pay(String connectorUrl, String inputMethod, String statusUrl, String paymentMethod, int timeout, int maxAttempts, boolean securityCode, Object postalCode, int minPostalCodeLength, String tokenType, String chargeAmount, String currency, String language, String voice, String description, String validCardTypes, List<Map<String,String>> parameters, List<Map<String,Object>> prompts). A shorter overload is available for the common case.
Key parameters:
connectorUrl: Payment processor webhook URLinputMethod:"dtmf"or"speech"chargeAmount: Amount to charge (as a string, e.g."29.99")currency: Currency code (e.g."usd")description: Payment description
Usage:
// Basic payment processing:
// pay(connectorUrl, inputMethod, statusUrl, timeout, maxAttempts)
result.pay("https://payment-processor.com/webhook", "dtmf",
"https://example.com/pay-status", 30, 3);Start call tapping/monitoring. Additional overloads accept TapDirection/Codec enums and the full-arity tap(uri, controlId, direction, codec, rtpPtime, statusUrl).
Parameters:
uri: URI to send tapped audio tocontrolId: Unique identifier for this tap (nullable)direction: Tap direction:"speak","hear","both"codec: Audio codec:"PCMU","PCMA","G722"
Usage:
// Tap with specific settings
result.tap("sip:quality@company.com", "quality_monitor_001", "both", "G722");Stop call tapping. Overload: stopTap().
Usage:
result.stopTap();
result.stopTap("quality_monitor_001");Execute custom SWML content. Overload: executeSwml(swmlContent).
Parameters:
swmlContent: SWML document or content to execute (aMap/Document)transfer: Whether this is a transfer operation
Usage:
// Execute custom SWML
Map<String, Object> customSwml = Map.of(
"version", "1.0.0",
"sections", Map.of(
"main", List.of(
Map.of("play", Map.of("url", "https://example.com/custom.mp3")),
Map.of("say", Map.of("text", "Custom SWML execution")))));
result.executeSwml(customSwml, false);Convert the result to a Map<String, Object> for serialization. (toJson() returns the JSON string form.)
Usage:
result = new FunctionResult("Hello world");
result.addAction("play", "music.mp3");
Map<String, Object> map = result.toMap();
System.out.println(map);
// {response=Hello world, action=[{play=music.mp3}]}Accessors for the response text (String), the accumulated actions (List<Map<String, Object>>), and the post-process flag (boolean).
Create a payment prompt configuration (Map<String, Object>).
Usage:
Map<String, Object> prompt = FunctionResult.createPaymentPrompt(
"card_number",
List.of(FunctionResult.createPaymentAction("say", "Please enter your card number")),
null, // cardType
null); // errorTypeCreate a payment action configuration (Map<String, String>).
Usage:
Map<String, String> action = FunctionResult.createPaymentAction("say", "Enter your card number");Create a payment parameter configuration (Map<String, String>).
Usage:
Map<String, String> param = FunctionResult.createPaymentParameter("merchant_id", "12345");All configuration methods return this, enabling fluent method chaining:
result = new FunctionResult("I'll help you with that")
.setPostProcess(true)
.updateGlobalData(Map.of("status", "helping"))
.setEndOfSpeechTimeout(800)
.addAction("play", "thinking.mp3");
// Complex workflow
var payment = new FunctionResult("Processing your payment")
.setPostProcess(true)
.updateGlobalData(Map.of("payment_status", "processing"))
.pay("https://payments.com/webhook", "dtmf", "https://example.com/pay-status", 30, 3)
.sendSms(
"+15551234567",
"+15559876543",
"Payment confirmation will be sent shortly",
null,
null);This concludes the FunctionResult class reference. The document continues with DataMap and other components.
The DataMap class provides a declarative approach to creating SWAIG tools that integrate with REST APIs without requiring webhook infrastructure. DataMap tools execute on SignalWire's server infrastructure, eliminating the need to expose your own webhook endpoints.
import com.signalwire.sdk.datamap.DataMap;
new DataMap(String functionName)Parameters:
functionName: Name of the SWAIG function this DataMap will create
Usage:
// Create a new DataMap tool
var weatherMap = new DataMap("get_weather");
var searchMap = new DataMap("search_docs");Set the function description/purpose. Returns the DataMap for chaining.
Usage:
dataMap = new DataMap("get_weather").purpose("Get current weather information for any city");Alias for purpose() — set the function description.
Usage:
dataMap = new DataMap("search_api").description("Search our knowledge base for information");parameter(String name, String paramType, String description, boolean required, List<String> enumValues)
Add a function parameter with JSON schema validation. Overloads: parameter(name, paramType, description, required) and parameter(name, paramType, description).
Parameters:
name: Parameter nameparamType: JSON schema type:"string","number","boolean","array","object"description: Parameter description for the AIrequired: Whether the parameter is requiredenumValues: List of allowed values for validation (nullable)
Usage:
// Required string parameter
dataMap.parameter("location", "string", "City name or ZIP code", true);
// Optional number parameter
dataMap.parameter("days", "number", "Number of forecast days", false);
// Enum parameter with allowed values
dataMap.parameter("units", "string", "Temperature units", false,
List.of("celsius", "fahrenheit"));
// Array parameter
dataMap.parameter("categories", "array", "Search categories to include");Configure an HTTP API call. Overload: webhook(method, url) (no headers).
Parameters:
method: HTTP method:"GET","POST","PUT","DELETE","PATCH"url: API endpoint URL (supports${variable}substitution)headers: HTTP headers to send (nullable)
Variable Substitution in URLs:
${args.parameter_name}: Function argument values${global_data.key}: Call-wide data store (user info, call state — NOT credentials)${meta_data.call_id}: Call and function metadata
Usage:
// Simple GET request with parameter substitution
dataMap.webhook("GET",
"https://api.weather.com/v1/current?key=API_KEY&q=${args.location}");
// POST request with authentication headers
dataMap.webhook("POST",
"https://api.company.com/search",
Map.of(
"Authorization", "Bearer YOUR_TOKEN",
"Content-Type", "application/json"));Set the JSON body for POST/PUT requests (supports ${variable} substitution).
Usage:
dataMap.body(Map.of(
"query", "${args.search_term}",
"limit", 5,
"filters", Map.of(
"category", "${args.category}",
"active", true)));Set URL query parameters (supports ${variable} substitution).
Usage:
dataMap.params(Map.of(
"api_key", "YOUR_API_KEY",
"q", "${args.location}",
"units", "${args.units}",
"lang", "en"));DataMap supports multiple webhook configurations for fallback scenarios:
dataMap = new DataMap("search_with_fallback")
.purpose("Search with multiple API fallbacks")
.parameter("query", "string", "Search query", true)
// Primary API
.webhook("GET", "https://api.primary.com/search?q=${args.query}")
.output(new FunctionResult("Primary result: ${response.title}"))
// Fallback API
.webhook("GET", "https://api.fallback.com/search?q=${args.query}")
.output(new FunctionResult("Fallback result: ${response.title}"))
// Final fallback if all APIs fail
.fallbackOutput(new FunctionResult("Sorry, all search services are currently unavailable"));Set the response template for successful API calls.
Variable Substitution in Outputs:
${response.field}: API response fields${response.nested.field}: Nested response fields${response.array[0].field}: Array element fields${args.parameter}: Original function arguments${global_data.key}: Call-wide data store (user info, call state)
Usage:
// Simple response template
dataMap.output(new FunctionResult(
"Weather in ${args.location}: ${response.current.condition.text}, ${response.current.temp_f}F"));
// Response with actions
dataMap.output(
new FunctionResult("Found ${response.total_results} results")
.updateGlobalData(Map.of("last_search", "${args.query}"))
.addAction("play", "search_complete.mp3"));Set the response when all webhooks fail.
Usage:
dataMap.fallbackOutput(
new FunctionResult("Sorry, the service is temporarily unavailable. Please try again later.")
.addAction("play", "service_unavailable.mp3"));Process array responses by iterating over elements.
Usage:
dataMap = new DataMap("search_docs")
.webhook("GET", "https://api.docs.com/search?q=${args.query}")
.foreach(Map.of("array", "${response.results}"))
.output(new FunctionResult("Found: ${foreach.title} - ${foreach.summary}"));
// Advanced foreach configuration
dataMap.foreach(Map.of(
"array", "${response.items}",
"limit", 3,
"filter", Map.of("field", "status", "value", "active")));Foreach Variable Access:
${foreach.field}: Current array element field${foreach.nested.field}: Nested fields in the current element${foreach_index}: Current iteration index (0-based)${foreach_count}: Total number of items being processed
Add a pattern-based response without an API call. Overload: expression(testValue, pattern, output).
Parameters:
testValue: Template string to test against the patternpattern: Regex pattern stringoutput: Response when the pattern matchesnomatchOutput: Response when the pattern doesn't match (nullable)
Usage:
var controlMap = new DataMap("file_control")
.purpose("Control file playback")
.parameter("command", "string", "Playback command", true)
.parameter("filename", "string", "File to control")
// Start commands
.expression("${args.command}", "start|play|begin",
new FunctionResult("Starting playback")
.addAction("start_playback", Map.of("file", "${args.filename}")))
// Stop commands
.expression("${args.command}", "stop|pause|halt",
new FunctionResult("Stopping playback")
.addAction("stop_playback", true))
// Volume commands
.expression("${args.command}", "volume (\\d+)",
new FunctionResult("Setting volume to ${match.1}")
.addAction("set_volume", "${match.1}"));Pattern Matching Variables:
${match.0}: Full match${match.1},${match.2}, etc.: Capture groups${match.group_name}: Named capture groups
Specify response fields that indicate errors.
Usage:
// Treat these response fields as errors
dataMap.errorKeys(List.of("error", "error_message", "status_code"));Set global error keys for all webhooks in this DataMap.
Usage:
dataMap.globalErrorKeys(List.of("error", "message", "code"));Add expression-based webhook selection.
Usage:
dataMap.webhookExpressions(List.of(
Map.of(
"test", "${args.type}",
"pattern", "weather",
"webhook", Map.of(
"method", "GET",
"url", "https://weather-api.com/current?q=${args.location}")),
Map.of(
"test", "${args.type}",
"pattern", "news",
"webhook", Map.of(
"method", "GET",
"url", "https://news-api.com/search?q=${args.query}"))));var weatherTool = new DataMap("get_weather")
.purpose("Get current weather information")
.parameter("location", "string", "City name or ZIP code", true)
.parameter("units", "string", "Temperature units", false, List.of("celsius", "fahrenheit"))
.webhook("GET",
"https://api.weather.com/v1/current?key=API_KEY&q=${args.location}&units=${args.units}")
.output(new FunctionResult(
"Weather in ${args.location}: ${response.current.condition.text}, ${response.current.temp_f}F"))
.errorKeys(List.of("error"));
// Register with the agent
agent.registerSwaigFunction(weatherTool.toSwaigFunction());var searchTool = new DataMap("search_knowledge")
.purpose("Search company knowledge base")
.parameter("query", "string", "Search query", true)
.parameter("category", "string", "Search category", false, List.of("docs", "faq", "policies"))
.webhook("POST", "https://api.company.com/search", Map.of("Authorization", "Bearer TOKEN"))
.body(Map.of(
"query", "${args.query}",
"category", "${args.category}",
"limit", 5))
.foreach(Map.of("array", "${response.results}"))
.output(new FunctionResult("Found: ${foreach.title} - ${foreach.summary}"))
.fallbackOutput(new FunctionResult("Search service is temporarily unavailable"));var controlTool = new DataMap("system_control")
.purpose("Control system functions")
.parameter("action", "string", "Action to perform", true)
.parameter("target", "string", "Target for the action")
// Restart commands
.expression("${args.action}", "restart|reboot",
new FunctionResult("Restarting ${args.target}")
.addAction("restart_service", Map.of("service", "${args.target}")))
// Status commands
.expression("${args.action}", "status|check",
new FunctionResult("Checking status of ${args.target}")
.addAction("check_status", Map.of("service", "${args.target}")))
// Default for unrecognized commands
.expression("${args.action}", ".*",
new FunctionResult("Unknown command: ${args.action}"),
new FunctionResult("Please specify a valid action"));Convert the DataMap to a SWAIG function map for registration.
Returns: Map<String, Object> — the complete SWAIG function definition.
Usage:
// Build the DataMap
var weatherMap = new DataMap("get_weather")
.purpose("Get weather")
.parameter("location", "string", "City", true);
// Convert to a SWAIG function and register
Map<String, Object> swaigFunction = weatherMap.toSwaigFunction();
agent.registerSwaigFunction(swaigFunction);All DataMap methods return this, enabling fluent method chaining:
var completeTool = new DataMap("comprehensive_search")
.purpose("Comprehensive search with fallbacks")
.parameter("query", "string", "Search query", true)
.parameter("category", "string", "Search category", false, List.of("all", "docs", "faq"))
.webhook("GET", "https://primary-api.com/search?q=${args.query}&cat=${args.category}")
.output(new FunctionResult("Primary: ${response.title}"))
.webhook("GET", "https://backup-api.com/search?q=${args.query}")
.output(new FunctionResult("Backup: ${response.title}"))
.fallbackOutput(new FunctionResult("All search services unavailable"))
.errorKeys(List.of("error", "message"));This concludes the DataMap class reference. The document continues with the Context System and other components.
The Context System enhances traditional prompt-based agents by adding structured workflows with sequential steps on top of a base prompt. Each step contains its own guidance, completion criteria, and function restrictions while building upon the agent's foundational prompt.
import com.signalwire.sdk.contexts.ContextBuilder;
The ContextBuilder is accessed via agent.defineContexts() and provides the main interface for creating structured workflows.
// Access the context builder
contexts = agent.defineContexts();
// Create contexts and steps
contexts.addContext("greeting")
.addStep("welcome")
.setText("Welcome! How can I help you today?")
.setStepCriteria("User has stated their need")
.setValidSteps(List.of("next"));Create a new context in the workflow.
Returns: Context — the context object for method chaining.
Usage:
// Create multiple contexts
var greetingContext = contexts.addContext("greeting");
var mainMenuContext = contexts.addContext("main_menu");
var supportContext = contexts.addContext("support");The Context class represents a conversation context containing multiple steps. Key methods (all return Context for chaining unless noted):
Step addStep(String name) // Create a new step (returns the Step)
Context setValidContexts(List<String> contexts) // Which contexts are reachable from here
Context setValidSteps(List<String> steps) // Valid next steps
// Context entry parameters (context-switch behavior)
Context setPostPrompt(String postPrompt) // Override agent's post prompt here
Context setSystemPrompt(String systemPrompt) // Trigger a context switch (makes this a switch context)
Context setConsolidate(boolean consolidate) // Consolidate history on entry
Context setFullReset(boolean fullReset) // Full system-prompt replacement vs injection
Context setUserPrompt(String userPrompt) // User message injected on entry
// Context prompts (guidance for all steps in this context)
Context setPrompt(String prompt) // Simple string prompt for all steps
Context addSection(String title, String body) // POM-style section
Context addBullets(String title, List<String> bullets) // POM-style bullet sectionContext Types:
- Workflow Container Context (no system prompt): Organizes steps without conversation state changes
- Context Switch Context (has system prompt): Triggers conversation state changes when entered, processing entry parameters like a
context_switchSWAIG action
Prompt Hierarchy: Base Agent Prompt → Context Prompt → Step Prompt
// Workflow container context (just organizes steps)
var mainContext = contexts.addContext("main");
mainContext.setPrompt("Follow standard customer service protocols");
// Context switch context (changes AI behavior)
var billingContext = contexts.addContext("billing");
billingContext.setSystemPrompt("You are now a billing specialist")
.setConsolidate(true)
.setUserPrompt("Customer needs billing assistance")
.addSection("Department", "Billing Department")
.addBullets("Services", List.of("Account inquiries", "Payments", "Refunds"));
// Full reset context (complete conversation reset)
var managerContext = contexts.addContext("manager");
managerContext.setSystemPrompt("You are a senior manager")
.setFullReset(true)
.setConsolidate(true);Steps are created via Context.addStep(name). Key methods (all return Step for chaining):
setText(String text): The step's spoken/instruction textsetStepCriteria(String criteria): Completion criteria for advancingsetFunctions(Object functions): Restrict which functions are callable (aList<String>, or"none")setValidSteps(List<String> steps)/setValidContexts(List<String> contexts): Allowed transitionsaddSection(String title, String body)/addBullets(String title, List<String> bullets): POM-style prompt contentsetEnd(boolean end),setSkipUserTurn(boolean),setSkipToNextStep(boolean): Step flow flags
Usage:
var greeting = contexts.addContext("greeting");
greeting.addStep("welcome")
.setText("Hello! Welcome to Acme Corp support. How can I help you today?")
.setStepCriteria("Customer has explained their issue")
.setValidSteps(List.of("categorize"));
greeting.addStep("categorize")
.addSection("Current Task", "Categorize the customer's request")
.addBullets("Categories", List.of(
"Technical issue - use diagnostic tools",
"Billing question - transfer to billing",
"General inquiry - handle directly"))
.setFunctions(List.of("transfer_to_billing", "run_diagnostics"))
.setStepCriteria("Request categorized and action taken");The Skills System provides modular, reusable capabilities that can be easily added to any agent via agent.addSkill(name, params).
Provides current date and time information.
Parameters:
timezone(optional): Timezone for date/time (default: system timezone)format(optional): Custom date/time format string
Usage:
// Basic datetime skill
agent.addSkill("datetime", null);
// With timezone
agent.addSkill("datetime", Map.of("timezone", "America/New_York"));
// With custom format
agent.addSkill("datetime", Map.of(
"timezone", "UTC",
"format", "yyyy-MM-dd HH:mm:ss z"));Safe mathematical expression evaluation.
Parameters:
precision(optional): Decimal precision for results (default: 2)max_expression_length(optional): Maximum expression length (default: 100)
Usage:
// Basic math skill
agent.addSkill("math", null);
// With custom precision
agent.addSkill("math", Map.of("precision", 4));Google Custom Search API integration with web scraping.
Parameters:
api_key(required): Google Custom Search API keysearch_engine_id(required): Google Custom Search Engine IDnum_results(optional): Number of results to return (default: 3)tool_name(optional): Custom tool name for multiple instancesdelay(optional): Delay between requests in secondsno_results_message(optional): Custom message when no results found
Usage:
// Basic web search
agent.addSkill("web_search", Map.of(
"api_key", "your-google-api-key",
"search_engine_id", "your-search-engine-id"));
// Multiple search instances
agent.addSkill("web_search", Map.of(
"api_key", "your-api-key",
"search_engine_id", "general-engine-id",
"tool_name", "search_general",
"num_results", 5));
agent.addSkill("web_search", Map.of(
"api_key", "your-api-key",
"search_engine_id", "news-engine-id",
"tool_name", "search_news",
"num_results", 3,
"delay", 0.5));SignalWire DataSphere knowledge search integration.
Parameters:
space_name(required): DataSphere space nameproject_id(required): DataSphere project IDtoken(required): DataSphere access tokendocument_id(optional): Specific document to searchtool_name(optional): Custom tool name for multiple instancescount(optional): Number of results to return (default: 3)tags(optional): Filter by document tags
Usage:
// Basic DataSphere search
agent.addSkill("datasphere", Map.of(
"space_name", "my-space",
"project_id", "my-project",
"token", "my-token"));
// Multiple DataSphere instances
agent.addSkill("datasphere", Map.of(
"space_name", "my-space",
"project_id", "my-project",
"token", "my-token",
"document_id", "drinks-menu",
"tool_name", "search_drinks",
"count", 5));
agent.addSkill("datasphere", Map.of(
"space_name", "my-space",
"project_id", "my-project",
"token", "my-token",
"tool_name", "search_policies",
"tags", List.of("HR", "Policies")));Document search against a remote vector-search server over HTTP. (The Java port supports remote mode only; it does not build or read local index files.)
Parameters:
remote_url(required): URL of the remote search server endpointindex_name(optional): Index name to query on the remote servertool_name(optional): Custom tool name (default:"search_knowledge")count(optional): Maximum results to return (default: 3)hints(optional): Additional speech hints to register
Usage:
// Remote search
agent.addSkill("native_vector_search", Map.of(
"remote_url", "http://localhost:8001/search",
"index_name", "knowledge"));
// With custom settings
agent.addSkill("native_vector_search", Map.of(
"remote_url", "http://localhost:8001/search",
"index_name", "docs",
"tool_name", "search_docs",
"count", 10));Create a new skill by implementing the SkillBase interface. Required methods are getName(), getDescription(), setup(params), and registerTools(); getVersion(), getRequiredEnvVars(), getHints(), getGlobalData(), getPromptSections(), and getSwaigFunctions() have sensible defaults you can override. registerTools() returns the ToolDefinitions (and getSwaigFunctions() returns DataMap-style function maps) that the SkillManager registers with the agent — skills return their tools rather than registering directly.
import com.signalwire.sdk.skills.SkillBase;
import com.signalwire.sdk.skills.SkillBase;
import com.signalwire.sdk.swaig.ToolDefinition;
import com.signalwire.sdk.swaig.FunctionResult;
import com.signalwire.sdk.datamap.DataMap;
import java.util.List;
import java.util.Map;
public class CustomSkill implements SkillBase {
private String apiKey;
@Override
public String getName() {
return "custom_skill";
}
@Override
public String getDescription() {
return "Description of what this skill does";
}
@Override
public List<String> getRequiredEnvVars() {
return List.of("API_KEY");
}
@Override
public boolean setup(Map<String, Object> params) {
// Validate and store configuration
if (params.get("api_key") == null) {
return false;
}
this.apiKey = (String) params.get("api_key");
return true;
}
@Override
public List<ToolDefinition> registerTools() {
// Handler-based tools go here; DataMap tools go in getSwaigFunctions().
return List.of();
}
@Override
public List<Map<String, Object>> getSwaigFunctions() {
// DataMap-based tool (bypasses handlers, executes server-side)
var tool = new DataMap("custom_function")
.description("Custom API integration")
.parameter("query", "string", "Search query", true)
.webhook("GET",
"https://api.example.com/search?key=" + apiKey + "&q=${args.query}")
.output(new FunctionResult("Found: ${response.title}"));
return List.of(tool.toSwaigFunction());
}
@Override
public List<String> getHints() {
return List.of("custom search", "find information");
}
@Override
public Map<String, Object> getGlobalData() {
return Map.of("skill_version", getVersion());
}
@Override
public List<Map<String, Object>> getPromptSections() {
return List.of(Map.of(
"title", "Custom Search Capability",
"body", "You can search our custom database for information.",
"bullets", List.of("Use the custom_function to search", "Results are real-time")));
}
}Built-in skills live in the skills/builtin/ package and are discovered by the SkillManager / SkillRegistry. Once a skill class is registered with the registry, add it to an agent by its name via agent.addSkill(name, params).
Represents a SWAIG function definition with metadata. Used with defineTool(ToolDefinition) when you need extra SWAIG fields beyond the defineTool(name, description, parameters, handler) shortcut.
import com.signalwire.sdk.swaig.ToolDefinition;
new ToolDefinition(String name, String description, Map<String, Object> parameters, ToolHandler handler)Parameters:
name: Function namedescription: Function descriptionparameters: JSON schema for parametershandler:ToolHandlerlambda(args, raw) -> FunctionResult
import com.signalwire.sdk.swaig.ToolDefinition;
import com.signalwire.sdk.swaig.FunctionResult;
var def = new ToolDefinition(
"get_weather",
"Get current weather",
Map.of(
"type", "object",
"properties", Map.of("location", Map.of("type", "string", "description", "City name")),
"required", List.of("location")),
(args, raw) -> new FunctionResult("Sunny and 75F"));
def.setSecure(true);
def.setExtraFields(Map.of("fillers", Map.of("en-US", List.of("Checking weather..."))));
// Register with the agent
agent.defineTool(def);Service (package com.signalwire.sdk.swml) is the base class providing SWML document generation and HTTP service capabilities. AgentBase extends Service. It exposes 38 schema-driven SWML verb methods plus SWML rendering.
Generate the complete SWML document (Map<String, Object>) for the service.
Handle incoming HTTP requests and generate appropriate responses (returns an HttpResult).
The dynamic configuration callback receives the agent instance directly, allowing you to configure it based on request data. Register it with setDynamicConfigCallback; the DynamicConfigCallback receives (queryParams, headers, body, agent).
Usage:
// Callback signature: configure(queryParams, bodyParams, headers, agent)
agent.setDynamicConfigCallback((queryParams, bodyParams, headers, cfg) -> {
// Configure based on request
if ("es".equals(queryParams.get("lang"))) {
cfg.addLanguage("Spanish", "es-ES", "nova.luna");
}
// Customer-specific configuration (header values are lists)
List<String> customerIdValues = headers.get("X-Customer-ID");
if (customerIdValues != null && !customerIdValues.isEmpty()) {
String customerId = customerIdValues.get(0);
cfg.setGlobalData(Map.of("customer_id", customerId));
cfg.promptAddSection("Customer Context", "You are helping customer " + customerId);
}
// Add skills dynamically
if ("true".equals(queryParams.get("enable_search"))) {
cfg.addSkill("web_search", Map.of("provider", "google"));
}
});The SDK supports various environment variables for configuration:
SWML_BASIC_AUTH_USER: Basic auth usernameSWML_BASIC_AUTH_PASSWORD: Basic auth password
SWML_SSL_ENABLED: Enable SSL (true/false)SWML_SSL_CERT_PATH: Path to SSL certificateSWML_SSL_KEY_PATH: Path to SSL private keySWML_DOMAIN: Domain name for SSL
SWML_PROXY_URL_BASE: Base URL for the proxy server
GOOGLE_SEARCH_API_KEY: Google Custom Search API keyGOOGLE_SEARCH_ENGINE_ID: Google Custom Search Engine IDDATASPHERE_SPACE_NAME: DataSphere space nameDATASPHERE_PROJECT_ID: DataSphere project IDDATASPHERE_TOKEN: DataSphere access token
Environment variables are read automatically by the agent (basic-auth credentials, proxy base, skill credentials). To supply a value programmatically for testing, use the builder's envProvider(EnvProvider) hook; otherwise the process environment is used.
// The agent reads SWML_BASIC_AUTH_USER / SWML_BASIC_AUTH_PASSWORD /
// GOOGLE_SEARCH_API_KEY from the environment automatically.
agent = AgentBase.builder().name("My Agent").build();
agent.addSkill("web_search", Map.of(
"search_engine_id", "your-engine-id"
// api_key is read from GOOGLE_SEARCH_API_KEY in the environment
));Here's a comprehensive example using multiple SDK components:
import com.signalwire.sdk.agent.AgentBase;
import com.signalwire.sdk.swaig.FunctionResult;
import com.signalwire.sdk.datamap.DataMap;
import java.util.List;
import java.util.Map;
public class ComprehensiveAgent {
public static void main(String[] args) throws Exception {
var agent = AgentBase.builder()
.name("Comprehensive Agent")
.route("/agent")
.port(3000)
.autoAnswer(true)
.recordCall(true)
.build();
// Configure voice and language
agent.addLanguage("English", "en-US", "rime.spore",
List.of("Let me check...", "One moment..."),
null, null, null);
// Add speech recognition hints
agent.addHints(List.of("SignalWire", "customer service", "technical support"));
// Configure AI parameters
agent.setParams(Map.of(
"ai_model", "gpt-4.1-nano",
"end_of_speech_timeout", 800,
"temperature", 0.7));
// Add skills
agent.addSkill("datetime", null);
agent.addSkill("math", null);
agent.addSkill("web_search", Map.of(
"api_key", "your-google-api-key",
"search_engine_id", "your-engine-id",
"num_results", 3));
// Set up a structured workflow
var contexts = agent.defineContexts();
var greeting = contexts.addContext("greeting");
greeting.addStep("welcome")
.setText("Hello! Welcome to Acme Corp support. How can I help you today?")
.setStepCriteria("Customer has explained their issue")
.setValidSteps(List.of("categorize"));
greeting.addStep("categorize")
.addSection("Current Task", "Categorize the customer's request")
.addBullets("Categories", List.of(
"Technical issue - use diagnostic tools",
"Billing question - transfer to billing",
"General inquiry - handle directly"))
.setFunctions(List.of("transfer_to_billing", "run_diagnostics"))
.setStepCriteria("Request categorized and action taken");
var tech = contexts.addContext("technical_support");
tech.addStep("diagnose")
.setText("Let me run some diagnostics to identify the issue.")
.setFunctions(List.of("run_diagnostics", "check_system_status"))
.setStepCriteria("Diagnostics completed")
.setValidSteps(List.of("resolve"));
tech.addStep("resolve")
.setText("Based on the diagnostics, here's how we'll fix this.")
.setFunctions(List.of("apply_fix", "schedule_technician"))
.setStepCriteria("Issue resolved or escalated");
// Register custom DataMap tools
var lookupTool = new DataMap("lookup_customer")
.description("Look up customer information")
.parameter("customer_id", "string", "Customer ID", true)
.webhook("GET", "https://api.company.com/customers/${args.customer_id}",
Map.of("Authorization", "Bearer YOUR_TOKEN"))
.output(new FunctionResult("Customer: ${response.name}, Status: ${response.status}"))
.errorKeys(List.of("error"));
agent.registerSwaigFunction(lookupTool.toSwaigFunction());
var controlTool = new DataMap("system_control")
.description("Control system functions")
.parameter("action", "string", "Action to perform", true)
.parameter("target", "string", "Target system")
.expression("${args.action}", "restart|reboot",
new FunctionResult("Restarting ${args.target}")
.addAction("restart_system", Map.of("target", "${args.target}")))
.expression("${args.action}", "status|check",
new FunctionResult("Checking ${args.target} status")
.addAction("check_status", Map.of("target", "${args.target}")));
agent.registerSwaigFunction(controlTool.toSwaigFunction());
// A code-defined tool with state tracking
agent.defineTool(
"transfer_to_billing",
"Transfer call to billing department",
Map.of("type", "object", "properties", Map.of()),
(toolArgs, raw) -> new FunctionResult("Transferring you to our billing department")
.updateGlobalData(Map.of("last_action", "transfer_to_billing"))
.connect("billing@company.com", false));
// Set global data
agent.setGlobalData(Map.of(
"company_name", "Acme Corp",
"support_hours", "9 AM - 5 PM EST",
"version", "2.0"));
// Handle conversation summaries
agent.onSummary((summary, raw) ->
System.out.println("Conversation completed: " + summary));
// Run the agent
agent.run();
}
}