【ChatGPT】Function callingの「対策・工夫」と「良い点・注意点」

# 概要
ChatGPT[^1]の新機能「Function calling」を使用し、**AIがToDoを管理をしてくれる**アプリケーションを作成しました。
このアプリケーションは、下の画像のようにToDoの作成・取得・削除といったタスク管理を行ってくれます。

<img width=250 alt="1" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534453/98ecc85c-fa7a-d529-093a-5a4085940f12.png">
<img width=250 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534453/7c285d6c-461f-a0b9-c2ef-85f2b4e0b30d.png">

[^1]: 厳密には、ChatGPTを提供しているOpenAIのAPIの新機能になります。わかりやすさ重視のためChatGPTと紹介しておりますご了承ください。

この「ToDo管理アプリケーション」を作成するにあたり、セキュリティインシデントを防ぐ「**対策**」や、返信の精度をよくするための「**工夫**」を入れました。また、このアプリケーション作成中に、Function callingの「**良かった点**」と「**注意すべき点**」を感じました。
この記事はそれら内容についてまとめたものになります。

AIの将来性を感じるとともに、実用に向かって乗り越えるべき困難も感じる結果となりました。

このアプリケーションはLINE Botを使用しています。試してみたい方はこちらから友達登録をお願いします。（予告なく終了する可能性があります。ご了承ください）

https://lin.ee/rRFdCXn

# Function callingとは
6/13にChatGPTにアップデートが行われ、その中の新機能として「Function calling」が追加されました。
この機能を使用すると、ChatGPTに呼び出し可能な関数を教えることができるようになります。そして、ChatGPTは会話の中で必要に応じて教えられた関数の呼び出しを行います。

ChatGPTは「ユーザーの会話」を「関数呼び出し」という内容に変換してくれるようになると言うことで、つまりユーザーの「曖昧な指示」を「具体的な関数」の実行に落とし込むことができるようになります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534453/f612f8a4-ef1a-c172-5f9c-c9d8e2434f79.png">

拙作で恐縮ですが、Function callingのさらに詳しい内容については下の記事にまとめてあります。

https://qiita.com/Haru141/items/b86afc15c883e2fda423

# ToDo管理アプリの開発
### アプリケーションの動作
ユーザーがAIにToDoについての操作を要求し、それを受けてAIがアプリケーションを操作するという形になります。
ToDoに関する操作はユーザーが直接行うのではなく、ユーザーの指示を受けてAIが間接的に行います。
例えば「掃除をToDoリストに追加して」とメッセージを送ると、ChatGPTは「掃除」というtodoの作成をアプリケーションに要求します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534453/97b895e6-51ea-dd6d-516b-ab7c87787f16.png">

### 技術の内容
フロントエンドとして、LINE Botを使用しました。
「ユーザー認証とUIの作成にかかる工数の削減」「ユーザーが利用する際のハードルの低下」が望め、個人的には大正解だったと感じています。
バックエンドはAWSのLambdaを使用して実装し、アプリケーションの言語はTypeScriptを使用しました。Dockerを利用したservelessでの実装になります。
また、アプリケーションのアーキテクチャはドメイン駆動で実装しています。

### DB設計
DBにはToDoのテーブルのみ存在しています。
ToDoは以下の3つのカラムを持ちます。

- userId
    - ユーザーID
- title
    - タイトル
- createdAt
    - 作成時刻

### AI部分
LINEでメッセージを受け取るとその内容をChatGPTに送信します。そしてそのレスポンスをChatGPTに返すと言うのが基本的な仕様になります。

この時に「Function calling」を使用して、ChatGPTには以下の3種の関数を伝えています。
- getAllTodos
    - ToDoを全て取得する
- addTodo
    - ToDoを追加する
- doneTodo
    - ToDoを削除する

ChatGPTがToDoを取得したい・追加したい・削除したいと思った際にはそれぞれの関数を呼び出します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534453/1f1ed6a1-e838-8644-63fc-aad78c241c37.png">

