Skip to main content
ai5 octobre 202510 min de lecture

Intégrer des fonctionnalités IA dans des produits existants

Un guide pratique pour intégrer l'IA dans des produits qui ont déjà des utilisateurs — du choix du bon modèle aux patterns de déploiement en production.

aillmintegration
Intégrer des fonctionnalités IA dans des produits existants

Ajouter de l'IA à un produit qui a déjà des utilisateurs est fondamentalement différent de construire une startup IA-first. Vous avez une infrastructure existante, des patterns UX établis, des attentes utilisateurs réelles et — surtout — des choses qui peuvent casser. Cet article couvre les patterns que j'ai trouvés fiables après avoir livré des fonctionnalités IA dans plusieurs applications en production.

Choisir entre les fournisseurs de modèles

La décision du fournisseur de modèle n'est pas permanente, et elle ne devrait pas être traitée comme telle. Chaque fournisseur a des forces distinctes qui comptent en production.

OpenAI (GPT-4o, GPT-4.1) reste l'option la plus éprouvée pour la génération de texte généraliste. L'API est stable, la documentation est complète et l'écosystème d'outils autour est mature. Si vous avez besoin de function calling, de sortie JSON structurée ou d'un large support multilingue, OpenAI est un choix par défaut sûr.

Anthropic (Claude) excelle dans le suivi d'instructions nuancé et les tâches à contexte long. Quand votre fonctionnalité implique le traitement de documents volumineux, le maintien de system prompts complexes ou la gestion de tâches où le modèle doit dire « je ne sais pas » plutôt qu'halluciner, Claude tend à mieux performer. Les capacités de réflexion/raisonnement des modèles Claude sont également solides pour les tâches analytiques multi-étapes.

Google Gemini vaut la peine d'être considéré quand votre fonctionnalité implique une entrée multimodale — particulièrement quand vous devez traiter des images, de la vidéo ou de l'audio en même temps que du texte dans la même requête. L'architecture multimodale native de Gemini évite l'impression de fonctionnalités de vision greffées sur des modèles conçus d'abord pour le texte. La tarification pour les cas d'usage à haut débit est également compétitive.

La réponse pratique : commencez avec le fournisseur que votre équipe connaît le mieux, mais architecturez votre système pour pouvoir changer. Le vendor lock-in est le vrai risque, pas le fait de choisir le « mauvais » modèle au premier jour.

Le pattern API Wrapper

Chaque intégration IA devrait être derrière une couche d'abstraction. Pas parce que vous allez forcément changer de fournisseur, mais parce que vous aurez forcément besoin d'ajouter du logging, du caching, du rate limiting et une logique de fallback — et vous ne voulez pas faire cela à 40 endroits différents.

interface AIProvider {
  generateText(prompt: string, options?: GenerateOptions): Promise<AIResponse>;
  generateStream(prompt: string, options?: GenerateOptions): AsyncGenerator<string>;
  generateStructured<T>(prompt: string, schema: z.ZodSchema<T>, options?: GenerateOptions): Promise<T>;
}

interface GenerateOptions {
  model?: string;
  temperature?: number;
  maxTokens?: number;
  systemPrompt?: string;
}

interface AIResponse {
  content: string;
  usage: { promptTokens: number; completionTokens: number };
  model: string;
  latencyMs: number;
}

L'implémentation concrète pour un fournisseur donné reste légère :

class AnthropicProvider implements AIProvider {
  private client: Anthropic;

  constructor(apiKey: string) {
    this.client = new Anthropic({ apiKey });
  }

