Chienomi

チャットAIフレームワーク(AIチャットボット) 旧Inflaton Stellaのデザイン

開発::webapp

  • TOP
  • Articles
  • 開発
  • チャットAIフレームワーク(AIチャットボット) 旧Inflaton Stellaのデザイン

旧 “Stella”

私がInflaton, Inc.で作っていたチャットAIフレームワーク。 (AIチャットボット)

開発費も支払われていないので私の手元にあるのだけれど、Stellaという名前を引き継ぐかどうかは未定。 名前を引き継がないとなれば、恐らくは私らしい(花に由来する)名前になるだろうけど、今のところあくまで未定である。

ここでは旧来の名前である「Stella」で言及する。

なお、Stellaのソフトウェアとサービスは既にInflatonにはなく、Mimir Yokohamaで複雑な条件付きで提供可能なものになっている。

Stellaの位置づけ

Stella自体がなにを為すものかというと、基本的には古典的なチャットボットである。

つまり、次のようなものだ。

if msg.include?("おはよう")
  reply("おはようございます。")
end

もうちょっと汎用性のあるデザインだとこんな感じ。

matchlib.each do |pattern, res|
  if msg.include?(pattern)
    reply(res)
    break
  end
end

もちろんこれだとほとんど価値はない。 Twitterのbotでもこういうものはたくさんあり、フリーのサービスでもあまり高品位なものとは言い難い。 そもそもこのコードは多くのプログラミング初学者が一度は書いたことがあるようなコードだ。

Stellaの考え方は根本的なところではこれとあまり違いない。

ただし、多くの機能を盛り込んでおり、ビジネス用途のサポートで利用することを想定している。 また、高度で自然な会話を可能にするチャットAIとしても利用可能である。

また、使い方も古典的なbotに近い。 古典的なボットではパターンと応答メッセージの組(場合によってはそれぞれが1つ以上)からなるが、Stellaも「条件」と「アクション」の組からなる。 このようにAIとしてのロジックをユーザーが手書きするのだが、単純なボットというわけでもない、というあまり類を見ないソフトウェアになっている。

ソフトウェアとサービスのデザイン

Stellaの根幹はそのデザインにある。非常に特殊なソフトウェアなのだ。

なお、StellaではStellaインスタンスを契約して設置する者を「ユーザー」、当該ページに訪問してbotと会話する人を「ヴィジター」と呼んでいる。

フレームワーク

Stellaがやろうとしていることを実現する、という点に絞って話すと、比較的初歩的なプログラミングができればそれほど難しいものではない。 もちろん、対応力の向上に従ってどんどんコードは大きくなってしまうが、書くこと自体は可能だ。

だが、現実問題として「初歩的なプログラミング」というのは「誰でもできる」ではない。 また、プログラミングで実現可能であるとしても、「何を書くべきか」というのは非常に難しく、高度な要求だ。

そこで一般的には機械学習のような「ユーザーは手間いらず」の手法が好まれるのだが、例え本質的にはプログラミングなのだとしても、誰しも専門的なスキルがなければできない、というものではない。

お手本となるのがRPGツクールである。 RPGツクールは「ダンテ」と呼ばれたゲーム制作ソフトウェアのひとつであり、それ自体が一種のゲームとして扱われることが多い。ダンテシリーズの中では恐らく唯一今に至るまで続いているのがRPGツクールだ。 やっていることはプログラミングなのだが、「RPG」という枠組みの元で「制作者の表現部分」のみを書くという形にすることによって作業量を減らし、プログラミング的な要素も言葉に言い換えなどを駆使してわかりやすくしている。

Stellaの考え方もこれと同じだ。Stellaが用意するのは「プログラミング上の制約」である。 Stella自体はRubyで書かれているが、StellaでできることはRubyでできることよりも極めて小さくなる。 だが、その制約から何をすれば良いかが明瞭であり、学習コストも下げ、誰でも簡単に書けるようになるのである。

さらに、Stellaはなんといっても高度に熟練された応答を可能にすることにある。 そのためには、「ユーザーが実際にどのような入力をして、人間的にはどのように回答するのが正解であり、それはどこからそのように解釈するか」というノウハウが必要になる。

本質的にそのノウハウは極めて高度である。 言葉からどのように解釈し、「適切な応答」を導き出すかというのを徹底して追求したのはErinaである。 Erinaはデータベースと調整によって応答を導出したが、この要素については後述する。

