Introduction
Les questions et réponses sur des données personnalisées constituent l’un des cas d’utilisation les plus recherchés des grands modèles linguistiques. Les compétences conversationnelles humaines des LLM, combinées aux méthodes de récupération de vecteurs, facilitent grandement l’extraction de réponses à partir de documents volumineux. Avec quelques variantes, nous pouvons créer des systèmes pour interagir avec toutes les données (structurées, non structurées et semi-structurées) stockées sous forme d’intégrations dans une base de données vectorielle. Cette méthode permettant d’augmenter les LLM avec des données récupérées en fonction des scores de similarité entre l’intégration de requêtes et l’intégration de documents est appelée RAG ou Génération Augmentée de Récupération. Cette méthode peut faciliter beaucoup de choses, comme la lecture d’articles arXiv.
Si vous êtes passionné d’IA et d’informatique, vous devez avoir entendu « arXiv » au moins une fois. arXiv est un référentiel en libre accès pour les pré-impressions et post-impressions électroniques. Il héberge des articles vérifiés mais non évalués par des pairs sur divers sujets, tels que le ML, l’IA, les mathématiques, la physique, les statistiques, l’électronique, etc. L’arXiv a joué un rôle central dans la promotion de la recherche ouverte sur l’IA et les sciences dures. Mais la lecture d’articles de recherche est souvent ardue et prend beaucoup de temps. Alors, pouvons-nous améliorer un peu les choses en utilisant un chatbot RAG qui nous permet d’extraire le contenu pertinent du journal et de nous chercher des réponses ?
Dans cet article, nous allons créer un chatbot RAG pour les articles aXiv à l’aide d’un outil open source appelé Haystack.

Objectifs d’apprentissage
- Comprenez ce qu’est Haystack ? Et ce sont des composants pour créer des applications basées sur LLM.
- Créez un composant pour récupérer les articles Arxiv à l’aide de la bibliothèque « arxiv ».
- Découvrez comment créer des pipelines d’indexation et de requêtes avec des nœuds Haystack.
- Apprenez à créer une interface de discussion avec Gradio, à coordonner des pipelines pour récupérer des documents à partir d’un magasin de vecteurs et à générer des réponses à partir d’un LLM.
Cet article a été publié dans le cadre du Blogathon sur la science des données.
Qu’est-ce que la botte de foin ?
Haystack est un framework NLP open source tout-en-un permettant de créer des applications évolutives basées sur LLM. Haystack fournit une approche hautement modulaire et personnalisable pour créer des applications NLP prêtes pour la production telles que la recherche sémantique, la réponse aux questions, RAG, etc. Il est construit autour du concept de pipelines et de nœuds ; les pipelines offrent une approche très rationalisée pour organiser les nœuds afin de créer des applications NLP efficaces.
- Nœuds: Les nœuds sont les éléments fondamentaux de Haystack. Un nœud accomplit une seule chose, comme le prétraitement de documents, la récupération à partir de magasins de vecteurs, la génération de réponses à partir de LLM, etc.
- Pipeline: Le pipeline permet de connecter un nœud à un autre pour construire une chaîne de nœuds. Cela facilite la création d’applications avec Haystack.
Haystack prend également en charge immédiatement les principaux magasins de vecteurs, tels que Weaviate, Milvus, Elastic Search, Qdrant, etc. Reportez-vous au référentiel public Haystack pour en savoir plus : https://github.com/deepset-ai/haystack.
Ainsi, dans cet article, nous utiliserons Haystack pour créer un chatbot de questions-réponses pour les articles Arxiv avec une interface Gradio.
Gradio
Gradio est une solution open source de Huggingface permettant de configurer et de partager une démo de n’importe quelle application de Machine Learning. Il est alimenté par Fastapi sur le backend et svelte pour les composants front-end. Il nous permet d’écrire des applications Web personnalisables avec Python. Idéal pour créer et partager des applications de démonstration pour des modèles d’apprentissage automatique ou des preuves de concepts. Pour en savoir plus, visitez le site officiel de Gradio GitHub. Pour en savoir plus sur la création d’applications avec Gradio, reportez-vous à cet article, «Créons Chat GPT avec Gradio.»
Construire le chatbot
Avant de créer l’application, décrivons brièvement le flux de travail. Cela commence par un utilisateur donnant l’identifiant du document Arxiv et se termine par la réception de réponses aux requêtes. Voici donc un workflow simple de notre chatbot Arxiv.

