botruntime
Разработка

Действия и триггеры

Action — переиспользуемое типизированное бизнес-действие. Trigger — подписка на событие. Как объявить, чем действие отличается от инструмента и когда что выбирать.

Действие (Action) — именованная бизнес-операция с типизированным входом и выходом. Вы вызываете её из кода бота. Или отдаёте агенту как инструмент.

Триггер (Trigger) — подписка на событие. Срабатывает сама, когда происходит именованное событие: старт бота, новое сообщение, событие интеграции или ваш собственный сигнал.

Инструмент агент зовёт сам внутри цикла рассуждения. Действие и триггер — про детерминированную логику. Действие вы запускаете явно. Триггер платформа запускает за вас по событию. Все три живут в src/ рядом с обработчиком диалога и попадают в бандл при сборке.

Действие

Объявите действие через new Action({ name, input, output, handler }). Схемы input и output — zod. Платформа валидирует данные на входе и на выходе обработчика. Значит, действие нельзя вызвать с кривыми аргументами или молча вернуть не тот результат.

src/actions/index.ts
import { Action, z } from '@holocronlab/botruntime-runtime'

export default new Action({
  name: 'listPrograms',
  title: 'Список программ',
  description: 'Возвращает активные программы, опционально по категории.',
  input: z.object({
    category: z.string().optional(),
  }),
  output: z.object({
    programs: z.array(
      z.object({ id: z.string(), title: z.string() }),
    ),
  }),
  handler: async ({ input }) => {
    const rows = await ProgramsTable.findRows({
      filter: input.category ? { category: input.category } : undefined,
      orderBy: 'title',
    })
    return {
      programs: rows.rows.map((r) => ({ id: r.id, title: r.title })),
    }
  },
})

handler получает { input, client }. input — уже провалидированный объект. client — сырой клиент API-бэкенда: таблицы, файлы, сообщения. Возвращаемое значение обязано соответствовать схеме output.

Поля конструктора:

ПолеТипОбяз.Назначение
namestringдаИдентификатор действия; только буквы и цифры, начинается с буквы
inputzod-схемадаВалидация аргументов
outputzod-схемадаВалидация результата
handlerasync ({ input, client }) => outputдаТело действия
titlestringнетЧеловекочитаемое имя
descriptionstringнетОписание; становится описанием инструмента при asTool()
cachedbooleanнетКэшировать результат по входу (по умолчанию false)

Имя действия обязано матчить /^[a-zA-Z][a-zA-Z0-9]*$/: только буквы и цифры, без подчёркиваний и дефисов, первый символ буква. На невалидном имени конструктор бросает ошибку. Поэтому операцию вроде prescreen_estimate действием не оформить — её делают инструментом (см. ниже).

Действие или инструмент

Действие и инструмент решают разные задачи. Хотя одно превращается в другое.

  • Инструмент (Autonomous.Tool) — то, что агент решает вызвать сам внутри execute(...). Имя допускает snake_case. Инструмент обычно контекстно-зависим: нужны conversation, state.
  • Действие (Action) — переиспользуемая операция, которую вы запускаете явно из кода. Имя строго alphanumeric. Логика самодостаточна и не зависит от текущего диалога.

Любое действие превращается в инструмент методом asTool(). Отдаёте его агенту тем же списком tools:

src/conversations/index.ts
import { Conversation } from '@holocronlab/botruntime-runtime'
import listPrograms from '../actions'

export default new Conversation({
  channel: '*',
  handler: async ({ execute }) => {
    await execute({
      instructions: 'Помоги подобрать программу. Список бери инструментом.',
      tools: [listPrograms.asTool()],
    })
  },
})

asTool() берёт name, input, output действия и его description как подсказку модели. Описание можно переопределить: asTool({ description: '…' }). Обратно инструмент в действие не конвертируется — направление одно.

Правило имени решает и выбор примитива. Нужны подчёркивания (snake_case, как любят LLM-инструменты) — берите инструмент. Операция чисто серверная, переиспользуемая, без диалога — берите действие. Понадобилось агенту — оборачиваете в asTool().

Вызов действия из кода

Кроме asTool(), действие доступно по имени через рантайм-глобал actions. Из обработчика диалога, из воркфлоу, из другого действия:

src/workflows/index.ts
import { actions } from '@holocronlab/botruntime-runtime'

const { programs } = await actions.listPrograms({ category: 'training' })

actions.<name>(input) валидирует вход, выполняет handler, валидирует выход. У той же ссылки есть actions.<name>.asTool() — короткий путь получить инструмент без импорта модуля действия.

Цены, лимиты и сроки рассчитывает код действий и инструментов. Модель может объяснить результат, но не должна подменять расчёт.

Триггер

Объявите триггер через new Trigger({ name, events, handler }). Он подписывается на список именованных событий. Запускается автоматически, когда любое из них происходит — без агента и без вызова из вашего кода.

src/triggers/index.ts
import { Trigger } from '@holocronlab/botruntime-runtime'

export default new Trigger({
  name: 'onRegister',
  description: 'Инициализирует состояние при старте бота.',
  events: ['register'],
  handler: async ({ event }) => {
    console.log('Бот запущен, событие:', event.type)
  },
})

handler получает { event } — совпавшее событие с полями id, type, payload, status, createdAt. Для событий диалога добавляются conversationId и userId. Обработчик ничего не возвращает (Promise<void>).

Поля конструктора:

ПолеТипОбяз.Назначение
namestringдаИмя триггера; 3–255 символов, /^[A-Za-z0-9_]+$/
eventsstring[]даИмена событий-источников
handlerasync ({ event }) => voidдаРеакция на событие
descriptionstringнетОписание, до 1024 символов

Имя триггера допускает подчёркивания (/^[A-Za-z0-9_]+$/, 3–255 символов) — в имени действия их нельзя. Событие register особое: оно срабатывает один раз при старте бота. Поэтому триггер на register — обычное место для инициализации состояния.

Источники событий

Триггер подписывается на события трёх происхождений. Имя события — просто строка в массиве events.

Жизненный цикл бота — генерирует платформа:

  • register — старт бота (однократно при инициализации).
  • message.created — пришло новое сообщение.
  • conversation.started / conversation.ended — диалог начался или завершился.
  • user.created — появился новый пользователь.
  • workflow.started / workflow.completed / workflow.failed — переходы состояния воркфлоу.

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

src/triggers/index.ts
import { Trigger } from '@holocronlab/botruntime-runtime'

export default new Trigger({
  name: 'reactToWebchatStart',
  events: ['webchat:conversationStarted', 'slack:reactionAdded'],
  handler: async ({ event }) => {
    console.log('Событие интеграции:', event.type, event.conversationId)
  },
})

Набор событий у каждой интеграции свой — он объявлен в её манифесте. Про модель интеграций и их установку — Модель интеграций.

Свои события — объявите их в agent.config.ts со схемой, затем подпишитесь по имени:

agent.config.ts
import { defineConfig, z } from '@holocronlab/botruntime-runtime'

export default defineConfig({
  name: 'my-bot',
  events: {
    paymentConfirmed: {
      description: 'Платёж подтверждён провайдером',
      schema: z.object({ orderId: z.string(), amount: z.number() }),
    },
  },
})
src/triggers/index.ts
import { Trigger } from '@holocronlab/botruntime-runtime'

export default new Trigger({
  name: 'onPayment',
  events: ['paymentConfirmed'],
  handler: async ({ event }) => {
    // event.payload типизирован схемой из agent.config.ts
  },
})

Когда что выбирать

Три примитива закрывают три сценария. Держите их разделёнными.

НужноБеритеКто запускает
Логику вызовет агент по ходу диалогаИнструмент (Autonomous.Tool / action.asTool())Модель внутри execute(...)
Переиспользуемую операцию, вызываемую явно из кодаДействие (Action)Ваш код (actions.<name>) или агент через asTool()
Реакцию на событие без участия агентаТриггер (Trigger)Платформа по событию

Маршрут простой. Чистая серверная операция — действие. Понадобилось звать её агенту — оборачиваете в asTool(). Нужна автоматическая реакция на факт (регистрация, сообщение, вебхук интеграции, свой сигнал) — триггер. Долгий многошаговый процесс с таймерами и ретраями — это уже воркфлоу, а не триггер.

Дальше

On this page