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

Почему RAG отвечает мимо вопроса
RAG промахивается не потому, что модель "глупая". Ошибка обычно начинается раньше: поиск находит куски текста, похожие на вопрос по словам, но не по смыслу. Человек спрашивает про действующее правило, а retriever приносит все документы, где встретились те же термины.
Во внутренних базах это обычная история. В одном индексе лежат регламенты, черновики, выгрузки, шаблоны писем и заметки команды. Поиск цепляется за совпадения в тексте и поднимает их рядом, хотя ценность у таких файлов разная.
Чаще всего мешают старые версии. Сотрудник спрашивает, как сейчас согласовывать договор, а система достает инструкцию прошлого года вместе с новым регламентом. Оба документа выглядят релевантно, потому что тема одна. Для модели это уже конфликт: она видит два похожих ответа и не всегда понимает, какой действует сейчас.
Во внутреннем помощнике банка, ритейла или SaaS это быстро становится заметно. Человек задает простой вопрос про лимит, срок или порядок согласования. Вместо одного точного фрагмента поиск приносит пять кусков: старую политику, новый приказ, технический шаблон, комментарий юриста и служебный файл с похожим названием. Дальше модель делает то, что умеет лучше всего: собирает из контекста связный текст.
Проблема в том, что связный текст не равен правильному ответу. Если в контексте много лишнего, модель усредняет смысл, переносит детали из одного документа в другой и отвечает уверенно даже там, где источники спорят между собой. Снаружи это выглядит правдоподобно. Внутри ответа уже может быть смесь несовместимых правил.
Вот здесь метаданные перестают быть второстепенной деталью. Без них поиск не отличает свежий документ от архива, рабочий регламент от служебного мусора, общий текст от материалов для узкой группы. Пока retriever не научится отсекать этот шум, полнота поиска падает в самом неприятном месте: система как будто находит много всего, но не находит именно то, что нужно человеку.
Какие метаданные хранить в индексе
Индекс без нормальных метаданных быстро превращается в склад текстов. Поиск цепляет похожие слова, но не тот смысл. Поэтому в индекс стоит класть только те поля, которые реально помогают сузить выборку без лишних потерь.
Первая важная пара - дата публикации и дата вступления в силу. Это не одно и то же. Политику могли утвердить в январе, а применять с марта. Если система не видит разницы, она легко отдаст устаревший ответ как актуальный.
Дальше нужен понятный тип документа. Лучше сразу задать короткий закрытый набор значений: политика, инструкция, договор, тикет. Когда типы расползаются до "внутренний регламент", "регламент 2", "рабочий документ" и десятков похожих вариантов, фильтры начинают шуметь почти так же сильно, как и при полном отсутствии фильтра.
Уровень доступа тоже должен храниться рядом с документом, а не добавляться потом. Для внутреннего помощника это базовое требование: сотрудник может задать нормальный вопрос, но система не должна даже рассматривать документы, которые ему нельзя читать. Рядом полезно держать и команду-владельца. Если текстом управляют юристы, финансы или HR, это помогает и в поиске, и в разборе спорных ответов.
Еще одно полезное поле - статус версии. Черновик, действует, архив звучит скучно, но именно это поле часто влияет на качество сильнее, чем сложный ранжировщик. Если в индексе лежат три редакции одной инструкции, модель должна понимать, какая версия рабочая, а какая нужна только для истории.
Источник тоже важен, когда один и тот же факт живет в нескольких системах. Срок оплаты может встречаться в договоре, в CRM и в тикете согласования. Если хранить source_system и source_id, проще убирать дубли, понимать происхождение ответа и потом не спорить с пользователем на уровне "поиск так нашел".
Хорошее правило простое: каждое поле должно отвечать на один вопрос. Документ уже действует? К какому типу он относится? Кому он доступен? Кто за него отвечает? Откуда он пришел? Если поле не помогает принять одно из этих решений, в индексе оно обычно только мешает.
Когда дата помогает, а когда мешает
Дата хорошо работает там, где ответ быстро устаревает. Это вопросы про тарифы, лимиты, дедлайны, правила оплаты, сроки подачи заявки. Если человек спрашивает "какой лимит действует сейчас" или "до какого числа можно отправить отчет", старый документ почти всегда только мешает.
Но дата не всегда улучшает поиск. Многие команды сразу режут выдачу окном в 30 дней и ждут более точного ответа. На деле полнота поиска часто падает. Политика безопасности, договор, инструкция по онбордингу или регламент могут не меняться месяцами. Жесткий фильтр просто убирает нужный текст.
Сначала лучше отделить действующие версии от архива. Это полезнее, чем грубый фильтр по свежести. Если в одном наборе лежат и текущие, и старые редакции, модель легко возьмет старый абзац только потому, что формулировка там ближе к вопросу. Намного надежнее сначала искать среди действующих документов, а архив подключать только в двух случаях: если вопрос явно про историю или если в текущем наборе ничего нет.
Для этого мало поля с датой создания. Нужна отдельная дата действия. Документ могли создать 12 марта, утвердить 20 марта, а в силу он мог вступить 1 апреля. Если смотреть только на created_at, поиск даст странный результат.
Обычно хватает четырех полей:
status:activeилиarchivevalid_from: дата начала действияvalid_to: дата окончания, если она естьcreated_at: техническая дата создания
Разница хорошо видна на простом примере. Финансовый отдел обновил лимиты по командировкам, но файл загрузили в систему за неделю до старта новых правил. Пользователь спрашивает про лимит на завтра. По дате создания документ уже "свежий", но опираться на него еще рано. По дате действия - уже можно.
Если запрос не требует строгой актуальности, не ставьте жесткий фильтр. Лучше понизить вес старых документов в ранжировании и оставить им шанс попасть в выдачу. Такой подход чаще дает честный результат: модель видит текущую версию первой, но не теряет редкий и все еще актуальный документ, который лежит в базе полгода.
Дата в индекс стоит добавлять почти всегда. Жестко фильтровать по ней стоит только там, где ошибка в сроке или лимите сразу бьет по делу.
Как тип документа меняет выдачу
Один и тот же вопрос может давать совсем разный результат, если поиск понимает тип документа. Ему мало знать тему текста. Ему полезно понимать, что перед ним: регламент, инструкция, лог, тикет или переписка поддержки.
Разница заметна сразу. Если сотрудник спрашивает, можно ли отправлять клиентские данные во внешний сервис, лучше поднять регламент и политику, а не чат с поддержкой, где кто-то однажды дал частный совет. Чат может звучать убедительно, но для такого вопроса он только уводит ответ в сторону.
Обратная ситуация тоже встречается постоянно. Когда команда разбирает сбой после релиза, инструкции помогают мало. Нужны логи, тикеты и заметки по инциденту. Если поиск смотрит только в базу знаний, ответ получится гладким, но бесполезным: он опишет, как система должна работать, а не что реально сломалось.
В этом и польза типа документа: он помогает выбрать не просто похожий текст, а подходящий источник. Для одних запросов тип лучше использовать как жесткий фильтр. Для других - как сигнал в ранжировании, чтобы не отрезать нужные фрагменты слишком рано.
Список типов стоит держать коротким. Обычно хватает 5-8 понятных категорий: политика или регламент, инструкция, тикет, лог или событие, переписка поддержки. Если категорий слишком много, команда быстро запутается. Тогда один и тот же файл попадает то в "регламент", то в "правило", а поиск начинает вести себя случайно. Такие близкие названия почти всегда вредят. Лучше выбрать одно слово и договориться, что оно значит.
Есть простое правило: типы должны отражать не структуру папок, а сценарий чтения. Человек обычно ищет либо норму, либо шаги, либо следы ошибки, либо прошлое решение. Под это и стоит строить категории.
На практике полезно проверить несколько типичных запросов вручную. Возьмите вопрос про политику доступа, потом вопрос про падение сервиса, и посмотрите, какие типы документов всплывают в первой десятке. Если в первом случае наверху тикеты, а во втором только инструкции, типы заданы плохо или поиск использует их не там, где нужно.
Если сомневаетесь, начните с мягкого правила: добавьте тип документа как буст в ранжировании, а не как жесткий отсев. Когда увидите, что намерение запроса определяется стабильно, можно включать строгий фильтр для части сценариев.
Как учитывать доступ пользователя
Права доступа нужно проверять раньше, чем модель увидит контекст. На практике именно здесь метаданные часто дают самый заметный эффект. Они не улучшают стиль ответа. Они не дают системе вытащить лишнее.
Фильтр по доступу ставьте сразу после поиска или прямо внутри запроса к индексу. Если поиск нашел 20 кусков, а пользователь имеет право видеть только 12, в модель должны уйти только эти 12. Если после фильтра не осталось ничего, лучше честно сказать, что доступных данных не хватает, чем собирать ответ из чужих документов.
Проверка на уровне файла часто не спасает. Один и тот же документ нередко читают разные роли по-разному: общую часть видит вся команда, приложение к договору - только юристы, таблицу с лимитами - только руководители. В таком случае права нужно хранить у каждого куска, а не только у файла целиком. Иначе поиск вернет "разрешенный" файл, но вместе с ним протащит закрытый фрагмент.
Если модель уже получила закрытый кусок, вы проиграли. Не стоит надеяться на инструкцию вроде "не используй секретные данные в ответе". Модель все равно может смешать факты из закрытого и открытого текста и потом выдать их в нейтральной формулировке без явной цитаты. Лучше потерять один сильный фрагмент, чем получить утечку в прилично звучащем ответе.
Что писать в лог
Лог нужен не для галочки. Без него невозможно понять, почему ответ оказался коротким или почему система "не нашла" документ, который точно есть в базе.
Обычно хватает четырех полей:
- кто сделал запрос и с какой ролью
- какой фильтр доступа сработал
- сколько кусков он отрезал
- какие источники остались после фильтра
Простой пример: сотрудник банка спрашивает у внутреннего помощника про лимиты на одобрение кредита. Поиск находит и регламент отдела, и служебное приложение для руководителей. Если помощник отправит в модель оба куска, ответ получится полнее, но уже с лишними деталями. Если он сначала отрежет закрытое приложение, ответ станет короче, зато безопаснее.
Если команда запускает такого помощника через AI Router, audit-логи помогают потом проверить, какой набор документов попал в контекст и что именно отрезал фильтр. Это заметно упрощает разбор спорных ответов и поиск мест, где полнота падает уже не из-за retriever, а из-за прав доступа.
Как настроить фильтры пошагово
Фильтры редко стоит включать "по умолчанию" для всех запросов. Если задать их слишком широко, ответ останется шумным. Если зажать слишком сильно, полнота поиска быстро просядет, и модель начнет отвечать по случайным обрывкам.
Нормальная настройка начинается не с индекса, а с живых вопросов. Возьмите 20-30 запросов, которые люди действительно задают: по регламентам, договорам, инструкциям, старым решениям, правам доступа. Лучше смешать простые и спорные случаи, где система уже ошибалась.
Для каждого вопроса зафиксируйте три вещи: нужна ли свежая версия документа, какой тип источника подходит и должен ли ответ зависеть от роли пользователя. Это звучит скучно, но именно такой список быстро показывает, где фильтры помогают, а где только режут поиск.
Дальше идите по одному шагу за раз:
- сначала прогоните все вопросы без фильтров и сохраните top-k результаты;
- потом включите только фильтр по дате и проверьте, где выдача стала лучше, а где исчезли нужные документы;
- затем отдельно протестируйте тип документа;
- после этого добавьте роль или уровень доступа пользователя;
- в конце сравните не только первый релевантный документ, но и весь top-k.
Смотреть на одну метрику мало. Нужны как минимум две: насколько чаще система находит нужный документ и насколько часто фильтр скрывает его полностью. Если после фильтра ответ стал аккуратнее в пяти вопросах, но в восьми документ вообще исчез, такой фильтр мешает.
Хороший пример - дата. Для вопроса про текущую редакцию политики она полезна. Для вопроса "почему в прошлом квартале согласовали такой процесс" фильтр по свежести уже портит результат, потому что убирает исторические версии.
Оставляйте фильтр только там, где он дает заметный выигрыш на реальных запросах. Нередко лучший вариант очень простой: доступ пользователя проверять всегда, тип документа включать только для части сценариев, а дату применять лишь к вопросам про актуальные правила и тарифы.
Пример для внутреннего помощника
Сотрудник банка спрашивает: "Какой лимит сейчас действует по продукту X для новых клиентов?" Вопрос простой, но без фильтров поиск часто тянет не тот фрагмент. В старом приказе есть те же слова, что и в новом, и система берет его просто потому, что текст похож.
Проблема усиливается, если в индексе рядом лежат приказы, письма, заметки после встреч и черновики. Тогда в выдачу легко попадает почтовая переписка, где команда обсуждала возможное изменение лимита. Для поиска это выглядит похоже на полезный документ. Для ответа пользователю это шум.
Отдельный риск дает доступ. Один и тот же вопрос могут задать сотрудник продаж и специалист по рискам. Если система не проверяет роль пользователя, модель может увидеть памятку для другой команды и пересказать ее как общий порядок. Ответ выглядит убедительно, но путает человека и нарушает внутренние правила.
В таком сценарии обычно хватает трех фильтров:
- дата действия или дата публикации;
- тип документа;
- роль пользователя или группа доступа.
После этого поиск меняется заметно. Помощник сначала отбирает свежие документы, потом оставляет только регламенты и приказы, а затем смотрит, что именно можно показывать этому сотруднику. Почтовые обсуждения и старые версии никуда не исчезают, но больше не лезут в ответ первыми.
До фильтров модель может выдать длинный абзац, где смешаны прошлогодний лимит, спор из переписки и памятка для соседней команды. Пользователь читает такой ответ и все равно идет перепроверять вручную.
После трех фильтров ответ обычно короче и чище: "Для новых клиентов по продукту X действует лимит 3 млн тенге. Основание - приказ от 12 февраля 2025 года". Один факт, один подходящий документ, без лишнего мусора.
Именно так метаданные приносят пользу на практике. Они не украшают поиск. Они убирают документы, которые похожи по словам, но не подходят по времени, типу или правам доступа.
Ошибки, из-за которых падает recall
Чаще всего поиск ломают не эмбеддинги, а грязные фильтры. Если метаданные неточные или слишком строгие, система просто не видит нужные куски текста и отвечает по тому, что осталось.
Одна из частых ошибок - фильтр по дате создания вместо даты действия документа. Для приказа, тарифа или политики это разные вещи. Файл могли загрузить в базу в марте, а сам документ действует с января. Если пользователь спрашивает про правила на февраль, фильтр по дате загрузки легко вырежет правильный ответ.
Не меньше проблем дает тип документа. В одной системе документ помечен как "регламент", в другой как "политика", в третьей как "внутренний акт". Для человека это почти одно и то же. Для фильтра это три разных значения. В итоге поиск идет по узкой части индекса и теряет полезные фрагменты.
Есть и более неприятный сбой: права доступа хранят только на уровне файла. Потом файл режут на чанки, но права на каждый чанк не переносят. После этого система либо показывает лишнее, либо, что бывает чаще, отбрасывает безопасные фрагменты, потому что не может понять, кому их можно выдать.
Обычно recall падает по пяти причинам:
- команда фильтрует по не той дате;
- один и тот же тип документа записан по-разному;
- чанки теряют права доступа после разбиения;
- строгие фильтры включены для всех запросов подряд;
- пустые поля и ошибки в метаданных никто не ловит.
Самая дорогая привычка - применять жесткие фильтры всегда. Если человек ищет "правила отпуска", ему не нужен сразу фильтр по отделу, статусу документа, стране, дате и роли. Сначала лучше найти широкий набор кандидатов, а потом сужать выдачу там, где это действительно нужно. Иначе система режет полноту раньше времени.
Еще одна тихая проблема - пустые и ошибочные поля. Если у части документов тип не заполнен, а у части дата записана в другом формате, фильтр начинает работать случайно. Поэтому перед запуском полезно сделать простую проверку: сколько чанков без даты, сколько без типа, сколько с неизвестным уровнем доступа. Один такой отчет часто объясняет, почему поиск иногда работает, а иногда нет.
Хороший фильтр не просто ограничивает выдачу. Он убирает шум и не выбрасывает нужные документы.
Быстрая проверка перед запуском
Если RAG уже неплохо отвечает на тестах, перед запуском стоит проверить не красоту демо, а самые скучные вещи. Обычно именно они решают, получит человек свежий регламент или старую версию, увидит закрытый документ или пустой ответ.
Быстрый тест выглядит просто: возьмите один вопрос, где есть новая политика, старая политика и документ с ограниченным доступом. Потом посмотрите, что система положила в top-k и почему. Если команда не может объяснить это за пару минут, фильтры еще сырые.
У каждого куска в индексе должны быть хотя бы три понятных поля: дата, тип документа и уровень доступа. Без этого метаданные быстро превращаются в шум. Система ищет по тексту, но не понимает, что приказ за прошлый год и черновик без согласования нельзя ставить рядом с действующей инструкцией.
Перед запуском полезно пройти короткий список:
- проверить, что дата есть у каждого чанка, а не только у файла целиком;
- убедиться, что тип документа задан единообразно;
- сравнить выдачу для свежих вопросов, архивных запросов и смешанных случаев;
- посмотреть логи по каждому промаху и понять, какой фильтр отсек нужный документ;
- договориться, кто и когда может ослабить фильтр вручную.
Особенно часто ломается доступ. Сотрудник спрашивает шаблон договора, нужный документ есть в базе, но фильтр доступа режет его слишком рано. Тогда система берет менее подходящий открытый файл и отвечает уверенно, но мимо. Такой промах хуже честного "не нашел".
Если вы используете общий API-шлюз и храните аудит-логи, разбор сильно упрощается. Например, в AI Router можно проверить не только итоговый ответ, но и путь до него: какие куски вошли в поиск, какие отсеялись и где именно просел recall.
Последняя проверка совсем простая: команда должна знать, в каких случаях фильтр можно ослабить, а в каких нельзя. Дату иногда можно расширить. Доступ пользователя - нет.
Что делать дальше
Если вы только настраиваете метаданные, не пытайтесь сразу покрыть все случаи. Для первого прохода хватит трех полей: дата, тип документа и доступ пользователя. Этого обычно достаточно, чтобы убрать старые версии, не смешивать политики с черновиками и не показывать закрытые материалы тем, кому они не положены.
Потом выберите один узкий сценарий и проверьте фильтры только на нем. Например, помощник отвечает на вопросы по отпускам и командировкам. Такой тест быстро показывает, где фильтр помогает, а где режет полноту и прячет нужный фрагмент.
Не смотрите только на финальный ответ. Сначала проверьте саму выдачу до генерации. Полезно сравнить поиск без фильтра и с фильтром на одном и том же наборе вопросов:
- сколько релевантных фрагментов система нашла в обоих режимах;
- как часто свежий документ оказался выше старого, когда это действительно нужно;
- сколько полезных результатов пропало из-за типа документа;
- были ли случаи, когда доступ пользователя скрыл единственный правильный источник.
Когда базовая схема уже работает, добавьте простые оценки. Отмечайте свежесть источника, ошибки по правам и пропуски в выдаче. Если ответ звучит уверенно, но retrieval не принес нужный документ, проблема не в модели. Обычно причина в слишком жестком фильтре или в плохих метках.
Хорошая практика - держать короткий набор контрольных вопросов, примерно 20-30 штук. Этого достаточно. После каждого изменения фильтров прогоняйте его заново и смотрите, что стало лучше, а что сломалось. Так команда видит реальный эффект, а не спорит по ощущениям.
Если вы сравниваете несколько моделей на одном наборе вопросов, единый шлюз вроде AI Router тоже экономит время. Можно отправлять одинаковые запросы через один OpenAI-совместимый эндпоинт, не переписывать интеграцию и потом разбирать audit-логи по тем же кейсам.
Начните с малого, измеряйте каждый шаг и не усложняйте схему раньше времени. Три поля, один сценарий, короткий тестовый набор. Для первого цикла этого вполне хватает.