Ingeniería · Agentes de voz

Dos segundos para contestar

Un agente de voz que atiende a los clientes de un distribuidor industrial y consulta Odoo mientras habla

Equipo de ingeniería de Ardora AILectura de 14 minutos
1,75 srespuesta mediana (74 turnos)
70 msconsulta mediana a Odoo
53llamadas de prueba registradas
137pruebas automáticas

Un distribuidor de motores, variadores y reductores recibe cada día decenas de llamadas con preguntas que tienen respuesta en su ERP: ¿tenéis este motor en stock?, ¿cuándo llega mi pedido?, ¿cuánto cuesta el variador de 2,2 kW? Si nadie está libre para coger el teléfono, la llamada se pierde. Con uno de esos distribuidores, que estrena Odoo, hemos construido la prueba de concepto de un agente de voz que atiende esas llamadas y consulta el ERP en tiempo real mientras habla con el cliente.

Este artículo cuenta cómo está hecho: la arquitectura, las decisiones que tomamos desde el primer día y por qué, las cifras que hemos medido y los fallos que encontramos al probarlo, que fueron los que más nos enseñaron. Todo sale del plan del proyecto, del código y de los registros de las 53 llamadas de prueba guardadas. Los datos de la demo son ficticios y el cliente queda anónimo.

El agente en 85 segundos: referencias dictadas, pedidos seguros, la consola en tiempo real y la latencia medida. Las capturas son de la demo real con datos ficticios.

01El problema: la voz no perdona

En un chat, una respuesta que tarda cuatro segundos es aceptable. Al teléfono, cuatro segundos de silencio hacen que el cliente pregunte «¿hola?». Y aquí, además, la respuesta depende de una consulta al ERP. Las dificultades de verdad son cinco:

  • Latencia y turnos de palabra. El sistema tiene que decidir cuándo ha terminado de hablar el cliente, entender, consultar Odoo, redactar y empezar a sintetizar voz. Cada paso suma, y el cliente solo percibe el total.
  • Referencias dictadas. «E, ge, eme, guion, i, e, tres, barra, ciento doce, eme, guion, cuatro» tiene que acabar siendo EGM-IE3/112M-4. Y VT-B700-4T-2.2 (2,2 kW) no puede confundirse con VT-B700-4T-22 (22 kW).
  • Identificar al cliente sin que el modelo pueda suplantarlo. Si el agente responde sobre pedidos, solo puede hacerlo sobre los de la empresa que llama, aunque alguien intente convencerle de lo contrario.
  • Degradar sin inventar. Si el ERP no contesta, el agente tiene que decirlo y ofrecer una salida, no improvisar un plazo de entrega.
  • Idiomas. Castellano y gallego, y ninguno más.

02La arquitectura

La prueba de concepto se usa desde el navegador: una página con micrófono sustituye al teléfono. Por debajo hay cuatro piezas:

PiezaQué haceTecnología
Consola de la demoMicrófono y altavoz; muestra la conversación, cada consulta del modelo y la latencia por turnoHTML y JavaScript, sin framework
Relay de vozPuente entre el navegador y el modelo; ejecuta las herramientas que pide el modelo; mide y registra cada llamadaPython, FastAPI, WebSocket
Motor de vozEscucha, razona y habla en un solo modeloGemini Live (gemini-3.8-live) o ElevenLabs Agents
SkillsCinco operaciones tipadas de solo lectura contra el ERPPython, FastAPI, en contenedor
ERPOdoo 18 Community con datos ficticiosPostgres 16; todo en Podman

El recorrido de un turno es: el cliente habla → el audio llega al relay → Gemini Live decide si necesita datos → si los necesita, pide una herramienta (get_stock, get_order_status, get_my_orders, take_message o transfer_to_human) → el relay llama a la skill → la skill consulta Odoo → el resultado vuelve al modelo → el modelo contesta con voz.

