Skip to main content

Dev Environment

Development Environment Setup

Summary: Configure a professional development environment for building SignalWire agents with proper project structure, environment variables, and debugging tools.

my-agent-project/
├── venv/ # Virtual environment
├── agents/ # Your agent modules
│ ├── __init__.py
│ ├── customer_service.py
│ └── support_agent.py
├── skills/ # Custom skills (optional)
│ └── my_custom_skill/
│ ├── __init__.py
│ └── skill.py
├── tests/ # Test files
│ ├── __init__.py
│ └── test_agents.py
├── .env # Environment variables (not in git)
├── .env.example # Example env file (in git)
├── .gitignore
├── requirements.txt
└── main.py # Entry point

Create the Project

## Create project directory
mkdir my-agent-project
cd my-agent-project

## Create virtual environment
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate

## Install dependencies
pip install signalwire-agents

## Create directory structure
mkdir -p agents skills tests

## Create initial files
touch agents/__init__.py
touch tests/__init__.py
touch .env .env.example .gitignore requirements.txt main.py

Environment Variables

Create a .env file for configuration:

## .env - DO NOT COMMIT THIS FILE

## Authentication
## These set your agent's basic auth credentials.
## If not set, SDK uses username "signalwire" with an auto-generated
## password that changes on every invocation (printed to console).
SWML_BASIC_AUTH_USER=my_username
SWML_BASIC_AUTH_PASSWORD=my_secure_password_here

## Server Configuration
SWML_PROXY_URL_BASE=https://my-agent.ngrok.io

## SSL (optional, for production)
SWML_SSL_ENABLED=false
SWML_SSL_CERT_PATH=
SWML_SSL_KEY_PATH=

## Skill API Keys (as needed)
GOOGLE_API_KEY=your_google_api_key
GOOGLE_CX_ID=your_custom_search_id
WEATHER_API_KEY=your_weather_api_key

## Logging
SIGNALWIRE_LOG_MODE=default

Important: The SWML_BASIC_AUTH_USER and SWML_BASIC_AUTH_PASSWORD environment variables let you set stable credentials for your agent. Without these:

  • Username defaults to signalwire
  • Password is randomly generated on each startup
  • The generated password is printed to the console

For development, you can leave these unset and use the printed credentials. For production, always set explicit values.

Create .env.example as a template (safe to commit):

## .env.example - Template for environment variables

## Authentication (optional - SDK generates credentials if not set)
SWML_BASIC_AUTH_USER=
SWML_BASIC_AUTH_PASSWORD=

## Server Configuration
SWML_PROXY_URL_BASE=

## Skill API Keys
GOOGLE_API_KEY=
WEATHER_API_KEY=

Loading Environment Variables

Install python-dotenv:

pip install python-dotenv

Load in your agent:

#!/usr/bin/env python3
## main.py - Main entry point with environment loading
"""Main entry point with environment loading."""

import os
from dotenv import load_dotenv

## Load environment variables from .env file
load_dotenv()

from agents.customer_service import CustomerServiceAgent


def main():
agent = CustomerServiceAgent()

# Use environment variables
host = os.getenv("AGENT_HOST", "0.0.0.0")
port = int(os.getenv("AGENT_PORT", "3000"))

print(f"Starting agent on {host}:{port}")
agent.run(host=host, port=port)


if __name__ == "__main__":
main()

The .gitignore File

## Virtual environment
venv/
.venv/
env/

## Environment variables
.env
.env.local
.env.*.local

## Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
dist/
*.egg-info/

## IDE
.idea/
.vscode/
*.swp
*.swo
*~

## Testing
.pytest_cache/
.coverage
htmlcov/

## Logs
*.log

## OS
.DS_Store
Thumbs.db

Requirements File

Create requirements.txt:

signalwire-agents>=1.0.10
python-dotenv>=1.0.0

Or generate from current environment:

pip freeze > requirements.txt

IDE Configuration

VS Code

Create .vscode/settings.json:

{
"python.defaultInterpreterPath": "${workspaceFolder}/venv/bin/python",
"python.envFile": "${workspaceFolder}/.env",
"python.testing.pytestEnabled": true,
"python.testing.pytestArgs": ["tests"],
"editor.formatOnSave": true,
"python.formatting.provider": "black"
}

Create .vscode/launch.json for debugging:

{
"version": "0.2.0",
"configurations": [
{
"name": "Run Agent",
"type": "python",
"request": "launch",
"program": "${workspaceFolder}/main.py",
"console": "integratedTerminal",
"envFile": "${workspaceFolder}/.env"
},
{
"name": "Run Current File",
"type": "python",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"envFile": "${workspaceFolder}/.env"
},
{
"name": "Test Agent with swaig-test",
"type": "python",
"request": "launch",
"module": "signalwire_agents.cli.test_swaig",
"args": ["${file}", "--dump-swml"],
"console": "integratedTerminal"
}
]
}