# アプリで行った「対策・工夫」
### プロンプトインジェクション対策
「**プロンプトインジェクション**」とは、AIを使用したアプリケーションに対する攻撃方法です。AIを騙して誤動作を起こさせたり、機密情報を盗み出すことができると言われています。

https://bocek.co.jp/media/exercise/prompt-engineer-exercise/1886/

このような攻撃が存在するため、AIの要求で関数をそのままの実行させてしまうと、
- 他人のToDoが盗み見られてしまう
- 他人のToDoが操作されてしまう

などと言った危険性を生んでしまいます。

そこで、ChatGPTに伝える関数と、実際に動作する関数を**別物**にし、この間にワンクッション設けるという対策を行いました。
例えば、ChatGPTにはaddTodo関数を実行するには引数として「title」のみ必要であると伝えておき、実際の関数には引数として「title」の他に「userId」を要求します。そしてChatGPTから関数の実行を要求された後、その要求にuserId情報を付加してから関数を発火させるようにしました。

関数に渡されるuserIdはChatGPTの要求に関係なく**強制的**に付与するため、誤ったuserIdで検索されることはありません。これによって他人のToDoが盗み見られるリスクや他人のToDoが操作される**リスクは排除**できたと言えます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534453/fd784ee7-246f-5893-e9e1-42e164a4abcb.png">

### ChatGPTへ伝える情報の削減
ChatGPTの動作は、伝える情報が増えるほど挙動が望ましくないものになるようになっていきました。そこで、ChatGPTが必要とする情報以外をレスポンスから削るようにしています。
例えば、ChatGPTの会話の上では**何がToDoにあるか**という情報以外は不要になります。そこで「userId」という情報をChatGPTに渡さないようにします。
このワンクッションを挟むことでAIのレスポンスの**安定性が向上**しました。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534453/03255e1b-f61e-92aa-2528-a4493490f56a.png">

### システムメッセージの追加
ChatGPTからのメッセージの他に、ChatGPTによって要求された内容もLINEに送信するようにしています。これは、ChatGPTが「ToDoを追加した」というメッセージを返したところで本当に動作したか保証できない可能性があったためです。
（実際にはChatGPTからのレスポンスと動作が一致しなかったことはほとんどありませんでした。）

# 良かった点
### 曖昧な言葉でも動作すること
このアプリケーションですが、ChatGPTへのメッセージに「ToDo」という言葉を入れなくても動作します。追加、削除をChatGPTに要求するにしても色々な言葉で指示ができました。
ToDoの作成では、「**todoに追加して**」と言えばしっかり動作してくれますが、これだけではなく、「**リストに追加して**」「**タスクに入れて**」という言葉でも動作してくれます。
このように特定の言葉でなくてもちゃんと動いてくれる、と言うことはFunction callingを使用する大きなメリットだと思われます。

また、「**タスク消して**」と送信するとChatGPTは「**消したい項目を教えてください**」と返信してくれます。これもUXの向上につながりやすいと感じます。

<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534453/0de261b3-b63b-2425-0c5e-ea43f30e7872.png">


### 複雑な指示でも動作すること
ChatGPTに「**A, B, Cをそれぞれリストに入れて**」と伝えると「A」「B」「C」という３つのToDoを作成してくれました。ToDo作成の関数は同時に作成できないため、この関数を3回発火してくれ他ことになります。
また、「**今あるタスクを全て完了にしておいて**」と伝えると、今すでにあるToDoを取得しに行き、取得したToDo全ての削除を行なってくれます。ToDoの取得からそれぞれに対する削除の発火まで行えるのは驚異的なことです。

<img width="70%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534453/5785fc80-9303-30c1-c11e-2edd06e5fff7.png">

```mermaid
sequenceDiagram
    User->>ChatGPT: 今あるタスクを全て完了にしておいて
    ChatGPT->>App: 「getAllTodo」を実行
    App->>ChatGPT: 「タスクA」「タスクB」を渡す
    ChatGPT->>App: 「doneTodo」で「タスクA」を削除
    ChatGPT->>App: 「doneTodo」で「タスクB」を削除
    ChatGPT->>User: 「タスクA」と「タスクB」を完了にしました
```

