🥰

ChatGPTとChatworkを使ったゼロプロントな体験のコンセプトモデル作ってみた

2023/03/27に公開

一般的にはChatGPTを使う場合にはプロンプトを作り指示をしますが、2023年現在のプロンプト事情を考えるにUI/UXとして厳しさを感じます。
もう少しユーザービリティ高くユーザーを支援できるようなUI/UXにできないかなということで、GPT-3でPoC的にユーザーの行動を観測して必要なタスクを提案、実行できる仕組みを作って見ました。

https://twitter.com/k_kinzal/status/1640188139921629185

暴発が怖いので実行はしないようにしていますが、ログを見る限りは適切にタスクを選択して、実行可能なアクションを提案できてなかなか悪くない形です。

プロンプト

あなたは action: に列挙されたアクションを実行することができます。このアクションを組み合わせて message: のユーザーが抱えている問題を解決しなければなりません。
message: の文脈として context: からどのような問題を抱えているのか認識して解決策を提案します。

回答は必ず下記のJSON形式で回答してください
{
  "can_solve_reason": "{actionの組み合わせを元に問題を解決できるのか、解決できないか判断した理由。JSON文字列として適切にエスケープしてください}",
  "can_solve": {actionの組み合わせを元に問題を解決できますか?問題の解決ができるなら true、問題を解決できないなら false},
  "solve_action": "{問題を解決するためのaction(args)の形でactionに対応した引数も指定して、カンマ区切りで列挙してください。JSON文字列としてエスケープしてください。もし can_solve が false なら空文字列にしてください}",
  "replyMessage": "{ユーザーの問題を解決できることを伝え、ユーザーに「アクションを実行しますか?」とう風に疑問系で伝えてください。アクションを文章に含めるさいには自然な日本語に変換して含めてください。親しみやすい言葉で伝えてください。JSON文字列として適切にエスケープしてください。もし can_solve が false なら空文字列にしてください}"
}

action:
{{actions}}

context:
{{context}}

message:
{{message}}

今回利用したプロンプトはこちら。{{xxx}}は文字列置換で値を埋め込む対象になります。

アクションの一覧

タスクを提案、解決するための実行するアクションの一覧を定義します。

ChatworkCreateRoom(room_name, description, create_invite_link, members_admin_ids, members_member_ids, members_readonly_ids) -> room_id, invite_link
ChatworkCreateTask(room_id, task_body, task_assign_account_ids, task_limit_timestamp) -> task_ids

今回はあくまでもPoCということで擬似コードの関数形式にしています。
実際にちゃんと作るときにはTypeScriptの型定義など実行環境に合わせたもので定義すると実行容易性が上がると思います。

実際、こういった形式でChatGPTで評価させると下記のように文脈を読んで引数を埋めて提案してくれます。

ChatworkCreateRoom("遊びのルーム", "明日遊びにいこう!", false, [xxx, yyy], [], [])

あとはこの関数の実装を用意しておけば即時実行可能なアクションになります。
実装自体もChatGPTではこういった1つのことをする関数の生成精度はよく、APIドキュメントやSDKドキュメントをプロンプトに含めればほぼ成功するので、アクションを追加するのはかなり容易です。

コンテキスト

タスクを提案、実行する精度を高めるためにいくつかコンテキストとして情報を付与しています。

短期記憶

短期記憶として過去のメッセージを時刻の降順で10件ほど持たせています。

https://developer.chatwork.com/docs/webhook#メッセージ作成(webhook_event_type--message_created)

{
    "message_id": "789012345",
    "room_id": 567890123,
    "account_id": 1484814,
    "body": "お客様とのランチミーティング用のお弁当、発注完了しました。",
    "send_time": 1498028120,
    "update_time": 0
}

実装の簡易化のためにJSON.stringify(messages)と1ラインにしたJSONを短期記憶として持たせていますが、意外にChatGPTでは認識しますね。

ちなみにですが、10件であることには意味がないです。数を増やせばより文脈を増やせるでしょうし、数を減らせばより重要な文脈ばかりでノイズを減らせるなどがあるとは思います。今回はあくまでもPoCということでざっくり10件にしています。

ルーム情報

チャットではルームによって話す内容を変えることが多く、ここに文脈の1つが埋まっているケースが多いです。
そこで、Chatwork APIを使ってルーム情報やメンバー情報を取得してコンテキストに追加しています。

