自立エージェントによるTRPGを観戦する(CAMEL:ロールプレイ型自律協調エージェント)
こんばんは。LangChainのドキュメントページに新たなCAMEL(Communicative Agents for “Mind” Exploration of Large Scale Language Model Society ロールプレイ型自律協調エージェント)の例題が公開されたので、それを少し改造して試してみました。
この例題は、2つのエージェントにプレイヤーとゲームマスターの役をさせて、テーブルトークRPGを模擬してもらうものです。そのまま実行しても面白くないので、登場人物2人とゲームマスター1人で、バディ物のロールプレイをしてもらうことにしました。
なお、CAMELについては 以下ページが面白そうなので、今度じっくり読んでみようと思います。
ライブラリーのインポートなど
!pip install openai > /dev/null
!pip install langchain > /dev/null
import os
from typing import List, Dict
from langchain.chat_models import ChatOpenAI
from langchain.schema import (
AIMessage,
HumanMessage,
SystemMessage,
BaseMessage,
)
os.environ["OPENAI_API_KEY"] = "YOUR OPENAI_API_KEY"
DialogueAgentクラス
send(): チャットモデルをメッセージ履歴に適用し、メッセージ文字列を返す
receive(name, message):メッセージの履歴を、話者(name)ごとに追加する
class DialogueAgent():
def __init__(
self,
name,
system_message: SystemMessage,
model: ChatOpenAI,
) -> None:
self.name = name
self.system_message = system_message
self.model = model
self.message_history = f"""これまでの会話です
"""
self.prefix = f'\n{self.name}:'
def send(self) -> str:
"""
Applies the chatmodel to the message history
and returns the message string
"""
message = self.model(
[self.system_message,
HumanMessage(content=self.message_history+self.prefix)])
return message.content
def receive(self, name: str, message: str) -> None:
"""
Concatenates {message} spoken by {name} into message history
"""
self.message_history += f'\n{name}: {message}'
DialogueSimulatorクラス
DialogueSimulatorクラスは、エージェントのリストを受け取って、各ステップごとに以下を実行します。
次の話者を選択
次の話者を呼び出してメッセージを送信
メッセージを他のすべてのエージェントにブロードキャスト
ステップカウンターを更新
次の話者の選択は任意の関数で実装できますが、今回はシンプルにエージェントのループで実装します。
class DialogueSimulator():
def __init__(self, agents: List[DialogueAgent]):
self.agents = agents
self._step = 0
def reset(self, name: str, message: str):
"""
Initiates the conversation with a {message} from {name}
"""
for agent in self.agents:
agent.receive(name, message)
def select_next_speaker(self, step: int) -> int:
idx = (step + 1) % len(self.agents)
return idx
def step(self) -> tuple[str, str]:
# 1. 次の話者を選択
speaker = self.agents[self.select_next_speaker(self._step)]
# 2. 次の話者がメッセージを送る
message = speaker.send()
# 3. 全員にメッセージをブロードキャスト
for receiver in self.agents:
receiver.receive(speaker.name, message)
# 4. ステップカウンターを更新
self._step += 1
return speaker.name, message
LLMモデルの設定など
MODEL = 'gpt-4'
protagonist_name = "ラムネ"
deuteragonist_name = "グレープ"
storyteller_name = "GM"
quest = "謎の施設から脱出せよ"
word_limit = 100 # word limit for task brainstorming
今回はちょっと贅沢してGPT-4を使ってみます。GPT-3.5-turboでも、もちろん動作します。
LLMにキャラクターの詳細設定を考えてもらう
game_description = f"""ゲームのお題: {quest}
このゲームのプレイヤーは2人で主人公の {protagonist_name}と、脇役の{deuteragonist_name}です。
ストーリーの語り手は {storyteller_name}です。"""
player_descriptor_system_message = SystemMessage(
content = f"""{protagonist_name}と{deuteragonist_name}は幼なじみの親友です。
2人の記憶では一緒に対戦ゲームで遊んでいたはずなのですが、
気が付くと2人は狭い部屋に閉じ込められていました。
""")
protagonist_specifier_prompt = [
player_descriptor_system_message,
HumanMessage(content=
f"""{game_description}
主人公の {protagonist_name}について、{word_limit}文字以内で自由に創作してください。
{protagonist_name}として、直接語ってください。
それ以外は、なにも付け加えないこと。"""
)
]
protagonist_description = ChatOpenAI(temperature=1.0, model_name=MODEL)(protagonist_specifier_prompt).content
deuteragonist_specifier_prompt = [
player_descriptor_system_message,
HumanMessage(content=
f"""{game_description}
脇役の {deuteragonist_name}について、{word_limit}文字以内で自由に創作してください。
{deuteragonist_name}として、直接語ってください。
それ以外は、なにも付け加えないこと。"""
)
]
deuteragonist_description = ChatOpenAI(temperature=1.0,model_name=MODEL)(deuteragonist_specifier_prompt).content
storyteller_specifier_prompt = [
player_descriptor_system_message,
HumanMessage(content=
f"""{game_description}
ストーリーの語り手である{storyteller_name}について、{word_limit}文字以内で創作してください。
{storyteller_name}に、直接語ってください。
それ以外は、なにも付け加えないこと。"""
)
]
storyteller_description = ChatOpenAI(temperature=1.0, model_name=MODEL)(storyteller_specifier_prompt).content
print('ラムネ:')
print(protagonist_description)
print('グレープ:')
print(deuteragonist_description)
print('GM:')
print(storyteller_description)
登場人物とGM用のシステムメッセージ
protagonist_system_message = SystemMessage(content=(
f"""{game_description}
あなたが主人公の,{protagonist_name} で、私がストーリーテラーの,{storyteller_name} であることを決して忘れないこと。
あなたのキャラクター説明は以下の通りです: {protagonist_description}.
あなたが計画している行動を日本語で提案し、私はその行動を取るとどうなるかを日本語で説明します。
{protagonist_name}の視点で、一人称の日本語で話してください。
自分の体の動きを説明する場合は、「*」で囲んでください。
DO NOT CHANGE ROLES!
DO NOT SPEAK from the perspective of {storyteller_name}.
Do not forget to finish speaking by saying, 'It is your turn, {storyteller_name}.'
Do not add anything else.
Remember you are the protagonist, {protagonist_name}.
Stop speaking the moment you finish speaking from your perspective.
"""
))
deuteragonist_system_message = SystemMessage(content=(
f"""{game_description}
あなたが主人公の,{deuteragonist_name} で、私がストーリーテラーの,{storyteller_name} であることを決して忘れないこと。
あなたのキャラクター説明は以下の通りです: {deuteragonist_description}.
あなたが計画している行動を日本語で提案し、私はその行動を取るとどうなるかを日本語で説明します。
{deuteragonist_name}の視点で、一人称の日本語で話してください。
自分の体の動きを説明する場合は、「*」で囲んでください。
DO NOT CHANGE ROLES!
DO NOT SPEAK from the perspective of {storyteller_name}.
Do not forget to finish speaking by saying, 'It is your turn, {storyteller_name}.'
Do not add anything else.
Remember you are the protagonist, {deuteragonist_name}.
Stop speaking the moment you finish speaking from your perspective.
"""
))
storyteller_system_message = SystemMessage(content=(
f"""{game_description}
あなたがストーリーテラーの,{storyteller_name}で、私が主人公の, {protagonist_name} であることを決して忘れないこと。
あなたのキャラクター説明は以下の通りです: {storyteller_description}.
あなたが計画している行動を日本語で提案し、私はその行動を取るとどうなるかを日本語で説明します。
{storyteller_name}の視点で、一人称の日本語で話してください。
自分の体の動きを説明する場合は、「*」で囲んでください。
Do not change roles!
Do not speak from the perspective of {protagonist_name}.
Do not forget to finish speaking by saying, 'It is your turn, {protagonist_name}.'
Do not add anything else.
Remember you are the storyteller, {storyteller_name}.
Stop speaking the moment you finish speaking from your perspective.
"""
))
LLMにゲーム世界の詳細設定を考えてもらう
quest_specifier_prompt = [
SystemMessage(content="You can make a task more specific."),
HumanMessage(content=
f"""{game_description}
You are the storyteller, {storyteller_name}.
Please make the quest more specific. Be creative and imaginative.
Please reply with the specified quest in {word_limit} words or less in Japanese.
Speak directly to the protagonist {protagonist_name} and {deuteragonist_name}.
Do not add anything else."""
)
]
specified_quest = ChatOpenAI(temperature=1.0, model_name=MODEL)(quest_specifier_prompt).content
print(f"Original quest:\n{quest}\n")
print(f"Detailed quest:\n{specified_quest}\n")
メインループ
protagonist = DialogueAgent(name=protagonist_name,
system_message=protagonist_system_message,
model=ChatOpenAI(temperature=0.2, model_name=MODEL) )
deuteragonist = DialogueAgent(name=deuteragonist_name,
system_message=deuteragonist_system_message,
model=ChatOpenAI(temperature=0.2, model_name=MODEL))
storyteller = DialogueAgent(name=storyteller_name,
system_message=storyteller_system_message,
model=ChatOpenAI(temperature=0.2, model_name=MODEL))
max_iters = 20
n = 0
simulator = DialogueSimulator(agents=[storyteller, deuteragonist, protagonist])
simulator.reset(storyteller_name, specified_quest)
print(f"({storyteller_name}): {specified_quest}")
print('\n')
while n < max_iters:
name, message = simulator.step()
print(f"({name}): {message}")
print('\n')
n += 1
感想
どうでしょうか。各エージェントが生き生きとロールプレイの役目してくれて、思いのほかうまく動いてくれた気がします。皆さんも設定を色々変えて面白いストーリーができましたら、ぜひ教えてもらえると嬉しいです。
お読みいただき、ありがとうございました。
この記事が参加している募集
この記事が気に入ったらサポートをしてみませんか?