Duration 2 hours
Day 4 of 5

Learning Objectives

By the end of this module, students will be able to:

  • Implement different transfer methods
  • Configure warm and cold transfers
  • Handle transfer failures gracefully
  • Build department routing systems

Topics

1. Transfer Types (20 min)

Cold Transfer (Blind)

Immediately connect caller to destination:

Caller ──> AI Agent ──> Destination
                        (immediate)
@agent.tool(description="Transfer to sales")
def transfer_sales(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
    return (
        SwaigFunctionResult("Connecting you to sales now.")
        .connect("+15551234567", final=True)
    )

Warm Transfer (Announced)

AI stays while destination answers:

Caller ──> AI Agent ──> Destination
              │            │
              └── bridges ─┘
@agent.tool(description="Warm transfer with context")
def transfer_with_context(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
    return (
        SwaigFunctionResult("Let me connect you. One moment.")
        .connect(
            "to": "+15551234567",
            "ringback": "https://example.com/hold-music.mp3"
        )
    )

2. Transfer Actions (30 min)

Simple Transfer

.connect("+15551234567"  # Phone number
, final=True)

SIP Transfer

.connect("sip:sales@pbx.example.com"
, final=True)

Transfer with Caller ID

.connect("+15551234567",
    "from": "+15559876543"  # Show this number
, final=True)

Transfer with Ringback

.connect("+15551234567", final=True)
# Note: ringback audio configured in SignalWire dashboard

3. Department Routing (25 min)

Basic Router

DEPARTMENTS = {
    "sales": "+15551111111",
    "support": "+15552222222",
    "billing": "+15553333333",
    "returns": "+15554444444"
}

@agent.tool(
    description="Transfer to a department",
    parameters={
        "type": "object",
        "properties": {
            "department": {
                "type": "string",
                "enum": list(DEPARTMENTS.keys()),
                "description": "Target department"
            }
        },
        "required": ["department"]
    }
)
def transfer_department(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
    department = args.get("department", "")
    if department not in DEPARTMENTS:
        return SwaigFunctionResult(
            f"I can transfer you to: {', '.join(DEPARTMENTS.keys())}"
        )

    return (
        SwaigFunctionResult(f"Connecting you to {department}.")
        .connect(DEPARTMENTS[department], final=True)
    )

Time-Based Routing

from datetime import datetime

def get_support_number():
    hour = datetime.now().hour

    if 9 <= hour < 17:  # Business hours
        return "+15551111111"  # Main support
    else:
        return "+15552222222"  # After-hours

@agent.tool(description="Transfer to support")
def transfer_support(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
    number = get_support_number()
    hour = datetime.now().hour

    if 9 <= hour < 17:
        msg = "Connecting you to our support team."
    else:
        msg = "Connecting you to our after-hours support."

    return (
        SwaigFunctionResult(msg)
        .connect(number, final=True)
    )

Skill-Based Routing

SPECIALISTS = {
    "billing": {
        "number": "+15551111111",
        "skills": ["payments", "invoices", "refunds"]
    },
    "technical": {
        "number": "+15552222222",
        "skills": ["troubleshooting", "setup", "integration"]
    },
    "account": {
        "number": "+15553333333",
        "skills": ["password", "profile", "subscription"]
    }
}

@agent.tool(description="Route to appropriate specialist")
def route_to_specialist(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
    issue_type = args.get("issue_type", "")
    issue_lower = issue_type.lower()

    for dept, info in SPECIALISTS.items():
        if any(skill in issue_lower for skill in info["skills"]):
            return (
                SwaigFunctionResult(f"Connecting you to our {dept} specialist.")
                .connect(info["number"], final=True)
            )

    # Default
    return (
        SwaigFunctionResult("Connecting you to general support.")
        .connect("+15559999999", final=True)
    )

4. Post-Process Transfers (20 min)

Transfer After Summary

Let AI finish before transferring:

@agent.tool(description="Escalate to supervisor")
def escalate_call(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
    return (
        SwaigFunctionResult(
            "I'll connect you with a supervisor who can help further. "
            "Thank you for your patience."
        )
        .connect("+15551234567"
        , final=True)  # Happens after AI finishes
    )

Transfer with Data Pass

@agent.tool(description="Transfer with context")
def transfer_with_notes(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
    department = args.get("department", "")
    issue_summary = args.get("issue_summary", "")
    raw_data = raw_data or {}
    global_data = raw_data.get("global_data", {})

    # Build context for receiving agent
    context = {
        "caller": global_data.get("customer_name", "Unknown"),
        "issue": issue_summary,
        "call_id": raw_data.get("call_id")
    }

    # In reality, store this for the receiving agent to access
    store_transfer_context(context)

    return (
        SwaigFunctionResult(f"Transferring to {department} with your information.")
        .connect(DEPARTMENTS[department], final=True)
    )

5. Transfer with SMS (15 min)

Send Info Before Transfer

@agent.tool(description="Transfer and send ticket number")
def transfer_with_ticket(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
    issue = args.get("issue", "")
    caller_phone = args.get("caller_phone", "")
    # Create ticket
    ticket_id = create_support_ticket(issue)

    return (
        SwaigFunctionResult(
            f"I've created ticket {ticket_id} and sent it to your phone. "
            "Connecting you to support now."
        )
        .send_sms(
            to_number=caller_phone,
            from_number="+15559876543",
            body=f"Your support ticket: {ticket_id}. Reference this when speaking with our team."
        )
        .connect("+15551234567", final=True)
    )

Complete Example

Click to reveal complete solution
#!/usr/bin/env python3
"""Department routing agent."""

from datetime import datetime
from signalwire_agents import AgentBase, SwaigFunctionResult


class RouterAgent(AgentBase):
    DEPARTMENTS = {
        "sales": {
            "number": "+15551111111",
            "hours": (9, 18),
            "after_hours": "+15551111112"
        },
        "support": {
            "number": "+15552222222",
            "hours": (8, 20),
            "after_hours": "+15552222223"
        },
        "billing": {
            "number": "+15553333333",
            "hours": (9, 17),
            "after_hours": None  # No after hours
        }
    }

    def __init__(self):
        super().__init__(name="router")

        self.prompt_add_section(
            "Role",
            "You are the main receptionist. Help callers reach the right department."
        )

        self.prompt_add_section(
            "Available Departments",
            bullets=[
                "Sales - for purchases and pricing",
                "Support - for technical help",
                "Billing - for payments and invoices"
            ]
        )

        self.add_language("English", "en-US", "rime.spore")
        self._setup_functions()

    def _get_department_number(self, dept: str) -> tuple:
        """Get appropriate number based on time."""
        info = self.DEPARTMENTS.get(dept)
        if not info:
            return None, "Department not found"

        hour = datetime.now().hour
        start, end = info["hours"]

        if start <= hour < end:
            return info["number"], None
        elif info["after_hours"]:
            return info["after_hours"], "after-hours"
        else:
            return None, f"Billing is only available {start}AM to {end}PM"

    def _setup_functions(self):
        @self.tool(
            description="Transfer to a department",
            parameters={
                "type": "object",
                "properties": {
                    "department": {
                        "type": "string",
                        "enum": ["sales", "support", "billing"]
                    }
                },
                "required": ["department"]
            }
        )
        def transfer(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
            department = args.get("department", "")
            number, status = self._get_department_number(department)

            if not number:
                return SwaigFunctionResult(status)

            if status == "after-hours":
                msg = f"Connecting you to {department} after-hours support."
            else:
                msg = f"Connecting you to {department}."

            return (
                SwaigFunctionResult(msg)
                .connect(number, final=True)
            )

        @self.tool(description="Check department availability")
        def check_availability(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
            department = args.get("department", "")
            info = self.DEPARTMENTS.get(department)
            if not info:
                return SwaigFunctionResult(
                    f"Unknown department. Available: {', '.join(self.DEPARTMENTS.keys())}"
                )

            hour = datetime.now().hour
            start, end = info["hours"]

            if start <= hour < end:
                return SwaigFunctionResult(
                    f"{department.title()} is open now until {end}:00."
                )
            elif info["after_hours"]:
                return SwaigFunctionResult(
                    f"{department.title()} regular hours are {start}:00 to {end}:00, "
                    "but after-hours support is available."
                )
            else:
                return SwaigFunctionResult(
                    f"{department.title()} is closed. Hours: {start}:00 to {end}:00."
                )


if __name__ == "__main__":
    agent = RouterAgent()
    agent.run()

Key Takeaways

  1. Multiple transfer methods - Cold, warm, SIP
  2. Department routing - Map names to numbers
  3. Time-based logic - Route by business hours
  4. Post-process for sequencing - Transfer after AI finishes
  5. Combine with SMS - Send context before transfer

Preparation for Lab 2.8

  • Get test phone numbers for transfers
  • Plan department structure
  • Consider business hours

Lab Preview

In Lab 2.8, you will:

  1. Build department router
  2. Add time-based routing
  3. Implement transfer with SMS
  4. Test transfer scenarios

Back to top

SignalWire AI Agents Certification Program