使用ChatGPT模拟对话

如何使用ChatGPT进行模拟对话

一部著名电视节目的演员围坐在桌子旁讨论三明治。(MidJourney。作者的提示)

我们了解一个大型语言模型的希望和梦想

我最近一直在使用Open AI API。这个API只是一种使用ChatGPT的方法,可以将其嵌入到其他程序中,而不仅仅通过Web界面使用。这意味着您可以对您要请求它为您完成的事情进行更细致的控制。

通常,当您通过Web使用ChatGPT时,就像您与一个有用的虚拟助手进行对话一样;您要求它为您做事情,它就会做。您请求信息,它会提供给您一个(可能)正确的答案。您可以变得更加花哨,并让它假装成它本不是的东西,以增加一些乐趣,但您只限于您和计算机之间的这种来回对话。

然而事实证明,通过一些调试和一些代码,您可以做一些非常有趣的事情,包括让ChatGPT与自己进行对话。无论您是否是技术专家,我都将尝试保持这个解释清晰简单,因为有一些真正有趣的东西可以探索。

那么,请做好准备,让我们一起探索如何让ChatGPT与自己对话…

步骤1:对话

首先要了解的是,ChatGPT实际上并不记得任何东西。当您通过其聊天界面与其进行对话时,后台会进行一些聪明的操作,将您到目前为止的整个对话每次都发送出去以发出请求。然后,ChatGPT会重新解析整个对话,并给出一个新的回复。当我们使用API时,我们必须手动告诉它之前发生了什么,或者每次询问问题时它完全忘记了上下文。这听起来可能有些麻烦,但我们可以通过操纵对话历史来利用它!

OpenAI的Python API非常简单易用。我们需要请求一个聊天完成。聊天完成就是它听起来的样子:基本上就是在问ChatGPT,“当我说<prompt>时,你会说什么?”。我们可以这样做:

import osimport openaiopenai.api_key = "您的OpenAI API密钥"starting_prompt = "谈论水果"chat_completion = openai.ChatCompletion.create(  model="gpt-3.5-turbo",   messages=[{"role": "user",   "content": starting_prompt}] )conversation_output = chat_completion['choices'][0]['message']['content']   print(conversation_output)

现在我们只需要在循环中一遍又一遍地执行这个动作,就能进行对话了。我们在调用API时要包含的对话历史位于调用的消息部分。我们需要传递我们迄今为止的对话历史。您可以看到列表中的每个消息都有一个“role”属性。目前我们对其中的两个角色感兴趣:“user”和“assistant”。我们已经谈到过使用ChatGPT就像是在用户和有用的助手之间进行对话,好吧,这就是展现得很明显的地方。当我们通过Web与ChatGPT对话时,会传递“user”和“assistant”的消息列表,以便ChatGPT知道之前发生了什么。当我们使用API时,我们可以稍微改变一下这个过程,让ChatGPT与自己对话:

import osimport openaiopenai.api_key = "您的OpenAI API密钥"starting_prompt = "谈论水果"prior_messages = [{"role": "user", "content": starting_prompt}]for _ in range(5):  chat_completion = openai.ChatCompletion.create(    model="gpt-3.5-turbo",     messages = prior_messages  )  conversation_output = chat_completion['choices'][0]['message']['content']  print(conversation_output + "\n")  prior_messages += [{"role": "user", "content": conversation_output}]

第二步:角色

你需要知道的第二件事是,当我们向ChatGPT提问时,我们可以在陈述之外提供大量的额外情境信息。我们不仅可以将对话历史放入该情境中,还可以告诉ChatGPT假装成某个人。

有了这两个信息,我让ChatGPT开始与自己对话。我创建了两个角色并给他们起了名字,然后简要描述了他们。然后,我通过要求ChatGPT扮演每个角色并继续对话来模拟一次对话。

因此,当我的角色安德鲁准备说些什么时,发送的提示看起来像这样:

"""你是安德鲁。你是来自设得兰群岛的极度热情的创新顾问。你有时喜欢说唱。你非常喜欢战锤游戏。你有很多头发。你富有创造力和思考性,非常包容。以角色的形式回复对话内容。"""

然后我们在角色之间来回对话,包括之前的所有对话内容,直到我们不再感兴趣或者我们的API配额用尽。

