Документация и уроки (старая версия)

Ядро системы - Маршрутизатор и RAG

Создание основы мультиагентной системы

Архитектура системы

В основе нашей мультиагентной системы лежит Маршрутизатор - это "диспетчер", который анализирует вопрос пользователя и решает, какой именно агент должен его обработать. По умолчанию система использует RAG для поиска информации в базе знаний.

svgviewer-output.png

svgviewer-output (1).png

Каждый агент выполняет свою специализированную задачу:

  1. CompanyInfo - отвечает на вопросы о компании
  2. SQL-агент - работает с базами данных

Часть 1: Установка и настройка основного агента

Шаг 1: Импорт готового решения

Мы подготовили готовую конфигурацию, которую можно быстро развернуть:

  1. Перейдите по предоставленной ссылке для скачивания конфигурации
  2. Скопируйте JSON-код конфигурации
  3. В интерфейсе Метабот откройте раздел "Импорт бизнеса/ботов"
  4. Вставьте скопированный JSON и нажмите "Импорт"

Экспорт бота.png


Импорт бота 2.png

Шаг 2: Проверка установленных компонентов

После успешного импорта в вашем боте должны появиться следующие элементы:

1. Системный раздел MRAG Это ядро системы, которое обрабатывает запросы и управляет агентами.

Pasted image 20250904155253.png

2. Базовые таблицы данных:

Pasted image 20250905110051.png

Pasted image 20250904155359.png

3. Плагин управления "AgentsParams" Центр управления всеми настройками агентов.

Pasted image 20250904155709.png

Шаг 3: Настройка конфигурации агентов

Мы создали систему конфигураций, которая позволяет управлять агентами без глубокого понимания кода. Все настройки вынесены в понятные конфигурационные файлы.

Совет для работы: В конфигурации есть множество параметров. Используйте поиск по документации, чтобы быстро найти описание нужного параметра и понять, как он работает.

Основные поля конфигурации:
let activeAgent = lead.getAttr("activeAgent") // Устанавливается при вызове скрипта
let agentCFG = {} // Объект с настройками текущего агента

if (activeAgent === "MainFlow") {
  agentCFG = {
    common: {
      title: "Основной Flow c маршрутизатором", // Понятное название агента
      agentName: "MainFlow", // Техническое имя для системы
      promptTable: "gpt_prompts", // Таблица с промптами
      userQueryAttibName: "user_query", // Атрибут для вопроса пользователя
      historyMaxLength: 4, // Сколько сообщений помнить в истории
      exitScript: "KB:FollowUp", // Что выполнить после завершения диалога
    },
    
    detectRoute: { // Настройки маршрутизатора
      provider: "OpenAI",
      model: "gpt-4o",
      modelParams: {
        "temperature": 1
      },
      prompt: "$route_prompt", // Промпт для определения маршрута
      routerTools: [{
        tools: "RAG_Tools", // Набор доступных инструментов
        route: "RAG:DetectIntent_and_FindChunks", // Скрипт для RAG
      }],
      errorScript: "RAG:ErrorFallback", // Обработка ошибок
    },
    
    detectIntent: { // Детектор намерений пользователя
      provider: "OpenAI",
      model: "gpt-4o",
      modelParams: {
        "temperature": 1
      },
      prompt: "$rag_intent_prompt", // Промпт для анализа намерений
      errorScript: "RAG:ErrorFallback",
      kbName: "defKnowBase", // База знаний
      kbDomain: "main", // Домен знаний
    },
    
    userReply: { // Генератор ответов пользователю
      provider: "OpenAI",
      model: "gpt-4o",
      modelParams: {
        "temperature": 1
      },
      errorScript: "RAG:ErrorFallback",
      useHistory: 1, // Использовать контекст диалога
      addUserQuery: 1, // Добавлять вопрос в промпт
      sendBotAnwser: 1, // Отправлять ответ пользователю
      systemPrompts: { // Структура промптов
        start: [["$start_prompt", "$rag_prompt"]], // Начальные промпты
        final: ["$final_prompt"] // Финальные промпты
        // Между start и final автоматически вставляется история диалога
      },
    }
  }
} else if (activeAgent === "newAgent") { 
  // Здесь описываются конфигурации других агентов
  // Поля могут отличаться в зависимости от специализации агента
} else {
  bot.sendMessage(`MainConfig:snippet - неизвестный activeAgent: ${activeAgent}`)
  bot.stop()
}

Шаг 4: Настройка таблицы промптов

Промпты - это инструкции для ИИ. Они определяют, как агент будет вести себя и отвечать на вопросы.

Процедура настройки:

  1. Откройте таблицу gpt_prompts в интерфейсе Метабот
  2. Создайте промпт для каждого агента с уникальным именем
  3. Заполните три обязательных поля:
    • agent_name - имя агента (например: "MainFlow")
    • name - название промпта (например: "route_prompt")
    • prompt - содержание промпта с макропеременными

Пример настройки промпта маршрутизатора в таблице:

agent_name: MainFlow
name: route_prompt
prompt: Ты агент ИИ который отвечает на вопросы о компании {{@company_name}}. 

У тебя есть инструменты в распоряжении:
<tools>
**RAG_Tools** - Используется, когда нужно ответить на вопросы об услугах 
и продукции компании, об условиях эксплуатации продукции, об используемых 
инструментах для монтажа продукции и т.д.
</tools>

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

История чата:
"""{{$chat_history_str}}"""

Запрос пользователя:
"""{{$user_query}}"""

Пожалуйста, отвечай как можно точнее и всегда обязательно указывай инструмент (Tools). 
Ответ всегда должен быть представлен в виде JSON-объекта, в котором выводятся 
указанные ниже поля. JSON в самом конце! Без пояснений.

{
  "tools": "",  string: название выбранных вами инструментов 
  "user_intent": "",  string: подробное намерение пользователя для системы RAG 
  "request": ""  "полученный вами запрос" 
}
Система макросов в промптах

Для динамической подстановки данных используйте макросы:

Данные из лида: {{$lead_attr}} - любой атрибут лида
Данные из бота: {{@bot_attr}} - любой атрибут бота

Системные макросы:

Часть 2: Настройка API-доступа

Шаг 5: Создание API-пользователя

Для работы мультиагентной системы необходимо настроить API-доступ:

  1. В настройках бизнеса создайте нового пользователя
  2. Установите галочку "Пользователь API"
  3. Предоставьте доступ к нужным ботам (всем или выбранным)
  4. Назначьте соответствующую роль доступа

Pasted image 20250904162224.png

Генерация токена доступа:

  1. Создайте API-клиента для пользователя
  2. Сгенерируйте токен доступа
  3. Важно: Сохраните токен - он потребуется для подключения к API-шлюзу
  4. Обязательно: Используйте токен в режиме 3

Pasted image 20250904162220.png

Pasted image 20250904162212.png

Шаг 6: Регистрация в API-шлюзе

API-шлюз обеспечивает асинхронное взаимодействие между компонентами системы.

  1. Обратитесь в поддержку. Сейчас регистрация производится специалистами Метабот. 
  2. Сообщите им параметры регистрации:
    • bot_id - идентификатор вашего бота
    • token - токен API, полученный на предыдущем шаге
    • domain - доменное имя сервера, где размещен бот. Например https://app.metabot24.com

Часть 3: Запуск и тестирование

Шаг 7: Создание пользовательского интерфейса

Создайте скрипт, который позволит пользователям взаимодействовать с системой:

Pasted image 20250904161316.png

Ключевые параметры запуска:

  1. MainFlow - выбор основного агента для обработки запроса
  2. user_query - атрибут для сохранения вопроса пользователя
  3. Flow 0 - запуск скрипта маршрутизатора

Результат работы системы

При правильной настройке ваша мультиагентная система будет:

Система готова к работе и может быть расширена дополнительными агентами в зависимости от ваших потребностей.

Создание простого агента

Введение

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

В этом разделе мы создадим агента CompanyInfo, который будет отвечать исключительно на вопросы о компании - контакты, адреса офисов, время работы. Особенность этого агента в том, что он не обращается к RAG базе знаний, а хранит всю информацию прямо в промпте. Это упрощает работу модели и ускоряет получение ответов.


Этап 1: Подготовка информации о компании

Создание промпта с данными компании

Первым делом подготовим базовый промпт с информацией о компании. Назовем его about_prompt:

_**ЦЕНТРАЛЬНЫЙ ОФИС**_

**Контакты**  
Адрес: 123456, г. Примерск, ул. Центральная, дом 10А  
Горячая линия: 8 800 000 00 00 (бесплатно по России)  
Часы работы: пн-вс с 9:00 до 21:00

**Отдел качества:** +7 (900) 123-45-67  
Часы работы: пн-вс с 9:00 до 21:00

**Интернет-банк:** 8 804 000 11 11

**Главный офис обслуживания, пос. Банковский**  
Адрес: 140000, Примерская область, г. Финансск, д. Кредитное,  
ул. Сберегательная, д.1, стр. 1  
Телефон: +7 (900) 765-43-21  
Часы работы: пн-пт c 9:00 до 18:00