En la propuesta inicial, Apache Airflow aparecía como orquestador. En la prueba lo dejamos fuera del turno de palabra: lanzar y esperar un DAG añade cientos de milisegundos o segundos justo donde el cliente los nota. Airflow sigue siendo la pieza adecuada para todo lo asíncrono (resúmenes, volcado a Odoo, recados, WhatsApp), pero no para contestar.

03Decisiones desde el primer día

El modelo no toca el ERP; toca herramientas

El modelo nunca habla con Odoo. Solo puede pedir cinco herramientas con argumentos tipados, y cada skill tiene una lista cerrada de modelos y campos de Odoo que puede leer, siempre con search_read. El usuario de servicio de Odoo no tiene ningún permiso de escritura (ver el apartado 07: al principio sí los tenía).

Cuatro estados y una frase lista para decir

Cada skill responde con un estado y una frase pensada para leerse en voz alta:

EstadoSignificaQué hace el agente
okDato encontradoLo dice tal cual, sin cambiar cifras
ambiguousVarias coincidencias o una parecida pero no idénticaLee las opciones y pide confirmación; nunca elige él
not_foundNo existe o no es de la empresa que llamaLo dice y ofrece repetir o dejar un recado
degradedOdoo no responde a tiempo«Ahora mismo no consigo acceder al sistema», y ofrece un recado

Cada llamada a Odoo tiene un límite de 2 segundos y cada skill un límite total de 2,5 segundos. Las skills nunca devuelven un error 500: cualquier excepción se convierte en degraded y queda registrada.

Un token firmado que el modelo no ve

Al descolgar, el relay identifica al llamante por su número y recibe un caller_token: el teléfono firmado con HMAC. Ese token viaja con cada consulta de pedidos, pero lo pone el relay, no el modelo, y el modelo nunca lo recibe. Aunque alguien le diga al agente «mi teléfono es el 600 000 002», no puede consultar pedidos de otra empresa. Si un mismo teléfono figura en dos empresas, no se identifica a nadie.

Medir desde el principio

Cada llamada deja un registro completo: lo dicho por las dos partes, cada herramienta con sus argumentos y la respuesta exacta que recibió el modelo, la latencia de cada turno, las interrupciones y los errores. Un script lo convierte en un informe legible con alertas: consultas repetidas, respuestas que no son ok, turnos lentos o silencios tras una consulta. Sin ese registro, la mitad de los fallos de este artículo no los habríamos encontrado.

Demo por navegador, no por teléfono

Lo decidimos para no esperar la verificación de un número de teléfono. Tiene un coste que declaramos siempre: sin tramo telefónico, el audio es de banda ancha y la latencia es más optimista que en una llamada real.

De ElevenLabs a Gemini Live, y vuelta

El plan inicial usaba ElevenLabs Agents. Con esa plataforma, las herramientas se ejecutan como webhooks llamados desde su nube, así que habría que exponer las skills a internet con un túnel. Cambiamos a Gemini Live por dos razones:

  • con la API Live es nuestro relay quien ejecuta las herramientas, de modo que las skills nunca salen de la máquina;
  • medimos la latencia en el servidor, sobre el audio real.

Después añadimos ElevenLabs como segundo motor (apartado 08), con herramientas «de cliente» que también acaban en el relay.

04Referencias dictadas

La normalización convierte lo que se dicta en algo comparable:

  • Letras y símbolos: «guion», «barra», «punto», «a de árbol».
  • Números en palabras: «ciento doce» es 112 y «setenta y cinco» es 75. «Uno dos tres», dictado cifra a cifra, es 123. Y en gallego, «dous», «dúas», «catro».
  • Punto decimal: 2.2 y 22 son productos distintos. Si la referencia solo coincide al quitar el punto, el agente pregunta y no da el dato.
  • Descripciones: «un motor de 4 kW» se busca por los términos del nombre del producto, y las cifras van unidas a su unidad, para que «4 kW» no case con «0,4 kW» ni con «4 polos».
  • Coincidencia aproximada con confirmación. El reconocimiento de voz oyó una vez «reductor planetario Brevini 90» en lugar de «brida 90» (Brevini es una marca real de reductores). Ahora, si no coincide todo, se ofrece el producto más parecido, pero solo si coinciden todas las cifras (tamaño, relación, potencia) y siempre pidiendo confirmación: «Lo más parecido es el RD-P90/I9. ¿Es ese?».