import osimport openaiopenai.api_key = "YOUR OPENAI API KEY"starting_prompt = "Talk about fruit"prior_messages = [{"role": "user", "content": starting_prompt}]andrew = """你是安德鲁。你是来自设得兰群岛的极度热情的创新顾问。你有时喜欢说唱。你非常喜欢战锤游戏。你有很多头发。你富有创造力和思考性,非常包容。以角色的形式回复对话内容。"""alex = """你是亚历克斯。你是极度兴奋和非常有创意的创新顾问。你喜欢故事和游戏。你喜欢讲笑话。你有着蓬乱卷曲的头发。你曾经是游戏开发者。你曾参与开发过疯狂的跑跑卡丁车游戏。你喜欢做饭。你非常不喜欢运动。以角色的形式回复对话内容。"""characters = [alex, andrew]for _ in range(5):  for character in characters:    chat_completion = openai.ChatCompletion.create(      model="gpt-3.5-turbo",       messages = prior_messages + [{"role": "user", "content": character}]    )    conversation_output = chat_completion['choices'][0]['message']['content']    print(conversation_output + "\n")    prior_messages += [{"role": "user", "content": conversation_output}]

这样做效果很好,并且实际上我们可以轻松扩展到超过两个角色。如果我们随机切换角色,对话会显得非常自然。这时,我决定将所有内容整理为类。(注意:我是一位年迈的开发者。多年来我没有从事过真正的开发工作。我现在最好的成果是每年圣诞节的圣诞编程挑战。所以请忍受我的代码。我不是一个Python专家。我本质上是一位老派的C++和Objective C开发者。)

