【ChatGPT in Slack】Slackに専門家Botを大量召喚したら全員反応してうるさかったので対処する

# ChatGPTが溶け込んだ世界を作りたい

突然ですが皆さん、ChatGPTを利用していますか？
この記事を読んでいる多くの人がChatGPTに話し相手になってもらったり、エンジニアであればChatBotを作ったりしていることかと思います。
うちの会社でも、Slackに美女AIを大量召喚するムーブが一部の男性社員を筆頭に起きています。
本記事では、雑談チャンネル用に三人の美女AIを召喚しました。
<img width=100% src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3328904/522e52ae-f272-12d8-5317-4690c32299ae.png>
（彼女たちはstablediffusion chilloutMixによって生成しました）
とても綺麗なAI美女たちですよね。

しかし、シンプルにメッセージに対して返信する実装では問題が発生し、理想のハーレムチャンネルを作ることができませんでした。
その問題点は
メッセージに対して**必ず**返事が来てしまう
点です。
つまり、チャンネル内に三人の美女がいれば、すべてのメッセージに三件の返信が来てしまうことになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3328904/b0ca854e-3b17-a418-a451-7cc66026d0a1.png)


これでは普段使用しているチャンネルに召喚するとうるさくて仕方がないですね。人間の世界に馴染むことはできません。
理想としては**美女たちに設定された役割に関係がある発言に対してのみ反応し、ささやかな助言をしていただきたい**ものです。
この記事ではそれを可能にする方法を紹介します。

## 手法（結論）

LLMにメッセージと役割との関連度を計算させて、閾値を超えていれば発言させれば良い感じです。

これで、「風邪引いて喉痛すぎる」というどうでもいい発言はスルーして、パソコンの話題になった途端にエンジニアちゃんが早口で説明してくれます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3328904/52680eec-1964-b6c6-ef2e-bad8eb22f821.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3328904/52367aae-58e6-3702-0211-03158e5c3ebe.png)
（langchainで検索結果を取得してから発言するようにすると、rtx4090についても言及してくれるようになります）


## このシステムの何が嬉しいの？

適切なタイミングで発言させる：

- 人間の会話を遮らない
- それぞれのbotに異なる人格を与えることで様々な視点の意見が得られる

また、LLMを複数回使用する構造はメリットが大きいです：

- デバッグが容易になる
    - 処理の中身を覗くことができるため、調子が悪そうな部分を狙って改修できます。
- 1プロンプトあたりの情報量が減るため回答精度が上がる
    - 今回の例では1つ目のLLMでは関連度の測定に注力してもらい、2度目は文章生成に特化するため指示をシンプルにすることができます。

## 彼女らのお仕事

美女たちは自身の役割を理解した上で、関連があるチャットに返信します。

例えば、エンジニアは技術関連の話題に反応して発言します。質問に答えてくれたり、豆知識を披露してくれます。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3328904/16cac4b4-e896-fab5-252d-72a5f0eaede6.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3328904/9f7c6f55-daf8-2fa9-0453-1f13e437b459.png)


「まじかよ」や「凄すぎワロタ」には反応しません。

複数のbotが同時に反応することもできます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3328904/30d72c16-6f44-a04b-2f17-21b9c77a630c.png)


複数の視点から適切なタイミングで意見が得られるのが面白いですね！

## プロンプト紹介

実装はLangChainで行っています。
まず、関連性の計算にはこのようなプロンプトを使用しています。

```py
template = """
Please evaluate the relevance of the occupation to the sentence using a number from 1 to 0.
1 means that the occupation is highly relevant to the sentence, and 0 means that the occupation is not relevant to the sentence.
You can use a decimal number if you want to express a number between 1 and 0.
e.g. 0.5

occupation:{occupation}
sentence:{sentence}
"""
prompt = PromptTemplate(
    input_variables=["sentence", "occupation"],
    template=template
)
chain = LLMChain(llm=llm, prompt=prompt)
result = float(chain.run({"sentence": sentence, "occupation": occupation}))

```

