Notes pratiques : Comment mettre en œuvre RAG (Retrieval-Augmented Generation) dans votre
Guide pratique pas à pas : Comment mettre en œuvre RAG (Retrieval-Augmented Generation) dans vos contrats, vérifications et espaces de code intégrable pour les équipes qui utilisent ce modèle.
Utilisez ceci comme une version révisée destinée aux opérateurs des idées présentées dans « Comment mettre en œuvre RAG (Retrieval-Augmented Generation) dans votre application web » : étapes claires, emplacements de code ordonnés et notes de récupération qui survivent au transfert de responsabilités. L’étape « Aperçu » fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un enregistrement exemplaire, un cas d’échec et la note de réversion avant d’élargir le périmètre. Conservez les configurations en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans devoir lire l’ensemble du système.
Comprendre l’architecture derrière RAG
Pour comprendre l’architecture sous-jacente, il faut définir les entrées, le responsable de chaque étape ainsi que les critères d’achèvement avant de modifier du code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu, sans avoir à deviner l’état caché. Documentez conjointement le parcours normal et les scénarios de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non traités font partie intégrante du produit, et non d’une mise en forme ultérieure. Citez les passages qui justifient réellement la réponse. Sans citations, les opérateurs ne peuvent pas distinguer une hallucination d’un manque d’indexation.
User Question
|
v
Generate Query Embedding
|
v
Metadata Filtering
|
v
Vector Similarity Search
|
v
Top-K Relevant Documents
|
v
Context Construction
|
v
LLM / Gemini
|
v
Grounded Response + Sources
Mise en place de l’infrastructure d’embedding
Pour la phase de mise en place des embeddings, définissez les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Préférez des unités petites et testables à des scripts complexes. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité plutôt qu’un processus embrouillé. Citez les passages qui ont réellement servi de base à la réponse. Sans citations, les opérateurs ne peuvent pas distinguer une hallucination d’un manque d’indexation.
const { PredictionServiceClient, helpers } =
require('@google-cloud/aiplatform');
const PROJECT_ID = process.env.PROJECT_ID;
const client = new PredictionServiceClient({
apiEndpoint: 'aiplatform.googleapis.com'
});
async function generateEmbedding(
text,
taskType = 'RETRIEVAL_DOCUMENT'
) {
const endpoint =
`projects/${PROJECT_ID}/locations/global/` +
`publishers/google/models/gemini-embedding-001`;
const instance = {
content: text,
task_type: taskType
};
const request = {
endpoint,
instances: [helpers.toValue(instance)]
};
const [response] = await client.predict(request);
return response.predictions[0].embeddings.values;
}
Pourquoi le lotage est important lors de la génération d’embeddings
Pour l’étape « Pourquoi le lotage est important », définissez les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Nommez les artefacts, définissez des vérifications de succès et refusez toute complétion partielle silencieuse. Citez les passages qui fondent réellement la réponse. Sans citations, les opérateurs ne peuvent pas distinguer une hallucination d’un manque d’indexation. Pour l’étape « Pourquoi le lotage est important », définissez les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Conservez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les flags fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans devoir lire l’intégralité du code.
aph.const EMBEDDING_CONFIG = {
maxSegmentsPerRequest: 100,
maxTokensPerRequest: 18000,
concurrency: 3,
tokenEstimateDivisor: 3
};
function estimateTokens(text) {
return Math.ceil(
text.length / EMBEDDING_CONFIG.tokenEstimateDivisor
);
}
function packIntoBatches(texts) {
const batches = [];
let currentBatch = [];
let currentTokens = 0;
for (const text of texts) {
const tokens = estimateTokens(text);
const exceedsCount =
currentBatch.length >=
EMBEDDING_CONFIG.maxSegmentsPerRequest;
const exceedsTokens =
currentTokens + tokens >
EMBEDDING_CONFIG.maxTokensPerRequest;
if (exceedsCount || exceedsTokens) {
if (currentBatch.length > 0) {
batches.push(currentBatch);
}
currentBatch = [text];
currentTokens = tokens;
} else {
currentBatch.push(text);
currentTokens += tokens;
}
}
if (currentBatch.length > 0) {
batches.push(currentBatch);
}
return batches;
}
Utilisation de Firebase Firestore pour la recherche vectorielle
Lors du travail sur l’étape « Utilisation de Firebase Firestore », notez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Documentez à la fois le parcours normal et le parcours de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non livrés font partie intégrante du produit, et non d’améliorations ultérieures. Mesurez le taux de rappel sur un ensemble fixe de questions avant d’ajuster les prompts. Le changement fréquent des prompts ne résout que rarement un système de récupération insuffisant.
const { Firestore } = require('@google-cloud/firestore');
const firestore = new Firestore();
async function storeDocumentWithEmbedding(
collectionPath,
docId,
text,
embedding,
metadata
) {
const docRef =
firestore.doc(`${collectionPath}/${docId}`);
await docRef.set({
text,
embedding,
...metadata,
createdAt: Firestore.FieldValue.serverTimestamp()
});
}
async function findSimilarDocuments(
collectionPath,
queryEmbedding,
limit = 5
) {
const collectionRef =
firestore.collection(collectionPath);
const vectorQuery = collectionRef.findNearest({
vectorField: 'embedding',
queryVector: queryEmbedding,
limit,
distanceMeasure: 'DOT_PRODUCT'
});
const snapshot = await vectorQuery.get();
return snapshot.docs.map(doc => ({
id: doc.id,
data: doc.data()
}));
}
Combinaison du filtrage des métadonnées avec la récupération sémantique
Lorsque vous travaillez sur l’étape de filtrage des métadonnées combiné, notez d’abord les exigences : entrées requises, signal de succès et conséquences en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Préférez des unités petites et testables à des scripts complexes. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité plutôt qu’un processus embrouillé. Évaluez le taux de rappel sur un ensemble de questions fixe avant d’ajuster les prompts. Un changement fréquent des prompts ne résout que rarement un système de récupération insuffisant.
async function retrieveContext(
queryText,
selectedTopics = [],
limit = 5
) {
const queryEmbedding =
await generateEmbedding(
queryText,
'RETRIEVAL_QUERY'
);
let collectionRef =
firestore.collection('knowledge_base');
if (selectedTopics.length > 0) {
collectionRef = collectionRef.where(
'topics',
'array-contains-any',
selectedTopics
);
}
const vectorQuery =
collectionRef.findNearest({
vectorField: 'embedding',
queryVector: queryEmbedding,
limit,
distanceMeasure: 'DOT_PRODUCT'
});
const snapshot = await vectorQuery.get();
return snapshot.docs.map(doc => {
const data = doc.data();
return {
text: data.text,
source: data.source,
metadata: data.metadata || {}
};
});
}
Transformer les documents récupérés en contexte pour le modèle
Lors de la phase de transformation des documents récupérés, notez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de garantir l’intégrité des modifications ultérieures du code. Considérez cette phase comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définez des vérifications de succès et refusez toute exécution partielle silencieuse. Cachez les instructions du système stables ainsi que les schémas des outils. L’envoi répété d’un préambule identique est une cause fréquente de problèmes. Lors de la phase de transformation des documents récupérés, notez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de garantir l’intégrité des modifications ultérieures du code. Gardez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les administrateurs peuvent auditer sans devoir lire l’ensemble du système.
async function generateRAGResponse(
userQuery,
contextDocuments
) {
const contextSection =
contextDocuments
.map((doc, index) => {
return `
### Reference ${index + 1}
${doc.text}
Source: ${doc.metadata?.source || 'Unknown'}
`;
})
.join('\n');
const prompt = `
You are an AI assistant with access
to a knowledge base.
Use the provided context to answer
the user's question accurately.
CONTEXT:
${contextSection}
USER QUESTION:
${userQuery}
INSTRUCTIONS:
1. Answer using the provided context.
2. If the context is insufficient, say so clearly.
3. Cite the references used.
4. Do not invent information.
ANSWER:
`;
const result =
await genAI.models.generateContent({
model: 'gemini-2.5-flash-lite',
contents: prompt
});
return result.text.trim();
}
Optimisation de RAG grâce au cache et à la logique de tentative
L’étape d’optimisation de RAG avec le cache fonctionne au mieux lorsqu’elle est considérée comme une entité mesurable. Capturez un enregistrement exemplaire, un cas d’échec ainsi que des notes de réversion avant d’élargir le périmètre. Documentez en même temps le parcours sans problème et celui de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non livrés font partie intégrante du produit, et non d’une étape de finition ultérieure. Séparez la politique de segmentation en chunks de la politique de récupération des données ; modifier l’une ne doit pas obliger à réécrire l’autre lorsque les indicateurs de qualité évoluent.
const embeddingCache = new Map();
async function getCachedEmbedding(text, taskType) {
const cacheKey = `${taskType}:${text}`;
if (embeddingCache.has(cacheKey)) {
return embeddingCache.get(cacheKey);
}
const embedding =
await generateEmbedding(text, taskType);
embeddingCache.set(cacheKey, embedding);
return embedding;
}
async function retryWithBackoff(
fn,
maxRetries = 3
) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
if (
error.code === 429 ||
error.message.includes('rate limit')
) {
const delay =
Math.pow(2, attempt) * 1000;
await new Promise(resolve =>
setTimeout(resolve, delay)
);
continue;
}
throw error;
}
}
throw new Error('Max retries exceeded');
}
Récupération de plusieurs types de contexte
La récupération de multiples types d’étapes fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un transcript parfait, un cas d’échec et la note de réversion avant d’élargir le périmètre. Préférez des unités petites et testables aux scripts complexes. Lorsqu’une étape échoue, l’erreur doit indiquer une seule responsabilité plutôt qu’un processus embrouillé. Séparez la politique de segmentation de la politique de récupération. Modifier l’une ne doit pas obliger à réécrire l’autre lorsque les métriques de qualité changent.
async function retrieveMultiContext(
queryText,
options = {}
) {
const {
includeDefinitions = true,
includeExamples = true,
includeHistorical = false,
topics = []
} = options;
const queryEmbedding =
await generateEmbedding(
queryText,
'RETRIEVAL_QUERY'
);
const contextPromises = [];
if (includeDefinitions) {
contextPromises.push(
findSimilarDocuments(
'definitions',
queryEmbedding,
3
).then(documents => ({
type: 'definitions',
documents
}))
);
}
if (includeExamples) {
contextPromises.push(
findSimilarDocuments(
'examples',
queryEmbedding,
5
).then(documents => ({
type: 'examples',
documents
}))
);
}
const contexts =
await Promise.all(contextPromises);
return contexts;
}
Surveiller RAG plutôt que de deviner la qualité
La phase de Monitoring RAG Instead of fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un transcript idéal, un cas d’échec et la note de réversion avant d’élargir le périmètre. Traitez cette phase comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définez des vérifications de succès et refusez toute complétion partielle silencieuse. Séparez la politique de segmentation des données de la politique de récupération. Modifier l’une ne doit pas obliger à réécrire l’autre lorsque les métriques de qualité évoluent. La phase de Monitoring RAG Instead of fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un transcript idéal, un cas d’échec et la note de réversion avant d’élargir le périmètre. Conservez les configurations en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans devoir lire l’ensemble du système.
class RAGMetrics {
constructor() {
this.metrics = {
totalQueries: 0,
averageLatency: 0,
retrievalAccuracy: [],
errors: []
};
}
logQuery(
query,
contextCount,
latency,
sources
) {
this.metrics.totalQueries++;
const previousLatency =
this.metrics.averageLatency *
(this.metrics.totalQueries - 1);
this.metrics.averageLatency =
(previousLatency + latency) /
this.metrics.totalQueries;
console.log({
query: query.substring(0, 100),
contextCount,
latency,
sourceCount: sources.length,
timestamp: new Date().toISOString()
});
}
logRetrievalAccuracy(
retrievedDocs,
relevantDocs
) {
const retrievedIds =
new Set(retrievedDocs.map(d => d.id));
const relevantIds =
new Set(relevantDocs.map(d => d.id));
const intersection =
new Set(
[...retrievedIds]
.filter(id => relevantIds.has(id))
);
const precision =
intersection.size / retrievedIds.size;
const recall =
intersection.size / relevantIds.size;
this.metrics.retrievalAccuracy.push({
precision,
recall
});
}
}
Le véritable pipeline RAG en production
Pour l’étape The Real Production RAG, définissez les entrées, le responsable de l’étape et les critères de fin avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Documentez ensemble le parcours idéal et le parcours de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non traités font partie du produit, et non d’une mise en forme ultérieure. Citez les passages qui ont réellement servi de base à la réponse. Sans citations, les opérateurs ne peuvent pas distinguer une hallucination d’un manque d’indexation.
DOCUMENT INGESTION
|
v
Clean + Split Documents
|
v
Generate Embeddings
|
v
Firestore + Metadata Storage
|
|
USER QUERY --------+
|
v
Query Embedding
|
v
Authorization + Filters
|
v
Vector Similarity Search
|
v
Relevant Context
|
v
Prompt Construction
|
v
Gemini / LLM
|
v
Answer + Sources + Metrics
Sur quoi vous concentreriez-vous en tant qu’ingénieur senior
Pour l’étape « Ce sur quoi vous vous concentrerez », définissez les entrées, le responsable de l’étape et les critères d’achèvement avant de modifier du code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Préférez des unités petites et testables aux scripts complexes. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité plutôt qu’un processus embrouillé. Citez les passages qui ont réellement servi de base à la réponse. Sans citations, les opérateurs ne peuvent pas distinguer une hallucination d’un manque dans l’indexation.
Conclusion : Construire un système RAG prêt pour la production
En conclusion, pour créer une étape prête à être mise en production, il convient de définir les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu, sans avoir à deviner l’état caché. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définites des vérifications de succès et refusez les terminations partielles silencieuses. Citez les passages qui ont réellement servi de base à la réponse. Sans citations, les opérateurs ne peuvent pas distinguer une hallucination d’un manque d’indexation. En conclusion, pour créer une étape prête à être mise en production, il convient de définir les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu, sans avoir à deviner l’état caché. Gardez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer.
Sans avoir à lire l’ensemble du graphique.Liste de contrôle opérationnelle
Lors de l’étape de la liste de contrôle opérationnelle, notez d’abord les éléments requis : entrées nécessaires, signal de succès et ce qui se passe en cas d’échec partiel. Cette liste permet de rester honnête lors des modifications ultérieures du code.
Enregistrez les temps d’exécution ainsi que le coût des tokens ou des requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts évite les factures inattendues lorsque le système passe de l’environnement de démonstration à des environnements partagés.
Évaluez le taux de rappel sur un ensemble fixe de questions avant d’ajuster les prompts. Changer fréquemment les prompts ne résout que rarement un système de récupération inefficace.
Gelez un ensemble de référence avant de modifier les prompts ou les modèles. Modifier à la fois le système et les critères d’évaluation cache les dégradations de performance.
Ajoutez un test de base qui exécute le chemin critique dans les processus d’intégration continue en utilisant des fixtures, et non des API payantes en ligne, chaque fois que le budget le permet.
Préférez des unités petites et testables plutôt que des scripts complexes. Lorsqu’une étape échoue, l’erreur doit indiquer une seule responsabilité et non un processus embrouillé.
Au préalable de promouvoir la pile technologique, figez les versions, conservez une transcription exemplaire pour le chemin critique, et vérifiez les étapes de rollback. Les environnements partagés nécessitent des limites de vitesse, des contrôles d’attribution et un responsable clair pour la rotation des secrets. Préférez une fiabilité banale à des démonstrations ingénieuses ponctuelles.
Note de batch pour 9607363b4f86 : gardez les clés du fournisseur hors du répertoire, fixez un plafond pour les tokens par session, et stockez les transcriptions à côté des fichiers de configuration d’évaluation afin que les remplacements ultérieurs de modèles restent comparables.