RAG agent : Laisser le LLM décider quand et où récupérer les informations.
Pourquoi le RAG statique échoue face aux questions multi-étapes et sensibles au temps, et comment un agent LangGraph gère les requêtes entre Qdrant et la recherche web, avec des règles de sécurité et des précautions.
Sources: Your RAG Pipeline Is Lying to You. The Problem Is Not the Embeddings — Agentic RAG
« Le RAG est pour l’essentiel un problème résolu. » On entend souvent cette phrase lors des réunions d’équipe, et la réponse honnête est : cela dépend de ce que l’on entend par récupération d’informations. Toute personne ayant mis en œuvre des pipelines RAG en production pendant un certain temps, que ce soit pour des résumés de dossiers médicaux, des workflows de conversion langage naturel en SQL ou des systèmes de recherche multi-agents, rencontre généralement les mêmes limites.
Ces limites ne viennent pas des embeddings. Elles proviennent de l’hypothèse selon laquelle la récupération d’informations a lieu une seule fois, à un étape fixe, à partir d’une seule source.
Le RAG statique (incorporer les documents, récupérer les k meilleurs résultats, les transmettre au LLM) gère bien les questions simples et autonomes basées sur des connaissances internes stables. Dès que la question devient complexe, sensible au temps, ou nécessite à la fois des documents internes et des données en temps réel, le pipeline statique commence à produire des hallucinations fiables. Un tel système peut recommander une procédure clinique en se basant sur des directives internes obsolètes, alors qu’une recherche rapide sur Internet durerait à peine une demi-minute et révélerait le changement.
De meilleurs embeddings ne résoudront pas ce problème. Ce qui aide, c’est de laisser le LLM décider quand et où effectuer les recherches.
Les problèmes de la méthode « rechercher une fois, répondre toujours »
Imaginez un bibliothécaire qui ne peut répondre qu’en se rendant sur une seule étagère, toujours la même, avant même que vous ayez fini votre phrase.
Demandez « Quels sont les effets secondaires du metformine ? » et l’outil fournit immédiatement la réponse. Demandez maintenant quelque chose de plus complexe : quelles interactions médicamenteuses l’FDA a-t-elle récemment mises en évidence pour le metformine en combinaison avec les nouveaux médicaments GLP-1, et le protocole actuel pour les patients les couvre-t-il ? Cela nécessite des documents internes et des données réglementaires à jour, ainsi qu’une analyse des écarts entre ces éléments. Le RAG statique extrait les informations une seule fois à partir d’une source, tandis que le LLM se contente de paraphraser ce qu’il a reçu.
Ce n’est pas hypothétique. Lors d’une évaluation interne d’un processus de soins de santé [1], le RAG statique a atteint 89 % de pertinence des réponses pour les questions stables, mais ce taux est tombé à 61 % lorsque la question exigeait une référence croisée entre des documents cliniques internes et des directives thérapeutiques externes. Les évaluations de base ont manqué ce problème parce que les réponses semblaient correctes, mais elles étaient simplement obsolètes.
La solution est facile à formuler mais délicate à mettre en œuvre : la récupération des informations devient une décision plutôt qu’une étape préétablie.
Que signifie l’agentic RAG
Dans l’agentic RAG, le LLM ne récupère pas les informations pour vous à un moment donné à l’avance. Il considère cette récupération comme un outil, à l’instar d’une calculatrice ou d’un exécuteur de code, et l’utilise uniquement lorsqu’il juge les preuves insuffisantes. Trois comportements en découlent qui ne peuvent être produits par un processus statique.
- Récupération conditionnelle. Si le contexte déjà répond à la question, l’agent omet complètement la récupération d’informations.
- Récupération en plusieurs étapes. L’agent obtient des informations, les lit, se rend compte qu’un élément manque et retourne en chercher davantage, chaque tentative étant influencée par les résultats de la précédente. C’est ainsi que les gens mènent des recherches.
Aucune de ces idées n’est nouvelle. L’article ReAct de Yao et ses collaborateurs (2022) a présenté le modèle d’alternance entre étapes de raisonnement et appels d’outils, et l’agent RAG est une application de ReAct à la récupération d’informations [2].
Le changement récent réside dans les outils. LangGraph permet enfin l’utilisation pratique de boucles à état avec des branches. Tavily propose une API de recherche sur le web conçue pour les agents, qui renvoie du contenu prêt à être traité par un LLM, et affirme gérer plus de 100 millions de requêtes par mois avec une latence de recherche moyenne de 180 ms [3]. Les détails se trouvent dans l’article Tavily 101.
Trois éléments dynamiques
Pensez à un détective plutôt qu’à un bibliothécaire. Un bibliothécaire récupère des informations sur commande. Un détective lit ce qui est disponible, détermine ce qui manque, va le chercher, lit à nouveau et continue jusqu’à ce qu’une conclusion puisse être défendue. Ce cycle comporte trois composants.
- Le routeur détermine le type de récupération nécessaire pour une requête. Le LLM examine la question et décide si le contexte existant suffit, s’il faut des documents, s’il faut Internet, ou les deux.
- Les outils de récupération constituent les sources de preuves. Deux outils constituent le minimum requis : l’un recherche dans un stockage vectoriel de documents internes, l’autre recherche sur Internet pour obtenir des informations en temps réel. Chacun est une fonction ordinaire que le LLM invoque avec une chaîne de requête.
- Vérification de la base : elle permet de déterminer si les preuves sont suffisantes. Avant de rédiger la réponse finale, l’agent évalue si les informations obtenues sont suffisantes et pertinentes ; dans le cas contraire, il effectue une nouvelle recherche. Les pipelines statiques ne peuvent pas représenter ce type de boucle.
Une implémentation fonctionnelle
Voici un agent hybride dans LangGraph qui alterne entre un stockage vectoriel Qdrant et une recherche web Tavily. Il choisit l’outil approprié, peut les utiliser l’un après l’autre et effectue une nouvelle recherche lorsque le premier résultat n’est pas suffisant pour fonder la réponse. La partie Qdrant suit le guide d’intégration de LangChain.
from typing import Annotated, Literal
from langgraph.graph import StateGraph, START, END
from langgraph.prebuilt import ToolNode
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from langchain_qdrant import QdrantVectorStore
from langchain_openai import OpenAIEmbeddings
from tavily import TavilyClient
from qdrant_client import QdrantClient
import operator
from typing import TypedDict, Sequence
# --- State definition ---
class AgentState(TypedDict):
messages: Annotated[Sequence, operator.add]
retrieval_count: int # guard against infinite loops
# --- Tool 1: Vector store retrieval (internal docs) ---
qdrant_client = QdrantClient(url="http://localhost:6333")
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vector_store = QdrantVectorStore(
client=qdrant_client,
collection_name="internal_docs",
embedding=embeddings,
)
@tool
def retrieve_from_docs(query: str) -> str:
"""
Search internal document knowledge base.
Use for questions about internal policies, protocols, or proprietary knowledge.
"""
results = vector_store.similarity_search(query, k=4)
if not results:
return "No relevant internal documents found for this query."
return "\n\n---\n\n".join([
f"[Source: {doc.metadata.get('source', 'internal')}]\n{doc.page_content}"
for doc in results
])
# --- Tool 2: Web retrieval (live data) ---
tavily = TavilyClient(api_key="YOUR_TAVILY_API_KEY")
@tool
def retrieve_from_web(query: str) -> str:
"""
Search the live web for current information.
Use for recent news, regulatory updates, or anything that may have changed
since the internal knowledge base was last updated.
"""
results = tavily.search(
query=query,
max_results=5,
search_depth="advanced", # deep retrieval for better accuracy
)
if not results.get("results"):
return "No relevant web results found."
return "\n\n---\n\n".join([
f"[Source: {r['url']}]\n{r['content']}"
for r in results["results"]
])
# --- Agent node ---
tools = [retrieve_from_docs, retrieve_from_web]
tool_node = ToolNode(tools)
llm = ChatOpenAI(model="gpt-4o", temperature=0).bind_tools(tools)
SYSTEM_PROMPT = """You are a research assistant with access to two retrieval tools:
- retrieve_from_docs: internal document knowledge base (policies, protocols, proprietary data)
- retrieve_from_web: live web search (recent news, regulatory updates, real-time information)
For any question:
1. Decide whether you need to retrieve information or can answer from context.
2. If retrieval is needed, choose the right source - or call both if the question spans internal and live knowledge.
3. After retrieving, check if the evidence is sufficient. If not, retrieve again with a refined query.
4. Always cite your sources in the final answer.
Do not hallucinate. If you cannot find sufficient evidence, say so explicitly."""
def call_agent(state: AgentState) -> AgentState:
messages = [SystemMessage(content=SYSTEM_PROMPT)] + list(state["messages"])
response = llm.invoke(messages)
return {
"messages": [response],
"retrieval_count": state.get("retrieval_count", 0)
}
def update_retrieval_count(state: AgentState) -> AgentState:
return {
"messages": [],
"retrieval_count": state.get("retrieval_count", 0) + 1
}
# --- Routing logic ---
MAX_RETRIEVALS = 3 # prevent infinite loops
def should_continue(state: AgentState) -> Literal["tools", "end"]:
last_message = state["messages"][-1]
# If the LLM called a tool and we haven't hit the loop guard, continue
if hasattr(last_message, "tool_calls") and last_message.tool_calls:
if state.get("retrieval_count", 0) < MAX_RETRIEVALS:
return "tools"
return "end"
# --- Build the graph ---
graph_builder = StateGraph(AgentState)
graph_builder.add_node("agent", call_agent)
graph_builder.add_node("tools", tool_node)
graph_builder.add_node("count_retrieval", update_retrieval_count)
graph_builder.add_edge(START, "agent")
graph_builder.add_conditional_edges(
"agent",
should_continue,
{"tools": "tools", "end": END}
)
graph_builder.add_edge("tools", "count_retrieval")
graph_builder.add_edge("count_retrieval", "agent")
agent = graph_builder.compile()
# --- Run it ---
if __name__ == "__main__":
result = agent.invoke({
"messages": [
HumanMessage(content=(
"What does our current patient protocol say about GLP-1 usage, "
"and are there any FDA safety updates issued in 2025 we should be aware of?"
))
],
"retrieval_count": 0,
})
print(result["messages"][-1].content)
Quelques détails méritent une attention particulière.
La protection MAX_RETRIEVALS présente dans la logique de routage n’est pas une simple formalité. Sans elle, les agents peuvent engendrer jusqu’à 15 appels de récupération pour une seule question ambiguë, gaspillant ainsi des ressources et la patience des utilisateurs. Trois récupérations suffisent généralement. S’il en faut plus, c’est que la question est trop large ou que les outils renvoient des informations inutiles.
C’est le champ retrieval_count stocké dans l’état qui permet de rompre la boucle. Le StateGraph de LangGraph le conserve à chaque transition de nœud, ce qui lui assure de persister tout au long de la conversation et non seulement au cours d’un seul cycle.
Le prompt système joue également un rôle crucial. Indiquer au modèle quand utiliser chaque outil, et non simplement lui faire savoir qu’ils existent, permet de distinguer un agent qui prend des décisions judicieuses d’un autre qui utilise systématiquement retrieve_from_web par paresse.
Lorsque l’approche agentic RAG aggrave les choses
Ce système ne remplace pas le RAG statique. Il le remplace pour des types de problèmes spécifiques, mais il est contre-productif dans quatre cas.
- La latence est la principale contrainte. Chaque passage dans la boucle entraîne une appel supplémentaire au modèle ainsi qu’un appel à un outil. Pour un assistant Q&R de base où la latence p95 doit rester inférieure à une seconde, un agent qui pourrait effectuer deux ou trois itérations n’est pas envisageable. Un récupérateur statique bien ajusté est plus rapide et suffisant.
- La base de connaissances est stable et autonome. Si toutes les questions des utilisateurs peuvent être répondues avec certitude à partir du stockage vectoriel, vous ajoutez de la complexité pour rien. Mesurez d’abord le pipeline statique à l’aide de RAGAS (article scientifique). Si le taux de rappel du contexte est supérieur à 85 % et la fiabilité des réponses à plus de 90 %, l’enveloppe agente n’est qu’un surcoût inutile.
- On ne peut pas se fier au choix des outils par le modèle. Grâce à une instruction système bien rédigée, GPT-4o et Claude Sonnet parviennent à choisir correctement entre la récupération de documents et celle sur le web dans environ 88 % des cas, selon les mêmes mesures internes. Les 12 % restants entraînent des erreurs de source plus difficiles à déboguer que des hallucinations classiques, car la réponse est fondée, mais au mauvais endroit. Avec un modèle moins performant, il est nécessaire de sélectionner les outils avant de lancer l’exécution.
- On ne peut pas se fier aux données en temps réel. La récupération sur le web crée une surface d’attaque en continu. Tavily filtre les injections d’instructions dans son API, mais aucun filtre n’est parfait ; par conséquent, tout ce que fait un agent en se basant sur du contenu web doit être traité comme potentiellement hostile.
Privilégiez le RAG agentic lorsque les connaissances proviennent de plusieurs sources et que certaines d’entre elles changent. Commencez par un RAG statique et mettez à niveau lorsque les scores RAGAS révèlent une faille spécifique que cette approche permet de résoudre.
Où commencer
Premier pas, en moins de cinq minutes : ajoutez un outil Tavily à un agent LangChain existant à l’aide de from langchain_community.tools.tavily_search import TavilySearchResults. Vous n’avez pas encore besoin de LangGraph. Exposez l’outil, rédigez un prompt système indiquant quand l’appeler, exécutez cinq requêtes de test et observez ce que l’outil sélectionne.
Niveau suivant, environ une après-midi : orientez l’exemple LangGraph ci-dessus vers votre propre stockage vectoriel, en conservant les deux outils. Omettez d’abord le compteur de récupération pour voir avec quelle fréquence l’agent fait des boucles, puis ajoutez-le. La différence apporte de nombreuses leçons.
Avancé : insérer une étape d’évaluation après chaque résultat obtenu par l’outil et avant le prochain tour de l’agent. Une appel à un LLM plus petit et moins coûteux répond par oui ou non à la question de savoir si le contexte récupéré est suffisant, et ce n’est qu’alors que le modèle principal rédige sa réponse. Cette étape permet de passer d’un niveau de fiabilité « suffisant pour les tests » à un niveau de fiabilité « fiable en production ».
Les erreurs de routage des outils, qui représentent 12 % de plus, sont à l’origine de la majeure partie du temps consacré au débogage en production. Instrumentez-les tôt afin que le nœud d’évaluation les détecte avant les utilisateurs.
Sources
[1] Essais d’évaluation internes sur un pipeline RAG dans le secteur de la santé utilisant RAGAS 0.1.x sur un ensemble de 200 requêtes cliniques sélectionnées. Ce n’est pas une étude publiée.
[2] Yao et al. (2022), ReAct : Synergizing Reasoning and Acting in Language Models, arXiv:2210.03629.
[3] Blog Tavily, Tavily 101, avec les chiffres de latence et de volume indiqués sur la page du produit en mai 2026.