**Склад документов «ПримерБанк»**  
Адрес: Россия, 140001, Примерская область, рп. Храновск,  
ул. Архивная, д. 5  
Телефон: +7 (900) 111-22-33  
Часы работы: пн-чт 9.00-18.00, пт 9.00-16.30  
Подробнее: https://primerbank.ru/contacts

---

_**ОФИСЫ В РЕГИОНАХ**_

**Воронеж**  
394000, г. Примероград, ул. Финансовая, 8, оф. 101  
Тел.: +7 (902) 234-56-78

**Екатеринбург**  
620000, г. Примерск-Урал, ул. Сибирская, 22, оф. 5  
Тел.: +7 (903) 345-67-89

**Краснодар**  
350000, г. Примеродар, ул. Южная, 50  
Тел.: +7 (904) 456-78-90

Добавление промпта в систему

Откройте таблицу gpt_prompts и создайте новую запись:


Этап 2: Интеграция агента в систему

Для подключения нового агента нужно выполнить три шага в строгом порядке:

  1. Конфигурация - описать параметры агента
  2. Скрипт - создать логику обработки
  3. Маршрутизация - добавить в систему выбора агентов

Шаг 1: Обновление конфигурации

Откройте плагин AgentsParams и найдите секцию с основным агентом. Нам нужно:

А) Добавить новый инструмент в маршрутизатор

В секции detectRoute найдите routerTools и дополните массив:

detectRoute: {
    // ... остальные параметры остаются без изменений
    routerTools: [{
        tools: "RAG_Tools", // Существующий инструмент
        route: "RAG:DetectIntent_and_FindChunks"
    }, {
        tools: "INFO_Tools", // Новый инструмент для информации о компании
        route: "CompanyInfo" // Имя скрипта, который создадим далее
    }],
    // ... остальные параметры остаются без изменений
}

Б) Создать конфигурацию для нового агента

Добавьте новое условие после основного блока MainFlow:

else if (activeAgent === "CompanyInfo") { // Агент для работы с информацией о компании
    agentCFG = {
        common: {
            title: "About Agent", // Понятное имя агента
            agentName: "CompanyInfo", // Техническое имя
            promptTable: "gpt_prompts", // Таблица с промптами
            userQueryAttibName: "user_query", // Атрибут с вопросом пользователя
            historyMaxLength: 4, // Длина истории диалога
            exitScript: "MainFlow:FollowUp" // Скрипт завершения
        },
        userReply: { // Настройки генерации ответов
            provider: "OpenAI",
            model: "gpt-4o",
            modelParams: {
                "temperature": 1
            },
            errorScript: "RAG:ErrorFallback", // Обработка ошибок
            useHistory: 1, // Использовать историю диалога
            addUserQuery: 1, // Добавлять вопрос пользователя
            sendBotAnwser: 1, // Отправлять ответ пользователю
            systemPrompts: {
                start: ["$about_prompt"], // Промпт с информацией о компании
                final: [] // Финальные промпты (пустой массив)
                // Структура: about_prompt + история диалога
            },
        }
    }
}

Шаг 2: Создание скрипта агента

Создайте новый скрипт с именем CompanyInfo (как указано в конфигурации выше):

// Устанавливаем активного агента
lead.setAttr('activeAgent', 'CompanyInfo')

// Загружаем конфигурацию агента
snippet("Business.AgentsParams.MainConfig")

// Подключаем библиотеку для работы с ИИ
const LLMClient = require("Common.MetabotAI.LLMClient")

// Создаем сессию для генерации ответа
const llm = new LLMClient("UserReply", agentCFG.common.agentName)

// Передаем вопрос пользователя в обработку
llm.addUserQuery(lead.getAttr(agentCFG.common.userQueryAttibName))

// Запускаем генерацию и отправку ответа пользователю
llm.nextFlow("RAG:UserReply")

Как работает этот скрипт:

  1. Устанавливает режим работы с агентом CompanyInfo
  2. Загружает его настройки из конфигурации
  3. Берет вопрос пользователя и передает в ИИ
  4. Генерирует ответ на основе промпта с информацией о компании
  5. Запускает скрипт UserReply для отправки ответа пользователю

Шаг 3: Обновление системы маршрутизации

Откройте таблицу gpt_prompts, найдите промпт route_prompt для агента MainFlow и обновите секцию с инструментами:

<tools>
**RAG_Tools** - Используется, когда нужно ответить на вопросы об услугах 
и продукции компании, об условиях эксплуатации продукции, об используемых 
инструментах для монтажа продукции и т.д.

**INFO_Tools** - Используется когда пользователь хочет узнать о филиалах 
компании, контактных данных или времени их работы
</tools>

Этап 3: Тестирование и результат

Запуск системы

После внесения всех изменений запустите бота так же, как в основном примере. Система теперь автоматически:

  1. Анализирует вопрос пользователя
  2. Определяет подходящий инструмент (RAG_Tools или INFO_Tools)
  3. Направляет запрос соответствующему агенту
  4. Получает точный ответ из подготовленной информации

Примеры работы

Вопросы для CompanyInfo агента:

Вопросы для RAG агента:

Преимущества такого подхода

  1. Скорость ответа - информация о компании доступна мгновенно
  2. Точность - данные всегда актуальные и не зависят от поиска в базе знаний
  3. Простота поддержки - легко обновить информацию в одном промпте
  4. Масштабируемость - можно добавить любое количество таких агентов

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

Документация по LLMClient

Как работает

LLMClient реализует ключевой паттерн Metabot Agent System (MAS) — фреймворк для построения мультиагентных систем через композицию простых, понятных блоков кода.

MAS следует принципу "сложное через простое":

 


Методы класса LLMClient

new LLMClient(sessionName, agentName = "", apiFormat = "")

Создаёт новый экземпляр клиента для работы с LLM.

Параметры

Имя Тип Описание
sessionName String Уникальное имя сессии
agentName String Имя агента (опционально)
apiFormat String Формат API (опционально)

Возвращает
LLMClient — экземпляр клиента.


setPromptTable(tableName, agentName)

Устанавливает таблицу промптов и имя агента.

Параметры

Имя Тип Описание
tableName String Имя таблицы промптов
agentName String Имя агента

addUserQuery(content)

Сохраняет пользовательский запрос для текущей сессии.

Параметры

Имя Тип Описание
content String Пользовательский запрос

getUserQuery()

Получает последний пользовательский запрос.

Параметры
Нет

Возвращает
String — последний пользовательский запрос.


addPrompt(role, content)

Добавляет промпт с указанной ролью.

Параметры

Имя Тип Описание
role String Роль: 'system', 'user', 'assistant'
content String/Array Текст или массив строк

addRawPrompts(prompts)

Добавляет массив промптов без обработки.

Параметры

Имя Тип Описание
prompts Array Массив объектов {role, content}

addSystemPrompt(content)

Добавляет системный промпт.

Параметры

Имя Тип Описание
content String Системный промпт

addUserPrompt(content)

Добавляет пользовательский промпт.

Параметры

Имя Тип Описание
content String Пользовательский промпт

addAssistantPrompt(content)

Добавляет промпт ассистента.

Параметры

Имя Тип Описание
content String Промпт ассистента

addHistoryToPrompts(historyOverride = null)

Добавляет историю сообщений к промптам.

Параметры

Имя Тип Описание
historyOverride Array/Null Массив сообщений или null

clearPrompts()

Очищает все промпты текущей сессии.

Параметры
Нет


setHistoryMaxLength(lmax)

Устанавливает максимальную длину истории сообщений.

Параметры

Имя Тип Описание
lmax Number Максимальная длина истории

setHistory(messages)

Сохраняет историю сообщений.

Параметры

Имя Тип Описание
messages Array Массив объектов {role, content}

getHistory()

Получает историю сообщений.

Параметры
Нет

Возвращает
Array — история сообщений.


addToHistory(message)

Добавляет сообщение в историю.

Параметры

Имя Тип Описание
message Object Объект {role, content}

disableHistory()

Отключает сохранение истории сообщений.

Параметры
Нет


clearHistory()

Очищает историю сообщений.

Параметры
Нет


getHistoryStr()

Получает историю сообщений в виде строки.

Параметры
Нет

Возвращает
String — история сообщений.


setModel(model)

Устанавливает модель для генерации ответа.

Параметры

Имя Тип Описание
model String Название модели

setProvider(providerName)

Устанавливает провайдера LLM.

Параметры

Имя Тип Описание
providerName String Название провайдера

getProvider()

Получает текущего провайдера.

Параметры
Нет

Возвращает
String — название провайдера.


setApiFormat(apiFormat)

Устанавливает формат API.

Параметры

Имя Тип Описание
apiFormat String Формат API

setModelParams(params)

Устанавливает параметры модели.

Параметры

Имя Тип Описание
params Object Объект с параметрами модели

setTemperature(temp)

Устанавливает температуру генерации.

Параметры

Имя Тип Описание
temp Number Температура генерации

getParams()

Получает параметры модели.

Параметры
Нет

Возвращает
Object — параметры модели.


getMessages()

Получает промпты запроса.

Параметры
Нет

Возвращает
Array — промпты запроса.


deleteMessages()

Удаляет промпты запроса.

Параметры
Нет


prepareRequest(callbackScript = null)

Сохраняет промпты и задаёт callback-скрипт.

Параметры

Имя Тип Описание
callbackScript String Код скрипта для обработки ответа (опц.)

