Разговаривайте с вашими документами в формате PDF, txt и даже веб-страницами

Общайтесь с документами в формате PDF, txt и веб-страницами

Полное руководство по созданию веб-приложения и интеллекта, позволяющего задавать вопросы к документам, таким как PDF, TXT и даже веб-страницы, с использованием LLM.

Содержание

· Введение· Как это работает· Шаги (часть 1) 👣· Перерыв: Подведение итогов (часть 1) 🌪️· Шаги (часть 2) 👣· Перерыв: Подведение итогов (часть 2) 🌪️· Веб-приложение· Для нетерпеливых (код)· Заключение· Ссылки

Введение

Мы все сталкиваемся с необходимостью читать многочисленные документы, чтобы найти два предложения с нужной нам информацией.

Вы когда-нибудь желали извлекать интересную информацию из документа, не теряясь в море слов?

Не ищите дальше! Добро пожаловать в проект «Общение с документами с использованием LLM». Это как извлечение сокровищ из PDF, TXT файлов или веб-страниц без усталости мозга. И не волнуйтесь, вам не нужно быть техническим гением, чтобы веселиться. Мы создали удобный пользовательский интерфейс с помощью Streamlit, чтобы даже ваши несведущие в технических вопросах друзья – помощники по маркетингу – могли присоединиться.

В этой статье мы погрузимся глубже в теорию и код приложения, рассмотрим принцип работы веб-приложений. Чтобы дать вам краткий обзор используемых технологий, ознакомьтесь с следующим изображением, на котором показано четыре основных инструмента.

Используемые ключевые технологии в этом проекте (изображение автора)

И, конечно, вы можете найти код в моем репозитории GitHub, или если вы хотите протестировать код напрямую, вы можете перейти в раздел «Для нетерпеливых (код)».

Как это работает

Давайте заглянем за покров «Общение с документами с использованием LLM». Этот проект состоит из интеллектуального плеера (бэк-энд) и удобного веб-сайта (фронт-энд).

  • Умный читатель: Искусственный интеллект читает и понимает документы, будто умный друг, отлично обрабатывая PDF, TXT и веб-контент.
  • Удобный веб-сайт: Пользовательский веб-интерфейс для настройки и взаимодействия с моделью. Он состоит из двух основных страниц, каждая из которых имеет свою цель: Шаг 1️⃣ Создание базы данных и Шаг 2️⃣ Запрос документов.

Как выглядит схема нашего проекта:

Схема работы проекта (изображение автора)

Хотя это может показаться сложным, мы упростим каждый важный шаг, чтобы сделать его функциональным. Сосредоточившись на принципах работы интеллекта, мы узнаем, как это работает. Эти шаги поддерживаются классом Python TalkDocument, и мы продемонстрируем каждый шаг с соответствующим кодом, чтобы оживить его.

Шаги (часть 1) 👣

Приготовьтесь исследовать каждый шаг пути, когда мы открываем волшебство за кулисами! 🚀🔍🎩

1- Импорт документов

Этот шаг очевиден, верно? Здесь вы решаете, какой тип материала предоставить. Будь то PDF, обычный текст, веб-URL или даже сырой строковый формат, все находится в меню.

# Внутри функции __init__ я закомментировал переменные,# которые на данный момент нас не интересуют.def __init__(self, HF_API_TOKEN,                   data_source_path=None,                   data_text=None,                   OPENAI_KEY=None) -> None:    # Вы можете ввести путь к файлу.    self.data_source_path = data_source_path    # Вы можете ввести файл в виде строки непосредственно    self.data_text = data_text    self.document = None    # self.document_splited = None    # self.embedding_model = None    # self.embedding_type = None    # self.OPENAI_KEY = OPENAI_KEY    # self.HF_API_TOKEN = HF_API_TOKEN    # self.db = None    # self.llm = None    # self.chain = None    # self.repo_id = Nonedef get_document(self, data_source_type="TXT"):# DS_TYPE_LIST= ["WEB", "PDF", "TXT"]       data_source_type = data_source_type if data_source_type.upper() in DS_TYPE_LIST else DS_TYPE_LIST[0]    if data_source_type == "TXT":        if self.data_text:            self.document = self.data_text        elif self.data_source_path:            loader = dl.TextLoader(self.data_source_path)            self.document = loader.load()    elif data_source_type == "PDF":        if self.data_text:            self.document = self.data_text        elif self.data_source_path:            loader = dl.PyPDFLoader(self.data_source_path)            self.document = loader.load()    elif data_source_type == "WEB":        loader = dl.WebBaseLoader(self.data_source_path)        self.document = loader.load()    return self.document

