Bienvenidos de nuevo a la segunda parte de nuestra serie de dos partes sobre la creación de una aplicación interactiva de atención al cliente que capacite a los agentes de soporte con la ayuda de la IA. El objetivo es mejorar su importante trabajo aprovechando respuestas previamente resueltas para preguntas abiertas actuales de forma rápida e intuitiva mediante la búsqueda vectorial.
tl;dr En caso de que desee pasar directamente a la aplicación, puede encontrar un ejemplo totalmente funcional de esta aplicación en GitHub junto con instrucciones detalladas README.
En la primera parte, configuramos todos los servicios que necesitamos para nuestra aplicación, incluyendo:
-
- Couchbase Capella
- API de mensajes de Vonage
- API de incrustación de OpenAI
También hemos creado el backend de nuestra aplicación con Ruby on Rails. Ahora, vamos a escribir el código que va a conectar estos tres servicios y unirlos para apoyar el trabajo de los agentes de atención al cliente que responden a las consultas de los usuarios a través de WhatsApp.
En primer lugar, vamos a definir la lógica de negocio de nuestra aplicación.
Definición de la lógica empresarial de la aplicación
Una de las cosas bonitas de Ruby on Rails es su noción de convención sobre configuración. Esto significa que no necesitamos reinventar la rueda para saber dónde poner la lógica de negocio de nuestra aplicación. Lo que construyamos ahora será una versión simplificada. No mostraremos todo el código de la aplicación en este tutorial, pero como mencionamos al principio, todo el código base está disponible para clonar localmente en GitHub.
En resumen, definiremos nuestra lógica en archivos de modelo y controlador.
La aplicación tendrá tres modelos:
-
- Billete
- Usuario
- Agente
La aplicación también tendrá dos controladores:
-
- Controlador de mensajes
- Controlador del salpicadero
Otras partes de la aplicación que necesitarán ser configuradas incluyen los inicializadores de Vonage, OpenAI y Couchbase en config/inicializadores
y las rutas para la aplicación en config/rutas.rb
. Todos ellos pueden consultarse en GitHub y copiarse directamente desde allí a su propia base de código.
Empecemos por los modelos.
Crear modelos
Es cierto que clásicamente Rails funciona con bases de datos SQL nada más sacarlo de la caja. ¿Y si queremos combinar lo mejor de Rails con lo mejor de una base de datos de documentos JSON como Couchbase? Ahora es posible hacerlo de una forma ágil gracias al ORM Ruby de Couchbase que pone a nuestra disposición gran parte de la funcionalidad ActiveRecord de Rails para trabajar con Couchbase. Cada uno de nuestros modelos heredará de CouchbaseOrm::Base
para poner a su disposición los métodos del ORM.
El modelo Ticket
Un ticket de la aplicación debe contener información sobre la consulta, su estado actual, quién la ha realizado, el resumen de la resolución final y el vector de dicha resolución. Podemos definir cada uno de ellos como atributos:
1 2 3 4 5 6 7 8 9 10 11 12 |
ABIERTO = abierto RESUELTO = resuelto pertenece_a :usuario pertenece_a :agente, opcional: verdadero atributo :consulta, :cadena atributo :estado, :cadena, por defecto: ABIERTO atributo :resumen, :cadena atributo :incrustación, :matriz, tipo: :float, por defecto: [] atributo :fecha_de_creación, :datetime, por defecto: -> { Tiempo.ahora } atributo :actualizado_a, :datetime, por defecto: -> { Tiempo.ahora } |
Como puedes ver arriba, también definimos dos constantes, ABIERTO y RESUELTO, que son los dos posibles estados en los que puede encontrarse un ticket. También creamos la relación de un ticket con un usuario y con un agente, opcionalmente.
Además de definir los atributos, también queremos crear algunos métodos en el ticket a los que se pueda acceder en nuestra aplicación. Queremos crear métodos de ayuda que se pueden utilizar para comprobar el estado de un billete como tal:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
def auto.abra_entradas donde(estado: ABIERTO).a_a fin def auto.resuelto_entradas donde(estado: RESUELTO).a_a fin def abra? estado == ABIERTO fin def resuelto? estado == RESUELTO fin def mark_as_resolved! actualización!(estado: RESUELTO) fin |
En conjunto, el modelo Ticket tendrá el siguiente aspecto, incluidas las validaciones adicionales:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
clase Billete < CouchbaseOrm::Base ABIERTO = abierto RESUELTO = resuelto pertenece_a :usuario pertenece_a :agente, opcional: verdadero atributo :consulta, :cadena atributo :estado, :cadena, por defecto: ABIERTO atributo :resumen, :cadena atributo :incrustación, :matriz, tipo: :float, por defecto: [] atributo :fecha_de_creación, :datetime, por defecto: -> { Tiempo.ahora } atributo :actualizado_a, :datetime, por defecto: -> { Tiempo.ahora } garantizar_documento_de_diseño! valida :consulta, presencia: verdadero antes_de_guardar :configure_marcas de tiempo def auto.abra_entradas donde(estado: ABIERTO).a_a fin def auto.resuelto_entradas donde(estado: RESUELTO).a_a fin def abra? estado == ABIERTO fin def resuelto? estado == RESUELTO fin def mark_as_resolved! actualización!(estado: RESUELTO) fin privado def configure_marcas de tiempo auto.actualizado_a = Tiempo.ahora fin fin |
Hagamos lo mismo con el modelo de usuario.
El modelo de usuario
Un usuario de la aplicación debe poder tener varios tickets, ya que es posible que un cliente tenga varios problemas para los que busca ayuda.
También queremos asegurarnos de que podemos encontrar fácilmente un usuario por su número de WhatsApp, por lo que crearemos un método helper en el modelo para eso también. En este método de ayuda, un usuario será encontrado por su número, o si no, entonces se creará un nuevo usuario y una parte de su número se utilizará para rellenar el campo de nombre.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
clase Usuario < CouchbaseOrm::Base tiene_muchos :entradas atributo :whatsapp_número, :cadena atributo :nombre, :cadena atributo :fecha_de_creación, :datetime, por defecto: -> { Tiempo.ahora } atributo :actualizado_a, :datetime, por defecto: -> { Tiempo.ahora } valida :whatsapp_número, presencia: verdadero valida :nombre, presencia: verdadero antes_de_guardar :configure_marcas de tiempo privado def configure_marcas de tiempo auto.actualizado_a = Tiempo.ahora fin def auto.buscar_o_crear_usuario_por_numero_whatsapp(whatsapp_número) usuario = Usuario.buscar_por(whatsapp_número: whatsapp_número) a menos que usuario usuario = Usuario.crear!(whatsapp_número: whatsapp_número, nombre: "User #{whatsapp_number[-4..-1]}") fin usuario fin fin |
El último modelo que vamos a crear es el modelo de Agente, vamos a hacerlo.
El modelo de agente
En muchos sentidos, el modelo de Agente es paralelo al modelo de Usuario en el sentido de que ambos tienen muchos tickets. Una diferencia clave es que el agente no usa su propio número de WhatsApp para comunicarse en la aplicación. En su lugar, se comunica con los usuarios a través del panel de control de la aplicación y la API de mensajes de Vonage envía mediante programación sus mensajes a la bandeja de entrada de WhatsApp del usuario.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
requiere couchbase-orm clase Agente < CouchbaseOrm::Base tiene_muchos :entradas atributo :nombre, :cadena atributo :correo electrónico, :cadena atributo :fecha_de_creación, :datetime, por defecto: -> { Tiempo.ahora } atributo :actualizado_a, :datetime, por defecto: -> { Tiempo.ahora } valida :correo electrónico, presencia: verdadero, unicidad: verdadero valida :nombre, presencia: verdadero antes_de_guardar :configure_marcas de tiempo privado def configure_marcas de tiempo auto.actualizado_a = Tiempo.ahora fin fin |
En este punto, hemos creado nuestros modelos. No es el momento de definir los controladores.
Crear controladores
La aplicación tendrá dos controladores que definen lo que sucede dentro de cada ruta del sitio. A saber, un controlador dashboard_controller
que supervisa la funcionalidad del Cuadro de mandos, y un controlador_de_mensajes
que supervisa la funcionalidad de mensajería.
Empezaremos por el panel de control.
El controlador del salpicadero
En índice y Mostrar se definirán en el controlador del cuadro de mandos, junto con métodos de ayuda para obtener la similitud vectorial de los tickets resueltos previamente para los agentes de soporte en su trabajo con los tickets actuales.
El SDK Ruby de Couchbase incluye muchas abstracciones útiles para hacer más ágil la interacción con la funcionalidad de Couchbase. Esto incluye la creación de una nueva búsqueda vectorial usando Couchbase::VectorSearch.new
definiendo los argumentos dentro de la instanciación de un nuevo VectorSearch
objeto como tal:
1 2 3 4 5 6 7 8 9 10 11 |
solicitar = Couchbase::Petición de búsqueda.nuevo( Couchbase::VectorSearch.nuevo( [ Couchbase::VectorQuery.nuevo(incrustación, incrustación) do |q| q.número_candidatos = 2 q.impulsar = 0.3 fin ], Couchbase::Opciones::VectorSearch.nuevo(vector_query_combination: :y) ) ) |
En primer lugar, el VectorSearch
se envuelve dentro de una nueva instancia de Couchbase::PeticiónDeBúsqueda
como el VectorSearch
es un tipo de solicitud de búsqueda. A continuación, una nueva Couchbase::VectorQuery
se pasa a la instancia VectorSearch
especificando el campo en el que se va a buscar (por ejemplo, incrustación) y la consulta del cliente convertida en su propia incrustación como segundo argumento.
Con esto en mente sobre cómo crear una búsqueda vectorial usando el SDK de Ruby echemos un vistazo al código completo del controlador del dashboard:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
clase DashboardController < Controlador de aplicación def índice @entradas_abiertas = Billete.donde(estado: abierto).to_a.ordenar_por { |billete| billete.creado_en }.invertir @entradas_resueltas = Billete.todos.to_a.seleccione { |billete| billete.estado == Billete::RESUELTO }.to_a.ordenar_por { |billete| billete.actualizado_en }.invertir fin def Mostrar @billete = encontrar_billete @usuario = encontrar_usuario @sugerencias = encontrar_sugerencias fin privado def encontrar_billete Billete.encontrar(parámetros[:id]) fin def encontrar_usuario buscar_billete.usuario fin def encontrar_sugerencias buscar_billetes_similares(buscar_billete.consulta) fin def buscar_billetes_similares(consulta) incrustación = OPENAI_CLIENT.incrustaciones( parámetros: { modelo: "text-embedding-ada-002", entrada: consulta } )[datos][0][incrustación] grupo = Couchbase::Grupo.conecte( ENV['COUCHBASE_CONNECTION_STRING'], ENV[COUCHBASE_USERNAME], ENV[COUCHBASE_PASSWORD] ) cubo = grupo.cubo(ENV[COUCHBASE_BUCKET]) alcance = cubo.alcance(por defecto) solicitar = Couchbase::Petición de búsqueda.nuevo( Couchbase::VectorSearch.nuevo( [ Couchbase::VectorQuery.nuevo(incrustación, incrustación) do |q| q.número_candidatos = 2 q.impulsar = 0.3 fin ], Couchbase::Opciones::VectorSearch.nuevo(vector_query_combination: :y) ) ) resultado = alcance.busque en('whatsapp_support_index', solicitar) resultado.filas.mapa do |fila| documento = cubo.colección_por_defecto.consiga(fila.id) { id: fila.id, puntuación: fila.puntuación, resumen: documento.contenido[Resumen] } fin fin fin |
Con ese código, hemos definido la lógica funcional tanto para la vista principal del cuadro de mandos como para la vista de cada ticket individual en el cuadro de mandos.
El código del frontend se encuentra en GitHub para ambas vistas y puede copiarse directamente en su código base o editarse para sus necesidades específicas.
El controlador de mensajes
Ahora necesitamos definir la lógica para todos los mensajes que serán recibidos y enviados a través de la aplicación vía WhatsApp. El controlador de mensajes también será responsable de garantizar que se creen nuevas entradas y usuarios cuando un usuario envíe un mensaje por primera vez.
Es posible que en una iteración posterior de la aplicación parte de esta funcionalidad pueda ser movida y separada a diferentes áreas en el código base, pero por simplicidad lo mantendremos todo en el controlador de mensajes por ahora.
Uno de los grandes aspectos de Rails es la capacidad de inyectar dinámicamente nuevo contenido en la ventana del navegador sin necesidad de utilizar JavaScript. Esto se consigue con ActionCableuna característica central de Rails. También utilizaremos ActionCable para actualizar la vista de ticket individual del panel de control con los últimos mensajes enviados y recibidos con la API de mensajes de Vonage.
Como se menciona en el Uso de la API de mensajes de Vonage para fines de desarrollo, estamos construyendo con la API sandbox, por lo que nuestra llamada a la API para enviar mensajes se construirá manualmente como una solicitud HTTP POST. Una vez que hayas completado los pasos necesarios descritos en la documentación de Vonage para obtener una cuenta empresarial Meta WhatsApp, puedes utilizar el SDK Ruby de Vonage para abstraer estas solicitudes HTTP por ti.
Aquí está el código para el controlador:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
clase Controlador de mensajes < Controlador de aplicación skip_before_action :verificar_autenticidad_ficha def entrada whatsapp_número = parámetros[:de] texto = parámetros[:texto] @respuesta = { remitente: Usuario, cuerpo: texto, fecha_de_creación: Tiempo.actual } @usuario = Usuario.buscar_por(whatsapp_número: whatsapp_número) si @usuario billete_abierto = Billete.buscar_por(usuario_id: @usuario.id, estado: Billete::ABIERTO) si billete_abierto.nil? Billete.crear!(usuario_id: @usuario.id, consulta: "En espera de consulta", estado: Billete::ABIERTO) enviar_mensaje(a: whatsapp_número, texto: "Gracias por su mensaje. Por favor, describa su consulta de soporte".) si billete_abierto.consulta == "En espera de consulta" billete_abierto.actualización!(consulta: texto) ActionCable.servidor.difusión "mensajes_#{@usuario.id}", { respuesta: @respuesta, billete: abra_billete } si no ActionCable.servidor.difusión "mensajes_#{@usuario.id}", { respuesta: @respuesta, billete: abra_billete } fin si no @usuario = Usuario.crear!(whatsapp_número: whatsapp_número, nombre: "User #{whatsapp_number[-4..-1]}") enviar_mensaje(a: whatsapp_número, texto: "Gracias por su mensaje. Por favor, describa su consulta de soporte".) Billete.crear!(usuario_id: @usuario.id, consulta: "En espera de consulta", estado: Billete::ABIERTO) fin responder_a do |formato| formato.turbo_flujo { render turbo_stream: turbo_stream.añadir("mensajes_#{@usuario.id}", parcial: "mensajes/respuesta", locales: { respuesta: @respuesta }) } fin fin def respuesta @ticket_id = parámetros[:ticket_id] @mensaje = parámetros[:mensaje] @resuelto = parámetros[:mark_as_resolved] == "1" @billete = Billete.encontrar(@ticket_id) @usuario = @billete.usuario @respuesta = { remitente: Agente, cuerpo: @mensaje, fecha_de_creación: Tiempo.actual } ActionCable.servidor.difusión "mensajes_#{@usuario.id}", { respuesta: @respuesta, billete: @billete } si @resuelto incrustación = OPENAI_CLIENT.incrustaciones( parámetros: { modelo: "text-embedding-ada-002", entrada: @mensaje } )[datos][0][incrustación] @billete.actualización!(resumen: @mensaje, estado: Billete::RESUELTO, incrustación: incrustación) enviar_mensaje( a: @usuario.whatsapp_número, texto: @mensaje ) responder_a do |formato| formato.html { redirigir_a solicitar.remitente, aviso: "Ticket marcado como resuelto". } fin si no enviar_mensaje( a: @usuario.whatsapp_número, texto: @mensaje ) responder_a do |formato| formato.turbo_flujo { render turbo_stream: turbo_stream.añadir("mensajes_#{@usuario.id}", parcial: "mensajes/respuesta", locales: { respuesta: @respuesta }) } fin fin fin def estado cabeza :ok fin privado def enviar_mensaje(a:, texto:) requiere net/http requiere uri requiere json uri = URI.analizar("https://messages-sandbox.nexmo.com/v1/messages") solicitar = Red::HTTP::Publicar en.nuevo(uri) solicitar.autenticidad_básica(ENV[VONAGE_API_KEY], ENV['VONAGE_API_SECRET']) solicitar.tipo_contenido = aplicación/json solicitar[Aceptar] = aplicación/json solicitar.cuerpo = { de: ENV['VONAGE_FROM_NUMBER'], a: a, tipo_mensaje: texto, texto: texto, canal: whatsapp }.a_json http = Red::HTTP.nuevo(uri.host, uri.puerto) http.use_ssl = verdadero respuesta = http.solicitar(solicitar) pone "Código de respuesta: #{response.code}" pone "Cuerpo de la respuesta: #{response.body}" fin fin |
Con la creación del controlador de mensajes, hemos definido casi toda la funcionalidad que necesitamos en nuestra aplicación.
Todo lo que aún esté intentando averiguar está disponible en la aplicación de ejemplo, totalmente completa y operativa, en GitHub.
Ahora sólo nos queda ejecutar nuestra aplicación.
Ejecutar la aplicación
Para utilizar la aplicación, abra una ventana de terminal y ejecute bin/dev
dentro de la carpeta de la aplicación. Esto iniciará un entorno de desarrollo de la aplicación. Ahora, en una ventana de terminal separada, inicia ngrok, si estás usando ngrok como se describió anteriormente para hacer tu entorno localhost accesible externamente, ejecutando ngrok http 3000
.
Una vez realizados ambos comandos, puedes abrir en tu navegador http://localhost:3000. Verá un tablero de mandos vacío sin entradas. Eso es porque aún no ha creado ninguna.
Continúa y envía un mensaje al número de WhatsApp que se te proporcionó en tu sandbox de la API de mensajes de Vonage. La aplicación responderá pidiéndote que expliques tu pregunta de soporte. Una vez que lo hagas, el panel de tu navegador se completará con el nuevo ticket. Luego, puedes abrir el ticket haciendo clic en Ver y empezar a interactuar con él.
Una vez que empiece a tener tickets marcados como resueltos, los tickets futuros se rellenarán con un Soluciones propuestas como puede ver en la captura de pantalla anterior. Cada solución sugerida se clasificará por su similitud con la pregunta de soporte actual.
De este modo, hemos creado una aplicación que utiliza la IA para capacitar a los miembros del equipo en su trabajo y no pretende sustituirlos en sus tareas.
Conclusión
Mediante la integración de Couchbase, Vonage y OpenAI, hemos creado una aplicación que permite a los agentes acceder rápidamente a la información relevante, mejorando su eficiencia y permitiéndoles centrarse en ofrecer un excelente servicio al cliente. Este proyecto muestra cómo se puede aprovechar la tecnología para apoyar y elevar las funciones humanas en el lugar de trabajo, lo que lleva a mejores resultados tanto para los empleados como para los clientes. Ahora que has visto el potencial, es hora de aplicar estos conceptos a tus propios proyectos y seguir innovando.
-
- Parte 1 - La IA en acción: Mejorar y no sustituir puestos de trabajo
- Empiece a utilizar Couchbase Capella hoy mismo, regístrese gratis