だが、Erinaの場合どのようなケースで何をヒントに応答を決定すべきか、というのを複雑なロジックにょって導出している。 これをプログラミング経験もない人が書くことは不可能であり、それを判断するのは極めて難しい。

そこでStellaの場合はできることを限り、なおかつできることをどのように組み立てるべきかというガイドラインを作っている。 できることはルールであり、RPGツクールにおいて実際のRPGで表現可能かどうかとは別にできることが決められているのと同じことである。 なおかつ、ガイドラインに従って記述することにより、小さいものから確実に成果を出すことができるソフトウェアとして利用できる。

Inflatonではルールのほうを変更する要求が多くて困ったが、素直にガイドラインに従って作っていけば本当に簡単に実用的なチャットボットが作れるのだ。

では実際にユーザーは何を書くことを求められるのか?

Context(コンテキストツリー)は1つ以上のコンテキストブランチからなる。

Context: <CXT-Branch>+

コンテキストブランチは1つ以上のコンテキストリーフからなる。 コンテキストリーフはtestとactionsの組だ。 testsは1つ以上のテストコンテキストリーフノード、actionsは1つ以上のアクションコンテキストリーフノードからなる。

<CXT-Branch>: <CXT-LEAF>+

<CXT-LEAF>:
  tests: <TEST>+
  actions: <ACTION>+

そして頭から順にテストしていき、テストが真である場合アクションを実行する、というわけである。

以前の記事で紹介しているが、Lispで書くとこんな感じだ。

'(
  ("Context" (
    ("main" (
      ("tests" (
        ("match" "Hello")
      ))
      ("actions" (
        ("msg" ("Hello, " "world!"))
      ))
    ))
  ))
)

YAMLで書くとこうなる。

Context:
  main:
    -
      tests:
        - [match, Hello]
      actions:
        - [msg, ["Hello,", "world!"]]

このレベルなら比較的すっきり書ける。JSONだと辛い。

{"Context":{"main":[{"tests":[["match","Hello"]],"actions":[["msg",["Hello, ","world!"]]]}]}}

ロジックファイル

Stellaのスクリプトに相当するのがロジックファイルだ。

ロジックファイルはYAMLを採用した。 手書きが可能で交換性があり、なおかつJSONだと手書きが難しい構造であるためである。

ロジックファイルの記述の原則は、YAMLを手書きすることではなくジェネレータを利用することである。 Stellaのロジックファイルは階層の深い配列になるため、手書きはミスしやすい。

つまりユーザー側にかなりの裁量をもたせているし、もたせるためのYAMLなのだが、だからこそ簡単なプログラミングができることを想定したものになっている。 だが、それはユーザーに対する要求が厳しいため、一例としてジェネレータ、及びジェネレータを書くためのRubyライブラリが私個人として提供されていた。

そしてその後、私個人のプロジェクトとしてSTLLという専用のDSLが提供された。 出来はかなりよく、STLLを使うと簡単に手書きすることができる。

このあたりの話はStellaの言語デザインという記事で解説しており、ここで示された「専用記法」の話がブラッシュアップされSTLLになった。

プログラミングと気づかないような感覚でプログラミングをさせる「ロジック書き」だが、「GUIはないの?」的な話をすると、「こういうのでどういうGUIが直感的というのは個人差が激しいのでサードパーティに任せるべき」である。

インターフェイスとサーバーサイド

まず特徴的なのがStellaのユーザーインターフェイスはSSHである、ということだ。 例えばロジックファイルのアップロードは次のように行う。

ssh stella upload < logic.yaml

ほとんど単純なコマンドラインで実行できる。密かにSSHでForceCommandなのだが、故にコマンドそのものが省略でき、なおかつサブコマンドが指定できる これは、一見難しいように感じるが、実際は(手順を覚えるための)学習コストはwebインターフェイス数回分程度でしかなく、webインターフェイスを使うよりずっと速い。

そして、webインターフェイスだとかなり難しい要素が絡んでくる。 コマンドラインで使えるということでStellaの使いみちがかなり広がるのだ。

前述の通り「ロジックファイル」はYAMLであり、他のプログラムから容易に生成できるものだ。 コマンドラインによって利用できることで、他のプログラムに基づいて生成されたロジックファイルをそのまま反映するということが可能である。

