Lab L2.1: Function Results and Actions

🎯 Assignment: Accept this lab on GitHub Classroom
You’ll get your own repository with starter code, instructions, and automatic grading.

   
Duration 45 minutes
Prerequisites Previous module completed

Objectives

  • Chain multiple actions in a single response
  • Implement post-process actions
  • Control call flow with actions

How to Complete This Lab

  1. Accept the Assignment — Click the GitHub Classroom link above
  2. Clone Your Repo — git clone <your-repo-url>
  3. Read the README — Your repo has detailed requirements and grading criteria
  4. Write Your Code — Implement the solution in solution/agent.py
  5. Test Locally — Use swaig-test to verify your agent works
  6. Push to Submit — git push triggers auto-grading

Key Concepts

The following exercises walk through the concepts you’ll need. Your GitHub Classroom repo README has the specific requirements for grading.

Part 1: Action Chaining (15 min)

Task

Build an agent that confirms appointments and performs multiple actions:

  1. Says a confirmation message
  2. Sends an SMS confirmation
  3. Updates metadata with appointment details

Starter Code

#!/usr/bin/env python3
"""Lab 2.1: Action chaining."""

from signalwire_agents import AgentBase, SwaigFunctionResult


class AppointmentAgent(AgentBase):
    def __init__(self):
        super().__init__(name="appointment-agent")

        self.prompt_add_section(
            "Role",
            "You help customers schedule and confirm appointments."
        )

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

        # TODO: Add the confirm_appointment function


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

Your Task

Add a confirm_appointment function that:

  • Takes date, time, and phone parameters
  • Returns a spoken confirmation
  • Sends an SMS with appointment details
  • Stores appointment info in metadata

Expected Result

@AgentBase.tool(
    description="Confirm an appointment",
    parameters={
        "type": "object",
        "properties": {
            "date": {"type": "string", "description": "Appointment date"},
            "time": {"type": "string", "description": "Appointment time"},
            "phone": {"type": "string", "description": "Customer phone"}
        },
        "required": ["date", "time", "phone"]
    }
)
def confirm_appointment(
    self,
    args: dict,
    raw_data: dict = None
) -> SwaigFunctionResult:
    date = args.get("date", "")
    time = args.get("time", "")
    phone = args.get("phone", "")
    return (
        SwaigFunctionResult(
            f"Your appointment is confirmed for {date} at {time}. "
            "I've sent a confirmation to your phone."
        )
        .send_sms(
            to_number=phone,
            from_number="+15559876543",
            body=f"Appointment confirmed: {date} at {time}"
        )
        .update_global_data({
            "appointment_date": date,
            "appointment_time": time,
            "confirmed": True
        })
    )

Part 2: Post-Process Actions (15 min)

Task

Create a function that schedules a callback after the conversation ends.

Your Task

Add a schedule_callback function that:

  • Takes phone and reason parameters
  • Tells the user a callback will be scheduled
  • Uses post-process to actually schedule it (simulated with metadata)

Expected Result

@AgentBase.tool(
    description="Schedule a callback for the customer",
    parameters={
        "type": "object",
        "properties": {
            "phone": {"type": "string"},
            "reason": {"type": "string"}
        },
        "required": ["phone", "reason"]
    }
)
def schedule_callback(
    self,
    args: dict,
    raw_data: dict = None
) -> SwaigFunctionResult:
    phone = args.get("phone", "")
    reason = args.get("reason", "")
    return (
        SwaigFunctionResult(
            "I've scheduled a callback for you. "
            "One of our team members will reach out soon.",
            post_process=True  # AI can respond to follow-up before action executes
        )
        .update_global_data({
            "callback_phone": phone,
            "callback_reason": reason,
            "callback_scheduled": True
        })
    )

Part 3: Call Flow Control (15 min)

Task

Build an escalation function that transfers to a supervisor.

Your Task

Add an escalate_call function that:

  • Takes a reason parameter
  • Explains the transfer to the caller
  • Performs a post-process transfer

Expected Result

@AgentBase.tool(
    description="Escalate call to supervisor",
    parameters={
        "type": "object",
        "properties": {
            "reason": {"type": "string", "description": "Escalation reason"}
        },
        "required": ["reason"]
    }
)
def escalate_call(self, args: dict, raw_data: dict = None) -> SwaigFunctionResult:
    reason = args.get("reason", "")
    return (
        SwaigFunctionResult(
            "I understand this needs supervisor attention. "
            "Let me transfer you now.",
            post_process=True  # AI finishes speaking before transfer
        )
        .update_global_data({"escalation_reason": reason})
        .connect("+15551234567", final=True)
    )

Testing

# Test the agent
swaig-test lab2_1_actions.py --dump-swml

