Les grands modèles de langage sont savants mais pas omniscients. Ils ne peuvent pas répondre aux questions sur la documentation interne de votre entreprise, votre catalogue de produits ou votre historique de support client. Ils hallucinent quand ils ne connaissent pas la réponse, générant avec confiance des informations plausibles mais incorrectes.
La génération augmentée par récupération (RAG) résout ce problème en ancrant les réponses du LLM dans vos données réelles. Au lieu de demander au modèle « Quelle est notre politique de remboursement ? », vous récupérez d'abord la documentation pertinente, puis demandez au modèle de répondre sur la base de cette documentation. Le modèle génère une réponse en utilisant vos données comme contexte, pas ses données d'entraînement.
FLIN implémente le RAG comme un pipeline composable : récupérer les documents pertinents avec la recherche sémantique, les reclasser avec un encodeur croisé pour la précision, les fournir au LLM comme contexte, et attribuer la réponse à ses sources.
Le pipeline RAG
flinfn answer_question(question) {
// Étape 1 : Récupérer les documents pertinents
docs = search question in Document by content limit 20
// Étape 2 : Reclasser pour la précision
ranked = rerank(question, docs, field: "content", limit: 5)
// Étape 3 : Construire le contexte
context = ranked.map(d => d.content).join("\n\n---\n\n")
// Étape 4 : Générer la réponse avec attribution
answer = ai_chat([
{ role: "system", content: "Answer questions based ONLY on the provided context. Cite sources by title. If the context doesn't contain the answer, say so." },
{ role: "user", content: "Context:\n" + context + "\n\nQuestion: " + question }
])
{
answer: answer,
sources: ranked.map(d => { title: d.title, id: d.id })
}
}Quatre étapes. Récupérer, reclasser, générer, attribuer. Chaque étape est un appel de fonction FLIN standard.
Étape 1 : Récupération sémantique
La première étape récupère les documents candidats en utilisant le mot-clé search :
flindocs = search question in Document by content limit 20Cela retourne les 20 documents les plus sémantiquement similaires. La limite est intentionnellement généreuse -- nous récupérons plus de candidats que nécessaire parce que l'étape de reclassement sélectionnera les plus pertinents. Récupérer 20 et reclasser à 5 est plus précis que récupérer 5 directement.
Étape 2 : Reclassement
La recherche sémantique utilise des modèles bi-encodeurs qui intègrent la requête et les documents indépendamment. C'est rapide mais imprécis. Le reclassement utilise un modèle encodeur croisé qui note les paires requête-document ensemble. C'est plus lent mais plus précis.
flinranked = rerank(question, docs, field: "content", limit: 5)L'encodeur croisé est un petit modèle (typiquement 50-100 Mo) qui s'exécute localement via ONNX Runtime. Noter 20 documents prend environ 100 millisecondes.
Étape 3 : Construction du contexte
Les documents reclassés sont assemblés en une chaîne de contexte qui sera envoyée au LLM :
flincontext = ranked.map(d => d.content).join("\n\n---\n\n")Étape 4 : Génération avec attribution
Le LLM reçoit le contexte et la question, et génère une réponse. Le prompt système contraint le LLM à n'utiliser que les informations du contexte fourni, à citer les sources et à indiquer explicitement quand la réponse n'est pas dans le contexte. Cet ancrage empêche l'hallucination.
Exemple RAG complet : base de connaissances
flin// app/knowledge.flin
guard auth
query = ""
answer_data = none
fn search_knowledge(q) {
if q.len < 3 { return }
docs = search q in Document by content limit 20
ranked = rerank(q, docs, field: "content", limit: 5)
if ranked.len == 0 {
answer_data = {
answer: "No relevant documents found.",
sources: []
}
return
}
context = ranked.map(d =>
"[Source: " + d.title + "]\n" + d.content
).join("\n\n---\n\n")
response = ai_chat([
{
role: "system",
content: "You are a knowledge base assistant. Answer questions based only on the provided context. Cite sources."
},
{
role: "user",
content: "Context:\n" + context + "\n\nQuestion: " + q
}
])
answer_data = {
answer: response,
sources: ranked.map(d => { title: d.title, id: d.id, score: d.score })
}
}
<main>
<h1>Base de connaissances</h1>
<input placeholder="Posez une question..."
value={query}
keyup.enter={search_knowledge(query)}>
{if answer_data != none}
<div class="answer-card">
<div class="answer-text">{answer_data.answer}</div>
<div class="sources">
<h4>Sources ({answer_data.sources.len})</h4>
{for source in answer_data.sources}
<a href={"/documents/" + to_text(source.id)}>
{source.title}
<span class="score">{to_text((source.score * 100).round)}% match</span>
</a>
{/for}
</div>
</div>
{/if}
</main>Profil de performance
| Étape | Latence | Notes |
|---|---|---|
| Récupération sémantique | 2-10 ms | Recherche dans l'index HNSW |
| Reclassement (20 docs) | 80-150 ms | Notation par encodeur croisé |
| Assemblage du contexte | < 1 ms | Opérations sur chaînes |
| Génération LLM | 500-2000 ms | Dépend du fournisseur/modèle |
| Total | 600-2200 ms | Acceptable pour Q&R |
Pourquoi le RAG dans le langage
Le RAG est typiquement implémenté comme une architecture multi-services : une base de données vectorielle (Pinecone/Weaviate/Qdrant), un service de récupération, un service de reclassement et une API LLM. Coordonner ces services nécessite de l'infrastructure, de la configuration et du code de liaison.
FLIN condense ce pipeline entier en fonctionnalités du langage : search pour la récupération, rerank pour la précision, ai_chat pour la génération. L'index vectoriel est intégré à FlinDB. L'encodeur croisé s'exécute localement. Le LLM est accessible via la passerelle IA.
Un développeur peut ajouter le RAG à son application en une fonction. Pas un service. Pas un déploiement d'infrastructure. Une fonction.
Dans le prochain article, nous couvrons l'analyse de documents -- comment FLIN extrait le texte des fichiers PDF, DOCX, CSV, JSON et YAML pour l'indexation et le RAG.
Ceci est la partie 120 de la série « Comment nous avons construit FLIN », documentant comment un CEO à Abidjan et un CTO IA ont conçu et construit un langage de programmation à partir de zéro.
Navigation de la série : - [119] Intégration FastEmbed pour les embeddings - [120] RAG : récupération, reclassement et attribution des sources (vous êtes ici) - [121] Analyse de documents : PDF, DOCX, CSV, JSON, YAML - [122] Découpage de code pour le RAG