paint-brush
Tutoriel complet sur la création d'une application RAG à l'aide de LangChainpar@bexgboost
354 lectures
354 lectures

Tutoriel complet sur la création d'une application RAG à l'aide de LangChain

par Bex19m2024/09/03
Read on Terminal Reader

Trop long; Pour lire

Apprenez à utiliser LangChain, le framework extrêmement populaire pour la création de systèmes RAG. À la fin du tutoriel, nous aurons un chatbot (avec une interface Streamlit et tout) qui se frayera un chemin à travers des données privées pour donner des réponses aux questions.
featured image - Tutoriel complet sur la création d'une application RAG à l'aide de LangChain
Bex HackerNoon profile picture

Les grands modèles linguistiques d'aujourd'hui ont accès à une quantité toujours croissante d'informations. Cependant, il reste une vaste réserve de données privées que ces modèles n'exploitent pas. C'est pourquoi l'une des applications les plus populaires des LLM dans les environnements d'entreprise est la génération augmentée de récupération (RAG en abrégé). Vizly , notre plateforme d'analyse de données basée sur l'IA, nous a permis d'acquérir des connaissances précieuses sur la création de systèmes RAG efficaces. Dans ce tutoriel, nous partagerons certains de nos apprentissages et vous montrerons comment créer votre propre système RAG.


Vous apprendrez à utiliser LangChain, le framework très populaire pour la création de systèmes RAG, pour créer un système RAG simple. À la fin du tutoriel, nous aurons un chatbot (avec une interface Streamlit et tout) qui se frayera un chemin à travers des données privées pour donner des réponses à des questions.

Qu'est-ce que RAG ?

Pour clarifier ce qu’est RAG, considérons un exemple simple.


Chandler, un étudiant de première année à l'université, envisage de sécher quelques cours mais veut s'assurer qu'il ne viole pas la politique d'assiduité de l'université. Comme pour tout de nos jours, il pose la question à ChatGPT .


Bien sûr, ChatGPT ne peut pas répondre à cette question. Le chatbot n'est pas idiot, il n'a simplement pas accès aux documents universitaires de Chandler. Chandler trouve donc lui-même le document de politique et découvre qu'il s'agit d'une lecture longue et technique qu'il ne veut pas parcourir. Au lieu de cela, il donne l'intégralité du document à ChatGPT et repose la question. Cette fois, il obtient sa réponse.


Il s'agit d'un cas particulier de génération augmentée par récupération. La réponse (génération) du modèle de langage est augmentée (enrichie) par le contexte récupéré à partir d'une source ne faisant pas partie de son apprentissage d'origine.


Une version évolutive d’un système RAG serait capable de répondre à n’importe quelle question d’un étudiant en recherchant lui-même les documents universitaires, en trouvant ceux qui sont pertinents et en récupérant les morceaux de texte qui contiennent très probablement la réponse.


D'une manière générale, dans un système RAG, vous récupérez des informations à partir d'une source de données privée et les alimentez à un modèle de langage, permettant au modèle de donner une réponse contextuellement pertinente.

Composants d'une application RAG

Un tel système, même s'il semble simple, comporterait de nombreux composants mobiles. Avant d'en construire un nous-mêmes, nous devons examiner ce qu'ils sont et comment ils fonctionnent ensemble.

Documents

Le premier composant est un document ou un ensemble de documents. Selon le type de système RAG que nous construisons, les documents peuvent être des fichiers texte, des PDF, des pages Web (RAG sur données non structurées) ou des bases de données graphiques, SQL ou NoSQL (RAG sur données structurées). Ils sont utilisés pour ingérer différents types de données dans le système.

Chargeurs de documents

LangChain implémente des centaines de classes appelées chargeurs de documents pour lire des données à partir de diverses sources de documents telles que les PDF, Slack, Notion, Google Drive, etc.


Chaque classe de chargeur de documents est unique, mais elles partagent toutes la même méthode .load() . Par exemple, voici comment charger un document PDF et une page Web dans LangChain :

 from langchain_community.document_loaders import PyPDFLoader, WebBaseLoader # pip install langchain-community pdf_loader = PyPDFLoader("framework_docs.pdf") web_loader = WebBaseLoader( "https://python.langchain.com/v0.2/docs/concepts/#document-loaders" ) pdf_docs = pdf_loader.load() web_docs = web_loader.load()


