Збагачення метаданих у RAG: фільтри, цитування, свіжість та контроль доступу
Чому система RAG повертає політику неправильного регіону та як метадані джерела, організації, часу та безпеки виправляють процес пошуку при їх додаванні під час обробки даних.
Джерела: Metadata Enrichment in RAG: The Secret Ingredient for Better Retrieval
Припустимо, команда створила чат-бота для генерації тексту з підтримкою пошуку інформації для компанії. Було витрачено багато часу на вибір моделі ембеддингу, налаштування векторної бази даних та використання потужного LLM. Коли все було готово, хтось ввів запитання щодо політики відпустки по вагітності та пологах для співробітників європейського офісу.
Відповідь надійшла швидко: співробітники мають право на 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 з політикою кадрової служби, який завантажується. Звичайний потік обробки виконує ці кроки по черзі:
- Завантажити документ.
- Витягнути текст.
- Розділити вміст на фрагменти.
- Додайте метадані.
- Створіть ембеддинги.
- Запишіть частини, ембеддинги та метадані до векторної бази даних.
Додавання метаданих відбувається після розділення на частини, а не до нього, і порядок має значення. Метадані мають бути додані до кожної окремої частини, на яку розділено документ, а не один раз до всього документа в цілому.
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'}
Хоча ці два фрагменти є окремими частинами одного документа, кожен з них успадковує ідентичні значення регіону, відділу та джерела. Саме це дозволяє працювати наведеним вище прикладам фільтрації: незалежно від того, як був розділений текст, кожен фрагмент все одно може підтвердити, що є частиною європейської політики у сфері кадрів. Тепер кожен з них містить цінний контекст поруч із своїм вбудованим змістом завдяки процедурам пошуку, фільтрації та цитування.
Дозвіл LLM на створення метаданих
Сучасні системи RAG все частіше використовують LLM для створення метаданих, замість повної залежності від ручного позначення. Замість того, щоб людина читала кожен документ та вручну присвоювала йому мітки, модель може проаналізувати документ та визначити такі поля, як тема, відділ, категорія, ключові слова та короткий опис. Наведений нижче код демонструє запит, який це забезпечує, а не лише результат:
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 важливо не лише вибрати модель ембеддингів, а й визначити, які метадані слід додати до фрагментів, адже в багатьох реальних системах кращі метадані означають кращий пошук, кращий пошук — кращі відповіді, а кращі відповіді — кращий досвід для користувачів.
Пов’язана література
- Vectorless RAG: Як PageIndex читає документи без вбудованих даних — Пошук за векторами розділяє довгі документи на частини, що призводить до втрати контексту. Дізнайтеся, як індекс у вигляді дерева, заснований на логічних міркуваннях, знаходить відповіді у складних звітах, разом із кодом.
- Практичні нотатки: інженерія запитів для проектування систем ШІ: RAG, пам’ять, інструменти — Детальний огляд Практичних нотаток: інженерія запитів для проектування систем ШІ: RAG, пам’ять, інструменти: контракти, перевірки та готові фрагменти коду для команд, які впроваджують цю модель.