Бот с обращением к языковым моделям
В этом туториале показано, как создать бота с обращением к языковым моделям, который:
- поддерживает беседу на любые темы, используя API Jay Copilot.
- отправляет историю диалога в сервис Caila для создания краткого содержания.
Подробнее об этих продуктах можно узнать в документации: Jay Copilot и Caila.
Подготовка
Перед началом работы нам необходимо получить ключи доступа к Jay Copilot и Caila и указать их в качестве переменных в JAICP:
-
Получите доступ к API Jay Copilot. Для этого отправьте запрос на адрес genai@just-ai.com.
-
Сгенерируйте ключ для работы API Jay Copilot. Инструкцию по выпуску ключа можно найти в документации Jay Copilot.
-
Скопируйте ключ и сохраните его в токен
COPILOT_TOKENв разделе Токены и переменные → Токены в JAICP. -
Сгенерируйте токен для работы с Caila.
-
Скопируйте токен и сохраните его в токен
MLP_TOKENв разделе Токены и переменные → Токены в JAICP.
В сценарии мы будем использовать обращения к сервису openai-proxy, который размещается в Caila.
Для этого нам нужно указать переменные окружения MLP_ACCOUNT и MLP_MODEL:
-
Перейдите на страницу сервиса openai-proxy в Caila.
-
В адресной строке будут указаны аккаунт-владельца сервиса и название сервиса:
https://caila.io/catalog/just-ai/openai-proxy,где
just-ai— аккаунт-владелец, аopenai-proxy— название сервиса. Эти значения и будут значениями переменных окруженияMLP_ACCOUNTиMLP_MODELсоответственно. -
Сохраните значения в разделе Токены и переменные → Переменные среды в JAICP. Опубликуйте переменные во все каналы.
Функции
Для обращения к Jay Copilot и Caila мы создадим несколько функций и сохраним их в директории scripts в файлах copilot.js и mlp.js.
copilot.js
//Функция создания диалога с приложением «Прямой доступ к нейросетям. ChatGPT».
function initConversation(systemPrompt) {
var headers = {
// Получение токена для CoPilot из раздела «Токены и переменные».
"x-api-key": $secrets.get("COPILOT_TOKEN")
};
var body = {
"app": {
"template": "directLLM",
"params": {
"modelName": "gpt-4o-mini",
"systemPrompt": systemPrompt,
"maxInputTokensGpt4OMini": 100000,
"maxTokensGpt4OMini": 16384
}
}
};
try {
var res = $http.post("https://app.jaycopilot.com/api/appsAdapter/conversations/", { headers: headers, body: body })
return res.data.id;
} catch (e) {
throw new Error(">>> Error calling Jay Copilot API in initConversation" + JSON.stringify(e));
};
};
//Функция отправки запроса в созданный диалог.
function conversate(conversationId, userMessage) {
var headers = {
"x-api-key": $secrets.get("COPILOT_TOKEN")
};
var form = {
"text": userMessage
};
try {
var res = $http.post("https://app.jaycopilot.com/api/appsAdapter/conversations/" + conversationId + "/message", { headers: headers, form: form, timeout: 25000 });
return res.data.content[0].text;
} catch (e) {
throw new Error(">>> Error calling Jay Copilot API in conversate" + JSON.stringify(e));
};
};
//Функция обработки сообщения пользователя с использованием LLM.
function chatLlm(userMessage) {
var systemPrompt = $prompt.smallTalk
if (!$.client.conversationId) {
$.client.conversationId = initConversation(systemPrompt);
}
var answer = conversate($.client.conversationId, userMessage);
return answer;
};
//Функция удаления созданного диалога.
function deleteConversation(conversationId) {
var headers = {
"x-api-key": $secrets.get("COPILOT_TOKEN")
};
try {
var res = $http.delete("https://app.jaycopilot.com/api/appsAdapter/conversations/" + conversationId, { headers: headers})
return res;
} catch (e) {
throw new Error(">>> Error calling Jay Copilot in deleteConversation" + JSON.stringify(e));
};
}
- initConversation
- conversate
- chatLlm
- deleteConversation
Функция initConversation принимает один параметр systemPrompt, который представляет собой системный промт для инициализации разговора.
-
Определение заголовков запроса:
-
В переменной
headersсоздается объект, содержащий заголовокx-api-key. -
Значение заголовка
x-api-keyполучается с помощью метода$secrets.get("COPILOT_TOKEN"). Этот метод ищет токен с именемCOPILOT_TOKEN.Подробнее о методе
$secrets.getчитайте в документации.
-
-
Определение тела запроса:
-
В переменной
bodyсоздается объект, содержащий параметры для инициализации разговора. -
В объекте
appуказывается шаблонtemplateс значением"directLLM". -
В объекте
paramsуказываются параметры модели:modelName: имя модели, в данном случае"gpt-4o-mini".systemPrompt: системный промт, переданный в функцию.maxInputTokensGpt4OMini: максимальное количество входных токенов для модели.maxTokensGpt4OMini: максимальное количество токенов для модели.
к сведениюВозможные значения
template, а также список параметров для каждого приложения можно узнать с помощью метода API Jay Copilot: GET /api/appsAdapter/templates. -
-
Отправка POST-запроса:
-
С помощью метода
$http.postотправляется POST-запрос на URLhttps://app.jaycopilot.com/api/appsAdapter/conversations/Подробнее об этом запросе можно узнать в спецификации API Jay Copilot.
-
В запросе передаются заголовки
headersи тело запросаbody.
-
-
Обработка ответа:
- Если запрос успешен, возвращается идентификатор разговора
res.data.id. - Если запрос не успешен, выбрасывается ошибка с сообщением
">>> Error calling Jay Copilot API in initConversation"и подробностями ошибки.
- Если запрос успешен, возвращается идентификатор разговора
Функция conversate принимает два параметра: conversationId (идентификатор разговора) и userMessage (сообщение пользователя).
-
Определение заголовков запроса:
-
В переменной
headersсоздается объект, содержащий заголовокx-api-key. -
Значение заголовка
x-api-keyполучается с помощью метода$secrets.get("COPILOT_TOKEN"). Этот метод ищет токен с именемCOPILOT_TOKEN.Подробнее о методе
$secrets.getчитайте в документации.
-
-
Определение тела запроса:
В переменной
formсоздается объект, содержащий полеtext, которое содержит сообщение пользователяuserMessage. -
Отправка POST-запроса:
-
С помощью метода
$http.postотправляется POST-запрос на URLhttps://app.jaycopilot.com/api/appsAdapter/conversations/+conversationId+/message.Подробнее об этом запросе можно узнать в спецификации API Jay Copilot.
-
В запросе передаются заголовки
headers, тело запросаformи таймаутtimeoutв 25 000 миллисекунд (25 секунд).
-
-
Обработка ответа:
- Если запрос успешен, возвращается текст ответа
res.data.content[0].text. - Если запрос не успешен, выбрасывается ошибка с сообщением
">>> Error calling Jay Copilot API in conversate"и подробностями ошибки.
- Если запрос успешен, возвращается текст ответа
Функция chatLlm принимает один параметр userMessage, который представляет собой сообщение пользователя.
-
Определение системного промта:
В переменной
systemPromptсохраняется системный промт, полученный из$prompt.smallTalk.Сам текст промта мы укажем в справочнике
dictionaries→prompt.yaml:smallTalk: |
Ты — дружелюбный и общительный виртуальный ассистент, который ведет непринужденные беседы с пользователями.
Твоя цель — поддерживать интересный и легкий разговор, задавать вопросы, чтобы лучше узнать собеседника, и делиться интересной информацией.
Ты можешь обсуждать повседневные темы, хобби, погоду, новости, фильмы, книги и многое другое.
Всегда будь вежливым, внимательным и готовым поддержать беседу.При этом переменную
$promptмы объявим в файлеrequirements.sc:require: dictionaries/prompt.yaml
var = $prompt -
Проверка существования идентификатора разговора:
- Условие
if (!$.client.conversationId)проверяет, существует ли идентификатор разговора$.client.conversationId. - Если идентификатор разговора не существует (значение
undefinedилиnull), вызывается функцияinitConversation(systemPrompt)для инициализации нового разговора, и результат сохраняется в$.client.conversationId.
подсказкаИмпорт
common.jsизzb-commonвrequirements.scпозволяет обращаться к встроенным переменным в JS-файлах через$., а не$jsapi.context().. - Условие
-
Отправка сообщения в разговор:
Затем вызывается функция
conversateс параметрами$.client.conversationIdиuserMessage, и результат сохраняется в переменнойanswer. -
Возврат ответа:
Функция возвращает ответ
answer, который представляет собой текст ответа от Jay Copilot.
Функция deleteConversation принимает один параметр conversationId, который представляет собой идентификатор разговора, который нужно удалить.
-
Определение заголовков запроса:
-
В переменной
headersсоздается объект, содержащий заголовокx-api-key. -
Значение заголовка
x-api-keyполучается с помощью метода$secrets.get("COPILOT_TOKEN"). Этот метод ищет токен с именемCOPILOT_TOKEN.Подробнее о методе
$secrets.getчитайте в документации.
-
-
Отправка DELETE-запроса:
-
С помощью метода
$http.deleteотправляется DELETE-запрос на URLhttps://app.jaycopilot.com/api/appsAdapter/conversations/+conversationId.Подробнее об этом запросе можно узнать в спецификации API Jay Copilot.
-
В запросе передаются заголовки
headers.
-
-
Обработка ответа:
- Если запрос успешен, возвращается результат
res. - Если запрос не успешен, выбрасывается ошибка с сообщением
">>> Error calling Jay Copilot in deleteConversation"и подробностями ошибки.
- Если запрос успешен, возвращается результат
mlp.js
function summarization(userMessage) {
var account = $env.get("MLP_ACCOUNT", "");
var model = $env.get("MLP_MODEL", "");
var token = $secrets.get("MLP_TOKEN", "");
var headers = {
"MLP-API-KEY": token,
"Content-Type": "application/json"
};
var body = {
"chat": {
"model": "gpt-4o",
"messages": [
{
"role": "system",
"content": "Предоставь краткое изложение диалога пользователя с ботом: "
},
{
"role": "user",
"content": userMessage
}
],
"temperature": 0
}
};
try {
var res = $http.post("https://caila.io/api/mlpgate/account/" + account + "/model/" + model + "/predict", { headers: headers, body: body })
return res.data.chat.choices[0].message.content
} catch (e) {
throw new Error(">>> Error calling Caila API" + JSON.stringify(e));
};
};
Функция summarization принимает один параметр userMessage, который представляет собой сообщение пользователя.
-
Получение переменных окружения и токенов:
- В переменной
accountсохраняется значение переменной окруженияMLP_ACCOUNT, полученное с помощью метода$env.get("MLP_ACCOUNT", ""). - В переменной
modelсохраняется значение переменной окруженияMLP_MODEL, полученное с помощью метода$env.get("MLP_MODEL", ""). - В переменной
tokenсохраняется значение токенаMLP_TOKEN, полученное с помощью метода$secrets.get("MLP_TOKEN", "").
к сведениюПодробнее об этих методах можно узнать в документации:
$env.get()и$secrets.get(). - В переменной
-
Определение заголовков запроса:
В переменной
headersсоздается объект, содержащий заголовкиMLP-API-KEY(с токеном) иContent-Type(с типом содержимогоapplication/json).к сведениюИнформацию о заголовках запроса можно найти на карточке сервиса, к которому будет отправляться запрос, или в спецификации API Caila.
-
Определение тела запроса:
- В переменной
bodyсоздается объект, содержащий параметры для запроса. - В объекте
chatуказывается модельmodelс значением"gpt-4o". - В объекте
messagesуказываются сообщения:- Сообщение от системы с ролью
systemи содержимым"Предоставь краткое изложение диалога пользователя с ботом: ". - Сообщение от пользователя с ролью
userи содержимымuserMessage.
- Сообщение от системы с ролью
- Устанавливается параметр
temperatureс значением0.
к сведениюФормат запроса и список параметров можно найти на карточке сервиса, к которому будет отправляться запрос.
- В переменной
-
Отправка POST-запроса:
- С помощью метода
$http.postотправляется POST-запрос на URLhttps://caila.io/api/mlpgate/account/+account+ /model/+model+/predict. - В запросе передаются заголовки
headersи тело запросаbody.
- С помощью метода
-
Обработка ответа:
- Если запрос успешен, возвращается текст ответа
res.data.chat.choices[0].message.content. - Если запрос не успешен, выбрасывается ошибка с сообщением
">>> Error calling Caila API"и подробностями ошибки.
- Если запрос успешен, возвращается текст ответа
Промт для LLM
Для работы с Jay Copilot нам нужно создать промт, который будет использоваться в функции chatLlm.
Создадим справочник dictionaries → prompt.yaml и добавим в него следующий текст:
smallTalk: |
Ты — дружелюбный и общительный виртуальный ассистент, который ведет непринужденные беседы с пользователями.
Твоя цель — поддерживать интересный и легкий разговор, задавать вопросы, чтобы лучше узнать собеседника, и делиться интересной информацией.
Ты можешь обсуждать повседневные темы, хобби, погоду, новости, фильмы, книги и многое другое.
Всегда будь вежливым, внимательным и готовым поддержать беседу.
Затем в файле requirements.sc мы импортируем этот файл и объявим переменную $prompt.
Сценарий
Оставим в файле main.sc основной сценарий бота, а импорты и объявление переменных вынесем в файл requirements.sc.
requirements.sc
require: slotfilling/slotFilling.sc
module = sys.zb-common
require: patterns.sc
module = sys.zb-common
# Импорт common.js необходим, в том числе, чтобы обращаться к контекстным переменным в JS-файлах через '$.'.
require: common.js
module = sys.zb-common
# Импорт файлов с функциями
require: scripts/copilot.js
require: scripts/mlp.js
# Импорт справочника
require: dictionaries/prompt.yaml
var = $prompt
main.sc
# Подключение файла requirements.sc.
require: requirements.sc
theme: /
state: Start
q!: $regex</start>
script:
$jsapi.startSession();
go!: /Hello
state: Hello
a: Привет! Я ботик, с которым можно поговорить. Жду твоего сообщения!
a: Если хочешь сбросить контекст беседы, то введи /reset. Для саммаризации всего диалога с ботом просто напиши /sumUp.
# Обработка сообщения пользователя, которое не соответствует ни одному из паттернов.
# Отправка сообщения в Jay Copilot при помощи функции chatLlm.
state: LlmChat
event!: noMatch
script:
var llmAnswer = chatLlm($request.query);
$reactions.answer(llmAnswer);
# Обработка команды /sumUp.
# Отправка запроса в Caila для получения краткого содержания диалога.
state: DialogSummary
q!: $regex</sumUp>
script:
var summary = summarization($jsapi.chatHistory());
$reactions.answer("Вот саммари всего диалога с ботом:");
$reactions.answer(summary);
# Обработка команды /reset.
state: Reset
q!: $regex</reset>
script:
if ($client.conversationId) {
deleteConversation($client.conversationId);
delete $client.conversationId;
}
a: Контекст беседы сброшен.
Тестирование бота
-
Запустим виджет тестирования.
-
В качестве приветствия бот отвечает:
Привет! Я ботик, с которым можно поговорить. Жду твоего сообщения!.
Если хочешь сбросить контекст беседы, то введи /reset. Для саммаризации всего диалога с ботом просто напиши /sumUp. -
Зададим вопрос, который не соответствует ни одному из паттернов. Бот отправит запрос в Jay Copilot и вернет ответ:
Вопрос:
Восхождение на Эльбрус легкое?Ответ:
Восхождение на Эльбрус считается довольно доступным для людей с хорошей физической подготовкой и минимальным опытом альпинизма. Хотя высота горы — 5642 метра — может создавать некоторые трудности, многие маршруты на Эльбрус не требуют специальных навыков.
Один из самых популярных маршрутов — это маршрут из лагеря «Приют 11», который можно пройти за один день. Тем не менее, важно учитывать такие факторы, как акклиматизация, погодные условия и правильное снаряжение.
Ты когда-нибудь задумывался о восхождении на Эльбрус или другие горы? -
Зададим еще один вопрос, который не соответствует ни одному из паттернов:
Легче восходить с северной стороны или южной?Ответ:
Восхождение на Эльбрус с южной стороны считается наиболее популярным и удобным вариантом для большинства туристов. Здесь находится более развитая инфраструктура, включая канатные дороги, которые значительно облегчают доступ к высоким лагерям. Южный маршрут часто выбирается для комфорта и безопасности.
Северная сторона также предоставляет захватывающие виды и менее многолюдная, но восхождение с этой стороны более сложное и требует изначально хороших навыков альпинизма и больше времени на акклиматизацию.
А ты предпочитаешь более «комфортные» маршруты или с радостью рискуешь ради красивых видов и меньшего количества туристов? -
Отправим команду
/sumUp. Бот отправит запрос в Caila и вернет краткое содержание диалога:Пользователь спросил о восхождении на Эльбрус. Бот объяснил, что восхождение на Эльбрус доступно для людей с хорошей физической подготовкой и минимальным опытом альпинизма, но важно учитывать акклиматизацию, погодные условия и правильное снаряжение. Пользователь уточнил, с какой стороны легче восходить, и бот объяснил, что южный маршрут более популярен и удобен, а северный — более сложный и требует больше навыков. Диалог завершился запросом пользователя на саммари.