С момента написания прошлой статьи прошло не так много времени, но прогресс не стоит на месте и произошло несколько важных изменений. Здесь я не буду рассматривать основы - почитайте оригинальную статью.
Контекст
Итак, первое важное изменение - это значительный рост размера контекстного окна и падение стоимости токенов, до инфляции лиры еще не дотягивает, но уже близко. Например, размер окна в самой большой модели Клод от Антропик составляет 200+ тыс токенов, а Gemini, судя по последним новостям до 10млн. В таких условиях для многих задач RAG просто не требуется (или не все его компоненты), т.к. все данные можно поместить в контекстное окно. Мы уже столкнулись на практике с несколькими проектами в финансово-аналитической сфере, где задача составления инвестиционных проспектов на основе отчётности компаний была полностью решена без использования векторной базы данных как промежуточного хранилища.
Тенденция на удешевление токенов и увеличение контекстного окна явно продолжится, поэтому актуальность использования сторонних для LLM механизмов будет падать. Но пока они еще требуются и есть причина по которой RAG может остаться с нами надолго - разделение прав и управление доступом. Делать это на уровне LLM никак нельзя, ибо LLM можно убедить в чем угодно и так останется ещё долго (если не всегда). Об этом ниже.
Если же (вдруг) размера контекста не хватает, то люди придумали разные методы суммаризации и сжатия контекста. В langchain появился класс, направленный на это: ConversationSummaryMemory
memory = ConversationSummaryMemory(llm=OpenAI(temperature=0))
memory.save_context({"input": "hi"}, {"output": "Hello there!"})
memory.load_memory_variables({})
{'history': '\nThe human greets the AI, to which the AI responds.'}
llm = OpenAI(temperature=0)
conversation_with_summary = ConversationChain(
llm=llm,
memory=ConversationSummaryMemory(llm=OpenAI()),
verbose=True
)
conversation_with_summary.predict(input="Hi, what's up?")
Карта Знаний aka Knowledge Graph
С ростом количества данных, в которых LLM приходится ориентироваться становится куда более важным возможность ориентироваться в данных. Иногда, без возможности проанализировать структуру данных и некоторые другие атрибуты не возможно ими нормально пользоваться. Например, допустим источником данных является вики компании. В вики есть страница с телефоном данной компании, но это никак явно нигде не указанно. Итак - как LLM понять что это телефон компании? Непонятно, поэтому на вопрос о телефоне компании стандартный RAG ничего не выдаст (т.к. не видит никакой взаимосвязи). Как человек понимает что это телефон компании в данном случае? Очень просто, страница хранится в подкаталоге, который называется “Информация о компании”. Т.е. человек способен из конвенции о том как данные хранятся (т.е. из структуры или иначе метаданных) понять о чем эти данные и эффективно их использовать. В случае с LLM данная проблема решается с помощью Knowledge Graphs с метаданными (они же Knowledge Maps), т.е. LLM имеет не только данные в чистом виде но и данные о структуре хранения и связях разных data entities в данных. Т.е. понимает что эти данные означают. Этот подход называют еще Graph Retrieval Augmented Generation или GraphRag.
Графы отлично подходят для представления и хранения разнородной и взаимосвязанной информации в структурированном виде, легко захватывая сложные отношения и атрибуты среди различных типов данных, с чем возникают проблемы у векторных баз.
Как создать граф знаний? Это интересный вопрос. Обычно этот процесс включает в себя сбор и структурирование данных, что требует глубокого понимания как предметной области, так и моделирования графов.
В значительной степени этот процесс можно автоматизировать с помощью LLM (сюрприз 🙂). Благодаря их пониманию языка и контекста, LLM могут автоматизировать значительные части процесса создания графа знаний. Анализируя текстовые данные, эти модели могут идентифицировать сущности, понимать их взаимосвязи и предлагать, как лучше всего представить их в структуре графа.
Ванила RAG выглядит примерно так:
Модифицированный процесс будет выглядеть так:
Тоесть, по факту это ансамбль из векторной базы и графа знаний. Как я упомянул в разделе про ансамбли в предыдущей статье, они в целом повышаю точность и часто сюда еще добавляют поиск по обычной базе данных или по keywords (например elastic search).
Я не буду описывать vector ретривер, так как это описано в первой статье. Но давайте поглядим на Knowledge Graph Retriever. Как я упомянул выше, самый очевидный способ - это попросить LLM. Например юзер задаёт вопрос про номер телефона компании:
Если вы делаете это в коде, то можно попросить форматировать найденные сущности в формате json или использовать with_structured_output из langchain.
Итак, сущности из вопроса вытащили - что дальше. А дальше мы разберём 100500 кейсов нашей компании о том как мы это применяли 😂. Ладно, я пошутил. Дальше, нужно сделать поиск по этим сущностям в Графе Знаний. Как это делается - будет зависеть где граф хранится.
Хранилищ графов уже наплодили немало (но это не мешает компаниям колхозить свои версии с блекджеком и сами знаете чем), для примера возьмём Nebula.
documents = parse_and_load_data_from_wiki_including_metadata()
graph_store = NebulaGraphStore(
space_name=”Company Wiki”,
tags=["entity"]
)
storage_context = StorageContext.from_defaults(graph_store=graph_store)
index = KnowledgeGraphIndex.from_documents(
documents,
max_triplets_per_chunk=2,
space_name=space_name,
tags=tags=["entity"]
)
query_engine = index.as_query_engine()
response = query_engine.query("Tell me more about our Company")
Как видите, поиск не сильно отличается от поиска в векторной базе, с той лишь разницей что мы ищем атрибуты и связанные сущности, а не похожие векторы. Возвращаясь к первому вопросу, так как структура вики была передана в граф, если все сработало как надо, к компании в графе будет добавлен телефон как связанная сущность.
Далее, мы передаем эти данные и данные из поиска по векторной базе в LLM для генерации полного ответа.
Выглядит просто, но есть несколько проблем.
Access Control
Первая это то что доступ к данным может быть не равномерным, т.е. В том же вики могут быть роли и права и не каждый юзер потенциально может видеть всю информацию. Эта же проблема, на самом деле существует и для поиска в векторной базе. Тоесть встаёт проблема управления доступом. Эта проблема еще и усложняется тем, что есть много разных подходов и их гибридов, и например, кто работал с SharePoint тот в цирке не смеётся.
Есть как минимум Role-Based Access Control (RBAC), Attribute-Based Access Control (ABAC), и Relationship-Based Access Control (ReBAC) и их сочетания.
Вообще говоря, User Directories (тот же Active Directory), например, тоже представляет собой граф, в котором вопрос доступа примерно звучит как “Есть ли путь от ноды юзер U к ноде ресурса R”. Если такой путь есть - доступ разрешен.
Права и категории - тоже форма метаданных, и для того чтобы это все хозяйство работало - эти метеданные нужно сохранить на шаге Data Ingestion в граф знаний и векторную базу.
И, соответственно, при поиске в векторной базе, нужно проверять на найденных документах соответствует ли роль или другие атрибуты доступа тому что доступно юзеру.
Некоторые (особенно коммерческие корпоративные векторные) базы уже имеют данный фунционал как стандартный.
Это все не сработает если данные были зашиты в LLM в процессе обучения 🙂. Тут придется полагаться на разумность самой LLM, и пока я бы не стал так делать.
Дополнительно и на всякий случай можно сверху поставить цензора (guard), фильтровать вывод модели, если уж что-то просочилось. Все известна Lakera, в нашей компании мы так же разработали подобный продукт.
Ingestion and parsing
Данные надо в граф как то засунуть, впрочем как и в векторную базу. Но для графа формат критичен, так как он отражает структуру данных и является метаданными.
Тут начинается кошмар всех датасаентистов, так же известный как формат PDF. В пдф можно засунуть все: таблицы, изображения, текст, графики. Вот только высунуть это все назад иногда не представляется возможным (особенно вложенные таблицы).
Есть разные фреймворки и библиотеки, которые делают это с разным уровнем успеха:
PyPDF2
PdfMiner
Tabula
PDFQuery
PyMyPDF
Pytesseract
Здесь, к сожалению, пока нет нормального решения, которое всегда работает, и иногда проще использовать OCR или распознавание снимка изображения документа вместо парсинга. Возможно кто то создаст отдельную модель, направленную только на парсинг PDF во что то более приемлемое. Мечтать не вредно..
В целом, направление мысли в данный момент направленно на улучшение качества ответов. Кроме использования графов знаний, есть несколько
Corrective Retrieval Augmented Generation (CRAG)
Мы видели что RAG иногда даёт неверные результаты и для их оценки могут использоваться разные методы, например сама LLM (или какой то более легковесный вариант). Если результаты не релевантны может происходить корректировка промпта, запрос к графу знаний или даже поиск в гугле. CRAG идет чуть дальше, предлагая фреймворк который автоматизирует этот процесс. По сути это еще один граф, реализующий машину состояний (сюрприз 🙂), который выглядит примерно так:
Для реализации проще всего использовать LangGraph, о нем дальше.
Self-RAG
Self-reflective RAG базируется на данном исследовании, которое утверждает что данный подход даёт лучшие результаты чем обычный RAG.
В целом, идея очень похожа на предыдущую (CRAG), но идет дальше.
Идея в том чтобы зафайнтюнить LLM на генерацию токенов саморефлексии, в дополнение к обычным. Токены используются чтобы направить процесс поиска ответа, то есть в каком-то смысле процесс управляется самой LLM (крамольная идея, я знаю, но тут команды ограничены и не все так страшно). Это очень удобно, так как не приходится догадываться насколько LLM уверенна и что с этим делать. Токены генерируются следующие:
Retrieve токен определяет нужно ли достать D чанков для данного промпта x. Варианты Yes, No, Continue
ISREL токен определяет, является ли чанк d из D релевантным ответом для данного промпта x. Варианты relevant и irrelevant
ISSUP токен определяет релевантен ли ответ(генрация) y LLM на чанк d, тоесть подтверждает ли чанк d ответ y. Варианты fully supported, partially supported, no support.
ISUSE токен определяет, является ли ответ LLM на каждый чанк d полезным ответом на запрос x. Варианты представляют шкалу полезности от 5 до 1.
Используя эти токены, можно построить стейт-машину, используя вышеупомянутый LangGraph, примерно такого типа:
Подробнее тут.
HyDe
Еще один метод, схожий с RAG Fusion в том что он модифицирует обычный для RAG процесс поиска. Hyde расшифровывается как Hypothetical Document Embeddings и базируется на исследовании Precise Zero-Shot Dense Retrieval without Relevance Labels.
Идея очень простая - вместо того чтобы использовать вопрос юзера для поиска в векторной базе мы используем LLM чтобы сгенерировать ответ (виртуальный гипотетический документ), и затем ответ используем для поиска в векторной базе (чтобы найти похожие ответы).
Для чего все это? Иногда вопросы юзера слишком абстрактны, и требуется больше контекста, который LLM может дать, и без которого поиск в базе не имеет никакого смысла.
Думаю это не исчерпывающий обзор новых изменений, если я что то забыл - пишите в комментариях.
Я со-основатель AI интегратора Рафт.
Делюсь опытом в ТГ-канале.
Всем добра и позитивного настроения!