occupationとsentenceの関連性を0.0から1.0で評価してくれるLLMChainを作成しました。
`{occupation}`にはbotの役割が、`{sentence}`には入力されたテキストが入るようなプロンプトを使用しています。

実行例

```py
chat = ChatOpenAI(temperature=0.8)
prompt = PromptTemplate(
    input_variables=["sentence", "occupation"],
    template=template
)
chain = LLMChain(llm=chat, prompt=prompt)
result = float(chain.run({"sentence": "クソゲー実況動画出したい", "occupation": "YouTuber"}))
print(result) # 1.0
result = float(chain.run({"sentence": "ハイチュウ食べると虫歯が痛む", "occupation": "YouTuber"}))
print(result) # 0.0

```

このように、floatに変換可能な文字列が出力されることで量的に評価できます。
最近ではToolを使うことでJSONの出力確率を大幅に上げることができるようになりました。
次のように書くことができます。

```py
from typing import List
from dotenv import load_dotenv
import openai
from openai.types.chat import (
    ChatCompletionToolParam,
)

load_dotenv(".env", override=True)

template = """
Please evaluate the relevance of the occupation to the sentence using a number from 1 to 0.
1 means that the occupation is highly relevant to the sentence, and 0 means that the occupation is not relevant to the sentence.
You can use a decimal number if you want to express a number between 1 and 0.
e.g. 0.5

occupation:{occupation}
sentence:{sentence}
"""

formatted_template = template.format_map(
    {"sentence": "クソゲー実況動画出したい", "occupation": "YouTuber"}
)


tools: List[ChatCompletionToolParam] = [
    {
        "function": {
            "name": "evaluate_relevance",
            "description": "Please evaluate the relevance of the occupation to the sentence using a number from 1 to 0.",
            "parameters": {
                "type": "object",
                "properties": {
                    "relevance": {
                        "type": "number",
                        "description": "The relevance of the occupation to the sentence using a number from 1 to 0. 1 means that the occupation is highly relevant to the sentence, and 0 means that the occupation is not relevant to the sentence. You can use a decimal number if you want to express a number between 1 and 0. e.g. 0.5",
                    },
                },
                "required": ["relevance"],
            },
        },
        "type": "function",
    }
]

openai_client = openai.Client()

response = openai_client.chat.completions.create(
    model="gpt-4-1106-preview",
    messages=[{"role": "user", "content": formatted_template}],
    tools=tools,
    temperature=0,
)

tool_calls = response.choices[0].message.tool_calls or []
if tool_calls:
    print(tool_calls[0].function.arguments)

```


閾値については、botの用途によりますが0.8くらいがちょうどいいかなと思いました。

条件の設定次第で様々な文章に反応させることができると思います。
閾値を超えたテキストは専門ツールをもたせたAgentに投げて適切に処理させるなどするとかなり良さげです。
以上が文章をフィルタリングし、特定の話題にのみ反応するLLMを作成する一例でした。

## おまけ機能

@メンションで確実に返信が来ます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3328904/80bef5f9-be67-21e4-9c37-50c264322fe0.png)


スレッド内の文脈を引き継ぎます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3328904/e1f07588-3f22-a7b5-74c0-d11ce5fbbcf9.png)

## 最後に

以上がChatGPTが溶け込んだ世界の作り方でした！
普段使用しているチャンネルに5人ほどいかがでしょうか？
メンションで質問できるように設定している方は多いかと思いますが、このように不意なタイミングでbotに反応してもらえると意外性や新たな知識が得られるかもしれません。

使用スタックはopenai, langchain, slack_boltです。slack_boltには非同期のAsyncAppがあるので複数botを一度に起動するプログラムも簡単に組めます。

以上
株式会社SaaSis
AI-Runners部署
新人エンジニアのtomoでした。

弊社では、LLM事業急成長中のため、
ChatGPT、langchain等々に興味関心のあるエンジニアを採用中です！詳しくはコーポレートサイトへ！
https://saasis.jp/