Retour au cours

Semaines 10-12 : API et intelligence artificielle

Brancher le Bestiaire sur une API intelligente : un prompt construit à partir de l'espèce affichée, des réponses enregistrées localement et dans le cloud

Objectifs des semaines 10 à 12

À la fin de cette semaine, vous saurez obtenir une clé d'API sur AI Studio et la garder hors de votre dépôt, appeler l'API Gemini depuis le processus principal de votre application Electron avec fetch, construire un prompt à partir du contexte de navigation (l'espèce dont la page est affichée), demander au modèle une réponse en JSON pur et la lire, puis enregistrer ce que l'IA produit : les enrichissements dans le cahier personnel (localStorage) et les nouvelles espèces dans MongoDB Atlas. C'est l'étape 5 du Projet Bestiaire, la dernière avant les points bonus.

Déroulement : l'étape 5 du Bestiaire en cinq gestes

Il n'y a pas de diaporama cette semaine : la matière, c'est le tutoriel de l'étape 5, recopié et détaillé ici. Tout se passe dans l'application que vous avez depuis l'étape 3, qui contient déjà les emplacements à compléter.

  1. Générer ta clé : sur aistudio.google.com/api-keys avec un compte Google, « Create API key ». La clé va dans le bloc ai de votre config.js (copié de config.template.js, déjà dans .gitignore). Voir la section « La clé et la sécurité » plus bas.
  2. Intégrer le HTML de la page intelligente : les deux sections existent déjà dans client/index.html : #vue-ai (« Enrichir les données avec IA », avec son champ de recherche) et #vue-intelligence (« Données enrichies par IA », avec #boite-intelligence). À vous d'y afficher la liste des espèces avec une action « Enrichir » sur chacune, puis le résultat.
  3. Tester la réponse de l'API : dans electron/main.js, le canal ia:enrichirEspece renvoie pour l'instant des données factices. Remplacez-les par un vrai appel (extrait 1) avec un prompt fixe, et regardez la réponse brute dans la console avant de la mettre en page.
  4. Personnaliser le prompt pour les deux sections : le prompt d'enrichissement reçoit le nom, la catégorie, l'habitat et la description de l'espèce affichée; le prompt de nouvelle espèce demande un document complet avec les mêmes champs que la collection espece (extrait 2). Deux prompts différents, un second canal ia:proposerEspece construit sur le modèle du premier.
  5. Enregistrer : l'enrichissement reste local, par sauvegarderDonneesEnrichies() de l'accesseur du cahier personnel (les deux méthodes DonneesEnrichies sont en TODO dans AccesseurCahierPersonnel.js, même patron que les favoris). La nouvelle espèce part dans le cloud par le canal mongo:ajouterEspece, exactement comme celles du populate (extrait 3). Puis git tag LIVRAISON_AI et git push --tags.

Trois extraits à garder sous la main

Les noms sont ceux de votre application : config.ai, le canal ia:enrichirEspece, accesseurCahier, les champs d'une espèce du populate.

1L'appel à l'API Gemini, dans electron/main.js
// remplace le contenu factice du canal ia:enrichirEspece
ipcMain.handle('ia:enrichirEspece', async (event, donneesEspece) => {
    try {
        let prompt = construirePromptEnrichissement(donneesEspece);
        let reponse = await fetch(
            `https://generativelanguage.googleapis.com/v1beta/models/${config.ai.model}:generateContent`,
            {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'x-goog-api-key': config.ai.apiKey
                },
                body: JSON.stringify({
                    contents: [{ parts: [{ text: prompt }] }],
                    generationConfig: { responseMimeType: 'application/json' }
                })
            }
        );
        let corps = await reponse.json();
        if (!reponse.ok) { throw new Error(corps.error.message); }
        let infoEnrichie = JSON.parse(corps.candidates[0].content.parts[0].text);
        infoEnrichie.dateGeneration = new Date().toISOString();
        await accesseurCahier.sauvegarderDonneesEnrichies(donneesEspece._id, infoEnrichie);
        return { success: true, data: infoEnrichie };
    } catch (erreur) {
        return { success: false, error: erreur.message };
    }
});
2Deux prompts construits à partir du contexte de navigation
// config.js, bloc ai (jamais versionné)
ai: { provider: 'gemini', apiKey: 'AIza...', model: 'gemini-2.5-flash' }

// enrichir l'espèce affichée : les champs attendus par #boite-intelligence
function construirePromptEnrichissement(espece) {
    return `Tu es un naturaliste québécois. Voici une espèce de mon bestiaire :
- nom commun : ${espece.nomCommun}
- nom scientifique : ${espece.nomScientifique}
- catégorie : ${espece.category}
- habitat : ${espece.habitat}
- description : ${espece.description}
Réponds en français, uniquement avec un objet JSON ayant exactement ces champs :
faitsInteressants (tableau de 3 phrases), comportementDetaille, roleEcosysteme,
conseilsObservation, detailsConservation (chaînes de caractères).`;
}

// proposer une nouvelle espèce : les champs de la collection espece
function construirePromptNouvelleEspece(category, nomsDejaPresents) {
    return `Propose une espèce réelle du Québec de la catégorie ${category},
différente de : ${nomsDejaPresents.join(', ')}.
Réponds uniquement avec un objet JSON ayant exactement ces champs :
nomCommun, nomScientifique, category, habitat, region, description,
statutConservation, diet, esperanceVie, caracteristiquesDistinctives.`;
}
3Côté page : appeler, afficher, enregistrer dans le cloud
// client/index.html : la fonction enrichirAvecAI() attend depuis l'étape 3
async function enrichirAvecAI(especeId) {
    let espece = especes.find((candidate) => candidate._id === especeId);
    let resultat = await ipcRenderer.invoke('ia:enrichirEspece', espece);
    if (!resultat.success) {
        console.error('Enrichissement refusé :', resultat.error);
        return;
    }
    afficherDonneesEnrichies(espece, resultat.data);   // dans #boite-intelligence
}

// la nouvelle espèce proposée par l'IA part dans Atlas, comme celles du populate
async function ajouterEspeceProposee(category) {
    let noms = especes.map((espece) => espece.nomCommun);
    let proposition = await ipcRenderer.invoke('ia:proposerEspece', category, noms);
    if (!proposition.success) { return; }
    let resultat = await ipcRenderer.invoke('mongo:ajouterEspece', proposition.data);
    if (resultat.success) { await afficherEspeces(); }   // la liste se recharge
}
Le modèle répond en JSON pur grâce à responseMimeType: 'application/json' : sans cette option, il entoure souvent sa réponse de clôtures ```json et JSON.parse échoue. Si fetch n'existe pas dans votre version d'Electron (processus principal trop ancien), npm install node-fetch@2 puis const fetch = require('node-fetch'); en tête de main.js. Une réponse 429 veut dire que le quota gratuit par minute est atteint : on attend une minute, on ne boucle pas.

La clé et la sécurité

  1. Obtenir la clé : aistudio.google.com/api-keys, connecté avec un compte Google, « Create API key ». Le palier gratuit d'AI Studio suffit largement pour le cours; aucune carte de crédit.
  2. La ranger : dans config.js seulement (cp config.template.js config.js si ce n'est pas déjà fait), bloc ai. Le code la lit par config.ai.apiKey; elle ne s'écrit nulle part ailleurs.
  3. Vérifier qu'elle ne part pas : git status ne doit jamais lister config.js. Si c'est le cas, votre .gitignore a perdu la ligne config.js : remettez-la avant tout commit.
  4. La garder côté processus principal : l'appel se fait dans electron/main.js, jamais dans index.html. La page demande par IPC, le processus principal parle à l'API. C'est le même découpage que pour MongoDB.
  5. Si elle fuit (collée dans Discord, dans une capture d'écran, dans un commit) : on la révoque immédiatement sur AI Studio et on en génère une autre. Une clé est un mot de passe.

Où ça va dans l'application

L'application de l'étape 3 a réservé la place de chaque morceau. Voici la carte :

FichierCe qui existe déjàCe que vous ajoutez
config.jsbloc ai avec provider, apiKey, modelvos valeurs : gemini, votre clé, gemini-2.5-flash
electron/main.jscanal ia:enrichirEspece avec des données facticesl'appel réel (extrait 1), les deux fonctions de prompt (extrait 2), un canal ia:proposerEspece
client/index.html#vue-ai, #vue-intelligence, enrichirAvecAI() videla liste des espèces avec l'action « Enrichir », l'affichage du résultat, l'appel (extrait 3)
client/accesseur/AccesseurCahierPersonnel.jsgetDonneesEnrichies() et sauvegarderDonneesEnrichies() en TODO, clé donneesEnrichies déjà initialiséeles deux méthodes, sur le patron des favoris
electron/main.js (Mongo)canal mongo:ajouterEspece déjà fonctionnelrien : la nouvelle espèce y passe telle quelle

Ressources

AI Studio : générer sa clé Documentation Gemini démarrage rapide réponse en JSON quotas du palier gratuit fetch sur MDN (en français) Outils : MongoDB Atlas et les bases de données en ligne Semaines 8-10 : NoSQL, Node et le Bestiaire

Évaluation : l'étape 5 du Projet Bestiaire (5 points)

La cinquième et dernière étape du Projet Bestiaire (25 %), remise par le tag LIVRAISON_AI sur votre dépôt GitHub. On regarde quatre choses : la clé absente du dépôt, un appel qui répond, deux prompts différents construits à partir du contexte de navigation, et les données enregistrées au bon endroit (enrichissements en local, nouvelles espèces dans Atlas).

Les points bonus restent ouverts jusqu'au dernier jour de la session : un visuel au choix, une base locale autre que localStorage, une seconde donnée complète (page navigable, enregistrement Mongo, cache local), des boîtes générées par mini-gabarit HTML, ou le réaffichage des favoris et des notes personnelles.