Главная / Статьи

Обогащение метаданных в RAG: фильтры, цитаты, свежесть и контроль доступа

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

Издатель: REACTAPP.TOP14 мин чтения3163 слов

Источники: Metadata Enrichment in RAG: The Secret Ingredient for Better Retrieval

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

Ответ пришёл быстро: сотрудницы имеют право на 20 недель оплачиваемого отпуска по беременности и родам. Всё казалось правильным. Однако проверка исходных документов показала, что взятый текст относится к политике, предназначенной для сотрудников США, а не Европы. Механизм поиска нашёл фрагмент о отпуске по беременности и родам, но не понял, к какому региону он относится, поскольку окружающий контекст был утрачен в процессе разбиения на фрагменты и индексации.

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

Что идёт не так при поиске только по содержимому

Посмотрите, как обычно работает система RAG для начинающих. Организация хранит три документа с правилами: руководство для сотрудников в США, аналогичное для Европы и для региона Азия-Тихоокеанский. Во всех трех есть раздел о отпуске по беременности и родам. После разбиения на фрагменты векторная база данных хранит практически идентичные предложения, отличающиеся только количеством: 20 недель в одном, 16 — в другом и 26 — в третьем.

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

Что такое метаданные

Метаданные — это данные о данных. В системе RAG они описывают фрагмент, не являясь частью его основного содержимого. Они включают всю информацию, верную относительно текста, но не являющуюся его прямым содержанием.

Возьмите фрагмент из примера выше:

Employees are entitled to 20 weeks of paid maternity leave.

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

Теперь тот же запрос с добавленными метаданными:

{
  "source": "EU_HR_Policy.pdf",
  "region": "Europe",
  "department": "Human Resources",
  "last_updated": "2025-03-15"
}

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

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

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

Как метаданные улучшают поиск

Вернитесь к примеру с отпуском по беременности и проследите за процессом поиска с использованием метаданных и без них.

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

Система с метаданными может вместо этого применить фильтр до или вместе с поиском по сходству:

filter = {
    "region": "Europe"
}

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

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

Вот подробное описание сценария отпуска по беременности и родам от начала до конца:

from sentence_transformers import SentenceTransformer
import numpy as np

embedding_model = SentenceTransformer("all-MiniLM-L6-v2")
chunks = [
    {"text": "Employees are entitled to 20 weeks of paid maternity leave.", "region": "United States"},
    {"text": "Employees are entitled to 16 weeks of paid maternity leave.", "region": "Europe"},
    {"text": "Employees are entitled to 26 weeks of paid maternity leave.", "region": "Asia-Pacific"},
]

for c in chunks:
    c["embedding"] = embedding_model.encode(c["text"])

query = "What is the maternity leave policy for employees in the European office?"
query_embedding = embedding_model.encode(query)

def search(chunks, query_embedding, region_filter=None):
    candidates = chunks if region_filter is None else [c for c in chunks if c["region"] == region_filter]
    scored = [(np.dot(query_embedding, c["embedding"]), c) for c in candidates]
    scored.sort(key=lambda x: x[0], reverse=True)
    return scored[0][1]

print("Without metadata filter:")
result = search(chunks, query_embedding)
print(f"  Returned: '{result['text']}' (region: {result['region']})")
print("\nWith metadata filter (region='Europe'):")
result = search(chunks, query_embedding, region_filter="Europe")
print(f"  Returned: '{result['text']}' (region: {result['region']})")

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

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

Четыре вида метаданных, которые стоит иметь

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

Исходные метаданные

Они указывают на источник информации. Типичными полями являются имя файла, номер страницы, URL и идентификатор документа. Они важны, когда пользователи нуждаются в цитатах или хотят проверить ответ. Вот как формируется цитата:

def format_citation(metadata):
    return f"Source: {metadata['source']}, Page {metadata['page']}"

chunk_metadata = {"source": "Employee_Handbook_2025.pdf", "page": 42}
print(format_citation(chunk_metadata))

Какой будет отпечатан:

Source: Employee_Handbook_2025.pdf, Page 42

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

Организационные метаданные

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

filter = {"department": "Finance"}

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

Временные метаданные

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

Рассмотрим два фрагмента, касающихся одного и того же правила, но с разными датами обновления:

chunks = [
    {"text": "Employees receive 15 vacation days per year.", "last_updated": "2023-01-10"},
    {"text": "Employees receive 20 vacation days per year.", "last_updated": "2025-03-15"},
]

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

most_recent = max(chunks, key=lambda c: c["last_updated"])
print(most_recent["text"])

Результат:

Employees receive 20 vacation days per year.

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

Метаданные безопасности

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

chunk_metadata = {
    "access_level": "confidential",
    "allowed_roles": ["HR_Manager", "HR_Admin"]
}

Перед извлечением система сравнивает роль запрашивающего пользователя с ролями, разрешенными для просмотра данного фрагмента:

def can_access(user_role, chunk_metadata):
    return user_role in chunk_metadata["allowed_roles"]

print(can_access("HR_Manager", chunk_metadata))   # True
print(can_access("Engineering", chunk_metadata))  # False

Результат:

True
False

В реальной системе такая проверка выполняется в качестве фильтра во время извлечения данных, а не позже, когда ЯЗЫК уже имеет доступ к содержимому. Фрагмент, к просмотру которого у пользователя нет разрешения, никогда не должен дойти до момента формирования запроса. Без метаданных создание безопасных систем RAG крайне затруднено, поскольку к каждому фрагменту не привязано надежное сигналы, которые система могла бы использовать для сравнения с правами пользователя перед принятием решения об извлечении его данных.

