Référence

Triage et moteur de risque

Filtrage de risque à deux niveaux, routage des agents et décision finale qui envoie automatiquement ou bloque une réponse pour approbation humaine.

Chaque message entrant traverse le même pipeline de triage. L'objectif est de classer le risque, router vers le bon agent, ancrer la réponse dans les bases de connaissances, et n'envoyer automatiquement que quand le verdict est vert.

Composants

  • TriageOrchestrator.processIncoming(ticket) — pilote tout le flux.
  • Router.pick(category, requiredSkills) — choisit le meilleur agent activé.
  • AgentService.executeAgent(agent, turns, { sources }) — construit le prompt et stream le brouillon.
  • RiskAssessor — règles déterministes de niveau 1 et classifieur LLM de niveau 2.

Flux de message

IncomingMessage
  → persister le message + upsert ticket
  → TriageOrchestrator.processIncoming
      ├── RiskAssessor.layer1(text)    → règles + niveau
      ├── RiskAssessor.layer2(text)    → { category, risk, confidence,
      │                                  reasoning, requiredSkills }
      │     sauté si layer1 = red
      ├── Router.pick(category, requiredSkills) → Agent
      ├── si agent.knowledgeBaseIds.length > 0 :
      │     RAG.search(...) → SourceSnippet[]
      ├── AgentService.executeAgent(agent, turns, { sources })
      │     → construire prompt (system + langue + sources + tour)
      │     → runner.generate(stream) → brouillon
      ├── sauvegarder brouillon agent + risk_assessment
      └── décider par finalLevel :
          ├── vert  → envoyer au canal, statut = auto_replied
          └── jaune | rouge → statut = waiting_operator
                               → Boîte de réception opérateur → approuver / modifier / rejeter

Niveau 1 — règles déterministes

Le niveau 1 est un moteur de règles rapide qui s'exécute en moins de 10 ms. Chaque règle est une fonction pure qui renvoie { triggered, level, reason }. Les règles vivent dans la table risk_rules ; les règles intégrées sont chargées au démarrage et ne peuvent pas être supprimées, seulement désactivées.

Catégories intégrées :

CatégorieNiveauExemples
piijauneemail, téléphone international
credentialsrougesk-…, xoxb-…, JWT, PEM, carte de crédit (Luhn)
destructiverougedrop table, rm -rf, « удалить всё »
destructivejaunedisable, « отключить », « забанить »
financialrougerefund, procès, « возврат денег »
financialjauneprice, billing, « цена »
accessrougegrant access, make admin, « выдать права »
volumejaunemessage de plus de 5000 caractères, plus de 10 URLs
profanityjaunefiltre de tonalité léger

Types de règles :

  • regex — une chaîne RegExp.
  • keywords — liste séparée par des virgules, insensible à la casse, correspondance par limite de mot.
  • builtin — identifiant d'un vérificateur intégré (luhn_credit_card, oversize_message, excess_urls, profanity_lite).

Résultat du niveau 1 :

{ level: 'green' | 'yellow' | 'red', triggeredRules: [...] }

Le niveau est le maximum des règles déclenchées. Pas de règles → vert, et le niveau 2 est invoqué.

Niveau 2 — classifieur LLM

Si le niveau 1 n'est pas rouge, un petit modèle classifieur (défaut qwen3-0.6b-q4) produit un verdict JSON structuré via grammaire :

type LLMRiskVerdict = {
  category: 'coding' | 'business' | 'marketing' | 'seo' | 'general' | 'other'
  risk: 'green' | 'yellow' | 'red'
  confidence: number        // 0..1
  reasoning: string         // audit en une ligne
}

Le prompt du classifieur est stocké dans risk_rules_config.llm_prompt et peut être édité dans Paramètres → Risque & Triage.

Routage

Router.pick score les agents activés par :

  1. agent.enabled === true.
  2. Correspondance exacte de rôle (agent.role === category) ou agent.role === 'general' en fallback.
  3. Plus grande intersection de compétences avec requiredSkills.
  4. Tie-break par last_used_at ASC (round-robin).

Verdict final et décision

finalLevel = max(L1.level, L2.risk)
finalCategory = L2.category ?? firstFiredRule.category ?? 'general'
  • green — la réponse est envoyée automatiquement.
  • yellow ou red — la réponse devient un ticket dans la Boîte de réception opérateur. Un humain peut approuver, modifier ou rejeter avant qu'elle n'atteigne le canal.

Audit

Chaque verdict est sauvegardé dans risk_assessments :

  • layer1_result — JSON complet du niveau 1.
  • layer2_result — JSON complet du niveau 2, ou null s'il a été sauté.
  • final_level, final_category.
  • created_at.

Dans l'application de bureau, ouvrez un ticket dans la Boîte de réception et dépliez la section Assessment pour inspecter le verdict brut.

Configuration

Les opérateurs gèrent le risque dans Paramètres → Risque & Triage :

  • Ajouter, modifier, activer ou désactiver des règles avec un aperçu en direct.
  • Modifier le prompt du classifieur de niveau 2.
  • Voir les règles intégrées (désactivation autorisée, suppression bloquée).