Accueil Big Data Créer un chatbot d’assurance qualité avec Haystack

Créer un chatbot d’assurance qualité avec Haystack

0
Créer un chatbot d’assurance qualité avec Haystack


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.

Arxiv

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.

Construire le 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.

Interface radio |  Arxiv

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.

Chatbot Arxiv

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

T1. Comment créer un chatbot IA personnalisé ?

A. Créez des chatbots IA personnalisés à l’aide de frameworks NLP modernes tels que Haystack, Llama Index et Langchain.

Q2. Que sont les chatbots d’assurance qualité ?

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.

Q3. Qu’est-ce que la botte de foin ?

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.

Q3. Comment pouvez-vous utiliser Arxiv ?

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.

Q4. Qu’est-ce que le chatbot IA ?

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.

Q5. Puis-je créer un chatbot gratuitement ?

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.

LAISSER UN COMMENTAIRE

S'il vous plaît entrez votre commentaire!
S'il vous plaît entrez votre nom ici