***

title: Results & Actions
description: FunctionResult is the return type for all SWAIG functions, containing response text and optional actions like transfers, SMS, or context changes.
slug: /guides/result-actions
max-toc-depth: 3
---------------------

For a complete index of all SignalWire documentation pages, fetch https://signalwire.com/docs/llms.txt

[ref-functionresult]: /docs/server-sdks/reference/python/agents/function-result

### Basic Results

Return a simple response:

<Tabs>
  <Tab title="Python">
    ```python
    from signalwire import FunctionResult

    def check_order(self, args, raw_data):
        order_number = args.get("order_number")
        return FunctionResult(f"Order {order_number} shipped yesterday")
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript
    import { FunctionResult } from 'signalwire-agents';

    function checkOrder(args: Record<string, any>): FunctionResult {
      return new FunctionResult(`Order ${args.order_number} shipped yesterday`);
    }
    ```
  </Tab>
</Tabs>

### [FunctionResult][ref-functionresult] Constructor Across Languages

| Language   | Constructor                           |
| ---------- | ------------------------------------- |
| Python     | `FunctionResult("response text")`     |
| TypeScript | `new FunctionResult("response text")` |

### FunctionResult Components

| Component      | Description                                                                      |
| -------------- | -------------------------------------------------------------------------------- |
| `response`     | Text the AI will speak to the caller                                             |
| `action`       | List of actions to execute (transfers, SMS, context changes, etc.)               |
| `post_process` | If `True`, AI speaks once more before actions execute (useful for confirmations) |

### Method Chaining

FunctionResult methods return self for chaining:

```python
def transfer_to_support(self, args, raw_data):
    department = args.get("department", "support")

    return (
        FunctionResult("I'll transfer you now")
        .connect("+15551234567", final=True)
    )
```

### Common Action Methods Across Languages

The key action methods are available in all SDK languages with consistent naming:

| Action       | Python                       |
| ------------ | ---------------------------- |
| Transfer     | `.connect(dest, final=True)` |
| Hang up      | `.hangup()`                  |
| Send SMS     | `.send_sms(to, from, body)`  |
| Global data  | `.update_global_data({})`    |
| Toggle funcs | `.toggle_functions([])`      |

<Note>
  The Python examples below demonstrate all action methods. The same methods are available in every SDK language using the naming conventions shown above.
</Note>

### Call Transfer

Transfer to another number:

```python
def transfer_call(self, args, raw_data):
    department = args.get("department")

    numbers = {
        "sales": "+15551111111",
        "support": "+15552222222",
        "billing": "+15553333333"
    }

    dest = numbers.get(department, "+15550000000")

    return (
        FunctionResult(f"Transferring you to {department}")
        .connect(dest, final=True)
    )
```

**Transfer options:**

```python
## 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)
```

**SIP REFER transfer:**

Use SIP REFER for attended transfers:

```python
def transfer_to_extension(self, args, raw_data):
    extension = args.get("extension")

    return (
        FunctionResult(f"Transferring to extension {extension}")
        .sip_refer(f"sip:{extension}@pbx.example.com")
    )
```

**SWML-specific transfer:**

Transfer with AI response for context handoff:

```python
def transfer_with_context(self, args, raw_data):
    department = args.get("department")

    return (
        FunctionResult("Let me connect you")
        .swml_transfer(
            dest="+15551234567",
            ai_response=f"Customer needs help with {department}",
            final=True
        )
    )
```

### Send SMS

Send a text message during the call:

```python
def send_confirmation(self, args, raw_data):
    phone = args.get("phone_number")
    order_id = args.get("order_id")

    return (
        FunctionResult("I've sent you a confirmation text")
        .send_sms(
            to_number=phone,
            from_number="+15559876543",
            body=f"Your order {order_id} has been confirmed!"
        )
    )
```

**SMS with media:**

