読者です 読者をやめる 読者になる 読者になる

ぴよぴよエンジニアの日記

クラウドベンダーに勤める見習いSEの日記です。発言は私自身の見解であり、必ずしも所属組織の立場、戦略、意見を代表するものではありません。

WPF で IBM Watson SDK for .NET Standard を使ってみた(Conversation 編)

santea.hateblo.jp

先日、IBM Watson に .NET Standard 用の SDK があることを紹介しました.

今回はこの SDK の Watson Conversation を使って WPF(Windows Presentation Foundation) のチャットアプリを作ってみます.


www.ibm.com

Watson Conversation の公式ページはこちら.


以下、Bluemix 上の Watson Conversation と、 WPF の実装について説明していきます.



Watson Conversation

https://www.change-makers.jp/technology/11268

Conversation の具体的な使用方法はこちらが分かりやすいです.
多少 UI の変更などありますが、おおよそニュアンスで読み取れる範囲です.

GUI 上でモデルを構築していくのは直観的でそれなりに使いやすいと思います.


f:id:Santea:20170416132821p:plain

今回作ったモデルは非常に簡易なもので、

  1. ピザの種類を聞く
  2. サイズを聞く

この2つしか機能はありません.

この程度ならば5分も掛からず構築できます.簡単!



WPF の実装

www.nuget.org

IBM Watson SDK for .NET Standard は .NET Standard 1.6 用なので、まずは .NET Framework 4.7 の WPF プロジェクトを作りましょう.


f:id:Santea:20170416134123p:plain

.NET Framework 4.7 を選びます.
(一覧に表示されない場合は、"Windows 10 Creators Update" を当てたあと、Visual Studio 2017 に "NET Framework 4.7 開発ツール" を追加してください)


f:id:Santea:20170416134704p:plain

NuGet パッケージマネージャーで IBM.WatsonDeveloperCloud.Conversation を追加します.
(同時に IBM.WatsonDeveloperCloud が追加されます)


f:id:Santea:20170416135245p:plain

素の状態だと実行時に System.Security.VerificationException が発生するので、System.Net.Http を最新版(現時点では 4.3.1)に上げます.


以上で準備は完了です.

以下は具体的な実装になります.



namespace WpfConversationSample
{
    public class MessageModel
    {
        public string Message { get; set; }
        public bool IsUserMessage { get; set; }
    }
}

モデルはメッセージと、ユーザのメッセージか Watson の応答かを判別する IsUserMessage を持ちます.



<Window x:Class="WpfConversationSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfConversationSample"
        mc:Ignorable="d"
        Title="WPF Conversation Sample" Height="350" Width="525">

    <Window.Resources>
        <local:ColorConverter x:Key="ColorConverter" />
        <local:AlignmentConverter x:Key="AlignmentConverter" />
        <DataTemplate x:Key="MessageTemplate">
            <Border Background="{Binding IsUserMessage, Converter={StaticResource ColorConverter}}"
                    CornerRadius="20" 
                    Padding="5" 
                    Margin="5"
                    HorizontalAlignment="{Binding IsUserMessage, Converter={StaticResource AlignmentConverter}}">
                <Grid>
                    <Label Content="{Binding Message}"/>
                </Grid>
            </Border>
        </DataTemplate>
    </Window.Resources>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <ListBox 
            ItemsSource="{Binding MessageCollection}" 
            ItemTemplate="{StaticResource MessageTemplate}" 
            HorizontalContentAlignment="Stretch" 
            IsSynchronizedWithCurrentItem="True" 
            Background="#7294c2"/>

        <Grid Grid.Row="1">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="Auto"/>
            </Grid.ColumnDefinitions>

            <TextBox Grid.Column="0"
                     Text="{Binding Message.Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
            <Button Grid.Column="1"
                    Padding="10,0,10,0"
                    Content="Send"
                    Command="{Binding MessageCommand}"
                    CommandParameter="{Binding Message.Value}"/>
        </Grid>
        
    </Grid>

