
Compartir:
Antiguo desarrollador .NET Advocate @Vonage, ingeniero de software poliglota full-stack, AI/ML
El ayudante de Papá Noel en Nexmo C# Advent Series
Tiempo de lectura: 8 minutos
Estamos en Adviento, una época muy ajetreada para muchos, pero para nadie más que para San Nicolás, cuya fiesta celebra hoy el rito oriental. En honor del tiempo de Adviento C vamos a ayudar a Papá Noel automatizando parte de su correspondencia y proporcionándole medios más modernos que el servicio postal para responder a sus preguntas.
Para cristalizar nuestro objetivo, vamos a crear un bot de preguntas frecuentes para Papá Noel al que se pueda acceder y que responda a través de Facebook Messenger, WhatsApp y SMS.
Vamos a construir esto con la ayuda de QnAMaker y por supuesto la Nexmo Messages API
Requisitos previos
Vonage API Account
To complete this tutorial, you will need a Vonage API account. If you don’t have one already, you can sign up today and start building with free credit. Once you have an account, you can find your API Key and API Secret at the top of the Vonage API Dashboard.
This tutorial also uses a virtual phone number. To purchase one, go to Numbers > Buy Numbers and search for one that meets your needs.
Visual Studio 2019 versión 16.3 o superior
Una Account Azure
Opcional: Ngrok para el despliegue de prueba
Opcional: Para Facebook Messenger, necesitaremos vincular una Facebook Page a nuestra Nexmo Account-puedes ver las instrucciones paso a paso en Desarrollador Nexmo. Completando parte 2 de la guía se creará una aplicación Nexmo con una página de Facebook vinculada; asegúrate de guardar el archivo de clave privada que se genera para esta aplicación.
Nota: El código de esta demostración funcionará con WhatsApp Messages una vez que WhatsApp Business esté configurado. Dicho esto, WhatsApp está más pensado para un caso empresarial; para ver más detalles sobre cómo configurar una aplicación para WhatsApp, puedes consultar la guía de Desarrollador Nexmo
Construir nuestro bot
Configurar
Para construir nuestro bot vamos a dirigirnos a QnAMaker e iniciaremos sesión con una Account Azure.
Haga clic en "Crear una base de conocimientos".
Siga las instrucciones del paso 1 para crear un servicio QnA en Azure.
Para el paso 2, configure lo siguiente:
ID de directorio de Microsoft Azure
El nombre de la Suscripción de la cuenta Azure
El servicio QnA que vamos a utilizar (coincidirá con el Nombre del servicio que acabamos de crear en el Azure Portal).
El lenguaje de nuestro bot

Para el paso 3 vamos a llamar a nuestra base de conocimiento "Ayudante Nexmo de Santa Claus".
En el paso 4 es donde QnA Maker se pone guay: rellenar la base de conocimientos de este bot es tan fácil como enlazarlo a una página de preguntas frecuentes o subir un archivo de preguntas frecuentes. Por supuesto, para esta demostración vamos a utilizar la página oficial de preguntas frecuentes del sitio web de Papá Noel de Papá Noel.
También vamos a añadir algo de cháchara a nuestro bot; como nuestro bot va a ser un elfo honorario, vamos a utilizar la selección de cháchara ingeniosa
Con todo esto configurado, haga clic en Crear base de conocimientos.
Esto ingerirá las FAQ's a las que apuntamos QnA Maker y nos llevará a una página parecida a esta:

Editar, publicar y probar nuestra base de conocimientos
Esta es la pantalla de edición de nuestra base de conocimientos. Desde aquí podemos ver el aspecto de nuestra base de conocimientos. También podemos editarla libremente si queremos cambiar algunas respuestas; por ejemplo, acortemos la respuesta "¿Quién es Papá Noel?".
Después de editar suficientemente la Base de conocimientos, al hacer clic en Save and Train se guardará y entrenará el Bot.
Para probar, pulse el botón Test en la esquina superior derecha. Se abrirá el cuadro de diálogo de prueba, puedes enviar una pregunta, por ejemplo how do reindeer fly? y el bot responderá.

Incluso es posible inspeccionar cómo ha tomado el bot su determinación. Haz clic en el enlace inspeccionar y aparecerá el cuadro de diálogo de inspección. Esto mostrará la confianza del bot en su respuesta y algunas alternativas que se le ocurrieron.

