A powerful FastAPI-based webhook server with automated tool code generation and registration for the Vapi (Voice API) platform. This system automatically discovers functions decorated with @vapi_function
, generates Python modules with CreateFunctionToolDto
objects, and registers them with Vapi for AI agent integration.
- Python 3.13+
- Vapi API key (get from Vapi Dashboard)
- Optional: Ngrok auth token for staging deployments
# Clone and install dependencies
git clone <repository-url>
cd auto-agent
pip install -e .
# Or using uv (recommended)
uv sync
# 1. Generate tool registration code
uv run codegen
# 2. Start your webhook server (choose one):
uv run python src/main.py --port 8000 # Local development
uv run python src/main.py --ngrok --port 8001 # With ngrok tunnel
# 3. Register tools with Vapi platform
uv run migrate --server-url http://localhost:8000 --api-key your_vapi_key
# Or with ngrok URL:
uv run migrate --server-url https://abc123.ngrok.io --api-key your_vapi_key
- Core Concepts
- Creating Vapi Functions
- Code Generation
- Tool Registration
- Server Deployment
- Deployment Options
- Advanced Usage
- API Reference
- Troubleshooting
- Function Discovery: Automatically scans your codebase for
@vapi_function
decorated functions - Code Generation: Creates Python modules with
CreateFunctionToolDto
objects ready for Vapi registration - Tool Registration: Registers your functions as callable tools with the Vapi platform
- Webhook Server: Provides a FastAPI server to handle Vapi tool calls and execute your functions
βββββββββββββββββββ ββββββββββββββββββββ βββββββββββββββββββ
β Your Code β β Code Generator β β Vapi Platform β
β β β β β β
β @vapi_function βββββΆβ Discovery βββββΆβ Tool Registry β
β def my_tool() β β Generation β β β
β β β Registration β β β
βββββββββββββββββββ ββββββββββββββββββββ βββββββββββββββββββ
β β
βΌ βΌ
ββββββββββββββββββββ βββββββββββββββββββ
β Generated Module β β Webhook Server β
β β β β
β - TOOL_METADATA β β FastAPI App β
β - generate_tools β β /webhook/vapi β
β - register_tools β β β
ββββββββββββββββββββ βββββββββββββββββββ
Create functions in your codebase and decorate them with @vapi_function
:
# src/services/my_tools.py
from typing import Dict, Any, Optional
from src.services.vapi_schema import vapi_function
@vapi_function(
name="get_weather",
description="Get current weather information for a location"
)
def get_weather(location: str, units: str = "celsius") -> Dict[str, Any]:
"""Get weather information for a specific location.
Args:
location: City and country (e.g., "San Francisco, CA")
units: Temperature units - celsius or fahrenheit
Returns:
Weather information including temperature and conditions
"""
# Your implementation here
return {
"location": location,
"temperature": 22,
"units": units,
"condition": "sunny"
}
from typing import List, Dict, Any, Optional
from datetime import datetime
@vapi_function(
name="schedule_meeting",
description="Schedule a meeting with participants"
)
def schedule_meeting(
title: str,
participants: List[str],
start_time: datetime,
duration_minutes: int = 30,
location: Optional[str] = None
) -> Dict[str, Any]:
"""Schedule a meeting with multiple participants.
Args:
title: Meeting title
participants: List of participant email addresses
start_time: Meeting start time in ISO format
duration_minutes: Meeting duration in minutes (default: 30)
location: Optional meeting location or video link
Returns:
Meeting details with confirmation ID
"""
return {
"meeting_id": "mtg_123",
"title": title,
"participants": participants,
"scheduled_for": start_time.isoformat(),
"duration": duration_minutes,
"location": location
}
@vapi_function(
name="send_email",
description="Send an email to recipients"
)
async def send_email(
to: List[str],
subject: str,
body: str,
cc: Optional[List[str]] = None
) -> Dict[str, Any]:
"""Send an email asynchronously.
Args:
to: List of recipient email addresses
subject: Email subject line
body: Email body content
cc: Optional list of CC recipients
Returns:
Email delivery status and message ID
"""
# Async email sending logic
await send_email_async(to, subject, body, cc)
return {
"message_id": "msg_456",
"status": "sent",
"recipients": to
}
# β
Good - Clear and specific
@vapi_function(
name="calculate_mortgage",
description="Calculate monthly mortgage payment including principal, interest, taxes, and insurance"
)
# β Bad - Too vague
@vapi_function(
name="calculate",
description="Does calculations"
)
# β
Good - Explicit types
def book_flight(
departure: str,
destination: str,
date: datetime,
passengers: int = 1
) -> Dict[str, Any]:
# β Bad - Missing or vague types
def book_flight(departure, destination, date, passengers=1):
# β
Good - Detailed docstring
def transfer_money(amount: float, from_account: str, to_account: str) -> Dict[str, Any]:
"""Transfer money between bank accounts.
Args:
amount: Amount to transfer in USD (must be positive)
from_account: Source account number (format: XXXX-XXXX-XXXX)
to_account: Destination account number (format: XXXX-XXXX-XXXX)
Returns:
Transfer confirmation with transaction ID and timestamp
Raises:
ValueError: If amount is negative or accounts are invalid
"""
# β
Good - Optional parameters with defaults
def search_products(
query: str,
category: Optional[str] = None,
max_price: Optional[float] = None,
sort_by: str = "relevance"
) -> List[Dict[str, Any]]:
Generate Python code with tool definitions:
# Generate with defaults
uv run codegen
# Custom output directory
uv run codegen --output-dir ./my_tools
# Custom module name
uv run codegen --module-name my_vapi_tools
# Specify server URL for webhooks
uv run codegen --server-url https://api.mycompany.com
# JSON output for automation
uv run codegen --json
Option | Description | Default |
---|---|---|
--output-dir |
Directory for generated code | ./generated |
--scan-path |
Directory to scan for functions | ./src |
--server-url |
Base URL for webhook endpoints | None |
--module-name |
Name of generated Python module | generated_tools |
--json |
Output results in JSON format | False |
The codegen
command creates a complete Python module:
# generated_tools.py
from typing import List, Dict, Any, Optional
from vapi import Vapi, CreateFunctionToolDto
from vapi.types.open_ai_function import OpenAiFunction
from vapi.types.server import Server
# Metadata extracted from your functions
TOOL_METADATA = [
{
"name": "get_weather",
"description": "Get current weather information for a location",
"parameters": {
"type": "object",
"properties": {
"location": {"type": "string", "description": "City and country"},
"units": {"type": "string", "description": "Temperature units"}
},
"required": ["location"]
},
"endpoint_path": "/api/v1/webhook/get-weather"
}
]
def generate_tools(server_base_url: str) -> List[CreateFunctionToolDto]:
"""Generate CreateFunctionToolDto objects for Vapi registration."""
# Implementation...
async def register_tools(api_key: str, server_base_url: str, timeout: int = 30) -> List[Dict[str, Any]]:
"""Register all tools with Vapi platform."""
# Implementation...
def get_tool_metadata() -> List[Dict[str, Any]]:
"""Get metadata about discovered functions."""
# Implementation...
# Import the generated module
from generated.generated_tools import generate_tools, register_tools, get_tool_metadata
# Generate tool DTOs
tools = generate_tools("https://my-webhook-server.com")
# Register with Vapi (async)
import asyncio
results = asyncio.run(register_tools(
api_key="your_vapi_api_key",
server_base_url="https://my-webhook-server.com"
))
# Check metadata
metadata = get_tool_metadata()
for tool in metadata:
print(f"Tool: {tool['name']} - {tool['description']}")
The migrate
command now follows a simple, explicit workflow:
# For local development
uv run python src/main.py --port 8000
# For public access with ngrok
uv run python src/main.py --ngrok --port 8001
# Or use your production server
# (already running at https://api.example.com)
# Register with the server URL from Step 1
uv run migrate --server-url http://localhost:8000 --api-key your_vapi_key
# Or with ngrok URL
uv run migrate --server-url https://abc123.ngrok.io --api-key your_vapi_key
# Or with production URL
uv run migrate --server-url https://api.example.com --api-key your_vapi_key
- β Explicit Control: You manage server lifecycle separately
- β Flexibility: Use any server (local, ngrok, production)
- β Simplicity: No complex environment modes or auto-server management
- β Reliability: Clear separation between server and registration
Option | Description | Default | Required |
---|---|---|---|
--server-url |
Server URL where webhook is accessible | - | β |
--api-key |
Vapi platform API key | - | β |
--output-dir |
Generated code directory | ./generated |
|
--timeout |
Registration timeout (seconds) | 30 |
|
--scan-path |
Function scan directory | ./src |
|
--json |
JSON output format | False |
Set these environment variables for easier usage:
# Required for tool registration
export VAPI_API_KEY="your_vapi_api_key_here"
# Optional: Default server URL for production
export SERVER_URL="https://your-webhook-server.com"
# Required for staging mode with ngrok
export NGROK_AUTHTOKEN="your_ngrok_auth_token"
The migrate command provides detailed feedback:
π Tool migration completed
π Environment: production
π Registered 3 tools successfully
π Server URL: https://api.example.com
π Tool IDs: tool_abc123, tool_def456, tool_ghi789
Registration Results (3):
Function | Status | Tool ID | Error
----------------------+-----------+--------------+------
get_weather | β
success | tool_abc123 |
schedule_meeting | β
success | tool_def456 |
send_email | β
success | tool_ghi789 |
If some tools fail to register:
β οΈ Tool migration partially completed
π Environment: production
β
Successful: 2 tools
β Failed: 1 tools
π Details:
β’ get_weather: success
β’ schedule_meeting: API rate limit exceeded
β’ send_email: success
Common registration errors:
- API rate limiting: Wait and retry
- Invalid webhook URL: Check server accessibility
- Duplicate tool names: Ensure unique function names
- API key issues: Verify your Vapi API key
# Start FastAPI server for development
uvicorn src.main:app --reload --host 0.0.0.0 --port 8000
# Or using the built-in CLI
uv run python src.main.py --reload
# With custom port
uv run python src.main.py --port 8080
# Production server (no auto-reload)
uvicorn src.main:app --host 0.0.0.0 --port 8000 --workers 4
FROM python:3.13-slim
WORKDIR /app
COPY . .
RUN pip install -e .
EXPOSE 8000
CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]
Heroku:
# Procfile
web: uvicorn src.main:app --host 0.0.0.0 --port $PORT
Railway/Render:
uvicorn src.main:app --host 0.0.0.0 --port $PORT
For development/staging with public webhooks:
# Install ngrok and get auth token from https://ngrok.com
export NGROK_AUTHTOKEN="your_token"
# Start server with ngrok tunnel
uv run python src.main.py --ngrok
# Custom port with ngrok
uv run python src.main.py --ngrok --port 8080
Once running, your server provides:
- Webhook Endpoint:
POST /webhook/vapi
- Handles Vapi tool calls - Health Check:
GET /health
- Server health status - API Documentation:
GET /docs
- Interactive API docs - OpenAPI Spec:
GET /openapi.json
- API specification
# Test health endpoint
curl http://localhost:8000/health
# Test webhook endpoint
curl -X POST http://localhost:8000/webhook/vapi \
-H "Content-Type: application/json" \
-d '{
"message": {
"toolCallList": [{
"id": "test_call",
"name": "get_weather",
"arguments": {"location": "San Francisco, CA"}
}]
}
}'
# Start server
uv run python src/main.py --port 8000
# Register tools
uv run migrate --server-url http://localhost:8000 --api-key your_key
Use cases:
- Local development and testing
- Function debugging
- Integration testing with localhost
# Start server with ngrok tunnel
export NGROK_AUTHTOKEN="your_ngrok_token"
uv run python src/main.py --ngrok --port 8001
# Copy the ngrok URL from output, then register
uv run migrate --server-url https://abc123.ngrok.io --api-key your_key
Use cases:
- Testing with real Vapi voice agents
- Sharing development environment
- Integration testing with public webhooks
# Your server is already running at https://api.example.com
# Just register the tools
uv run migrate --server-url https://api.example.com --api-key your_key
Use cases:
- Deploying to production
- Updating tool registrations
- CI/CD pipeline integration
By default, the system scans ./src
for functions. Customize the scan path:
# Scan specific directory
uv run codegen --scan-path ./my_functions
# Scan multiple directories (run multiple times)
uv run codegen --scan-path ./services
uv run codegen --scan-path ./utils --output-dir ./generated/utils
The migrate command handles both generation and registration:
# Automatically generates code and registers tools
uv run migrate --server-url https://api.example.com --api-key your_key
# Just generate code without registration
uv run codegen
# Get JSON output for scripting
uv run codegen --json > codegen_results.json
uv run migrate --server-url https://api.example.com --json > registration_results.json
Example JSON output:
{
"success": true,
"operation": "migrate",
"environment": "production",
"server_url": "https://api.example.com",
"total_tools": 3,
"successful_registrations": 3,
"failed_registrations": 0,
"tool_ids": ["tool_abc123", "tool_def456", "tool_ghi789"],
"registration_details": [...]
}
Create environment-specific configurations:
# .env.development
VAPI_API_KEY=dev_api_key
SERVER_URL=http://localhost:8000
# .env.staging
VAPI_API_KEY=staging_api_key
NGROK_AUTHTOKEN=staging_ngrok_token
# .env.production
VAPI_API_KEY=prod_api_key
SERVER_URL=https://api.mycompany.com
# GitHub Actions example
name: Deploy Vapi Tools
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.13'
- name: Install dependencies
run: pip install -e .
- name: Register tools
env:
VAPI_API_KEY: ${{ secrets.VAPI_API_KEY }}
run: |
uv run migrate --server-url ${{ secrets.WEBHOOK_SERVER_URL }} --json
Generate Python code with CreateFunctionToolDto objects.
uv run codegen [OPTIONS]
Options:
--output-dir PATH
: Output directory (default:./generated
)--scan-path PATH
: Directory to scan (default:./src
)--server-url URL
: Base webhook server URL--module-name NAME
: Generated module name (default:generated_tools
)--json
: Output JSON format--help
: Show help message
Generate code and register tools with Vapi platform.
uv run migrate --server-url URL --api-key KEY [OPTIONS]
Required Arguments:
--server-url URL
: Server URL where webhook is accessible--api-key KEY
: Vapi API key for registration
Options:
--output-dir PATH
: Generated code directory (default:./generated
)--timeout SECONDS
: Registration timeout (default: 30)--scan-path PATH
: Function scan directory (default:./src
)--json
: Output JSON format--help
: Show help message
POST /webhook/vapi
Content-Type: application/json
{
"message": {
"toolCallList": [
{
"id": "call_123",
"name": "function_name",
"arguments": { "param": "value" }
}
]
}
}
Response:
{
"results": [
{
"toolCallId": "call_123",
"result": { "status": "success", "data": "..." }
}
]
}
GET /health
Response:
{
"status": "healthy",
"timestamp": "2024-01-01T12:00:00Z",
"functions_registered": 3
}
from src.services.vapi_schema import vapi_function
@vapi_function(
name: Optional[str] = None, # Function name (defaults to Python function name)
description: Optional[str] = None, # Description (defaults to docstring)
max_tokens: Optional[int] = None, # Max tokens for execution
strict: bool = True, # Strict parameter validation
async_execution: bool = False, # Async execution flag
parameter_descriptions: Optional[Dict[str, str]] = None # Parameter description overrides
)
def your_function(...):
pass
# Problem: codegen finds 0 functions
uv run codegen
# β
Code generation completed
# π§ Generated 0 tool definitions
Solutions:
- Check that functions use
@vapi_function
decorator - Verify import path:
from src.services.vapi_schema import vapi_function
- Ensure scan path contains your function files
- Check for Python syntax errors in scanned files
# Problem: Functions found but import fails
ModuleNotFoundError: No module named 'src.services.vapi_schema'
Solutions:
- Fix import paths in your function files
- Ensure all dependencies are installed
- Check Python path and working directory
# β Problem: This creates required parameters
def my_function(param: str) -> str:
# β
Solution: Add default values for optional parameters
def my_function(param: str = None) -> str:
# Problem: Registration fails with auth error
β Tool migration failed: API authentication failed
Solutions:
- Verify
VAPI_API_KEY
environment variable - Check API key format and validity
- Ensure API key has tool registration permissions
# Problem: Development server won't start
β Tool migration failed: Port 8000 is already in use
Solutions:
- Use different port:
--port 8001
- Kill existing process on port 8000
- Check for other running servers
# Problem: Vapi can't reach webhook URLs
β Tool registration failed: Webhook URL accessibility
Solutions:
- Ensure server is publicly accessible
- Check firewall and security group settings
- Use ngrok for local development:
--staging
- Verify SSL certificate for HTTPS URLs
Enable debug output for troubleshooting:
# Add --debug flag (if implemented) or check logs
uv run migrate --dev --debug
# Or check server logs
uvicorn src.main:app --log-level debug
The system provides detailed logging:
import logging
logging.basicConfig(level=logging.DEBUG)
# Function discovery logs
logger = logging.getLogger('src.services.discovery')
# Registration logs
logger = logging.getLogger('src.cli.migrate')
Test functions before registration:
# Import your function directly
from src.services.my_tools import get_weather
# Test manually
result = get_weather("San Francisco, CA", "celsius")
print(result)
# Test with webhook server
curl -X POST http://localhost:8000/webhook/vapi \
-H "Content-Type: application/json" \
-d '{"message":{"toolCallList":[{"id":"test","name":"get_weather","arguments":{"location":"San Francisco, CA"}}]}}'
For large numbers of functions:
# Use timeout adjustment for many tools
uv run migrate --timeout 60 --server-url https://api.example.com
# Register in batches if needed (split scan paths)
uv run migrate --scan-path ./services/batch1
uv run migrate --scan-path ./services/batch2
# Command help
uv run codegen --help
uv run migrate --help
# Check server status
curl http://localhost:8000/health
# View API documentation
# Visit: http://localhost:8000/docs
- Create the function:
# src/services/weather.py
from typing import Dict, Any, Optional
from src.services.vapi_schema import vapi_function
@vapi_function(
name="get_weather_forecast",
description="Get detailed weather forecast for a location with multiple days"
)
def get_weather_forecast(
location: str,
days: int = 3,
units: str = "celsius",
include_hourly: bool = False
) -> Dict[str, Any]:
"""Get weather forecast for a location.
Args:
location: City and country (e.g., "Paris, France")
days: Number of forecast days (1-7)
units: Temperature units (celsius/fahrenheit)
include_hourly: Include hourly breakdown
Returns:
Detailed weather forecast data
"""
return {
"location": location,
"forecast_days": days,
"units": units,
"daily_forecasts": [
{
"date": f"2024-01-{i+1:02d}",
"high": 20 + i,
"low": 10 + i,
"condition": "sunny"
}
for i in range(days)
],
"hourly_included": include_hourly
}
- Generate and test locally:
# Start server
uv run python src/main.py --port 8000
# Register tools
uv run migrate --server-url http://localhost:8000 --api-key your_key
# Test the function
curl -X POST http://localhost:8000/webhook/vapi \
-H "Content-Type: application/json" \
-d '{
"message": {
"toolCallList": [{
"id": "weather_test",
"name": "get_weather_forecast",
"arguments": {
"location": "Tokyo, Japan",
"days": 5,
"include_hourly": true
}
}]
}
}'
- Deploy to production:
# Register with production server
uv run migrate --server-url https://your-production-webhook.com --api-key your_production_key
After registering your tools, use them in Vapi:
// Vapi dashboard configuration
{
"model": {
"provider": "openai",
"model": "gpt-4",
"functions": [
{
"name": "get_weather_forecast",
"description": "Get detailed weather forecast for a location",
// Tool automatically registered by migrate command
}
]
},
"voice": {
"provider": "11labs",
"voiceId": "..."
}
}
The voice agent can now call your function during conversations:
User: "What's the weather going to be like in Tokyo for the next 5 days?"
Agent: [Calls get_weather_forecast with location="Tokyo, Japan", days=5]
Agent: "Based on the forecast, Tokyo will have sunny weather for the next 5 days with temperatures ranging from 10Β°C to 25Β°C..."
- Install Python 3.13+ and dependencies
- Get Vapi API key from dashboard
- Create your first
@vapi_function
decorated function - Run
uv run codegen
to generate tool code - Start server with
uv run python src/main.py --port 8000
- Test with
uv run migrate --server-url http://localhost:8000 --api-key your_key
- Deploy webhook server to production
- Register tools with
uv run migrate --server-url <production-url> --api-key your_key
- Configure Vapi voice agent to use your tools
- Test end-to-end voice conversation