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
- Accept the Assignment — Click the GitHub Classroom link above
- Clone Your Repo —
git clone <your-repo-url> - Read the README — Your repo has detailed requirements and grading criteria
- Write Your Code — Implement the solution in
solution/agent.py - Test Locally — Use
swaig-testto verify your agent works - Push to Submit —
git pushtriggers 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:
- Greeting - Welcome and menu info
- Ordering - Collect pizza order
- 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()