В зависимости от типа загружаемого файла, документ будет считываться различными методами. Интересным улучшением может быть добавление автоматического определения формата.

2- Тип разделения

Теперь вы можете задаться вопросом, зачем существует термин “тип”? В приложении вы можете выбрать метод разделения. Но прежде чем погрузиться в эту тему, давайте объясним, что такое разделение документа.

Представьте себе это так: так же, как людям нужны главы, абзацы и предложения для структурирования информации (представьте, что вы читаете книгу с бесконечным абзацем – да!), машинам тоже нужна структура. Нам нужно разделить документ на несколько меньших частей, чтобы лучше его понять. Это разделение можно выполнить посимвольно или по токенам.

# SPLIT_TYPE_LIST = ["CHARACTER", "TOKEN"]def get_split(self, split_type="character", chunk_size=200, chunk_overlap=10):    split_type = split_type.upper() if split_type.upper()                 in SPLIT_TYPE_LIST else SPLIT_TYPE_LIST[0]    if self.document:        if split_type == "CHARACTER":          text_splitter = ts.RecursiveCharacterTextSplitter(chunk_size=chunk_size, chunk_overlap=chunk_overlap)      elif split_type == "TOKEN":          text_splitter  = ts.TokenTextSplitter(chunk_size=chunk_size, chunk_overlap=chunk_overlap)              # Если вы вводите строку в качестве документа, мы выполним split_text.      if self.data_text:          try:              self.document_splited = text_splitter.split_text(text=self.document)          except Exception as error:              print( error)        # Если вы загружаете документ, мы выполним split_documents.      elif self.data_source_path:          try:              self.document_splited = text_splitter.split_documents(documents=self.document)          except Exception as error:              print( error)    return self.document_splited

3- Тип встраивания

Мы, люди, легко понимаем слова и картинки, но машинам нужна немного большая помощь. Это становится очевидным, когда:

  • Мы пытаемся преобразовать категориальные переменные в наборе данных в числа
  • Управление изображениями в нейронных сетях. Например, перед тем как изображение будет подано на модель нейронной сети, оно проходит преобразования, чтобы стать числовым тензором.

Как мы видим, математические модели имеют язык чисел. Это явление также наблюдается в области обработки естественного языка (NLP), где продвигается концепция word embedding.

По сути, то, что мы делаем на этом шаге, – это преобразование разделов с предыдущего этапа (куски документа) в числовые векторы.

Это преобразование или кодирование выполняется с использованием специализированных алгоритмов. Важно отметить, что этот процесс преобразует предложения в цифровые векторы, и это кодирование не является случайным; оно следует структурированному методу.

Этот код очень прост: мы создаем объект, ответственный за интеграцию. Важно отметить, что на данном этапе мы просто создаем объект интеграции. На следующих шагах мы выполним фактическое преобразование.

