Lab L2.5: Contexts and Workflows

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

   
Duration 75 minutes
Prerequisites Previous module completed

Objectives

  • Build multi-step workflows with contexts
  • Implement context transitions
  • Handle workflow completion

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.

Scenario

Build a pizza ordering agent with three contexts:

  1. Greeting - Welcome and menu info
  2. Ordering - Collect pizza order
  3. Checkout - Confirm and complete

Part 1: Define Contexts (25 min)

Task

Create the three contexts with appropriate steps.

Starter Code

#!/usr/bin/env python3
"""Lab 2.5: Multi-context pizza ordering."""

from signalwire_agents import AgentBase, SwaigFunctionResult


class PizzaAgent(AgentBase):
    MENU = {
        "margherita": 12.99,
        "pepperoni": 14.99,
        "veggie": 13.99,
        "meat lovers": 16.99
    }

    SIZES = {
        "small": 0,
        "medium": 2,
        "large": 4
    }

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

        self.prompt_add_section(
            "Role",
            "You are a pizza ordering assistant for Pizza Palace."
        )

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

        self._setup_contexts()
        self._setup_functions()

    def _setup_contexts(self):
        # TODO: Create greeting, ordering, and checkout contexts
        pass

    def _setup_functions(self):
        # TODO: Create functions for each context
        pass


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

Part 2: Greeting Context (15 min)

Task

Create the greeting context with menu information.

Expected Implementation

def _setup_contexts(self):
    contexts = self.define_contexts()

    # Greeting context
    greeting = contexts.add_context("greeting")
    greeting.add_step("welcome") \
        .set_text("Welcome to Pizza Palace! Would you like to hear our menu "
                  "or start ordering?") \
        .set_step_criteria("Customer wants to order or hear menu") \
        .set_functions(["get_menu", "start_order"]) \
        .set_valid_contexts(["ordering"])

Greeting Functions

