Технология составления промптов для модели ИИ на примере одного чат-бота

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

В статье мы обсудим, как писать ПО для извлечения максимальных преимуществ из таких сервисов, как ChatGPT. Для этого рассмотрим пример чат-бота, который называется “Чат-бот, отвечающий на все заданные вопросы”. 

Проект чат-бота 

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

Предлагаю решить проблему с помощью чат-бота с ИИ, отвечающего на такие вопросы. Так и назовем его: “Чат-бот, отвечающий на все заданные вопросы”. Он должен: 

  • отвечать на все заданные вопросы, оставшиеся без ответа; 
  • отвечать на все заданные вопросы, получившие неправильные ответы;
  • отмечать пользователя, который задал вопрос, оставшийся без ответа или получивший неправильный ответ;
  • не реагировать на вопросы с правильными ответами, предоставленными человеком или ботом;
  • запускаться автоматически с интервалом в 1 минуту. 

До появления генеративного ИИ о создании такого проекта можно было только мечтать. Но при использовании Prompt Engineering, технологии составления инструкций (они же запросы, подсказки, промпты) для моделей ИИ, данный проект займет пару часов.

Примечание. В статье не представлен полный исходный код изучаемого проекта. Ее цель  —  показать, как структурировать инструкции для генеративного ИИ. 

Определение концепции мышления модели

Вы когда-нибудь применяли “метод 6 шляп”? Например, говорили кому-нибудь: “Думай как инженер-программист” или “Поразмышляй с позиции продукта”? Такой метод помогает настроиться на определенный образ мышления для ответа на вопрос. 

Допустим, вы строите дом. Попросите покупателя, архитектора, строителей описать кухню  —  и каждый из них ответит по-разному. Чтобы получить искомый ответ, нужно обратиться к правильному человеку. 

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

Прежде чем продолжить, вернемся чуть назад и посмотрим на входные данные для chatCompletion, используя OpenAI Node.js SDK:

const result = await openai.createChatCompletion({
model: 'gpt-4',
temperature: .7,
messages: messages
});

Данный вызов применяется для взаимодействия с OpenAI, в частности с хорошо всем известной моделью gpt-4. Поле temperature указывает на “креативность” ответов, предоставляемых моделью. Чем меньше число, тем более изобретательные/необычные ответы. Это значение варьируется от 0 до 1. Опыт показывает, что значение .7 обеспечивает стабильно качественные результаты. 

Поле messages —  это настоящее место силы. Вы можете передавать в этот массив полный текст диалогов, задавая модели содержательный контекст (подробнее об этом далее). 

Для формулировки концепции вызова необходимо передать сообщение со следующими свойствами: 

“content”: “Ты  —  мастер игры-викторины. Ты даешь быстрые точные ответы на все вопросы, которые видишь. Ты не боишься поправлять других, когда они ошибаются”.

{
"role": "system",
"content": "You are a master trivia player. You provide quick, to-the-point answers to any and all questions you see. You aren't afraid to correct others when they are wrong"
}

Указывая роль “system”, мы задаем концепцию мышления модели ИИ. Чат-бот, отвечающий на все заданные вопросы, должен мыслить как профессиональный игрок викторины и давать точные ответы. И мы инструктируем его, делать именно так. 

Установка контекста 

Итак, мы проинформировали модель ИИ, как трактовать инструкцию. Теперь необходимо предоставить ей данные для обработки. Поскольку мы создаем чат-бот, то передаем ему массив объектов JSON, представляющих историю чата. Эти исходные данные выглядят следующим образом: 

[
{
"username": "allenheltondev",
"message": "Does anyone know what color you get when you mix purple and green?"
},
{
"username": "andmoredev",
"message": "definitely pink"
},
{
"username": "astuyve",
"message": "How many pounds are in a stone?"
},
{
"username": "allenheltondev",
"message": "Thanks. And how would you center a div in css?"
},
{
"username": "astuyve",
"message": "display: flex; align-items: center; justify-content: center;"
}
]

В этом разговоре происходит много интересного для нас. Он содержит: 1) неверный ответ; 2) вопрос, оставшийся вообще без ответа, и 3) вопрос с правильным ответом. Чат-бот должен скорректировать ответ на вопрос: “Какой цвет получается при смешении фиолетового и зеленого?”; ответить на вопрос: “Сколько фунтов в одном стоуне?” и пропустить вопрос о центрировании div в CSS. Но перед этим следует обеспечить ChatGPT данными. Для этого добавляем еще одно сообщение в массив сообщений:

“content”: “Вот история чата за последнюю минуту. Это массив объектов JSON, который указывает пользователя, отправившего сообщения, и сами отправленные сообщения”.

const chatHistory = await getChatHistory(chatId);
const historyMessage = {
"role": "user",
"content": `Here is a chat history for the past minute. It's an array of json objects indicating the user that sent the message and what message they sent. ${JSON.stringify(chatHistory)}`
};
messages.push(historyMessage);

Так мы снабжаем ChatGPT необходимой информацией, чтобы добиться от него выполнения нужных действий. Пока мы не просим его что-нибудь сделать, но указываем, как он должен трактовать входящую инструкцию. Для этого предоставляем ему все данные для осмысленных действий. 

Формат вывода ответа 

Мы пишем приложение. А приложения должны вести себя последовательно. Однако ChatGPT иногда сбивается и выдает неожиданные ответы. 

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

