龙与地下城文字游戏模拟器

参考链接:https://www.51cto.com/article/771371.html/
langchain入门:https://www.langchain.com.cn/modules/models/chat/getting_started
运行环境:Google coda

!pip install openai langchain

该命令用于安装Python包,以支持后续的开发工作:

  1. openai:OpenAI的Python客户端库。
  2. langchain:与LangChain相关的库,支持与大语言模型的交互。
from typing import List, Callable
import random
import time

# 导入langchain库中的相关模型
from langchain.chat_models import ChatOpenAI
from langchain import PromptTemplate, LLMChain
from langchain.prompts.chat import (
    ChatPromptTemplate,
    SystemMessagePromptTemplate,
    AIMessagePromptTemplate,
    HumanMessagePromptTemplate,
)
from langchain.schema import (
    AIMessage,
    HumanMessage,
    SystemMessage
)

ListCallable是Python的内置类型,用于表示列表和函数类型。

  • List是Python中用于表示列表的类型,可以容纳任意数量的元素,这些元素可以是不同类型的对象。例如,一个List对象可以包含多个整数、字符串或其他Python对象。
  • Callable是Python中用于表示函数类型的类型,可以用于指定函数参数的类型和返回值类型。Callable类型的对象可以像函数一样被调用,因为它们实现了__call__方法。

在该代码中,我们使用List类型来表示代理列表,使用Callable类型来表示选择下一个说话者的函数。通过使用这些类型,我们可以提高代码的可读性和可维护性,并使代码更加健壮和可扩展。

SystemMessage 类的作用是表示系统发送的消息,例如场景的背景信息或者代理之间的协议消息等。它可以帮助 DialogueAgent 类将系统消息与人类消息区分开来,并在合适的时候将它们传递给聊天模型进行对话生成。

通常情况下,系统消息不会包含来自用户的输入,而主要用于提供背景信息、提示或引导对话流程等。例如,在一个问答场景中,系统消息可能包括问题的描述或提示,而用户则需要回答问题并提供相关信息。

下面是一个简单定义的SystemMessage类:

class SystemMessage:
    def __init__(self, content: str):
        self.content = content

    def __str__(self):
        return f"System: {self.content}"

LangChain的SystemMessage是一个特殊的消息类型,它用于在与大语言模型的交互中提供上下文或指令。通过SystemMessage,可以向模型传达关于游戏的背景信息或其他重要指示,帮助模型生成更符合场景的回应。它是在构建交互式应用时,提供上下文和指导信息的有效方式。

OPENAI_API_KEY = ""		# 填自己的openai-key

DialogueAgent 类

一个简单的封装器,围绕 ChatOpenAI 模型进行操作,通过简单地将消息作为字符串连接起来,从而存储 dialogue_agent 角度看到的消息历史。

它提供了两个方法:

  • send():将 chatmodel 应用于消息历史,并返回消息字符串。
  • receive(name, message):将由 name 发出的消息添加到消息历史中。
# 定义DialogueAgent类,用于表示对话中的一个代理(或参与者)
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()  # 调用reset方法初始化消息历史

    # reset方法,用于重置或初始化消息历史
    def reset(self):
        self.message_history = ["Here is the conversation so far."]  # 初始化消息历史

    # send方法,用于生成并发送消息
    def send(self) -> str:
        """
        Applies the chat model to the message history
        and returns the message string
        """
        # 调用模型生成消息
        message = self.model(
            [
                self.system_message,  # 系统消息:背景
                HumanMessage(content="\n".join(self.message_history + [self.prefix])),  # 人类消息(包含历史和前缀)
            ]
        )
        #message = self.model(
        #  "\n".join([
        #    str(self.system_message),
        #    str(HumanMessage(content="\n".join(self.message_history + [self.prefix])))
        #  ])
        #)
        #return message.content

        return message  # 返回生成的消息

    # receive方法,用于接收并记录消息
    def receive(self, name: str, message: str) -> None:
        """
        Concatenates message spoken by {name} into message history
        """
        # 将接收的消息加入到消息历史中
        self.message_history.append(f"{name}: {message}")

