Avec l'essor des grands modèles de langage et leurs capacités impressionnantes, de nombreuses applications sophistiquées sont construites sur des fournisseurs LLM géants comme OpenAI et Anthropic. Le mythe derrière de telles applications est le framework RAG, qui a été expliqué en détail dans les articles suivants :
Pour vous familiariser avec RAG, je vous recommande de parcourir ces articles. Cependant, cet article ignorera les bases et vous guidera directement dans la création de votre propre application RAG pouvant s'exécuter localement sur votre ordinateur portable sans vous soucier de la confidentialité des données et du coût des jetons.
Nous allons créer une application similaire à ChatPD F mais plus simple. Où les utilisateurs peuvent télécharger un document PDF et poser des questions via une interface utilisateur simple. Notre pile technologique est très simple avec Langchain, Ollama et Streamlit.
D'accord, commençons à le configurer.
Comme mentionné ci-dessus, la configuration et l'exécution d'Ollama sont simples. Première visite
Ensuite, ouvrez votre terminal et exécutez la commande suivante pour extraire la dernière version
ollama pull mistral
Ensuite, exécutez ollama list
pour vérifier si le modèle a été extrait correctement. La sortie du terminal doit ressembler à ce qui suit :
Maintenant, si le serveur LLM n'est pas déjà en cours d'exécution, lancez-le avec ollama serve
. Si vous rencontrez un message d'erreur du type "Error: listen tcp 127.0.0.1:11434: bind: address already in use"
, cela indique que le serveur est déjà en cours d'exécution par défaut et que vous pouvez passer à l'étape suivante.
La deuxième étape de notre processus consiste à créer le pipeline RAG. Compte tenu de la simplicité de notre application, nous avons principalement besoin de deux méthodes : ingest
et ask
.
La méthode ingest
accepte un chemin de fichier et le charge dans le stockage vectoriel en deux étapes : premièrement, elle divise le document en morceaux plus petits pour s'adapter à la limite de jetons du LLM ; Deuxièmement, il vectorise ces morceaux à l'aide de Qdrant FastEmbeddings et les stocke dans Chroma.
La méthode ask
gère les requêtes des utilisateurs. Les utilisateurs peuvent poser une question, puis RetrievalQAChain récupère les contextes pertinents (morceaux de documents) à l'aide de techniques de recherche de similarité vectorielle.
Avec la question de l'utilisateur et les contextes récupérés, nous pouvons composer une invite et demander une prédiction au serveur LLM.
from langchain.vectorstores import Chroma from langchain.chat_models import ChatOllama from langchain.embeddings import FastEmbedEmbeddings from langchain.schema.output_parser import StrOutputParser from langchain.document_loaders import PyPDFLoader from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain.schema.runnable import RunnablePassthrough from langchain.prompts import PromptTemplate from langchain.vectorstores.utils import filter_complex_metadata class ChatPDF: vector_store = None retriever = None chain = None def __init__(self): self.model = ChatOllama(model="mistral") self.text_splitter = RecursiveCharacterTextSplitter(chunk_size=1024, chunk_overlap=100) self.prompt = PromptTemplate.from_template( """ <s> [INST] You are an assistant for question-answering tasks. Use the following pieces of retrieved context to answer the question. If you don't know the answer, just say that you don't know. Use three sentences maximum and keep the answer concise. [/INST] </s> [INST] Question: {question} Context: {context} Answer: [/INST] """ ) def ingest(self, pdf_file_path: str): docs = PyPDFLoader(file_path=pdf_file_path).load() chunks = self.text_splitter.split_documents(docs) chunks = filter_complex_metadata(chunks) vector_store = Chroma.from_documents(documents=chunks, embedding=FastEmbedEmbeddings()) self.retriever = vector_store.as_retriever( search_type="similarity_score_threshold", search_kwargs={ "k": 3, "score_threshold": 0.5, }, ) self.chain = ({"context": self.retriever, "question": RunnablePassthrough()} | self.prompt | self.model | StrOutputParser()) def ask(self, query: str): if not self.chain: return "Please, add a PDF document first." return self.chain.invoke(query) def clear(self): self.vector_store = None self.retriever = None self.chain = None
L'invite provient du hub Langchain :
Vous pouvez en savoir plus sur les techniques d'invite LLM
Plus de détails sur la mise en œuvre :
ingest
: Nous utilisons PyPDFLoader pour charger le fichier PDF téléchargé par l'utilisateur. Le RecursiveCharacterSplitter, fourni par Langchain, divise ensuite ce PDF en morceaux plus petits. Il est important de filtrer les métadonnées complexes non prises en charge par ChromaDB à l'aide de la fonction filter_complex_metadata
de Langchain.
Pour le stockage vectoriel, Chroma est utilisé, associé à
ask
: Cette méthode transmet simplement la question de l'utilisateur dans notre chaîne prédéfinie puis renvoie le résultat.
clear
: Cette méthode est utilisée pour effacer la session de discussion et le stockage précédents lorsqu'un nouveau fichier PDF est téléchargé. Pour une interface utilisateur simple, nous utiliserons
import os import tempfile import streamlit as st from streamlit_chat import message from rag import ChatPDF st.set_page_config(page_title="ChatPDF") def display_messages(): st.subheader("Chat") for i, (msg, is_user) in enumerate(st.session_state["messages"]): message(msg, is_user=is_user, key=str(i)) st.session_state["thinking_spinner"] = st.empty() def process_input(): if st.session_state["user_input"] and len(st.session_state["user_input"].strip()) > 0: user_text = st.session_state["user_input"].strip() with st.session_state["thinking_spinner"], st.spinner(f"Thinking"): agent_text = st.session_state["assistant"].ask(user_text) st.session_state["messages"].append((user_text, True)) st.session_state["messages"].append((agent_text, False)) def read_and_save_file(): st.session_state["assistant"].clear() st.session_state["messages"] = [] st.session_state["user_input"] = "" for file in st.session_state["file_uploader"]: with tempfile.NamedTemporaryFile(delete=False) as tf: tf.write(file.getbuffer()) file_path = tf.name with st.session_state["ingestion_spinner"], st.spinner(f"Ingesting {file.name}"): st.session_state["assistant"].ingest(file_path) os.remove(file_path) def page(): if len(st.session_state) == 0: st.session_state["messages"] = [] st.session_state["assistant"] = ChatPDF() st.header("ChatPDF") st.subheader("Upload a document") st.file_uploader( "Upload document", type=["pdf"], key="file_uploader", on_change=read_and_save_file, label_visibility="collapsed", accept_multiple_files=True, ) st.session_state["ingestion_spinner"] = st.empty() display_messages() st.text_input("Message", key="user_input", on_change=process_input) if __name__ == "__main__": page()
Exécutez ce code avec la commande streamlit run app.py
pour voir à quoi il ressemble.
D'accord, c'est tout ! Nous disposons désormais d'une application ChatPDF qui fonctionne entièrement sur votre ordinateur portable. Étant donné que cet article se concentre principalement sur la fourniture d’un aperçu de haut niveau de la façon de créer votre propre application RAG, plusieurs aspects doivent être peaufinés. Vous pouvez envisager les suggestions suivantes pour améliorer votre application et développer davantage vos compétences :
Enfin, merci d'avoir lu. Si vous trouvez ces informations utiles, pensez à vous abonner à mon
Code source complet :