def get_embedding(self, embedding_type="HF", OPENAI_KEY=None):    if not self.embedding_model:      embedding_type = embedding_type.upper() if embedding_type.upper() in EMBEDDING_TYPE_LIST else EMBEDDING_TYPE_LIST[0]      # Если мы выбираем использовать модель Hugging Face для встраивания    if embedding_type == "HF":        self.embedding_model = embeddings.HuggingFaceEmbeddings()    # Если мы выбираем модель OpenAI для встраивания    elif embedding_type == "OPENAI":        self.OPENAI_KEY = self.OPENAI_KEY if self.OPENAI_KEY else OPENAI_KEY        if self.OPENAI_KEY:            self.embedding_model = embeddings.OpenAIEmbeddings(openai_api_key=OPENAI_KEY)        else:            print("Вам нужно ввести ключ API OPENAI")      # Объект    self.embedding_type = embedding_type      return self.embedding_model

Перерыв: Давайте подведем итоги (часть 1) 🌪️

Чтобы понять первые три шага, давайте рассмотрим следующий пример:

Первые три шага процесса (изображение автора). Примечание: В этом примере векторы представлены с 6 измерениями и были случайно созданы для пояснения.

Мы начинаем с входного текста.

  1. Мы делаем разделение на основе количества символов (приблизительно 50 символов в этом примере).
  2. Мы делаем интеграцию, преобразуя отрывки текста в цифровые векторы.

Как здорово! 🚀 Теперь давайте начнем захватывающее путешествие через остальные уровни. Пристегните ремни безопасности, потому что мы собираемся раскрыть некоторую настоящую техническую магию! 🔥🔓

Шаги (часть 2) 👣

Мы продолжим рассматривать последующие шаги.

4- Тип хранилища моделей векторов

Теперь, когда мы преобразовали наш текст в код (встроенный), нам нужно место для их хранения. Здесь и вступает в силу концепция “векторного хранилища”. Это похоже на умную библиотеку этих кодов, что облегчает поиск и извлечение похожих кодов при задании вопроса.

Можно представить это как аккуратное хранилище, которое позволяет быстро вернуться к тому, что вам нужно!

Создание этого типа базы данных управляется специализированными алгоритмами, разработанными для этой цели, такими как FAISS (Facebook AI Similarity Search). Есть и другие варианты, и в настоящее время этот класс поддерживает CHROMA и SVM.

На этом и следующем шаге используется один и тот же код. На этом шаге вы выбираете тип репозитория векторов, который хотите создать, а на следующем шаге происходит фактическое создание.

5. Модель VectoreStore (Создание)

Этот тип базы данных обрабатывает два основных аспекта:

  • Хранение векторов: Он сохраняет векторы, созданные при интеграции.
  • Расчет сходства: Он вычисляет сходство между векторами.

Но что именно такое сходство между этими векторами и почему это важно?

Что ж, помните, что я упоминал, что интеграция не случайна? Она разработана таким образом, чтобы слова или фразы с похожими значениями имели похожие векторы. Таким образом, мы можем вычислить расстояние между векторами (например, с помощью евклидова расстояния), и это дает нам меру их “сходства”.

Для визуализации этого на примере представим, что у нас есть три предложения.

Концепция встраивания и сходства. Обратите внимание, что эта представление упрощает фактический процесс функционирования. Для более глубокого понимания обратитесь к следующей статье для получения дополнительной информации. Введение в поиск сходства Facebook AI (Faiss). (Изображение автора)

Два из них связаны с рецептами, а третье связано с мотоциклами. Представляя их в виде векторов (благодаря интеграции), мы можем рассчитать расстояние между этими точками или предложениями. Это расстояние служит мерой их сходства.

С точки зрения кода, давайте рассмотрим требования:

  1. Разделение текста
  2. Тип встраивания
  3. Модель хранилища векторов