```python
def send_receipt(self, args, raw_data):
    phone = args.get("phone_number")
    receipt_url = args.get("receipt_url")

    return (
        FunctionResult("I've sent your receipt")
        .send_sms(
            to_number=phone,
            from_number="+15559876543",
            body="Here's your receipt:",
            media=[receipt_url]
        )
    )
```

### Payment Processing

Process credit card payments during the call:

```python
def collect_payment(self, args, raw_data):
    amount = args.get("amount")
    description = args.get("description", "Purchase")

    return (
        FunctionResult("I'll collect your payment information now")
        .pay(
            payment_connector_url="https://api.example.com/payment",
            charge_amount=amount,
            description=description,
            input_method="dtmf",
            security_code=True,
            postal_code=True
        )
    )
```

**Payment with custom prompts:**

```python
def subscription_payment(self, args, raw_data):
    return (
        FunctionResult("Let's set up your monthly subscription")
        .pay(
            payment_connector_url="https://api.example.com/subscribe",
            charge_amount="29.99",
            description="Monthly Subscription",
            token_type="reusable",
            prompts=[
                {
                    "say": "Please enter your credit card number",
                    "type": "card_number"
                },
                {
                    "say": "Enter the expiration month and year",
                    "type": "expiration"
                }
            ]
        )
    )
```

### Call Recording

Start and stop call recording:

```python
def start_recording(self, args, raw_data):
    return (
        FunctionResult("Starting call recording")
        .record_call(
            control_id="my_recording",
            stereo=True,
            format="mp3",
            direction="both"
        )
    )

def stop_recording(self, args, raw_data):
    return (
        FunctionResult("Recording stopped")
        .stop_record_call(control_id="my_recording")
    )
```

**Record with auto-stop:**

```python
def record_with_timeout(self, args, raw_data):
    return (
        FunctionResult("Recording your message")
        .record_call(
            control_id="voicemail",
            max_length=120.0,  # Stop after 2 minutes
            end_silence_timeout=3.0,  # Stop after 3s silence
            beep=True
        )
    )
```

### Audio Tapping

Tap audio to external endpoint for monitoring or transcription. Supports WebSocket (`wss://`) or RTP (`rtp://`) URIs:

**WebSocket tap:**

```python
def start_websocket_monitoring(self, args, raw_data):
    return (
        FunctionResult("Call monitoring started")
        .tap(
            uri="wss://monitor.example.com/audio",
            control_id="supervisor_tap",
            direction="both",
            codec="PCMU"
        )
    )
```

**RTP tap:**

```python
def start_rtp_tap(self, args, raw_data):
    return (
        FunctionResult("Recording to RTP endpoint")
        .tap(
            uri="rtp://192.168.1.100:5004",
            control_id="rtp_tap",
            direction="both",
            codec="PCMU",
            rtp_ptime=20
        )
    )

def stop_monitoring(self, args, raw_data):
    return (
        FunctionResult("Monitoring stopped")
        .stop_tap(control_id="supervisor_tap")
    )
```

### Call Control

**Hold:**

Put caller on hold:

```python
def hold_for_agent(self, args, raw_data):
    return (
        FunctionResult("Please hold while I find an available agent")
        .hold(timeout=60)  # Hold for up to 60 seconds
    )
```

### Hang Up

End the call:

```python
def end_call(self, args, raw_data):
    return (
        FunctionResult("Thank you for calling. Goodbye!")
        .hangup()
    )
```

### RPC Actions (Cross-Call Communication)

RPC actions enable communication between calls, essential for call screening and multi-party coordination scenarios.

**Basic RPC execution:**

```python
def execute_custom_rpc(self, args, raw_data):
    return (
        FunctionResult("Executing RPC")
        .execute_rpc(
            method="ai_message",
            call_id="target-call-id",
            params={"role": "system", "message_text": "Hello"}
        )
    )
```

**Call screening pattern - dial out while holding caller:**

