| Duration | 2 hours |
| Day | 3 of 5 |
Learning Objectives
By the end of this module, students will be able to:
- Use SwaigFunctionResult for complex responses
- Implement actions like transfers and SMS
- Chain multiple actions together
- Handle errors appropriately
Topics
1. SwaigFunctionResult Deep Dive (25 min)
Beyond Simple Responses
In Level 1, we returned simple strings:
return SwaigFunctionResult("Order shipped yesterday.")
But SwaigFunctionResult can do much more!
Constructor Options
SwaigFunctionResult(
response="Text for the AI to use",
action=None, # Single action
post_process=False, # Execute after call
)
Response Types
String response:
return SwaigFunctionResult("Your balance is $150.")
Structured data:
data = {"balance": 150, "due_date": "Jan 15"}
return SwaigFunctionResult(str(data))
Empty response (action only):
return SwaigFunctionResult("").connect(...)
2. Action Types (40 min)
The SDK provides convenience methods for all actions. Use method chaining on SwaigFunctionResult.
Call Control Actions
| Method | Purpose |
|---|---|
.connect(dest, final, from_addr) | Transfer call to number/SIP URI |
.swml_transfer(dest, ai_response, final) | Transfer with AI context handoff |
.sip_refer(to_uri) | SIP REFER attended transfer |
.hangup() | End the call |
.hold(timeout) | Put caller on hold (max 900s) |
.send_sms(to, from, body, media) | Send SMS/MMS message |
.record_call(control_id, stereo, ...) | Start call recording |
.stop_record_call(control_id) | Stop call recording |
.tap(uri, control_id, direction, ...) | Tap audio to WebSocket/RTP |
.stop_tap(control_id) | Stop audio tapping |
.pay(payment_connector_url, ...) | Process credit card payment |
.join_room(name) | Join a SignalWire room |
.join_conference(name, muted, ...) | Join a conference |
Speech & Audio Actions
| Method | Purpose |
|---|---|
.say(text) | Have AI speak specific text |
.stop() | Stop AI from speaking |
.play_background_file(url, wait) | Play background audio |
.stop_background_file() | Stop background audio |
.simulate_user_input(text) | Inject text as user speech |
.wait_for_user(enabled, timeout) | Wait for user to speak |
Context & Workflow Actions
| Method | Purpose |
|---|---|
.switch_context(system_prompt, user_prompt) | Dynamic context switch |
.swml_change_context(ctx) | Switch to named SWML context |
.swml_change_step(step) | Change to workflow step |
Data Management Actions
| Method | Purpose |
|---|---|
.update_global_data(data) | Set global session data |
.remove_global_data(keys) | Remove keys from global data |
.set_metadata(data) | Set function-specific metadata |
.remove_metadata(keys) | Remove function metadata |
AI Behavior Actions
| Method | Purpose |
|---|---|
.toggle_functions(funcs) | Enable/disable functions |
.enable_functions_on_timeout(enabled) | Enable functions on timeout |
.update_settings(config) | Modify AI settings dynamically |
.set_end_of_speech_timeout(ms) | Adjust speech timeout |
.set_speech_event_timeout(ms) | Adjust speech event timeout |
Advanced Actions
| Method | Purpose |
|---|---|
.execute_swml(doc, transfer) | Execute raw SWML document |
.swml_user_event(data) | Fire custom user event |
Transfer Action
from signalwire_agents import SwaigFunctionResult
@agent.tool(description="Transfer to sales department")
def transfer_to_sales(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
return (
SwaigFunctionResult("Transferring you to sales now.")
.connect("+15551234567", final=True)
)
Transfer options:
# Permanent transfer - call leaves agent completely
.connect("+15551234567", final=True)
# Temporary transfer - returns to agent if far end hangs up
.connect("+15551234567", final=False)
# With custom caller ID
.connect("+15551234567", final=True, from_addr="+15559876543")
# Transfer to SIP address
.connect("support@company.com", final=True)
# Transfer with AI context handoff
.swml_transfer(
dest="+15551234567",
ai_response="Customer needs help with billing",
final=True
)
SMS Action
@agent.tool(description="Send order confirmation via SMS")
def send_confirmation(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
phone = args.get("phone", "")
order_id = args.get("order_id", "")
return (
SwaigFunctionResult("I've sent a confirmation to your phone.")
.send_sms(
to_number=phone,
from_number="+15559876543",
body=f"Your order {order_id} is confirmed!"
)
)
SMS with media (MMS):
.send_sms(
to_number=phone,
from_number="+15559876543",
body="Here's your receipt:",
media=["https://example.com/receipt.pdf"]
)
Hangup Action
@agent.tool(description="End the call politely")
def end_call(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
return (
SwaigFunctionResult("Thank you for calling. Goodbye!")
.hangup()
)
3. Method Chaining (20 min)
Fluent API
SwaigFunctionResult supports chaining:
@agent.tool(description="Complete order and notify customer")
def complete_order(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
order_id = args.get("order_id", "")
phone = args.get("phone", "")
return (
SwaigFunctionResult(f"Order {order_id} is complete.")
.send_sms(
to_number=phone,
from_number="+15559876543",
body=f"Order {order_id} complete! Thank you."
)
.update_global_data({
"order_id": order_id,
"status": "completed"
})
)
Multiple Actions
Actions execute in order:
return (
SwaigFunctionResult("Processing your request...")
.stop_record_call(control_id="main") # First - pause recording
.send_sms( # Second - send SMS
to_number=phone,
from_number="+15559876543",
body="Your request is being processed"
)
.record_call(control_id="main") # Third - resume recording
)
Terminal Actions
Some actions end the call. Put them last:
# Good - data saved before transfer
return (
SwaigFunctionResult("Transferring you now")
.update_global_data({"transferred": True}) # Executes
.send_sms(to_number=phone, ...) # Executes
.connect("+15551234567", final=True) # Terminal - call leaves
)
# Bad - SMS may not send
return (
SwaigFunctionResult("Transferring you now")
.connect("+15551234567", final=True) # Terminal
.send_sms(to_number=phone, ...) # May not execute!
)
4. Post-Process Actions (20 min)
What is Post-Process?
Post-process lets the AI speak once more before executing actions. Useful for confirmations:
@agent.tool(description="Transfer to specialist after gathering info")
def request_callback(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
return (
SwaigFunctionResult(
"I'll transfer you to billing. Is there anything else first?",
post_process=True # AI can respond to follow-up before transfer
)
.connect("+15559876543", final=True)
)
Use Cases
| Use Case | Why Post-Process |
|---|---|
| Confirm before transfer | Let caller ask last questions |
| Warning before hangup | Give caller chance to continue |
| Confirm destructive action | Let AI verify intent |
5. Error Handling (15 min)
Graceful Failures
@agent.tool(description="Process payment")
def process_payment(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
amount = args.get("amount", 0.0)
card_last_four = args.get("card_last_four", "")
try:
# Attempt payment
success = payment_api.charge(amount, card_last_four)
if success:
return SwaigFunctionResult(
f"Payment of ${amount} processed successfully."
)
else:
return SwaigFunctionResult(
"I'm sorry, the payment couldn't be processed. "
"Please check your card details."
)
except Exception as e:
# Log the error
logger.error(f"Payment failed: {e}")
# Return user-friendly message
return SwaigFunctionResult(
"I'm having trouble processing the payment right now. "
"Would you like me to transfer you to billing?"
)
Never Expose Technical Errors
Bad:
return SwaigFunctionResult(f"Error: {str(e)}") # Exposes internals
Good:
return SwaigFunctionResult(
"I'm having trouble with that. Let me transfer you to someone who can help."
)
Complete Examples
Click to reveal complete examples
Customer Support with Transfer
DEPARTMENTS = {
"sales": "+15551111111",
"support": "+15552222222",
"billing": "+15553333333",
}
@agent.tool(
description="Transfer caller to a department",
parameters={
"type": "object",
"properties": {
"department": {
"type": "string",
"enum": ["sales", "support", "billing"],
"description": "Target department"
}
},
"required": ["department"]
}
)
def transfer_to_department(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
department = args.get("department", "")
if department not in DEPARTMENTS:
return SwaigFunctionResult(
"I don't recognize that department. "
"We have sales, support, and billing."
)
number = DEPARTMENTS[department]
return (
SwaigFunctionResult(f"Transferring you to {department} now.")
.connect(number, final=True)
)
Order Completion with SMS
@agent.tool(
description="Complete an order and send confirmation",
fillers=["Processing your order...", "Almost done..."]
)
def complete_order(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
order_id = args.get("order_id", "")
customer_phone = args.get("customer_phone", "")
# Validate order exists
order = get_order(order_id)
if not order:
return SwaigFunctionResult(f"Order {order_id} not found.")
# Process the order
order.complete()
# Return with confirmation SMS and metadata
return (
SwaigFunctionResult(
f"Order {order_id} is complete! "
"I'm sending a confirmation to your phone now."
)
.send_sms(
to_number=customer_phone,
from_number="+15559876543",
body=f"Order {order_id} confirmed! Total: ${order.total}"
)
.update_global_data({
"order_id": order_id,
"completed_at": datetime.now().isoformat()
})
)
Key Takeaways
- SwaigFunctionResult is powerful - Not just for text
- Actions extend capabilities - Transfers, SMS, recording
- Chaining is clean - Fluent API for multiple actions
- Post-process for sequencing - After AI conversation
- Handle errors gracefully - Never expose technical details
Preparation for Lab 2.1
- Review action types
- Think about use cases for transfers and SMS
- Have phone numbers ready for testing
Lab Preview
In Lab 2.1, you will:
- Build a department routing function
- Implement SMS confirmations
- Create a post-process transfer
- Handle error scenarios