Cuando el bot esté listo, haz clic en publicar en la parte superior de la página y, a continuación, en el cuadro de diálogo que aparece. Cuando esto se complete, aparecerá una pantalla con algunas estructuras de solicitud útiles que se pueden utilizar para generar una respuesta del bot. Se verá algo como:
POST /knowledgebases/YOUR_KNOWLEDGE_BASE_ID/generateAnswer
Host: https://nexmofaqbot.azurewebsites.net/qnamaker
Authorization: EndpointKey YOUR_KNOWLEDGE_BASE_ENDPOINT_KEY
Content-Type: application/json
{"question":"YOUR_QUESTION"}Guarda esta cadena-va a ser usada para crear el WebService que manejará nuestro bot a través del Messages API.
Construir nuestra aplicación
En el cuadro de diálogo que se abre, seleccione una aplicación web ASP.NET Core y nómbrela como "QnAMakerMessagesDemo". Ponle un nombre como "QnAMakerMessagesDemo", selecciona ASP.NET Core 3.0, "Web Application (Model-View-Control)" como tipo y haz clic en crear.
Instalar paquetes NuGet
En Visual Studio vaya a Herramientas -> Administrador de paquetes NuGet -> Administrar paquetes NuGet para la solución.
Instale los siguientes paquetes NuGet:
Newtonsoft.Json
Nexmo.Csharp.Cliente
Castillo hinchable
jose-jwt
Creación de nuestro generador de tokens
Crea una clase llamada TokenGenerator y añádele el siguiente código:
public static string GenerateToken(IConfiguration config)
{
// retrieve appID and privateKey from configuration
var appId = config["Authentication:appId"];
var priavteKeyPath = config["Authentication:privateKey"];
string privateKey = "";
using (var reader = File.OpenText(priavteKeyPath)) // file containing RSA PKCS1 private key
privateKey = reader.ReadToEnd();
//generate claims list
const int SECONDS_EXPIRY = 3600;
var t = DateTime.UtcNow - new DateTime(1970, 1, 1);
var iat = new Claim("iat", ((Int32)t.TotalSeconds).ToString(), ClaimValueTypes.Integer32); // Unix Timestamp for right now
var application_id = new Claim("application_id", appId); // Current app ID
var exp = new Claim("exp", ((Int32)(t.TotalSeconds + SECONDS_EXPIRY)).ToString(), ClaimValueTypes.Integer32); // Unix timestamp for when the token expires
var jti = new Claim("jti", Guid.NewGuid().ToString()); // Unique Token ID
var claims = new List<Claim>() { iat, application_id, exp, jti };
//create rsa parameters
RSAParameters rsaParams;
using (var tr = new StringReader(privateKey))
{
var pemReader = new PemReader(tr);
var kp = pemReader.ReadObject();
var privateRsaParams = kp as RsaPrivateCrtKeyParameters;
rsaParams = DotNetUtilities.ToRSAParameters(privateRsaParams);
}
//generate and return JWT
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
{
rsa.ImportParameters(rsaParams);
Dictionary<string, object> payload = claims.ToDictionary(k => k.Type, v => (object)v.Value);
return Jose.JWT.Encode(payload, rsa, Jose.JwsAlgorithm.RS256);
}
}Esto generará el JWT necesario para autenticarse en la aplicación de mensajes creada como parte de los requisitos previos.
Añadir appId y la ruta de la clave privada a la configuración
El generador de JWT requiere un appId y una ruta a la clave privada guardada anteriormente en el archivo appsettings.json.
Abra este archivo y añada lo siguiente al objeto de configuración:
"Authentication": {
"appId": "NEXMO_APPLICATION_ID",
"privateKey": "C:\\Path\\to\\Private\\key.key"
} Construir estructuras de datos para recibir y enviar datos
Se necesitan un par de POCOs para esta demostración; son un poco verbosos, y no hacen nada excepto definir los objetos de mensajes según la especificación especificaciónpor lo que la estructura completa se omite en este post. Simplemente añade las siguientes clases al proyecto:
InboundMessage.cs MessageRequest.cs
Enviar mensajes
Con las estructuras ordenadas el siguiente paso es enviar mensajes a través de la API de mensajes de Nexmo. Crea una clase llamada MessageSenderque tendrá un único método estático SendMessage que simplemente creará una Solicitud de Mensaje, creará un JWT, creará una solicitud, y enviará la solicitud a la API de Mensajes-debería verse algo como esto:
public static void SendMessage(string message, string fromId, string toId, IConfiguration config, string type)
{
const string MESSAGING_URL = @"https://api.nexmo.com/v0.1/messages";
try
{
var jwt = TokenGenerator.GenerateToken(config);
//construct message Request
var requestObject = new MessageRequest()
{
to = new MessageRequest.To()
{
type = type
},
from = new MessageRequest.From()
{
type = type
},
message = new MessageRequest.Message()
{
content = new MessageRequest.Message.Content()
{
type = "text",
text = message
}
}
};
//special messenger request formatting (use to/from id rather than number, set category to RESPONSE)
if (type == "messenger")
{
requestObject.message.messenger = new MessageRequest.Message.Messenger()
{
category = "RESPONSE"
};
requestObject.to.id = toId;
requestObject.from.id = fromId;
}
else
{
requestObject.to.number = toId;
requestObject.from.number = fromId;
}
//Generate Request payload from requestObject
var requestPayload = JsonConvert.SerializeObject(requestObject, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore });
//build request
var httpWebRequest = (HttpWebRequest)WebRequest.Create(MESSAGING_URL);
httpWebRequest.ContentType = "application/json";
httpWebRequest.Accept = "application/json";
httpWebRequest.Method = "POST";
httpWebRequest.PreAuthenticate = true;
httpWebRequest.Headers.Add("Authorization", "Bearer " + jwt);
using (var streamWriter = new StreamWriter(httpWebRequest.GetRequestStream()))
{
streamWriter.Write(requestPayload);
}
//handle response
using (var httpResponse = (HttpWebResponse)httpWebRequest.GetResponse())
{
using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
{
var result = streamReader.ReadToEnd();
Console.WriteLine(result);
Console.WriteLine("Message Sent");
}
}
}
catch (Exception e)
{
Debug.WriteLine(e.ToString());
}
} Haz una pregunta al bot y envía una respuesta
Ahora es el momento de hablar con el bot de FAQ desde la aplicación. Aquí es donde las llamadas REST de ejemplo que QnAMaker presentó anteriormente entrarán en juego. Recuerda esta cadena de antes.
POST /knowledgebases/YOUR_KNOWLEDGE_BASE_ID/generateAnswer
Host: https://AZURE_APP_NAME.azurewebsites.net/qnamaker
Authorization: EndpointKey YOUR_KNOWLEDGE_BASE_ENDPOINT_KEY
Content-Type: application/json
{"question":"YOUR_QUESTION"}Utiliza esa cadena para crear algunas constantes útiles/de sólo lectura para el Cuestionador-rellénalas con los valores apropiados de la cadena anterior:
//TODO: fill in with Knowledgebase ID
const string kb_id = "YOUR_KNOWLEDGE_BASE_ID";
//TODO: fill in with Knowledgebase Endpoint key
const string ENDPOINT_KEY = "YOUR_KNOWLEDGE_BASE_ENDPOINT_KEY";
const string QUESTION_FORMAT = @"{{'question': '{0}'}}";
//TODO fill in base url
static readonly string URI = $"https://AZURE_APP_NAME.azurewebsites.net/qnamaker/knowledgebases/{kb_id}/generateAnswer";A continuación, cree una tarea para formular la pregunta:
public static async Task<string> RequestAnswer(string question)
{
using (var client = new HttpClient())
using (var request = new HttpRequestMessage())
{
request.Method = HttpMethod.Post;
request.RequestUri = new Uri(URI);
var formatted_question = string.Format(QUESTION_FORMAT, question);
request.Content = new StringContent(formatted_question, Encoding.UTF8, "application/json");
request.Headers.Add("Authorization", "EndpointKey " + ENDPOINT_KEY);
var response = await client.SendAsync(request);
var jsonResponse = await response.Content.ReadAsStringAsync();
JObject obj = JObject.Parse(jsonResponse);
var answer = ((JArray)obj["answers"])[0]["answer"];
return answer.ToString();
}
}
Esto simplemente formatea la pregunta desde la API de mensajes y envía la solicitud como una solicitud de publicación generateAnswer al bot QnAMaker.
Por último, crea un método que dirija la solicitud y responda con una respuesta.
public static async Task AskQuestion(string to, string from, string type, string question, IConfiguration config)
{
question = HttpUtility.JavaScriptStringEncode(question);
var response = await RequestAnswer(question);
MessageSender.SendMessage(response, from, to, config, type);
} Construir controlador para recibir mensajes entrantes
La pieza final del rompecabezas es el controlador que se encargará de la afluencia de mensajes de la Messages API. Crea un controlador MVC vacío llamado MessagesController.
Inyección de dependencia y configuración
Añade un campo IConfiguration llamado _config a esto y configura la inyección de dependencia de la configuración creando un constructor de controlador que tome un objeto IConfiguration:
private IConfiguration _config;
public MessagesController(IConfiguration config)
{
_config = config;
} Solicitud de estado
A continuación, cree una petición Status Post que simplemente no devuelva contenido:
[HttpPost]
public HttpStatusCode Status()
{
return HttpStatusCode.NoContent;
} Mensajes entrantes
A continuación, añada una Post Request para los mensajes entrantes de la Messages API.
Este método extraerá el mensaje entrante del cuerpo y, a continuación, reenviará las tareas del interrogador a partir del contenido del cuerpo de la solicitud.
[HttpPost]
public HttpStatusCode Inbound([FromBody]InboundMessage message)
{
Debug.WriteLine(JsonConvert.SerializeObject(message));
if (message.from.type == "messenger")
{
_ = Questioner.AskQuestion(message.from.id, message.to.id, message.from.type, message.message.content.text, _config);
}
else
{
_ = Questioner.AskQuestion(message.from.number, message.to.number, message.from.type, message.message.content.text, _config);
}
return HttpStatusCode.NoContent;
} SMS entrantes
Por último, añada una solicitud HttpGet para gestionar los mensajes SMS entrantes. Esto extraerá de forma similar la información necesaria del mensaje entrante y formulará la pregunta al interrogador.
[HttpGet]
public HttpStatusCode InboundSms([FromQuery] SMS.SMSInbound inboundMessage)
{
_ = Questioner.AskQuestion(inboundMessage.msisdn, inboundMessage.to, "sms", inboundMessage.text, _config);
return HttpStatusCode.NoContent;
}Con esto resuelto, el servicio está listo para su despliegue.
Pruebas
Lo último que se necesita es activar el servicio, exponerlo a Internet, y cablear la aplicación Nexmo Messages para enviar webhooks al servicio.
Configuración de IIS Express
Para simplificar esta demostración utiliza IIS. Para facilitar la configuración de ngrok, desactive SSL para IIS Express yendo a las propiedades de depuración del proyecto y desmarcando la opción "Activar SSL":

