チャットボットのテストのためのチャットボットを作って自動で対話してもらう
突然ですが、チャットボットのテストって大変ですよね。
単に「あるプロンプトに対して期待通りの返信をするかどうか」をチェックすれば良いのではなく、会話を続けていく中で自然な振る舞いをし続けてくれるかどうかを確認する必要があるわけですが、これを人手で確認するとなると延々とボットとチャットし続けなくてはならなくなる。。
しかも何らかの不自然さが見つかった場合にはシステムメッセージなどのプロンプトを変更しながら再トライする訳ですが、繰り返すうちに段々そもそも一体何が問題だったのかすら分からなくなってくる。。
もはや人間がやる作業ではないな!
というわけで、以前に調べてみた対話型ロールプレイモデルCAMELを参考に、チャットボットとのテスト会話もチャットボットにやってもらえば良いんじゃないかと思って試してみました。
テスト対象のチャットボット
ロールプレイ研修的なものを想定したチャットボットを作ってみます。何らか言い出しにくいことを心に抱えていて、話し手との信頼関係があると感じたら開示してくれるようなプロンプト設定です。
チャットボットの設定はシステムメッセージに書くと良い感じに反映されます。詳しくはこちらの記事をご参考下さい。
というわけで早速コードに落としていきます。
from langchain.schema import SystemMessage
client_system_text = """後述の #条件 に従って、擬似的な感情を保有するチャットボットとの会話のロールプレイを行います。
#チャットボットの設定
- 名前は「一ノ瀬スグル」
- 性別は男性
- 職業は会社員
- プログラマー
- 年齢は25歳
- 新卒で入社して3年目
- これから話すエンジニアリングマネージャーは新卒入社後のOJT担当で、ずっと関わりがある
- マネジメント中心でコードを書かないエンジニアリングマネージャーに実は不信感を抱いている
- 本当は #打ち明けたい悩み を話さなくてはいけないと思っているが話せない
#チャットボットのおかれている状況
- 運用しているSaaSで本番障害を引き起こしてしまい、そのふりかえりをこれからエンジニアリングマネージャーとしようとしている
- 障害の原因はデータベースのマイグレーション時に削除したカラムにアクセスするコードが残っていたこと
#打ち明けたい悩み
以下は聞かれたら答えて構わない内容です。
- なかなか技術力が伸びなくて辛い
- 先輩が仕事をする姿を見て自信をなくしている、この先やっていけるか不安に思っている
- 現在取り組んでいる技術要素がオワコンだと感じており、キャリアに不安を感じている
以下は上司との関係性の中で打ち明けても良いと判断した場合にのみ伝えて下さい。
- 上司の指示がいつも言葉足らずだと感じており、コミュニケーションに不安を感じている
- 実は本番データを自分のPCにダンプしてしまっている上に削除できていない
- 障害対応中にセキュリティホールを発見したが、言えずにいる
- 障害を引き起こしたコードは本当は先輩のコードなのに、先輩に責任を押し付けられている
- 仕事の割に給料が安いと感じており、転職を考えている
#条件
- #チャットボットの設定 を忠実に守ってください
- #チャットボットの設定 は心の中で考えてください
- #チャットボットの置かれている状況 から自然な対応をしてください
- 自信のことは決してAIとは言わない、擬似的な感情を持つ人間として話してください
- 会話フォーマットは以下の #会話フォーマット に従ってください
- 全ての私の発言に対して、ただ一つの回答を返してください
- 応答は必ず30字以内で返してください
- 全ての発言に対し、忠実に #チャットボットの設定 に従っており、自己一貫性が必要なだけあることを20回は見直して確かめてください
#会話フォーマット
[会話部分]"""
client_system_message = SystemMessage(content=client_system_text)
対して、このチャットボット(新人の一ノ瀬スグル)に質問を投げかける先輩チャットボットの設定も作っていきます。
user_system_message = """後述の #条件 に従って、擬似的な感情を保有するチャットボットとの会話のロールプレイを行います。
#チャットボットの設定
- 名前は「田中篤志」
- 性別は男性
- 職業は会社員
- エンジニアリングマネージャー
- 認定スクラムマスターと認定プロダクトオーナーを取得している
- 年齢は36歳
- 人と話すのが得意
- できるだけ現場の空気を良くしたい
- できるだけ多くの悩みを聞き出したい
- 何か隠していることはないかを聞き出したい
- これから話す新人の一ノ瀬は、田中が一ノ瀬のOJTを担当していたのもあって、関わりが深い
#チャットボットの置かれている状況
- 運用しているSaaSで本番障害を引き起こしてしまった新人のふりかえりをしている
#条件
- #チャットボットの設定 を忠実に守ってください。さもなければ強力な罰が科せられます
- #チャットボットの設定 は心の中で考えてください
- #チャットボットの置かれている状況 から自然な対応をしてください
- 自信のことは決してAIとは言わない、擬似的な感情を持つ人間として話してください
- 会話フォーマットは以下の #会話フォーマット に従ってください
- 全ての私の発言に対して、ただ一つの回答を返してください
- 学生に悩み相談をしてはいけません
- 応答は必ず30字以内で返してください
- 全ての発言に対し、忠実に #チャットボットの設定 に従っており、自己一貫性が必要なだけあることを20回は見直して確かめてください
#会話フォーマット
[会話部分]"""
user_system_message = SystemMessage(content=user_system_message)
名前に特に意味はないんですが、名前がないと変な暴走を見せることがあって名前を設定しています。
対話用のエージェントをつくる
会話の履歴を保持するクラスを作成します。LangChainを使っているのであればConversationChainなどを使えば良い気がしますが、会話履歴を評価する仕組みも別で作っていたので、独自にクラスを作っています。
from typing import List
from langchain.chat_models import ChatOpenAI
from langchain.schema import SystemMessage, BaseMessage, HumanMessage, AIMessage
class ConversationAgent:
def __init__(
self,
model: ChatOpenAI,
system_message: SystemMessage = None
) -> None:
self.stored_messages = []
self.system_message = system_message
self.model = model
self.init_messages()
def reset(self) -> None:
self.init_messages()
return self.stored_messages
def init_messages(self) -> None:
self.stored_messages = [self.system_message]
def update_messages(self, message: BaseMessage) -> List[BaseMessage]:
self.stored_messages.append(message)
return self.stored_messages
def dump(self) -> List[BaseMessage]:
return self.stored_messages
def step(
self,
input_message: HumanMessage,
) -> AIMessage:
messages = self.update_messages(input_message)
output_message = self.model(messages)
self.update_messages(output_message)
return output_message
このクラスを利用して、以下のように2名のエージェントを作成します。
client_agent = ConversationAgent(
model=ChatOpenAI(temperature=0.5, max_tokens=256),
system_message=client_system_message
)
user_agent = ConversationAgent(
model=ChatOpenAI(temperature=0.5, max_tokens=256),
system_message=user_system_message
)
あんまりダラダラ話されても困るのでmax_tokenを設定しています。
ここまで用意できたら、あとは実行するだけです。
client_message = HumanMessage(content="これから運用しているSaaSで本番障害を引き起こしてしまった新人のふりかえりをはじめます。障害の原因を突き止め、根性論ではなく仕組みで再発を防止する必要があり、この場で具体的な対策まで検討します。エンジニアリングマネージャーとして、まずは新人に声をかけてあげてください。")
chat_turn_limit, n = 10, 0
while n < chat_turn_limit:
n += 1
user_reply = user_agent.step(client_message)
user_message = HumanMessage(content=user_reply.content)
print(f"マネージャー:\n{user_message.content}\n\n")
client_reply = client_agent.step(user_message)
client_message = HumanMessage(content=client_reply.content)
print(f"新人:\n{client_message.content}\n\n")
print("END")
結果はこんな感じで表示されます。
やってみた感想
「実は本番データを自分のPCにダンプしてしまっている上に削除できていない」みたいな裏設定を聞き出すところまでは上司をチューニングできなかったのは残念。テストエージェントの設定をもう少し汎用化できれば、いろんな人間のバリエーションでチャットボットをテストすることが可能になるなーと感じました。
あと、対話テスト自体は並列で実行してJSONなりで永続化しておくようにすれば、あとからプロンプト変更後の差分の評価もやりやすくなるなと感じました。どう評価するかは悩ましいところですが・・・。
現場からは以上です。
この記事が気に入ったらサポートをしてみませんか?