Compartir a través de


Integrar el propio canal personalizado con Direct Line

Con la Plataforma omnicanal para Customer Service, puede implementar un conector para integrar canales de mensajería personalizados mediante Direct Line API 3.0, que forma parte del SDK de .NET. El código de ejemplo completo ilustra cómo puede crear su propio conector. Para obtener más información sobre la API de Direct Line 3.0, consulte Conceptos clave de la API de Direct Line 3.0.

En este artículo se explica cómo un canal se conecta al Microsoft Direct Line Bot Framework, que está conectado internamente a Omnichannel para el Servicio al Cliente. En la siguiente sección se incluyen fragmentos de código que utilizan Direct Line API 3.0 para crear un cliente de Direct Line y la IChannelAdapter interfaz para crear un conector de ejemplo.

Nota:

El código fuente y la documentación describen el flujo general de cómo el canal puede conectarse a la Plataforma omnicanal para Customer Service a través de Direct Line, y no se centran en aspectos de confiabilidad y escalabilidad.

Componentes

Servicio de API de Webhook de adaptador

Cuando el usuario escribe un mensaje, se invoca la API del adaptador desde el canal. Procesa la solicitud entrante y envía un estado de éxito o error como respuesta. El servicio de API del adaptador debe implementar la IChannelAdapter interfaz y envía la solicitud entrante al adaptador de canal respectivo para procesar la solicitud.

/// <summary>
/// Accept an incoming web-hook request from MessageBird Channel
/// </summary>
/// <param name="requestPayload">Inbound request Object</param>
/// <returns>Executes the result operation of the action method asynchronously.</returns>
    [HttpPost("postactivityasync")]
    public async Task<IActionResult> PostActivityAsync(JToken requestPayload)
    {
        if (requestPayload == null)
        {
            return BadRequest("Request payload is invalid.");
        }

        try
        {
            await _messageBirdAdapter.ProcessInboundActivitiesAsync(requestPayload, Request).ConfigureAwait(false);
        }
        catch (Exception ex)
        {
            _logger.LogError($"postactivityasync: {ex}");
            return StatusCode(500, "An error occured while handling your request.");
        }

        return StatusCode(200);
    }

Adaptadores de canal

El adaptador de canal procesa las actividades de entrada y salida, y debe implementar la IAdapterBuilder interfaz.

Procesar actividades entrantes

El adaptador de canal realiza las siguientes actividades entrantes:

  1. Valide la firma de la solicitud de mensaje entrante.

La solicitud entrante del canal se valida en función de la clave de firma. Si la solicitud no es válida, se genera un mensaje de excepción de "firma no válida". Si la solicitud es válida, procede de la siguiente manera:

  /// <summary>
  /// Validate Message Bird Request
  /// </summary>
  /// <param name="content">Request Content</param>
  /// <param name="request">HTTP Request</param>
  /// <param name="messageBirdSigningKey">Message Bird Signing Key</param>
  /// <returns>True if there request is valid, false if there aren't.</returns>
  public static bool ValidateMessageBirdRequest(string content, HttpRequest request, string messageBirdSigningKey)
  {
      if (string.IsNullOrWhiteSpace(messageBirdSigningKey))
      {
          throw new ArgumentNullException(nameof(messageBirdSigningKey));
      }
      if (request == null)
      {
          throw new ArgumentNullException(nameof(request));
      }
      if (string.IsNullOrWhiteSpace(content))
      {
          throw new ArgumentNullException(nameof(content));
      }
      var messageBirdRequest = new MessageBirdRequest(
          request.Headers?["Messagebird-Request-Timestamp"],
          request.QueryString.Value?.Equals("?",
              StringComparison.CurrentCulture) != null
              ? string.Empty
              : request.QueryString.Value,
          GetBytes(content));

      var messageBirdRequestSigner = new MessageBirdRequestSigner(GetBytes(messageBirdSigningKey));
      string expectedSignature = request.Headers?["Messagebird-Signature"];
      return messageBirdRequestSigner.IsMatch(expectedSignature, messageBirdRequest);
  }
  1. Convierta la solicitud entrante en una actividad de bot.

La carga de la solicitud entrante se convierte en una actividad que Bot Framework puede entender.

Nota:

La carga útil de la actividad no debe superar el límite de tamaño del mensaje de 28 KB.

Este objeto Activity incluye los siguientes atributos:

Atributo Descripción
de Almacena la información de la cuenta del canal que consta del identificador único del usuario y el nombre (combinación de nombre y apellido, separados por un delimitador de espacio).
channelId Indica el identificador del canal. En el caso de las solicitudes entrantes, el ID de canal es directline.
serviceUrl Indica la dirección URL del servicio. En el caso de las solicitudes entrantes, la URL del servicio es https://directline.botframework.com/.
tipo Indica el tipo de actividad. Para actividades de mensajes, el tipo es message.
Mensaje de texto Almacena el contenido del mensaje.
ID Indica el identificador que utiliza el adaptador para responder a los mensajes salientes.
channelData Indica los datos de canal que constan de channelType, conversationcontexty customercontext.
tipo de canal Indica el nombre del canal a través del cual el cliente envía mensajes. Por ejemplo, MessageBird, KakaoTalk, Snapchat
ConversationContext Hace referencia a un objeto de diccionario que contiene las variables de contexto definidas en la secuencia de trabajo. La Plataforma omnicanal para el servicio de atención al cliente utiliza esta información para dirigir la conversación al representante de servicio al cliente adecuado (representante de servicio o representante). Por ejemplo:
"conversationcontext ":{ "ProductName" : "Xbox", "Issue":"Installation" }
En este ejemplo, el contexto enruta la conversación al representante de servicio que se ocupa de la instalación de Xbox.
Contexto del cliente Hace referencia a un objeto de diccionario que contiene los datos del cliente, como el número de teléfono y la dirección de correo electrónico. La Plataforma omnicanal para el servicio de atención al cliente utiliza esta información para identificar el registro de contacto del usuario.
"customercontext":{ "email":email@email.com, "número_de_teléfono":"1234567890" }
  /// <summary>
  /// Build Bot Activity type from the inbound MessageBird request payload<see cref="Activity"/>
  /// </summary>
  /// <param name = "messagePayload"> Message Bird Activity Payload</param>
  /// <returns>Direct Line Activity</returns>
  public static Activity PayloadToActivity(MessageBirdRequestModel messagePayload)
  {
  if (messagePayload == null)
  {
      throw new ArgumentNullException(nameof(messagePayload));
  }
  if (messagePayload.Message?.Direction == ConversationMessageDirection.Sent ||
  messagePayload.Type == ConversationWebhookMessageType.MessageUpdated)
  {
      return null;
  }
  var channelData = new ActivityExtension
  {
      ChannelType = ChannelType.MessageBird,
      // Add Conversation Context in below dictionary object. Please refer the document for more information.
      ConversationContext = new Dictionary<string, string>(),
      // Add Customer Context in below dictionary object. Please refer the document for more information.
      CustomerContext = new Dictionary<string, string>()
  };
  var activity = new Activity
      {
          From = new ChannelAccount(messagePayload.Message?.From, messagePayload.Contact?.DisplayName),
          Text = messagePayload.Message?.Content?.Text,
          Type = ActivityTypes.Message,
          Id = messagePayload.Message?.ChannelId,
          ServiceUrl = Constant.DirectLineBotServiceUrl,
          ChannelData = channelData
      };

      return activity;
  }

La carga JSON de ejemplo es la siguiente:

{
    "type": "message",
    "id": "bf3cc9a2f5de...",    
    "serviceUrl": https://directline.botframework.com/,
    "channelId": "directline",
    "from": {
        "id": "1234abcd",// userid which uniquely identify the user
        "name": "customer name" // customer name as First Name <space> Last Name
    },
    "text": "Hi,how are you today.",
    "channeldata":{
        "channeltype":"messageBird",
        "conversationcontext ":{ // this holds context variables defined in Workstream
            "ProductName" : "XBox",
            "Issue":"Installation"
        },
        "customercontext":{            
            "email":email@email.com,
            "phonenumber":"1234567890"           
        }
    }
}

  1. Envíe la actividad al procesador de retransmisión de mensajes.

Después de crear la carga útil de la actividad, llama al método PostActivityAsync del procesador de retransmisión de mensajes para enviar la actividad a Direct Line. El adaptador de canal también debe pasar el controlador de eventos, que el procesador de retransmisión invocará cuando reciba un mensaje saliente de Plataforma omnicanal para Customer Service a través de Direct Line.

Procesar actividades salientes

El procesador de retransmisión invoca el controlador de eventos para enviar actividades de salida al adaptador de canal respectivo y, a continuación, el adaptador procesa las actividades de salida. El adaptador de canal realiza las siguientes actividades de salida:

  1. Convertir las actividades salientes al modelo de respuesta del canal.

Las actividades de Direct Line se convierten al modelo de respuesta específico del canal.

  /// <summary>
  /// Creates MessageBird response object from a Bot Framework <see cref="Activity"/>.
  /// </summary>
  /// <param name="activities">The outbound activities.</param>
  /// <param name="replyToId">Reply Id of Message Bird user.</param>
  /// <returns>List of MessageBird Responses.</returns>
  public static List<MessageBirdResponseModel> ActivityToMessageBird(IList<Activity> activities, string replyToId)
  {
      if (string.IsNullOrWhiteSpace(replyToId))
      {
          throw new ArgumentNullException(nameof(replyToId));
      }

      if (activities == null)
      {
          throw new ArgumentNullException(nameof(activities));
      }

      return activities.Select(activity => new MessageBirdResponseModel
      {
          To = replyToId,
          From = activity.ChannelId,
          Type = "text",
          Content = new Content
          {
              Text = activity.Text
          }
      }).ToList();
  }
  1. Envíe respuestas a través de la API de REST del canal.

El adaptador de canal llama a la API de REST para enviar una respuesta saliente al canal, que luego se envía al usuario.

  /// <summary>
  /// Send Outbound Messages to Message Bird
  /// </summary>
  /// <param name="messageBirdResponses">Message Bird Response object</param>
  /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
  public async Task SendMessagesToMessageBird(IList<MessageBirdResponseModel> messageBirdResponses)
  {
      if (messageBirdResponses == null)
      {
          throw new ArgumentNullException(nameof(messageBirdResponses));
      }

      foreach (var messageBirdResponse in messageBirdResponses)
      {
          using (var request = new HttpRequestMessage(HttpMethod.Post, $"{MessageBirdDefaultApi}/send"))
          {
              var content = JsonConvert.SerializeObject(messageBirdResponse);
              request.Content = new StringContent(content, Encoding.UTF8, "application/json");
              await _httpClient.SendAsync(request).ConfigureAwait(false);
          }
      }
  }

Procesador de retransmisión de mensajes

El procesador de retransmisión de mensajes recibe la actividad entrante del adaptador de canal y realiza la validación del modelo de actividad. Antes de enviar esta actividad a Direct Line, el procesador de retransmisión comprueba si la conversación está activa para la actividad en particular.

Para ver si la conversación está activa, el procesador de retransmisión mantiene una colección de conversaciones activas en un diccionario. Este diccionario contiene key como ID de usuario, que identifica de forma única al usuario y Value como un objeto de la siguiente clase:

 /// <summary>
/// Direct Line Conversation to store as an Active Conversation
/// </summary>
public class DirectLineConversation
{
    /// <summary>
    /// .NET SDK Client to connect to Direct Line Bot
    /// </summary>
    public DirectLineClient DirectLineClient { get; set; }

    /// <summary>
    /// Direct Line response after start a new conversation
    /// </summary>
    public Conversation Conversation { get; set; }

    /// <summary>
    /// Watermark to guarantee that no messages are lost
    /// </summary>
    public string WaterMark { get; set; }
}

Si la conversación no está activa para la actividad recibida por el procesador de retransmisión, realiza los siguientes pasos:

  1. Inicia una conversación con Direct Line y almacena el objeto de conversación enviado por Direct Line contra el ID de usuario en el diccionario.
 /// <summary>
 /// Initiate Conversation with Direct Line Bot
 /// </summary>
 /// <param name="inboundActivity">Inbound message from Aggregator/Channel</param>
 /// <param name="adapterCallBackHandler">Call Back to send activities to Messaging API</param>
 /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
 private async Task InitiateConversation(Activity inboundActivity, EventHandler<IList<Activity>> adapterCallBackHandler)
 {
     var directLineConversation = new DirectLineConversation
     {
         DirectLineClient = new DirectLineClient(_relayProcessorConfiguration.Value.DirectLineSecret)
     };
     // Start a conversation with Direct Line Bot
     directLineConversation.Conversation = await directLineConversation.DirectLineClient.Conversations.
         StartConversationAsync().ConfigureAwait(false);

     await directLineConversation.DirectLineClient.Conversations.
         StartConversationAsync().ConfigureAwait(false);
     if (directLineConversation.Conversation == null)
     {
         throw new Exception(
             "An error occurred while starting the Conversation with direct line. Please validate the direct line secret in the configuration file.");
     }

     // Adding the Direct Line Conversation object to the lookup dictionary and starting a thread to poll the activities from the direct line bot.
     if (ActiveConversationCache.ActiveConversations.TryAdd(inboundActivity.From.Id, directLineConversation))
     {
         // Starts a new thread to poll the activities from Direct Line Bot
         new Thread(async () => await PollActivitiesFromBotAsync(
             directLineConversation.Conversation.ConversationId, inboundActivity, adapterCallBackHandler).ConfigureAwait(false))
         .Start();
     }
 }
  1. Inicia un nuevo subproceso para sondear las actividades de salida del bot de Direct Line en función del intervalo de sondeo configurado en el archivo de configuración. El hilo de sondeo está activo hasta que se recibe el final de la actividad de conversación desde Direct Line.
