Intelligence Artificielle

Gemini API : premiers pas avec Google AI

GeminiGoogle-AiApiLlmGemini-ApiMultimodalIa-GenerativeVision-IaChatbotJavascriptPrompt-EngineeringGoogle-CloudAi-Studio
Gemini API : premiers pas avec Google AI

Configurez l'API Gemini de Google : génération de contenu, chat conversationnel et capacités multimodales texte/image pour enrichir vos applications web.

Installation et configuration

Google propose deux SDKs : @google/generative-ai pour les projets JavaScript/TypeScript (Node.js et navigateur) et le SDK Python pour les usages backend. Pour les applications Angular, le SDK JS/TS est adapté.

npm install @google/generative-ai

# TypeScript : les types sont inclus dans le package
# Node.js 18+ requis (fetch API native)
// config/gemini.ts — configuration centralisée
import { GoogleGenerativeAI, HarmCategory, HarmBlockThreshold } from '@google/generative-ai';

// Ne jamais exposer la clé API côté client !
// En Node.js/Angular SSR : utiliser les variables d'environnement
// En Angular client-side : passer par un backend proxy
const API_KEY = process.env['GEMINI_API_KEY'] ?? '';

const genAI = new GoogleGenerativeAI(API_KEY);

// Configuration par défaut partagée
export function getGeminiModel(modelName = 'gemini-2.0-flash') {
  return genAI.getGenerativeModel({
    model: modelName,
    // Safety settings : contrôler la modération du contenu
    safetySettings: [
      {
        category: HarmCategory.HARM_CATEGORY_HARASSMENT,
        threshold: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE
      },
      {
        category: HarmCategory.HARM_CATEGORY_HATE_SPEECH,
        threshold: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE
      }
    ]
  });
}
Sécurité clé API : Ne jamais inclure la clé API dans un bundle Angular client-side (elle sera visible dans les DevTools). Utiliser un endpoint backend (Node.js, Cloud Functions, Firebase Functions) qui proxifie les requêtes vers l'API Gemini.

generateContent() — requête simple

generateContent() envoie un prompt et attend la réponse complète. C'est le mode non-streaming — adapté aux tâches courtes où la latence totale n'est pas critique.

import { getGeminiModel } from './config/gemini';

const model = getGeminiModel('gemini-2.0-flash');

// Forme simple : string
const result = await model.generateContent('Explique REST en 3 phrases.');
console.log(result.response.text());

// Forme complète avec system instruction
const result2 = await model.generateContent({
  contents: [{
    role: 'user',
    parts: [{ text: 'Génère un composant Angular pour un formulaire de login.' }]
  }],
  systemInstruction: {
    role: 'system',
    parts: [{ text: 'Tu es un expert Angular 18. Utilise les Signals et standalone components. Commente chaque partie importante du code.' }]
  }
});

console.log(result2.response.text());

// Métadonnées de l'usage (important pour surveiller les coûts)
const usage = result2.response.usageMetadata;
console.log(`Tokens: ${usage.promptTokenCount} input, ${usage.candidatesTokenCount} output`);

// Vérifier si la réponse a été bloquée par les safety settings
const candidate = result2.response.candidates[0];
if (candidate.finishReason !== 'STOP') {
  console.warn('Réponse incomplète:', candidate.finishReason);
  // Valeurs possibles: STOP, MAX_TOKENS, SAFETY, RECITATION, OTHER
}

generationConfig — contrôler la sortie

generationConfig contrôle comment le modèle génère sa réponse : la température (créativité), la longueur maximale, les formats de sortie structurés.

// generationConfig complet avec explication de chaque paramètre
const config = {
  generationConfig: {
    temperature: 0.7,
    // 0.0 = déterministe (toujours la même réponse)
    // 1.0 = créatif et varié
    // Pour extraction de données: 0.0-0.2
    // Pour contenu créatif: 0.7-1.0

    topP: 0.95,
    // Noyau de probabilité cumulée pour la sélection des tokens
    // 0.95 = considère les tokens jusqu'à 95% de probabilité cumulée

    topK: 40,
    // Limite le pool de tokens candidats aux K plus probables
    // topK=1 = greedy decoding (même effet que temperature=0)

    maxOutputTokens: 2048,
    // Limite la longueur de la réponse (1 token ≈ 4 caractères)
    // Gemini 2.0 Flash : max 8192 tokens output

    candidateCount: 1,
    // Nombre de réponses alternatives générées (1-8)
    // candidateCount > 1 multiplie les coûts proportionnellement

    stopSequences: ['---END---'],
    // Si le modèle génère ces séquences, la génération s'arrête

    responseMimeType: 'application/json',
    // 'text/plain' (défaut) ou 'application/json'
    // Avec 'application/json', Gemini garantit un JSON valide en sortie

    responseSchema: {
      // JSON Schema pour structurer la sortie (si responseMimeType: 'application/json')
      type: 'object',
      properties: {
        title: { type: 'string' },
        tags: { type: 'array', items: { type: 'string' } },
        difficulty: { type: 'string', enum: ['beginner', 'intermediate', 'advanced'] }
      },
      required: ['title', 'tags', 'difficulty']
    }
  }
};

// Extraction JSON structurée garantie
const model = getGeminiModel();
const result = await model.generateContent({
  contents: [{ role: 'user', parts: [{ text: 'Analyse cet article Angular: [contenu]' }] }],
  ...config
});

const parsed = JSON.parse(result.response.text());
// parsed.title, parsed.tags, parsed.difficulty sont garantis présents et typés

Session de chat multi-tours

startChat() crée une session conversationnelle qui maintient l'historique automatiquement. Chaque appel à sendMessage() envoie le message avec tout l'historique précédent, permettant des échanges contextuels.

const model = getGeminiModel();

const chat = model.startChat({
  history: [
    // Historique optionnel pour démarrer avec du contexte
    { role: 'user',  parts: [{ text: 'Je travaille sur une app Angular de e-commerce.' }] },
    { role: 'model', parts: [{ text: 'Super ! Je suis là pour t\'aider sur ton projet e-commerce Angular. Que veux-tu développer ?' }] }
  ],
  generationConfig: {
    temperature: 0.7,
    maxOutputTokens: 2000
  },
  systemInstruction: {
    role: 'system',
    parts: [{ text: 'Tu es un assistant développeur spécialisé Angular. Sois concis et pratique. Inclus toujours des exemples de code.' }]
  }
});

// Premier message de l'utilisateur
const r1 = await chat.sendMessage('Comment implémenter un panier d\'achat avec les Signals ?');
console.log(r1.response.text());

// Le contexte est conservé — Gemini sait qu'on parle d'Angular e-commerce
const r2 = await chat.sendMessage('Et pour persister le panier dans localStorage ?');
console.log(r2.response.text());

// Accéder à l'historique complet
const history = await chat.getHistory();
console.log(`${history.length} messages dans l'historique`);

// Pattern Angular Service pour encapsuler une session de chat
@Injectable({ providedIn: 'root' })
export class GeminiChatService {
  private chat = signal<ChatSession | null>(null);
  private messages = signal<ChatMessage[]>([]);

  initChat(systemPrompt: string) {
    const model = getGeminiModel('gemini-2.0-flash');
    const session = model.startChat({
      systemInstruction: { role: 'system', parts: [{ text: systemPrompt }] }
    });
    this.chat.set(session);
    this.messages.set([]);
  }

  async sendMessage(text: string): Promise<string> {
    const session = this.chat();
    if (!session) throw new Error('Chat not initialized');

    this.messages.update(msgs => [...msgs, { role: 'user', content: text }]);

    const result = await session.sendMessage(text);
    const responseText = result.response.text();

    this.messages.update(msgs => [...msgs, { role: 'assistant', content: responseText }]);
    return responseText;
  }

  getMessages = this.messages.asReadonly();
}

Streaming — réponse progressive

generateContentStream() retourne un flux de chunks de texte. Essentiel pour les interfaces conversationnelles où l'utilisateur doit voir la réponse s'écrire en temps réel plutôt qu'attendre plusieurs secondes.

// Node.js / Backend
const model = getGeminiModel('gemini-2.5-pro');

const stream = await model.generateContentStream(
  'Explique en détail le pattern Repository en Angular avec des exemples.'
);