# VECTORSTORE_TYPE_LIST = ["FAISS", "CHROMA", "SVM"]def get_storage(self,                  vectorstore_type = "FAISS",                 embedding_type="HF",                 OPENAI_KEY=None):  self.embedding_type = self.embedding_type if self.embedding_type else embedding_type  vectorstore_type = vectorstore_type.upper() if vectorstore_type.upper() in VECTORSTORE_TYPE_LIST else VECTORSTORE_TYPE_LIST[0]    # Здесь мы вызываем алгоритм, который выполнил встраивание и создаем объект  self.get_embedding(embedding_type=self.embedding_type, OPENAI_KEY=OPENAI_KEY)    # Здесь мы выбираем тип хранилища векторов, который хотим использовать  if vectorstore_type == "FAISS":      model_vectorstore = vs.FAISS    elif vectorstore_type == "CHROMA":      model_vectorstore = vs.Chroma    elif vectorstore_type == "SVM":      model_vectorstore = retrievers.SVMRetriever  # Здесь мы создаем хранилище векторов. В данном случае   # документ поступает из исходного текста  if self.data_text:      try:          self.db = model_vectorstore.from_texts(self.document_splited,                                                 self.embedding_model)      except Exception as error:          print( error)    # Здесь мы создаем хранилище векторов. В данном случае,  # документ поступает из документа, такого как pdf txt...  elif self.data_source_path:      try:          self.db = model_vectorstore.from_documents(self.document_splited,                                                     self.embedding_model)      except Exception as error:          print( error)    return self.db

Чтобы проиллюстрировать, что происходит на этом шаге, мы можем визуализировать следующее изображение. Оно показывает, как закодированные фрагменты текста сохраняются в хранилище векторов, позволяя нам рассчитывать расстояние/сходство между векторами/точками.

Визуальное представление создания хранилища векторов (Изображение автора). Примечание: нереальные значения.

Перерыв: Подведем итоги (часть 2) 🌪️

Отлично! Теперь у нас есть база данных, способная хранить наши документы и рассчитывать сходство между зашифрованными фрагментами текста. Представьте ситуацию, когда мы хотим закодировать внешнее предложение и сохранить его в нашем хранилище векторов. Это позволит нам рассчитать расстояние между новым вектором и разделенным документом. (Обратите внимание, что здесь должна использоваться та же самая интеграция для создания хранилища векторов.) Мы можем визуализировать это с помощью следующего изображения.

Пример расчета расстояний между точками/текстом. Обратите внимание, что эта представление упрощает фактический процесс функционирования. Для более глубокого понимания обратитесь к следующей статье для получения дополнительной информации. Введение в поиск сходства Facebook AI (Faiss). (Изображение автора).

Помните предыдущую картинку… у нас есть два вопроса, и мы превращаем их в числа, используя интеграцию. Затем мы измеряем расстояние и находим ближайшие предложения к нашему вопросу. Эти предложения – как идеальные сочетания! Это похоже на то, как мы мгновенно находим лучший кусочек пазла! 🚀🧩🔍

6- Вопрос

Помните, что наша цель – задать вопрос о документе и получить ответ. На этом этапе мы собираем вопросы в качестве входных данных, предоставляемых пользователем.

7 & 8- Релевантные разделы

Здесь мы вставляем наш вопрос в репозиторий векторов, встраивая его, чтобы превратить предложение в цифровой вектор. Затем мы вычисляем расстояние между нашим вопросом и частями документа, определяя, какие части наиболее близки к нашему вопросу. Код выглядит так:

# В зависимости от типа векторного хранилища, # мы будем использовать определенную функцию. Все они возвращают # список наиболее релевантных разделов.def get_search(self, question, with_score=False):  relevant_docs = None    if self.db and "SVM" not in str(type(self.db)):        if with_score:          relevant_docs = self.db.similarity_search_with_relevance_scores(question)      else:          relevant_docs = self.db.similarity_search(question)  elif self.db:      relevant_docs = self.db.get_relevant_documents(question)    return relevant_docs

Попробуйте с кодом ниже. Узнайте, как отвечать в виде списка из 4 элементов. И когда мы заглянем внутрь, это фактически отдельные части введенного вами документа. Это похоже на приложение, которое предлагает вам 4 лучших части пазла, которые подходят к вашему вопросу, как влитые! 🧩💬

