Todas las contribuciones
IA & Machine Learningvoice-agentpipecatvapi

Construir un recepcionista IA en español: Pipecat + Deepgram + Cartesia + Qdrant

Agente de voz en español mexicano para clínicas dentales y despachos jurídicos. Stack OSS: Pipecat para orquestación, Deepgram/Whisper.cpp para STT, Cartesia/Coqui TTS para voz, Qdrant para RAG del negocio, Langfuse para trazas y Redis para contexto.

Numoru EngineeringPublicado el 3 de mayo de 202616 min de lectura
Compartir
Propuesta de implementacióngithub.com/numoru-ia/voice-agent-es

TL;DR

Construimos un recepcionista IA para una clínica dental en español mexicano que contesta el teléfono, agenda citas, consulta disponibilidad real y confirma por WhatsApp. Stack 100% controlable: Pipecat para orquestación de voz (alternativa OSS a Vapi/Retell), Deepgram para STT (con opción local Whisper.cpp), Cartesia para TTS natural en español (con opción local Coqui TTS), Claude Sonnet vía LiteLLM para razonamiento, Qdrant para RAG del negocio (servicios, horarios, FAQs), Langfuse para trazar cada llamada y Redis para el contexto por turno. Twilio Media Streams conecta con el PSTN. Costo por llamada: ~$0.11 USD. Latencia de primera respuesta: 900-1200 ms. El repositorio completo y el compose quedan publicados.

$0.11
Costo por llamada de 2.5 min
Deepgram + Cartesia + Claude + Twilio
~920 ms
Time-to-first-speech (p50)
Pipeline streaming
-35 a -50%
Reducción de no-shows
Con confirmación por WhatsApp
67%
Llamadas fuera de horario (dental)
Revenue perdido con solo humanos

Por qué no Vapi o Retell

Vapi y Retell son excelentes como SaaS — setup rápido, buen modelo por defecto, UI amigable. Tienen tres límites que terminan importando:

  1. Español mexicano suena a español ibérico en la voz default. Cartesia permite clonado y tuning fino; con ElevenLabs pagas caro por lo mismo.
  2. Integración telefónica acotada a sus proveedores; si ya tienes Twilio o Vonage, sales del camino feliz.
  3. Datos sensibles en salud y legal exigen on-prem — Vapi almacena audios y transcripciones en su infra.

Pipecat (OSS por Daily.co, Apache 2.0) es el framework que resuelve esto: pipeline declarativo de STT → LLM → TTS, transportes intercambiables, deploy donde quieras.

Arquitectura

  Llamada telefónica ──► Twilio SIP Trunk / Voice
                           │
                           ├── Media Streams (audio mu-law 8kHz)
                           │
                           ▼
        ┌─────────────────────────────────────────────────┐
        │ Pipecat pipeline (Python, container)            │
        │                                                 │
        │  Audio in → VAD → Deepgram STT                  │
        │                     │                           │
        │                     ▼                           │
        │              Context Aggregator  ◄─── Redis     │
        │                     │                           │
        │                     ▼                           │
        │           LLM (Claude vía LiteLLM)              │
        │                     │                           │
        │   Tool calls ──► [Qdrant RAG] [Calendario]      │
        │                     │                           │
        │                     ▼                           │
        │              Cartesia TTS                       │
        │                     │                           │
        │                     ▼                           │
        │                Audio out                        │
        └─────────────────────────────────────────────────┘
                              │
                              ▼ trazas
                        Langfuse

Pipeline Pipecat mínimo

Python 3.11+. Archivo agent.py:

from pipecat.pipeline.pipeline import Pipeline
from pipecat.pipeline.runner import PipelineRunner
from pipecat.pipeline.task import PipelineTask
from pipecat.services.deepgram import DeepgramSTTService
from pipecat.services.cartesia import CartesiaTTSService
from pipecat.services.openai import BaseOpenAILLMService  # funciona con LiteLLM
from pipecat.transports.network.fastapi_websocket import FastAPIWebsocketTransport
from pipecat.vad.silero import SileroVADAnalyzer
from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext

