Accueil / Articles / Laisser Gemini choisir la source : FAISS, Tavily et Direct Answers dans LangGraph

Laisser Gemini choisir la source : FAISS, Tavily et Direct Answers dans LangGraph

Créez un petit flux de travail LangGraph dans lequel Gemini dirige chaque question vers une base de connaissances FAISS, une recherche web Tavily ou une réponse directe, en utilisant un routage de sortie structurée.

3771 mots

La plupart des prototypes de systèmes de questions-réponses font passer chaque requête par un pipeline fixe, bien que les questions diffèrent quant à leurs besoins : certaines dépendent de documents internes à une entreprise, d’autres de faits qui changent quotidiennement, et d’autres encore ne nécessitent que des connaissances générales. Cette présentation met en place un flux de travail Python compact dans lequel un modèle de langage examine d’abord chaque question avant de la diriger vers l’endroit approprié : un stockage vectoriel FAISS pour les connaissances internes, Tavily pour des résultats web en temps réel, ou directement vers Gemini. À la fin, vous comprendrez comment les états, les nœuds et les arêtes conditionnelles s’assemblent dans LangGraph, pourquoi une sortie structurée rend un routeur d’LLM fiable, et où ce design de taille pédagogique nécessite des améliorations avant d’être utilisé par des utilisateurs réels. Si vous souhaitez un catalogue plus complet de formes graphiques, consultez l’article sur le routage, le fan-out.

Les modèles de critique et d’approbation dans LangGraph complètent cette mise en pratique concrète.

Trois questions, trois sources différentes

Imaginons trois utilisateurs. Le premier demande des informations sur la politique de congés de l’entreprise ; seuls les documents internes peuvent y répondre. Le deuxième souhaite connaître la météo d’aujourd’hui dans l’Uttarakhand ; aucun document interne ne contient cette information, donc le système doit rechercher sur Internet. Le troisième demande ce qu’est RAG ; le modèle en connaît déjà la réponse, et chercher des informations supplémentaires ne ferait qu’augmenter le temps de réponse.

L’architecture cible place donc un routeur alimenté par Gemini devant trois branches qui convergent toutes vers une seule étape de génération de réponse :

User Question
                               |
                               v
                       +----------------+
                       |   AI Router    |
                       |    (Gemini)    |
                       +----------------+
                         /      |      \
                        /       |       \
                       v        v        v
                    FAISS    Tavily    Gemini
                 Internal DB  Web      Direct
                       \        |        /
                        \       |       /
                         v      v       v
                       +----------------+
                       | Generate Answer|
                       |     Gemini     |
                       +----------------+
                               |
                               v
                            Answer

Le routeur constitue le cœur de cette conception. La solution rapide qui semble attractive est la correspondance par mots-clés, comme le montre le pseudocode ci-dessous, où des mots spécifiques dans la question sélectionnent une branche :

if "weather" in question:
    use_tavily()
elif "leave" in question:
    use_faiss()
else:
    use_gemini()

Ici, cette décision est déléguée à Gemini. Comme le modèle interprète la question plutôt que de la scanner pour y détecter des mots déclencheurs, ce flux de travail est qualifié d’agentif.

IA générative, agents et flux de travail agentifs

Ces termes sont souvent utilisés de manière interchangeable, mais ils décrivent différents niveaux d’implication du modèle.

IA générative simple

L’application la plus basique envoie une instruction à un modèle et renvoie tout ce que celui-ci produit :

User Question
      |
      v
     LLM
      |
      v
    Answer

Il peut expliquer le RAG à partir des données d’entraînement, mais il ne sait rien de votre politique de congés.

Agents qui appellent des outils

User Question
      |
      v
     LLM
      |
      +------> Database
      |
      +------> Web Search
      |
      +------> API
      |
      v
    Answer

Une question météorologique, par exemple, peut être répondue en interrogeant un service météo plutôt que de faire en sorte que le modèle invente une prévision plausible.

Flux de travail agents

Un flux de travail agents permet au modèle d’influencer le parcours suivi lors de l’exécution en temps réel. Dans ce projet, le flux se présente comme suit :