# Verify functions are registered
swaig-test lab2_1_actions.py --list-tools

# Test specific function
swaig-test lab2_1_actions.py --exec confirm_appointment \
  --date "2024-01-15" \
  --time "2:00 PM" \
  --phone "+15559876543"

Validation Checklist

  • confirm_appointment returns 3 chained actions
  • schedule_callback uses post_process=True
  • escalate_call performs transfer after speaking
  • All functions appear in SWML output
  • SMS actions include required fields

Challenge Extension

Add a cancel_appointment function that:

  1. Confirms cancellation verbally
  2. Sends cancellation SMS
  3. Updates metadata to mark as cancelled
  4. Offers to reschedule (context switch if you’ve learned contexts)

Submission

Upload your completed lab2_1_actions.py file.


Complete Agent Code

Click to reveal complete solution
#!/usr/bin/env python3
"""Appointment agent with action chaining.

Lab 2.1 Deliverable: Demonstrates SwaigFunctionResult action chaining
including post-process actions for transfers and callbacks.
"""

from signalwire_agents import AgentBase, SwaigFunctionResult


class AppointmentAgent(AgentBase):
    """Agent for scheduling appointments with action chaining."""

    def __init__(self):
        super().__init__(name="appointment-agent")

        self.prompt_add_section(
            "Role",
            "You help customers schedule and confirm appointments."
        )

        self.prompt_add_section(
            "Capabilities",
            bullets=[
                "Confirm appointments with SMS notifications",
                "Schedule callbacks for follow-up",
                "Escalate to supervisor when needed"
            ]
        )

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

    def _setup_functions(self):
        @self.tool(
            description="Confirm an appointment",
            parameters={
                "type": "object",
                "properties": {
                    "date": {"type": "string", "description": "Appointment date"},
                    "time": {"type": "string", "description": "Appointment time"},
                    "phone": {"type": "string", "description": "Customer phone"}
                },
                "required": ["date", "time", "phone"]
            }
        )
        def confirm_appointment(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
            """Confirm appointment with multiple chained actions."""
            date = args.get("date", "")
            time = args.get("time", "")
            phone = args.get("phone", "")
            return (
                SwaigFunctionResult(
                    f"Your appointment is confirmed for {date} at {time}. "
                    "I've sent a confirmation to your phone."
                )
                .send_sms(
                    to_number=phone,
                    from_number="+15559999999",
                    body=f"Appointment confirmed: {date} at {time}"
                )
                .update_global_data({
                    "appointment_date": date,
                    "appointment_time": time,
                    "confirmed": True
                })
            )

        @self.tool(
            description="Schedule a callback for the customer",
            parameters={
                "type": "object",
                "properties": {
                    "phone": {"type": "string"},
                    "reason": {"type": "string"}
                },
                "required": ["phone", "reason"]
            }
        )
        def schedule_callback(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
            """Schedule callback using post-process action."""
            phone = args.get("phone", "")
            reason = args.get("reason", "")
            return (
                SwaigFunctionResult(
                    "I've scheduled a callback for you. "
                    "One of our team members will reach out soon.",
                    post_process=True
                )
                .update_global_data({
                    "callback_phone": phone,
                    "callback_reason": reason,
                    "callback_scheduled": True
                })
            )

        @self.tool(
            description="Escalate call to supervisor",
            parameters={
                "type": "object",
                "properties": {
                    "reason": {"type": "string", "description": "Escalation reason"}
                },
                "required": ["reason"]
            }
        )
        def escalate_call(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
            """Escalate to supervisor with post-process transfer."""
            reason = args.get("reason", "")
            return (
                SwaigFunctionResult(
                    "I understand this needs supervisor attention. "
                    "Let me transfer you now.",
                    post_process=True
                )
                .update_global_data({"escalation_reason": reason})
                .connect("+15551234567", final=True)
            )

        @self.tool(
            description="Cancel an appointment",
            parameters={
                "type": "object",
                "properties": {
                    "appointment_id": {"type": "string"},
                    "phone": {"type": "string"}
                },
                "required": ["appointment_id", "phone"]
            }
        )
        def cancel_appointment(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
            """Cancel appointment with SMS confirmation."""
            appointment_id = args.get("appointment_id", "")
            phone = args.get("phone", "")
            return (
                SwaigFunctionResult(
                    f"Appointment {appointment_id} has been cancelled. "
                    "I've sent a confirmation to your phone. "
                    "Would you like to reschedule?"
                )
                .send_sms(
                    to_number=phone,
                    from_number="+15559999999",
                    body=f"Appointment {appointment_id} cancelled."
                )
                .update_global_data({
                    "cancelled_appointment": appointment_id,
                    "cancellation_confirmed": True
                })
            )


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

Back to top

SignalWire AI Agents Certification Program