sendRequest()

Отправляет асинхронный запрос к LLM.

Параметры
Нет

Пример

const llm = new LLMClient("DetectIntent")
llm.setModel("gpt-3.5-turbo")
llm.setProvider("OpenAI")
llm.setPromptTable("gpt_prompts", "MyAgent")
llm.addSystemPrompt("$start")
llm.addUserPrompt(`Запрос пользователя: ${lead.getAttr("user_input")}`)
llm.prepareRequest("MyAgent:MyScript")
return llm.sendRequest()

handleResponse()

Обрабатывает ответ от LLM, сохраняет результат и историю.

Параметры
Нет


setErrorScript(scriptCode)

Назначает fallback-скрипт при ошибке.

Параметры

Имя Тип Описание
scriptCode String Код скрипта для обработки ошибки

getErrorScript()

Получает fallback-скрипт.

Параметры
Нет

Возвращает
String — код fallback-скрипта.


setFallbackConfig(scriptCode, timeout)

Настраивает резервный сценарий и таймаут.

Параметры

Имя Тип Описание
scriptCode String Код резервного скрипта
timeout Number Таймаут в секундах

scheduleFallback()

Запланировать отложенный запуск fallback-скрипта.

Параметры
Нет


unscheduleFallback()

Удалить отложенный запуск fallback-скрипта.

Параметры
Нет


getResponseResult()

Получает результат запроса (объект ответа провайдера).

Параметры
Нет

Возвращает
Object — объект ответа провайдера.


getProviderResult()

Получает "сырые" данные от провайдера.

Параметры
Нет

Возвращает
Object — исходные данные от провайдера.


getResponseText()

Извлекает текст ответа из результата.

Параметры
Нет

Возвращает
String — текст ответа.


extractAnswerForModel(content)

Извлекает ответ по формату модели.

Параметры

Имя Тип Описание
content Object Объект ответа провайдера

getModelContextSize(modelName = null)

Получает максимальный размер контекста модели.

Параметры

Имя Тип Описание
modelName String Название модели или null

Возвращает
Number — максимальный размер контекста.


getLastJSON(text = null)

Извлекает JSON из текста ответа.

Параметры

Имя Тип Описание
text String Текст ответа или null

Возвращает
Object — найденный JSON.


extractTextWithoutJSON(text = null)

Удаляет JSON из текста и возвращает чистый текст.

Параметры

Имя Тип Описание
text String Текст ответа или null

Возвращает
String — текст без JSON.


removeMarkdownHeaders(text = null)

Удаляет заголовки Markdown из текста.

Параметры

Имя Тип Описание
text String Текст ответа или null

Возвращает
String — текст без заголовков.


getAgentAnswer(agentName)

Получает ответ агента по имени.

Параметры

Имя Тип Описание
agentName String Имя агента

Возвращает
String — ответ агента.


getLastTokenUsage()

Получает статистику токенов последнего запроса.

Параметры
Нет

Возвращает
Object — статистика токенов.


setStatus(status)

Устанавливает статус сессии.

Параметры

Имя Тип Описание
status String Статус: 'IDLE', 'PREPARED', ...

getStatus()

Получает текущий статус.

Параметры
Нет

Возвращает
String — текущий статус.


clearStatus()

Очищает статус.

Параметры
Нет


nextFlow(scriptCode)

Переходит к следующему flow-скрипту.

Параметры

Имя Тип Описание
scriptCode String Код следующего flow-скрипта

sendResponse()

Отправляет ответ в лог админа.

Параметры
Нет


sendFormattedResponse(format = 'Markdown')

Отправляет ответ в заданном формате.

Параметры

Имя Тип Описание
format String Формат: 'Markdown', 'HTML', ...

finishFlow()

Завершает работу сессии.

Параметры
Нет


Примеры использования

const LLMClient = require("Common.MetabotAI.LLMClient")
const llm = new LLMClient("DetectIntent")
llm.setModel("gpt-3.5-turbo")
llm.setProvider("OpenAI")
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()

Жизненный цикл и статусы

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

Отладка и переменные

Переменная Пример значения Описание
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_<session>_error_script MyAgent:Fallback Скрипт для ошибки
llm_<session>_raw_response Вот ваш ответ... Текст ответа до форматирования
llm_<session>_user_query Сколько лет космосу? Последний пользовательский запрос

FAQ

Вопрос: Как добавить свой промпт?
Ответ: Используйте addSystemPrompt, addUserPrompt или addAssistantPrompt. Можно ссылаться на промпты из таблицы через $name или @common_name.

Вопрос: Как обработать ошибку?
Ответ: Назначьте обработчик через setErrorScript(scriptCode) или используйте резервный сценарий через setFallbackConfig(scriptCode, timeout).

Вопрос: Как получить статистику токенов?
Ответ: Вызовите getLastTokenUsage() после получения ответа.

Вопрос: Как интегрировать с другими компонентами?
Ответ: Используйте методы перехода (nextFlow), отправки (sendResponse, sendFormattedResponse) и работы с историей.

Создание ClickHouse - SQL агента

Введение

Обычные LLM модели плохо справляются с числовыми данными - придумывают несуществующие цифры, неточно считают и не могут обрабатывать большие объемы информации. Для решения этой проблемы мы соединим возможности ИИ с профессиональными инструментами работы с данными.

В этом руководстве мы создадим специализированного агента для работы с базой данных ClickHouse, который сможет:

Пример использования: Банк собрал данные о конкурентах в одну таблицу и хочет, чтобы бот мог отвечать на вопросы типа "У какого банка самая низкая комиссия за перевод 25 000 долларов?"


Этап 1: Подключение к ClickHouse

Настройка подключения к базе данных

Первым шагом необходимо развернуть собственный кластер ClickHouse. После развертывания:

  1. Переходим в атрибуты бота
  2. Создаем атрибут CLICK_HOUSE_CREDENTIALS
  3. Заполняем данные подключения в формате JSON:
{
    "host": "http://111.1111.11.101:8000",
    "user": "admin", 
    "password": "12345",
    "database": "test"
}

Тестирование соединения

Создайте новый скрипт в боте для проверки подключения:

const ClickHouse = require('Common.Integrations.ClickHouse')
const CLICK_HOUSE_CREDENTIALS = bot.getJsonAttr("CLICK_HOUSE_CREDENTIALS")

const ch = new ClickHouse(CLICK_HOUSE_CREDENTIALS)

// 1. ПРОВЕРКА СОЕДИНЕНИЯ
const ping = ch.ping()
bot.sendMessage('PING → ' + ping.result)

// 2. ПРОСМОТР СУЩЕСТВУЮЩИХ ТАБЛИЦ
const tablesList = ch.select('SHOW TABLES')
bot.sendMessage('TABLES → ' + JSON.stringify(tablesList))

Запустите скрипт и проверьте результат. Если возникают ошибки - проверьте доступы к кластеру и правильность данных подключения.


Этап 2: Проектирование структуры таблицы

Подготовка исходных данных

Для примера используем таблицу сравнения банковских тарифов:

Банк Мин. сумма Сроки До 20k USD 20–50k USD 50–100k USD Валютный контроль Мин. руб. Макс. руб.



Кофейный нет 3–4 дня 3% 1,2% 0,6% 0,36% 0,36% нет н/д 0,15% 750 нет
Жёлтый нет 1–3 дня 3% 2,5% 2,5% 2% 2,5% нет курс ЦБ 0,16% 360 29000
Салатовый нет до 5 дней 3,5% 3,5% 3,5% 2,8% 2,5% нет курс ЦБ 0,15% 2000 нет
Зелёный нет 3–5 дней 3% 3% 2,7% 2,3–2,5% 2,2% 130 USD за SWIFT до 50k банк/ЦБ/Investing 0,24% 2000
Розовый нет 5 дней 3,5–4,5% 3,3–3,9% 3–3,5% 2,3–2,5% 2,2% 70–150 USD за SWIFT до 15k Investing/ЦБ (макс ставка) 0,15% 750 нет
Охра нет 3 дня 55000 руб. 2,65% 2,2% 2,1% 2,1% нет курс ЦБ 0,1% 700 20000
Малиновый 20 тыс. 3 дня 2–3% 2–3% 2–3% 2–3% нет ЦБ/Investing 0,12% 500 60000
Синий 50 тыс. 2–4 дня Китай: 2,5% Иные: 2–4% нет Investing 0,15% 1000 20000
Бирюзовый 10 тыс. 3–4 дня 2–4% 2–4% 2–4% 2–4% 2–4% нет ЦБ/profinance 0,12% 60000
Оранжевый 1–6 дней 4,5% мин. 80k руб. 4,5% мин. 80k руб. 2,5–4% 2,5–4% 2,5–4% нет курс ЦБ 0,15% 1000 20000
Бурый 50 тыс. 3 дня нет нет 2–3,5% 2–3,5% 2–3,5% нет Investing 0,15% 600 50000
Среднее 30 тыс. 3 дня 3,7% 3,1% 3,1% 2,7% 2,4% нет нет 0,14% 750 45000
Красный 3 дня 3% мин. 500 USD / 1,5–2% мин. 400 USD то же то же 2,5% / 1,5–2% мин. 400 USD 2% / 1,5–2% мин. 400 USD нет банк/ЦБ 0,22% 1000 нет

