Lab L2.11: Serverless Deployment

🎯 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

  • Deploy agent to AWS Lambda
  • Configure API Gateway
  • Handle serverless constraints

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: Lambda Handler (20 min)

handler.py

#!/usr/bin/env python3
"""AWS Lambda handler for SignalWire agent."""

import os
from signalwire_agents import AgentBase, SwaigFunctionResult


class LambdaAgent(AgentBase):
    def __init__(self):
        super().__init__(name="lambda-agent")

        self.prompt_add_section(
            "Role",
            "You are a serverless assistant running on AWS Lambda."
        )

        self.prompt_add_section(
            "Capabilities",
            bullets=[
                "Answer questions about the service",
                "Provide status information",
                "Help with basic inquiries"
            ]
        )

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

    def _setup_functions(self):
        @self.tool(description="Get service status")
        def get_status(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
            return SwaigFunctionResult(
                "The service is running on AWS Lambda. All systems operational."
            )

        @self.tool(description="Get deployment info")
        def get_deployment_info(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
            region = os.getenv("AWS_REGION", "unknown")
            function = os.getenv("AWS_LAMBDA_FUNCTION_NAME", "unknown")
            return SwaigFunctionResult(
                f"Running in {region} as {function}."
            )

        @self.tool(
            description="Echo a message back",
            parameters={
                "type": "object",
                "properties": {
                    "message": {"type": "string"}
                },
                "required": ["message"]
            }
        )
        def echo(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
            message = args.get("message", "")
            return SwaigFunctionResult(f"You said: {message}")


# Create agent instance (outside handler for warm starts)
agent = LambdaAgent()


def lambda_handler(event, context):
    """AWS Lambda entry point."""
    return agent.handle_lambda(event, context)

requirements.txt

signalwire-agents>=1.0.7

Part 2: Serverless Framework Config (20 min)

serverless.yml

service: voice-agent

frameworkVersion: '3'

provider:
  name: aws
  runtime: python3.11
  region: us-east-1
  stage: ${opt:stage, 'dev'}

  environment:
    SIGNALWIRE_SPACE_NAME: ${env:SIGNALWIRE_SPACE_NAME}
    SIGNALWIRE_PROJECT_ID: ${env:SIGNALWIRE_PROJECT_ID}
    SIGNALWIRE_TOKEN: ${env:SIGNALWIRE_TOKEN}

functions:
  agent:
    handler: handler.lambda_handler
    events:
      - http:
          path: /agent
          method: post
          cors: true
      - http:
          path: /agent/swaig
          method: post
          cors: true
    timeout: 30
    memorySize: 512

plugins:
  - serverless-python-requirements

custom:
  pythonRequirements:
    dockerizePip: true
    slim: true
    strip: false

package.json

{
  "name": "voice-agent",
  "devDependencies": {
    "serverless": "^3.38.0",
    "serverless-python-requirements": "^6.0.0"
  },
  "scripts": {
    "deploy": "serverless deploy",
    "remove": "serverless remove",
    "logs": "serverless logs -f agent -t",
    "info": "serverless info"
  }
}

Part 3: Deploy (15 min)

Task

Deploy the agent to AWS Lambda.

Prerequisites

  1. AWS CLI configured with credentials
  2. Node.js installed
  3. Serverless Framework installed

Commands

# Install dependencies
npm install

# Set environment variables
export SIGNALWIRE_SPACE_NAME=your-space
export SIGNALWIRE_PROJECT_ID=your-project-id
export SIGNALWIRE_TOKEN=your-token

# Deploy
npm run deploy

# Get endpoint URL
npm run info

Expected Output

Service Information
service: voice-agent
stage: dev
region: us-east-1
stack: voice-agent-dev
endpoints:
  POST - https://abc123.execute-api.us-east-1.amazonaws.com/dev/agent
  POST - https://abc123.execute-api.us-east-1.amazonaws.com/dev/agent/swaig
functions:
  agent: voice-agent-dev-agent

Part 4: Testing (5 min)

Task

Test the deployed Lambda function.

Commands

# Test the endpoint
curl -X POST https://your-api-endpoint/dev/agent \
  -H "Content-Type: application/json" \
  -d '{}'

# Test function execution
curl -X POST https://your-api-endpoint/dev/agent/swaig \
  -H "Content-Type: application/json" \
  -d '{
    "function": "get_status",
    "args": {}
  }'

# View logs
npm run logs

Cold Start Testing

Measure Cold Start

# Force cold start (wait 15+ minutes or redeploy)
serverless deploy

# Time the first request
time curl -X POST https://your-api-endpoint/dev/agent

# Time subsequent requests (warm)
time curl -X POST https://your-api-endpoint/dev/agent

Expected Results

Request Type Typical Time
Cold start 2-5 seconds
Warm request 100-300ms

Validation Checklist

  • Lambda function deploys successfully
  • API Gateway endpoints created
  • Agent responds to POST requests
  • SWAIG functions execute correctly
  • Logs show in CloudWatch
  • Cold start time acceptable

Optimization Tips

Minimize Cold Starts

  1. Keep dependencies minimal

    # Import only what you need
    from signalwire_agents import AgentBase, SwaigFunctionResult
    
  2. Initialize outside handler

    # Good - reused across invocations
    agent = LambdaAgent()
    
    def lambda_handler(event, context):
        return agent.handle_lambda(event, context)
    
  3. Use Provisioned Concurrency (adds cost)

    functions:
      agent:
        provisionedConcurrency: 1
    

Cleanup

# Remove all AWS resources
npm run remove

Alternative: Google Cloud Functions

main.py

from signalwire_agents import AgentBase, SwaigFunctionResult

class GCFAgent(AgentBase):
    def __init__(self):
        super().__init__(name="gcf-agent")
        self.prompt_add_section("Role", "GCF assistant")
        self.add_language("English", "en-US", "rime.spore")

agent = GCFAgent()

def handle_request(request):
    return agent.handle_cloud_function(request)

Deploy to GCF

gcloud functions deploy voice-agent \
  --runtime python311 \
  --trigger-http \
  --allow-unauthenticated \
  --entry-point handle_request

Submission

Upload your project directory containing:

  • handler.py
  • requirements.txt
  • serverless.yml
  • package.json
  • Screenshot of successful deployment

Complete Agent Code

Click to reveal complete solution
#!/usr/bin/env python3
"""AWS Lambda handler for SignalWire agent.

Lab 2.11 Deliverable: Demonstrates serverless deployment patterns
for AWS Lambda with proper initialization and warm start handling.
"""

import os
from signalwire_agents import AgentBase, SwaigFunctionResult


class LambdaAgent(AgentBase):
    """Agent optimized for serverless deployment."""

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

        self.prompt_add_section(
            "Role",
            "You are a serverless assistant running on AWS Lambda."
        )

        self.prompt_add_section(
            "Capabilities",
            bullets=[
                "Answer questions about the service",
                "Provide status information",
                "Help with basic inquiries"
            ]
        )

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

    def _setup_functions(self):
        @self.tool(description="Get service status")
        def get_status() -> SwaigFunctionResult:
            return SwaigFunctionResult(
                "The service is running on AWS Lambda. All systems operational."
            )

        @self.tool(description="Get deployment info")
        def get_deployment_info() -> SwaigFunctionResult:
            region = os.getenv("AWS_REGION", "unknown")
            function = os.getenv("AWS_LAMBDA_FUNCTION_NAME", "unknown")
            memory = os.getenv("AWS_LAMBDA_FUNCTION_MEMORY_SIZE", "unknown")
            return SwaigFunctionResult(
                f"Running in {region} as {function} with {memory}MB memory."
            )

        @self.tool(
            description="Echo a message back",
            parameters={
                "type": "object",
                "properties": {
                    "message": {
                        "type": "string",
                        "description": "Message to echo"
                    }
                },
                "required": ["message"]
            }
        )
        def echo(message: str) -> SwaigFunctionResult:
            return SwaigFunctionResult(f"You said: {message}")

        @self.tool(description="Get help information")
        def get_help() -> SwaigFunctionResult:
            return SwaigFunctionResult(
                "I can provide status info, deployment details, or echo messages. "
                "How can I help you today?"
            )


# Create agent instance outside handler for warm starts
# This allows the agent to be reused across invocations
agent = LambdaAgent()


def lambda_handler(event, context):
    """AWS Lambda entry point.

    Args:
        event: Lambda event (API Gateway request)
        context: Lambda context with runtime info

    Returns:
        API Gateway response dict
    """
    return agent.handle_lambda(event, context)

Back to top

SignalWire AI Agents Certification Program