Con el auge de los modelos de lenguajes grandes y sus impresionantes capacidades, se están creando muchas aplicaciones sofisticadas sobre proveedores gigantes de LLM como OpenAI y Anthropic. El mito detrás de tales aplicaciones es el marco RAG, que se explica detalladamente en los siguientes artículos:
Para familiarizarse con RAG, recomiendo leer estos artículos. Sin embargo, esta publicación omitirá los conceptos básicos y lo guiará directamente en la creación de su propia aplicación RAG que pueda ejecutarse localmente en su computadora portátil sin preocuparse por la privacidad de los datos y el costo de los tokens.
Construiremos una aplicación similar a ChatPD F pero más simple. Donde los usuarios pueden cargar un documento PDF y hacer preguntas a través de una interfaz de usuario sencilla. Nuestra pila tecnológica es muy sencilla con Langchain, Ollama y Streamlit.
Bien, comencemos a configurarlo.
Como se mencionó anteriormente, configurar y ejecutar Ollama es sencillo. Primera visita
A continuación, abra su terminal y ejecute el siguiente comando para obtener la última versión
ollama pull mistral
Luego, ejecute ollama list
para verificar si el modelo se extrajo correctamente. La salida del terminal debería parecerse a la siguiente:
Ahora, si el servidor LLM aún no se está ejecutando, inícielo con ollama serve
. Si encuentra un mensaje de error como "Error: listen tcp 127.0.0.1:11434: bind: address already in use"
, indica que el servidor ya se está ejecutando de forma predeterminada y puede continuar con el siguiente paso.
El segundo paso de nuestro proceso es construir el oleoducto RAG. Dada la simplicidad de nuestra aplicación, necesitamos principalmente dos métodos: ingest
y ask
.
El método ingest
acepta una ruta de archivo y la carga en un almacenamiento vectorial en dos pasos: primero, divide el documento en fragmentos más pequeños para acomodar el límite de token del LLM; en segundo lugar, vectoriza estos fragmentos utilizando Qdrant FastEmbeddings y los almacena en Chroma.
El método ask
maneja las consultas de los usuarios. Los usuarios pueden plantear una pregunta y luego RetrievalQAChain recupera los contextos relevantes (fragmentos de documentos) utilizando técnicas de búsqueda por similitud de vectores.
Con la pregunta del usuario y los contextos recuperados, podemos redactar un mensaje y solicitar una predicción del servidor 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
El mensaje proviene del centro Langchain:
Puede obtener más información sobre las técnicas de estimulación de LLM.
Más detalles sobre la implementación:
ingest
: Usamos PyPDFLoader para cargar el archivo PDF cargado por el usuario. El RecursiveCharacterSplitter, proporcionado por Langchain, luego divide este PDF en partes más pequeñas. Es importante filtrar metadatos complejos que no son compatibles con ChromaDB utilizando la función filter_complex_metadata
de Langchain.
Para el almacenamiento de vectores, se utiliza Chroma, junto con
ask
: este método simplemente pasa la pregunta del usuario a nuestra cadena predefinida y luego devuelve el resultado.
clear
: este método se utiliza para borrar la sesión de chat anterior y el almacenamiento cuando se carga un nuevo archivo PDF. Para una interfaz de usuario simple, usaremos
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()
Ejecute este código con el comando streamlit run app.py
para ver cómo se ve.
¡Está bien, eso es todo! Ahora tenemos una aplicación ChatPDF que se ejecuta completamente en su computadora portátil. Dado que esta publicación se centra principalmente en brindar una descripción general de alto nivel sobre cómo crear su propia aplicación RAG, hay varios aspectos que necesitan ajustes. Puede considerar las siguientes sugerencias para mejorar su aplicación y desarrollar aún más sus habilidades:
Finalmente, gracias por leer. Si encuentra útil esta información, considere suscribirse a mi
Código fuente completo: