IA (ai-kit)

Référence technique du domaine IA — providers (Scaleway/Gemini), workflows OCR/enrichissement, observabilité coûts et budget mensuel.

IA (ai-kit)

Module transverse qui sert les autres domaines : OCR factures fournisseur (Achats), enrichissement prospects terrain (CRM ciblage), OCR plaque signalétique constructeur (Assets), cotation CERFA (Rating). Il ne possède pas d'écran métier propre, mais expose une route d'observabilité coûts et un endpoint dev pour déclencher des workflows.

Les routes API vivent sous /api/workspace/ai/*. Source de cette page : docs/17-domain-ai-kit.md et le code sous server/lib/ai/.

Vocabulaire & entités

TermeEntité / typeDéfinition
ProviderAIProviderAdaptateur d'appel IA (scaleway par défaut, anthropic en alt).
CompletionAICompletionRequest / AICompletionResultUnité d'appel : prompt + options → texte + tokens + coût.
AttachmentAIAttachmentPièce jointe multimodale (image ou PDF base64) transmise au modèle vision.
Workflowstring (clé)Identifiant fonctionnel du pipeline (invoice-ocr-extract, prospect-enrich…).
CostEventAICostEvent (Prisma)Ligne d'audit par appel : provider, modèle, tokens, coût USD, statut, durée.
ModeAI_PROVIDER_MODEfixtures (défaut dev) ou live. En fixtures : réponses canned, aucun crédit consommé.

Le modèle AICostEvent est dans docs/04-data-model.md §13. Les DTOs Zod sont dans shared/dto/ai.dto.ts.

Écrans

ÉcranRouteContenu
Coûts IA/workspace/ai/costsListe paginée des AICostEvent : workflow, provider, modèle, tokens, coût USD, statut.
Agrégats mensuelsvia endpointKPIs par workflow : nb appels, coût total/moyen, tokens in/out.

Pas d'écran admin dédié implémenté à ce stade (stub dans la doc cible docs/17-).

Modèle de données (points clés)

  • AICostEvent : enregistré après chaque appel IA (succès et erreur). Champ status vaut success ou error ; errorMessage non-null en cas d'échec.
  • costUsd est un Decimal Prisma — converti en number à la sérialisation (Number(e.costUsd)).
  • triggeredByUserId est nullable (jobs batch déclenchés sans session user).
  • Multi-tenant strict : organizationId toujours présent, jamais lu depuis le body.

API

Les deux routes workspace exigent admin.audit:read.

MéthodeRoutePermission
GET/api/workspace/ai/costsadmin.audit:read
GET/api/workspace/ai/costs/monthlyadmin.audit:read
POST/api/dev/ai/trigger-workflowdev uniquement (route non protégée)
GET/api/workspace/ai/costsAuth

Liste paginée des événements de coût IA pour l'organisation courante.

Query string

page
number
Numéro de page (défaut : 1).
pageSize
number
Taille de page (défaut : 20).
workflow
string
Filtre par clé de workflow (invoice-ocr-extract, prospect-enrich…).
yearMonth
string
Filtre par mois au format YYYY-MM.

Requête

curl -s "$API/api/workspace/ai/costs?yearMonth=2026-06&workflow=invoice-ocr-extract" \
  -H "Cookie: $SESSION"

Réponse

{
  "items": [{
    "id": "cst_…",
    "workflow": "invoice-ocr-extract",
    "provider": "scaleway",
    "model": "pixtral-12b-2409",
    "inputTokens": 800,
    "outputTokens": 90,
    "costUsd": 0.0009,
    "status": "success",
    "durationMs": 1420,
    "createdAt": "2026-06-10T14:32:00.000Z"
  }],
  "page": 1,
  "pageSize": 20,
  "total": 1
}

Workflows implémentés

invoice-ocr-extract — OCR facture fournisseur

Fichier : server/lib/ai/workflows/invoice-ocr-extract.ts

PDF facture → pdfToImageAttachments() → pixtral (vision)
          → parseAiJson() → InvoiceOcrExtractResult → AICostEvent
  1. Le PDF est rastérisé en images avant l'appel (pdf-to-images.ts) car pixtral n'ingère pas un PDF brut — repli sur PDF base64 si le rendu échoue.
  2. Appel AIProvider.completion() avec attachments (multimodal).
  3. Extraction JSON : vendor, vendorSiret, invoiceNumber, issueDate, dueDate, totalHt, totalTtc.
  4. AICostEvent créé dans tous les cas (succès ou erreur).

plate-ocr-extract — OCR plaque signalétique constructeur

Fichier : server/lib/ai/workflows/plate-ocr-extract.ts

Photo d'une plaque → provider vision → serialNumber, manufacturer, model, voltage, refrigerant, power, dimensions. Même pattern que invoice-ocr-extract (image base64, pas de rasterisation PDF).

prospect-enrich — Enrichissement Gemini grounded

Fichier : server/lib/ai/workflows/prospect-enrich.ts

nom + adresse → PII masking → Gemini + googleSearch (étape 1 : texte libre)
             → Gemini structured output (étape 2 : JSON)
             → ProspectEnrichmentOutput → AICostEvent (provider: 'google')
  • Deux temps obligatoires : Gemini interdit tool use + sortie structurée JSON dans le même appel (validé live).
  • Vérification budget IA avant tout appel (isAiBudgetExhausted) ; lève AiBudgetExhaustedError si le seuil est atteint.
  • Cache 15 j sur l'appel (cachedCall('gemini-enrich', …)).
  • Sortie : siret, siren, emails[], openingHours, socialNetworks, description, confidence (0-100).
  • Requiert GOOGLE_GENERATIVE_AI_API_KEY en mode live.

call-summary, lead-enrichment, rating-cerfa-extract

Présents en fixtures déterministes (server/lib/ai/providers/fixtures.ts). Workflows live non encore implémentés pour ces cas (stub spec docs/17-).

Providers

Scaleway (défaut, AI_PROVIDER=scaleway)

Fichier : server/lib/ai/providers/scaleway/index.ts

  • Modèle texte : gpt-oss-120b (variable SCALEWAY_MODEL).
  • Modèle vision : pixtral-12b-2409 (variable SCALEWAY_VISION_MODEL) — activé automatiquement si attachments présents dans la requête.
  • Utilise @ai_kit/coreAgent + scaleway(model) en mode live.
  • Requiert SCALEWAY_API_KEY.

Anthropic (alternatif, AI_PROVIDER=anthropic)

Fichier : server/lib/ai/providers/anthropic/index.ts

  • Utilise l'API Anthropic avec le modèle configuré.

Gemini (enrichissement ciblage uniquement)

Utilisé exclusivement par prospect-enrich via @ai_kit/core + googleSearch. Ne passe pas par le registry de providers — appelé directement dans runtime.ts.

Mode fixtures (défaut dev)

AI_PROVIDER_MODE absent ou différent de live → réponses canned dans server/lib/ai/providers/fixtures.ts. Aucun crédit consommé, latence simulée 20 ms. AICostEvent créé quand même (coût calculé via tarif Anthropic fixtures).

Observabilité

  • Langfuse : traceCompletion() dans server/lib/ai/observability/langfuse.ts. No-op silencieux si LANGFUSE_PUBLIC_KEY / LANGFUSE_SECRET_KEY absents. SDK non encore installé (stub Phase 12+). Debug activé via LANGFUSE_DEBUG=true.
  • AICostEvent : trace permanente en base (persist même en cas d'erreur).

Règles métier

  • Budget mensuel : seuil AI_MONTHLY_BUDGET_USD (défaut 50 USD). Si dépassé :
    • checkBudgetAlert() crée une notification GENERIC pour tous les admins de l'org avec lien /workspace/ai/costs.
    • isAiBudgetExhausted() (sans effet de bord) : verrou préventif avant appel dans les workflows batch (ex. prospect-enrich).
  • Multi-tenant strict : organizationId toujours issu de la session, jamais du body.
  • PII masking : maskPii() appliqué sur le prompt avant tout envoi externe (prospect-enrich). Emails, téléphones, noms masqués si non nécessaires.
  • Décisions automatisées : aucune décision impactant un client (refus, suspension) sans validation humaine — toute sortie IA est présentée pour confirmation.
  • Hallucinations : les sorties IA sont présentées à l'utilisateur pour validation avant persistance (OCR facture, plaque, enrichissement).

Variables d'environnement

VariableDéfautUsage
AI_PROVIDERscalewayProvider actif : scaleway | anthropic.
AI_PROVIDER_MODE(absent = fixtures)live pour activer les vrais appels.
SCALEWAY_API_KEYRequis en mode live Scaleway.
SCALEWAY_MODELgpt-oss-120bModèle texte Scaleway.
SCALEWAY_VISION_MODELpixtral-12b-2409Modèle vision Scaleway.
GOOGLE_GENERATIVE_AI_API_KEYRequis pour prospect-enrich live (Gemini).
GEMINI_MODELgemini-2.5-flashModèle Gemini.
AI_MONTHLY_BUDGET_USD50Seuil alerte/blocage budget mensuel.
LANGFUSE_PUBLIC_KEY / LANGFUSE_SECRET_KEYActive les traces Langfuse.