DarkRiDDeR13 мин

Поиск по инженерной базе: источник важнее уверенного пересказа

AIДокументация

Поиск по инженерной базе часто ломается не на ранжировании. В ответ попадает заметка с высоким score, но она уже истекла, закрыта для автора вопроса или не даёт точного места, где сказано нужное правило. Внешне всё выглядит убедительно: заголовок совпал, фрагмент знакомый, формулировка гладкая. Цена ошибки конкретна: инженер меняет контракт по старой инструкции, раскрывает закрытый фрагмент или не может показать коллеге, на какой абзац он опирался.

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

Симптом → причина → проверка → действие

  1. Симптом. Результат кажется релевантным, но рядом нет версии, даты, access label или точного фрагмента источника.
  2. Причина. Index хранит только текст и score, а признаки допуска к ответу живут в другой системе или не собраны совсем.
  3. Проверка. Для каждого кандидата отдельно сравнить access labels, publishedAt, indexedAt, expiresAt, URI и anchor с фиксированным моментом retrieval.
  4. Действие. В ответ передавать только запись, прошедшую все проверки; при пустом пересечении остановить ответ и открыть human review, а не подбирать правдоподобный пересказ.

Запись индекса — это не только фрагмент и embedding

Поисковый индекс нужен, чтобы сузить очередь чтения. В закреплённом README OpenSearch k-NN описан nearest-neighbor similarity search: система находит похожие документы, а фильтры могут уточнять выборку. Это полезная механика candidate retrieval. Но из неё нельзя получить четыре других факта: что документ разрешён конкретному человеку, что он ещё действует, что отрывок действительно подтверждает тезис и что в корпусе нет более подходящего источника. Эти вопросы не лежат в одном vector score.

Поэтому единицей хранения должна быть запись, а не голый chunk. У записи есть устойчивый идентификатор, версия источника, владелец, publishedAt, indexedAt, expiresAt, labels доступа, URI и anchor. Для текста без anchor честный результат — «кандидат найден, но не может стать цитатой». Для записи без версии — «возможный материал для ручного разбора», а не доказательство. Эта дисциплина стоит нескольких полей в index, но убирает спор о том, почему ответ выглядит уверенным, а проверить его нельзя.

Минимальный контракт записи индекса
ПолеЗачем оно retrievalЧто проверяет reviewerЧего поле не доказывает
recordId и sourceVersionсвязывают результат с конкретной редакциейчто reader и answer говорят об одной версиисемантическую верность вывода
publishedAt и expiresAtдают boundary актуальности для фиксированного retrieval timeчто запись не просрочена по объявленному правилучто владелец обновил все связанные документы
accessLabelsне дают закрытому fragment попасть в общий candidate answerчто label допуска совпадает с requester scopeличность реального пользователя или полноту policy
citationUri и citationAnchorделают фрагмент открываемым и адресуемымчто claim можно сопоставить с точным местомчто читатель уже прочитал и понял источник
vectorScoreупорядочивает похожие candidatesтолько порядок в выбранной функции похожестиистину, право доступа, свежесть или причинную связь
Вертикальная схема пути от записи индекса через retrieval, проверки access и freshness, точную цитату до human verification. Красное правило внизу останавливает ответ, если нет одновременно свежего, разрешённого и цитируемого источника.
Score появляется до проверки источника. Он сокращает список для чтения, но не даёт право сформулировать ответ без metadata и human verification.

Проверка должна идти до генерации текста

Соблазнительный порядок выглядит так: сначала составить развернутый ответ, затем попытаться приклеить ссылки. В этом порядке draft уже успевает выбрать слова, которых нет в источнике. Безопаснее получить citations раньше. Тогда генератор, редактор или человек работают не с «знанием системы», а с ограниченным набором фрагментов: у каждого видны версия, дата, срок, access label и точная ссылка. Если фрагмент не проходит проверку, он не исчезает тайно: в retrieval output остаётся reject reason.

RFC 9110 полезен здесь как граница языка. Last-Modified сообщает, когда origin server считает выбранное представление изменённым, и сам RFC оставляет способ получения значения implementation detail. Значит, HTTP timestamp можно сохранить как один источник metadata, но нельзя выдать его за полную политику свежести. Внутренняя база может иметь schedule пересмотра, документ — deprecation date, а индекс — собственную дату ingestion. В ответе нужно назвать, какую из этих дат проверили и зачем.

import {
  createFixedSyntheticKnowledgeRetrievalInput,
  inspectFixedSyntheticKnowledgeRetrieval,
  prepareSyntheticKnowledgeHumanReview,
  stopSyntheticKnowledgeAnswer,
  runKnowledgeRetrievalFixture,
} from './upgrade-2025-03.mjs';

const report = inspectFixedSyntheticKnowledgeRetrieval(
  createFixedSyntheticKnowledgeRetrievalInput('fixed-fresh-allowed-v1'),
);
const review = prepareSyntheticKnowledgeHumanReview(report);
const stopped = stopSyntheticKnowledgeAnswer(review);

if (!Object.values(runKnowledgeRetrievalFixture().assertions).every(Boolean)) {
  throw new Error('fixed synthetic fixture failed');
}

console.log({ accepted: report.accepted, citation: report.citations[0].citation, stopped: stopped.stopped });

// Fixed synthetic records in memory only.
// No files, Git, network, CI, clock, telemetry, production, user data or external search is used.
// stopped means discard of an in-memory review draft, not withdrawal of a real answer or document.

