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 Thales Gnimavo & 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 prompt disait de l'annoncer. Le modèle ne l'a pas fait. Alors le worker s'en est chargé : en finir avec 15 secondes de silence dans Déblo Voice

Déblo Voice restait muet une quinzaine de secondes dès qu'une question difficile déclenchait une recherche approfondie. Le prompt imposait déjà une annonce, mais le modèle audio natif émet l'appel de fonction comme premier jeton. Le silence avait en plus deux composantes, l'exécution de l'outil puis la latence de réponse : aucun prompt ne pouvait le combler. Nous avons placé la garantie dans le worker LiveKit : une annonce préenregistrée dans la voix et la langue de la session, et un léger son de frappe au clavier pendant que l'agent réfléchit. Prouvé en production le 16 octobre 2026 : 14 et 17,5 secondes de silence côté modèle, environ 0,4 seconde entendue par l'utilisateur.

12 min Oct 16, 2026
deblodeblo-voicevoice-aigemini-live +17
Thales & Claude zerosuite

Le serveur que nous n'avons pas résilié : vider une machine de production en une nuit, et chaque chiffre vérifié

Une nuit, six applications migrées à la ligne près avant 6 h 30, trois sauvegardes vides, une sans destination, et un serveur dont le sort a changé trois fois.

19 min Oct 2, 2026
caspclaude-codeclaude-opus-5.5infrastructure +10
Thales & Claude zerosuite

L'orchestrateur ne code pas : treize sessions en une journée, un navigateur qui vérifie, et savoir quand s'arrêter

Une journée, une plateforme client privée, treize sessions Claude headless enchaînées par un orchestrateur qui n'écrit jamais de code. Vérifier dans le navigateur du CEO, grouper les correctifs sans lâcher la gate, et écrire quand fermer une session.

19 min Sep 30, 2026
caspclaude-codeclaude-opus-5.5multi-session +12