Concevoir un pipeline RAG permettant aux agents IA de raisonner, et non seulement de poser des requêtes.
Un pipeline RAG destiné aux agents IA doit afficher le niveau de confiance, la diversité des sources et le degré de couverture, afin que l’agent puisse repérer les lacunes, effectuer de nouvelles recherches et expliquer comment il en est arrivé à cette conclusion.
Sources: How to Design a RAG Pipeline So AI Agents Reason, Not Just Query It
Un agent appelle une pipeline RAG et reçoit une liste de documents. Que se passe-t-il ensuite ?
Dans la plupart des configurations d’agents, RAG est traité comme une boîte de recherche : on envoie une requête, on prend ce qui revient, et on continue. L’agent ne peut pas savoir si les résultats sont fiables, s’ils couvrent tous les aspects de la question, ou si une deuxième tentative avec une hypothèse plus précise aurait permis de trouver quelque chose d’important. Il agit donc à l’aveugle.
Cela est important car les agents agissent sur des informations incomplètes. Un détecteur de hallucinations peut repérer des faits inventés, mais un défaut dans le processus de récupération est bien plus difficile à détecter. L’agent tire des conclusions avec assurance en se basant sur une image incomplète, et personne ne sait ce qu’il a manqué. Le processus de développement habituel, qui consiste à ingérer, puis à récupérer, puis à générer, ne comporte absolument aucun signal de retour.
Rendre la récupération visible pour l’agent
La solution consiste en un harnais fin autour du pipeline RAG qui expose des signaux structurés sur lesquels l’agent peut raisonner et non seulement les consommer.
Plutôt que de renvoyer une simple liste de documents, cet harnais renvoie un objet plus riche. Il contient les documents, un score de confiance, une mesure de la diversité des sources (nombre de domaines distincts et plage de dates), des signaux de couverture indiquant quels aspects de la question ont été abordés et lesquels manquent, ainsi que des métadonnées de récupération telles que le modèle d’embedding, la taille des blocs, le paramètre top-k et l’indication d’un éventuel reranking.
{
"results": [Document, Document, Document],
"confidence_score": 0.87,
"source_diversity": {"domains": 3, "timestamps": ["2024-01", "2023-11", "2023-09"]},
"coverage_signals": {
"question_aspects_covered": ["technical", "cost"],
"question_aspects_missing": ["compliance", "deployment"]
},
"retrieval_metadata": {
"embedding_model": "text-embedding-3-large",
"chunk_size": 512,
"top_k": 5,
"reranker_applied": true
}
}
Ainsi, l’agent dispose d’un canal de retour d’information. Il peut constater que la couverture est partielle et que les sources sont limitées. Il peut décider de relancer la recherche sous un angle différent, de faire appel à une personne ou d’enregistrer les lacunes dans son raisonnement.
Trois niveaux
Niveau 1 : récupération des instruments
Commencez par recueillir ce que le système calcule déjà. Les bases de données vectorielles renvoient des scores de similarité, les outils de réclassement fournissent un niveau de confiance, et les blocs contiennent des timestamps ainsi que des URLs de sources. Ce premier niveau rassemble ces valeurs sans modifier la logique de récupération : il exécute la requête habituelle, extrait les scores, les sources et les timestamps, calcule une valeur approximative de confiance en moyennant les scores et compte le nombre de sources distinctes.
def retrieve_with_signals(query: str, top_k: int = 5):
# Standard retrieval
results = vector_db.query(query, top_k=top_k)
# Capture raw signals
similarity_scores = [r.score for r in results]
sources = [r.metadata.get("source") for r in results]
timestamps = [r.metadata.get("timestamp") for r in results]
# Compute confidence as mean similarity
confidence = sum(similarity_scores) / len(similarity_scores)
return {
"results": results,
"confidence_score": confidence,
"source_diversity": {
"domains": len(set(sources)),
"timestamps": sorted(set(timestamps))
}
}
C’est non invasif, car vous ne faites que lire des données qui étaient déjà présentes.
Niveau 2 : détection de la couverture
Ensuite vient un niveau qui relier le contenu récupéré à la question initiale. Une simple comparaison sémantique suffit, sans besoin de réclasser complètement les résultats.
Divisez la question en aspects. Pour « Comment déployer un modèle avec des contraintes de coût ? », les aspects pourraient être le déploiement, le coût et les contraintes. Vérifiez ensuite lesquels apparaissent dans le texte récupéré. Un LLM ou une extraction simple de mots-clés peut générer la liste des aspects, et la vérification peut se limiter à rechercher chaque aspect dans le texte document combiné. La fonction indique quels aspects ont été couverts, quels sont manquants, ainsi que le taux de couverture.
def detect_coverage(query: str, documents: list) -> dict:
# Extract query aspects (simple approach: use an LLM or keyword extraction)
aspects = extract_query_aspects(query) # ["deployment", "cost"]
# Check document coverage
doc_text = " ".join([d.content for d in documents])
covered = [a for a in aspects if a.lower() in doc_text.lower()]
missing = [a for a in aspects if a not in covered]
return {
"question_aspects_covered": covered,
"question_aspects_missing": missing,
"coverage_ratio": len(covered) / len(aspects) if aspects else 1.0
}
C’est ici que l’agent reçoit des retours sur lesquels il peut agir. Les aspects manquants devraient déclencher une nouvelle requête. Pour améliorer cette approche, il convient de marquer chaque document par des aspects lors de l’ingestion et de comparer ces marques au moment de la récupération.
Niveau 3 : le transmettre à l’agent
Finalement, fournir à l’agent un outil qui restitue les résultats de la récupération. Cet outil prend en charge une requête ainsi qu’un aspect optionnel sur lequel se concentrer, exécute la récupération en utilisant des signaux, ajoute des informations sur la couverture et propose une recommandation : relancer la requête lorsque le taux de couverture est inférieur à 0,7, sinon continuer.
tools = [
{
"name": "search_with_reasoning",
"description": "Search the knowledge base and receive coverage signals",
"parameters": {
"query": "str",
"aspect_focus": "optional str (e.g., 'deployment' or 'cost')"
}
}
]
def search_with_reasoning(query: str, aspect_focus: str = None) -> dict:
retrieval_output = retrieve_with_signals(query, top_k=5)
coverage = detect_coverage(query, retrieval_output["results"])
return {
**retrieval_output,
"coverage_signals": coverage,
"recommendation": (
"re-query" if coverage["coverage_ratio"] < 0.7 else "proceed"
)
}
L’agent reçoit cette recommandation et peut la suivre ou la contredire en s’appuyant sur ses connaissances du domaine.
Vérifier que les signaux méritent confiance
Le système de récupération n’a de valeur que si ses signaux sont précis, il faut donc tester trois éléments.
- Précision des signaux. Le score de confiance prédit-il réellement la qualité des résultats ? Effectuez une vingtaine de requêtes, évaluez manuellement les résultats de un à cinq et comparez ces évaluations avec le score. Une corrélation supérieure à environ 0,75 est un objectif raisonnable.
- Détection de la couverture. Marquez manuellement quels aspects de chaque question sont répondus par les documents, puis comparez avec le détecteur sur une dizaine de requêtes variées. Visez une précision supérieure à 85 %.
- Comportement de l’agent. Enregistrez chaque décision de nouvelle requête. Si, dans la plupart des cas, un taux de couverture inférieur à 0,7 conduit à une nouvelle requête, l’agent prête attention au signal.
Coûts et limites
Une latence supplémentaire apparaît. La couverture nécessite une deuxième analyse sémantique, et pour la récupération de cinq documents, on peut observer un ajout de 50 à 100 millisecondes. Si c’est trop important, il faut mettre en cache les signaux ou les calculer de manière asynchrone.
L’outil suppose également que la couche de récupération est suffisamment fiable pour être utilisée. Si le stockage vectoriel ne renvoie pas de scores ou que les blocs ne contiennent aucune métadonnée, l’instrumentation doit être mise en place en premier.
De plus, ces signaux sont basés sur des heuristiques. Un score de confiance élevé ne garantit pas une déduction correcte. L’outil fournit à l’agent des entrées de meilleure qualité, mais pas parfaites, ce qui rend nécessaires des vérifications contre les hallucinations ultérieures.
Pourquoi cela en vaut la peine
Les agents capables de voir à l’intérieur du processus de récupération des informations sont plus transparents et plus fiables. Ils peuvent expliquer pourquoi ils effectuent à nouveau une recherche, signaler les incertitudes et passer le relais à un humain lorsque la couverture est faible. C’est là la différence entre un moteur de recherche et un assistant de recherche : l’un fournit simplement des résultats, tandis que l’autre fournit des résultats tout en indiquant ce dont il n’est pas sûr.
Début
- Faites l’Étape 1 cette semaine. Affichez le niveau de confiance et la diversité des sources, enregistrez ces données pendant un jour et observez les schémas qui se dégagent.
- Ajoutez une détection de la couverture pour vos types de requêtes les plus fréquents, et vérifiez-la manuellement sur dix exemples.
- Intégrez ce mécanisme dans l’agent et mesurez s’il effectue à nouveau des recherches lorsque la couverture est faible, ainsi que si sa capacité de raisonnement s’améliore.
Lorsque le harnais est en place, de plus en plus de signaux peuvent être intégrés facilement : détection de la dérive sémantique, vérifications de fraîcheur, outils de vérification des faits. Chacun fournit à l’agent davantage d’éléments sur lesquels raisonner. Commencez par petit, créez un canal de retour d’information et laissez l’agent apprendre à l’écouter.