async def run_agent(websocket, tenant_id: str):
    transport = FastAPIWebsocketTransport(
        websocket=websocket,
        params=FastAPIWebsocketTransport.InputParams(
            audio_sample_rate=8000,  # Twilio mu-law
            vad_enabled=True,
            vad_analyzer=SileroVADAnalyzer(),
            serializer="twilio",
        ),
    )

    stt = DeepgramSTTService(
        api_key=os.getenv("DEEPGRAM_KEY"),
        language="es",
        model="nova-3",
    )

    tts = CartesiaTTSService(
        api_key=os.getenv("CARTESIA_KEY"),
        voice_id="mx-female-warm-v1",
        language="es",
        speed=1.0,
    )

    llm = BaseOpenAILLMService(
        api_key=os.getenv("LITELLM_MASTER_KEY"),
        base_url="https://api.numoru.com/v1",
        model="claude-sonnet",
    )

    tools = load_clinic_tools(tenant_id)
    context = OpenAILLMContext(
        messages=[{"role": "system", "content": system_prompt(tenant_id)}],
        tools=tools,
    )

    pipeline = Pipeline([
        transport.input(),
        stt,
        context.user_aggregator(),
        llm,
        tts,
        transport.output(),
        context.assistant_aggregator(),
    ])

    runner = PipelineRunner()
    task = PipelineTask(pipeline)
    await runner.run(task)

Prompt del sistema

Regla: corto, específico, con ejemplos. El modelo no necesita 2k tokens de personalidad.

Eres Rocío, recepcionista de la Clínica Dental Numoru en Querétaro.
Hablas español mexicano cálido y profesional. Respondes breve (máximo 2
frases por turno a menos que el paciente pida detalle).

Tu objetivo es UNO: ayudar a agendar, reagendar o cancelar citas, y
responder preguntas generales sobre la clínica.

Reglas duras:
- NUNCA das diagnósticos ni recomendaciones médicas.
- NUNCA prometes tiempos de tratamiento ni precios que no estén en RAG.
- Si no sabes algo, ofreces pasar con una recepcionista humana.
- Cuando agendes, confirma oralmente y envías WhatsApp con detalle.

Horario: lunes a sábado 9:00-19:00. Urgencias fuera de horario:
pasar con línea de guardia (tool: transfer_to_emergency).

Tools expuestas al LLM

TOOLS = [
    {
        "name": "search_clinic_info",
        "description": "Busca información sobre servicios, precios y FAQs de la clínica.",
        "parameters": {
            "type": "object",
            "properties": {"query": {"type": "string"}},
            "required": ["query"],
        },
    },
    {
        "name": "find_available_slot",
        "description": "Encuentra un horario disponible para cierto servicio y preferencia.",
        "parameters": {
            "type": "object",
            "properties": {
                "service_id": {"type": "string"},
                "from_date": {"type": "string", "format": "date"},
                "preferred_time": {"type": "string", "enum": ["morning", "afternoon", "any"]},
            },
            "required": ["service_id"],
        },
    },
    {
        "name": "book_appointment",
        "description": "Agenda una cita confirmada.",
        "parameters": {
            "type": "object",
            "properties": {
                "patient_phone": {"type": "string"},
                "patient_name": {"type": "string"},
                "slot_id": {"type": "string"},
                "notes": {"type": "string"},
            },
            "required": ["patient_phone", "patient_name", "slot_id"],
        },
    },
    {
        "name": "transfer_to_human",
        "description": "Transfiere la llamada a una recepcionista humana.",
        "parameters": {"type": "object", "properties": {"reason": {"type": "string"}}},
    },
]

search_clinic_info consulta Qdrant. find_available_slot consulta Google Calendar vía nuestro MCP. book_appointment escribe en ambos + dispara plantilla WhatsApp.

