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

Документация LLM Client

 

🔹 I. Введение и суть нового паттерна с LLMClient

🎯 Что изменилось?

Раньше мы создавали "агента" через объект Agents, устанавливали промпты через setStartInstruction, setFinalInstruction, сохраняли историю и вызывали generateActiveAgentAnswer(...). Этот подход оставался привязанным к структуре агентов как сущностей.

Теперь — всё по-другому.

👉 Вы сами создаете "агента" как сценарий или flow (поток диалога), используя LLMClient, и назначаете ему столько задач, сколько нужно — через разные этапы flow, инструкции и промпты. Каждый шаг — просто JS-код, больше не нужно регистрировать агентов отдельно.


🧠 Что такое LLMClient?

Это универсальный компонент для общения с LLM:

  • формирует список промптов (в нужном порядке),

  • берет промпты как из кода, так и из таблицы ($name, @common),

  • вставляет значения из lead и bot (макропеременные),

  • может подставить историю диалога в нужное место,

  • делает асинхронный запрос (через MBQuery),

  • обрабатывает ответ в этом же скрипте и отправляет дальше.


✅ Почему это лучше?

📌 Возможность Agents LLMClient
Многоагентные шаги (несколько запросов подряд) ⚠️ Ограничено ✅ Полностью контролируемо
Промпты из таблицы ($name / @common) ✅ Да ✅ Да
Порядок промптов ❌ Неявный (в коде разбросано) ✅ Вы задаёте руками
Управление историей ⚠️ Встроено, не выключается ✅ Можно включать, отключать, вставлять вручную
Асинхронность ✅ Да ✅ Да
Обработка ошибок 🔧 Сложно ✅ Полный контроль на каждом запросе к ИИ
Переподключение flow ⚠️ Привязано к агенту ✅ Любая логика, любой скрипт
Переиспользование компонентов ⚠️ Мало ✅ Есть LLMFollowUp, KnowbaseSearch, макросы

🔹 II. Жизненный цикл работы с LLMClient


🟦 1. Инициализация

const LLMClient = require("Common.MetabotAI.LLMClient")const llm = new LLMClient("DetectIntent") // session name

Пример подключения gpt-3.5-turbo:

llm.setModel("gpt-3.5-turbo")llm.setProvider("OpenAI")

🟦 2. Добавление промптов

llm.setPromptTable("gpt_prompts", "MyAgent")
llm.addSystemPrompt("$start") // из таблицы (по имени)
llm.addSystemPrompt("@common_instruction") // глобальный prompt

Прямо в коде:

llm.addUserPrompt(`Сообщение пользователя: ${user_input}`)llm.addUserPrompt(`Имя пользователя: ${lead.getAttr('name')}`)

🟦 3. Макросы и подстановки в таблице промптов

Можно использовать:

  • {{$lead_attr}} → из лида
  • {{@bot_attr}} → из бота

Или можно подставлять значения прямо в коде:

llm.addUserPrompt(`Город пользователя: ${lead.getAttr('city')}`)

🟦 4. История

По умолчанию история включена, но вы можете:

llm.disableHistory() // полностью отключитьllm.clearHistory() // очиститьllm.addHistoryToPrompts() // вручную подставить историю

📌 Важно!

Управляйте порядком промптов прямо в коде:
сначала — system, instruction, потом history, потом user.
Иначе GPT не поймет, что от него хотят.


🟦 5. Подготовка и отправка

llm.prepareRequest() // собираем промпты и сохраняемllm.sendRequest() // отправка асинхронного запроса

Можно указать другой скрипт, куда прилетит асинхронный ответ:

llm.prepareRequest("AgentName:MyFlow")

🟦 6. Обработка ответа

Ответ приходит в этот же скрипт, в блоке ниже:

llm.handleResponse()

📌 Не нужно проверять статус — если была ошибка, сработает скрипт обработки ошибки, устанавливаемый через setErrorScript(...), если всё ОК — данные уже подготовлены для дальнейшего использования.


🟦 7. Работа с результатом