05Pedidos: lo que el agente sabe y lo que no

La primera versión de la herramienta de pedidos devolvía estado, fecha prevista y seguimiento. En la primera prueba el cliente preguntó cuándo se hizo el pedido, qué llevaba y por dónde iba el transporte, y el agente no tenía ninguno de esos datos. Además, consultó dos veces el mismo pedido esperando más información. La respuesta es ahora completa en una sola llamada: fecha de creación, líneas con cantidades pedidas, entregadas y pendientes, disponibilidad de cada línea, importes con y sin IVA, fecha de salida, transportista y seguimiento. El prompt le dice al modelo que pregunte una vez y conteste lo demás con esos datos.

El estado de un pedido tampoco es trivial. Un albarán «listo» en Odoo puede tener una línea sin stock. La skill mira el estado de cada movimiento y distingue entre preparado, parcialmente disponible, pendiente de stock y salido en parte.

06Latencia medida

Medimos en el relay, desde el último fragmento de audio con voz del cliente hasta el primer audio del agente. Esa cifra incluye los 700 ms que el modelo espera en silencio para saber que el cliente ha terminado, porque eso también lo oye el cliente. Con Gemini Live, en 21 llamadas reales:

MedidaMedianap95Muestra
Hasta que el agente empieza a responder1,75 s2,50 s74 turnos
Hasta que da el dato, cuando hay consulta2,47 s3,79 s25 turnos
Consulta del modelo a Odoo (ida y vuelta de la skill)70 ms284 ms46 consultas

Dos matices importantes:

  • Odoo corre en la misma máquina, así que los 70 ms son optimistas. Con el Odoo en la nube del cliente habrá que sumar la red.
  • No hay tramo telefónico.

Para que la espera no se note, el agente dice una muletilla breve («un momento, lo miro») justo antes de consultar, y solo entonces. En las primeras pruebas la decía sin llegar a consultar nada.

El silencio tras la consulta

En la segunda tanda de pruebas el agente dijo «un momento, lo miro», recibió el stock de Odoo en 56 ms y no volvió a hablar durante 47 segundos. No había ningún error: la respuesta había llegado al modelo. La documentación de Google se contradice sobre si en gemini-3.8-live las herramientas son bloqueantes por defecto. Si no lo son, una respuesta que llega mientras el modelo aún habla puede quedarse pendiente sin decirse. Lo arreglamos por partida doble:

  • declarando las herramientas como BLOCKING de forma explícita;
  • con un vigilante en el relay: si tras entregar un resultado el agente no habla en 6 segundos, se le recuerda que ya tiene el dato. Queda como aviso en el registro, para saber si vuelve a pasar.

07Fallos que nos hicieron cambiar cosas

Antes de la primera llamada pedimos a un segundo modelo, Claude Opus, que auditara el código buscando fallos, y después probamos con voz real. Estos son los que más pesaron:

