Aller au contenu
Toutes les analyses
Tutoriel13 min de lecture

Pour une base de connaissances d'entreprise, commencez par le RAG et un jeu de test, et ne faites du fine-tuning que pour le format

Un tableau de décision RAG contre fine-tuning, et un test de deux semaines qui montre si le retrieval sur vos propres documents répond suffisamment bien à vos propres questions.

Les équipes qui veulent un assistant sur leurs propres documents posent en général la même première question : faut-il entraîner un modèle sur nos documents, ou connecter un modèle à nos documents ? Pour une base de connaissances, la réponse par défaut est de le connecter, ce que fait la génération augmentée par la recherche (RAG), et de construire un jeu de test de questions réelles avant de choisir quoi que ce soit d'autre. Cette note explique les deux approches en termes simples, donne un tableau de décision, puis détaille un test de deux semaines que vous pouvez mener avec un ingénieur et une personne qui connaît les documents.

RAG et fine-tuning en termes simples

Un modèle de langage sait ce qui se trouvait dans ses données d'entraînement. Il ne connaît pas votre politique de retours, la liste de prix du mois dernier ni la procédure interne d'approbation d'un fournisseur.

Le RAG règle cela au moment de la question. Quand quelqu'un pose une question, le système cherche dans vos documents, prend la poignée de passages qui semblent les plus pertinents et les donne au modèle avec une instruction : réponds à partir de ces passages et indique lesquels tu as utilisés. Les documents restent où ils sont. Modifiez une politique, réindexez-la, et la réponse suivante reflète la modification. La technique a été décrite dans un article de recherche de 2020 qui associait un générateur de texte à un index de documents interrogeable ; l'idée centrale n'a pas changé depuis.

Le fine-tuning poursuit l'entraînement d'un modèle sur des exemples d'entrées et des sorties souhaitées. Il change les habitudes du modèle : ton, structure, vocabulaire, manière de traiter une tâche étroite. C'est un moyen faible d'enseigner des faits qui changent. Chaque modification de la source exige un nouvel entraînement, le modèle ne peut pas indiquer d'où vient une réponse, et tout ce qu'il a appris est accessible à quiconque peut l'utiliser.

La plupart des entreprises travaillent déjà ainsi. Le rapport de Menlo Ventures de décembre 2025 sur l'IA générative en entreprise a montré que la conception de prompts est la manière la plus courante d'adapter les modèles, le RAG arrivant en deuxième ; le fine-tuning et l'apprentissage par renforcement restaient des niches, utilisées surtout par les équipes de pointe.

Ce que cela coûte. Le test de deux semaines ci-dessous coûte le temps d'un ingénieur et quelques jours d'un expert métier, plus une consommation de modèle faible à l'échelle d'un jeu de test. En production, le coût d'exploitation suit une formule : questions par mois × tokens par question × prix par token. À titre d'illustration : 10 000 questions par mois, chacune envoyant cinq passages d'environ 400 tokens plus 500 tokens d'instructions et de question, et recevant une réponse de 300 tokens, cela fait 10 000 × 2 800 = 28 millions de tokens. À un prix mixte illustratif de 5 $ par million de tokens, cela fait 140 $ par mois. Utilisez les prix actuels de votre fournisseur ; c'est la formule qui compte. Si vous voulez que le système soit construit plutôt que testé, notre fourchette publiée pour un copilote RAG interne est de 8k–18k $ sur 4–8 semaines, et la page des tarifs montre la grille tarifaire correspondante.

Le tableau de décision

Ce dont vous avez besoin RAG Fine-tuning
Des connaissances qui changent chaque semaine ou chaque jour Les modifications apparaissent après la réindexation suivante Chaque changement exige un nouvel entraînement et une nouvelle évaluation
Un lien vers la source pour chaque réponse Intégré : la réponse cite les passages utilisés Aucun moyen fiable de dire d'où vient une réponse
Des personnes autorisées à voir des documents différents Les passages sont filtrés selon les permissions de l'utilisateur avant que le modèle ne les voie Tout ce qui est appris est visible par chaque utilisateur du modèle
Un format de sortie fixe ou un ton maison Possible avec des instructions et des exemples dans le prompt, parfois de manière irrégulière Bien adapté
Une faible latence Ajoute une étape de recherche et un prompt plus long Pas d'étape supplémentaire ; un modèle ajusté plus petit peut aussi être plus rapide
Coût de mise à jour Réindexer les documents modifiés Préparer des exemples, réentraîner, réévaluer
Données nécessaires pour démarrer Les documents et un jeu de test de questions réelles Des exemples d'entrées avec des sorties idéales vérifiées par des personnes, plus le même jeu de test

Comptez les lignes qui comptent dans votre cas. Pour une base de connaissances, les trois premières décident généralement : le contenu change, les gens veulent vérifier la source, et tout le monde n'a pas forcément le droit de tout lire.

Le fine-tuning trouve sa place dans un ensemble de cas plus restreint : les réponses doivent toujours suivre une structure stricte ; une tâche à fort volume, comme le routage de tickets par catégorie, pourrait tourner sur un modèle plus petit et moins cher ; ou le ton doit respecter une charte éditoriale que le prompt ne tient pas. Même dans ces cas, il vient généralement s'ajouter au retrieval plutôt que de le remplacer, sous la forme d'un modèle ajusté qui lit toujours les passages récupérés.

Le choix par défaut est donc le RAG avec d'abord un jeu d'évaluation. Le fine-tuning vient plus tard, pour le format, le ton ou une tâche étroite, et seulement si le jeu de test montre que les erreurs restantes sont de cette nature.

Le test de deux semaines

Le test remplace les opinions par un chiffre. À la fin, vous avez un score sur vos propres questions et une liste d'échecs triés par cause, qui vous dit s'il faut construire, quoi corriger et si le fine-tuning aiderait vraiment.

Étape 1 : collecter des questions réelles

Prenez des questions que des gens ont réellement posées : tickets du helpdesk, fils de discussion internes, logs de recherche de l'intranet, questions auxquelles le responsable d'une politique répond par e-mail. Visez 150 à 300. Incluez 10–15 % de questions auxquelles vos documents ne peuvent pas répondre, car savoir refuser correctement fait partie du travail.

Pourquoi autant : avec 200 questions et une exactitude réelle autour de 80 %, la marge d'erreur à 95 % est d'environ ±5,5 points de pourcentage (1,96 × √(0,8 × 0,2 / 200) ≈ 0,055). Un jeu plus petit ne permet pas de distinguer deux versions du système.

Pour chaque question, une personne qui connaît le sujet écrit la réponse attendue et nomme le document dont elle doit provenir. Une ligne JSON par question :

{"id": "q041", "question": "Who approves a new supplier above the purchasing threshold?", "expected": "Procurement lead and finance, both in writing.", "doc": "supplier-onboarding", "groups": ["procurement"], "answerable": true}

Chaque réponse reçoit ensuite l'une de quatre étiquettes :

  • correct : juste, complète et citant le bon document ;
  • partly (partiel) : juste mais incomplète, ou juste avec une citation fausse ou manquante ;
  • wrong (faux) : affirme quelque chose de faux, ou répond à une question à laquelle les documents ne peuvent pas répondre ;
  • declined (refusé) : indique ne pas pouvoir répondre.

Le refus est une étiquette distincte à dessein. Sur une question à laquelle on peut répondre, c'est un échec, mais un échec moins coûteux qu'une réponse fausse, et c'est le métier qui décide combien de chacun il peut accepter. Fixez ce seuil avant le premier passage, de la même façon qu'on fixe une métrique avant d'entraîner un modèle.

Un expert du domaine note à la main les 50 premières réponses. Ensuite, un second modèle peut noter le reste par rapport aux réponses attendues, à condition que ses étiquettes concordent avec celles de l'expert sur ces 50. Les deux pratiques sont courantes : dans l'enquête de LangChain auprès de 1 340 praticiens qui construisent des agents, menée en novembre et décembre 2025, 59,8 % utilisaient une revue humaine, 53,3 % un modèle comme juge, et 52,4 % faisaient des évaluations hors ligne sur des jeux de test.