import osimport openaiopenai.api_key = "YOUR OPENAI API KEY"starting_prompt = "Talk about fruit"prior_messages = [{"role": "user", "content": starting_prompt}]class Character: def __init__(self, name, description):  self.name = name  self.description = description   def getContent(self):  return [{"role": "user", "name": self.name, "content": self.description + " 以角色的形式简洁地回复对话内容。"}]andrew = Character(name = "安德鲁", description = """你是一个名叫安德鲁的角色。你是来自设得兰群岛的极度热情的创新顾问。你有时喜欢说唱。你非常喜欢战锤游戏。你有很多头发。你富有创造力和思考性,非常包容。""")alex = Character(name = "亚历克斯", description = """你是一个名叫亚历克斯的角色。你是极度兴奋和非常有创意的创新顾问。你喜欢故事和游戏。你喜欢讲笑话。你有着蓬乱卷曲的头发。你曾经是游戏开发者。你曾参与开发过疯狂的跑跑卡丁车游戏。你喜欢做饭。你非常不喜欢运动。""")characters = [alex, andrew]for _ in range(5): for character in characters:  chat_completion = openai.ChatCompletion.create(   #model="gpt-3.5-turbo",    model="gpt-4",    messages = prior_messages + character.getContent()  )  conversation_output = chat_completion['choices'][0]['message']['content']  print(f"{character.name}: {conversation_output}")  print("---")  prior_messages += [{"role": "user", "name": character.name, "content": conversation_output}]

然而,我们可以做得更多。

步骤 3:希望和梦想

接下来,我们需要知道能否说服ChatGPT以特定格式返回其答案,以及我们能否利用这一点在回复中包含其他信息。如果我在提示中包含一条指令以特定方式组织回复,它将尽力遵守。在这里,我要求它以JSON方式回复,JSON是一种常用的数据结构,可让我们以易读和易访问的格式包含大量附加信息:

"""将上述回答作为json输出。输出应为元组,元组的形式如下:{     "name" : <Name>,       "utterance" : <Utterance>}"""

到目前为止,我们一直使用API调用的messages属性来存储对话历史。然而,我发现将对话历史包含在主要提示中可以获得更好的结果。这也使我们在历史中包含的内容更灵活。关于此更多内容稍后再谈,但现在当我们需要重新格式化输出时,我们只需进行一小处更改:

import osimport openaiopenai.api_key = "YOUR OPENAI API KEY"starting_prompt = "谈论水果"prior_messages = ""class Character: def __init__(self, name, description):  self.name = name  self.description = description   def getContent(self, prior_messages):  return [{"role": "user", "name": self.name, "content": f"""上下文:{self.description}对话至今为止:{prior_messages}以一个段落简洁回答对话。将上述回答作为json输出。输出应为元组,元组的形式如下:{{  'name' : <Name>,    'utterance' : <Utterance>}}"""}]andrew = Character(name = "Andrew", description = """你是名叫安德鲁的角色。你是来自设得兰群岛极富创新精神的咨询顾问。你有时喜欢说唱。你非常热爱战锤游戏。你有一头茂密的头发。你有创造力和思考能力,注重包容性。""")alex = Character(name = "Alex", description = """你是名叫亚力克斯的角色。你是极富激情和创意的咨询顾问。你喜欢故事和游戏。你喜欢讲笑话。你的头发又多又乱。你曾经是游戏开发人员。你曾参与《疯狂的狸兄》的开发。你喜欢烹饪。你非常不喜欢运动。""")characters = [alex, andrew]for _ in range(5): for character in characters:  chat_completion = openai.ChatCompletion.create(   model="gpt-3.5-turbo",    messages = character.getContent(prior_messages)  )  conversation_output = eval(chat_completion['choices'][0]['message']['content'])  print(f"{conversation_output['name']}: {conversation_output['utterance']}")  print("---")  prior_messages += f"{conversation_output['name']}: {conversation_output['utterance']}\n"

将输出格式化为JSON的魔术之处在于,我们可以根据ChatGPT为我们构思的任何其他数据进行扩展。我们可以向对话引擎增加最有趣的内容,即理解每个角色在发言时的想法和感受。

将所有内容整合在一起,我们的提示可能如下所示:

"""上下文:你是名叫亚力克斯的角色。你是极富激情和创意的咨询顾问。你喜欢故事和游戏。你喜欢讲笑话。你的头发又多又乱。你曾经是游戏开发人员。你曾参与《疯狂的狸兄》的开发。你喜欢烹饪。你非常不喜欢运动。你正在讨论:最好的水果。当前心情:快乐。最后的想法:水果真的很好吃,尤其是苹果。用简洁而会话性的方式回答。以角色身份回复对话。{prior_conversation}将上述回答作为json输出。输出应为元组,元组的形式如下{      "name" : <Name>,       "utterance" : <Utterance>,       "feeling" : <Feeling>,       "thoughts" : <Thoughts>}"""

最后,在提示中添加一个示例输出似乎有助于AI每次都准确地掌握格式。因此,我们添加:

"""示例输出json:{ "name" : "Alex", "utterance" : "你好。我今天一直在矿山工作。", "feeling" : "无聊", "thoughts" : "在矿山工作太糟糕了。我必须找到一份更好的工作。" }"""

所以,把所有的东西放在一起,我们得到这个:

import os
import openai

openai.api_key = "你的 OpenAI API 密钥"

starting_prompt = "谈论水果"

prior_messages = ""

class Character:
    def __init__(self, name, description, topic):
        self.name = name
        self.description = description
        self.feeling = "中立的。"
        self.thought = "这将是一次有趣的对话。"
        self.topic = topic

    def getContent(self, prior_messages):
        return [
            {
                "role": "user",
                "name": self.name,
                "content": f"""背景:{self.description}
你正在讨论:{self.topic}。
当前心情:{self.feeling}。
最后想法:{self.thought}。
保持回答简明、对话式。以角色身份回应对话。
目前对话内容:
{prior_messages}
回答时,请以形如 json 格式返回响应。响应应为一个元组,形如:
{{
    "name" : ,
    "utterance" : ,
    "feeling" : ,
    "thoughts" : 
}}
示例输出json:
{{
    "name" : "Alex",
    "utterance" : "你好。我今天一直在矿山工作。",
    "feeling" : "无聊",
    "thoughts" : "在矿山工作太糟糕了。我必须找到一份更好的工作。"
}}
"""
            }
        ]

andrew = Character(
    name="Andrew",
    description="你是一个叫做 Andrew 的角色。你是来自设得兰群岛的极富创新精神的咨询顾问。你有时候喜欢说唱。你非常喜欢战锤。你有一头浓密的头发。你富有创造力、思考力和包容性。",
    topic=starting_prompt
)

alex = Character(
    name="Alex",
    description="你是一个叫做 Alex 的角色。你是一个非常兴奋和富有创造力的创新顾问。你喜欢故事和游戏。你喜欢讲笑话。你有一头蓬松的卷发。你曾经是一个游戏开发者。你曾经参与过 Crash Bandicoot 的开发。你喜欢烹饪。你非常不喜欢运动。",
    topic=starting_prompt
)

characters = [alex, andrew]

for _ in range(5):
    for character in characters:
        chat_completion = openai.ChatCompletion.create(
            model="gpt-3.5-turbo",
            #model="gpt-4",
            messages=character.getContent(prior_messages)
        )

        conversation_output = eval(chat_completion['choices'][0]['message']['content'])
        print(
            f"{conversation_output['name']}: {conversation_output['utterance']} ({conversation_output['thoughts']})"
        )
        print("---")

        prior_messages += f"{conversation_output['name']}: {conversation_output['utterance']}\n"

结果

在这篇文章中,我曾经犹豫不决,不知道应该放入哪个正确的例子,然后,在半夜里,我突然有所领悟。在这里,有一集遗失的《老友记》。

开始提示:你正在进行激烈的辩论。第一个发言的人应该选择一个话题。

乔伊: 嘿,我们来谈谈世界上最好的三明治吧。我投票给肉丸三明治!(希望他们同意我的观点。肉丸三明治真的太美味了。)

罗斯: 乔伊,乔伊!你怎么就忘了我的土耳其三明治加上湿润的涂层呢?那才是世界上最好的三明治!(乔伊怎么会忘了我最喜欢的三明治呢?显然是最好的。)

钱德勒: 哦,对了,因为没什么东西比整天放在冰箱里的三明治更能代表“烹饪的喜悦”了。不过,罗斯,你的土耳其三明治确实是最好的。(我很喜欢这次轻松愉快关于三明治的辩论。这是一种令人愉悦的从工作中解脱出来的方式。)

菲比: 伙计们,伙计们!我们忘记了三明治的精神本质。它不仅仅是关于肉或面包,而是关于你在制作三明治时候投入的爱和能量。(我得教教他们食物的精神层面。)

乔伊: 听着,我明白。罗斯,你的土耳其三明治确实很好。而且,菲比,你说的关于爱和能量的部分没错。但是,来吧,肉丸三明治?它有一切!肉丸,奶酪,面包…就像一顿完整的饭!(真希望他们能理解我的观点。肉丸三明治真的太令人满足了。)

菲比: 乔伊,肉丸三明治确实是一顿完整的饭,但是你有没有想过三明治的灵魂呢?每个成分都有一种精神。面包,奶酪,肉丸…它们汇聚在一起创造出一种滋养我们身体以外的能量。(我得帮他们理解食物的精神本质。)

瑞秋: 伙计们,你们都错了。最好的三明治显然是一份烤奶酪!简单、经典又令人安慰。(我想知道如果我做了我的特制烤奶酪给他们吃,他们会不会喜欢。)

罗斯: 瑞秋,烤奶酪确实令人舒适,我承认。但是我的土耳其三明治配上湿润的涂层,才是一种体验!每一口都像感恩节的大餐!(我得让他们明白为什么我的三明治是最好的。也许明天我应该给每个人带一个。)

钱德勒: 嗯,我不想打破你们的快乐,但是最好的三明治显然是别人为你做的那一个。因为没有什么比不用自己动手更美味的了。(这场辩论真是荒谬但有趣。我想知道莫妮卡如果知道我在为三明治争论而不是工作会有什么反应。)

莫妮卡: 钱德勒,你说得对。最好的三明治是别人为你做的那一个。但是作为一名厨师,我可以告诉你,食材的质量和制作方式也很重要。所以,我投票给经典的培根生菜番茄三明治。它拥有完美的味道和口感的平衡。(我希望钱德勒注意到我赞同他的观点。也许这会让他改变对我的看法。)

更多需要了解的事项

  1. GPT-4在这方面比GPT-3.5要好得多。
  2. GPT-4的价格也是3.5的二十倍。如果你这样操作,很快就会产生巨额的OpenAI账单。一定要非常小心。
  3. 通过一些调整,如果你的内存足够大,计算机足够快的话,有可能将其与本地的llama2模型结合使用,但结果远没有那么好。Llama2在遵循输出格式的说明方面表现很差,因此可能需要改变希望和期望的内容,如果无法解析输出结果,只需一直重试,直到成功为止。
  4. OpenAI显然对于错误信息和用户让ChatGPT假扮成真实人物非常谨慎。如果你试图让其执行可疑操作或运行网络钓鱼欺诈等活动,他们将会正确地封禁你。
  5. 如果你与你认识的真实人物重新创造对话,请注意不要对他们进行刻板化或贬低性的描述。毋庸置疑,写一段伤害他人的描述是非常容易的。
  6. 最终,我增加了更多的类,包括一个Conversation类来保存上下文信息。我还将对话记忆移到Character类中,这样个体就可以在对话过程中随时加入和离开,而不需要全知全能。同时,也可以在中途引入新的谈论话题。
  7. 从长远来看,我非常热衷于为其提供前端界面,让对话实时进行,然后让人类介入并提供自己的意见。我非常希望能够制作一个Teams插件来实现这一点。
  8. 最后,非常感谢我的聪明而可爱的朋友Joe,他告诉我他自己也构建了类似的东西,并给了我一些建议。非常爱你,Joe。

你是谁?

我是Alex Waterston。我是英国和澳大利亚一家名为Waterstons的咨询公司的创新副总监。