Бот с обращением к языковым моделям
В этом туториале показано, как создать бота с обращением к языковым моделям, который:
- поддерживает беседу на любые темы, используя 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 и вернет краткое содержание диалога:Пользователь спросил о восхождении на Эльбрус. Бот объяснил, что восхождение на Эльбрус доступно для людей с хорошей физической подготовкой и минимальным опытом альпинизма, но важно учитывать акклиматизацию, погодные условия и правильное снаряжение. Пользователь уточнил, с какой стороны легче восходить, и бот объяснил, что южный маршрут более популярен и удобен, а северный — более сложный и требует больше навыков. Диалог завершился запросом пользователя на саммари.