Русский
Fuzzy Pointer: как поиск находит нужный раздел
Векторный поиск не возвращает точное место в документе — он возвращает fuzzy pointer, нечёткий указатель: цепочку заголовков, которая говорит «ответ где-то здесь». Это не ограничение, а намеренное устройство. Указатель достаточно точен, чтобы найти нужный раздел, и система разрешает его до конкретного абзаца по запросу.
Эта заметка объясняет, как выглядит fuzzy pointer, как его разрешить до раздела и как строить на этом агентные сценарии.
Что такое fuzzy pointer
Когда trip2g индексирует заметку, он разбивает её на фрагменты. Каждый фрагмент получает в начало цепочку заголовков — breadcrumb:
{название заметки} > {H1} > {H2}
...тело раздела...
Например, заметка Паттерны конкурентности в Go с разделом первого уровня Горутины и подразделом Пул воркеров даёт фрагмент:
Паттерны конкурентности в Go > Горутины > Пул воркеров
Пул воркеров — это набор горутин, ожидающих задачи...
Эта цепочка встраивается вместе с телом раздела, поэтому векторная модель видит полный путь к разделу, а не отдельный абзац без контекста. Когда пользователь ищет «пул воркеров Go», модель находит именно этот фрагмент — и сниппет в результатах поиска показывает breadcrumb.
Breadcrumb — это и есть fuzzy pointer. Он указывает, в какой заметке и в каком разделе найден ответ, но не в какой именно строке.
Три шага от нечёткого к точному
Шаг 1 — нечёткое совпадение. Векторный поиск возвращает результаты. Каждый результат содержит:
- полное оглавление заметки (
toc— структурированный список всех заголовков с массивомpathдля каждого) - breadcrumb-путь к разделу, где нашлось совпадение (
matches[].toc_path— самый вложенный заголовок, содержащий фрагмент)
Шаг 2 — определение раздела по TOC. Поле toc_path указывает раздел в структуре заметки. Для примера выше это:
["Горутины", "Пул воркеров"]
Шаг 3 — загрузка раздела. Передайте этот путь в note_html, чтобы получить HTML только этого раздела, а не всей заметки:
note_html(pid=42, toc_path=["Горутины", "Пул воркеров"])
Ответ содержит только раздел Пул воркеров — несколько сотен токенов вместо всей заметки.
Конкретный пример
Пользователь спрашивает AI-агента: «Как ограничить конкурентность в Go?»
search("ограничить конкурентность Go")возвращает совпадение в заметке Паттерны конкурентности в Go. В совпаденииtoc_path: ["Горутины", "Пул воркеров"].- Агент читает: нужный раздел — Пул воркеров внутри Горутины.
note_html(pid=42, toc_path=["Горутины", "Пул воркеров"])возвращает HTML раздела.- Агент отвечает по этому тексту со ссылкой на заметку.
Итого токенов: результаты поиска + один раздел (~300 токенов) вместо полной заметки (~3 000 токенов).
Антипример: как делать не надо
Агент получает результат поиска и сразу вызывает note_html(pid=42) — без toc_path. Он загружает всю заметку ради одного абзаца.
Это работает, но стоит в 10 раз больше токенов. Нужный ответ оказывается в середине длинного контекста, а модели сложнее его найти. toc_path из результата поиска уже указывает, где именно искать — используйте его.
Почему указатель нечёткий, а не точный
Breadcrumb указывает на раздел, а не на предложение. Это осознанный компромисс:
- Указатели на уровне строки нестабильны: любое редактирование заметки сдвигает номера строк, и сохранённые указатели устаревают.
- Указатели на уровне раздела стабильны: пока заголовок существует, путь разрешается правильно даже после переписывания тела раздела.
- Breadcrumb встраивается вместе с фрагментом, поэтому векторная модель понимает позицию раздела в иерархии документа, а не только слова конкретного абзаца.
Как используют агенты
Рекомендованный сценарий для AI-агента с базой знаний на trip2g:
1. search(query)
→ результаты с toc + matches[].toc_path
2. Читаем toc_path лучшего совпадения
→ определяем, какой раздел загрузить
3. note_html(pid=N, toc_path=match.toc_path)
→ получаем только этот раздел
4. Нужен соседний раздел?
Используем элементы toc из того же результата поиска
→ навигация без второго запроса search
Полный сценарий описан в MCP-сервере.
Ограничения
pid— стабильный PathID заметки, который возвращают результаты поиска (не совпадает сnote_id). Именно его передают вnote_html.- Путь в breadcrumb должен совпадать с текущими заголовками заметки. Если заголовок переименован после индексации, путь не разрешится до следующего переиндексирования.
- Если раздел не имеет заголовка (текст между двумя заголовками без собственного),
toc_pathуказывает на ближайший родительский заголовок. - Заметки, в которых нет ни одного раздела с заголовком, возвращают
toc_path: [](пустой массив). Это не то же самое, что однофрагментная заметка: короткая заметка с хотя бы одним заголовком всё равно получит непустой путь. Пустой массив означает отсутствиеdata-header-разделов — в таком случае нужно читать всю заметку целиком.