Observaciones en la elaboración de un asistente conversacional de voz mediante llamada telefónica
Presento aquí ciertos aspectos generales de mi trabajo con interfaces de voz automáticas
Una conversación tiene estos niveles (ISI):
- intercambio: pares de expresiones por dos o más participantes, como pregunta y respuesta. Estos pueden anidarse (A B CC' B' A'). Si la pregunta previa ya no se recuerda se puede repetir (A B CC' B' AA')
- secuencia: intercambios sobre un mismo tema
- interacción: conjunto de secuencias contiguas en un espacio/tiempo por los mismos participantes.
Componentes del sistema:
- establecimiento de llamada entre participantes (infraestructura que permita alternancia de participantes: bot-usuario, técnico/agente-usuario)
- audio a texto en directo (streaming)
- texto a intención (clusterización con semejanza semántica)
- flujo conversacional del bot
- guión específico del dominio
Existen servicios en la nube que permiten implementar estas tareas. A continuación indicaré cómo implementé los tres últimos desde cero (en Python y con un editor de grafos).
En una interacción ideal el usuario se mantiene dentro del dominio que definen los objetivos de la interacción, mantiene un rol pasivo, no es ambigüo y cada expresión suya se corresponde con una intención ya anticipada en el diseño. Como quiera que tales escenarios no son frecuentes y que se complican especialmente si el usuario se siente confuso sobre la interacción o sus contenidos, indicaré a continuación algunas problemáticas de la automatización de las conversaciones.
2. Nivel de audio
Dificultades:
- latencias
- distinguir adecuadamente entre ruido y voz
- distinguir adecuadamente entre la voz de nuestro interlocutor y la de terceras personas
cuya voz se oye.
- Esto puede hacerse con biometría/diarización tras identificar al usuario
- Reconocimiento de voz preciso. Ver cómo responde a las ineficiencias del canal o de la articulación del usuario y a variedades de una lengua infrarrepresentadas (como probablemente las variedades no estándar) en el entrenamiento de su red neuronal.
- tener, lo más inmediatamente posible, reconocimientos parciales del usuario. De este modo se puede anticipar la intención del usuario según va profiriendo. Así sabemos hacia dónde va y la repercusión posible de su intervención para gestionar el solapamiento.
3. nivel de intención
La intención se obtiene usando una red neuronal entrenada para corresponder un corpus anotado de pares expresión-intención.
Dificultades:
- latencias
- limitación al dominio: no podemos responder a todo lo que el usuario diga si no queremos usar generadores de lenguaje (NLG), difícilmente controlables. Pero podemos pedirle aclaración, colaboración o ignorar lo que no entendamos y repetir la pregunta.
-
ininteligibilidad (falta de entendimiento): no siempre es posible, en un modelo
simplificado, determinar si lo que dice el usuario no está contemplado (en el dominio o en el corpus)
puesto que siempre buscará
asemejar la expresión a los grupos de intención establecidos.
- Aunque se establezca un umbral para el índice de confianza, este método no es infalible.
- Según se identifiquen estos problemas podemos forzar la expresión inadecuadamente categorizada en el corpus de pares expresión-intención.
- problemas de segmentación del audio: a veces no hay una relación de uno a uno entre
expresión e intención. Una expresión puede tener más de una intención
buenos días ¿qué desea? ¿cómo dice que se llama?
o dos expresiones ser la misma o redundantes, al haberse segmentado más de lo necesario"no, no"
-> (no
,no
). -
ambigüedad en la intención: en caso de que haya dos intenciones compatibles con una
misma expresión podemos preferir el contexto local al general o establecer una jerarquía, por
ejemplo
una de efectos y preferir aquella que tenga menor o mayor repercusión en el guión.
esto ocurre, por ejemplo, en
sí/vale/...
pues puede interpretarse como afirmación o retroalimentación. La retroalimentación establece puntos de comprobación en la interacción para indicar que la comunicación se está produciendo adecuadamente y así seguir progresando con seguridad. Es útil distinguirlas: la retroalimentación no constituye turno y no debe interrumpir al bot; sin embargo la afirmación sí.
4. nivel de flujo de la conversación
Dificultades:
- solapamiento: el usuario habla mientras el bot habla. Ante un solapamiento en una
conversación natural, en la mayor parte de los casos (salvo que el interlocutor responda brevemente,
retroalimente o replique convencionalmente a un saludo) decidimos interrumpirnos.
- Son importantes los
análisis parciales de la intención del usuario pues de este modo el bot no se
interrumpe
ante la réplica, por ejemplo, de un
buenos días
. Para implementar adecuadamente este comportamiento sin crear caos y si no queremos evidenciar que nuestro bot es una máquina debemos contar con las susodichas tecnologías de audio. - En caso de que esto no funcione bien lo mejor que puede hacerse es imponerse y terminar la locución en curso pero no avanzar a otro nodo hasta que el usuario deje de hablar y pueda tomarse una decisión al respecto. Esto requiere contar con tecnologías que determinen si el sonido es voz y si es la voz de nuestro interlocutor y no la de un tercero de fondo
- Son importantes los
análisis parciales de la intención del usuario pues de este modo el bot no se
interrumpe
ante la réplica, por ejemplo, de un
- cambio de roles: el usuario pregunta en vez de responder.
- Determinar la entonación interrogativa ayuda. No obstante, no todas las peticiones de información tienen entonación interrogativa o pronombres interrogativos siquiera.
- Las respuestas pueden calcularse mediante el uso de transformers Q&A sobre una FAQ o demás documentos. Si, por el contrario, se usan grabaciones de voz natural y se tiene un repertorio finito de respuestas, resulta difícil dar respuesta precisa a todas las preguntas que formule el usuario
- repetición: ha de evaluarse a qué punto del grafo debe retornarse.
Esto
puede hacerse teniendo en cuenta el último retroalimentador del usuario, después de la última pregunta,
o no
más allá de un número predeterminado de nodos o palabras que representen la memoria reciente
- Son muy convenientes las alternativas de texto para hacer más natural la repetición
- temporizador: entre sus principales funciones está la de gestionar el silencio del usuario. Si el usuario acaba diciendo algo que tiene algún efecto relevante, el temporizador se cancela; en el caso de que diga algo que deba dejarse sin efecto (caso diferente del de la falta de comprensión), el temporizador debe reiniciarse.
El progreso en la interacción puede implementarse mediante un autómata finito. Y este se implementa a través de un grafo, que puede constituir un lenguaje de programación en sí mismo (con asignación, condición, user I/O, módulos...).
Este autómata debe definir contextos: aparte del contexto inmediato de cada nodo constituido por sus arcos, debe haber otros generales como por ejemplo los módulos de preguntas+respuestas o de gestión general de la interacción, etc... Y el contexto inmediato debe tener preferencia en la toma de decisiones sobre los generales en caso de competencia de arcos.
La secuencia de avance es:
- arco a nodo (el primer nodo
START
es de sección y este tipo de nodos no se ejecutan) - arco nodo nodo: en caso de salto a sección
Los nodos de la propia interfaz de voz pueden definirse mediante lenguaje de marcado. En la siguiente
notación se
expresan metadatos <>
, alternativas textuales /
y variables {}
.
Las
alternativas son importantes para que se sienta el bot menos artificial en caso de repeticiones,
especialmente
tras una interrupción.
<greeting>
encantad@ de saludarle, {user_name}
/
es un placer saludarle, {user_name}
Si usamos grabaciones de voz y, por eficiencia, concatenamos segmentos de voz la posición de la variable importa. Por ejemplo, en el caso de nombres resulta mucho más conveniente concatenar vocativos (posición inicial) que ubicar el nombre en posición final; esto es así porque el vocativo posee una entonación propia e interpone una breve pausa con el resto del texto por lo que, bien realizado, no hay que afinar la continuidad prosódica.
A continuación se enumeran los tipos de nodos y arcos del grafo:
nodos | equivalente en python |
---|---|
información | print() |
pregunta | input() |
asignación (individual y múltiple mediante yaml) | a = X |
condición | if elif ... else |
sección y salto a sección | def, return |
directrices que alteran el funcionamiento del programa |
arcos | |
---|---|
no condicionados | Se ejecutan en ausencia de otros (son equivalentes a else ) o si los arcos
condicionados, al ser evaluados, no superan cierto umbral mínimo |
condicionados | Las condiciones pueden ser múltiples y de distintos tipos: intenciones, comparaciones algebraicas, simples valores de verdad, etc... |
temporizadores | son arcos condicionados pero se inician sólo tras ejecutarse un paso arco-nodo. Sólo si expiran avanzan a su nodo de destino; se cancelan si antes de expirar se materializa algún otro avance. El objetivo de estos arcos es limitar el periodo de silencio del usuario, permitiendo efectos como preguntarle si sigue ahí y finalizar la llamada tras un tiempo mayor. |
con punto de retorno | establece un punto de retorno, en caso de que una rama finalice |
sin nodo de destino | finaliza la rama y retornan el cursor a un punto anterior (como un salto u otro nodo establecido explícitamente). Si la lista de puntos de retorno está vacía, se finalizará el bot. |
colectores | permite que varios nodos compartan parcial o totalmente la lógica de la toma de decisiones. |