Добавление метаданных во время загрузки

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

Возьмем PDF с правилами HR, который загружается в систему. Обычная процедура загрузки выполняет следующие шаги в последовательности:

  1. Загрузка документа.
  2. Извлечение текста.
  3. Разбиение содержимого на фрагменты.
  4. Прикрепить метаданные.
  5. Сгенерировать эмбеддинги.
  6. Записать фрагменты, эмбеддинги и метаданные в векторную базу данных.

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

from langchain_core.documents import Document

doc = Document(
    page_content="Employees receive 20 weeks of maternity leave.",
    metadata={
        "department": "HR",
        "region": "Europe",
        "source": "EU_HR_Policy.pdf"
    }
)

print(doc.page_content)
print(doc.metadata)

Результат:

Employees receive 20 weeks of maternity leave.
{'department': 'HR', 'region': 'Europe', 'source': 'EU_HR_Policy.pdf'}

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

def chunk_with_metadata(document, chunk_size=60):
    text = document.page_content
    chunks = [text[i:i+chunk_size] for i in range(0, len(text), chunk_size)]
    return [
        Document(page_content=chunk_text, metadata=document.metadata)
        for chunk_text in chunks
    ]

source_doc = Document(
    page_content="Employees receive 20 weeks of maternity leave. Eligibility begins after six months of employment.",
    metadata={"department": "HR", "region": "Europe", "source": "EU_HR_Policy.pdf"}
)
chunked_docs = chunk_with_metadata(source_doc)
for i, chunk in enumerate(chunked_docs, 1):
    print(f"Chunk {i}: {chunk.page_content!r}")
    print(f"  Metadata: {chunk.metadata}\n")

Результат:

Chunk 1: 'Employees receive 20 weeks of maternity leave. Eligibility b'
  Metadata: {'department': 'HR', 'region': 'Europe', 'source': 'EU_HR_Policy.pdf'}
Chunk 2: 'egins after six months of employment.'
  Metadata: {'department': 'HR', 'region': 'Europe', 'source': 'EU_HR_Policy.pdf'}

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

Позволение ЯЗЫКОВОЙ МОДЕЛИ ГЕНЕРИРОВАТЬ МЕТАДАННЫЕ

Современные системы RAG всё чаще используют ЯЗЫКОВЫЕ МОДЕЛИ для создания метаданных вместо полного использования ручного разметки. Вместо того чтобы человек вручную читал каждый документ и присваивал ему метки, модель может проанализировать документ и определить такие поля, как тема, отдел, категория, ключевые слова и краткое содержание. Приведённый ниже код демонстрирует запрос, который генерирует эти метаданные, а не только их результат:

def generate_metadata(text, llm):
    prompt = f"""Analyze the following document and return metadata as JSON with these fields:
topic, category, and keywords (a list of 3-5 relevant terms).

Document:
{text}
Respond with only the JSON, no other text."""
    response = llm.generate(prompt)
    return response

sample_text = "Employees are entitled to 20 weeks of paid maternity leave, with eligibility beginning after six months of continuous employment. Additional unpaid leave may be requested with manager approval."
# In practice, llm.generate() would call an actual model (Claude, GPT, etc.)
# Below is the kind of output this prompt is designed to produce:

example_output = {
    "topic": "Employee Benefits",
    "category": "HR Policy",
    "keywords": ["maternity leave", "eligibility", "employee benefits"]
}

print(example_output)

Результат:

{'topic': 'Employee Benefits', 'category': 'HR Policy', 'keywords': ['maternity leave', 'eligibility', 'employee benefits']}

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

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

Фильтрация метаданных и гибридный поиск

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

В строгом смысле гибридный поиск означает совместное использование двух методов поиска — плотного векторного поиска и разреженного поиска по ключевым словам (BM25) — с одновременной обработкой и объединением полученных результатов. Фильтрация метаданных — это что-то другое. Это не ещё один метод поиска, а способ сокращения списка кандидатов до начала или в процессе использования любого метода или комбинации методов, которые вы уже применяете.

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

Вместе они значительно повышают качество поиска, как показывает пример отпуска по беременности и родам. Система перестаёт бездумно искать во всей базе знаний и начинает интеллектуально искать в наиболее релевантной подмножестве, причём сходство определяет лишь порядок элементов в уже суженном и правильном наборе. Именно поэтому современные корпоративные решения типа RAG редко опираются исключительно на эмбеддинги: эмбеддинги определяют, что является похожим, но метаданные определяют, что вообще может рассматриваться.

Проигнорированный фактор

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

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

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

Вывод

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

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

Связанные материалы

  • Разбиение на чанки типа «родитель-дочерний» и иерархическое индексирование для улучшения поиска в RAG — Почему маленькие чанки помогают в поиске, но не дают правильного ответа, и как разбиение на чанки типа «родитель-дочерний», удаление дубликатов и иерархическое индексирование восстанавливают контекст при поиске в RAG.
  • Рецепт Spring AI: фильтрация результатов RAG с использованием метаданных по названию игры — Пошаговая инструкция Spring AI, предусматривающая присвоение документам названия, использование большой языковой модели для выбора подходящего значения из вопроса и фильтрацию векторного поиска на его основе.
  • Поиск в технической вики: BM25, защищённые окна токенов и метод RRF — Подход к поиску, основанный на оценке, для технической вики: цели с точными доказательствами, базовая модель BM25, ошибка обрезки в MiniLM, окна токенов и результаты слияния рангов по принципу взаимности.