9- Ответ (естественный язык)

Отлично, теперь у нас есть наиболее релевантный текст для нашего вопроса. Но мы не можем просто передать эти части пользователю и закончить. Нам нужен лаконичный и точный ответ на их вопрос. И вот где наш Языковая Модель (LLM) вступает в игру! Есть много вариантов LLM. В коде мы устанавливаем “flan-alpaca-large” по умолчанию. Не стесняйтесь выбирать того, кого вы больше всего трогает! 🚀🎉

Вот план:

  1. Мы восстанавливаем наиболее важные части (разделы), связанные с вопросом.
  2. Мы готовим подсказку, которая включает вопрос, как мы хотим получить ответ, и эти текстовые элементы.
  3. Мы передаем эту подсказку нашей Интеллектуальной Языковой Модели (LLM). Он знает вопрос и информацию, содержащуюся в этих элементах, и дает нам естественные ответы.️

Эту последнюю часть показывает следующее изображение: 🖼️

Pipeline for Obtaining the Answer 🌐📜🧠 (Image by Author).

Как и следовало ожидать, этот точный поток выполняется в коде. Вы заметите, что в коде есть дополнительный шаг, связанный с созданием “подсказки”. В самом деле, мы можем получить окончательный ответ, который является комбинацией ответов, найденных LLM, и других вариантов. В целях упрощения давайте сделаем это простым способом: “stuff”. Это означает, что ответ – это первое решение, найденное LLM. Здесь вы не заблудитесь в теории! 🌟

    def do_question(self,                      question,                     repo_id="declare-lab/flan-alpaca-large",                      chain_type="stuff",                      relevant_docs=None,                      with_score=False,                      temperature=0,                      max_length=300):  # Мы получаем наиболее релевантные разделы.  relevant_docs = self.get_search(question, with_score=with_score)    # Мы определяем LLM, который хотим использовать,   # мы должны ввести идентификатор репо, так как мы используем huggingface.  self.repo_id = self.repo_id if self.repo_id is not None else repo_id  chain_type = chain_type.lower() if chain_type.lower() in CHAIN_TYPE_LIST else CHAIN_TYPE_LIST[0]    # Эта проверка необходима, поскольку мы можем вызывать функцию несколько раз,  # но не имеет смысла создавать LLM каждый раз при вызове функции.  # Поэтому она проверяет, существует ли уже llm внутри класса   # или если изменился идентификатор репо (тип llm).  if (self.repo_id != repo_id ) or (self.llm is None):     self.repo_id = repo_id      # Мы создали LLM.     self.llm = HuggingFaceHub(repo_id=self.repo_id,huggingfacehub_api_token=self.HF_API_TOKEN,                                model_kwargs=                                {"temperature":temperature,                                 "max_length": max_length})  # Мы создаем подсказку  prompt_template = """Используйте следующие контекстные элементы для ответа на вопрос в конце.   Если вы не знаете ответа, просто скажите, что не знаете, не пытайтесь придумать ответ.  Если вопрос похож на [Расскажи мне о документе],   ответ должен быть кратким комментарием о наиболее важных моментах документа      {context}  Вопрос: {question}  """  PROMPT = PromptTemplate(    template=prompt_template, input_variables=["context", "question"]  )      # Мы создаем цепочку, chain_type = "stuff".  self.chain = self.chain if self.chain is not None                 else load_qa_chain(self.llm,                                   chain_type=chain_type,                                   prompt = PROMPT)    # Мы делаем запрос к LLM с использованием подсказки  # Мы проверяем, определена ли уже цепочка,   # если она не существует, создается  response = self.chain({"input_documents": relevant_docs, "question": question}, return_only_outputs=True)    return response

Вы справились, достигнув конца понимания того, как работает мозг нашего веб-приложения. Дайте пять, если вы здесь! Теперь давайте погрузимся в финальный этап статьи, где мы исследуем страницы веб-приложения. 🎉🕵️‍♂️

