С появлением моделей больших языков и их впечатляющими возможностями на базе таких гигантских поставщиков LLM, как OpenAI и Anthropic, создается множество необычных приложений. Мифом, лежащим в основе таких приложений, является фреймворк RAG, подробно описанный в следующих статьях: Создание приложений LLM для производства на основе RAG Объяснение поисковой дополненной генерации (RAG): понимание ключевых концепций Что такое генерация с расширенным поиском? Для знакомства с RAG рекомендую просмотреть эти статьи. Однако в этом посте мы пропустим основы и покажем вам, как создать собственное приложение RAG, которое можно будет запускать локально на вашем ноутбуке, не беспокоясь о конфиденциальности данных и стоимости токенов. Мы создадим приложение, похожее на F, но более простое. Где пользователи могут загружать PDF-документы и задавать вопросы через простой пользовательский интерфейс. Наш технологический стек очень прост: Langchain, Ollama и Streamlit. ChatPD : наиболее важным компонентом этого приложения является LLM-сервер. Благодаря , у нас есть надежный LLM-сервер, который можно настроить локально, даже на ноутбуке. Пока вариант, я считаю, что Ollama, написанная на Go, проще в настройке и запуске. LLM-сервер Оллама лама.cpp : Несомненно, две ведущие библиотеки в области LLM — это и . В этом проекте я буду использовать Langchain, поскольку знаком с ним по профессиональному опыту. Важным компонентом любой среды RAG является векторное хранилище. Мы будем использовать здесь, поскольку он хорошо интегрируется с Langchain. РАГ Лангчейн ЛламИндекс цветность : Пользовательский интерфейс также является важным компонентом. Хотя существует множество доступных технологий, я предпочитаю использовать , библиотека Python, для вашего спокойствия. Пользовательский интерфейс чата Стримлит Хорошо, приступим к настройке. Настройка Олламы Как упоминалось выше, настройка и запуск Ollama просты. Сначала посетите и загрузите приложение, подходящее для вашей операционной системы. оллама.ай Затем откройте терминал и выполните следующую команду, чтобы получить последнюю версию . Хотя есть много других , я выбираю Мистраль-7Б за его компактность и конкурентоспособное качество. Мистраль-7Б Доступны модели LLM ollama pull mistral После этого запустите , чтобы проверить, правильно ли вытянута модель. Вывод терминала должен выглядеть следующим образом: ollama list Теперь, если сервер LLM еще не запущен, запустите его с помощью . Если вы столкнулись с сообщением об ошибке типа , это означает, что сервер уже запущен по умолчанию, и вы можете перейти к следующему шагу. ollama serve "Error: listen tcp 127.0.0.1:11434: bind: address already in use" Постройте трубопровод RAG Вторым шагом в нашем процессе является создание конвейера RAG. Учитывая простоту нашего приложения, нам в первую очередь нужны два метода: и . ingest ask Метод принимает путь к файлу и загружает его в векторное хранилище в два этапа: во-первых, он разбивает документ на более мелкие фрагменты, чтобы соответствовать лимиту токенов LLM; во-вторых, он векторизует эти фрагменты с помощью Qdrant FastEmbeddings и сохраняет их в Chroma. ingest Метод обрабатывает запросы пользователей. Пользователи могут задать вопрос, а затем RetrivalQAChain извлекает соответствующие контексты (фрагменты документов), используя методы поиска по векторному сходству. ask Используя вопрос пользователя и полученные контексты, мы можем составить подсказку и запросить прогноз от сервера 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. Langchain RAG подсказывает Мистраль Вы можете узнать больше о методах подсказок LLM. . здесь Подробнее о реализации: : мы используем PyPDFLoader для загрузки PDF-файла, загруженного пользователем. RecursiveCharacterSplitter, предоставленный Langchain, затем разбивает этот PDF-файл на более мелкие фрагменты. Важно отфильтровывать сложные метаданные, не поддерживаемые ChromaDB, с помощью функции из Langchain. ingest filter_complex_metadata Для векторного хранения используется Chroma в сочетании с в качестве нашей модели встраивания. Эта облегченная модель затем преобразуется в ретривер с порогом оценки 0,5 и k=3, что означает, что она возвращает 3 верхних фрагмента с наивысшими оценками выше 0,5. Наконец, мы строим простую цепочку разговоров, используя . Qdrant FastEmbed ЛЕКЛ : этот метод просто передает вопрос пользователя в нашу предопределенную цепочку, а затем возвращает результат. ask : этот метод используется для очистки предыдущего сеанса чата и хранилища при загрузке нового PDF-файла. clear Проект простого пользовательского интерфейса Для простого пользовательского интерфейса мы будем использовать , инфраструктура пользовательского интерфейса, предназначенная для быстрого создания прототипов приложений AI/ML. Стримлит 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, есть несколько аспектов, которые требуют тонкой настройки. Вы можете рассмотреть следующие предложения, чтобы улучшить свое приложение и развить свои навыки: : в настоящее время он не запоминает ход разговора. Добавление временной памяти поможет вашему помощнику осознавать контекст. Добавить память в цепочку разговора : можно обсуждать один документ одновременно. Но представьте, если бы мы могли обсуждать несколько документов — вы могли бы поместить туда всю свою книжную полку. Это было бы супер круто! Разрешить загрузку нескольких файлов : хотя Mistral эффективен, существует множество других альтернатив. Возможно, вы найдете модель, которая лучше соответствует вашим потребностям, например LlamaCode для разработчиков. Однако помните, что выбор модели зависит от вашего оборудования, особенно от объема оперативной памяти 💵 Используйте другие модели LLM . В RAG есть место для экспериментов. Возможно, вы захотите изменить метрику поиска, модель внедрения или добавить слои, например, для повторного ранжирования, чтобы улучшить результаты. Улучшите конвейер RAG Наконец, спасибо за чтение. Если вы найдете эту информацию полезной, пожалуйста, рассмотрите возможность подписки на мою рассылку. или мое личное . Я планирую написать больше о приложениях RAG и LLM, и вы можете предлагать темы, оставив комментарий ниже. Ваше здоровье! Подстек блог Полный исходный код: https://github.com/vndee/local-rag-example