let fullText = '';
process.stdout.write('\nRéponse: ');

for await (const chunk of stream.stream) {
  const chunkText = chunk.text();
  process.stdout.write(chunkText);  // affichage progressif
  fullText += chunkText;
}

const finalResponse = await stream.response;
console.log('\n\nTokens utilisés:', finalResponse.usageMetadata);

// Angular : intégrer le streaming dans un composant
@Component({
  template: `
    <div class="chat-response">
      {{ streamingResponse() }}
      @if (isStreaming()) {
        <span class="cursor-blink">▊</span>
      }
    </div>
  `
})
export class ChatComponent {
  streamingResponse = signal('');
  isStreaming = signal(false);

  async sendStreamingMessage(prompt: string) {
    this.streamingResponse.set('');
    this.isStreaming.set(true);

    const model = getGeminiModel();
    const stream = await model.generateContentStream(prompt);

    for await (const chunk of stream.stream) {
      this.streamingResponse.update(text => text + chunk.text());
      // Chaque update déclenche un re-render Angular (mode zoneless)
    }

    this.isStreaming.set(false);
  }
}

Multimodal — images et documents

Gemini est natif multimodal — il peut analyser des images, des PDF et même des vidéos dans un même prompt. La fenêtre de 1M tokens permet d'envoyer des documents entiers.

import fs from 'fs';

const model = getGeminiModel('gemini-2.0-flash');

// Analyser une image locale
const imageData = {
  inlineData: {
    data: fs.readFileSync('./mockup.png').toString('base64'),
    mimeType: 'image/png'
  }
};

const r1 = await model.generateContent([
  imageData,
  { text: 'Analyse ce mockup UI et génère le composant Angular correspondant avec Bootstrap 5.' }
]);
console.log(r1.response.text());

// Analyser un PDF (via File API pour les gros fichiers)
import { GoogleAIFileManager } from '@google/generative-ai/server';

const fileManager = new GoogleAIFileManager(API_KEY);

// Upload du PDF (jusqu'à 20 MB en inline, 2 GB via File API)
const uploadResult = await fileManager.uploadFile('./contrat.pdf', {
  mimeType: 'application/pdf',
  displayName: 'Contrat client'
});

const r2 = await model.generateContent([
  {
    fileData: {
      mimeType: uploadResult.file.mimeType,
      fileUri: uploadResult.file.uri  // URI retourné par l'upload
    }
  },
  { text: 'Extrais les clauses importantes de ce contrat au format JSON structuré.' }
]);

console.log(JSON.parse(r2.response.text()));

// Multiple images dans un même prompt
const r3 = await model.generateContent([
  { text: 'Compare ces deux designs UI et explique les différences :' },
  { inlineData: { data: img1Base64, mimeType: 'image/jpeg' } },
  { text: 'VS' },
  { inlineData: { data: img2Base64, mimeType: 'image/jpeg' } }
]);

Function calling — intégrer des APIs

Le function calling permet à Gemini de décider d'appeler des fonctions de ton code pour obtenir des données réelles. Gemini retourne les arguments, ton code exécute la fonction, et tu renvoies le résultat à Gemini qui génère la réponse finale.

import { FunctionDeclarationSchemaType } from '@google/generative-ai';

// Déclarer les fonctions disponibles pour Gemini
const tools = [{
  functionDeclarations: [
    {
      name: 'getProductInfo',
      description: 'Retourne les informations d\'un produit par son ID ou son nom',
      parameters: {
        type: FunctionDeclarationSchemaType.OBJECT,
        properties: {
          productId: { type: FunctionDeclarationSchemaType.STRING, description: 'ID du produit' },
          includeReviews: { type: FunctionDeclarationSchemaType.BOOLEAN, description: 'Inclure les avis' }
        },
        required: ['productId']
      }
    },
    {
      name: 'searchProducts',
      description: 'Recherche des produits par catégorie et mots-clés',
      parameters: {
        type: FunctionDeclarationSchemaType.OBJECT,
        properties: {
          query: { type: FunctionDeclarationSchemaType.STRING },
          category: { type: FunctionDeclarationSchemaType.STRING },
          maxResults: { type: FunctionDeclarationSchemaType.NUMBER }
        },
        required: ['query']
      }
    }
  ]
}];

// Implémentations locales des fonctions
const functionImplementations = {
  getProductInfo: async ({ productId, includeReviews }) => {
    const product = await productService.getById(productId);
    const reviews = includeReviews ? await reviewService.getByProduct(productId) : [];
    return { ...product, reviews };
  },
  searchProducts: async ({ query, category, maxResults = 10 }) => {
    return await productService.search({ query, category, limit: maxResults });
  }
};

// Pipeline function calling
const model = genAI.getGenerativeModel({ model: 'gemini-2.0-flash', tools });

async function chatWithTools(userMessage: string) {
  let response = await model.generateContent(userMessage);
  let candidate = response.response.candidates[0];

  // Boucle function calling : Gemini peut appeler plusieurs fonctions
  while (candidate.content.parts.some(p => p.functionCall)) {
    const functionCallParts = candidate.content.parts.filter(p => p.functionCall);
    const functionResults = await Promise.all(
      functionCallParts.map(async part => {
        const { name, args } = part.functionCall;
        const result = await functionImplementations[name](args);
        return { functionResponse: { name, response: { content: result } } };
      })
    );

    // Envoyer les résultats à Gemini pour la réponse finale
    response = await model.generateContent({
      contents: [
        { role: 'user', parts: [{ text: userMessage }] },
        { role: 'model', parts: candidate.content.parts },
        { role: 'function', parts: functionResults }
      ]
    });
    candidate = response.response.candidates[0];
  }

  return response.response.text();
}

// Exemple : "Montre-moi les 3 meilleures laptops sous 1000€ avec leurs avis"
// → Gemini appelle searchProducts({ query: 'laptop', maxResults: 3 })
// → Gemini appelle getProductInfo({ productId: '...', includeReviews: true }) x3
// → Gemini génère une réponse structurée avec les vraies données

Embeddings — recherche sémantique

Les embeddings Gemini transforment du texte en vecteurs numériques (768 dimensions) qui capturent le sens sémantique. Deux textes proches sémantiquement auront des vecteurs proches dans cet espace.

// Modèle d'embedding dédié (plus rapide et moins cher que les modèles de génération)
const embeddingModel = genAI.getGenerativeModel({ model: 'text-embedding-004' });

// Générer un embedding
async function embed(text: string): Promise<number[]> {
  const result = await embeddingModel.embedContent(text);
  return result.embedding.values;  // tableau de 768 numbers
}

// Similarité cosinus entre deux vecteurs
function cosineSimilarity(a: number[], b: number[]): number {
  const dotProduct = a.reduce((sum, val, i) => sum + val * b[i], 0);
  const magnitudeA = Math.sqrt(a.reduce((sum, val) => sum + val ** 2, 0));
  const magnitudeB = Math.sqrt(b.reduce((sum, val) => sum + val ** 2, 0));
  return dotProduct / (magnitudeA * magnitudeB);
}

// Système de recherche sémantique dans une FAQ
const faqItems = [
  { id: 1, question: 'Comment réinitialiser mon mot de passe ?' },
  { id: 2, question: 'Quels sont les délais de livraison ?' },
  { id: 3, question: 'Comment retourner un article ?' }
];

// Indexation (à faire une fois, résultats à cacher)
const faqEmbeddings = await Promise.all(
  faqItems.map(async item => ({
    ...item,
    embedding: await embed(item.question)
  }))
);

// Recherche sémantique
async function findRelevantFAQ(userQuery: string) {
  const queryEmbedding = await embed(userQuery);

  const scores = faqEmbeddings.map(item => ({
    ...item,
    score: cosineSimilarity(queryEmbedding, item.embedding)
  }));

  return scores.sort((a, b) => b.score - a.score)[0];
}

// Exemple : "je n'arrive pas à me connecter" → trouve "Comment réinitialiser mon mot de passe ?"
const result = await findRelevantFAQ('je n\'arrive pas à me connecter');
console.log(result.question, result.score);  // → "Comment réinitialiser mon mot de passe ?" 0.87

Gestion des erreurs et retry

