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 :
- Charger
ai_taskdepuis DB - Vérifier quotas/rate limiting via
AiPolicyService - Construire prompt via
PromptFactoryselontype - Appeler
LlmProviderPort.complete(prompt, model) - Post-processing selon type :
WRITE_EMAIL: Valider format emailEXTRACT: Valider avec Zod schemaSUMMARIZE: Limiter longueur
- Calculer coûts :
tokens_in × price_in + tokens_out × price_out - Persister
output,tokens_in,tokens_out,cost_cents - 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 };
}