Перейти к основному содержимому

Бот с обращением к языковым моделям

В этом туториале показано, как создать бота с обращением к языковым моделям, который:

  • поддерживает беседу на любые темы, используя API Jay Copilot.
  • отправляет историю диалога в сервис Caila для создания краткого содержания.

Подробнее об этих продуктах можно узнать в документации: Jay Copilot и Caila.

Подготовка

Перед началом работы нам необходимо получить ключи доступа к Jay Copilot и Caila и указать их в качестве переменных в JAICP:

  1. Получите доступ к API Jay Copilot. Для этого отправьте запрос на адрес genai@just-ai.com.

  2. Сгенерируйте ключ для работы API Jay Copilot. Инструкцию по выпуску ключа можно найти в документации Jay Copilot.

  3. Скопируйте ключ и сохраните его в токен COPILOT_TOKEN в разделе Токены и переменныеТокены в JAICP.

  4. Сгенерируйте токен для работы с Caila.

  5. Скопируйте токен и сохраните его в токен MLP_TOKEN в разделе Токены и переменныеТокены в JAICP.

В сценарии мы будем использовать обращения к сервису openai-proxy, который размещается в Caila. Для этого нам нужно указать переменные окружения MLP_ACCOUNT и MLP_MODEL:

  1. Перейдите на страницу сервиса openai-proxy в Caila.

  2. В адресной строке будут указаны аккаунт-владельца сервиса и название сервиса:

    https://caila.io/catalog/just-ai/openai-proxy,

    где just-ai — аккаунт-владелец, а openai-proxy — название сервиса. Эти значения и будут значениями переменных окружения MLP_ACCOUNT и MLP_MODEL соответственно.

  3. Сохраните значения в разделе Токены и переменныеПеременные среды в 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 принимает один параметр 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-запрос на URL https://app.jaycopilot.com/api/appsAdapter/conversations/

      Подробнее об этом запросе можно узнать в спецификации API Jay Copilot.

    • В запросе передаются заголовки headers и тело запроса body.

  • Обработка ответа:

    • Если запрос успешен, возвращается идентификатор разговора res.data.id.
    • Если запрос не успешен, выбрасывается ошибка с сообщением ">>> Error calling Jay Copilot API in initConversation" и подробностями ошибки.

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-запрос на URL https://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. Создадим справочник dictionariesprompt.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: Контекст беседы сброшен.

Тестирование бота

  1. Запустим виджет тестирования.

  2. В качестве приветствия бот отвечает:

    Привет! Я ботик, с которым можно поговорить. Жду твоего сообщения!.

    Если хочешь сбросить контекст беседы, то введи /reset. Для саммаризации всего диалога с ботом просто напиши /sumUp.
  3. Зададим вопрос, который не соответствует ни одному из паттернов. Бот отправит запрос в Jay Copilot и вернет ответ:

    Вопрос:

    Восхождение на Эльбрус легкое?

    Ответ:

    Восхождение на Эльбрус считается довольно доступным для людей с хорошей физической подготовкой и минимальным опытом альпинизма. Хотя высота горы — 5642 метра — может создавать некоторые трудности, многие маршруты на Эльбрус не требуют специальных навыков. 

    Один из самых популярных маршрутов — это маршрут из лагеря «Приют 11», который можно пройти за один день. Тем не менее, важно учитывать такие факторы, как акклиматизация, погодные условия и правильное снаряжение.

    Ты когда-нибудь задумывался о восхождении на Эльбрус или другие горы?
  4. Зададим еще один вопрос, который не соответствует ни одному из паттернов:

    Легче восходить с северной стороны или южной?

    Ответ:

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

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

    А ты предпочитаешь более «комфортные» маршруты или с радостью рискуешь ради красивых видов и меньшего количества туристов?
  5. Отправим команду /sumUp. Бот отправит запрос в Caila и вернет краткое содержание диалога:

    Пользователь спросил о восхождении на Эльбрус. Бот объяснил, что восхождение на Эльбрус доступно для людей с хорошей физической подготовкой и минимальным опытом альпинизма, но важно учитывать акклиматизацию, погодные условия и правильное снаряжение. Пользователь уточнил, с какой стороны легче восходить, и бот объяснил, что южный маршрут более популярен и удобен, а северный — более сложный и требует больше навыков. Диалог завершился запросом пользователя на саммари.