Генерация оптимальной структуры с помощью ИИ

Чтобы создать правильную структуру базы данных, воспользуемся специальным промптом:

  1. Переходим на https://console.anthropic.com
  2. Используем проверенный промпт:
You are an AI agent (Data Engineer) tasked with building ClickHouse database tables for a bank's calculation purposes. Your goal is to analyze JSON data and create an optimal table structure that allows for easy querying in ClickHouse.

Here are some important guidelines:

- Always split percentages into minimum and maximum values.

- Never store percentages as STRING or in quotes like "0.3-0.5".

- Use appropriate data types: FLOAT for percentages and decimals, INT for whole numbers.

- Design the structure carefully, thinking like a database engineer.

Analyze the following JSON data:

<json_data>

{{JSON_DATA}}

</json_data>

Your task is to:

1. Carefully examine the JSON structure and its contents.

2. Design an appropriate database structure that will allow for efficient querying in ClickHouse. Consider:

- What fields should be created?

- What data types should be used for each field?

- How to handle nested structures or arrays if present?

- How to split percentage ranges into separate minimum and maximum fields?

3. Write a SQL query to create the database table(s) based on your designed structure. Ensure that:

- All field names are clear and descriptive

- Appropriate data types are used (FLOAT for percentages and decimals, INT for whole numbers, etc.)

- The structure allows for easy and efficient querying

Remember: The goal is to create a structure that will enable straightforward and performant queries in ClickHouse. Think carefully about how the data will be used and queried in the future.

Provide your response in the following format:

<database_design>

[Explain your thought process and the rationale behind your database design here]

</database_design>

<sql_query>

[Write the SQL query to create the database table(s) here]

</sql_query>

If you need to make any assumptions or have any questions about the data structure, state them clearly in your explanation.
  1. В параметр JSON_DATA подставляете несколько строк из вашей таблицы
  2. Получаете готовый SQL для создания оптимальной структуры

Создание таблицы в ClickHouse

Создайте скрипт с полученным SQL-запросом:

const ClickHouse = require('Common.Integrations.ClickHouse')
const CLICK_HOUSE_CREDENTIALS = bot.getJsonAttr("CLICK_HOUSE_CREDENTIALS")

const ch = new ClickHouse(CLICK_HOUSE_CREDENTIALS)

// 1. ПРОВЕРКА СОЕДИНЕНИЯ
const ping = ch.ping()
bot.sendMessage('PING → ' + ping.result)

// 2. УДАЛЕНИЕ СТАРОЙ ТАБЛИЦЫ (если существует)
const dropResult = ch.query(`DROP TABLE IF EXISTS bank_commissions`)
bot.sendMessage('DROP → DONE')

// 3. СОЗДАНИЕ НОВОЙ ТАБЛИЦЫ (Сюда подставляем SQL из прошлого шага)
const createResult = ch.createTable(`
CREATE TABLE bank_commissions
(
    bank_name String,
    min_payment Nullable(Int32),
    delivery_days_min UInt8,
    delivery_days_max UInt8,
    commission_under_20k_min Float32,
    commission_under_20k_max Float32,
    commission_20k_50k_min Float32,
    commission_20k_50k_max Float32,
    commission_50k_100k_min Float32,
    commission_50k_100k_max Float32,
    currency_control_min Float32,
    currency_control_max Float32,
    min_rub Nullable(Int32),
    max_rub Nullable(Int32)
)
ENGINE = MergeTree
ORDER BY bank_name`)
bot.sendMessage('CREATE → DONE')

Этап 3: Загрузка данных

Подготовка данных в JSON формате

Преобразуйте табличные данные в структурированный JSON с полями соответствующими полученной таблице:

[{
    "bank_name": "Кофейный",
    "min_payment": null,
    "delivery_days_min": 3,
    "delivery_days_max": 4,
    "commission_under_20k_min": 3.0,
    "commission_under_20k_max": 3.0,
    "commission_20k_50k_min": 1.2,
    "commission_20k_50k_max": 1.2,
    "commission_50k_100k_min": 0.6,
    "commission_50k_100k_max": 0.6,
    "currency_control_min": 0.15,
    "currency_control_max": 0.15,
    "min_rub": 750,
    "max_rub": null
}, {
    "bank_name": "Жёлтый",
    "min_payment": null,
    "delivery_days_min": 1,
    "delivery_days_max": 3,
    "commission_under_20k_min": 3.0,
    "commission_under_20k_max": 3.0,
    "commission_20k_50k_min": 2.5,
    "commission_20k_50k_max": 2.5,
    "commission_50k_100k_min": 2.5,
    "commission_50k_100k_max": 2.5,
    "currency_control_min": 0.16,
    "currency_control_max": 0.16,
    "min_rub": 360,
    "max_rub": 29000
}]

Загрузка через Метабот

Создайте скрипт для загрузки данных:

const ClickHouse = require('Common.Integrations.ClickHouse')
const CLICK_HOUSE_CREDENTIALS = bot.getJsonAttr("CLICK_HOUSE_CREDENTIALS")

const ch = new ClickHouse(CLICK_HOUSE_CREDENTIALS)

// 1. ПРОВЕРКА СОЕДИНЕНИЯ
const ping = ch.ping()
bot.sendMessage('PING → ' + ping.result)

// 2. ОЧИСТКА СТАРЫХ ДАННЫХ
const truncateResult = ch.query('TRUNCATE TABLE bank_commissions')
bot.sendMessage('TRUNCATE → DONE')

// 3. ПОДГОТОВКА ДАННЫХ
const banksData = [
    // Вставьте здесь ваш JSON массив с данными банков
]

// 4. ЗАГРУЗКА В БАЗУ
const insertResult = ch.insert('bank_commissions', banksData)
bot.sendMessage('INSERT → DONE')

Этап 4: Интеграция SQL-агента в систему

Теперь создадим специализированного агента, который будет генерировать SQL-запросы и возвращать точные ответы на основе данных из ClickHouse.

Шаг 1: Обновление конфигурации

Откройте плагин AgentsParams.MainConfig и дополните конфигурацию:

А) Добавить новый инструмент в маршрутизатор

detectRoute: {
    // ... остальные параметры остаются без изменений
    routerTools: [{
        tools: "RAG_Tools",
        route: "RAG:DetectIntent_and_FindChunks"
    }, {
        tools: "INFO_Tools",
        route: "CompanyInfo" 
    }, { 
        tools: "TABLE1_Tools", // Новый инструмент для работы с данными
        route: "ClickHouse:GenerateSQL"
    }]
    // ... остальные параметры остаются без изменений
}

Б) Создать конфигурацию для ClickHouse агента

else if (activeAgent === "ClickHouse") { // Агент для работы с SQL таблицами ClickHouse
    agentCFG = {
        common: {
            title: "ClickHouse Query Agent",
            agentName: "ClickHouse",
            promptTable: "gpt_prompts",
            userQueryAttibName: "user_query",
            historyMaxLength: 2,
            exitScript: "MainFlow:FollowUp"
        },
        generateSQL: {
            provider: "OpenAI",
            model: "gpt-4o",
            modelParams: {
                "temperature": 1
            },
            prompt: "$clickhouse_sql_prompt",
            errorScript: "RAG:ErrorFallback",
            exitScript: "RAG:UserReply",
            useHistory: 1,
            addUserQuery: 1,
            sendBotAnwser: 1
        },
        userReply: {
            provider: "OpenAI",
            model: "gpt-4o",
            modelParams: {
                "temperature": 1
            },
            errorScript: "RAG:ErrorFallback",
            useHistory: 1,
            addUserQuery: 1,
            sendBotAnwser: 1,
            systemPrompts: {
                start: [
                    ["$clickhouse_result_prompt"]
                ],
                final: []
            },
        }
    }
}

Шаг 2: Создание скрипта агента

Создайте новый скрипт с именем ClickHouse:GenerateSQL:

Код создавайте в блоке "Выполнить асинхронный API-запрос"

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

// Установка активного агента ClickHouse
lead.setAttr('activeAgent', 'ClickHouse')
snippet("Business.AgentsParams.MainConfig")
const llm = new LLMClient("UserReply", agentCFG.common.agentName)

// Настройка истории диалога
if (!agentCFG.generateSQL.useHistory) {
  llm.disableHistory()
}

if (isFirstImmediateCall) {
  // Настройка LLM клиента
  llm.setProvider(agentCFG.generateSQL.provider)
  llm.setModel(agentCFG.generateSQL.model)
  llm.setModelParams(agentCFG.generateSQL.modelParams)
  llm.addSystemPrompt(agentCFG.generateSQL.prompt)

  llm.addUserQuery(lead.getAttr(agentCFG.common.userQueryAttibName))
  
  llm.prepareRequest()  
  return llm.sendRequest()
}

// Обработка ответа LLM
llm.handleResponse()

// Извлечение SQL-запроса из ответа
let sqlQuery = extractContentBetweenTags("sql_query")

// Проверка корректности SQL
if (!sqlQuery) {
  return llm.nextFlow(agentCFG.generateSQL.errorScript)
}

