O programador

Fale comigo, Parte 4: Feliza ganha espaço

Ted Neward

 

Ted NewardEstamos arrancando para a linha de chegada agora. A primeira coluna desta série introduziu Tropo (consulte msdn.microsoft.com/magazine/hh781028). A segunda parte introduziu Feliza, um assembly F# que pratica alguns truques de análise de cadeia de caracteres simples para se parecer razoavelmente como um mau psiquiatra (consulte msdn.microsoft.com/magazine/hh852597). A terceira parte conectou Tropo ao site da Feliza (então, Tropo estava recebendo seus comandos do site em vez de um script hospedado em seus próprios servidores) para que pudéssemos fazer a conexão final para ter Feliza (consulte msdn.microsoft.com/magazine/hh975378). E agora, concluímos o trabalho de conexão e soltamos Feliza no mundo.

Lembre-se da última parte, que nosso sonho de Feliza poder receber chamadas telefônicas é, no momento, limitado pela API Tropo, mas Feliza não tem problemas com SMS—em 425-247-3096—ou com mensagens instantâneas (usando o identificador registrado Jabber de feliza@tropo.im), portanto, continuaremos com isso. Lembre-se, Tropo pode facilmente manipular entradas de voz se houver um conjunto vinculado de opções do qual escolher, portanto, se quiséssemos limitar Feliza a entrada fixa, poderíamos facilmente voltar para voz. No entanto, para os objetivos de Feliza, isso não me parece uma grande experiência de usuário: Alô! Se estiver deprimido, diga ‘deprimido’; se estiver apenas se sentindo mal, diga ‘sentindo mal’; se estiver pronto para matar um sistema de terapia automatizado, diga ‘morra, morra, morra’ …”

Apesar da limitação, Feliza ainda pode ajudar pessoas (bem, mais ou menos), então vamos continuar. Lembre-se de que precisamos que Feliza fique por detrás de um site que sabe como responder às solicitações REST/JSON que o Tropo nos envia, portanto, precisamos poder interpretar o JSON de entrada, entregá-lo a Feliza e, então, gerar a resposta. Isso significa que precisamos de uma estrutura ASP.NET MVC para representar o JSON de entrada, e um para representar a resposta de saída.

Criar uma classe do Microsoft .NET Framework que se compare a isso (de modo que ASP.NET MVC possa fazer o trabalho pesado de analisar o JSON) é bastante direto, como mostrado na Figura 1.

Figura 1 A classe do .NET para comparar as estruturas do Tropo aos objetos JSON

public class Session
{
  public string accountId { get; set; }
  public string callId { get; set; }
  public class From
  {
    public string id { get; set; }
    public string name { get; set; }
    public string channel { get; set; }
    public string network { get; set; }
  }
  public From from { get; set; }
  public class To
  {
    public string id { get; set; }
    public string name { get; set; }
    public string channel { get; set; }
    public string network { get; set; }
  }
  public To to { get; set; }
  public string id { get; set; }
  public string initialText { get; set; }
  public string timestamp { get; set; }
  public string userType { get; set; }
};

Nem todas as propriedades mostradas na Figura 1 serão sempre preenchidas, mas ele fornece um exemplar prático de como mapear as estruturas do Tropo em relação a um objeto JSON.

Assim, pareceria ser um caso bastante direto analisar o JSON de entrada (um objeto “sessão”), extrair o texto do campo initialText, gerar um objeto de resposta e entregá-lo de volta , como mostrado na Figura 2.

Figura 2 Analisando o JSON

[AcceptVerbs("GET", "POST")]
public ActionResult Index(Tropo.JSON.Session session)
{
  string incoming = session.initialText;
  object felizaResponse = new
  {
    tropo = new object[]
    {
      new
      {
        ask = new Tropo.JSON.Ask()
        {
          say = new Tropo.JSON.Say() {
          value = "I'm Feliza. What about '" + incoming +
            "' would you like to talk about?"
          },
          choices = new Tropo.JSON.Ask.Choices() {
            value = "[ANY]"
          }
        }
      }
    }
  };
  return Json(felizaResponse);
}

Infelizmente, nos deparamos com uma limitação do serializador ASP.NET MVC JSON, pois qualquer valor nulo que ele vê no objeto retornado será convertido em valores “nulos” na resposta serializada pelo JSON, e o Tropo tem alguns problemas com campos do JSON de valores nulos. Felizmente, podemos corrigir isso usando um serializador JSON personalizado (nesse caso, o excelente serializador Json.NET de James Newton-King em bit.ly/a27Ou), que pode ser configurado para não serializar valores nulos. Isso muda um pouco o código para retornar um ActionResult personalizado (gratamente obtido de bit.ly/1DVucR) em vez do padrão (consulte a Figura 3).

Figura 3 Um ActionResult personalizado

public class JsonNetResult : ActionResult
{
  public Encoding ContentEncoding { get; set; }
  public string ContentType { get; set; }
  public object Data { get; set; }
  public JsonSerializerSettings SerializerSettings { get; set; }
  public Formatting Formatting { get; set; }
  public JsonNetResult() { SerializerSettings =
    new JsonSerializerSettings(); }
  public override void ExecuteResult(ControllerContext context)
  {     
    if (context == null)
      throw new ArgumentNullException("context");
    HttpResponseBase response = context.HttpContext.Response;
    response.ContentType = !string.IsNullOrEmpty(ContentType) ?
      ContentType : "application/json";
    if (ContentEncoding != null)
      response.ContentEncoding = ContentEncoding;
    if (Data != null)
    {
      JsonTextWriter writer =
        new JsonTextWriter(response.Output) 
        { Formatting = Formatting };
      JsonSerializer serializer =
        JsonSerializer.Create(SerializerSettings);
      serializer.Serialize(writer, Data);
      writer.Flush();
    }
  }
}