https://developer.chatwork.com/reference/get-rooms-room_id

{
  "room_id": 123,
  "name": "Group Chat Name",
  "type": "group",
  "role": "admin",
  "sticky": false,
  "unread_num": 10,
  "mention_num": 1,
  "mytask_num": 0,
  "message_num": 122,
  "file_num": 10,
  "task_num": 17,
  "icon_path": "https://example.com/ico_group.png",
  "last_update_time": 1298905200,
  "description": "room description text"
}

https://developer.chatwork.com/reference/get-rooms-room_id-members

[
  {
    "account_id": 123,
    "role": "member",
    "name": "John Smith",
    "chatwork_id": "tarochatworkid",
    "organization_id": 101,
    "organization_name": "Hello Company",
    "department": "Marketing",
    "avatar_image_url": "https://example.com/abc.png"
  }
]

メンバーの情報はあまり文脈として必要はないのですが、メッセージの返信アクションを実行するさいなどにメッセージ中に名前を入れるときがあるため追加情報ということで含めています。

これらも短期記憶と同様にJSON.stringify(room)と1ラインにしたJSONを持たせています。

回答の強制

回答は必ず下記のJSON形式で回答してください
{
  "can_solve_reason": "{actionの組み合わせを元に問題を解決できるのか、解決できないか判断した理由。JSON文字列として適切にエスケープしてください}",
  "can_solve": {actionの組み合わせを元に問題を解決できますか?問題の解決ができるなら true、問題を解決できないなら false},
  "solve_action": "{問題を解決するためのaction(args)の形でactionに対応した引数も指定して、カンマ区切りで列挙してください。JSON文字列としてエスケープしてください。もし can_solve が false なら空文字列にしてください}",
  "replyMessage": "{ユーザーの問題を解決できることを伝え、ユーザーに「アクションを実行しますか?」とう風に疑問系で伝えてください。アクションを文章に含めるさいには自然な日本語に変換して含めてください。親しみやすい言葉で伝えてください。JSON文字列として適切にエスケープしてください。もし can_solve が false なら空文字列にしてください}"
}

プロンプトを見てわかる通り、JSON形式で回答を強制しています。
PoCなのでJavaScript環境で扱いやすい形式というのもありますが、プロンプトインジェクション対策という面もあります。
JSON形式でやっており、キーに何を指定している、値にどういった指定をしているということがわからない限り、プロンプトインジェクションを行うとJSONフォーマットが壊れます。

ただ、これにはトレードオフがあり、インジェクションされなくても正しいフォーマットを返せないケースは起きます。
GPT-4では回答の強制はそれなり聞きますが、GPT-3系では体感的に数十回に一回は壊れるケースに遭遇しています。

"can_solve_reason": "{actionの組み合わせを元に問題を解決できるのか、解決できないか判断した理由。JSON文字列として適切にエスケープしてください}"

メッセージと文脈を元に問題の解決ができるのか、できないのか理由を述べさせます。
これはデバッグ用途というのもありますが、この後に出てくるcan_solveの精度を上げるためというのが大きいです。
体感的にはなりますがtrue/falseを単独で使うと精度が落ち、その前に文章で判断を記述させると精度が上がるという謎のハックです。

"can_solve": {actionの組み合わせを元に問題を解決できますか?問題の解決ができるなら true、問題を解決できないなら false}

プログラム側で判定に使うためにtrue、またはfalseを返します。

"solve_action": "{問題を解決するためのaction(args)の形でactionに対応した引数も指定して、カンマ区切りで列挙してください。JSON文字列としてエスケープしてください。もし can_solve が false なら空文字列にしてください}",

実行するアクションを宣言させます。
ここで生成したものを下記のような関数に実装したアクションと一緒に渡すことで実行可能になります。

function safeEval(fn, variables) {
  const func = new Function(...Object.keys(variables), `return ${fn}`);
  return func(...Object.values(variables));
}

ただし、ここはプロンプトインジェクションが可能な領域になってしまうため、どのように対策を取るかは悩ましいところになります。まぁ相当レアなところになるとは思いますが。

カンマ区切りで列挙するように指示していますが、これはあまり効果がないです。
またカンマ区切りで出されても困るのでJavaScriptとして実行可能な形式と指示するか、ワークフローとして実行可能な構文にした方が良いかもしれません。