「Stella自身は機械学習を利用しないが、ユーザーが機械学習を利用してStellaを利用することは可能である」というのはここに由来する。さらに後述するが、hook機能を使って連携する方法もある。

ヴィジターのインターフェイスは普通にwebになっている。 ページ自体はユーザー側で用意することもでき、普通にHTTPリクエストで利用できる。

驚くかもしれないが、リクエストもレスポンスもMIMEタイプはtext/plainである! リクエスト時はヴィジターの発言そのものを乗せたテキストで送り、レスポンスは1発言1行のテキストで返ってくる。 これ以上の情報は必要ないということでもあるが、Stellaのデザイン上課している制約でもある。

サーバー側では “Nginx - Unix Socket - FastCGI - Rack/FastCGI - Adaptor - Rinda - Instance” という構成になっている。 Adaptorとの間に入っているのはFastCGIだが、Unicornも準備されていた。

AdaptorとInstanceが分かれているのは、インターフェイスに本体が縛られることのないようにという配慮だが、同時に「webインターフェイスが唯一ではない」ということでもある。

Stellaはロジックのステートが2段階あり、アップロードすると“canditate logic”となり、これはヴィジターが利用するためにはcommit操作が必要である。 このcanditate logicはユーザーのインターフェイスでテストすることができるのだが、その際には端末から対話的操作で会話できるようになっている。このときは “SSH client - SSH server - (Interface -> Adaptor) - Rinda - Instance” という構造である(SSHで呼び出されるのはIntefaceだが、テストに入るとexecされる)。

そして私のテクニックとしておなじみになりつつあるRindaだが、そもそもは私は「チャットのメッセージさばきに最適」という理由で採用し始めたのであり、これこそが本命である。 さばけるコネクション数は分間で20kを越えているのは確か。

また、ちょっとした特徴として「サーバー側はテキストを受け取り、テキストで返すだけ」ということがある。 これは、いくつかの機能を生んでいるのだ。 なによりポイントになるのは、「送信内容を生成するのも、レスポンスを解析するのも手元のJavaScript」ということだ。

まず、「コマンドの実装」が可能。 例えばIRCのように/で始まるものをコマンドとみなすとかいうこともできるし、JavaScript側でコマンドになる文字列の送信を阻止したり改変したりしても良いし(つまり、JavaScript側で裏でコマンドを送るようなテクニックを使っても良い)。

そして、テキストしか返さず、改行が発言区切りであるため改行もできないのだが、それを解釈するのがJavaScriptなのでなんらかのマークアップを実装し、JavaScriptでそれを解釈するようにすれば別に単なるテキストを越えた表現を行うこと自体は可能である。

さらに、特にウェブブラウザからインスタンスに送信することを強制する設計にはなっていないため、ウェブブラウザから別のサーバーへ送信し、特殊なメッセージとしてStellaインスタンスに送信したり、加工して応答したりといったこともできる。

ユーザーがページを用意していない場合に利用されるデフォルトのインターフェイスページでは特に加工などは行われていないが、デフォルトのインターフェイスではコメントアウトされる形でいくつかのマークアップに対応した機能が定義されている。

hook

hook系のアクションはそのアクションに到達したときにHTTPによるリクエストを出すことができるものである。 利用可能なのは上級プランに限られていた。

アクションは単純に投げるだけのhookpost, 投げた後の応答(plain形式)を応答テキストとして返すhookの2種類。リクエスト形式はJSONである。

外部連携ができるので可能性がとても広がる。 この機能が「後から」入ったのは、Stellaの標準的ユーザーが必要とするものでも、私が制作する上で必要なものでもなかったからだ。どらちかというと、販売上「ディープラーニングAIの連携」という話をするために入れたという意味合いが強い。

コンテキスト

Stellaの最大の特徴といえるのが「コンテキストに対応している」ことだ。

会話AIの場合、私にとって最大の不満は「ここまで話していた経緯を一切無視した応答をする」ということである。 その場だけの応答なので、話が広がらないし続かないのだ。

Stellaはコンテキストを持ち、コンテキストブランチ、つまり「テストとアクション」群の配列はコンテキストごとに独立している。 これにより、「今の話題に即して」応答することができ、不自然さを解消できるし、対話的な絞り込みなども可能だ。

コンテキストはスイッチすることもできるし、スタックすることもできる。 このあたりは流れを図に書けば比較的容易に理解できる自然なものだ。