llm.sendResponse() // показать как естьllm.sendFormattedResponse() // markdown-оформлениеllm.nextFlow("MyAgent:NextStep") // перейти на следующий скриптllm.finishFlow() // завершить текущий шаг

🔚 Завершение диалога: LLMFollowUp

Для ожидания ответа от пользователя — текстового или кнопочного — используем LLMFollowUp.

Добавьте новую команду JavaScript Callback.

const LLMFollowUp = require("Common.MetabotAI.LLMFollowUp")

🔹 Ожидание текста

LLMFollowUp.waitForInput("MyAgent:Step2")

🔹 С выводом сообщения

LLMFollowUp.waitForInput("MyAgent:Step2", "Введите ваш номер телефона:")

🔹 Подмена поля для ввода

По умолчанию, ввод от пользователя запишется в лида в user_input, но вы можете переопределить это поле (третий параметр):

LLMFollowUp.waitForInput("MyAgent:Step2", "", "my_user_input")

🔹 Ожидание текста или кнопки

  1. В скрипте добавьте нужные вам кнопки.

  2. В коде:

LLMFollowUp.waitForInputOrButton("MyAgent:Step2", "Выберите или напишите свой вопрос:")

🔹 III. Архитектура команды (скрипта)

Каждый LLM-запрос реализуется в одной JS-команде, которая состоит из 3-х логических блоков:


🟦 1. Инициализация и запуск запроса (вверху)

Это всегда первый запуск команды (isFirstImmediateCall):

