---
title: "RAG против карты и grep: слепое сравнение на своей базе из 2000 заметок"
canonical_url: https://evstygney.ru/articles/rag-vs-map-grep-blind-test/
date_published: 2026-08-20
date_modified: 2026-08-20
author: Егор (https://evstygney.ru/about/)
summary: "Слепое сравнение RAG (bge-m3 + sqlite-vec) с картой воркспейса и grep на 24 замороженных вопросах: ничья 9:9 и разворот по типам вопросов."
tags: ["ИИ и инструменты"]
---

# RAG против карты и grep: слепое сравнение на своей базе из 2000 заметок
**TL;DR.** В прошлой статье я отмахнулся от RAG для личной базы знаний: «пушка по воробьям». Потом собрал его руками и измерил. Локальная модель bge-m3, sqlite-vec, 29 295 чанков, 18 часов индексации на процессоре, поисковая тула для агента. Дальше — слепое сравнение с моей текущей навигацией (карта `main.md` + grep) на 24 вопросах, два режима, одинаковый лимит шагов, обезличенные пары и судья, который не знал, чей ответ читает. Итог — ничья 9:9. Внутри ничьей разворот: точечные вопросы семантический поиск берёт 7:1 и вдвое дешевле по шагам, обзорные проигрывает 8:2. Точный тест Фишера на этом развороте даёт p ≈ 0,015. Ниже — стек, методика замера, все числа и раздел про то, где мой тест врёт. Чтение займёт минут десять.

**Простыми словами.** RAG здесь — поиск по смыслу: текст режется на куски, каждый кусок превращается в вектор, вопрос тоже превращается в вектор, дальше ищем ближайшие. Карта (`main.md`) — файл-оглавление, по строке на каждый проект и раздел базы: агент читает его и понимает, куда идти.

## Зачем я полез проверять собственное утверждение

Четыре месяца мой ИИ-агент живёт на файловой памяти: карта воркспейса, память по проекту, база знаний в markdown. Про эту конструкцию была [прошлая статья](/articles/ai-agent-file-memory/), и в ней я двумя абзацами закрыл тему семантического поиска: инфраструктура ради задачи «не пересказывать проект», результат поиска по эмбеддингам не отревьюишь глазами так же просто, как файл. И оставил себе лазейку: «если у вас тысячи заметок с перекрёстными смысловыми запросами — RAG вернётся в разговор».

Потом я посчитал файлы. Их 3156.

Дальше было два пути: продолжать рассуждать о том, чего не пробовал, или собрать RAG и померить. Выбрал второе, с условием — мерить честно, включая вариант «отрицательный результат». Отрицательный результат меня устраивал: я бы сэкономил вечера и написал, почему не стоит.

## База и что в неё вошло

3156 markdown-файлов, 64 МБ текста. Из них 31 МБ — выгрузки таблиц в markdown, куски исследований по 1–2 МБ. Табличные дампы я из индекса выкинул сразу: чанк из такой простыни — набор ячеек без смысла, он будет мусорить выдачу и портить сравнение в пользу карты.

Осталось 1978 файлов и 26 МБ живого текста. При чанках по 600 токенов я прикинул 12–18 тысяч кусков. Реально получилось **29 295** — промах вдвое, потому что заголовков в базе больше, чем я думал, и резка по секциям даёт много коротких чанков. Первый урок дешёвый: считайте чанки токенизатором целевой модели до того, как планировать время индексации.

**Замечание.** В базе есть то, что не хочется отдавать на чужой сервер, поэтому облачные эмбеддинги отпали на входе. Полная индексация через API стоила бы центов тридцать — сумма смешная, но текст при этом уезжает на чужой сервер. Второй довод в пользу локальной модели прагматичнее приватности: индекс намертво привязан к эмбеддеру. Депрекейтнут модель в облаке — поиск умер до полной переиндексации. Модель на диске никуда не денется.

## Стек и 18 часов на процессоре

- **Эмбеддер:** bge-m3, 1024 измерения, мультиязычная. Веса 2,2 ГБ на диске.
- **Хранилище:** SQLite с расширением sqlite-vec для векторов и штатным FTS5 для BM25. Один файл на 120 МБ, никакого сервера. Векторов мало (29 тысяч), приближённый поиск не нужен — полный перебор укладывается в миллисекунды.
- **Поиск:** гибрид, вектор и BM25 идут параллельно, результаты сливаются ранговым методом RRF. Это важно для чистоты сравнения: режим RAG получил внутрь и лексический поиск тоже, так что проигрыш нельзя списать на «вектор не знает слова».
- **Чанкинг:** по заголовкам, 600 токенов, перекрытие 100, метаданные из frontmatter.
- **Интерфейс для агента:** MCP-сервер с одной тулой `search_kb(запрос, k, префикс_пути)`, отдаёт текст чанков с путями.

Дальше начались измерения, которые я в план не закладывал. Бенчмарк bge-m3 на моём ноутбуке (12 логических ядер, дискретной видеокарты нет) — **0,45 чанка в секунду**. Это 18 часов на полный прогон.

Я честно попробовал разогнать. ONNX через onnxruntime — 0,46 чанка/с. Динамическое int8-квантование — 0,42, то есть медленнее. Векторы при этом совпали с исходными до косинуса 1,0, так что квантование как минимум ничего не сломало, просто не помогло. Вывод для тех, кто пойдёт тем же путём: такая модель упирается в пропускную способность памяти, поэтому типовые процессорные ускорители на ней не дают ничего. Индекс собирался ночами, полтора суток по календарю; инкрементальность по SHA-256 спасла, когда прогон оборвался на 60% — второй запуск продолжил с того же места.

**Плюсы:** запросы работают мгновенно. Эмбеддинг вопроса — десятые доли секунды, поиск по 29 тысячам векторов — миллисекунды. Медленная только индексация, и она разовая.

Грабли, на которые я потратил вечер:

| Грабля | Что происходит | Фикс |
|---|---|---|
| fastembed не поддерживает bge-m3 | модели нет в списке, а хочется именно мультиязычную | sentence-transformers + torch с процессорного индекса |
| В пакете `mcp` 2.0 нет `FastMCP` | `ModuleNotFoundError` на импорте по любому туториалу | класс `MCPServer` из `mcp.server.mcpserver`, API тот же |
| Модель грузится 13 секунд при импорте | клиент считает сервер мёртвым по таймауту рукопожатия | ленивая загрузка при первом запросе, старт мгновенный |
| Индексер и поиск дерутся за файл базы | «database is locked» посреди прогона | читателю — соединение read-only и `busy_timeout` |
| Логи модели в stdout | ломают протокол обмена с агентом | всё, кроме ответа, гнать в stderr |

## Как я мерил, чтобы не обмануть себя

Соблазн при таком замере один: подсознательно подыграть системе, в которую вложил три дня. Поэтому методику я зафиксировал до первого прогона.

**Эталонный набор — 24 вопроса, замороженные заранее.** 14 точечных (ответ лежит в одном конкретном месте: значение параметра, правило, шаблон ссылки) и 10 обзорных, где ответ собирается из 2–5 файлов разных проектов. К каждому вопросу — список эталонных файлов и ключевой факт, который обязан прозвучать.

**Эталоны собирал отдельный агент-разведчик обычными Grep и Glob.** Если искать источники правды тем же инструментом, который потом тестируешь, набор молча подгонится под его выдачу. Разведка по двум вопросам поправила меня: я помнил цифры не так, как они записаны в базе. Формулировки этих двух вопросов я привёл к тому, что реально лежит в файлах.

**Два режима, одинаковые условия.** Режим R: агенту доступна только поисковая тула, читать можно лишь файлы из её выдачи. Режим G: только `main.md`, Grep, Glob и чтение. Одна и та же модель, один и тот же промпт, жёсткий лимит 15 вызовов инструментов, свежая сессия на каждый вопрос — 48 прогонов.

**Слепое судейство.** Судья получал вопрос, эталон и два обезличенных ответа со сменным порядком внутри пары. Какой ответ чьей системы — он не знал.

**Отдельная защита от протечки.** Сам проект сравнения лежит в той же базе, и к моменту прогонов эталонный набор с ответами уже был проиндексирован. Поисковый сервер жёстко исключает каталог проекта из выдачи, иначе режим R получил бы шпаргалку.

Весь эксперимент — 72 агентских прогона и около 3 миллионов токенов.

## Числа

| Метрика | Точечные (14): RAG | Точечные: карта+grep | Обзорные (10): RAG | Обзорные: карта+grep |
|---|---|---|---|---|
| Победы по судье | **7** | 1 | 2 | **8** |
| Ключевой факт найден | 1,00 | 1,00 | 0,50 | **0,80** |
| Покрытие эталонных файлов | **0,93** | 0,89 | 0,69 | **0,77** |
| Вызовов инструментов (среднее) | **2,3** | 4,1 | 5,6 | 5,2 |
| Галлюцинации | 0 | 0 | 0 | 0 |

Ничьих по решению судьи — 6, все на точечных вопросах. Общий счёт по всем 24 вопросам: 9:9.

Про значимость сразу и честно. Ничья 9:9 сама по себе не говорит ничего. Каждая половина по отдельности тоже не дотягивает до значимости: двусторонний знаковый тест даёт p ≈ 0,07 на точечных и p ≈ 0,11 на обзорных. А вот **разворот** между типами вопросов держится: точный тест Фишера на таблице 2×2 (тип вопроса × победитель) даёт p ≈ 0,015. То есть выборка мала для утверждения «RAG лучше на точечных на столько-то», но достаточна для утверждения «тип вопроса меняет победителя».

## Где семантический поиск выиграл

Точечный факт достаётся с тем же качеством, что у grep, и почти вдвое дешевле по шагам. На четырёх вопросах хватило одного вызова: спросил человеческим языком — получил канонический файл с нужной строкой. Режим G на тех же вопросах шёл цепочкой: карта → раздел → файл → чтение, четыре-пять шагов.

Выигрыш накапливается там, где формулировка вопроса не совпадает с лексикой документа. Спрашиваешь «какое значение параметра принято в расчёте», а в файле он назван техническим именем переменной — вектор такой мост строит, grep требует угадать слово.

**В шагах.** 2,3 против 4,1 на точечных вопросах. Для агента шаг — это чтение файла в контекст, то есть деньги и место в окне. На сотне точечных запросов в неделю разница заметна в счёте.

## Где проиграл

На обзорных вопросах RAG находил правильные файлы (0,69 против 0,77 — почти паритет), но ключевой факт передавал вдвое реже: 0,50 против 0,80. Два показательных случая.

**Потерянный статус.** Вопрос: «где всё, что касается интеграции с внешним рекламным API, и в каком она состоянии». Режим R собрал механику целиком — порядок вызовов, авторизацию, ограничения. И не сказал главного: работа стоит на паузе. Строка про статус живёт во frontmatter памяти проекта, короткая, тематически не похожая на вопрос о содержании — в топ выдачи она не попала. Режим G прочитал README проекта и увидел статус в шапке файла.

**Полный промах.** Вопрос: «какие материалы у нас есть по такой-то теме и в каком проекте они лежат». Режим R сжёг 12 вызовов из 15, не достал **ни одного** эталонного файла и собрал ответ по косвенным упоминаниям в соседних проектах — с ошибками в деталях (насчитал три материала вместо четырёх и потерял ещё один). Канонический README проекта утонул среди тематически похожих чанков. Режим G дошёл через карту за 4 вызова с полным попаданием.

Ноль галлюцинаций на 48 прогонов — единственная метрика, где системы одинаково хороши. Оба режима отвечали строго от источников, разница была в полноте.

## Почему так вышло

Гипотеза механизма, которую я вынес из разбора: **вектор ищет похожее, карта хранит каноническое.** На вопрос «где правда про X» похожесть — плохой заменитель авторитетности. Канонический документ и десять чанков, упоминающих X по касательной, для эмбеддера выглядят одинаково релевантными; карта же прямо указывает: вот источник правды.

Отсюда три следствия, которые видно в числах:

1. **Мета-слой не попадает в чанки.** Статус, владелец, дата, «что уже закрыто» лежат во frontmatter и README — короткими строками, не похожими на содержательный вопрос. Поиск по смыслу их системно недооценивает.
2. **Чанк теряет место в дереве.** Принадлежность файла проекту записана структурой каталогов; в тексте самого чанка её нет. Grep со списком путей эту структуру видит бесплатно.
3. **Карта — уже посчитанный слой синтеза.** Кто-то (я или агент по моим правилам) один раз проделал работу по агрегации и записал результат. RAG каждый раз выводит картину заново из фрагментов и на этом теряет.

Отдельно отмечу: BM25 у режима R был внутри гибрида. Проигрыш на обзорных вопросах не объясняется тем, что «вектор не знает точных слов» — не хватало именно курирующего слоя.

## Где мой тест врёт

- **24 вопроса — мало.** Разворот значим, абсолютные величины разрывов — нет. На другой базе цифры поедут.
- **Судья — модель, и один на вопрос.** Я перепроверил выборку оценок руками и согласился с ними, но полноценной второй разметки человеком не было.
- **Число вызовов — самоотчёт агента.** Телеметрию рантайма я не снимал: порядок величин верный, точность до десятых — нет.
- **Время не сравнивал.** У режима R оно искажено загрузкой модели и очередью к общему серверу; честный замер требовал бы отдельной обвязки.
- **Соперник — не голый grep, а ухоженная карта.** Это сознательный выбор: я сравнивал с реальной своей навигацией. На базе без карты и README расклад на обзорных вопросах почти наверняка сместился бы в пользу RAG. Если вы прикидываете RAG на свалку из тысячи заметок — мой результат к вам применим только наполовину.
- **Вопросы придумывал я, зная базу.** Я старался держаться человеческого языка и подальше от лексики документов, но полностью снять этот эффект нельзя.
- **Прогоны шли в два захода** (упёрся в лимит сессии), продолжение делалось с кэшем. На слепоту судейства это не влияло.

## Что с этим делать

Если вы стоите перед тем же выбором для своей базы:

**Оставить карту руками.** Слой синтеза окупается на обзорных вопросах и не воспроизводится поиском по смыслу автоматически. Двадцать минут в неделю на обновление статусов дают то, чего 18 часов индексации не дали.

**Подключить семантический поиск как инструмент точечных запросов.** Он дешевле по шагам и не требует угадывать слово из документа. Как единственный интерфейс к базе — не годится, агент теряет рамку.

**Считать чанки заранее — токенизатором целевой модели.** Оценка на глаз промахнётся: разница между «прикинул 15 тысяч» и «получил 29 тысяч» — это разница между вечером и двумя ночами.

**Проверить на своей базе.** Методика переносится за вечер: 20–25 вопросов с эталонами (самая дорогая часть, часа полтора), два режима с одинаковым лимитом шагов, обезличенные пары, слепой судья. Заморозьте набор до первого прогона — иначе замер превратится в подгонку.

## Краткие выводы

1. Семантический поиск по личной базе окупается на точечных фактах: то же качество за половину шагов. На вопросах «собери картину» он теряет статусы и рамку и проигрывает курируемой карте 8:2.
2. Тип вопроса определяет победителя сильнее, чем сам инструмент. Общий счёт 9:9 без разбивки по типам не значит ничего.
3. Локальный bge-m3 на процессоре — 0,45 чанка в секунду, и ускорители тут не помогают. Планируйте ночи, делайте индексацию инкрементальной.
4. Курируемая карта оказалась сильным соперником, которого в сравнениях RAG обычно не измеряют: меряются с поиском по ключевым словам, а живой человек, поддерживающий оглавление, в сравнение не попадает.

Инструмент я оставил подключённым, карту продолжаю вести руками. Три дня на эксперимент вернулись знанием, какому вопросу какой инструмент отдавать, — это дешевле, чем год пользоваться поиском, который тихо теряет половину контекста на обзорных запросах.

Свою базу вы знаете лучше меня: посчитайте, каких вопросов вы задаёте агенту больше — «где точно записано вот это» или «собери всё, что мы знаем о теме»? От ответа зависит, стоит ли вам эти 18 часов вообще тратить.

Отдельно интересно: если у кого-то есть замер, где RAG выигрывает обзорные вопросы у живой карты, — покажите методику, я хочу понять, что у вас устроено иначе.

---
Автор: Егор, маркетинг на языке денег. Telegram: https://t.me/evstygney_ru. Все цифры — модельные прикидки, если не сказано иное.
