С появлением моделей больших языков и их впечатляющими возможностями на базе таких гигантских поставщиков LLM, как OpenAI и Anthropic, создается множество необычных приложений. Мифом, лежащим в основе таких приложений, является фреймворк RAG, подробно описанный в следующих статьях:
Для знакомства с RAG рекомендую просмотреть эти статьи. Однако в этом посте мы пропустим основы и покажем вам, как создать собственное приложение RAG, которое можно будет запускать локально на вашем ноутбуке, не беспокоясь о конфиденциальности данных и стоимости токенов.
Мы создадим приложение, похожее на ChatPD F, но более простое. Где пользователи могут загружать PDF-документы и задавать вопросы через простой пользовательский интерфейс. Наш технологический стек очень прост: Langchain, Ollama и Streamlit.
Хорошо, приступим к настройке.
Как упоминалось выше, настройка и запуск Ollama просты. Сначала посетите
Затем откройте терминал и выполните следующую команду, чтобы получить последнюю версию
ollama pull mistral
После этого запустите ollama list
, чтобы проверить, правильно ли вытянута модель. Вывод терминала должен выглядеть следующим образом:
Теперь, если сервер LLM еще не запущен, запустите его с помощью ollama serve
. Если вы столкнулись с сообщением об ошибке типа "Error: listen tcp 127.0.0.1:11434: bind: address already in use"
, это означает, что сервер уже запущен по умолчанию, и вы можете перейти к следующему шагу.
Вторым шагом в нашем процессе является создание конвейера RAG. Учитывая простоту нашего приложения, нам в первую очередь нужны два метода: ingest
и ask
.
Метод ingest
принимает путь к файлу и загружает его в векторное хранилище в два этапа: во-первых, он разбивает документ на более мелкие фрагменты, чтобы соответствовать лимиту токенов LLM; во-вторых, он векторизует эти фрагменты с помощью Qdrant FastEmbeddings и сохраняет их в Chroma.
Метод ask
обрабатывает запросы пользователей. Пользователи могут задать вопрос, а затем RetrivalQAChain извлекает соответствующие контексты (фрагменты документов), используя методы поиска по векторному сходству.
Используя вопрос пользователя и полученные контексты, мы можем составить подсказку и запросить прогноз от сервера 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
Приглашение получено из хаба Langchain:
Вы можете узнать больше о методах подсказок LLM.
Подробнее о реализации:
ingest
: мы используем PyPDFLoader для загрузки PDF-файла, загруженного пользователем. RecursiveCharacterSplitter, предоставленный Langchain, затем разбивает этот PDF-файл на более мелкие фрагменты. Важно отфильтровывать сложные метаданные, не поддерживаемые ChromaDB, с помощью функции filter_complex_metadata
из Langchain.
Для векторного хранения используется Chroma в сочетании с
ask
: этот метод просто передает вопрос пользователя в нашу предопределенную цепочку, а затем возвращает результат.
clear
: этот метод используется для очистки предыдущего сеанса чата и хранилища при загрузке нового PDF-файла. Для простого пользовательского интерфейса мы будем использовать
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()
Запустите этот код с помощью streamlit run app.py
, чтобы увидеть, как он выглядит.
Хорошо, вот и все! Теперь у нас есть приложение ChatPDF, которое полностью работает на вашем ноутбуке. Поскольку этот пост в основном посвящен общему обзору того, как создать собственное приложение RAG, есть несколько аспектов, которые требуют тонкой настройки. Вы можете рассмотреть следующие предложения, чтобы улучшить свое приложение и развить свои навыки:
Наконец, спасибо за чтение. Если вы найдете эту информацию полезной, пожалуйста, рассмотрите возможность подписки на мою рассылку.
Полный исходный код: