Овладение поиском в Arxiv Пошаговое руководство по созданию чат-бота для вопросов и ответов с использованием Haystack
Мастерство в поиске на Arxiv пошаговое руководство по созданию чат-бота для вопросов и ответов с использованием Haystack
Введение
Вопрос-ответ на основе пользовательских данных является одним из наиболее востребованных случаев использования больших языковых моделей. Человекоподобные навыки беседы LLM, объединенные с методами векторного поиска, значительно облегчают извлечение ответов из больших документов. С некоторыми вариациями мы можем создавать системы для взаимодействия с любыми данными (структурированными, неструктурированными и полуструктурированными), хранящимися в виде векторов в базе данных. Этот метод расширения LLM с помощью извлеченных данных на основе оценок сходства между векторными представлениями запроса и векторными представлениями документов называется RAG или улучшенная генерация на основе поиска. Этот метод может упростить многие вещи, такие как чтение статей из arXiv.
Если вы интересуетесь ИИ и компьютерными науками, вы, вероятно, хотя бы раз слышали о «arXiv». arXiv – это репозиторий с открытым доступом для электронных предпечатных и послепечатных публикаций. Он содержит проверенные, но не прошедшие рецензирование статьи на различные темы, такие как машинное обучение, искусственный интеллект, математика, физика, статистика, электроника и другие. arXiv сыграл важную роль в развитии открытых исследований в области ИИ и точных наук. Однако чтение научных статей часто является трудоемким и занимает много времени. Можно ли сделать это проще, используя чат-бот RAG, который позволяет извлекать содержимое статей и отвечать на вопросы?
В этой статье мы создадим чат-бот RAG для статей arXiv, используя инструмент с открытым исходным кодом под названием Haystack.
- Окунитесь в будущее с отчетом Kaggle о искусственном интеллекте на 2023 год – узнайте, что сейчас в тренде.
- Руководство для начинающих по статистическому анализу | 5 шагов и примеры
- Райан Джонсон, главный продуктовый директор в CallRail – Интервью с серией
Цели обучения
- Понять, что такое Haystack и какие компоненты он содержит для создания приложений на основе LLM.
- Создать компонент для извлечения статей из arXiv с использованием библиотеки “arxiv”.
- Изучить, как создавать индексационные и запросные конвейеры с помощью узлов Haystack.
- Научиться создавать интерфейс чата с помощью Gradio, координировать конвейеры для извлечения документов из векторного хранилища и генерировать ответы с помощью LLM.
Эта статья была опубликована в рамках Data Science Blogathon.
Что такое Haystack?
Haystack — это открытая платформа NLP, которая объединяет в себе все необходимое для создания масштабируемых приложений, основанных на LLM. Haystack предоставляет высокомодульный и настраиваемый подход к созданию готовых к производству NLP-приложений, таких как семантический поиск, вопросно-ответная система RAG и другие. Он основан на концепции конвейеров и узлов; конвейеры обеспечивают очень гибкое построение цепочки узлов для создания эффективных NLP-приложений.
- Узлы: Узлы являются основными строительными блоками Haystack. Каждый узел выполняет отдельную задачу, такую как предварительная обработка документов, извлечение из векторных хранилищ, генерация ответов на основе LLM и другие.
- Конвейер: Конвейер позволяет связывать узлы между собой, чтобы строить цепочки узлов. Это упрощает создание приложений с помощью Haystack.
Haystack также содержит встроенную поддержку ведущих векторных хранилищ, таких как Weaviate, Milvus, Elastic Search, Qdrant и других. Более подробную информацию можно найти в общедоступном репозитории Haystack: https://github.com/deepset-ai/haystack.
Таким образом, в этой статье мы будем использовать Haystack для создания чат-бота Q&A для статей Arxiv с использованием интерфейса Gradio.
Gradio
Gradio – это открытое решение от Huggingface для настройки и обмена демонстрацией любого приложения машинного обучения. Оно работает на основе Fastapi на серверной части и Svelte для компонентов веб-интерфейса. Оно позволяет создавать настраиваемые веб-приложения на Python. Идеально подходит для создания и обмена демонстрационными приложениями моделей машинного обучения или концептами. Дополнительную информацию можно найти на официальной странице Gradio в GitHub: https://github.com/gradio-app/gradio. Чтобы узнать больше о создании приложений с помощью Gradio, см. статью “Let’s Build Chat GPT with Gradio” [https://github.com/gradio-app/gradio].
Создание чат-бота
Перед созданием приложения давайте кратко опишем рабочий процесс. Он начинается с того, что пользователь указывает идентификатор статьи в Arxiv, и заканчивается получением ответов на запросы. Вот простой рабочий процесс нашего чат-бота Arxiv.
У нас есть две конвейеры: конвейер индексации и конвейер запросов. Когда пользователь вводит идентификатор статьи в Arxiv, он попадает в компонент Arxiv, который извлекает и загружает соответствующую статью в указанный каталог и запускает конвейер индексации. Конвейер индексации состоит из четырех узлов, каждый из которых отвечает за выполнение одной задачи. Так что давайте посмотрим, что делают эти узлы.
Конвейер индексации
В конвейере Haystack вывод предыдущего узла используется в качестве входных данных для текущего узла. В индексирующем конвейере начальным входом является путь к документу.
- PDFToTextConverter: Библиотека Arxiv позволяет загружать статьи в формате PDF. Но нам нужны данные в текстовом формате. Поэтому этот узел извлекает тексты из PDF.
- Preprocessor: Извлеченные данные должны быть очищены и обработаны перед хранением их в векторной базе данных. Этот узел отвечает за очистку и разделение текстов.
- EmbeddingRetriver: Этот узел определяет хранилище векторов, где данные должны быть сохранены, и модель вложения, используемую для получения вложений.
- InMemoryDocumentStore: Это хранилище векторов, в котором хранятся вложения. В данном случае мы использовали стандартное хранилище документов In-memory от Haystack. Но вы также можете использовать другие хранилища векторов, такие как Qdrant, Weaviate, Elastic Search, Milvus и т. д.
Конвейер запросов
Конвейер запросов запускается, когда пользователь отправляет запросы. Конвейер запросов извлекает “k” ближайших документов к вложениям запроса из векторного хранилища и генерирует ответ LLM. Здесь также есть четыре узла.
- Retriever: Извлекает “k” ближайших документов к вложениям запроса из векторного хранилища.
- Sampler: Фильтрует документы на основе совокупной вероятности оценок сходства между запросом и документами с использованием выборки верхних p.
- LostInTheMiddleRanker: Этот алгоритм переупорядочивает извлеченные документы. Он помещает наиболее релевантные документы в начало или конец контекста.
- PromptNode: PromptNode ответственен за генерацию ответов на запросы из предоставленного контекста для LLM.
Так что это был рабочий процесс нашего чат-бота Arxiv. Теперь давайте перейдем к части с кодированием.
Настройка Dev Env
Перед установкой зависимостей создайте виртуальное окружение. Вы можете использовать Venv и Poetry для создания виртуального окружения.
python -m venv my-env-namesource bin/activate
Теперь установите следующие зависимости разработки. Для загрузки статей Arxiv нам понадобится установленная библиотека Arxiv.
farm-haystackarxivgradio
Теперь мы импортируем библиотеки.
import arxivimport osfrom haystack.document_stores import InMemoryDocumentStorefrom haystack.nodes import ( EmbeddingRetriever, PreProcessor, PDFToTextConverter, PromptNode, PromptTemplate, TopPSampler )from haystack.nodes.ranker import LostInTheMiddleRankerfrom haystack.pipelines import Pipelineimport gradio as gr
Построение компонента Arxiv
Этот компонент будет отвечать за загрузку и хранение файлов PDF Arxiv. Вот как мы определяем компонент.
class ArxivComponent: """ Этот компонент отвечает за извлечение статей ArXiv на основе идентификатора ArXiv. """ def run(self, arxiv_id: str = None): """ Извлекает и сохраняет статью ArXiv для заданного идентификатора ArXiv. Args: arxiv_id (str): Идентификатор ArXiv статьи, которую нужно извлечь. """ # Установите путь каталога, где будут храниться статьи ArXiv dir: str = DIR # Создайте экземпляр клиента arXiv arxiv_client = arxiv.Client() # Проверяем, предоставлен ли идентификатор arXiv; если нет, то вызываем ошибку if arxiv_id is None: raise ValueError("Пожалуйста, укажите идентификатор ArXiv статьи для извлечения.") # Поиск статьи ArXiv с использованием предоставленного идентификатора ArXiv search = arxiv.Search(id_list=[arxiv_id]) response = arxiv_client.results(search) paper = next(response) # Получить первый результат title = paper.title # Извлечь название статьи # Проверяем, существует ли указанный каталог if os.path.isdir(dir): # Проверяем, существует ли уже файл PDF для статьи if os.path.isfile(dir + "/" + title + ".pdf"): return {"file_path": [dir + "/" + title + ".pdf"]} else: # Если каталог не существует, создаем его os.mkdir(dir) # Попытка загрузить PDF для статьи ArXiv try: paper.download_pdf(dirpath=dir, filename=title + ".pdf") return {"file_path": [dir + "/" + title + ".pdf"]} except: # Если происходит ошибка во время загрузки, вызываем ошибку подключения raise ConnectionError(message=f"Произошла ошибка при загрузке PDF для \ статьи ArXiv с идентификатором: {arxiv_id}")
Вышеуказанный компонент инициализирует клиент Arxiv, затем извлекает статью Arxiv, связанную с идентификатором и проверяет, была ли она уже загружена; он возвращает путь к PDF-файлу или загружает его в директорию.
Создание индексной конвейера
Теперь мы определим индексный конвейер для обработки и хранения документов в нашей векторной базе данных.
document_store = InMemoryDocumentStore()embedding_retriever = EmbeddingRetriever( document_store=document_store, embedding_model="sentence-transformers/All-MiniLM-L6-V2", model_format="sentence_transformers", top_k=10 )def indexing_pipeline(file_path: str = None): pdf_converter = PDFToTextConverter() preprocessor = PreProcessor(split_by="word", split_length=250, split_overlap=30) indexing_pipeline = Pipeline() indexing_pipeline.add_node( component=pdf_converter, name="PDFConverter", inputs=["File"] ) indexing_pipeline.add_node( component=preprocessor, name="PreProcessor", inputs=["PDFConverter"] ) indexing_pipeline.add_node( component=embedding_retriever, name="EmbeddingRetriever", inputs=["PreProcessor"] ) indexing_pipeline.add_node( component=document_store, name="InMemoryDocumentStore", inputs=["EmbeddingRetriever"] ) indexing_pipeline.run(file_paths=file_path)
Сначала мы определяем нашу встроенную в память хранилище документов, а затем извлекатель вложений. В извлекателе вложений мы указываем хранилище документов, модели вложений и количество документов для извлечения.
Мы также определили четыре узла, о которых мы ранее говорили. Конвертер pdf_converter преобразует PDF-файл в текст, предварительная обработка очищает и создает фрагменты текста, извлекатель вложений создает вложения документов, а InMemoryDocumentStore хранит векторные вложения. Метод run с указанием пути к файлу запускает конвейер, и каждый узел выполняется в порядке, в котором они были определены. Вы также можете заметить, как каждый узел использует выходные данные предыдущих узлов в качестве входных данных.
Создание конвейера запросов
Конвейер запросов также состоит из четырех узлов. Он отвечает за получение вложения из запрошенного текста, поиск похожих документов в векторном хранилище и, наконец, генерацию ответов с использованием LLM.
def query_pipeline(query: str = None): if not query: raise gr.Error("Пожалуйста, укажите запрос.") prompt_text = """Синтезировать полный ответ из предоставленных абзацев статьи Arxiv и заданного вопроса. Сосредоточьтесь на вопросе и избегайте ненужной информации в своем ответе. Параграфы: {join(documents)} Вопрос: {query} Ответ:""" prompt_node = PromptNode( "gpt-3.5-turbo", default_prompt_template=PromptTemplate(prompt_text), api_key="api-key", max_length=768, model_kwargs={"stream": False}, ) query_pipeline = Pipeline() query_pipeline.add_node( component = embedding_retriever, name = "Retriever", inputs=["Query"] ) query_pipeline.add_node( component=TopPSampler( top_p=0.90), name="Sampler", inputs=["Retriever"] ) query_pipeline.add_node( component=LostInTheMiddleRanker(1024), name="LostInTheMiddleRanker", inputs=["Sampler"] ) query_pipeline.add_node( component=prompt_node, name="Prompt", inputs=["LostInTheMiddleRanker"] ) pipeline_obj = query_pipeline.run(query = query) return pipeline_obj["results"]
Recall that embedding_retriever извлекает “k” похожих документов из векторного хранилища. Sampler отвечает за выбор образцов документов. LostInTheMiddleRanker ранжирует документы в начале или конце контекста на основе их релевантности. Наконец, prompt_node, где LLM – “gpt-3.5-turbo”. Мы также добавили шаблон подсказки, чтобы добавить больше контекста в беседу. Метод run возвращает объект конвейера – словарь.
Это было наше бэкэнд. Теперь дизайнируем интерфейс.
Интерфейс Gradio
Здесь есть класс Blocks для создания настраиваемого веб-интерфейса. Так что для этого проекта нам нужно текстовое поле, которое принимает Arxiv ID в качестве ввода пользователя, интерфейс чата и текстовое поле, которое принимает запросы пользователя. Вот как мы можем это сделать.
with gr.Blocks() as demo: with gr.Row(): with gr.Column(scale=60): text_box = gr.Textbox(placeholder="Введите Arxiv ID", interactive=True).style(container=False) with gr.Column(scale=40): submit_id_btn = gr.Button(value="Отправить") with gr.Row(): chatbot = gr.Chatbot(value=[]).style(height=600) with gr.Row(): with gr.Column(scale=70): query = gr.Textbox(placeholder = "Введите строку запроса", interactive=True).style(container=False)
Запустите команду gradio app.py в командной строке и перейдите по отображенному адресу localhost.
Теперь нам нужно определить события-триггеры.
submit_id_btn.click( fn=embed_arxiv, inputs=[text_box], outputs=[text_box], )query.submit( fn=add_text, inputs=[chatbot, query], outputs=[chatbot, ], queue=False ).success( fn=get_response, inputs = [chatbot, query], outputs = [chatbot,] )demo.queue()demo.launch()
Чтобы сработали события, нам нужно определить функции, упомянутые в каждом событии. Нажмите на кнопку submit_iid_btn и отправьте входные данные из текстового поля в функцию embed_arxiv. Эта функция будет координировать получение и сохранение PDF-файла Arxiv в векторное хранилище.
arxiv_obj = ArxivComponent()def embed_arxiv(arxiv_id: str): """ Args: arxiv_id: Arxiv ID of the article to be retrieved. """ global FILE_PATH dir: str = DIR file_path: str = None if not arxiv_id: raise gr.Error("Provide an Arxiv ID") file_path_dict = arxiv_obj.run(arxiv_id) file_path = file_path_dict["file_path"] FILE_PATH = file_path indexing_pipeline(file_path=file_path) return "Successfully embedded the file"
Мы определили объект ArxivComponent и функцию embed_arxiv. Он запускает метод “run” и использует возвращаемый путь к файлу в качестве параметра для Indexing Pipeline.
Теперь переходим к событию submit с функцией add_text в качестве параметра. Она отвечает за отображение чата в интерфейсе чата.
def add_text(history, text: str): if not text: raise gr.Error('enter text') history = history + [(text,'')] return history
Теперь мы определяем функцию get_response, которая получает и передает ответы LLM в интерфейс чата.
def get_response(history, query: str): if not query: gr.Error("Please provide a query.") response = query_pipeline(query=query) for text in response[0]: history[-1][1] += text yield history, ""
Эта функция берет строку запроса и передает ее в Query Pipeline для получения ответа. Наконец, мы перебираем строку ответа и возвращаем ее в чат-бота.
Собираем все вместе.
# Создаем экземпляр класса ArxivComponentarxiv_obj = ArxivComponent()def embed_arxiv(arxiv_id: str): """ Получает и встраивает статью ArXiv по заданному идентификатору ArXiv. Args: arxiv_id (str): Идентификатор ArXiv статьи, которую нужно получить. """ # Обращение к глобальной переменной FILE_PATH global FILE_PATH # Установка директории, в которой хранятся статьи ArXiv dir: str = DIR # Инициализация file_path None file_path: str = None # Проверка, предоставлен ли идентификатор ArXiv if not arxiv_id: raise gr.Error("Provide an Arxiv ID") # Вызов метода run класса ArxivComponent для получения и сохранения статьи ArXiv file_path_dict = arxiv_obj.run(arxiv_id) # Извлечение пути к файлу из словаря file_path = file_path_dict["file_path"] # Обновление глобальной переменной FILE_PATH FILE_PATH = file_path # Вызов функции indexing_pipeline для обработки загруженной статьи indexing_pipeline(file_path=file_path) return "Successfully embedded the file"def get_response(history, query: str): if not query: gr.Error("Please provide a query.") # Вызов функции query_pipeline для обработки запроса пользователя response = query_pipeline(query=query) # Добавление ответа в историю чата for text in response[0]: history[-1][1] += text yield historydef add_text(history, text: str): if not text: raise gr.Error('Enter text') # Добавление предоставленного пользователем текста в историю чата history = history + [(text, '')] return history# Создание интерфейса Gradio с блоками gr.Blocks() as demo: with gr.Row(): with gr.Column(scale=60): # Ввод текста для идентификатора Arxiv text_box = gr.Textbox(placeholder="Введите идентификатор Arxiv", interactive=True).style(container=False) with gr.Column(scale=40): # Кнопка для отправки идентификатора Arxiv submit_id_btn = gr.Button(value="Submit") with gr.Row(): # Интерфейс чат-бота chatbot = gr.Chatbot(value=[]).style(height=600) with gr.Row(): with gr.Column(scale=70): # Ввод текста для запросов пользователя query = gr.Textbox(placeholder="Введите строку запроса", interactive=True).style(container=False) # Определение действий для щелчка по кнопке и отправки запроса submit_id_btn.click( fn=embed_arxiv, inputs=[text_box], outputs=[text_box], ) query.submit( fn=add_text, inputs=[chatbot, query], outputs=[chatbot, ], queue=False ).success( fn=get_response, inputs=[chatbot, query], outputs=[chatbot,] )# Выполнение очереди и запуск интерфейсадemo.queue()demo.launch()
Запустите приложение с помощью команды gradio app.py и посетите URL-адрес для взаимодействия с чат-ботом Arxic.
Вот как это будет выглядеть.
Вот репозиторий GitHub для приложения sunilkumardash9/chat-arxiv.
Возможные улучшения
Мы успешно создали простое приложение для общения с любой статьей Arxiv, но можно внести несколько улучшений.
- Отдельное хранилище векторов: Вместо использования готового хранилища векторов вы можете использовать отдельные хранилища векторов, доступные с помощью Haystack, такие как Weaviate, Milvus и т.д. Это не только даст вам большую гибкость, но также значительно повысит производительность.
- Цитирование: Мы можем добавить уверенность в ответы LLM, добавив правильные цитаты.
- Дополнительные функции: Вместо просто чат-интерфейса мы можем добавить функции для отображения страниц PDF, используемых в качестве источников ответов LLM. Ознакомьтесь с этой статьей «Build a ChatGPT for PDFs with Langchain» и репозиторием GitHub для аналогичного приложения GitHub repository.
- Фронтенд: Более хороший и интерактивный фронтенд был бы гораздо лучше.
Заключение
Таким образом, это было все о создании чат-приложения для статей Arxiv. Это приложение не ограничивается только Arxiv. Мы также можем расширить его до других сайтов, таких как PubMed. С некоторыми модификациями мы также можем использовать подобную архитектуру для общения с любым веб-сайтом. Так что в этой статье мы прошли от создания компонента Arxiv для загрузки статей Arxiv до встраивания их с использованием конвейеров haystack и наконец получения ответов от LLM.
Основные выдержки
- Haystack – это решение с открытым исходным кодом для создания масштабируемых, готовых к производству приложений NLP.
- Haystack предоставляет высокомодульный подход к созданию приложений реального мира. Он предоставляет узлы и конвейеры для оптимизации поиска информации, предобработки данных, вложения и генерации ответов.
- Это библиотека с открытым исходным кодом от Huggingface для быстрого прототипирования любого приложения. Она обеспечивает простой способ обмена моделями ML с кем угодно.
- Используйте аналогичный рабочий процесс для создания чат-приложений для других сайтов, таких как PubMed.
Часто задаваемые вопросы
Все изображения, показанные в этой статье, не принадлежат Analytics Vidhya и используются по усмотрению автора.