/// <summary>
/// Polling the activities from BOT for the active conversation
/// </summary>
/// <param name="conversationId">Direct Line Conversation Id</param>
/// <param name="inboundActivity">Inbound Activity from Channel/Aggregator</param>
/// <param name="lineActivitiesReceived">Call Back to send activities to Messaging API</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
private async Task PollActivitiesFromBotAsync(string conversationId, Activity inboundActivity, EventHandler<IList<Activity>> lineActivitiesReceived)
{
    if (!int.TryParse(_relayProcessorConfiguration.Value.PollingIntervalInMilliseconds, out var pollingInterval))
    {
        throw new FormatException($"Invalid Configuration value of PollingIntervalInMilliseconds: {_relayProcessorConfiguration.Value.PollingIntervalInMilliseconds}");
    }
    if (!ActiveConversationCache.ActiveConversations.TryGetValue(inboundActivity.From.Id,
        out var conversationContext))
    {
        throw new KeyNotFoundException($"No active conversation found for {inboundActivity.From.Id}");
    }
    while (true)
    {
        var watermark = conversationContext.WaterMark;
        // Retrieve the activity set from the bot.
        var activitySet = await conversationContext.DirectLineClient.Conversations.
            GetActivitiesAsync(conversationId, watermark).ConfigureAwait(false);
        // Set the watermark to the message received
        watermark = activitySet?.Watermark;

        // Extract the activities sent from our bot.
        if (activitySet != null)
        {
            var activities = (from activity in activitySet.Activities
                              where activity.From.Id == _relayProcessorConfiguration.Value.BotHandle
                              select activity).ToList();
            if (activities.Count > 0)
            {
                SendReplyActivity(activities, inboundActivity, lineActivitiesReceived);
            }
            // Update Watermark
            ActiveConversationCache.ActiveConversations[inboundActivity.From.Id].WaterMark = watermark;
            if (activities.Exists(a => a.Type.Equals("endOfConversation", StringComparison.InvariantCulture)))
            {
                if (ActiveConversationCache.ActiveConversations.TryRemove(inboundActivity.From.Id, out _))
                {
                    Thread.CurrentThread.Abort();
                }
            }
        }
        await Task.Delay(TimeSpan.FromMilliseconds(pollingInterval)).ConfigureAwait(false);
    }
}

Nota:

En el corazón del código que recibe el mensaje se encuentra el método GetActivitiesAsync que toma ConversationId y watermark como parámetros. El propósito del watermark parámetro es recuperar los mensajes que aún no se entregan por Direct Line. Si se especifica el parámetro de marca de agua, la conversación se reproduce desde la marca de agua, de modo que no se pierde ningún mensaje.

Enviar la actividad a Direct Line

 /// <summary>
 /// Send the activity to the bot using Direct Line client
 /// </summary>
 /// <param name="inboundActivity">Inbound message from Aggregator/Channel</param>
 /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
 private static async Task SendActivityToBotAsync(Activity inboundActivity)
 {
     if (!ActiveConversationCache.ActiveConversations.TryGetValue(inboundActivity.From.Id,
         out var conversationContext))
     {
         throw new KeyNotFoundException($"No active conversation found for {inboundActivity.From.Id}");
     }
     await conversationContext.DirectLineClient.Conversations.PostActivityAsync(
         conversationContext.Conversation.ConversationId, inboundActivity).ConfigureAwait(false);
 }

Si la conversación está activa para la actividad recibida por el procesador de retransmisión, envía la actividad al procesador de retransmisión de mensajes.

Finalizar una conversación

Para finalizar la conversación, consulte Finalizar una conversación en Direct Line.

Pasos siguientes

Soporte para chat en vivo y canales asincrónicos
Formatos de Markdown en canales personalizados que utilizan Direct Line

Configurar el canal de mensajería personalizado
Referencia de la API de MessageBird
Prácticas recomendadas para la configuración de bots