Tome nota del número de puerto en el campo URL de la aplicación, se utilizará en el siguiente paso.
Configuración de Ngrok
El siguiente paso es exponer este punto final a Internet. Para esta demo, algo como ngrok para crear un túnel de vuelta al puerto Express de IIS. Después de instalar ngrok utilizar un comando como:
Para configurar el túnel, sustituya "PORT_NUMBER" por el número de puerto de IIS Express indicado anteriormente. Esto creará una salida que se parece a esto:

Fíjese en la url base http: en la imagen anterior, la url base es http://dc0feb1d.ngrok.io.
Configuración de webhooks
El último paso antes de activar el servicio es configurar los webhooks para que realicen llamadas de retorno al servicio.
SMS entrantes
Ir al Panel Nexmo y vaya a Settings. Establezca la URL de mensajes entrantes para SMS en ngrok_baseurl/messages/InboundSms, según el ejemplo anterior.
http://dc0feb1d.ngroke.io/messages/InboundSms Otros mensajes entrantes
En el Panel de Nexmo abra Mensajes y Despacho -> Sus Applications. Abra la aplicación asociada a las cuentas vinculadas y haga clic en "Editar". En Capacidades en la sección Mensajes, establezca la URL de entrada y la URL de estado para que coincidan con la baseurl ngrok /Messages/Inbound y /Messages/Status respectivamente y haga clic en Guardar.
Según el ejemplo del túnel ngrok tendrá un aspecto similar:

NOTA: Los 8 caracteres que preceden a ngrok.io no son fijos en el nivel gratuito. Esto significa que cada vez que se ejecute el comando ngrok será necesario cambiar donde apuntan los webhooks. Es posible crear un nombre de host estático mediante la actualización a un nivel de pago ngrok.
Encender y probar
Y ya está. El Nexmo Helper de Papá Noel está listo para desplegarse. Inicie IIS Express y envíe mensajes. Esto puede ser alcanzado a través de cualquier canal configurado para llegar a la aplicación de mensajes.
He aquí un ejemplo de Facebook:

Y una de SMS:

Pues ya está, el Nexmo Ayudante de Papá Noel ya está en marcha.
Para saber más
El código fuente completo de esta demostración se encuentra en GitHub
Para más información sobre QnAMaker, visite su sitio web aquí
Para robots totalmente interactivos, echa un vistazo a Luis Ai
Para obtener más información sobre la API Messages API de Nexmo, consulte la documentación en Desarrollador Nexmo
Para más APIs de Nexmo, visite nuestro Sitio para desarrolladores
Para comprobar el Nexmo .NET SDK puedes echar un vistazo a nuestro GitHub Repo
