Development Environment Setup

View as MarkdownOpen in Claude

The project layout varies by language. Here are the recommended structures:

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