DialogueSimulator 类

DialogueSimulator 类接受一个代理列表。在每一步中,它执行以下操作:

选择下一个发言者。调用下一个发言者来发送消息。将消息广播给所有其他代理。更新步骤计数器。下一个发言者的选择可以通过任何函数来实现,但在这种情况下,我们简单地遍历代理。

# 定义一个DialogueSimulator类来模拟对话
class DialogueSimulator:
    # 初始化方法,用于设置类的基本属性
    def __init__(
        self,
        agents: List[DialogueAgent],  # 代理列表,包含所有对话的参与者
        selection_function: Callable[[int, List[DialogueAgent]], int],  # 选择下一个说话者的函数
    ) -> None:
        self.agents = agents  # 将传入的代理列表保存为实例变量
        self._step = 0  # 初始化步数为0
        self.select_next_speaker = selection_function  # 设置选择下一个说话者的函数

    # 重置方法,用于重置所有代理的状态
    def reset(self):
        for agent in self.agents:  # 遍历每个代理
            agent.reset()  # 调用每个代理的reset方法进行重置

    # 初始化对话的方法
    def inject(self, name: str, message: str):
        """
        Initiates the conversation with a message from a specific agent
        """
        for agent in self.agents:  # 遍历每个代理
            agent.receive(name, message)  # 让每个代理接收初始消息
        self._step += 1  # 增加步数计数

    # 进行一步对话的方法
    def step(self) -> tuple[str, str]:
        # 1. 选择下一个说话的代理
        speaker_idx = self.select_next_speaker(self._step, self.agents)  # 使用选择函数选择代理
        speaker = self.agents[speaker_idx]  # 获取选中的代理

        # 2. 让选中的代理发送消息
        message = speaker.send()  # 获取代理发送的消息

        # 3. 让所有代理接收这条消息
        for receiver in self.agents:  # 遍历所有代理
            receiver.receive(speaker.name, message)  # 让每个代理接收消息

        # 4. 增加步数计数
        self._step += 1  # 更新步数

        # 返回说话代理的名字和消息内容
        return speaker.name, message

定义角色

在这段代码中,定义了四个变量,用于存储故事或任务的关键信息。

这四个变量为我们在进一步编写或生成故事提供了基础信息。

protagonist_name = "马小虎"
storyteller_name = "神秘老人"
quest = "找到传说中的七件神器。"
word_limit = 50
  1. protagonist_name = "马小虎":这一行定义了主角(故事中的主要人物)的名字为"马小虎"。

  2. storyteller_name = "神秘老人":这一行定义了讲故事的人(故事的叙述者)的名字为"神秘老人"。

  3. quest = "找到传说中的七件神器。":这一行定义了主角需要完成的任务或探险目标。

  4. word_limit = 50:这一行定义了用于任务的字数限制为50字。在进行任务时,需要设置一个字数限制以保持焦点和简洁性。

故事背景描述

下面这段代码主要用于生成一个地下城冒险游戏的描述和提示。它涉及到两个主要角色:主人公(由变量 protagonist_name 定义)和故事讲述者(由变量 storyteller_name 定义)。代码的目的是通过自然语言模型(在这里是 GPT-3.5 Turbo 或一个名为 “QianfanLLMEndpoint” 的模型)来生成这两个角色的详细描述。

game_description = f"""这是一场地下城冒险游戏: {quest}.
        游戏中存在一名玩家: 主人公, {protagonist_name}.
        故事由故事的讲述者来描述, {storyteller_name}."""

player_descriptor_system_message = SystemMessage(
    content="你可以为这场地下城冒险游戏添加细节."
)

