Как построить систему RAG на 10 строках Python без фреймворков

Раскройте силу RAG (Retrieval Augmented Generation) с помощью этого 10-строчного учебника по Python-коду. Погрузитесь в информационный поиск, создайте свою собственную систему документов и используйте LLM для получения надежных ответов - все это без использования сложных фреймворков. Оптимизируйте свой контент для SEO и вовлечения.

24 февраля 2025 г.

party-gif

Узнайте, как построить мощную систему генерации, усиленную поиском (RAG), с нуля всего за 10 строк кода Python, без использования каких-либо внешних фреймворков. Этот краткий, но всеобъемлющий подход дает вам глубокое понимание основных компонентов, позволяя создавать надежные и настраиваемые решения на основе искусственного интеллекта для ваших приложений, основанных на документах.

Понимание концепции генерации с помощью извлечения (Retrieval Augmented Generation, RAG)

Ретриевальное усиленное генерирование (RAG) - это техника, используемая для поиска информации в ваших собственных документах. Она включает в себя двухэтапный процесс:

  1. Создание базы знаний: Документы разбиваются на более мелкие поддокументы, и для каждого фрагмента вычисляются встраивания. Эти встраивания и исходные фрагменты обычно хранятся в векторном хранилище.

  2. Поиск и генерация: Когда поступает новый запрос пользователя, вычисляется встраивание для запроса. Наиболее релевантные фрагменты извлекаются путем сравнения встраивания запроса со встраиваниями сохраненных фрагментов. Извлеченные фрагменты затем добавляются к исходному запросу и подаются в модель языка (LLM) для генерации окончательного ответа.

Эта настройка называется "конвейером ретриевального усиленного генерирования", поскольку генерация LLM усиливается соответствующей информацией, извлеченной из базы знаний.

Научитесь разбивать документы на абзацы

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

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

Код для этого шага выглядит следующим образом:

# Разбить документ на абзацы
chunks = [paragraph.strip() for paragraph in text.split('\n') if paragraph.strip()]

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

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

Встраивание документов и пользовательских запросов с помощью предложенных преобразователей

Для встраивания фрагментов документа и запроса пользователя мы будем использовать библиотеку Sentence Transformers. Эта библиотека предоставляет предварительно обученные модели, которые могут генерировать высококачественные встраивания для текста.

Сначала мы загружаем модель встраивания:

from sentence_transformers import SentenceTransformer
embedding_model = SentenceTransformer('all-mpnet-base-v2')

Затем мы вычисляем встраивания для фрагментов документа:

chunk_embeddings = embedding_model.encode(chunks, normalize_embeddings=True)

Здесь chunks - это список текстовых фрагментов из документа. Параметр normalize_embeddings=True обеспечивает нормализацию встраиваний, что важно для последующего вычисления сходства.

Чтобы получить встраивание для запроса пользователя, мы просто выполняем:

query_embedding = embedding_model.encode([user_query], normalize_embeddings=True)[0]

Теперь у нас есть встраивания для фрагментов документа и запроса пользователя, которые мы можем использовать для этапа поиска.

Извлечение соответствующих фрагментов на основе косинусного сходства

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

  1. Вычислить встраивание запроса пользователя с использованием той же модели встраивания, что и для фрагментов документа.
  2. Вычислить скалярное произведение между встраиванием запроса и каждым из встраиваний фрагментов документа. Это даст нам оценки косинусного сходства.
  3. Отсортировать фрагменты документа по оценкам косинусного сходства и выбрать K наиболее релевантных фрагментов.
  4. Извлечь текстовое содержимое выбранных фрагментов для использования в качестве контекста для модели языка.

Ключевые шаги:

  1. Вычислить встраивание запроса
  2. Вычислить оценки косинусного сходства
  3. Отсортировать и выбрать K лучших фрагментов
  4. Извлечь текстовое содержимое фрагментов

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

Дополнение пользовательского запроса извлеченными фрагментами и генерация ответа с использованием OpenAI GPT-4

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

Вот код:

# Получить запрос пользователя
user_query = "What is the capital of France?"

# Извлечь наиболее релевантные фрагменты на основе запроса пользователя
top_chunk_ids = [6, 8, 5]
top_chunks = [chunks[i] for i in top_chunk_ids]

# Создать подсказку
prompt = "Use the following context to answer the question at the end. If you don't know the answer, say that you don't know and do not try to make up an answer.\n\nContext:\n"
for chunk in top_chunks:
    prompt += chunk + "\n\n"
prompt += f"\nQuestion: {user_query}"

# Сгенерировать ответ с использованием OpenAI GPT-4
response = openai_client.chat.create(
    model="gpt-4",
    messages=[
        {"role": "user", "content": prompt}
    ],
    max_tokens=1024,
    n=1,
    stop=None,
    temperature=0.7,
).choices[0].message.content

print(response)

В этом коде мы сначала извлекаем наиболее релевантные фрагменты на основе запроса пользователя, найдя 3 лучших фрагмента с наивысшими оценками сходства. Затем мы создаем подсказку, которая включает эти фрагменты и запрос пользователя. Наконец, мы используем модель OpenAI GPT-4 для генерации ответа на основе этой подсказки.

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

Заключение

В этом учебнике мы узнали, как построить полную систему чата с системой поиска документов, используя всего 10 строк кода Python. Мы рассмотрели ключевые компоненты конвейера ретриевального усиленного генерирования (RAG), включая создание базы знаний, разбиение документов на фрагменты, встраивание, поиск и генерацию с использованием крупной языковой модели.

Основные выводы:

  1. Вы можете реализовать базовый конвейер RAG без необходимости в сложных фреймворках, таких как Langchain или LlamaIndex. Достаточно чистого Python и нескольких библиотек.
  2. Разбиение ваших документов на основе структуры (например, абзацы) - простая, но эффективная стратегия для большинства случаев использования.
  3. Встраивание фрагментов документа и запроса пользователя, а затем вычисление оценок сходства, позволяет вам извлекать наиболее релевантную информацию для дополнения запроса пользователя.
  4. Интеграция извлеченных фрагментов с запросом пользователя и передача их в крупную языковую модель позволяет генерировать релевантный и информативный ответ.

Хотя этот пример обеспечивает прочную основу, существует множество возможностей для построения более надежных и продвинутых систем RAG. Фреймворки, такие как Langchain и LlamaIndex, могут быть полезны при интеграции с различными векторными хранилищами и языковыми моделями. Однако начало с реализации на чистом Python может помочь вам лучше понять основные концепции и компоненты конвейера RAG.

Если вы заинтересованы в изучении более продвинутых методов RAG, я рекомендую ознакомиться с моим курсом "RAG Beyond Basics", в котором более подробно рассматривается построение сложных, готовых к производству систем RAG.

Часто задаваемые вопросы