Моделирование разговоров с ChatGPT

Мастерство ведения разговоров с ChatGPT

Команда известного телешоу сидит за столом и обсуждает бутерброды. (MidJourney. Задание от автора)

Как найти надежды и мечты большой языковой модели

Недавно я занимался использованием API Open AI. API – это просто способ использования ChatGPT из другой программы вместо использования веб-интерфейса. Это означает, что вы можете получить немного больше контроля над тем, о чем вы просите его делать для вас.

Обычно, когда вы используете ChatGPT через веб-интерфейс, это похоже на разговор с полезным виртуальным помощником; вы просите его делать что-то для вас, и он выполняет это. Вы запрашиваете информацию, и он предоставляет вам ответ (возможно, правильный). Можно пойти дальше и попросить его притвориться тем, чем он не является, чтобы повеселиться, но вы ограничены этим общением между вами и компьютером.

Однако выяснилось, что с небольшими манипуляциями и немного кода можно сделать веселые вещи, включая разговоры ChatGPT с самим собой. Я постараюсь сделать это объяснение понятным и простым, потому что есть действительно интересные вещи, которые можно исследовать, независимо от вашей технической экспертизы.

Итак, пристегнитесь и давайте исследовать, как заставить ChatGPT разговаривать с самим собой…

Шаг 1: Разговоры

Первое, что нужно понять, – ChatGPT на самом деле ничего не запоминает. Когда вы ведете с ним разговор через его интерфейс чата, в фоновом режиме происходят несколько изобретательных трюков, которые отправляют всю вашу беседу до этого на каждый запрос. Затем ChatGPT разбирает всю беседу снова и придумывает новый ответ. Когда мы используем API, нам нужно вручную сообщить ему, что произошло раньше, иначе каждый раз, когда мы задаем вопрос, контекст полностью забывается. Это звучит неудобно, но мы можем использовать это в своих интересах, манипулируя историей разговора!

Использование Python API OpenAI крайне просто. Нам нужно запросить завершение чата. Завершение чата – это в точности то, о чем звучит: это по сути означает следующее вопрос ChatGPT: “Когда я говорю , что вы отвечаете?”. Мы делаем это следующим образом:

import osimport openaiopenai.api_key = "ВАШ КЛЮЧ API OPENAI"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, можно найти в части messages. Мы должны передать историю нашего разговора до сих пор. Вы можете видеть, что у каждого сообщения в списке есть атрибут “role”. В настоящее время нас интересуют две из них: “user” и “assistant”. Мы уже говорили о том, что использование ChatGPT похоже на разговор между пользователем и полезным помощником, и, как ни странно, здесь это становится очень ясным. Когда мы ведем разговор с ChatGPT через веб, передается список сообщений “user” и “assistant”, чтобы ChatGPT знал, что произошло раньше. Когда мы используем API, мы можем немного пошалить с этим, чтобы ChatGPT разговаривал сам с собой:

import osimport openaiopenai.api_key = "ВАШ КЛЮЧ API OPENAI"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}]

Шаг 2: Персонажи

Второе, что вам нужно знать, заключается в том, что когда мы задаем вопрос ChatGPT, мы можем предоставить много дополнительной контекстной информации наряду с нашими утверждениями. Мы не только можем включить историю разговора в этот контекст, но также можем сказать ChatGPT притвориться кем-то.

Вооружившись этой информацией, я заставил ChatGPT начать разговаривать сам с собой. Я создал двух персонажей, которых я назвал и кратко описал. Затем я смоделировал разговор, попросив ChatGPT притвориться каждым персонажем и продолжить диалог.

Когда мой персонаж Андрей готов что-то сказать, отправляемый нами запрос выглядит следующим образом:

"""Вы - Андрей. Вы крайне энтузиастичный инновационный консультант из Шетланда. Иногда вы любите рэповать. Вы очень-очень любите Warhammer. У вас много волос. Вы творческий, заботливый и включающий в себя. Ответьте на диалог в характере."""