  async generateText(prompt: string, options?: GenerateOptions): Promise<AIResponse> {
    const start = Date.now();
    const response = await this.client.messages.create({
      model: options?.model ?? "claude-sonnet-4-20250514",
      max_tokens: options?.maxTokens ?? 1024,
      temperature: options?.temperature ?? 0.7,
      system: options?.systemPrompt,
      messages: [{ role: "user", content: prompt }],
    });

    const textBlock = response.content.find((b) => b.type === "text");
    return {
      content: textBlock?.text ?? "",
      usage: {
        promptTokens: response.usage.input_tokens,
        completionTokens: response.usage.output_tokens,
      },
      model: response.model,
      latencyMs: Date.now() - start,
    };
  }

  // ... generateStream, generateStructured
}

Puis une couche de service gère les préoccupations transversales :

class AIService {
  constructor(
    private provider: AIProvider,
    private cache: CacheStore,
    private logger: Logger,
    private fallbackProvider?: AIProvider
  ) {}

  async generate(prompt: string, options?: GenerateOptions): Promise<AIResponse> {
    const cacheKey = this.buildCacheKey(prompt, options);
    const cached = await this.cache.get<AIResponse>(cacheKey);
    if (cached) return cached;

    try {
      const response = await this.provider.generateText(prompt, options);
      this.logger.info("ai_generation", {
        model: response.model,
        tokens: response.usage,
        latencyMs: response.latencyMs,
      });
      await this.cache.set(cacheKey, response, { ttl: 3600 });
      return response;
    } catch (error) {
      if (this.fallbackProvider) {
        this.logger.warn("ai_fallback_triggered", { error: String(error) });
        return this.fallbackProvider.generateText(prompt, options);
      }
      throw error;
    }
  }
}

Ce pattern se rentabilise dès la première semaine. Quand OpenAI a une panne (et ça arrivera), vous basculez vers le fournisseur de secours. Quand vous devez débugger un problème de prompt en production, les logs sont déjà là.

Prompt Engineering en production

Les prompts en production ne sont pas des chaînes de caractères dans votre code source. Ce sont une préoccupation séparée qui nécessite du versioning, des tests et de l'observabilité.

Le système de templates que j'utilise est simple :

interface PromptTemplate {
  id: string;
  version: number;
  system: string;
  user: string;
  variables: string[];
}

const LISTING_DESCRIPTION: PromptTemplate = {
  id: "listing-description",
  version: 3,
  system: `You are a professional copywriter for a restaurant platform.
Write compelling menu item descriptions.
Rules:
- Max 2 sentences
- Mention key ingredients
- Never use the word "delicious" or "mouth-watering"
- Match the restaurant's tone: {{tone}}`,
  user: `Write a description for: {{itemName}}
Category: {{category}}
Ingredients: {{ingredients}}`,
  variables: ["tone", "itemName", "category", "ingredients"],
};

function renderPrompt(
  template: PromptTemplate,
  vars: Record<string, string>
): { system: string; user: string } {
  let system = template.system;
  let user = template.user;

  for (const key of template.variables) {
    const value = vars[key];
    if (!value) throw new Error(`Missing variable: ${key}`);
    system = system.replaceAll(`{{${key}}}`, value);
    user = user.replaceAll(`{{${key}}}`, value);
  }

  return { system, user };
}

Le numéro de version est important. Quand vous modifiez un prompt, incrémentez la version et loguez-la avec chaque requête. Quand un utilisateur signale que la sortie IA a changé, vous pouvez remonter jusqu'à la version exacte du prompt. Stockez les templates de prompt dans une base de données ou un fichier de configuration — pas en dur — pour pouvoir les mettre à jour sans redéployer.

Testez vos prompts comme vous testez votre code. Maintenez un ensemble de fixtures entrée/sortie. Quand vous modifiez un prompt, passez les fixtures et revoyez manuellement le diff. L'évaluation automatisée s'améliore, mais la revue humaine des modifications de prompts détecte encore des problèmes que les métriques manquent.

Streaming des réponses pour l'UX

Les utilisateurs toléreront une attente de 3 secondes pour une réponse complète. Ils ne toléreront pas de fixer un spinner pendant 15 secondes. Le streaming résout ce problème.

