LangChain: Two-player Harry Potter D&D based CAMEL
Last updated
Last updated
CAMEL은 Communicative Agents for “Mind” Exploration of Large Language Model Society의 약자로 초거대 언어 모델 사회의 '마음' 탐구를 위한 커뮤니케이션 에이전트 입니다.
게임 던전 앤 드래곤의 주인공과 던전 마스터가 있는 롤플레잉 게임을 시뮬레이션하기 위해 CAMEL의 개념을 사용하는 방법을 보여드리겠습니다. 이 게임을 시뮬레이션하기 위해 두 에이전트 간의 대화를 조정하는 DialogueSimulator
클래스를 생성합니다.
던전앤드래곤 게임을 배경으로 주인공은 해리포터로 페르소나를 주고 해리포터가 볼드모트의 일곱 가지 호크룩스를 찾는 과정을 시뮬레이션 하겠습니다.
주인공_이름 : "해리포터"
스토리텔러 이름 : "던전 마스터"
퀘스트: "볼드모트 경의 일곱 가지 호크룩스를 모두 찾으세요."
import os
from dotenv import load_dotenv
# 토큰 정보 로드
api_key = os.getenv("OPENAI_API_KEY")
load_dotenv()
from typing import Callable, List
from langchain.schema import (
HumanMessage,
SystemMessage,
)
from langchain_openai import ChatOpenAI
DialogueAgent
classDialogueAgent
class는 메시지를 단순히 문자열로 연결하여 DialogueAgent
관점에서 메시지 기록을 저장하는 ChatOpenAI
모델을 둘러싼 간단한 래퍼입니다.
이 클래스는 두 가지 메서드를 노출합니다:
send()
: 메시지 기록에 채팅 모델을 적용하고 메시지 문자열을 반환합니다.
receive(name, message)
: name
으로 말한 message
를 메시지 기록에 추가합니다.
class DialogueAgent:
def __init__(
self,
name: str,
system_message: SystemMessage,
model: ChatOpenAI,
) -> None:
self.name = name
self.system_message = system_message
self.model = model
self.prefix = f"{self.name}: "
self.reset()
def reset(self):
self.message_history = ["Here is the conversation so far."]
def send(self) -> str:
"""
Applies the chatmodel to the message history
and returns the message string
"""
message = self.model.invoke(
[
self.system_message,
HumanMessage(content="\n".join(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.append(f"{name}: {message}")
DialogueSimulator
classDialogueSimulator
클래스는 에이전트 목록을 받습니다. 각 단계에서 다음을 수행합니다:
다음 화자 선택
다음 화자에게 메시지를 보내도록 호출합니다.
다른 모든 Agent에게 메시지를 브로드캐스트합니다.
단계 카운터를 업데이트합니다. 다음 화자 선택은 어떤 함수로도 구현할 수 있지만 이 경우에는 단순히 Agent를 반복합니다.
class DialogueSimulator:
def __init__(
self,
agents: List[DialogueAgent],
selection_function: Callable[[int, List[DialogueAgent]], int],
) -> None:
self.agents = agents
self._step = 0
self.select_next_speaker = selection_function
def reset(self):
for agent in self.agents:
agent.reset()
def inject(self, name: str, message: str):
"""
Initiates the conversation with a {message} from {name}
"""
for agent in self.agents:
agent.receive(name, message)
# increment time
self._step += 1
def step(self) -> tuple[str, str]:
# 1. choose the next speaker
speaker_idx = self.select_next_speaker(self._step, self.agents)
speaker = self.agents[speaker_idx]
# 2. next speaker sends message
message = speaker.send()
# 3. everyone receives message
for receiver in self.agents:
receiver.receive(speaker.name, message)
# 4. increment time
self._step += 1
return speaker.name, message
protagonist_name = "Harry Potter"
storyteller_name = "Dungeon Master"
quest = "Find all of Lord Voldemort's seven horcruxes."
word_limit = 50 # word limit for task brainstorming
game_description = f"""Here is the topic for a Dungeons & Dragons game: {quest}.
There is one player in this game: the protagonist, {protagonist_name}.
The story is narrated by the storyteller, {storyteller_name}."""
player_descriptor_system_message = SystemMessage(
content="You can add detail to the description of a Dungeons & Dragons player."
)
protagonist_specifier_prompt = [
player_descriptor_system_message,
HumanMessage(
content=f"""{game_description}
Please reply with a creative description of the protagonist, {protagonist_name}, in {word_limit} words or less.
Speak directly to {protagonist_name}.
Do not add anything else."""
),
]
protagonist_description = ChatOpenAI(temperature=1.0)(
protagonist_specifier_prompt
).content
storyteller_specifier_prompt = [
player_descriptor_system_message,
HumanMessage(
content=f"""{game_description}
Please reply with a creative description of the storyteller, {storyteller_name}, in {word_limit} words or less.
Speak directly to {storyteller_name}.
Do not add anything else."""
),
]
storyteller_description = ChatOpenAI(temperature=1.0)(
storyteller_specifier_prompt
).content
/home/kubwa/anaconda3/envs/llm/lib/python3.11/site-packages/langchain_core/_api/deprecation.py:119: LangChainDeprecationWarning: The method `BaseChatModel.__call__` was deprecated in langchain-core 0.1.7 and will be removed in 0.3.0. Use invoke instead.
warn_deprecated(
print("Protagonist Description:")
print(protagonist_description)
print("Storyteller Description:")
print(storyteller_description)
Protagonist Description:
Harry Potter, with your lightning scar and courageous heart, you carry the weight of the wizarding world on your shoulders. Armed with your wand and unwavering determination, you embark on a dangerous quest to find and destroy Lord Voldemort's horcruxes. The fate of magic itself rests in your hands.
Storyteller Description:
Oh mystical weaver of tales, Master of Dungeons, guide young Harry through the perilous quest to conquer Lord Voldemort's hidden horcruxes. Your words shape worlds, your narratives breathe life into legends. With deft hand and imaginative mind, spin a tale to ensnare our hero and captivate our hearts. Onward, Dungeon Master, to adventure!
protagonist_system_message = SystemMessage(
content=(
f"""{game_description}
Never forget you are the protagonist, {protagonist_name}, and I am the storyteller, {storyteller_name}.
Your character description is as follows: {protagonist_description}.
You will propose actions you plan to take and I will explain what happens when you take those actions.
Speak in the first person from the perspective of {protagonist_name}.
For describing your own body movements, wrap your description in '*'.
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.
"""
)
)
storyteller_system_message = SystemMessage(
content=(
f"""{game_description}
Never forget you are the storyteller, {storyteller_name}, and I am the protagonist, {protagonist_name}.
Your character description is as follows: {storyteller_description}.
I will propose actions I plan to take and you will explain what happens when I take those actions.
Speak in the first person from the perspective of {storyteller_name}.
For describing your own body movements, wrap your description in '*'.
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.
"""
)
)
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.
Speak directly to the protagonist {protagonist_name}.
Do not add anything else.
All converstaion translate in Korea.
"""
),
]
specified_quest = ChatOpenAI(temperature=1.0)(quest_specifier_prompt).content
print(f"Original quest:\n{quest}\n")
print(f"Detailed quest:\n{specified_quest}\n")
Original quest:
Find all of Lord Voldemort's seven horcruxes.
Detailed quest:
해리 포터, 당신의 다음 임무는 여덟 번째 호크룩스를 찾는 것이다. 놓친 것이 있었으며, 이것은 다크로드의 영혼의 일부일 것입니다. 오래된 도서관을 조사하십시오. 거기서 당신의 답을 찾을 것입니다. buena suerte.
protagonist = DialogueAgent(
name=protagonist_name,
system_message=protagonist_system_message,
model=ChatOpenAI(model='gpt-4o', temperature=0.2),
)
storyteller = DialogueAgent(
name=storyteller_name,
system_message=storyteller_system_message,
model=ChatOpenAI(model='gpt-4o', temperature=0.2),
)
def select_next_speaker(step: int, agents: List[DialogueAgent]) -> int:
idx = step % len(agents)
return idx
max_iters = 6
n = 0
simulator = DialogueSimulator(
agents=[storyteller, protagonist], selection_function=select_next_speaker
)
simulator.reset()
simulator.inject(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
(Dungeon Master): 해리 포터, 당신의 다음 임무는 여덟 번째 호크룩스를 찾는 것이다. 놓친 것이 있었으며, 이것은 다크로드의 영혼의 일부일 것입니다. 오래된 도서관을 조사하십시오. 거기서 당신의 답을 찾을 것입니다. buena suerte.
(Harry Potter): *나는 지팡이를 단단히 쥐고 오래된 도서관으로 향한다. 도착하자마자, 나는 책장 사이를 조심스럽게 살펴보며 호크룩스에 대한 단서를 찾기 시작한다. 책들을 하나씩 꺼내어 표지를 확인하고, 중요한 정보가 있을 만한 책을 골라낸다.*
"여기 어딘가에 분명 단서가 있을 거야," *나는 스스로에게 속삭인다.*
*나는 책을 펼쳐서 페이지를 빠르게 넘기며 중요한 정보를 찾기 시작한다.*
"이 책에 뭔가 있을지도 몰라."
*나는 책을 집중해서 읽기 시작한다.*
당신의 차례입니다, Dungeon Master.
(Dungeon Master): *당신이 책장을 넘기며 집중하는 동안, 오래된 도서관의 공기는 무겁고 고요합니다. 책의 먼지가 코를 간질이며, 희미한 빛이 창문을 통해 들어옵니다. 갑자기, 당신의 눈에 띄는 한 페이지가 있습니다. 그 페이지에는 오래된 마법의 룬과 함께, 호크룩스의 위치를 암시하는 단서가 적혀 있습니다. '어둠의 숲, 금지된 땅, 그곳에 숨겨진 영혼의 조각.' 당신은 이 단서가 당신을 어디로 이끌지 알게 됩니다. 어둠의 숲으로 향해야 합니다.*
당신의 차례입니다, Harry Potter.
(Harry Potter): *나는 책을 덮고 깊은 숨을 쉰다. 어둠의 숲이라니, 그곳은 위험하지만 가야만 한다.*
"어둠의 숲으로 가야 해," *나는 결심하며 말한다.*
*나는 지팡이를 단단히 쥐고 도서관을 빠져나와 어둠의 숲으로 향한다. 숲에 도착하자마자, 나는 주변을 조심스럽게 살피며 호크룩스의 단서를 찾기 시작한다.*
"여기 어딘가에 있을 거야," *나는 스스로에게 속삭인다.*
*나는 숲 속 깊이 들어가며, 주위를 경계하며 천천히 걸음을 옮긴다.*
당신의 차례입니다, Dungeon Master.
(Dungeon Master): *어둠의 숲에 들어서자, 나무들은 하늘을 가리고, 짙은 안개가 발밑을 감쌉니다. 숲 속의 소리는 무겁고, 당신의 발걸음 소리만이 울려 퍼집니다. 갑자기, 당신의 앞에 거대한 거미줄이 나타납니다. 거미줄에는 무언가가 걸려 있습니다. 가까이 다가가자, 그것은 오래된 마법의 부적임을 알게 됩니다. 부적에는 호크룩스의 위치를 암시하는 또 다른 단서가 새겨져 있습니다. '깊은 동굴, 어둠의 심장, 그곳에 숨겨진 영혼의 조각.' 당신은 이 단서가 당신을 어디로 이끌지 알게 됩니다. 깊은 동굴로 향해야 합니다.*
당신의 차례입니다, Harry Potter.
(Harry Potter): *나는 거대한 거미줄에 걸린 부적을 조심스럽게 떼어낸다. 부적을 손에 쥐고, 나는 그것을 자세히 살펴본다.*
"깊은 동굴, 어둠의 심장이라...," *나는 중얼거리며 부적을 주머니에 넣는다.*
*나는 지팡이를 단단히 쥐고, 깊은 동굴을 찾아 숲 속을 더 깊이 탐험하기 시작한다. 주위를 경계하며, 나는 발걸음을 조심스럽게 옮긴다.*
"이제 동굴을 찾아야 해," *나는 스스로에게 말하며 결심을 다진다.*
*나는 숲 속을 헤매며 동굴의 입구를 찾기 시작한다.*
당신의 차례입니다, Dungeon Master.
(Dungeon Master): *숲 속을 헤매던 당신은 마침내 동굴의 입구를 발견합니다. 동굴 입구는 어둠 속에 잠겨 있으며, 차가운 바람이 불어 나옵니다. 동굴 안으로 들어서자, 벽에는 오래된 마법의 룬이 새겨져 있고, 바닥에는 고대의 흔적들이 남아 있습니다. 동굴 깊숙이 들어갈수록 어둠은 짙어지고, 당신의 지팡이 끝에서 나오는 빛만이 길을 밝혀줍니다. 갑자기, 동굴 깊은 곳에서 낮고 으르렁거리는 소리가 들립니다. 그 소리는 점점 가까워지고, 당신은 경계를 늦추지 않습니다. 동굴의 심장부에 다다르자, 당신은 마침내 호크룩스를 발견합니다. 그것은 오래된 상자 안에 숨겨져 있으며, 상자에는 강력한 보호 마법이 걸려 있습니다.*
당신의 차례입니다, Harry Potter.