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

  1. SwaigFunctionResult is powerful - Not just for text
  2. Actions extend capabilities - Transfers, SMS, recording
  3. Chaining is clean - Fluent API for multiple actions
  4. Post-process for sequencing - After AI conversation
  5. 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:

  1. Build a department routing function
  2. Implement SMS confirmations
  3. Create a post-process transfer
  4. Handle error scenarios

Back to top

SignalWire AI Agents Certification Program