async function* streamAIResponse(
  provider: AIProvider,
  prompt: string,
  options?: GenerateOptions
): AsyncGenerator<string> {
  const stream = provider.generateStream(prompt, options);

  for await (const chunk of stream) {
    yield chunk;
  }
}

// In your API route (Next.js example)
export async function POST(request: Request) {
  const { prompt, options } = await request.json();

  const encoder = new TextEncoder();
  const stream = new ReadableStream({
    async start(controller) {
      try {
        for await (const chunk of streamAIResponse(aiProvider, prompt, options)) {
          controller.enqueue(encoder.encode(`data: ${JSON.stringify({ text: chunk })}\n\n`));
        }
        controller.enqueue(encoder.encode("data: [DONE]\n\n"));
        controller.close();
      } catch (error) {
        controller.enqueue(
          encoder.encode(`data: ${JSON.stringify({ error: "Generation failed" })}\n\n`)
        );
        controller.close();
      }
    },
  });

  return new Response(stream, {
    headers: {
      "Content-Type": "text/event-stream",
      "Cache-Control": "no-cache",
      Connection: "keep-alive",
    },
  });
}

Côté client, consommez le stream et mettez à jour l'interface progressivement. La différence de performance perçue est spectaculaire — les utilisateurs voient le contenu apparaître en 200-400 ms au lieu d'attendre la génération complète.

Un détail d'implémentation important : bufferisez les mots partiels côté client. Certains fournisseurs envoient des tokens qui coupent les mots en plein milieu. Accumulez un petit buffer et ne rendez que les mots complets pour éviter les saccades visuelles.

Contrôle des coûts et stratégies de cache

Les coûts des API IA peuvent vous surprendre. Une fonctionnalité qui coûte 2 $/jour en staging peut coûter 2 000 $/jour en production si vous n'avez pas réfléchi au caching.

Le cache sémantique est l'optimisation à plus fort levier. Si deux utilisateurs posent des questions fonctionnellement identiques, servez la réponse en cache. Vous n'avez pas besoin d'une base de données vectorielle pour cela — commencez par du cache exact-match sur des entrées normalisées. Hachez le prompt (après injection des variables) et stockez la réponse avec un TTL.

Le routage de modèle par niveaux économise de l'argent sans dégrader la qualité. Chaque requête n'a pas besoin de votre modèle le plus cher. Routez les tâches de classification simples vers un modèle plus petit et réservez le grand modèle pour la génération complexe :

function selectModel(task: AITask): string {
  switch (task.complexity) {
    case "classification":
    case "extraction":
      return "gpt-4o-mini"; // fast, cheap
    case "generation":
      return "claude-sonnet-4-20250514"; // balanced
    case "reasoning":
      return "claude-opus-4-20250514"; // maximum quality
  }
}

Fixez des limites de budget strictes. La plupart des fournisseurs supportent des limites d'utilisation au niveau de la clé API. Utilisez-les. Implémentez également un rate limiting au niveau applicatif par utilisateur — un seul utilisateur abusif ne devrait pas épuiser votre budget mensuel en un après-midi.

Suivez le coût par fonctionnalité, pas juste le total dépensé. Taguez chaque appel API avec la fonctionnalité qui l'a déclenché. Quand la facture arrive, vous devez savoir que la fonctionnalité « génération automatique de descriptions SEO » représente 60 % de vos dépenses, pas juste que vous avez dépensé X $ au total.

Dégradation gracieuse

Les fonctionnalités IA vont tomber en panne. Les pannes de fournisseur arrivent. Les limites de taux sont atteintes. Les requêtes réseau expirent. Votre produit doit continuer à fonctionner.

Le principe : les fonctionnalités IA doivent améliorer l'expérience, pas la conditionner. Si la recherche alimentée par l'IA est indisponible, revenez à la recherche par mots-clés. Si la génération de contenu IA échoue, montrez à l'utilisateur un formulaire de saisie manuelle. Ne mettez jamais l'IA sur un chemin critique sans contournement possible.