Étape 2 : indexer les passages avec leurs permissions

Découpez les documents selon leurs titres en passages de quelques centaines de mots. Conservez avec chaque passage l'identifiant du document, son titre, son lien et ses groupes d'accès. Les permissions appartiennent au passage dès le départ ; les ajouter plus tard oblige à tout réindexer.

Étape 3 : retrieval hybride avec reciprocal rank fusion

La recherche par mots-clés (BM25) trouve les termes exacts comme les codes produits, les numéros de formulaires et les acronymes. La recherche par embeddings trouve les paraphrases : « jours off » quand la politique dit « congés annuels ». Chacune manque ce que l'autre attrape, donc utilisez les deux et fusionnez les classements.

La reciprocal rank fusion (RRF) fusionne par position plutôt que par score, si bien qu'il n'est jamais nécessaire de calibrer les deux échelles de scores : chaque passage reçoit 1 / (k + rang) de chaque liste, et on additionne. L'article de 2009 qui l'a introduite fixait k à 60 et jugeait cette valeur quasi optimale mais pas critique.

import numpy as np
from rank_bm25 import BM25Okapi

def tokenize(text):
    return text.lower().split()

class HybridIndex:
    def __init__(self, chunks, embed):
        # chunks: dicts with "text", "doc", "groups" (a set); embed: list[str] -> unit vectors
        self.chunks, self.embed = chunks, embed
        self.bm25 = BM25Okapi([tokenize(c["text"]) for c in chunks])
        self.vectors = np.asarray(embed([c["text"] for c in chunks]))

    def search(self, query, user_groups, k=5, depth=50, rrf_k=60):
        allowed = [i for i, c in enumerate(self.chunks) if c["groups"] & user_groups]
        lexical = self.bm25.get_scores(tokenize(query))
        semantic = self.vectors @ np.asarray(self.embed([query]))[0]
        fused = {}
        for scores in (lexical, semantic):
            ranked = sorted(allowed, key=lambda i: scores[i], reverse=True)[:depth]
            for rank, i in enumerate(ranked, start=1):
                fused[i] = fused.get(i, 0.0) + 1.0 / (rrf_k + rank)
        top = sorted(fused, key=fused.get, reverse=True)[:k]
        return [{**self.chunks[i], "similarity": float(semantic[i])} for i in top]

embed est n'importe quelle fonction d'embedding, hébergée ou open source ; en changer modifie un seul argument. Le filtre de permissions s'exécute avant le classement, donc un passage que l'utilisateur ne peut pas ouvrir n'atteint jamais le modèle.

Étape 4 : répondre avec des citations, ou refuser

Deux contrôles empêchent le système d'inventer. Avant d'appeler le modèle : si le meilleur passage n'est pas assez similaire à la question, refuser sans rien générer. Après : chaque réponse doit citer des passages, et chaque numéro cité doit exister. Tout le reste compte comme un refus.

import re

PROMPT = """Answer the question using only the numbered passages below.
After each sentence, cite the passages you used, for example [2].
If the passages do not contain the answer, reply with exactly: NOT_FOUND

{passages}

Question: {question}"""

def answer(question, index, user_groups, llm, min_similarity=0.35):
    hits = index.search(question, user_groups)
    if not hits or max(h["similarity"] for h in hits) < min_similarity:
        return {"status": "declined", "text": None, "docs": []}
    passages = "\n\n".join(f"[{n}] ({h['doc']}) {h['text']}" for n, h in enumerate(hits, 1))
    text = llm(PROMPT.format(passages=passages, question=question)).strip()
    cited = {int(n) for n in re.findall(r"\[(\d+)\]", text)}
    if text == "NOT_FOUND" or not cited or not cited <= set(range(1, len(hits) + 1)):
        return {"status": "declined", "text": None, "docs": []}
    return {"status": "answered", "text": text, "docs": [hits[n - 1]["doc"] for n in sorted(cited)]}

