数か月の計画と読書により、次の課題を解決することになりました。
理論のビット
詳細な説明をよりよく理解できるようにするために、少し理論を説明します。
システム全体はナレッジベースを中心に構築されています(以降、ナレッジベースの削減を使用します)。 その中の知識は、 セマンティックネットワークの形式で提示されます (知識表現の言語の詳細については、 記事を参照してください )。 いくつかの簡単な例を挙げます。 これにより、テキストと画像をさらに理解できるようになります。 私の例では、グラフィック-SCgとテキスト-SCの 2つの言語を使用します
まず、 「is a」関係がどのように定義されているかを学ぶ必要があります。 すべてが非常に簡単です。 左の画像は、リンゴとパイナップルが果物であることを示しています。 これを行うには、単にすべてのリンゴのセットを表すノード-appleを作成し、すべての果物のセットを表すノードから着信アクセサリーアークを追加します-fruit 。 パイナップルでも同じことをしました。
原則として、 「is a」関係に基づいて、他の関係を含むナレッジベース内のすべてのストレージが構築されます。 たとえば、オブジェクトまたはオブジェクトのクラスのイメージがKBでどのように示されるかを検討してください。 この図では、リレーションシップのすべてのインスタンスを「何かのイメージである」ことを示すセットを導入し、バイナリ結合を示すすべてのアークをそれに追加していることがわかります。
ここで、保存された知識の処理がどのように行われるかについて少し話しましょう。 このために、 エージェントベースのアプローチが使用されます。 重要な原則は、エージェントが知識ベース(状態の変更)を介してのみ互いに対話できることです。 すぐに、C ++でのエージェントの開発に関するドキュメントへのリンクを提供します。 エージェント間の相互作用は、特殊なクエリコマンド言語を使用して実装されます(そのドキュメントは開発中です)。 ここで、1つのリクエストの例を示します( 「is a」リレーションを使用して、過去2つの例としてこの画像を読んでください):
これは、クエリの最も単純な例、ミンスクの天気です。 この画像では、リクエストインスタンスを指定し、 コマンドとcommand_find_weatherのセットに含まれるノードを見ることができます。後者はリクエストのタイプを決定します。 本質的に、クエリは、引数が含まれる要素のセットです。
データベース内の各エージェントには、予想される結果、開始条件、およびエージェントの他のプロパティを示す独自の仕様があります。 これらのプロパティの1つは、トリガーされるイベントです。 現時点では、次のタイプのイベントを使用できます:着信(発信)アークの削除(作成)、要素の削除、リンクのコンテンツの変更。 言い換えると、知識ベースのイベントにエージェントをサブスクライブし、それが起こるとエージェントがトリガーされます。 チームの場合、このようなイベントはcommand_initiatedセットへのリクエストインスタンスの追加です。 これが発生すると、このイベントにサブスクライブしているすべてのエージェントがトリガーされます。 その後、このタイプのリクエストを処理できるエージェントのみが機能し続けます。
出力で、各エージェントはnrel_result関係を使用してリクエストインスタンスに関連付けられた結果を生成します。
したがって、異なるエージェントが専門の言語を使用して互いに通信できるフォーラムのようなものになります。 同時に、リクエストを作成するときに、答えがそこにあるかどうか、誰がそれを与えるかさえも、彼らの誰も知りません。 これにより、エージェントのさまざまな実装を使用して、作業を停止することなく、外出先でそれらをシステムに追加できます。
説明は最小限に抑え、理解しやすいものにしようとしましたが、説明はかなり大きいことが判明しました。 コメントに関する質問に詳細に回答するか、必要に応じて、詳細な説明と例を記載した別の記事を作成します。 しかし、私たちは先に進みます。
カーネルの改善
私がJarvisで作業を開始する頃には、 カーネルはかなり安定した動作状態にありましたが、エージェント開発はC言語のみを使用して可能でした。 そのようなエージェントの例はまだリポジトリにありました。
最初に行う必要があるのは、開発を簡素化することでした。 したがって、多くのルーチンタスクを引き受けるC ++ライブラリを作成することが決定されました。 当時、私はアンリアルエンジン4でゲーム開発者として働いていたため、コードジェネレーターを使用すると便利だと判断し、この機会を実現しました。
エージェントの説明は次のように要約され始めました。
class AMyAgent : public ScAgentAction { SC_CLASS(Agent, CmdClass("command_my")) SC_GENERATED_BODY() };
このエージェントの実装は、1つの機能を実装することでした:
SC_AGENT_ACTION_IMPLEMENTATION(AMyAgent) { // implement agent logic there return SC_RESULT_OK; }
エージェントの実装の詳細については、 ドキュメントをご覧ください。 新しいライブラリを備えたエージェントの例はここにあります (彼らは一時的に私のプライベートリポジトリに移動しましたが、すぐに再びオープンします)。
2番目に行う必要があるのは、エージェントのマルチスレッド起動です。 以前は、KBでイベントを処理するために、キューからエージェントを順次実行する単一のスレッドが使用されていました。 これで、システムで使用可能なすべてのカーネルが使用されます。 すぐに、異なるスレッドの複数のエージェントからデータベースへの非同期アクセスを提供することは非常に困難であったと言わなければなりません。 このメカニズムの説明は、おそらく、そのような必要が生じた場合に書くことができる別の記事です。 ここでは、いくつかのニュアンスのみに焦点を当てます。
- メモリに格納された要素の操作は、 ロックフリーのアプローチを使用して実装されます。 私はそのようなアプローチを分類するのは得意ではありませんが、少なくとも1人のエージェント(多くの場合、複数またはほとんどすべてのエージェントがいる)が前進しているときに「ロックなし」の実装を持っていると思います。
- 現時点では、エージェントは、使用するデータが別のエージェントによって削除されないことを保証できません。 将来的には、エージェントが必要とする限り適切なコンテキストでオブジェクトを強制的に保存するロックを導入する予定です(必要なロックの種類の議論は進行中です)。 現在のエージェントの実装では、要素が削除された場合、この要素を使用してメモリ内で何かを行おうとするとエージェントがエラーを返すため、これは恐ろしいことではありません。
3つ目に出会ったのは、ほとんどのサービスがAmazon、Googleなどであるということです。 さまざまな言語の既製のAPIがありますが、C ++用ではありません。 そのため、PythonコードをC ++内で実行できるようにすることが決定されました。 これはすべて、Boost-Pythonを使用して実装されます (Python 3を使用すると、これで既に多くのことができます)。
説明されたものに加えて、ドキュメントが作成されました(リンクは既に提供されています)。 C ++ライブラリ全体が単体テストでカバーされています。 これには約1年かかりましたが、これはすべて他の作業と並行して行われ、現在も行われています。
視覚モデル
視覚モデルは、概念を改良し、基本的なことをデバッグするために開発されました。 このモデルの小さな概念実証のビデオ:
このモデルは、他の人が組み立てて再生できるため、システムのデバッグに非常に役立ちました。 実装の詳細については触れませんが、興味がある人はコメントでお答えします。 現時点では、これは開発中ではなく、すべてが実際のデバイスに渡されています。
音声インターフェース
初期のプロトタイプでは、Android APIを使用して音声入力を実装し、結果のテキストをapi.aiを使用して解析しました 。これにより、開始要求とそのパラメーターのクラスが返されました。 この形式またはわずかに変更された形式では、現在存在しています。 しかし、私たちは異なる方向に会話を導きます-このメカニズムがエージェントの助けを借りてどのように実装されるか。
エージェントとフォーラムとのコミュニケーションの比較を思い出すと、次のログでこのメカニズムを説明できます(<エージェント名>は異なるタスクを解決するエージェント、ユーザーはユーザーです)。
user: ADialogueProcessMessage: " " AApiAiParseUserTextAgent: ""? AResolveAddr: "" - 3452 AApiAiParseUserTextAgent: ""? AResolveAddr: "" - 3443 AApiAiParseUserTextAgent: 3452 3443 AAddIntoSet: ADialogueProcessMessageAgent: AGenCmdTextResult: "..." AGenText: - " "
これは、このような単純なタスクを解決するときに内部で発生することです。 しかし、それだけではありません。さらに2つの概念を導入する必要があります。 既に説明したエージェント(ナレッジベースの変更に対応し、その状態を変更する)に加えて、さらに2つのタイプのエージェントがあります。
- エフェクター -これらは、知識ベースの変更のために外部環境を変更するエージェントです。 つまり、画面、マニピュレーターなどの情報の出力を担当します。
- 受容体 -これらは、外部環境の変化に対して環境を変化させるエージェントです。 情報の入力を担当します:入力デバイス、センサーなど。
エフェクターエージェントビデオ
ビデオでは、点灯しているデバイスのセットにランプを指定するノードが追加されたときに、ランプ(キューブ)がどのように点灯するかを確認できます。 逆に、そこから削除されるとオフになります。 同じことがクレーンでも起こります。
ビデオでは、点灯しているデバイスのセットにランプを指定するノードが追加されたときに、ランプ(キューブ)がどのように点灯するかを確認できます。 逆に、そこから削除されるとオフになります。 同じことがクレーンでも起こります。
エージェントがユーザーの要求を解析し、同じ言語でそれに対する応答を作成すると、この応答はユーザー(ユーザー)との対話を示すセットに追加されます。 この時点で、「エフェクター」エージェントの1つがこの応答を文字列としてユーザーに表示します。 ビデオでの作業の例を次に示します。
それとは別に、ユーザーインターフェイスは、テキストを介した音声生成を要求します。 エージェントは、ivonaサービスを使用して音声ファイル(OGG)として生成します。
次は?
昨年はデバッグと新しいカーネル機能に費やしました。 興味深い問題をすでに解決できる実用的なプロトタイプが得られました。 過去1年間で、メイン作業から約750時間の空き時間が開発に費やされました。
このアプローチには次の可能性があります。
- 新しい機能を追加しても、既存の機能には影響しません。 たとえば、タスクスケジューラを作成する必要がある場合(特定の時間または定期的にタスクを開始するため)、このために、指定された時間に開始されたセットに既に生成された要求のみを追加するエージェントが1つだけ必要でした。 クエリ言語で利用可能なすべてのリクエストをスケジュールできることがわかりました。 例: " "妻に電話することを思い出させる " "; 「10でライトをオフにします」 ; 「11.05にテレビをオンにする」 ...;
- この記事では説明しませんでしたが、テンプレートによる検索があり、これに基づいて論理的な結論を引き出すことができます(そして、幾何学と物理学の問題のソルバーをテスト済みです)。
- Cloud Natural Language APIを入力テキストアナライザーとして使用する予定です。これにより、言語インターフェイスの品質が向上します。
基本的な原則と、「スマートホーム」、「スマートバトラー」、または「ジャービス」(ご希望に応じて)の実装で何が行われたかを説明しようとしたこの最初の記事はおそらく終了するでしょう。 素材が良かったという幻想はないので、フィードバックをお願いします。 それを読んだすべての人に感謝します。