# 注意すべき点
やはりと言うべきか、ChatGPTを使用している関係上AIによる誤作動が発生します。
このアプリケーション作成中に出会った誤作動について紹介します。

### 勝手にTodoを追加しようとする
「以下のタスクを追加して」とChatGPTに送信すると、なぜか全く関係ないTodoが追加されます。
同様の状況で「タスクのタイトルを教えてください」と返すことも多いので、使い物にならないと言うわけではないですが不安は感じます。

<img width="70%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534453/772fa4f5-660b-c014-2274-bfd403b1df22.png">

### 存在しない関数を呼び出すことがある
あまりなかったが、存在しない関数の呼び出しをすることがありました。「タスクの削除」を頼んだら「doneTodo」ではなく「deleteTodo」という**存在しない関数**を呼び出そうとしたことがありました。
こちらの発生頻度はあまり高くなく、気にするほどではなさそうに感じます。

### 存在しないTodoを削除しようとする
「洗濯」というTodoがない状態で「洗濯を完了にして」と送信するとそのまま「洗濯」というTodoを削除しようとしてエラーが発生します。
このエラーはまま発生しましたが、このエラーが発生したことをChatGPTに伝えればその旨をユーザーに送信してくれるため、大きな問題にはならなそうに感じます。

### 解釈を間違えることがある
「〇〇と△△をタスクに追加して」と指定した時、「〇〇」と「△△」というToDoを追加せずに「〇〇と△△」というToDoを追加しようとします。こちらの発生頻度は多く困りましたが、仕様上仕方なく妥協した内容になります。
また、「『〇〇』と『△△』をタスクに追加して」と送信すると、ちゃんと「〇〇」と「△△」のTodoが追加されました。

<img width="70%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534453/15d3c55c-c5c6-cc4d-e85c-b21e072a83e7.png">

### ToDoの内容が特殊だとバグを起こす
ToDoのタイトルが「掃除」「買い物」という具体的なないようであれば動作の信頼度は高いのですが、一方でタイトルを「タスクA」や「タスクB」という名称にすると途端に誤作動する確率が上がります。
このようなタイトルのToDoを使用した場合にChatGPTがタイトル名を勘違いするのが主な原因です。
例えば、タイトルが「タスクA」のToDoを削除して欲しい時にChatGPTがタイトルが「A」のToDoだと勘違いして、タイトル「A」のToDoを削除しようとします。当然そのようなToDoはないのでエラーが発生してしまいます。
通常利用の範囲内であればこのようなエラーは起こりづらいと思うのですが、いずれにせよ注意は必要に感じました。

# 結論
Function callingを利用してアプリケーションを作成した結果、予期しない動作は多少あるものの、**通常の利用の範囲内ではしっかり動作する**という結果になりました。
曖昧な指示でもしっかりとアプリケーションを動かしてくれますし、複雑な動作も特に問題なく動いてくれました。この機構をアプリに取り入れることでUXの向上が望めるのはおそらく間違いないと思われます。

そうはいっても実用に持っていくには多少の課題もあるように感じました。
プロンプトや処理をいくら調整しても100%の動作保証をするのは難しいですし、AI関連の攻撃をされるのも恐ろしいです。

そのため、今回行ったような「**工夫や対策**」は有効な手段だと思われます。
ChatGPTからの要求をそのまま関数に渡すのではなく「**userIdの付加**」や「**システムメッセージの導入**」を行うことは良い対策になるのではないかと感じています。今回のアプリケーションでは行いませんでしたが、購入などの重要な動作を行う際には、ユーザーに「本当にこの商品を購入しますか？」など再度尋ねるような仕様にするという対策も有効そうです。

# 感想
Function callingを使うとAlexaやGoogleHomeが簡単に作れそうに思えてきてしまうのが恐ろしいです。
個人開発でもこのレベルのことができるのは嬉しいですし、どんなものができあがっていくのだろうと思うと夢が広がります。

# スペシャルサンクス
アプリのテストや記事の推敲を手伝ってくれた友人