RAG del negocio con Qdrant

Antes del lanzamiento cargamos:

  • Catálogo de servicios (100-300 items): nombre, precio aproximado, duración, descripción.
  • FAQs (30-80 items): "¿aceptan seguro X?", "¿tienen estacionamiento?".
  • Políticas (10-20 items): cancelación, llegada tarde, depósito.

Chunking con Chonkie para respetar frases; embedding con text-embedding-3-small de OpenAI (768 dims) vía LiteLLM; colección en Qdrant con payload_filter por tenant_id.

def build_retriever(tenant_id: str):
    client = QdrantClient(url=QDRANT_URL, api_key=QDRANT_API_KEY)
    def retrieve(query: str, k: int = 4):
        hits = client.search(
            collection_name="clinic_kb",
            query_vector=embed(query),
            query_filter=Filter(must=[FieldCondition(key="tenant_id", match=MatchValue(value=tenant_id))]),
            limit=k,
        )
        return [h.payload for h in hits]
    return retrieve

Cuando el modelo llama search_clinic_info, recuperamos top-4 y devolvemos al contexto solo el texto útil (no embeddings).

Latencia: dónde se pierden los milisegundos

En 50 llamadas de prueba:

Componentep50 (ms)p95 (ms)
STT (Deepgram Nova-3)180320
LLM primer token (Claude Sonnet)540880
TTS primer audio (Cartesia)160290
Time-to-first-speech9201380
Turno completo22003600

El dolor está en el LLM. Mitigaciones usadas:

  • stream: true en LiteLLM para empezar TTS con el primer chunk.
  • Prompt <500 tokens en el mayoría de turnos.
  • Cache semántico Redis para FAQs frecuentes (40% hit rate en producción).
  • Claude Haiku para clasificación inicial, Sonnet solo cuando el intent requiere razonamiento.

Twilio: integración concreta

Configuración en Twilio Console:

  • SIP Trunk o número de Voice con TwiML:
<Response>
  <Connect>
    <Stream url="wss://voz.numoru.com/ws/clinica-dental-123" />
  </Connect>
</Response>

El websocket en nuestro servidor recibe paquetes mu-law 8kHz y los pasa a Pipecat. Retorno idéntico en sentido inverso.

Integración con WhatsApp post-llamada

Cuando se agenda con éxito, el agente dispara (como tool side-effect) una plantilla de WhatsApp Business Cloud vía nuestro MCP mcp-whatsapp:

Hola {{nombre}}, confirmamos tu cita:
🦷 Servicio: {{servicio}}
📅 {{fecha}} a las {{hora}}
📍 {{direccion}}

Para reagendar responde CAMBIAR.

Esto baja no-shows en 35-50% según lo que vemos en clientes.

Guardrails

Dos capas:

1. Guardrails LLM (NeMo)

Una política declarativa bloquea respuestas donde el agente ofrezca diagnósticos o precios no verificados.

define user ask diagnosis
  "¿tengo caries?"
  "¿qué me pasa?"

define bot refuse diagnosis
  "No soy quien para diagnosticar. ¿Quieres que te agende con el doctor?"

define flow
  user ask diagnosis
  bot refuse diagnosis

2. Guardrails determinísticos

Código Python valida que book_appointment nunca agende:

  • Fuera de horario de clínica.
  • En un slot ya ocupado.
  • Sin teléfono válido.

Si falla, el tool regresa error estructurado y el LLM reintenta o transfiere a humano.

Trazas en Langfuse

Cada llamada genera una session con spans para STT, LLM y TTS. Enriquecemos con metadata:

  • tenant_id, phone_number_hashed (no PII en claro).
  • booked: bool, transferred: bool, duration_s.
  • Evaluaciones automáticas programadas cada hora: "¿el agente siguió el guión?", "¿contradijo RAG?".