```python
def screen_call_to_human(self, args, raw_data):
    """Place caller on hold and dial out to a human."""
    human_number = args.get("phone_number")
    caller_info = args.get("caller_name", "Unknown")

    return (
        FunctionResult("Please hold while I connect you.")
        .hold(timeout=120)
        .rpc_dial(
            to_number=human_number,
            from_number="+15559876543",
            dest_swml=f"https://example.com/screening-agent?caller={caller_info}"
        )
    )
```

**Inject message into held caller's AI:**

```python
def notify_caller(self, args, raw_data):
    """Tell the held caller's AI to relay a message."""
    caller_call_id = args.get("original_call_id")

    return (
        FunctionResult("I'll let them know.")
        .rpc_ai_message(
            call_id=caller_call_id,
            message_text="The person you're trying to reach is unavailable. Please leave a message."
        )
    )
```

**Release caller from hold:**

```python
def release_caller(self, args, raw_data):
    """Unhold the caller and have their AI continue."""
    caller_call_id = args.get("original_call_id")

    return (
        FunctionResult("Returning you to the caller.")
        .rpc_ai_message(caller_call_id, "You can take their message now.")
        .rpc_ai_unhold(caller_call_id)
    )
```

### Dynamic Hints

Dynamically update speech recognition hints during a call. This is useful when the conversation context changes and different vocabulary becomes relevant.

**Add dynamic hints:**

```python
def set_product_context(self, args, raw_data):
    category = args.get("category", "general")

    hints = {
        "electronics": ["HDMI", "USB-C", "Bluetooth", "WiFi"],
        "medical": ["prescription", "dosage", "milligrams", "refill"]
    }

    return (
        FunctionResult(f"Switching to {category} mode")
        .add_dynamic_hints(hints.get(category, []))
    )
```

**Pronunciation pattern hints:**

Hints can also include pronunciation patterns for words that are commonly misrecognized:

```python
def set_name_hints(self, args, raw_data):
    name = args.get("customer_name", "")

    return (
        FunctionResult(f"I'll listen for {name}")
        .add_dynamic_hints([
            name,
            {"pattern": "cab bee", "replace": "Cabby", "ignore_case": True}
        ])
    )
```

Each hint can be a simple string or a dict with `pattern` (regex), `replace` (replacement text), and optional `ignore_case` (bool).

**Clear dynamic hints:**

```python
def reset_context(self, args, raw_data):
    return (
        FunctionResult("Context reset")
        .clear_dynamic_hints()
    )
```

### History Management

Control the conversation history visible to the AI. The `replace_in_history()` method lets you remove or replace a conversation turn to keep the history clean and focused.

**Remove a conversation turn:**

```python
def process_sensitive_data(self, args, raw_data):
    # Process the data, then remove this exchange from history
    ssn = args.get("ssn")
    # ... process ssn ...

    return (
        FunctionResult("I've verified your identity.")
        .replace_in_history(True)  # Removes this Q&A pair from history
    )
```

**Replace with summary text:**

```python
def collect_payment_info(self, args, raw_data):
    card_number = args.get("card_number")
    # ... process payment ...

    return (
        FunctionResult("Payment processed successfully.")
        .replace_in_history("Payment information was collected and processed.")
    )
```

<Note>
  When `True` is passed, the entire question/answer pair is removed from the AI's conversation history. When a string is passed, the pair is replaced with that text. This is useful for keeping sensitive information out of the conversation context.
</Note>

### Speech Control

**Direct speech with .say():**

Make the AI speak specific text immediately:

```python
def announce_status(self, args, raw_data):
    order_status = args.get("status")

    return (
        FunctionResult()
        .say(f"Your order status is: {order_status}")
    )
```

**Stop AI from speaking:**

```python
def interrupt_speech(self, args, raw_data):
    return (
        FunctionResult()
        .stop()  # Immediately stop AI speech
        .say("Let me start over")
    )
```

**Wait for user input:**

Pause and wait for the user to speak:

```python
def wait_for_confirmation(self, args, raw_data):
    return (
        FunctionResult("I'll wait for your response")
        .wait_for_user(enabled=True, timeout=10)
    )
```

**Simulate user input:**

Inject text as if the user spoke it:

```python
def auto_confirm(self, args, raw_data):
    return (
        FunctionResult()
        .simulate_user_input("yes, I confirm")
    )
```

### Background Audio

Play audio files in the background during conversation:

```python
def play_hold_music(self, args, raw_data):
    return (
        FunctionResult("Please hold")
        .play_background_file(
            filename="https://example.com/hold-music.mp3",
            wait=False
        )
    )

def stop_hold_music(self, args, raw_data):
    return (
        FunctionResult("I'm back")
        .stop_background_file()
    )
```

### Update Global Data

Store data accessible throughout the call:

```python
def save_customer_info(self, args, raw_data):
    customer_id = args.get("customer_id")
    customer_name = args.get("name")

    return (
        FunctionResult(f"I've noted your information, {customer_name}")
        .update_global_data({
            "customer_id": customer_id,
            "customer_name": customer_name,
            "verified": True
        })
    )
```

**Remove global data:**

```python
def clear_session_data(self, args, raw_data):
    return (
        FunctionResult("Session data cleared")
        .remove_global_data(["customer_id", "verified"])
    )
```

### Metadata Management

Store function-specific metadata (separate from global data):

```python
def track_function_usage(self, args, raw_data):
    return (
        FunctionResult("Usage tracked")
        .set_metadata({
            "function_called": "check_order",
            "timestamp": "2024-01-15T10:30:00Z",
            "user_id": args.get("user_id")
        })
    )
```

**Remove metadata:**

```python
def clear_function_metadata(self, args, raw_data):
    return (
        FunctionResult("Metadata cleared")
        .remove_metadata(["timestamp", "user_id"])
    )
```

### Context Switching

**Advanced context switch:**

Change the agent's prompt/context with new system and user prompts:

```python
def switch_to_technical(self, args, raw_data):
    return (
        FunctionResult("Switching to technical support mode")
        .switch_context(
            system_prompt="You are now a technical support specialist. "
                         "Help the customer with their technical issue.",
            user_prompt="The customer needs help with their account"
        )
    )
```

**SWML context switch:**

Switch to a named SWML context:

```python
def switch_to_billing(self, args, raw_data):
    return (
        FunctionResult("Let me connect you with billing")
        .swml_change_context("billing_context")
    )
```

**SWML step change:**

Change to a specific workflow step:

```python
def move_to_checkout(self, args, raw_data):
    return (
        FunctionResult("Moving to checkout")
        .swml_change_step("checkout_step")
    )
```

### Function Control

Dynamically enable or disable functions during the call:

```python
def enable_payment_functions(self, args, raw_data):
    return (
        FunctionResult("Payment functions are now available")
        .toggle_functions([
            {"function": "collect_payment", "active": True},
            {"function": "refund_payment", "active": True},
            {"function": "check_balance", "active": False}
        ])
    )
```

**Enable functions on timeout:**

```python
def enable_escalation_on_timeout(self, args, raw_data):
    return (
        FunctionResult("I'll help you with that")
        .enable_functions_on_timeout(enabled=True)
    )
```

**Update AI settings:**

```python
def adjust_speech_timing(self, args, raw_data):
    return (
        FunctionResult("Adjusting response timing")
        .update_settings({
            "end_of_speech_timeout": 1000,
            "attention_timeout": 30000
        })
    )
```

**Set speech timeouts:**

```python
def configure_timeouts(self, args, raw_data):
    return (
        FunctionResult()
        .set_end_of_speech_timeout(800)  # 800ms
        .set_speech_event_timeout(5000)  # 5s
    )
```

### Conference & Rooms

**Join a conference:**