// Подключение к ClickHouse
const ClickHouse = require('Common.Integrations.ClickHouse')
const CLICK_HOUSE_CREDENTIALS = bot.getJsonAttr("CLICK_HOUSE_CREDENTIALS")
const ch = new ClickHouse(CLICK_HOUSE_CREDENTIALS)

// Выполнение SQL-запроса
const queryResult = ch.query(sqlQuery)

// Добавление результата в историю
llm.addToHistory({
  role: "assistant",
  content: JSON.stringify(queryResult)
})

// Переход к формированию ответа пользователю
return llm.nextFlow(agentCFG.generateSQL.exitScript)

// Функция извлечения контента между тегами
function extractContentBetweenTags(tagName, text = null) {
  text = text || llm.getResponseText()
  const openTag = `<${tagName}>`
  const closeTag = `</${tagName}>`

  const startIndex = text.indexOf(openTag)
  if (startIndex === -1) return null

  const endIndex = text.indexOf(closeTag, startIndex + openTag.length)
  if (endIndex === -1) return null

  return text.substring(startIndex + openTag.length, endIndex).trim()
}

Шаг 3: Создание промптов

1. Откройте таблицу gpt_prompts, cоздайте промпт clickhouse_sql_prompt для агента ClickHouse:


You are a Data Engineer who communicates exclusively through SQL queries. Your task is to interpret requests from a senior agent and formulate appropriate SQL queries for a ClickHouse database. Here's how you should proceed:

First, familiarize yourself with the structure of the ClickHouse table:

<table_structure>
{{@click_house_tables}}
</table_structure>

When you receive a query from the senior agent, it will be in simple language. Your job is to interpret this query and create an SQL statement that will retrieve the requested information from the ClickHouse database.

The user's query will be provided in the following format:

<user_query>
{{$user_query}}
</user_query>

Based on this query and your knowledge of the table structure, formulate an SQL query that will retrieve the requested information. Your query should be as detailed as possible, providing comprehensive data for the senior agent to analyze and make decisions.

Remember:
1. You should only output SQL code. Do not provide any explanations, comments, or additional text.
2. Strive to create complex queries when necessary to calculate the required data.
3. Always aim to provide detailed data in your query results.
4. Do not use any functions or features that are not standard SQL or specific to ClickHouse.

Your response should consist solely of the SQL query, enclosed in <sql_query> tags. For example:

<sql_query>
SELECT column1, column2, COUNT(*) as count
FROM table_name
WHERE condition
GROUP BY column1, column2
ORDER BY count DESC
LIMIT 10
</sql_query>

Do not include any text before or after the SQL query. Your entire response should be contained within the <sql_query> tags.

2. Создайте промпт clickhouse_result_prompt для агента ClickHouse:

Вы являетесь Помощником банка. Ваша задача - проанализировать историю диалога, получить из неё запрос пользователя, проанализировать ответ SQL агента и дать понятный ответ, соответствующий запросу пользователя. Отвечайте четко и по существу, представляя числовые данные в удобном для восприятия формате.

3. Создайте атрибут бота click_house_tables с описанием структуры таблицы:

TABLE: bank_commissions

COLUMNS:
- bank_name (String): Название банка
- min_payment (Nullable Int32): Минимальная сумма платежа в рублях
- delivery_days_min (UInt8): Минимальное время доставки в днях
- delivery_days_max (UInt8): Максимальное время доставки в днях
- commission_under_20k_min (Float32): Минимальная комиссия до 20k USD в процентах
- commission_under_20k_max (Float32): Максимальная комиссия до 20k USD в процентах
- commission_20k_50k_min (Float32): Минимальная комиссия 20-50k USD в процентах
- commission_20k_50k_max (Float32): Максимальная комиссия 20-50k USD в процентах
- commission_50k_100k_min (Float32): Минимальная комиссия 50-100k USD в процентах
- commission_50k_100k_max (Float32): Максимальная комиссия 50-100k USD в процентах
- currency_control_min (Float32): Минимальная комиссия валютного контроля в процентах
- currency_control_max (Float32): Максимальная комиссия валютного контроля в процентах
- min_rub (Nullable Int32): Минимальная сумма в рублях
- max_rub (Nullable Int32): Максимальная сумма в рублях

Шаг 4: Обновление маршрутизации

Откройте таблицу gpt_prompts, найдите промпт route_prompt для агента MainFlow и обновите секцию с инструментами:

<tools>
**RAG_Tools** - Используется, когда нужно ответить на вопросы об услугах 
и продукции компании, об условиях эксплуатации продукции, об используемых 
инструментах для монтажа продукции и т.д.

**INFO_Tools** - Используется когда пользователь хочет узнать о филиалах 
компании, контактных данных или времени их работы

**TABLE1_Tools** используется для поиска информации и ответов на вопросы о: 
- Ценах и стоимости переводов 
- Комиссиях за переводы и платежи 
- Точных цифрах и числовых данных, хранящихся в локальной базе данных 
- Сравнении различных параметров (цены, комиссии, характеристики) 
- Поиске минимальных/максимальных значений (самая низкая комиссия, самая высокая цена и т.д.) 
- Условиях и тарифах банков-партнеров 
- Расчетах и вычислениях на основе имеющихся данных
</tools>

Этап 5: Тестирование и результат

Примеры работы системы

После настройки система сможет отвечать на сложные вопросы с числовыми данными:

Простые запросы:

Сложные аналитические запросы:

Принцип работы

  1. Пользователь задает вопрос о числовых данных
  2. Главный агент определяет - нужен TABLE1_Tools
  3. SQL-агент генерирует соответствующий запрос к ClickHouse
  4. База данных возвращает точные результаты
  5. Система формирует понятный ответ для пользователя

Pasted image 20250905130654.png

Преимущества решения

  1. Точность данных - никаких выдуманных цифр
  2. Сложные вычисления - система может выполнять расчеты любой сложности
  3. Масштабируемость - легко добавлять новые таблицы и данные
  4. Актуальность - данные всегда свежие из базы
  5. Производительность - ClickHouse обеспечивает быстрые запросы

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

Руководство по работе с базой знаний (RAG)

Pasted image 20250905130654.png

Структура таблицы базы знаний

Представьте базу знаний как умный склад информации. Когда нужно полностью "перезагрузить" этот склад, мы поступаем просто:

  1. Полная очистка базы знаний: удаляем старую таблицу и создаём точно такую же заново

    • Это как снести старый склад и построить новый по тому же чертежу
    • Гарантирует, что не останется "мусора" от предыдущих данных
  2. Размер вектора (embedding): может изменяться в зависимости от выбранного алгоритма векторизации

    • Вектор — это числовое представление смысла текста
    • Разные алгоритмы создают векторы разной длины (как разные форматы фотографий)

Pasted image 20250905110051.png

Создание компонента базы знаний

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

Думайте о компоненте как о "визитной карточке" вашей базы знаний:

Pasted image 20250905110046.png

После создания в атрибутах появится компонент с описанием базы знаний:

Pasted image 20250905110029.png

Подготовка текстового файла

Перед загрузкой файл нужно правильно "подписать" — добавить служебную информацию в начало:

Pasted image 20250905110023.png

Pasted image 20250905110020.png

Обязательная служебная строка в начале файла:

{{Domain: METABOT33}}{{Table: gpt_knowledge_base}}{{chunk_size: 250}}{{chunk_overlap: 160}}

Эта строка работает как "инструкция по применению":

Pasted image 20250905110015.png

Загрузка и векторизация

Процесс состоит из двух этапов:

Этап 1: Загрузка файла

Pasted image 20250905105957.png

Pasted image 20250905105955.png

 

Система принимает подготовленный файл и сохраняет его содержимое.

Pasted image 20250905105950.png

Этап 2: Векторизация

Pasted image 20250905105946.png

Система превращает текст в числовые векторы для быстрого поиска по смыслу.

Важно: сохраните имя defKnowBase — оно понадобится в конфигурации в параметре kbName.


Совет: процесс загрузки и векторизации похож на работу переводчика — сначала он читает текст (загрузка), потом переводит его на "язык чисел" (векторизация), чтобы компьютер мог быстро найти нужную информацию по смыслу, а не только по точным словам.

Документация по LLMTracer

 

Модуль LLMTracer предназначен для трассировки запросов к LLM, а также для сбора статистики по токенам, времени выполнения и ошибкам. Все данные записываются в таблицу llm_tracer.


Класс: Session

Подключается через

let LLMTracer = require("Common.AIHelpers.LLMTracer")

createSession(options)

Создаёт новую сессию трассировки.

Пример:

let session = LLMTracer.createSession({
  business_id: "123",
  bot_id: "456",
  lead_id: "789",
  script_id: "script_1",
  command_id: "cmd_1",
  agent_name: "MyAgent",
  provider: "OpenAI",
  model: "gpt-4"
})

Параметры:

Имя Тип Описание
business_id String Идентификатор бизнеса
bot_id String Идентификатор бота
lead_id String Идентификатор лида
script_id String Идентификатор скрипта
command_id String Идентификатор команды
agent_name String Имя агента
provider String Название провайдера LLM
model String Название модели LLM

Return
Session — Экземпляр сессии трассировки.


getCurrentSession()

Получает текущую сессию из атрибута лида.

Пример:

let session = LLMTracer.getCurrentSession()

Параметры:
Нет

Return
Session | null — Текущий объект сессии или null, если не найден.