Nous avons deux pipelines : le pipeline d’indexation et le pipeline de requêtes. Lorsqu’un utilisateur saisit un identifiant d’article Arxiv, il accède au composant Arxiv, qui récupère et télécharge l’article correspondant dans un répertoire spécifié et déclenche le pipeline d’indexation. Le pipeline d’indexation se compose de quatre nœuds, chacun chargé d’accomplir une seule tâche. Voyons donc ce que font ces nœuds.
Pipeline d’indexation
Dans un pipeline Haystack, la sortie du nœud précédent sera utilisée comme entrée du nœud actuel. Dans un pipeline d’indexation, l’entrée initiale est le chemin d’accès au document.
- PDFToTextConverter : la bibliothèque Arxiv nous permet de télécharger des articles au format PDF. Mais nous avons besoin des données dans le texte. Ainsi, ce nœud extrait les textes du PDF.
- Préprocesseur : les données extraites doivent être nettoyées et traitées avant de les stocker dans la base de données vectorielles. Ce nœud est responsable du nettoyage et du découpage des textes.
- EmbeddingRetriver : ce nœud définit le magasin Vector dans lequel les données doivent être stockées et le modèle d’intégration utilisé pour obtenir les intégrations.
- InMemoryDocumentStore : il s’agit du magasin de vecteurs dans lequel les intégrations sont stockées. Dans ce cas, nous avons utilisé le magasin de documents en mémoire par défaut de Haystacks. Mais vous pouvez également utiliser d’autres magasins de vecteurs, tels que Qdrant, Weaviate, Elastic Search, Milvus, etc.
Pipeline de requêtes
Le pipeline de requêtes est déclenché lorsque l’utilisateur envoie des requêtes. Le pipeline de requêtes récupère les « k » documents les plus proches des intégrations de requêtes à partir du magasin de vecteurs et génère une réponse LLM. Nous avons également quatre nœuds ici.
- Retriever : récupère le document « k » le plus proche des intégrations de requêtes à partir du magasin vectoriel.
- Échantillonneur : filtrez les documents en fonction de la probabilité cumulée des scores de similarité entre la requête et les documents en utilisant l’échantillonnage des p premiers.
- LostInTheMiddleRanker : cet algorithme réorganise les documents extraits. Il place les documents les plus pertinents au début ou à la fin du contexte.
- PromptNode : PromptNode est chargé de générer des réponses aux requêtes à partir du contexte fourni au LLM.
Il s’agissait donc du flux de travail de notre chatbot Arxiv. Passons maintenant à la partie codage.
Configurer l’environnement de développement
Avant d’installer une dépendance, créez un environnement virtuel. Vous pouvez utiliser Venv et Poetry pour créer un environnement virtuel.
python -m venv my-env-name
source bin/activate
Maintenant, installez les dépendances de développement suivantes. Pour télécharger les articles Arxiv, nous devons installer la bibliothèque Arxiv.
farm-haystack
arxiv
gradio
Maintenant, nous allons importer les bibliothèques.
import arxiv
import os
from haystack.document_stores import InMemoryDocumentStore
from haystack.nodes import (
EmbeddingRetriever,
PreProcessor,
PDFToTextConverter,
PromptNode,
PromptTemplate,
TopPSampler
)
from haystack.nodes.ranker import LostInTheMiddleRanker
from haystack.pipelines import Pipeline
import gradio as gr
Création du composant Arxiv
Ce composant sera responsable du téléchargement et du stockage des fichiers PDF Arxiv. Donc. voici comment nous définissons le composant.
class ArxivComponent:
"""
This component is responsible for retrieving arXiv articles based on an arXiv ID.
"""
def run(self, arxiv_id: str = None):
"""
Retrieves and stores an arXiv article for the given arXiv ID.
Args:
arxiv_id (str): ArXiv ID of the article to be retrieved.
"""
# Set the directory path where arXiv articles will be stored
dir: str = DIR
# Create an instance of the arXiv client
arxiv_client = arxiv.Client()
# Check if an arXiv ID is provided; if not, raise an error
if arxiv_id is None:
raise ValueError("Please provide the arXiv ID of the article to be retrieved.")
# Search for the arXiv article using the provided arXiv ID
search = arxiv.Search(id_list=[arxiv_id])
response = arxiv_client.results(search)
paper = next(response) # Get the first result
title = paper.title # Extract the title of the article
# Check if the specified directory exists
if os.path.isdir(dir):
# Check if the PDF file for the article already exists
if os.path.isfile(dir + "/" + title + ".pdf"):
return {"file_path": [dir + "/" + title + ".pdf"]}
else:
# If the directory does not exist, create it
os.mkdir(dir)
# Attempt to download the PDF for the arXiv article
try:
paper.download_pdf(dirpath=dir, filename=title + ".pdf")
return {"file_path": [dir + "/" + title + ".pdf"]}
except:
# If there's an error during the download, raise a ConnectionError
raise ConnectionError(message=f"Error occurred while downloading PDF for \
arXiv article with ID: {arxiv_id}")
Le composant ci-dessus initialise un client Arxiv, puis récupère l’article Arxiv associé à l’ID et vérifie s’il a déjà été téléchargé ; il renvoie le chemin du PDF ou le télécharge dans le répertoire.
Construire le pipeline d’indexation
Nous allons maintenant définir le pipeline d’indexation pour traiter et stocker les documents dans notre base de données vectorielles.
document_store = InMemoryDocumentStore()
embedding_retriever = EmbeddingRetriever(
document_store=document_store,
embedding_model="sentence-transformers/All-MiniLM-L6-V2",
model_format="sentence_transformers",
top_k=10
)
def indexing_pipeline(file_path: str = None):
pdf_converter = PDFToTextConverter()
preprocessor = PreProcessor(split_by="word", split_length=250, split_overlap=30)
indexing_pipeline = Pipeline()
indexing_pipeline.add_node(
component=pdf_converter,
name="PDFConverter",
inputs=["File"]
)
indexing_pipeline.add_node(
component=preprocessor,
name="PreProcessor",
inputs=["PDFConverter"]
)
indexing_pipeline.add_node(
component=embedding_retriever,
name="EmbeddingRetriever",
inputs=["PreProcessor"]
)
indexing_pipeline.add_node(
component=document_store,
name="InMemoryDocumentStore",
inputs=["EmbeddingRetriever"]
)
indexing_pipeline.run(file_paths=file_path)
Tout d’abord, nous définissons notre magasin de documents en mémoire, puis nous intégrons le récupérateur. Dans l’embedding-retriever, nous spécifions le magasin de documents, les modèles d’intégration et le nombre de documents à récupérer.
Nous avons également défini les quatre nœuds dont nous avons parlé plus tôt. Le pdf_converter convertit le PDF en texte, le préprocesseur nettoie et crée des morceaux de texte, l’embedding_retriever réalise des intégrations de documents et InMemoryDocumentStore stocke les intégrations vectorielles. La méthode run avec le chemin du fichier déclenche le pipeline et chaque nœud est exécuté dans l’ordre dans lequel il a été défini. Vous pouvez également remarquer comment chaque nœud utilise les sorties des nœuds précédents comme entrées.
Construire le pipeline de requêtes
Le pipeline de requêtes se compose également de quatre nœuds. Ceci est responsable de l’intégration du texte interrogé, de la recherche de documents similaires dans les magasins de vecteurs et enfin de la génération de réponses de LLM.
def query_pipeline(query: str = None):
if not query:
raise gr.Error("Please provide a query.")
prompt_text = """
Synthesize a comprehensive answer from the provided paragraphs of an Arxiv
article and the given question.\n
Focus on the question and avoid unnecessary information in your answer.\n
\n\n Paragraphs: {join(documents)} \n\n Question: {query} \n\n Answer:
"""
prompt_node = PromptNode(
"gpt-3.5-turbo",
default_prompt_template=PromptTemplate(prompt_text),
api_key="api-key",
max_length=768,
model_kwargs={"stream": False},
)
query_pipeline = Pipeline()
query_pipeline.add_node(
component = embedding_retriever,
name = "Retriever",
inputs=["Query"]
)
query_pipeline.add_node(
component=TopPSampler(
top_p=0.90),
name="Sampler",
inputs=["Retriever"]
)
query_pipeline.add_node(
component=LostInTheMiddleRanker(1024),
name="LostInTheMiddleRanker",
inputs=["Sampler"]
)
query_pipeline.add_node(
component=prompt_node,
name="Prompt",
inputs=["LostInTheMiddleRanker"]
)
pipeline_obj = query_pipeline.run(query = query)
return pipeline_obj["results"]
L’embedding_retriever récupère «k» documents similaires du magasin de vecteurs. L’échantillonneur est responsable de l’échantillonnage des documents. Le LostInTheMiddleRanker classe les documents au début ou à la fin du contexte en fonction de leur pertinence. Enfin, le prompt_node, où le LLM est « gpt-3.5-turbo ». Nous avons également ajouté un modèle d’invite pour ajouter plus de contexte à la conversation. La méthode run renvoie un objet pipeline, un dictionnaire.
C’était notre back-end. Maintenant, nous concevons l’interface.
Interface radio
Celui-ci a une classe Blocks pour créer une interface Web personnalisable. Ainsi, pour ce projet, nous avons besoin d’une zone de texte qui prend l’ID Arxiv comme entrée utilisateur, d’une interface de discussion et d’une zone de texte qui prend en charge les requêtes des utilisateurs. C’est ainsi que nous pouvons procéder.
with gr.Blocks() as demo:
with gr.Row():
with gr.Column(scale=60):
text_box = gr.Textbox(placeholder="Input Arxiv ID",
interactive=True).style(container=False)
with gr.Column(scale=40):
submit_id_btn = gr.Button(value="Submit")
with gr.Row():
chatbot = gr.Chatbot(value=[]).style(height=600)
with gr.Row():
with gr.Column(scale=70):
query = gr.Textbox(placeholder = "Enter query string",
interactive=True).style(container=False)
Exécutez la commande gradio app.py dans votre ligne de commande et visitez l’URL localhost affichée.

Maintenant, nous devons définir les événements déclencheurs.
submit_id_btn.click(
fn = embed_arxiv,
inputs=[text_box],
outputs=[text_box],
)
query.submit(
fn=add_text,
inputs=[chatbot, query],
outputs=[chatbot, ],
queue=False
).success(
fn=get_response,
inputs = [chatbot, query],
outputs = [chatbot,]
)
demo.queue()
demo.launch()
Pour faire fonctionner les événements, nous devons définir les fonctions mentionnées dans chaque événement. Cliquez sur submit_iid_btn, envoyez l’entrée de la zone de texte en tant que paramètre à la fonction embed_arxiv. Cette fonction coordonnera la récupération et le stockage du PDF Arxiv dans le magasin de vecteurs.
arxiv_obj = ArxivComponent()
def embed_arxiv(arxiv_id: str):
"""
Args:
arxiv_id: Arxiv ID of the article to be retrieved.
"""
global FILE_PATH
dir: str = DIR
file_path: str = None
if not arxiv_id:
raise gr.Error("Provide an Arxiv ID")
file_path_dict = arxiv_obj.run(arxiv_id)
file_path = file_path_dict["file_path"]
FILE_PATH = file_path
indexing_pipeline(file_path=file_path)
return"Successfully embedded the file"
Nous avons défini un objet ArxivComponent et la fonction embed_arxiv. Il exécute la méthode « run » et utilise le chemin du fichier renvoyé comme paramètre du pipeline d’indexation.
Passons maintenant à l’événement submit avec la fonction add_text comme paramètre. Ceci est responsable du rendu du chat dans l’interface de chat.
def add_text(history, text: str):
if not text:
raise gr.Error('enter text')
history = history + [(text,'')]
return history
Maintenant, nous définissons la fonction get_response, qui récupère et diffuse les réponses LLM dans l’interface de discussion.
def get_response(history, query: str):
if not query:
gr.Error("Please provide a query.")
response = query_pipeline(query=query)
for text in response[0]:
history[-1][1] += text
yield history, ""
Cette fonction prend la chaîne de requête et la transmet au pipeline de requête pour obtenir une réponse. Enfin, nous parcourons la chaîne de réponse et la renvoyons au chatbot.
Mettre tous ensemble.
# Create an instance of the ArxivComponent class
arxiv_obj = ArxivComponent()
def embed_arxiv(arxiv_id: str):
"""
Retrieves and embeds an arXiv article for the given arXiv ID.
Args:
arxiv_id (str): ArXiv ID of the article to be retrieved.
"""
# Access the global FILE_PATH variable
global FILE_PATH
# Set the directory where arXiv articles are stored
dir: str = DIR
# Initialize file_path to None
file_path: str = None
# Check if arXiv ID is provided
if not arxiv_id:
raise gr.Error("Provide an Arxiv ID")
# Call the ArxivComponent's run method to retrieve and store the arXiv article
file_path_dict = arxiv_obj.run(arxiv_id)
# Extract the file path from the dictionary
file_path = file_path_dict["file_path"]
# Update the global FILE_PATH variable
FILE_PATH = file_path
# Call the indexing_pipeline function to process the downloaded article
indexing_pipeline(file_path=file_path)
return "Successfully embedded the file"
def get_response(history, query: str):
if not query:
gr.Error("Please provide a query.")
# Call the query_pipeline function to process the user's query
response = query_pipeline(query=query)
# Append the response to the chat history
for text in response[0]:
history[-1][1] += text
yield history
def add_text(history, text: str):
if not text:
raise gr.Error('Enter text')
# Add user-provided text to the chat history
history = history + [(text, '')]
return history
# Create a Gradio interface using Blocks
with gr.Blocks() as demo:
with gr.Row():
with gr.Column(scale=60):
# Text input for Arxiv ID
text_box = gr.Textbox(placeholder="Input Arxiv ID",
interactive=True).style(container=False)
with gr.Column(scale=40):
# Button to submit Arxiv ID
submit_id_btn = gr.Button(value="Submit")
with gr.Row():
# Chatbot interface
chatbot = gr.Chatbot(value=[]).style(height=600)
with gr.Row():
with gr.Column(scale=70):
# Text input for user queries
query = gr.Textbox(placeholder="Enter query string",
interactive=True).style(container=False)
# Define the actions for button click and query submission
submit_id_btn.click(
fn=embed_arxiv,
inputs=[text_box],
outputs=[text_box],
)
query.submit(
fn=add_text,
inputs=[chatbot, query],
outputs=[chatbot, ],
queue=False
).success(
fn=get_response,
inputs=[chatbot, query],
outputs=[chatbot,]
)
# Queue and launch the interface
demo.queue()
demo.launch()
Exécutez l’application à l’aide de la commande gradio app.py et visitez l’URL pour interagir avec le chatbot Arxic.
Voilà à quoi cela ressemblera.