Затем мы продолжаем диалог между нашими персонажами, включая всю предыдущую беседу, пока не надоедаем или не исчерпаем лимит кредитов для API.

import osimport openaiopenai.api_key = "ВАШ КЛЮЧ К API OPENAI"starting_prompt = "Говорите о фруктах"prior_messages = [{"role": "user", "content": starting_prompt}]andrew = """Вы - Андрей. Вы крайне энтузиастичный инновационный консультант из Шетланда. Иногда вы любите рэповать. Вы очень-очень любите Warhammer. У вас много волос. Вы творческий, заботливый и включающий в себя. Ответьте на диалог в характере."""alex = """Вы - Алекс. Вы крайне восхитительный и очень креативный инновационный консультант. Вы любите истории и игры. Вы любите рассказывать шутки. У вас много непослушных кудрявых волос. Вы ранее работали разработчиком игр. Вы когда-то работали над игрой Crash Bandicoot. Вы любите готовить. Вы очень не любите спорт. Ответьте на диалог в характере."""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}]

Это работает довольно хорошо, и, фактически, мы можем легко расширить его на более чем двух персонажей. Если мы случайным образом переключаемся между персонажами, разговор может выглядеть вполне естественным. На этом этапе я решил собраться с духом и начать объединять все в классы. (ПРИМЕЧАНИЕ: я – престарелый разработчик. Я не занимался реальной разработкой уже много лет. Лучшее, на что я могу рассчитывать сегодня, это Advent of Code каждое Рождество. Поэтому, пожалуйста, простите мой код. Я не python-специалист. В душе я старомодный C++ и Objective C разработчик.)

import osimport openaiopenai.api_key = "ВАШ КЛЮЧ К API OPENAI"starting_prompt = "Говорите о фруктах"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 = "Andrew", description = """Вы - персонаж по имени Андрей. Вы крайне энтузиастичный инновационный консультант из Шетланда. Иногда вы любите рэповать. Вы очень-очень любите Warhammer. У вас много волос. Вы творческий, заботливый и включающий в себя.""")alex = Character(name = "Alex", description = """Вы - персонаж по имени Алекс. Вы крайне восхитительный и очень креативный инновационный консультант. Вы любите истории и игры. Вы любите рассказывать шутки. У вас много непослушных кудрявых волос. Вы ранее работали разработчиком игр. Вы когда-то работали над игрой Crash Bandicoot. Вы любите готовить. Вы очень не любите спорт.""")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 вернуть ответы в определенном формате и использовать это для включения дополнительной информации в ответы. Если я включу инструкцию о структуре ответов в запросе, то ChatGPT будет стараться следовать ей. Здесь я прошу его отвечать в формате JSON, который является широко используемой структурой данных, позволяющей нам включать большое количество дополнительных фрагментов информации в удобном для чтения и доступном формате:

"""Выведите ответ на указанную выше проблему в формате JSON. Результат должен быть кортежем, где кортеж имеет следующую форму:{     "name" : <Имя>,       "высказывание" : <Высказывание>}"""

До этого момента мы использовали атрибут messages в API-запросе для хранения истории переписки. Однако я обнаружил, что можно получить лучшие результаты, включив историю переписки в основной запрос. Это также дает нам большую гибкость в том, что мы можем включить в историю. Намного больше об этом ниже, но сейчас мы просто внесем небольшие изменения, когда переходим к запросу переформатирования вывода:

import osimport openaiopenai.api_key = "ВАШ API-КЛЮЧ OPENAI"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' : <Имя>,    'высказывание' : <Высказывание>}}"""}]andrew = Character(name = "Andrew", description = """Ты персонаж по имени Андрей. Ты очень энтузиастичный инновационный консультант из Шетланда. Иногда ты любишь рэпировать. Ты просто обожаешь Warhammer. У тебя много волос. Ты творческий, заботливый и инклюзивный.""")alex = Character(name = "Alex", description = """Ты персонаж по имени Алекс. Ты очень восторженный и творческий инновационный консультант. Ты любишь истории и игры. Ты любишь рассказывать шутки. У тебя много непослушных вьющихся волос. Ты раньше работал разработчиком игр. Ты однажды работал над Crash Bandicoot. Ты любишь готовить. Ты не очень любишь спорт.""")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 сможет предоставить нам. Самое интересное, что мы можем добавить в нашу систему разговора, это понимание мыслей и чувств каждого персонажа, когда они говорят.

Все вместе наш запрос может выглядеть так:

"""Контекст: Ты персонаж по имени Алекс. Ты очень восторженный и творческий инновационный консультант. Ты любишь истории и игры. Ты любишь рассказывать шутки. У тебя много непослушных вьющихся волос. Ты раньше работал разработчиком игр. Ты однажды работал над Crash Bandicoot. Ты любишь готовить. Ты не очень любишь спорт.Ты обсуждаешь: Лучший фрукт.Текущее настроение: Счастливое.Последняя мысль: Фрукты действительно вкусные. Основные ответы должны быть лаконичными и беседовать. Ответьте на переписку в формате персонажа.{предыдущая_беседа}Выведите ответ на указанную выше проблему в формате JSON. Результат должен быть кортежем, где кортеж имеет следующую форму:{      "name" : <Имя>,       "высказывание" : <Высказывание>,       "настроение" : <Настроение>,       "мысли" : <Мысли>}"""

Наконец, добавление примера вывода в запрос кажется помогает ИИ лучше установить требуемый формат каждый раз. Поэтому мы добавляем:

"""Пример вывода json: {"name" : "Алекс", "фраза" : "Привет. Сегодня я работал в шахте.", "чувство" : "Скучный", "мысли" : "Работать в шахте ужасно. Мне нужно найти лучшую работу"}"""

Итак, объединив все это вместе, мы получаем следующее:

import osimport openaiopenai.api_key = "ВАШ КЛЮЧ OPENAI"начальный_запрос = "Говорите о фруктах"предыдущие_сообщения = ""class Персонаж: def __init__(self, имя, описание, тема):  self.name = имя  self.description = описание  self.feeling = "Нейтральное."  self.thought = "Это будет интересный разговор."  self.topic = тема   def getContent(self, предыдущие_сообщения):  return [{"роль": "пользователь", "имя": self.name, "содержание": f"""Контекст: {self.description}Вы обсуждаете: {self.topic}.Текущее настроение: {self.feeling}.Последние мысли: {self.thought}.Отвечайте кратко и разговорчиво. Отвечайте в характере.Диалог до сих пор:{предыдущие_сообщения}Ответить кратко, в одном параграфе, в характере.Выведите ответ на указанный выше запрос в json. Результат должен быть кортежем в формате{{"имя" : <Name>, "фраза" : <Utterance>, "чувство" : <Feeling>, "мысли" : <Thoughts>}}Пример вывода json:{{"имя" : "Алекс", "фраза" : "Привет. Сегодня я работал в шахте.", "чувство" : "Скучный", "мысли" : "Работать в шахте ужасно. Мне нужно найти лучшую работу"}}"""}]andrew = Персонаж(имя = "Андрей", описание = """Вы персонаж по имени Андрей. Вы крайне энтузиастичный инновационный консультант из Шетланда. Иногда вы любите рэповать. Вы очень любите Warhammer. У вас много волос. Вы креативны и обдуманны.""", тема = начальный_запрос)alex = Персонаж(имя = "Алекс", описание = """Вы персонаж по имени Алекс. Вы крайне восторженный и очень креативный инновационный консультант. Вы любите истории и игры. Вы любите рассказывать шутки. У вас много непослушных вьющихся волос. Раньше вы работали на создании игр. Вы работали над Crash Bandicoot. Вам нравится готовить. Вы не любите спорт.""", тема = начальный_запрос)персонажи = [alex, andrew]for _ in range(5): for персонаж in персонажи:  chat_completion = openai.ChatCompletion.create(   model="gpt-3.5-turbo",    #model="gpt-4",    messages = персонаж.getContent(предыдущие_сообщения)  )  conversation_output = eval(chat_completion['choices'][0]['message']['content'])  print(f"{conversation_output['name']}: {conversation_output['utterance']} ({conversation_output['thoughts']})")  print("---")  предыдущие_сообщения += f"{conversation_output['name']}: {conversation_output['utterance']}\n"

Результаты

Мне было сложно подумать о подходящем примере для этой статьи, и тогда, посреди ночи, я осознал. Вот вам… потерянная серия «Друзей».

Начальный запрос: У вас идет оживленный спор. Первый говорящий должен выбрать тему.

Джоуи: Эй, давайте поговорим о лучшем сэндвиче в мире. Я голосую за мясной саб! (Надеюсь, они согласятся со мной. Мясной саб так вкусен.)

Росс: Джоуи, Джоуи! Как ты можешь забыть о моем индюшачьем сэндвиче с соком от булочки? Это лучший сэндвич в мире! (Как Джоуи может забыть о моем любимом сэндвиче? Очевидно, что он самый лучший из всех.)

Чендлер: О да, потому что ничто не говорит «кулинарное наслаждение» как сэндвич, побывавший в холодильнике весь день. Но, конечно, Росс, твой индюшачий сэндвич – лучший. (Мне нравится этот легкий спор о сэндвичах. Он приятно отвлекает от работы.)

Фиби: Ребята, ребята! Мы забываем о духовной сущности сэндвича. Это не о мясе или хлебе, это о любви и энергии, которые вы вкладываете в его приготовление. (Мне нужно научить их о духовной стороне вещей.)

Джоуи: Понимаю. Росс, твой индюшачий сэндвич хорош, и Фиби, ты права про любовь и энергию. Но сэндвич с мясными шариками? В нем есть все! Мясные шарики, сыр, хлеб…это как полноценный прием пищи в одном блюде! (Очень надеюсь, что они видят мою точку зрения. Мясной саб так удовлетворителен.)

Фиби: Джоуи, мясной саб действительно полноценный прием пищи, но ты когда-нибудь думал о душе сэндвича? Каждый ингредиент имеет свой дух. Хлеб, сыр, мясные шарики… они все соединяются, создавая энергию, которая питает не только наши тела. (Я должна помочь им понять духовную сущность пищи.)

Рэйчел: Парни, вы все не понимаете. Лучший сэндвич, очевидно, гриль-чиз! Простой, классический и такой утешительный. (Интересно, понравится ли им, если я приготовлю для них мой особенный гриль-чиз.)

Росс: Рейчел, гриль-чиз утешительный, я согласен. Но мой индюшачий сэндвич с соком от булочки – это опыт! Это как трапеза на День благодарения в каждом кусочке! (Мне нужно заставить их понять, почему мой сэндвич лучший. Возможно, завтра я принесу каждому по одному.)

Чендлер: Что ж, я не люблю разбивать иллюзии, но лучший сэндвич, конечно же, – тот, который кто-то другой готовит для тебя. Потому что нет ничего вкуснее, чем не делать ни одной работы. (Этот спор смешной, но занимательный. Интересно, как бы Монике это понравилось, если бы она знала, что я спорю о сэндвичах вместо работы.)

Моника: Чендлер, ты прав. Лучший сэндвич – тот, который кто-то другой готовит для тебя. Но как шеф-повар, я могу сказать, что качество ингредиентов и способ приготовления имеют значение. Поэтому мой голос за классический BLT. У него идеальное сочетание вкусов и текстур. (Надеюсь, Чендлер заметил, что я согласилась с ним. Может быть, это заставит его по-другому взглянуть на меня.)

Еще несколько вещей, о которых следует знать

  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. Наконец, огромное спасибо моему невероятно умному и милому другу Джо, который сказал мне, что он сам создал нечто подобное, и дал мне несколько указаний. Люблю тебя, Джо.

Кто ты?

Я Алекс Уотерстон. Я заместитель директора по инновациям в консультационной компании Waterstons в Великобритании и Австралии.