El Problema que Inició Todo
El dueño de un negocio de plomería me llamó frustrado. Su empresa estaba perdiendo el 40% de las llamadas entrantes. Las emergencias iban al buzón de voz a las 2 a.m. Los clientes potenciales llamaban a la competencia. Tenía una recepcionista a tiempo completo durante horario laboral pero nada fuera de esa ventana. Estaba perdiendo dinero real, todas las noches.
Tres horas después de que hablamos, Captain Plumber tenía una recepcionista de voz con IA completamente desplegada construida sobre Vapi.ai. Ahora maneja más de 200 llamadas por mes, enruta emergencias a un plomero de guardia en menos de 90 segundos, captura datos de leads en un CRM automáticamente, y ha reducido las llamadas perdidas del 40% a menos del 2%.
Esta guía es el recorrido completo. Le mostraré la arquitectura, la configuración de Vapi.ai, el backend en TypeScript, el manejo de casos extremos y la configuración de despliegue en producción - todo lo que realmente utilicé. Al final, tendrá una recepcionista de voz con IA funcional que puede desplegar para cualquier negocio de servicios.
Por Qué la Voz con IA Es la Automatización de Mayor ROI para Negocios de Servicios
Antes de escribir una línea de código, vale la pena entender por qué la voz específicamente importa. La mayoría de los proyectos de automatización en los que trabajo están en la categoría de "agradable tener". Los agentes de voz son diferentes - operan en el punto exacto donde un negocio gana o pierde un cliente.
Cuando alguien llama a un plomero a las 11 p.m. con una tubería rota, no va a intentar un segundo número si nadie contesta. Está buscando de nuevo inmediatamente. El negocio que contesta se queda con el trabajo.
Así es como la voz con IA se compara con las alternativas a las que la mayoría de los pequeños negocios recurren por defecto:
| Opción | Disponibilidad | Costo/Mes | Captura de Leads | Personalización |
|---|---|---|---|---|
| Recepcionista humana | Horario laboral | $2.500-$4.000 | Manual | Alta |
| Buzón de voz | 24/7 | ~$0 | Frecuentemente abandonada | Ninguna |
| Call center | 24/7 | $800-$2.000 | Inconsistente | Baja |
| Agente de voz IA | 24/7 | $50-$200 | Automatizada | Configurable |
El agente de voz con IA no es solo más barato. Es estructuralmente mejor para la tarea: nunca tiene un mal día, siempre sigue el guion, captura los mismos campos cada vez y enruta correctamente a cualquier hora.
Para Captain Plumber, el cálculo de ROI fue simple. Un trabajo de emergencia capturado de $400-$800 cubre el costo mensual completo del sistema de IA.
Visión General de la Arquitectura
Una recepcionista de voz con IA construida sobre Vapi.ai tiene cuatro capas. Entender las cuatro antes de empezar a construir le ahorrará horas de debugging después.
Recepcionista de Voz con IA - Arquitectura del Sistema
Cómo fluye una llamada desde el llamante hasta su CRM
Capa Telefónica - Twilio
El llamante marca su número. Twilio enruta la llamada a Vapi mediante un webhook. Usted es dueño del número y controla las reglas de enrutamiento.
Procesamiento de Voz - Vapi.ai
Vapi maneja Speech-to-Text (Deepgram), orquestación del LLM (GPT-4 o Claude), y Text-to-Speech (ElevenLabs). Latencia menor a 1 segundo.
Lógica de Negocio - Su Backend
Una API en Next.js o Express recibe function calls de Vapi. Maneja guardado de leads, enrutamiento de emergencias, programación de citas y confirmaciones por SMS.
Capa de Datos - CRM / Base de Datos
Cada llamada produce datos estructurados: nombre del llamante, dirección, tipo de problema, urgencia, horario preferido de callback. Sincronizado con Airtable, HubSpot o Supabase.
El insight clave: Vapi maneja la complejidad que tomaría meses construir usted mismo - detección de turnos, manejo de interrupciones, optimización de latencia y failover de proveedores. Su trabajo es configurar el comportamiento del agente y escribir los endpoints de lógica de negocio.
Requisitos Previos y Stack
Esto es todo lo que necesita antes de empezar:
- Cuenta de Vapi.ai - el tier gratuito le da $10 en créditos, suficiente para probar a fondo
- Cuenta de Twilio - para un número telefónico dedicado (~$1/mes)
- API key de OpenAI - GPT-4o es el mejor balance entre latencia y calidad a partir de 2026
- Node.js 18+ con TypeScript
- Un backend desplegado - Vercel, Railway o Render funcionan bien
Tiempo estimado de configuración: 3-4 horas para su primer despliegue en producción.
Paso 1 - Configuración de Cuenta y Primer Asistente
Instalar el SDK
npm install @vapi-ai/server-sdk @vapi-ai/webCrear Su Primer Asistente vía la API
Prefiero crear asistentes programáticamente en lugar de hacerlo a través del dashboard. Esto hace que su configuración esté versionada y sea replicable entre entornos.
// lib/vapi/create-assistant.ts
import { VapiClient } from "@vapi-ai/server-sdk";
const client = new VapiClient({ token: process.env.VAPI_API_KEY! });
export async function createPlumbingReceptionist() {
const assistant = await client.assistants.create({
name: "Captain Plumber - AI Receptionist",
model: {
provider: "openai",
model: "gpt-4o",
temperature: 0.4,
systemPrompt: buildSystemPrompt(),
},
voice: {
provider: "elevenlabs",
voiceId: "21m00Tcm4TlvDq8ikWAM", // Rachel - warm, professional
stability: 0.5,
similarityBoost: 0.75,
},
firstMessage:
"Thanks for calling Captain Plumber. I'm here to help. Can I get your name and tell me what's going on with your plumbing?",
endCallMessage:
"You're all set. A member of our team will be in touch shortly. Have a good one.",
recordingEnabled: true,
transcriptPlan: {
enabled: true,
},
silenceTimeoutSeconds: 30,
maxDurationSeconds: 600,
serverUrl: process.env.VAPI_WEBHOOK_URL!, // your backend endpoint
});
console.log("Assistant created:", assistant.id);
return assistant;
}
function buildSystemPrompt(): string {
return `You are the AI receptionist for Captain Plumber, a licensed plumbing company serving the Greater Boston area.
## Your Role
You handle inbound calls to collect information, assess urgency, and route the caller appropriately. You are professional, warm, and efficient. You do not diagnose problems - you gather information and connect people to the right help.
## Information to Collect on Every Call
1. Caller full name
2. Service address (street, city)
3. Brief description of the issue
4. Best callback number (confirm if different from caller ID)
5. Whether the situation is urgent or can wait
## Emergency Classification
A call is an EMERGENCY if the caller mentions:
- Active water leak or flooding
- No hot water (in winter months)
- Sewage backup
- Gas smell (redirect to 911 first, then collect info)
- Pipe burst
## Conversation Rules
- Keep responses under 25 words when possible - this is a phone call
- Repeat back key details to confirm accuracy
- Never say "I don't know" - say "Let me make sure someone calls you about that"
- If a caller is distressed, acknowledge it before asking questions
- Confirm the callback number by reading it back digit by digit
## Routing Logic
- EMERGENCY: Tell them a plumber will call within 15 minutes. Trigger the emergency function immediately.
- NON-EMERGENCY: Offer available appointment slots for the next business day. Trigger the schedule function.
- INFORMATION ONLY: Answer basic questions from the FAQ, then offer to book an estimate.
## What You Never Do
- Quote prices over the phone
- Guarantee specific arrival times beyond the 15-minute emergency callback
- Collect payment information
- Make promises outside your defined scope`;
}¿Por Qué Temperatura 0.4?
Para una recepcionista, se busca consistencia por encima de creatividad. Una temperatura más baja (0.3-0.5) significa que el modelo se adhiere estrechamente a sus instrucciones. He visto agentes con temperatura 0.8+ empezar a improvisar respuestas que entran en conflicto con las reglas del negocio. Para contextos profesionales, manténgala baja.
Paso 2 - Configuración de Function Calling
El function calling es lo que separa una demo de juguete de un sistema de producción. Sin él, su agente solo puede hablar - no puede hacer nada. Las funciones permiten que el LLM ejecute acciones reales en sus sistemas durante la llamada.
Defina Sus Funciones en el Asistente
// lib/vapi/assistant-functions.ts
export const vapiTools = [
{
type: "function" as const,
function: {
name: "capture_lead",
description:
"Save the caller's information as a new lead in the CRM. Call this once you have collected name, address, issue, and callback number.",
parameters: {
type: "object",
properties: {
callerName: {
type: "string",
description: "Full name of the caller",
},
callbackPhone: {
type: "string",
description: "Phone number to call back, digits only",
},
serviceAddress: {
type: "string",
description: "Full service address including city",
},
issueDescription: {
type: "string",
description: "Brief description of the plumbing issue in the caller's own words",
},
isEmergency: {
type: "boolean",
description: "True if the caller described an active leak, flooding, or similar emergency",
},
preferredTime: {
type: "string",
description: "Preferred appointment window if not an emergency, e.g. 'tomorrow morning'",
},
},
required: ["callerName", "callbackPhone", "serviceAddress", "issueDescription", "isEmergency"],
},
},
},
{
type: "function" as const,
function: {
name: "route_emergency",
description:
"Immediately notify the on-call plumber via SMS and paging. Only call this when isEmergency is true.",
parameters: {
type: "object",
properties: {
callerName: { type: "string" },
callbackPhone: { type: "string" },
serviceAddress: { type: "string" },
issueDescription: { type: "string" },
},
required: ["callerName", "callbackPhone", "serviceAddress", "issueDescription"],
},
},
},
{
type: "function" as const,
function: {
name: "check_availability",
description:
"Check available appointment slots for the next 3 business days. Call this for non-emergency scheduling.",
parameters: {
type: "object",
properties: {
preferredDate: {
type: "string",
description: "Caller's preferred date if mentioned, e.g. 'tomorrow' or 'Thursday'",
},
timePreference: {
type: "string",
enum: ["morning", "afternoon", "any"],
description: "Caller's time preference",
},
},
required: [],
},
},
},
];Actualice el Asistente para Incluir Funciones
// Add tools to the assistant create call
const assistant = await client.assistants.create({
name: "Captain Plumber - AI Receptionist",
model: {
provider: "openai",
model: "gpt-4o",
temperature: 0.4,
systemPrompt: buildSystemPrompt(),
tools: vapiTools, // attach the functions here
},
// ... rest of config
});Paso 3 - Construcción del Handler de Webhooks
Cuando el LLM decide llamar a una función, Vapi envía una solicitud POST a su serverUrl. Aquí es donde vive su lógica de negocio.
// app/api/vapi/webhook/route.ts (Next.js App Router)
import { NextRequest, NextResponse } from "next/server";
import { captureLeadInCRM } from "@/lib/crm/capture-lead";
import { routeEmergency } from "@/lib/notifications/route-emergency";
import { getAvailableSlots } from "@/lib/calendar/availability";
interface VapiFunctionCallPayload {
message: {
type: "function-call";
functionCall: {
name: string;
parameters: Record<string, unknown>;
};
call: {
id: string;
phoneNumber?: { number: string };
};
};
}
export async function POST(req: NextRequest): Promise<NextResponse> {
const body = (await req.json()) as VapiFunctionCallPayload;
const { message } = body;
if (message.type !== "function-call") {
return NextResponse.json({ result: "ok" });
}
const { name, parameters } = message.functionCall;
const callId = message.call.id;
try {
switch (name) {
case "capture_lead":
return await handleCaptureLead(parameters, callId);
case "route_emergency":
return await handleRouteEmergency(parameters, callId);
case "check_availability":
return await handleCheckAvailability(parameters);
default:
console.warn(`Unknown function called: ${name}`);
return NextResponse.json({
result: "Function not recognized. Apologize to the caller and offer a callback.",
});
}
} catch (error) {
console.error(`Webhook handler error for ${name}:`, error);
// Return a graceful message - the agent will speak this to the caller
return NextResponse.json({
result: "There was a technical issue on our end. Please tell the caller a team member will call them back within the hour.",
});
}
}
async function handleCaptureLead(
params: Record<string, unknown>,
callId: string
): Promise<NextResponse> {
const lead = {
callerName: params.callerName as string,
callbackPhone: params.callbackPhone as string,
serviceAddress: params.serviceAddress as string,
issueDescription: params.issueDescription as string,
isEmergency: params.isEmergency as boolean,
preferredTime: (params.preferredTime as string) ?? null,
callId,
source: "ai-receptionist",
createdAt: new Date().toISOString(),
};
const savedLead = await captureLeadInCRM(lead);
return NextResponse.json({
result: `Lead saved successfully. Reference number ${savedLead.id}. Tell the caller their information has been recorded and confirm the callback number you collected.`,
});
}
async function handleRouteEmergency(
params: Record<string, unknown>,
callId: string
): Promise<NextResponse> {
await routeEmergency({
callerName: params.callerName as string,
callbackPhone: params.callbackPhone as string,
serviceAddress: params.serviceAddress as string,
issueDescription: params.issueDescription as string,
callId,
});
return NextResponse.json({
result: "Emergency alert sent. Tell the caller: an on-call plumber has been notified and will call them back within 15 minutes. Give them a confirmation.",
});
}
async function handleCheckAvailability(
params: Record<string, unknown>
): Promise<NextResponse> {
const slots = await getAvailableSlots({
preferredDate: params.preferredDate as string | undefined,
timePreference: (params.timePreference as "morning" | "afternoon" | "any") ?? "any",
});
if (slots.length === 0) {
return NextResponse.json({
result: "No slots available this week. Offer the caller the first available slot next week or take their info for a callback when slots open.",
});
}
const slotDescriptions = slots
.slice(0, 3)
.map((s) => s.label)
.join(", ");
return NextResponse.json({
result: `Available slots: ${slotDescriptions}. Read these options to the caller and ask which works best.`,
});
}El Patrón Clave: Devolver Instrucciones, No Solo Datos
Note que cada función devuelve un string result formulado como una instrucción para el agente. El LLM lee este resultado y lo usa para formular su respuesta hablada. Esto le da control preciso sobre lo que el agente dice después de cada acción sin necesidad de actualizar el system prompt constantemente.
Paso 4 - Lógica de Enrutamiento de Emergencias
Esta es la parte que tuvo el mayor impacto de negocio para Captain Plumber. Cuando alguien llama a las 2 a.m. con una tubería rota, el sistema necesita alertar a un humano inmediatamente.
// lib/notifications/route-emergency.ts
import twilio from "twilio";
const twilioClient = twilio(
process.env.TWILIO_ACCOUNT_SID!,
process.env.TWILIO_AUTH_TOKEN!
);
interface EmergencyAlert {
callerName: string;
callbackPhone: string;
serviceAddress: string;
issueDescription: string;
callId: string;
}
export async function routeEmergency(alert: EmergencyAlert): Promise<void> {
const oncallNumbers = getOnCallNumbers(); // rotate through your on-call schedule
const messageBody = buildEmergencyMessage(alert);
// SMS all on-call staff simultaneously
const smsPromises = oncallNumbers.map((number) =>
twilioClient.messages.create({
body: messageBody,
from: process.env.TWILIO_FROM_NUMBER!,
to: number,
})
);
// Also call the primary on-call number - SMS can be missed
const callPromise = twilioClient.calls.create({
twiml: buildEmergencyTwiML(alert),
from: process.env.TWILIO_FROM_NUMBER!,
to: oncallNumbers[0],
});
await Promise.all([...smsPromises, callPromise]);
// Log for audit trail
console.log(`Emergency routed for call ${alert.callId}`, {
caller: alert.callerName,
address: alert.serviceAddress,
notifiedNumbers: oncallNumbers.length,
});
}
function buildEmergencyMessage(alert: EmergencyAlert): string {
return [
"EMERGENCY CALL - Captain Plumber",
`Caller: ${alert.callerName}`,
`Phone: ${alert.callbackPhone}`,
`Address: ${alert.serviceAddress}`,
`Issue: ${alert.issueDescription}`,
"Please call back within 15 min.",
].join("\n");
}
function buildEmergencyTwiML(alert: EmergencyAlert): string {
return `<Response>
<Say voice="Polly.Joanna">
Emergency plumbing call. Caller ${alert.callerName} at ${alert.serviceAddress}
reports ${alert.issueDescription}.
Call back ${alert.callbackPhone} within 15 minutes.
Press 1 to acknowledge.
</Say>
<Gather numDigits="1" />
</Response>`;
}
function getOnCallNumbers(): string[] {
// In production, pull this from your scheduling system or a simple env var
const raw = process.env.ONCALL_NUMBERS ?? "";
return raw.split(",").filter(Boolean);
}Paso 5 - Integración con CRM
Cada llamada debería producir un registro limpio. Usé Airtable para Captain Plumber porque el dueño ya lo estaba usando para el seguimiento de trabajos, pero el patrón funciona con cualquier CRM.
// lib/crm/capture-lead.ts
import Airtable from "airtable";
const base = new Airtable({ apiKey: process.env.AIRTABLE_API_KEY }).base(
process.env.AIRTABLE_BASE_ID!
);
interface LeadRecord {
callerName: string;
callbackPhone: string;
serviceAddress: string;
issueDescription: string;
isEmergency: boolean;
preferredTime: string | null;
callId: string;
source: string;
createdAt: string;
}
interface SavedLead {
id: string;
}
export async function captureLeadInCRM(lead: LeadRecord): Promise<SavedLead> {
const record = await base("Leads").create({
Name: lead.callerName,
Phone: lead.callbackPhone,
Address: lead.serviceAddress,
Issue: lead.issueDescription,
Emergency: lead.isEmergency,
"Preferred Time": lead.preferredTime ?? "Flexible",
"Call ID": lead.callId,
Source: lead.source,
Status: lead.isEmergency ? "Emergency - Needs Immediate Follow-up" : "New Lead",
"Created At": lead.createdAt,
});
return { id: record.id };
}Confirmación por SMS al Llamante
Después de guardar el lead, envíe al llamante una confirmación. Este único paso aumentó significativamente la satisfacción post-llamada de Captain Plumber - los llamantes sienten que trataron con un negocio real, no con un bot.
// lib/notifications/send-caller-confirmation.ts
import twilio from "twilio";
const client = twilio(
process.env.TWILIO_ACCOUNT_SID!,
process.env.TWILIO_AUTH_TOKEN!
);
export async function sendCallerConfirmation(
phone: string,
callerName: string,
isEmergency: boolean
): Promise<void> {
const message = isEmergency
? `Hi ${callerName}, this is Captain Plumber. An on-call plumber has been notified and will call you within 15 minutes. Save this number: ${process.env.BUSINESS_PHONE}.`
: `Hi ${callerName}, thanks for calling Captain Plumber. We've got your info and will be in touch to confirm your appointment. Questions? Call or text ${process.env.BUSINESS_PHONE}.`;
await client.messages.create({
body: message,
from: process.env.TWILIO_FROM_NUMBER!,
to: phone,
});
}Paso 6 - Manejo de Casos Extremos
Los agentes de voz en producción fallan de maneras predecibles. Estos son los casos extremos que manejé para Captain Plumber, y cómo.
El Llamante No Habla
Si alguien llama y se queda en silencio, el agente debería manejarlo con gracia en lugar de colgar abruptamente.
// Add to assistant config
silenceTimeoutSeconds: 10,
// Vapi will end the call after 10s of silence
// Set firstMessage to prompt immediately so callers know someone is there
firstMessage: "Thanks for calling Captain Plumber. Can you hear me okay? Take your time and let me know what you need.",El Llamante Dice que Quiere Hablar con un Humano
Agregue esto a su system prompt:
If the caller explicitly asks to speak with a person, say: "Absolutely, I understand. Let me make sure I get your details first so our team can call you back with everything they need. What's the best number to reach you?"
Do not argue. Collect the info, then tell them: "Perfect, someone from our team will call you back shortly. We appreciate your patience."
Esto retiene la captura de leads incluso cuando el llamante opta por no interactuar con la IA - lo cual es el objetivo real.
Audio Poco Claro o Distorsionado
If you cannot understand what the caller said, ask once for clarification: "Sorry, I missed that - could you repeat that for me?"
If you still cannot understand after a second attempt, say: "Let me make sure our team reaches out directly. Can I get your callback number?"
Degradar con gracia hacia la captura de leads. Siempre.
Llamada Se Corta o Desconecta
Configure Vapi para manejar eventos de fin de llamada y guardar cualquier dato parcial recolectado:
// app/api/vapi/call-end/route.ts
import { NextRequest, NextResponse } from "next/server";
interface CallEndPayload {
message: {
type: "end-of-call-report";
call: { id: string };
transcript: string;
summary: string;
};
}
export async function POST(req: NextRequest): Promise<NextResponse> {
const body = (await req.json()) as CallEndPayload;
if (body.message.type !== "end-of-call-report") {
return NextResponse.json({ ok: true });
}
const { call, transcript, summary } = body.message;
// Save the full transcript and summary to your database
// Even if the lead capture function was never triggered, you have the transcript
await saveCallRecord({
callId: call.id,
transcript,
summary,
endedAt: new Date().toISOString(),
});
return NextResponse.json({ ok: true });
}Vapi envía un reporte de fin de llamada con un transcript completo y un resumen generado. Para Captain Plumber, esto ha sido invaluable - cuando una llamada se corta a mitad de camino, el dueño puede revisar el transcript y hacer seguimiento manualmente.
Paso 7 - Conectar Su Número de Twilio
Una vez que su asistente está creado y su webhook está activo, conecte un número telefónico:
// lib/vapi/connect-phone-number.ts
import { VapiClient } from "@vapi-ai/server-sdk";
const client = new VapiClient({ token: process.env.VAPI_API_KEY! });
export async function connectPhoneNumber(
twilioPhoneNumber: string,
assistantId: string
): Promise<void> {
await client.phoneNumbers.create({
number: twilioPhoneNumber,
twilioAccountSid: process.env.TWILIO_ACCOUNT_SID!,
twilioAuthToken: process.env.TWILIO_AUTH_TOKEN!,
assistantId,
name: "Captain Plumber Main Line",
});
console.log(`Phone number ${twilioPhoneNumber} connected to assistant ${assistantId}`);
}Llame esto una sola vez durante la configuración. Después de esto, cada llamada a ese número de Twilio va directamente a su recepcionista con IA.
Enrutamiento de Respaldo
Para Captain Plumber, configuré un respaldo para que si Vapi no es accesible (raro pero posible), Twilio recurra a un mensaje grabado con el número celular del dueño. Siempre tenga un respaldo.
// Twilio Webhook fallback TwiML (paste in your Twilio console)
<Response>
<Say>
Thanks for calling Captain Plumber. We're experiencing a brief technical issue.
Please call back at 617-555-0100 or leave a message and we'll return your call shortly.
</Say>
<Record maxLength="60" />
</Response>Captain Plumber - Resultados del Mundo Real
Así es como se vieron 90 días de datos de producción para este despliegue específico.
Captain Plumber - Resultados de 90 Días
Antes y después del despliegue de la recepcionista de voz con IA
98%
Llamadas contestadas
Antes era 60%
217
Llamadas atendidas/mes
Promedio 7.2 por día
34
Enrutamientos de emergencia/mes
Respuesta promedio: 11 min
$127
Costo mensual de IA
vs $3.200 recepcionista
Calidad de leads
100% de los leads capturados con nombre, dirección, descripción del problema y número de callback - comparado con aproximadamente 60% de completitud en los mensajes de buzón de voz.
Ingresos fuera de horario
Los trabajos de emergencia reservados entre las 9 p.m. y las 7 a.m. aumentaron 3x en el primer mes. El dueño estima que esto solo cubre más de 15x el costo mensual de IA.
Un número que me sorprendió: el 34% de todas las llamadas llegaron fuera del horario laboral. Antes de la recepcionista con IA, esas llamadas iban al buzón de voz. La mayoría nunca fueron devueltas porque las personas ya habían encontrado otro plomero.
Lista de Verificación para Despliegue en Producción
Antes de salir en vivo, revise cada elemento de esta lista:
Lista de Verificación Pre-Lanzamiento
Desglose de Costos a Escala
Entender el modelo de costos es importante antes de presentar esto a un cliente o comprometerse con un despliegue.
| Componente | Costo Unitario | 200 llamadas/mes (3 min promedio) | 1.000 llamadas/mes |
|---|---|---|---|
| Plataforma Vapi | ~$0,05/min | $30 | $150 |
| OpenAI GPT-4o | ~$0,01/min equiv. | $6 | $30 |
| ElevenLabs TTS | ~$0,003/min | $1,80 | $9 |
| Twilio (llamadas) | $0,013/min | $7,80 | $39 |
| Twilio (SMS) | $0,0079/msg | $1,58 (200 SMS) | $7,90 |
| Total | ~$47/mes | ~$236/mes |
Con 200 llamadas por mes, el costo total es aproximadamente $50. Un solo trabajo de emergencia convertido a $500 da un retorno de 10x en el primer mes solo. A medida que el volumen aumenta, puede optimizar hacia GPT-4o-mini para interacciones más simples y reducir costos significativamente sin pérdida notable de calidad.
Consejos de Ingeniería de System Prompts
El system prompt es donde se originan la mayoría de los problemas de producción. Estos son los patrones que hicieron la mayor diferencia en Captain Plumber:
1. Defina el persona en las primeras dos oraciones. No entierre la descripción del rol. La primera prioridad del LLM debería ser clara inmediatamente.
2. Use listas enumeradas para reglas, no párrafos. Cuando el modelo tiene que seguir 10 reglas, las listas numeradas se procesan de manera más confiable que la prosa fluida.
3. Incluya anti-patrones explícitamente. La instrucción "no cotizar precios" es más confiable que esperar que el modelo lo infiera. Diga exactamente lo que no quiere.
4. Mantenga el prompt por debajo de 1.500 tokens. Los prompts más largos diluyen el enfoque. Si su prompt se está haciendo largo, usualmente significa que su agente está intentando hacer demasiadas cosas. Separe las responsabilidades.
5. Versione sus prompts. Almacénelos en su codebase como constantes, no en el dashboard de Vapi. Cuando algo se rompe a las 3 a.m., quiere un trail de git blame, no un misterio.
Lo Que Este Sistema No Hace
Quiero ser directo sobre las limitaciones para que establezca expectativas realistas con los clientes.
La recepcionista con IA destaca en la recolección estructurada de información y el enrutamiento. No negocia precios, no maneja programación compleja entre múltiples técnicos, ni maneja llamantes hostiles que escalan a lenguaje abusivo (debería agregar rutas de escalamiento explícitas para esto).
Para Captain Plumber, establecimos una barrera clara: cualquier llamante que pidiera un técnico específico por nombre, o que levantara la voz, era enrutado inmediatamente a un buzón de voz-a-texto con un callback humano garantizado en 2 horas. La IA no es la herramienta adecuada para cada situación - saber cuándo transferir es parte del buen diseño de sistemas.
Próximos Pasos
Si quiere ir más allá con esta configuración:
- Agregar análisis de sentimiento en el transcript de la llamada para marcar llamantes molestos para seguimiento prioritario
- Conectar Google Calendar en lugar de gestión de slots personalizada para disponibilidad en tiempo real
- Construir un dashboard usando la API de analytics de llamadas de Vapi para que el dueño del negocio pueda revisar resúmenes de llamadas sin escuchar grabaciones
- Hacer pruebas A/B de voces - ElevenLabs tiene docenas de opciones de voz, y la correcta para un consultorio médico es diferente a la correcta para un plomero
- Integrar con Google My Business para obtener contexto del llamante antes de que comience la conversación
La arquitectura en esta guía es la misma base que uso para todas estas extensiones. Una vez que el handler de webhooks y el patrón de function calling están en su lugar, agregar capacidades es sencillo.
Resumen
Construir una recepcionista de voz con IA usando Vapi.ai se reduce a cinco cosas bien hechas:
- Un system prompt claro y restrictivo que define el persona y las barreras
- Function calling que conecta al agente con sus sistemas reales
- Un handler de webhooks que devuelve instrucciones accionables, no solo datos
- Manejo adecuado de casos extremos para silencio, escalamientos y llamadas cortadas
- Una ruta de enrutamiento de emergencias que funciona a las 2 a.m. sin fallar
Para Captain Plumber, esto fue de concepto a producción en una sesión de trabajo. El negocio ahora contesta el 98% de las llamadas, captura cada lead con información completa y enruta emergencias en menos de 90 segundos - las 24 horas, por menos de $130 al mes.
La voz con IA no es el futuro del servicio al cliente para pequeños negocios. Está disponible ahora mismo, y los negocios que la despliegan hoy están construyendo una ventaja estructural sobre los competidores que aún envían llamadas al buzón de voz.
¿Necesita ayuda construyendo una recepcionista de voz con IA para su negocio? Construyo estos sistemas de principio a fin - desde la configuración de Vapi hasta la integración con CRM y el despliegue en producción. Comuníquese y podemos hablar sobre cuál es la configuración adecuada para su caso de uso.