prepareRecordData(data)

Преобразует данные в безопасный формат для записи в таблицу.

Пример:

let safeData = LLMTracer.prepareRecordData({ message: "Тест", status: "ok" })

Параметры:

Имя Тип Описание
data Object Объект с исходными данными

Return
Object — Объект с преобразованными значениями.


recordTrace(traceData)

Записывает трассировку в таблицу llm_tracer.

Пример:

LLMTracer.recordTrace({
  agent_name: "MyAgent",
  outclient_name: "ClientA",
  step: "step1",
  outclient_time: 120,
  provider: "OpenAI",
  model: "gpt-4",
  type: "info",
  message: "Запрос выполнен",
  record: {},
  params: {},
  status: "ok",
  input_tokens: 100,
  output_tokens: 50
})

Параметры:

Имя Тип Описание
agent_name String Имя агента
outclient_name String Имя внешнего клиента
step String Название шага
outclient_time Number Время выполнения шага (мс)
provider String Название провайдера LLM
model String Название модели LLM
type String Тип трассировки (info, error и др.)
message String Сообщение
record Object Дополнительные данные
params Object Параметры запроса
status String Статус выполнения
input_tokens Number Количество входных токенов
output_tokens Number Количество выходных токенов

Return
Boolean — true при успехе, false при ошибке.


recordTraceLLM(llm, timerCtrl, traceData)

Записывает трассировку по активному LLMClient.

Пример:

LLMTracer.recordTraceLLM(llmClient, "START_TIME", { message: "Старт" })

Параметры:

Имя Тип Описание
llm Object Экземпляр LLMClient
timerCtrl String Контроллер времени ("", "START_TIME", "END_TIME")
traceData Object Дополнительные параметры трассировки

Return
Boolean — true при успехе, false при ошибке.


recordTraceQuery(llm, timerCtrl, traceData)

Записывает трассировку для запросов к внешним сервисам.

Пример:

LLMTracer.recordTraceQuery(llmClient, "END_TIME", { message: "Финиш" })

Параметры:

Имя Тип Описание
llm Object Экземпляр LLMClient
timerCtrl String Контроллер времени ("", "START_TIME", "END_TIME")
traceData Object Дополнительные параметры трассировки

Return
Boolean — true при успехе, false при ошибке.


info(message, data)

Записывает информационную трассировку.

Пример:

LLMTracer.info("Запрос выполнен", { step: "step1" })

Параметры:

Имя Тип Описание
message String Текст сообщения
data Object Дополнительные данные

Return
Boolean — true при успехе, false при ошибке.


error(message, data)

Записывает ошибку в трассировку и отправляет сообщение админу.

Пример:

LLMTracer.error("Ошибка запроса", { code: 500 })

Параметры:

Имя Тип Описание
message String Текст ошибки
data Object Дополнительные данные

Return
Boolean — true при успехе, false при ошибке.


countTokens(dateRange)

Подсчитывает количество токенов за указанный период.

Пример:

LLMTracer.countTokens({ from: "2024-06-01 00:00:00", to: "2024-06-30 23:59:59" })

Параметры:

Имя Тип Описание
from String Начальная дата ("YYYY-MM-DD HH:MM:SS")
to String Конечная дата

Return
Object — { input_tokens, output_tokens, total_tokens }


getActiveSessionsCount(minutes)

Подсчитывает количество активных сессий за последние N минут.

Пример:

LLMTracer.getActiveSessionsCount(10)

Параметры:

Имя Тип Описание
minutes Number Глубина окна в минутах (по умолчанию 5)

Return
Number — Количество активных сессий.


Быстрый старт

  1. Создайте сессию: LLMTracer.createSession({ ... })
  2. Запишите шаг: LLMTracer.recordTrace({ ... })
  3. Для LLM: используйте recordTraceLLM()
  4. Для ошибок: используйте error()
  5. Для статистики: используйте countTokens()

Аналитика и нагрузки

 

В стандартный шаблон MA_Router входит модуль, который отвечает за контроль нагрузки, лимитов и доступности моделей LLM.

Основные проверки

Логика работы

  1. Перед стартом диалога — проверяются лимиты и нагрузка.
  2. Если лимит превышен — пользователю отправляется уведомление, диалог переносится.
  3. Если нагрузка высокая — диалог откладывается на несколько секунд.
  4. При старте диалога — увеличивается счётчик, открывается сессия в LLMTracer.
  5. При завершении — сессия закрывается.

Метрики и отчёты

📊 Статистика LLM Tracer
🕒 За последние 7 дней:
• Токены входящие: 2 533 129
• Токены исходящие: 313 036
• Всего токенов: 2 846 165
• Уникальных лидов: 3
• Уникальных сессий: 29
• Среднее вопросов на пользователя: 9.7
• Превышений 20 запросов в сутки: 1
📈 За всё время:
• Токены входящие: 3 861 082
• Токены исходящие: 351 484
• Всего токенов: 4 212 566
• Уникальных лидов: 3
• Уникальных сессий: 38
• Среднее вопросов на пользователя: 12.7
• Превышений 20 запросов в сутки: 1
Обновлено: 2025-10-06 18:53:52

Таймауты

Для каждого запроса к LLM настраиваются таймауты в конфигурации. При превышении времени ожидания:

Таймауты защищают систему от зависаний при проблемах на стороне LLM-провайдера.


Быстрые ответы на вопросы

LLMTracer - Плагин для трассировки запросов к LLM

Описание

LLMTracer - это модуль для трассировки запросов к языковым моделям (LLM) и внешним сервисам. Он позволяет записывать данные о запросах, ответах, ошибках и использовании токенов в таблицу llm_tracer.

Установка

Импортируйте модуль в ваш код:

const LLMTracer = require('Common.Platform.LLMTracer');

Основные возможности

Быстрый старт

1. Создание сессии трассировки

// Создание сессии с параметрами по умолчанию
const session = LLMTracer.createSession();

// Создание сессии с указанием параметров
const session = LLMTracer.createSession({
  agent_name: "ChatGPT Assistant",
  provider: "OpenAI",
  model: "gpt-4"
});

Параметры:

2. Прямая запись трассировки

// Прямая запись трассировки с указанием всех параметров
LLMTracer.recordTrace({
  outclient_name: "OpenAI API",
  step: 1,
  outclient_time_sec: 2.5,
  provider: "OpenAI",
  model: "gpt-4",
  type: "info",
  message: "Запрос выполнен",
  status: "success",
  input_tokens: 150,
  output_tokens: 50
});

Параметры:

3. Запись информационных сообщений

// Запись простого сообщения
LLMTracer.info("Запрос к LLM выполнен успешно");

// Запись сообщения с дополнительными данными
LLMTracer.info("Запрос к LLM выполнен успешно", {
  outclient_name: "OpenAI API",
  input_tokens: 150,
  output_tokens: 50,
  outclient_time_sec: 2.5
});

Параметры:

3. Запись ошибок

try {
  // Ваш код...
} catch (error) {
  LLMTracer.error("Ошибка при запросе к LLM", {
    error_message: error.message,
    outclient_name: "OpenAI API"
  });
}

4. Подсчет токенов

// Подсчет токенов за все время
const allTokens = LLMTracer.countTokens();
debug(`Всего использовано токенов: ${allTokens.total_tokens}`);

// Подсчет токенов за определенный период
const tokensForPeriod = LLMTracer.countTokens({
  from: "2023-01-01 00:00:00",
  to: "2023-01-31 23:59:59"
});

debug(`Входящие токены: ${tokensForPeriod.input_tokens}`);
debug(`Исходящие токены: ${tokensForPeriod.output_tokens}`);
debug(`Всего токенов: ${tokensForPeriod.total_tokens}`);

Параметры:

Возвращает: Объект с количеством токенов

Структура таблицы llm_tracer

Модуль записывает данные в таблицу llm_tracer со следующими полями:

Импортировать таблицу можно по ссылке: https://stage.metabot.dev/business/export/1216/show?bots=&b_c=0&b_o=0&b_i=0&b_ls=0&b_r=0&b_s=0&b_li=0&b_t=0&b_br=0&b_aie=0&b_aee=0&b_sa=0&cts_llm_tracer=1&b_llm_tracer=1

Конфигурация

Конфигурационный плагин бизнеса (snippet Business.AgentsParams.BSPbConfig) используется для централизованного управления параметрами агентов в системе MAS. Он определяет настройки для различных сценариев работы агентов, включая маршрутизацию, выбор моделей, параметры генерации, обработку ошибок и интеграцию с внешними инструментами.

Структура файла

Файл представляет собой JavaScript-объект, где каждая ветка соответствует определённому агенту или сценарию. Основные разделы:

Пример структуры