La classe PyPDFLoader gère les fichiers PDF à l'aide du package PyPDF2 sous le capot, tandis que WebBaseLoader récupère le contenu de la page Web donnée.


pdf_docs contient quatre objets de document, un pour chaque page :


 >>> len(pdf_docs) 4


Alors que web_docs n'en contiennent qu'un :

 >>> print(web_docs[0].page_content[125:300].strip()) You can view the v0.1 docs here.IntegrationsAPI referenceLatestLegacyMorePeopleContributingCookbooks3rd party tutorialsYouTubearXivv0.2v0.2v0.1🦜️🔗LangSmithLangSmith DocsLangCh


Ces objets de document sont ensuite transmis à des modèles d'intégration pour comprendre la signification sémantique derrière leur texte.


Pour plus de détails sur d'autres types de chargeurs de documents, LangChain propose un page de conseils dédiée .

Séparateurs de texte

Une fois vos documents chargés, il est essentiel de les décomposer en morceaux de texte plus petits et plus faciles à gérer. Voici les principales raisons :

  1. De nombreux modèles d'intégration (nous y reviendrons plus tard) ont une limite maximale de jetons.
  2. La récupération est plus précise lorsque vous avez des morceaux plus petits.
  3. Le modèle de langage est alimenté par le contexte exact.


LangChain propose de nombreux types de séparateurs de texte dans son package langchain_text_splitters, et ils diffèrent en fonction du type de document.

Voici comment utiliser RecursiveCharacterTextSplitter pour diviser du texte brut en fonction d'une liste de séparateurs et de la taille des blocs :

 !pip install langchain_text_splitters from langchain_text_splitters import RecursiveCharacterTextSplitter # Example text text = """ RAG systems combine the power of large language models with external knowledge sources. This allows them to provide up-to-date and context-specific information. The process involves several steps including document loading, text splitting, and embedding. """ # Create a text splitter text_splitter = RecursiveCharacterTextSplitter( chunk_size=50, chunk_overlap=10, length_function=len, separators=["\n\n", "\n", " ", ""], ) # Split the text chunks = text_splitter.split_text(text) # Print the chunks for i, chunk in enumerate(chunks): print(f"Chunk {i + 1}: {chunk}")

Sortir:

 Chunk 1: RAG systems combine the power of large language Chunk 2: language models with external knowledge sources. Chunk 3: This allows them to provide up-to-date and Chunk 4: and context-specific information. Chunk 5: The process involves several steps including Chunk 6: including document loading, text splitting, and Chunk 7: and embedding.

Ce séparateur est polyvalent et fonctionne bien pour de nombreux cas d'utilisation. Il crée chaque bloc avec un nombre de caractères aussi proche que possible chunk_size . Il peut basculer de manière récursive entre les séparateurs à diviser pour conserver le nombre de caractères.


Dans l'exemple ci-dessus, notre séparateur essaie d'abord de se diviser sur les nouvelles lignes, puis sur les espaces simples, et enfin entre les caractères pour atteindre la taille de bloc souhaitée.


Il existe de nombreux autres séparateurs dans le package langchain_text_splitters . En voici quelques-uns :

  • HTMLSectionSplitter
  • PythonCodeTexSplitter
  • RecursiveJsonSplitter

et ainsi de suite. Certains séparateurs créent des morceaux sémantiquement significatifs en utilisant un modèle de transformateur sous le capot.


Le bon séparateur de texte a un impact significatif sur les performances d'un système RAG.


Pour plus de détails sur l'utilisation des séparateurs de texte, consultez les guides pratiques ici .

Incorporation de modèles

Une fois les documents divisés en texte, ils doivent être codés dans leur représentation numérique, ce qui est une exigence pour tous les modèles de calcul travaillant avec des données textuelles.


Dans le contexte de RAG, ce codage est appelé incorporation et est réalisé par des modèles d'incorporation . Ils créent une représentation vectorielle d'un morceau de texte qui capture sa signification sémantique. En présentant le texte de cette manière, vous pouvez effectuer des opérations mathématiques sur celui-ci, comme rechercher dans notre base de données de documents le texte le plus similaire en termes de signification ou trouver une réponse à une requête utilisateur.


LangChain prend en charge tous les principaux fournisseurs de modèles d'intégration, tels que OpenAI, Cohere, HuggingFace, etc. Ils sont implémentés en tant que classes Embedding et fournissent deux méthodes : une pour l'intégration de documents et une pour l'intégration de requêtes (invites).


Voici un exemple de code qui intègre les morceaux de texte que nous avons créés dans la section précédente à l'aide d'OpenAI :

 from langchain_openai import OpenAIEmbeddings # Initialize the OpenAI embeddings embeddings = OpenAIEmbeddings() # Embed the chunks embedded_chunks = embeddings.embed_documents(chunks) # Print the first embedded chunk to see its structure print(f"Shape of the first embedded chunk: {len(embedded_chunks[0])}") print(f"First few values of the first embedded chunk: {embedded_chunks[0][:5]}")


Sortir:

 Shape of the first embedded chunk: 1536 First few values of the first embedded chunk: [-0.020282309502363205, -0.0015041005099192262, 0.004193042870610952, 0.00229285703971982, 0.007068077567964792]

La sortie ci-dessus montre que le modèle d’intégration crée un vecteur de 1536 dimensions pour tous les blocs de nos documents.


Pour intégrer une seule requête, vous pouvez utiliser la méthode embed_query() :

 query = "What is RAG?" query_embedding = embeddings.embed_query(query) print(f"Shape of the query embedding: {len(query_embedding)}") print(f"First few values of the query embedding: {query_embedding[:5]}")


Sortir:

 Shape of the query embedding: 1536 First few values of the query embedding: [-0.012426204979419708, -0.016619959846138954, 0.007880032062530518, -0.0170428603887558, 0.011404196731746197]

Magasins de vecteurs

Dans les applications RAG à grande échelle, où vous pouvez avoir des gigaoctets de documents, vous vous retrouverez avec des milliards de morceaux de texte et donc de vecteurs. Ils ne servent à rien si vous ne pouvez pas les stocker de manière fiable.


C'est pourquoi les bases de données ou magasins de vecteurs sont à la mode aujourd'hui. En plus de stocker vos intégrations, les bases de données vectorielles se chargent d'effectuer la recherche de vecteurs pour vous. Ces bases de données sont optimisées pour trouver rapidement les vecteurs les plus similaires lorsqu'on leur donne un vecteur de requête, ce qui est essentiel pour récupérer des informations pertinentes dans les systèmes RAG.


Voici un extrait de code qui intègre le contenu d'une page Web et stocke les vecteurs dans une base de données vectorielles Chroma ( Chroma est une solution de base de données vectorielle open source qui s'exécute entièrement sur votre machine) :

 !pip install chromadb langchain_chroma from langchain_community.document_loaders import WebBaseLoader from langchain_text_splitters import RecursiveCharacterTextSplitter # Load the web page loader = WebBaseLoader("https://python.langchain.com/v0.2/docs/tutorials/rag/") docs = loader.load() # Split the documents into chunks text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200) chunks = text_splitter.split_documents(docs)


Tout d'abord, nous chargeons la page avec WebBaseLoader et créons nos blocs. Ensuite, nous pouvons directement transmettre les blocs à la méthode from_documents de Chroma avec notre modèle d'intégration choisi :

 from langchain_openai import OpenAIEmbeddings from langchain_chroma import Chroma db = Chroma.from_documents(chunks, OpenAIEmbeddings())


Tous les objets de base de données vectorielles dans LangChain exposent une méthode similarity_search qui accepte une chaîne de requête :

 query = "What is indexing in the context of RAG?" docs = db.similarity_search(query) print(docs[1].page_content)


Sortir:

 If you are interested for RAG over structured data, check out our tutorial on doing question/answering over SQL data.Concepts​A typical RAG application has two main components:Indexing: a pipeline for ingesting data from a source and indexing it. This usually happens offline.Retrieval and generation: the actual RAG chain, which takes the user query at run time and retrieves the relevant data from the index, then passes that to the model.The most common full sequence from raw data to answer looks like:Indexing​Load: First we need to load our data. This is done with Document Loaders.Split: Text splitters break large Documents into smaller chunks. This is useful both for indexing data and for passing it in to a model, since large chunks are harder to search over and won't fit in a model's finite context window.Store: We need somewhere to store and index our splits, so that they can later be searched over. This is often done using a VectorStore and Embeddings model.Retrieval and

Le résultat de similarity_search est une liste de documents qui contiennent très probablement les informations que nous demandons dans la requête.


