8 lines
18 KiB
JSON
8 lines
18 KiB
JSON
{
|
||
"index": 100,
|
||
"slug": "editorial-2025-03-field-knowledge-retrieval",
|
||
"title": "Когда поиск по базе знаний должен остановиться",
|
||
"excerpt": "Высокая похожесть найденного фрагмента не доказывает его актуальность, доступность и пригодность для цитирования. Разбираем stop-условие, проверку источника и безопасный путь для неполного результата.",
|
||
"contentHtml": "<p>Поиск по инженерной базе часто ломается не тогда, когда ничего не нашёл. Опаснее другой симптом: система возвращает убедительный фрагмент, а команда принимает его за действующее правило. Документ уже закрыт для текущего пользователя, срок его действия истёк или в нём нет точного места, которое подтверждает вывод. Поиск показывает высокий score, интерфейс показывает ответ, а ошибка обнаруживается позже.</p>\n<p>Цена такой ошибки измеряется не длиной задержки. Старая инструкция может привести к неверной миграции. Закрытый фрагмент может попасть в ответ человеку без права доступа. Неточная цитата превращает предположение в решение на ревью. Исправлять последствия дороже, чем остановить ответ на несколько минут и передать вопрос владельцу источника.</p>\n<h2>Тезис: похожесть не равна доказательству</h2>\n<p>Retrieval должен отвечать на два разных вопроса. Первый: насколько найденный фрагмент похож на запрос. Второй: можно ли использовать его для конкретного ответа. Векторный или текстовый поиск решает только первый вопрос. Он ранжирует результаты. Он не подтверждает дату, права и точный смысл документа.</p>\n<p>Поэтому ответ разрешается продолжить только для записи, которая одновременно проходит три проверки: источник доступен этому запросу, источник свеж по заданному правилу и в нём есть точная цитата с устойчивым anchor. Если хотя бы одно условие не выполнено у всех кандидатов, система возвращает stop. Она не заполняет пробел вероятным пересказом.</p>\n<p>Stop не означает «в базе ничего нет». Он означает «найденное нельзя безопасно использовать». Это важное различие для диагностики. Пользователь должен увидеть безопасную причину и следующий маршрут, но не закрытый текст. Владелец документа должен понять, что обновить или разрешить. Так система сохраняет и полезность, и границу доказательств.</p>\n<h2>Механизм проверки</h2>\n<p>У записи источника должен быть контракт. Минимальный набор полей выглядит так: стабильный идентификатор, версия, URI, anchor, владелец, класс доступа, дата публикации, дата индексации и локальная дата истечения. Поле <code>retrievedAt</code> фиксирует момент, когда поиск увидел запись. Эти даты нельзя сводить к одному timestamp: задержка индекса и срок действия документа описывают разные риски.</p>\n<p>Сначала система получает кандидатов. Затем применяет policy-фильтр. Проверка прав происходит до передачи excerpt в контекст ответа. Проверка свежести зависит от типа вопроса. Для операционного вопроса истёкшая инструкция непригодна. Для исторического вопроса она может быть полезна, но ответ должен назвать версию и дату. В обоих случаях правило задают метаданные и владелец политики, а не score.</p>\n<p>Положительная ветка возвращает source id, version, URI, anchor, retrievedAt и разрешённый фрагмент. Отрицательная ветка возвращает код причины: <code>access-denied</code>, <code>expired</code>, <code>missing-anchor</code> или <code>unknown-policy</code>. Закрытый excerpt не входит в отрицательный результат. Такой контракт не делает источник истинным автоматически. Он не даёт системе скрыть отсутствие доказательства.</p>\n<pre><code>const result = retrieve(query, records, policy);\n\nif (!result.accepted) {\n return {\n status: 'stop',\n code: result.reason,\n sourceId: result.sourceId,\n next: result.ownerAction,\n };\n}\n\nreturn {\n status: 'review',\n claim: draftClaim(query, result.excerpt),\n citation: {\n uri: result.uri,\n version: result.version,\n anchor: result.anchor,\n retrievedAt: result.retrievedAt,\n },\n};</code></pre>\n<p>Это учебный пример. Функции <code>retrieve</code> и <code>draftClaim</code> здесь не подключены к настоящей базе и не подтверждают реальную авторизацию. Пример показывает границу: положительный результат ещё требует ручного сравнения утверждения с источником, а отрицательный результат останавливает дальнейшую обработку.</p>\n<h2>Симптомы, причины и действия</h2>\n<table><caption>Диагностика retrieval-ответа</caption><thead><tr><th>Симптом</th><th>Причина</th><th>Проверка</th><th>Действие</th></tr></thead><tbody><tr><td>Высокий score, но документ старый</td><td>Ранжирование не учитывает срок действия</td><td>Сравнить <code>expiresAt</code> и <code>retrievedAt</code></td><td>Остановить ответ и направить к владельцу документа</td></tr><tr><td>Найден закрытый фрагмент</td><td>Поиск и проверка прав разделены или проверка выполнена поздно</td><td>Проверить access decision до передачи excerpt</td><td>Вернуть безопасную причину без текста источника</td></tr><tr><td>Цитата ведёт на страницу без места</td><td>В индексе сохранён URI, но нет устойчивого anchor</td><td>Открыть URI и проверить заголовок, номер раздела или якорь</td><td>Остановить ответ и исправить карточку источника</td></tr><tr><td>Разные даты в индексе и документе</td><td>Не различены публикация, индексация и срок действия</td><td>Сопоставить все даты и владельца каждой</td><td>Исправить policy или обновить индекс</td></tr><tr><td>Система отвечает «ничего нет»</td><td>Reject reasons потерялись после фильтра</td><td>Проверить structured result и журнал решения</td><td>Показать safe reason и следующий маршрут</td></tr></tbody></table>\n<h2>Почему HTTP-дата не решает задачу свежести</h2>\n<p>HTTP-заголовок <code>Last-Modified</code> сообщает дату, когда origin считает выбранное представление изменённым. Это полезный сигнал для индексации и условных запросов. Но он не доказывает, что инструкция всё ещё действует. Правило могло устареть из-за миграции, смены владельца или изменения контекста, даже если файл с тех пор не менялся.</p>\n<p>Практическая политика должна назвать, какая дата управляет ответом. Например, <code>publishedAt</code> отвечает за версию документа, <code>indexedAt</code> показывает задержку конвейера, а <code>expiresAt</code> задаёт допустимый срок для операционных вопросов. Если источник не предоставляет нужные данные, это не повод угадывать. Нужна остановка или явное решение владельца о том, какое ограничение действует.</p>\n<h2>Права доступа проверяются отдельно</h2>\n<p>Доступ к документу нельзя выводить из факта, что поисковый сервис смог его прочитать. Индекс может работать с расширенными правами, а запрос пользователя — с ограниченными. Проверка должна учитывать субъекта, ресурс и цель обращения. Deny должен произойти до того, как закрытый фрагмент попадёт в контекст следующего шага.</p>\n<p>Безопасный stop-ответ содержит только то, что разрешено policy: например, внутренний идентификатор записи, общий код причины и группу владельца. Не следует возвращать закрытый заголовок, соседние предложения или признаки, по которым можно восстановить содержание. Уровень подробности диагностического сообщения — тоже часть модели доступа.</p>\n<figure><img src=\"/assets/editorial/2025/knowledge-retrieval-2025-verification-loop.svg\" alt=\"Схема проверки источника: запрос, кандидаты, проверки доступа и свежести, точная цитата, ручная проверка или остановка\"><figcaption>Остановка происходит внутри цикла: кандидат проходит проверку доступа, свежести и anchor до формирования ответа.</figcaption></figure>\n<h2>Порядок действий для одной базы</h2>\n<ol><li>Разделите вопросы на операционные и исторические. Зафиксируйте разные правила свежести, если они действительно различаются.</li><li>Опишите карточку источника. Сохраните id, версию, URI, anchor, владельца, класс доступа и нужные даты.</li><li>Определите обязательные условия ответа: доступ, свежесть и точная цитата. Назовите stop-коды для каждого нарушения.</li><li>Проверьте отрицательный путь. Подложите кандидата с высоким score, но с истёкшим сроком или запрещённым доступом.</li><li>Убедитесь, что excerpt закрытого кандидата не покидает слой проверки. В результате должны остаться только разрешённая причина и маршрут.</li><li>Проверьте положительный путь вручную. Откройте exact source, сравните claim с anchor, подтвердите версию и сохраните citation.</li><li>Назначьте владельца остаточного риска. Если policy неизвестна, вопрос должен попасть к владельцу policy, а не к слою ответа.</li></ol>\n<h2>Отрицательный путь важнее красивого ответа</h2>\n<p>Качество retrieval видно не только по найденным документам. Оно видно по тому, как система ведёт себя при конфликте. Если документ похож на запрос, но истёк, она должна сохранить причину и остановиться. Если фрагмент разрешён, но anchor отсутствует, она не должна ссылаться на всю страницу. Если policy неизвестна, она не должна превращать неизвестность в allow.</p>\n<p>Простой тест проверяет именно это поведение: highest-score candidate получает значение <code>expired</code> или <code>access-denied</code>; итоговый объект имеет статус <code>stop</code>; поля <code>claim</code> и <code>citation.excerpt</code> отсутствуют; присутствуют причина и действие владельца. Это проверяет контракт учебного модуля, но не доказывает работу вашей production-базы. Для реальной системы нужны отдельные проверки identity, источника и наблюдаемости.</p>\n<h2>Ограничения</h2>\n<p>Эта модель не выбирает лучший алгоритм поиска и не обещает, что keyword search, embeddings или reranking дадут конкретную точность. Она не заменяет классификацию документов, аудит прав и управление версиями. Score остаётся полезным для порядка кандидатов, но не становится доказательством. На качество ответа также влияет полнота корпуса: если нужного документа нет, фильтр не создаст его.</p>\n<p>Учебный код не читает настоящую wiki, не обращается к сети и не выполняет реальную authorization decision. Все записи в примере условны. Нельзя переносить его значения дат, score или статусы в production. Переносить стоит только форму результата: accepted либо stop, структурированную причину, citation с anchor и явного владельца следующего действия.</p>\n<h2>Проверяемый критерий готовности</h2>\n<p>Работа готова, когда для одного выбранного типа вопроса система может показать полный путь. Разрешённый свежий источник даёт версию, URI, anchor и дату retrieval, после чего человек подтверждает claim. Просроченный, закрытый или нецитируемый кандидат даёт stop без раскрытия запрещённого текста. В обоих случаях есть воспроизводимая проверка и назначенный владелец.</p>\n<p>Если тест с высоким score и истёкшим документом всё ещё формирует ответ, проблема находится не в формулировке prompt. Не хватает контракта между индексом, policy и слоем ответа. Сначала добавьте metadata и отрицательную ветку. Только после этого имеет смысл настраивать ranking.</p>\n<h2>Проверяемые источники</h2>\n<ul><li><a href=\"https://www.rfc-editor.org/rfc/rfc9110.html#section-10.2.2\" target=\"_blank\" rel=\"noopener\">RFC 9110: HTTP Semantics, Last-Modified</a> — определение HTTP-даты изменения представления.</li><li><a href=\"https://csrc.nist.gov/pubs/sp/800/207/final\" target=\"_blank\" rel=\"noopener\">NIST SP 800-207: Zero Trust Architecture</a> — разделение authentication и authorization перед доступом к ресурсу.</li><li><a href=\"https://docs.opensearch.org/latest/im-plugin/similarity/\" target=\"_blank\" rel=\"noopener\">OpenSearch Documentation: Similarity</a> — score как мера релевантности и порядок результатов, а не проверка фактов.</li></ul>"
|
||
}
|