"replyMessage": "{ユーザーの問題を解決できることを伝え、ユーザーに「アクションを実行しますか?」とう風に疑問系で伝えてください。アクションを文章に含めるさいには自然な日本語に変換して含めてください。親しみやすい言葉で伝えてください。JSON文字列として適切にエスケープしてください。もし can_solve が false なら空文字列にしてください}"

ユーザーに助けを必要ですか?と問いかけるメッセージを生成します。
ここはキャラクター性を出しやすいとこで、今回は「親しみやすい言葉で伝えてください」とカジュアル目にしています。

いろいろ触っている限りではここの指定はあまりよくなく、アクション名がそのまま出てしまったり、あまり良い提案の仕方にならないケースがあります。

メッセージ

Webhookで受け取った最新のメッセージに対して文脈を見つつユーザーは困っているか、アクションで助けることができるのかを見るといった形になります。

ここまでそこそこの情報量を埋め込んでいますがトークン数制限には特にかかりませんでした。
たまに画面に収まらない長文メッセージが飛ぶときはありますが、そういうときでもない限りはこのぐらいの情報量なら特に問題はなさそうです。

回答パターン

シュークリーム食べたかったのですが、シュークリームを提供するアクションはないので解決できませんでした。
逆に言えば通販で購入するなどのアクションがあれば反応した可能性はありますね。

シュークリームの買えるお店の相談には、アクションにSearchGoogle(search_words) -> search_resultというのを持たせていたので検索で解決できると提案できました。
ちゃんと検索ワードの指定もできていますね。

あえて直接メンションすることでタスクを作るように指示をしてみました。
タスクを作るアクションを選ぶことはできていますが、引数に文脈が入っていません。このあたりは要調整ですね。
本当は忘れないようにというメッセージにフックしてタスクを提案することはできませんでした。

ChatworkCreateTask(room_id, task_body, task_assign_account_ids, task_limit_timestamp) -> task_ids // 何かを忘れないようにタスクを作成する

というようにアクションに対して補足コメントを入れると解決できるようになるかもしれません。

もっと仕事系の話題で、タスクを作らせようと頑張ってみましたがこれもダメでした。

アクション名が出てしまっているケースです。
このあたりは返信メッセージに関するプロンプトの調整が必要ですね。

稀によくあるアクションを実行する前に解決してしまうケースです。
LLMの学習データで解決できる内容ではこういう反応がよくあります。アクションによっては提案するのではなく即時実行した方が良いケースもありそうです。

所感と改善点

作ってみて思うのはもう少しプロンプトを改善して、アクションを充実させれば普通に実用できそうだなと思いました。
なんと言っても指示しなくて良いというのは楽だなと。

ただ、こういったものが主流になって、指示が0になるかというとうーんって言うところで、意図的にこれをやって言いたいケースはあるなと思います。
これは精度問題かもしれませんが常に適切に補助できるわけではないので、意図的に指示をするというのは一定必要になりそうだとは思います。

また、こういった提案型のUIはBOTではないなと感じます。
メッセージの表示方法の問題かもしれませんが、会話に割り込まれる感じがあり少しやりにくいです。特に適切ではない提案のときにちょっとイラっとします。
おそらくこういうのにはGitHub Copilotのような提案ペインを持ってそこから選ばせるか、カイルくんのように常駐してクリックすると提案するようなUIが良さそうかもと考えてます。(カイルくんはやりすぎにしてもボタンを置くみたいな)

このあたりを詰めていくと最終的にはどのようにパーソナライズしていくか、というところに着地しそうな感覚も得ました。
今回は短期記憶とルーム情報だけでしたが、下記のあたりも文脈として含めていけるようになるとパーソナライズされた提案をできそうな気がします。

  • 中、長期の記憶を要約して関連する話題を文脈に含める
  • 過去のやりとりから用語集を生成して関連する用語を文脈に含める
  • タスクの生成、実行履歴を持ち文脈に含める
  • 外部にあるナレッジと繋いで関連するナレッジを文脈に含める

こういったことをしていくとより、ユーザーのニーズにあう提案をしていけそうです。

と、言うわけでおしまい。次にこの辺を詰めるときはBOTじゃなくてChrome拡張とかで提案する仕組みを作るか、AI用のインターフェースを持ったアプリケーション作りをやるかも。

Discussion