Pour plus de détails sur la façon d'utiliser les magasins vectoriels, consultez les guides pratiques ici .

Retrievers

Bien que tous les magasins de vecteurs prennent en charge la récupération sous forme de recherche de similarité, LangChain implémente une interface Retriever dédiée qui renvoie les documents à partir d'une requête non structurée. Un récupérateur n'a besoin que de renvoyer ou de récupérer des documents, et non de les stocker.


Voici comment vous pouvez convertir n'importe quel magasin de vecteurs en récupérateur dans LangChain :

 # Convert the vector store to a retriever chroma_retriever = db.as_retriever() docs = chroma_retriever.invoke("What is indexing in the context of RAG?") >>> len(docs) 4


Il est possible de limiter le nombre de documents pertinents au top k en utilisant search_kwargs :

 chroma_retriever = db.as_retriever(search_kwargs={"k": 1}) docs = chroma_retriever.invoke("What is indexing in the context of RAG?") >>> len(docs) 1

Vous pouvez transmettre d'autres paramètres liés à la recherche à search_kwargs. Pour en savoir plus sur l'utilisation des récupérateurs, consultez guides pratiques spécifiques .

Flux de travail étape par étape pour créer une application RAG dans LangChain

Maintenant que nous avons couvert les composants clés d'un système RAG, nous allons en construire un nous-mêmes. Je vais vous guider pas à pas dans la mise en œuvre d'un chatbot RAG conçu spécifiquement pour la documentation de code et les tutoriels. Vous le trouverez particulièrement utile lorsque vous aurez besoin d'une assistance au codage de l'IA pour de nouveaux frameworks ou de nouvelles fonctionnalités de frameworks existants qui ne font pas encore partie de la base de connaissances des LLM d'aujourd'hui.

0. Création de la structure du projet

Tout d’abord, remplissez votre répertoire de travail avec la structure de projet suivante :

 rag-chatbot/ ├── .gitignore ├── requirements.txt ├── README.md ├── app.py ├── src/ │ ├── __init__.py │ ├── document_processor.py │ └── rag_chain.py └── .streamlit/ └── config.toml


Voici les commandes :

 $ touch .gitignore requirements.txt README.md app.py $ mkdir src .streamlit $ touch src/{.env,__init__.py,document_processor.py,rag_chain.py} $ touch .streamlit/{.env,config.toml}

1. Configuration de l'environnement

Dans cette étape, vous créez d’abord un nouvel environnement Conda et l’activez :

 $ conda create -n rag_tutorial python=3.9 -y $ conda activate rag_tutorial


Ensuite, ouvrez le fichier requirements.txt et collez les dépendances suivantes :

 langchain==0.2.14 langchain_community==0.2.12 langchain_core==0.2.35 langchain_openai==0.1.22 python-dotenv==1.0.1 streamlit==1.37.1 faiss-cpu pypdf

et installez-les :

 $ pip install -r requirements.txt


Créez également un fichier .gitignore pour masquer les fichiers de l'indexation git :

 # .gitignore venv/ __pycache__/ .env *.pdf *.png *.jpg *.jpeg *.gif *.svg

2. Configuration des chargeurs de documents

Ensuite, ouvrez le fichier src/document_processor.py et collez les extraits de code suivants.


Les importations nécessaires :

 import logging from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain.text_splitter import Language from langchain_community.document_loaders import PyPDFLoader from langchain_community.document_loaders.parsers.pdf import ( extract_from_images_with_rapidocr, ) from langchain.schema import Document


Explication des importations :

  • RecursiveCharacterTextSplitter : divise le texte en morceaux plus petits de manière récursive.
  • Language : Énumération permettant de spécifier les langages de programmation dans le fractionnement de texte.
  • PyPDFLoader : Charge et extrait du texte à partir de fichiers PDF.
  • extract_from_images_with_rapidocr : fonction OCR pour extraire du texte à partir d'images.
  • Document : représente un document avec du contenu et des métadonnées.
  • logging : fournit une fonctionnalité de journalisation pour le débogage et les informations.


Ensuite, une fonction pour traiter les PDF :

 def process_pdf(source): loader = PyPDFLoader(source) documents = loader.load() # Filter out scanned pages unscanned_documents = [doc for doc in documents if doc.page_content.strip() != ""] scanned_pages = len(documents) - len(unscanned_documents) if scanned_pages > 0: logging.info(f"Omitted {scanned_pages} scanned page(s) from the PDF.") if not unscanned_documents: raise ValueError( "All pages in the PDF appear to be scanned. Please use a PDF with text content." ) return split_documents(unscanned_documents)


Voici comment cela fonctionne :

  1. Il charge le PDF à l'aide PyPDFLoader .
  2. Il filtre les pages numérisées en supprimant les documents avec un contenu vide.
  3. Il enregistre le nombre de pages numérisées omises, le cas échéant.
  4. Si toutes les pages sont numérisées (c'est-à-dire sans contenu textuel), une ValueError est générée.
  5. Enfin, il divise les documents non numérisés restants en morceaux plus petits à l'aide de la fonction split_documents.

La fonction gère les cas où un PDF peut contenir un mélange de texte et de pages numérisées, garantissant que seules les pages textuelles sont traitées ultérieurement. Ceci est crucial pour les tâches d'analyse de texte où les pages numérisées sans OCR seraient inutilisables. Nous définirons la fonction split_documents plus tard.


Ensuite, nous écrivons une fonction pour récupérer des informations à partir d’images (captures d’écran d’extraits de code et/ou de pages Web) :


 def process_image(source): # Extract text from image using OCR with open(source, "rb") as image_file: image_bytes = image_file.read() extracted_text = extract_from_images_with_rapidocr([image_bytes]) documents = [Document(page_content=extracted_text, metadata={"source": source})] return split_documents(documents)


Cette fonction traite un fichier image en extrayant du texte à l'aide de la reconnaissance optique de caractères (OCR). Elle lit le fichier image, le convertit en octets, puis utilise la bibliothèque RapidOCR pour extraire le texte de l'image. Le texte extrait est ensuite enveloppé dans un objet Document avec des métadonnées contenant le chemin du fichier source. Enfin, la fonction divise le document en morceaux plus petits à l'aide de la fonction split_documents , que nous définissons ensuite :


 def split_documents(documents): # Split documents into smaller chunks for processing text_splitter = RecursiveCharacterTextSplitter.from_language( language=Language.PYTHON, chunk_size=1000, chunk_overlap=200 ) return text_splitter.split_documents(documents)


La fonction utilise la classe RecursiveCharacterTextSplitter avec la syntaxe Python pour diviser le texte en morceaux de 1000 caractères et 200 caractères se chevauchant.


Notre fonction finale combine les fonctions d'analyse PDF et d'image en une seule :


 def process_document(source): # Determine file type and process accordingly if source.lower().endswith(".pdf"): return process_pdf(source) elif source.lower().endswith((".png", ".jpg", ".jpeg")): return process_image(source) else: raise ValueError(f"Unsupported file type: {source}")


Cette fonction finale sera utilisée par l'interface utilisateur Streamlit pour créer, intégrer et stocker des morceaux de documents fournis et les transmettre au composant RAG de notre système.

3. Configuration de RAG

Maintenant, ouvrez le fichier src/rag_chain.py et collez les extraits de code suivants.


Tout d’abord, importez les modules nécessaires :


 import os from dotenv import load_dotenv from langchain.prompts import PromptTemplate from langchain_community.vectorstores import FAISS from langchain_core.output_parsers import StrOutputParser from langchain_core.runnables import RunnablePassthrough from langchain_openai import ChatOpenAI, OpenAIEmbeddings # Load the API key from env variables load_dotenv() api_key = os.getenv("OPENAI_API_KEY")


Voici une explication des importations :

os : interactions avec le système d'exploitation • dotenv : charger les variables d'environnement • composants langchain :

  • PromptTemplate : création d'invite personnalisée
  • FAISS : Un magasin vectoriel léger pour les documents
  • StrOutputParser : Conversion d'objets de message LLM en sorties de chaîne
  • RunnablePassthrough : Créer des chaînes composables
  • ChatOpenAI , OpenAIEmbeddings : interactions du modèle OpenAI


Ensuite, nous créons notre invite pour le système RAG :


 RAG_PROMPT_TEMPLATE = """ You are a helpful coding assistant that can answer questions about the provided context. The context is usually a PDF document or an image (screenshot) of a code file. Augment your answers with code snippets from the context if necessary. If you don't know the answer, say you don't know. Context: {context} Question: {question} """ PROMPT = PromptTemplate.from_template(RAG_PROMPT_TEMPLATE)


L'invite du système RAG est l'un des facteurs critiques de son succès. Notre version est simple mais fera l'affaire la plupart du temps. En pratique, vous passerez beaucoup de temps à itérer et à améliorer l'invite.


Si vous le remarquez, nous utilisons une classe PromptTemplate pour construire l'invite. Cette construction nous permet d'ingérer dynamiquement le contexte récupéré à partir des documents et de la requête de l'utilisateur dans une invite finale.


En parlant de documents, nous avons besoin d'une fonction pour les formater avant qu'ils ne soient transmis comme contexte dans l'invite du système :


 def format_docs(docs): return "\n\n".join(doc.page_content for doc in docs)


Il s'agit d'une fonction simple qui concatène le contenu de la page des documents récupérés.


Enfin, nous créons une fonction qui va développer notre chaîne RAG :


 def create_rag_chain(chunks): embeddings = OpenAIEmbeddings(api_key=api_key) doc_search = FAISS.from_documents(chunks, embeddings) retriever = doc_search.as_retriever( search_type="similarity", search_kwargs={"k": 5} ) llm = ChatOpenAI(model_name="gpt-4o-mini", temperature=0) rag_chain = ( {"context": retriever | format_docs, "question": RunnablePassthrough()} | PROMPT | llm | StrOutputParser() ) return rag_chain


La fonction accepte des morceaux de documents, qui seront fournis par la fonction process_document dans le script document_processor.py .

La fonction commence par définir le modèle d'intégration et par stocker les documents dans un magasin de vecteurs FAISS. Elle est ensuite convertie en interface de récupération avec recherche de similarité qui renvoie les cinq premiers documents correspondant à la requête de l'utilisateur.


Pour le modèle de langage, nous utiliserons gpt-4o-mini mais vous pouvez utiliser d'autres modèles comme GPT-4o en fonction de votre budget et de vos besoins.

Ensuite, nous allons rassembler tous ces composants à l'aide du LangChain Expression Language (LCEL). Le premier composant de la chaîne est un dictionnaire avec context et question comme clés. Les valeurs de ces clés sont fournies par le récupérateur formaté par notre fonction de formatage et par RunnablePassthrough() , respectivement. Cette dernière classe agit comme un espace réservé pour la requête de l'utilisateur.


Le dictionnaire est ensuite transmis à notre invite système ; l'invite est transmise au LLM, qui génère une classe de message de sortie. La classe de message est transmise à un analyseur de sortie de chaîne qui renvoie une réponse en texte brut.

4. Création d'une interface utilisateur simplifiée

Dans cette section, nous allons créer l'interface utilisateur ci-dessous pour notre application :


La capture d'écran de notre interface utilisateur Streamlit.

Il s'agit d'une interface épurée et minimaliste avec deux champs de saisie : l'un pour le document, l'autre pour poser des questions sur le document. Dans la barre latérale gauche, l'utilisateur est invité à saisir sa clé API.


Pour créer l'interface, ouvrez le script app.py au niveau le plus élevé de votre répertoire de travail et collez le code suivant :


 import streamlit as st import os from dotenv import load_dotenv from src.document_processor import process_document from src.rag_chain import create_rag_chain # Load environment variables load_dotenv() st.set_page_config(page_title="RAG Chatbot", page_icon="🤖") st.title("RAG Chatbot") # Initialize session state if "rag_chain" not in st.session_state: st.session_state.rag_chain = None # Sidebar for API key input with st.sidebar: api_key = st.text_input("Enter your OpenAI API Key", type="password") if api_key: os.environ["OPENAI_API_KEY"] = api_key # File uploader uploaded_file = st.file_uploader("Choose a file", type=["pdf", "png", "jpg", "jpeg"]) if uploaded_file is not None: if st.button("Process File"): if api_key: with st.spinner("Processing file..."): # Save the uploaded file temporarily with open(uploaded_file.name, "wb") as f: f.write(uploaded_file.getbuffer()) try: # Process the document chunks = process_document(uploaded_file.name) # Create RAG chain st.session_state.rag_chain = create_rag_chain(chunks) st.success("File processed successfully!") except ValueError as e: st.error(str(e)) finally: # Remove the temporary file os.remove(uploaded_file.name) else: st.error("Please provide your OpenAI API key.") # Query input query = st.text_input("Ask a question about the uploaded document") if st.button("Ask"): if st.session_state.rag_chain and query: with st.spinner("Generating answer..."): result = st.session_state.rag_chain.invoke(query) st.subheader("Answer:") st.write(result) elif not st.session_state.rag_chain: st.error("Please upload and process a file first.") else: st.error("Please enter a question.")


Bien qu'il ne fasse que 65 lignes, il implémente les fonctionnalités suivantes :

  1. Saisie de la clé API : permet aux utilisateurs de saisir leur clé API OpenAI en toute sécurité.
  2. Téléchargement de fichiers : prend en charge le téléchargement de fichiers PDF, PNG, JPG et JPEG.
  3. Traitement du document : traite le fichier téléchargé et crée des blocs de texte.
  4. Création de chaîne RAG : crée une chaîne de génération augmentée de récupération à l'aide des blocs de documents traités.
  5. Gestion des requêtes : accepte les questions des utilisateurs sur le document téléchargé.
  6. Génération de réponses : utilise la chaîne RAG pour générer des réponses en fonction du document téléchargé et de la requête de l'utilisateur.
  7. Gestion des erreurs : fournit des messages d’erreur appropriés pour les clés API manquantes, les fichiers non traités ou les requêtes vides.
  8. Commentaires de l'utilisateur : affiche des spinners pendant le traitement et des messages de réussite/erreur pour tenir l'utilisateur informé.
  9. Gestion de l'état : utilise l'état de session de Streamlit pour maintenir la chaîne RAG à travers les interactions.

5. Déploiement en tant que chatbot Streamlit

Il ne reste plus qu'une étape : déployer notre application Streamlit. Il existe de nombreuses options, mais la plus simple est d'utiliser Streamlit Cloud, qui est gratuit et facile à configurer.


Tout d’abord, ouvrez le script .streamlit/config.toml et collez les configurations suivantes :


 [theme] primaryColor = "#F63366" backgroundColor = "#FFFFFF" secondaryBackgroundColor = "#F0F2F6" textColor = "#262730" font = "sans serif"


Voici quelques ajustements de thème qui découlent de préférences personnelles. Ensuite, rédigez le fichier README.md (vous pouvez copier son contenu à partir de ce fichier hébergé sur GitHub ).


Enfin, allez sur GitHub.com et créez un nouveau dépôt. Copiez son lien et revenez à votre répertoire de travail :


 $ git init $ git add . $ git commit -m "Initial commit" $ git remote add origin https://github.com/YourUsername/YourRepo.git $ git push --set-upstream origin master


Les commandes ci-dessus initialisent Git, créent un commit initial et poussent le tout vers le référentiel (n'oubliez pas de remplacer le lien du référentiel par le vôtre).


Vous devez maintenant créer un compte gratuit sur Streamlit Cloud . Connectez votre compte GitHub et sélectionnez le référentiel contenant votre application.


Ensuite, configurez les paramètres de l’application :

  • Définir la version Python (par exemple, 3.9)
  • Définissez le chemin du fichier principal sur app.py
  • Ajoutez tous les secrets nécessaires (comme OPENAI_API_KEY ) dans les paramètres de l'application


Enfin, cliquez sur « Déployer » !


L'application devrait être opérationnelle en quelques minutes. L'application que j'ai créée pour ce tutoriel est disponible sur ce lien . Essayez-la !

Le système RAG en action

Conclusion

Ce didacticiel présente le puissant mélange de génération augmentée de récupération (RAG) et de Streamlit qui forme un système interactif de questions-réponses basé sur des documents. Il guide le lecteur tout au long du processus, de la configuration d'un environnement et du traitement des documents à la création d'une chaîne RAG et au déploiement d'une application Web conviviale.


Les points importants incluent :

  • RAG pour un modèle de langage plus intelligent (au sens de la connaissance externe)
  • Les chaînes RAG peuvent être construites à l'aide de LangChain, des modèles d'OpenAI et des intégrations communautaires tierces.
  • L'application peut être rendue interactive à l'aide de Streamlit et être déployée pour un usage public.


Ce projet constitue la base d'applications plus avancées. Il peut être étendu de manière significative, par exemple en intégrant plusieurs types de documents, en améliorant la précision de la recherche et en proposant des fonctionnalités telles que la synthèse de documents. Et pourtant, il sert en réalité à démontrer la puissance potentielle de ces technologies, individuellement ou combinées.