Então, no Controlador, usamos esse ActionResult em vez do baseado em JSON, garantindo configurá-lo para não enviar de volta valores nulos:

[AcceptVerbs("GET", "POST")]
public ActionResult Index(Tropo.JSON.Session session)
{
  string incoming = session.initialText;
  object felizaResponse = // ...;
  JsonNetResult jsonNetResult = new JsonNetResult();
  jsonNetResult.SerializerSettings.NullValueHandling =
    NullValueHandling.Ignore;
  jsonNetResult.Data = felizaResponse;
  return jsonNetResult;
}

Assim, nesse ponto, para os leitores que estão brincando com o código em casa, o servidor é capaz de receber mensagens SMS de entrada, selecionar o texto de entrada e gerar uma resposta de volta. Você talvez possa imaginar o que vem em seguida.

Prazer em conhecer, Feliza.

É hora de completar o circuito pegando os binários F# da Feliza da segunda coluna dessa série. Copie os binários compilados do código antigo, ou, se preferir, crie um (segundo) projeto de Biblioteca F# na solução Tropo do ASP.NET MVC, marque o projeto TropoApp como dependente do projeto Feliza e copie o código. (Verifique se o projeto ASP.NET MVC também conhece as dependências do F#, particularmente FSharp.Core.dll.)

Bem, gerar a resposta é tão trivial quanto receber o texto de entrada, passá-lo para o método responddo Feliza1 e capturar os resultados no JSON retornado, como mostrado na Figura 4.

Figura 4 Gerando uma resposta

[AcceptVerbs("GET", "POST")]
public ActionResult Index(Tropo.JSON.Session session)
{
  object felizaResponse = new
  {
    tropo = new object[]
    {
      new
      {
        ask = new Tropo.JSON.Ask()
        {
          say = new Tropo.JSON.Say() {
            value = Feliza1.respond(session.initialText)
          },
          choices = new Tropo.JSON.Choices() {
            value = "[ANY]"
          }
        }
      }
    }
  };
  JsonNetResult jsonNetResult = new JsonNetResult();
  jsonNetResult.SerializerSettings.NullValueHandling =
    NullValueHandling.Ignore;
  jsonNetResult.Data = felizaResponse;
  return jsonNetResult;
}

É realmente fácil—e se um telefone estiver acessível, pegue-o e você verá que mandar um texto para Feliza resulta em um tipo de conversação. Ela não é o chat-bot mais esperto do mundo, mas com o núcleo do F# por detrás dela, há muito que podemos fazer para melhorar suas respostas.

Incorporando voz ou SMS nos aplicativos

Feliza está pronta, e agora o mundo pode descansar em paz sabendo que se alguém estiver acordado até tarde querendo apenas que alguém lhe mande um texto, Feliza está pronta e esperando. Certamente, não será uma conversação muito satisfatória, pois na maioria das vezes ela não terá uma dica sobre o que ela está lendo.

A API Tropo tem algumas peculiaridades interessantes, mas claramente há algum potencial significativo aí. Os modelos gêmeos—os scripts hospedados além da “API Web”—oferecem alguma flexibilidade interessante quando pensamos sobre como incorporar voz, SMS ou mensagens instantâneas nos aplicativos.

O site da Feliza também poderia ser aprimorado de diversas maneiras, por exemplo, para os que são mais voltados à Web móvel, seria apropriado criar um conjunto de páginas HTML5 estáticas usando jQuery para alcançar Feliza com o texto digitado e acrescentar as respostas à página. Em essência, Tropo oferece um novo conjunto de “canais” através dos quais pode-se receber entradas, e idealmente esses canais normalizariam as entradas em um conjunto único de estruturas JSON para simplificar o código de ponto de extremidade ASP.NET MVC. E claramente a história do Tropo cairia bem junto com outros meios de comunicação com clientes ou usuários. Outros canais óbvios que seriam interessantes de adicionar são Twitter ou Facebook, por exemplo. (Tropo tem a capacidade de se conectar ao Twitter, a propósito, o que simplificaria esse canal específico, mas um canal do Facebook seria algo que teríamos de criar à mão.)

Outros tópicos estão nos pressionando, portanto, é hora de dizer adeus para Feliza e prosseguir. Não se preocupe, ela ainda estará por aí quando você estiver se sentindo só. Enquanto isso...

Boa codificação.

Ted Neward é um consultor de arquitetura na Neudesic LLC. Ele já escreveu mais de 100 artigos, é um MVP de C# e palestrante da INETA, e autor e coautor de dezenas de livros, incluindo o recém-lançado “Professional F# 2.0” (Wrox, 2010). Ele atua como consultor e mentor regularmente. Entre em contato com ele pelo email ted@tedneward.com se desejar que ele venha trabalhar com sua equipe ou leia seu blog em blogs.tedneward.com.