Триаж и риск-движок
Двухуровневая фильтрация рисков, маршрутизация агентов и финальное решение: автоответ или остановка на одобрении оператора.
Каждое входящее сообщение проходит один и тот же триаж-пайплайн. Цель — классифицировать риск, выбрать подходящего агента, обосновать ответ в базах знаний и отправлять автоматически только при зелёном вердикте.
Компоненты
TriageOrchestrator.processIncoming(ticket)— управляет всем потоком.Router.pick(category, requiredSkills)— выбирает лучшего включённого агента.AgentService.executeAgent(agent, turns, { sources })— собирает промпт и стримит черновик.RiskAssessor— Layer 1 детерминированные правила и Layer 2 LLM-классификатор.
Поток сообщения
IncomingMessage
→ сохранить сообщение + upsert тикета
→ TriageOrchestrator.processIncoming
├── RiskAssessor.layer1(text) → rules + level
├── RiskAssessor.layer2(text) → { category, risk, confidence,
│ reasoning, requiredSkills }
│ skipped если layer1 = red
├── Router.pick(category, requiredSkills) → Agent
├── если agent.knowledgeBaseIds.length > 0:
│ RAG.search(...) → SourceSnippet[]
├── AgentService.executeAgent(agent, turns, { sources })
│ → собрать промпт (system + language + sources + turn)
│ → runner.generate(stream) → draft
├── сохранить draft агента + risk_assessment
└── решение по finalLevel:
├── green → отправить в канал, status = auto_replied
└── yellow | red → status = waiting_operator
→ operator Inbox → approve / edit / rejectLayer 1 — детерминированные правила
Layer 1 — быстрый rule engine, работает менее 10 мс. Каждое правило — чистая
функция (text: string) => { triggered, level, reason }. Правила загружаются из
таблицы risk_rules при старте и на riskRules:update.
Встроенные категории:
| Категория | Уровень | Примеры |
|---|---|---|
pii | yellow | email, международный телефон |
credentials | red | sk-…, xoxb-…, JWT, PEM, кредитная карта (Luhn) |
destructive | red | drop table, rm -rf, "удалить всё" |
destructive | yellow | disable, "отключить", "забанить" |
financial | red | refund, lawsuit, "возврат денег" |
financial | yellow | price, billing, "цена" |
access | red | grant access, make admin, "выдать права" |
volume | yellow | сообщение более 5000 символов, более 10 URL |
profanity | yellow | лёгкий tone screen |
Типы правил:
regex— строка RegExp.keywords— comma-separated список, case-insensitive, word-boundary.builtin— идентификатор встроенного checker (luhn_credit_card,oversize_message,excess_urls,profanity_lite).
Результат Layer 1:
{ level: 'green' | 'yellow' | 'red', triggeredRules: [...] }Уровень — максимум из сработавших правил. Нет правил → green, и Layer 2 вызывается.
Layer 2 — LLM-классификатор
Если Layer 1 не red, маленькая classifier-модель (по умолчанию qwen3-0.6b-q4)
через grammar выдаёт структурированный JSON-вердикт:
type LLMRiskVerdict = {
category: 'coding' | 'business' | 'marketing' | 'seo' | 'general' | 'other'
risk: 'green' | 'yellow' | 'red'
confidence: number // 0..1
reasoning: string // одна строка для аудита
}Промпт классификатора хранится в risk_rules_config.llm_prompt и редактируется
в Settings → Risk & Triage.
Маршрутизация
Router.pick оценивает включённых агентов:
agent.enabled === true.- Точное совпадение роли (
agent.role === category) илиagent.role === 'general'как fallback. - Наибольшее пересечение
agent.skillsсrequiredSkills. - Тай-брейк по
last_used_at ASC(round-robin).
Финальный вердикт и решение
finalLevel = max(L1.level, L2.risk)
finalCategory = L2.category ?? 'general'green— ответ уходит автоматически.yellowилиred— ответ становится тикетом в Inbox оператора. Человек одобряет, редактирует или отклоняет перед отправкой в канал.
Аудит
Каждый вердикт сохраняется в risk_assessments:
layer1_result— полный JSON Layer 1.layer2_result— полный JSON Layer 2, илиnull, если пропущен.final_level,final_category.created_at.
В desktop-приложении откройте тикет в Inbox и раскройте секцию Assessment, чтобы увидеть raw-вердикт.
Конфигурация
Операторы управляют рисками в Settings → Risk & Triage:
- Добавляйте, редактируйте, включайте и отключайте правила с live preview.
- Редактируйте промпт Layer 2 классификатора.
- Встроенные правила помечены
is_builtin=true— можно отключить, но нельзя удалить.