{ "index": 102, "slug": "editorial-2025-03-practice-knowledge-retrieval", "title": "Поиск по инженерной базе: как не превратить похожий текст в доказательство", "excerpt": "Практический маршрут для retrieval по инженерной документации: сначала проверить версию, срок, права и точную цитату, а затем решать, можно ли отвечать.", "contentHtml": "
Инженер задаёт вопрос по внутренней документации. Поиск возвращает фрагмент с высоким score. Заголовок совпадает, формулировка выглядит знакомой, ответ можно написать за минуту. Потом выясняется, что фрагмент описывает старый контракт, закрыт для этого читателя или ведёт на страницу без точного места в тексте. Ошибка уже повлияла на решение: изменился адаптер, в ответ попал закрытый материал, а reviewer не может быстро проверить источник.
Цена такой ошибки складывается из отката, повторного расследования и потери доверия к базе знаний. Пустой результат заметен. Уверенный пересказ устаревшего правила — нет. Поэтому поисковый ответ должен сначала доказать право на использование конкретной записи, её применимость на момент запроса и адресуемость цитаты. Только после этого можно формулировать вывод.
Similarity search решает узкую задачу: он упорядочивает похожие документы или фрагменты. В score нет ответа на вопросы «кто может читать запись», «действует ли правило сейчас» и «подтверждает ли этот абзац конкретный claim». Эти вопросы требуют других данных и других проверок. Если объединить их в одно число, система перестанет объяснять, почему кандидат отклонён.
Рабочая граница выглядит так: query → candidates → access check → freshness check → exact citation → human verification. Retrieval сокращает очередь чтения. Metadata задают условия допуска. Человек сопоставляет утверждение с источником. Если пересечение условий пусто, система останавливается и сообщает причину. Она не заменяет недостающий evidence правдоподобной фразой.
| Симптом | Причина | Проверка | Действие |
|---|---|---|---|
| Первый кандидат имеет высокий score, но у него нет версии | Индекс хранит chunk и embedding без связи с редакцией источника | Найти stable recordId, sourceVersion и URI исходного документа | Оставить кандидата для поиска, но не использовать как цитату |
| Фрагмент точно отвечает на вопрос, но expiresAt уже прошёл | Ранжирование не учитывает локальную политику актуальности | Сравнить expiresAt и зафиксированный retrievedAt | Отклонить запись и запросить действующую редакцию |
| Кандидат закрыт для requester | Access policy проверяется после генерации или не проверяется | Сопоставить accessLabels записи и scope запроса до показа excerpt | Скрыть закрытый текст, сохранить безопасную причину отказа |
| Ссылка ведёт на документ, но не на нужный абзац | В индексе нет citationAnchor | Открыть URI и проверить точный раздел, строку или якорь | Остановить ответ до появления адресуемого фрагмента |
| Ответ написан, ссылки добавлены позже | Текст успел включить выводы, которых нет в evidence | Сравнить каждый claim с источником до публикации | Сначала собрать допущенные citations, затем писать ответ |
Голый фрагмент удобен для векторного поиска, но плох для проверки. Минимальная запись связывает chunk с источником и сохраняет границы его использования. Нужны устойчивый recordId, sourceVersion, publishedAt, indexedAt, expiresAt, класс доступа, citationUri и citationAnchor. Поле vectorScore тоже полезно, но только для порядка кандидатов.
publishedAt отвечает на вопрос, существовала ли редакция к моменту поиска. indexedAt показывает задержку между публикацией и попаданием в индекс. expiresAt выражает локальное правило применимости. retrievedAt фиксирует срез, на котором система приняла решение. Ни одна из этих дат сама по себе не доказывает смысл утверждения. Вместе они позволяют воспроизвести проверку.
Такая схема важна после chunking. Когда документ режут на части, title и текст часто сохраняют, а версию, владельца и раздел оставляют в исходном хранилище. Затем в ответ попадает удобный snippet, который нельзя связать с конкретной редакцией. Metadata должны наследоваться каждым chunk или однозначно находиться по stable source id. Иначе retrieval создаёт видимость точности, а не проверяемую provenance.
Ниже — учебный пример на фиксированных синтетических записях. Он показывает порядок решения и отрицательный путь. Он не обращается к реальной базе, identity provider, часам, production или сети. Синтетические значения нужны только для проверки контракта обработки.
const retrievedAt = '2025-03-17T10:00:00Z';\nconst requesterLabels = ['engineering-read'];\n\nconst candidates = [\n {\n recordId: 'adapter-v1',\n sourceVersion: 'v1',\n expiresAt: '2025-03-01T00:00:00Z',\n accessLabels: ['engineering-read'],\n citationUri: 'https://docs.example.test/adapter',\n citationAnchor: '#old-field',\n vectorScore: 0.97,\n },\n {\n recordId: 'adapter-v3',\n sourceVersion: 'v3',\n expiresAt: '2025-04-01T00:00:00Z',\n accessLabels: ['engineering-read'],\n citationUri: 'https://docs.example.test/adapter',\n citationAnchor: '#schema-upgrade',\n vectorScore: 0.91,\n },\n];\n\nconst allowed = candidates.filter((item) =>\n item.expiresAt > retrievedAt &&\n item.accessLabels.some((label) => requesterLabels.includes(label)) &&\n item.citationUri && item.citationAnchor,\n);\n\nif (!allowed.length) {\n throw new Error('stop-no-fresh-authorized-citable-source');\n}\n\n// Учебный результат: v1 имеет больший score, но истёк.\n// v3 остаётся кандидатом для human verification.Здесь высокий score у adapter-v1 не отменяет истечение срока. adapter-v3 проходит технический фильтр, но это ещё не автоматическое доказательство ответа. Reviewer должен открыть citationUri#schema-upgrade и проверить, что фрагмент действительно говорит о нужной замене. Если у всех записей истёк срок, нет доступа или отсутствует anchor, функция должна вернуть stop condition. Скрытый fallback на старый текст создаёт именно ту ошибку, от которой защищает схема.
Безопасный порядок начинается с вопроса и времени retrieval. Затем система сохраняет candidates вместе с score и metadata. После этого она проверяет права, срок и citation. В ответ проходит только допущенная запись. Generator или автор получают ограниченный набор evidence, а не абстрактное «знание базы». Для каждого отклонённого кандидата остаётся причина: access-label-not-granted, expired-at-retrieval-time или missing-exact-citation-anchor.
Ссылка в конце готового текста не исправляет неверный порядок. Draft уже мог добавить условие, которого нет в документе, или смешать две версии. Citation должна появиться в момент выбора evidence. Тогда reviewer видит claim, sourceVersion, дату и точный fragment, а не пытается восстановить происхождение ответа по памяти.
HTTP-метаданные помогают, но не заменяют внутреннюю политику. В RFC 9110 Last-Modified описывает время, когда origin server считает изменённым выбранное представление. Это полезный сигнал о представлении, но не вся политика актуальности инженерного правила. Документ может иметь собственный срок пересмотра, дату deprecation или область действия. В ответе нужно назвать, какое условие применялось.
retrievedAt. Не меняйте эти значения в середине проверки.Проверяемый ответ не обязан раскрывать внутренний record целиком. Читателю достаточно названия источника, версии, citation, дат публикации и retrieval, а также краткого ограничения. Конкретные роли, токены и закрытые поля не нужно включать в provenance карточку. Класс доступа можно показать только тогда, когда это разрешает сама policy.
Отдельно храните result decision и human verification. Статус candidate-found означает, что поиск нашёл похожий материал. Статус citation-accepted означает, что запись прошла технические условия. Это всё ещё не равно «утверждение истинно», пока человек не сопоставил claim с фрагментом. Такое разделение делает интерфейс честнее: пользователь видит, что уже проверено, а что ещё нет.
Эта схема не доказывает полноту корпуса, качество embeddings, корректность access policy или семантическую истинность ответа. Она не говорит, что vector search лучше keyword search. Она задаёт более узкую границу: score не заменяет version, freshness, authorization и citation. Учебный код не является benchmark и не сообщает production-результаты.
Если источник закрыт, просрочен или не имеет точного anchor, система не должна пересказывать его «для справки». Безопасное действие — показать безопасную причину, сохранить recordId без restricted excerpt и направить вопрос владельцу документа или policy. Если policy неизвестна, нельзя молча выбрать allow или deny как окончательное решение: нужен owner и явное правило. Отрицательный путь входит в контракт наравне с успешным.
Один вопрос должен проходить тестовый набор из четырёх случаев: свежая разрешённая запись с anchor, просроченная запись с высоким score, закрытая запись с высоким score и запись без anchor. В первом случае результат содержит citation и требует human verification. В трёх остальных случаях citation не появляется, а decision содержит конкретную причину отказа. Повторный запуск с теми же query, retrievedAt и входными записями даёт тот же decision. Это проверяемый критерий готовности маршрута.
После этого можно отдельно измерять recall, latency и качество ранжирования. Такие метрики улучшают поиск кандидатов, но не отменяют проверку допуска. Если команда не может показать, почему выбран конкретный fragment и почему отклонены остальные, retrieval ещё не стал надёжным источником ответа.