Read in:
Русский

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?»

  1. search("ограничить конкурентность Go") возвращает совпадение в заметке Паттерны конкурентности в Go. В совпадении toc_path: ["Горутины", "Пул воркеров"].
  2. Агент читает: нужный раздел — Пул воркеров внутри Горутины.
  3. note_html(pid=42, toc_path=["Горутины", "Пул воркеров"]) возвращает HTML раздела.
  4. Агент отвечает по этому тексту со ссылкой на заметку.

Итого токенов: результаты поиска + один раздел (~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-разделов — в таком случае нужно читать всю заметку целиком.