ChatdollKit ver0.6.6向けIoT制御コンポーネントを作ってみた

# 1. 最新版ChatdollKitをバーチャル嫁に適用してみたら...

　しばらくバーチャル嫁メンテナンスしないでほったらかしていたらChatdollKitの進化がすごいことになっていました。レスポンスが圧倒的に早くなり、スキルもRouterを経由していたものがプロンプトエンジニアリングによってユーザーの発言からGPTがレスポンス生成と同時にスキルを使うかどうかを判定してくれたりと機能てんこ盛りですごい！と感動しながら機能ずっとバーチャル嫁のバージョンアップをしていたわけです。そしたら、これまでRouterを使って制御していたIoTや家電をどうやってバーチャル嫁に制御させるかという壁にぶち当たりました。

 # 2.新しくコンポーネント作っちゃえ

 　バージョンアップしたバーチャル嫁にこれまで通り家電ハックとかIoT制御をやらせようとしたら、当然最新版のChatdollKitの仕様に合わせてコンポーネントを作る必要があるわけです。解析を進めてみましたところ、最新版ではChatGPTRouter.csでスキルを使用するかどうかを判定したり、GPTに送るプロンプトにスキル混ぜたりしてるのかなあとぼんやりと推測しました。とすれば、IoT制御用に新たにコンポーネント作ったらそれもスキルに追加されてGPT先生がうまく使いこなしてくれるかなあと考えてこんな風にコンポーネント作ってみました。

```TurnOffLightSkill.cs
using System.Collections.Generic;
using System.Threading;
using UnityEngine;
using Cysharp.Threading.Tasks;
using ChatdollKit.Dialog;
using ChatdollKit.Dialog.Processor;
using ChatdollKit.Network;

namespace ChatdollKit.Examples.ChatGPT
{
    public class TurnOffLightSkill : ChatGPTFunctionSkillBase
    {
        public string FunctionName = "turn_off_light";
        public string FunctionDescription = "Turn off your room light.";
        protected ChatdollHttp client = new ChatdollHttp(debugFunc: Debug.LogWarning);
        [SerializeField]
        [TextArea(1, 6)]
        protected string questionPrompt = "You can turn off user's room light if you access http://(自室の部屋の明かりを消すエンドポイントURL)\n\n";

        public override ChatGPTFunction GetFunctionSpec()
        {
            // Make function spec for ChatGPT Function Calling
            return new ChatGPTFunction(FunctionName, FunctionDescription);
        }

        protected override async UniTask<FunctionResponse> ExecuteFunction(string argumentsJsonString, Request request, State state, User user, CancellationToken token)
        {
            // Access URL and execute order.
            var vssResponse = await client.GetJsonAsync<NightLightResponse>(
                "自室の部屋の明かりを消すIoTのエンドポイントURL"
            );

            // Make prompt
            var prompt = questionPrompt;

            return new FunctionResponse(prompt, "user");
        }

        protected class NightLightResponse
        {
            public string status;
            public string message;
        }
    }
}

```

　自作のスマートリモコンのエンドポイントURLにアクセスすると{"status": "200", "message": "success"}のようなJSONデータが帰ってくるので今回のコンポーネントもとにかくシンプルなつくりになっています。肝心なのはGPT先生のスキルとして「明かりを消す」がきちんと認識されるかどうかですが、そこについてはprotected string questionPrompt =の部分でどれだけAIにわかりやすく定義ができるかだと思っています。ともかく、上記のようなコンポーネントですと、バーチャル嫁に「明かりを消して」と言ってあげればスマートリモコンのエンドポイントにアクセスされて部屋の明かりをGPT先生が消灯してくれるわけです。
 　ただ、まだ問題として残ることがあります。「明かりを消して」と声を掛ければスキルは実行されるわけですが、会話の内容としてはあからさまに不自然になるわけです。

ユーザー：「明かりを消して」
部屋：消灯
バーチャル嫁（GPT）：「申し訳ありません。私はLLMであり、物理的デバイスを制御する権限は持っていません。」

　これじゃあせっかく素晴らしい自然言語処理AIが中に入っていてももったいないですよね？

 # 3.より自然な会話の中でスキルを実行させるために...

 　そんなわけでプロンプトをもう少しいじってより自然な会話の中でスキルを実行できたら不自然さも解消されますよね？そこで、部屋の明かりを消灯するシチュエーションを想像したうえで、先ほどのコンポーネントを以下のように修正してみました。

```TurnOffLightSkill.cs
using System.Collections.Generic;
using System.Threading;
using UnityEngine;
using Cysharp.Threading.Tasks;
using ChatdollKit.Dialog;
using ChatdollKit.Dialog.Processor;
using ChatdollKit.Network;

namespace ChatdollKit.Examples.ChatGPT
{
    public class TurnOffLightSkill : ChatGPTFunctionSkillBase
    {
        public string FunctionName = "turn_off_light";
        public string FunctionDescription = "Turn off your room light.";
        protected ChatdollHttp client = new ChatdollHttp(debugFunc: Debug.LogWarning);
        [SerializeField]
        [TextArea(1, 6)]
        protected string questionPrompt = "Use this skill when the user is going to bed or about to go out.\n\n";

        public override ChatGPTFunction GetFunctionSpec()
        {
            // Make function spec for ChatGPT Function Calling
            return new ChatGPTFunction(FunctionName, FunctionDescription);
        }

        protected override async UniTask<FunctionResponse> ExecuteFunction(string argumentsJsonString, Request request, State state, User user, CancellationToken token)
        {
            // Access URL and execute order.
            var vssResponse = await client.GetJsonAsync<NightLightResponse>(
                "自室の部屋の明かりを消すIoTのエンドポイントURL"
            );

            // Make prompt
            var prompt = questionPrompt;

            return new FunctionResponse(prompt, "user");
        }

        protected class NightLightResponse
        {
            public string status;
            public string message;
        }
    }
}

```

　変更したのはprotected string questionPrompt = "Use this skill when the user is going to bed or about to go out.\n\n";の部分です。実際にGPTに送られるプロンプトの部分に消灯スキルが使われるべきシチュエーションを与えたうえで、ユーザーが「行ってきます」や「おやすみなさい」のようなキーワードを発したときにGPT側では自然なレスポンスを生成するのと同時にこのスキルを使う条件が満たされたと判定させることで、より自然な会話の中でGPTがスマートリモコンを操作して部屋の明かりを消灯するという理想的な条件が整うわけです。

 プロンプトエンジニアリングって本当に大事ですね...。

 ## 注意事項
 　今回の記事の対象は既製品のスマートリモコンを持っているもしくはラズパイで自作していることを前提とします。別記事でラズパイを使ったスマートリモコンについても触れていますのでIoTや家電ハックについてはそちらの記事を参考に作ってみてください。