Introduction
Dans l'article précédent, nous avons étendu notre chatbot avec une classification d'intention et un routage conditionnel. Notre chatbot peut désormais déterminer si une question de l'utilisateur concerne Hiwa AI avant de décider s'il doit continuer à traiter la demande.
Le graphe ressemble à ceci :

Cependant, notre chatbot présente encore une limitation importante.
Il peut déterminer si une question est liée à Hiwa AI, mais le modèle de langage lui-même ne connaît pas nécessairement les informations actuelles sur le site web de l'entreprise. Par exemple, un utilisateur pourrait demander :
Quels services Hiwa AI propose-t-il ?
On ne peut pas s'attendre à ce que le modèle connaisse la réponse simplement parce que l'information existe sur le site web de l'entreprise.
Nous pourrions intégrer tout le contenu du site web dans l'invite système, mais cette approche ne passe pas à l'échelle. Un site web peut contenir des centaines ou des milliers de pages, et envoyer toutes ces informations au modèle pour chaque question serait extrêmement inefficace. À la place, nous avons besoin d'un moyen de récupérer uniquement les informations pertinentes par rapport à la question de l'utilisateur.
C'est là qu'intervient la Génération Augmentée par Récupération (RAG).
Qu'est-ce que le RAG ?
RAG signifie Génération Augmentée par Récupération.
L'idée de base est simple :
- Stocker des informations dans une base de connaissances consultable.
- Recherchez dans la base de connaissances lorsque l'utilisateur pose une question.
- Récupérez les informations les plus pertinentes.
- Fournissez ces informations au modèle de langage.
- Permettez au modèle de générer une réponse en utilisant le contexte récupéré.
Au lieu de cela :

nous introduisons une étape de récupération :

Le modèle de langage reste responsable de la génération de la réponse, mais les informations utilisées pour générer cette réponse proviennent de notre propre base de connaissances. Cela est particulièrement utile lors de l'utilisation de modèles locaux, car nous n'avons pas besoin d'entraîner le modèle sur le contenu du site web.
Étape 1 : Collecte du contenu du site web
Avant de pouvoir effectuer une recherche sur le site web, nous devons en extraire le contenu. Un site web contient de nombreux types d'informations différents :
- Vous êtes un traducteur technique expert spécialisé dans les systèmes d'IA, les logiciels d'entreprise et l'architecture blockchain. Traduisez le texte avec précision en français. RÈGLES : 1. Préservez la précision technique et le ton. 2. NE traduisez PAS la syntaxe de code, les mots-clés de programmation, les noms de fonctions, les noms de variables, les URL ou les entrées en ligne de commande. 3. Affichez UNIQUEMENT la chaîne traduite sans guillemets ni commentaires conversationnels. 4. Le texte peut contenir des marqueurs sentinelles comme §L0§...§/L0§ encadrant l'étiquette visible d'un hyperlien. Traduisez normalement les mots entre une paire de marqueurs, mais reproduisez les marqueurs §L0§/§/L0§ eux-mêmes EXACTEMENT, inchangés, dans la même paire correspondante, dans la sortie. Ne les supprimez, renommez ou fusionnez jamais.
- Menus de navigation
- En-têtes et pieds de page
- Images
- Liens
- Éléments HTML
- Métadonnées
Pour notre chatbot, nous nous intéressons principalement au contenu textuel significatif. Un simple crawler pourrait récupérer une page web comme ceci :
import requests
from bs4 import BeautifulSoup
def extract_text(url: str) -> str:
response = requests.get(url)
response.raise_for_status()
soup = BeautifulSoup(response.text, "html.parser")
for element in soup(["script", "style", "nav", "footer"]):
element.decompose()
return soup.get_text(" ", strip=True)Cette fonction télécharge la page, supprime les éléments qui ne sont pas utiles pour notre base de connaissances et renvoie le texte restant. Cependant, pour un site web réel, nous avons généralement besoin d'un crawler plus sophistiqué.
Nous pourrions avoir besoin de :
- Découvrez plusieurs pages.
- Suivre les liens.
- Lisez le plan du site.
- Évitez d'explorer plusieurs fois la même page.
- Gérer le contenu généré dynamiquement.
- Stocker l'URL associée à chaque document.
L'idée importante est que le RAG commence avant le chatbot lui-même. Nous devons d'abord constituer la base de connaissances que le chatbot devra ensuite interroger.
Étape 2 : Découpage du contenu en segments
Une page web peut contenir une grande quantité de texte. Nous ne souhaitons généralement pas stocker une page entière sous forme d'un seul document géant. À la place, nous divisons le contenu en morceaux plus petits appelés chunks. Par exemple, imaginez qu'une page contienne :
- Hiwa AI propose plusieurs services alimentés par l'intelligence artificielle.
- La plateforme permet aux entreprises d'automatiser différents flux de travail.
- Les utilisateurs peuvent également intégrer des fonctionnalités d'IA dans leurs applications existantes.
- Hiwa AI propose des outils pour le traitement de documents et des assistants intelligents.
Au lieu de traiter la page entière comme un seul document, nous pouvons la diviser en sections plus petites :
- Hiwa AI propose plusieurs services alimentés par l'IA.
- La plateforme permet aux entreprises d'automatiser différents flux de travail.
- Les utilisateurs peuvent également intégrer des capacités d'IA dans leurs applications existantes.
- Chunk 4 : Hiwa AI propose des outils pour le traitement de documents et des assistants intelligents.
Cela rend la récupération beaucoup plus précise. Si l'utilisateur demande :
Que peuvent automatiser les entreprises ?
nous voulons récupérer le segment traitant de l'automatisation des workflows plutôt que l'intégralité de la page web.
Étape 3 : Création des embeddings
Maintenant, nous avons des morceaux de texte, mais nous avons encore besoin d'un moyen de les rechercher en fonction de leur signification. C'est là que les embeddings sont utiles. Un modèle d'embedding convertit le texte en une liste de nombres appelée vecteur. Par exemple :
Quels services Hiwa AI propose-t-elle ?
pourrait être converti en quelque chose de conceptuellement similaire à :
[0,12, -0,43, 0,87, 0,21, ...]
Le vecteur réel est beaucoup plus grand que cet exemple. L'idée importante est que les segments de texte sémantiquement similaires ont tendance à avoir des vecteurs similaires. Par exemple :
Quels services Hiwa AI propose-t-elle ?
et
Hiwa AI propose des services alimentés par l'IA pour les entreprises.
sont des phrases différentes, mais elles ont une signification similaire. Leurs embeddings devraient donc être relativement proches les uns des autres dans l'espace vectoriel. Nous pouvons générer un embedding à l'aide d'un modèle d'embedding :
from openai import OpenAI
client = OpenAI( api_key="YOUR_API_KEY" )
def embed_text(text: str) -> list[float]:
response = client.embeddings.create( model="BAAI/bge-m3", input=text, )
return response.data[0].embedding Le modèle d'embedding exact n'est pas important pour comprendre l'architecture. L'essentiel est que le même modèle d'embedding soit utilisé lors du stockage des documents et lors de leur recherche.
Étape 4 : Stockage des fragments
Chaque segment doit être stocké avec son embedding. Un enregistrement de base de données simple pourrait ressembler à ceci :
| id | texte | intégration | url |
|---|---|---|---|
| Quarante-deux | Hiwa AI propose des outils alimentés par l'IA pour les entreprises. | [0,12, -0,43, 0,87, ...] | §L0§https://hiwaai.com/services§/L0§ |
Le texte est important car c'est ce que nous donnerons finalement au modèle de langage. L'embedding est important car c'est ce que nous utilisons pour trouver du texte pertinent. L'URL est utile pour suivre la provenance de l'information et peut ensuite être utilisée pour fournir des citations à l'utilisateur.
Étape 5 : Recherche dans la base de connaissances
Maintenant, nous avons tout ce qui est nécessaire pour effectuer la récupération. Supposons que l'utilisateur demande :
Quels services Hiwa AI propose-t-elle ?
D'abord, nous créons un embedding pour la question.
query_embedding = embed_text( "What services does Hiwa AI provide?")Nous avons maintenant un vecteur représentant la question de l'utilisateur. Ensuite, nous comparons ce vecteur avec les vecteurs stockés dans notre base de données. Conceptuellement, le processus se présente ainsi :
Question de l'utilisateur │ ▼ Encodage de la requête │ ▼ Comparaison avec les encodages stockés │ ▼ Trouver les segments les plus similaires │ ▼ Retourner le texte pertinent
Le calcul de similarité peut utiliser différentes métriques de distance. Une métrique couramment utilisée est la similarité cosinus. Le résultat peut ressembler à :
[
{"text": "Hiwa AI provides AI-powered tools for businesses.",
"score": 0.91 },
{"text": "The platform provides intelligent document processing.",
"score": 0.87 },
{"text": "Businesses can integrate AI assistants into their workflows.",
"score": 0.82 } ] Les segments les mieux notés sont ceux que nous considérons comme les plus pertinents pour la question de l'utilisateur.
Étape 6 : Fournir les informations récupérées au LLM
À ce stade, nous disposons des informations nécessaires au modèle. Nous pouvons combiner les segments récupérés avec la question de l'utilisateur. Par exemple :
context = "\n\n".join( chunk["text"] for chunk in retrieved_chunks ) Ensuite, nous construisons une invite :
prompt = f""" You are an assistant for Hiwa AI. Answer the user's question using the provided context. Context: {context} Question: {user_question} """Nous pouvons maintenant envoyer cette invite à notre modèle de langage :
response = llm.invoke(prompt) print(response.content) L'important à noter est que le modèle n'a pas besoin de connaître le site web au préalable. Nous fournissons les informations pertinentes au moment de l'inférence. C'est l'idée fondamentale derrière le Retrieval-Augmented Generation.
Assemblage du Pipeline de Récupération
Nous pouvons maintenant combiner les étapes individuelles en une simple fonction de récupération :
def retrieve_documents( query: str, top_k: int = 3,):
query_embedding = embed_text(query)
results = search_similar_chunks(query_embedding=query_embedding, top_k=top_k,)
return resultsLe chatbot peut ensuite l'utiliser comme ceci :
user_question = "What services does Hiwa AI provide?"
documents = retrieve_documents(user_question)
context = "\n\n".join( document["text"] for document in documents )
prompt = f""" You are an assistant for Hiwa AI. Use the following context to answer the question. Context: {context} Question: {user_question} """
response = llm.invoke(prompt)
print(response.content) Le flux de travail global est désormais :

