Introdução
Recentemente construí uma recepcionista IA 24/7 para uma empresa de encanamento que agora gerencia mais de 200 chamadas por mês. O dono passou de perder 40% das chamadas para capturar 98%. Tempo total de configuração: 3 horas.
Este guia ensina como construir agentes de voz com IA prontos para produção usando Vapi.ai, a plataforma que torna o desenvolvimento de voice AI realmente viável para equipes pequenas.
Ao final, você vai entender:
- A arquitetura STT → LLM → TTS
- Como configurar o Vapi com function calling adequado
- Padrões de produção para tratamento de erros
- Estratégias de otimização de custos
- Padrões reais de deploy que eu uso
Vamos começar.
A Arquitetura Voice AI
Antes de escrever código, entenda o pipeline:
Arquitetura do Pipeline Voice AI
Como a voice AI transforma fala em respostas inteligentes
O Fluxo:
- Speech-to-Text (STT): Vapi usa Deepgram ou Whisper para transcrever a fala
- Processamento LLM: OpenAI, Claude ou seu modelo customizado processa a intenção
- Function Calling: Seus endpoints de backend gerenciam a lógica de negócio
- Text-to-Speech (TTS): ElevenLabs ou OpenAI converte a resposta em voz
- Resposta: O chamador ouve a IA falar
Por que Vapi?
- Gerencia todas as partes difíceis (latência, interrupções, detecção de fala)
- Tempos de resposta inferiores a 1 segundo
- Function calling integrado
- API simples
- ~$0,05-0,10 por minuto (vs construir você mesmo: $50k+ em tempo de dev)
Pré-requisitos
Antes de começar, você vai precisar de:
- Conta Vapi (tier gratuito disponível)
- Conta Twilio (para números de telefone)
- Chave API OpenAI (ou Anthropic para Claude)
- Conhecimento básico de TypeScript/JavaScript
- Endpoint backend (pode ser Vercel, Railway ou seu servidor)
Tempo de dedicação: 2-4 horas para o primeiro agente.
Passo 1: Configurando Seu Primeiro Assistente
Criar o Assistente
// lib/vapi.ts
import { VapiClient } from "@vapi-ai/server-sdk";
const client = new VapiClient({ token: process.env.VAPI_API_KEY });
export async function createAssistant() {
const assistant = await client.assistants.create({
name: "Plumbing Receptionist",
model: {
provider: "openai",
model: "gpt-4",
temperature: 0.7,
systemPrompt: `You are a professional receptionist for a plumbing company.
Your job:
- Greet callers warmly
- Collect their name, address, and issue description
- Determine if it's an emergency
- Schedule appointments for non-emergencies
- Capture lead information
Rules:
- Be concise (under 30 seconds per response)
- Always confirm details back to the caller
- If emergency, collect contact number and say a plumber will call within 15 minutes
- Never say "I don't know", offer to have a human call back`,
},
voice: {
provider: "elevenlabs",
voiceId: "21m00Tcm4TlvDq8ikWAM", // Rachel
},
firstMessage: "Hello! Thanks for calling. I'm here to help with your plumbing needs. What can I assist you with today?",
});
return assistant;
}Configuração Principal Explicada
Seleção de Modelo:
gpt-4: Melhor qualidade, maior latência (~1,5s), $0,03/1K tokensgpt-3.5-turbo: Mais rápido (~0,8s), mais barato, bom para agentes simplesclaude-3-sonnet: Excelente para raciocínio complexo
Seleção de Voz:
- Vozes ElevenLabs soam mais naturais
- Vozes OpenAI são mais baratas mas mais robóticas
- Teste múltiplas vozes — importa mais do que você imagina
Dicas para o System Prompt:
- Seja específico sobre tom e comprimento
- Inclua exemplos do que NÃO dizer
- Adicione guardrails para casos extremos
- Mantenha abaixo de 2000 tokens
Passo 2: Adicionando Function Calling
Funções permitem que sua IA interaja com seus sistemas. Aqui está uma função de captura de leads:
// lib/vapi-functions.ts
interface LeadCaptureParams {
name: string;
phone: string;
address: string;
issue: string;
isEmergency: boolean;
}
export async function captureLead(params: LeadCaptureParams) {
// Save to your CRM (Airtable, HubSpot, etc.)
await saveToCRM({
...params,
source: "voice-agent",
timestamp: new Date().toISOString(),
});
// Send notification
if (params.isEmergency) {
await sendEmergencyAlert(params);
}
return {
success: true,
message: params.isEmergency
? "Emergency logged. A plumber will call you within 15 minutes."
: "Great! I've scheduled you for tomorrow between 2-4 PM. You'll get a confirmation text.",
};
}
// Vapi function schema
export const captureLeadSchema = {
name: "captureLead",
description: "Capture lead information and schedule appointment",
parameters: {
type: "object",
properties: {
name: {
type: "string",
description: "Caller's full name",
},
phone: {
type: "string",
description: "Caller's phone number",
},
address: {
type: "string",
description: "Service address",
},
issue: {
type: "string",
description: "Brief description of plumbing issue",
},
isEmergency: {
type: "boolean",
description: "True if water damage, no water, or sewage backup",
},
},
required: ["name", "phone", "issue", "isEmergency"],
},
};Function Calling do Agente de Voz
Como sua IA se conecta a sistemas reais de negócio
Registrar Funções com o Assistente
// Update your assistant creation
const assistant = await client.assistants.create({
// ... previous config
functions: [
{
name: "captureLead",
description: captureLeadSchema.description,
parameters: captureLeadSchema.parameters,
async: true, // Your endpoint handles this
},
],
});Criar o Endpoint de Webhook
// app/api/vapi/webhook/route.ts
import { NextRequest, NextResponse } from "next/server";
import { captureLead } from "@/lib/vapi-functions";
export async function POST(request: NextRequest) {
const body = await request.json();
// Vapi sends different message types
const { message } = body;
switch (message.type) {
case "function-call":
if (message.functionCall.name === "captureLead") {
const result = await captureLead(message.functionCall.parameters);
return NextResponse.json({
result,
});
}
break;
case "end-of-call-report":
// Log call analytics
console.log("Call ended:", message);
break;
case "speech-update":
// Real-time transcription (optional)
break;
}
return NextResponse.json({ status: "ok" });
}Passo 3: Conectando um Número de Telefone
Comprar um Número no Twilio
// lib/twilio.ts
import twilio from "twilio";
const client = twilio(
process.env.TWILIO_ACCOUNT_SID,
process.env.TWILIO_AUTH_TOKEN
);
export async function buyPhoneNumber(areaCode: string) {
const numbers = await client.availablePhoneNumbers("US")
.local
.list({ areaCode, limit: 1 });
if (numbers.length === 0) {
throw new Error("No numbers available in this area code");
}
const purchased = await client.incomingPhoneNumbers.create({
phoneNumber: numbers[0].phoneNumber,
voiceUrl: "https://your-domain.com/api/vapi/inbound", // We'll create this
});
return purchased.phoneNumber;
}Criar Handler de Chamadas Recebidas
// app/api/vapi/inbound/route.ts
import { NextRequest, NextResponse } from "next/server";
import twilio from "twilio";
const TWIML = `
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Connect>
<Stream url="wss://your-vapi-websocket-url">
<Parameter name="assistantId" value="your-assistant-id" />
</Stream>
</Connect>
</Response>
`;
export async function POST(request: NextRequest) {
// You can add call screening logic here
// E.g., check if business hours, spam detection, etc.
return new NextResponse(TWIML, {
headers: {
"Content-Type": "text/xml",
},
});
}Usando Número de Telefone do Vapi
Alternativamente, compre diretamente pelo Vapi:
// Easier but less control
const phoneNumber = await client.phoneNumbers.create({
provider: "twilio",
areaCode: "555",
assistantId: assistant.id,
});Opções de Configuração de Número de Telefone
Conecte seu agente de voz a linhas telefônicas
Passo 4: Padrões de Produção
Tratamento de Erros
Agentes de voz falham. Planeje para isso:
// lib/vapi-error-handler.ts
interface ErrorContext {
callId: string;
assistantId: string;
customerPhone: string;
error: Error;
transcript: string;
}
export async function handleVoiceError(context: ErrorContext) {
// 1. Log to monitoring
await logError(context);
// 2. Send fallback SMS
await sendSMS({
to: context.customerPhone,
body: "Sorry, we had a technical issue. A human will call you back within 10 minutes.",
});
// 3. Alert team
await sendSlackAlert({
channel: "#voice-agent-alerts",
text: `Voice agent failed for call ${context.callId}`,
error: context.error.message,
});
// 4. Create task for human follow-up
await createTask({
type: "voice-agent-fallback",
priority: "high",
customerPhone: context.customerPhone,
context: context.transcript,
});
}Rate Limiting e Prevenção de Abuso
// middleware.ts or API route
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(10, "1 m"), // 10 calls per minute per number
});
export async function middleware(request: NextRequest) {
const ip = request.ip ?? "127.0.0.1";
const { success, limit, remaining } = await ratelimit.limit(ip);
if (!success) {
return new NextResponse("Rate limit exceeded", { status: 429 });
}
return NextResponse.next();
}Otimização de Custos
Agentes de voz podem ficar caros rapidamente. Veja como mantenho os custos abaixo de $200/mês para mais de 200 chamadas:
| Estratégia | Economia | Implementação |
|---|---|---|
| Usar gpt-3.5 para consultas simples | 60% | Roteamento baseado em intenção |
| Cachear respostas comuns | 30% | Redis para FAQs |
| Encurtar prompts | 20% | Manter abaixo de 1000 tokens |
| Usar vozes OpenAI | 50% | vs ElevenLabs |
| Agrupar chamadas de função | 15% | Combinar operações |
// Smart routing example
const model = detectSimpleQuery(transcript)
? "gpt-3.5-turbo" // $0.0015/1K tokens
: "gpt-4"; // $0.03/1K tokensPasso 5: Testes e Monitoramento
Testes Automatizados
// tests/vapi.test.ts
import { test, expect } from "@playwright/test";
test("AI receptionist captures leads correctly", async ({ page }) => {
// Mock a call
const call = await startTestCall({
phoneNumber: "+15551234567",
scenario: "emergency_leak",
});
// Simulate conversation
await call.speak("Hi, I have a water leak in my basement");
const response = await call.waitForResponse();
expect(response).toContain("emergency");
expect(response).toContain("15 minutes");
// Verify function was called
const lead = await getLastLead();
expect(lead.isEmergency).toBe(true);
expect(lead.issue).toContain("leak");
});Dashboard de Monitoramento
Monitore estas métricas:
// Key metrics to log
interface CallMetrics {
callId: string;
duration: number; // seconds
cost: number; // USD
transcriptLength: number; // characters
functionsCalled: number;
errors: number;
customerSatisfaction?: number; // Post-call survey
resolved: boolean; // Did it solve the problem?
}
// Alert on:
// - >5% error rate
// - >$0.50 average cost per call
// - >10s average latency
// - <80% resolution rateExemplo Real: Resultados do Negócio de Encanamento
A Configuração:
- 1 número de telefone (código de área local)
- GPT-4 para questões complexas, GPT-3.5 para simples
- 3 funções: captureLead, scheduleAppointment, checkAvailability
- Fallback para humano após 3 erros
Resultados Após 3 Meses:
- 247 chamadas gerenciadas
- 94% capturadas (vs 60% antes)
- Duração média de chamada: 2m 15s
- Custo por chamada: $0,12
- 23 chamadas de emergência fora do horário capturadas
- Tempo economizado do dono: 15 horas/semana
O Que Funcionou:
- System prompt específico com exemplos
- Função de detecção de emergência
- Fallback por SMS para chamadas com falha
- Revisão diária de transcrições nas primeiras 2 semanas
O Que Não Funcionou:
- Inicialmente muito verboso (corrigido com instrução "seja conciso")
- Não capturava email (adicionado à função)
- Confundia endereços (adicionado passo de confirmação)
Próximos Passos
Agora que você entende o básico:
- Construa seu primeiro agente: Comece simples, adicione funções gradualmente
- Teste com chamadas reais: Peça a amigos/família para ligar e dar feedback
- Monitore e itere: Revise transcrições diariamente no início
- Escale gradualmente: Adicione funcionalidades conforme entende os modos de falha
No próximo post, vou cobrir padrões avançados:
- Suporte multi-idioma
- Fine-tuning de LLM customizado
- Integração com sistemas CRM
- Construindo agentes de voz que soam verdadeiramente humanos
FAQ
P: Quanto custa o Vapi? R: ~$0,05-0,15 por minuto dependendo da configuração. Uma chamada de 2 minutos custa cerca de $0,20.
P: Posso usar meu próprio número de telefone? R: Sim, via integração com Twilio. Ou compre diretamente pelo Vapi.
P: E se a IA não entender? R: Construa lógica de fallback. Após 2 mal-entendidos, ofereça transferir ou deixar uma mensagem.
P: É compatível com HIPAA? R: Vapi tem certificação SOC 2. Para HIPAA, você vai precisar de um BAA e salvaguardas adicionais.
P: Consegue lidar com sotaques? R: Sim, mas teste com sua base de clientes específica. Deepgram lida bem com a maioria dos sotaques.
Precisa de ajuda para implementar isso no seu negócio? Agende uma chamada gratuita de 30 minutos e vou ajudar você a projetar seu agente de voz.
Navegação da Série: ← Anterior: [Voice AI Architecture Overview] | Próximo: [Advanced Vapi Patterns: Functions & Workflows] →