#主人公提示词
protagonist_specifier_prompt = [
    player_descriptor_system_message,
    HumanMessage(
        content=f"""{game_description}
        请用创意的方式来描述主人公, {protagonist_name}, 描述字数不超过 {word_limit} 个.
        并且说出主人公的名字, {protagonist_name}.
        除此之外不要添加其他信息."""
    ),
]
#llm 生成对主人公的描述
protagonist_description = ChatOpenAI(openai_api_key=OPENAI_API_KEY, model_name="gpt-3.5-turbo",temperature=1.0)(
    protagonist_specifier_prompt
).content

#llm = QianfanLLMEndpoint( model="ChatGLM2-6B-32K", temperature = 1.0)
#protagonist_description= llm.generate([str(protagonist_specifier_prompt)])

#故事讲述者提示词
storyteller_specifier_prompt = [
    player_descriptor_system_message,
    HumanMessage(
        content=f"""{game_description}
        请用创意的方式来描述故事讲述者, {storyteller_name}, 描述字数不超过 {word_limit} 个.
        并且说出故事讲述者的名字, {storyteller_name}.
        除此之外不要添加其他信息."""
    ),
]
#llm 生成对故事描述者的描述
storyteller_description = ChatOpenAI(openai_api_key=OPENAI_API_KEY, model_name="gpt-3.5-turbo",temperature=1.0)(
    storyteller_specifier_prompt
).content
#storyteller_description = llm.generate([str(storyteller_specifier_prompt)])
  1. 生成游戏描述: 使用前面定义的 quest, protagonist_name, 和 storyteller_name 变量来形成一个完整的游戏描述 (game_description)。

  2. 创建系统消息:一个名为 SystemMessage 的类被用来生成一个提示消息,提示玩家可以为这场游戏添加更多细节。

  3. 生成主人公描述:使用自然语言模型和一个特定的提示 (protagonist_specifier_prompt) 来生成主人公的描述 (protagonist_description)。

  4. 生成故事讲述者描述:同样地,使用自然语言模型和一个特定的提示 (storyteller_specifier_prompt) 来生成故事讲述者的描述 (storyteller_description)。

print("Protagonist Description:")
print(protagonist_description)
print("Storyteller Description:")
print(storyteller_description)
Protagonist Description:
主人公,马小虎,是身姿矫健的年轻剑客。他穿着漆黑的斗篷,手执一柄古老的宝剑。他的眼神中闪烁着无尽的勇气,准备迎接前方未知的挑战。
Storyteller Description:
神秘老人的名字是渊明,他细长的白胡须如同飘逸的瀑布,眼中闪烁着智慧的光芒,声音如风吹过树林的低语。

主角与地下城主的系统消息

这段代码生成一个更具体和详细的任务描述(specified_quest),该任务描述是为地下城冒险游戏的主角(protagonist_name)准备的。

# 定义一个名为 quest_specifier_prompt 的列表,包含 SystemMessage 和 HumanMessage 对象
quest_specifier_prompt = [
    # 系统消息,提示可以使任务更具体
    SystemMessage(content="你可以使任务更具体。"),

    # 人类消息,要求故事讲述者更具体地描述任务
    HumanMessage(
        content=f"""{game_description}

        你是故事讲述者,{storyteller_name}。
        请使任务更具体化。请富有创意和想象力。
        请用{word_limit}个词或更少回复指定的任务。
        直接对主角{protagonist_name}说话。
        不要添加其他任何内容。"""
    ),
]

specified_quest = ChatOpenAI(openai_api_key=OPENAI_API_KEY,model_name='gpt-3.5-turbo',temperature=1.0)(quest_specifier_prompt).content
#specified_quest = llm.generate([str(quest_specifier_prompt)])