Веб-приложение

Этот интерфейс разработан для людей без технических знаний, чтобы вы могли максимально использовать эту технологию без проблем. 🚀👩‍💻

Использование сайта крайне простое и мощное. Пользователю в основном просто нужно предоставить документ. Если вы не готовы использовать дополнительные параметры, на следующем шаге вы уже можете начать задавать вопросы. 🌟🤖

Давайте рассмотрим шаги, которые нужно выполнить:

  1. Предоставьте документ или веб-ссылку. На странице Шаг 1️⃣ Создание базы данных.
  2. Настройте конфигурацию (необязательно). На странице Шаг 1️⃣ Создание базы данных.
  3. Задавайте вопросы и получайте ответы! 📚🔍🚀 На странице Шаг 2️⃣ Задайте вопрос документу.

1. Предоставьте документ или веб-ссылку.

На этом шаге вы добавляете документ, загружая его в веб. Помните, что если у вас нет ключа API Face Hugging в качестве переменной среды, вкладка “главная” попросит вас ввести его. 📂🔑

Страница, где вы предоставляете документ или URL: 📁🌐 (Изображение автора). Реальное изображение веб-приложения на странице “Шаг 1️⃣ Создание базы данных”.

После того, как вы прикрепили документ и настроили свои предпочтения, отображается сводная таблица вашей конфигурации. Кнопка “Создать базу данных” тогда будет разблокирована. При нажатии на кнопку будет создана база данных или векторное хранилище, и вы будете перенаправлены на раздел с вопросами. 📑🔒🚀

Сводная таблица: 📊📋 (Изображение автора). Реальное изображение веб-приложения на странице “Шаг 1️⃣ Создание базы данных”.

2. Настройте конфигурацию (необязательно).

Как я уже упоминал ранее, мы можем настроить векторное хранилище в соответствии с нашими предпочтениями. Существуют различные методы разделения документов, встраивания и другие. Эта вкладка позволяет настроить ее по вашему усмотрению. Она поставляется с настройками по умолчанию для более быстрой настройки. ⚙️🛠️

Настройка векторного хранилища и LLM: ⚙️🔧🤖 (Изображение автора). Реальное изображение веб-приложения.

3. Задавайте вопросы и получайте ответы!

На этом этапе вы можете начать задавать все вопросы, которые вы хотите. Теперь пришло время наслаждаться и взаимодействовать с инструментом на свое усмотрение! 🤗🔍💬

Страница для задавания вопросов: 💬🔍 (Изображение автора). Реальное изображение веб-приложения на странице «Шаг 2️⃣ Запросить документ».

Для нетерпеливых (код)

Для тех, кто хочет сразу погружаться в код, вы можете сразу взять класс TalkDocument и вставить его в блокнот Jupyter для начала экспериментов. Вам может понадобиться установить несколько зависимостей, но я уверен, что это не будет вызывать у вас трудностей. Приятного времяпрепровождения при исследовании и экспериментах! Счастливого кодирования!! 🚀📚😄

Класс TalkDocument в коде для игры (Код автора)

Выводы

Поздравляю вас с продвижением в нашем захватывающем путешествии! Мы погрузились в то, как работает интеллект за этим веб-приложением. От загрузки документов до получения ответов, вы прошли долгий путь! Если вы чувствуете вдохновение, код доступен в моем репозитории на GitHub. Не стесняйтесь сотрудничать и связываться со мной в LinkedIn с любыми вопросами или предложениями! Приятного исследования и экспериментирования с этим мощным инструментом!

Если хотите, можете проверить мой GitHub

damiangilgonzalez1995 – Обзор

Увлеченный данными, я перешел от физики к науке о данных. Работал в Telefonica, HP, и теперь CTO в…

github.com

Ссылки

  • Документация Langchain
  • Документация Hugging Face
  • Введение в Facebook AI Similarity Search (Faiss)
  • Документация Steamlit