От теории из оригинальной академической статьи до ее реализации на Python с OpenAI, Weaviate и LangChain

Рабочий процесс генерации дополненного извлечения
Рабочий процесс генерации дополненного извлечения

С тех пор как мы осознали, что мы можем перегрузить крупные языковые модели (LLM) нашими собственными данными, активно велись дискуссии по поводу того, как наиболее эффективно преодолеть разрыв между общими знаниями LLM и нашими собственными данными. Было много споров о том, что больше всего подходит для этого: тонкая настройка или генерация дополненного извлечения (RAG) (внимание, спойлер: и то, и другое).

В этой статье основное внимание уделяется концепции RAG и сначала рассматривается ее теория. Затем в статье демонстрируется, как можно реализовать простой конвейер RAG с использованием LangChain для языковых моделей OpenAI и векторной базы данных Weaviate.

Что такое генерация дополненного извлечения

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

Проблема

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

Ответ ChatGPT на вопрос «Что президент сказал о судье Брейере?»
Ответ ChatGPT на вопрос «Что президент сказал о судье Брейере?»

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

Решение

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

В 2020 году в статье «Генерация дополненного извлечения для задач обработки естественного языка с интенсивным использованием знаний» (Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks)» Льюис и др. предложили более гибкий метод под названием «Генерация дополненного извлечения» (RAG). В этой статье исследователи объединили генеративную модель с модулем извлечения, чтобы предоставить дополнительную информацию из внешнего источника знаний, которую можно было бы легче обновлять.

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

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

  • Параметрические знания: полученные во время обучения, которые неявно хранятся в весах нейронной сети.

  • Непараметрические знания: сохраненные во внешнем источнике знаний, например, в векторной базе данных.

(Кстати, это гениальное сравнение придумал не я. Насколько мне известно, это сравнение впервые упомянул JJ во время конкурса Kaggle — LLM Science Exam).

Ниже изображен рабочий процесс стандартного RAG:

Рабочий процесс генерации дополненного извлечения
Рабочий процесс генерации дополненного извлечения
  1. Извлечь: пользовательский запрос используется для извлечения соответствующего контекста из внешнего источника знаний. Для этого пользовательский запрос встраивается с моделью встраивания в то же векторное пространство, что и дополнительный контекст в векторной базе данных. Это позволяет выполнить поиск по сходству и вернуть первые k ближайших объектов данных из векторной базы данных.

  2. Дополнить: пользовательский запрос и извлеченный дополнительный контекст вставляются в шаблон подсказки.

  3. Генерировать: и, наконец, подсказка с дополненным извлечением передается в LLM.

Реализация генерации дополненного извлечения с использованием LangChain

В этом разделе реализуется конвейер RAG на Python с использованием LLM от OpenAI в сочетании с векторной базой данных Weaviate и моделью встраивания OpenAI. LangChain используется для оркестровки.

Предварительные условия

Убедитесь, что вы установили необходимые пакеты Python:

  • langchain для оркестровки

  • openai для модели встраивания и LLM

  • weaviate-client для векторной базы данных

#!pip install langchain openai weaviate-client

Кроме того, определите соответствующие переменные среды в файле .env в корневом каталоге. Чтобы получить ключ API OpenAI, вам нужна учетная запись OpenAI, где вам нужно создать новый секретный ключ в разделе ключей API.

OPENAI_API_KEY="<YOUR_OPENAI_API_KEY>"

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

import dotenv
dotenv.load_dotenv()

Подготовка

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

  1. Собрать и загрузить данные

  2. Разбить документы на части

  3. Встроить и сохранить фрагменты

Первый шаг — собрать и загрузить данные. В этом примере в качестве дополнительного контекста вы будете использовать Послание президента Байдена о положении страны от 2022 года. Необработанный текстовый документ доступен в репозитории LangChain на GitHub. Для загрузки данных вы можете использовать один из множества встроенных DocumentLoaders на LangChain. Document представляет собой словарь с текстом и метаданными. Для загрузки текста вы будете использовать TextLoader на LangChain.

import requests
from langchain.document_loaders import TextLoader

url = "https://raw.githubusercontent.com/langchain-ai/langchain/master/docs/docs/modules/state_of_the_union.txt"
res = requests.get(url)
with open("state_of_the_union.txt", "w") as f:
    f.write(res.text)

loader = TextLoader('./state_of_the_union.txt')
documents = loader.load()

