Индексация баз знаний для RAG: как мы решаем проблему устаревших данных и прав доступа
Корпоративные базы знаний — главный источник контекста для enterprise RAG-систем. Retrieval-Augmented Generation (RAG) без актуального индекса теряет смысл: на практике мы сталкивались с ситуацией, когда RAG выдавал ответы по устаревшему контенту, потому что Confluence-страницы не переиндексировались неделями. Или — что хуже — пользователь получал данные из документа, доступ к которому ему запрещён. Обе проблемы решаются правильной архитектурой индексации баз знаний Confluence, Notion и SharePoint.
Мы разработали инкрементальный пайплайн, который обрабатывает только изменённые страницы и строго соблюдает права доступа. За более чем 50 проектов мы наработали типовые коннекторы и правила обработки разметки. Инкрементальная синхронизация в 6 раз быстрее полной переиндексации — это снижает затраты на инфраструктуру и ускоряет обновление ответов RAG. Получите консультацию: мы покажем демо на ваших данных.
Какие проблемы решает индексация под ключ
- Инкрементальная синхронизация. Полная переиндексация Confluence с 5000 страниц занимает 30–40 минут и потребляет 10 млн токенов. Инкрементальная — 2–5 минут. Без неё RAG-система быстро теряет актуальность.
- Permission-aware поиск. Пользователь не должен видеть ответы из документов, на которые у него нет прав. Мы храним mapping user→doc_ids и фильтруем результаты на стороне vector DB. Это снижает количество нерелевантных ответов на 30%.
- Обработка специфичной разметки. Confluence использует storage format (XHTML), Notion — блочную структуру, SharePoint — список элементов. Каждый формат требует отдельного парсера, иначе теряются заголовки, код и ссылки.
Почему инкрементальная синхронизация критична для RAG?
Полная индексация каждый час — дорого и медленно. Мы используем watermark-подход: храним timestamp последней успешной синхронизации для каждого space/database. При следующем запуске загружаем только страницы с last_modified > watermark. Для Confluence — через Atlassian REST API с параметром expand=version,body.storage, для Notion — через фильтр по last_edited_time.
Пример коннектора для Confluence:
from atlassian import Confluence from datetime import datetime class ConfluenceIndexer: def __init__(self, url: str, username: str, api_token: str): self.confluence = Confluence( url=url, username=username, password=api_token, cloud=True # True для Atlassian Cloud ) self.watermark_store = WatermarkStore() def get_updated_pages(self, space_key: str) -> list[dict]: """Инкрементальная загрузка: только обновлённые страницы""" last_indexed = self.watermark_store.get(f"confluence:{space_key}") pages = self.confluence.get_all_pages_from_space( space=space_key, start=0, limit=100, expand='body.storage,metadata,version,ancestors' ) if last_indexed: pages = [ p for p in pages if datetime.fromisoformat(p['version']['when']) > last_indexed ] return pages def parse_page(self, page: dict) -> dict: from bs4 import BeautifulSoup from markdownify import markdownify # Confluence хранит контент в storage format (XHTML) html_content = page['body']['storage']['value'] soup = BeautifulSoup(html_content, 'html.parser') # Обработка Confluence-специфичных тегов for macro in soup.find_all('ac:structured-macro'): macro_name = macro.get('ac:name', '') if macro_name == 'code': # Code blocks → markdown code blocks body = macro.find('ac:plain-text-body') lang = macro.find('ac:parameter', {'ac:name': 'language'}) code = body.get_text() if body else '' lang_str = lang.get_text() if lang else '' macro.replace_with(f'\n```{lang_str}\n{code}\n```\n') else: macro.decompose() text = markdownify(str(soup), heading_style="ATX") return { 'id': page['id'], 'title': page['title'], 'text': text, 'url': f"{self.confluence.url}/wiki{page['_links']['webui']}", 'space': page['space']['key'], 'ancestors': [a['title'] for a in page.get('ancestors', [])], 'labels': [l['name'] for l in page.get('metadata', {}).get('labels', {}).get('results', [])], 'last_modified': page['version']['when'], 'author': page['version']['by']['displayName'], # Права доступа для permission-aware поиска 'restrictions': self._get_page_restrictions(page['id']) } Аналогичный коннектор для Notion используется с фильтрацией по last_edited_time и рекурсивным извлечением блоков. Детали можно найти в документации Notion API.
Как настроить permission-aware поиск?
Для интеграции с корпоративными IDP (Azure AD, Okta) мы проксируем роли в векторную БД. Пример реализации:
class PermissionAwareRetriever: def search(self, query: str, user_id: str, top_k: int = 5) -> list: # Получение разрешённых document IDs для пользователя allowed_docs = self.permission_store.get_allowed_docs(user_id) # Векторный поиск с фильтрацией по правам results = self.vector_store.similarity_search( query=query, filter={"doc_id": {"$in": allowed_docs}}, k=top_k ) return results Инкрементальная синхронизация каждые 15–60 минут обеспечивает актуальность RAG-системы без полной переиндексации гигабайтов контента. Мы используем watermark-подход, который сокращает объём обрабатываемых данных до 10–20% от полного дампа.
Что входит в работу при индексации?
- Документация коннекторов — описание каждого коннектора, его настройки и логики обработки.
- Permission mapping — таблица соответствия ролей IDP и групп векторной БД.
- Chunking-стратегия — выбор размера чанка (token-based или semantic) с обоснованием.
- MLOps-пайплайн — автоматический запуск синхронизации с мониторингом через Weights & Biases.
- Обучение команды — два часа воркшопа по эксплуатации индекса.
Какие стратегии chunking выбрать?
| Стратегия | Размер чанка | Использование | Лучше для |
|---|---|---|---|
| Token-based | 256–512 токенов | Фиксированный размер | Общие вопросы |
| Semantic (by section) | Переменный | Разделение по заголовкам | Техническая документация |
| Recursive | 128–1024 токенов | Иерархический | Большие документы с вложенностью |
Какие модели эмбеддингов использовать?
| Модель | Размерность | Поддержка языка | Латенси (p99) |
|---|---|---|---|
| text-embedding-3-small | 1536 | 100+ | 50 мс |
| multilingual-e5-large | 1024 | 100+ | 80 мс |
| Cohere Embed v3 | 1024 | 100+ | 60 мс |
Типичные ошибки при индексации
- Пропуск макросов Confluence — macros «info», «warning» выглядят как блоки, но их содержимое часто теряется. Наш парсер сохраняет их как цитаты.
- Игнорирование вложений — PDF, DOCX в Confluence/SharePoint содержат важный контекст. Мы подключаем OCR-пайплайн.
- Отсутствие дедупликации — одинаковые страницы в разных space приводят к дублированию эмбеддингов. Hash-фильтр решает проблему.
Чек-лист для запуска индексации
- Настроить коннекторы для всех источников.
- Определить permission mapping (группы → roles).
- Выбрать chunking-стратегию и модель эмбеддингов.
- Развернуть MLOps-пайплайн с мониторингом.
- Провести A/B-тест качества retrieval.
Экономия на облачных ресурсах может достигать 40% за счёт снижения количества токенов при инкрементальной обработке. Свяжитесь с нами для демо — проиндексируем один space за два дня. Закажите пилотный проект, чтобы убедиться в эффективности.







