7 lines
20 KiB
JSON
7 lines
20 KiB
JSON
{
|
||
"index": 102,
|
||
"slug": "editorial-2025-03-practice-knowledge-retrieval",
|
||
"title": "Поиск по инженерной базе: как не превратить похожий текст в доказательство",
|
||
"excerpt": "Практический маршрут для retrieval по инженерной документации: сначала проверить версию, срок, права и точную цитату, а затем решать, можно ли отвечать.",
|
||
"contentHtml": "<p>Инженер задаёт вопрос по внутренней документации. Поиск возвращает фрагмент с высоким score. Заголовок совпадает, формулировка выглядит знакомой, ответ можно написать за минуту. Потом выясняется, что фрагмент описывает старый контракт, закрыт для этого читателя или ведёт на страницу без точного места в тексте. Ошибка уже повлияла на решение: изменился адаптер, в ответ попал закрытый материал, а reviewer не может быстро проверить источник.</p><p>Цена такой ошибки складывается из отката, повторного расследования и потери доверия к базе знаний. Пустой результат заметен. Уверенный пересказ устаревшего правила — нет. Поэтому поисковый ответ должен сначала доказать право на использование конкретной записи, её применимость на момент запроса и адресуемость цитаты. Только после этого можно формулировать вывод.</p><h2>Тезис: score выбирает кандидата, но не подтверждает утверждение</h2><p>Similarity search решает узкую задачу: он упорядочивает похожие документы или фрагменты. В score нет ответа на вопросы «кто может читать запись», «действует ли правило сейчас» и «подтверждает ли этот абзац конкретный claim». Эти вопросы требуют других данных и других проверок. Если объединить их в одно число, система перестанет объяснять, почему кандидат отклонён.</p><p>Рабочая граница выглядит так: <strong>query → candidates → access check → freshness check → exact citation → human verification</strong>. Retrieval сокращает очередь чтения. Metadata задают условия допуска. Человек сопоставляет утверждение с источником. Если пересечение условий пусто, система останавливается и сообщает причину. Она не заменяет недостающий evidence правдоподобной фразой.</p><h2>Симптом → причина → проверка → действие</h2><div class='table-scroll'><table><caption>Диагностика результата поиска по инженерной базе</caption><thead><tr><th scope='col'>Симптом</th><th scope='col'>Причина</th><th scope='col'>Проверка</th><th scope='col'>Действие</th></tr></thead><tbody><tr><td>Первый кандидат имеет высокий score, но у него нет версии</td><td>Индекс хранит chunk и embedding без связи с редакцией источника</td><td>Найти stable recordId, sourceVersion и URI исходного документа</td><td>Оставить кандидата для поиска, но не использовать как цитату</td></tr><tr><td>Фрагмент точно отвечает на вопрос, но expiresAt уже прошёл</td><td>Ранжирование не учитывает локальную политику актуальности</td><td>Сравнить expiresAt и зафиксированный retrievedAt</td><td>Отклонить запись и запросить действующую редакцию</td></tr><tr><td>Кандидат закрыт для requester</td><td>Access policy проверяется после генерации или не проверяется</td><td>Сопоставить accessLabels записи и scope запроса до показа excerpt</td><td>Скрыть закрытый текст, сохранить безопасную причину отказа</td></tr><tr><td>Ссылка ведёт на документ, но не на нужный абзац</td><td>В индексе нет citationAnchor</td><td>Открыть URI и проверить точный раздел, строку или якорь</td><td>Остановить ответ до появления адресуемого фрагмента</td></tr><tr><td>Ответ написан, ссылки добавлены позже</td><td>Текст успел включить выводы, которых нет в evidence</td><td>Сравнить каждый claim с источником до публикации</td><td>Сначала собрать допущенные citations, затем писать ответ</td></tr></tbody></table></div><h2>Механизм: запись индекса должна нести provenance</h2><p>Голый фрагмент удобен для векторного поиска, но плох для проверки. Минимальная запись связывает chunk с источником и сохраняет границы его использования. Нужны устойчивый <code>recordId</code>, <code>sourceVersion</code>, <code>publishedAt</code>, <code>indexedAt</code>, <code>expiresAt</code>, класс доступа, <code>citationUri</code> и <code>citationAnchor</code>. Поле <code>vectorScore</code> тоже полезно, но только для порядка кандидатов.</p><p><code>publishedAt</code> отвечает на вопрос, существовала ли редакция к моменту поиска. <code>indexedAt</code> показывает задержку между публикацией и попаданием в индекс. <code>expiresAt</code> выражает локальное правило применимости. <code>retrievedAt</code> фиксирует срез, на котором система приняла решение. Ни одна из этих дат сама по себе не доказывает смысл утверждения. Вместе они позволяют воспроизвести проверку.</p><p>Такая схема важна после chunking. Когда документ режут на части, title и текст часто сохраняют, а версию, владельца и раздел оставляют в исходном хранилище. Затем в ответ попадает удобный snippet, который нельзя связать с конкретной редакцией. Metadata должны наследоваться каждым chunk или однозначно находиться по stable source id. Иначе retrieval создаёт видимость точности, а не проверяемую provenance.</p><figure><img src='/assets/editorial/2025/knowledge-retrieval-2025-query-retrieval-citation.svg' alt='Схема пути от запроса и retrieval через проверки прав и свежести к точной цитате и human verification; при отсутствии условий ответ останавливается.' loading='lazy' /><figcaption>Score сокращает список кандидатов. Ответ появляется только после проверки свежего, разрешённого и адресуемого источника.</figcaption></figure><h2>Пример: фильтр допуска перед формированием ответа</h2><p>Ниже — учебный пример на фиксированных синтетических записях. Он показывает порядок решения и отрицательный путь. Он не обращается к реальной базе, identity provider, часам, production или сети. Синтетические значения нужны только для проверки контракта обработки.</p><pre><code>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.</code></pre><p>Здесь высокий score у <code>adapter-v1</code> не отменяет истечение срока. <code>adapter-v3</code> проходит технический фильтр, но это ещё не автоматическое доказательство ответа. Reviewer должен открыть <code>citationUri#schema-upgrade</code> и проверить, что фрагмент действительно говорит о нужной замене. Если у всех записей истёк срок, нет доступа или отсутствует anchor, функция должна вернуть stop condition. Скрытый fallback на старый текст создаёт именно ту ошибку, от которой защищает схема.</p><h2>Проверка должна идти до генерации текста</h2><p>Безопасный порядок начинается с вопроса и времени retrieval. Затем система сохраняет candidates вместе с score и metadata. После этого она проверяет права, срок и citation. В ответ проходит только допущенная запись. Generator или автор получают ограниченный набор evidence, а не абстрактное «знание базы». Для каждого отклонённого кандидата остаётся причина: <code>access-label-not-granted</code>, <code>expired-at-retrieval-time</code> или <code>missing-exact-citation-anchor</code>.</p><p>Ссылка в конце готового текста не исправляет неверный порядок. Draft уже мог добавить условие, которого нет в документе, или смешать две версии. Citation должна появиться в момент выбора evidence. Тогда reviewer видит claim, sourceVersion, дату и точный fragment, а не пытается восстановить происхождение ответа по памяти.</p><p>HTTP-метаданные помогают, но не заменяют внутреннюю политику. В RFC 9110 <code>Last-Modified</code> описывает время, когда origin server считает изменённым выбранное представление. Это полезный сигнал о представлении, но не вся политика актуальности инженерного правила. Документ может иметь собственный срок пересмотра, дату deprecation или область действия. В ответе нужно назвать, какое условие применялось.</p><h2>Порядок действий</h2><ol><li>Зафиксируйте точный query, requester scope и <code>retrievedAt</code>. Не меняйте эти значения в середине проверки.</li><li>Получите top-k candidates и сохраните для каждого recordId, score, sourceVersion и все поля допуска. Не оставляйте только текстовый snippet.</li><li>Проверьте access до показа excerpt. Неподходящий label превращает запись в diagnostic result, а не в материал для ответа.</li><li>Сравните publishedAt и expiresAt с retrievedAt по заранее объявленной policy. Не используйте «выглядит свежим» как критерий.</li><li>Проверьте citationUri и citationAnchor. Они должны вести к конкретной редакции и месту, которое можно открыть и сопоставить с claim.</li><li>Откройте источник и проверьте смысл утверждения. Зафиксируйте короткое подтверждение или точную границу применимости.</li><li>Если хотя бы одно обязательное свойство отсутствует у всех candidates, верните stop condition с reject reasons и владельцем следующего действия.</li></ol><h2>Что показывать читателю</h2><p>Проверяемый ответ не обязан раскрывать внутренний record целиком. Читателю достаточно названия источника, версии, citation, дат публикации и retrieval, а также краткого ограничения. Конкретные роли, токены и закрытые поля не нужно включать в provenance карточку. Класс доступа можно показать только тогда, когда это разрешает сама policy.</p><p>Отдельно храните result decision и human verification. Статус <code>candidate-found</code> означает, что поиск нашёл похожий материал. Статус <code>citation-accepted</code> означает, что запись прошла технические условия. Это всё ещё не равно «утверждение истинно», пока человек не сопоставил claim с фрагментом. Такое разделение делает интерфейс честнее: пользователь видит, что уже проверено, а что ещё нет.</p><h2>Ограничения и отрицательный путь</h2><p>Эта схема не доказывает полноту корпуса, качество embeddings, корректность access policy или семантическую истинность ответа. Она не говорит, что vector search лучше keyword search. Она задаёт более узкую границу: score не заменяет version, freshness, authorization и citation. Учебный код не является benchmark и не сообщает production-результаты.</p><p>Если источник закрыт, просрочен или не имеет точного anchor, система не должна пересказывать его «для справки». Безопасное действие — показать безопасную причину, сохранить recordId без restricted excerpt и направить вопрос владельцу документа или policy. Если policy неизвестна, нельзя молча выбрать allow или deny как окончательное решение: нужен owner и явное правило. Отрицательный путь входит в контракт наравне с успешным.</p><h2>Проверяемый критерий готовности</h2><p>Один вопрос должен проходить тестовый набор из четырёх случаев: свежая разрешённая запись с anchor, просроченная запись с высоким score, закрытая запись с высоким score и запись без anchor. В первом случае результат содержит citation и требует human verification. В трёх остальных случаях citation не появляется, а decision содержит конкретную причину отказа. Повторный запуск с теми же query, retrievedAt и входными записями даёт тот же decision. Это проверяемый критерий готовности маршрута.</p><p>После этого можно отдельно измерять recall, latency и качество ранжирования. Такие метрики улучшают поиск кандидатов, но не отменяют проверку допуска. Если команда не может показать, почему выбран конкретный fragment и почему отклонены остальные, retrieval ещё не стал надёжным источником ответа.</p><h2>Проверяемые источники</h2><ul><li><a href='https://raw.githubusercontent.com/opensearch-project/k-NN/150c589849a8ec3bc442d830b43a3eaf4e25fa0c/README.md' target='_blank' rel='noopener noreferrer'>OpenSearch k-NN README, immutable commit 150c589</a> — официальное описание nearest-neighbor similarity search и фильтров; источник подтверждает механику поиска кандидатов, но не истинность claim, права или свежесть.</li><li><a href='https://www.rfc-editor.org/rfc/rfc9110.html#section-8.8.2' target='_blank' rel='noopener noreferrer'>IETF RFC 9110, section 8.8.2 Last-Modified</a> — определение HTTP-метаданных изменения представления; источник не задаёт внутреннюю policy актуальности документации.</li><li><a href='https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-207.pdf' target='_blank' rel='noopener noreferrer'>NIST SP 800-207, Zero Trust Architecture</a> — принцип явной проверки доступа к ресурсу; документ не задаёт формат vector index, labels или citation.</li></ul>"
|
||
} |