{
  common: {
    title: "Основной Flow c маршрутизатором",
    agentName: "bsp",
    promptTable: "gpt_prompts",
    userQueryAttibName: "user_query",
    historyMaxLength: 4,
    useRoute: 0,
    exitScript: "MainFlow_FollowUp",
    MBQuery_fallback: {
      script_code: "MBQuery_TimeOut",
      timeout: 180
    }
  },
  detectRoute: {
    provider: "OpenAI",
    model: "gpt-5-mini",
    modelParams: { temperature: 1 },
    prompt: "$route_prompt",
    routerTools: [ ... ],
    errorScript: "RAG_ErrorFallback"
  },
  detectIntent: {
    provider: "OpenAI",
    apiFormat: "OpenAI",
    model: "gpt-5-mini",
    modelParams: { temperature: 1 },
    prompt: "$rag_intent_prompt",
    errorScript: "RAG_ErrorFallback",
    kbName: "defKnowBase",
    kbDomain: "tech_domain"
  },
  userReply: {
    provider: "OpenAI",
    apiFormat: "OpenAI",
    model: "gpt-5-mini",
    modelParams: { temperature: 1 },
    errorScript: "RAG_ErrorFallback",
    useHistory: 1,
    addUserQuery: 1,
    sendBotAnwser: 1,
    systemPrompts: {
      start: [["$start_prompt", "$rag_prompt"]],
      final: ["$final_prompt"]
    }
  }
}

Как работает подстановка

  1. Загрузка snippet: Конфигурация подгружается через snippet с именем Business.AgentsParams.BSPbConfig.
  2. Выбор активного агента: В коде определяется активный агент через переменную activeAgent (например, bsp, CompanyInfo, Table1).
  3. Инициализация agentCFG: В зависимости от значения activeAgent выбирается соответствующая ветка конфигурации и присваивается переменной agentCFG.
  4. Передача параметров: agentCFG используется для инициализации LLMClient, настройки промптов, истории, провайдера, модели и других параметров.
  5. Маршрутизация и обработка: Ветка detectRoute определяет инструменты маршрутизации, которые используются для выбора сценария обработки запроса пользователя.
  6. Обработка ошибок: Для каждого сценария можно задать обработчик ошибок (errorScript) и fallback-скрипты.
  7. Интеграция с базой знаний: Ветка detectIntent может содержать параметры для подключения к базе знаний (kbName, kbDomain).

Структура кода

let aAgent = lead.getAttr("activeAgent")
let agentCFG
if (aAgent === "bsp") {
  agentCFG = { ... } // параметры для bsp
} else if (aAgent === "CompanyInfo") {
  agentCFG = { ... } // параметры для CompanyInfo
} else if (aAgent === "Table1") {
  agentCFG = { ... } // параметры для Table1
} else {
  bot.sendMessage(`BSPbConfig:snippet - неизвестный activeAgent:  ${aAgent}`)
  bot.stop()
}

Куда подставляются параметры

Рекомендации

FAQ

Вопрос: Как добавить нового агента?
Ответ: Добавьте новую ветку в объект конфигурации с нужными параметрами и обработчиками.

Вопрос: Как изменить модель или провайдера?
Ответ: Измените значения model и provider в нужной ветке конфигурации.

Вопрос: Как задать fallback-скрипт?
Ответ: Укажите параметры в MBQuery_fallback или errorScript для нужного сценария.

Вопрос: Как интегрировать базу знаний?
Ответ: Добавьте параметры kbName и kbDomain в ветку сценария, где требуется интеграция.

Ошибки и отладка

В этом документе описаны все основные механизмы обработки ошибок, настройки таймаутов, использование нотификатора, трассировки и отладки.


1. Таймауты: где и как настраиваются

Таймауты — это максимальное время ожидания ответа от LLM или внешнего сервиса. Если таймаут превышен, происходит ошибка и запускается обработка сбоя.

Где настраиваются:

Как работает:


2. Нотификатор: что это и как работает

Notifier — это модуль для отправки уведомлений о сбоях и важных событиях админам.


3. LLMTracer: трассировка ошибок

LLMTracer — модуль для логирования всех событий, включая ошибки.


4. Режим debug и самостоятельная отладка

Если не удаётся найти причину ошибки:

Eval - Тестирование

Схема работы системы Eval

Полная схема флоу с блокировками и таблицами

graph TB
    Start([Пользователь запускает тест]) --> StartRun[Eval_StartRun]
    
    StartRun --> CheckRun{Проверка run_status}
    CheckRun -->|pending| CreateReport[Создать report в eval_reports]
    CheckRun -->|!pending| Error1[Ошибка: run уже запущен]
    
    CreateReport --> UpdateRun[Обновить eval_runs:<br/>status=running, started_at]
    UpdateRun --> InitLeads[Запустить Eval_InitLead<br/>для каждого run_lead_id]
    
    InitLeads --> SetBatchTest[Установить batchTest=1]
    SetBatchTest --> SaveContext[Сохранить eval_context:<br/>run_id, suite_id, report_id]
    SaveContext --> ProcessQuestion[Eval_ProcessQuestion]
    
    ProcessQuestion --> CheckContext{Есть текущая серия<br/>в контексте?}
    
    CheckContext -->|Да| GetNextQuestion[Взять следующий вопрос<br/>из серии]
    CheckContext -->|Нет| GetUnclaimed[getUnclaimedSeries<br/>report_id, suite_id]
    
    GetUnclaimed --> CheckTable1[Проверить eval_answers:<br/>найти свободные серии/вопросы]
    CheckTable1 --> AvailableList{Есть доступные<br/>элементы?}
    
    AvailableList -->|Нет| AggregateReport[Eval_AggregateReport]
    AvailableList -->|Да| TryLock[Попытка захвата блокировки]
    
    TryLock --> LockType{Тип элемента?}
    LockType -->|Серия| LockSeries["eval_series_runId_seriesIndex<br/>TTL=60s"]
    LockType -->|Standalone| LockQuestion["eval_q_runId_questionId<br/>TTL=5s"]
    
    LockSeries --> CheckLock{Блокировка<br/>получена?}
    LockQuestion --> CheckLock
    
    CheckLock -->|Нет| ReleaseNotNeeded[continue<br/>блокировка не была получена]
    CheckLock -->|Да| DoubleCheck[Двойная проверка:<br/>проверить eval_answers]
    
    ReleaseNotNeeded --> TryLock
    
    DoubleCheck --> AlreadyClaimed{Элемент уже<br/>занят?}
    AlreadyClaimed -->|Да| ReleaseLock[bot.releaseLockForBot<br/>освободить блокировку]
    AlreadyClaimed -->|Нет| CreateAnswers[Создать записи в eval_answers<br/>status=PROCESSING<br/>для всех вопросов серии]
    
    ReleaseLock --> TryLock
    
    CreateAnswers --> SaveSeriesContext[Сохранить серию в контекст:<br/>current_series, answerIds]
    
    GetNextQuestion --> CheckFirst{Первый вопрос<br/>серии?}
    SaveSeriesContext --> CheckFirst
    
    CheckFirst -->|Да| ClearHistory[Очистить историю mainAgent]
    CheckFirst -->|Нет| SetQuery[Установить user_query]
    ClearHistory --> SetQuery
    
    SetQuery --> MARouter[MA_Router<br/>обработка вопроса ботом]
    
    MARouter --> CheckBatchTest{batchTest==1?}
    CheckBatchTest -->|Нет| NormalFlow[Обычный flow]
    CheckBatchTest -->|Да| SaveAnswer[Eval_SaveAnswer]
    
    SaveAnswer --> GetSession[Получить session_id<br/>из LLMTracer]
    GetSession --> SaveActual[Сохранить actual_answer<br/>в eval_answers]
    SaveActual --> GetMetrics[getTraceMetrics<br/>из llm_tracer]
    GetMetrics --> SaveMetrics[Сохранить метрики:<br/>latency_ms, tokens, etc.]
    
    SaveMetrics --> LockCounter["eval_run_runId_counter<br/>TTL=10s, wait=30s"]
    LockCounter --> IncrementCounter[incrementProcessedQuestions<br/>в eval_reports]
    IncrementCounter --> JudgeAnswer[Eval_JudgeAnswer]
    
    JudgeAnswer --> CallJudge[Вызов LLM-ассессора]
    CallJudge --> ParseJSON{Успешно<br/>распарсить JSON?}
    
    ParseJSON -->|Да| SaveRatings[Сохранить ratings:<br/>overall, accuracy, etc.<br/>status=COMPLETED]
    ParseJSON -->|Нет| SaveDefault[Сохранить дефолтные<br/>ratings=50<br/>status=COMPLETED]
    
    SaveRatings --> UpdateSeriesIdx[Увеличить current_series_question_idx]
    SaveDefault --> UpdateSeriesIdx
    UpdateSeriesIdx --> ProcessQuestion
    
    MARouter -->|Ошибка| ErrorFallback[RAG_ErrorFallback]
    ErrorFallback --> CheckBatchTest2{batchTest==1?}
    CheckBatchTest2 -->|Да| HandleError[Eval_HandleError]
    CheckBatchTest2 -->|Нет| NormalError[Обычная обработка ошибки]
    
    HandleError --> MarkFailed[markAnswerFailed<br/>status=FAILED<br/>в eval_answers]
    MarkFailed --> IncrementFailed[incrementFailedQuestions<br/>в eval_reports]
    IncrementFailed --> ProcessQuestion
    
    AggregateReport --> CheckAllProcessed[areAllQuestionsProcessed<br/>report_id, suite_id]
    
    CheckAllProcessed --> CheckStatuses[Проверить статусы через table.find:<br/>PENDING, PROCESSING<br/>используя IN условие]
    CheckStatuses --> CheckSeries[Проверить целостность серий:<br/>все вопросы в финальных статусах<br/>COMPLETED или FAILED]
    
    CheckSeries --> AllDone{Все обработано?}
    AllDone -->|Нет| SendStatusMsg[Отправить статус главному лиду:<br/>обработка продолжается]
    AllDone -->|Да| FinalizeLock["eval_run_runId_finalize<br/>TTL=60s"]
    
    SendStatusMsg --> ExitFlow
    
    FinalizeLock --> LockAcquired{Блокировка<br/>получена?}
    LockAcquired -->|Нет| OtherLeadFinalizes[Другой лид финализирует<br/>и отправит уведомление]
    LockAcquired -->|Да| FinalizeReport[finalizeReport:<br/>calculateReportStats<br/>status=COMPLETED]
    
    OtherLeadFinalizes --> ExitFlow
    FinalizeReport --> FinalizeRun[finalizeRun:<br/>status=COMPLETED<br/>finished_at]
    
    FinalizeRun --> SendNotify[Eval_Notify<br/>отправить уведомление]
    
    SendNotify --> UpdateNotify[Обновить notification_sent=1]
    UpdateNotify --> ExitFlow
    
    Note1[Блокировка финализации гарантирует<br/>единственность отправки уведомления]
    
    ExitFlow --> End([Завершение])
    
    style Start fill:#e1f5ff
    style End fill:#e1f5ff
    style Error1 fill:#ffcccc
    style HandleError fill:#ffcccc
    style MarkFailed fill:#ffcccc
    style LockSeries fill:#fff4cc
    style LockQuestion fill:#fff4cc
    style LockCounter fill:#fff4cc
    style FinalizeLock fill:#fff4cc
    style NotifyLock fill:#fff4cc
    style CreateReport fill:#ccffcc
    style CreateAnswers fill:#ccffcc
    style SaveRatings fill:#ccffcc
    style FinalizeReport fill:#ccffcc

