A production-grade, multi-tenant AI agent gateway that provides reliable, billed, and observable access to multiple LLM providers with automatic fallback, retry logic, and comprehensive usage tracking.
VBDemo.1.1.mp4
VocalBridge Ops is a backend-for-frontend (BFF) system that allows multiple tenants to create and manage AI agents backed by different LLM providers (vendorA, vendorB). The system ensures:
- Multi-tenancy: Complete tenant isolation with API key authentication
- Reliability: Automatic retries, exponential backoff, and fallback to secondary providers
- Billing accuracy: Per-token usage tracking with idempotency to prevent double-billing
- Observability: Comprehensive usage analytics, cost tracking, and performance metrics
- Voice support: Optional voice bot channel with mock STT/TTS (bonus feature)
- Runtime: Node.js 20+ with TypeScript
- Framework: Fastify (high-performance HTTP server)
- Database: Prisma ORM with SQLite (easily swappable to PostgreSQL/MySQL)
- Testing: Vitest
- Logging: Pino
- Framework: React 19 with TypeScript
- Build: Vite
- Routing: React Router v7
- Styling: Inline styles with CSS variables
- State: React hooks + Context API
- Charts: Recharts
- Node.js 20.19+ or 22.12+
- npm 10+
- Git
# Clone repository
git clone https://github.com/design-smith/vocalbridge.git
cd StitchFinTH
# Install backend dependencies
cd backend
npm install
# Install frontend dependencies
cd ../frontend
npm installcd backend
# Run migrations
npm run db:migrate
# Seed initial data (2 tenants, 3 agents)
npm run seedImportant: The seed script will output API keys. Save these immediately - they cannot be retrieved later!
Expected output:
======================================================================
SEED COMPLETE - API KEYS
======================================================================
⚠️ IMPORTANT: Save these API keys! They cannot be retrieved later.
Tenant 1: Acme Corporation
Tenant ID: tnt_xxxxxxxxxxxxx
API Key: sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Tenant 2: GlobalTech Industries
Tenant ID: tnt_xxxxxxxxxxxxx
API Key: sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
No environment variables required - uses sensible defaults.
Optional .env (backend root):
PORT=3000
HOST=0.0.0.0
LOG_LEVEL=info
NODE_ENV=developmentCreate frontend/.env:
VITE_API_BASE_URL=http://localhost:3000cd backend
npm run devBackend runs on http://localhost:3000
cd frontend
npm run devFrontend runs on http://localhost:5173 (or 5174 if 5173 is busy)
- Navigate to http://localhost:5173
- Enter one of the API keys from the seed output
- Click "Login"
- View agents: Navigate to "Agents" page
- Create agent: Click "+ New Agent"
- Choose primary provider (vendorA or vendorB)
- Optional: Choose fallback provider
- Enter system prompt
- Select enabled tools
- Edit agent: Click agent card, modify, save
- Navigate to: "Try It" page
- Select agent: Dropdown at top
- Enter customer ID: For tracking (default: customer_123)
- Send messages: Type and press Enter or click send
- View metadata: Each assistant response shows:
- Provider used
- Tokens (in/out)
- Cost (USD)
- Latency (ms)
- Fallback indicator
- Check "Use idempotency key" checkbox
- Send a message
- Copy the idempotency key
- Send another message with same key
- Result: Second request returns cached response, no duplicate billing
- Start recording: Click microphone icon (when input is empty)
- Stop recording: Click stop button
- Result:
- Audio uploaded to backend
- Mock STT transcribes to text
- Agent processes message
- Mock TTS generates audio response
- Audio plays automatically
- Navigate to: "Usage" page
- Select date range: Default last 7 days
- View metrics:
- Total sessions
- Total tokens
- Total cost (USD)
- Provider breakdown chart
- Top agents by cost
- Monthly cost trends
- Recent usage events table
All endpoints require authentication via X-API-Key header.
curl -H "X-API-Key: sk_xxxxxxxxxxxxx" \
http://localhost:3000/v1/meResponse:
{
"id": "tnt_xxxxxxxxxxxxx",
"name": "Acme Corporation",
"createdAt": "2024-01-01T00:00:00.000Z"
}curl -H "X-API-Key: sk_xxxxxxxxxxxxx" \
http://localhost:3000/v1/agentscurl -X POST \
-H "X-API-Key: sk_xxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"name": "My Agent",
"primaryProvider": "vendorA",
"fallbackProvider": "vendorB",
"systemPrompt": "You are a helpful assistant.",
"enabledTools": ["Tool1", "Tool2"]
}' \
http://localhost:3000/v1/agentscurl -X PUT \
-H "X-API-Key: sk_xxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"name": "Updated Agent Name",
"systemPrompt": "New system prompt"
}' \
http://localhost:3000/v1/agents/agt_xxxxxxxxxxxxxcurl -X POST \
-H "X-API-Key: sk_xxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"agentId": "agt_xxxxxxxxxxxxx",
"customerId": "customer_123",
"metadata": {"source": "web"}
}' \
http://localhost:3000/v1/sessionscurl -X POST \
-H "X-API-Key: sk_xxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unique-key-12345" \
-d '{
"role": "user",
"content": "Hello, how can you help me?"
}' \
http://localhost:3000/v1/sessions/ses_xxxxxxxxxxxxx/messagesImportant: Same Idempotency-Key returns cached response without duplicate billing.
curl -H "X-API-Key: sk_xxxxxxxxxxxxx" \
http://localhost:3000/v1/sessions/ses_xxxxxxxxxxxxx/transcriptcurl -X POST \
-H "X-API-Key: sk_xxxxxxxxxxxxx" \
-H "Idempotency-Key: voice-unique-key-12345" \
-F "audio=@recording.webm" \
-F "audioDurationMs=5000" \
http://localhost:3000/v1/sessions/ses_xxxxxxxxxxxxx/voiceResponse includes:
transcriptText: Mocked transcriptionassistant: Agent responseaudio: Base64-encoded WAV audio (mock TTS)
curl -H "X-API-Key: sk_xxxxxxxxxxxxx" \
"http://localhost:3000/v1/usage/rollup?from=2024-01-01&to=2024-01-31"curl -H "X-API-Key: sk_xxxxxxxxxxxxx" \
"http://localhost:3000/v1/usage/events?from=2024-01-01&to=2024-01-31&limit=100"curl -H "X-API-Key: sk_xxxxxxxxxxxxx" \
http://localhost:3000/v1/usage/monthlycd backend
npm testnpm run test:uiThe test suite includes:
Unit Tests:
- ✅ Cost calculation (pricing.test.ts)
- ✅ Provider adapter normalization (providers.test.ts)
- ✅ Retry/backoff logic (reliability.test.ts)
- ✅ Utility functions (utils.test.ts)
Note: Idempotency is implemented and working in the codebase (see ConversationService.ts:82-150). Integration tests for this feature are planned for future development.
npm test -- pricing.test.tscd backend
# Run migrations
npm run db:migrate
# Push schema changes (dev)
npm run db:push
# Generate Prisma client
npm run db:generate
# Open Prisma Studio (DB GUI)
npm run db:studio
# Seed database
npm run seed
# Reset database (⚠️ deletes all data)
npx prisma migrate reset- Limitation: Single-file database, not suitable for production scale
- Tradeoff: Easy setup, zero external dependencies, perfect for demo/development
- Production: Switch to PostgreSQL or MySQL (Prisma makes this trivial)
- Limitation: vendorA and vendorB are mock implementations
- Tradeoff: Deterministic behavior, no API costs, fast tests
- Production: Implement real adapters for OpenAI, Anthropic, etc.
- Limitation: Speech-to-Text and Text-to-Speech are mock implementations
- Tradeoff: No external API dependencies, deterministic for testing
- Production: Integrate OpenAI Whisper, Google Cloud Speech, AWS Polly, etc.
- Limitation: Simple bearer token auth, no JWT/OAuth
- Tradeoff: Sufficient for B2B backend API gateway
- Production: Consider OAuth2, JWT refresh tokens, or API key rotation
- Limitation: No per-tenant rate limiting
- Production: Add Redis-based rate limiting with
@fastify/rate-limit
- Limitation: Only idempotency caching (in-memory)
- Production: Add Redis for distributed caching, agent config caching
StitchFinTH/
├── backend/
│ ├── src/
│ │ ├── api/routes/ # REST API endpoints
│ │ ├── billing/ # Cost calculation & pricing
│ │ ├── channels/voice/ # Voice bot channel (STT/TTS)
│ │ ├── db/ # Database & repositories
│ │ ├── middleware/ # Auth, request context
│ │ ├── providers/ # LLM provider adapters
│ │ ├── reliability/ # Retry, backoff, fallback
│ │ ├── services/ # Business logic
│ │ ├── utils/ # Helpers, errors, IDs
│ │ ├── seed/ # Database seeding
│ │ └── server.ts # Main entry point
│ ├── tests/unit/ # Unit tests
│ ├── prisma/
│ │ └── schema.prisma # Database schema
│ └── package.json
├── frontend/
│ ├── src/
│ │ ├── api/ # API client & types
│ │ ├── auth/ # Authentication context
│ │ ├── components/ # Reusable UI components
│ │ ├── pages/ # Page components
│ │ ├── utils/ # Formatters, helpers
│ │ └── main.tsx # Entry point
│ └── package.json
├── README.md # This file
├── ARCHITECTURE.md # System architecture (see below)
└── SUBMISSION_NOTES.md # Design decisions & tradeoffs
- Integration Tests: E2E tests for idempotency (message + voice), session management, voice flow
- Real Providers: Implement OpenAI, Anthropic Claude adapters
- PostgreSQL: Migrate from SQLite to PostgreSQL for production
- Rate Limiting: Add per-tenant rate limiting with Redis
- Agent Versioning: Track agent config changes over time
- Streaming: Support streaming responses (SSE or WebSockets)
- Advanced Analytics: Usage prediction, cost optimization suggestions
- API Key Management: Rotation, expiration, scoping
- Webhook Support: Notify external systems of events
- Docker: Containerize for easy deployment
# Kill process on port 3000 (backend)
npx kill-port 3000
# Kill process on port 5173 (frontend)
npx kill-port 5173# Stop all backend processes
cd backend
rm prisma/dev.db
npm run db:migrate
npm run seed- Ensure you copied the full key from seed output
- Keys start with
sk_ - Check you're using the correct tenant's key
- Try re-seeding:
npx prisma migrate resetthennpm run seed
- Check
frontend/.envhasVITE_API_BASE_URL=http://localhost:3000 - Restart frontend dev server after changing .env
- Verify backend is running on port 3000
- Check CORS is enabled (it is by default in development)
- Architecture: See ARCHITECTURE.md for detailed system design
- Submission Notes: See SUBMISSION_NOTES.md for design decisions
ISC
Built with ❤️ for VocalBridge Ops Take-Home Assignment



