Lab L2.10: Production Deployment

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

   
Duration 60 minutes
Prerequisites Previous module completed

Objectives

  • Configure production settings
  • Implement health checks
  • Set up environment management

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: Production Agent Code (20 min)

agent.py

#!/usr/bin/env python3
"""Production-ready agent."""

import os
import logging
from datetime import datetime
from dotenv import load_dotenv
from signalwire_agents import AgentServer, AgentBase, SwaigFunctionResult

# Load environment
load_dotenv()

# Configure logging
log_level = os.getenv("LOG_LEVEL", "INFO").upper()
logging.basicConfig(
    level=log_level,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)


class ProductionAgent(AgentBase):
    def __init__(self):
        super().__init__(
            name=os.getenv("AGENT_NAME", "production-agent"),
            route=os.getenv("AGENT_ROUTE", "/agent")
        )

        # Authentication
        auth_user = os.getenv("AUTH_USER")
        auth_pass = os.getenv("AUTH_PASSWORD")
        if auth_user and auth_pass:
            self.set_params({
                "swml_basic_auth_user": auth_user,
                "swml_basic_auth_password": auth_pass
            })
            logger.info("Basic auth configured")

        # Prompt configuration
        self.prompt_add_section(
            "Role",
            os.getenv("AGENT_ROLE", "You are a helpful assistant.")
        )

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

        self._setup_functions()
        logger.info(f"Agent initialized: {self._name}")

    def _setup_functions(self):
        @self.tool(description="Get system status")
        def get_status(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
            logger.info("Status check requested")
            return SwaigFunctionResult("All systems operational.")

        @self.tool(description="Get current time")
        def get_time(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
            now = datetime.now().strftime("%I:%M %p")
            return SwaigFunctionResult(f"The current time is {now}.")


def create_app():
    """Create the application."""
    host = os.getenv("HOST", "0.0.0.0")
    port = int(os.getenv("PORT", "3000"))

    logger.info(f"Creating server on {host}:{port}")

    server = AgentServer(host=host, port=port)
    server.register(ProductionAgent())

    # Health endpoint
    @server.app.get("/health")
    async def health():
        return {
            "status": "healthy",
            "timestamp": datetime.utcnow().isoformat(),
            "version": os.getenv("APP_VERSION", "1.0.0")
        }

    # Readiness endpoint
    @server.app.get("/ready")
    async def ready():
        return {"ready": True}

    return server


# For uvicorn
server = create_app()
app = server.app


if __name__ == "__main__":
    server.run()

Part 2: Docker Configuration (25 min)

Dockerfile

FROM python:3.11-slim

# Set working directory
WORKDIR /app

# Install curl for health checks
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*

# Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy application
COPY . .

# Create non-root user
RUN useradd -m appuser && chown -R appuser:appuser /app
USER appuser

# Expose port
EXPOSE 3000

# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:3000/health || exit 1

# Run with uvicorn
CMD ["uvicorn", "agent:app", "--host", "0.0.0.0", "--port", "3000"]

requirements.txt

signalwire-agents>=1.0.7
python-dotenv>=1.0.0
uvicorn[standard]>=0.20.0

docker-compose.yml

version: '3.8'

services:
  agent:
    build: .
    ports:
      - "3000:3000"
    env_file:
      - .env.production
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 10s

.env.production

# SignalWire credentials
SIGNALWIRE_SPACE_NAME=your-space
SIGNALWIRE_PROJECT_ID=your-project-id
SIGNALWIRE_TOKEN=your-token

# Server configuration
HOST=0.0.0.0
PORT=3000

# Agent configuration
AGENT_NAME=production-agent
AGENT_ROUTE=/agent
AGENT_ROLE=You are a helpful production assistant.

# Authentication
AUTH_USER=signalwire
AUTH_PASSWORD=change-this-password

# Logging
LOG_LEVEL=INFO

# Version
APP_VERSION=1.0.0

Part 3: Build and Run (20 min)

Task

Build and test the containerized agent.

Commands

# Build the image
docker build -t my-agent:latest .

# Run with docker-compose
docker-compose up -d

# Check logs
docker-compose logs -f

# Check health
curl http://localhost:3000/health

# Test the agent
curl http://localhost:3000/agent

# Stop
docker-compose down

Part 4: Production Testing (25 min)

Task

Verify all production components.

Test Checklist

# 1. Health check
curl -s http://localhost:3000/health | jq
# Expected: {"status": "healthy", "timestamp": "...", "version": "1.0.0"}

# 2. Readiness check
curl -s http://localhost:3000/ready | jq
# Expected: {"ready": true}

# 3. Agent SWML
curl -s http://localhost:3000/agent | jq
# Expected: SWML document with agent configuration

# 4. Authentication (if configured)
curl -s -u signalwire:change-this-password http://localhost:3000/agent | jq

# 5. Container health
docker inspect --format='' $(docker-compose ps -q agent)
# Expected: healthy

# 6. Logs
docker-compose logs --tail=50 agent

Validation Checklist

  • Dockerfile builds without errors
  • Container starts and stays running
  • Health check passes
  • Readiness endpoint responds
  • Agent serves SWML at configured route
  • Authentication works (if configured)
  • Logs show expected output
  • Container runs as non-root user

Production Considerations

Security

  • Changed default AUTH_PASSWORD
  • HTTPS configured (via reverse proxy)
  • Secrets not committed to git

Monitoring

  • Health checks configured
  • Log aggregation set up
  • Alerts configured for failures

Scaling

  • Resource limits set
  • Multiple replicas if needed
  • Load balancer configured

Challenge Extension

Add a Kubernetes deployment:

deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: voice-agent
spec:
  replicas: 2
  selector:
    matchLabels:
      app: voice-agent
  template:
    metadata:
      labels:
        app: voice-agent
    spec:
      containers:
      - name: agent
        image: my-agent:latest
        ports:
        - containerPort: 3000
        envFrom:
        - secretRef:
            name: agent-secrets
        livenessProbe:
          httpGet:
            path: /health
            port: 3000
          initialDelaySeconds: 10
          periodSeconds: 30
        readinessProbe:
          httpGet:
            path: /ready
            port: 3000
          initialDelaySeconds: 5
          periodSeconds: 10
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"

Submission

Upload your project directory containing:

  • agent.py
  • Dockerfile
  • docker-compose.yml
  • requirements.txt
  • .env.production.example (without real credentials)

Complete Agent Code

Click to reveal complete solution
#!/usr/bin/env python3
"""Production-ready agent with health checks.

Lab 2.10 Deliverable: Demonstrates production deployment patterns
including environment configuration, logging, and health endpoints.

Environment variables:
    SWML_BASIC_AUTH_USER: Basic auth username (auto-detected by SDK)
    SWML_BASIC_AUTH_PASSWORD: Basic auth password (auto-detected by SDK)
    AGENT_NAME: Agent name (default: production-agent)
    AGENT_ROLE: Agent role description
    HOST: Bind host (default: 0.0.0.0)
    PORT: Bind port (default: 3000)
    LOG_LEVEL: Logging level (default: INFO)
    SUPPORT_EMAIL: Support email address
    APP_VERSION: Application version
"""

import os
import logging
from datetime import datetime
from signalwire_agents import AgentServer, AgentBase, SwaigFunctionResult

try:
    from dotenv import load_dotenv
    load_dotenv()
except ImportError:
    pass  # dotenv not required

# Configure logging
log_level = os.getenv("LOG_LEVEL", "INFO").upper()
logging.basicConfig(
    level=log_level,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)


class ProductionAgent(AgentBase):
    """Production-ready agent with configuration from environment."""

    def __init__(self):
        super().__init__(name=os.getenv("AGENT_NAME", "production-agent"))

        # Prompt configuration
        self.prompt_add_section(
            "Role",
            os.getenv("AGENT_ROLE", "You are a helpful assistant.")
        )

        self.prompt_add_section(
            "Guidelines",
            bullets=[
                "Keep responses concise",
                "Be helpful and professional",
                "Escalate if needed"
            ]
        )

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

        self._setup_functions()
        logger.info(f"Agent initialized: {self.get_name()}")

    def _setup_functions(self):
        @self.tool(description="Get system status")
        def get_status() -> SwaigFunctionResult:
            logger.info("Status check requested")
            return SwaigFunctionResult("All systems operational.")

        @self.tool(description="Get current time")
        def get_time() -> SwaigFunctionResult:
            now = datetime.now().strftime("%I:%M %p")
            return SwaigFunctionResult(f"The current time is {now}.")

        @self.tool(
            description="Get help or support information"
        )
        def get_help() -> SwaigFunctionResult:
            support_email = os.getenv("SUPPORT_EMAIL", "support@company.com")
            return SwaigFunctionResult(
                f"For additional help, contact {support_email}."
            )


def create_app():
    """Create the application with health endpoints."""
    host = os.getenv("HOST", "0.0.0.0")
    port = int(os.getenv("PORT", "3000"))

    logger.info(f"Creating server on {host}:{port}")

    server = AgentServer(host=host, port=port)
    server.register(ProductionAgent())

    # Health endpoint
    @server.app.get("/health")
    async def health():
        return {
            "status": "healthy",
            "timestamp": datetime.utcnow().isoformat(),
            "version": os.getenv("APP_VERSION", "1.0.0")
        }

    # Readiness endpoint
    @server.app.get("/ready")
    async def ready():
        return {"ready": True}

    # Info endpoint
    @server.app.get("/info")
    async def info():
        return {
            "name": os.getenv("AGENT_NAME", "production-agent"),
            "version": os.getenv("APP_VERSION", "1.0.0"),
            "environment": os.getenv("ENVIRONMENT", "production")
        }

    return server


if __name__ == "__main__":
    server = create_app()
    server.run()

Back to top

SignalWire AI Agents Certification Program