さらにコンテキストをまたいで同じテストとアクションを実行することもできる。 これはコンテキストコーリングと呼ばれ、複数のコンテキストにおいて共通するものの重複した記述を避けることができる。

もっと高度な機能としてフラグ機能とスコア機能がある。 応答を置き換えるような変数機能はないが、必要であればJavaScriptにおいて実装することが推奨されている。

フラグ機能とスコア機能は一般的なユーザーが利用することを想定したものではなく、私がデモンストレーションする際に使用することを想定したものである。 つまり、「自然な会話」を実現するために利用するもので、実用的にはあまり必要ではない。 一方、コンテキスト機能については親切で実用的なチャットボットを実現する上で非常に有力である。

Stellaの理屈

Stella立ち上げ当時の話は過去の記事で詳しく話している。 非常に概念的だが、Stellaの基本的な考え方が書かれている。

Stellaの根本的な部分としてはErinaを作っていて感じる

  • あまり多くはない事情を無視しないようにすると一般的な事情だけに対応するのとは根本的に違うコードになる
  • 単純な前提を了解として得られないがためにその情報を埋めるための方法が無限に大変

という点を踏まえたものである。 だから、「ある程度前提を設けることができれば70-80点くらい取れるEQAIを書くのは超簡単」という話だ。

最も大変なのが、「人間を擬態する」である。

Erinaは当然ながら「AIであると知ることができない」ことを前提とするため、どれほど予想外の言葉を投げかけられようが、意味不明に思えるような言葉を投げかけられようが、いかにも人間らしく対応しなくてはいけないし、そういう「普通じゃない状況」においても人間的な理解力を働かせる必要がある。 これがものすごく大変なのだ。そもそも綺麗に整形された言葉だけなら解析は超簡単なのだから。

実用的には、別にAIであることを隠す必要はないし、むしろビジネス的に使うのであれば応答はコントロールできないと困る。 そしてそもそもビジネス的にはサポートの経験さえあれば「投げかけられる言葉は予測可能」なのである。

なぜならば、ある程度以上問い合わせの数がある場合、問い合わせの内容は具体的な値が異なるとしても概要的には共通するものが大半になる。 ボットであることを予め明かしており、なおかつビジネス的に利用する場合は、「対話的FAQ」として利用できれば良いのである。

どういうことかというと、前述のコンテキスト機能を使って次のような応答を可能にするということだ。 下記は 私がデモンストレーションとして実際に書いたことがあるものであり、実際に可能なものである 12

< Mimir Yokohamaのチャットサポートへようこそ。何についてお聞きになりたいですか?

> 授業のことについて聞きたいのですが

< IT家庭教師サービスについてですね。 どのようなことについてのご質問ですか?

> いくらかかるの?

< 料金表はこちらにございます https://mimir.yokohama/services/tutor-detail.html#%E6%96%99%E9%87%91%E3%81%A8%E6%8E%88%E6%A5%AD%E4%BE%8B
< チャットにて詳しい内容のご説明を希望されますか?

> お願いします

< それでは料金について詳しく説明させていだだきます。
< 内容的にはどのようなものになりますか? 以下からお選びください
< 1. パソコンの初歩的な使い方、あるいはLinuxについての基本的な事柄、ケータイの使い方など初歩的なこと
  2. プログラミングの初歩やパソコン・OSに関する基礎、あるいはプロフェッショナル・エンジニアを目指す方の初学など専門的なこと
  3. 実践的なプログラミングやセキュリティなど専門家を目指す方のためのもの
  4. その他

> 1

< 基本的な料金は1回の授業で2時間を基本として18000円です。
< 1回の授業は2時間と決まっていますが、希望される場合は1時間、または2時間の追加をすることができます。
< 追加をした場合、その時間で「たくさんの内容をやる」のではなく、「同じ内容を時間をかけて学ぶ」ことになります。焦らずじっくり取り組みたい方にお勧めしていますが、一般的にはあまり必要ではありません。
< 追加授業を希望されますか?

> 結構です

< わかりました。 Mimir Yokohamaの事業所から時間のかかる場所ですと、出張料金が必要となる場合もございます。詳しいことはメールフォームなどから直接お問い合わせください
< https://mimir.yokohama/contact.html