Implémentation pratique :

  • Timeouts. Fixez des timeouts agressifs sur les appels IA (10-15 secondes maximum). Une réponse lente est pire que pas de réponse pour la plupart des flux UX.
  • Circuit breakers. Après N échecs consécutifs, arrêtez d'appeler le fournisseur pendant une période de refroidissement. Cela empêche les défaillances en cascade et évite de dépenser de l'argent sur des requêtes qui vont échouer.
  • Fallbacks pré-générés. Pour des fonctionnalités comme les descriptions de produits ou les recommandations, maintenez un ensemble de fallbacks basés sur des templates qui fonctionnent sans IA. Ils ne seront pas aussi bons, mais ils seront quelque chose.
  • Communication UI. Dites à l'utilisateur ce qui s'est passé. « Les suggestions IA sont temporairement indisponibles » est bien mieux qu'une erreur générique ou un spinner infini.

Exemples concrets

La génération de contenu IA est le point d'intégration le plus courant. Pour une plateforme marketing, cela a signifié construire un pipeline qui prend un brief produit, génère des variations de texte publicitaire, les évalue par rapport aux directives de marque (en utilisant un second appel IA), et présente les meilleurs candidats à un réviseur humain. L'insight clé : l'IA génère, les humains sélectionnent. La fonctionnalité qui permet aux utilisateurs d'éditer et d'affiner la sortie IA est aussi importante que la génération elle-même.

La vision par ordinateur pour le design d'intérieur nécessite une architecture différente. Le traitement de photos de pièces pour l'analyse de style et la détection de meubles implique l'envoi d'images à un modèle de vision, le parsing de la sortie structurée et la correspondance des résultats avec un catalogue de produits. La latence est plus élevée, donc le pattern UX bascule vers un traitement asynchrone avec des notifications push plutôt qu'un affichage synchrone en attente.

La recherche intelligente remplace la correspondance traditionnelle par mots-clés par une compréhension sémantique. Pour une plateforme de restauration, cela a signifié indexer les éléments du menu avec des embeddings, de sorte qu'une recherche pour « quelque chose d'épicé et végétarien » retourne des résultats pertinents même si ces mots exacts n'apparaissent dans aucune fiche. La génération d'embeddings se fait au moment de l'écriture (quand les menus sont mis à jour), pas au moment de la requête — cela garde la recherche rapide indépendamment de la latence du fournisseur IA.

Dans chaque cas, les mêmes principes s'appliquent : encapsulez le fournisseur, versionnez les prompts, cachez agressivement, et ayez toujours un fallback.

Livrer des fonctionnalités IA de manière responsable

L'écart entre une démo IA et une fonctionnalité IA en production est énorme. Les démos n'ont pas besoin de cache, de gestion d'erreurs, de contrôle des coûts ou de dégradation gracieuse. La production si. Les patterns de cet article ne sont pas théoriques — ils viennent de la livraison de fonctionnalités IA dont de vrais utilisateurs dépendent quotidiennement.

Du redesign de pièces alimenté par l'IA aux studios de contenu automatisés, j'ai livré des fonctionnalités IA dans des applications mobiles, des plateformes SaaS et des systèmes backend. La technologie est réellement puissante, mais c'est la discipline d'ingénierie autour d'elle qui détermine si les utilisateurs adorent la fonctionnalité ou apprennent à l'éviter.

Commencez par le pattern wrapper, ajoutez l'observabilité dès le premier jour, cachez tout ce qui peut être caché, et donnez toujours aux utilisateurs un chemin possible quand l'IA n'est pas disponible. Les modèles vont continuer à s'améliorer. Votre travail est de vous assurer que l'intégration est suffisamment solide pour en tirer parti.

DU

Danil Ulmashev

Full Stack Developer

Intéressé par une collaboration ?