Pages

Thursday, June 1, 2017

BOT para resolver expresiones en C# usando el engine de Python (Bots 101)

Este ejemplo sirve como una introduccion a el uso de Microsoft Bots y su manejo de dialogos, y tambien para mostrar que podemos usar funcionalidades de Python desde C#.


Pre-requisitos

El primer paso es obtener todos los prerequisitos para crear un proyecto de tipo BOT usando el Microsoft Bot Framework en Visual Studio.
Estos pasos tambien se pueden ver en este link:

https://docs.microsoft.com/en-us/bot-framework/dotnet/bot-builder-dotnet-quickstart

- Instala Visual Studio 2017 para Windows (Si no lo tienes puedes bajar e instalar la version community de VS 2017 gratis).

- Baja la plantilla de una aplicacion tipo Bot (Bot Application template) de aqui: 

http://aka.ms/bf-bc-vstemplate 

e instala la plantilla grabando el archivo .zip en el directorio de plantillas de  proyectos de Visual Studio 2017.Este folder esta generalmente ubicado aqui:

%USERPROFILE%\Documents\Visual Studio 2017\Templates\ProjectTemplates\Visual C#\



Crear el BOT:

Ahora crearemos nuestro proyecto Bot.
Abre Visual Studio y cre un nuevo proyecto C#. Escoge la plantilla llamada 'Bot Application' para tu nuevo proyecto. Y ponle el nombre 'SolveBot'.

 

Ahora necesitamos adicionar el paquete Nuget de Microsoft.Bot para que nuestro codigo compile:

1. Click-Derecho en el nombre del proyecto y selecciona 'Manage NuGet Packages'.
2. en el Tab 'Browse', pon "Microsoft.Bot.Builder".
3. Ubica el paquete 'Microsoft.Bot.Builder' en la lista de resultados , e instala o actualiza esa libreria.


Gracias a la plantilla de proyecto Bot, tu proyecto contiene todo el codigo necesario para crear el Bot. lo tenemos listo para correr aunque en este punto aun no hace lo que queremos que haga.

Una cosa mas, para poder testear nuestro bot necesitaremos el Bot Framework Emulator, lo puedes bajar de aqui:

https://emulator.botframework.com/

Te permitira interactuar con tu proyecto y probar sus respuestas.

Modificar el Bot por defecto:

Si revisas el codigo de tu app, veras que funciona como una Web API con un controlador 'Messages Controller', el trabajo de este controlador es (entre otras cosas) el de iniciar el contacto al recivir un mensaje inicial y pasando el control de la conversasion a nuestro dialogo raiz, RootDialog, esta clase RootDialog es el punto inicial de nuestra conversacion con el usuario. Asi que dejaremos nuestro controlador tal y como esta:


La conversacion o dialogo es manejada por una o mas clases dialogo que interactuan con el usuario y esperan respuestas del mismo, dependiendo de nuestro codigo la conversacion puede ir por varios caminos, todo depende de como creamos la logica de nuestro dialogo.

En este caso solo usaremos dos clases dialog:

La RootDialog - Saludara al usuario, le dirigira a el dialogo que solicita por una expresion a evaluar y, una vez que ese dialogo termine, usara nuestro servicio de resolucion y mostrara el resultado al usuario.

La ReadExpressionDialog - Esta preguntara al usuario por una expresion a resolver y terminara retornando el valor introducido a el dialogo que lo llamo, en este caso Root, o un error si el usuario falla en proveer una expresion valida.

RootDialog:

Comenzaremos con los cambios en la clase RootDialog, primero adicionaremos dos variables (expresion y firstTime), las usaremos para almacenar la expresion introducida por el usuario, y la segunda para evitar saludar al usuario dos veces, asi que adiciona eso a tu Root Dialog:


Dejaremos la funcion StartAsync tal como esta, pero tenemos que cambiar la funcion MessageReceivedAsync, por que por defecto lo unico que hace es leer lo que manda el usuario y responder con el numero de caracteres tecleados.

Lo que vamos a hacer es que ignore ese primer mensaje, y llamar a nuestra funcion 'SendWellcome' Para mostrar un mensaje de bienvenida al usuario, asi que replazalo con este codigo:


Ahora adicionemos la funcion SendWelcomeMessageAsync, en esta funcion lo que queremos lograr es saludar al usuario si es la primera interaccion, luego pasar el control a nuestro otro dialogo 'ReadExpressionDialog' , y una vez que ese dialogo retorne queremos ejecutar la funcion  'ReadExpresssionDialogResumeAfter' en la cual manejaremos el resultado del dialogo, asi que adiciona esta funcion:


Ahora , antes de construir la clase ReadExpressionDialog, vamos a adiconar al funcion de respuesta para manejar la respuesta, no te preocupes del subrayado rojo, se ire cuando adicionemos nuestro otro dialogo. Asi que adiciona la funcion 'ReadExpresssionDialogResumeAfter' , en esta:

- Recibiremos el resultado de ReadExpressionDialog, y lo almacenaremos en nuestra variable 'expresion'.
- Luego llamaremos a nuestro servicio  Solver para procesar la expresion (crearemos esa clase en un momento) y mostrar el resultado al usuario
- Si hubo un error leyendo la expresion del usuario manejaremos ese error e informaremos al usuario, para que el proceso comience de nuevo.
- Luego pasaremos el control a nuestra funcion Welcoming, para que el proceso comience de nuevo, esta vez sin saludar, ya que ya lo hicimos:

Esta es la funcion:


Ahora adicionaremos nuestro nuevo dialogo. 
- Click derecho en el folder Dialogs   > Adicionar > Clase


Esta clase tiene que implementar el interfaz ' IDialog<string>' ya que devolveremos una cadena de caracteres como resultado de esa conversacion.
Incluye las siguientes referencias:


Luego necesitamos adicionar nuestra funcion StartAsync, ese es el punto inicial de este dialogo. Vamos a mostrar nuestra pregunta y esperar por la respuesta del usuario para posteriormente pasar el control a la funcion que manejara esa respuesta. Nota: nuestras clases dialogo tienen que ser Serializables asi que adiciona eso:


Ahora adicionemos la funcion final de este dialogo 'MessageReceivedAsync' esta funcion leera la respuesta del usuario y evaluara si no esta vacia, dara 3 intentos al usuario para ingresar un valor valido antes de lanzar una excepcion. si la entrada del usuario es correcta entonces terminara el dialogo actual devolviendo el valor leido a la clase que lo llamo (RootDialog) usando:
'context.Done('result of this dialog')'


Con eso tenemos la logica de nuestros dialogos terminada. La unica pieza que falta es la clase para resolver la expresion.

El Solucionador con Python:

como no tenemos uan libreria standar en C# para resolver expresiones vamos a solicitar la yuda de Python, que tiene un motor que puede resolver este tipo de problemas.

Asi que primero adicionemos el paquete Nuget de  'IronPython'  a nuestro proyecto. click derecho sobre el nombre del proyecto y selecciona 'Manage Nuget Packages', luego buscalo e instalalo:


Ahora crearemos una clase 'Solver' en el folder de nuestros dialogos, Esta clase hace referencia a la libreria Python qeu adicionamos, y tendra solo una funcion 'Execute', esta funcion:
- Recibira una expresion como cadena.
- Instanciara el motor(engine) de Python.
- Usara la funcion 'Execute' de ese engine para evaluar la expresion.
- Y devolvera el resultado o un mensaje 'non solvable' si ocurre algun problema. 

Puedes explorar otras funciones del engine de Python, pero en este caso como puedes ver a continuacion es muy simple, gracias a IronPython.

Esta es la clase Solver completa:



A probarlo:

Ahora haz correr tu solucion, obtendras esta ventana en tu explorador si la aplicacion esta corriendo correctamente:


si bajaste e instalaste el emulador de Bot abrelo ahora e introduce el URL de tu solucion adicionando la ruta api/messages en la casilla de endpoint del emulador:

'http://localhost:3979/api/messages'

Y haz clic en Conectar.


Espera unos segundos, y comienza escribiendo 'hola'.
El Bot te mostrara un mensaje de bienvenida y luego preguntara por una expresion, introduce una expresion valida, por ejemplo: " (8+3)*4" y deberias obtener tu respuesta.


Y listo, ya tienes tu propio Bot 'solver' en C# y potenciado por Python. Ahora imaginate lo que podrias hacer ;).

Puedes bajar el codigo completo de:

Y eso, amigo mio, es todo!





No comments:

Post a Comment