// Types d'erreurs Gemini API
// 400 Bad Request : prompt ou paramètres invalides
// 403 Forbidden : clé API invalide ou quotas dépassés
// 429 Too Many Requests : rate limiting
// 500 Internal Server Error : erreur côté Google (rare)

// Wrapper avec retry exponentiel pour la 429
async function generateWithRetry(
  model: GenerativeModel,
  prompt: string,
  maxRetries = 3
): Promise<string> {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      const result = await model.generateContent(prompt);

      // Vérifier si la réponse a été bloquée
      const candidate = result.response.candidates?.[0];
      if (!candidate || candidate.finishReason === 'SAFETY') {
        throw new Error(`Réponse bloquée: ${candidate?.finishReason}`);
      }

      return result.response.text();

    } catch (error: unknown) {
      const isRateLimit = error instanceof Error &&
        error.message.includes('429');
      const isLastAttempt = attempt === maxRetries - 1;

      if (isRateLimit && !isLastAttempt) {
        const delay = Math.pow(2, attempt) * 1000;  // 1s, 2s, 4s
        console.log(`Rate limit, retry dans ${delay}ms...`);
        await new Promise(resolve => setTimeout(resolve, delay));
        continue;
      }

      throw error;
    }
  }
  throw new Error('Max retries reached');
}

Modèles et tarification 2025

ModèleContexteTarif inputTarif outputCas d'usage
gemini-2.0-flash1M tokens$0.075/1M$0.30/1MUsage général, multimodal, rapide
gemini-2.5-pro1M tokens$1.25/1M$10.00/1MRaisonnement avancé, code complexe
gemini-2.5-flash1M tokens$0.15/1M$0.60/1MÉquilibre qualité/coût
text-embedding-0042048 tokens$0.00/1M (gratuit)N/AEmbeddings pour RAG, recherche sémantique
Tier gratuit AI Studio : 15 requêtes/minute, 1 million de tokens/minute, 1 500 requêtes/jour pour Gemini 2.0 Flash — suffisant pour prototyper et développer. Passer à Vertex AI (facturation à l'usage) pour la production.
Contexte 1M tokens : 1 million de tokens correspond à environ 750 000 mots — l'équivalent d'un roman de 3000 pages. Cela permet d'analyser des codebases entières, des rapports annuels complets, ou des historiques de conversations très longs sans découpage.

Questions fréquentes

Pour démarrer : (1) obtenez une clé API gratuite sur ai.google.dev (Google AI Studio), (2) installez le SDK : npm install @google/generative-ai (JavaScript) ou pip install google-generativeai (Python), (3) importez et initialisez : const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY), (4) créez un modèle : const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" }), (5) générez du contenu : const result = await model.generateContent("Votre prompt"). Le plan gratuit offre 15 requêtes/minute et 1500 requêtes/jour.

Gemini 1.5 Flash est optimisé pour la vitesse et le coût — idéal pour les tâches rapides et le traitement en masse. Gemini 1.5 Pro offre une fenêtre de contexte de 1 million de tokens et excelle pour l'analyse de documents longs, la compréhension multimodale et les tâches complexes. Gemini Ultra est le modèle le plus puissant pour les raisonnements avancés. Pour la plupart des applications web, Flash est le meilleur choix rapport qualité/prix/vitesse.

L'API Gemini supporte le multimodal nativement. Avec JavaScript : chargez l'image en base64 (fs.readFileSync ou URL), créez la partie image : { inlineData: { data: base64String, mimeType: "image/jpeg" } }, et passez-la avec votre prompt dans generateContent(). Exemple complet : model.generateContent(["Décris cette image en détail.", imagePart]). Vous pouvez analyser des schémas techniques, extraire du texte depuis des images, comparer des designs, ou décrire des graphiques pour l'accessibilité.

Utilisez startChat() pour créer une session de chat avec historique : const chat = model.startChat({ history: [] }). Envoyez des messages avec await chat.sendMessage("votre message"). L'historique est automatiquement géré par le SDK. Pour persister entre sessions, sauvegardez chat.getHistory() dans votre base de données et restaurez-le au démarrage : model.startChat({ history: savedHistory }). Attention à la limite de contexte (1M tokens pour Gemini 1.5 Pro) pour les conversations très longues.

Partager