{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "MessageFormat",
"type": "array",
"items": {
"type": "object",
"properties": {
"username": {
"type": "string",
"description": "Username of the person sending a message"
},
"message": {
"type": "string",
"description": "Message sent to all users in the chat room"
}
},
"required": [ "username", "message" ]
}
}

Предоставим эту схему для ChatGPT в виде еще одного сообщения в массиве messages. Так он узнает, как форматировать сгенерированный ответ. Кроме того, схема может применяться для последующих проверок вывода. Такой подход гарантирует, что нисходящие сервисы получают ожидаемые данные:

“content”: “Вот JSON-схема MessageFormat. Пожалуйста, дай ответ в этом формате, когда тебя попросят ответить, используя схему “MessageFormat”.

const messageSchema = require('./messages.json');
// существующий код
const outputMessage = {
"role": "user",
"content": `Here is a MessageFormat json schema. Please provide an answer in this format when asked to respond with "MessageFormat" schema. ${messageSchema}`
}
messages.push(outputMessage);

Построение вопроса 

Теперь, когда мы задали концепцию мышления, предоставили контекстные данные и требуемый формат вывода, можно задавать вопросы. Как ни странно, но это самая простая часть! Нужно лишь помнить другие фрагменты данных, предоставленных в массиве messages, чтобы соответствующим образом на них ссылаться. Вопрос строится в том же формате, что и другие сообщения. Однако на этот раз попросим сделать то, что нам нужно. 

“content”: “Ответь на все вопросы без ответа или с неправильными ответами из истории чата. Обязательно отмечай в своих ответах пользователя, задавшего вопрос (для этого используй use @{username}). Предлагаемые ответы должны исходить от бота с именем пользователя “NoQuestionsLeftBehindBot”. Структурируй ответ в соответствии со схемой MessageFormat”.

{
"role": "user",
"content": `Answer any unanswered or incorrect questions from that chat history. Be sure to tag the user who asked the question in your answers (use @{username} to tag the user). The answers you come up with should come from a bot with the username "NoQuestionsLeftBehindBot". Structure your answer in the MessageFormat schema.`
}

Здесь контекстные данные рассматриваются как “история чата”. Исходя из всего массива предоставленных данных, модель уже знает, что представляет собой эта история чата. Так что мы можем на нее ссылаться. Тот же принцип действует и в отношении формата вывода. Мы уже проинформировали модель, как структурировать ответ, указав на схему MessageFormat

Проверка формата ответа 

Как уже ранее упоминалось, вы должны проверить формат ответа, чтобы гарантировать получение данных в правильной форме. Для выполнения этой процедуры в Node.js используем ajv в качестве валидатора: 

const Ajv = require('ajv');
const addFormats = require('ajv-formats');
const messageSchema = require('./messages.json');
const ajv = new Ajv();
addFormats(ajv);

// код для создания массива сообщений

const result = await openai.createChatCompletion({
model: 'gpt-4',
temperature: .7,
messages: messages
});

try {
const answerArray = JSON.parse(result.data.choices[0].message.content);
const validate = ajv.compile(messageSchema);
const valid = validate(answerArray);
if (!valid) {
throw new Error ('Invalid data format');
}

return answerArray;
} catch(err) {
console.error(err);
throw new Error ('Invalid data format');
}

Если мы не получаем данные в нужном формате, то повторяем попытку установленное количество раз перед тем, как прекратить выполнение и уведомить человека. Но получив данные в требуемом формате, передаем их нисходящим сервисам. В случае с нашим ботом эти сообщения отправляются через соединение WebSocket для передачи ответов сразу в браузеры пользователей, подключенных к чату. 

Продолжение диалога 

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

Когда ChatGPT выдает ответ, он возвращает сообщение в предварительно установленном формате. Единственное отличие состоит в том, что в качестве свойства role устанавливается assistant, чтобы указать ответ, исходящий от ChatGPT.

Я создал диалоговую функцию Lambda в личном бессерверном комплекте инструментов для поддержания диалога между пользователем и ChatGPT. Она записывает в хронологическом порядке все сообщения от ChatGPT и вопросы от пользователя. При каждом своем вызове она добавляет данные к существующему диалогу. Таким образом вы автоматически получаете весь текст диалога с моделью. Вы можете применить только что приобретенные знания в сочетании с функцией Lambda для выстраивания эффективного диалога с ИИ. 

Заключение

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

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

“message”: “@allenheltondev, при смешении фиолетового и зеленого вы получаете темно-серый цвет”. 

“message”: “В одном стоуне 14 фунтов”. 

[
{
"username": "NoQuestionsLeftBehindBot",
"message": "@allenheltondev, you get dark gray when mixing purple and green."
},
{
"username": "NoQuestionsLeftBehindBot",
"message": "There are 14 pounds in a stone, @astuyve."
}
]

Со временем ChatGPT и другие сервисы генеративного ИИ будут совершенствоваться. Они возьмут на себя решение большинства наших задач, станут разумнее мыслить и, возможно, даже подскажут, как лучше всего писать для них инструкции!

Но одно знаю наверняка  —  ИИ никуда не исчезнет. Он будет становиться все более популярным и доступным. Поэтому приобщайтесь сейчас, пока мы только встали на путь исследований. Присоединяйтесь, создавайте приложений и открывайте ранее невиданные возможности. 

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи Allen Helton: Prompt Engineering: The Future of AI-Driven Development

Предыдущая статьяГлубокое погружение в режим Copy-on-Write в pandas. Часть 2
Следующая статьяCosmo Route  —  молниеносный маршрутизатор с открытым исходным кодом и поддержкой Federation V1/V2