Question
   |
   v
Router
   |
   +----> FAISS
   |
   +----> Tavily
   |
   +----> Gemini

Le développeur définit les trois itinéraires possibles ; le modèle ne fait que choisir parmi eux pour chaque question reçue. Il n’a pas un contrôle total sur l’application, mais seulement un menu d’actions autorisées. « Flux de travail de routage agents » est le nom le plus précis pour cette organisation.

Mise en place du projet

Vous avez besoin de Python 3.10 ou ultérieur, d’une clé API pour Google Gemini et d’une clé API pour Tavily. Installez les bibliothèques en une seule fois :

pip install -U langchain langchain-google-genai langchain-community langgraph faiss-cpu tavily-python python-dotenv pydantic

Conservez les deux clés dans un fichier .env plutôt que dans le code source :

GOOGLE_API_KEY=your_google_api_key
TAVILY_API_KEY=your_tavily_api_key

Les importations comprennent des outils d’aide à la typographie, Pydantic pour les schémas de routage, des wrappers de documents et de FAISS de LangChain, les classes de chat et d’embedding de Gemini, les primitives graphiques de LangGraph ainsi que le client Tavily :

import os
from typing import List, Literal
from typing_extensions import TypedDict
from dotenv import load_dotenv
from pydantic import BaseModel
from langchain_core.documents import Document
from langchain_community.vectorstores import FAISS
from langchain_google_genai import (
    ChatGoogleGenerativeAI,
    GoogleGenerativeAIEmbeddings,
)
from langgraph.graph import StateGraph, START, END
from tavily import TavilyClient

La étape suivante, présentée sous forme d’extrait en une ligne, consiste simplement à charger les variables d’environnement :

Load the environment variables:

Avec override=True, les valeurs provenant de .env prennent le pas sur les variables déjà définies dans votre shell :

load_dotenv(override=True)

Configuration de Gemini pour le routage, les réponses et les embeddings

Le modèle de chat remplit deux fonctions. Il choisit d’abord quelle source doit gérer une question, puis il compose la réponse finale à partir du contexte collecté. La configuration ci-dessous active deux tentatives automatiques et laisse les limites de tokens et de délai non définies :

model = ChatGoogleGenerativeAI(
    model="gemini-3.6-flash",
    max_tokens=None,
    timeout=None,
    max_retries=2,
    api_key=os.getenv("GOOGLE_API_KEY"),
)

Les identifiants des modèles changent fréquemment, il est donc nécessaire de vérifier le nom indiqué dans l’extrait par rapport à la liste actuelle des modèles Gemini avant de le lancer.

Les embeddings constituent une question distincte gérée par un modèle séparé. Gemini Embedding convertit chaque document en un vecteur numérique :

embeddings = GoogleGenerativeAIEmbeddings(
    model="models/gemini-embedding-001",
    google_api_key=os.getenv("GOOGLE_API_KEY"),
)

Ce sont les vecteurs qui rendent possible la recherche sémantique : les textes ayant un sens similaire se trouvent proches l’un de l’autre dans l’espace vectoriel, ce qui permet à une question de récupérer des passages pertinents même lorsqu’elle ne partage que peu de mots exacts avec eux.

Une petite base de connaissances interne dans FAISS

Afin de maintenir l’attention sur le flux de travail, la base de connaissances ne contient que trois documents courts : une politique de remboursement sur 30 jours, un droit à 20 jours de congé payé avec une exigence de préavis de sept jours, ainsi que les heures d’assistance en semaine. Dans un système réel, ces textes proviendraient de manuels, de PDF, de pages Notion, de tickets d’assistance, de documents internes ou de lignes de base de données.

documents = [
    Document(
        page_content="""
        Our company provides a 30-day refund policy.
        Customers can request a refund within 30 days of purchase.
        """
    ),
    Document(
        page_content="""
        Employees receive 20 paid vacation days per year.
        Vacation requests must be submitted at least 7 days in advance.
        """
    ),
    Document(
        page_content="""
        The company provides technical support from Monday to Friday,
        9 AM to 6 PM IST.
        """
    ),
]