CuándoQué pasóCausaCambio
Primera pruebaVT-B700-4T-2.2, que no existe, devolvía el stock del de 22 kWLa normalización borraba el punto decimalEl punto cuenta; una coincidencia sin punto pide confirmación
Primera pruebaEl usuario «de solo lectura» tenía 52 permisos de escritura (mensajes, adjuntos, claves de API)Su grupo heredaba del grupo de usuario interno de OdooGrupo propio sin herencias y auditoría automática que falla si aparece un permiso de escritura
Primera prueba«Treinta y uno» devolvía el pedido S00001Los números en palabras no se interpretabanConversión de números en palabras; ante la duda, pedir que se repita
Primera prueba«Motor 4 kW» devolvía motores de 0,37, 1,5 y 3 kWSe descartaban las palabras cortas, como el «4»Cifra y unidad como un solo término
Primera pruebaUn pedido con una línea sin stock se anunciaba «preparado para enviar»Solo se miraba el estado del albaránEstado por movimiento; «parcialmente disponible»
Primera prueba«No me consta ese pedido», sobre un pedido que sí era del clienteEl contexto inicial listaba solo los pedidos en curso y el modelo creyó que era la lista completaHerramienta get_my_orders y regla: consultar siempre antes de decir que algo no existe
Primera pruebaAnte un «vale, vale» mal transcrito como chino, el agente respondió en chinoDetección automática de idiomaReconocimiento limitado a es-ES y gl-ES, regla en el prompt y un vigilante que detecta otros alfabetos y lo corrige en el turno siguiente
Primera pruebaOdoo mostraba 1 unidad en stock y el agente decía «no hay stock»La unidad estaba reservada para otro pedidoSe distingue «en almacén» de «disponible» y se explica
Segunda pruebaSilencio de 47 s tras una consulta correctaHerramientas no bloqueantes por defectoBLOCKING explícito y vigilante de 6 s
Segunda pruebaEl agente de ElevenLabs no habría consultado OdooEl agente se creó sin los eventos client_tool_call ni vad_scoreActivados y comprobados al configurar

Cada uno tiene su test de regresión, salvo el último, que comprueba el propio script de configuración. Hoy pasan 74 tests de las skills (contra el Odoo de la demo) y 63 del relay.

08Dos motores con la misma lógica

ElevenLabs Agents entró como segundo motor para poder comparar. Comparte con Gemini Live el mismo prompt y las mismas herramientas: el agente se crea por API con un script que comprueba que el prompt guardado en ElevenLabs es idéntico. Las diferencias:

Gemini LiveElevenLabs Agents
AudioPasa por nuestro relayDirecto del navegador a ElevenLabs
HerramientasLas ejecuta el relay«De cliente»: ElevenLabs las pide a la página y la página al relay
Modelo de lenguajeEl propio modelo de vozgemini-3.5-flash, elegido por nosotros
Voces de EspañaPor instrucción en el promptVoces nativas de acento peninsular
LatenciaMedida en el servidorMedida en el navegador con su detector de voz

Con ElevenLabs solo tenemos 8 turnos medidos, con una mediana de 2,9 s, y la medida no se hace igual. Es pronto para compararlos. El coste también difiere: con Gemini Live estimamos unos 0,004 $ por llamada de prueba a partir de los tokens. ElevenLabs cobra 0,08 $ por minuto de plataforma, más el modelo de lenguaje y la telefonía.

09Lo que falta

Es una prueba de concepto, y estas son las piezas que separan la demo de una centralita en producción:

  • Telefonía. Recibir las llamadas de la centralita virtual del cliente por SIP o desvío. Antes de nada hay que comprobar que al desviar se conserva el número del llamante, porque sin él no hay identificación. Con Gemini Live hay que construir el puente de audio telefónico; ElevenLabs lo trae de serie.
  • Concurrencia. El objetivo son 5 o 6 llamadas a la vez. Hay que pasar de un portátil a una máquina en la UE y hacer una prueba de carga con siete llamadas simultáneas sintéticas.
  • Odoo real. Adaptar las skills a sus datos y a un catálogo de miles de referencias, y medir la latencia con la red de por medio.
  • Cumplimiento. El aviso de que atiende un asistente de IA, APIs de pago (los planes gratuitos usan los datos o no tienen licencia comercial) y una política de retención para las transcripciones.
  • Operación. Una consola para varias llamadas a la vez, recados que lleguen a su destinatario y una batería de llamadas guionizadas que se ejecute tras cada cambio de prompt.
  • Escritura en Odoo, con revisión humana, por el camino asíncrono.

Lo que sí ha demostrado la prueba es que un agente de voz puede consultar un ERP real mientras habla y responder en menos de dos segundos, sin poder escribir ni ver lo que no le corresponde, y que cuando falla deja un rastro suficiente para corregirlo.