VoiceLab is a React and FastAPI workspace for configuring, persisting, and testing voice agents powered by Pipecat ADK.
The interface is inspired by ElevenLabs and includes an agent builder, JSON configuration editor, test-call panel, run traces, reports, and provider settings. The runnable single-agent configuration is created in the UI and persisted in Postgres.
- React 19, TypeScript, Vite, Tailwind CSS
- Python 3.12, FastAPI
- SQLAlchemy async, Alembic, Postgres 16
- Google ADK with
pipecat-adk - Browser WebSocket and microphone APIs
voice-lab/
|-- client/ # React application
| |-- src/components/ # Builder, test-call, runs, reports, and UI components
| |-- src/views/ # Application views
| |-- src/lib/ # API client, WebSocket helpers, and shared types
| `-- index.html # Minimal Vite mount shell only
|-- server/
| |-- app/api/routes/ # FastAPI HTTP and WebSocket routes
| |-- app/models/ # SQLAlchemy models
| |-- app/repositories/ # Database access
| |-- app/services/ # Pipecat ADK runtime and session services
| |-- app/migrations/ # Alembic migrations
| `-- tests/ # Backend tests
`-- docker-compose.yml # Local Postgres service
Install:
- Python 3.12+
- uv
- Node.js 20+ and npm
- Docker with Docker Compose
- Git
From the repository root:
docker compose up -d postgres
docker compose psThe project database runs on host port 5433 to avoid conflicts with a local Postgres installation on 5432:
postgresql://voicelab:voicelab@localhost:5433/voicelab
cd server
cp .env.example .env
uv syncEdit server/.env and add provider credentials:
DATABASE_URL=postgresql+asyncpg://voicelab:voicelab@localhost:5433/voicelab
ADK_DATABASE_URL=postgresql+asyncpg://voicelab:voicelab@localhost:5433/voicelab
GEMINI_API_KEY=your-gemini-key
STT_PROVIDER=deepgram
STT_API_KEY=your-stt-provider-key
TTS_PROVIDER=deepgram
TTS_API_KEY=optional-deepgram-tts-key
CORS_ORIGINS=http://localhost:5173,http://localhost:5174DATABASE_URL stores VoiceLab agent configurations, run summaries, and trace events. ADK_DATABASE_URL stores Google ADK conversation sessions. Both can use the same Postgres database.
From server/:
uv run alembic upgrade headExpected output includes:
Running upgrade -> 0001_create_core_tables, create core tables
uv run uvicorn app.main:app --reload --host 0.0.0.0 --port 8000Backend URLs:
- API:
http://localhost:8000 - Health check:
http://localhost:8000/health - OpenAPI documentation:
http://localhost:8000/docs
Open a second terminal:
cd client
npm install
npm run devOpen the URL printed by Vite, normally http://localhost:5173.
If port 8000 is occupied, start FastAPI on another port and point Vite to it:
# Backend terminal
uv run uvicorn app.main:app --reload --port 8001
# Frontend terminal
VITE_API_TARGET=http://127.0.0.1:8001 npm run dev- Open the Builder.
- Create or select an agent.
- Configure the model, instruction, STT, TTS, voice, temperature, first message, and tools.
- Optionally edit the same configuration through the JSON tab.
- Select Save config to persist the agent in Postgres.
- Select Test call and grant browser microphone permission.
- Review session and trace information under Test Runs.
Backend:
cd server
uv run ruff check .
uv run pytest -qFrontend:
cd client
npm run buildApply migrations:
cd server
uv run alembic upgrade headCreate a migration after changing SQLAlchemy models:
uv run alembic revision --autogenerate -m "describe the change"Roll back one migration:
uv run alembic downgrade -1Stop Postgres:
docker compose downDelete the local database volume too:
docker compose down -vChange the host-side port in docker-compose.yml, then update DATABASE_URL and ADK_DATABASE_URL in server/.env to match.
Make sure the URLs use the Compose credentials:
user: voicelab
password: voicelab
database: voicelab
host port: 5433
If an old volume was created with different credentials, reset it:
docker compose down -v
docker compose up -d postgres
uv run alembic upgrade headConfirm FastAPI is healthy:
curl http://localhost:8000/healthIf FastAPI is using another port, set VITE_API_TARGET when starting the client and include the frontend origin in CORS_ORIGINS.