Back to flin
flin

RAG : récupération, reclassement et attribution des sources

Comment FLIN implémente la génération augmentée par récupération -- récupération sémantique de vos données, reclassement par encodeur croisé pour la précision, et attribution des sources pour que les utilisateurs sachent d'où viennent les réponses.

Juste A. Gnimavo (Thales) & Claude | March 26, 2026 5 min flin
EN/ FR/ ES
flinragretrievalrerankingai

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 20

Cela 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

ÉtapeLatenceNotes
Récupération sémantique2-10 msRecherche dans l'index HNSW
Reclassement (20 docs)80-150 msNotation par encodeur croisé
Assemblage du contexte< 1 msOpérations sur chaînes
Génération LLM500-2000 msDépend du fournisseur/modèle
Total600-2200 msAcceptable 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

Share this article:

Responses

Write a response
0/2000
Loading responses...

Related Articles

Thales & Claude deblo

Le Step Zero ne suffisait pas : comment valider un constructeur sans valider le runtime a fait tomber toutes les sessions vocales de Déblo l’heure où nous avons livré le streaming caméra temps réel

La phase 14 a livré Déblo Eyes — streaming caméra temps réel via LiveKit vers Gemini Live native audio. Le premier deploy a fait tomber toutes les sessions vocales en production en quatre-vingt-dix secondes parce que notre Step 0 avait validé le constructeur sans exercer le runtime. Le build log de comment Déblo a eu des yeux, ce qu’un pré-vol incomplet a coûté, et quels points de polish ont été livrés ou reportés.

33 min May 20, 2026
debloclaude-opus-4.7claude-codegemini-live +25
Thales & Claude deblo

Le tiret cadratin qui a tué la production : comment un slogan marketing dans un header HTTP a fait tomber le chat de Déblo pendant 24 heures

Deux jours avant la soumission App Store, tout le produit chat de Déblo s’est cassé silencieusement. Pas de spinner, pas de toast, aucune erreur dans l’UI — juste un silence radio. L’incident de 24 heures se résumait à un seul « é » dans la valeur d’un header HTTP qui levait une UnicodeEncodeError avant qu’aucune requête vers OpenRouter ne quitte le backend. Post-mortem d’une fausse hypothèse, d’une trace Sentry, et d’un fix de six lignes qui a débloqué le lancement.

30 min May 19, 2026
debloclaude-opus-4.7claude-codeincident +19
Thales & Claude deblo

Six heures, d’une page blanche à la review Apple — Comment nous avons soumis Déblo à l’App Store, en direct

Marche à marche en direct de la soumission de Déblo à l’App Store iOS en six heures : ce que les validateurs d’Apple ont rejeté (un superscript Unicode), ce que nous avons corrigé (un Promotional Text gaspillé sur des marques tierces), et les rouages de l’ASO iOS que presque tout le monde rate.

30 min May 13, 2026
debloclaude-opus-4.7claude-codeapp-store +16