Connecter ceci à notre LangGraph
À ce stade, nous avons deux éléments distincts. De l'article précédent, nous disposons de notre classification d'intention et de routage conditionnel. Maintenant, nous avons un pipeline de récupération. L'étape suivante consiste à combiner ces deux concepts. Au lieu d'envoyer directement une question connexe au chatbot, nous pouvons d'abord la router via un nœud de récupération :

Le chatbot dispose maintenant des trois composants importants :
- Il peut comprendre l'intention de l'utilisateur.
- Il peut récupérer les informations pertinentes.
- Il peut générer une réponse en utilisant ces informations.
Dans le prochain article, nous combinerons le pipeline de récupération avec notre workflow LangGraph et construirons le chatbot RAG complet.
Conclusion
Un modèle de langage seul ne suffit pas toujours à construire une application utile. Lorsque nous avons besoin d'un chatbot capable de répondre à des questions sur une source d'informations spécifique et évolutive, nous devons trouver un moyen de fournir ces connaissances au modèle.
Le RAG résout ce problème en séparant la récupération des connaissances de la génération des réponses. Le processus est simple :

L'essentiel est que nous n'ayons pas besoin de réentraîner notre modèle de langage chaque fois que le site web change. Nous pouvons simplement mettre à jour la base de connaissances. Cela fait du RAG une approche pratique pour construire des applications d'IA qui doivent fonctionner avec des informations privées, spécifiques à un domaine ou fréquemment mises à jour.

