← Query Engine

Граница с Knowledge Store

query-engine · workzone

Retrieval (поиск и права) принадлежит Knowledge Store; Query Engine — RAG-слой поверх. Страница держит стык: где кончается хранилище и начинается RAG — граница вызова; как личность уходит вниз как вход фильтра; какой сигнал обращений хранилище читает для staleness; и где живая проверка прав достраивает pre-filter в v2. Узел реципрокности модуля: сюда сходятся входящие ссылки KS, Auth и Harvester.

Граница вызова: запрос вниз, кандидаты вверх

Когда chat-модель зовёт search_knowledge, Query Engine вызывает у Knowledge Store собранный hybrid-результат одним запросом — это и есть тело инструмента. Четыре примитива поиска (vector · lexical · graph · sql), их слияние (fusion / RRF) и ACL pre-filter живут в KS целиком; QE их не повторяет и не собирает кросс-сторово. Контракт узкий: вниз уходит запрос с личностью, вверх приходит готовый список кандидатов под правами — дальше начинается RAG.

Граница не пересекается при пустой базе. Если Knowledge Store пуст (is_empty), search_knowledge модели не подан — звать нечего, вызов вниз не идёт, и QE просто возвращает ответ модели из общих знаний.

Knowledge Store retrieval
  • Четыре примитива: vector · lexical · graph · sql.
  • Слияние списков — fusion / RRF в один результат.
  • ACL pre-filter — права как условие запроса, до ранжирования.
  • Отдаёт кандидатов: фрагменты и сущности, под правами.
запрос + identity ▾ ▴ кандидаты под правами
Query Engine RAG
  • Один вызов hybrid-результата — без сборки поиска у себя.
  • Rerank кандидатов — порядок RRF из KS.
  • Упаковка контекста из лучших фрагментов.
  • Генерация AI ответа LLM.
Личность — вход фильтра, не схема прав у QE

Query Engine передаёт вниз личность пользователя — email и его маппинг на source-аккаунты. Knowledge Store разворачивает её в резолв-джойн и фильтрует кандидатов одним SQL до ранжирования.

Query Engine передаёт identity
──▶ email → source-аккаунты
Knowledge Store резолв-джойн + pre-filter
──▶ одним SQL · до ранжирования
кандидаты под правами вход RAG

Источник прав — у других модулей, QE их лишь предъявляет. Harvester захватывает права и личности из источников и пишет в KS; их разворот в доступные сущности — резолюция KS. Платформенную же сторону доступа держит Auth & Security — слой «ACL данных»: в v1 Hybrid, где доступ определяет сам источник; платформенная интерпретация поверх source-прав — v2.

Пометка о скрытом — content-free. Pre-filter убирает запретное насовсем: в списке под правами скрытого нет, и «сколько убрали» из него не видно. Поэтому рядом с кандидатами хранилище отдаёт лёгкую пометку об отфильтрованном-но-релевантном — был ли скрытый кандидат, в какой системе-источнике и кто его автор (author_principal_id, резолвится в email), без заголовка и содержимого. На ней стоит подсказка о скрытом доступе: автор-контакт «к кому идти» есть в v1; формальный грантор «кто выдаёт доступ» потребует нового поля в модели прав — v2.

Аргументы модели — недоверенный ввод. search_knowledge принимает запрос и фильтры как значения, не как SQL, а identity — из сессии, не из аргументов модели. Поэтому prompt-инъекция бессильна: чужого доступа модели не даст (права — не её аргумент), SQL-инъекцию не построит (значение не станет кодом — это забота retrieval-слоя). text-to-SQL не даём осознанно — он открыл бы оба вектора.

Сигнал обращений для staleness → access_counter

Что и как часто запрашивают — видит только Query Engine. Этот сигнал нужен staleness-decay Knowledge Store как вход «частота обращений»: востребованное держит вес, забытое опускается. Носителя в модели KS нет намеренно — единственный, кто видит обращения, этот модуль; он и держит счётчик access_counter (hits, last_accessed_at на сущность). Сам decay и его формула — у Knowledge Store.

«Обращение» ≠ сырой retrieve. Засчитываем, когда сущность реально попала в ответ или по её источнику кликнули. Сырой retrieve шумен: попадание в кандидаты ещё не польза, и считать его — подкреплять то, что лишь однажды всплыло. Каждое засчитанное обращение — дешёвый upsert в access_counter (hits++, last_accessed_at); кросс-модульного вызова на горячем пути ответа нет.

Сигнал не отправляется — читается при пересчёте. Decay — это абсолютный пересчёт trust_score в Curation Pass из текущих входов. Спрос он берёт так же, как авторитет: JOIN'ом к таблице-носителю в момент прогона — authority_tier из Harvester, частота обращений из access_counter Query Engine. Отдельного расписания «отправки» нет: каденс сигнала = каденс Curation Pass, горячий путь ответа и холодный путь доводки развязаны. Давно не запрашиваемое (last_accessed_at давний) затухает к нейтрали — забытое опускается само, без удаления.

access_counter · QE hits · last_accessed_at
──▶ JOIN при пересчёте · как authority_tier
Curation Pass · KS авторитет × свежесть × спрос
──▶ абсолютный пересчёт
entities.trust_score ранжирующий вес
Живая проверка прав на выдачеv2

Pre-filter по сохранённому ACL отвечает быстро, но кэш отстаёт от источника: отозванный доступ может ещё висеть в нашей базе — Harvester синхронизирует права eventually. Чтобы устаревший отзыв не утёк в ответ, для финальных top-N кандидатов делаем живую проверку прав у источника (late-binding security trim): подтверждаем доступ перед самым показом.

pre-filter По сохранённому ACL — весь индекс, один SQL. Быстро, но eventually consistent.
rerank → top-N Кандидаты доведены до горстки финалистов.
live-check у источника Подтверждаем доступ к каждому финалисту перед показом — только на top-N.

Источник не ответил — закрываем (fail-closed). Если в момент проверки источник недоступен (сбой, таймаут), кандидата не показываем: неподтверждённый доступ трактуем как запрет — то же fail-safe-умолчание, что и во всём слое доступа.