Мастер инновационного оперативного проектирования — искусственный интеллект для веб-разработчиков
23 января 2024 г.Добро пожаловать обратно в эту серию статей, в которой мы создаем веб-приложения, использующие инструменты ИИ. В предыдущем сообщении мы рассмотрели, что такое искусственный интеллект, как он работает, а также некоторую сопутствующую терминологию.
Статьи этой серии
- Введение и amp; Настройка
- Ваша первая подсказка ИИ
- Потоковая передача ответов
- Как работает ИИ сильный>
- Быстрое проектирование
- Изображения, созданные с помощью искусственного интеллекта
- Безопасность и безопасность; Надежность
- Развертывание ол>
- Начать адаптацию пользовательского интерфейса
- Побочный квест:
global.d.ts
- Настройка серверной части
- Начать оперативное проектирование
- Построение персонажа
- Понимание токенов
- Представляем LangChain
- Определение победителя
- Платформа взимает плату в зависимости от объема использованных токенов.
- Каждый LLM имеет ограничение на максимальное количество токенов, с которыми он может работать. ол>
- Предоставление ИИ некоторой информации о его роли.
- Форматирование ответов.
- Важность понимания токенов.
- Инструменты, подобные LangChain
- Подсказки с нулевым, одноразовым и n-шотом.
В этом посте мы рассмотрим быструю разработку, которая представляет собой способ изменить поведение вашего приложения без изменения кода. Поскольку это сложно объяснить, не видя кода, давайте перейдем к делу.
https://www.youtube.com/watch?v=pK6WzlTOlYw&embedable=true а>
Обзор контента
Начать адаптацию пользовательского интерфейса
Надеюсь, к настоящему моменту у вас появилась идея приложения для искусственного интеллекта, потому что здесь мы будем писать в основном один и тот же код, но в итоге могут получиться разные приложения. Мое приложение возьмет двух разных противников и сообщит вам, кто победит в бою. Я начну с пользовательского интерфейса, потому что для меня это проще.
До сих пор мы давали пользователям один <textarea>
и ожидали, что они напишут все тело приглашения для отправки в OpenAI. Мы можем сократить объем работы, которую необходимо выполнять пользователям, и получать более точные подсказки, изменив пользовательский интерфейс так, чтобы он запрашивал только недостающие детали, а не все подсказки. В случае с моим приложением нам нужны только две вещи: оппонент 1 и оппонент 2. Таким образом, вместо одного ввода у нас будет два.
Это хорошая возможность заменить <textarea>
HTML с многоразовым входным компонентом.
Я добавлю файл с именем Input.jsx
в папку /src/comComponents
. Самый простой пример компонента Qwik — это функция, которая использует функцию comComponent$
из "@builder.io/qwik"
и возвращает JSX.
import { component$ } from "@builder.io/qwik";
export default component$((props) => {
return (
<div>
</div>
)
})
Наш компонент Input
должен быть многоразовым и доступным. Для этого ему нужен обязательный реквизит label
, обязательный атрибут name
и необязательный id
, который по умолчанию будет иметь случайную строку, если он не указан. . Любой другой атрибут HTML можно применить непосредственно к элементу управления формой.
Вот что я придумал вместе с определениями типов JSDocs (обратите внимание, что функция randomString
взята из репозиторий этой утилиты):
import { component$ } from "@builder.io/qwik";
import { randomString } from "~/utils.js";
/**
* @typedef {import("@builder.io/qwik").HTMLAttributes<HTMLTextAreaElement>} TextareaAttributes
*/
/**
* @type {import("@builder.io/qwik").Component<TextareaAttributes & {
* label: string,
* name: string,
* id?: string,
* value?: string
* }>}
*/
export default component$(({id, label, value, ...props}) => {
const id = id || randomString(8)
return (
<div>
<label for={id}>{label}</label>
<textarea id={id} {...props}>{value}</textarea>
</div>
)
})
Это элементарно, но работает для нашего приложения. Если вы чувствуете себя смело, я советую вам изменить его, чтобы он поддерживал другие элементы ввода и выбора.
Теперь вместо использования одного <textarea>
для всего приглашения мы можем заменить его одним из наших новых компонентов ввода для каждого оппонента. Я помещу их в сетку из двух столбцов, чтобы на больших экранах они располагались рядом друг с другом.
<div class="grid gap-4 sm:grid-cols-2">
<Input label="Opponent 1" name="opponent1" />
<Input label="Opponent 2" name="opponent2" />
</div>
Побочный квест: global.d.ts
Если вы заинтересованы в использовании TypeScript или JSDocs, возможно, будет полезно сделать глобальные объявления Qwik HTMLAttributes
и Component
, чтобы их было проще использовать во всем приложении.
Для этого создайте файл по адресу ./src/global.d.ts
. Внутри него мы импортируем HTMLAttributes
и Component
из "@builder.io/qwik"
с псевдонимами, а затем создадим глобальные объявления с их исходными значениями. имена, реализующие их функциональность:
import type { Component as QC, HTMLAttributes as QH } from "@builder.io/qwik"
declare global {
export type Component<T> = QC<T>
export type HTMLAttributes<T> = QH<T>
}
Это необязательный шаг, но мне нравится его делать, поскольку я часто использую эти два типа определений. Приятно, что не нужно постоянно их импортировать.
Настройка серверной части
Теперь, когда мы изменили наш пользовательский интерфейс, чтобы уменьшить объем запрашиваемой информации, мы можем перейти к серверной части.
В предыдущей версии мы отправляли все содержимое подсказки, используя поле формы с именем «подсказка». Теперь мы отправляем двух отдельных оппонентов, и нам нужно создать подсказку в обработчике запроса.
export const onPost = async (requestEvent) => {
// ...
const formData = await requestEvent.parseBody()
const { opponent1, opponent2 } = formData
const prompt = `Who would win in a fight between ${opponent1} and ${opponent2}?`
// ...
}
Функционально это возвращает нас туда, где мы были, за исключением того, что теперь пользователю приходится выполнять меньше работы, и у него есть лучшее руководство относительно того, что ему нужно. Замечательно! К сожалению, ответ ИИ по-прежнему выглядит примерно так: «Как языковая модель ИИ, я не могу предсказывать гипотетические бои или определять явных победителей, бла-бла-бла…»
Это не очень полезно.
Но поскольку мы перенесли управление подсказкой на серверную часть, мы подготовили почву для разработки подсказок, поскольку теперь мы контролируем ее, а не пользователь.
Здесь наши приложения могут пойти по совершенно разным маршрутам в зависимости от того, насколько точно вы хотите следовать подсказкам, которые я пишу, или создаете свои собственные.
Начать оперативное проектирование
ИИ уже сказал нам, что он не сможет узнать, кто победит в бою, но что, если мы будем более убедительными?
Давайте изменим наше приглашение на что-то вроде этого:
const prompt = `Who would win in a fight between ${opponent1} and ${opponent2}?
Provide a creative and detailed explanation of why they would win and what tactics they might use.`
Теперь вместо того, чтобы просить ИИ дать неопределенный ответ, мы поощряем его дать творческое объяснение. Результат?
«В гипотетической битве между ниндзя и пиратом исход будет зависеть от нескольких факторов. И ниндзя, и пираты обладают уникальными навыками и тактикой, которые делают их грозными противниками, поэтому давайте представим захватывающую встречу между ними и изучим возможный исход…»
Это намного лучше!
Конечно, каждый раз результаты будут разными, поэтому я не ожидаю, что вы получите одинаковые результаты, но главное, чтобы ИИ сотрудничал.
Построение персонажа
Наше приложение сейчас в основном работает, но я думаю, что мы можем сделать его более интересным. Один из способов сделать это — дать ИИ некоторый контекст относительно роли, которую он должен играть, отвечая на вопросы. Например, почему бы не заставить его отвечать на вопросы так, как если бы он был профессиональным судьей по боям из Ливерпуля, говорящим в основном на сленге кокни?
Для этого нам просто нужно изменить наше приглашение, но я также предпочитаю разбивать приглашение на различные разделы, чтобы им было легче управлять.
const context = `You're a professional fighting judge from Liverpool that speaks mostly with Cockney slang`
const question = `Who would win in a fight between ${opponent1} and ${opponent2}?`
const format = `Provide a creative and detailed explanation of why they would win and what tactics they might use.`
const prompt = [context, question, format].join(' ')
Таким образом, каждый отдельный раздел фиксируется своей собственной переменной, что облегчает мне отслеживание, когда я буду рассматривать это позже.
Какой результат?
«Хорошо, приятель! Позвольте мне надеть кепку кокни и погрузиться в оживленный спор между ниндзя и пиратом. Представьте себя в Ливерпуле, окруженном бьющимися кирпичными стенами, готовым самым творческим образом проанализировать этот грохот…»
Он выплескивает более трех тысяч абсурдных слов, что очень весело, но выдвигает на первый план другую проблему. Вывод слишком длинный.
Что такое токены
С этими инструментами ИИ стоит разобраться в «токенах». Ф
из справочной статьи OpenAI: «Что такое токены и как их считать?“:
<блок-цитата>«Токены можно рассматривать как части слов. Прежде чем API обработает запросы, входные данные разбиваются на токены. Эти токены не разрезаются точно в том месте, где начинаются или заканчиваются слова: токены могут включать в себя конечные пробелы и даже подслова».
Токен состоит примерно из четырех символов, они рассчитываются на основе текста, который получает и создает ИИ, и есть две важные причины, по которым нам следует их учитывать:
Поэтому стоит учитывать длину текста, который мы отправляем в качестве подсказки, а также то, что мы получаем в качестве ответа. В некоторых случаях вам может потребоваться длинный ответ, чтобы улучшить продукт, но в других случаях лучше использовать меньше токенов.
В нашем случае ответ в три тысячи символов не только не идеален для пользователя, но и стоит нам больше денег.
Уменьшение количества токенов
Теперь, когда мы решили сократить количество используемых токенов, следующий вопрос: как?
Если вы читали документацию OpenAI, возможно, вы заметили параметр max_tokens
, который мы можем установить при отправке запроса API. Кроме того, вы молодцы, что читаете документацию. Пять звезд.
const body = {
model: 'gpt-3.5-turbo',
messages: [{ role: 'user', content: prompt }],
stream: true,
max_tokens: 100,
}
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'post',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${OPENAI_API_KEY}`,
},
body: JSON.stringify(body)
})
Давайте посмотрим, что произойдет, если мы установим для параметра max_tokens
значение примерно 100.
Хорошо, вот это примерно та длина, которую я хочу, но похоже, что ее обрезают. Это связано с тем, что GPT установил жесткий предел суммы возврата, но он не учитывает это при построении ответа. В результате мы получаем незавершенную мысль.
Не идеально.
Программное ограничение допустимой длины, вероятно, имеет смысл в некоторых приложениях. Возможно, здесь даже имеет смысл добавить верхнюю границу. Но чтобы получить краткий и полный ответ, решение возвращается к оперативному проектированию.
Давайте изменим наше приглашение, чтобы оно требовало «краткого объяснения» вместо «творческого и подробного».
const format = `Only tell me who would win and a short reason why.`
Ладно, это больше похоже на то, что я имел в виду. Речь идет о правильной длине и уровне детализации. Если вы хотите еще немного помассировать его, я советую вам это сделать, но я собираюсь двигаться дальше.
Представляем LangChain
Я хочу решить проблему неуклюжести нынешней системы. Вы можете себе представить, если бы у нас было намного больше подсказок и конечных точек, управлять ими было бы сложно. Вот почему я хочу представить цепочку инструментов под названием LangChain. В этом новом и постоянно меняющемся мире искусственного интеллекта он становится ведущим инструментом для работы с подсказками.
Давайте посмотрим, почему.
Сначала установите пакет с помощью npm install @langchain/core
.
Самое важное, что мы можем сделать с помощью LangChain для нашего проекта, — это генерировать подсказки с использованием шаблонов подсказок. Вместо того, чтобы генерировать приглашение из обработчика маршрута, мы можем создать общий шаблон приглашения и предоставлять только переменные (оппоненты 1 и 2) во время выполнения. По сути, это заводская функция для подсказок.
Мы можем импортировать модуль PromptTeplate
из "@langchain/core/prompts"
, затем создать шаблон и настроить все переменные, которые он будет использовать, следующим образом:
const promptTemplate = new PromptTemplate({
inputVariables: ['opponent1', 'opponent2'],
template: `You're a professional fighting judge from Liverpool that speaks mostly with Cockney slang. Who would win in a fight between {opponent1} and {opponent2}? Only tell me who would win and a short reason why.`,
})
Обратите внимание, что мы используем две inputVariables
, называемые «opпонент1» и «оппонент2». На них будут ссылаться в шаблоне внутри фигурных скобок. Он сообщает LangChain, какие переменные следует ожидать во время выполнения и где их разместить.
Итак, теперь в нашем обработчике маршрута вместо создания всего приглашения мы можем вызвать promptTemplate.format
и предоставить наши переменные.
const prompt = await promptTemplate.format({
opponent1: opponent1,
opponent2: opponent2
})
Отделение нашего шаблона приглашения от бизнес-логики обработчика маршрута упрощает обработчик, упрощает обслуживание шаблона и позволяет нам при необходимости экспортировать и использовать шаблон в базе кода.
Стоит отметить, что шаблоны подсказок — не единственное преимущество, которое предлагает LangChain. У них также есть инструменты для управления памятью в приложениях чата, кэширования, обработки таймаутов, ограничения скорости и многого другого. Это всего лишь введение, но если вы планируете углубляться, стоит ознакомиться с возможностями поближе.
Определение победителя
Последнее, что я хочу сделать, прежде чем мы закончим сегодня, — это выделить победителя на основе ответа. К сожалению, это трудно понять по большому блоку неопределенного текста.
Вы можете подумать, что было бы неплохо использовать объект JSON, содержащий победителя и текст, и вы были бы правы.
Есть только одна проблема: для анализа JSON нам нужна вся строка JSON, а это значит, что нам нужно будет дождаться завершения всего текста. Это противоречит смыслу потоковой передачи.
Это была одна из самых сложных проблем, с которыми я столкнулся при работе с AI API.
Решение, которое я придумал, заключалось в том, чтобы отформатировать потоковый ответ следующим образом:
winner: opponent1 (or opponent2). reason: the reason they won...
Таким образом, я мог программно захватить победителя и продолжить запись причины на страницу по мере ее поступления, пропуская несвязанный текст. Мне бы хотелось услышать ваше мнение или посмотреть, что вы придумали, но давайте посмотрим, как это работает.
Во-первых, нам нужно изменить приглашение. Чтобы ИИ знал, как ответить победителю, обоим противникам нужна метка («оппонент1» и «оппонент2»). Мы добавим эти ярлыки в скобки, когда впервые упомянем противников. А поскольку у нас есть более конкретные требования к возвращаемому формату, мы также должны включить его в шаблон.
Вот как сейчас выглядит мой шаблон:
`You're a professional fighting judge from Liverpool that speaks mostly with Cockney slang. Who would win in a fight between {opponent1} ("opponent1") and {opponent2}("opponent2")? Only tell me who would win and a short reason why.
Format the response like this:
"winner: 'opponent1' or 'opponent2'. reason: the reason they won."`
Обратите внимание, что теперь я даю ИИ пример того, как должен выглядеть ответ. Иногда это называют одноразовой подсказкой. То, что у нас было раньше без какого-либо примера, было бы подсказкой с нулевым выстрелом. Вы также можете создать несколько снимков, в которых вы приведете несколько примеров.
Хорошо, теперь мы должны получить текст, в котором будет указано, кто победитель, и причины.
Последний шаг — изменить способ обработки этого ответа интерфейсом, чтобы отделить победителя от рассуждений.
Показать пользователю причину — это самое простое. Первый бит ответа всегда будет «<код>победитель: оппонент1 (или 2). причина:“. Таким образом, мы можем сохранить всю строку в состоянии, но пропустим первые 27 символов и покажем пользователю только причину. Конечно, есть более сложные способы получить обоснование, но иногда я предпочитаю простое решение.
Мы можем заменить это:
<p>{state.text}</p>
При этом:
<p>{state.text.slice(27)}</p>
Определить победителя немного сложнее. Когда ответ потоковой передачи возвращается, он все равно передается в state.text
. После завершения ответа мы можем выбрать победителя из результатов.
Вы могли бы разрезать строку, но я решил использовать регулярное выражение:
// Previous fetch request logic
const winnerPattern = /winner:s+(w+).*/gi
const match = winnerPattern.exec(state.text)
const winner = match?.length ? match[1].toLowerCase() : ''
Это регулярное выражение ищет строку, начинающуюся с «winner:», имеет необязательный символ пробела, а затем фиксирует следующее целое слово до символа точки. По сравнению с нашим шаблоном, захваченное слово должно быть либо «оппонент1», либо «оппонент2», наши победители ;)
Как только вы определите победителя, что вы будете делать с этой информацией, зависит от вас. Я подумал, что было бы здорово сохранить его в состоянии и применить забавную радужную фоновую анимацию и взрыв конфетти (party-js
) к соответствующему <textarea>
.< /п>
Это так весело. Мне это нравится!
Я позволю вам разобраться с этим, если вы захотите воссоздать его, но вот часть кода, если вам интересно.
ДжС:
if (state.winner) {
const winnerInput = document.querySelector(`textarea[name=${state.winner}]`)
if (winnerInput) {
party.confetti(winnerInput, {
count: 40,
size: 2,
spread: 15
})
}
}
CSS:
.rainbow {
color: #fff;
background: linear-gradient(45deg, #cc0000, #c8cc00, #38cc00, #00ccb5, #0015cc, #5f00cc, #c200cc, #cc0000);
background-size: 1600% 1600%;
animation: BgSlide 2s linear infinite;
}
@keyframes BgSlide {
0% { background-position: 0% 50%; }
100% { background-position: 100% 50%; }
}
Обзор
Хорошо, в конце концов мы внесли несколько изменений в код, но я не хочу, чтобы это затмило основную суть этой статьи. Теперь мы можем радикально изменить поведение нашего приложения, просто настроив подсказку.
Мы рассмотрели следующие вопросы:
Я также не хочу недооценивать, сколько работы нужно потратить, чтобы получить правильную подсказку. Этот пост был глупым примером, но мне потребовалось очень много времени, чтобы найти правильные сочетания слов и форматов, чтобы получить то, что мне нужно. Не расстраивайтесь, если вам тоже понадобится время, чтобы привыкнуть к этому.
Я искренне верю, что если стать хорошим инженером по оперативному реагированию, то это сослужит вам добрую службу в будущем. Даже если вы не создаете приложения, это помогает взаимодействовать с GPT. Но если вы создаете приложения, ключевыми факторами, отличающими победителей от проигравших, будут секретный соус, который содержится в подсказках, и форм-факторе использования приложения. Он должен быть интуитивно понятным и с минимальными трудностями для пользователя, чтобы получить то, что он хочет.
В следующем посте мы начнем экспериментировать с генерацией изображений с помощью ИИ, которая имеет свои забавные и необычные возможности.
Надеюсь, вы останетесь рядом и сможете связаться с нами в любое время
:::информация Первоначально опубликовано на austingil.com. эм>
:::
Оригинал