print(f"原始任务描述:\n{quest}\n")
print(f"详细任务描述:\n{specified_quest}\n")
  1. 定义任务指定提示(quest_specifier_prompt)**:这个列表包含两种类型的消息对象:SystemMessageHumanMessageSystemMessage 提供了一种简单的提示,即"你可以使任务更具体"。HumanMessage 则给出了详细的指示,包括当前的游戏描述、故事讲述者的名字,以及对任务应如何具体化的要求。

  2. 生成具体任务(specified_quest)**:使用前面定义的 quest_specifier_prompt 和自然语言模型(在这里是 GPT-3.5 Turbo)来生成一个更具体和详细的任务描述。

  3. 输出结果:最后,代码打印出原始的任务描述和新生成的更具体的任务描述。

    原始任务描述:
    找到传说中的七件神器。

    详细任务描述:
    马小虎,你被派去一座废弃的古墓找到七件神器。途中,你将遭遇失落的魔法生物、谜题和隐藏的通道。记住:每个神器都有独特的力量。快去展现你的勇气和智慧吧!

主函数入口

这段代码的主要目的是模拟一个交互式故事,其中包括两个角色:主人公(protagonist)和故事讲述者(storyteller)。这些角色在一个预定义的对话模拟器(DialogueSimulator)中轮流互动。

#主人公
protagonist = DialogueAgent(
    name=protagonist_name,
    system_message=SystemMessage(content="欢迎来到故事中!我是主人公,让我们开始吧!"),
    model=ChatOpenAI(openai_api_key=OPENAI_API_KEY,model_name='gpt-3.5-turbo',temperature=0.2),
    #model = llm,
)
#故事描述者
storyteller = DialogueAgent(
    name=storyteller_name,
    system_message=SystemMessage(content="我是故事描述者,让我为你讲述一个精彩的故事!"),
    model=ChatOpenAI(openai_api_key=OPENAI_API_KEY,model_name='gpt-3.5-turbo',temperature=0.2),
    #model = llm,
)
#发言顺序:轮流发言
def select_next_speaker(step: int, agents: List[DialogueAgent]) -> int:
    idx = step % len(agents)
    return idx

max_iters = 15
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()
    time.sleep(random.randint(10, 20))
    print(f"({name}): {message}")
    print("\n")
    n += 1
  1. 初始化角色:使用 DialogueAgent 类创建两个不同的角色,其中包括他们的名字、系统消息和使用的模型(这里是 GPT-3.5 Turbo)。

  2. 选择发言人函数: select_next_speaker 函数用于确定在对话中哪个角色应该接下来发言。这里使用了一个简单的轮流机制。

  3. 对话模拟:使用 DialogueSimulator 类创建一个对话模拟器,该模拟器接受角色列表和选择发言人的函数。

  4. 注入初始任务:使用 simulator.inject() 方法将特定的任务描述(specified_quest)作为故事讲述者的初始发言注入。

  5. 对话循环:在一个循环中,角色轮流发言,直到达到最大迭代次数(max_iters)。

执行代码我们可以看到如下的结果。

