Aller au contenu principal

AI Orchestration - Aaperture

Module C : Assistant IA pour rédaction, résumés, extraction champs, classification, reporting avec quotas et suivi des coûts.

Contexte

Assistant IA : rédaction, résumés, extraction champs, classification, reporting. Préserver latence, coûts, quotas.

Bounded Context (DDD)

AIOrchestration

  • Agrégats : AiTask, AiResult
  • Domain services : PromptFactory, AiPolicyService (quota/rate limiting)
  • Ports : LlmProviderPort (OpenAI), StoragePort

Schéma DB

ai_tasks

CREATE TABLE ai_tasks (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
user_id UUID NOT NULL,
type TEXT NOT NULL, -- WRITE_EMAIL|SUMMARIZE|EXTRACT|CLASSIFY|REPORT
status TEXT NOT NULL DEFAULT 'QUEUED', -- QUEUED|RUNNING|DONE|FAILED
input JSONB NOT NULL,
output JSONB,
model TEXT NOT NULL DEFAULT 'gpt-4o-mini', -- gpt-4o-mini|gpt-4o|gpt-4-turbo
tokens_in INT,
tokens_out INT,
cost_cents INT, -- Coût en centimes (ex: 150 = 1.50€)
error TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
finished_at TIMESTAMPTZ
);

CREATE INDEX idx_ai_tasks_org ON ai_tasks(org_id);
CREATE INDEX idx_ai_tasks_user ON ai_tasks(user_id);
CREATE INDEX idx_ai_tasks_status ON ai_tasks(status);
CREATE INDEX idx_ai_tasks_type ON ai_tasks(type);
CREATE INDEX idx_ai_tasks_created ON ai_tasks(created_at);

Jobs

ai:run (worker-ai)

Responsabilités :

  • Exécuter tâche IA via LlmProviderPort
  • Post-processing si nécessaire
  • Calculer coûts (tokens × prix modèle)
  • Persister résultat

Payload :

{
v: 1,
orgId: "uuid",
userId: "uuid",
entityId: "uuid", // ai_task.id
}

Processus :

  1. Charger ai_task depuis DB
  2. Vérifier quotas/rate limiting via AiPolicyService
  3. Construire prompt via PromptFactory selon type
  4. Appeler LlmProviderPort.complete(prompt, model)
  5. Post-processing selon type :
    • WRITE_EMAIL : Valider format email
    • EXTRACT : Valider avec Zod schema
    • SUMMARIZE : Limiter longueur
  6. Calculer coûts : tokens_in × price_in + tokens_out × price_out
  7. Persister output, tokens_in, tokens_out, cost_cents
  8. Mettre à jour status = 'DONE', finished_at = NOW()

Gestion erreurs :

  • Rate limit OpenAI → retry avec backoff (2 tentatives max)
  • Quota dépassé → status = 'FAILED', error = 'QUOTA_EXCEEDED'
  • Timeout → retry (2 tentatives max)

ai:postprocess (optionnel)

Responsabilités :

  • Post-processing lourd (ex: génération PDF depuis résultat)
  • Upload artefacts vers R2

Payload :

{
v: 1,
orgId: "uuid",
userId: "uuid",
entityId: "uuid", // ai_task.id
postprocessType: "GENERATE_PDF" | "UPLOAD_ARTIFACT",
}

Types de tâches

WRITE_EMAIL

Input :

{
recipient: string;
context: {
quoteId?: string;
invoiceId?: string;
sessionId?: string;
contactId?: string;
};
tone: "formal" | "friendly" | "professional";
purpose: string; // "reminder" | "follow-up" | "thank-you" | ...
}

Output :

{
subject: string;
body: string; // HTML
suggestions: string[]; // Suggestions d'amélioration
}

SUMMARIZE

Input :

{
content: string; // Texte à résumer
maxLength: number; // Longueur max du résumé
focus?: string; // Points clés à mettre en avant
}

Output :

{
summary: string;
keyPoints: string[];
}

EXTRACT

Input :

{
content: string; // Texte source
schema: "CONTRACT" | "INVOICE" | "QUESTIONNAIRE" | "CUSTOM";
customSchema?: ZodSchema; // Si CUSTOM
}

Output :

{
extracted: Record<string, any>; // Données extraites selon schema
confidence: number; // 0-1
warnings?: string[]; // Champs manquants ou incertains
}

CLASSIFY

Input :

{
content: string;
categories: string[]; // Catégories possibles
}

Output :

{
category: string;
confidence: number; // 0-1
reasoning?: string;
}

REPORT

Input :

{
data: Record<string, any>; // Données à analyser
reportType: "ANALYTICS" | "INSIGHTS" | "RECOMMENDATIONS";
timeframe?: string; // "last_month" | "last_quarter" | ...
}

Output :

{
report: {
summary: string;
insights: Array<{
title: string;
description: string;
impact: "high" | "medium" | "low";
}>;
recommendations: string[];
};
}

Quotas & Rate Limiting

AiPolicyService

Quotas par organisation :

  • FREE : 10 tâches/jour, max 1000 tokens/tâche
  • BASIC : 50 tâches/jour, max 4000 tokens/tâche
  • PRO : 200 tâches/jour, max 16000 tokens/tâche