La création du magasin et son encapsulation en tant que récupérateur nécessite deux appels. En définissant k à 3, on demande les trois documents les plus proches ; avec ce corpus simplifié, cela signifie que chaque document est retourné classé selon sa similarité :

vector_store = FAISS.from_documents(
    documents,
    embeddings,
)

retriever = vector_store.as_retriever(
    search_kwargs={"k": 3}
)

FAISS ne comprend pas le langage ; c’est un index pour la recherche de voisins les plus proches. Le modèle d’incorporation transforme la question reçue en un vecteur, et FAISS renvoie les documents stockés dont les vecteurs sont les plus proches de celui-ci.

Ajout d’un client Tavily pour des informations en temps réel

La recherche sur le Web ne nécessite qu’une instance client créée à partir de la deuxième clé API :

tavily_client = TavilyClient(
    api_key=os.getenv("TAVILY_API_KEY")
)

Conception de l’état partagé

L’état est l’idée centrale de LangGraph : un dictionnaire typé qui circule à travers le graphe, que chaque nœud lit et auquel il contribue. Ce flux de travail nécessite la question, tous les documents récupérés, les résultats de Tavily, la source choisie ainsi que la réponse finale :

class AgentState(TypedDict):
    question: str
    documents: List[Document]
    tavily_response: str
    source: str
    answer: str

Conceptuellement, cela fonctionne comme un enregistrement partagé que chaque étape peut consulter :

AgentState
                    |
        +-----------+-----------+
        |           |           |
    question    documents    source
                    |
             tavily_response
                    |
                  answer

Un nœud reçoit l’état actuel, utilise les champs qui l’intéressent et renvoie des mises à jour, que LangGraph fusionne avant de transmettre l’état au nœud suivant.

Le nœud de récupération FAISS

Ce nœud prend la question provenant de l’état, la passe par le moteur de recherche et stocke les documents correspondants :

def retrieve_from_faiss(state : AgentState) -> AgentState:
    question = state['question']

    """ Fetch the details from the FAISS vector database
    """

    result = retriever.invoke(question)

    return {**state, "documents": result}

Il s’exécute uniquement lorsque le routeur a choisi la base de connaissances interne. Une question comme celle ci-dessous constitue un déclencheur typique :

"What is our company leave policy?"

Pour cette entrée, le récupérateur doit afficher le document relatif aux vacances :

Employees receive 20 paid vacation days per year.
Vacation requests must be submitted at least 7 days in advance.

Le nœud de recherche Tavily

Le nœud de recherche web envoie la question à Tavily avec search_depth réglé sur advanced, puis extrait le champ content de chaque résultat :

def search_with_tavily(state: AgentState) -> AgentState:
    question = state['question']
    """ Using the Tavily to search the web and
      fetch the latest information about user query
    """

    response = tavilyClient.search(
        query=question,
        search_depth='advanced'
    )

    contents = [result["content"] for result in response["results"]]
    return {**state, "tavilyResponse":contents}

Ces extraits deviennent le contexte que Gemini lira lors de la rédaction de la réponse. Notez que le nœud renvoie une liste de chaînes de caractères ; si vous préférez un seul bloc de texte, concaténez les éléments avant de les stocker afin que le champ corresponde au type str déclaré dans l’état.

Rendre le routeur fiable grâce à une sortie structurée

Un routeur naïf demande au modèle de répondre par l’un de trois mots simples :

Return only:
faiss
tavily
gemini

Les modèles de langage ne respectent pas toujours les instructions de formatage. Vous pourriez obtenir une phrase complète en retour :

I would choose tavily.

Ou une formulation légèrement différente :

The best option is: tavily

Chacune de ces réponses perturbe un graphe qui nécessite une chaîne de caractères exacte pour choisir une arête. Une sortie structurée comble cette lacune. Décrivez d’abord les décisions autorisées sous forme de modèle Pydantic dont le seul champ est restreint à trois valeurs littérales :