PyCharm

  1. Open Settings → Project → Python Interpreter
  2. Select your virtual environment
  3. Go to Run → Edit Configurations
  4. Create a Python configuration:
    • Script path: main.py
    • Working directory: Project root
    • Environment variables: Load from .env

Using swaig-test for Development

The swaig-test CLI is essential for development:

## View SWML output (formatted)
swaig-test agents/customer_service.py --dump-swml

## View raw SWML JSON
swaig-test agents/customer_service.py --dump-swml --raw

## List all registered functions
swaig-test agents/customer_service.py --list-tools

## Execute a specific function
swaig-test agents/customer_service.py --exec get_customer --customer_id 12345

## Simulate serverless environment
swaig-test agents/customer_service.py --simulate-serverless lambda --dump-swml

Development Workflow

1. Edit Code

Modify your agent in agents/.

2. Quick Test

  • swaig-test agents/my_agent.py --dump-swml
  • Verify SWML looks correct

3. Function Test

  • swaig-test agents/my_agent.py --exec my_function --arg value
  • Verify function returns expected result

4. Run Server

  • python main.py
  • curl http://localhost:3000/

5. Integration Test

  • Start ngrok (see next section)
  • Configure SignalWire webhook
  • Make test call

Sample Agent Module

#!/usr/bin/env python3
## customer_service.py - Customer service agent
"""
Customer Service Agent

A production-ready customer service agent template.
"""

import os
from signalwire_agents import AgentBase, SwaigFunctionResult


class CustomerServiceAgent(AgentBase):
"""Customer service voice AI agent."""

def __init__(self):
super().__init__(
name="customer-service",
route="/",
host="0.0.0.0",
port=int(os.getenv("AGENT_PORT", "3000"))
)

self._configure_voice()
self._configure_prompts()
self._configure_functions()

def _configure_voice(self):
"""Set up voice and language."""
self.add_language("English", "en-US", "rime.spore")

self.set_params({
"end_of_speech_timeout": 500,
"attention_timeout": 15000,
})

self.add_hints([
"account",
"billing",
"support",
"representative"
])

def _configure_prompts(self):
"""Set up AI prompts."""
self.prompt_add_section(
"Role",
"You are a helpful customer service representative for Acme Corp. "
"Help customers with their questions about accounts, billing, and products."
)

self.prompt_add_section(
"Guidelines",
body="Follow these guidelines:",
bullets=[
"Be professional and courteous",
"Ask clarifying questions when needed",
"Offer to transfer to a human if you cannot help",
"Keep responses concise"
]
)

def _configure_functions(self):
"""Register SWAIG functions."""
self.define_tool(
name="lookup_account",
description="Look up a customer account by phone number or account ID",
parameters={
"type": "object",
"properties": {
"identifier": {
"type": "string",
"description": "Phone number or account ID"
}
},
"required": ["identifier"]
},
handler=self.lookup_account
)

self.define_tool(
name="transfer_to_human",
description="Transfer the call to a human representative",
parameters={"type": "object", "properties": {}},
handler=self.transfer_to_human
)

def lookup_account(self, args, raw_data):
"""Look up account information."""
identifier = args.get("identifier", "")

# In production, query your database here
return SwaigFunctionResult(
f"Found account for {identifier}: Status is Active, Balance is $0.00"
)

def transfer_to_human(self, args, raw_data):
"""Transfer to human support."""
return SwaigFunctionResult(
"Transferring you to a human representative now."
).connect("+15551234567", final=True, from_addr="+15559876543")


## Allow running directly for testing
if __name__ == "__main__":
agent = CustomerServiceAgent()
agent.run()

Testing Your Agent

#!/usr/bin/env python3
## test_agents.py - Tests for agents
"""Tests for agents."""

import pytest
from agents.customer_service import CustomerServiceAgent


class TestCustomerServiceAgent:
"""Test customer service agent."""

def setup_method(self):
"""Set up test fixtures."""
self.agent = CustomerServiceAgent()

def test_agent_name(self):
"""Test agent has correct name."""
assert self.agent.name == "customer-service"

def test_lookup_account(self):
"""Test account lookup function."""
result = self.agent.lookup_account(
{"identifier": "12345"},
{}
)
assert "Found account" in result

def test_has_functions(self):
"""Test agent has expected functions."""
functions = self.agent._tool_registry.get_function_names()
assert "lookup_account" in functions
assert "transfer_to_human" in functions

Run tests:

pytest tests/ -v

Next Steps

Your development environment is ready. Now let's expose your agent to the internet so SignalWire can reach it.