Technologie

Implémenter un assistant à la publication avec Firebase Gentkit et GEMINI 2.0 Flash

Armel Yara
Implémenter un assistant à la publication avec Firebase Gentkit et GEMINI 2.0 Flash

Salut les devs,

Comment nous avons intégré la suggestion de tags et la génération de résumés directement dans les fonctions Firebase?

Lorsque vous construisez une plateforme de publication technique, le point de friction le plus important n'est pas le processus de révision, c'est le formulaire de soumission. 

Les développeurs sont excellents pour construire des systèmes, mais leur demander de décrire leur propre travail de manière concise, avec le bon vocabulaire et les bons tags, produit souvent des données inexactes qui peuvent nuire à la visibilité.

Pour résoudre ce problème, nous avons intégré directement dans notre infrastructure Firebase Functions existante, Firebase Genkit et Gemini 2.0 Flash. 

Le formulaire de publication comportait deux champs à forte friction :

  1. Le Stack Technique (tags) : Les développeurs sous-taguait ("Flutter") ou sur-taguait ("Flutter, Dart, Android, iOS, Firebase, Firestore, Google Cloud, REST, HTTP, JSON..."). Un étiquetage incohérent qui dégradait directement la qualité de notre système de recherche hybride.

  2. Le Résumé/Synopsis : Un résumé professionnel de 2 à 3 phrases est souvent difficile à écrire sur son propre travail. 


La solution : afficher des suggestions en ligne, déclenchées par un simple bouton, sans bloquer le formulaire.

L'ARCHITECTURE

Publishing assistant architecture

Ainsi donc, il faudra retenir quatre (4) principes de conception clés tel que la clé API ne doit jamais t’être exposée au client - uniquement côté serveur, le formulaire doit fonctionner parfaitement sans les fonctionnalités d'intelligence artificielle générative donc non-bloquant, les flux sont requis au moment de l'appel, et non au démarrage à froid (cold-start) et enfin les erreurs doivent être affichées sous forme de toasts, le formulaire reste utilisable. 


Mais pourquoi ne pas appeler Gemini directement alors ?

Bonne question ! 

Nous aurions pu appeler l'API REST Gemini directement, mais Genkit nous offre :

  • Des définitions de flux typées avec traçage et observabilité intégrés

  • Une interface agnostique au modèle — remplacez gemini20Flash par un autre modèle en une seule ligne

  • Une intégration native à Firebase — les flux s'exécutent dans le même processus Node.js que nos autres fonctions

  • La gestion des prompts : pas de manipulation de requêtes HTTP brutes

Nous avons un compromis qui est que Genkit ajoute environ 2,5 Mo au bundle des fonctions et c’est pour nous, c'est acceptable.


IMPLÉMENTATION DE LA FONCTIONNALITÉ

ÉTAPE 1 — Genkit Singleton (functions/services/genkit.js)

Nous initialisons Genkit une seule fois, partagé entre tous les flux :

  const {genkit} = require('genkit');

  const {googleAI} = require('@genkit-ai/googleai');

  const ai = genkit({

    plugins: [googleAI({ apiKey: process.env.GEMINI_API_KEY })]

  });

  module.exports = {ai};


Étape 2 — Tag Suggestion Flow 

Le flux prend (title, description, type) et retourne un string de 5–8 tags.

  const {ai} = require('../services/genkit');

  const {gemini20Flash} = require('@genkit-ai/googleai');


  const suggestTagsFlow = ai.defineFlow(

    {name: 'suggestTags'},

    async ({title, description, type}) => {

      const prompt = `

        Tu es un expert en indexation de publications technologiques.

        Suggère 5 à 8 tags techniques précis pour:

        Titre: ${title}

        Description: ${description}

        Type: ${type}

        

        Réponds UNIQUEMENT avec les tags séparés par des virgules.

        Pas de phrases. Pas d'explication.

      `;

      const {text} = await ai.generate({

        model: gemini20Flash,

        prompt,

        config: {temperature: 0.3, maxOutputTokens: 100}

      });

      const tags = text.split(',').map(t => t.trim()).filter(Boolean).slice(0, 8);

      return {tags};

    }

  );


Étape 3 — Abstract Generation Flow 

Same pattern, different prompt engineering:


  config: {temperature: 0.4, maxOutputTokens: 200}


La température 0,4 permet légèrement plus de variation pour une écriture à consonance naturelle.

L’invite demande au modèle de correspondre à la langue du contenu d’entrée (français ou anglais), rester sous 3 phrases, et écrire à la troisième personne tout en respectant les conventions du résumé.


ÉTAPE  4 — HTTP Endpoints 

Chaque flux est encapsulé dans un gestionnaire onRequest :


  exports.suggestPublicationTags = onRequest(

    {cors: true, timeoutSeconds: 30},

    async (req, res) => {

      if (req.method !== 'POST') { res.status(405).json({...}); return; }

      const {title, description, type} = req.body;

      const {suggestTagsFlow} = require('./flows/suggestTags'); // lazy require

      const result = await suggestTagsFlow({title, description, type});

      res.json({success: true, tags: result.tags});

    }

  );

