Skip to main content

Best Practices

Best Practices

Summary: Guidelines and recommendations for building production-quality SignalWire voice AI agents.

Overview

CategoryFocus Area
Prompt DesignEffective prompts and POM structure
Function DesignWell-structured SWAIG functions
Error HandlingGraceful failure and recovery
SecurityAuthentication and data protection
PerformanceOptimization and efficiency
TestingValidation and quality assurance
MonitoringLogging and observability

Prompt Design

Use POM (Prompt Object Model)

Structure prompts with clear sections:

from signalwire_agents import AgentBase

agent = AgentBase(name="service", route="/service")

## Good: Structured sections
agent.prompt_add_section("Role", """
You are a customer service representative for Acme Corp.
""")

agent.prompt_add_section("Guidelines", body="Follow these rules:", bullets=[
"Be professional and courteous",
"Verify customer identity before account access",
"Never share sensitive information",
"Escalate complex issues to human agents"
])

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

Be Specific About Behavior

## Good: Specific instructions
agent.prompt_add_section("Response Style", """
- Keep responses under 3 sentences for simple questions
- Ask one question at a time
- Confirm understanding before taking action
- Use the customer's name when known
""")

Function Design

Clear Descriptions

from signalwire_agents import AgentBase
from signalwire_agents.core.function_result import SwaigFunctionResult

agent = AgentBase(name="accounts", route="/accounts")

## Good: Descriptive with parameter details
@agent.tool(
description="Look up customer account by account number. "
"Returns account status, balance, and last activity date."
)
def lookup_account(
account_number: str # The 8-digit account number
) -> SwaigFunctionResult:
pass

Return Actionable Information

@agent.tool(description="Check product availability")
def check_availability(product_id: str) -> SwaigFunctionResult:
stock = get_stock(product_id)

if stock > 10:
return SwaigFunctionResult(
f"Product {product_id} is in stock with {stock} units available. "
"The customer can place an order."
)
elif stock > 0:
return SwaigFunctionResult(
f"Product {product_id} has limited stock ({stock} units). "
"Suggest ordering soon."
)
else:
return SwaigFunctionResult(
f"Product {product_id} is out of stock. "
"Expected restock date: next week."
)

Error Handling

Graceful Degradation

@agent.tool(description="Look up order status")
def order_status(order_id: str) -> SwaigFunctionResult:
try:
order = fetch_order(order_id)
return SwaigFunctionResult(
f"Order {order_id}: Status is {order['status']}"
)
except OrderNotFoundError:
return SwaigFunctionResult(
f"Order {order_id} was not found. "
"Please verify the order number and try again."
)
except ServiceUnavailableError:
return SwaigFunctionResult(
"The order system is temporarily unavailable. "
"Please try again in a few minutes."
)

Security

Use Authentication

import os

agent = AgentBase(
name="secure",
route="/secure",
basic_auth=(
os.environ.get("AGENT_USER", "agent"),
os.environ.get("AGENT_PASSWORD")
)
)

Secure Function Tokens

## Require token authentication for sensitive functions
@agent.tool(
description="Process payment",
secure=True # Requires valid token
)
def process_payment(amount: float, card_last_four: str) -> SwaigFunctionResult:
pass

Environment Variables

VariablePurpose
SWML_BASIC_AUTH_USERBasic auth username
SWML_BASIC_AUTH_PASSWORDBasic auth password (required for production)
SWML_SSL_ENABLEDEnable HTTPS
SWML_SSL_CERT_PATHSSL certificate path
SWML_SSL_KEY_PATHSSL key path

Performance

Use DataMap for Simple API Calls

from signalwire_agents.core.data_map import DataMap

## Good: DataMap for simple lookups (no webhook roundtrip)
weather_map = DataMap(
name="get_weather",
description="Get weather for a city"
)
weather_map.add_parameter("city", "string", "City name", required=True)
weather_map.add_webhook(
url="https://api.weather.com/v1/current?q=${enc:args.city}",
method="GET",
output_map={"response": "Weather: ${response.temp}F, ${response.condition}"}
)
agent.add_data_map_tool(weather_map)

Use Fillers for Long Operations

@agent.tool(
description="Search database",
fillers=["Searching...", "This may take a moment..."]
)
def search_db(query: str) -> SwaigFunctionResult:
# Long-running search
results = search_database(query)
return SwaigFunctionResult(f"Found {len(results)} matching orders.")

Testing

Use swaig-test

## Validate agent configuration
swaig-test agent.py --dump-swml

## List available functions
swaig-test agent.py --list-tools

## Test specific function
swaig-test agent.py --exec lookup_account --account_number "12345678"

Monitoring

Use Structured Logging

import structlog

logger = structlog.get_logger()

@agent.tool(description="Process refund")
def process_refund(order_id: str, amount: float) -> SwaigFunctionResult:
logger.info(
"refund_requested",
order_id=order_id,
amount=amount
)
# Process refund
return SwaigFunctionResult(f"Refund of ${amount} processed.")

Production Readiness Checklist

  • Authentication configured (basic_auth or environment variables)
  • SSL/HTTPS enabled for production
  • Sensitive functions marked as secure
  • Error handling in all functions
  • Input validation for user-provided data
  • Logging configured (no sensitive data in logs)
  • All functions tested with swaig-test
  • Edge cases and error scenarios tested
  • Prompts reviewed for clarity and completeness
  • Transfer/escalation paths defined
  • Timeout values appropriate for use case
  • Summary handling for call analytics