(神秘老人): 马小虎,你被派去一座废弃的古墓找到七件神器。途中,你将遭遇失落的魔法生物、谜题和隐藏的通道。记住:每个神器都有独特的力量。快去展现你的勇气和智慧吧!
(马小虎): content='谢谢您的信任,神秘老人。我会尽全力完成这个任务。我已经准备好面对挑战,展现我的勇气和智慧。请告诉我古墓的具体位置和如何进入。'
(神秘老人): content='神秘老人: 古墓位于一座被遗忘的山脉中,名为幽影山。进入古墓的方法是通过一条隐藏的通道,位于山脉的北面。你需要找到一块巨大的石头,上面刻有古老的符文。按照符文的指示,你需要转动石头上的三个按钮,才能打开通道的入口。但要小心,按钮的顺序是随机的,你需要观察周围的线索来找到正确的顺序。一旦进入通道,你将面临一系列谜题和魔法生物的挑战。祝你好运,马小虎!'
(马小虎): content="content='非常感谢您的指引,神秘老人。我会前往幽影山,找到那块刻有符文的巨石,并解开隐藏通道的入口。我明白需要观察周围的线索来找到正确的按钮顺序。一旦进入通道,我会小心应对谜题和魔法生物的挑战。再次感谢您的祝福,我会尽全力完成任务!'"
(神秘老人): content='神秘老人: 非常好,马小虎!我相信你能成功完成任务。记住,勇气和智慧是你的武器。祝你好运!\n马小虎: 谢谢您的鼓励,神秘老人!我会尽力以最好的状态完成任务。我现在就出发去幽影山,寻找那块刻有符文的巨石。再次感谢您的信任和祝福!\n马小虎踏上了前往幽影山的旅程。他穿越茂密的森林,攀爬陡峭的山脉,终于来到了幽影山的北面。在一片荒凉的地方,他发现了一块巨大的石头,上面刻满了古老的符文。\n\n马小虎仔细观察着符文,发现其中有三个按钮,但并没有明确的顺序。他环顾四周,寻找线索。突然,他注意到地上有几块散落的石块,上面也刻有符文。他将这些石块拾起,发现它们上面的符文与巨石上的符文有些相似。\n\n马小虎开始仔细比对符文,试图找到正确的按钮顺序。他发现有一块石块上的符文与巨石上的第一个按钮相似,另一块石块上的符文与第二个按钮相似,最后一块石块上的符文与第三个按钮相似。\n\n经过一番思考,马小虎决定按照这个顺序来转动按钮。他小心翼翼地按下第一个按钮,巨石发出一阵低沉的声音。接着,他按下第二个按钮,巨石开始微微震动。最后,他按下第三个按钮,巨石发出一声巨响,一道隐藏的通道缓缓打开。\n\n马小虎进入了通道,眼前是一片黑暗。他点亮手中的火把,继续前行。在通道的尽头,他看到了一扇巨大的石门。门上刻着一道谜题:“我有四个脚,却不能行走;我有两个耳朵,却不能听见;我有两个嘴巴,却不能说话。我是什么?”\n\n马小虎沉思片刻,然后回答:“一个石像。”石门发出一阵轰鸣声,缓缓打开。马小虎成功通过了第一个谜题。\n\n他继续前行,面对着更多的谜题和魔法生物的挑战。每一次,他都运用自己的智慧和勇气,成功解开谜题,战胜魔法生物。\n\n最终,马小虎来到了古墓的最深处。在一个巨大的石台上,他找到了七件神器。每一件神器都散发着强大的能量,代表着不同的力量。\n\n马小虎感到自己的心跳加速,他知道这是他完成任务的时刻。他小心翼翼地将神器收集起来,感受着它们的力量流动在自己的身体中。\n\n成功完成任务后,马小虎返回了神秘老人的身边。神秘老人对他的勇气和智慧表示赞赏,并将他封为勇者。\n\n马小虎成为了传说中的勇者,他的故事被世人传颂。他用自己的勇气和智慧,保护了神器的力量,守护了世界的和平。'
(马小虎): content="content='我成功找到了七件神器,并成功完成了任务!我感到非常自豪和满足。这次的冒险让我学到了很多,不仅锻炼了我的勇气和智慧,还让我明白了保护世界和平的重要性。我将继续努力,成为更好的勇者!'"
(神秘老人): content="content='神秘老人: 马小虎,你真是一个了不起的勇者!你的勇气和智慧让我非常钦佩。你成功找到了七件神器,并且明白了保护世界和平的重要性。我相信你将成为一个更好的勇者,并继续为世界的和平而战。祝福你,马小虎!'"
(马小虎): content="content='马小虎: 谢谢您的夸奖,神秘老人!我会牢记您的教诲,继续努力成为更好的勇者。保护世界的和平将永远是我的责任。再次感谢您的指引和祝福!'"
(神秘老人): content="content='神秘老人: 马小虎,你已经展现了出色的勇气和智慧。我相信你将继续成长,并为世界的和平而战。祝福你,马小虎!'"
(马小虎): content="content='马小虎: 谢谢您的祝福,神秘老人!我会继续努力成长,为世界的和平而战。我相信在您的指引下,我一定能够成为更好的勇者。再次感谢您的信任和祝福!'"
(神秘老人): content='神秘老人: 马小虎,你已经展现了出色的勇气和智慧。我相信你将继续成长,并为世界的和平而战。祝福你,马小虎!'
(马小虎): content="content='马小虎: 谢谢您的祝福,神秘老人!我会继续努力成长,为世界的和平而战。我相信在您的指引下,我一定能够成为更好的勇者。再次感谢您的信任和祝福!'"
(神秘老人): content='神秘老人: 马小虎,你已经展现了出色的勇气和智慧。我相信你将继续成长,并为世界的和平而战。祝福你,马小虎!'
(马小虎): content='content="content=\'马小虎: 谢谢您的祝福,神秘老人!我会继续努力成长,为世界的和平而战。我相信在您的指引下,我一定能够成为更好的勇者。再次感谢您的信任和祝福!\'"'
(神秘老人): content='神秘老人: 马小虎,你已经展现了出色的勇气和智慧。我相信你将继续成长,并为世界的和平而战。祝福你,马小虎!'
(马小虎): content="content='马小虎: 谢谢您的祝福,神秘老人!我会继续努力成长,为世界的和平而战。我相信在您的指引下,我一定能够成为更好的勇者。再次感谢您的信任和祝福!'"

