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
}
]
});
}
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èle | Contexte | Tarif input | Tarif output | Cas d'usage |
|---|---|---|---|---|
gemini-2.0-flash | 1M tokens | $0.075/1M | $0.30/1M | Usage général, multimodal, rapide |
gemini-2.5-pro | 1M tokens | $1.25/1M | $10.00/1M | Raisonnement avancé, code complexe |
gemini-2.5-flash | 1M tokens | $0.15/1M | $0.60/1M | Équilibre qualité/coût |
text-embedding-004 | 2048 tokens | $0.00/1M (gratuit) | N/A | Embeddings pour RAG, recherche sémantique |