```python
def join_team_conference(self, args, raw_data):
    conf_name = args.get("conference_name")

    return (
        FunctionResult(f"Joining {conf_name}")
        .join_conference(
            name=conf_name,
            muted=False,
            beep="true",
            start_conference_on_enter=True
        )
    )
```

**Join a SignalWire room:**

```python
def join_support_room(self, args, raw_data):
    return (
        FunctionResult("Connecting to support room")
        .join_room(name="support-room-1")
    )
```

### Post-Processing

Let AI speak once more before executing actions:

```python
def transfer_with_confirmation(self, args, raw_data):
    return (
        FunctionResult(
            "I'll transfer you to billing. Is there anything else first?",
            post_process=True  # AI can respond to follow-up before transfer
        )
        .connect("+15551234567", final=True)
    )
```

### Multiple Actions

Chain multiple actions together:

```python
def complete_interaction(self, args, raw_data):
    customer_phone = args.get("phone")

    return (
        FunctionResult("I've completed your request")
        .update_global_data({"interaction_complete": True})
        .send_sms(
            to_number=customer_phone,
            from_number="+15559876543",
            body="Thank you for calling!"
        )
    )
```

### Action Execution Order and Interactions

When chaining multiple actions, understanding how they interact is important.

#### Execution Order

Actions execute in the order they're added to the FunctionResult. The response text is processed first, then actions execute sequentially.

```python
# These execute in order: 1, 2, 3
return (
    FunctionResult("Starting process")
    .update_global_data({"step": 1})      # 1st
    .send_sms(to_number=phone, ...)       # 2nd
    .update_global_data({"step": 2})      # 3rd
)
```

#### Terminal Actions

Some actions end the call or AI session. Once a terminal action executes, subsequent actions may not run:

**Terminal actions:**

* `.connect(final=True)` - Transfers call away permanently
* `.hangup()` - Ends the call
* `.swml_transfer(final=True)` - Transfers to another SWML endpoint

**Non-terminal actions:**

* `.update_global_data()` - Continues normally
* `.send_sms()` - Continues normally
* `.say()` - Continues normally
* `.connect(final=False)` - Returns to agent if far end hangs up

**Best practice:** Put terminal actions last in the chain.

```python
# Good - data saved before transfer
return (
    FunctionResult("Transferring you now")
    .update_global_data({"transferred": True})  # Executes
    .send_sms(to_number=phone, body="...")      # Executes
    .connect("+15551234567", final=True)        # Terminal
)

# Risky - SMS might not send
return (
    FunctionResult("Transferring you now")
    .connect("+15551234567", final=True)        # Terminal - call leaves
    .send_sms(to_number=phone, body="...")      # May not execute
)
```

#### Conflicting Actions

Some action combinations don't make sense together:

| Combination                          | Result                                |
| ------------------------------------ | ------------------------------------- |
| Multiple `.connect()`                | Last one wins                         |
| `.hangup()` then `.connect()`        | Hangup executes, connect ignored      |
| `.connect(final=True)` then `.say()` | Say won't execute (call transferred)  |
| Multiple `.update_global_data()`     | Merged (later keys overwrite earlier) |
| Multiple `.send_sms()`               | All execute (multiple SMS sent)       |

#### Using post\_process with Actions

When `post_process=True`, the AI speaks the response and can respond to follow-up before actions execute:

```python
return (
    FunctionResult(
        "I'll transfer you. Anything else first?",
        post_process=True  # AI waits for response
    )
    .connect("+15551234567", final=True)  # Executes after AI finishes
)
```

This is useful for:

* Confirming before transfers
* Last-chance questions before hangup
* Warning before destructive actions

#### Action Timing Considerations

**Immediate actions** execute as soon as the function returns:

* `.update_global_data()`
* `.toggle_functions()`

**Speech actions** execute during AI's turn:

* `.say()`
* `.stop()`

**Call control actions** affect the call flow:

* `.connect()` - Immediate transfer
* `.hangup()` - Immediate disconnect
* `.hold()` - Immediate hold

**External actions** may have latency:

* `.send_sms()` - Network delay possible
* `.record_call()` - Recording starts immediately but storage is async

### Advanced: Execute Raw SWML

For advanced use cases, execute raw SWML documents directly:

```python
def execute_custom_swml(self, args, raw_data):
    swml_doc = {
        "version": "1.0.0",
        "sections": {
            "main": [
                {"play": {"url": "https://example.com/announcement.mp3"}},
                {"hangup": {}}
            ]
        }
    }

    return (
        FunctionResult()
        .execute_swml(swml_doc, transfer=False)
    )
```

<Note>
  Most use cases are covered by the convenience methods above. Use `execute_swml()` only when you need SWML features not available through other action methods.
</Note>

### Action Reference

#### Call Control Actions

| Method                                     | Description                                 |
| ------------------------------------------ | ------------------------------------------- |
| `.connect(dest, final, from_addr)`         | Transfer call to another number or SIP URI  |
| `.swml_transfer(dest, ai_response, final)` | SWML-specific transfer with AI response     |
| `.sip_refer(to_uri)`                       | SIP REFER transfer                          |
| `.hangup()`                                | End the call                                |
| `.hold(timeout)`                           | Put caller on hold (default 300s, max 900s) |
| `.send_sms(to, from, body, media)`         | Send SMS message                            |
| `.record_call(control_id, stereo, ...)`    | Start call recording                        |
| `.stop_record_call(control_id)`            | Stop call recording                         |
| `.tap(uri, control_id, direction, ...)`    | Tap call audio to external URI              |
| `.stop_tap(control_id)`                    | Stop call tapping                           |
| `.pay(payment_connector_url, ...)`         | Process payment                             |
| `.execute_swml(doc, transfer)`             | Execute raw SWML document                   |
| `.execute_rpc(method, params, call_id)`    | Execute RPC method on a call                |
| `.rpc_dial(to, from, dest_swml)`           | Dial out with destination SWML              |
| `.rpc_ai_message(call_id, message)`        | Inject message into another call's AI       |
| `.rpc_ai_unhold(call_id)`                  | Release another call from hold              |
| `.join_room(name)`                         | Join a SignalWire room                      |
| `.join_conference(name, muted, ...)`       | Join a conference                           |

#### Dynamic Hints & History Actions

| Method                      | Description                                                   |
| --------------------------- | ------------------------------------------------------------- |
| `.add_dynamic_hints(hints)` | Add speech recognition hints dynamically during a call        |
| `.clear_dynamic_hints()`    | Remove all dynamically added hints                            |
| `.replace_in_history(text)` | Remove (`True`) or replace (string) this Q\&A pair in history |

#### Speech & Audio Actions

| Method                                           | Description                 |
| ------------------------------------------------ | --------------------------- |
| `.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, answer_first)` | Wait for user to speak      |

#### Context & Workflow Actions

| Method                                        | Description                              |
| --------------------------------------------- | ---------------------------------------- |
| `.switch_context(system_prompt, user_prompt)` | Advanced context switch with new prompts |
| `.swml_change_context(ctx)`                   | Switch to named context                  |
| `.swml_change_step(step)`                     | Change to specific workflow step         |

#### Data Management Actions

| Method                      | Description                    |
| --------------------------- | ------------------------------ |
| `.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 keys  |

#### AI Behavior Actions

| Method                                  | Description                          |
| --------------------------------------- | ------------------------------------ |
| `.toggle_functions(funcs)`              | Enable/disable specific functions    |
| `.enable_functions_on_timeout(enabled)` | Enable functions when timeout occurs |
| `.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          |
| `.enable_extensive_data(enabled)`       | Enable extended data in webhooks     |

#### Events

| Method                   | Description            |
| ------------------------ | ---------------------- |
| `.swml_user_event(data)` | Fire custom user event |