Пример намеренно не читает базу, Git, сеть, CI, часы, telemetry или production. В нём три fixed synthetic cases лежат прямо в модуле. В первом case высокий score у просроченной записи и у закрытого фрагмента; в citation выходит только более низкий, но разрешённый, свежий и адресуемый record. Во втором и третьем case пересечение пустое: один candidate истёк, другой недоступен по labels. Результат не «поиск не нашёл ответ», а точнее: stop-no-fresh-authorized-citable-source.

Metadata превращает проверку в короткий маршрут

У маршрута есть владелец на каждом переходе. Владелец corpus решает, какие источники разрешено индексировать. Владелец access policy решает, кому доступен source class. Владелец freshness rule определяет expiry и порядок обновления. Автор ответа не должен молча заменять ни одну из этих политик своим ощущением релевантности. Его задача уже: показать query, дату retrieval, выбранную запись и ссылку на fragment; затем прочитать fragment и сказать, подтверждает ли он тезис.

NIST SP 800-207 формулирует общий принцип: нельзя выдавать неявное доверие только из сетевого положения, а authentication и authorization — отдельные функции до доступа к enterprise resource. В статье это не превращается в обещание готовой zero-trust архитектуры для документации. Вывод уже авторский и уже узкий: similarity score не может заменить отдельную проверку access label. Если label не совпал, запись остаётся в диагностике с reject reason, но не становится material для ответа.

Четыре даты и их разные вопросы
ДатаВопросПример реакцииГраница
publishedAtсуществовал ли документ к moment retrieval?не брать future-dated записьне подтверждает, что содержимое действует сейчас
indexedAtкогда corpus увидел эту редакцию?искать lag и пересобрать index по policyне является датой документа
expiresAtразрешает ли local rule использовать запись?отклонить expired candidateне доказывает удаление исходного URI
retrievedAtкакой срез проверял ответ?показать timestamp рядом с citationне равен live observation после ответа

Exact citation нужна читателю, а не только audit

Citation без anchor заставляет читателя заново угадывать, какой абзац имелся в виду. Citation без sourceVersion не объясняет, относится ли правило к старому adapter или к новой схеме. Citation без retrievedAt прячет, на каком срезе был сделан вывод. Полный record не делает утверждение автоматически верным, но делает проверку короткой: открыть URI, перейти к anchor, сравнить фрагмент с текстом ответа, увидеть date и rights boundary. Это важнее длинного confidence-пояснения после готового текста.

Не надо отдавать в ответ весь internal record. Для читателя достаточно минимальной provenance карточки: title, version, citation, publishedAt, indexedAt, expiresAt, retrievedAt и краткая причина допуска. Access labels можно раскрывать как класс, если сама policy это разрешает; конкретные роли, токены и закрытый metadata не становятся частью ответа. Отделение proof path от секретов — ещё одна причина не строить цитату из одного snippet поля.

Ограничения и следующий проверяемый шаг

Эта модель не измеряет качество production retrieval, не открывает реальный document, не проверяет identity и не доказывает, что corpus полный. Все names, timestamps, excerpts, labels, scores, URI, anchors и decisions внутри upgrade-2025-03.mjs — fixed synthetic in-memory values. Они нужны, чтобы проверить contract обработки: expired, closed и anchorless candidate не проходят в citations. Они не являются данными инженерной базы, доступом к ней или историей реальной команды.

Следующий шаг для одного вопроса: выпишите одну запись index в формате таблицы выше. Затем попробуйте удалить любое поле — version, expiry, access label или anchor. Ожидаемый результат не «система всё равно ответила», а явная stop condition. Если значение нельзя назвать, ответ должен завершиться запросом к owner или human review. После этого уже можно обсуждать retriever, chunking и reranker: они ускоряют выбор кандидата, но не меняют границу допуска к цитате.

Историческая граница марта 2025

Материал использует только узкие утверждения источников, доступных до марта 2025: immutable commit OpenSearch k-NN от 12 июня 2024 для механики similarity candidates; RFC 9110 от июня 2022 для смысла Last-Modified; NIST SP 800-207 от августа 2020 для явной authorization boundary. Правило «не считать score доказательством истины» — вывод автора из того, что эти сигналы отвечают на разные вопросы. Оно не приписывается OpenSearch, RFC или NIST как их формальная гарантия.

Проверяемые источники

  • OpenSearch k-NN README, tag 2.15.0.0, immutable commit 150c589 (12 June 2024) — Официальный README описывает k-NN как nearest-neighbor similarity search по документам и измерениям, а также возможность уточнять similarity search фильтрами. Граница: Источник описывает поиск похожих кандидатов и фильтры. Он не говорит, что score подтверждает истинность утверждения, актуальность документа, право пользователя читать фрагмент или полноту корпуса.
  • RFC 9110: HTTP Semantics, section 8.8.2 Last-Modified (June 2022) — Last-Modified сообщает время, в которое origin server считает выбранное представление изменённым; способ определения значения остаётся implementation detail. Граница: Это метаданные представления HTTP, а не доказательство семантической правильности текста, владельца документа, прав читателя или отсутствия более нового источника.
  • NIST SP 800-207: Zero Trust Architecture (August 2020) — NIST описывает отсутствие неявного доверия по сетевому положению и отдельные authentication/authorization functions до установления сессии к enterprise resource. Граница: Публикация не задаёт схему vector index, формат цитаты или policy свежести инженерной документации. Она поддерживает только принцип явного контроля доступа к ресурсу.