働くプログラマ

相談室 (第 4 回): Feliza が自分の声を手に入れる

Ted Neward

 

Ted Newardいよいよ、最後の追い込みです。このシリーズ最初のコラムでは Tropo を紹介しました (msdn.microsoft.com/magazine/hh781028 (英語) 参照)。第 2 回は、簡単な文字列を解析するからくりを使って、ちょっとした精神科医のような音声を発する Feliza という F# アセンブリを紹介しました (msdn.microsoft.com/magazine/hh852597 (英語) 参照)。第 3 回は、最終的には Feliza に繋がるように、Tropo を Feliza Web サイトに接続しました。その結果、Tropo は独自のサーバーでホストされるスクリプトではなく、Web サイトからコマンドを受け取るようになります (msdn.microsoft.com/magazine/hh975378 参照)。今回は、こうした接続作業を完了し、Feliza を世界に旅立たせます。

前回の最後を思い出してみます。Feliza が電話に応対するという夢は、現在のところ Tropo API によって制限を受けています。しかし、Feliza は SMS (425-247-3096) や IM (登録済み Jabber ハンドルの feliza@tropo.im を使用) を問題なく利用できるので、このまま続けましょう。Tropo は、選択肢の固定セットがあれば、音声入力を簡単に処理できました。そのため Feliza を固定の入力に限定すれば、簡単に音声処理に戻れます。ただし、Feliza の目的を考えると、次のような応答では優れたユーザー エクスペリエンスとは言えません。「こんにちは。落ち込んでいるのなら「落ち込んでいる」と言ってください。気分が悪いだけならば「気分が悪い」と言ってください。自動セラピー システムを強制終了したければ、「もうおしまい」と言ってください...」。

制限はありますが、Feliza はまだ人々の役に立ちます (おそらくなんらかの形で)。ですから、続きに取り組みましょう。Feliza は Tropo が発生させる Tropo REST/JSON 要求への応答方法を把握している Web サイトを利用する必要がありました。そのため、着信する JSON を解釈し、Feliza に渡して、応答を生成できる必要があります。つまり、着信した JSON を表す ASP.NET MVC 構造と、送信する応答を表す ASP.NET MVC 構造が必要です。

図 1 に示すように、これに対応する Microsoft .NET Framework クラスの作成は、非常に簡単です (これにより、ASP.NET MVC が厄介な JSON の解析を行えるようになります)。

図 1 Tropo 構造を JSON オブジェクトに対応させる .NET クラス

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; }
};

図 1 に示したプロパティのすべてが常に生成されるわけではありませんが、Tropo 構造を JSON オブジェクトに対応付ける方法の例として役立ちます。

そのため、着信する JSON ("session" オブジェクト) を解析し、initialText フィールドからテキストを抽出し、応答オブジェクトを生成して返す、非常にわかりやすい例になります (図 2 参照)。

図 2 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);
}

残念ながら、ASP.NET MVC JSON シリアライザーの制限事項に直面します。これは、返されたオブジェクトの null 値はすべて JSON がシリアル化する応答でも "null" 値になり、Tropo は null 値の JSON フィールドの処理で問題に直面するためです。さいわい、この問題にはカスタム JSON シリアライザーを使用して対処できます (この場合は、James Newton-King が作成したすばらしい Json.NET シリアライザー (bit.ly/a27Ou、英語) を使用できます)。これにより、null 値をシリアル化しないように構成できます。このためコードを少し変更して、標準の結果の代わりにカスタムの ActionResult (bit.ly/1DVucR、英語) を返すようにします (図 3 参照)。

図 3 カスタマイズした ActionResult

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();
    }
  }
}

次に、コントローラーで、JSON ベースの結果の代わりにこの ActionResult を使用し、null 値を返さないように構成します。

[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;
}

これで、自宅でコードを試している読者は、サーバーが着信 SMS メッセージを受け取り、着信テキストを選択して、応答を生成できるようになります。次に行うことは予想できるでしょう。‏

初めまして、Feliza さん

それでは、このシリーズ第 2 回のコラムの Feliza F# バイナリを取り出し、回路を完成させましょう。以前のコードのコンパイルしたバイナリをコピーするか、必要に応じて、ASP.NET MVC Tropo ソリューションに (2 つ目の) F# Library プロジェクトを作成し、TropoApp プロジェクトと Feliza プロジェクトとの依存関係を作成してマークし、コードをコピーします (ASP.NET MVC プロジェクトは、F# の依存関係 (FSharp.Core.dll) も把握するようにします)。

これで、応答の生成は着信テキストの取得と同様簡単になったので、Feliza1 の "respond" (応答) メソッドに渡し、返される JSON に結果をキャプチャします (図 4 参照)。

図 4 応答を生成する

[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;
}

これは本当に簡単です。電話が近くにあるなら取り出してみてください。Feliza にテキスト メッセージを送信すると会話のように応答が返ってくるでしょう。Feliza は世界で最も賢いチャットボットではありませんが、背後の F# コアを使って、応答を改良するためにできることはたくさんあります。

アプリケーションに音声または SMS を組み込む

Feliza は完成しました。これで、だれかが夜更かしし、ただテキスト メッセージを返してほしい場合には Feliza がいつでも応答するので、私たちは安心して眠れます。もちろん、Feliza はほとんどの場合、自分の読んでいるものを理解しないので、非常に満足できる会話にはなりません。

Tropo API には奇妙な動作もありますが、非常に役立つことは明らかです。ホストされたスクリプトと "Web API" の 1 組のモデルにより、アプリケーションに音声、SMS、およびインスタント メッセージを組み込む方法を柔軟に検討できます。

Feliza の Web サイトも、さまざまな方法で強化できます。たとえば、モバイルの Web を志向する人にとっては、jQuery を使用する静的 HTML5 ページのセットを作成し、Feliza を入力したテキストにヒットさせ、ページに応答を追加するのが適切だと考えるでしょう。基本的に、Tropo は入力を受信する "チャネル" の新しいセットを提供し、理想的にはそれらのチャネルが単一セットの JSON 構造に入力を標準化し、ASP.NET MVC エンドポイントのコードを簡略化します。また、Tropo の機能は、顧客かユーザーの他の通信手段と連携させるのに適しています。追加するのが明らかに興味深い他のチャネルとしては、Twitter や Facebook などがあります (ところで、Tropo には特定のチャネルを簡略化する Twitter に接続する機能はありますが、Facebook チャネルは手動で構築する必要があります)。

ここでは、他のトピックが差し迫っているので、Feliza とはお別れし、次に進みましょう。さびしくなったときにはいつでも Feliza がいるので、心配しないでください。それでは...

コーディングを楽しんでください。

Ted Neward は、Neudesic LLC のアーキテクチャ コンサルタントです。これまでに 100 本を超える記事を執筆している Ted は、C# MVP であり、INETA の講演者でもあります。さまざまな書籍を執筆および共同執筆していて、『Professional F# 2.0』(Wrox 2010 年、英語) もそのうちの 1 冊です。彼は定期的にコンサルティングを行い、開発者を指導しています。彼の連絡先は ted@tedneward.com (英語のみ) です。彼がチームの作業に加わることに興味を持ったり、ブログをご覧になったりする場合は、blogs.tedneward.com (英語) にアクセスしてください。