Далее разбить документы на фрагменты — поскольку Document в своем исходном состоянии слишком длинный, чтобы поместиться в контекстное окно LLM, вам нужно разбить его на более мелкие части. Для этой цели в LangChain имеется множество встроенных разделителей текста. Для этого простого примера вы можете использовать CharacterTextSplitter с chunk_size около 500 и chunk_overlap 50, чтобы сохранить непрерывность текста между фрагментами.

from langchain.text_splitter import CharacterTextSplitter
text_splitter = CharacterTextSplitter(chunk_size=500, chunk_overlap=50)
chunks = text_splitter.split_documents(documents)

И, наконец, встроить и сохранить фрагменты — чтобы включить семантический поиск по текстовым фрагментам, вам нужно сгенерировать векторные встраивания для каждого фрагмента, а затем сохранить их вместе с их встраиваниями. Для генерации векторных встраиваний вы можете использовать модель встраивания OpenAI, а для их хранения вы можете использовать векторную базу данных Weaviate. При вызове .from_documents() векторная база данных автоматически заполняется фрагментами.

from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Weaviate
import weaviate
from weaviate.embedded import EmbeddedOptions

client = weaviate.Client(
  embedded_options = EmbeddedOptions()
)

vectorstore = Weaviate.from_documents(
    client = client,    
    documents = chunks,
    embedding = OpenAIEmbeddings(),
    by_text = False
)

Шаг 1: Извлечение

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

retriever = vectorstore.as_retriever()

Шаг 2: Дополнение

Далее, чтобы дополнить подсказку дополнительным контекстом, вам необходимо подготовить шаблон подсказки. Подсказку можно легко настроить из шаблона подсказки, как показано ниже.

from langchain.prompts import ChatPromptTemplate

template = """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.
Question: {question} 
Context: {context} 
Answer:
"""
prompt = ChatPromptTemplate.from_template(template)

print(prompt)

Шаг 3: Генерация

И, наконец, вы можете построить цепочку для конвейера RAG, объединив вместе извлекатель, шаблон подсказки и LLM. Как только цепочка RAG определена, вы можете вызвать ее.

from langchain.chat_models import ChatOpenAI
from langchain.schema.runnable import RunnablePassthrough
from langchain.schema.output_parser import StrOutputParser

llm = ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0)

rag_chain = (
    {"context": retriever,  "question": RunnablePassthrough()} 
    | prompt 
    | llm
    | StrOutputParser() 
)

query = "What did the president say about Justice Breyer"
rag_chain.invoke(query)
"The president thanked Justice Breyer for his service and acknowledged his dedication to serving the country. 
The president also mentioned that he nominated Judge Ketanji Brown Jackson as a successor to continue Justice Breyer's legacy of excellence."

Ниже изображен полученный конвейер RAG для этого конкретного примера.

Рабочий процесс генерации дополненного извлечения
Рабочий процесс генерации дополненного извлечения

Резюме

Мы рассмотрели концепцию RAG, которая была представлена ​​в статье «Генерация дополненного извлечения для задач обработки естественного языка с интенсивным использованием знаний» (Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks)» от 2020 года. Сначала мы рассмотрели теорию, лежащую в основе этой концепции, включая мотивацию и решение проблем, затем - ее реализацию в Python. В этой статье мы реализовали конвейер RAG с использованием LLM от OpenAI в сочетании с векторной базой данных Weaviate и моделью встраивания OpenAI. Для оркестровки мы использовали LangChain.

Комментарии (5)


  1. ENick
    02.12.2024 12:23

    "from langchain.embeddings import OpenAIEmbeddings" Вместо этого организационно гораздо проще использовать HuggingFace


    1. yonesko
      02.12.2024 12:23

      А как ? Я вот хочу у себя на работе сделать rag по документации на русском языке, но не знаю как


      1. ENick
        02.12.2024 12:23

        Зависит от многих факторов. Как один из вариантов - применить HuggingFaceEmbeddings


  1. madevgesha
    02.12.2024 12:23

    Зачем использовать openai, когда есть более достойные модели для эмбеддинга с HF ? Так много пишите про RAG, который к слову в той же anything llm запускается в два клика , но никто не пишет нормальных статей про пред обработку текста перед эмбеддингом, у Вас семантическая связь от одной только точки может пойти по...


    1. ENick
      02.12.2024 12:23

      "Зачем использовать openai..." - это они так умность свою показывают.

      При применении openai теряется основной смысл RAG как локальной системы. Сразу видно, что автор сам не сознал ни одного RAG