Le lazy require('./flows/suggestTags') est intentionnel : il évite de charger le Genkit flux au démarrage à froid pour les fonctions qui n’en ont pas besoin. 


ÉTAPE 5 — Frontend Intégration 

Chaque bouton d'assistant suit ce modèle :


  async function suggestTags(section) {

    const btn = document.getElementById(`suggestTagsBtn-${section}`);

    setAiBtnLoading(btn, true);

    try {

      const resp = await fetch(FIREBASE_CONFIG.suggestTagsUrl, {

        method: 'POST',

        headers: {'Content-Type': 'application/json'},

        body: JSON.stringify({title, description, type})

      });

      const data = await resp.json();

      if (data.success) {

        const existing = input.value.split(',').map(t => t.trim()).filter(Boolean);

        const merged = [...new Set([...existing, ...data.tags])];

        input.value = merged.join(', ');

      } else {

        showAiToast('❌ ' + data.error);

      }

    } 

  }


Nous fusionnons les étiquettes d'IA avec les étiquettes existantes saisies par l'utilisateur en utilisant la déduplication d'ensemble.

Nous n'écrasons jamais ce que l'utilisateur a saisi, c’est un assistant, il ne remplace pas.

Pour la suggestion de tags :

  • La contrainte explicite de format de sortie ("UNIQUEMENT avec les tags séparés par des virgules") réduit les erreurs d'analyse de ~18% à <1%.

  • L'inclusion du TYPE de publication comme contexte améliore considérablement la pertinence. "Flutter" sans type → tags génériques. "Flutter" + type="app-mobile" → ["Flutter","Dart","Firebase","Google Maps API","Geolocation","Android","iOS"].

Pour la génération de résumés :

  • "Réponds dans la même langue que le contenu" évite les résumés bilingues.

  • "Maximum 3 phrases" est obligatoire — sans cela, Gemini écrit des paragraphes.

  • "Ton factuel et professionnel, troisième personne" s'aligne sur les conventions de type résumé académique (PubMed-style).


SÉLECTION DU MODÈLE : POURQUOI gemini20Flash et non gemini15Pro ?

Critère

gemini15Pro

gemini20Flash

Latence

~2800ms

~800ms

Coût par 1M tokens

~$7

~$0.10

Qualité des tags

Équivalente

Équivalente

Qualité du résumé

Légèrement ↑

Suffisamment bonne

Ce qu’on remarque pour ce cas d'utilisation est que Flash gagne sur toutes les dimensions. 

Pour la suggestion de tags et les résumés courts, l'écart de qualité entre Pro et Flash est imperceptible. Nous économisons 70× sur le coût et obtenons des réponses 3× plus rapides.


BUG CRITIQUE RENCONTRÉ EN PRODUCTION

Le déploiement initial utilisait gemini15Flash comme identifiant de modèle.

Cela a provoqué des erreurs 404 : Error: 404 model not found: models/gemini-1.5-flash dont la cause profonde est que le plugin Genkit @genkit-ai/googleai dans la version que nous avions installée mappe gemini15Flash au endpoint v1beta, qui n'expose que des versions de modèle dépréciées.

Nous avons donc appliquer la correction suivante : passer à gemini20Flash (Gemini 2.0 Flash), qui :

  • Utilise le endpoint de l'API stable v1

  • Est plus rapide que Gemini 1.5 Flash

  • Coûte le même prix, voire moins

Ce que nous retenons est de toujours tester les identifiants de modèle par rapport à l'API réelle après la mise à niveau de Genkit.


RÉSULTATS

Après le déploiement et les tests avec de vraies publications, on obtient les résultats suivants :

  • Latence de suggestion de tags (chaud) : ~820ms

  • Latence de génération de résumé (chaud) : ~950ms

  • Qualité des tags (échantillon d'examen manuel) : 9/10 publications ont obtenu des suggestions utiles

  • Qualité du résumé : 8/10 n'ont nécessité aucune modification avant soumission

  • Zéro erreur bloquant le formulaire en production (la dégradation gracieuse fonctionnant correctement)


Voilà, c'est tout pour l'article du jour et j'espère que cela vous a apporté quelque chose de nouveau.

Sur ce, je vous souhaite une excellente semaine !!!
Résumé par IA

Trop long ? Obtenez un résumé rapide de cet article généré par l'IA.

Vous Pourriez Aussi Aimer
Recherche de suggestions...
Statistiques de l'article
Engagement des lecteurs avec cet article.

1

Vues

0

Commentaires

📧 Restez informé

Recevez une notification par email à chaque nouvel article ou modification

Commentaires (0)

Aucun commentaire pour le moment. Soyez le premier à commenter !

Laisser un commentaire