スマートリモコンをChatGPT(Function Calling)を使ってよりスマートにしよう！

# スマートリモコンをよりスマートに!？
　声から操作ができてしまうスマートリモコンですが、文脈から意図を認識するというところまでではないようです。そこで、今回はChatGPTのFunction Callingと呼ばれる機能を使って、人の意図をくみ取って、家電を操作してくれる装置を作ってみたいと思います。
　実際の処理の流れとしては、以下になります。
1. プロンプトをChatGPTのAPIへ渡し、GPTにどの家電をどのように操作するのか判断してもらう(今回)
2. ChatGPTの判断結果から、ラズパイが赤外線で家電を操作

　今回はプロンプトを入力して、特定のスクリプトをたたくところまでのポイントとなった部分を紹介します。実際のコードは、GitHubにあげています。(https://github.com/arumi123/homebridge/tree/develop)

また、結果だけ見たいという方は、実際に呼び出してみよう！まで進んでください。

# Function Callingとは
　人が入力した文字列(プロンプト)から、ChatGptが意図を読み取り必要な関数やAPIを指定してくれる仕組みです。この仕組みを使って、自然言語からエアコンとシーリングを操作するということをやってみようという取り組みです。
 　今回は、FunctionCallingを使って、エアコンを操作するスクリプト(HeaterCooler.py)を実行する部分を作成します。

# 呼び出したい関数の定義
　まずは、GPTに操作させたい関数をtoolsという変数に定義してあげます。
ポイントとしては以下です。

- 呼び出したい関数は、リストでAPIに伝える
- descriptionに関数や引数の説明を書くことで、ユーザが入力するコマンドからGPTが関数の呼び出しが必要か判断してくれる

余談ですが、以下のコードをChatGPTに書かせてみたところ、バージョンの古いAPIに準拠したものが生成されてしまいました。参考文献にある、FunctionCallingの機能をAPIの仕様をよく読み、実際に書いてみたものは以下になります。
 
~~~python:TextCommander.py
# GPTに定義する関数（HearterColler.pyの制御）
tools = [
    {
        "type": "function",
        "function": {
             "name": "control_temperature_system",
             "description": "Control the temperature system to cooler, heater, or off mode with a target temperature.",
             "parameters": {
                "type": "object",
                "properties": {
                    "mode": {
                        "type": "string",
                        "enum": ["cooler", "heater", "off"],
                        "description": "The mode of the system: cooler, heater, or off."
                    },
                    "temperature": {
                        "type": "integer",
                        "description": "The target temperature to set."
                    }
                },
                "required": ["mode", "temperature"],
                "additionalProperties": False,
            }
        }
    }
]
~~~

# GPTに期待するふるまいを定義する
　続いて、GPTに渡すmessageと呼ばれる変数を定義していきます。
 messagesで定義してあげる内容としては以下です。

- gptに事前情報や、GPTに期待する役割
- 過去にgptとやり取りした会話の記録(GPTは過去の会話の記録を記憶する機能がない)

今回は、「　GPTは家電を操作する人　」であること、「　日本語の返答を期待すること　」を事前に伝えました。
以下が実際の実装です。

~~~python:TextCommander.py
# GPTに、「　どのようにふるまって欲しいのか・これまでのGPTの回答・ユーザの入力　」を送る
messages = [
    {"role": "system", "content": "あなたは家電を操作してくれる人です。日本語で返答してください"},
    {"role": "user", "content": user_prompt}
]
~~~
今回は、ユーザの入力に対してgptが返答もなく操作する仕様となっています。
本当は、ユーザとの会話の中から、自然と家電を操作してくるものが理想ですが、会話を記録する部分の実装が間に合わず、このような仕様となっています。

# Function Callingを実装する！
実際にGPTを呼び出し、今まで定義してきた変数を渡してあげます。
今回はfunctioncallingの機能が使える中で、比較的安いモデルを選びました。
~~~python:TextCommander.py
# GPT を呼び出して function calling を行う
response = openai.chat.completions.create(
    model="gpt-3.5-turbo-0125",  # 使用するモデルの選択
    messages = messages,
    tools=tools,
    tool_choice="auto"           # functioncallingを使用するかの判断を自動でしてくれるように設定
)
~~~

# 結果の処理
responseオブジェクトに格納された変数から、ほしい情報を取り出します。
functioncalling機能が使われた場合、情報がどのように格納されてくるかは、参考文献にある、FunctionCallingの機能をAPIの仕様をよく読み実装しました。

ポイントとしてjson.loadとあるのは、APIからjsonで結果がAPIから渡されてくるため、のちのパイソン呼び出しに使えるように、辞書型の変数に変換しているためです。


~~~python:TextCommander.py
# 関数呼び出しの結果を取得
arguments = json.loads(response.choices[0].message.tool_calls[0].function.arguments)
print("GPT is calling the function:", arguments['mode'], arguments['temperature'])
~~~

# 実際に呼び出してみよう！
実装の主なポイントは以上です。具体的な実装は、こちらのリンクからお願いします。
では、実際によびだしてみます！

まずは、シンプルに、
「　クーラーを26度に設定して！　」
とお願いしてみます。
~~~powershell
> python .\TextCommander.py
Enter your command (e.g., 'cooler 22', 'heater 25', 'off'): クーラーを26度に設定して！
GPT is calling the function: cooler 26

HeaterCooler.py has been called
HeaterCooler has been changed to this following:  cooler 26
~~~
結果の2行目で、GPTがcoolerと26度を狙い通り選択し、
4行目で実際に、HeaterCooler.pyが呼び出されていることがわかると思います。
うれいしい・・・

ただこれだけでは、今でもある〇クサと変わりません。
GPTを使うメリットとして、人間の意図をくみ取ることです。
そこで、直接的な支持を避け、

「　寒気がするなあ、沖縄の平均気温くらいにならないかなああ　」

と打ち込んでいます。寒気というフレーズの寒という字からクーラーになってしまわないか、沖縄の平均気温という単純な温度の指定ではないところから、具体的な数字を出してこれるか？という点がポイントです。
実際に打ち込んでみると、
~~~powershell
> python .\TextCommander.py
Enter your command (e.g., 'cooler 22', 'heater 25', 'off'): 寒気がするなあ、沖縄の平均気温くらいにならないかなああ    
GPT is calling the function: heater 25

HeaterCooler.py has been called
HeaterCooler has been changed to this following:  heater 25
~~~
なんと、ヒーターを25度で稼働させてくれました！
モデルが学習してるのは過去の情報であることや、実際にこの値が正しいかは注意する必要がありますが、文脈から期待する動作をしてくれています。

# まとめ：家電に知性を感じた！
　抽象的なお願いから、実際にやってほしいことまで実行までできるようになっていることはかなり驚きでした。
また、APIを呼び出して、システムを実装するみたいなことはあまりなかった自分からすると、今回の学びとして以下のようなものがありました。

- APIの仕様に基づいて、コードを実装する
  - WebサービスのAPIをたたいたことがあまりなく、どういった情報をどういったフォーマットで渡すのか、公式ドキュメントから読み取って書くという行為自体が新鮮で勉強になりました。
- APIキーの管理
  - APIキーの管理に注意しました。普段からWebサービスのAPIをたたく方には、当然かもしれませんが、GitHubでキーを公開しないように、PC環境変数から持ってくる仕様にしています。
- ChatGPT自体の機能・仕様
  - ChatGPTは過去の会話ができないこと、messages変数でChatGPTの役割を定義してあげられること、FunctionCallingのように決まったフォーマットを出力する機能があること、などChatGPTを使いこなす第一歩を踏み出せたのではないかと思います。

今後は、会話の中で機能を呼び出せるようにしたり、音声・画像入力から人の行動を読み取って機能を呼び出せないか検討してみたいと思います。

次回は、赤外線の送受信モジュールを作成し、ラズパイから家電を操作する部分の解説をしたいと思います。

至らない箇所多々あるかとおもいますのでご指摘いただければと思います。
最後まで読んでくださりありがとうございました！

 # 参考文献
1. Function Calling - OpenAI API （参照 24-09-15）
https://platform.openai.com/docs/guides/function-calling
2. Assistants Deep Dive - OpenAI API （参照 24-09-15）
https://platform.openai.com/docs/assistants/deep-dive/managing-threads-and-messages
3. Pricing | OpenAI （参照 24-09-15）
https://openai.com/api/pricing/