ADR-10 — AI provider + RAG strategy
Claude Haiku 4.5 default concierge, Sonnet 4.6 Enterprise, quotas par plan, prompt caching ON, RAG via pgvector prêt mais désactivé MVP, pas de fine-tuning
Statut : Accepté Date : 2026-04-20 Sujet : Choix du modèle LLM, stratégie RAG, gestion des coûts pour le concierge IA Bell
Contexte
L'ancien projet Bell utilisait Azure OpenAI (GPT-4 class) sans stratégie claire. Coûts non trackés, pas de prompt caching, fine-tuning jamais fait bien qu'évoqué. Maintenant qu'on reconçoit proprement, il faut trancher :
- Quel provider LLM ? (OpenAI / Anthropic / Azure / Google / multi)
- Quel modèle par défaut ? (Sonnet, Haiku, GPT-4o, GPT-4o-mini)
- Fine-tuning ou pas ?
- RAG ou pas ?
- Comment contrôler les coûts pour rester rentable même à 100 hôtels ?
Le business model HOAIY s'appuie sur un plan Pro à ~99 €/hôtel/mois. Le coût AI ne doit JAMAIS manger la marge. Un hôtel actif doit coûter < 5 $/mois en AI.
Alternatives considérées
Option 1 — Azure OpenAI (GPT-4o) uniquement
Ce que le collègue faisait. Même stack, pas de friction.
Pour :
- Écosystème Azure (si on est déjà dessus pour d'autres raisons)
- Azure OpenAI "on your data" = RAG empaqueté
- Conformité RGPD via régions EU d'Azure
- GPT-4o : latence faible, bonne qualité
Contre :
- Lock-in Azure
- Prompt caching Azure OpenAI limité vs Anthropic (factor 10 vs factor 3)
- Tool use de GPT-4o plus faible que Claude (hallucinations "j'ai fait X" sans appeler le tool)
- Prix GPT-4o : $2.50/M input, $10/M output — mange la marge à 100+ hôtels
Option 2 — Anthropic Claude uniquement
Pour :
- Prompt caching natif (divise le coût input par 10 sur les system prompts répétés)
- Tool use fiable — Claude respecte les schémas de tools, rare les hallucinations
- Instructions strictes respectées ("Ne JAMAIS inventer, escalade si pas sûr")
- Claude Haiku 4.5 : $1/M input, $5/M output — très compétitif
- Qualité raisonnement sur demandes ambiguës
Contre :
- Pas de fine-tuning public (sauf Bedrock pour Haiku, niche)
- Un seul provider = single point of failure si Anthropic a un incident
- Azure OpenAI plus ancien et plus testé en prod Enterprise
Option 3 — Multi-provider via Vercel AI SDK (retenu)
@ai-sdk/openai + @ai-sdk/anthropic via @elysiajs/ai-sdk. Le code ne dépend pas du provider — c'est une config runtime.
Pour :
- Aucun lock-in — on swap de provider sans changer le code
- Possibilité d'A/B tester par hôtel (feature flag)
- Fallback automatique si un provider est down
- Le modèle par défaut peut être différent selon la tâche (concierge ≠ auxiliaire)
- Embeddings OpenAI + concierge Claude : chacun sa force
Contre :
- Légère complexité de config (2 API keys, 2 monitoring, 2 pricings à suivre)
- Test dev plus coûteux si on test contre les 2 APIs (mock en tests)
Option 4 — LLM self-hosted (Ollama, vLLM, Llama 3)
Pour :
- Coût par token = 0 après amortissement infra
- Data 100 % chez nous
- Pas de quota, pas de rate limit externe
Contre :
- Infra GPU dédiée : ~200-500 €/mois pour un GPU correct
- Amortit à partir de ~15 k conversations/mois (donc ~50 hôtels actifs)
- Qualité Llama 3 70B < Claude Sonnet 4.6 sur tool use
- Latence et stabilité à gérer
- Pas MVP — à considérer à 50+ hôtels si les coûts AI deviennent le 1er poste
Option 5 — Fine-tuning sur domaine hôtelier
Fine-tuner GPT-4o-mini ou Claude Haiku sur des conversations hôtel synthétiques.
Contre :
- Gain souvent marginal vs prompt engineering + tools + RAG
- Dette opérationnelle permanente : chaque nouveau menu/hôtel/procédure = re-fine-tune
- Précision factuelle (prix, horaires, dispo) = tools, pas fine-tuning
- Azure OpenAI fine-tuning GPT-4o-mini dispo, Claude fine-tuning limité à Bedrock Haiku
- Rejeté — on ne fine-tune pas.
Option 6 — RAG uniquement (pas de LLM custom)
RAG = on stocke des docs, on embedding, au runtime on retrieve + inject dans le prompt. Le LLM reste inchangé.
Pour :
- Factuellement précis : l'IA répond depuis la vraie doc hôtel
- Pas de ré-entraînement : on met à jour les docs, live
- Compatible avec tous les providers
- Sensibilité RGPD : data chez nous (pgvector)
Contre :
- Ajoute une complexité (pipeline embeddings + vector search)
- Latence légèrement supérieure (retrieve avant de prompt)
- Pas nécessaire au MVP si
organization.aiContext(markdown édité par le GM) couvre déjà 95 % des cas
Décision
Multi-provider via Vercel AI SDK, stack hybride, pas de fine-tuning, RAG prévu mais désactivé MVP.
Répartition par tâche
import { openai } from "@ai-sdk/openai";
import { anthropic } from "@ai-sdk/anthropic";
export const models = {
// Concierge principal : ultra-fiable sur tool use, prompt caching puissant
concierge: anthropic("claude-haiku-4-5"), // default
// Concierge premium (Enterprise plan) : raisonnement plus fin
conciergeStrong: anthropic("claude-sonnet-4-6"),
// Tâches auxiliaires (titre conversation, résumé, classification simple)
auxiliary: openai("gpt-4o-mini"),
// Embeddings RAG (quand activé)
embeddings: openai("text-embedding-3-small"),
};| Tâche | Modèle default | Prix input | Prix output | Pourquoi |
|---|---|---|---|---|
| Concierge guest (Pro plan) | Claude Haiku 4.5 | $1/M | $5/M | sweet spot qualité/prix, tool use fiable |
| Concierge guest (Enterprise plan) | Claude Sonnet 4.6 | $3/M | $15/M | raisonnement premium |
| Génération titre/résumé | GPT-4o-mini | $0.15/M | $0.60/M | 10× moins cher, qualité suffisante |
| Embeddings RAG | text-embedding-3-small | $0.02/M | — | standard communauté |
| Content moderation (futur) | GPT-4o-mini ou Azure Content Safety | — | — | à décider plus tard |
Prompt caching Anthropic ON par défaut
Le system prompt Bell (~3 k tokens) + organization.aiContext (~2 k tokens) + menu featured (~1 k tokens) = ~6 k tokens quasi-statiques à chaque turn.
Avec Anthropic prompt caching :
- Cache write : 25 % plus cher que input normal (facturé une fois)
- Cache read : 10 % du prix input normal (facturé à chaque hit)
- TTL : 5 minutes (ou 1h avec écriture premium)
Sur notre pattern (5 turns/conversation, cache TTL 5 min) :
- Turn 1 : write cache → facturé 125 % sur 6k tokens
- Turns 2-5 : cache hit → facturé 10 % sur 6k tokens
Économie estimée : 50-70 % sur les tokens input selon le pattern réel.
Activé via l'option cacheControl dans @ai-sdk/anthropic.
RAG prévu mais désactivé MVP
MVP : zéro RAG. Le champ organization.metadata.aiContext (markdown édité par le GM, ~2-5 k tokens) est injecté dans le system prompt. Couvre :
- Horaires (spa, resto, bar, réception)
- Infos pratiques (wifi, parking, pets, fumeurs)
- Spécialités de l'hôtel
- Règles custom ("ne jamais proposer de remboursement")
Quand activer le RAG (déclenchable par org via feature flag ai_rag_enabled) :
- Un hôtel veut charger brochure PDF 30+ pages
- FAQ longue (50+ entrées)
- Procédures internes volumineuses
- Contenu multi-langue
Stack RAG quand activé :
- Upload PDF → RustFS
- Extract text (pdf-parse) → chunks ~500 tokens
- Embed chunks via
text-embedding-3-small(OpenAI, $0.02/M) - Store vectors dans Postgres
vector(1536)via pgvector (déjà chargé) - Runtime : embed question → cosine similarity top-5 → inject dans prompt
Pas de Pinecone / Qdrant / Weaviate. pgvector suffit jusqu'à 1M+ chunks par hôtel.
Routing dynamique (optionnel phase 2)
Pour plan Pro, on peut router 80 % du trafic vers Haiku et 20 % complexes vers Sonnet :
export function pickConcierge(ctx: { turnCount: number; intent?: string; plan: Plan }) {
if (ctx.plan === "enterprise") return models.conciergeStrong; // Sonnet systématique
if (ctx.turnCount > 6) return models.conciergeStrong; // conversation longue
if (ctx.intent === "complex_booking") return models.conciergeStrong; // tool use sensible
return models.concierge; // default Haiku
}Pas MVP — on start full Haiku, on mesure la qualité, on ajoute le routing si signaux d'insatisfaction.
Quotas par plan (hard limits)
| Plan | Prix | Messages AI/mois/hôtel | Modèle |
|---|---|---|---|
| Free | 0 € | 0 IA (FAQ statique + tools UI seulement) | — |
| Pro | 99 €/mois | 2 000 msg | Haiku 4.5 (+ caching) |
| Enterprise | devis (≥ 299 €/mois) | illimité | Sonnet 4.6 + RAG dispo |
Enforced côté API :
// packages/api/src/modules/ai/ai.service.ts
export async function checkQuota(opts: { organizationId: string }) {
const org = await loadOrganization(opts.organizationId);
const monthlyUsed = await countAIMessagesThisMonth(opts.organizationId);
const limit = quotaByPlan[org.plan];
if (limit !== Infinity && monthlyUsed >= limit) {
throw new QuotaExceededError({ plan: org.plan, used: monthlyUsed, limit });
}
}Si quota dépassé : l'IA est remplacée par un message "Le concierge IA n'est plus disponible pour ce mois — parlez directement à notre staff" + escalade automatique vers staff.
Rentabilité calculée
Pour un hôtel Pro (2 000 msg/mois) :
- Input moyen : 400 tokens (system caché 300 + user message 100)
- Output moyen : 200 tokens
- Total : 800 k input + 400 k output
Coût Haiku 4.5 avec caching 70 % :
- Non-cached input (30 %) : 240 k × $1/M = $0.24
- Cached input (70 %) : 560 k × $0.10/M = $0.06
- Output : 400 k × $5/M = $2.00
- Total : ~$2.30/mois/hôtel
Sur 99 € facturé → marge 97 %.
Pour un Enterprise (illimité, Sonnet 4.6) :
- Supposons 10 000 msg/mois
- Coût estimé : ~$30/mois
- Facturé ≥ 299 € → marge 90 %+
Monitoring cost
Métriques obligatoires Signoz :
bell.ai.cost_usd{org_id, model}
bell.ai.tokens.input{org_id, model, cached}
bell.ai.tokens.output{org_id, model}
bell.ai.quota.usage{org_id, plan}
bell.ai.quota.exceeded{org_id}Alertes :
| Alerte | Seuil | Canal |
|---|---|---|
| Org dépasse $5/jour | warning | Slack #bell-alerts |
| Org dépasse $10/jour | critical | Slack + email admin HOAIY |
| Total HOAIY dépasse $50/jour | critical | Slack + email |
| Spike 3× moyenne sur un hôtel | info | Slack |
| Quota atteint par un hôtel Pro | info | log + email GM |
Env vars
# Providers
ANTHROPIC_API_KEY=sk-ant-api03-...
OPENAI_API_KEY=sk-...
# Models (config-driven pour swap sans redeploy)
AI_MODEL_CONCIERGE=claude-haiku-4-5
AI_MODEL_CONCIERGE_STRONG=claude-sonnet-4-6
AI_MODEL_AUXILIARY=gpt-4o-mini
AI_MODEL_EMBEDDINGS=text-embedding-3-small
# Feature flags
AI_PROMPT_CACHING_ENABLED=true
AI_ROUTING_DYNAMIC=false # phase 2
AI_RAG_GLOBAL_ENABLED=false # activable par org via DB flag
# Limits absolues (safety net)
AI_MAX_TOKENS_PER_TURN=4000
AI_MAX_TURNS_PER_CONVERSATION=100
AI_MAX_CONVERSATION_PER_USER=20Dev / test avec vraie API
- Clé API dev avec spend limit $5 côté Anthropic console
- Mock providers via Vercel AI SDK dans les tests unitaires/intégration (
MockLanguageModelV1) - Appels réels uniquement sur smoke tests manuels ciblés
bun scripts/ai-cost-simulator.ts <scenario>pour estimer le coût d'un flow avant prod
Conséquences
Positives :
- Marge business préservée (97 %+ sur Pro, 90 %+ sur Enterprise)
- Zéro lock-in provider (swap via env var)
- Prompt caching divise les coûts par 2-3× sur Claude
- RAG infrastructure prête (pgvector déjà chargé), activable à la demande
- Quota hard limit = pas de surprise de facturation
- Monitoring par hôtel pour détecter abuse ou bug
Négatives :
- 2 API keys à gérer (Anthropic + OpenAI) vs 1 seule
- Légère complexité dans la config
models.ts(plusieurs variantes) - Tests dev coûtent un peu (mitigé par mock + spend limit)
- Dépendance partielle à Anthropic (single-point si leur API est down → fallback OpenAI configurable via env)
Métriques à surveiller
- Coût AI moyen par hôtel (cible : < $5/mois/hôtel Pro, < $40/mois Enterprise)
- Marge AI sur revenue plan (cible : > 95 % sur Pro, > 90 % sur Enterprise)
- Cache hit rate Anthropic (cible : > 70 %)
- Taux d'escalade vers staff (cible : < 20 % — si plus, signale que l'IA est insuffisante)
- Taux de quota exceeded (cible : < 5 % des hôtels Pro — si plus, le quota est trop bas)
- Latence p95 TTFT (cible : < 1s)