llm est n'importe quelle fonction qui prend un prompt et renvoie du texte, donc le modèle peut être remplacé sans toucher à ce code. Le seuil de 0.35 est une valeur provisoire : les échelles de similarité diffèrent selon les modèles d'embedding. Réglez-le sur votre jeu de test de sorte que les questions sans réponse soient refusées sans perdre trop de questions auxquelles on peut répondre.

Étape 5 : la boucle de notation

import json
from collections import Counter

def run_eval(path, index, llm, judge):
    items = [json.loads(line) for line in open(path)]
    labels, found = Counter(), 0
    for item in items:
        groups = set(item["groups"])
        if item["answerable"]:
            hits = index.search(item["question"], groups)
            found += item["doc"] in {h["doc"] for h in hits}
        result = answer(item["question"], index, groups, llm)
        if not item["answerable"]:
            label = "correct" if result["status"] == "declined" else "wrong"
        elif result["status"] == "declined":
            label = "declined"
        else:  # judge returns "correct", "partly" or "wrong"
            label = judge(item["question"], item["expected"], result["text"])
        labels[label] += 1
    answerable = sum(1 for item in items if item["answerable"])
    print({k: round(v / len(items), 3) for k, v in labels.items()})
    print("expected document in top 5:", round(found / answerable, 3))

Le second chiffre compte autant que le premier. Si le bon document n'était pas dans les cinq premiers, le modèle n'avait aucune chance, et la correction se trouve dans le retrieval ou dans les documents, pas dans le modèle. Enregistrez les étiquettes de chaque passage avec la date et le changement effectué, pour que chaque changement soit comparé à la même référence.

Semaine 1 et semaine 2

Semaine 1 : la référence.

  • Jours 1–2 : collecter et rédiger les questions avec les responsables des documents ; fixer le seuil d'acceptation.
  • Jour 3 : indexer les documents ; lancer la recherche par mots-clés seule, puis la recherche hybride, et noter le taux de présence dans le top 5 pour les deux.
  • Jours 4–5 : premier passage complet ; l'expert note 50 réponses ; vérifier le juge par rapport à elles ; noter le reste.

Le résultat de la semaine 1 est un score et une liste d'échecs classés selon quatre causes : le document manque ou n'est pas à jour ; le retrieval a manqué le bon passage ; le modèle avait le bon passage et a tout de même mal répondu ; le système aurait dû refuser et ne l'a pas fait.

Semaine 2 : corriger par ordre d'importance.

  • Commencez par la cause la plus importante. Les documents manquants ou périmés sont une tâche de contenu pour leurs responsables. Les ratés du retrieval se corrigent généralement par un meilleur découpage, l'ajout du titre du document et des intertitres à chaque passage, ou des synonymes pour le jargon interne. Les mauvaises réponses à partir de bons passages se corrigent par des changements de prompt et, parfois, par un autre modèle.
  • Relancez le jeu complet après chaque changement. Ne gardez un changement que si le score bouge de plus que la marge d'erreur.
  • Réglez le seuil de refus en dernier, sur la configuration de retrieval finale.

À la fin de la semaine 2, la décision est généralement claire. Si le taux de réponses fausses est sous le seuil convenu, construisez la version de production et gardez le jeu de test comme critère de mise en production. Si les échecs restants portent sur le format ou le ton alors que les faits sont justes, c'est le cas où le fine-tuning mérite d'être testé, et le jeu de test existe déjà pour montrer s'il a aidé. Si la plupart des échecs viennent de documents manquants, le problème est la base de connaissances, et aucun choix de modèle ne le résoudra.

C'est l'ordre que nous suivons pour construire des copilotes RAG : d'abord des questions réelles, ensuite le retrieval, puis un score que chaque version doit battre.

Sources

Un processus vous coûte trop cher ?

Parlez-nous-en. Le premier appel est gratuit, et si l'IA n'est pas la réponse, nous le disons.

Discuter de votre tâche