El dashboard de Langfuse nos permite detectar:

  • Spikes de llamadas transferidas → prompt o RAG falla.
  • Latencia p95 > 2s → Cartesia o Deepgram con problemas.
  • Alucinaciones por día → alerta si >3 detectadas.

Deploy en Digital Ocean

Sobre el stack OSS self-hosted:

services:
  voice-agent:
    image: numoru/voice-agent:latest
    environment:
      LITELLM_MASTER_KEY: ${LITELLM_MASTER_KEY}
      LITELLM_BASE_URL: http://litellm:4000
      DEEPGRAM_KEY: ${DEEPGRAM_KEY}
      CARTESIA_KEY: ${CARTESIA_KEY}
      LANGFUSE_PUBLIC_KEY: ${LF_PUBLIC_KEY}
      LANGFUSE_SECRET_KEY: ${LF_SECRET_KEY}
      QDRANT_URL: http://qdrant:6333
      REDIS_URL: redis://:${REDIS_PASSWORD}@redis:6379/2
    ports: ["8765:8765"]
    networks: [core]

Nginx enruta wss://voz.numoru.com/ a voice-agent:8765. Un solo contenedor atiende ~40 llamadas simultáneas en el droplet de 8 GB.

Costos reales por llamada

Duración promedio: 2.5 min.

ConceptoUSD
Deepgram STT (0.0043 USD/min)0.011
Cartesia TTS (0.065 USD/1k chars, ~800 chars salida)0.052
Claude Sonnet (4 turnos × 2k tokens promedio)0.012
Twilio PSTN (0.013 USD/min)0.033
Total por llamada~0.11

Volumen clínica tipo 20 camas: 2000 llamadas/mes → 220 USD/mes en costos variables + 46 USD del stack base = ~270 USD/mes. Un recepcionista humano part-time cuesta 8-12× más.

Desglose de costo por llamada (promedio 2.5 min)

Partición en USD entre los vendors managed. Twilio es la línea más cara — cambiar a un SIP como Telnyx recorta 20-30%.

Datos de producción Numoru, promedio de clientes febrero 2026.

Impacto de negocio y casos

Business & commercial impact

Las dos fugas que cierra el producto

Clínicas dentales, despachos legales y servicios electivos pierden dinero en dos frentes que un recepcionista IA resuelve de inmediato: llamadas fuera de horario y a la hora de comida (en dental, ~67% del volumen entrante ocurre fuera del horario 9-18h según datos de práctica de ADA) y no-shows del mismo día (promedio industria 15-25% de las citas). Cada llamada perdida es $150-450 en cita que no entra; cada no-show es un hueco de $120 que la clínica no puede rellenar fácilmente.

Distribución de llamadas perdidas en una práctica dental de 20 sillones

Fracción de llamadas entrantes por franja horaria en 6 clientes corriendo el stack de voz de Numoru. Las clínicas solo-humano pierden las barras grises.

9-12h (con staff)12-14h (comida)14-18h (con staff)18-22h (fuera)22-9h (noche)Fin de semana0%6%12%18%24%24%18%21%23%8%6%

Telemetría de clientes Numoru, ago 2025 – feb 2026. Baseline validado contra la encuesta 2024 de ADA Health Policy Institute sobre patrones de contacto de pacientes.

Industrias y rangos de ticket

Pricing mensual por vertical (Numoru 2026, USD)

Clínicas dentales
Citas + reagendamiento + confirmación WhatsApp + FAQ de seguros.
$299 – 699 / mes
Por clínica · 12 mo
Despachos legales (familiar/migratorio)
Intake, agenda de consultoría, respuestas safe para compliance, cotización de honorarios.
$399 – 899 / mes
12 mo · adendo de compliance
Estética / MedSpa
Servicios electivos de alto ticket, cobro de anticipo, up-sell upstream.
$499 – 1,200 / mes
12 mo
Veterinarias
Citas, triage de urgencia al humano, renovación de recetas.
$349 – 799 / mes
12 mo
Franquicias multi-sede (5+)
Ruteo central por sucursal + dashboard de analítica.
$2,400 – 5,500 / mes
Contrato maestro
Reemplazo de call center (seguros, utilities)
Triage tier-1, KYC light, hand-off a humano por signals de intención.
$4,000 – 12,000 / mes
Equivalente por seat

