Ваша RAG-система врёт. И знаете почему? Она не может выбрать между правдой вчерашней и правдой сегодняшней
Представьте себе: у вас есть база знаний компании. Вчера клиент написал в поддержку — его запрос зафиксировали в SQL-таблице. Сегодня проблема решилась — статус обновили в той же таблице. А ваш RAG-пайплайн продолжает возвращать ответы на основе вчерашнего чанка, который когда-то завекторизировали и забыли.
Это не баг. Это системная проблема архитектуры, которую почему-то все игнорируют. Векторные базы данных отлично хранят семантические связи, но понятия не имеют о времени. SQL-базы знают, что данные изменились, но не умеют об этом кричать векторному поиску.
Реальная история из продакшена: Система технической поддержки на RAG. Клиент спрашивает "Статус моей заявки #12345". В SQL статус уже "Решено", но RAG возвращает ответ на основе старого вектора с текстом "Заявка находится в обработке". Потому что семантически этот чанк ближе к запросу. Катастрофа доверия.
Почему это происходит? Механика конфликта
Когда вы строите RAG-систему с гибридным поиском (векторный + SQL), вы фактически создаёте двух независимых свидетелей. Один — векторная БД — помнит всё, что было сказано когда-либо. Другой — SQL — знает, что происходит прямо сейчас.
При запросе система:
- Ищет в векторах семантически близкие чанки
- Параллельно опрашивает SQL на предмет актуального состояния
- Собирает всё в контекст и отправляет в LLM
- Надеется, что модель сама разберётся, чему верить
А вот надеяться не на что. Современные LLM вроде GPT-4o (актуально на февраль 2026) или Claude 3.5 Sonnet не имеют встроенного детектора свежести. Они видят два противоречащих факта и выбирают тот, который лучше вписывается в контекст. Часто — старый.
Три подхода, которые НЕ работают (и один, который сломает вам систему)
Перед тем как показать работающее решение, давайте пройдёмся по популярным антипаттернам:
1. Довериться реранкерам
"Давайте поставим реранкер, который будет оценивать свежесть!" — звучит логично. Пока вы не понимаете, что реранкеры в 2026 всё ещё работают на семантике, а не на временных метках. Cohere Rerank, BGE Reranker — все они ищут релевантность, а не актуальность.
2. Добавить временные метки в промпт
"Система: Учитывай, что данные из SQL свежие, а из векторной БД могут быть устаревшими". LLM кивает и делает ровно то, что делала раньше. Потому что промпт — не программа, а вежливая просьба.
3. Полностью отказаться от векторов для изменяемых данных
Радикально, но глупо. Вы теряете главное преимущество RAG — семантический поиск. Теперь находить можно только по точным совпадениям в SQL. Прощай, естественный язык.
4. Hard delete устаревших векторов (осторожно!)
Удалить чанк из векторной БД при изменении данных в SQL. Теоретически работает. Практически — вы теряете историю. А что если пользователь спросит "А какое было описание товара до обновления?" Или "Покажи мне все версии документа?". Упс.
Кстати, если думаете, что ваша проблема уникальна — почитайте обзор свежих исследований RAG. Там GraphRAG пытается решать похожие проблемы через связи между сущностями, но временное измерение всё равно хромает.
Рабочее решение: TTL Gating + Metadata Priority Routing
Вот архитектура, которая реально работает в продакшене. Суть в том, чтобы не выбирать между SQL и векторами, а управлять их взаимодействием через временные шлюзы.
1 Добавляем временные метаданные ко всему
Каждый чанк в векторной БД должен иметь:
{
"chunk_id": "doc_123_chunk_5",
"source_id": "doc_123",
"created_at": "2026-02-13T10:30:00Z",
"updated_at": "2026-02-13T10:30:00Z",
"ttl_hours": 24,
"is_mutable": true,
"sql_check_required": true
}
Ключевые поля:
- ttl_hours — через сколько часов чанк считается "подозрительным" на устаревание
- is_mutable — могут ли данные меняться со временем (статусы, цены, описания)
- sql_check_required — нужно ли проверять свежесть в SQL при попадании в топ результатов
2 Строим двухэтапный пайплайн поиска
Вместо параллельного запроса к SQL и векторам — последовательный с приоритетами:
async def hybrid_search_with_freshness(query: str, user_id: str):
# Этап 1: Векторный поиск с фильтрацией по времени
vector_results = await vector_db.search(
query=query,
filter={
"$or": [
{"is_mutable": False}, # Неизменяемые данные всегда ок
{
"is_mutable": True,
"updated_at": {"$gte": datetime.now() - timedelta(hours=24)}
}
]
},
limit=20
)
# Этап 2: Для "старых" изменяемых чанков — проверка в SQL
fresh_data_from_sql = {}
for chunk in vector_results:
if chunk["is_mutable"] and chunk["ttl_expired"]:
sql_data = await sql_check_freshness(chunk["source_id"])
if sql_data["is_fresher"]:
# Помечаем векторный результат как deprecated
chunk["deprecated_by_sql"] = True
fresh_data_from_sql[chunk["source_id"]] = sql_data
# Этап 3: Сбор финального контекста с приоритетом SQL
context_parts = []
# Сначала свежие SQL-данные
for sql_item in fresh_data_from_sql.values():
context_parts.append(f"[СВЕЖИЕ ДАННЫЕ из SQL]: {sql_item['content']}")
# Затем не устаревшие векторы
for chunk in vector_results:
if not chunk.get("deprecated_by_sql", False):
context_parts.append(f"[ИЗ ВЕКТОРНОЙ БАЗЫ]: {chunk['content']}")
return await llm.generate(context="\n\n".join(context_parts), query=query)
3 Реализуем фоновую синхронизацию
TTL gating — это хорошо, но ещё лучше предотвращать конфликты заранее:
# Фоновая задача, которая обновляет векторы при изменении SQL
async def background_vector_sync():
while True:
# Находим документы, изменённые в SQL за последний час
changed_docs = await sql_db.query("""
SELECT doc_id, updated_at
FROM documents
WHERE updated_at > :last_sync
AND vector_sync_required = true
""", {"last_sync": last_sync_time})
for doc in changed_docs:
# Реиндексируем чанки с новыми данными
new_chunks = await chunker.process(doc["content"])
await vector_db.upsert(
vectors=embed(new_chunks),
metadata=[{
**chunk.metadata,
"updated_at": doc["updated_at"],
"sql_check_required": False # Теперь свежий!
} for chunk in new_chunks]
)
await asyncio.sleep(300) # Каждые 5 минут
Типичные ошибки, которые всё испортят
Даже с правильной архитектурой можно наступить на грабли:
| Ошибка | Последствие | Как исправить |
|---|---|---|
| Единый TTL для всех типов данных | Новости устаревают за часы, документация — за годы | Динамический TTL на основе типа контента |
| Игнорирование часовых поясов | В 23:59 данные свежие, в 00:01 — уже устаревшие | UTC для всех меток времени, конвертация на клиенте |
| Слишком агрессивная синхронизация | Каждое изменение в SQL вызывает реиндексирование | Дебаунсинг обновлений (например, раз в 15 минут) |
| Отсутствие fallback-стратегии | При падении SQL система не возвращает ничего | Кэширование последних успешных SQL-ответов |
А что насчёт мультимодальности? Там всё ещё хуже
Добавьте к этой проблеме изображения, аудио, видео — и получите настоящий ад. Представьте: в SQL обновилось описание товара, но связанное с ним изображение в векторной БД осталось старым. Или транскрипт видео обновили, а эмбеддинги аудио — нет.
Для мультимодальных систем нужна cross-modal синхронизация. Если меняется текстовое описание, нужно пересчитывать эмбеддинги связанных медиа. В 2026 году это всё ещё экспериментальная область, но некоторые фреймворки начинают предлагать решения. Посмотрите мультимодальный RAG в 2025 — там есть наработки по синхронизации модальностей.
Практический стек на 2026 год
Что реально работает сегодня (февраль 2026):
- Векторные БД с TTL поддержкой: Pinecone с expiration policies, Weaviate с auto-schema для временных меток, Qdrant с фильтрацией по datetime
- Оркестраторы пайплайнов: Haystack 2.0 с кастомными компонентами для freshness routing, LlamaIndex с hybrid search nodes
- Мониторинг: Phoenix для трассировки, какой источник данных использовался для ответа
- LLM: GPT-4o с system prompt, явно описывающим приоритет источников, или открытые модели с fine-tuning на конфликтных сценариях
Не доверяйте "умным" системам, которые обещают автоматическое разрешение конфликтов. В 2026 году все они либо переоценивают векторы, либо недооценивают SQL. Нужна явная, прозрачная логика приоритизации.
Что будет дальше? Прогноз на 2027
Проблема свежести данных в RAG станет только острее. С появлением real-time систем и agentic RAG (где агенты сами обновляют знания), конфликт источников будет возникать чаще.
Мои прогнозы:
- Появятся специализированные "временные" векторные БД с встроенной поддержкой versioning чанков
- LLM начнут получать доступ к metadata filters напрямую, без промежуточного слоя оркестрации
- Стандартом станет multi-source confidence scoring — каждый источник данных будет иметь оценку достоверности и свежести
- Мы увидим первые реализации Dreaming Engine для автоматического разрешения конфликтов на основе графа знаний
А пока что — внедряйте TTL gating, явные метаданные и двухэтапные пайплайны. Ваши пользователи не должны страдать от того, что ваша система не может отличить вчера от сегодня.
P.S. Если думаете, что ваша RAG-система не страдает от этой проблемы — задайте ей вопрос о чём-то, что менялось в последние 24 часа. И приготовьтесь к неловкому молчанию.