class RouteDecision(BaseModel):
    source: Literal["faiss", "tavily", "gemini"]

Ensuite, dérivez un modèle de routage qui doit retourner une instance de ce schéma. La méthode json_schema demande au fournisseur de limiter la génération au schéma plutôt que de se fier uniquement à la formulation de l’instruction :

router_model = model.with_structured_output(
    RouteDecision,
    method="json_schema",
)

La sortie est validée par rapport au type Literal, de sorte qu’une valeur invalide apparaît comme une erreur au lieu de diriger le graphe vers une destination non définie.

Rédaction du nœud de décision

L’invite de routage décrit chaque option : faiss pour les questions relatives à la politique de l’entreprise et aux autres connaissances internes, tavily pour tout ce qui nécessite des informations actuelles ou en ligne, et gemini pour les connaissances générales qui n’exigent ni l’un ni l’autre :

def decide_source(state:AgentState)-> AgentState:
    question = state["question"]
    prompt = f"""
    Decide the best source for answering this question.

    Choose exactly one:

    faiss:
    Use when the question can be answered using our internal
    knowledge base.related to company policy and all

    tavily:
    Use when the question requires current, recent, or web-based
    information.

    gemini:
    Use when the question is general knowledge and does not
    require our internal documents or current web information.

    Question:
    {question}

    Return only one word:
    faiss, tavily, or gemini
    """

    response = model.invoke(prompt)
    # return response

    return {**state,"source":response.text}

Regardez attentivement les dernières lignes. Telles quelles, le nœud appelle encore directement model et stocke response.text, ce qui correspond exactement à l’approche fragile basée sur du texte libre décrite ci-dessus. Pour bénéficier du schéma, appelez plutôt router_model.invoke(prompt) et stockez l’attribut source du résultat. Avec ce changement, le routage renvoie un objet typé comme celui-ci plutôt que du texte arbitraire :

RouteDecision(source="faiss")

Indiquer à LangGraph où aller ensuite

Les arêtes conditionnelles nécessitent une fonction qui indique quel branch suivre. Celle-ci ne prend aucune décision par elle-même ; elle lit le choix déjà enregistré dans l’état par le routeur et le renvoie à LangGraph :

def route_source(
    state: AgentState,
) -> Literal["faiss", "tavily", "gemini"]:
    return state["source"]

Un nœud de réponse pour chaque itinéraire

Tous les trois branches aboutissent à l’étape de génération identique. Elle examine source et crée en conséquence une requête : des documents récupérés combinés en contexte pour FAISS, des extraits de recherche comme matériel de référence pour Tavily, ou la question brute pour des réponses directes :

# Generate the answer for the user
def generateAnswer(state:AgentState) -> AgentState:
    source = state["source"]
    question = state['question']
    documents = state['documents']
    tavilyResponse = state['tavilyResponse']

    if source == "faiss":
        context = "\n\n".join([doc.page_content for doc in documents])
        prompt = f"""Based on the following context answer the question below
           Context:
           {context}

        Question:
        {question}
           """
    elif source == "tavily":
        prompt = f""" Based on the following search result , use this as an reference and provdie the
         answer to the below question

         Context:
         {tavilyResponse}

         Question:
         {question}
        """
    else:
        prompt = f" Answer the following question : {question}"
    response = model.invoke(prompt)
    answer = response.content

    return {**state, "answer":answer}

Le même modèle Gemini génère chaque réponse. La seule chose qui varie est le contexte placé devant lui, ce qui constitue l’idée fondamentale de RAG : des entrées de meilleure qualité, et non un modèle différent, produisent des réponses fondées.

Assurer la cohérence des noms lors de l’assemblage des extraits

Ces extraits combinent deux styles de nommage, et les incohérences provoqueront des erreurs si vous les collez ensemble sans modification. L’état déclare tavily_response tandis que les nœuds lisent et écrivent tavilyResponse ; le client est créé sous le nom de tavily_client mais est appelé tavilyClient ; et la fonction de réponse est définie comme generateAnswer mais enregistrée sous le nom de generate_answer. Choisissez une convention et appliquez-la partout avant d’exécuter le graphe.

Montage du graphe

À ce stade, les éléments consistent en un nœud de décision ainsi que trois possibilités de continuation :

decide_source
      |
      +----> faiss
      |
      +----> tavily
      |
      +----> gemini

Il y a une subtilité ici. Le chemin gemini n’est pas du tout une étape de récupération ; il signifie « sauter la récupération et répondre directement ». Ainsi, au lieu de créer un nœud vide pour ce cas, cette branche peut pointer directement vers le nœud de génération partagé.

Commencez par créer un graphe représentant le type d’état :

workflow = StateGraph(AgentState)

Enregistrez les quatre nœuds :

workflow.add_node("decide", decide_source)
workflow.add_node("faiss", retrieve_from_faiss)
workflow.add_node("tavily", search_with_tavily)
workflow.add_node("generate", generate_answer)

Faites du nœud de décision le point d’entrée :

workflow.add_edge(START, "decide")

Connectez les arêtes conditionnelles. La mise en correspondance transforme chaque valeur que la fonction de routage peut retourner en un nom de nœud, c’est là que gemini est envoyé directement vers generate :

workflow.add_conditional_edges(
    "decide",
    route_source,
    {
        "faiss": "faiss",
        "tavily": "tavily",
        "gemini": "generate",
    },
)

Les branches de récupération et de recherche doivent ensuite conduire à la génération de la réponse :

workflow.add_edge("faiss", "generate")
workflow.add_edge("tavily", "generate")

La génération est l’étape finale avant la fin du graphe :

workflow.add_edge("generate", END)

La compilation transforme cette définition en une application exécutable :

app = workflow.compile()

Le graphe final

Le flux complet, du début à la fin :

START
                           |
                           v
                    +--------------+
                    |    decide    |
                    |    source    |
                    +--------------+
                     /      |      \
                    /       |       \
                   v        v        v
               +------+ +--------+ +---------+
               |FAISS | | Tavily | | Generate|
               |      | |        | | directly|
               +------+ +--------+ +---------+
                   \        |         /
                    \       |        /
                     v      v       v
                    +----------------+
                    |    generate    |
                    |     answer     |
                    +----------------+
                            |
                            v
                           END

Gardez à l’esprit le principe fondamental : le graphe définit l’ensemble des chemins possibles, et le modèle en choisit un à l’exécution. C’est cette combinaison qui rend le flux de travail agent sans en faire quelque chose d’imprévisible.

Essayer les trois approches

def ask_question(question: str):
    initial_state = {
        "question":question,
        "documents":[],
        "tavilyResponse":"",
        "source":""
    }

    result = app.invoke(initial_state)
    return result

Une question qui nécessite le web

La question météorologique doit être envoyée à Tavily :

result = ask_question(
    "What is the current weather in Uttarakhand?"
)

Affichez à la fois la source choisie et la réponse générée :

print("Source:", result["source"])
print("Answer:", result["answer"])

Le chemin attendu :

Source: tavily

Les conditions actuelles n’existent que sur le web.

Une question de connaissance générale

Ensuite, posez une question sur la génération augmentée par récupération d’informations :

result = ask_question(
    "What is Retrieval Augmented Generation?"
)

Affichez le résultat de la même manière :

print("Source:", result["source"])
print("Answer:", result["answer"])

Le routeur devrait omettre complètement la récupération d’informations :

Source: gemini

Le modèle peut expliquer le concept par lui-même.

Une question concernant la politique interne

Finalement, la question relative à la politique de congés :

result = ask_question(
    "What is our company leave policy?"
)

Et les mêmes instructions print :

print("Source:", result["source"])
print("Answer:", result["answer"])

Cette fois, la base de connaissances interne devrait l’emporter :

Source: faiss

Le routage des LLM est probabiliste, il faut donc considérer ces résultats comme attendus et non comme des garanties.

Pourquoi le routage sémantique bat les règles basées sur des mots-clés

Voici à nouveau l’alternative codée en dur :

if "weather" in question:
    use_tavily()
elif "leave" in question:
    use_faiss()
else:
    use_gemini()

De telles règles s’usent rapidement. Prenons l’exemple d’un utilisateur qui demande si le bureau est ouvert le samedi. Rien dans cette phrase ne mentionne de politique, pourtant la réponse se trouve très probablement dans la documentation interne. Un routeur basé sur un modèle peut déduire l’intention ; une liste de mots-clés ne le peut pas, à moins que quelqu’un n’anticipe toutes les formulations possibles.

Cette approche s’échelle également mieux. Lorsque de nouveaux backends apparaissent, comme ceux ci-dessous, on étend le schéma et la instruction plutôt que de créer un enchevêtrement de conditions :

SQL Database
Internal API
CRM
Customer Support System
Documentation
Web Search

Le compromis consiste en une appel supplémentaire au modèle, avec ses coûts et sa latence, avant même que le travail réel ne commence.

S’agit-il vraiment d’un agent IA ?

Il est plus exact de le qualifier de petit flux de travail agent que d’agent autonome. Les actions disponibles sont définies à l’avance :

FAISS
Tavily
Direct Gemini

Le modèle ne peut pas décider de son propre chef d’effectuer quelque chose comme suit, car ces capacités ne lui ont jamais été conférées :

delete a database
send an email
call an arbitrary API

L’architecture ressemble à une chaîne de responsabilités :

Developer defines possible actions
              |
              v
        LLM chooses action
              |
              v
        LangGraph executes
              |
              v
           Result

Cette contrainte est en fait un avantage : des choix limités permettent de garder le comportement prévisible et auditable en production.

Le rôle de chaque composant

LangGraph : le moteur de flux de travail

LangGraph contrôle la structure de l’application : l’état, les nœuds, les arêtes, le routage conditionnel ainsi que l’ordre d’exécution. En théorie, chaque étape suit le même schéma :

State
  |
  v
Node
  |
  v
Updated State
  |
  v
Conditional Edge
  |
  +----> Node A
  |
  +----> Node B
  |
  +----> Node C

Un graphe composé de petites étapes est plus facile à tester et à observer qu’une fonction complexe et étendue.

FAISS : la couche de récupération

FAISS gère la partie RAG du système. De manière simplifiée :

Company Documents
       |
       v
   Embeddings
       |
       v
     FAISS
       |
       v
Similar Documents
       |
       v
     Gemini
       |
       v
     Answer

Une mise en production réelle ajoute un pipeline d’ingestion devant ce système :

Documents
   |
   v
Load
   |
   v
Split into chunks
   |
   v
Generate embeddings
   |
   v
Store vectors
   |
   v
Retrieve relevant chunks
   |
   v
Generate answer

L’exemple omet le chargement et la segmentation des données pour se concentrer sur le flux de travail ; les guides réels nécessitent ces deux étapes.

Tavily : recherche web en temps réel

Tavily traite les informations qui évoluent au fil du temps :

User Question
      |
      v
   Router
      |
      v
    Tavily
      |
      v
 Search Results
      |
      v
    Gemini
      |
      v
    Answer

Les cas typiques incluent la météo en temps réel, les actualités urgentes, les nouvelles sorties de produits, la documentation à jour, les événements récents ainsi que des données de marché en direct. En environnement de production, il faut également déterminer comment les résultats de recherche seront cités, filtrés, validés et présentés, car le contenu web n’est ni garanti précis ni sûr à transmettre à un modèle sans vérification.

Le schéma à retenir

Les composants sont interchangeables. L’idée fondamentale réside dans le routage : associer chaque question à la capacité la plus adaptée pour y répondre.

Internal Knowledge
        |
        +------ FAISS
Current Information
        |
        +------ Tavily
General Knowledge
        |
        +------ Gemini

Ce choix de capacité par requête réapparaît dans presque toutes les applications agentes sérieuses.

Où aller ensuite

Ajouter plus d’outils

Le routeur pourrait choisir parmi bien d’autres backends :

SQL Database
REST APIs
CRM
Email
Calendar
Internal Documentation

Améliorer la récupération

Les trois documents présentés ne constituent qu’une démonstration, et non une base de connaissances. Un véritable système RAG nécessite des chargeurs de documents, un découpage en blocs, des métadonnées, de meilleures stratégies de recherche, un réclassement des résultats, des citations des sources ainsi qu’un contrôle d’accès, afin que les utilisateurs ne récupèrent que ce à quoi ils ont droit.

Vérifier les décisions de routage

Une étape de validation entre le routeur et les outils peut rejeter des choix peu plausibles et permettre un retour en arrière sécurisé :

Router
   |
   v
Validator
   |
   +---- valid ----> Tool
   |
   +---- invalid --> Fallback

Cela devient d’autant plus important à mesure que le nombre d’outils augmente.

Gérer les échecs

Une sortie structurée garantit un itinéraire valide, mais pas nécessairement une appel à outil réussi. Une API de recherche peut expirer ou ne rien retourner :

Router
   |
   v
Tavily
   |
   X
Search failed
   |
   v
Fallback

Les systèmes de production nécessitent des tentatives répétées ainsi qu’un mécanisme de secours, comme une réponse directe accompagnée d’une mise en garde claire. L’article sur la conception de graphes d’agents résilients avec tentatives répétées et mécanismes de secours aborde ce sujet plus en détail.

Rendre cela observable

Lorsqu’une réponse est incorrecte, il faut savoir à quel stade l’opération a échoué :

Wrong route?
      |
      v
Bad retrieval?
      |
      v
Bad search results?
      |
      v
Bad generation?

En enregistrant la source sélectionnée ainsi que les résultats intermédiaires, il devient possible de répondre à cette question.

Introduire des boucles

Le graphique actuel ne prend qu’une seule décision :

Question
   |
   v
Router
   |
   v
Tool
   |
   v
Answer

Un agent plus performant évalue ce qu’il a trouvé et décide s’il doit agir à nouveau :

Question
   |
   v
Reason
   |
   v
Tool
   |
   v
Evaluate Result
   |
   +---- Need more information?
   |            |
   |            v
   |          Tool
   |            |
   +------------+
   |
   v
Final Answer

C’est ici que les systèmes agents acquièrent une véritable puissance : le modèle détermine si les informations disponibles sont suffisantes ou s’une autre action est nécessaire. C’est également ici qu’il faut impérativement des limites d’itération, afin qu’une boucle ne puisse pas s’exécuter indéfiniment.

Points clés

Tout le système répond à une seule question : comment une application d’IA doit-elle décider de l’origine d’une réponse ? Le flux de travail final se présente comme suit :

User Question
                   |
                   v
                Gemini
                Router
                   |
        +----------+----------+
        |          |          |
        v          v          v
      FAISS      Tavily    Gemini
   Internal DB    Web      Direct
        |          |          |
        +----------+----------+
                   |
                   v
                Gemini
              Final Answer
  • Définissez les capacités et les limites dans le graphe ; laissez le modèle choisir parmi elles en temps de exécution.
  • Utilisez des sorties structurées pour les décisions de routage, et assurez-vous que le nœud de décision appelle réellement le modèle structuré.
  • Gardez un seul nœud de génération de réponse et modifiez uniquement le contexte que vous lui fournissez.
  • Considérez le routage comme un composant testable : enregistrez les décisions et vérifiez-les à l’aide de questions représentatives.
  • Ajoutez des mécanismes de validation, des solutions de secours et des capacités d’observation avant d’ajouter de nouveaux outils, car chaque nouvel outil multiplie les façons dont une requête peut échouer.
  • À partir de là, cette même base s’étend naturellement aux bases de données SQL, aux API, à la mémoire, à l’approbation humaine, aux nœuds d’évaluation, aux tentatives de réessai et aux configurations multi-agents. Le graphe se développe, mais le principe reste inchangé : donnez au modèle des capacités utiles, définez comment elles peuvent être utilisées, et laissez-le choisir celle qui convient à la tâche.

    Lectures complémentaires