Dev Environment

View as Markdown

Development Environment Setup

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)
tests# Test files
.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:

1#!/usr/bin/env python3
2## main.py - Main entry point with environment loading
3"""Main entry point with environment loading."""
4
5import os
6from dotenv import load_dotenv
7
8## Load environment variables from .env file
9load_dotenv()
10
11from agents.customer_service import CustomerServiceAgent
12
13
14def main():
15 agent = CustomerServiceAgent()
16
17 # Use environment variables
18 host = os.getenv("AGENT_HOST", "0.0.0.0")
19 port = int(os.getenv("AGENT_PORT", "3000"))
20
21 print(f"Starting agent on {host}:{port}")
22 agent.run(host=host, port=port)
23
24
25if __name__ == "__main__":
26 main()

The .gitignore File

1## Virtual environment
2venv/
3.venv/
4env/
5
6## Environment variables
7.env
8.env.local
9.env.*.local
10
11## Python
12__pycache__/
13*.py[cod]
14*$py.class
15*.so
16.Python
17build/
18dist/
19*.egg-info/
20
21## IDE
22.idea/
23.vscode/
24*.swp
25*.swo
26*~
27
28## Testing
29.pytest_cache/
30.coverage
31htmlcov/
32
33## Logs
34*.log
35
36## OS
37.DS_Store
38Thumbs.db

Requirements File

Create requirements.txt:

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

Or generate from current environment:

$pip freeze > requirements.txt

IDE Configuration

VS Code

Create .vscode/settings.json:

1{
2 "python.defaultInterpreterPath": "${workspaceFolder}/venv/bin/python",
3 "python.envFile": "${workspaceFolder}/.env",
4 "python.testing.pytestEnabled": true,
5 "python.testing.pytestArgs": ["tests"],
6 "editor.formatOnSave": true,
7 "python.formatting.provider": "black"
8}

Create .vscode/launch.json for debugging:

1{
2 "version": "0.2.0",
3 "configurations": [
4 {
5 "name": "Run Agent",
6 "type": "python",
7 "request": "launch",
8 "program": "${workspaceFolder}/main.py",
9 "console": "integratedTerminal",
10 "envFile": "${workspaceFolder}/.env"
11 },
12 {
13 "name": "Run Current File",
14 "type": "python",
15 "request": "launch",
16 "program": "${file}",
17 "console": "integratedTerminal",
18 "envFile": "${workspaceFolder}/.env"
19 },
20 {
21 "name": "Test Agent with swaig-test",
22 "type": "python",
23 "request": "launch",
24 "module": "signalwire_agents.cli.test_swaig",
25 "args": ["${file}", "--dump-swml"],
26 "console": "integratedTerminal"
27 }
28 ]
29}

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

1#!/usr/bin/env python3
2## customer_service.py - Customer service agent
3"""
4Customer Service Agent
5
6A production-ready customer service agent template.
7"""
8
9import os
10from signalwire_agents import AgentBase, SwaigFunctionResult
11
12
13class CustomerServiceAgent(AgentBase):
14 """Customer service voice AI agent."""
15
16 def __init__(self):
17 super().__init__(
18 name="customer-service",
19 route="/",
20 host="0.0.0.0",
21 port=int(os.getenv("AGENT_PORT", "3000"))
22 )
23
24 self._configure_voice()
25 self._configure_prompts()
26 self._configure_functions()
27
28 def _configure_voice(self):
29 """Set up voice and language."""
30 self.add_language("English", "en-US", "rime.spore")
31
32 self.set_params({
33 "end_of_speech_timeout": 500,
34 "attention_timeout": 15000,
35 })
36
37 self.add_hints([
38 "account",
39 "billing",
40 "support",
41 "representative"
42 ])
43
44 def _configure_prompts(self):
45 """Set up AI prompts."""
46 self.prompt_add_section(
47 "Role",
48 "You are a helpful customer service representative for Acme Corp. "
49 "Help customers with their questions about accounts, billing, and products."
50 )
51
52 self.prompt_add_section(
53 "Guidelines",
54 body="Follow these guidelines:",
55 bullets=[
56 "Be professional and courteous",
57 "Ask clarifying questions when needed",
58 "Offer to transfer to a human if you cannot help",
59 "Keep responses concise"
60 ]
61 )
62
63 def _configure_functions(self):
64 """Register SWAIG functions."""
65 self.define_tool(
66 name="lookup_account",
67 description="Look up a customer account by phone number or account ID",
68 parameters={
69 "type": "object",
70 "properties": {
71 "identifier": {
72 "type": "string",
73 "description": "Phone number or account ID"
74 }
75 },
76 "required": ["identifier"]
77 },
78 handler=self.lookup_account
79 )
80
81 self.define_tool(
82 name="transfer_to_human",
83 description="Transfer the call to a human representative",
84 parameters={"type": "object", "properties": {}},
85 handler=self.transfer_to_human
86 )
87
88 def lookup_account(self, args, raw_data):
89 """Look up account information."""
90 identifier = args.get("identifier", "")
91
92 # In production, query your database here
93 return SwaigFunctionResult(
94 f"Found account for {identifier}: Status is Active, Balance is $0.00"
95 )
96
97 def transfer_to_human(self, args, raw_data):
98 """Transfer to human support."""
99 return SwaigFunctionResult(
100 "Transferring you to a human representative now."
101 ).connect("+15551234567", final=True, from_addr="+15559876543")
102
103
104## Allow running directly for testing
105if __name__ == "__main__":
106 agent = CustomerServiceAgent()
107 agent.run()

Testing Your Agent

1#!/usr/bin/env python3
2## test_agents.py - Tests for agents
3"""Tests for agents."""
4
5import pytest
6from agents.customer_service import CustomerServiceAgent
7
8
9class TestCustomerServiceAgent:
10 """Test customer service agent."""
11
12 def setup_method(self):
13 """Set up test fixtures."""
14 self.agent = CustomerServiceAgent()
15
16 def test_agent_name(self):
17 """Test agent has correct name."""
18 assert self.agent.name == "customer-service"
19
20 def test_lookup_account(self):
21 """Test account lookup function."""
22 result = self.agent.lookup_account(
23 {"identifier": "12345"},
24 {}
25 )
26 assert "Found account" in result
27
28 def test_has_functions(self):
29 """Test agent has expected functions."""
30 functions = self.agent._tool_registry.get_function_names()
31 assert "lookup_account" in functions
32 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.