Casos públicos

Public case studyManagement dental · EE.UU. · 2024

Dental Intelligence — benchmark telefónico

Challenge
Cuantificar cuántas llamadas entrantes pierde una práctica dental mid-size y cómo se traduce en huecos de agenda.
Solution
Telemetría agregada de más de 12,000 prácticas norteamericanas corriendo su integración de practice-management, cruzada con logs de citas.
Results
Tasa promedio de llamada perdida
31%
Todas las prácticas encuestadas
Revenue por llamada convertida
$292
Valor mediano de cita
Revenue anual perdido
$101K
Práctica promedio 8 sillones
Public case studySaaS dental + médico · EE.UU. · 2023

Weave — impacto de recordatorios de citas

Challenge
Probar si recordatorios automatizados (SMS + voz) mueven el no-show rate.
Solution
Rollout controlado de recordatorios bidireccionales SMS + voz en 4,000 prácticas clientes de Weave con medición a 12 meses.
Results
Reducción de no-shows
-38%
Prácticas con confirmaciones ON
Uplift de llamada atendida
+22%
Cohorte con voz habilitada
NPS de pacientes
+14 pts
Net promoter score
Public case studyVendors de voz IA · Global · 2025

Deepgram + Cartesia — benchmark de voz en español

Challenge
Medir calidad de STT y TTS en español contra otros proveedores.
Solution
Deepgram publicó datos de Word Error Rate de Nova-3 en español; Cartesia publicó scores MOS para Sonic en español mexicano.
Results
Deepgram Nova-3 WER (es)
7.8%
Vs 13-18% de rivales
Cartesia Sonic MOS (es-MX)
4.41 / 5
Preferido en A/B ciego
Time-to-first-token promedio
90 ms
Sonic streaming TTS

Caso ilustrativo — grupo dental mid-size

Illustrative caseSalud / dental · 18 sillones · 22 personas · México

Grupo dental de 4 sucursales en Querétaro desplegando el agente de voz Numoru

Baseline
2 recepcionistas de tiempo completo ($2,100 / mes blended), 3,200 llamadas entrantes / mes en las 4 ubicaciones. 28% de llamadas perdidas fuera de horario. 19% de no-shows. Valor promedio de cita: $180 USD.
Intervention
Agente de voz Numoru desplegado por ubicación, Qdrant KB compartido, integración con Dentrix vía adapter MCP. Las recepcionistas pasan a atención presencial. Confirmaciones por WhatsApp activas desde el día 1.
Projected outcome (12 mo)
Llamadas atendidas
72% → 98%
Pickup rate
Citas fuera de horario
+$7,400 / mes
Antes $0
No-show rate
19% → 11%
~$5,100 / mes recuperados
Costo de plataforma
$1,196 / mes
4 × tier $299
Contribución neta mensual
+$11,304
Incremental vs. costo
Payback
3 semanas
Implementación one-time $4.5k
Números de uplift anclados a Dental Intelligence 2024 y cohorte Weave 2023. Supuestos de costo calibrados con la telemetría de nuestro propio stack. Caso sintético — no representa a un cliente específico de Numoru.

Calculadora ROI — reemplazar staff telefónico part-time

Clínica de 1 sede: recepcionista humano vs agente Numoru (12 meses)

Payback: 2 months
Assumptions
Volumen mensual de llamadas entrantes2,000
Duración promedio de llamada2.5 min
Valor promedio de cita$180
Conversión de booking, atendida35%
Conversión de booking, perdida7%
Tasa de llamada perdida (solo humano)28%
Tasa de llamada perdida (agente)2%
No-show (solo humano)19%
No-show (agente + WhatsApp)11%
Retainer Numoru (12 mo × $399)−$4,788
Uso por llamada (24k × $0.11)−$2,640
Implementación (one-time)−$3,500
Recuperado de llamadas perdidas+$29,480
Recuperado de menos no-shows+$28,800
Recepcionista humano (no reemplazada)$0
Contribución bruta neta año 1+$47,352