Voici le référentiel GitHub de l’application sunilkumardash9/chat-arxiv.
Améliorations possibles
Nous avons réussi à créer une application simple pour discuter avec n’importe quel journal Arxiv, mais quelques améliorations peuvent être apportées.
- Magasin vectoriel autonome: Au lieu d’utiliser le magasin de vecteurs prêt à l’emploi, vous pouvez utiliser les magasins de vecteurs autonomes disponibles avec Haystack, tels que Weaviate, Milvus, etc. Cela vous donnera non seulement plus de flexibilité, mais également des améliorations significatives des performances.
- Citations: Nous pouvons ajouter de la certitude aux réponses LLM en ajoutant des citations appropriées.
- Plus de fonctionnalités: Au lieu d’une simple interface de discussion, nous pouvons ajouter des fonctionnalités pour restituer les pages de PDF utilisées comme sources pour les réponses LLM. Consultez cet article, « Créez un ChatGPT pour les PDF avec Langchain« , et le Dépôt GitHub pour une application similaire.
- L’extrémité avant: Une interface meilleure et plus interactive serait bien meilleure.
Conclusion
Il s’agissait donc de créer une application de chat pour les journaux Arxiv. Cette application ne se limite pas à Arxiv. Nous pouvons également étendre cela à d’autres sites, comme PubMed. Avec quelques modifications, nous pouvons également utiliser une architecture similaire pour discuter avec n’importe quel site Web. Ainsi, dans cet article, nous sommes passés de la création d’un composant Arxiv pour télécharger des articles Arxiv à leur intégration à l’aide de pipelines de botte de foin et enfin à la récupération des réponses du LLM.
Points clés à retenir
- Haystack est une solution open source permettant de créer des applications NLP évolutives et prêtes pour la production.
- Haystack propose une approche hautement modulaire pour créer des applications du monde réel. Il fournit des nœuds et des pipelines pour rationaliser la récupération d’informations, le prétraitement des données, l’intégration et la génération de réponses.
- Il s’agit d’une bibliothèque open source de Huggingface permettant de prototyper rapidement n’importe quelle application. Il offre un moyen simple de partager des modèles ML avec n’importe qui.
- Utilisez un flux de travail similaire pour créer des applications de chat pour d’autres sites, tels que PubMed.
Questions fréquemment posées
A. Créez des chatbots IA personnalisés à l’aide de frameworks NLP modernes tels que Haystack, Llama Index et Langchain.
A. Les chatbots de réponse aux questions sont spécialement conçus à l’aide de méthodes NLP de pointe pour répondre aux questions sur des données personnalisées, telles que des PDF, des feuilles de calcul, des CSV, etc.
A. Haystack est un framework NLP open source permettant de créer des applications basées sur LLM, telles que des agents IA, QA, RAG, etc.
A. Arxiv est un référentiel en libre accès permettant de publier des articles de recherche dans diverses catégories, notamment les mathématiques, l’informatique, la physique, les statistiques, etc.
A. Les chatbots IA utilisent des technologies de pointe de traitement du langage naturel pour offrir des capacités de conversation semblables à celles des humains.
A. Créez un chatbot gratuitement à l’aide de frameworks open source comme Langchain, haystack, etc. Mais l’inférence à partir de LLM, comme get-3.5, coûte de l’argent.
Les médias présentés dans cet article n’appartiennent pas à Analytics Vidhya et sont utilisés à la discrétion de l’auteur.