Rate limiting :

  • Max 5 tâches simultanées par organisation
  • Cooldown 2s entre tâches d'un même utilisateur

Vérification :

async checkQuota(orgId: string, userId: string): Promise<{
allowed: boolean;
reason?: string;
remaining?: number;
}> {
// Vérifier quota quotidien
const todayTasks = await this.countTasksToday(orgId);
const quota = await this.getQuota(orgId);
if (todayTasks >= quota.dailyLimit) {
return { allowed: false, reason: 'QUOTA_EXCEEDED' };
}

// Vérifier rate limiting
const runningTasks = await this.countRunningTasks(orgId);
if (runningTasks >= 5) {
return { allowed: false, reason: 'RATE_LIMIT_EXCEEDED' };
}

return { allowed: true, remaining: quota.dailyLimit - todayTasks };
}

Coûts

Calcul des coûts

Modèles OpenAI (prix par 1M tokens) :

  • gpt-4o-mini : $0.15 input, $0.60 output
  • gpt-4o : $2.50 input, $10.00 output
  • gpt-4-turbo : $10.00 input, $30.00 output

Calcul :

const costCents = Math.round(
(tokensIn / 1_000_000) * priceIn * 100 + // Convertir en centimes
(tokensOut / 1_000_000) * priceOut * 100
);

Suivi :

  • Coût par tâche dans ai_tasks.cost_cents
  • Agrégation quotidienne/mensuelle par organisation
  • Alertes si coût dépasse seuil (ex: 100€/mois)

Endpoints

POST /ai/tasks

Créer tâche IA.

Request :

{
type: "WRITE_EMAIL" | "SUMMARIZE" | "EXTRACT" | "CLASSIFY" | "REPORT";
input: Record<string, any>; // Selon type
model?: "gpt-4o-mini" | "gpt-4o" | "gpt-4-turbo";
}

Response :

{
taskId: string;
status: "QUEUED";
jobId: string; // job ai:run
}

GET /ai/tasks/:id

Statut/résultat de la tâche.

Response :

{
id: string;
status: "QUEUED" | "RUNNING" | "DONE" | "FAILED";
output?: Record<string, any>; // Si DONE
error?: string; // Si FAILED
tokensIn?: number;
tokensOut?: number;
costCents?: number;
createdAt: string;
finishedAt?: string;
}

GET /ai/tasks

Liste tâches avec filtres.

Query params :

  • type?: Filtrer par type
  • status?: Filtrer par statut
  • userId?: Filtrer par utilisateur
  • dateFrom?, dateTo?: Plage de dates

Response :

{
tasks: Array<AiTask>;
total: number;
page: number;
pageSize: number;
}

GET /ai/stats

Statistiques IA (quotas, coûts).

Response :

{
quota: {
dailyLimit: number;
usedToday: number;
remaining: number;
};
costs: {
today: number; // En centimes
thisMonth: number;
lastMonth: number;
};
usage: {
totalTasks: number;
byType: Record<string, number>;
avgCostPerTask: number;
};
}

Frontend

Hooks

useCreateAiTask()

const { mutate: createTask } = useCreateAiTask();

createTask({
type: "WRITE_EMAIL",
input: {
recipient: "client@example.com",
context: { quoteId: "..." },
tone: "professional",
purpose: "reminder"
}
}, {
onSuccess: ({ taskId }) => {
// Polling status
}
});

useAiTask(id) (polling)

const { data: task } = useAiTask(taskId, {
refetchInterval: (query) => {
return query.state.data?.status === "QUEUED" || query.state.data?.status === "RUNNING"
? 2000
: false;
}
});

Composants

AiTaskStatus

Affiche statut avec spinner si en cours, résultat si terminé, erreur si échec.

AiTaskForm

Formulaire pour créer tâche selon type :

  • WRITE_EMAIL : Champs recipient, context, tone, purpose
  • SUMMARIZE : Textarea content, maxLength
  • EXTRACT : Textarea content, sélecteur schema
  • CLASSIFY : Textarea content, liste catégories
  • REPORT : Sélecteur reportType, timeframe

AiStatsCard

Affiche quotas, coûts, usage avec graphiques.


Tests

Unit

  • PromptFactory : Génération prompts selon type
  • AiPolicyService : Vérification quotas/rate limiting
  • Calcul coûts (tokens × prix)

Integration

  • ai:run : Exécution complète avec mock OpenAI
  • Quotas : Blocage si quota dépassé
  • Coûts : Calcul correct selon modèle

E2E

  • Créer tâche → polling → résultat disponible
  • Quota dépassé → erreur retournée

Edge Cases

Idempotence

  • Vérifier si tâche avec même input existe déjà (même jour)
  • Retourner résultat existant si disponible

Retries

  • Rate limit OpenAI → retry avec backoff (2 tentatives max)
  • Timeout → retry (2 tentatives max)
  • Quota dépassé → pas de retry

Performance

  • Limiter nombre de tâches simultanées par organisation
  • Cooldown entre tâches d'un même utilisateur

Références