</Window>

View は大きく ListBox, TextBox, Button で構成されています.

モデルの IsUserMessage を ColorConverter と AlignmentConverter で変換することでメッセージの送信者ごとに見た目を変えています.
(ColorConverter と AlignmentConverter の実装は説明を省略します)



using IBM.WatsonDeveloperCloud.Conversation.v1;
using IBM.WatsonDeveloperCloud.Conversation.v1.Model;
using Reactive.Bindings;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Input;
using System.Security;

[assembly: SecurityRules(SecurityRuleSet.Level1, SkipVerificationInFullTrust = true)]
namespace WpfConversationSample
{
    public partial class MainWindow : Window
    {
        public ObservableCollection<MessageModel> MessageCollection { get; set; }
        public ReactiveProperty<string> Message { get; set; }
        public ICommand MessageCommand { get; private set; }
        private ConversationService _conversation;
        private Context _context;

        public MainWindow()
        {
            InitializeComponent();
            DataContext = this;

            MessageCollection = new ObservableCollection<MessageModel>();
            Message = new ReactiveProperty<string>();
            MessageCommand = new DelegateCommand<string>(ExecuteMessageCommand);

            _conversation = new ConversationService();
            _conversation.SetCredential("<username>", "<password>");

            // Watson の初期メッセージを受け取るために明示的に呼び出す
            ExecuteMessageCommand("");
        }

        private void ExecuteMessageCommand(string message)
        {
            Message.Value = "";

            if (!string.IsNullOrEmpty(message))
            {
                MessageCollection.Add(new MessageModel
                {
                    Message = message,
                    IsUserMessage = true
                });
            }

            var messageRequest = new MessageRequest()
            {
                Input = new InputData()
                {
                    Text = message
                },
                Context = _context
            };

            var result = _conversation.Message("<workspace-id>", messageRequest);
            _context = result.Context;
            
            MessageCollection.Add(new MessageModel
            {
                Message = result.Output.Text[0],
                IsUserMessage = false
            });

        }
    }
}

今回は ViewModel を用いず、コードビハインドに直書きしました.

Conversation SDK の部分をピックアップして説明します.



_conversation = new ConversationService();
_conversation.SetCredential("<username>", "<password>");

ConversationService の初期化を行います.


f:id:Santea:20170416141019p:plain

ユーザ名とパスワードは Conversation のワークスペース作成画面の値を用います.



private void ExecuteMessageCommand(string message)
{
    var messageRequest = new MessageRequest()
    {
        Input = new InputData()
        {
            Text = message
        },
        Context = _context
    };

     var result = _conversation.Message("<workspace-id>", messageRequest);
    _context = result.Context;
            
    MessageCollection.Add(new MessageModel
    {
        Message = result.Output.Text[0],
        IsUserMessage = false
    });

}

ExecuteMessageCommand の SDK に関わる部分を抜粋しました.

ポイントは MessageRequest 生成時に Context を保持しておいて代入することです.
Context は会話の履歴のようなもので、これが無いと Watson は初期状態のレスポンスを繰り返します.
(今回の例だと「こんにちは!ご注文をどうぞ!」を繰り返します)

ConversationService#Message の "workspace-id" の引数は、先程のワークスペース作成画面の WorkspaceID です.


f:id:Santea:20170416143535g:plain

こちらが実装結果になります.



まとめ

今回は IBM Watson SDK for .NET Standard の Conversation を試してみました.
簡単にチャットボットを作れるのでとても有用だと思います.

SDK としては .NET Standard 1.6 用であるというなかなかに厳しい制約があるので、現状発展途上と言わざるを得ません.
(Xamarin で使えなかったりします)

ただ、.NET Standard は今後まさしくスタンダードな選択肢になっていく(スタンダードになりますよね?)と思うので、その SDK が開発されているのは非常に良いと思います.

次回以降もこの SDK を試していきたいと思います.


以上です.




参考