この例では、ほとんどの場合1つの応答ごとにコンテキストが変更されている。 より正確には「応答する」と「コンテキストを変更する」という2つのアクションが設定されているのである。

特徴的な部分について述べよう。

お願いします

の部分は、「ヴィジターの発言がYESまたはNOに属するか」ということを推測する機能があり、YES or NOで質問した後の判定に利用することができる。 これはErinaと同じような「言葉の形状における推測」を利用しており、正規表現パターンになっている。

これはStellaシステムによって設定される_stella_confirm_yes_stella_confirm_noという名前のフラグであり、フラグが立っているかどうかをチェックするchkflagというテストによって判定することができる。 そして、「お願いします」という発言は_stella_confirm_yesが真になる。その後の「結構です」もstella_confirm_noが真になる。

ちなみに、このチェックはメッセージを正規化した上で表記ゆれにも対応した形になっており、かなり自然に拾える。

その次の3択はselectというアクションで、電話ガイダンスのように選択肢を与えて選ばせることができる。 これはStella default UIにおいてもJavaScriptによって選択肢が提示されたときに選択肢がリンクになるようになっていて、クリック/タップで選択することができ、スマートフォンでも親切なUIになっている。

ちなみに、selectは応答を自分で拾う必要がないのだが、その分selectのカスタマイズ性は低く、ここで「プログラミングのことなんですが」とか言われても番号で答えろと応答することになる。

コンテキストがあり、「今何を聞いているのか」を踏まえて回答していることで、単なるFAQと比べて人間的で優しいUIになっていること、そして一般的なチャットボットと比べて極めて自然かつ有効であることが伝わるだろうか。

Stellaのガイドラインでは「相手の意図を汲み取って提示できるFAQを書くことを心がけろ」とあり、これがStellaの使い方の基本になる。 Stellaを書くことはウェブサイトに情報を充実させることに似ており、ガイドラインにはまた「詳しい説明をまとめたウェブページを用意すること」を推奨してもいる。

どうだろう? 「実践的」であり、「自然」であり、他のチャットボットと比べても「的確で有用」だと感じないだろうか?

ロジックへの道

Stellaのロジックの組み立てにはコツがある。

だが、重要なのは「ビジネスをやる人間であれば、サポートや情報発信の経験があり、また関心もあるはずである」という観点である。

また、Erinaの話と共通するが、「中核的な単語を拾え」ということも重要だ。

「料金」というのは多くの人が気にすることがあり、例えば

  • 料金
  • 値段
  • いくら
  • 価格
  • 費用

といった言葉は料金の話をしている可能性が高い。 であれば、こうしたワードを拾って料金の話をするべきなのだ。 表記ゆれは確実に拾うようにしなければならない。

コンテキストブランチは「fallthroughしないswitch文」なのでコンテキストリーフの「順序」に気をつける、というのも強調して説明している。 この場合に重要になるのが、「ワードを確実に拾うことを意識して順序を書く」ということだ。

ひらがなであったり短かったりといった、「他の言葉で内包される可能性があるワード」を拾いたい場合、そのワードを内包するより長いワードを先に拾うことで「本来拾うべきワードを食ってしまう」という状況を避けるようにする必要がある。

この場合、拾うべきワードが複数ある場合には先に拾うべきワードが競合してしまい、順序が決められないこともある。 このような場合には「同じアクションをするのだとしてもテストが正しい順序になるようにコンテキストリーフを分ける」ことが推奨されている。

ちなみに、この場合同じアクションは重複して書く以外に方法がない。 一応、actions aliasという機能が検討されていたのだが、このような場合の重複排除はジェネレータ側で変数を使うべきだというのが私の意見である。理由は、alias機能などを追加していくとプログラミング的になっていき、とっつきづらいからだ。 機能がなくそうせざるをえないという不自由が物事をうまく回すこともある。

こうして拾ったワードによって「何を答えるべきか」が明瞭である場合はそれを答えれば良い。 だが、それが網羅的に回答すると長くなってしまったり、あるいは何を答えるべきかがさらなる質問によって絞り込める場合はコンテキストを移動するべきだ。 これによって、今何を知りたがっているという前提を持った上で話を進めることができ、「話のわかるサポート」になることができる。

また、基本的に最終的な回答以外は質問基調で進めることが強く推奨されている。 一般的にサポートはそのようにすると思われるが、質問をすることでヴィジター側に答えやすくするだけでなく、 ユーザーとしてもヴィジターの発言を予測するのが容易になる 。 つまり、ヴィジターの発言が絞り込まれるので大量にテストを書かなくて済む。 場にそぐわない発言についてはボットだと断りがあるので、予測範疇にある言葉にうまく誘導してあげれば良いのだ。

Stellaのロジックはほとんどがこれらの要素によって成り立っており、これらを守ることであとはガリガリ書いていけば実用的に機能する。

どのような言葉を拾うべきか、そしてそれに対してどのような言葉を返すべきか、あるいは相手の求めているものを特定するにはどのような質問を追加する必要があるかということはサポートをしている人間が知っているはずである。

プログラム

StellaはRubyで書かれており、ただし機能の一部はブラウザで実行されるJavaScriptで補完されることを期待している。

Stellaのコードはメタプログラミングが用いられている。 テストはstella_test_*という名称の、アクションはstella_action_*という名称のメソッドとして定義されており、例えば

[msg, [Hello, John]]

というアクションであれば、

stella_action_msg("Hello", "John")

というメソッド呼び出しが行われるのである。単純な変換であり、比較的容易に書けるが、このテクニックによってStellaは驚くべき省サイズを実現している。 Stella本体は0.0.1で169行、複雑な機能が搭載された0.0.16でも428行に収まっている。

ちなみに、最もややこしいのはselectである。折角なのでselect部分のコードを掲載しよう。

# Selectは挙動が他と違うので少し特殊な内容
def stella_actions_select(conditions)
  # 選択肢を作る
  qs = conditions.each_with_index.map {|i, index| "#{index + 1}. #{i[0]}" }

  # 特異コンテキストを作る。 #numberic_match で選択を確認し、特異コンテキストをリセットしてパスする
  cx = conditions.each_with_index.map do |i, index|
    { "tests" => [["numeric_match", (index + 1)]],
      "actions" => [["reset_singleton"], ["pass", i[1]]]
    }
  end

  # 正しく選択されなかった場合の挙動。 select_default_actionsが設定されている場合はそれをデフォルトの挙動とする
  if @current_branch["select_default_actions"]
    cx.push({ 
      "tests" => ["default"],
      "actions" => @current_branch["select_default_actions"]
    })
  elsif @logic["Override"] && @logic["Override"]["select_default_actions"]
    cx.push({ 
      "tests" => ["default"],
      "actions" => @logic["Override"]["select_default_actions"]
    })
  else
    cx.push({ 
      "tests" => ["default"],
      "actions" => [["msg", "選択肢の番号で回答してください"]]
    })
  end

  save_context do
    @session[] = cx
    @session[] =             #特異コンテキストを要求する
  end

  send_msg(qs)

  nil
end

selectに関しては「ロジックファイルで定義されていないコンテキストを呼び出す」という「特異コンテキスト」という機能があり、その特異コンテキストをビルドするという方法で実現している。コンテキスト機能をフル活用しており、実は概念的にもStellaとしては難しい部類に入る機能である。

なお、selectで使われているnemuric_matchというテストは外部には公開されておらず、ユーザーが直接使うことはできない。これはreset_singletonアクションに関しても同様だ。

また、ロジックファイルの妥当性をチェックするプログラムに関してはダックタイピングを駆使したものになっている。 具体的には、コンテキストツリー、コンテキストブランチなどStellaのロジックで使われる全ての要素に対してクラスが設定されており、StellaChecker.checkを呼び出すと再帰的に===メソッドが呼ばれるという方式である。

Stella側で楽をするためにこのCheckerでは厳密にチェックするようになっており、0.0.16時点で269行と意外と長い。

Stellaはコンポーネント数が意外と多くて、本体とアダプタのほか、アダプタをwrapするRackミドルウェア、ユーザーインターフェイスになるSSHから呼ばれるフロントエンドプログラム、さらにSystemdユニットとリスタートをコントロールするためのサーバー、FCGI用のラッパー、Rindaサーバーからなり、さらにデフォルトヴィジターインターフェイスとなるページとJavaScriptもある。 ドキュメントも多いので全体ではそこそこのサイズになっている。

サーバーサイド

サーバーはConoHa VPSを使用していた。 Stellaは負荷自体は軽いプログラムなので、サーバー要件低めで動作してくれる。

問題はコネクションの多さだが、StellaのデザインとしてHTTPコネクションを一瞬で切るようなものになっている。 JavaScriptで制御されてはいるものの、同期的リクエストを行い、応答を受け取ったらそれでOKだ。 そのため、「コネクションの重なり」自体が少なく、相当なヘビーユースをさばくことができる。 (ちなみに、用途的に人間が手で打つものなので、ハッシュリミットも厳し目に設定されている)

Nginx + FastCGI + Rackなのは、まずCGIにはあまり適さないアプリケーションなのでデーモナイズは必要であり、逆に言えばデーモナイズできればそんなに問題はないので作るのが簡単なんFastCGIが採用されたわけだ。 しかし、性能要件がより厳しくなった場合に備えてRackを採用している。あとはちょっと楽だから、という面もある。

実はRailsを使わないRackの情報って全然なくて、そのあたりで苦労したりもした。

サーバーOSはArch Linuxであったが、これは私がManjaro使いであり、自分が使っているサーバーもArch Linuxだからだ。 Systemdを使いたいのでLinuxであることは条件である。ただ、Systemdが動くサーバーであれば他にはRubyのバージョンくらいしか重要な部分はなく、比較的移行もしやすい。

マイグレーションに関してはシェルスクリプトによって賄われている。 Rubyさえあれば依存している外部ライブラリはRackだけであり、かなり身軽だ。 要求されるRubyのバージョンは2.3以降と割と新しい。これはぼっち演算子が使われている関係だ。

私のウェブサイト(Chienomiも含む)は2017年までDeleGate(!)で運用されていた。 Nginxへの移行は2015年から検討されていたが、実行されたのは2017年である。

DeleGate(Wikipedia, サポートサイト)なんてものは今や知らない人のほうがずっとずっと多いだろう。 1994年にAISTで開発されたプロキシサーバで、多彩なプロトコルの相互変換ができたり、あるいはウェブサーバーとして利用できたりしてものすごく有用なプログラムだった。文字コード関連で混沌としていた時代には「文字コード変換器」としてめちゃくちゃ有用だった。 大規模システムやミッションクリティカル用途でも使われ、阪神・淡路大震災を支えたプログラムでもあったのだが、2000年くらいにはもうあまり聞かなくなってきた存在だ。最新版は2014年10月13日リリースだが、2005年くらいにはもう化石となりつつあった。

Rackアプリケーションの初採用は2018年である。 それまでは私は手元で色々試していた(主にチャットサーバーの開発で)ぐらいで、Rackアプリケーションを実際に展開したのはこれが初である。

こうした、私が比較的近年試してきたシステムを採用しており、その構造は他で使ってきたものを最適化したものになっている。その最たるものがRindaだろう。

私がRindaを試しはじめたのは2009年だから、採用されているテクノロジーの中では経験は長い。 「C10k」という言葉が話題になり、チャットサーバーを研究していた私にとっては重要な課題となったのだ。 世の中的にはイベントドリブンプログラミングとかの必要性について回るセールストークだったりしたが、ZeroMQなんかも注目のテクノロジーだった(あまり聞かなくなってしまったが)。 私はチャットサーバーのメッセージさばきにおいてZeroMQが最適ではない(縛り付けられてあまり良い結果が得られない)と感じ、Rindaを採用した。

RindaがOrbital Designとも相性がよかったことから、以降Rindaを応用して並列性を高めるプログラムをたくさん書いたが、Stellaにとってはまさにうってつけの選択だった。 もちろん、チャットサーバーでの研究がそのまま活きる。

Stellaのサーバーサイドは「抽象化レイヤーを組み合わせた連携」という構造になっているという点を除けば、割と実直な構造である。 開発も運用も、さらには役員としての業務も私ひとりでこなす状況であったし、それでいながらタダ働きだったから、維持の手間のかかる構造は避けたかったのだ。だから、なるべくシンプルにして、「落ちたらそのときさ」みたいな感じになっている。 それでも、Systemdユニットにしていたりとか、死活監視プログラムを作ってあったりとかわりかし手は入れていたけれども。

ちなみに、セキュリティ関係はNessusを使っていたりして、そこそこちゃんとやっていた。 まぁ、ここらへんのサーバーツールが私の運用の負担と持ち出しでの費用として凄まじい負担になっていたことは否めない。

あとがき

Stellaは本当にいいプログラムである。