@self.tool(description="Get the pizza menu")
def get_menu(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
    menu_items = [f"{name.title()}: ${price}" for name, price in self.MENU.items()]
    return SwaigFunctionResult(
        "Our pizzas are: " + ", ".join(menu_items) + ". "
        "Sizes are small, medium, or large."
    )

@self.tool(description="Start a new pizza order")
def start_order(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
    return (
        SwaigFunctionResult("Great! What pizza would you like?")
        .swml_change_context("ordering")
        .set_metadata({"order_started": True, "items": []})
    )

Part 3: Ordering Context (20 min)

Task

Create the ordering context for collecting pizza details.

Expected Implementation

    # Ordering context (continuing from _setup_contexts)
    ordering = contexts.add_context("ordering")
    ordering.add_step("take_order") \
        .set_text("What pizza would you like to order?") \
        .set_step_criteria("Customer has specified pizza type and size") \
        .set_functions(["add_pizza", "finish_order", "get_menu"]) \
        .set_valid_contexts(["checkout"])

Ordering Functions

@self.tool(
    description="Add a pizza to the order",
    parameters={
        "type": "object",
        "properties": {
            "pizza_type": {
                "type": "string",
                "enum": list(self.MENU.keys())
            },
            "size": {
                "type": "string",
                "enum": list(self.SIZES.keys())
            }
        },
        "required": ["pizza_type", "size"]
    }
)
def add_pizza(
    args: dict,
    raw_data: dict = None
) -> SwaigFunctionResult:
    pizza_type = args.get("pizza_type", "")
    size = args.get("size", "")
    raw_data = raw_data or {}
    global_data = raw_data.get("global_data", {})
    items = global_data.get("items", [])

    base_price = self.MENU.get(pizza_type, 0)
    size_upcharge = self.SIZES.get(size, 0)
    price = base_price + size_upcharge

    items.append({
        "type": pizza_type,
        "size": size,
        "price": price
    })

    return (
        SwaigFunctionResult(
            f"Added {size} {pizza_type} pizza (${price:.2f}). "
            "Would you like anything else?"
        )
        .set_metadata({"items": items})
    )

@self.tool(description="Finish ordering and proceed to checkout")
def finish_order(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
    raw_data = raw_data or {}
    global_data = raw_data.get("global_data", {})
    items = global_data.get("items", [])

    if not items:
        return SwaigFunctionResult(
            "Your cart is empty. What pizza would you like?"
        )

    total = sum(item["price"] for item in items)

    return (
        SwaigFunctionResult(
            f"Your total is ${total:.2f}. Ready to checkout?"
        )
        .swml_change_context("checkout")
        .set_metadata({"total": total})
    )

Part 4: Checkout Context (15 min)

Task

Create the checkout context for order completion.

Expected Implementation

    # Checkout context (continuing from _setup_contexts)
    checkout = contexts.add_context("checkout")
    checkout.add_step("confirm") \
        .set_text("Please confirm your order.") \
        .set_step_criteria("Customer has confirmed or cancelled order") \
        .set_functions(["confirm_order", "cancel_order", "add_more"]) \
        .set_valid_contexts(["greeting", "ordering"])

Checkout Functions

@self.tool(description="Confirm and place the order")
def confirm_order(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
    raw_data = raw_data or {}
    global_data = raw_data.get("global_data", {})
    total = global_data.get("total", 0)
    items = global_data.get("items", [])

    order_summary = ", ".join(
        f"{i['size']} {i['type']}" for i in items
    )

    return (
        SwaigFunctionResult(
            f"Order confirmed! {order_summary}. "
            f"Total: ${total:.2f}. Ready in 20 minutes. Thank you!"
        )
        .set_metadata({"order_confirmed": True})
    )

@self.tool(description="Cancel the order")
def cancel_order(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
    return (
        SwaigFunctionResult("Order cancelled. Come back anytime!")
        .set_metadata({"items": [], "total": 0})
        .swml_change_context("greeting")
    )

@self.tool(description="Go back to add more items")
def add_more(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
    return (
        SwaigFunctionResult("Sure! What else would you like?")
        .swml_change_context("ordering")
    )

Testing

# Test agent
swaig-test lab2_5_contexts.py --dump-swml

# Verify contexts in SWML
swaig-test lab2_5_contexts.py --dump-swml | grep -A10 "contexts"

# Test workflow functions
swaig-test lab2_5_contexts.py --exec start_order
swaig-test lab2_5_contexts.py --exec add_pizza \
  --pizza_type "pepperoni" \
  --size "large"

Validation Checklist

  • Three contexts defined (greeting, ordering, checkout)
  • Context transitions work correctly
  • State persists across context switches
  • Order total calculates correctly
  • Can cancel and return to greeting

Submission

Upload your completed lab2_5_contexts.py file.


Complete Agent Code

Click to reveal complete solution
#!/usr/bin/env python3
"""Multi-context pizza ordering agent.

Lab 2.5 Deliverable: Demonstrates context-based workflows with three
contexts (greeting, ordering, checkout) and context switching.
"""

from signalwire_agents import AgentBase, SwaigFunctionResult


class PizzaAgent(AgentBase):
    """Pizza ordering agent with multi-context workflow."""

    MENU = {
        "margherita": 12.99,
        "pepperoni": 14.99,
        "veggie": 13.99,
        "meat lovers": 16.99,
        "hawaiian": 14.99,
        "supreme": 17.99
    }

    SIZES = {
        "small": 0,
        "medium": 2,
        "large": 4
    }

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

        self.prompt_add_section(
            "Role",
            "You are a pizza ordering assistant for Pizza Palace."
        )

        self.prompt_add_section(
            "Guidelines",
            bullets=[
                "Be friendly and helpful",
                "Confirm orders before checkout",
                "Suggest popular items if asked"
            ]
        )

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

        self._setup_contexts()
        self._setup_functions()

    def _setup_contexts(self):
        """Define workflow contexts using SDK context system."""
        contexts = self.define_contexts()

        # Greeting context - entry point
        greeting = contexts.add_context("greeting")
        greeting.add_step("welcome") \
            .set_text(
                "Welcome to Pizza Palace! Would you like to hear our menu "
                "or start ordering?"
            ) \
            .set_step_criteria("Customer has indicated they want to order or hear menu") \
            .set_valid_steps(["next"])
        greeting.add_step("ready") \
            .set_text("Ready to take your order when you are!") \
            .set_functions(["get_menu", "start_order"])

        # Ordering context - pizza selection
        ordering = contexts.add_context("ordering")
        ordering.add_step("select") \
            .set_text("What pizza would you like to order?") \
            .set_step_criteria("Customer has selected pizzas or wants to checkout") \
            .set_functions(["add_pizza", "finish_order", "get_menu", "remove_last_item"])

        # Checkout context - confirmation
        checkout = contexts.add_context("checkout")
        checkout.add_step("confirm") \
            .set_text("Please confirm your order.") \
            .set_functions(["confirm_order", "cancel_order", "add_more"])

    def _setup_functions(self):
        """Define SWAIG functions for all contexts."""

        @self.tool(description="Get the pizza menu")
        def get_menu(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
            menu_items = [f"{name.title()}: ${price}" for name, price in self.MENU.items()]
            sizes = [f"{size.title()}: +${upcharge}" for size, upcharge in self.SIZES.items() if upcharge > 0]
            return SwaigFunctionResult(
                f"Our pizzas: {', '.join(menu_items)}. "
                f"Size upcharges: {', '.join(sizes)}. Small has no upcharge."
            )

        @self.tool(description="Start a new pizza order")
        def start_order(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
            return (
                SwaigFunctionResult("Great! What pizza would you like?")
                .swml_change_context("ordering")
                .update_global_data({"order_started": True, "items": []})
            )

        @self.tool(
            description="Add a pizza to the order",
            parameters={
                "type": "object",
                "properties": {
                    "pizza_type": {
                        "type": "string",
                        "enum": list(self.MENU.keys()),
                        "description": "Type of pizza"
                    },
                    "size": {
                        "type": "string",
                        "enum": list(self.SIZES.keys()),
                        "description": "Pizza size"
                    }
                },
                "required": ["pizza_type", "size"]
            }
        )
        def add_pizza(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
            pizza_type = args.get("pizza_type", "")
            size = args.get("size", "medium")
            raw_data = raw_data or {}
            global_data = raw_data.get("global_data", {})
            items = global_data.get("items", [])

            base_price = self.MENU.get(pizza_type, 0)
            size_upcharge = self.SIZES.get(size, 0)
            price = base_price + size_upcharge

            items.append({
                "type": pizza_type,
                "size": size,
                "price": price
            })

            return (
                SwaigFunctionResult(
                    f"Added {size} {pizza_type} pizza (${price:.2f}). "
                    f"You have {len(items)} item(s). Would you like anything else?"
                )
                .update_global_data({"items": items})
            )

        @self.tool(description="Remove the last item from the order")
        def remove_last_item(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
            raw_data = raw_data or {}
            global_data = raw_data.get("global_data", {})
            items = global_data.get("items", [])

            if not items:
                return SwaigFunctionResult("Your cart is empty.")

            removed = items.pop()
            return (
                SwaigFunctionResult(
                    f"Removed {removed['size']} {removed['type']}. "
                    f"You have {len(items)} item(s) remaining."
                )
                .update_global_data({"items": items})
            )

        @self.tool(description="Finish ordering and proceed to checkout")
        def finish_order(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
            raw_data = raw_data or {}
            global_data = raw_data.get("global_data", {})
            items = global_data.get("items", [])

            if not items:
                return SwaigFunctionResult(
                    "Your cart is empty. What pizza would you like?"
                )

            total = sum(item["price"] for item in items)
            order_summary = ", ".join(
                f"{item['size']} {item['type']}" for item in items
            )

            return (
                SwaigFunctionResult(
                    f"Your order: {order_summary}. "
                    f"Total: ${total:.2f}. Ready to checkout?"
                )
                .swml_change_context("checkout")
                .update_global_data({"total": total})
            )

        @self.tool(description="Confirm and place the order")
        def confirm_order(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
            raw_data = raw_data or {}
            global_data = raw_data.get("global_data", {})
            total = global_data.get("total", 0)
            items = global_data.get("items", [])

            order_summary = ", ".join(
                f"{item['size']} {item['type']}" for item in items
            )

            return (
                SwaigFunctionResult(
                    f"Order confirmed! {order_summary}. "
                    f"Total: ${total:.2f}. Ready in 20 minutes. Thank you!"
                )
                .update_global_data({"order_confirmed": True})
            )

        @self.tool(description="Cancel the order")
        def cancel_order(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
            return (
                SwaigFunctionResult("Order cancelled. Come back anytime!")
                .update_global_data({"items": [], "total": 0})
                .swml_change_context("greeting")
            )

        @self.tool(description="Go back to add more items")
        def add_more(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
            return (
                SwaigFunctionResult("Sure! What else would you like?")
                .swml_change_context("ordering")
            )


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

Back to top

SignalWire AI Agents Certification Program