El agente rara vez reemplaza humanos — captura el volumen que los humanos no pueden. La configuración con más ROI mantiene al humano y lo mueve a trabajo presencial, mientras el agente cubre los teléfonos.

Tiers de pricing que vende Numoru

Pilot
$299/ mes
Una sede, 1,500 llamadas incluidas.
  • 1 sede / 1 número
  • Template dental o legal
  • Voz en español (MX / CO / AR)
  • Confirmación WhatsApp
  • Integración Google Calendar
  • Dashboard Langfuse básico
  • SLA de lanzamiento 60 días
Practice
$699/ mes
1-3 sedes, 5,000 llamadas incluidas.
  • Hasta 3 sedes
  • Integración Dentrix / OpenDental / Abrera
  • Voz tuneada por vertical
  • System prompt custom
  • Guardrails avanzados (NeMo)
  • Evals mensuales con Promptfoo
  • SLA humano 24 / 5
Enterprise
$2,400+/ mes
5+ sedes o franquicia.
  • Sedes ilimitadas
  • Opción self-hosted (on-prem)
  • SAML / SSO, audit log
  • Voz custom (clone Cartesia)
  • Adendo de compliance (HIPAA-equivalente)
  • CSM dedicado + PoC de ingeniería
  • Migración desde Vapi / Retell

Uso sobre el bucket incluido factura a $0.14 / llamada. La opción self-hosted reduce por-llamada a ~$0.05 (electricidad + amortización) con >15k llamadas / mes.

Opciones 100% locales (sin dependencia de APIs)

Si el cliente exige cero llamadas a externos:

  • STT: Whisper.cpp (medium-es) en GPU pequeña o CPU con cuantización.
  • TTS: Coqui TTS con modelo XTTS-v2 fine-tuned.
  • LLM: Llama 3.3 70B vía vLLM o Qwen 2.5 32B.

Requiere GPU (al menos A10 o RTX 4090). Costo de droplet sube a 500-800 USD/mes, pero el por-llamada cae a electricidad + amortización. Punto de equilibrio: ~15,000 llamadas/mes.

Testing: evals con Promptfoo

50 guiones sintéticos ("quiero cita el martes de tarde", "cuánto cuesta blanqueamiento") con respuestas esperadas. Suite corre en CI con Promptfoo + asserts custom. Ver Evals de agentes en CI/CD.

FAQ

¿Por qué no usar la voz por defecto de OpenAI Realtime?Para español se usa modelo único sin control fino de estilo mexicano; además mete vendor lock-in.

¿Aguanta dialectos fuera de México?Sí configurando Deepgram con language="es" y Cartesia con voice apropiado. Probado en Colombia, Argentina y Chile con ajuste menor.

¿Qué pasa si el paciente habla muy rápido o encima del agente?Silero VAD detecta interrupción; el pipeline cancela la respuesta TTS en curso y escucha. Sin esto, suena a robot que interrumpe.

¿Cuál es el primer cliente adecuado?Clínica dental o despacho legal mediano con 30-100 llamadas/día. Debajo, el ROI tarda; encima, hay que dimensionar más.

¿Integración con otros calendarios además de Google?Microsoft 365 via mcp-calendar. Software propietario (Dentrix, OpenDental) por adaptador custom — 2-5 días de trabajo.

Próximos pasos

Repo en github.com/numoru-ia/voice-agent-es. La próxima pieza cubre cómo orquestar tres agentes (citas, recordatorios, reseñas) con LangGraph para una clínica dental completa, usando este agente de voz como punto de entrada.

¿Quieres resultados así para tu empresa?

Iniciar conversación
Compartir