Схема таблиц и их связи

erDiagram
    eval_runs ||--o{ eval_reports : "has"
    eval_suites ||--o{ eval_questions : "contains"
    eval_suites ||--o{ eval_runs : "used_in"
    eval_reports ||--o{ eval_answers : "contains"
    eval_questions ||--o{ eval_answers : "answered_by"
    
    eval_runs {
        int id PK
        int suite_id FK
        text run_lead_ids "lead1,lead2,lead3"
        text run_status "pending|running|completed|failed"
        datetime started_at
        datetime finished_at
    }
    
    eval_suites {
        int id PK
        text name
        int bot_id
    }
    
    eval_questions {
        int id PK
        int suite_id FK
        text question_text
        text optimal_answer
        int series_index "null для standalone"
        int order_in_series "1,2,3..."
    }
    
    eval_reports {
        int id PK
        int run_id FK
        datetime started_at
        datetime finished_at
        int total_questions
        int processed_questions
        int failed_questions
        decimal average_rating
        text report_status "running|completed|failed"
        int notification_sent
    }
    
    eval_answers {
        int id PK
        int report_id FK
        int question_id FK
        text lead_id
        text session_id
        text question_text
        text optimal_answer
        text actual_answer
        text answer_status "pending|processing|completed|failed"
        decimal rating_overall
        decimal rating_accuracy
        int latency_ms
        int input_tokens
        int output_tokens
        text error_message
    }
    
    llm_tracer {
        text session_id PK
        decimal outclient_time
        int input_tokens
        int output_tokens
    }

Схема блокировок и их жизненный цикл

sequenceDiagram
    participant L1 as Lead 1
    participant L2 as Lead 2
    participant DB as eval_answers
    participant Lock as Lock System
    
    Note over L1,L2: Параллельная обработка вопросов
    
    L1->>DB: getUnclaimedSeries(report_id, suite_id)
    DB-->>L1: [Серия 1, Серия 2, Вопрос 3]
    
    L2->>DB: getUnclaimedSeries(report_id, suite_id)
    DB-->>L2: [Серия 1, Серия 2, Вопрос 3]
    
    L1->>Lock: waitForBotLock("eval_series_{runId}_1")
    Lock-->>L1: true (получена)
    
    L2->>Lock: waitForBotLock("eval_series_{runId}_1")
    Lock-->>L2: false (занята L1)
    
    L1->>DB: Двойная проверка: серия свободна?
    DB-->>L1: Да, свободна
    
    L1->>DB: Создать записи status=PROCESSING
    DB-->>L1: answerIds: [101, 102, 103]
    
    L2->>Lock: waitForBotLock("eval_series_{runId}_2")
    Lock-->>L2: true (получена)
    
    L2->>DB: Двойная проверка: серия свободна?
    DB-->>L2: Да, свободна
    
    L2->>DB: Создать записи status=PROCESSING
    DB-->>L2: answerIds: [104, 105]
    
    Note over L1: Обработка вопросов серии 1
    L1->>DB: Обновить answer_id=101: actual_answer, status=COMPLETED
    
    Note over L2: Обработка вопросов серии 2
    L2->>DB: Обновить answer_id=104: actual_answer, status=COMPLETED
    
    Note over L1,L2: Блокировки автоматически освобождаются по TTL
    Note over L1,L2: или при ошибке через releaseLockForBot()

Схема обработки ошибок

flowchart TD
    Start[Обработка вопроса] --> Process[MA_Router обрабатывает]
    
    Process --> Success{Успешно?}
    Process --> Timeout{Таймаут?}
    Process --> Error{Ошибка API?}
    
    Success -->|Да| SaveAnswer[Eval_SaveAnswer]
    SaveAnswer --> Judge[Eval_JudgeAnswer]
    
    Judge --> JudgeSuccess{Ассессор<br/>ответил?}
    JudgeSuccess -->|Да| SaveRatings[Сохранить ratings<br/>status=COMPLETED]
    JudgeSuccess -->|Нет| SaveDefault[Сохранить дефолт<br/>status=COMPLETED]
    
    SaveRatings --> NextQuestion[Следующий вопрос]
    SaveDefault --> NextQuestion
    
    Timeout -->|Да| ErrorFallback[RAG_ErrorFallback]
    Error -->|Да| ErrorFallback
    
    ErrorFallback --> CheckBatch{batchTest==1?}
    CheckBatch -->|Нет| UserError[Показать ошибку<br/>пользователю]
    CheckBatch -->|Да| HandleError[Eval_HandleError]
    
    HandleError --> MarkFailed[markAnswerFailed<br/>status=FAILED<br/>error_message]
    MarkFailed --> IncrementFailed[incrementFailedQuestions]
    IncrementFailed --> NextQuestion
    
    NextQuestion --> CheckMore{Есть ещё<br/>вопросы?}
    CheckMore -->|Да| Process
    CheckMore -->|Нет| Aggregate[Eval_AggregateReport]
    
    Aggregate --> CheckAll{Все вопросы<br/>обработаны?}
    CheckAll -->|Нет| Wait[Ждать завершения<br/>других лидов]
    CheckAll -->|Да| Finalize[Финализация report]
    
    style Error fill:#ffcccc
    style Timeout fill:#ffcccc
    style HandleError fill:#ffcccc
    style MarkFailed fill:#ffcccc
    style Success fill:#ccffcc
    style SaveRatings fill:#ccffcc
    style Finalize fill:#ccffcc

Схема статусов вопросов

stateDiagram-v2
    [*] --> PENDING: Создан в claimSeries
    
    PENDING --> PROCESSING: Захвачен лидом
    
    PROCESSING --> COMPLETED: Успешно обработан<br/>и оценён
    PROCESSING --> FAILED: Ошибка при обработке
    
    COMPLETED --> [*]
    FAILED --> [*]
    
    note right of PROCESSING
        Блокировка активна
        TTL: 5s (вопрос) или 60s (серия)
    end note
    
    note right of COMPLETED
        Финальный статус
        Включает ratings и метрики
    end note

Схема параллельной обработки (Run Leads Pool)

graph LR
    subgraph "Run Leads Pool"
        L1[Lead 1]
        L2[Lead 2]
        L3[Lead 3]
    end
    
    subgraph "Общий Report"
        R[(eval_reports<br/>report_id=1)]
    end
    
    subgraph "Вопросы Suite"
        Q1[Серия 1: Q1, Q2, Q3]
        Q2[Серия 2: Q4, Q5]
        Q3[Вопрос 6]
        Q4[Вопрос 7]
    end
    
    subgraph "Блокировки (run_id=5)"
        B1[eval_series_5_1]
        B2[eval_series_5_2]
        B3[eval_q_5_6]
        B4[eval_q_5_7]
    end
    
    L1 -->|claimSeries| B1
    L2 -->|claimSeries| B2
    L3 -->|claimSeries| B3
    
    B1 --> Q1
    B2 --> Q2
    B3 --> Q3
    
    Q1 --> R
    Q2 --> R
    Q3 --> R
    
    L1 -.->|Параллельно| L2
    L2 -.->|Параллельно| L3
    
    style R fill:#ccffcc
    style B1 fill:#fff4cc
    style B2 fill:#fff4cc
    style B3 fill:#fff4cc
    style B4 fill:#fff4cc

Ключевые моменты

Блокировки

Таблицы

Обработка ошибок

Оптимизация запросов

Серии vs Standalone