错误记录

Error code: 429

RateLimitError: Error code: 429 - {'error': {'message': 'Rate limit reached for gpt-3.5-turbo in organization org-ZOImUcZ5Wsk6tgZHMIwExQP3 on requests per min (RPM): Limit 3, Used 3, Requested 1. Please try again in 20s. Visit https://platform.openai.com/account/rate-limits to learn more. You can increase your rate limit by adding a payment method to your account at https://platform.openai.com/account/billing.', 'type': 'requests', 'param': None, 'code': 'rate_limit_exceeded'}}

这个错误报告了您的 OpenAI API 的速率限制已达到或超过了每分钟请求的限制。要解决这个问题,您可以尝试以下几种方法:

  1. 等待一段时间:根据错误消息中的建议,您可以在报错的 20 秒后再次尝试请求。这是因为 OpenAI API 对每个帐户设置了请求速率限制,超过限制后需要等待一段时间才能继续使用。

  2. 增加支付方式:您可以通过访问 OpenAI 控制台(https://platform.openai.com/account/billing)为您的帐户添加支付方式,从而提高您的请求速率限制。按照页面上的说明完成支付方式的添加即可。

  3. 优化代码:您可以检查您的代码,确保您没有在短时间内发送过多的请求。如果您的代码中存在循环或重复请求的情况,您可以尝试优化代码以减少请求次数。

如果您仍然遇到问题,请参考 OpenAI 的官方文档(https://platform.openai.com/docs/errors/rate-limit-exceeded)或联系 OpenAI 支持团队以获取进一步的帮助和支持。

Error code: 400

BadRequestError: Error code: 400 - {'error': {'message': "This model's maximum context length is 4097 tokens. However, your messages resulted in 4115 tokens. Please reduce the length of the messages.", 'type': 'invalid_request_error', 'param': 'messages', 'code': 'context_length_exceeded'}}

这个错误是由 OpenAI API 返回的错误,表示您的对话消息超过了模型的最大上下文长度限制。

在使用GPT-3.5 Turbo模型时,该模型对话的上下文长度限制为4096个令牌(tokens)。令牌是指输入文本中的单词、标点符号和其他字符,模型将其分解为可以处理的单位。您的对话消息总共包含4115个令牌,超过了模型的限制。

为了解决这个问题,您可以考虑缩短对话消息的长度,以适应模型的限制。一种方法是缩减消息中的文本数量,删除一些不必要的细节或重复的信息。另一种方法是将对话拆分成更小的部分,每次只发送模型可以接受的最大上下文长度的消息。

例如,您可以修改代码中的 max_iters 变量,将其设置为一个更小的值,以减少对话的轮次。您还可以在对话中限制每个角色的回合数,以减少消息长度。

一旦您缩短了消息的长度,就可以重新运行代码,应该就不会再出现该错误了。

你可能感兴趣的:(windows,microsoft)