const LLMClient = require("Common.MetabotAI.LLMClient")const llm = new LLMClient("MyLLMSession")if (isFirstImmediateCall) { llm.setProvider("OpenAI") llm.setModel("gpt-3.5-turbo") llm.setPromptTable("gpt_prompts", "MyAgent") llm.addSystemPrompt("$start") // инструкция llm.addUserPrompt(`Запрос пользователя: ${lead.getAttr("user_input")}`) llm.prepareRequest("MyAgent:MyScript") // указываем этот же скрипт llm.setErrorScript("MyAgent:ErrorFlow") return llm.sendRequest()}

🟦 2. Обработка ответа (ниже, после if (isFirstImmediateCall))

Ответ приходит сюда, автоматически:

llm.handleResponse()// Далее логика ответаllm.sendFormattedResponse()llm.nextFlow("MyAgent:NextStep")

📌 Если в запросе произошла ошибка, LLMClient автоматически вызовет setErrorScript(...).
Если всё хорошо — handleResponse() сохраняет результат и позволяет продолжить.


🟦 3. UI, кнопки, меню (опционально)

Кнопки настраиваются в этом же скрипте или можете перенаправить в другой скрипт, где настроить кнопки и подключить JavaScript Callback c LLMFollowUp.


📦 Пример структуры скрипта (общее):

// ВЕРХ -- запуск запросаif (isFirstImmediateCall) { // ... init return llm.sendRequest()}// ЦЕНТР -- handleResponse()llm.handleResponse()// НИЖЕ -- логика ответаllm.sendResponse()llm.nextFlow("MyAgent:NextStep") // или если нужно завершить работу с ИИ // и далее предоставить пользователю выбор llm.finishFlow()

🔹 IV. Работа с вводом пользователя и кнопками

После ответа от LLM мы часто хотим:

  • задать уточняющий вопрос пользователю,

  • дождаться текста,

  • или предложить кнопки с вариантами.

Для этого используется мощный компонент:

📦 LLMFollowUp

(из Common.MetabotAI.LLMFollowUp)


🟦 1. Ожидание только текста

const LLMFollowUp = require("Common.MetabotAI.LLMFollowUp")LLMFollowUp.waitForInput( "MyAgent:NextStep", // скрипт, куда перейти "✍️ Введите ваш номер телефона:", // текст-приглашение "user_phone" // атрибут для сохранения)

📌 После того, как пользователь напишет, значение попадет в:

lead.getAttr("user_phone")

🟦 2. Ожидание текста или кнопки

Частый сценарий: предложить выбор + оставить возможность ввести свой текст.

const LLMFollowUp = require("Common.MetabotAI.LLMFollowUp")LLMFollowUp.waitForInputOrButton( "MyAgent:HandleCustomInput", // targetScriptCode для текстового ввода "👇 Выберите вопрос или напишите свой:", "user_input")

🛎️ А кнопки добавляем в этом же скрипте в меню:

Button 1 ("Где мой заказ?", "MyAgent:Flow_OrderStatus")Button 2 ("Связаться с оператором", "MyAgent:Flow_Operator")

📎 Примечания

  • Все кнопки работают как меню, определяемое в скрипте.

  • Если пользователь нажал кнопку — мы сразу переходим по скрипту и там запускаем следующий флоу.

  • Если ввел текст — идем в targetScriptCode и берем lead.getAttr("user_input")


✅ Резюме

С помощью LLMFollowUp вы:

  • задаёте гибкие формы обратной связи,

  • даёте кнопки без необходимости их отдельно обрабатывать,

  • делаете UX максимально удобным.

Отлично! Ниже — готовый рабочий пример агента из двух flow, реализованного через LLMClient:


🤖 Агент с двумя флоу: INTENT ➜ ANSWER

Сценарий:

  1. Пользователь пишет сообщение.

  2. Первый flow (IntentDetect) распознаёт интент и (опционально) ищет в БЗ/таблице.

  3. Второй flow (FinalAnswer) формирует финальный ответ на основе результата первого шага и истории.


📁 Структура скриптов:

MyAgent/├── Start -- Точка входа├── DetectIntent -- Flow 1: извлечение намерения├── FinalAnswer -- Flow 2: генерация ответа├── ErrorFlow -- Обработка ошибок

1️⃣ Скрипт: MyAgent:Start

Запросить значение Что бы вы хотели узнать или сделать? 👇: [ user_input ]
Выполнить JavaScript // Очищаем историю диалога основного флоу const LLMClient = require("Common.MetabotAI.LLMClient") const dialog = new LLMClient("FinalAnswer") dialog.clearHistory()
Выполнить скрипт 🔹 Flow 1: Определение интента и KB поиск

2️⃣ Скрипт: MyAgent:DetectIntent

Отправить текст Думаю. Подождите... 🕐
Выполнить асинхронный API-запрос const LLMClient = require("Common.MetabotAI.LLMClient")const llm = new LLMClient("DetectIntent");llm.setProvider("OpenAI")llm.setModel("gpt-3.5-turbo")llm.setErrorScript("MyAgent:ErrorFlow")llm.setPromptTable("gpt_prompts", "SupportBot")llm.disableHistory() // Отключаем сохранение историиif (isFirstImmediateCall) { // Берем историю из следующего флоу const dialog = new LLMClient("UserDialog") const history = dialog.getHistory() llm.addSystemPrompt( `По контексту общения и сообщению пользователя, `+ `определи истинное намерение пользователя.\n` + `Контекст общения: ${JSON.stringify(history)}\n` + `Сообщение пользователя: ${lead.getAttr("user_input")}\n` + `Ответь строго в JSON в поле intent в развернутом виде на русском языке.`) llm.prepareRequest() return llm.sendRequest()}llm.handleResponse() // Обрабатываем ответ от LLM bot.sendMessage(`Ответ от LLM: ${llm.getResponseText()}`)// Получаем JSON и излекаем поле intentconst result = llm.getLastJSON()const userIntent = result?.intentlead.setAttr("user_intent", userIntent)// Если интент есть -- ищем по базе знанийif (userIntent) { const KnowbaseSearch = require('Common.MetabotAI.KnowbaseSearch') const kbSearch = new KnowbaseSearch() const chunks = kbSearch.findBestChunks("DefaultKB", userIntent, "rehau") // Сохраняем все, что нашли в kb_chunks lead.setJsonAttr("kb_chunks", chunks) bot.sendMessage(`chunks: ${JSON.stringify(chunks)}`)} else { lead.setJsonAttr("kb_chunks", []) }// Переходим к следующему этапуllm.nextFlow("MyAgent:FinalAnswer")

3️⃣ Скрипт: MyAgent:FinalAnswer

Выполнить асинхронный API-запрос const LLMClient = require("Common.MetabotAI.LLMClient")const llm = new LLMClient("FinalAnswer")llm.setProvider("OpenAI")llm.setModel("gpt-3.5-turbo")llm.setErrorScript("MyAgent:ErrorFlow")llm.setPromptTable("gpt_prompts", "SupportBot")if (isFirstImmediateCall) { llm.addSystemPrompt(`Ты должен ответить пользователю, используя найденную информацию из базы знаний.`) llm.addSystemPrompt( `Если в базе знаний будут найдены ссылки на медиа контент, `+ `определи релевантность контента, проанализировав их описание, и включи релевантные ссылки в формате markdown. ` + `Сам ссылки не придумывай!`) llm.addSystemPrompt(`Информация из базы: {{$kb_chunks}}`) llm.addSystemPrompt(`Распознаннный интент: {{$user_intent}}`) llm.addHistoryToPrompts() // Добавляем историю llm.addUserPrompt(`{{$user_input}}`) llm.addSystemPrompt(`$UserDialog:final`) // Промпт агента из таблицы llm.addSystemPrompt(`@общий`) // Общий промпт из таблицы llm.prepareRequest() return llm.sendRequest()}llm.handleResponse()llm.sendFormattedResponse()return llm.finishFlow() // Завершаем
Выполнить JavaScript Callback const LLMFollowUp = require("Common.MetabotAI.LLMFollowUp")// Просто ожидание ввода от пользователя без сообщенияLLMFollowUp.waitForInput("MyAgent:DetectIntent")// СЦЕНАРИЙ 2: Ожидание ввода + кнопки меню//LLMFollowUp.waitForInputOrButton("SupportBot:DetectIntentAndSearch")// СЦЕНАРИЙ 3: Ввод + кнопки + своё сообщение //LLMFollowUp.waitForInputOrButton(// "SupportBot:DetectIntentAndSearch",// "✍️ Можете задать новый вопрос или выбрать из меню:"//)// СЦЕНАРИЙ 4: Используем другую переменную вместо user_input//LLMFollowUp.waitForInputOrButton(// "SupportBot:DetectIntentAndSearch",// "Выберите вариант или напишите свой вопрос:",// "my_user_input"//)

🛑 Скрипт: MyAgent:ErrorFlow

Выполнить асинхронный API-запрос const LLMClient = require("Common.MetabotAI.LLMClient");const llm = new LLMClient("DetectIntent");// Можно дополнительно отправить системное сообщениеbot.sendMessage("⚠️ Произошла ошибка при обращении к ИИ. Давайте попробуем ещё раз!");// Очищаем временные данные (если нужно)lead.setAttr("user_intent", "");lead.setJsonAttr("intent_payload", {});lead.setJsonAttr("kb_chunks", []);// Переходим к начальному этапуllm.nextFlow("SupportBot:DetectIntent")

🧠 Почему история не в первом флоу?

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

📋 Промпты в таблице gpt_prompts

Промпты вы может указать, как прямо в коде, как в примере выше, так и в таблице, например так:

agent_name name prompt
MyAgent start Ты ассистент техподдержки. Помогай пользователям ясно.
MyAgent intent Определи намерение пользователя в JSON: {intent, topic}.

Отлично! Мы уже прошли:

  1. Введение

  2. Жизненный цикл LLMClient

  3. Архитектуру команды

  4. Ввод пользователя и кнопки

  5. Пример из двух flow ✅


👉 Следующим по плану был:

🔹 V. Замена агентов: теперь всё делается через LLMClient


🧠 Раньше:

Чтобы использовать GPT, приходилось:

  • явно создавать "агента" через Agents()

  • регистрировать имя, провайдера, параметры

  • использовать методы вроде generateActiveAgentAnswer(...)

  • вызывать агентов из конкретных скриптов по имени

  • копить историю автоматически

Это был плотно связанный black-box, не всегда удобный для многошаговых диалогов.


✅ Теперь:

Агент — это просто набор флоу-сценариев, реализованных на LLMClient.

Вы управляете:

  • моделью (gpt-3.5-turbo, gpt-4, и др.)

  • провайдером (OpenAI, GigaChat, и т.п.)

  • промптами (из кода или из таблицы)

  • структурой диалога (разбиваете на флоу)

  • когда и где включать/выключать историю

  • форматированием, ошибками, кнопками, интерактивом

📌 А "агент" — это просто имя сессии и структура ваших флоу.


💡 Пример:

const llm = new LLMClient("SalesAssistant")

// Это и есть "агент", но он нигде не регистрируется

// Вы просто пишете логику под это имя

Вы можете в одном проекте создать десятки таких "агентов", не создавая ни одной сущности в базе.


🛠 Агент = LLMClient + промпты + flow-сценарии

Вы теперь сами:

  • назначаете, какие шаги делает LLM,

  • подсказываете ему через $prompts,

  • вставляете куски истории (или не вставляете),

  • вызываете API, ищете в таблицах, всё вручную комбинируется.

Отлично, поехали 👇


🔹 VI. Варианты источников данных: Knowbase / Таблицы / API

Когда вы строите flow на LLMClient, вы можете дополнительно обогатить ответы LLM:

  • фрагментами из базы знаний (RAG),

  • результатами поиска в таблице,

  • ответами из внешних API,

  • вообще любой своей логикой.

👉 Всё, что вам нужно — вставить результат в system или user prompt.


🧠 1. Поиск в базе знаний (Knowbase)

Если у вас уже загружена база знаний (Knowbase), можно:

const KnowbaseSearch = require("Common.MetabotAI.KnowbaseSearch")const search = new KnowbaseSearch()const chunks = search.findBestChunks("MyKnowledgeBase", lead.getAttr("user_input"), "domain")const result = chunks.map(c => c.content).join("\n\n")llm.addSystemPrompt(`Вот полезная информация из базы знаний:\n\n${result}`)

📌 Не забудь: база знаний должна быть создана и векторизована заранее, например через knowbaseCreate(...).


📦 Альтернатива базе знаний:

Ты не обязан использовать именно Knowbase. Можно:


📊 2. Поиск в таблице (любая логика)

const items = table.find("product_catalog", ["title", "desc", "price"], [["category", "=", "laptop"]])const top3 = items.slice(0, 3)let txt = top3.map(p => `• ${p.title} -- ${p.price}₽\n${p.desc}`).join("\n\n")llm.addSystemPrompt(`Вот найденные товары:\n\n${txt}`)

🌐 3. Обращение к API

| const city = lead.getAttr("user_city")const url = `https://my-api/weather?city=${encodeURIComponent(city)}\`const response = api.getJson(url)const weather = response?.forecast || "Погода недоступна"lead.setAttr("weather_info", weather) |
| :---- |

А затем подставить результат как промпт

llm.addUserPrompt(`Вот погода для пользователя: ${lead.getAttr("weather_info")}`)

🧠 Суть:

Все эти данные (таблицы, API, поиск) не обрабатываются автоматически — вы сами решаете:

  • где выполнить логику,

  • как вставить результат в prompt,

  • стоит ли сохранять это в lead для следующих шагов,

  • нужно ли показывать пользователю или использовать только для генерации.

🔹 VIII. Статусы жизненного цикла LLMClient

Взято напрямую из исходника Common.MetabotAI.LLMClient, в том числе с точным моментом установки каждого статуса.


🧭 Таблица: жизненный цикл и статусы LLMClient

Статус Устанавливается после Что означает
IDLE new LLMClient(...) Начальное состояние. Клиент создан, но ничего ещё не сделано.
PREPARED prepareRequest() Промпты собраны, request готов к отправке.
WAITING sendRequest() Запрос ушёл, ожидаем ответ от провайдера.
SUCCESS handleResponse() (200) Ответ успешно получен, результат готов для логики.
ERROR handleResponse() (≠200) Произошла ошибка при получении ответа.
ERROR_HANDLED handleResponse() → fallback Обработчик ошибки сработал (если задан через setErrorScript).
DONE (опционально вручную) Не используется напрямую — можно установить в логике вручную.

🛠 Как посмотреть текущий статус?

llm.getStatus() // вернёт текущий статус, например: "WAITING"

✅ Где он хранится?

lead.getAttr("llm_<sessionName>_status")

🔹 IX. Переменные и атрибуты для отладки в LLMClient

Компонент LLMClient автоматически сохраняет множество атрибутов в lead, чтобы вы могли отлаживать поведение прямо внутри скрипта.

🧪 Таблица: отладочные переменные LLMClient

Переменная Пример значения Что хранит / зачем нужна
llm_<session>_status WAITING Текущий статус запроса
llm_<session>_provider OpenAI Название LLM-провайдера
llm_<session>_params { model: "gpt-3.5-turbo", temperature: 0.7 } Параметры модели
llm_<session>_messages [{"role": "system", ...}, ...] Все промпты, отправленные в запрос
llm_<session>_history [{"role": "user", ...}, ...] История переписки (если включена)
llm_<session>_callback_script MyAgent:NextStep Скрипт, куда вернётся ответ от LLM
llm_<session>_error_script MyAgent:Fallback Скрипт, куда уйдёт в случае ошибки
llm_<session>_raw_response Вот ваш ответ... Текст, возвращённый моделью, до форматирования
llm_<session>_user_query Сколько лет космосу? Последний пользовательский запрос (если использован addUserPrompt)

🔹 X. Таблица методов LLMClient

📦 Версия: Common.MetabotAI.LLMClient v2.0


📌 🔧 Базовые методы управления

Метод Назначение
new LLMClient(session) Создание клиента. session — уникальное имя для группы запросов.
setModel(model) Установить модель (например "gpt-3.5-turbo")
setProvider(provider) Установить провайдера ("OpenAI", "GigaChat", и т.д.)
setModelParams(params) Установить параметры модели (temperature, max_tokens, и т.д.)

🧠 Работа с промптами

Метод Назначение
addSystemPrompt(text) Добавить системный промпт (не сохраняется в историю по умолчанию)
addUserPrompt(text) Добавить промпт от пользователя (сохраняется в историю)
addAssistantPrompt(text) Добавить промпт от ассистента (опционально в историю)
setPromptTable(table, name) Назначить таблицу и имя агента для $prompt / @common
addHistoryToPrompts() Добавить сохранённую историю в промпты
clearPrompts() Очистить текущий список промптов

🕓 Управление историей

Метод Назначение
disableHistory() Отключить накопление истории
clearHistory() Очистить историю
addToHistory(msg) Добавить сообщение в историю вручную
getHistory() Получить сохранённую историю

🚀 Запуск запроса

Метод Назначение
prepareRequest([script]) Сохранить промпты и задать callback-скрипт
sendRequest() Отправить асинхронный запрос к LLM через MBQuery
setErrorScript(script) Назначить fallback-скрипт при ошибке

📥 Ответ и логика

Метод Назначение
handleResponse() Обработка ответа от LLM (в этом же скрипте)
getResponseText() Извлечь текст ответа
getResponseResult() Получить весь JSON-ответ
getLastJSON([text]) Извлечь JSON из текста ответа (если там JSON)
extractTextWithoutJSON() Удалить JSON из текста и вернуть только чистый текст

📦 Постобработка и переход

Метод Назначение
sendResponse() Отправить текст ответа пользователю
sendFormattedResponse() Отправить форматированный markdown-ответ
nextFlow("ScriptName") Перейти к следующему скрипту
finishFlow() Завершить текущий flow

🧩 Внутренние утилиты (для макросов и отладки)

Метод Назначение
_applyMacros(str) Подставить макропеременные {{$lead_attr}}, {{@bot_attr}} и т.д.
setStatus(status) Установить статус (WAITING, SUCCESS, и т.д.)
getStatus() Получить текущий статус