Источник:
smarty-code/smarty-backend-stable/smarty-dialog/docs/API.md
Справочник по HTTP-эндпоинтам, RMQ-событиям, tool-calling интерфейсу и кодам ошибок подсистемы AI-ботов Smarty CRM.
Документация актуальна на 2026-06-13.
Все маршруты — под префиксом /dialogs (определяется routes/dialog.js). Авторизация через middleware Restify (cookie/token + workspace scope).
routes/dialog.js)| Метод | Путь | Назначение | Вход | Выход |
|---|---|---|---|---|
POST |
/dialogs |
Создать диалог | body: { employeeIds[], type?, title?, baseObject? } |
DialogConfiguration object |
GET |
/dialogs |
Список диалогов текущего пользователя | query params (pagination) | DialogConfiguration[] |
GET |
/dialogs/:id |
Получить конфигурацию диалога | path: id = dialogConfId |
DialogConfiguration object |
PUT |
/dialogs/:id |
Обновить диалог (title, imageSrc, type) | path: id, body: fields |
DialogConfiguration object |
DELETE |
/dialogs/:id |
Архивировать диалог | path: id |
empty |
GET |
/dialogs/:id/attaches |
Список прикреплённых объектов | path: id |
Attach[] |
GET |
/dialogs/:id/meeting |
Получить Zoom meeting link | path: id |
{ joinUrl, meetingId } |
Валидация:
employeeIds — массив ObjectId, проверяется по модели Employee.type: individual (только 2 участника), private, public.baseObject: { collectionName, _id } — привязка к объекту CRM.routes/dialog_messages.js)| Метод | Путь | Назначение | Вход | Выход |
|---|---|---|---|---|
GET |
/dialogs/:id/messages |
Список сообщений диалога | query: pagination, filters | DialogMessage[] |
POST |
/dialogs/:id/messages |
Отправить сообщение | body: { content, mentions?, contentType? } |
DialogMessage object |
GET |
/dialogs/:id/messages/:msgId |
Получить сообщение | path: ids | DialogMessage object |
POST |
/dialogs/:id/messages/track |
Отследить прочтение сообщений | body: { messageIds[] } |
empty |
POST |
/dialogs/:id/messages/:msgId/edit |
Пометить как «редактируемое» | path: msgId | DialogMessage object |
POST |
/dialogs/:id/messages/:msgId/update-content |
Редактировать текст сообщения | body: { content } |
DialogMessage object |
Валидация:
content — строка, max 12000 символов, non-empty after trim.manage_access).softDeletedBy — удалённые сообщения не редактируются.routes/dialog_participants.js)| Метод | Путь | Назначение | Вход | Выход |
|---|---|---|---|---|
POST |
/dialogs/:id/participants |
Добавить участников | body: { employeeIds[] } |
empty |
DELETE |
/dialogs/:id/participants/:empId |
Удалить участника | path: empId | empty |
Валидация: employeeIds — массив ObjectId Employee, required.
routes/hook.js)| Метод | Путь | Назначение | Вход | Выход |
|---|---|---|---|---|
POST |
/dialogs/hook/:token |
Отправить сообщение по webhook-токену | path: token, body: { content } |
empty |
Публикует событие dialogHook в RMQ (system-events queue). Авторизация по токену в URL.
dialog-eventsВсе обработчики — в events/index.js, реализованы через HandlersMap.
| Событие | Продюсер | Обработчик | Payload | Эффект |
|---|---|---|---|---|
newChatMessage |
smarty-api | newChatMessage.js |
{ _dialogId, _dialogConfId, message } |
Push-уведомления, запуск ботов (Responder.handleMessageForBot) |
dialogArchive |
smarty-api | DialogArchive.js |
{ dialogConfId } |
Архивация, cleanup |
dialogRead |
smarty-api | DialogRead.js |
{ dialogConfId, employeeId } |
Отметка прочтения, clearMentions |
dialogExternalModify |
ext-chat | DialogExternalModify.js |
{ dialogId, ... } |
Синхронизация внешних изменений |
dialogAdminsChanged |
smarty-api | DialogAdminsChanged.js |
{ dialogConfId } |
Обновление кэша админов |
dialogParticipantsChanged |
smarty-api | DialogParticipantsChanged.js |
{ dialogConfId } |
Обновление participantsCache |
dialogMetaChanged |
smarty-api | DialogMetaChanged.js |
{ dialogConfId } |
Обновление метаданных |
dialogTypeChanged |
smarty-api | DialogMetaChanged.js |
{ dialogConfId, newType } |
Изменение типа диалога |
dialogReminder |
cron | dialogReminder.js |
{ dialogId, ... } |
Напоминание в диалог |
sendSystemDialogMessage |
internal | sendSystemDialogMessage.js |
{ dialogId, content, systemType } |
Системное сообщение |
sipcallFinishedMessage |
sip | sipcallFinishedMessage.js |
{ dialogId, ... } |
Сообщение о завершении звонка |
botActionTriggered |
frontend | botActionTriggered.js |
{ messageId, actionId, decision } |
Обработка approve/deny кнопки |
agent-task-eventsОбработчики — в lib/employeeBots/agentTaskWorker.js.
| Событие | Продюсер | Payload | Эффект |
|---|---|---|---|
agent.task.queued |
spawn_dialog_subagent tool |
{ taskId } |
Запуск runSpecialistAgent или runInlineSubagent |
agent.task.message_in_dialog |
Responder.handleNewMessage |
{ taskId?, dialogId } |
Пробуждение субагента по новому сообщению |
agent.task.tick |
cron (30 сек) / external | {} |
Проверка deadline → force-finalize просроченных |
Инварианты:
findOneAndUpdate claim с status filter).unref() — не блокирует graceful shutdown.botWorkerEvents.js)| Событие | Очередь | Payload | Эффект |
|---|---|---|---|
crm-trigger |
RMQ queue | { wsId, event: { event, instanceId, collectionName, triggerId, ... } } |
Обработка триггера: idempotency check → fire-and-forget agentLoop |
Двухфазная idempotency:
SET NX EX 30 (inflight).tools/definitions.js)Каждый инструмент — объект:
{
name: 'create_contact', // строка, уникальна
nameRu: 'Создать контакт', // RU-название для UI
requiredPermission: 'create_contacts', // permission scope (permissionChecker)
sideEffect: 'destructive', // optional: 'external' | 'destructive'
definition: {
type: 'function',
function: {
name: 'create_contact',
description: '...', // описание для LLM
parameters: {
type: 'object',
properties: { /* JSON Schema */ },
required: ['fullName', 'groupId']
}
}
}
}
Контракт getToolsForBot(bot):
TOOLS из definitions.js.permissionChecker.hasPermission(bot, tool.requiredPermission).use_skill — дополнительно проверяет наличие активных skills.search_knowledge_base — pseudo-permission use_knowledge_base (grant если KB настроена).call_subagent — доступен если botSettings.subagents non-empty и provider ≠ smarty_custom.spawn_dialog_subagent — доступен если !botSettings.disableSubagentSpawn и provider ≠ smarty_custom.Кэширование: toolLoader.getCachedToolsForBot — Redis + in-process Map, TTL 5 мин. Инвалидация при обновлении бота.
tools/executor.js)execute(bot, toolCall, context) — основная функция:
JSON.parse(toolCall.function.arguments).({24hex|Имя}) → 24hex в аргументах.[имя], X руб, ___, TBD → reject.sideEffect → highImpactGate.decide() → allow/warn/block.{ status: 'DONE', data: {...} } или { error: '...' }.Формат результата:
// Успех:
{ status: 'DONE', result: { /* данные */ }, data: { /* данные */ } }
// Уже выполнено (idempotent):
{ status: 'ALREADY_DONE', result: { /* данные */ } }
// Ошибка:
{ error: 'Описание ошибки для LLM' }
// Terminal tool (субагенты):
{ status: 'DONE', final: true, data: { /* данные */ } }
_wsIdВсе tool handlers проверяют _wsId scope:
{ _wsId: bot._wsId } в Mongo-запросах._wsId ставится автоматически из bot context.create_external_dialog, negotiate_with_suppliers): явная проверка targetWsId.Для tools с sideEffect: 'external' или 'destructive':
Режим env (SMARTY_HIGH_IMPACT_GATE) |
Поведение |
|---|---|
off |
Пропуск |
warn (default) |
Warn-лог, пропуск |
enforce |
Проверка botSettings.highImpactAllowlist.includes(toolName) → block если нет |
Помеченные tools:
external: negotiate_with_suppliers, record_supplier_response, make_decision, send_message_to_dialog, create_external_dialog, generate_docxdestructive: delete_group, delete_object, remove_responsibles, remove_subscribers, unlink_objectssubagentTools.js)Изолированный набор для dialog-субагентов (НЕ доступен обычным ботам):
| Tool | Назначение | Побочные эффекты |
|---|---|---|
send_message_in_dialog |
Отправка сообщения в assigneeDialogId | Создание DialogMessage |
record_finding |
Запись данных в task.payload._findings |
Обновление AgentTask |
finalize_subagent_dialog |
Terminal: финальное сообщение + callback + статус | DialogMessage + AgentTask update + system-message в orchestrator |
Контракт finalize_subagent_dialog:
Вход: { result: object, finalText: string, failureReason?: string, mentions?: [{employeeId, name}] }
Выход: { status: 'DONE', final: true }
Эффекты:
1. Отправляет finalText в assigneeDialogId
2. Обновляет AgentTask: status='done'|'failed', result, failureReason
3. Постит system-message в orchestratorDialogId с meta.subagentTaskCompleted
smarty_customПользовательский LLM-эндпоинт, настраиваемый per-bot через UI. Два режима:
| Режим | Флаг | Описание |
|---|---|---|
| OpenAI-совместимый | openaiCompatMode: true (default) |
Запрос/ответ по OpenAI /chat/completions схеме. Tools pass through. |
| Raw template | openaiCompatMode: false |
Кастомный body с {{placeholders}}, ответ парсится по customResponseJsonPath. |
| Поле | Тип | Описание |
|---|---|---|
provider |
'smarty_custom' |
Провайдер |
customProviderEndpoint |
string (URL) | URL эндпоинта. Обязательно. SSRF-guard. |
customProviderHeaders |
[{key, value}] |
Доп. HTTP-заголовки |
openaiCompatMode |
boolean | Режим (default: true) |
customBodyTemplate |
string | Raw-mode body с {{placeholders}} |
customResponseJsonPath |
string | Raw-mode JSON path для извлечения ответа (default: choices[0].message.content) |
customConnectionToken |
string | Auto-generated sck_-prefixed токен |
forwardConnectionToken |
boolean | Пересылать ли X-API-Key (default: true) |
model, temperature, apiToken |
— | Наследуются от общих botSettings |
| Placeholder | Описание |
|---|---|
{{model}} |
Имя модели |
{{messages}} |
Полный массив сообщений (JSON) |
{{message}} |
Последнее user-сообщение (строка) |
{{system_prompt}} |
Конкатенация system-сообщений |
{{temperature}} |
Температура |
{{bot_id}} |
ID бота |
{{user_id}} |
ID текущего пользователя |
{{session_id}} |
ID сессии |
{{crm_context}} |
CRM-контекст (ws_id, employee_id, dialog_config_id, group_id, ...) |
OpenAI-совместимый режим:
POST {customProviderEndpoint}/chat/completions
Authorization: Bearer {apiToken}
Content-Type: application/json
{customProviderHeaders}
Body: OpenAI /v1/chat/completions schema (messages, model, tools, tool_choice, temperature)
Response: { choices: [{ message: { content, tool_calls }, finish_reason }], usage: { prompt_tokens, completion_tokens } }
Raw режим:
POST {customProviderEndpoint}
{customProviderHeaders}
Body: rendered customBodyTemplate
Response: parsed by customResponseJsonPath (e.g. "choices[0].message.content" → string)
providers/_aiErrors.js)Единый класс ошибок для всех LLM-провайдеров. Наследует Error.
class AIProviderError extends Error {
httpStatus // number | null — HTTP код ответа
code // string | null — машинный код провайдера
retryable // boolean — стоит ли retry
userMessage // string — RU текст для чата/UI
providerName // string — 'openai' / 'anthropic' / ...
isAIProviderError = true // маркер
}
Маппинг:
| HTTP Status | Code | retryable | userMessage (RU) |
|---|---|---|---|
| 429 | insufficient_quota |
false | «Закончились средства на счёте OpenAI...» |
| 429 | rate_limit_exceeded |
true | «Превышен лимит запросов к OpenAI...» |
| 429 | (other) | true | «Сервер ИИ временно отказывает...» |
| 401 | invalid_api_key |
false | «Ключ API провайдера ИИ недействителен...» |
| 403 | forbidden |
false | «Доступ к API провайдера ИИ запрещён...» |
| 404 | not_found |
false | «Указанная модель ИИ не найдена...» |
| 400 | context_length_exceeded |
false | «Запрос превысил контекстное окно модели...» |
| 400 | (other) | false | «Запрос к ИИ отклонён как некорректный...» |
| 5xx | (any) | true | «Сервер ИИ-провайдера ответил ошибкой 5xx...» |
| — | ETIMEDOUT / ECONNABORTED |
true | «Сервер ИИ не ответил вовремя...» |
| — | ECONNREFUSED / ENOTFOUND |
true | «Не удалось связаться с сервером ИИ...» |
| — | (other) | false | «Не удалось обратиться к ИИ-провайдеру...» |
| Ошибка | Код/условие | Формат ответа |
|---|---|---|
| Permission denied | hasPermission → false |
{ error: 'Bot "..." lacks permission: ...' } |
| Placeholder detected | [имя], X руб, ___ |
{ error: 'Placeholder detected: ...' } |
| High-impact blocked | highImpactGate enforce + not in allowlist |
{ error: 'High-impact tool ... blocked by policy' } |
| Chat-context gate | Dialog-only tool in extchat | { error: 'Tool ... is only available in internal/external dialogs' } |
| Embedded ref parse error | Invalid ({hex|name}) syntax |
{ error: 'Failed to parse arguments: ...' } |
| Ошибка | Условие | Поведение |
|---|---|---|
| MAX_ITERATIONS reached | 25 итераций | Принудительный final summary |
| Planner parse failure | Не удалось извлечь JSON из ответа planner | Fallback: empty plan (executor с tool_choice=auto) |
| Plan hard cap | >30 steps от planner | Truncation до 30 + warn-лог |
| Fabricated IDs | 24-hex в аргументах не в реестре | Block (enforce) / warn (warn mode) |
| Terminal tool | result.final === true |
Loop exit, _skipSave: true |
| Ошибка | Условие | Эффект |
|---|---|---|
maxRounds_exceeded |
agentLoop hit maxRounds | status='failed', failureReason='max_rounds_exceeded' |
| Timeout | deadline < now | Cron force-finalize: status='timeout', fallback message |
| Previous subagent pending | Субагент уже активен для этого dialogId | { error: 'previous_subagent_pending' } |
| Circuit breaker | 3+ consecutive failures с тем же idempotencyKey | { error: 'circuit_breaker_open' } |
| Ошибка | Условие | Эффект |
|---|---|---|
| KB not found | knowledgeBaseId не существует | Tool returns { error: 'Knowledge base not found' } |
| Embedding failed | Embedder API error | Logged, returns empty results |
| File too large | >5 MB | { error: 'File too large (max 5MB)' } |
| Unsupported format | Не PDF/DOCX/XLSX/CSV/TXT/MD/JSON/XML | { error: 'Unsupported file format' } |
| Провайдер | Ошибка | Код |
|---|---|---|
| OpenAI | Rate limit | 429 + rate_limit_exceeded |
| OpenAI | Quota exceeded | 429 + insufficient_quota |
| OpenAI | Context overflow | 400 + context_length_exceeded |
| Anthropic | Overloaded | 529 + overloaded_error |
| Anthropic | Rate limit | 429 |
| Resource exhausted | 429 | |
| smarty_custom | SSRF blocked | { error: 'URL blocked by ssrfGuard' } |
То, на что могут полагаться внешние потребители, хотя это не часть формального контракта.
| Поведение | Где | Риск |
|---|---|---|
response._debugLog — массив {tool, args, result} после agentLoop |
Responder.js | Внутренняя трассировка, может измениться |
response._plan — массив шагов planner |
agentLoop.js | Debug-only, не API |
response._generatedFileIds — fileId из tool результатов |
agentLoop.js | Используется для attach |
response._skipSave — terminal tool marker |
agentLoop.js | Контракт: Responder пропускает save |
response._terminalTool — {name, data} для terminal tools |
agentLoop.js | Контракт: используется для аудита |
| Bot trace steps order | BotTrace model | Порядок steps = порядок выполнения |
Session memory entries shape {role, content, author, meta} |
sessionMemory.js | Author format: {type, _id, name} |
status: 'DONE' / 'ALREADY_DONE' в tool response |
executor.js | LLM читает статус для навигации |
meta.subagentTaskCompleted в system-message |
subagentTools.js | Responder детектирует для callback |
Redis key pattern bot:session:{wsId}:{scopeType}:{scopeId} |
sessionMemory.js | Мониторинг может завязываться |
Redis key pattern bot:trigger:fired:{wsId}:... |
botWorkerEvents.js | Мониторинг TTL/key count |