私の経験と研究成果を惜しみなく注ぎ込んだ、私が経てきた歳月の集大成ともいうべきソフトウェアだった。

本当に実用的で、よくできていて、機能的で、人間心理に沿ったいいプログラムなのだ。 Inflatonでのゴタゴタによって埋もれさせるのはあまりにも惜しい。

Inflatonとして扱う予定だったときには、商業的な要素が大きく、また私としても勝手なことはできないので、プロモーショなるで概念的な説明に終始していた。 だが、二度と日の目を浴びることはないかもしれない今となっては、それはあまりにも寂しく、悲しい。 だから、Stellaがどんなプログラムなのが、どれほど良いプログラムなのか、世の中に示したかったのだ。

本当に、あまりにも埋もれさせるには惜しい。

私としては、チャットボットが商売になるという主張にはとても懐疑的なのだが、誰かが活かしてくれたらなぁ、という気持ちは非常に強い。 差別化という意味でいえば、ここまで確実な成果を出せるプログラムは他にないと思うので、例え世の中に数多あろうとも敵ではない、と思っている。

そもそもStellaは、割と甘えを許さないソフトウェアだ。 別に学習量がすごいということもないので、使うの自体はマニュアルを覚える程度で良いのだが、「楽をして使える」みたいな発想だといつまで経っても成果が出ない。 本気で成果を出したい、チャットボットを本当に機能させたい人が使うことで大いなる力を発揮するタイプのソフトウェアであり、その意味でも差別化は強い。 Stellaを使うために容赦なくsshコマンドを打たせる時点でそれは明らかだ。

Stellaには「フラッグシップチャット」という計画があり、イメージキャラクターを置いたキャラクター戦略も含めて、「そのキャラクターと会話できる」チャットを作るアイディアがあった。 依頼絵師も確定していたのだが、そのクオリティを実現するのに数千万円規模の予算が必要であった。 取締役は容易な話だと主張したが、実際には当人が全く販売しなかったので、当然ながら実現はしなかった。

だが、労力面さえクリアできれば本当にキャラクターと会話しているような自然な会話も実現できる。 なんといっても、私がErinaを作った上で「7-8割でよければこれで十分」と考えた機能が搭載されているのであり、労力的な限界はあれど、「Erinaの7-8割」が機能的には実現できるということを意味しているのだから。

Erinaの巨大さ、膨大さ、複雑さを考えれば、500行に満たないプログラムがそれを実現するというのは驚くべきことだ。 もちろん、実際には非常に大きなロジックが必要となるから、そうなるとErinaに近づくかもしれない。 だが、それでもそれはErinaにとってはプログラムではなくデータベースに属するものであり、「データベースを手書きする」という意味合いに留まる。

それほどまでに可能性を秘めたソフトウェアだ。

一方で、「ツボを抑えた設計」を心がけており、高度な知識やノウハウがなかったとしても、そして多大な労力をかけなかったとしても、ピンポイントに回答できることで非常に実用的で有益なサポートを提供できる「ビジネス的な実際性」も持っている。

これは、先に示したMimir Yokohamaのテストボットが、「Stellaの基本的で典型的な書き方」に沿っているだけであるにも関わらず、有効なサポートを提供できていることからも明らかである。私がこのテストを書くのにかけた時間は2時間程度であり、単に書くだけであれば30分もあれば書ける内容だ。 (YAML手書きだったので時間がかかった側面も大きい)

思い入れも強いので何度も繰り返して言ってしまうが、本当にいいソフトウェアなのだ。 使ってもらえばわかると思う。 ポイントを抑えて作られているから、小さいけれど本当によく機能する。

本当、誰かが拾ってくれると良いのだけれど。

2020-01-25追記: 実働しました

Mimir Yokohamaのウェブサイトで動作するStellaインスタンスが公開された

網羅的に入れてあるわけではなく、とりあえずお試しできる程度に入れただけだが、動作することは確認できると思う。 対応している範囲であればまぁまぁ自然に回答してくれるはずだ。


  1. じゃあなぜMimir Yokohamaに設置されていないのかというと、あらゆることに関して網羅的に入力するだけの時間がとれなかったのである。実際、書いてある分だけでは家庭教師サービスに関することのほかは、ルートコンテキストでいくつか簡単なことを答えられるだけである。↩︎

  2. このチャットで示されている料金及び内容の分類は当時のものであり、現在とは異なる。↩︎