RAG для AI-агентов: почему ваш retrieval работает неправильно
Представьте торговую систему, которая накапливает 700 typed markdown-мемо — решения, тупики, calibration notes, постмортемы «мы попробовали это, и оно провалилось». Каждое утро система переиндексирует свою память и начинает работу с запроса: «Мы уже тестировали mean-reversion filter на этом рынке?» Если честный ответ — «нет, никогда», но retrieval всё равно возвращает три наиболее похожих мемо (скорее всего про другой фильтр на другом рынке), агент видит уверенные результаты и делает вывод: «да, мы это изучили». Только что система приняла решение на основе галлюцинации.
Проблема не в плохом ранжировании. Топ-результаты могут быть действительно ближайшими мемо. Проблема в том, что у системы нет способа сказать: «здесь ничего нет».
Почему документный RAG и агентский RAG — разные задачи
Почти каждый туториал по RAG, который вы читали, решает одну и ту же задачу: у вас есть куча документов, пользователь задаёт вопрос, вы извлекаете чанки, которые отвечают на этот вопрос. Ранжируете правильный passage наверх, пихаете его в промпт, готово.
Есть второй тип RAG, который ведёт себя совершенно иначе, и почти никто о нём не пишет: агент, извлекающий из собственной памяти. В этом случае knowledge base — это не статичная коллекция документов, а растущая память самого агента. И как только вы смотрите на память таким образом, RAG-проблема переворачивается — и стандартный retrieval тихо делает неправильную вещь.
В документном QA есть несущее предположение, о котором вы probablemente никогда не думаете: ответ находится в корпусе. Кто-то задал вопрос, потому что документы могут на него ответить. Вся ваша работа — ранжирование. В self-recall самые важные запросы — это именно те, где ответа нет. Агент спрашивает «пробовали ли мы mean-reversion filter на этом рынке?» Если честный ответ — нет, никогда, ранжированный retriever всё равно радостно вернёт три наиболее cosine-similar мемо, и агент, видя уверенные результаты, заключит «да, мы это смотрели».
Три failure mode, которых нет в документном RAG
Как только вы начинаете смотреть на память как на собственную растущую базу знаний агента, проявляются три отчётливых режима отказа, с которыми документный RAG никогда не сталкивается.
Галлюцинация над пробелами. Запрос не имеет реального ответа в памяти, но retrieval возвращает ближайших соседей anyway, и их присутствие читается как «да». Системе нужно воздерживаться — помечать «это вероятно пробел» вместо притворства. Это не проблема качества embedding'а или reranker'а — лучший retriever просто возвращает более уверенно неправильные результаты быстрее.
Пересмотр решённых решений. Агент предлагает идею, которую он уже попробовал и убил три месяца назад, потому что мемо «мы решили против этого, вот почему» не всплыло в момент предложения. Память, которая не может защитить собственные прошлые решения, обречена их переживать. Это не ranking problem — это проблема темпоральной осведомлённости.
Действие на устаревшую память. Мемо, которое было истинно в апреле, извлекается и трактуется как текущее в июле. Без сигнала свежести старая истина и текущая истина неразличимы — а в системе, которая касается денег, это дорого.
Ни одна из этих проблем не является проблемой ранжирования. Вы не можете исправить их лучшим embedding model или более продвинутым reranker'ом, потому что лучший retriever просто возвращает более уверенно неправильные результаты быстрее.
Рефрейминг: abstention вместо ranking
Вот тезис, который стоит понять: документный RAG оптимизирует ранжирование. Агентский RAG должен оптимизировать calibrated abstention — знать, когда он не знает. Это имеет значение, потому что метрики, которые вас учили заботить — MRR, nDCG, precision@k — вообще не измеряют abstention. Они оценивают, насколько хорошо вы упорядочили результаты, предполагая, что ответ существует. Они молчат о случае, который важнее всего в self-recall: запросе без ответа, где правильное поведение — вернуть ничего и сказать так.
Вот почему достижение off-the-shelf RAG стека и направление его на память вашего агента выглядит хорошо в демо и гниёт в продакшене. Стек настроен на неправильную цель. Его никогда не спрашивали воздерживаться, поэтому он не воздерживается.
RE-call: reference implementation с abstention из коробки
Чтобы проработать это правильно, автор построил и open-sourced RE-call — retrieval engine, спроектированный вокруг abstention с самого начала, а не прикрученный после. Однопараграфная версия, которую остальная часть серии распаковывает: хранение и retrieval используют PostgreSQL + pgvector как единый транзакционный store — плотный векторный поиск и разреженный полнотекстовый поиск в одной базе данных, слитые с Reciprocal Rank Fusion, с опциональным cross-encoder reranker.
Три честных гарда защищают от failure modes. Gap warning срабатывает, когда лучший match слишком слаб, чтобы доверять. Freshness signal помечает устаревшую память. Anti-re-litigation check всплывает закрытые решения до того, как агент предложит их заново.
Честная оценка использует harness, который измеряет не только качество ранжирования, но и false-confident rate — как часто система не воздерживается, когда должна. Система поставляется как MCP server, так что агент (Claude или другой) может запрашивать собственную память напрямую.
Архитектура: почему Postgres вместо векторной базы
Вторая часть серии строит retrieval core: плотный плюс разреженный плюс fusion плюс reranking, всё внутри одной Postgres базы данных, с plug-in embedders — и аргумент за то, почему в 2026 году вам probablement не нужен отдельный vector store, чтобы делать это хорошо.
Автор не потянулся к dedicated vector database по нескольким причинам. Во-первых, pgvector достаточно зрелый для большинства use cases — вы не теряете в качестве поиска, но получаете транзакционную согласованность из коробки. Во-вторых, гибридный поиск (dense + sparse) в одной базе упрощает fusion — Reciprocal Rank Fusion работает естественно, когда оба индекса живут в одной транзакции. В-третьих, операционная сложность снижается: один backup, одна миграция, один мониторинг.
Cross-encoder reranker — опциональный, но полезный для случаев, когда вам нужно переупорядочить топ-k результатов после initial retrieval. Он добавляет латентность, но значительно улучшает точность, особенно когда начальный retrieval возвращает много полу-релевантных результатов.
Третья часть: обучение RAG говорить «я не знаю»
Три честных гарда — это ядро системы. Gap warning использует порог similarity score: если лучший match ниже порога, система возвращает результат с флагом «возможно пробел». Это не бинарное решение — это calibrated uncertainty. Freshness signal смотрит на timestamp мемо и применяет exponential decay: мемо старше N дней получает пониженный вес, если только оно явно помечено как evergreen. Anti-re-litigation check ищет паттерны «мы решили против X» в памяти и всплывает их, когда агент предлагает X снова.
Автор подчёркивает, что эти гарды не идеальны — они эвристики, а не формальная верификация. Но они значительно лучше, чем отсутствие гардов. И что важнее, они измеряемы: вы можете отследить, как часто каждый гард срабатывает, и настроить пороги на основе ваших данных.
Четвёртая часть: бенчмаркинг retrieval и честности
Eval harness измеряет не только MRR и nDCG, но и false-confident rate — как часто система возвращает результат с высокой уверенностью, когда должна была воздержаться. Это критическая метрика для агентских систем, потому что ложная уверенность дороже, чем ложный пробел. Агент, который думает, что знает, действует на основе галлюцинации. Агент, который знает, что не знает, может запросить дополнительную информацию или отказаться от действия.
Автор показывает, что стандартные retrieval бенчмарки молчат о false-confident rate. Вы можете иметь MRR 0.85 и при этом возвращать уверенно неправильные результаты в 30% случаев, когда ответа нет. Это неприемлемо для агентских систем, где каждое решение имеет последствия.
Пятая часть: порог abstention, который не трансферится
Одно из самых удивительных открытий серии: single hard-coded abstention threshold бесполезен при смене embedding models. Порог, который работал для OpenAI text-embedding-3-small, полностью ломался для Cohere embed-v3. Это не баг реализации — это фундаментальное свойство embedding space. Разные модели калибруют similarity scores по-разному, и универсального порога не существует.
Это означает, что вы не можете просто скопировать конфигурацию из продакшена в продакшен. Каждый раз, когда вы меняете embedding model, вы должны перекалибровать пороги abstention на ваших данных. Это операционная сложность, но это честная сложность — лучше знать, что вам нужно калибровать, чем притворяться, что универсальный порог работает.
Шестая часть: fine-tune, который ничего не дал
Последняя часть серии честно сообщает о negative result: fine-tuning попытка улучшить abstention не produjo никакого подъёма. Автор обучал модель предсказывать, когда retrieval должна воздержаться, но fine-tuned модель работала не лучше, чем эвристики. Это не означает, что fine-tuning бесполезен в принципе — это означает, что для этого конкретного use case простые правила работают так же хорошо, как обученная модель.
Автор всё равно deploy'ит систему как MCP server, потому что MCP (Model Context Protocol) позволяет агенту запрашивать память напрямую через стандартизированный интерфейс. Это закрывает loop между памятью и агентом: агент не нужен отдельный retrieval код, он просто вызывает MCP tool.
Почему это важно сейчас
AI-агенты становятся всё более распространёнными, и каждый агент накапливает память. Claude Code хранит CLAUDE.md с решениями и предпочтениями. Cursor хранит контекст проекта. AutoGPT хранит историю действий. Все эти системы используют какую-то форму retrieval для доступа к памяти, и все они сталкиваются с теми же тремя failure modes: галлюцинация над пробелами, пересмотр решённых решений, действие на устаревшую память.
Проблема в том, что большинство разработчиков используют стандартный RAG стек — Pinecone, Weaviate, LangChain retrieval chain — и предполагают, что он работает для агентской памяти. Он не работает. Эти стеки оптимизированы для документного QA, где ответ всегда есть. Для агентской памяти вам нужна система, которая умеет говорить «я не знаю».
RE-call — это не единственное решение, но это reference implementation, которая показывает, как правильно построить такую систему. Open-source, на Postgres, с честными гардами и измеряемой честностью. Вы можете использовать его как есть или как вдохновение для построения собственной системы. Главное — понять, что abstention не менее важен, чем retrieval.
Часто задаваемые вопросы
Чем агентский RAG отличается от обычного RAG?
Обычный RAG предполагает, что ответ находится в корпусе документов, и оптимизирует ранжирование. Агентский RAG работает с собственной растущей памятью агента, где самые важные запросы — те, где ответа нет. Вместо ранжирования он оптимизирует calibrated abstention — способность говорить «я не знаю».
Почему MRR и nDCG не подходят для оценки агентского RAG?
MRR и nDCG оценивают качество ранжирования, предполагая, что ответ существует. Они молчат о случае, когда ответа нет, но система возвращает результат с высокой уверенностью. Для агентских систем нужна дополнительная метрика — false-confident rate, которая измеряет, как часто система не воздерживается, когда должна.
Можно ли использовать Pinecone или Weaviate для агентской памяти?
Можно, но вам нужно добавить abstention mechanisms поверх. Эти векторные базы оптимизированы для retrieval, а не для честности. Вам нужно реализовать gap warning (порог similarity), freshness signal (temporal decay), и anti-re-litigation check (поиск закрытых решений). RE-call показывает, как это сделать на Postgres, но те же принципы применимы к любой векторной базе.
Что делать, если смена embedding model ломает пороги abstention?
Перекалибровать пороги на ваших данных. Это не баг — это фундаментальное свойство embedding space. Разные модели калибруют similarity scores по-разному, и универсального порога не существует. Каждый раз, когда вы меняете embedding model, вы должны заново настроить пороги abstention. Это операционная сложность, но она честная.
Итог
Стандартный RAG ломается, когда агент извлекает из собственной памяти, потому что он оптимизирован для ранжирования, а не для честности. Три failure mode — галлюцинация над пробелами, пересмотр решённых решений, действие на устаревшую память — требуют abstention mechanisms, а не лучшего reranker'а. RE-call показывает, как построить retrieval engine вокруг abstention: Postgres + pgvector, три честных гарда, и метрика false-confident rate вместо MRR. Если вы строите AI-агента с памятью, начните с вопроса «как система узнает, что она не знает?» — а не «как извлечь релевантные чанки?».