АРЧ АП11 - Вынести политику оркестрационной маршрутизации из assistantService в отдельный модуль
This commit is contained in:
parent
606654641b
commit
f1333c457e
|
|
@ -0,0 +1,333 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.createAssistantLivingModePolicy = createAssistantLivingModePolicy;
|
||||||
|
function createAssistantLivingModePolicy(deps) {
|
||||||
|
const { featureAssistantLivingChatRouterV1, compactWhitespace, repairAddressMojibake, toNonEmptyString, normalizeOrganizationScopeValue, hasReferentialPointer, hasSmallTalkSignal, hasAssistantCapabilityQuestionSignal, hasOperationalAdminActionRequestSignal } = deps;
|
||||||
|
function hasStrongDataIntentSignal(text) {
|
||||||
|
const lower = String(text ?? "").toLowerCase();
|
||||||
|
return /(база|док|документ|проводк|контрагент|договор|контракт|счет|сч[её]т|остат|сальдо|хвост|платеж|плат[её]ж|операц|поставщик|клиент|заказчик|дебитор|кредитор|оборот|баланс|период|месяц|год|инн|аванс|предоплат|отгруз|задолж|долг|склад|товар|номенклат|материал|mcp|bank|counterparty|contract|document|ledger|posting|account|organization|company|advance|prepay|shipment|receivab|payab|warehouse|inventory|stock|item|организац|компан|контор|фирм)/i.test(lower);
|
||||||
|
}
|
||||||
|
function hasDataRetrievalRequestSignal(text) {
|
||||||
|
const lower = compactWhitespace(String(text ?? "").toLowerCase());
|
||||||
|
if (!lower) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hasBroadInterrogative = /(?:\u0433\u0434\u0435|\u0432\s+\u043a\u0430\u043a\u0438\u0445|\u043f\u043e\s+\u043a\u0430\u043a\u0438\u043c|\u043f\u043e\s+\u043a\u043e\u043c\u0443|\u043a\u0430\u043a\u0438\u0435|\u043a\u0430\u043a\u043e\u0439|\u043a\u0442\u043e|\u0441\u043a\u043e\u043b\u044c\u043a\u043e|where|which|who|how\s+many)/iu.test(lower);
|
||||||
|
const hasBroadBusinessObject = /(?:\u0430\u0432\u0430\u043d\u0441|\u043f\u0440\u0435\u0434\u043e\u043f\u043b\u0430\u0442|\u043e\u0442\u0433\u0440\u0443\u0437|\u0437\u0430\u0434\u043e\u043b\u0436|\u0434\u043e\u043b\u0433|\u0441\u0430\u043b\u044c\u0434\u043e|\u043e\u043f\u043b\u0430\u0442|\u043f\u043b\u0430\u0442(?:\u0435|\u0451)\u0436|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442|\u0434\u043e\u0433\u043e\u0432\u043e\u0440|\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442|\u0441\u0447(?:\u0435|\u0451)\u0442|\u043e\u0431\u043e\u0440\u043e\u0442|\u043f\u0435\u0440\u0438\u043e\u0434|\u043c\u0435\u0441\u044f\u0446|\u0433\u043e\u0434|\u0441\u043a\u043b\u0430\u0434|\u0442\u043e\u0432\u0430\u0440|\u043d\u043e\u043c\u0435\u043d\u043a\u043b\u0430\u0442|\u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b|advance|prepay|shipment|receivab|payab|counterparty|contract|document|account|balance|turnover|warehouse|inventory|stock|item)/iu.test(lower);
|
||||||
|
if (hasBroadInterrogative && hasBroadBusinessObject) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const hasRussianRetrievalAction = /(?:^|\s)(?:\u043f\u043e\u043a\u0430\u0436\u0438|\u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c|\u043d\u0430\u0439\u0434\u0438|\u0432\u044b\u0432\u0435\u0434\u0438|\u0434\u0430\u0439|\u0440\u0430\u0441\u043a\u0440\u043e\u0439|\u0441\u043f\u0438\u0441\u043e\u043a|\u043f\u0440\u043e\u0432\u0435\u0440\u044c|\u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c)(?:$|[\s,.!?;:])/iu.test(lower);
|
||||||
|
const hasRussianRetrievalObject = /(?:\u0434\u043e\u0433\u043e\u0432\u043e\u0440|\u043a\u043e\u043d\u0442\u0440\u0430\u043a\u0442|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442|\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442|\u0441\u0447(?:\u0435|\u0451)\u0442|\u043e\u0441\u0442\u0430\u0442|\u0441\u0430\u043b\u044c\u0434\u043e|\u043e\u0431\u043e\u0440\u043e\u0442|\u043f\u043b\u0430\u0442(?:\u0435|\u0451)\u0436|\u043e\u043f\u0435\u0440\u0430\u0446|\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|\u043a\u043b\u0438\u0435\u043d\u0442|\u0433\u043e\u0434|\u043f\u0435\u0440\u0438\u043e\u0434|\u043c\u0435\u0441\u044f\u0446|\u0430\u0432\u0430\u043d\u0441|\u043f\u0440\u0435\u0434\u043e\u043f\u043b\u0430\u0442|\u043e\u0442\u0433\u0440\u0443\u0437|\u0437\u0430\u0434\u043e\u043b\u0436|\u0434\u043e\u043b\u0433|\u0441\u043a\u043b\u0430\u0434|\u0442\u043e\u0432\u0430\u0440|\u043d\u043e\u043c\u0435\u043d\u043a\u043b\u0430\u0442|\u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b)/iu.test(lower);
|
||||||
|
if (hasRussianRetrievalAction && hasRussianRetrievalObject) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const hasExplicitRetrievalAction = /(?:\bпокажи\b|\bпоказать\b|\bвыведи\b|\bнайди\b|\bсписок\b|\bдай\b|\bраскрой\b|\bshow\b|\blist\b|\bfind\b|\bcount\b)/i.test(lower);
|
||||||
|
const hasInterrogativeRetrievalAction = /(?:\bсколько\b|\bкакой\b|\bкакая\b|\bкакое\b|\bкакую\b|\bкакие\b|\bкто\b|\bгде\b|\bпо\s+каким\b|\bпо\s+кому\b|\bу\s+кого\b|\bwhich\b|\bwho\b|\bwhere\b)/i.test(lower);
|
||||||
|
if (!hasExplicitRetrievalAction && !hasInterrogativeRetrievalAction) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hasRetrievalObject = /(1с|база|док|документ|контрагент|договор|контракт|счет|сч[её]т|остат|сальдо|хвост|платеж|плат[её]ж|операц|поставщик|клиент|заказчик|дебитор|кредитор|период|месяц|год|инн|аванс|предоплат|отгруз|задолж|долг|склад|товар|номенклат|материал|bank|counterparty|contract|document|account|balance|ledger|posting|advance|prepay|shipment|receivab|payab|warehouse|inventory|stock|item|организац|компан|контор|фирм|возраст|дата\s+регистрац|регистрац|основан)/i.test(lower);
|
||||||
|
if (!hasRetrievalObject) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (hasExplicitRetrievalAction) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const hasMetaCapabilityShape = /(?:мож(?:ем|ешь|ете|но)|уме(?:ешь|ете)|доступ|подключ|чья|как\s+называ(?:ет|ется)|работ(?:ать|аем|аешь|аете)|в\s+тебе|у\s+тебя)/i.test(lower);
|
||||||
|
return !hasMetaCapabilityShape;
|
||||||
|
}
|
||||||
|
function hasOrganizationFactLookupSignal(text) {
|
||||||
|
const repaired = repairAddressMojibake(String(text ?? ""));
|
||||||
|
const normalized = compactWhitespace(repaired.toLowerCase()).replace(/ё/g, "е");
|
||||||
|
if (!normalized) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hasFactCue = /(?:возраст|сколько\s+лет|дата\s+регистрац|когда\s+(?:зарегистр|создан|основан)|год\s+регистрац|год\s+основан|с\s+какого\s+года|when\s+was\s+(?:it\s+)?(?:registered|founded|created))/i.test(normalized);
|
||||||
|
if (!hasFactCue) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return /(?:организац|компан|контор|фирм|ооо|ао|зао|ип|альтернатив|лайсвуд|райм|organization|company)/i.test(normalized);
|
||||||
|
}
|
||||||
|
function findLastAssistantLivingChatDebug(items) {
|
||||||
|
if (!Array.isArray(items)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (let index = items.length - 1; index >= 0; index -= 1) {
|
||||||
|
const item = items[index];
|
||||||
|
if (!item || item.role !== "assistant") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (item.debug && typeof item.debug === "object") {
|
||||||
|
return item.debug;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
function hasMetaAnswerFollowupSignal(userMessage) {
|
||||||
|
const rawText = compactWhitespace(String(userMessage ?? "").toLowerCase());
|
||||||
|
const repairedText = compactWhitespace(repairAddressMojibake(String(userMessage ?? "")).toLowerCase());
|
||||||
|
const samples = [rawText, repairedText]
|
||||||
|
.filter((item) => item.length > 0)
|
||||||
|
.map((item) => item.replace(/ё/g, "е"));
|
||||||
|
if (samples.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hasReflectionCue = samples.some((sample) => sample.includes("дума") ||
|
||||||
|
sample.includes("скаж") ||
|
||||||
|
sample.includes("мнение") ||
|
||||||
|
sample.includes("как тебе") ||
|
||||||
|
sample.includes("норм") ||
|
||||||
|
sample.includes("стран") ||
|
||||||
|
sample.includes("логич") ||
|
||||||
|
sample.includes("смуща") ||
|
||||||
|
sample.includes("выгляд"));
|
||||||
|
const hasTopicPointerCue = samples.some((sample) => sample.includes("на эту тему") ||
|
||||||
|
sample.includes("по этому поводу") ||
|
||||||
|
sample.includes("об этом") ||
|
||||||
|
(sample.includes("это") && hasReferentialPointer(sample)));
|
||||||
|
const hasEvaluationCue = samples.some((sample) => /\b(?:много|мало|нормально|хорошо|плохо|критично|перебор|слабо)\b/iu.test(sample));
|
||||||
|
if (!((hasReflectionCue || hasEvaluationCue) &&
|
||||||
|
(hasTopicPointerCue || (hasEvaluationCue && samples.some((sample) => /^(?:это|ну это)\b/iu.test(sample)))))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return !samples.some((sample) => hasAssistantDataScopeMetaQuestionSignal(sample) ||
|
||||||
|
shouldHandleAsAssistantCapabilityMetaQuery(sample) ||
|
||||||
|
hasDataRetrievalRequestSignal(sample) ||
|
||||||
|
hasStrongDataIntentSignal(sample));
|
||||||
|
}
|
||||||
|
function hasConversationMemoryRecallFollowupSignal(userMessage) {
|
||||||
|
const rawText = compactWhitespace(String(userMessage ?? "").toLowerCase());
|
||||||
|
const repairedText = compactWhitespace(repairAddressMojibake(String(userMessage ?? "")).toLowerCase());
|
||||||
|
const samples = [rawText, repairedText]
|
||||||
|
.filter((item) => item.length > 0)
|
||||||
|
.map((item) => item.replace(/ё/g, "е"));
|
||||||
|
if (samples.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hasMemoryCue = samples.some((sample) => /(?:помни(?:шь|те|м)?|remember|recall)/iu.test(sample));
|
||||||
|
const hasDiscussionCue = samples.some((sample) => /(?:обсуждал[аи]?|говорил[аи]?|смотрел[аи]?|разбирал[аи]?|спрашивал[аи]?)/iu.test(sample));
|
||||||
|
if (!hasMemoryCue || !hasDiscussionCue) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return !samples.some((sample) => hasAssistantDataScopeMetaQuestionSignal(sample) ||
|
||||||
|
shouldHandleAsAssistantCapabilityMetaQuery(sample) ||
|
||||||
|
hasDataRetrievalRequestSignal(sample) ||
|
||||||
|
hasStrongDataIntentSignal(sample));
|
||||||
|
}
|
||||||
|
function hasHistoricalCapabilityFollowupSignal(text) {
|
||||||
|
const repaired = repairAddressMojibake(String(text ?? ""));
|
||||||
|
const normalized = compactWhitespace(repaired.toLowerCase()).replace(/ё/g, "е");
|
||||||
|
if (!normalized) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hasHistoryCue = /(?:историческ|история|архив|прошл(?:ый|ые|ую|ых)?|раньше|ретро|старые\s+данные)/iu.test(normalized);
|
||||||
|
if (!hasHistoryCue) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return /(?:мож(?:ешь|ете|но)|уме(?:ешь|ете)|показ|вывед|дай|раскрой)/iu.test(normalized);
|
||||||
|
}
|
||||||
|
function hasOrganizationFactFollowupSignal(userMessage, items) {
|
||||||
|
const repaired = repairAddressMojibake(String(userMessage ?? ""));
|
||||||
|
const normalized = compactWhitespace(repaired.toLowerCase()).replace(/ё/g, "е");
|
||||||
|
if (!normalized) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (hasOrganizationFactLookupSignal(normalized)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hasFollowupCue = /(?:^|\s)(?:давай|го|погнали|ок(?:ей)?|хорошо|принято|подтверждаю|запрашивай|запроси|проверь|продолжай|ну\s+давай|да\s+давай)(?=$|[\s,.!?;:])/iu.test(normalized);
|
||||||
|
if (!hasFollowupCue) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const lastDebug = findLastAssistantLivingChatDebug(items);
|
||||||
|
const lastSource = toNonEmptyString(lastDebug?.living_chat_response_source);
|
||||||
|
const lastGuardReason = toNonEmptyString(lastDebug?.living_chat_grounding_guard_reason);
|
||||||
|
const inOrganizationFactBoundary = lastSource === "deterministic_organization_fact_boundary" ||
|
||||||
|
lastSource === "deterministic_organization_fact_boundary_followup" ||
|
||||||
|
lastGuardReason === "organization_fact_without_live_source_blocked";
|
||||||
|
return inOrganizationFactBoundary;
|
||||||
|
}
|
||||||
|
function shouldEmitOrganizationSelectionReply(userMessage, selectedOrganization) {
|
||||||
|
const selected = normalizeOrganizationScopeValue(selectedOrganization);
|
||||||
|
if (!selected) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const repaired = repairAddressMojibake(String(userMessage ?? ""));
|
||||||
|
const normalized = compactWhitespace(repaired.toLowerCase()).replace(/ё/g, "е");
|
||||||
|
if (!normalized) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (hasOrganizationFactLookupSignal(normalized) || hasDataRetrievalRequestSignal(normalized) || hasStrongDataIntentSignal(normalized)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hasAnalyticalCue = /(?:какой|какая|какие|когда|сколько|кто|почему|зачем|возраст|дата|регистрац|ндс|налог|контракт|договор|документ|операц|оборот|сумм|остат|сальдо|founded|registered|created)/i.test(normalized);
|
||||||
|
if (hasAnalyticalCue) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hasSelectionCue = /(?:давай|го|погнали|ок(?:ей)?|хорошо|отлично|берем|выберем|выбираем|переключ(?:им|аем|ай)|фиксир|работаем|обсудим|тогда)\b/i.test(normalized);
|
||||||
|
if (hasSelectionCue) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const hasAffectiveReactionCue = /(?:^|[\s,.;:!?()\-])(?:ну|мда|ох|ах|офигеть|офигенно|ахуеть|охуеть|пиздец|пизда|нихуя|хуево|хуёво|ебать|ебан|бля|блять|fuck|shit|damn)(?=$|[\s,.;:!?()\-])/iu.test(normalized) ||
|
||||||
|
normalized.includes("\u0430\u0445\u0443") ||
|
||||||
|
normalized.includes("\u043e\u0445\u0443") ||
|
||||||
|
normalized.includes("\u043f\u0438\u0437\u0434") ||
|
||||||
|
normalized.includes("\u0431\u043b\u044f");
|
||||||
|
if (hasAffectiveReactionCue) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return normalized.length <= 36 && !/[?]/.test(String(userMessage ?? ""));
|
||||||
|
}
|
||||||
|
function hasAssistantDataScopeMetaQuestionSignal(text) {
|
||||||
|
const repaired = repairAddressMojibake(String(text ?? ""));
|
||||||
|
const lower = compactWhitespace(repaired.toLowerCase()).replace(/ё/g, "е");
|
||||||
|
const normalized = lower.replace(/\b1\s*[cс]\b/giu, "1с");
|
||||||
|
if (!normalized) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hasDirectSlangScopeLead = /(?:по\s+каким\s+(?:контор(?:ам|ы|а)?|кантор(?:ам|ы|а)?|компан(?:иям|ии|ию|ия)|организац(?:иям|ии|ию|ия))\s+мож(?:ем|но)\s+(?:общат|работ)|база\s+какой\s+(?:контор|компан|организац|фирм)|какая\s+база\s+(?:подключ|подруб|актив))/iu.test(normalized);
|
||||||
|
if (hasDirectSlangScopeLead) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const hasSlangScopeQuestion = /(?:\u043f\u043e\s+\u043a\u0430\u043a\u0438\u043c\s+(?:\u043a\u043e\u043d\u0442\u043e\u0440(?:\u0430\u043c|\u044b|\u0430)?|\u043a\u043e\u043c\u043f\u0430\u043d(?:\u0438\u044f\u043c|\u0438\u0438|\u0438\u044e|\u0438\u044f)|\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446(?:\u0438\u044f\u043c|\u0438\u0438|\u0438\u044e|\u0438\u044f)|\u0444\u0438\u0440\u043c(?:\u0430\u043c|\u0435|\u0443|\u0430)).*(?:\u043c\u043e\u0436(?:\u0435\u043c|\u043d\u043e)|\u0440\u0430\u0431\u043e\u0442|\u043e\u0431\u0449\u0430\u0442|\u043f\u043e\u0434\u0440\u0443\u0431|\u043f\u043e\u0434\u043a\u043b\u044e\u0447)|(?:\u0431\u0430\u0437\u0430\s+\u043a\u0430\u043a\u043e\u0439\s+(?:\u043a\u043e\u043d\u0442\u043e\u0440|\u043a\u043e\u043c\u043f\u0430\u043d|\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446|\u0444\u0438\u0440\u043c))|(?:\u043a\u0430\u043a\u0430\u044f\s+\u0431\u0430\u0437\u0430\s+(?:\u043f\u043e\u0434\u043a\u043b\u044e\u0447|\u0430\u043a\u0442\u0438\u0432)))/iu.test(normalized);
|
||||||
|
if (hasSlangScopeQuestion) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const hasBaseOrTenantObject = /(?:баз(?:а|е|у|ы)?|тенант|tenant|контур)/i.test(normalized);
|
||||||
|
const hasCompanyObject = /(?:компан(?:ия|ии|ию|ией)|компин(?:ия|ии|ию|ией)?|компини(?:я|и|ю|ей)?|компани[яеию]|организац(?:ия|ии|ию|ией)|контор(?:а|ы|у|ой)?|фирм(?:а|ы|у|ой)?)/i.test(normalized);
|
||||||
|
const hasConnectionCue = /(?:подключен(?:а|о|ы)?|подруб|воткнут|активн(?:ый|ая)\s+канал|mcp-?канал|канал)/i.test(normalized);
|
||||||
|
const hasNamingCue = /(?:как\s+называ(?:ет|ется)|что\s+за\s+(?:контор|компан|организац|фирм))/i.test(normalized);
|
||||||
|
const hasWorkabilityCue = /(?:мож(?:ем|ешь|ете|но)\s+работ|работ(?:ать|аем|аешь|аете))/i.test(normalized);
|
||||||
|
const hasScopeObject = hasBaseOrTenantObject || hasCompanyObject || hasConnectionCue;
|
||||||
|
if (!hasScopeObject) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hasMetaPerspective = /(?:ты|тебе|твой|у\s+тебя|в\s+тебе|мы|нам|наш(?:а|е|и|у|ей)?|сейчас|щас)/i.test(normalized);
|
||||||
|
const hasScopedInterrogativePair = /(?:^|\s)(?:по\s+какой|с\s+какой|какая|какой|какие)\s+(?:баз|компан|компин|компини|компани|организац|контор|фирм|тенант|контур)/i.test(normalized);
|
||||||
|
const hasScopeQuestion = /(?:чья|чье|чьи|доступн|подключен|подруб|воткнут|какая\s+баз|какой\s+баз)/i.test(normalized) ||
|
||||||
|
hasNamingCue ||
|
||||||
|
hasWorkabilityCue ||
|
||||||
|
hasScopedInterrogativePair;
|
||||||
|
const hasInterrogativeScopeLead = /(?:^|\s)(?:по\s+какой|с\s+какой|чья|чье|чьи|which|who|what)/i.test(normalized);
|
||||||
|
const isQuestionLike = /[?]/.test(String(text ?? "")) || hasInterrogativeScopeLead || hasScopedInterrogativePair;
|
||||||
|
const hasExplicitScopeContext = hasBaseOrTenantObject || hasConnectionCue || hasWorkabilityCue || hasNamingCue;
|
||||||
|
const hasRetrievalSignal = hasDataRetrievalRequestSignal(normalized);
|
||||||
|
const hasContractAnalyticsCue = /(?:договор|контракт|contract).*(?:топ|сам(?:ый|ая|ое|ые)|крупн|жирн|оборот|бюджет|сумм|стоим|value|turnover|all\s+time|всю\s+истори|за\s+вс[её]\s+время)/iu.test(normalized);
|
||||||
|
if (hasContractAnalyticsCue) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (hasRetrievalSignal && !hasExplicitScopeContext) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hasEligibleScopeObject = hasBaseOrTenantObject || (hasCompanyObject && (hasConnectionCue || hasWorkabilityCue || hasNamingCue || hasMetaPerspective));
|
||||||
|
return hasEligibleScopeObject && hasScopeQuestion && (hasMetaPerspective || isQuestionLike || hasExplicitScopeContext);
|
||||||
|
}
|
||||||
|
function shouldHandleAsAssistantCapabilityMetaQuery(text) {
|
||||||
|
const raw = String(text ?? "");
|
||||||
|
const repaired = repairAddressMojibake(raw);
|
||||||
|
const hasScopeMetaSignal = hasAssistantDataScopeMetaQuestionSignal(raw) || hasAssistantDataScopeMetaQuestionSignal(repaired);
|
||||||
|
if (hasScopeMetaSignal) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const hasCapabilitySignal = hasAssistantCapabilityQuestionSignal(raw) ||
|
||||||
|
hasAssistantCapabilityQuestionSignal(repaired) ||
|
||||||
|
hasOperationalAdminActionRequestSignal(raw) ||
|
||||||
|
hasOperationalAdminActionRequestSignal(repaired);
|
||||||
|
const hasRetrievalSignal = hasDataRetrievalRequestSignal(raw) || hasDataRetrievalRequestSignal(repaired);
|
||||||
|
return hasCapabilitySignal && !hasRetrievalSignal;
|
||||||
|
}
|
||||||
|
function hasLivingChatSignal(text) {
|
||||||
|
const lower = compactWhitespace(String(text ?? "").toLowerCase());
|
||||||
|
if (!lower) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (/^(?:а\s+)?(?:тут|здесь|там|сюда|туда)[\s!?.,:;\-]*$/iu.test(lower)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (/^(ага|угу|ок|окей|ясно|понял|поняла|принято|спасибо|благодарю|супер|класс|норм|го|давай|погнали|привет|хай|йо|yo|че\s+там|ч[её]\s+как|че\s+как|hello|hi|thanks?)$/i.test(lower)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (/(как дела|как ты|что нового|расскажи о себе|чем можешь помочь|давай поговорим|поговорим|обсудим|посоветуй|что думаешь)/i.test(lower)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return hasSmallTalkSignal(lower);
|
||||||
|
}
|
||||||
|
function resolveLivingAssistantModeDecision(input) {
|
||||||
|
const userMessage = String(input?.userMessage ?? "");
|
||||||
|
if (input?.addressLaneTriggered) {
|
||||||
|
return {
|
||||||
|
mode: "address_data",
|
||||||
|
reason: "address_lane_triggered"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (!featureAssistantLivingChatRouterV1) {
|
||||||
|
return {
|
||||||
|
mode: "deep_analysis",
|
||||||
|
reason: "living_chat_router_disabled"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (Boolean(input?.useMock)) {
|
||||||
|
return {
|
||||||
|
mode: "deep_analysis",
|
||||||
|
reason: "mock_mode_keeps_deep_pipeline"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (hasAssistantDataScopeMetaQuestionSignal(userMessage)) {
|
||||||
|
return {
|
||||||
|
mode: "chat",
|
||||||
|
reason: "assistant_data_scope_query_detected"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (shouldHandleAsAssistantCapabilityMetaQuery(userMessage)) {
|
||||||
|
return {
|
||||||
|
mode: "chat",
|
||||||
|
reason: "assistant_capability_query_detected"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (hasOrganizationFactLookupSignal(userMessage) || hasOrganizationFactFollowupSignal(userMessage)) {
|
||||||
|
return {
|
||||||
|
mode: "chat",
|
||||||
|
reason: "organization_fact_lookup_signal_detected"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (hasStrongDataIntentSignal(userMessage)) {
|
||||||
|
return {
|
||||||
|
mode: "deep_analysis",
|
||||||
|
reason: "strong_data_signal_detected"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (hasLivingChatSignal(userMessage)) {
|
||||||
|
return {
|
||||||
|
mode: "chat",
|
||||||
|
reason: "living_chat_signal_detected"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const predecomposeMode = toNonEmptyString(input?.predecomposeMode);
|
||||||
|
const predecomposeConfidence = toNonEmptyString(input?.predecomposeModeConfidence);
|
||||||
|
if (predecomposeMode === "unsupported" && (predecomposeConfidence === "low" || predecomposeConfidence === "medium")) {
|
||||||
|
return {
|
||||||
|
mode: "deep_analysis",
|
||||||
|
reason: "predecompose_unsupported_mode_fallback_to_deep"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
mode: "deep_analysis",
|
||||||
|
reason: "default_deep_pipeline"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
hasStrongDataIntentSignal,
|
||||||
|
hasDataRetrievalRequestSignal,
|
||||||
|
hasOrganizationFactLookupSignal,
|
||||||
|
hasMetaAnswerFollowupSignal,
|
||||||
|
hasConversationMemoryRecallFollowupSignal,
|
||||||
|
hasHistoricalCapabilityFollowupSignal,
|
||||||
|
hasOrganizationFactFollowupSignal,
|
||||||
|
shouldEmitOrganizationSelectionReply,
|
||||||
|
hasAssistantDataScopeMetaQuestionSignal,
|
||||||
|
shouldHandleAsAssistantCapabilityMetaQuery,
|
||||||
|
hasLivingChatSignal,
|
||||||
|
resolveLivingAssistantModeDecision
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,582 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.createAssistantRoutePolicy = createAssistantRoutePolicy;
|
||||||
|
// @ts-nocheck
|
||||||
|
const ADDRESS_INTENTS_KEEP_ADDRESS_LANE = new Set([
|
||||||
|
"period_coverage_profile",
|
||||||
|
"document_type_and_account_section_profile",
|
||||||
|
"counterparty_population_and_roles",
|
||||||
|
"counterparty_activity_lifecycle",
|
||||||
|
"customer_revenue_and_payments",
|
||||||
|
"supplier_payouts_profile",
|
||||||
|
"open_contracts_confirmed_as_of_date",
|
||||||
|
"list_open_contracts",
|
||||||
|
"open_items_by_counterparty_or_contract",
|
||||||
|
"list_payables_counterparties",
|
||||||
|
"list_receivables_counterparties",
|
||||||
|
"inventory_on_hand_as_of_date",
|
||||||
|
"payables_confirmed_as_of_date",
|
||||||
|
"receivables_confirmed_as_of_date",
|
||||||
|
"list_documents_by_contract",
|
||||||
|
"bank_operations_by_contract",
|
||||||
|
"list_documents_by_counterparty",
|
||||||
|
"bank_operations_by_counterparty",
|
||||||
|
"list_contracts_by_counterparty",
|
||||||
|
"inventory_purchase_provenance_for_item",
|
||||||
|
"inventory_purchase_documents_for_item",
|
||||||
|
"inventory_supplier_stock_overlap_as_of_date",
|
||||||
|
"inventory_sale_trace_for_item",
|
||||||
|
"inventory_profitability_for_item",
|
||||||
|
"inventory_purchase_to_sale_chain",
|
||||||
|
"inventory_aging_by_purchase_date",
|
||||||
|
"contract_usage_overview",
|
||||||
|
"contract_usage_and_value",
|
||||||
|
"vat_payable_forecast",
|
||||||
|
"vat_liability_confirmed_for_tax_period",
|
||||||
|
"vat_payable_confirmed_as_of_date"
|
||||||
|
]);
|
||||||
|
const ADDRESS_INTENTS_ALLOW_STRICT_DEEP_INVESTIGATION_BYPASS = new Set([
|
||||||
|
"inventory_purchase_provenance_for_item",
|
||||||
|
"inventory_purchase_documents_for_item",
|
||||||
|
"inventory_sale_trace_for_item",
|
||||||
|
"inventory_profitability_for_item",
|
||||||
|
"inventory_purchase_to_sale_chain"
|
||||||
|
]);
|
||||||
|
function shouldBypassStrictDeepInvestigationCueForAddressIntent(intent) {
|
||||||
|
return Boolean(intent && ADDRESS_INTENTS_ALLOW_STRICT_DEEP_INVESTIGATION_BYPASS.has(intent));
|
||||||
|
}
|
||||||
|
function createAssistantRoutePolicy(deps) {
|
||||||
|
const { repairAddressMojibake, findLastGroundedAddressAnswerDebug, findLastOrganizationClarificationAddressDebug, mergeKnownOrganizations, normalizeOrganizationScopeValue, resolveOrganizationSelectionFromMessage, hasAssistantDataScopeMetaQuestionSignal, shouldHandleAsAssistantCapabilityMetaQuery, hasDataRetrievalRequestSignal, hasAggregateBusinessAnalyticsSignal, hasStandaloneAddressTopicSignal, hasOpenContractsAddressSignal, detectAddressQuestionMode, resolveAddressIntent, toNonEmptyString, hasStrictDeepInvestigationCue, hasStrongDataIntentSignal, hasAccountingSignal, hasDangerOrCoercionSignal, hasAddressFollowupContextSignal, hasShortDebtMirrorFollowupSignal, isInventorySelectedObjectIntent, hasShortInventoryObjectFollowupSignal, hasHistoricalCapabilityFollowupSignal, isGroundedInventoryContextDebug, hasConversationMemoryRecallFollowupSignal, findLastAddressAssistantItem, hasMetaAnswerFollowupSignal, resolveAddressToolGateDecision, hasSameDateAccountFollowupSignalForPredecompose, hasLooseAllTimeAddressLookupSignal, hasDeepAnalysisPreferenceSignal, hasDirectDeepAnalysisSignal, compactWhitespace, hasDeepSessionContinuationSignal, resolveLivingAssistantModeDecision } = deps;
|
||||||
|
function resolveAssistantOrchestrationDecision(input) {
|
||||||
|
const rawUserMessage = String(input?.rawUserMessage ?? input?.userMessage ?? "");
|
||||||
|
const effectiveAddressUserMessage = String(input?.effectiveAddressUserMessage ?? rawUserMessage);
|
||||||
|
const repairedRawUserMessage = repairAddressMojibake(rawUserMessage);
|
||||||
|
const repairedEffectiveAddressUserMessage = repairAddressMojibake(effectiveAddressUserMessage);
|
||||||
|
const followupContext = input?.followupContext ?? null;
|
||||||
|
const llmPreDecomposeMeta = input?.llmPreDecomposeMeta ?? null;
|
||||||
|
const useMock = Boolean(input?.useMock);
|
||||||
|
const sessionItems = Array.isArray(input?.sessionItems) ? input.sessionItems : null;
|
||||||
|
const sessionOrganizationScope = input?.sessionOrganizationScope && typeof input.sessionOrganizationScope === "object"
|
||||||
|
? input.sessionOrganizationScope
|
||||||
|
: null;
|
||||||
|
const lastGroundedAddressDebug = findLastGroundedAddressAnswerDebug(sessionItems);
|
||||||
|
const lastOrganizationClarificationDebug = findLastOrganizationClarificationAddressDebug(sessionItems);
|
||||||
|
const organizationClarificationCandidates = Array.isArray(lastOrganizationClarificationDebug?.organization_candidates)
|
||||||
|
? mergeKnownOrganizations([
|
||||||
|
...lastOrganizationClarificationDebug.organization_candidates,
|
||||||
|
...((Array.isArray(sessionOrganizationScope?.knownOrganizations)
|
||||||
|
? sessionOrganizationScope.knownOrganizations
|
||||||
|
: []))
|
||||||
|
])
|
||||||
|
: [];
|
||||||
|
const organizationClarificationSelectionFromScope = normalizeOrganizationScopeValue(sessionOrganizationScope?.selectedOrganization);
|
||||||
|
const organizationClarificationSelection = resolveOrganizationSelectionFromMessage(rawUserMessage, organizationClarificationCandidates) ??
|
||||||
|
resolveOrganizationSelectionFromMessage(repairedRawUserMessage, organizationClarificationCandidates) ??
|
||||||
|
resolveOrganizationSelectionFromMessage(effectiveAddressUserMessage, organizationClarificationCandidates) ??
|
||||||
|
resolveOrganizationSelectionFromMessage(repairedEffectiveAddressUserMessage, organizationClarificationCandidates) ??
|
||||||
|
(organizationClarificationSelectionFromScope &&
|
||||||
|
organizationClarificationCandidates.some((candidate) => normalizeOrganizationScopeValue(candidate) === organizationClarificationSelectionFromScope)
|
||||||
|
? organizationClarificationSelectionFromScope
|
||||||
|
: null);
|
||||||
|
const dataScopeMetaQuery = hasAssistantDataScopeMetaQuestionSignal(rawUserMessage) ||
|
||||||
|
hasAssistantDataScopeMetaQuestionSignal(repairedRawUserMessage) ||
|
||||||
|
hasAssistantDataScopeMetaQuestionSignal(effectiveAddressUserMessage) ||
|
||||||
|
hasAssistantDataScopeMetaQuestionSignal(repairedEffectiveAddressUserMessage);
|
||||||
|
const capabilityMetaQuery = shouldHandleAsAssistantCapabilityMetaQuery(rawUserMessage) ||
|
||||||
|
shouldHandleAsAssistantCapabilityMetaQuery(repairedRawUserMessage) ||
|
||||||
|
shouldHandleAsAssistantCapabilityMetaQuery(effectiveAddressUserMessage) ||
|
||||||
|
shouldHandleAsAssistantCapabilityMetaQuery(repairedEffectiveAddressUserMessage);
|
||||||
|
const dataRetrievalSignal = hasDataRetrievalRequestSignal(rawUserMessage) ||
|
||||||
|
hasDataRetrievalRequestSignal(repairedRawUserMessage) ||
|
||||||
|
hasDataRetrievalRequestSignal(effectiveAddressUserMessage) ||
|
||||||
|
hasDataRetrievalRequestSignal(repairedEffectiveAddressUserMessage);
|
||||||
|
const aggregateBusinessAnalyticsSignal = hasAggregateBusinessAnalyticsSignal(rawUserMessage) ||
|
||||||
|
hasAggregateBusinessAnalyticsSignal(repairedRawUserMessage) ||
|
||||||
|
hasAggregateBusinessAnalyticsSignal(effectiveAddressUserMessage) ||
|
||||||
|
hasAggregateBusinessAnalyticsSignal(repairedEffectiveAddressUserMessage);
|
||||||
|
const standaloneAddressTopicSignal = hasStandaloneAddressTopicSignal(rawUserMessage) ||
|
||||||
|
hasStandaloneAddressTopicSignal(repairedRawUserMessage) ||
|
||||||
|
hasStandaloneAddressTopicSignal(effectiveAddressUserMessage) ||
|
||||||
|
hasStandaloneAddressTopicSignal(repairedEffectiveAddressUserMessage);
|
||||||
|
const openContractsAddressSignal = hasOpenContractsAddressSignal(rawUserMessage) ||
|
||||||
|
hasOpenContractsAddressSignal(repairedRawUserMessage) ||
|
||||||
|
hasOpenContractsAddressSignal(effectiveAddressUserMessage) ||
|
||||||
|
hasOpenContractsAddressSignal(repairedEffectiveAddressUserMessage);
|
||||||
|
const modeSample = repairedEffectiveAddressUserMessage || effectiveAddressUserMessage;
|
||||||
|
const modeDetection = detectAddressQuestionMode(modeSample);
|
||||||
|
const modeDetectionRaw = detectAddressQuestionMode(repairedRawUserMessage || rawUserMessage);
|
||||||
|
const resolvedModeDetection = modeDetection.mode === "address_query" ? modeDetection : modeDetectionRaw;
|
||||||
|
const intentResolution = resolveAddressIntent(modeSample);
|
||||||
|
const intentResolutionRaw = resolveAddressIntent(repairedRawUserMessage || rawUserMessage);
|
||||||
|
const resolvedIntentResolution = intentResolution.intent !== "unknown" ? intentResolution : intentResolutionRaw;
|
||||||
|
const llmContractIntent = toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.intent);
|
||||||
|
const llmPreDecomposeReason = toNonEmptyString(llmPreDecomposeMeta?.reason);
|
||||||
|
const llmRuntimeUnavailableDetected = Boolean(llmPreDecomposeReason &&
|
||||||
|
/(?:openai\s+api\s+key\s+is\s+missing|api\s+key\s+is\s+missing|missing\s+api\s+key|authentication)/iu.test(llmPreDecomposeReason));
|
||||||
|
const semanticExtractionContract = llmPreDecomposeMeta?.semanticExtractionContract &&
|
||||||
|
typeof llmPreDecomposeMeta.semanticExtractionContract === "object"
|
||||||
|
? llmPreDecomposeMeta.semanticExtractionContract
|
||||||
|
: null;
|
||||||
|
const semanticContractValid = semanticExtractionContract?.valid !== false;
|
||||||
|
const semanticApplyCanonicalRecommended = semanticExtractionContract?.apply_canonical_recommended !== false;
|
||||||
|
const semanticReasonCodes = Array.isArray(semanticExtractionContract?.reason_codes)
|
||||||
|
? semanticExtractionContract.reason_codes
|
||||||
|
: [];
|
||||||
|
const strictDeepInvestigationCueDetected = hasStrictDeepInvestigationCue(rawUserMessage) ||
|
||||||
|
hasStrictDeepInvestigationCue(repairedRawUserMessage) ||
|
||||||
|
hasStrictDeepInvestigationCue(effectiveAddressUserMessage) ||
|
||||||
|
hasStrictDeepInvestigationCue(repairedEffectiveAddressUserMessage);
|
||||||
|
const strictDeepInvestigationBypassAllowed = shouldBypassStrictDeepInvestigationCueForAddressIntent(resolvedIntentResolution.intent) ||
|
||||||
|
shouldBypassStrictDeepInvestigationCueForAddressIntent(llmContractIntent);
|
||||||
|
const keepAddressLaneByIntent = semanticApplyCanonicalRecommended &&
|
||||||
|
Boolean((resolvedIntentResolution.intent && ADDRESS_INTENTS_KEEP_ADDRESS_LANE.has(resolvedIntentResolution.intent)) ||
|
||||||
|
(llmContractIntent && ADDRESS_INTENTS_KEEP_ADDRESS_LANE.has(llmContractIntent)) ||
|
||||||
|
openContractsAddressSignal) &&
|
||||||
|
(!strictDeepInvestigationCueDetected || strictDeepInvestigationBypassAllowed);
|
||||||
|
const strongDataSignal = hasStrongDataIntentSignal(rawUserMessage) ||
|
||||||
|
hasStrongDataIntentSignal(repairedRawUserMessage) ||
|
||||||
|
hasStrongDataIntentSignal(effectiveAddressUserMessage) ||
|
||||||
|
hasStrongDataIntentSignal(repairedEffectiveAddressUserMessage) ||
|
||||||
|
hasAccountingSignal(rawUserMessage) ||
|
||||||
|
hasAccountingSignal(repairedRawUserMessage) ||
|
||||||
|
hasAccountingSignal(effectiveAddressUserMessage) ||
|
||||||
|
hasAccountingSignal(repairedEffectiveAddressUserMessage) ||
|
||||||
|
hasDataRetrievalRequestSignal(rawUserMessage) ||
|
||||||
|
hasDataRetrievalRequestSignal(repairedRawUserMessage);
|
||||||
|
const llmContractMode = toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.mode);
|
||||||
|
const llmFirstAddressCandidate = Boolean(llmContractMode === "address_query" && llmContractIntent && llmContractIntent !== "unknown");
|
||||||
|
const llmFirstUnsupportedCandidate = Boolean(llmContractMode === "unsupported" &&
|
||||||
|
(!llmContractIntent || llmContractIntent === "unknown"));
|
||||||
|
const dangerOrCoercionSignal = hasDangerOrCoercionSignal(rawUserMessage) ||
|
||||||
|
hasDangerOrCoercionSignal(repairedRawUserMessage) ||
|
||||||
|
hasDangerOrCoercionSignal(effectiveAddressUserMessage) ||
|
||||||
|
hasDangerOrCoercionSignal(repairedEffectiveAddressUserMessage);
|
||||||
|
const explicitAddressFollowupSignal = hasAddressFollowupContextSignal(rawUserMessage) ||
|
||||||
|
hasAddressFollowupContextSignal(repairedRawUserMessage) ||
|
||||||
|
hasAddressFollowupContextSignal(effectiveAddressUserMessage) ||
|
||||||
|
hasAddressFollowupContextSignal(repairedEffectiveAddressUserMessage) ||
|
||||||
|
hasShortDebtMirrorFollowupSignal(rawUserMessage) ||
|
||||||
|
hasShortDebtMirrorFollowupSignal(repairedRawUserMessage) ||
|
||||||
|
hasShortDebtMirrorFollowupSignal(effectiveAddressUserMessage) ||
|
||||||
|
hasShortDebtMirrorFollowupSignal(repairedEffectiveAddressUserMessage);
|
||||||
|
const protectedInventoryShortFollowup = Boolean(followupContext &&
|
||||||
|
isInventorySelectedObjectIntent(toNonEmptyString(followupContext.previous_intent)) &&
|
||||||
|
(hasShortInventoryObjectFollowupSignal(rawUserMessage) ||
|
||||||
|
hasShortInventoryObjectFollowupSignal(repairedRawUserMessage) ||
|
||||||
|
hasShortInventoryObjectFollowupSignal(effectiveAddressUserMessage) ||
|
||||||
|
hasShortInventoryObjectFollowupSignal(repairedEffectiveAddressUserMessage)));
|
||||||
|
const organizationClarificationContinuationDetected = Boolean(followupContext &&
|
||||||
|
lastOrganizationClarificationDebug &&
|
||||||
|
organizationClarificationSelection &&
|
||||||
|
!dataScopeMetaQuery &&
|
||||||
|
!capabilityMetaQuery &&
|
||||||
|
!dataRetrievalSignal);
|
||||||
|
const effectiveAddressFollowupSignal = explicitAddressFollowupSignal && !dangerOrCoercionSignal;
|
||||||
|
const deterministicNonDomainGuard = Boolean(!dataScopeMetaQuery &&
|
||||||
|
!capabilityMetaQuery &&
|
||||||
|
!dataRetrievalSignal &&
|
||||||
|
!effectiveAddressFollowupSignal &&
|
||||||
|
resolvedModeDetection.mode === "unsupported" &&
|
||||||
|
resolvedIntentResolution.intent === "unknown");
|
||||||
|
const nonDomainQueryIndexed = Boolean(!llmFirstAddressCandidate &&
|
||||||
|
deterministicNonDomainGuard &&
|
||||||
|
(llmFirstUnsupportedCandidate || llmContractMode === null) &&
|
||||||
|
!protectedInventoryShortFollowup &&
|
||||||
|
!organizationClarificationContinuationDetected);
|
||||||
|
const contextualHistoricalCapabilityFollowupDetected = Boolean(capabilityMetaQuery &&
|
||||||
|
!dataScopeMetaQuery &&
|
||||||
|
!dataRetrievalSignal &&
|
||||||
|
(hasHistoricalCapabilityFollowupSignal(rawUserMessage) ||
|
||||||
|
hasHistoricalCapabilityFollowupSignal(repairedRawUserMessage) ||
|
||||||
|
hasHistoricalCapabilityFollowupSignal(effectiveAddressUserMessage) ||
|
||||||
|
hasHistoricalCapabilityFollowupSignal(repairedEffectiveAddressUserMessage)) &&
|
||||||
|
isGroundedInventoryContextDebug(lastGroundedAddressDebug));
|
||||||
|
const contextualMemoryRecapFollowupDetected = Boolean(!dataScopeMetaQuery &&
|
||||||
|
!capabilityMetaQuery &&
|
||||||
|
!dataRetrievalSignal &&
|
||||||
|
!strongDataSignal &&
|
||||||
|
!aggregateBusinessAnalyticsSignal &&
|
||||||
|
(hasConversationMemoryRecallFollowupSignal(rawUserMessage) ||
|
||||||
|
hasConversationMemoryRecallFollowupSignal(repairedRawUserMessage) ||
|
||||||
|
hasConversationMemoryRecallFollowupSignal(effectiveAddressUserMessage) ||
|
||||||
|
hasConversationMemoryRecallFollowupSignal(repairedEffectiveAddressUserMessage)) &&
|
||||||
|
(lastGroundedAddressDebug ||
|
||||||
|
findLastAddressAssistantItem(sessionItems)?.debug));
|
||||||
|
const hardMetaMode = dataScopeMetaQuery
|
||||||
|
? "data_scope"
|
||||||
|
: capabilityMetaQuery && !dataRetrievalSignal
|
||||||
|
? "capability"
|
||||||
|
: null;
|
||||||
|
if (hardMetaMode === "data_scope") {
|
||||||
|
return {
|
||||||
|
runAddressLane: false,
|
||||||
|
toolGateDecision: "skip_address_lane",
|
||||||
|
toolGateReason: "assistant_data_scope_query_detected",
|
||||||
|
livingMode: "chat",
|
||||||
|
livingReason: "assistant_data_scope_query_detected",
|
||||||
|
orchestrationContract: {
|
||||||
|
schema_version: "assistant_orchestration_contract_v1",
|
||||||
|
hard_meta_mode: "data_scope",
|
||||||
|
address_mode: resolvedModeDetection.mode,
|
||||||
|
address_mode_confidence: resolvedModeDetection.confidence,
|
||||||
|
address_intent: resolvedIntentResolution.intent,
|
||||||
|
address_intent_confidence: resolvedIntentResolution.confidence,
|
||||||
|
strong_data_signal_detected: strongDataSignal,
|
||||||
|
data_retrieval_signal_detected: dataRetrievalSignal,
|
||||||
|
followup_context_detected: Boolean(followupContext),
|
||||||
|
unsupported_address_intent_fallback_to_deep: false,
|
||||||
|
final_decision: {
|
||||||
|
run_address_lane: false,
|
||||||
|
tool_gate_decision: "skip_address_lane",
|
||||||
|
tool_gate_reason: "assistant_data_scope_query_detected",
|
||||||
|
living_mode: "chat",
|
||||||
|
living_reason: "assistant_data_scope_query_detected"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (hardMetaMode === "capability") {
|
||||||
|
if (contextualHistoricalCapabilityFollowupDetected) {
|
||||||
|
return {
|
||||||
|
runAddressLane: false,
|
||||||
|
toolGateDecision: "skip_address_lane",
|
||||||
|
toolGateReason: "inventory_history_capability_followup_detected",
|
||||||
|
livingMode: "chat",
|
||||||
|
livingReason: "inventory_history_capability_followup_detected",
|
||||||
|
orchestrationContract: {
|
||||||
|
schema_version: "assistant_orchestration_contract_v1",
|
||||||
|
hard_meta_mode: "capability",
|
||||||
|
address_mode: resolvedModeDetection.mode,
|
||||||
|
address_mode_confidence: resolvedModeDetection.confidence,
|
||||||
|
address_intent: resolvedIntentResolution.intent,
|
||||||
|
address_intent_confidence: resolvedIntentResolution.confidence,
|
||||||
|
strong_data_signal_detected: strongDataSignal,
|
||||||
|
data_retrieval_signal_detected: dataRetrievalSignal,
|
||||||
|
followup_context_detected: Boolean(followupContext || lastGroundedAddressDebug),
|
||||||
|
unsupported_address_intent_fallback_to_deep: false,
|
||||||
|
final_decision: {
|
||||||
|
run_address_lane: false,
|
||||||
|
tool_gate_decision: "skip_address_lane",
|
||||||
|
tool_gate_reason: "inventory_history_capability_followup_detected",
|
||||||
|
living_mode: "chat",
|
||||||
|
living_reason: "inventory_history_capability_followup_detected"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
runAddressLane: false,
|
||||||
|
toolGateDecision: "skip_address_lane",
|
||||||
|
toolGateReason: "assistant_capability_query_detected",
|
||||||
|
livingMode: "chat",
|
||||||
|
livingReason: "assistant_capability_query_detected",
|
||||||
|
orchestrationContract: {
|
||||||
|
schema_version: "assistant_orchestration_contract_v1",
|
||||||
|
hard_meta_mode: "capability",
|
||||||
|
address_mode: resolvedModeDetection.mode,
|
||||||
|
address_mode_confidence: resolvedModeDetection.confidence,
|
||||||
|
address_intent: resolvedIntentResolution.intent,
|
||||||
|
address_intent_confidence: resolvedIntentResolution.confidence,
|
||||||
|
strong_data_signal_detected: strongDataSignal,
|
||||||
|
data_retrieval_signal_detected: dataRetrievalSignal,
|
||||||
|
followup_context_detected: Boolean(followupContext),
|
||||||
|
unsupported_address_intent_fallback_to_deep: false,
|
||||||
|
final_decision: {
|
||||||
|
run_address_lane: false,
|
||||||
|
tool_gate_decision: "skip_address_lane",
|
||||||
|
tool_gate_reason: "assistant_capability_query_detected",
|
||||||
|
living_mode: "chat",
|
||||||
|
living_reason: "assistant_capability_query_detected"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (nonDomainQueryIndexed) {
|
||||||
|
if (contextualMemoryRecapFollowupDetected) {
|
||||||
|
return {
|
||||||
|
runAddressLane: false,
|
||||||
|
toolGateDecision: "skip_address_lane",
|
||||||
|
toolGateReason: "memory_recap_followup_detected",
|
||||||
|
livingMode: "chat",
|
||||||
|
livingReason: "memory_recap_followup_detected",
|
||||||
|
orchestrationContract: {
|
||||||
|
schema_version: "assistant_orchestration_contract_v1",
|
||||||
|
hard_meta_mode: "non_domain",
|
||||||
|
address_mode: resolvedModeDetection.mode,
|
||||||
|
address_mode_confidence: resolvedModeDetection.confidence,
|
||||||
|
address_intent: resolvedIntentResolution.intent,
|
||||||
|
address_intent_confidence: resolvedIntentResolution.confidence,
|
||||||
|
strong_data_signal_detected: strongDataSignal,
|
||||||
|
data_retrieval_signal_detected: dataRetrievalSignal,
|
||||||
|
followup_context_detected: Boolean(followupContext || lastGroundedAddressDebug),
|
||||||
|
unsupported_address_intent_fallback_to_deep: false,
|
||||||
|
final_decision: {
|
||||||
|
run_address_lane: false,
|
||||||
|
tool_gate_decision: "skip_address_lane",
|
||||||
|
tool_gate_reason: "memory_recap_followup_detected",
|
||||||
|
living_mode: "chat",
|
||||||
|
living_reason: "memory_recap_followup_detected"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
runAddressLane: false,
|
||||||
|
toolGateDecision: "skip_address_lane",
|
||||||
|
toolGateReason: "non_domain_query_indexed",
|
||||||
|
livingMode: "chat",
|
||||||
|
livingReason: "non_domain_query_indexed",
|
||||||
|
orchestrationContract: {
|
||||||
|
schema_version: "assistant_orchestration_contract_v1",
|
||||||
|
hard_meta_mode: "non_domain",
|
||||||
|
address_mode: resolvedModeDetection.mode,
|
||||||
|
address_mode_confidence: resolvedModeDetection.confidence,
|
||||||
|
address_intent: resolvedIntentResolution.intent,
|
||||||
|
address_intent_confidence: resolvedIntentResolution.confidence,
|
||||||
|
strong_data_signal_detected: strongDataSignal,
|
||||||
|
data_retrieval_signal_detected: dataRetrievalSignal,
|
||||||
|
followup_context_detected: Boolean(followupContext),
|
||||||
|
unsupported_address_intent_fallback_to_deep: false,
|
||||||
|
final_decision: {
|
||||||
|
run_address_lane: false,
|
||||||
|
tool_gate_decision: "skip_address_lane",
|
||||||
|
tool_gate_reason: "non_domain_query_indexed",
|
||||||
|
living_mode: "chat",
|
||||||
|
living_reason: "non_domain_query_indexed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const metaAnswerFollowupSignal = hasMetaAnswerFollowupSignal(rawUserMessage) ||
|
||||||
|
hasMetaAnswerFollowupSignal(repairedRawUserMessage) ||
|
||||||
|
hasMetaAnswerFollowupSignal(effectiveAddressUserMessage) ||
|
||||||
|
hasMetaAnswerFollowupSignal(repairedEffectiveAddressUserMessage);
|
||||||
|
const baseToolGate = resolveAddressToolGateDecision(effectiveAddressUserMessage, followupContext, llmPreDecomposeMeta, rawUserMessage);
|
||||||
|
const preserveAddressLaneSignal = Boolean((llmPreDecomposeMeta?.llmCanonicalCandidateDetected &&
|
||||||
|
llmPreDecomposeMeta?.applied &&
|
||||||
|
llmContractMode === "address_query") ||
|
||||||
|
hasSameDateAccountFollowupSignalForPredecompose(rawUserMessage) ||
|
||||||
|
hasSameDateAccountFollowupSignalForPredecompose(effectiveAddressUserMessage) ||
|
||||||
|
hasSameDateAccountFollowupSignalForPredecompose(repairedRawUserMessage) ||
|
||||||
|
hasSameDateAccountFollowupSignalForPredecompose(repairedEffectiveAddressUserMessage) ||
|
||||||
|
hasLooseAllTimeAddressLookupSignal(rawUserMessage) ||
|
||||||
|
hasLooseAllTimeAddressLookupSignal(effectiveAddressUserMessage) ||
|
||||||
|
hasLooseAllTimeAddressLookupSignal(repairedRawUserMessage) ||
|
||||||
|
hasLooseAllTimeAddressLookupSignal(repairedEffectiveAddressUserMessage) ||
|
||||||
|
hasAddressFollowupContextSignal(rawUserMessage) ||
|
||||||
|
hasAddressFollowupContextSignal(effectiveAddressUserMessage) ||
|
||||||
|
hasAddressFollowupContextSignal(repairedRawUserMessage) ||
|
||||||
|
hasAddressFollowupContextSignal(repairedEffectiveAddressUserMessage) ||
|
||||||
|
hasShortDebtMirrorFollowupSignal(rawUserMessage) ||
|
||||||
|
hasShortDebtMirrorFollowupSignal(effectiveAddressUserMessage) ||
|
||||||
|
hasShortDebtMirrorFollowupSignal(repairedRawUserMessage) ||
|
||||||
|
hasShortDebtMirrorFollowupSignal(repairedEffectiveAddressUserMessage));
|
||||||
|
const supportedAddressIntentDetected = (!strictDeepInvestigationCueDetected || strictDeepInvestigationBypassAllowed) &&
|
||||||
|
Boolean((resolvedIntentResolution.intent && ADDRESS_INTENTS_KEEP_ADDRESS_LANE.has(resolvedIntentResolution.intent)) ||
|
||||||
|
(llmContractIntent && ADDRESS_INTENTS_KEEP_ADDRESS_LANE.has(llmContractIntent)) ||
|
||||||
|
openContractsAddressSignal);
|
||||||
|
const semanticGuardHints = semanticExtractionContract?.guard_hints &&
|
||||||
|
typeof semanticExtractionContract.guard_hints === "object"
|
||||||
|
? semanticExtractionContract.guard_hints
|
||||||
|
: null;
|
||||||
|
const semanticExtraction = semanticExtractionContract?.extraction &&
|
||||||
|
typeof semanticExtractionContract.extraction === "object"
|
||||||
|
? semanticExtractionContract.extraction
|
||||||
|
: null;
|
||||||
|
const semanticDeepInvestigationHintDetected = semanticGuardHints?.deep_investigation_signal_detected === true;
|
||||||
|
const semanticAggregateShapeDetected = semanticExtraction?.query_shape === "AGGREGATE_LOOKUP" ||
|
||||||
|
semanticExtraction?.aggregation_profile === "management_profile";
|
||||||
|
const rootContextOnlyFollowup = Boolean(followupContext && followupContext.root_context_only === true);
|
||||||
|
const followupSemanticOverrideToDeepAllowed = Boolean(followupContext &&
|
||||||
|
!supportedAddressIntentDetected &&
|
||||||
|
(rootContextOnlyFollowup ||
|
||||||
|
llmContractMode === "unsupported" ||
|
||||||
|
semanticAggregateShapeDetected ||
|
||||||
|
semanticDeepInvestigationHintDetected ||
|
||||||
|
!semanticApplyCanonicalRecommended));
|
||||||
|
const unsupportedIntentOrMode = (resolvedModeDetection.mode !== "address_query" && resolvedIntentResolution.intent === "unknown") ||
|
||||||
|
llmContractMode === "unsupported" ||
|
||||||
|
(rootContextOnlyFollowup &&
|
||||||
|
resolvedIntentResolution.intent === "unknown" &&
|
||||||
|
(!llmContractIntent || llmContractIntent === "unknown"));
|
||||||
|
const unsupportedAddressIntentFallbackToDeep = Boolean(baseToolGate?.runAddressLane &&
|
||||||
|
!llmRuntimeUnavailableDetected &&
|
||||||
|
unsupportedIntentOrMode &&
|
||||||
|
strongDataSignal &&
|
||||||
|
(rootContextOnlyFollowup ||
|
||||||
|
llmContractMode === "deep_analysis" ||
|
||||||
|
!dataRetrievalSignal ||
|
||||||
|
strictDeepInvestigationCueDetected ||
|
||||||
|
semanticDeepInvestigationHintDetected ||
|
||||||
|
aggregateBusinessAnalyticsSignal) &&
|
||||||
|
!preserveAddressLaneSignal &&
|
||||||
|
!keepAddressLaneByIntent &&
|
||||||
|
!supportedAddressIntentDetected &&
|
||||||
|
(!followupContext || followupSemanticOverrideToDeepAllowed));
|
||||||
|
const deepAnalysisPreferenceDetected = Boolean(hasDeepAnalysisPreferenceSignal(rawUserMessage) ||
|
||||||
|
hasDeepAnalysisPreferenceSignal(repairedRawUserMessage) ||
|
||||||
|
hasDeepAnalysisPreferenceSignal(effectiveAddressUserMessage) ||
|
||||||
|
hasDeepAnalysisPreferenceSignal(repairedEffectiveAddressUserMessage) ||
|
||||||
|
hasDirectDeepAnalysisSignal(rawUserMessage) ||
|
||||||
|
hasDirectDeepAnalysisSignal(repairedRawUserMessage) ||
|
||||||
|
hasDirectDeepAnalysisSignal(effectiveAddressUserMessage) ||
|
||||||
|
hasDirectDeepAnalysisSignal(repairedEffectiveAddressUserMessage));
|
||||||
|
const vatExplainFollowupSignal = Boolean(followupContext &&
|
||||||
|
toNonEmptyString(followupContext.previous_intent) === "vat_payable_forecast" &&
|
||||||
|
/(?:\u043f\u043e\u0447\u0435\u043c\u0443|why).*(?:\u043f\u0440\u043e\u0433\u043d\u043e\u0437|forecast).*(?:\u0443\u043f\u043b\u0430\u0442|payable|\b0\b)/iu.test(compactWhitespace(`${repairedRawUserMessage} ${repairedEffectiveAddressUserMessage}`)));
|
||||||
|
const vatEvaluativeFollowupSignal = Boolean(followupContext &&
|
||||||
|
toNonEmptyString(followupContext.previous_intent) === "vat_payable_forecast" &&
|
||||||
|
/(?:^|\s)(?:это\s+)?много\s+или\s+мало(?:\?|$)|(?:^|\s)(?:это\s+)?нормально(?:\?|$)|(?:^|\s)(?:это\s+)?плохо(?:\?|$)|(?:^|\s)(?:это\s+)?хорошо(?:\?|$)/iu.test(compactWhitespace(`${repairedRawUserMessage} ${repairedEffectiveAddressUserMessage}`)));
|
||||||
|
const deepAnalysisSignalFallbackToDeep = Boolean(baseToolGate?.runAddressLane &&
|
||||||
|
!llmRuntimeUnavailableDetected &&
|
||||||
|
(deepAnalysisPreferenceDetected || semanticDeepInvestigationHintDetected) &&
|
||||||
|
!keepAddressLaneByIntent &&
|
||||||
|
!supportedAddressIntentDetected &&
|
||||||
|
!vatExplainFollowupSignal &&
|
||||||
|
(!followupContext || !dataRetrievalSignal || followupSemanticOverrideToDeepAllowed));
|
||||||
|
const aggregateAnalyticsFallbackToDeep = Boolean(baseToolGate?.runAddressLane &&
|
||||||
|
!llmRuntimeUnavailableDetected &&
|
||||||
|
aggregateBusinessAnalyticsSignal &&
|
||||||
|
!keepAddressLaneByIntent &&
|
||||||
|
!supportedAddressIntentDetected &&
|
||||||
|
(!followupContext ||
|
||||||
|
llmContractMode === "unsupported" ||
|
||||||
|
semanticAggregateShapeDetected ||
|
||||||
|
!semanticApplyCanonicalRecommended ||
|
||||||
|
standaloneAddressTopicSignal));
|
||||||
|
const deepSessionContinuationFallbackToDeep = Boolean(!followupContext &&
|
||||||
|
baseToolGate?.runAddressLane &&
|
||||||
|
!llmRuntimeUnavailableDetected &&
|
||||||
|
hasDeepSessionContinuationSignal({
|
||||||
|
rawUserMessage,
|
||||||
|
repairedRawUserMessage,
|
||||||
|
effectiveAddressUserMessage,
|
||||||
|
repairedEffectiveAddressUserMessage,
|
||||||
|
sessionItems
|
||||||
|
}));
|
||||||
|
const hasPriorAddressAnswerContext = Boolean(lastGroundedAddressDebug || toNonEmptyString(followupContext?.previous_intent));
|
||||||
|
const metaFollowupOverGroundedAnswer = Boolean(followupContext &&
|
||||||
|
hasPriorAddressAnswerContext &&
|
||||||
|
(metaAnswerFollowupSignal || vatEvaluativeFollowupSignal) &&
|
||||||
|
!dataScopeMetaQuery &&
|
||||||
|
!capabilityMetaQuery &&
|
||||||
|
!aggregateBusinessAnalyticsSignal &&
|
||||||
|
!dataRetrievalSignal &&
|
||||||
|
!strongDataSignal &&
|
||||||
|
resolvedModeDetection.mode !== "address_query" &&
|
||||||
|
resolvedIntentResolution.intent === "unknown" &&
|
||||||
|
(!llmContractIntent || llmContractIntent === "unknown") &&
|
||||||
|
llmContractMode !== "address_query");
|
||||||
|
let runAddressLane = Boolean(baseToolGate?.runAddressLane);
|
||||||
|
let toolGateDecision = String(baseToolGate?.decision ?? "skip_address_lane");
|
||||||
|
let toolGateReason = String(baseToolGate?.reason ?? "no_address_signal_after_l0");
|
||||||
|
if (unsupportedAddressIntentFallbackToDeep) {
|
||||||
|
runAddressLane = false;
|
||||||
|
toolGateDecision = "skip_address_lane";
|
||||||
|
toolGateReason = "address_signal_unsupported_intent_fallback_to_deep";
|
||||||
|
}
|
||||||
|
if (deepAnalysisSignalFallbackToDeep && !unsupportedAddressIntentFallbackToDeep) {
|
||||||
|
runAddressLane = false;
|
||||||
|
toolGateDecision = "skip_address_lane";
|
||||||
|
toolGateReason = "deep_analysis_signal_fallback_to_deep";
|
||||||
|
}
|
||||||
|
if (aggregateAnalyticsFallbackToDeep &&
|
||||||
|
!unsupportedAddressIntentFallbackToDeep &&
|
||||||
|
!deepAnalysisSignalFallbackToDeep) {
|
||||||
|
runAddressLane = false;
|
||||||
|
toolGateDecision = "skip_address_lane";
|
||||||
|
toolGateReason = "aggregate_analytics_signal_fallback_to_deep";
|
||||||
|
}
|
||||||
|
if (deepSessionContinuationFallbackToDeep) {
|
||||||
|
runAddressLane = false;
|
||||||
|
toolGateDecision = "skip_address_lane";
|
||||||
|
toolGateReason = "deep_session_continuation_fallback_to_deep";
|
||||||
|
}
|
||||||
|
if (metaFollowupOverGroundedAnswer) {
|
||||||
|
runAddressLane = false;
|
||||||
|
toolGateDecision = "skip_address_lane";
|
||||||
|
toolGateReason = "meta_followup_over_grounded_answer";
|
||||||
|
}
|
||||||
|
let livingDecision = resolveLivingAssistantModeDecision({
|
||||||
|
userMessage: rawUserMessage,
|
||||||
|
addressLaneTriggered: runAddressLane,
|
||||||
|
useMock,
|
||||||
|
predecomposeMode: llmPreDecomposeMeta?.predecomposeContract?.mode ?? null,
|
||||||
|
predecomposeModeConfidence: llmPreDecomposeMeta?.predecomposeContract?.mode_confidence ?? null
|
||||||
|
});
|
||||||
|
if (unsupportedAddressIntentFallbackToDeep) {
|
||||||
|
livingDecision = {
|
||||||
|
mode: "deep_analysis",
|
||||||
|
reason: "unsupported_address_intent_fallback_to_deep"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (deepAnalysisSignalFallbackToDeep && !unsupportedAddressIntentFallbackToDeep) {
|
||||||
|
livingDecision = {
|
||||||
|
mode: "deep_analysis",
|
||||||
|
reason: "deep_analysis_signal_fallback_to_deep"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (aggregateAnalyticsFallbackToDeep &&
|
||||||
|
!unsupportedAddressIntentFallbackToDeep &&
|
||||||
|
!deepAnalysisSignalFallbackToDeep) {
|
||||||
|
livingDecision = {
|
||||||
|
mode: "deep_analysis",
|
||||||
|
reason: "aggregate_analytics_signal_fallback_to_deep"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (deepSessionContinuationFallbackToDeep) {
|
||||||
|
livingDecision = {
|
||||||
|
mode: "deep_analysis",
|
||||||
|
reason: "deep_session_continuation_fallback_to_deep"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (metaFollowupOverGroundedAnswer) {
|
||||||
|
livingDecision = {
|
||||||
|
mode: "chat",
|
||||||
|
reason: "meta_followup_over_grounded_answer"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
runAddressLane,
|
||||||
|
toolGateDecision,
|
||||||
|
toolGateReason,
|
||||||
|
livingMode: livingDecision.mode,
|
||||||
|
livingReason: livingDecision.reason,
|
||||||
|
orchestrationContract: {
|
||||||
|
schema_version: "assistant_orchestration_contract_v1",
|
||||||
|
hard_meta_mode: null,
|
||||||
|
address_mode: resolvedModeDetection.mode,
|
||||||
|
address_mode_confidence: resolvedModeDetection.confidence,
|
||||||
|
address_intent: resolvedIntentResolution.intent,
|
||||||
|
address_intent_confidence: resolvedIntentResolution.confidence,
|
||||||
|
strong_data_signal_detected: strongDataSignal,
|
||||||
|
data_retrieval_signal_detected: dataRetrievalSignal,
|
||||||
|
semantic_contract_valid: semanticContractValid,
|
||||||
|
semantic_apply_canonical_recommended: semanticApplyCanonicalRecommended,
|
||||||
|
semantic_reason_codes: semanticReasonCodes,
|
||||||
|
semantic_route_arbitration: {
|
||||||
|
supported_address_intent_detected: supportedAddressIntentDetected,
|
||||||
|
strict_deep_investigation_bypass_allowed: strictDeepInvestigationBypassAllowed,
|
||||||
|
semantic_deep_investigation_hint_detected: semanticDeepInvestigationHintDetected,
|
||||||
|
semantic_aggregate_shape_detected: semanticAggregateShapeDetected,
|
||||||
|
followup_semantic_override_to_deep_allowed: followupSemanticOverrideToDeepAllowed
|
||||||
|
},
|
||||||
|
followup_context_detected: Boolean(followupContext),
|
||||||
|
unsupported_address_intent_fallback_to_deep: unsupportedAddressIntentFallbackToDeep,
|
||||||
|
deep_analysis_signal_fallback_to_deep: deepAnalysisSignalFallbackToDeep,
|
||||||
|
aggregate_analytics_signal_fallback_to_deep: aggregateAnalyticsFallbackToDeep,
|
||||||
|
deep_session_continuation_fallback_to_deep: deepSessionContinuationFallbackToDeep,
|
||||||
|
final_decision: {
|
||||||
|
run_address_lane: runAddressLane,
|
||||||
|
tool_gate_decision: toolGateDecision,
|
||||||
|
tool_gate_reason: toolGateReason,
|
||||||
|
living_mode: livingDecision.mode,
|
||||||
|
living_reason: livingDecision.reason
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
resolveAssistantOrchestrationDecision
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -67,6 +67,8 @@ const assistantAddressAttemptRuntimeAdapter_1 = __importStar(require("./assistan
|
||||||
const assistantCoverageGrounding_1 = __importStar(require("./assistantCoverageGrounding"));
|
const assistantCoverageGrounding_1 = __importStar(require("./assistantCoverageGrounding"));
|
||||||
const assistantDeepTurnAttemptRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnAttemptRuntimeAdapter"));
|
const assistantDeepTurnAttemptRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnAttemptRuntimeAdapter"));
|
||||||
const assistantBoundaryPolicy_1 = __importStar(require("./assistantBoundaryPolicy"));
|
const assistantBoundaryPolicy_1 = __importStar(require("./assistantBoundaryPolicy"));
|
||||||
|
const assistantLivingModePolicy_1 = __importStar(require("./assistantLivingModePolicy"));
|
||||||
|
const assistantRoutePolicy_1 = __importStar(require("./assistantRoutePolicy"));
|
||||||
const assistantOrganizationScopeRuntimeAdapter_1 = __importStar(require("./assistantOrganizationScopeRuntimeAdapter"));
|
const assistantOrganizationScopeRuntimeAdapter_1 = __importStar(require("./assistantOrganizationScopeRuntimeAdapter"));
|
||||||
const assistantTurnAttemptRuntimeAdapter_1 = __importStar(require("./assistantTurnAttemptRuntimeAdapter"));
|
const assistantTurnAttemptRuntimeAdapter_1 = __importStar(require("./assistantTurnAttemptRuntimeAdapter"));
|
||||||
const assistantTurnRuntimeDepsAdapter_1 = __importStar(require("./assistantTurnRuntimeDepsAdapter"));
|
const assistantTurnRuntimeDepsAdapter_1 = __importStar(require("./assistantTurnRuntimeDepsAdapter"));
|
||||||
|
|
@ -4252,623 +4254,17 @@ function hasOpenContractsAddressSignal(text) {
|
||||||
const hasTemporalCue = hasPeriodLiteral(normalized) || /\b\d{4}[-/.]\d{2}[-/.]\d{2}\b/.test(normalized);
|
const hasTemporalCue = hasPeriodLiteral(normalized) || /\b\d{4}[-/.]\d{2}[-/.]\d{2}\b/.test(normalized);
|
||||||
return hasRequestCue || hasTemporalCue;
|
return hasRequestCue || hasTemporalCue;
|
||||||
}
|
}
|
||||||
const ADDRESS_INTENTS_KEEP_ADDRESS_LANE = new Set([
|
|
||||||
"period_coverage_profile",
|
|
||||||
"document_type_and_account_section_profile",
|
|
||||||
"counterparty_population_and_roles",
|
|
||||||
"counterparty_activity_lifecycle",
|
|
||||||
"customer_revenue_and_payments",
|
|
||||||
"supplier_payouts_profile",
|
|
||||||
"open_contracts_confirmed_as_of_date",
|
|
||||||
"list_open_contracts",
|
|
||||||
"open_items_by_counterparty_or_contract",
|
|
||||||
"list_payables_counterparties",
|
|
||||||
"list_receivables_counterparties",
|
|
||||||
"inventory_on_hand_as_of_date",
|
|
||||||
"payables_confirmed_as_of_date",
|
|
||||||
"receivables_confirmed_as_of_date",
|
|
||||||
"list_documents_by_contract",
|
|
||||||
"bank_operations_by_contract",
|
|
||||||
"list_documents_by_counterparty",
|
|
||||||
"bank_operations_by_counterparty",
|
|
||||||
"list_contracts_by_counterparty",
|
|
||||||
"inventory_purchase_provenance_for_item",
|
|
||||||
"inventory_purchase_documents_for_item",
|
|
||||||
"inventory_supplier_stock_overlap_as_of_date",
|
|
||||||
"inventory_sale_trace_for_item",
|
|
||||||
"inventory_profitability_for_item",
|
|
||||||
"inventory_purchase_to_sale_chain",
|
|
||||||
"inventory_aging_by_purchase_date",
|
|
||||||
"contract_usage_overview",
|
|
||||||
"contract_usage_and_value",
|
|
||||||
"vat_payable_forecast",
|
|
||||||
"vat_liability_confirmed_for_tax_period",
|
|
||||||
"vat_payable_confirmed_as_of_date"
|
|
||||||
]);
|
|
||||||
const ADDRESS_INTENTS_ALLOW_STRICT_DEEP_INVESTIGATION_BYPASS = new Set([
|
|
||||||
"inventory_purchase_provenance_for_item",
|
|
||||||
"inventory_purchase_documents_for_item",
|
|
||||||
"inventory_sale_trace_for_item",
|
|
||||||
"inventory_profitability_for_item",
|
|
||||||
"inventory_purchase_to_sale_chain"
|
|
||||||
]);
|
|
||||||
function shouldBypassStrictDeepInvestigationCueForAddressIntent(intent) {
|
|
||||||
return Boolean(intent && ADDRESS_INTENTS_ALLOW_STRICT_DEEP_INVESTIGATION_BYPASS.has(intent));
|
|
||||||
}
|
|
||||||
function resolveAssistantOrchestrationDecision(input) {
|
function resolveAssistantOrchestrationDecision(input) {
|
||||||
const rawUserMessage = String(input?.rawUserMessage ?? input?.userMessage ?? "");
|
return assistantRoutePolicy.resolveAssistantOrchestrationDecision(input);
|
||||||
const effectiveAddressUserMessage = String(input?.effectiveAddressUserMessage ?? rawUserMessage);
|
|
||||||
const repairedRawUserMessage = repairAddressMojibake(rawUserMessage);
|
|
||||||
const repairedEffectiveAddressUserMessage = repairAddressMojibake(effectiveAddressUserMessage);
|
|
||||||
const followupContext = input?.followupContext ?? null;
|
|
||||||
const llmPreDecomposeMeta = input?.llmPreDecomposeMeta ?? null;
|
|
||||||
const useMock = Boolean(input?.useMock);
|
|
||||||
const sessionItems = Array.isArray(input?.sessionItems) ? input.sessionItems : null;
|
|
||||||
const sessionOrganizationScope = input?.sessionOrganizationScope && typeof input.sessionOrganizationScope === "object"
|
|
||||||
? input.sessionOrganizationScope
|
|
||||||
: null;
|
|
||||||
const lastGroundedAddressDebug = findLastGroundedAddressAnswerDebug(sessionItems);
|
|
||||||
const lastOrganizationClarificationDebug = findLastOrganizationClarificationAddressDebug(sessionItems);
|
|
||||||
const organizationClarificationCandidates = Array.isArray(lastOrganizationClarificationDebug?.organization_candidates)
|
|
||||||
? mergeKnownOrganizations([
|
|
||||||
...lastOrganizationClarificationDebug.organization_candidates,
|
|
||||||
...((Array.isArray(sessionOrganizationScope?.knownOrganizations)
|
|
||||||
? sessionOrganizationScope.knownOrganizations
|
|
||||||
: []))
|
|
||||||
])
|
|
||||||
: [];
|
|
||||||
const organizationClarificationSelectionFromScope = normalizeOrganizationScopeValue(sessionOrganizationScope?.selectedOrganization);
|
|
||||||
const organizationClarificationSelection = resolveOrganizationSelectionFromMessage(rawUserMessage, organizationClarificationCandidates) ??
|
|
||||||
resolveOrganizationSelectionFromMessage(repairedRawUserMessage, organizationClarificationCandidates) ??
|
|
||||||
resolveOrganizationSelectionFromMessage(effectiveAddressUserMessage, organizationClarificationCandidates) ??
|
|
||||||
resolveOrganizationSelectionFromMessage(repairedEffectiveAddressUserMessage, organizationClarificationCandidates) ??
|
|
||||||
(organizationClarificationSelectionFromScope &&
|
|
||||||
organizationClarificationCandidates.some((candidate) => normalizeOrganizationScopeValue(candidate) === organizationClarificationSelectionFromScope)
|
|
||||||
? organizationClarificationSelectionFromScope
|
|
||||||
: null);
|
|
||||||
const dataScopeMetaQuery = hasAssistantDataScopeMetaQuestionSignal(rawUserMessage) ||
|
|
||||||
hasAssistantDataScopeMetaQuestionSignal(repairedRawUserMessage) ||
|
|
||||||
hasAssistantDataScopeMetaQuestionSignal(effectiveAddressUserMessage) ||
|
|
||||||
hasAssistantDataScopeMetaQuestionSignal(repairedEffectiveAddressUserMessage);
|
|
||||||
const capabilityMetaQuery = shouldHandleAsAssistantCapabilityMetaQuery(rawUserMessage) ||
|
|
||||||
shouldHandleAsAssistantCapabilityMetaQuery(repairedRawUserMessage) ||
|
|
||||||
shouldHandleAsAssistantCapabilityMetaQuery(effectiveAddressUserMessage) ||
|
|
||||||
shouldHandleAsAssistantCapabilityMetaQuery(repairedEffectiveAddressUserMessage);
|
|
||||||
const dataRetrievalSignal = hasDataRetrievalRequestSignal(rawUserMessage) ||
|
|
||||||
hasDataRetrievalRequestSignal(repairedRawUserMessage) ||
|
|
||||||
hasDataRetrievalRequestSignal(effectiveAddressUserMessage) ||
|
|
||||||
hasDataRetrievalRequestSignal(repairedEffectiveAddressUserMessage);
|
|
||||||
const aggregateBusinessAnalyticsSignal = hasAggregateBusinessAnalyticsSignal(rawUserMessage) ||
|
|
||||||
hasAggregateBusinessAnalyticsSignal(repairedRawUserMessage) ||
|
|
||||||
hasAggregateBusinessAnalyticsSignal(effectiveAddressUserMessage) ||
|
|
||||||
hasAggregateBusinessAnalyticsSignal(repairedEffectiveAddressUserMessage);
|
|
||||||
const standaloneAddressTopicSignal = hasStandaloneAddressTopicSignal(rawUserMessage) ||
|
|
||||||
hasStandaloneAddressTopicSignal(repairedRawUserMessage) ||
|
|
||||||
hasStandaloneAddressTopicSignal(effectiveAddressUserMessage) ||
|
|
||||||
hasStandaloneAddressTopicSignal(repairedEffectiveAddressUserMessage);
|
|
||||||
const openContractsAddressSignal = hasOpenContractsAddressSignal(rawUserMessage) ||
|
|
||||||
hasOpenContractsAddressSignal(repairedRawUserMessage) ||
|
|
||||||
hasOpenContractsAddressSignal(effectiveAddressUserMessage) ||
|
|
||||||
hasOpenContractsAddressSignal(repairedEffectiveAddressUserMessage);
|
|
||||||
const modeSample = repairedEffectiveAddressUserMessage || effectiveAddressUserMessage;
|
|
||||||
const modeDetection = (0, addressQueryClassifier_1.detectAddressQuestionMode)(modeSample);
|
|
||||||
const modeDetectionRaw = (0, addressQueryClassifier_1.detectAddressQuestionMode)(repairedRawUserMessage || rawUserMessage);
|
|
||||||
const resolvedModeDetection = modeDetection.mode === "address_query" ? modeDetection : modeDetectionRaw;
|
|
||||||
const intentResolution = (0, addressIntentResolver_1.resolveAddressIntent)(modeSample);
|
|
||||||
const intentResolutionRaw = (0, addressIntentResolver_1.resolveAddressIntent)(repairedRawUserMessage || rawUserMessage);
|
|
||||||
const resolvedIntentResolution = intentResolution.intent !== "unknown" ? intentResolution : intentResolutionRaw;
|
|
||||||
const llmContractIntent = toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.intent);
|
|
||||||
const llmPreDecomposeReason = toNonEmptyString(llmPreDecomposeMeta?.reason);
|
|
||||||
const llmRuntimeUnavailableDetected = Boolean(llmPreDecomposeReason &&
|
|
||||||
/(?:openai\s+api\s+key\s+is\s+missing|api\s+key\s+is\s+missing|missing\s+api\s+key|authentication)/iu.test(llmPreDecomposeReason));
|
|
||||||
const semanticExtractionContract = llmPreDecomposeMeta?.semanticExtractionContract &&
|
|
||||||
typeof llmPreDecomposeMeta.semanticExtractionContract === "object"
|
|
||||||
? llmPreDecomposeMeta.semanticExtractionContract
|
|
||||||
: null;
|
|
||||||
const semanticContractValid = semanticExtractionContract?.valid !== false;
|
|
||||||
const semanticApplyCanonicalRecommended = semanticExtractionContract?.apply_canonical_recommended !== false;
|
|
||||||
const semanticReasonCodes = Array.isArray(semanticExtractionContract?.reason_codes)
|
|
||||||
? semanticExtractionContract.reason_codes
|
|
||||||
: [];
|
|
||||||
const strictDeepInvestigationCueDetected = hasStrictDeepInvestigationCue(rawUserMessage) ||
|
|
||||||
hasStrictDeepInvestigationCue(repairedRawUserMessage) ||
|
|
||||||
hasStrictDeepInvestigationCue(effectiveAddressUserMessage) ||
|
|
||||||
hasStrictDeepInvestigationCue(repairedEffectiveAddressUserMessage);
|
|
||||||
const strictDeepInvestigationBypassAllowed = shouldBypassStrictDeepInvestigationCueForAddressIntent(resolvedIntentResolution.intent) ||
|
|
||||||
shouldBypassStrictDeepInvestigationCueForAddressIntent(llmContractIntent);
|
|
||||||
const keepAddressLaneByIntent = semanticApplyCanonicalRecommended &&
|
|
||||||
Boolean((resolvedIntentResolution.intent && ADDRESS_INTENTS_KEEP_ADDRESS_LANE.has(resolvedIntentResolution.intent)) ||
|
|
||||||
(llmContractIntent && ADDRESS_INTENTS_KEEP_ADDRESS_LANE.has(llmContractIntent)) ||
|
|
||||||
openContractsAddressSignal) &&
|
|
||||||
(!strictDeepInvestigationCueDetected || strictDeepInvestigationBypassAllowed);
|
|
||||||
const strongDataSignal = hasStrongDataIntentSignal(rawUserMessage) ||
|
|
||||||
hasStrongDataIntentSignal(repairedRawUserMessage) ||
|
|
||||||
hasStrongDataIntentSignal(effectiveAddressUserMessage) ||
|
|
||||||
hasStrongDataIntentSignal(repairedEffectiveAddressUserMessage) ||
|
|
||||||
hasAccountingSignal(rawUserMessage) ||
|
|
||||||
hasAccountingSignal(repairedRawUserMessage) ||
|
|
||||||
hasAccountingSignal(effectiveAddressUserMessage) ||
|
|
||||||
hasAccountingSignal(repairedEffectiveAddressUserMessage) ||
|
|
||||||
hasDataRetrievalRequestSignal(rawUserMessage) ||
|
|
||||||
hasDataRetrievalRequestSignal(repairedRawUserMessage);
|
|
||||||
const llmContractMode = toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.mode);
|
|
||||||
const llmFirstAddressCandidate = Boolean(llmContractMode === "address_query" && llmContractIntent && llmContractIntent !== "unknown");
|
|
||||||
const llmFirstUnsupportedCandidate = Boolean(llmContractMode === "unsupported" &&
|
|
||||||
(!llmContractIntent || llmContractIntent === "unknown"));
|
|
||||||
const dangerOrCoercionSignal = hasDangerOrCoercionSignal(rawUserMessage) ||
|
|
||||||
hasDangerOrCoercionSignal(repairedRawUserMessage) ||
|
|
||||||
hasDangerOrCoercionSignal(effectiveAddressUserMessage) ||
|
|
||||||
hasDangerOrCoercionSignal(repairedEffectiveAddressUserMessage);
|
|
||||||
const explicitAddressFollowupSignal = hasAddressFollowupContextSignal(rawUserMessage) ||
|
|
||||||
hasAddressFollowupContextSignal(repairedRawUserMessage) ||
|
|
||||||
hasAddressFollowupContextSignal(effectiveAddressUserMessage) ||
|
|
||||||
hasAddressFollowupContextSignal(repairedEffectiveAddressUserMessage) ||
|
|
||||||
hasShortDebtMirrorFollowupSignal(rawUserMessage) ||
|
|
||||||
hasShortDebtMirrorFollowupSignal(repairedRawUserMessage) ||
|
|
||||||
hasShortDebtMirrorFollowupSignal(effectiveAddressUserMessage) ||
|
|
||||||
hasShortDebtMirrorFollowupSignal(repairedEffectiveAddressUserMessage);
|
|
||||||
const protectedInventoryShortFollowup = Boolean(followupContext &&
|
|
||||||
isInventorySelectedObjectIntent(toNonEmptyString(followupContext.previous_intent)) &&
|
|
||||||
(hasShortInventoryObjectFollowupSignal(rawUserMessage) ||
|
|
||||||
hasShortInventoryObjectFollowupSignal(repairedRawUserMessage) ||
|
|
||||||
hasShortInventoryObjectFollowupSignal(effectiveAddressUserMessage) ||
|
|
||||||
hasShortInventoryObjectFollowupSignal(repairedEffectiveAddressUserMessage)));
|
|
||||||
const organizationClarificationContinuationDetected = Boolean(followupContext &&
|
|
||||||
lastOrganizationClarificationDebug &&
|
|
||||||
organizationClarificationSelection &&
|
|
||||||
!dataScopeMetaQuery &&
|
|
||||||
!capabilityMetaQuery &&
|
|
||||||
!dataRetrievalSignal);
|
|
||||||
const effectiveAddressFollowupSignal = explicitAddressFollowupSignal && !dangerOrCoercionSignal;
|
|
||||||
const deterministicNonDomainGuard = Boolean(!dataScopeMetaQuery &&
|
|
||||||
!capabilityMetaQuery &&
|
|
||||||
!dataRetrievalSignal &&
|
|
||||||
!effectiveAddressFollowupSignal &&
|
|
||||||
resolvedModeDetection.mode === "unsupported" &&
|
|
||||||
resolvedIntentResolution.intent === "unknown");
|
|
||||||
const nonDomainQueryIndexed = Boolean(!llmFirstAddressCandidate &&
|
|
||||||
deterministicNonDomainGuard &&
|
|
||||||
(llmFirstUnsupportedCandidate || llmContractMode === null) &&
|
|
||||||
!protectedInventoryShortFollowup &&
|
|
||||||
!organizationClarificationContinuationDetected);
|
|
||||||
const contextualHistoricalCapabilityFollowupDetected = Boolean(capabilityMetaQuery &&
|
|
||||||
!dataScopeMetaQuery &&
|
|
||||||
!dataRetrievalSignal &&
|
|
||||||
(hasHistoricalCapabilityFollowupSignal(rawUserMessage) ||
|
|
||||||
hasHistoricalCapabilityFollowupSignal(repairedRawUserMessage) ||
|
|
||||||
hasHistoricalCapabilityFollowupSignal(effectiveAddressUserMessage) ||
|
|
||||||
hasHistoricalCapabilityFollowupSignal(repairedEffectiveAddressUserMessage)) &&
|
|
||||||
isGroundedInventoryContextDebug(lastGroundedAddressDebug));
|
|
||||||
const contextualMemoryRecapFollowupDetected = Boolean(!dataScopeMetaQuery &&
|
|
||||||
!capabilityMetaQuery &&
|
|
||||||
!dataRetrievalSignal &&
|
|
||||||
!strongDataSignal &&
|
|
||||||
!aggregateBusinessAnalyticsSignal &&
|
|
||||||
(hasConversationMemoryRecallFollowupSignal(rawUserMessage) ||
|
|
||||||
hasConversationMemoryRecallFollowupSignal(repairedRawUserMessage) ||
|
|
||||||
hasConversationMemoryRecallFollowupSignal(effectiveAddressUserMessage) ||
|
|
||||||
hasConversationMemoryRecallFollowupSignal(repairedEffectiveAddressUserMessage)) &&
|
|
||||||
(lastGroundedAddressDebug ||
|
|
||||||
findLastAddressAssistantItem(sessionItems)?.debug));
|
|
||||||
const hardMetaMode = dataScopeMetaQuery
|
|
||||||
? "data_scope"
|
|
||||||
: capabilityMetaQuery && !dataRetrievalSignal
|
|
||||||
? "capability"
|
|
||||||
: null;
|
|
||||||
if (hardMetaMode === "data_scope") {
|
|
||||||
return {
|
|
||||||
runAddressLane: false,
|
|
||||||
toolGateDecision: "skip_address_lane",
|
|
||||||
toolGateReason: "assistant_data_scope_query_detected",
|
|
||||||
livingMode: "chat",
|
|
||||||
livingReason: "assistant_data_scope_query_detected",
|
|
||||||
orchestrationContract: {
|
|
||||||
schema_version: "assistant_orchestration_contract_v1",
|
|
||||||
hard_meta_mode: "data_scope",
|
|
||||||
address_mode: resolvedModeDetection.mode,
|
|
||||||
address_mode_confidence: resolvedModeDetection.confidence,
|
|
||||||
address_intent: resolvedIntentResolution.intent,
|
|
||||||
address_intent_confidence: resolvedIntentResolution.confidence,
|
|
||||||
strong_data_signal_detected: strongDataSignal,
|
|
||||||
data_retrieval_signal_detected: dataRetrievalSignal,
|
|
||||||
followup_context_detected: Boolean(followupContext),
|
|
||||||
unsupported_address_intent_fallback_to_deep: false,
|
|
||||||
final_decision: {
|
|
||||||
run_address_lane: false,
|
|
||||||
tool_gate_decision: "skip_address_lane",
|
|
||||||
tool_gate_reason: "assistant_data_scope_query_detected",
|
|
||||||
living_mode: "chat",
|
|
||||||
living_reason: "assistant_data_scope_query_detected"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (hardMetaMode === "capability") {
|
|
||||||
if (contextualHistoricalCapabilityFollowupDetected) {
|
|
||||||
return {
|
|
||||||
runAddressLane: false,
|
|
||||||
toolGateDecision: "skip_address_lane",
|
|
||||||
toolGateReason: "inventory_history_capability_followup_detected",
|
|
||||||
livingMode: "chat",
|
|
||||||
livingReason: "inventory_history_capability_followup_detected",
|
|
||||||
orchestrationContract: {
|
|
||||||
schema_version: "assistant_orchestration_contract_v1",
|
|
||||||
hard_meta_mode: "capability",
|
|
||||||
address_mode: resolvedModeDetection.mode,
|
|
||||||
address_mode_confidence: resolvedModeDetection.confidence,
|
|
||||||
address_intent: resolvedIntentResolution.intent,
|
|
||||||
address_intent_confidence: resolvedIntentResolution.confidence,
|
|
||||||
strong_data_signal_detected: strongDataSignal,
|
|
||||||
data_retrieval_signal_detected: dataRetrievalSignal,
|
|
||||||
followup_context_detected: Boolean(followupContext || lastGroundedAddressDebug),
|
|
||||||
unsupported_address_intent_fallback_to_deep: false,
|
|
||||||
final_decision: {
|
|
||||||
run_address_lane: false,
|
|
||||||
tool_gate_decision: "skip_address_lane",
|
|
||||||
tool_gate_reason: "inventory_history_capability_followup_detected",
|
|
||||||
living_mode: "chat",
|
|
||||||
living_reason: "inventory_history_capability_followup_detected"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
runAddressLane: false,
|
|
||||||
toolGateDecision: "skip_address_lane",
|
|
||||||
toolGateReason: "assistant_capability_query_detected",
|
|
||||||
livingMode: "chat",
|
|
||||||
livingReason: "assistant_capability_query_detected",
|
|
||||||
orchestrationContract: {
|
|
||||||
schema_version: "assistant_orchestration_contract_v1",
|
|
||||||
hard_meta_mode: "capability",
|
|
||||||
address_mode: resolvedModeDetection.mode,
|
|
||||||
address_mode_confidence: resolvedModeDetection.confidence,
|
|
||||||
address_intent: resolvedIntentResolution.intent,
|
|
||||||
address_intent_confidence: resolvedIntentResolution.confidence,
|
|
||||||
strong_data_signal_detected: strongDataSignal,
|
|
||||||
data_retrieval_signal_detected: dataRetrievalSignal,
|
|
||||||
followup_context_detected: Boolean(followupContext),
|
|
||||||
unsupported_address_intent_fallback_to_deep: false,
|
|
||||||
final_decision: {
|
|
||||||
run_address_lane: false,
|
|
||||||
tool_gate_decision: "skip_address_lane",
|
|
||||||
tool_gate_reason: "assistant_capability_query_detected",
|
|
||||||
living_mode: "chat",
|
|
||||||
living_reason: "assistant_capability_query_detected"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (nonDomainQueryIndexed) {
|
|
||||||
if (contextualMemoryRecapFollowupDetected) {
|
|
||||||
return {
|
|
||||||
runAddressLane: false,
|
|
||||||
toolGateDecision: "skip_address_lane",
|
|
||||||
toolGateReason: "memory_recap_followup_detected",
|
|
||||||
livingMode: "chat",
|
|
||||||
livingReason: "memory_recap_followup_detected",
|
|
||||||
orchestrationContract: {
|
|
||||||
schema_version: "assistant_orchestration_contract_v1",
|
|
||||||
hard_meta_mode: "non_domain",
|
|
||||||
address_mode: resolvedModeDetection.mode,
|
|
||||||
address_mode_confidence: resolvedModeDetection.confidence,
|
|
||||||
address_intent: resolvedIntentResolution.intent,
|
|
||||||
address_intent_confidence: resolvedIntentResolution.confidence,
|
|
||||||
strong_data_signal_detected: strongDataSignal,
|
|
||||||
data_retrieval_signal_detected: dataRetrievalSignal,
|
|
||||||
followup_context_detected: Boolean(followupContext || lastGroundedAddressDebug),
|
|
||||||
unsupported_address_intent_fallback_to_deep: false,
|
|
||||||
final_decision: {
|
|
||||||
run_address_lane: false,
|
|
||||||
tool_gate_decision: "skip_address_lane",
|
|
||||||
tool_gate_reason: "memory_recap_followup_detected",
|
|
||||||
living_mode: "chat",
|
|
||||||
living_reason: "memory_recap_followup_detected"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
runAddressLane: false,
|
|
||||||
toolGateDecision: "skip_address_lane",
|
|
||||||
toolGateReason: "non_domain_query_indexed",
|
|
||||||
livingMode: "chat",
|
|
||||||
livingReason: "non_domain_query_indexed",
|
|
||||||
orchestrationContract: {
|
|
||||||
schema_version: "assistant_orchestration_contract_v1",
|
|
||||||
hard_meta_mode: "non_domain",
|
|
||||||
address_mode: resolvedModeDetection.mode,
|
|
||||||
address_mode_confidence: resolvedModeDetection.confidence,
|
|
||||||
address_intent: resolvedIntentResolution.intent,
|
|
||||||
address_intent_confidence: resolvedIntentResolution.confidence,
|
|
||||||
strong_data_signal_detected: strongDataSignal,
|
|
||||||
data_retrieval_signal_detected: dataRetrievalSignal,
|
|
||||||
followup_context_detected: Boolean(followupContext),
|
|
||||||
unsupported_address_intent_fallback_to_deep: false,
|
|
||||||
final_decision: {
|
|
||||||
run_address_lane: false,
|
|
||||||
tool_gate_decision: "skip_address_lane",
|
|
||||||
tool_gate_reason: "non_domain_query_indexed",
|
|
||||||
living_mode: "chat",
|
|
||||||
living_reason: "non_domain_query_indexed"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const metaAnswerFollowupSignal = hasMetaAnswerFollowupSignal(rawUserMessage) ||
|
|
||||||
hasMetaAnswerFollowupSignal(repairedRawUserMessage) ||
|
|
||||||
hasMetaAnswerFollowupSignal(effectiveAddressUserMessage) ||
|
|
||||||
hasMetaAnswerFollowupSignal(repairedEffectiveAddressUserMessage);
|
|
||||||
const baseToolGate = resolveAddressToolGateDecision(effectiveAddressUserMessage, followupContext, llmPreDecomposeMeta, rawUserMessage);
|
|
||||||
const preserveAddressLaneSignal = Boolean((llmPreDecomposeMeta?.llmCanonicalCandidateDetected &&
|
|
||||||
llmPreDecomposeMeta?.applied &&
|
|
||||||
llmContractMode === "address_query") ||
|
|
||||||
hasSameDateAccountFollowupSignalForPredecompose(rawUserMessage) ||
|
|
||||||
hasSameDateAccountFollowupSignalForPredecompose(effectiveAddressUserMessage) ||
|
|
||||||
hasSameDateAccountFollowupSignalForPredecompose(repairedRawUserMessage) ||
|
|
||||||
hasSameDateAccountFollowupSignalForPredecompose(repairedEffectiveAddressUserMessage) ||
|
|
||||||
hasLooseAllTimeAddressLookupSignal(rawUserMessage) ||
|
|
||||||
hasLooseAllTimeAddressLookupSignal(effectiveAddressUserMessage) ||
|
|
||||||
hasLooseAllTimeAddressLookupSignal(repairedRawUserMessage) ||
|
|
||||||
hasLooseAllTimeAddressLookupSignal(repairedEffectiveAddressUserMessage) ||
|
|
||||||
hasAddressFollowupContextSignal(rawUserMessage) ||
|
|
||||||
hasAddressFollowupContextSignal(effectiveAddressUserMessage) ||
|
|
||||||
hasAddressFollowupContextSignal(repairedRawUserMessage) ||
|
|
||||||
hasAddressFollowupContextSignal(repairedEffectiveAddressUserMessage) ||
|
|
||||||
hasShortDebtMirrorFollowupSignal(rawUserMessage) ||
|
|
||||||
hasShortDebtMirrorFollowupSignal(effectiveAddressUserMessage) ||
|
|
||||||
hasShortDebtMirrorFollowupSignal(repairedRawUserMessage) ||
|
|
||||||
hasShortDebtMirrorFollowupSignal(repairedEffectiveAddressUserMessage));
|
|
||||||
const supportedAddressIntentDetected = (!strictDeepInvestigationCueDetected || strictDeepInvestigationBypassAllowed) &&
|
|
||||||
Boolean((resolvedIntentResolution.intent && ADDRESS_INTENTS_KEEP_ADDRESS_LANE.has(resolvedIntentResolution.intent)) ||
|
|
||||||
(llmContractIntent && ADDRESS_INTENTS_KEEP_ADDRESS_LANE.has(llmContractIntent)) ||
|
|
||||||
openContractsAddressSignal);
|
|
||||||
const semanticGuardHints = semanticExtractionContract?.guard_hints &&
|
|
||||||
typeof semanticExtractionContract.guard_hints === "object"
|
|
||||||
? semanticExtractionContract.guard_hints
|
|
||||||
: null;
|
|
||||||
const semanticExtraction = semanticExtractionContract?.extraction &&
|
|
||||||
typeof semanticExtractionContract.extraction === "object"
|
|
||||||
? semanticExtractionContract.extraction
|
|
||||||
: null;
|
|
||||||
const semanticDeepInvestigationHintDetected = semanticGuardHints?.deep_investigation_signal_detected === true;
|
|
||||||
const semanticAggregateShapeDetected = semanticExtraction?.query_shape === "AGGREGATE_LOOKUP" ||
|
|
||||||
semanticExtraction?.aggregation_profile === "management_profile";
|
|
||||||
const rootContextOnlyFollowup = Boolean(followupContext && followupContext.root_context_only === true);
|
|
||||||
const followupSemanticOverrideToDeepAllowed = Boolean(followupContext &&
|
|
||||||
!supportedAddressIntentDetected &&
|
|
||||||
(rootContextOnlyFollowup ||
|
|
||||||
llmContractMode === "unsupported" ||
|
|
||||||
semanticAggregateShapeDetected ||
|
|
||||||
semanticDeepInvestigationHintDetected ||
|
|
||||||
!semanticApplyCanonicalRecommended));
|
|
||||||
const unsupportedIntentOrMode = (resolvedModeDetection.mode !== "address_query" && resolvedIntentResolution.intent === "unknown") ||
|
|
||||||
llmContractMode === "unsupported" ||
|
|
||||||
(rootContextOnlyFollowup &&
|
|
||||||
resolvedIntentResolution.intent === "unknown" &&
|
|
||||||
(!llmContractIntent || llmContractIntent === "unknown"));
|
|
||||||
const unsupportedAddressIntentFallbackToDeep = Boolean(baseToolGate?.runAddressLane &&
|
|
||||||
!llmRuntimeUnavailableDetected &&
|
|
||||||
unsupportedIntentOrMode &&
|
|
||||||
strongDataSignal &&
|
|
||||||
(rootContextOnlyFollowup ||
|
|
||||||
llmContractMode === "deep_analysis" ||
|
|
||||||
!dataRetrievalSignal ||
|
|
||||||
strictDeepInvestigationCueDetected ||
|
|
||||||
semanticDeepInvestigationHintDetected ||
|
|
||||||
aggregateBusinessAnalyticsSignal) &&
|
|
||||||
!preserveAddressLaneSignal &&
|
|
||||||
!keepAddressLaneByIntent &&
|
|
||||||
!supportedAddressIntentDetected &&
|
|
||||||
(!followupContext || followupSemanticOverrideToDeepAllowed));
|
|
||||||
const deepAnalysisPreferenceDetected = Boolean(hasDeepAnalysisPreferenceSignal(rawUserMessage) ||
|
|
||||||
hasDeepAnalysisPreferenceSignal(repairedRawUserMessage) ||
|
|
||||||
hasDeepAnalysisPreferenceSignal(effectiveAddressUserMessage) ||
|
|
||||||
hasDeepAnalysisPreferenceSignal(repairedEffectiveAddressUserMessage) ||
|
|
||||||
hasDirectDeepAnalysisSignal(rawUserMessage) ||
|
|
||||||
hasDirectDeepAnalysisSignal(repairedRawUserMessage) ||
|
|
||||||
hasDirectDeepAnalysisSignal(effectiveAddressUserMessage) ||
|
|
||||||
hasDirectDeepAnalysisSignal(repairedEffectiveAddressUserMessage));
|
|
||||||
const vatExplainFollowupSignal = Boolean(followupContext &&
|
|
||||||
toNonEmptyString(followupContext.previous_intent) === "vat_payable_forecast" &&
|
|
||||||
/(?:\u043f\u043e\u0447\u0435\u043c\u0443|why).*(?:\u043f\u0440\u043e\u0433\u043d\u043e\u0437|forecast).*(?:\u0443\u043f\u043b\u0430\u0442|payable|\b0\b)/iu.test(compactWhitespace(`${repairedRawUserMessage} ${repairedEffectiveAddressUserMessage}`)));
|
|
||||||
const vatEvaluativeFollowupSignal = Boolean(followupContext &&
|
|
||||||
toNonEmptyString(followupContext.previous_intent) === "vat_payable_forecast" &&
|
|
||||||
/(?:^|\s)(?:это\s+)?много\s+или\s+мало(?:\?|$)|(?:^|\s)(?:это\s+)?нормально(?:\?|$)|(?:^|\s)(?:это\s+)?плохо(?:\?|$)|(?:^|\s)(?:это\s+)?хорошо(?:\?|$)/iu.test(compactWhitespace(`${repairedRawUserMessage} ${repairedEffectiveAddressUserMessage}`)));
|
|
||||||
const deepAnalysisSignalFallbackToDeep = Boolean(baseToolGate?.runAddressLane &&
|
|
||||||
!llmRuntimeUnavailableDetected &&
|
|
||||||
(deepAnalysisPreferenceDetected || semanticDeepInvestigationHintDetected) &&
|
|
||||||
!keepAddressLaneByIntent &&
|
|
||||||
!supportedAddressIntentDetected &&
|
|
||||||
!vatExplainFollowupSignal &&
|
|
||||||
(!followupContext || !dataRetrievalSignal || followupSemanticOverrideToDeepAllowed));
|
|
||||||
const aggregateAnalyticsFallbackToDeep = Boolean(baseToolGate?.runAddressLane &&
|
|
||||||
!llmRuntimeUnavailableDetected &&
|
|
||||||
aggregateBusinessAnalyticsSignal &&
|
|
||||||
!keepAddressLaneByIntent &&
|
|
||||||
!supportedAddressIntentDetected &&
|
|
||||||
(!followupContext ||
|
|
||||||
llmContractMode === "unsupported" ||
|
|
||||||
semanticAggregateShapeDetected ||
|
|
||||||
!semanticApplyCanonicalRecommended ||
|
|
||||||
standaloneAddressTopicSignal));
|
|
||||||
const deepSessionContinuationFallbackToDeep = Boolean(!followupContext &&
|
|
||||||
baseToolGate?.runAddressLane &&
|
|
||||||
!llmRuntimeUnavailableDetected &&
|
|
||||||
hasDeepSessionContinuationSignal({
|
|
||||||
rawUserMessage,
|
|
||||||
repairedRawUserMessage,
|
|
||||||
effectiveAddressUserMessage,
|
|
||||||
repairedEffectiveAddressUserMessage,
|
|
||||||
sessionItems
|
|
||||||
}));
|
|
||||||
const hasPriorAddressAnswerContext = Boolean(lastGroundedAddressDebug || toNonEmptyString(followupContext?.previous_intent));
|
|
||||||
const metaFollowupOverGroundedAnswer = Boolean(followupContext &&
|
|
||||||
hasPriorAddressAnswerContext &&
|
|
||||||
(metaAnswerFollowupSignal || vatEvaluativeFollowupSignal) &&
|
|
||||||
!dataScopeMetaQuery &&
|
|
||||||
!capabilityMetaQuery &&
|
|
||||||
!aggregateBusinessAnalyticsSignal &&
|
|
||||||
!dataRetrievalSignal &&
|
|
||||||
!strongDataSignal &&
|
|
||||||
resolvedModeDetection.mode !== "address_query" &&
|
|
||||||
resolvedIntentResolution.intent === "unknown" &&
|
|
||||||
(!llmContractIntent || llmContractIntent === "unknown") &&
|
|
||||||
llmContractMode !== "address_query");
|
|
||||||
let runAddressLane = Boolean(baseToolGate?.runAddressLane);
|
|
||||||
let toolGateDecision = String(baseToolGate?.decision ?? "skip_address_lane");
|
|
||||||
let toolGateReason = String(baseToolGate?.reason ?? "no_address_signal_after_l0");
|
|
||||||
if (unsupportedAddressIntentFallbackToDeep) {
|
|
||||||
runAddressLane = false;
|
|
||||||
toolGateDecision = "skip_address_lane";
|
|
||||||
toolGateReason = "address_signal_unsupported_intent_fallback_to_deep";
|
|
||||||
}
|
|
||||||
if (deepAnalysisSignalFallbackToDeep && !unsupportedAddressIntentFallbackToDeep) {
|
|
||||||
runAddressLane = false;
|
|
||||||
toolGateDecision = "skip_address_lane";
|
|
||||||
toolGateReason = "deep_analysis_signal_fallback_to_deep";
|
|
||||||
}
|
|
||||||
if (aggregateAnalyticsFallbackToDeep &&
|
|
||||||
!unsupportedAddressIntentFallbackToDeep &&
|
|
||||||
!deepAnalysisSignalFallbackToDeep) {
|
|
||||||
runAddressLane = false;
|
|
||||||
toolGateDecision = "skip_address_lane";
|
|
||||||
toolGateReason = "aggregate_analytics_signal_fallback_to_deep";
|
|
||||||
}
|
|
||||||
if (deepSessionContinuationFallbackToDeep) {
|
|
||||||
runAddressLane = false;
|
|
||||||
toolGateDecision = "skip_address_lane";
|
|
||||||
toolGateReason = "deep_session_continuation_fallback_to_deep";
|
|
||||||
}
|
|
||||||
if (metaFollowupOverGroundedAnswer) {
|
|
||||||
runAddressLane = false;
|
|
||||||
toolGateDecision = "skip_address_lane";
|
|
||||||
toolGateReason = "meta_followup_over_grounded_answer";
|
|
||||||
}
|
|
||||||
let livingDecision = resolveLivingAssistantModeDecision({
|
|
||||||
userMessage: rawUserMessage,
|
|
||||||
addressLaneTriggered: runAddressLane,
|
|
||||||
useMock,
|
|
||||||
predecomposeMode: llmPreDecomposeMeta?.predecomposeContract?.mode ?? null,
|
|
||||||
predecomposeModeConfidence: llmPreDecomposeMeta?.predecomposeContract?.mode_confidence ?? null
|
|
||||||
});
|
|
||||||
if (unsupportedAddressIntentFallbackToDeep) {
|
|
||||||
livingDecision = {
|
|
||||||
mode: "deep_analysis",
|
|
||||||
reason: "unsupported_address_intent_fallback_to_deep"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (deepAnalysisSignalFallbackToDeep && !unsupportedAddressIntentFallbackToDeep) {
|
|
||||||
livingDecision = {
|
|
||||||
mode: "deep_analysis",
|
|
||||||
reason: "deep_analysis_signal_fallback_to_deep"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (aggregateAnalyticsFallbackToDeep &&
|
|
||||||
!unsupportedAddressIntentFallbackToDeep &&
|
|
||||||
!deepAnalysisSignalFallbackToDeep) {
|
|
||||||
livingDecision = {
|
|
||||||
mode: "deep_analysis",
|
|
||||||
reason: "aggregate_analytics_signal_fallback_to_deep"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (deepSessionContinuationFallbackToDeep) {
|
|
||||||
livingDecision = {
|
|
||||||
mode: "deep_analysis",
|
|
||||||
reason: "deep_session_continuation_fallback_to_deep"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (metaFollowupOverGroundedAnswer) {
|
|
||||||
livingDecision = {
|
|
||||||
mode: "chat",
|
|
||||||
reason: "meta_followup_over_grounded_answer"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
runAddressLane,
|
|
||||||
toolGateDecision,
|
|
||||||
toolGateReason,
|
|
||||||
livingMode: livingDecision.mode,
|
|
||||||
livingReason: livingDecision.reason,
|
|
||||||
orchestrationContract: {
|
|
||||||
schema_version: "assistant_orchestration_contract_v1",
|
|
||||||
hard_meta_mode: null,
|
|
||||||
address_mode: resolvedModeDetection.mode,
|
|
||||||
address_mode_confidence: resolvedModeDetection.confidence,
|
|
||||||
address_intent: resolvedIntentResolution.intent,
|
|
||||||
address_intent_confidence: resolvedIntentResolution.confidence,
|
|
||||||
strong_data_signal_detected: strongDataSignal,
|
|
||||||
data_retrieval_signal_detected: dataRetrievalSignal,
|
|
||||||
semantic_contract_valid: semanticContractValid,
|
|
||||||
semantic_apply_canonical_recommended: semanticApplyCanonicalRecommended,
|
|
||||||
semantic_reason_codes: semanticReasonCodes,
|
|
||||||
semantic_route_arbitration: {
|
|
||||||
supported_address_intent_detected: supportedAddressIntentDetected,
|
|
||||||
strict_deep_investigation_bypass_allowed: strictDeepInvestigationBypassAllowed,
|
|
||||||
semantic_deep_investigation_hint_detected: semanticDeepInvestigationHintDetected,
|
|
||||||
semantic_aggregate_shape_detected: semanticAggregateShapeDetected,
|
|
||||||
followup_semantic_override_to_deep_allowed: followupSemanticOverrideToDeepAllowed
|
|
||||||
},
|
|
||||||
followup_context_detected: Boolean(followupContext),
|
|
||||||
unsupported_address_intent_fallback_to_deep: unsupportedAddressIntentFallbackToDeep,
|
|
||||||
deep_analysis_signal_fallback_to_deep: deepAnalysisSignalFallbackToDeep,
|
|
||||||
aggregate_analytics_signal_fallback_to_deep: aggregateAnalyticsFallbackToDeep,
|
|
||||||
deep_session_continuation_fallback_to_deep: deepSessionContinuationFallbackToDeep,
|
|
||||||
final_decision: {
|
|
||||||
run_address_lane: runAddressLane,
|
|
||||||
tool_gate_decision: toolGateDecision,
|
|
||||||
tool_gate_reason: toolGateReason,
|
|
||||||
living_mode: livingDecision.mode,
|
|
||||||
living_reason: livingDecision.reason
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
function hasStrongDataIntentSignal(text) {
|
function hasStrongDataIntentSignal(text) {
|
||||||
const lower = String(text ?? "").toLowerCase();
|
return assistantLivingModePolicy.hasStrongDataIntentSignal(text);
|
||||||
return /(база|док|документ|проводк|контрагент|договор|контракт|счет|сч[её]т|остат|сальдо|хвост|платеж|плат[её]ж|операц|поставщик|клиент|заказчик|дебитор|кредитор|оборот|баланс|период|месяц|год|инн|аванс|предоплат|отгруз|задолж|долг|склад|товар|номенклат|материал|mcp|bank|counterparty|contract|document|ledger|posting|account|organization|company|advance|prepay|shipment|receivab|payab|warehouse|inventory|stock|item|организац|компан|контор|фирм)/i.test(lower);
|
|
||||||
}
|
}
|
||||||
function hasDataRetrievalRequestSignal(text) {
|
function hasDataRetrievalRequestSignal(text) {
|
||||||
const lower = compactWhitespace(String(text ?? "").toLowerCase());
|
return assistantLivingModePolicy.hasDataRetrievalRequestSignal(text);
|
||||||
if (!lower) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const hasBroadInterrogative = /(?:\u0433\u0434\u0435|\u0432\s+\u043a\u0430\u043a\u0438\u0445|\u043f\u043e\s+\u043a\u0430\u043a\u0438\u043c|\u043f\u043e\s+\u043a\u043e\u043c\u0443|\u043a\u0430\u043a\u0438\u0435|\u043a\u0430\u043a\u043e\u0439|\u043a\u0442\u043e|\u0441\u043a\u043e\u043b\u044c\u043a\u043e|where|which|who|how\s+many)/iu.test(lower);
|
|
||||||
const hasBroadBusinessObject = /(?:\u0430\u0432\u0430\u043d\u0441|\u043f\u0440\u0435\u0434\u043e\u043f\u043b\u0430\u0442|\u043e\u0442\u0433\u0440\u0443\u0437|\u0437\u0430\u0434\u043e\u043b\u0436|\u0434\u043e\u043b\u0433|\u0441\u0430\u043b\u044c\u0434\u043e|\u043e\u043f\u043b\u0430\u0442|\u043f\u043b\u0430\u0442(?:\u0435|\u0451)\u0436|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442|\u0434\u043e\u0433\u043e\u0432\u043e\u0440|\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442|\u0441\u0447(?:\u0435|\u0451)\u0442|\u043e\u0431\u043e\u0440\u043e\u0442|\u043f\u0435\u0440\u0438\u043e\u0434|\u043c\u0435\u0441\u044f\u0446|\u0433\u043e\u0434|\u0441\u043a\u043b\u0430\u0434|\u0442\u043e\u0432\u0430\u0440|\u043d\u043e\u043c\u0435\u043d\u043a\u043b\u0430\u0442|\u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b|advance|prepay|shipment|receivab|payab|counterparty|contract|document|account|balance|turnover|warehouse|inventory|stock|item)/iu.test(lower);
|
|
||||||
if (hasBroadInterrogative && hasBroadBusinessObject) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const hasRussianRetrievalAction = /(?:^|\s)(?:\u043f\u043e\u043a\u0430\u0436\u0438|\u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c|\u043d\u0430\u0439\u0434\u0438|\u0432\u044b\u0432\u0435\u0434\u0438|\u0434\u0430\u0439|\u0440\u0430\u0441\u043a\u0440\u043e\u0439|\u0441\u043f\u0438\u0441\u043e\u043a|\u043f\u0440\u043e\u0432\u0435\u0440\u044c|\u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c)(?:$|[\s,.!?;:])/iu.test(lower);
|
|
||||||
const hasRussianRetrievalObject = /(?:\u0434\u043e\u0433\u043e\u0432\u043e\u0440|\u043a\u043e\u043d\u0442\u0440\u0430\u043a\u0442|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442|\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442|\u0441\u0447(?:\u0435|\u0451)\u0442|\u043e\u0441\u0442\u0430\u0442|\u0441\u0430\u043b\u044c\u0434\u043e|\u043e\u0431\u043e\u0440\u043e\u0442|\u043f\u043b\u0430\u0442(?:\u0435|\u0451)\u0436|\u043e\u043f\u0435\u0440\u0430\u0446|\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|\u043a\u043b\u0438\u0435\u043d\u0442|\u0433\u043e\u0434|\u043f\u0435\u0440\u0438\u043e\u0434|\u043c\u0435\u0441\u044f\u0446|\u0430\u0432\u0430\u043d\u0441|\u043f\u0440\u0435\u0434\u043e\u043f\u043b\u0430\u0442|\u043e\u0442\u0433\u0440\u0443\u0437|\u0437\u0430\u0434\u043e\u043b\u0436|\u0434\u043e\u043b\u0433|\u0441\u043a\u043b\u0430\u0434|\u0442\u043e\u0432\u0430\u0440|\u043d\u043e\u043c\u0435\u043d\u043a\u043b\u0430\u0442|\u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b)/iu.test(lower);
|
|
||||||
if (hasRussianRetrievalAction && hasRussianRetrievalObject) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const hasExplicitRetrievalAction = /(?:\bпокажи\b|\bпоказать\b|\bвыведи\b|\bнайди\b|\bсписок\b|\bдай\b|\bраскрой\b|\bshow\b|\blist\b|\bfind\b|\bcount\b)/i.test(lower);
|
|
||||||
const hasInterrogativeRetrievalAction = /(?:\bсколько\b|\bкакой\b|\bкакая\b|\bкакое\b|\bкакую\b|\bкакие\b|\bкто\b|\bгде\b|\bпо\s+каким\b|\bпо\s+кому\b|\bу\s+кого\b|\bwhich\b|\bwho\b|\bwhere\b)/i.test(lower);
|
|
||||||
if (!hasExplicitRetrievalAction && !hasInterrogativeRetrievalAction) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const hasRetrievalObject = /(1с|база|док|документ|контрагент|договор|контракт|счет|сч[её]т|остат|сальдо|хвост|платеж|плат[её]ж|операц|поставщик|клиент|заказчик|дебитор|кредитор|период|месяц|год|инн|аванс|предоплат|отгруз|задолж|долг|склад|товар|номенклат|материал|bank|counterparty|contract|document|account|balance|ledger|posting|advance|prepay|shipment|receivab|payab|warehouse|inventory|stock|item|организац|компан|контор|фирм|возраст|дата\s+регистрац|регистрац|основан)/i.test(lower);
|
|
||||||
if (!hasRetrievalObject) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (hasExplicitRetrievalAction) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const hasMetaCapabilityShape = /(?:мож(?:ем|ешь|ете|но)|уме(?:ешь|ете)|доступ|подключ|чья|как\s+называ(?:ет|ется)|работ(?:ать|аем|аешь|аете)|в\s+тебе|у\s+тебя)/i.test(lower);
|
|
||||||
return !hasMetaCapabilityShape;
|
|
||||||
}
|
}
|
||||||
function hasOrganizationFactLookupSignal(text) {
|
function hasOrganizationFactLookupSignal(text) {
|
||||||
const repaired = repairAddressMojibake(String(text ?? ""));
|
return assistantLivingModePolicy.hasOrganizationFactLookupSignal(text);
|
||||||
const normalized = compactWhitespace(repaired.toLowerCase()).replace(/ё/g, "е");
|
|
||||||
if (!normalized) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const hasFactCue = /(?:возраст|сколько\s+лет|дата\s+регистрац|когда\s+(?:зарегистр|создан|основан)|год\s+регистрац|год\s+основан|с\s+какого\s+года|when\s+was\s+(?:it\s+)?(?:registered|founded|created))/i.test(normalized);
|
|
||||||
if (!hasFactCue) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return /(?:организац|компан|контор|фирм|ооо|ао|зао|ип|альтернатив|лайсвуд|райм|organization|company)/i.test(normalized);
|
|
||||||
}
|
}
|
||||||
function findLastAssistantLivingChatDebug(items) {
|
function findLastAssistantLivingChatDebug(items) {
|
||||||
if (!Array.isArray(items)) {
|
if (!Array.isArray(items)) {
|
||||||
|
|
@ -4929,67 +4325,13 @@ function findLastOrganizationClarificationAddressDebug(items) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
function hasMetaAnswerFollowupSignal(userMessage) {
|
function hasMetaAnswerFollowupSignal(userMessage) {
|
||||||
const rawText = compactWhitespace(String(userMessage ?? "").toLowerCase());
|
return assistantLivingModePolicy.hasMetaAnswerFollowupSignal(userMessage);
|
||||||
const repairedText = compactWhitespace(repairAddressMojibake(String(userMessage ?? "")).toLowerCase());
|
|
||||||
const samples = [rawText, repairedText]
|
|
||||||
.filter((item) => item.length > 0)
|
|
||||||
.map((item) => item.replace(/ё/g, "е"));
|
|
||||||
if (samples.length === 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const hasReflectionCue = samples.some((sample) => sample.includes("дума") ||
|
|
||||||
sample.includes("скаж") ||
|
|
||||||
sample.includes("мнение") ||
|
|
||||||
sample.includes("как тебе") ||
|
|
||||||
sample.includes("норм") ||
|
|
||||||
sample.includes("стран") ||
|
|
||||||
sample.includes("логич") ||
|
|
||||||
sample.includes("смуща") ||
|
|
||||||
sample.includes("выгляд"));
|
|
||||||
const hasTopicPointerCue = samples.some((sample) => sample.includes("на эту тему") ||
|
|
||||||
sample.includes("по этому поводу") ||
|
|
||||||
sample.includes("об этом") ||
|
|
||||||
(sample.includes("это") && hasReferentialPointer(sample)));
|
|
||||||
const hasEvaluationCue = samples.some((sample) => /\b(?:много|мало|нормально|хорошо|плохо|критично|перебор|слабо)\b/iu.test(sample));
|
|
||||||
if (!((hasReflectionCue || hasEvaluationCue) &&
|
|
||||||
(hasTopicPointerCue || (hasEvaluationCue && samples.some((sample) => /^(?:это|ну это)\b/iu.test(sample)))))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return !samples.some((sample) => hasAssistantDataScopeMetaQuestionSignal(sample) ||
|
|
||||||
shouldHandleAsAssistantCapabilityMetaQuery(sample) ||
|
|
||||||
hasDataRetrievalRequestSignal(sample) ||
|
|
||||||
hasStrongDataIntentSignal(sample));
|
|
||||||
}
|
}
|
||||||
function hasConversationMemoryRecallFollowupSignal(userMessage) {
|
function hasConversationMemoryRecallFollowupSignal(userMessage) {
|
||||||
const rawText = compactWhitespace(String(userMessage ?? "").toLowerCase());
|
return assistantLivingModePolicy.hasConversationMemoryRecallFollowupSignal(userMessage);
|
||||||
const repairedText = compactWhitespace(repairAddressMojibake(String(userMessage ?? "")).toLowerCase());
|
|
||||||
const samples = [rawText, repairedText]
|
|
||||||
.filter((item) => item.length > 0)
|
|
||||||
.map((item) => item.replace(/ё/g, "е"));
|
|
||||||
if (samples.length === 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const hasMemoryCue = samples.some((sample) => /(?:помни(?:шь|те|м)?|remember|recall)/iu.test(sample));
|
|
||||||
const hasDiscussionCue = samples.some((sample) => /(?:обсуждал[аи]?|говорил[аи]?|смотрел[аи]?|разбирал[аи]?|спрашивал[аи]?)/iu.test(sample));
|
|
||||||
if (!hasMemoryCue || !hasDiscussionCue) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return !samples.some((sample) => hasAssistantDataScopeMetaQuestionSignal(sample) ||
|
|
||||||
shouldHandleAsAssistantCapabilityMetaQuery(sample) ||
|
|
||||||
hasDataRetrievalRequestSignal(sample) ||
|
|
||||||
hasStrongDataIntentSignal(sample));
|
|
||||||
}
|
}
|
||||||
function hasHistoricalCapabilityFollowupSignal(text) {
|
function hasHistoricalCapabilityFollowupSignal(text) {
|
||||||
const repaired = repairAddressMojibake(String(text ?? ""));
|
return assistantLivingModePolicy.hasHistoricalCapabilityFollowupSignal(text);
|
||||||
const normalized = compactWhitespace(repaired.toLowerCase()).replace(/ё/g, "е");
|
|
||||||
if (!normalized) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const hasHistoryCue = /(?:историческ|история|архив|прошл(?:ый|ые|ую|ых)?|раньше|ретро|старые\s+данные)/iu.test(normalized);
|
|
||||||
if (!hasHistoryCue) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return /(?:мож(?:ешь|ете|но)|уме(?:ешь|ете)|показ|вывед|дай|раскрой)/iu.test(normalized);
|
|
||||||
}
|
}
|
||||||
function isGroundedInventoryContextDebug(debug) {
|
function isGroundedInventoryContextDebug(debug) {
|
||||||
if (!debug || typeof debug !== "object") {
|
if (!debug || typeof debug !== "object") {
|
||||||
|
|
@ -5006,56 +4348,10 @@ function isGroundedInventoryContextDebug(debug) {
|
||||||
rootIntent === "inventory_on_hand_as_of_date";
|
rootIntent === "inventory_on_hand_as_of_date";
|
||||||
}
|
}
|
||||||
function hasOrganizationFactFollowupSignal(userMessage, items) {
|
function hasOrganizationFactFollowupSignal(userMessage, items) {
|
||||||
const repaired = repairAddressMojibake(String(userMessage ?? ""));
|
return assistantLivingModePolicy.hasOrganizationFactFollowupSignal(userMessage, items);
|
||||||
const normalized = compactWhitespace(repaired.toLowerCase()).replace(/ё/g, "е");
|
|
||||||
if (!normalized) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (hasOrganizationFactLookupSignal(normalized)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const hasFollowupCue = /(?:^|\s)(?:давай|го|погнали|ок(?:ей)?|хорошо|принято|подтверждаю|запрашивай|запроси|проверь|продолжай|ну\s+давай|да\s+давай)(?=$|[\s,.!?;:])/iu.test(normalized);
|
|
||||||
if (!hasFollowupCue) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const lastDebug = findLastAssistantLivingChatDebug(items);
|
|
||||||
const lastSource = toNonEmptyString(lastDebug?.living_chat_response_source);
|
|
||||||
const lastGuardReason = toNonEmptyString(lastDebug?.living_chat_grounding_guard_reason);
|
|
||||||
const inOrganizationFactBoundary = lastSource === "deterministic_organization_fact_boundary" ||
|
|
||||||
lastSource === "deterministic_organization_fact_boundary_followup" ||
|
|
||||||
lastGuardReason === "organization_fact_without_live_source_blocked";
|
|
||||||
return inOrganizationFactBoundary;
|
|
||||||
}
|
}
|
||||||
function shouldEmitOrganizationSelectionReply(userMessage, selectedOrganization) {
|
function shouldEmitOrganizationSelectionReply(userMessage, selectedOrganization) {
|
||||||
const selected = normalizeOrganizationScopeValue(selectedOrganization);
|
return assistantLivingModePolicy.shouldEmitOrganizationSelectionReply(userMessage, selectedOrganization);
|
||||||
if (!selected) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const repaired = repairAddressMojibake(String(userMessage ?? ""));
|
|
||||||
const normalized = compactWhitespace(repaired.toLowerCase()).replace(/ё/g, "е");
|
|
||||||
if (!normalized) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (hasOrganizationFactLookupSignal(normalized) || hasDataRetrievalRequestSignal(normalized) || hasStrongDataIntentSignal(normalized)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const hasAnalyticalCue = /(?:какой|какая|какие|когда|сколько|кто|почему|зачем|возраст|дата|регистрац|ндс|налог|контракт|договор|документ|операц|оборот|сумм|остат|сальдо|founded|registered|created)/i.test(normalized);
|
|
||||||
if (hasAnalyticalCue) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const hasSelectionCue = /(?:давай|го|погнали|ок(?:ей)?|хорошо|отлично|берем|выберем|выбираем|переключ(?:им|аем|ай)|фиксир|работаем|обсудим|тогда)\b/i.test(normalized);
|
|
||||||
if (hasSelectionCue) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const hasAffectiveReactionCue = /(?:^|[\s,.;:!?()\-])(?:ну|мда|ох|ах|офигеть|офигенно|ахуеть|охуеть|пиздец|пизда|нихуя|хуево|хуёво|ебать|ебан|бля|блять|fuck|shit|damn)(?=$|[\s,.;:!?()\-])/iu.test(normalized) ||
|
|
||||||
normalized.includes("\u0430\u0445\u0443") ||
|
|
||||||
normalized.includes("\u043e\u0445\u0443") ||
|
|
||||||
normalized.includes("\u043f\u0438\u0437\u0434") ||
|
|
||||||
normalized.includes("\u0431\u043b\u044f");
|
|
||||||
if (hasAffectiveReactionCue) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return normalized.length <= 36 && !/[?]/.test(String(userMessage ?? ""));
|
|
||||||
}
|
}
|
||||||
function hasOperationalAdminActionRequestSignal(text) {
|
function hasOperationalAdminActionRequestSignal(text) {
|
||||||
const lower = compactWhitespace(String(text ?? "").toLowerCase()).replace(/ё/g, "е");
|
const lower = compactWhitespace(String(text ?? "").toLowerCase()).replace(/ё/g, "е");
|
||||||
|
|
@ -5131,78 +4427,13 @@ function hasAssistantCapabilityQuestionSignal(text) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
function hasAssistantDataScopeMetaQuestionSignal(text) {
|
function hasAssistantDataScopeMetaQuestionSignal(text) {
|
||||||
const repaired = repairAddressMojibake(String(text ?? ""));
|
return assistantLivingModePolicy.hasAssistantDataScopeMetaQuestionSignal(text);
|
||||||
const lower = compactWhitespace(repaired.toLowerCase()).replace(/ё/g, "е");
|
|
||||||
const normalized = lower.replace(/\b1\s*[cс]\b/giu, "1с");
|
|
||||||
if (!normalized) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const hasDirectSlangScopeLead = /(?:по\s+каким\s+(?:контор(?:ам|ы|а)?|кантор(?:ам|ы|а)?|компан(?:иям|ии|ию|ия)|организац(?:иям|ии|ию|ия))\s+мож(?:ем|но)\s+(?:общат|работ)|база\s+какой\s+(?:контор|компан|организац|фирм)|какая\s+база\s+(?:подключ|подруб|актив))/iu.test(normalized);
|
|
||||||
if (hasDirectSlangScopeLead) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const hasSlangScopeQuestion = /(?:\u043f\u043e\s+\u043a\u0430\u043a\u0438\u043c\s+(?:\u043a\u043e\u043d\u0442\u043e\u0440(?:\u0430\u043c|\u044b|\u0430)?|\u043a\u043e\u043c\u043f\u0430\u043d(?:\u0438\u044f\u043c|\u0438\u0438|\u0438\u044e|\u0438\u044f)|\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446(?:\u0438\u044f\u043c|\u0438\u0438|\u0438\u044e|\u0438\u044f)|\u0444\u0438\u0440\u043c(?:\u0430\u043c|\u0435|\u0443|\u0430)).*(?:\u043c\u043e\u0436(?:\u0435\u043c|\u043d\u043e)|\u0440\u0430\u0431\u043e\u0442|\u043e\u0431\u0449\u0430\u0442|\u043f\u043e\u0434\u0440\u0443\u0431|\u043f\u043e\u0434\u043a\u043b\u044e\u0447)|(?:\u0431\u0430\u0437\u0430\s+\u043a\u0430\u043a\u043e\u0439\s+(?:\u043a\u043e\u043d\u0442\u043e\u0440|\u043a\u043e\u043c\u043f\u0430\u043d|\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446|\u0444\u0438\u0440\u043c))|(?:\u043a\u0430\u043a\u0430\u044f\s+\u0431\u0430\u0437\u0430\s+(?:\u043f\u043e\u0434\u043a\u043b\u044e\u0447|\u0430\u043a\u0442\u0438\u0432)))/iu.test(normalized);
|
|
||||||
if (hasSlangScopeQuestion) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const hasBaseOrTenantObject = /(?:баз(?:а|е|у|ы)?|тенант|tenant|контур)/i.test(normalized);
|
|
||||||
const hasCompanyObject = /(?:компан(?:ия|ии|ию|ией)|компин(?:ия|ии|ию|ией)?|компини(?:я|и|ю|ей)?|компани[яеию]|организац(?:ия|ии|ию|ией)|контор(?:а|ы|у|ой)?|фирм(?:а|ы|у|ой)?)/i.test(normalized);
|
|
||||||
const hasConnectionCue = /(?:подключен(?:а|о|ы)?|подруб|воткнут|активн(?:ый|ая)\s+канал|mcp-?канал|канал)/i.test(normalized);
|
|
||||||
const hasNamingCue = /(?:как\s+называ(?:ет|ется)|что\s+за\s+(?:контор|компан|организац|фирм))/i.test(normalized);
|
|
||||||
const hasWorkabilityCue = /(?:мож(?:ем|ешь|ете|но)\s+работ|работ(?:ать|аем|аешь|аете))/i.test(normalized);
|
|
||||||
const hasScopeObject = hasBaseOrTenantObject || hasCompanyObject || hasConnectionCue;
|
|
||||||
if (!hasScopeObject) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const hasMetaPerspective = /(?:ты|тебе|твой|у\s+тебя|в\s+тебе|мы|нам|наш(?:а|е|и|у|ей)?|сейчас|щас)/i.test(normalized);
|
|
||||||
const hasScopedInterrogativePair = /(?:^|\s)(?:по\s+какой|с\s+какой|какая|какой|какие)\s+(?:баз|компан|компин|компини|компани|организац|контор|фирм|тенант|контур)/i.test(normalized);
|
|
||||||
const hasScopeQuestion = /(?:чья|чье|чьи|доступн|подключен|подруб|воткнут|какая\s+баз|какой\s+баз)/i.test(normalized) ||
|
|
||||||
hasNamingCue ||
|
|
||||||
hasWorkabilityCue ||
|
|
||||||
hasScopedInterrogativePair;
|
|
||||||
const hasInterrogativeScopeLead = /(?:^|\s)(?:по\s+какой|с\s+какой|чья|чье|чьи|which|who|what)/i.test(normalized);
|
|
||||||
const isQuestionLike = /[?]/.test(String(text ?? "")) || hasInterrogativeScopeLead || hasScopedInterrogativePair;
|
|
||||||
const hasExplicitScopeContext = hasBaseOrTenantObject || hasConnectionCue || hasWorkabilityCue || hasNamingCue;
|
|
||||||
const hasRetrievalSignal = hasDataRetrievalRequestSignal(normalized);
|
|
||||||
const hasContractAnalyticsCue = /(?:договор|контракт|contract).*(?:топ|сам(?:ый|ая|ое|ые)|крупн|жирн|оборот|бюджет|сумм|стоим|value|turnover|all\s+time|всю\s+истори|за\s+вс[её]\s+время)/iu.test(normalized);
|
|
||||||
if (hasContractAnalyticsCue) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (hasRetrievalSignal && !hasExplicitScopeContext) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const hasEligibleScopeObject = hasBaseOrTenantObject || (hasCompanyObject && (hasConnectionCue || hasWorkabilityCue || hasNamingCue || hasMetaPerspective));
|
|
||||||
return hasEligibleScopeObject && hasScopeQuestion && (hasMetaPerspective || isQuestionLike || hasExplicitScopeContext);
|
|
||||||
}
|
}
|
||||||
function shouldHandleAsAssistantCapabilityMetaQuery(text) {
|
function shouldHandleAsAssistantCapabilityMetaQuery(text) {
|
||||||
const raw = String(text ?? "");
|
return assistantLivingModePolicy.shouldHandleAsAssistantCapabilityMetaQuery(text);
|
||||||
const repaired = repairAddressMojibake(raw);
|
|
||||||
const hasScopeMetaSignal = hasAssistantDataScopeMetaQuestionSignal(raw) || hasAssistantDataScopeMetaQuestionSignal(repaired);
|
|
||||||
if (hasScopeMetaSignal) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const hasCapabilitySignal = hasAssistantCapabilityQuestionSignal(raw) ||
|
|
||||||
hasAssistantCapabilityQuestionSignal(repaired) ||
|
|
||||||
hasOperationalAdminActionRequestSignal(raw) ||
|
|
||||||
hasOperationalAdminActionRequestSignal(repaired);
|
|
||||||
const hasRetrievalSignal = hasDataRetrievalRequestSignal(raw) || hasDataRetrievalRequestSignal(repaired);
|
|
||||||
return hasCapabilitySignal && !hasRetrievalSignal;
|
|
||||||
}
|
}
|
||||||
function hasLivingChatSignal(text) {
|
function hasLivingChatSignal(text) {
|
||||||
const lower = compactWhitespace(String(text ?? "").toLowerCase());
|
return assistantLivingModePolicy.hasLivingChatSignal(text);
|
||||||
if (!lower) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (/^(?:а\s+)?(?:тут|здесь|там|сюда|туда)[\s!?.,:;\-]*$/iu.test(lower)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (/^(ага|угу|ок|окей|ясно|понял|поняла|принято|спасибо|благодарю|супер|класс|норм|го|давай|погнали|привет|хай|йо|yo|че\s+там|ч[её]\s+как|че\s+как|hello|hi|thanks?)$/i.test(lower)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (/(как дела|как ты|что нового|расскажи о себе|чем можешь помочь|давай поговорим|поговорим|обсудим|посоветуй|что думаешь)/i.test(lower)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return hasSmallTalkSignal(lower);
|
|
||||||
}
|
}
|
||||||
function buildAssistantCapabilityContractReply() {
|
function buildAssistantCapabilityContractReply() {
|
||||||
return (0, capabilitiesRegistry_1.buildCapabilityContractReplyFromRegistry)();
|
return (0, capabilitiesRegistry_1.buildCapabilityContractReplyFromRegistry)();
|
||||||
|
|
@ -5306,6 +4537,55 @@ function normalizeOrganizationScopeValue(value) {
|
||||||
.trim();
|
.trim();
|
||||||
return unwrapped ? unwrapped : null;
|
return unwrapped ? unwrapped : null;
|
||||||
}
|
}
|
||||||
|
const assistantLivingModePolicy = (0, assistantLivingModePolicy_1.createAssistantLivingModePolicy)({
|
||||||
|
featureAssistantLivingChatRouterV1: config_1.FEATURE_ASSISTANT_LIVING_CHAT_ROUTER_V1,
|
||||||
|
compactWhitespace,
|
||||||
|
repairAddressMojibake,
|
||||||
|
toNonEmptyString,
|
||||||
|
normalizeOrganizationScopeValue,
|
||||||
|
hasReferentialPointer,
|
||||||
|
hasSmallTalkSignal,
|
||||||
|
hasAssistantCapabilityQuestionSignal,
|
||||||
|
hasOperationalAdminActionRequestSignal
|
||||||
|
});
|
||||||
|
const assistantRoutePolicy = (0, assistantRoutePolicy_1.createAssistantRoutePolicy)({
|
||||||
|
repairAddressMojibake,
|
||||||
|
findLastGroundedAddressAnswerDebug,
|
||||||
|
findLastOrganizationClarificationAddressDebug,
|
||||||
|
mergeKnownOrganizations,
|
||||||
|
normalizeOrganizationScopeValue,
|
||||||
|
resolveOrganizationSelectionFromMessage,
|
||||||
|
hasAssistantDataScopeMetaQuestionSignal: assistantLivingModePolicy.hasAssistantDataScopeMetaQuestionSignal,
|
||||||
|
shouldHandleAsAssistantCapabilityMetaQuery: assistantLivingModePolicy.shouldHandleAsAssistantCapabilityMetaQuery,
|
||||||
|
hasDataRetrievalRequestSignal: assistantLivingModePolicy.hasDataRetrievalRequestSignal,
|
||||||
|
hasAggregateBusinessAnalyticsSignal,
|
||||||
|
hasStandaloneAddressTopicSignal,
|
||||||
|
hasOpenContractsAddressSignal,
|
||||||
|
detectAddressQuestionMode: addressQueryClassifier_1.detectAddressQuestionMode,
|
||||||
|
resolveAddressIntent: addressIntentResolver_1.resolveAddressIntent,
|
||||||
|
toNonEmptyString,
|
||||||
|
hasStrictDeepInvestigationCue,
|
||||||
|
hasStrongDataIntentSignal: assistantLivingModePolicy.hasStrongDataIntentSignal,
|
||||||
|
hasAccountingSignal,
|
||||||
|
hasDangerOrCoercionSignal,
|
||||||
|
hasAddressFollowupContextSignal,
|
||||||
|
hasShortDebtMirrorFollowupSignal,
|
||||||
|
isInventorySelectedObjectIntent,
|
||||||
|
hasShortInventoryObjectFollowupSignal,
|
||||||
|
hasHistoricalCapabilityFollowupSignal: assistantLivingModePolicy.hasHistoricalCapabilityFollowupSignal,
|
||||||
|
isGroundedInventoryContextDebug,
|
||||||
|
hasConversationMemoryRecallFollowupSignal: assistantLivingModePolicy.hasConversationMemoryRecallFollowupSignal,
|
||||||
|
findLastAddressAssistantItem,
|
||||||
|
hasMetaAnswerFollowupSignal: assistantLivingModePolicy.hasMetaAnswerFollowupSignal,
|
||||||
|
resolveAddressToolGateDecision,
|
||||||
|
hasSameDateAccountFollowupSignalForPredecompose,
|
||||||
|
hasLooseAllTimeAddressLookupSignal,
|
||||||
|
hasDeepAnalysisPreferenceSignal,
|
||||||
|
hasDirectDeepAnalysisSignal,
|
||||||
|
compactWhitespace,
|
||||||
|
hasDeepSessionContinuationSignal,
|
||||||
|
resolveLivingAssistantModeDecision: assistantLivingModePolicy.resolveLivingAssistantModeDecision
|
||||||
|
});
|
||||||
function normalizeOrganizationScopeSearchText(value) {
|
function normalizeOrganizationScopeSearchText(value) {
|
||||||
const source = normalizeScopeKey(value);
|
const source = normalizeScopeKey(value);
|
||||||
return source
|
return source
|
||||||
|
|
@ -6105,67 +5385,7 @@ function applyLivingChatGroundingGuard(input) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
function resolveLivingAssistantModeDecision(input) {
|
function resolveLivingAssistantModeDecision(input) {
|
||||||
const userMessage = String(input?.userMessage ?? "");
|
return assistantLivingModePolicy.resolveLivingAssistantModeDecision(input);
|
||||||
if (input?.addressLaneTriggered) {
|
|
||||||
return {
|
|
||||||
mode: "address_data",
|
|
||||||
reason: "address_lane_triggered"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (!config_1.FEATURE_ASSISTANT_LIVING_CHAT_ROUTER_V1) {
|
|
||||||
return {
|
|
||||||
mode: "deep_analysis",
|
|
||||||
reason: "living_chat_router_disabled"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (Boolean(input?.useMock)) {
|
|
||||||
return {
|
|
||||||
mode: "deep_analysis",
|
|
||||||
reason: "mock_mode_keeps_deep_pipeline"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (hasAssistantDataScopeMetaQuestionSignal(userMessage)) {
|
|
||||||
return {
|
|
||||||
mode: "chat",
|
|
||||||
reason: "assistant_data_scope_query_detected"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (shouldHandleAsAssistantCapabilityMetaQuery(userMessage)) {
|
|
||||||
return {
|
|
||||||
mode: "chat",
|
|
||||||
reason: "assistant_capability_query_detected"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (hasOrganizationFactLookupSignal(userMessage) || hasOrganizationFactFollowupSignal(userMessage)) {
|
|
||||||
return {
|
|
||||||
mode: "chat",
|
|
||||||
reason: "organization_fact_lookup_signal_detected"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (hasStrongDataIntentSignal(userMessage)) {
|
|
||||||
return {
|
|
||||||
mode: "deep_analysis",
|
|
||||||
reason: "strong_data_signal_detected"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (hasLivingChatSignal(userMessage)) {
|
|
||||||
return {
|
|
||||||
mode: "chat",
|
|
||||||
reason: "living_chat_signal_detected"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const predecomposeMode = toNonEmptyString(input?.predecomposeMode);
|
|
||||||
const predecomposeConfidence = toNonEmptyString(input?.predecomposeModeConfidence);
|
|
||||||
if (predecomposeMode === "unsupported" && (predecomposeConfidence === "low" || predecomposeConfidence === "medium")) {
|
|
||||||
return {
|
|
||||||
mode: "deep_analysis",
|
|
||||||
reason: "predecompose_unsupported_mode_fallback_to_deep"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
mode: "deep_analysis",
|
|
||||||
reason: "default_deep_pipeline"
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
class AssistantService {
|
class AssistantService {
|
||||||
normalizerService;
|
normalizerService;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,395 @@
|
||||||
|
// @ts-nocheck
|
||||||
|
export interface ResolveLivingAssistantModeDecisionInput {
|
||||||
|
userMessage?: unknown;
|
||||||
|
addressLaneTriggered?: boolean;
|
||||||
|
useMock?: boolean;
|
||||||
|
predecomposeMode?: unknown;
|
||||||
|
predecomposeModeConfidence?: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AssistantLivingModePolicyDeps {
|
||||||
|
featureAssistantLivingChatRouterV1: boolean;
|
||||||
|
compactWhitespace: (text: string) => string;
|
||||||
|
repairAddressMojibake: (text: string) => string;
|
||||||
|
toNonEmptyString: (value: unknown) => string | null;
|
||||||
|
normalizeOrganizationScopeValue: (value: unknown) => string | null;
|
||||||
|
hasReferentialPointer: (text: string) => boolean;
|
||||||
|
hasSmallTalkSignal: (text: string) => boolean;
|
||||||
|
hasAssistantCapabilityQuestionSignal: (text: string) => boolean;
|
||||||
|
hasOperationalAdminActionRequestSignal: (text: string) => boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AssistantLivingModeDecision {
|
||||||
|
mode: "address_data" | "deep_analysis" | "chat";
|
||||||
|
reason: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AssistantLivingModePolicy {
|
||||||
|
hasStrongDataIntentSignal: (text: unknown) => boolean;
|
||||||
|
hasDataRetrievalRequestSignal: (text: unknown) => boolean;
|
||||||
|
hasOrganizationFactLookupSignal: (text: unknown) => boolean;
|
||||||
|
hasMetaAnswerFollowupSignal: (text: unknown) => boolean;
|
||||||
|
hasConversationMemoryRecallFollowupSignal: (text: unknown) => boolean;
|
||||||
|
hasHistoricalCapabilityFollowupSignal: (text: unknown) => boolean;
|
||||||
|
hasOrganizationFactFollowupSignal: (userMessage: unknown, items: unknown[]) => boolean;
|
||||||
|
shouldEmitOrganizationSelectionReply: (userMessage: unknown, selectedOrganization: unknown) => boolean;
|
||||||
|
hasAssistantDataScopeMetaQuestionSignal: (text: unknown) => boolean;
|
||||||
|
shouldHandleAsAssistantCapabilityMetaQuery: (text: unknown) => boolean;
|
||||||
|
hasLivingChatSignal: (text: unknown) => boolean;
|
||||||
|
resolveLivingAssistantModeDecision: (input: ResolveLivingAssistantModeDecisionInput) => AssistantLivingModeDecision;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createAssistantLivingModePolicy(deps: AssistantLivingModePolicyDeps): AssistantLivingModePolicy {
|
||||||
|
const {
|
||||||
|
featureAssistantLivingChatRouterV1,
|
||||||
|
compactWhitespace,
|
||||||
|
repairAddressMojibake,
|
||||||
|
toNonEmptyString,
|
||||||
|
normalizeOrganizationScopeValue,
|
||||||
|
hasReferentialPointer,
|
||||||
|
hasSmallTalkSignal,
|
||||||
|
hasAssistantCapabilityQuestionSignal,
|
||||||
|
hasOperationalAdminActionRequestSignal
|
||||||
|
} = deps;
|
||||||
|
|
||||||
|
function hasStrongDataIntentSignal(text) {
|
||||||
|
const lower = String(text ?? "").toLowerCase();
|
||||||
|
return /(база|док|документ|проводк|контрагент|договор|контракт|счет|сч[её]т|остат|сальдо|хвост|платеж|плат[её]ж|операц|поставщик|клиент|заказчик|дебитор|кредитор|оборот|баланс|период|месяц|год|инн|аванс|предоплат|отгруз|задолж|долг|склад|товар|номенклат|материал|mcp|bank|counterparty|contract|document|ledger|posting|account|organization|company|advance|prepay|shipment|receivab|payab|warehouse|inventory|stock|item|организац|компан|контор|фирм)/i.test(lower);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasDataRetrievalRequestSignal(text) {
|
||||||
|
const lower = compactWhitespace(String(text ?? "").toLowerCase());
|
||||||
|
if (!lower) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hasBroadInterrogative = /(?:\u0433\u0434\u0435|\u0432\s+\u043a\u0430\u043a\u0438\u0445|\u043f\u043e\s+\u043a\u0430\u043a\u0438\u043c|\u043f\u043e\s+\u043a\u043e\u043c\u0443|\u043a\u0430\u043a\u0438\u0435|\u043a\u0430\u043a\u043e\u0439|\u043a\u0442\u043e|\u0441\u043a\u043e\u043b\u044c\u043a\u043e|where|which|who|how\s+many)/iu.test(lower);
|
||||||
|
const hasBroadBusinessObject = /(?:\u0430\u0432\u0430\u043d\u0441|\u043f\u0440\u0435\u0434\u043e\u043f\u043b\u0430\u0442|\u043e\u0442\u0433\u0440\u0443\u0437|\u0437\u0430\u0434\u043e\u043b\u0436|\u0434\u043e\u043b\u0433|\u0441\u0430\u043b\u044c\u0434\u043e|\u043e\u043f\u043b\u0430\u0442|\u043f\u043b\u0430\u0442(?:\u0435|\u0451)\u0436|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442|\u0434\u043e\u0433\u043e\u0432\u043e\u0440|\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442|\u0441\u0447(?:\u0435|\u0451)\u0442|\u043e\u0431\u043e\u0440\u043e\u0442|\u043f\u0435\u0440\u0438\u043e\u0434|\u043c\u0435\u0441\u044f\u0446|\u0433\u043e\u0434|\u0441\u043a\u043b\u0430\u0434|\u0442\u043e\u0432\u0430\u0440|\u043d\u043e\u043c\u0435\u043d\u043a\u043b\u0430\u0442|\u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b|advance|prepay|shipment|receivab|payab|counterparty|contract|document|account|balance|turnover|warehouse|inventory|stock|item)/iu.test(lower);
|
||||||
|
if (hasBroadInterrogative && hasBroadBusinessObject) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const hasRussianRetrievalAction = /(?:^|\s)(?:\u043f\u043e\u043a\u0430\u0436\u0438|\u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c|\u043d\u0430\u0439\u0434\u0438|\u0432\u044b\u0432\u0435\u0434\u0438|\u0434\u0430\u0439|\u0440\u0430\u0441\u043a\u0440\u043e\u0439|\u0441\u043f\u0438\u0441\u043e\u043a|\u043f\u0440\u043e\u0432\u0435\u0440\u044c|\u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c)(?:$|[\s,.!?;:])/iu.test(lower);
|
||||||
|
const hasRussianRetrievalObject = /(?:\u0434\u043e\u0433\u043e\u0432\u043e\u0440|\u043a\u043e\u043d\u0442\u0440\u0430\u043a\u0442|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442|\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442|\u0441\u0447(?:\u0435|\u0451)\u0442|\u043e\u0441\u0442\u0430\u0442|\u0441\u0430\u043b\u044c\u0434\u043e|\u043e\u0431\u043e\u0440\u043e\u0442|\u043f\u043b\u0430\u0442(?:\u0435|\u0451)\u0436|\u043e\u043f\u0435\u0440\u0430\u0446|\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|\u043a\u043b\u0438\u0435\u043d\u0442|\u0433\u043e\u0434|\u043f\u0435\u0440\u0438\u043e\u0434|\u043c\u0435\u0441\u044f\u0446|\u0430\u0432\u0430\u043d\u0441|\u043f\u0440\u0435\u0434\u043e\u043f\u043b\u0430\u0442|\u043e\u0442\u0433\u0440\u0443\u0437|\u0437\u0430\u0434\u043e\u043b\u0436|\u0434\u043e\u043b\u0433|\u0441\u043a\u043b\u0430\u0434|\u0442\u043e\u0432\u0430\u0440|\u043d\u043e\u043c\u0435\u043d\u043a\u043b\u0430\u0442|\u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b)/iu.test(lower);
|
||||||
|
if (hasRussianRetrievalAction && hasRussianRetrievalObject) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const hasExplicitRetrievalAction = /(?:\bпокажи\b|\bпоказать\b|\bвыведи\b|\bнайди\b|\bсписок\b|\bдай\b|\bраскрой\b|\bshow\b|\blist\b|\bfind\b|\bcount\b)/i.test(lower);
|
||||||
|
const hasInterrogativeRetrievalAction = /(?:\bсколько\b|\bкакой\b|\bкакая\b|\bкакое\b|\bкакую\b|\bкакие\b|\bкто\b|\bгде\b|\bпо\s+каким\b|\bпо\s+кому\b|\bу\s+кого\b|\bwhich\b|\bwho\b|\bwhere\b)/i.test(lower);
|
||||||
|
if (!hasExplicitRetrievalAction && !hasInterrogativeRetrievalAction) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hasRetrievalObject = /(1с|база|док|документ|контрагент|договор|контракт|счет|сч[её]т|остат|сальдо|хвост|платеж|плат[её]ж|операц|поставщик|клиент|заказчик|дебитор|кредитор|период|месяц|год|инн|аванс|предоплат|отгруз|задолж|долг|склад|товар|номенклат|материал|bank|counterparty|contract|document|account|balance|ledger|posting|advance|prepay|shipment|receivab|payab|warehouse|inventory|stock|item|организац|компан|контор|фирм|возраст|дата\s+регистрац|регистрац|основан)/i.test(lower);
|
||||||
|
if (!hasRetrievalObject) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (hasExplicitRetrievalAction) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const hasMetaCapabilityShape = /(?:мож(?:ем|ешь|ете|но)|уме(?:ешь|ете)|доступ|подключ|чья|как\s+называ(?:ет|ется)|работ(?:ать|аем|аешь|аете)|в\s+тебе|у\s+тебя)/i.test(lower);
|
||||||
|
return !hasMetaCapabilityShape;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasOrganizationFactLookupSignal(text) {
|
||||||
|
const repaired = repairAddressMojibake(String(text ?? ""));
|
||||||
|
const normalized = compactWhitespace(repaired.toLowerCase()).replace(/ё/g, "е");
|
||||||
|
if (!normalized) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hasFactCue = /(?:возраст|сколько\s+лет|дата\s+регистрац|когда\s+(?:зарегистр|создан|основан)|год\s+регистрац|год\s+основан|с\s+какого\s+года|when\s+was\s+(?:it\s+)?(?:registered|founded|created))/i.test(normalized);
|
||||||
|
if (!hasFactCue) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return /(?:организац|компан|контор|фирм|ооо|ао|зао|ип|альтернатив|лайсвуд|райм|organization|company)/i.test(normalized);
|
||||||
|
}
|
||||||
|
|
||||||
|
function findLastAssistantLivingChatDebug(items) {
|
||||||
|
if (!Array.isArray(items)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (let index = items.length - 1; index >= 0; index -= 1) {
|
||||||
|
const item = items[index];
|
||||||
|
if (!item || item.role !== "assistant") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (item.debug && typeof item.debug === "object") {
|
||||||
|
return item.debug;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasMetaAnswerFollowupSignal(userMessage) {
|
||||||
|
const rawText = compactWhitespace(String(userMessage ?? "").toLowerCase());
|
||||||
|
const repairedText = compactWhitespace(repairAddressMojibake(String(userMessage ?? "")).toLowerCase());
|
||||||
|
const samples = [rawText, repairedText]
|
||||||
|
.filter((item) => item.length > 0)
|
||||||
|
.map((item) => item.replace(/ё/g, "е"));
|
||||||
|
if (samples.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hasReflectionCue = samples.some((sample) => sample.includes("дума") ||
|
||||||
|
sample.includes("скаж") ||
|
||||||
|
sample.includes("мнение") ||
|
||||||
|
sample.includes("как тебе") ||
|
||||||
|
sample.includes("норм") ||
|
||||||
|
sample.includes("стран") ||
|
||||||
|
sample.includes("логич") ||
|
||||||
|
sample.includes("смуща") ||
|
||||||
|
sample.includes("выгляд"));
|
||||||
|
const hasTopicPointerCue = samples.some((sample) => sample.includes("на эту тему") ||
|
||||||
|
sample.includes("по этому поводу") ||
|
||||||
|
sample.includes("об этом") ||
|
||||||
|
(sample.includes("это") && hasReferentialPointer(sample)));
|
||||||
|
const hasEvaluationCue = samples.some((sample) => /\b(?:много|мало|нормально|хорошо|плохо|критично|перебор|слабо)\b/iu.test(sample));
|
||||||
|
if (!((hasReflectionCue || hasEvaluationCue) &&
|
||||||
|
(hasTopicPointerCue || (hasEvaluationCue && samples.some((sample) => /^(?:это|ну это)\b/iu.test(sample)))))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return !samples.some((sample) => hasAssistantDataScopeMetaQuestionSignal(sample) ||
|
||||||
|
shouldHandleAsAssistantCapabilityMetaQuery(sample) ||
|
||||||
|
hasDataRetrievalRequestSignal(sample) ||
|
||||||
|
hasStrongDataIntentSignal(sample));
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasConversationMemoryRecallFollowupSignal(userMessage) {
|
||||||
|
const rawText = compactWhitespace(String(userMessage ?? "").toLowerCase());
|
||||||
|
const repairedText = compactWhitespace(repairAddressMojibake(String(userMessage ?? "")).toLowerCase());
|
||||||
|
const samples = [rawText, repairedText]
|
||||||
|
.filter((item) => item.length > 0)
|
||||||
|
.map((item) => item.replace(/ё/g, "е"));
|
||||||
|
if (samples.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hasMemoryCue = samples.some((sample) => /(?:помни(?:шь|те|м)?|remember|recall)/iu.test(sample));
|
||||||
|
const hasDiscussionCue = samples.some((sample) => /(?:обсуждал[аи]?|говорил[аи]?|смотрел[аи]?|разбирал[аи]?|спрашивал[аи]?)/iu.test(sample));
|
||||||
|
if (!hasMemoryCue || !hasDiscussionCue) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return !samples.some((sample) => hasAssistantDataScopeMetaQuestionSignal(sample) ||
|
||||||
|
shouldHandleAsAssistantCapabilityMetaQuery(sample) ||
|
||||||
|
hasDataRetrievalRequestSignal(sample) ||
|
||||||
|
hasStrongDataIntentSignal(sample));
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasHistoricalCapabilityFollowupSignal(text) {
|
||||||
|
const repaired = repairAddressMojibake(String(text ?? ""));
|
||||||
|
const normalized = compactWhitespace(repaired.toLowerCase()).replace(/ё/g, "е");
|
||||||
|
if (!normalized) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hasHistoryCue = /(?:историческ|история|архив|прошл(?:ый|ые|ую|ых)?|раньше|ретро|старые\s+данные)/iu.test(normalized);
|
||||||
|
if (!hasHistoryCue) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return /(?:мож(?:ешь|ете|но)|уме(?:ешь|ете)|показ|вывед|дай|раскрой)/iu.test(normalized);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasOrganizationFactFollowupSignal(userMessage, items) {
|
||||||
|
const repaired = repairAddressMojibake(String(userMessage ?? ""));
|
||||||
|
const normalized = compactWhitespace(repaired.toLowerCase()).replace(/ё/g, "е");
|
||||||
|
if (!normalized) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (hasOrganizationFactLookupSignal(normalized)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hasFollowupCue = /(?:^|\s)(?:давай|го|погнали|ок(?:ей)?|хорошо|принято|подтверждаю|запрашивай|запроси|проверь|продолжай|ну\s+давай|да\s+давай)(?=$|[\s,.!?;:])/iu.test(normalized);
|
||||||
|
if (!hasFollowupCue) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const lastDebug = findLastAssistantLivingChatDebug(items);
|
||||||
|
const lastSource = toNonEmptyString(lastDebug?.living_chat_response_source);
|
||||||
|
const lastGuardReason = toNonEmptyString(lastDebug?.living_chat_grounding_guard_reason);
|
||||||
|
const inOrganizationFactBoundary = lastSource === "deterministic_organization_fact_boundary" ||
|
||||||
|
lastSource === "deterministic_organization_fact_boundary_followup" ||
|
||||||
|
lastGuardReason === "organization_fact_without_live_source_blocked";
|
||||||
|
return inOrganizationFactBoundary;
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldEmitOrganizationSelectionReply(userMessage, selectedOrganization) {
|
||||||
|
const selected = normalizeOrganizationScopeValue(selectedOrganization);
|
||||||
|
if (!selected) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const repaired = repairAddressMojibake(String(userMessage ?? ""));
|
||||||
|
const normalized = compactWhitespace(repaired.toLowerCase()).replace(/ё/g, "е");
|
||||||
|
if (!normalized) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (hasOrganizationFactLookupSignal(normalized) || hasDataRetrievalRequestSignal(normalized) || hasStrongDataIntentSignal(normalized)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hasAnalyticalCue = /(?:какой|какая|какие|когда|сколько|кто|почему|зачем|возраст|дата|регистрац|ндс|налог|контракт|договор|документ|операц|оборот|сумм|остат|сальдо|founded|registered|created)/i.test(normalized);
|
||||||
|
if (hasAnalyticalCue) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hasSelectionCue = /(?:давай|го|погнали|ок(?:ей)?|хорошо|отлично|берем|выберем|выбираем|переключ(?:им|аем|ай)|фиксир|работаем|обсудим|тогда)\b/i.test(normalized);
|
||||||
|
if (hasSelectionCue) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const hasAffectiveReactionCue = /(?:^|[\s,.;:!?()\-])(?:ну|мда|ох|ах|офигеть|офигенно|ахуеть|охуеть|пиздец|пизда|нихуя|хуево|хуёво|ебать|ебан|бля|блять|fuck|shit|damn)(?=$|[\s,.;:!?()\-])/iu.test(normalized) ||
|
||||||
|
normalized.includes("\u0430\u0445\u0443") ||
|
||||||
|
normalized.includes("\u043e\u0445\u0443") ||
|
||||||
|
normalized.includes("\u043f\u0438\u0437\u0434") ||
|
||||||
|
normalized.includes("\u0431\u043b\u044f");
|
||||||
|
if (hasAffectiveReactionCue) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return normalized.length <= 36 && !/[?]/.test(String(userMessage ?? ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasAssistantDataScopeMetaQuestionSignal(text) {
|
||||||
|
const repaired = repairAddressMojibake(String(text ?? ""));
|
||||||
|
const lower = compactWhitespace(repaired.toLowerCase()).replace(/ё/g, "е");
|
||||||
|
const normalized = lower.replace(/\b1\s*[cс]\b/giu, "1с");
|
||||||
|
if (!normalized) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hasDirectSlangScopeLead = /(?:по\s+каким\s+(?:контор(?:ам|ы|а)?|кантор(?:ам|ы|а)?|компан(?:иям|ии|ию|ия)|организац(?:иям|ии|ию|ия))\s+мож(?:ем|но)\s+(?:общат|работ)|база\s+какой\s+(?:контор|компан|организац|фирм)|какая\s+база\s+(?:подключ|подруб|актив))/iu.test(normalized);
|
||||||
|
if (hasDirectSlangScopeLead) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const hasSlangScopeQuestion = /(?:\u043f\u043e\s+\u043a\u0430\u043a\u0438\u043c\s+(?:\u043a\u043e\u043d\u0442\u043e\u0440(?:\u0430\u043c|\u044b|\u0430)?|\u043a\u043e\u043c\u043f\u0430\u043d(?:\u0438\u044f\u043c|\u0438\u0438|\u0438\u044e|\u0438\u044f)|\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446(?:\u0438\u044f\u043c|\u0438\u0438|\u0438\u044e|\u0438\u044f)|\u0444\u0438\u0440\u043c(?:\u0430\u043c|\u0435|\u0443|\u0430)).*(?:\u043c\u043e\u0436(?:\u0435\u043c|\u043d\u043e)|\u0440\u0430\u0431\u043e\u0442|\u043e\u0431\u0449\u0430\u0442|\u043f\u043e\u0434\u0440\u0443\u0431|\u043f\u043e\u0434\u043a\u043b\u044e\u0447)|(?:\u0431\u0430\u0437\u0430\s+\u043a\u0430\u043a\u043e\u0439\s+(?:\u043a\u043e\u043d\u0442\u043e\u0440|\u043a\u043e\u043c\u043f\u0430\u043d|\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446|\u0444\u0438\u0440\u043c))|(?:\u043a\u0430\u043a\u0430\u044f\s+\u0431\u0430\u0437\u0430\s+(?:\u043f\u043e\u0434\u043a\u043b\u044e\u0447|\u0430\u043a\u0442\u0438\u0432)))/iu.test(normalized);
|
||||||
|
if (hasSlangScopeQuestion) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const hasBaseOrTenantObject = /(?:баз(?:а|е|у|ы)?|тенант|tenant|контур)/i.test(normalized);
|
||||||
|
const hasCompanyObject = /(?:компан(?:ия|ии|ию|ией)|компин(?:ия|ии|ию|ией)?|компини(?:я|и|ю|ей)?|компани[яеию]|организац(?:ия|ии|ию|ией)|контор(?:а|ы|у|ой)?|фирм(?:а|ы|у|ой)?)/i.test(normalized);
|
||||||
|
const hasConnectionCue = /(?:подключен(?:а|о|ы)?|подруб|воткнут|активн(?:ый|ая)\s+канал|mcp-?канал|канал)/i.test(normalized);
|
||||||
|
const hasNamingCue = /(?:как\s+называ(?:ет|ется)|что\s+за\s+(?:контор|компан|организац|фирм))/i.test(normalized);
|
||||||
|
const hasWorkabilityCue = /(?:мож(?:ем|ешь|ете|но)\s+работ|работ(?:ать|аем|аешь|аете))/i.test(normalized);
|
||||||
|
const hasScopeObject = hasBaseOrTenantObject || hasCompanyObject || hasConnectionCue;
|
||||||
|
if (!hasScopeObject) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hasMetaPerspective = /(?:ты|тебе|твой|у\s+тебя|в\s+тебе|мы|нам|наш(?:а|е|и|у|ей)?|сейчас|щас)/i.test(normalized);
|
||||||
|
const hasScopedInterrogativePair = /(?:^|\s)(?:по\s+какой|с\s+какой|какая|какой|какие)\s+(?:баз|компан|компин|компини|компани|организац|контор|фирм|тенант|контур)/i.test(normalized);
|
||||||
|
const hasScopeQuestion = /(?:чья|чье|чьи|доступн|подключен|подруб|воткнут|какая\s+баз|какой\s+баз)/i.test(normalized) ||
|
||||||
|
hasNamingCue ||
|
||||||
|
hasWorkabilityCue ||
|
||||||
|
hasScopedInterrogativePair;
|
||||||
|
const hasInterrogativeScopeLead = /(?:^|\s)(?:по\s+какой|с\s+какой|чья|чье|чьи|which|who|what)/i.test(normalized);
|
||||||
|
const isQuestionLike = /[?]/.test(String(text ?? "")) || hasInterrogativeScopeLead || hasScopedInterrogativePair;
|
||||||
|
const hasExplicitScopeContext = hasBaseOrTenantObject || hasConnectionCue || hasWorkabilityCue || hasNamingCue;
|
||||||
|
const hasRetrievalSignal = hasDataRetrievalRequestSignal(normalized);
|
||||||
|
const hasContractAnalyticsCue = /(?:договор|контракт|contract).*(?:топ|сам(?:ый|ая|ое|ые)|крупн|жирн|оборот|бюджет|сумм|стоим|value|turnover|all\s+time|всю\s+истори|за\s+вс[её]\s+время)/iu.test(normalized);
|
||||||
|
if (hasContractAnalyticsCue) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (hasRetrievalSignal && !hasExplicitScopeContext) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hasEligibleScopeObject = hasBaseOrTenantObject || (hasCompanyObject && (hasConnectionCue || hasWorkabilityCue || hasNamingCue || hasMetaPerspective));
|
||||||
|
return hasEligibleScopeObject && hasScopeQuestion && (hasMetaPerspective || isQuestionLike || hasExplicitScopeContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldHandleAsAssistantCapabilityMetaQuery(text) {
|
||||||
|
const raw = String(text ?? "");
|
||||||
|
const repaired = repairAddressMojibake(raw);
|
||||||
|
const hasScopeMetaSignal = hasAssistantDataScopeMetaQuestionSignal(raw) || hasAssistantDataScopeMetaQuestionSignal(repaired);
|
||||||
|
if (hasScopeMetaSignal) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const hasCapabilitySignal = hasAssistantCapabilityQuestionSignal(raw) ||
|
||||||
|
hasAssistantCapabilityQuestionSignal(repaired) ||
|
||||||
|
hasOperationalAdminActionRequestSignal(raw) ||
|
||||||
|
hasOperationalAdminActionRequestSignal(repaired);
|
||||||
|
const hasRetrievalSignal = hasDataRetrievalRequestSignal(raw) || hasDataRetrievalRequestSignal(repaired);
|
||||||
|
return hasCapabilitySignal && !hasRetrievalSignal;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasLivingChatSignal(text) {
|
||||||
|
const lower = compactWhitespace(String(text ?? "").toLowerCase());
|
||||||
|
if (!lower) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (/^(?:а\s+)?(?:тут|здесь|там|сюда|туда)[\s!?.,:;\-]*$/iu.test(lower)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (/^(ага|угу|ок|окей|ясно|понял|поняла|принято|спасибо|благодарю|супер|класс|норм|го|давай|погнали|привет|хай|йо|yo|че\s+там|ч[её]\s+как|че\s+как|hello|hi|thanks?)$/i.test(lower)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (/(как дела|как ты|что нового|расскажи о себе|чем можешь помочь|давай поговорим|поговорим|обсудим|посоветуй|что думаешь)/i.test(lower)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return hasSmallTalkSignal(lower);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveLivingAssistantModeDecision(input) {
|
||||||
|
const userMessage = String(input?.userMessage ?? "");
|
||||||
|
if (input?.addressLaneTriggered) {
|
||||||
|
return {
|
||||||
|
mode: "address_data",
|
||||||
|
reason: "address_lane_triggered"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (!featureAssistantLivingChatRouterV1) {
|
||||||
|
return {
|
||||||
|
mode: "deep_analysis",
|
||||||
|
reason: "living_chat_router_disabled"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (Boolean(input?.useMock)) {
|
||||||
|
return {
|
||||||
|
mode: "deep_analysis",
|
||||||
|
reason: "mock_mode_keeps_deep_pipeline"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (hasAssistantDataScopeMetaQuestionSignal(userMessage)) {
|
||||||
|
return {
|
||||||
|
mode: "chat",
|
||||||
|
reason: "assistant_data_scope_query_detected"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (shouldHandleAsAssistantCapabilityMetaQuery(userMessage)) {
|
||||||
|
return {
|
||||||
|
mode: "chat",
|
||||||
|
reason: "assistant_capability_query_detected"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (hasOrganizationFactLookupSignal(userMessage) || hasOrganizationFactFollowupSignal(userMessage)) {
|
||||||
|
return {
|
||||||
|
mode: "chat",
|
||||||
|
reason: "organization_fact_lookup_signal_detected"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (hasStrongDataIntentSignal(userMessage)) {
|
||||||
|
return {
|
||||||
|
mode: "deep_analysis",
|
||||||
|
reason: "strong_data_signal_detected"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (hasLivingChatSignal(userMessage)) {
|
||||||
|
return {
|
||||||
|
mode: "chat",
|
||||||
|
reason: "living_chat_signal_detected"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const predecomposeMode = toNonEmptyString(input?.predecomposeMode);
|
||||||
|
const predecomposeConfidence = toNonEmptyString(input?.predecomposeModeConfidence);
|
||||||
|
if (predecomposeMode === "unsupported" && (predecomposeConfidence === "low" || predecomposeConfidence === "medium")) {
|
||||||
|
return {
|
||||||
|
mode: "deep_analysis",
|
||||||
|
reason: "predecompose_unsupported_mode_fallback_to_deep"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
mode: "deep_analysis",
|
||||||
|
reason: "default_deep_pipeline"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
hasStrongDataIntentSignal,
|
||||||
|
hasDataRetrievalRequestSignal,
|
||||||
|
hasOrganizationFactLookupSignal,
|
||||||
|
hasMetaAnswerFollowupSignal,
|
||||||
|
hasConversationMemoryRecallFollowupSignal,
|
||||||
|
hasHistoricalCapabilityFollowupSignal,
|
||||||
|
hasOrganizationFactFollowupSignal,
|
||||||
|
shouldEmitOrganizationSelectionReply,
|
||||||
|
hasAssistantDataScopeMetaQuestionSignal,
|
||||||
|
shouldHandleAsAssistantCapabilityMetaQuery,
|
||||||
|
hasLivingChatSignal,
|
||||||
|
resolveLivingAssistantModeDecision
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,616 @@
|
||||||
|
// @ts-nocheck
|
||||||
|
const ADDRESS_INTENTS_KEEP_ADDRESS_LANE = new Set([
|
||||||
|
"period_coverage_profile",
|
||||||
|
"document_type_and_account_section_profile",
|
||||||
|
"counterparty_population_and_roles",
|
||||||
|
"counterparty_activity_lifecycle",
|
||||||
|
"customer_revenue_and_payments",
|
||||||
|
"supplier_payouts_profile",
|
||||||
|
"open_contracts_confirmed_as_of_date",
|
||||||
|
"list_open_contracts",
|
||||||
|
"open_items_by_counterparty_or_contract",
|
||||||
|
"list_payables_counterparties",
|
||||||
|
"list_receivables_counterparties",
|
||||||
|
"inventory_on_hand_as_of_date",
|
||||||
|
"payables_confirmed_as_of_date",
|
||||||
|
"receivables_confirmed_as_of_date",
|
||||||
|
"list_documents_by_contract",
|
||||||
|
"bank_operations_by_contract",
|
||||||
|
"list_documents_by_counterparty",
|
||||||
|
"bank_operations_by_counterparty",
|
||||||
|
"list_contracts_by_counterparty",
|
||||||
|
"inventory_purchase_provenance_for_item",
|
||||||
|
"inventory_purchase_documents_for_item",
|
||||||
|
"inventory_supplier_stock_overlap_as_of_date",
|
||||||
|
"inventory_sale_trace_for_item",
|
||||||
|
"inventory_profitability_for_item",
|
||||||
|
"inventory_purchase_to_sale_chain",
|
||||||
|
"inventory_aging_by_purchase_date",
|
||||||
|
"contract_usage_overview",
|
||||||
|
"contract_usage_and_value",
|
||||||
|
"vat_payable_forecast",
|
||||||
|
"vat_liability_confirmed_for_tax_period",
|
||||||
|
"vat_payable_confirmed_as_of_date"
|
||||||
|
]);
|
||||||
|
const ADDRESS_INTENTS_ALLOW_STRICT_DEEP_INVESTIGATION_BYPASS = new Set([
|
||||||
|
"inventory_purchase_provenance_for_item",
|
||||||
|
"inventory_purchase_documents_for_item",
|
||||||
|
"inventory_sale_trace_for_item",
|
||||||
|
"inventory_profitability_for_item",
|
||||||
|
"inventory_purchase_to_sale_chain"
|
||||||
|
]);
|
||||||
|
function shouldBypassStrictDeepInvestigationCueForAddressIntent(intent) {
|
||||||
|
return Boolean(intent && ADDRESS_INTENTS_ALLOW_STRICT_DEEP_INVESTIGATION_BYPASS.has(intent));
|
||||||
|
}
|
||||||
|
export function createAssistantRoutePolicy(deps) {
|
||||||
|
const {
|
||||||
|
repairAddressMojibake,
|
||||||
|
findLastGroundedAddressAnswerDebug,
|
||||||
|
findLastOrganizationClarificationAddressDebug,
|
||||||
|
mergeKnownOrganizations,
|
||||||
|
normalizeOrganizationScopeValue,
|
||||||
|
resolveOrganizationSelectionFromMessage,
|
||||||
|
hasAssistantDataScopeMetaQuestionSignal,
|
||||||
|
shouldHandleAsAssistantCapabilityMetaQuery,
|
||||||
|
hasDataRetrievalRequestSignal,
|
||||||
|
hasAggregateBusinessAnalyticsSignal,
|
||||||
|
hasStandaloneAddressTopicSignal,
|
||||||
|
hasOpenContractsAddressSignal,
|
||||||
|
detectAddressQuestionMode,
|
||||||
|
resolveAddressIntent,
|
||||||
|
toNonEmptyString,
|
||||||
|
hasStrictDeepInvestigationCue,
|
||||||
|
hasStrongDataIntentSignal,
|
||||||
|
hasAccountingSignal,
|
||||||
|
hasDangerOrCoercionSignal,
|
||||||
|
hasAddressFollowupContextSignal,
|
||||||
|
hasShortDebtMirrorFollowupSignal,
|
||||||
|
isInventorySelectedObjectIntent,
|
||||||
|
hasShortInventoryObjectFollowupSignal,
|
||||||
|
hasHistoricalCapabilityFollowupSignal,
|
||||||
|
isGroundedInventoryContextDebug,
|
||||||
|
hasConversationMemoryRecallFollowupSignal,
|
||||||
|
findLastAddressAssistantItem,
|
||||||
|
hasMetaAnswerFollowupSignal,
|
||||||
|
resolveAddressToolGateDecision,
|
||||||
|
hasSameDateAccountFollowupSignalForPredecompose,
|
||||||
|
hasLooseAllTimeAddressLookupSignal,
|
||||||
|
hasDeepAnalysisPreferenceSignal,
|
||||||
|
hasDirectDeepAnalysisSignal,
|
||||||
|
compactWhitespace,
|
||||||
|
hasDeepSessionContinuationSignal,
|
||||||
|
resolveLivingAssistantModeDecision
|
||||||
|
} = deps;
|
||||||
|
function resolveAssistantOrchestrationDecision(input) {
|
||||||
|
const rawUserMessage = String(input?.rawUserMessage ?? input?.userMessage ?? "");
|
||||||
|
const effectiveAddressUserMessage = String(input?.effectiveAddressUserMessage ?? rawUserMessage);
|
||||||
|
const repairedRawUserMessage = repairAddressMojibake(rawUserMessage);
|
||||||
|
const repairedEffectiveAddressUserMessage = repairAddressMojibake(effectiveAddressUserMessage);
|
||||||
|
const followupContext = input?.followupContext ?? null;
|
||||||
|
const llmPreDecomposeMeta = input?.llmPreDecomposeMeta ?? null;
|
||||||
|
const useMock = Boolean(input?.useMock);
|
||||||
|
const sessionItems = Array.isArray(input?.sessionItems) ? input.sessionItems : null;
|
||||||
|
const sessionOrganizationScope = input?.sessionOrganizationScope && typeof input.sessionOrganizationScope === "object"
|
||||||
|
? input.sessionOrganizationScope
|
||||||
|
: null;
|
||||||
|
const lastGroundedAddressDebug = findLastGroundedAddressAnswerDebug(sessionItems);
|
||||||
|
const lastOrganizationClarificationDebug = findLastOrganizationClarificationAddressDebug(sessionItems);
|
||||||
|
const organizationClarificationCandidates = Array.isArray(lastOrganizationClarificationDebug?.organization_candidates)
|
||||||
|
? mergeKnownOrganizations([
|
||||||
|
...lastOrganizationClarificationDebug.organization_candidates,
|
||||||
|
...((Array.isArray(sessionOrganizationScope?.knownOrganizations)
|
||||||
|
? sessionOrganizationScope.knownOrganizations
|
||||||
|
: []))
|
||||||
|
])
|
||||||
|
: [];
|
||||||
|
const organizationClarificationSelectionFromScope = normalizeOrganizationScopeValue(sessionOrganizationScope?.selectedOrganization);
|
||||||
|
const organizationClarificationSelection = resolveOrganizationSelectionFromMessage(rawUserMessage, organizationClarificationCandidates) ??
|
||||||
|
resolveOrganizationSelectionFromMessage(repairedRawUserMessage, organizationClarificationCandidates) ??
|
||||||
|
resolveOrganizationSelectionFromMessage(effectiveAddressUserMessage, organizationClarificationCandidates) ??
|
||||||
|
resolveOrganizationSelectionFromMessage(repairedEffectiveAddressUserMessage, organizationClarificationCandidates) ??
|
||||||
|
(organizationClarificationSelectionFromScope &&
|
||||||
|
organizationClarificationCandidates.some((candidate) => normalizeOrganizationScopeValue(candidate) === organizationClarificationSelectionFromScope)
|
||||||
|
? organizationClarificationSelectionFromScope
|
||||||
|
: null);
|
||||||
|
const dataScopeMetaQuery = hasAssistantDataScopeMetaQuestionSignal(rawUserMessage) ||
|
||||||
|
hasAssistantDataScopeMetaQuestionSignal(repairedRawUserMessage) ||
|
||||||
|
hasAssistantDataScopeMetaQuestionSignal(effectiveAddressUserMessage) ||
|
||||||
|
hasAssistantDataScopeMetaQuestionSignal(repairedEffectiveAddressUserMessage);
|
||||||
|
const capabilityMetaQuery = shouldHandleAsAssistantCapabilityMetaQuery(rawUserMessage) ||
|
||||||
|
shouldHandleAsAssistantCapabilityMetaQuery(repairedRawUserMessage) ||
|
||||||
|
shouldHandleAsAssistantCapabilityMetaQuery(effectiveAddressUserMessage) ||
|
||||||
|
shouldHandleAsAssistantCapabilityMetaQuery(repairedEffectiveAddressUserMessage);
|
||||||
|
const dataRetrievalSignal = hasDataRetrievalRequestSignal(rawUserMessage) ||
|
||||||
|
hasDataRetrievalRequestSignal(repairedRawUserMessage) ||
|
||||||
|
hasDataRetrievalRequestSignal(effectiveAddressUserMessage) ||
|
||||||
|
hasDataRetrievalRequestSignal(repairedEffectiveAddressUserMessage);
|
||||||
|
const aggregateBusinessAnalyticsSignal = hasAggregateBusinessAnalyticsSignal(rawUserMessage) ||
|
||||||
|
hasAggregateBusinessAnalyticsSignal(repairedRawUserMessage) ||
|
||||||
|
hasAggregateBusinessAnalyticsSignal(effectiveAddressUserMessage) ||
|
||||||
|
hasAggregateBusinessAnalyticsSignal(repairedEffectiveAddressUserMessage);
|
||||||
|
const standaloneAddressTopicSignal = hasStandaloneAddressTopicSignal(rawUserMessage) ||
|
||||||
|
hasStandaloneAddressTopicSignal(repairedRawUserMessage) ||
|
||||||
|
hasStandaloneAddressTopicSignal(effectiveAddressUserMessage) ||
|
||||||
|
hasStandaloneAddressTopicSignal(repairedEffectiveAddressUserMessage);
|
||||||
|
const openContractsAddressSignal = hasOpenContractsAddressSignal(rawUserMessage) ||
|
||||||
|
hasOpenContractsAddressSignal(repairedRawUserMessage) ||
|
||||||
|
hasOpenContractsAddressSignal(effectiveAddressUserMessage) ||
|
||||||
|
hasOpenContractsAddressSignal(repairedEffectiveAddressUserMessage);
|
||||||
|
const modeSample = repairedEffectiveAddressUserMessage || effectiveAddressUserMessage;
|
||||||
|
const modeDetection = detectAddressQuestionMode(modeSample);
|
||||||
|
const modeDetectionRaw = detectAddressQuestionMode(repairedRawUserMessage || rawUserMessage);
|
||||||
|
const resolvedModeDetection = modeDetection.mode === "address_query" ? modeDetection : modeDetectionRaw;
|
||||||
|
const intentResolution = resolveAddressIntent(modeSample);
|
||||||
|
const intentResolutionRaw = resolveAddressIntent(repairedRawUserMessage || rawUserMessage);
|
||||||
|
const resolvedIntentResolution = intentResolution.intent !== "unknown" ? intentResolution : intentResolutionRaw;
|
||||||
|
const llmContractIntent = toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.intent);
|
||||||
|
const llmPreDecomposeReason = toNonEmptyString(llmPreDecomposeMeta?.reason);
|
||||||
|
const llmRuntimeUnavailableDetected = Boolean(llmPreDecomposeReason &&
|
||||||
|
/(?:openai\s+api\s+key\s+is\s+missing|api\s+key\s+is\s+missing|missing\s+api\s+key|authentication)/iu.test(llmPreDecomposeReason));
|
||||||
|
const semanticExtractionContract = llmPreDecomposeMeta?.semanticExtractionContract &&
|
||||||
|
typeof llmPreDecomposeMeta.semanticExtractionContract === "object"
|
||||||
|
? llmPreDecomposeMeta.semanticExtractionContract
|
||||||
|
: null;
|
||||||
|
const semanticContractValid = semanticExtractionContract?.valid !== false;
|
||||||
|
const semanticApplyCanonicalRecommended = semanticExtractionContract?.apply_canonical_recommended !== false;
|
||||||
|
const semanticReasonCodes = Array.isArray(semanticExtractionContract?.reason_codes)
|
||||||
|
? semanticExtractionContract.reason_codes
|
||||||
|
: [];
|
||||||
|
const strictDeepInvestigationCueDetected = hasStrictDeepInvestigationCue(rawUserMessage) ||
|
||||||
|
hasStrictDeepInvestigationCue(repairedRawUserMessage) ||
|
||||||
|
hasStrictDeepInvestigationCue(effectiveAddressUserMessage) ||
|
||||||
|
hasStrictDeepInvestigationCue(repairedEffectiveAddressUserMessage);
|
||||||
|
const strictDeepInvestigationBypassAllowed = shouldBypassStrictDeepInvestigationCueForAddressIntent(resolvedIntentResolution.intent) ||
|
||||||
|
shouldBypassStrictDeepInvestigationCueForAddressIntent(llmContractIntent);
|
||||||
|
const keepAddressLaneByIntent = semanticApplyCanonicalRecommended &&
|
||||||
|
Boolean((resolvedIntentResolution.intent && ADDRESS_INTENTS_KEEP_ADDRESS_LANE.has(resolvedIntentResolution.intent)) ||
|
||||||
|
(llmContractIntent && ADDRESS_INTENTS_KEEP_ADDRESS_LANE.has(llmContractIntent)) ||
|
||||||
|
openContractsAddressSignal) &&
|
||||||
|
(!strictDeepInvestigationCueDetected || strictDeepInvestigationBypassAllowed);
|
||||||
|
const strongDataSignal = hasStrongDataIntentSignal(rawUserMessage) ||
|
||||||
|
hasStrongDataIntentSignal(repairedRawUserMessage) ||
|
||||||
|
hasStrongDataIntentSignal(effectiveAddressUserMessage) ||
|
||||||
|
hasStrongDataIntentSignal(repairedEffectiveAddressUserMessage) ||
|
||||||
|
hasAccountingSignal(rawUserMessage) ||
|
||||||
|
hasAccountingSignal(repairedRawUserMessage) ||
|
||||||
|
hasAccountingSignal(effectiveAddressUserMessage) ||
|
||||||
|
hasAccountingSignal(repairedEffectiveAddressUserMessage) ||
|
||||||
|
hasDataRetrievalRequestSignal(rawUserMessage) ||
|
||||||
|
hasDataRetrievalRequestSignal(repairedRawUserMessage);
|
||||||
|
const llmContractMode = toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.mode);
|
||||||
|
const llmFirstAddressCandidate = Boolean(llmContractMode === "address_query" && llmContractIntent && llmContractIntent !== "unknown");
|
||||||
|
const llmFirstUnsupportedCandidate = Boolean(llmContractMode === "unsupported" &&
|
||||||
|
(!llmContractIntent || llmContractIntent === "unknown"));
|
||||||
|
const dangerOrCoercionSignal = hasDangerOrCoercionSignal(rawUserMessage) ||
|
||||||
|
hasDangerOrCoercionSignal(repairedRawUserMessage) ||
|
||||||
|
hasDangerOrCoercionSignal(effectiveAddressUserMessage) ||
|
||||||
|
hasDangerOrCoercionSignal(repairedEffectiveAddressUserMessage);
|
||||||
|
const explicitAddressFollowupSignal = hasAddressFollowupContextSignal(rawUserMessage) ||
|
||||||
|
hasAddressFollowupContextSignal(repairedRawUserMessage) ||
|
||||||
|
hasAddressFollowupContextSignal(effectiveAddressUserMessage) ||
|
||||||
|
hasAddressFollowupContextSignal(repairedEffectiveAddressUserMessage) ||
|
||||||
|
hasShortDebtMirrorFollowupSignal(rawUserMessage) ||
|
||||||
|
hasShortDebtMirrorFollowupSignal(repairedRawUserMessage) ||
|
||||||
|
hasShortDebtMirrorFollowupSignal(effectiveAddressUserMessage) ||
|
||||||
|
hasShortDebtMirrorFollowupSignal(repairedEffectiveAddressUserMessage);
|
||||||
|
const protectedInventoryShortFollowup = Boolean(followupContext &&
|
||||||
|
isInventorySelectedObjectIntent(toNonEmptyString(followupContext.previous_intent)) &&
|
||||||
|
(hasShortInventoryObjectFollowupSignal(rawUserMessage) ||
|
||||||
|
hasShortInventoryObjectFollowupSignal(repairedRawUserMessage) ||
|
||||||
|
hasShortInventoryObjectFollowupSignal(effectiveAddressUserMessage) ||
|
||||||
|
hasShortInventoryObjectFollowupSignal(repairedEffectiveAddressUserMessage)));
|
||||||
|
const organizationClarificationContinuationDetected = Boolean(followupContext &&
|
||||||
|
lastOrganizationClarificationDebug &&
|
||||||
|
organizationClarificationSelection &&
|
||||||
|
!dataScopeMetaQuery &&
|
||||||
|
!capabilityMetaQuery &&
|
||||||
|
!dataRetrievalSignal);
|
||||||
|
const effectiveAddressFollowupSignal = explicitAddressFollowupSignal && !dangerOrCoercionSignal;
|
||||||
|
const deterministicNonDomainGuard = Boolean(!dataScopeMetaQuery &&
|
||||||
|
!capabilityMetaQuery &&
|
||||||
|
!dataRetrievalSignal &&
|
||||||
|
!effectiveAddressFollowupSignal &&
|
||||||
|
resolvedModeDetection.mode === "unsupported" &&
|
||||||
|
resolvedIntentResolution.intent === "unknown");
|
||||||
|
const nonDomainQueryIndexed = Boolean(!llmFirstAddressCandidate &&
|
||||||
|
deterministicNonDomainGuard &&
|
||||||
|
(llmFirstUnsupportedCandidate || llmContractMode === null) &&
|
||||||
|
!protectedInventoryShortFollowup &&
|
||||||
|
!organizationClarificationContinuationDetected);
|
||||||
|
const contextualHistoricalCapabilityFollowupDetected = Boolean(capabilityMetaQuery &&
|
||||||
|
!dataScopeMetaQuery &&
|
||||||
|
!dataRetrievalSignal &&
|
||||||
|
(hasHistoricalCapabilityFollowupSignal(rawUserMessage) ||
|
||||||
|
hasHistoricalCapabilityFollowupSignal(repairedRawUserMessage) ||
|
||||||
|
hasHistoricalCapabilityFollowupSignal(effectiveAddressUserMessage) ||
|
||||||
|
hasHistoricalCapabilityFollowupSignal(repairedEffectiveAddressUserMessage)) &&
|
||||||
|
isGroundedInventoryContextDebug(lastGroundedAddressDebug));
|
||||||
|
const contextualMemoryRecapFollowupDetected = Boolean(!dataScopeMetaQuery &&
|
||||||
|
!capabilityMetaQuery &&
|
||||||
|
!dataRetrievalSignal &&
|
||||||
|
!strongDataSignal &&
|
||||||
|
!aggregateBusinessAnalyticsSignal &&
|
||||||
|
(hasConversationMemoryRecallFollowupSignal(rawUserMessage) ||
|
||||||
|
hasConversationMemoryRecallFollowupSignal(repairedRawUserMessage) ||
|
||||||
|
hasConversationMemoryRecallFollowupSignal(effectiveAddressUserMessage) ||
|
||||||
|
hasConversationMemoryRecallFollowupSignal(repairedEffectiveAddressUserMessage)) &&
|
||||||
|
(lastGroundedAddressDebug ||
|
||||||
|
findLastAddressAssistantItem(sessionItems)?.debug));
|
||||||
|
const hardMetaMode = dataScopeMetaQuery
|
||||||
|
? "data_scope"
|
||||||
|
: capabilityMetaQuery && !dataRetrievalSignal
|
||||||
|
? "capability"
|
||||||
|
: null;
|
||||||
|
if (hardMetaMode === "data_scope") {
|
||||||
|
return {
|
||||||
|
runAddressLane: false,
|
||||||
|
toolGateDecision: "skip_address_lane",
|
||||||
|
toolGateReason: "assistant_data_scope_query_detected",
|
||||||
|
livingMode: "chat",
|
||||||
|
livingReason: "assistant_data_scope_query_detected",
|
||||||
|
orchestrationContract: {
|
||||||
|
schema_version: "assistant_orchestration_contract_v1",
|
||||||
|
hard_meta_mode: "data_scope",
|
||||||
|
address_mode: resolvedModeDetection.mode,
|
||||||
|
address_mode_confidence: resolvedModeDetection.confidence,
|
||||||
|
address_intent: resolvedIntentResolution.intent,
|
||||||
|
address_intent_confidence: resolvedIntentResolution.confidence,
|
||||||
|
strong_data_signal_detected: strongDataSignal,
|
||||||
|
data_retrieval_signal_detected: dataRetrievalSignal,
|
||||||
|
followup_context_detected: Boolean(followupContext),
|
||||||
|
unsupported_address_intent_fallback_to_deep: false,
|
||||||
|
final_decision: {
|
||||||
|
run_address_lane: false,
|
||||||
|
tool_gate_decision: "skip_address_lane",
|
||||||
|
tool_gate_reason: "assistant_data_scope_query_detected",
|
||||||
|
living_mode: "chat",
|
||||||
|
living_reason: "assistant_data_scope_query_detected"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (hardMetaMode === "capability") {
|
||||||
|
if (contextualHistoricalCapabilityFollowupDetected) {
|
||||||
|
return {
|
||||||
|
runAddressLane: false,
|
||||||
|
toolGateDecision: "skip_address_lane",
|
||||||
|
toolGateReason: "inventory_history_capability_followup_detected",
|
||||||
|
livingMode: "chat",
|
||||||
|
livingReason: "inventory_history_capability_followup_detected",
|
||||||
|
orchestrationContract: {
|
||||||
|
schema_version: "assistant_orchestration_contract_v1",
|
||||||
|
hard_meta_mode: "capability",
|
||||||
|
address_mode: resolvedModeDetection.mode,
|
||||||
|
address_mode_confidence: resolvedModeDetection.confidence,
|
||||||
|
address_intent: resolvedIntentResolution.intent,
|
||||||
|
address_intent_confidence: resolvedIntentResolution.confidence,
|
||||||
|
strong_data_signal_detected: strongDataSignal,
|
||||||
|
data_retrieval_signal_detected: dataRetrievalSignal,
|
||||||
|
followup_context_detected: Boolean(followupContext || lastGroundedAddressDebug),
|
||||||
|
unsupported_address_intent_fallback_to_deep: false,
|
||||||
|
final_decision: {
|
||||||
|
run_address_lane: false,
|
||||||
|
tool_gate_decision: "skip_address_lane",
|
||||||
|
tool_gate_reason: "inventory_history_capability_followup_detected",
|
||||||
|
living_mode: "chat",
|
||||||
|
living_reason: "inventory_history_capability_followup_detected"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
runAddressLane: false,
|
||||||
|
toolGateDecision: "skip_address_lane",
|
||||||
|
toolGateReason: "assistant_capability_query_detected",
|
||||||
|
livingMode: "chat",
|
||||||
|
livingReason: "assistant_capability_query_detected",
|
||||||
|
orchestrationContract: {
|
||||||
|
schema_version: "assistant_orchestration_contract_v1",
|
||||||
|
hard_meta_mode: "capability",
|
||||||
|
address_mode: resolvedModeDetection.mode,
|
||||||
|
address_mode_confidence: resolvedModeDetection.confidence,
|
||||||
|
address_intent: resolvedIntentResolution.intent,
|
||||||
|
address_intent_confidence: resolvedIntentResolution.confidence,
|
||||||
|
strong_data_signal_detected: strongDataSignal,
|
||||||
|
data_retrieval_signal_detected: dataRetrievalSignal,
|
||||||
|
followup_context_detected: Boolean(followupContext),
|
||||||
|
unsupported_address_intent_fallback_to_deep: false,
|
||||||
|
final_decision: {
|
||||||
|
run_address_lane: false,
|
||||||
|
tool_gate_decision: "skip_address_lane",
|
||||||
|
tool_gate_reason: "assistant_capability_query_detected",
|
||||||
|
living_mode: "chat",
|
||||||
|
living_reason: "assistant_capability_query_detected"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (nonDomainQueryIndexed) {
|
||||||
|
if (contextualMemoryRecapFollowupDetected) {
|
||||||
|
return {
|
||||||
|
runAddressLane: false,
|
||||||
|
toolGateDecision: "skip_address_lane",
|
||||||
|
toolGateReason: "memory_recap_followup_detected",
|
||||||
|
livingMode: "chat",
|
||||||
|
livingReason: "memory_recap_followup_detected",
|
||||||
|
orchestrationContract: {
|
||||||
|
schema_version: "assistant_orchestration_contract_v1",
|
||||||
|
hard_meta_mode: "non_domain",
|
||||||
|
address_mode: resolvedModeDetection.mode,
|
||||||
|
address_mode_confidence: resolvedModeDetection.confidence,
|
||||||
|
address_intent: resolvedIntentResolution.intent,
|
||||||
|
address_intent_confidence: resolvedIntentResolution.confidence,
|
||||||
|
strong_data_signal_detected: strongDataSignal,
|
||||||
|
data_retrieval_signal_detected: dataRetrievalSignal,
|
||||||
|
followup_context_detected: Boolean(followupContext || lastGroundedAddressDebug),
|
||||||
|
unsupported_address_intent_fallback_to_deep: false,
|
||||||
|
final_decision: {
|
||||||
|
run_address_lane: false,
|
||||||
|
tool_gate_decision: "skip_address_lane",
|
||||||
|
tool_gate_reason: "memory_recap_followup_detected",
|
||||||
|
living_mode: "chat",
|
||||||
|
living_reason: "memory_recap_followup_detected"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
runAddressLane: false,
|
||||||
|
toolGateDecision: "skip_address_lane",
|
||||||
|
toolGateReason: "non_domain_query_indexed",
|
||||||
|
livingMode: "chat",
|
||||||
|
livingReason: "non_domain_query_indexed",
|
||||||
|
orchestrationContract: {
|
||||||
|
schema_version: "assistant_orchestration_contract_v1",
|
||||||
|
hard_meta_mode: "non_domain",
|
||||||
|
address_mode: resolvedModeDetection.mode,
|
||||||
|
address_mode_confidence: resolvedModeDetection.confidence,
|
||||||
|
address_intent: resolvedIntentResolution.intent,
|
||||||
|
address_intent_confidence: resolvedIntentResolution.confidence,
|
||||||
|
strong_data_signal_detected: strongDataSignal,
|
||||||
|
data_retrieval_signal_detected: dataRetrievalSignal,
|
||||||
|
followup_context_detected: Boolean(followupContext),
|
||||||
|
unsupported_address_intent_fallback_to_deep: false,
|
||||||
|
final_decision: {
|
||||||
|
run_address_lane: false,
|
||||||
|
tool_gate_decision: "skip_address_lane",
|
||||||
|
tool_gate_reason: "non_domain_query_indexed",
|
||||||
|
living_mode: "chat",
|
||||||
|
living_reason: "non_domain_query_indexed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const metaAnswerFollowupSignal = hasMetaAnswerFollowupSignal(rawUserMessage) ||
|
||||||
|
hasMetaAnswerFollowupSignal(repairedRawUserMessage) ||
|
||||||
|
hasMetaAnswerFollowupSignal(effectiveAddressUserMessage) ||
|
||||||
|
hasMetaAnswerFollowupSignal(repairedEffectiveAddressUserMessage);
|
||||||
|
const baseToolGate = resolveAddressToolGateDecision(effectiveAddressUserMessage, followupContext, llmPreDecomposeMeta, rawUserMessage);
|
||||||
|
const preserveAddressLaneSignal = Boolean((llmPreDecomposeMeta?.llmCanonicalCandidateDetected &&
|
||||||
|
llmPreDecomposeMeta?.applied &&
|
||||||
|
llmContractMode === "address_query") ||
|
||||||
|
hasSameDateAccountFollowupSignalForPredecompose(rawUserMessage) ||
|
||||||
|
hasSameDateAccountFollowupSignalForPredecompose(effectiveAddressUserMessage) ||
|
||||||
|
hasSameDateAccountFollowupSignalForPredecompose(repairedRawUserMessage) ||
|
||||||
|
hasSameDateAccountFollowupSignalForPredecompose(repairedEffectiveAddressUserMessage) ||
|
||||||
|
hasLooseAllTimeAddressLookupSignal(rawUserMessage) ||
|
||||||
|
hasLooseAllTimeAddressLookupSignal(effectiveAddressUserMessage) ||
|
||||||
|
hasLooseAllTimeAddressLookupSignal(repairedRawUserMessage) ||
|
||||||
|
hasLooseAllTimeAddressLookupSignal(repairedEffectiveAddressUserMessage) ||
|
||||||
|
hasAddressFollowupContextSignal(rawUserMessage) ||
|
||||||
|
hasAddressFollowupContextSignal(effectiveAddressUserMessage) ||
|
||||||
|
hasAddressFollowupContextSignal(repairedRawUserMessage) ||
|
||||||
|
hasAddressFollowupContextSignal(repairedEffectiveAddressUserMessage) ||
|
||||||
|
hasShortDebtMirrorFollowupSignal(rawUserMessage) ||
|
||||||
|
hasShortDebtMirrorFollowupSignal(effectiveAddressUserMessage) ||
|
||||||
|
hasShortDebtMirrorFollowupSignal(repairedRawUserMessage) ||
|
||||||
|
hasShortDebtMirrorFollowupSignal(repairedEffectiveAddressUserMessage));
|
||||||
|
const supportedAddressIntentDetected = (!strictDeepInvestigationCueDetected || strictDeepInvestigationBypassAllowed) &&
|
||||||
|
Boolean((resolvedIntentResolution.intent && ADDRESS_INTENTS_KEEP_ADDRESS_LANE.has(resolvedIntentResolution.intent)) ||
|
||||||
|
(llmContractIntent && ADDRESS_INTENTS_KEEP_ADDRESS_LANE.has(llmContractIntent)) ||
|
||||||
|
openContractsAddressSignal);
|
||||||
|
const semanticGuardHints = semanticExtractionContract?.guard_hints &&
|
||||||
|
typeof semanticExtractionContract.guard_hints === "object"
|
||||||
|
? semanticExtractionContract.guard_hints
|
||||||
|
: null;
|
||||||
|
const semanticExtraction = semanticExtractionContract?.extraction &&
|
||||||
|
typeof semanticExtractionContract.extraction === "object"
|
||||||
|
? semanticExtractionContract.extraction
|
||||||
|
: null;
|
||||||
|
const semanticDeepInvestigationHintDetected = semanticGuardHints?.deep_investigation_signal_detected === true;
|
||||||
|
const semanticAggregateShapeDetected = semanticExtraction?.query_shape === "AGGREGATE_LOOKUP" ||
|
||||||
|
semanticExtraction?.aggregation_profile === "management_profile";
|
||||||
|
const rootContextOnlyFollowup = Boolean(followupContext && followupContext.root_context_only === true);
|
||||||
|
const followupSemanticOverrideToDeepAllowed = Boolean(followupContext &&
|
||||||
|
!supportedAddressIntentDetected &&
|
||||||
|
(rootContextOnlyFollowup ||
|
||||||
|
llmContractMode === "unsupported" ||
|
||||||
|
semanticAggregateShapeDetected ||
|
||||||
|
semanticDeepInvestigationHintDetected ||
|
||||||
|
!semanticApplyCanonicalRecommended));
|
||||||
|
const unsupportedIntentOrMode = (resolvedModeDetection.mode !== "address_query" && resolvedIntentResolution.intent === "unknown") ||
|
||||||
|
llmContractMode === "unsupported" ||
|
||||||
|
(rootContextOnlyFollowup &&
|
||||||
|
resolvedIntentResolution.intent === "unknown" &&
|
||||||
|
(!llmContractIntent || llmContractIntent === "unknown"));
|
||||||
|
const unsupportedAddressIntentFallbackToDeep = Boolean(baseToolGate?.runAddressLane &&
|
||||||
|
!llmRuntimeUnavailableDetected &&
|
||||||
|
unsupportedIntentOrMode &&
|
||||||
|
strongDataSignal &&
|
||||||
|
(rootContextOnlyFollowup ||
|
||||||
|
llmContractMode === "deep_analysis" ||
|
||||||
|
!dataRetrievalSignal ||
|
||||||
|
strictDeepInvestigationCueDetected ||
|
||||||
|
semanticDeepInvestigationHintDetected ||
|
||||||
|
aggregateBusinessAnalyticsSignal) &&
|
||||||
|
!preserveAddressLaneSignal &&
|
||||||
|
!keepAddressLaneByIntent &&
|
||||||
|
!supportedAddressIntentDetected &&
|
||||||
|
(!followupContext || followupSemanticOverrideToDeepAllowed));
|
||||||
|
const deepAnalysisPreferenceDetected = Boolean(hasDeepAnalysisPreferenceSignal(rawUserMessage) ||
|
||||||
|
hasDeepAnalysisPreferenceSignal(repairedRawUserMessage) ||
|
||||||
|
hasDeepAnalysisPreferenceSignal(effectiveAddressUserMessage) ||
|
||||||
|
hasDeepAnalysisPreferenceSignal(repairedEffectiveAddressUserMessage) ||
|
||||||
|
hasDirectDeepAnalysisSignal(rawUserMessage) ||
|
||||||
|
hasDirectDeepAnalysisSignal(repairedRawUserMessage) ||
|
||||||
|
hasDirectDeepAnalysisSignal(effectiveAddressUserMessage) ||
|
||||||
|
hasDirectDeepAnalysisSignal(repairedEffectiveAddressUserMessage));
|
||||||
|
const vatExplainFollowupSignal = Boolean(followupContext &&
|
||||||
|
toNonEmptyString(followupContext.previous_intent) === "vat_payable_forecast" &&
|
||||||
|
/(?:\u043f\u043e\u0447\u0435\u043c\u0443|why).*(?:\u043f\u0440\u043e\u0433\u043d\u043e\u0437|forecast).*(?:\u0443\u043f\u043b\u0430\u0442|payable|\b0\b)/iu.test(compactWhitespace(`${repairedRawUserMessage} ${repairedEffectiveAddressUserMessage}`)));
|
||||||
|
const vatEvaluativeFollowupSignal = Boolean(followupContext &&
|
||||||
|
toNonEmptyString(followupContext.previous_intent) === "vat_payable_forecast" &&
|
||||||
|
/(?:^|\s)(?:это\s+)?много\s+или\s+мало(?:\?|$)|(?:^|\s)(?:это\s+)?нормально(?:\?|$)|(?:^|\s)(?:это\s+)?плохо(?:\?|$)|(?:^|\s)(?:это\s+)?хорошо(?:\?|$)/iu.test(compactWhitespace(`${repairedRawUserMessage} ${repairedEffectiveAddressUserMessage}`)));
|
||||||
|
const deepAnalysisSignalFallbackToDeep = Boolean(baseToolGate?.runAddressLane &&
|
||||||
|
!llmRuntimeUnavailableDetected &&
|
||||||
|
(deepAnalysisPreferenceDetected || semanticDeepInvestigationHintDetected) &&
|
||||||
|
!keepAddressLaneByIntent &&
|
||||||
|
!supportedAddressIntentDetected &&
|
||||||
|
!vatExplainFollowupSignal &&
|
||||||
|
(!followupContext || !dataRetrievalSignal || followupSemanticOverrideToDeepAllowed));
|
||||||
|
const aggregateAnalyticsFallbackToDeep = Boolean(baseToolGate?.runAddressLane &&
|
||||||
|
!llmRuntimeUnavailableDetected &&
|
||||||
|
aggregateBusinessAnalyticsSignal &&
|
||||||
|
!keepAddressLaneByIntent &&
|
||||||
|
!supportedAddressIntentDetected &&
|
||||||
|
(!followupContext ||
|
||||||
|
llmContractMode === "unsupported" ||
|
||||||
|
semanticAggregateShapeDetected ||
|
||||||
|
!semanticApplyCanonicalRecommended ||
|
||||||
|
standaloneAddressTopicSignal));
|
||||||
|
const deepSessionContinuationFallbackToDeep = Boolean(!followupContext &&
|
||||||
|
baseToolGate?.runAddressLane &&
|
||||||
|
!llmRuntimeUnavailableDetected &&
|
||||||
|
hasDeepSessionContinuationSignal({
|
||||||
|
rawUserMessage,
|
||||||
|
repairedRawUserMessage,
|
||||||
|
effectiveAddressUserMessage,
|
||||||
|
repairedEffectiveAddressUserMessage,
|
||||||
|
sessionItems
|
||||||
|
}));
|
||||||
|
const hasPriorAddressAnswerContext = Boolean(lastGroundedAddressDebug || toNonEmptyString(followupContext?.previous_intent));
|
||||||
|
const metaFollowupOverGroundedAnswer = Boolean(followupContext &&
|
||||||
|
hasPriorAddressAnswerContext &&
|
||||||
|
(metaAnswerFollowupSignal || vatEvaluativeFollowupSignal) &&
|
||||||
|
!dataScopeMetaQuery &&
|
||||||
|
!capabilityMetaQuery &&
|
||||||
|
!aggregateBusinessAnalyticsSignal &&
|
||||||
|
!dataRetrievalSignal &&
|
||||||
|
!strongDataSignal &&
|
||||||
|
resolvedModeDetection.mode !== "address_query" &&
|
||||||
|
resolvedIntentResolution.intent === "unknown" &&
|
||||||
|
(!llmContractIntent || llmContractIntent === "unknown") &&
|
||||||
|
llmContractMode !== "address_query");
|
||||||
|
let runAddressLane = Boolean(baseToolGate?.runAddressLane);
|
||||||
|
let toolGateDecision = String(baseToolGate?.decision ?? "skip_address_lane");
|
||||||
|
let toolGateReason = String(baseToolGate?.reason ?? "no_address_signal_after_l0");
|
||||||
|
if (unsupportedAddressIntentFallbackToDeep) {
|
||||||
|
runAddressLane = false;
|
||||||
|
toolGateDecision = "skip_address_lane";
|
||||||
|
toolGateReason = "address_signal_unsupported_intent_fallback_to_deep";
|
||||||
|
}
|
||||||
|
if (deepAnalysisSignalFallbackToDeep && !unsupportedAddressIntentFallbackToDeep) {
|
||||||
|
runAddressLane = false;
|
||||||
|
toolGateDecision = "skip_address_lane";
|
||||||
|
toolGateReason = "deep_analysis_signal_fallback_to_deep";
|
||||||
|
}
|
||||||
|
if (aggregateAnalyticsFallbackToDeep &&
|
||||||
|
!unsupportedAddressIntentFallbackToDeep &&
|
||||||
|
!deepAnalysisSignalFallbackToDeep) {
|
||||||
|
runAddressLane = false;
|
||||||
|
toolGateDecision = "skip_address_lane";
|
||||||
|
toolGateReason = "aggregate_analytics_signal_fallback_to_deep";
|
||||||
|
}
|
||||||
|
if (deepSessionContinuationFallbackToDeep) {
|
||||||
|
runAddressLane = false;
|
||||||
|
toolGateDecision = "skip_address_lane";
|
||||||
|
toolGateReason = "deep_session_continuation_fallback_to_deep";
|
||||||
|
}
|
||||||
|
if (metaFollowupOverGroundedAnswer) {
|
||||||
|
runAddressLane = false;
|
||||||
|
toolGateDecision = "skip_address_lane";
|
||||||
|
toolGateReason = "meta_followup_over_grounded_answer";
|
||||||
|
}
|
||||||
|
let livingDecision = resolveLivingAssistantModeDecision({
|
||||||
|
userMessage: rawUserMessage,
|
||||||
|
addressLaneTriggered: runAddressLane,
|
||||||
|
useMock,
|
||||||
|
predecomposeMode: llmPreDecomposeMeta?.predecomposeContract?.mode ?? null,
|
||||||
|
predecomposeModeConfidence: llmPreDecomposeMeta?.predecomposeContract?.mode_confidence ?? null
|
||||||
|
});
|
||||||
|
if (unsupportedAddressIntentFallbackToDeep) {
|
||||||
|
livingDecision = {
|
||||||
|
mode: "deep_analysis",
|
||||||
|
reason: "unsupported_address_intent_fallback_to_deep"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (deepAnalysisSignalFallbackToDeep && !unsupportedAddressIntentFallbackToDeep) {
|
||||||
|
livingDecision = {
|
||||||
|
mode: "deep_analysis",
|
||||||
|
reason: "deep_analysis_signal_fallback_to_deep"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (aggregateAnalyticsFallbackToDeep &&
|
||||||
|
!unsupportedAddressIntentFallbackToDeep &&
|
||||||
|
!deepAnalysisSignalFallbackToDeep) {
|
||||||
|
livingDecision = {
|
||||||
|
mode: "deep_analysis",
|
||||||
|
reason: "aggregate_analytics_signal_fallback_to_deep"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (deepSessionContinuationFallbackToDeep) {
|
||||||
|
livingDecision = {
|
||||||
|
mode: "deep_analysis",
|
||||||
|
reason: "deep_session_continuation_fallback_to_deep"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (metaFollowupOverGroundedAnswer) {
|
||||||
|
livingDecision = {
|
||||||
|
mode: "chat",
|
||||||
|
reason: "meta_followup_over_grounded_answer"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
runAddressLane,
|
||||||
|
toolGateDecision,
|
||||||
|
toolGateReason,
|
||||||
|
livingMode: livingDecision.mode,
|
||||||
|
livingReason: livingDecision.reason,
|
||||||
|
orchestrationContract: {
|
||||||
|
schema_version: "assistant_orchestration_contract_v1",
|
||||||
|
hard_meta_mode: null,
|
||||||
|
address_mode: resolvedModeDetection.mode,
|
||||||
|
address_mode_confidence: resolvedModeDetection.confidence,
|
||||||
|
address_intent: resolvedIntentResolution.intent,
|
||||||
|
address_intent_confidence: resolvedIntentResolution.confidence,
|
||||||
|
strong_data_signal_detected: strongDataSignal,
|
||||||
|
data_retrieval_signal_detected: dataRetrievalSignal,
|
||||||
|
semantic_contract_valid: semanticContractValid,
|
||||||
|
semantic_apply_canonical_recommended: semanticApplyCanonicalRecommended,
|
||||||
|
semantic_reason_codes: semanticReasonCodes,
|
||||||
|
semantic_route_arbitration: {
|
||||||
|
supported_address_intent_detected: supportedAddressIntentDetected,
|
||||||
|
strict_deep_investigation_bypass_allowed: strictDeepInvestigationBypassAllowed,
|
||||||
|
semantic_deep_investigation_hint_detected: semanticDeepInvestigationHintDetected,
|
||||||
|
semantic_aggregate_shape_detected: semanticAggregateShapeDetected,
|
||||||
|
followup_semantic_override_to_deep_allowed: followupSemanticOverrideToDeepAllowed
|
||||||
|
},
|
||||||
|
followup_context_detected: Boolean(followupContext),
|
||||||
|
unsupported_address_intent_fallback_to_deep: unsupportedAddressIntentFallbackToDeep,
|
||||||
|
deep_analysis_signal_fallback_to_deep: deepAnalysisSignalFallbackToDeep,
|
||||||
|
aggregate_analytics_signal_fallback_to_deep: aggregateAnalyticsFallbackToDeep,
|
||||||
|
deep_session_continuation_fallback_to_deep: deepSessionContinuationFallbackToDeep,
|
||||||
|
final_decision: {
|
||||||
|
run_address_lane: runAddressLane,
|
||||||
|
tool_gate_decision: toolGateDecision,
|
||||||
|
tool_gate_reason: toolGateReason,
|
||||||
|
living_mode: livingDecision.mode,
|
||||||
|
living_reason: livingDecision.reason
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
resolveAssistantOrchestrationDecision
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -21,6 +21,8 @@ import * as assistantAddressAttemptRuntimeAdapter_1 from "./assistantAddressAtte
|
||||||
import * as assistantCoverageGrounding_1 from "./assistantCoverageGrounding";
|
import * as assistantCoverageGrounding_1 from "./assistantCoverageGrounding";
|
||||||
import * as assistantDeepTurnAttemptRuntimeAdapter_1 from "./assistantDeepTurnAttemptRuntimeAdapter";
|
import * as assistantDeepTurnAttemptRuntimeAdapter_1 from "./assistantDeepTurnAttemptRuntimeAdapter";
|
||||||
import * as assistantBoundaryPolicy_1 from "./assistantBoundaryPolicy";
|
import * as assistantBoundaryPolicy_1 from "./assistantBoundaryPolicy";
|
||||||
|
import * as assistantLivingModePolicy_1 from "./assistantLivingModePolicy";
|
||||||
|
import * as assistantRoutePolicy_1 from "./assistantRoutePolicy";
|
||||||
import * as assistantOrganizationScopeRuntimeAdapter_1 from "./assistantOrganizationScopeRuntimeAdapter";
|
import * as assistantOrganizationScopeRuntimeAdapter_1 from "./assistantOrganizationScopeRuntimeAdapter";
|
||||||
import * as assistantOrganizationMatcher_1 from "./assistantOrganizationMatcher";
|
import * as assistantOrganizationMatcher_1 from "./assistantOrganizationMatcher";
|
||||||
import * as assistantTurnAttemptRuntimeAdapter_1 from "./assistantTurnAttemptRuntimeAdapter";
|
import * as assistantTurnAttemptRuntimeAdapter_1 from "./assistantTurnAttemptRuntimeAdapter";
|
||||||
|
|
@ -4211,623 +4213,18 @@ function hasOpenContractsAddressSignal(text) {
|
||||||
const hasTemporalCue = hasPeriodLiteral(normalized) || /\b\d{4}[-/.]\d{2}[-/.]\d{2}\b/.test(normalized);
|
const hasTemporalCue = hasPeriodLiteral(normalized) || /\b\d{4}[-/.]\d{2}[-/.]\d{2}\b/.test(normalized);
|
||||||
return hasRequestCue || hasTemporalCue;
|
return hasRequestCue || hasTemporalCue;
|
||||||
}
|
}
|
||||||
const ADDRESS_INTENTS_KEEP_ADDRESS_LANE = new Set([
|
|
||||||
"period_coverage_profile",
|
|
||||||
"document_type_and_account_section_profile",
|
|
||||||
"counterparty_population_and_roles",
|
|
||||||
"counterparty_activity_lifecycle",
|
|
||||||
"customer_revenue_and_payments",
|
|
||||||
"supplier_payouts_profile",
|
|
||||||
"open_contracts_confirmed_as_of_date",
|
|
||||||
"list_open_contracts",
|
|
||||||
"open_items_by_counterparty_or_contract",
|
|
||||||
"list_payables_counterparties",
|
|
||||||
"list_receivables_counterparties",
|
|
||||||
"inventory_on_hand_as_of_date",
|
|
||||||
"payables_confirmed_as_of_date",
|
|
||||||
"receivables_confirmed_as_of_date",
|
|
||||||
"list_documents_by_contract",
|
|
||||||
"bank_operations_by_contract",
|
|
||||||
"list_documents_by_counterparty",
|
|
||||||
"bank_operations_by_counterparty",
|
|
||||||
"list_contracts_by_counterparty",
|
|
||||||
"inventory_purchase_provenance_for_item",
|
|
||||||
"inventory_purchase_documents_for_item",
|
|
||||||
"inventory_supplier_stock_overlap_as_of_date",
|
|
||||||
"inventory_sale_trace_for_item",
|
|
||||||
"inventory_profitability_for_item",
|
|
||||||
"inventory_purchase_to_sale_chain",
|
|
||||||
"inventory_aging_by_purchase_date",
|
|
||||||
"contract_usage_overview",
|
|
||||||
"contract_usage_and_value",
|
|
||||||
"vat_payable_forecast",
|
|
||||||
"vat_liability_confirmed_for_tax_period",
|
|
||||||
"vat_payable_confirmed_as_of_date"
|
|
||||||
]);
|
|
||||||
const ADDRESS_INTENTS_ALLOW_STRICT_DEEP_INVESTIGATION_BYPASS = new Set([
|
|
||||||
"inventory_purchase_provenance_for_item",
|
|
||||||
"inventory_purchase_documents_for_item",
|
|
||||||
"inventory_sale_trace_for_item",
|
|
||||||
"inventory_profitability_for_item",
|
|
||||||
"inventory_purchase_to_sale_chain"
|
|
||||||
]);
|
|
||||||
function shouldBypassStrictDeepInvestigationCueForAddressIntent(intent) {
|
|
||||||
return Boolean(intent && ADDRESS_INTENTS_ALLOW_STRICT_DEEP_INVESTIGATION_BYPASS.has(intent));
|
|
||||||
}
|
|
||||||
export function resolveAssistantOrchestrationDecision(input) {
|
export function resolveAssistantOrchestrationDecision(input) {
|
||||||
const rawUserMessage = String(input?.rawUserMessage ?? input?.userMessage ?? "");
|
return assistantRoutePolicy.resolveAssistantOrchestrationDecision(input);
|
||||||
const effectiveAddressUserMessage = String(input?.effectiveAddressUserMessage ?? rawUserMessage);
|
|
||||||
const repairedRawUserMessage = repairAddressMojibake(rawUserMessage);
|
|
||||||
const repairedEffectiveAddressUserMessage = repairAddressMojibake(effectiveAddressUserMessage);
|
|
||||||
const followupContext = input?.followupContext ?? null;
|
|
||||||
const llmPreDecomposeMeta = input?.llmPreDecomposeMeta ?? null;
|
|
||||||
const useMock = Boolean(input?.useMock);
|
|
||||||
const sessionItems = Array.isArray(input?.sessionItems) ? input.sessionItems : null;
|
|
||||||
const sessionOrganizationScope = input?.sessionOrganizationScope && typeof input.sessionOrganizationScope === "object"
|
|
||||||
? input.sessionOrganizationScope
|
|
||||||
: null;
|
|
||||||
const lastGroundedAddressDebug = findLastGroundedAddressAnswerDebug(sessionItems);
|
|
||||||
const lastOrganizationClarificationDebug = findLastOrganizationClarificationAddressDebug(sessionItems);
|
|
||||||
const organizationClarificationCandidates = Array.isArray(lastOrganizationClarificationDebug?.organization_candidates)
|
|
||||||
? mergeKnownOrganizations([
|
|
||||||
...lastOrganizationClarificationDebug.organization_candidates,
|
|
||||||
...((Array.isArray(sessionOrganizationScope?.knownOrganizations)
|
|
||||||
? sessionOrganizationScope.knownOrganizations
|
|
||||||
: []))
|
|
||||||
])
|
|
||||||
: [];
|
|
||||||
const organizationClarificationSelectionFromScope = normalizeOrganizationScopeValue(sessionOrganizationScope?.selectedOrganization);
|
|
||||||
const organizationClarificationSelection = resolveOrganizationSelectionFromMessage(rawUserMessage, organizationClarificationCandidates) ??
|
|
||||||
resolveOrganizationSelectionFromMessage(repairedRawUserMessage, organizationClarificationCandidates) ??
|
|
||||||
resolveOrganizationSelectionFromMessage(effectiveAddressUserMessage, organizationClarificationCandidates) ??
|
|
||||||
resolveOrganizationSelectionFromMessage(repairedEffectiveAddressUserMessage, organizationClarificationCandidates) ??
|
|
||||||
(organizationClarificationSelectionFromScope &&
|
|
||||||
organizationClarificationCandidates.some((candidate) => normalizeOrganizationScopeValue(candidate) === organizationClarificationSelectionFromScope)
|
|
||||||
? organizationClarificationSelectionFromScope
|
|
||||||
: null);
|
|
||||||
const dataScopeMetaQuery = hasAssistantDataScopeMetaQuestionSignal(rawUserMessage) ||
|
|
||||||
hasAssistantDataScopeMetaQuestionSignal(repairedRawUserMessage) ||
|
|
||||||
hasAssistantDataScopeMetaQuestionSignal(effectiveAddressUserMessage) ||
|
|
||||||
hasAssistantDataScopeMetaQuestionSignal(repairedEffectiveAddressUserMessage);
|
|
||||||
const capabilityMetaQuery = shouldHandleAsAssistantCapabilityMetaQuery(rawUserMessage) ||
|
|
||||||
shouldHandleAsAssistantCapabilityMetaQuery(repairedRawUserMessage) ||
|
|
||||||
shouldHandleAsAssistantCapabilityMetaQuery(effectiveAddressUserMessage) ||
|
|
||||||
shouldHandleAsAssistantCapabilityMetaQuery(repairedEffectiveAddressUserMessage);
|
|
||||||
const dataRetrievalSignal = hasDataRetrievalRequestSignal(rawUserMessage) ||
|
|
||||||
hasDataRetrievalRequestSignal(repairedRawUserMessage) ||
|
|
||||||
hasDataRetrievalRequestSignal(effectiveAddressUserMessage) ||
|
|
||||||
hasDataRetrievalRequestSignal(repairedEffectiveAddressUserMessage);
|
|
||||||
const aggregateBusinessAnalyticsSignal = hasAggregateBusinessAnalyticsSignal(rawUserMessage) ||
|
|
||||||
hasAggregateBusinessAnalyticsSignal(repairedRawUserMessage) ||
|
|
||||||
hasAggregateBusinessAnalyticsSignal(effectiveAddressUserMessage) ||
|
|
||||||
hasAggregateBusinessAnalyticsSignal(repairedEffectiveAddressUserMessage);
|
|
||||||
const standaloneAddressTopicSignal = hasStandaloneAddressTopicSignal(rawUserMessage) ||
|
|
||||||
hasStandaloneAddressTopicSignal(repairedRawUserMessage) ||
|
|
||||||
hasStandaloneAddressTopicSignal(effectiveAddressUserMessage) ||
|
|
||||||
hasStandaloneAddressTopicSignal(repairedEffectiveAddressUserMessage);
|
|
||||||
const openContractsAddressSignal = hasOpenContractsAddressSignal(rawUserMessage) ||
|
|
||||||
hasOpenContractsAddressSignal(repairedRawUserMessage) ||
|
|
||||||
hasOpenContractsAddressSignal(effectiveAddressUserMessage) ||
|
|
||||||
hasOpenContractsAddressSignal(repairedEffectiveAddressUserMessage);
|
|
||||||
const modeSample = repairedEffectiveAddressUserMessage || effectiveAddressUserMessage;
|
|
||||||
const modeDetection = (0, addressQueryClassifier_1.detectAddressQuestionMode)(modeSample);
|
|
||||||
const modeDetectionRaw = (0, addressQueryClassifier_1.detectAddressQuestionMode)(repairedRawUserMessage || rawUserMessage);
|
|
||||||
const resolvedModeDetection = modeDetection.mode === "address_query" ? modeDetection : modeDetectionRaw;
|
|
||||||
const intentResolution = (0, addressIntentResolver_1.resolveAddressIntent)(modeSample);
|
|
||||||
const intentResolutionRaw = (0, addressIntentResolver_1.resolveAddressIntent)(repairedRawUserMessage || rawUserMessage);
|
|
||||||
const resolvedIntentResolution = intentResolution.intent !== "unknown" ? intentResolution : intentResolutionRaw;
|
|
||||||
const llmContractIntent = toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.intent);
|
|
||||||
const llmPreDecomposeReason = toNonEmptyString(llmPreDecomposeMeta?.reason);
|
|
||||||
const llmRuntimeUnavailableDetected = Boolean(llmPreDecomposeReason &&
|
|
||||||
/(?:openai\s+api\s+key\s+is\s+missing|api\s+key\s+is\s+missing|missing\s+api\s+key|authentication)/iu.test(llmPreDecomposeReason));
|
|
||||||
const semanticExtractionContract = llmPreDecomposeMeta?.semanticExtractionContract &&
|
|
||||||
typeof llmPreDecomposeMeta.semanticExtractionContract === "object"
|
|
||||||
? llmPreDecomposeMeta.semanticExtractionContract
|
|
||||||
: null;
|
|
||||||
const semanticContractValid = semanticExtractionContract?.valid !== false;
|
|
||||||
const semanticApplyCanonicalRecommended = semanticExtractionContract?.apply_canonical_recommended !== false;
|
|
||||||
const semanticReasonCodes = Array.isArray(semanticExtractionContract?.reason_codes)
|
|
||||||
? semanticExtractionContract.reason_codes
|
|
||||||
: [];
|
|
||||||
const strictDeepInvestigationCueDetected = hasStrictDeepInvestigationCue(rawUserMessage) ||
|
|
||||||
hasStrictDeepInvestigationCue(repairedRawUserMessage) ||
|
|
||||||
hasStrictDeepInvestigationCue(effectiveAddressUserMessage) ||
|
|
||||||
hasStrictDeepInvestigationCue(repairedEffectiveAddressUserMessage);
|
|
||||||
const strictDeepInvestigationBypassAllowed = shouldBypassStrictDeepInvestigationCueForAddressIntent(resolvedIntentResolution.intent) ||
|
|
||||||
shouldBypassStrictDeepInvestigationCueForAddressIntent(llmContractIntent);
|
|
||||||
const keepAddressLaneByIntent = semanticApplyCanonicalRecommended &&
|
|
||||||
Boolean((resolvedIntentResolution.intent && ADDRESS_INTENTS_KEEP_ADDRESS_LANE.has(resolvedIntentResolution.intent)) ||
|
|
||||||
(llmContractIntent && ADDRESS_INTENTS_KEEP_ADDRESS_LANE.has(llmContractIntent)) ||
|
|
||||||
openContractsAddressSignal) &&
|
|
||||||
(!strictDeepInvestigationCueDetected || strictDeepInvestigationBypassAllowed);
|
|
||||||
const strongDataSignal = hasStrongDataIntentSignal(rawUserMessage) ||
|
|
||||||
hasStrongDataIntentSignal(repairedRawUserMessage) ||
|
|
||||||
hasStrongDataIntentSignal(effectiveAddressUserMessage) ||
|
|
||||||
hasStrongDataIntentSignal(repairedEffectiveAddressUserMessage) ||
|
|
||||||
hasAccountingSignal(rawUserMessage) ||
|
|
||||||
hasAccountingSignal(repairedRawUserMessage) ||
|
|
||||||
hasAccountingSignal(effectiveAddressUserMessage) ||
|
|
||||||
hasAccountingSignal(repairedEffectiveAddressUserMessage) ||
|
|
||||||
hasDataRetrievalRequestSignal(rawUserMessage) ||
|
|
||||||
hasDataRetrievalRequestSignal(repairedRawUserMessage);
|
|
||||||
const llmContractMode = toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.mode);
|
|
||||||
const llmFirstAddressCandidate = Boolean(llmContractMode === "address_query" && llmContractIntent && llmContractIntent !== "unknown");
|
|
||||||
const llmFirstUnsupportedCandidate = Boolean(llmContractMode === "unsupported" &&
|
|
||||||
(!llmContractIntent || llmContractIntent === "unknown"));
|
|
||||||
const dangerOrCoercionSignal = hasDangerOrCoercionSignal(rawUserMessage) ||
|
|
||||||
hasDangerOrCoercionSignal(repairedRawUserMessage) ||
|
|
||||||
hasDangerOrCoercionSignal(effectiveAddressUserMessage) ||
|
|
||||||
hasDangerOrCoercionSignal(repairedEffectiveAddressUserMessage);
|
|
||||||
const explicitAddressFollowupSignal = hasAddressFollowupContextSignal(rawUserMessage) ||
|
|
||||||
hasAddressFollowupContextSignal(repairedRawUserMessage) ||
|
|
||||||
hasAddressFollowupContextSignal(effectiveAddressUserMessage) ||
|
|
||||||
hasAddressFollowupContextSignal(repairedEffectiveAddressUserMessage) ||
|
|
||||||
hasShortDebtMirrorFollowupSignal(rawUserMessage) ||
|
|
||||||
hasShortDebtMirrorFollowupSignal(repairedRawUserMessage) ||
|
|
||||||
hasShortDebtMirrorFollowupSignal(effectiveAddressUserMessage) ||
|
|
||||||
hasShortDebtMirrorFollowupSignal(repairedEffectiveAddressUserMessage);
|
|
||||||
const protectedInventoryShortFollowup = Boolean(followupContext &&
|
|
||||||
isInventorySelectedObjectIntent(toNonEmptyString(followupContext.previous_intent)) &&
|
|
||||||
(hasShortInventoryObjectFollowupSignal(rawUserMessage) ||
|
|
||||||
hasShortInventoryObjectFollowupSignal(repairedRawUserMessage) ||
|
|
||||||
hasShortInventoryObjectFollowupSignal(effectiveAddressUserMessage) ||
|
|
||||||
hasShortInventoryObjectFollowupSignal(repairedEffectiveAddressUserMessage)));
|
|
||||||
const organizationClarificationContinuationDetected = Boolean(followupContext &&
|
|
||||||
lastOrganizationClarificationDebug &&
|
|
||||||
organizationClarificationSelection &&
|
|
||||||
!dataScopeMetaQuery &&
|
|
||||||
!capabilityMetaQuery &&
|
|
||||||
!dataRetrievalSignal);
|
|
||||||
const effectiveAddressFollowupSignal = explicitAddressFollowupSignal && !dangerOrCoercionSignal;
|
|
||||||
const deterministicNonDomainGuard = Boolean(!dataScopeMetaQuery &&
|
|
||||||
!capabilityMetaQuery &&
|
|
||||||
!dataRetrievalSignal &&
|
|
||||||
!effectiveAddressFollowupSignal &&
|
|
||||||
resolvedModeDetection.mode === "unsupported" &&
|
|
||||||
resolvedIntentResolution.intent === "unknown");
|
|
||||||
const nonDomainQueryIndexed = Boolean(!llmFirstAddressCandidate &&
|
|
||||||
deterministicNonDomainGuard &&
|
|
||||||
(llmFirstUnsupportedCandidate || llmContractMode === null) &&
|
|
||||||
!protectedInventoryShortFollowup &&
|
|
||||||
!organizationClarificationContinuationDetected);
|
|
||||||
const contextualHistoricalCapabilityFollowupDetected = Boolean(capabilityMetaQuery &&
|
|
||||||
!dataScopeMetaQuery &&
|
|
||||||
!dataRetrievalSignal &&
|
|
||||||
(hasHistoricalCapabilityFollowupSignal(rawUserMessage) ||
|
|
||||||
hasHistoricalCapabilityFollowupSignal(repairedRawUserMessage) ||
|
|
||||||
hasHistoricalCapabilityFollowupSignal(effectiveAddressUserMessage) ||
|
|
||||||
hasHistoricalCapabilityFollowupSignal(repairedEffectiveAddressUserMessage)) &&
|
|
||||||
isGroundedInventoryContextDebug(lastGroundedAddressDebug));
|
|
||||||
const contextualMemoryRecapFollowupDetected = Boolean(!dataScopeMetaQuery &&
|
|
||||||
!capabilityMetaQuery &&
|
|
||||||
!dataRetrievalSignal &&
|
|
||||||
!strongDataSignal &&
|
|
||||||
!aggregateBusinessAnalyticsSignal &&
|
|
||||||
(hasConversationMemoryRecallFollowupSignal(rawUserMessage) ||
|
|
||||||
hasConversationMemoryRecallFollowupSignal(repairedRawUserMessage) ||
|
|
||||||
hasConversationMemoryRecallFollowupSignal(effectiveAddressUserMessage) ||
|
|
||||||
hasConversationMemoryRecallFollowupSignal(repairedEffectiveAddressUserMessage)) &&
|
|
||||||
(lastGroundedAddressDebug ||
|
|
||||||
findLastAddressAssistantItem(sessionItems)?.debug));
|
|
||||||
const hardMetaMode = dataScopeMetaQuery
|
|
||||||
? "data_scope"
|
|
||||||
: capabilityMetaQuery && !dataRetrievalSignal
|
|
||||||
? "capability"
|
|
||||||
: null;
|
|
||||||
if (hardMetaMode === "data_scope") {
|
|
||||||
return {
|
|
||||||
runAddressLane: false,
|
|
||||||
toolGateDecision: "skip_address_lane",
|
|
||||||
toolGateReason: "assistant_data_scope_query_detected",
|
|
||||||
livingMode: "chat",
|
|
||||||
livingReason: "assistant_data_scope_query_detected",
|
|
||||||
orchestrationContract: {
|
|
||||||
schema_version: "assistant_orchestration_contract_v1",
|
|
||||||
hard_meta_mode: "data_scope",
|
|
||||||
address_mode: resolvedModeDetection.mode,
|
|
||||||
address_mode_confidence: resolvedModeDetection.confidence,
|
|
||||||
address_intent: resolvedIntentResolution.intent,
|
|
||||||
address_intent_confidence: resolvedIntentResolution.confidence,
|
|
||||||
strong_data_signal_detected: strongDataSignal,
|
|
||||||
data_retrieval_signal_detected: dataRetrievalSignal,
|
|
||||||
followup_context_detected: Boolean(followupContext),
|
|
||||||
unsupported_address_intent_fallback_to_deep: false,
|
|
||||||
final_decision: {
|
|
||||||
run_address_lane: false,
|
|
||||||
tool_gate_decision: "skip_address_lane",
|
|
||||||
tool_gate_reason: "assistant_data_scope_query_detected",
|
|
||||||
living_mode: "chat",
|
|
||||||
living_reason: "assistant_data_scope_query_detected"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (hardMetaMode === "capability") {
|
|
||||||
if (contextualHistoricalCapabilityFollowupDetected) {
|
|
||||||
return {
|
|
||||||
runAddressLane: false,
|
|
||||||
toolGateDecision: "skip_address_lane",
|
|
||||||
toolGateReason: "inventory_history_capability_followup_detected",
|
|
||||||
livingMode: "chat",
|
|
||||||
livingReason: "inventory_history_capability_followup_detected",
|
|
||||||
orchestrationContract: {
|
|
||||||
schema_version: "assistant_orchestration_contract_v1",
|
|
||||||
hard_meta_mode: "capability",
|
|
||||||
address_mode: resolvedModeDetection.mode,
|
|
||||||
address_mode_confidence: resolvedModeDetection.confidence,
|
|
||||||
address_intent: resolvedIntentResolution.intent,
|
|
||||||
address_intent_confidence: resolvedIntentResolution.confidence,
|
|
||||||
strong_data_signal_detected: strongDataSignal,
|
|
||||||
data_retrieval_signal_detected: dataRetrievalSignal,
|
|
||||||
followup_context_detected: Boolean(followupContext || lastGroundedAddressDebug),
|
|
||||||
unsupported_address_intent_fallback_to_deep: false,
|
|
||||||
final_decision: {
|
|
||||||
run_address_lane: false,
|
|
||||||
tool_gate_decision: "skip_address_lane",
|
|
||||||
tool_gate_reason: "inventory_history_capability_followup_detected",
|
|
||||||
living_mode: "chat",
|
|
||||||
living_reason: "inventory_history_capability_followup_detected"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
runAddressLane: false,
|
|
||||||
toolGateDecision: "skip_address_lane",
|
|
||||||
toolGateReason: "assistant_capability_query_detected",
|
|
||||||
livingMode: "chat",
|
|
||||||
livingReason: "assistant_capability_query_detected",
|
|
||||||
orchestrationContract: {
|
|
||||||
schema_version: "assistant_orchestration_contract_v1",
|
|
||||||
hard_meta_mode: "capability",
|
|
||||||
address_mode: resolvedModeDetection.mode,
|
|
||||||
address_mode_confidence: resolvedModeDetection.confidence,
|
|
||||||
address_intent: resolvedIntentResolution.intent,
|
|
||||||
address_intent_confidence: resolvedIntentResolution.confidence,
|
|
||||||
strong_data_signal_detected: strongDataSignal,
|
|
||||||
data_retrieval_signal_detected: dataRetrievalSignal,
|
|
||||||
followup_context_detected: Boolean(followupContext),
|
|
||||||
unsupported_address_intent_fallback_to_deep: false,
|
|
||||||
final_decision: {
|
|
||||||
run_address_lane: false,
|
|
||||||
tool_gate_decision: "skip_address_lane",
|
|
||||||
tool_gate_reason: "assistant_capability_query_detected",
|
|
||||||
living_mode: "chat",
|
|
||||||
living_reason: "assistant_capability_query_detected"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (nonDomainQueryIndexed) {
|
|
||||||
if (contextualMemoryRecapFollowupDetected) {
|
|
||||||
return {
|
|
||||||
runAddressLane: false,
|
|
||||||
toolGateDecision: "skip_address_lane",
|
|
||||||
toolGateReason: "memory_recap_followup_detected",
|
|
||||||
livingMode: "chat",
|
|
||||||
livingReason: "memory_recap_followup_detected",
|
|
||||||
orchestrationContract: {
|
|
||||||
schema_version: "assistant_orchestration_contract_v1",
|
|
||||||
hard_meta_mode: "non_domain",
|
|
||||||
address_mode: resolvedModeDetection.mode,
|
|
||||||
address_mode_confidence: resolvedModeDetection.confidence,
|
|
||||||
address_intent: resolvedIntentResolution.intent,
|
|
||||||
address_intent_confidence: resolvedIntentResolution.confidence,
|
|
||||||
strong_data_signal_detected: strongDataSignal,
|
|
||||||
data_retrieval_signal_detected: dataRetrievalSignal,
|
|
||||||
followup_context_detected: Boolean(followupContext || lastGroundedAddressDebug),
|
|
||||||
unsupported_address_intent_fallback_to_deep: false,
|
|
||||||
final_decision: {
|
|
||||||
run_address_lane: false,
|
|
||||||
tool_gate_decision: "skip_address_lane",
|
|
||||||
tool_gate_reason: "memory_recap_followup_detected",
|
|
||||||
living_mode: "chat",
|
|
||||||
living_reason: "memory_recap_followup_detected"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
runAddressLane: false,
|
|
||||||
toolGateDecision: "skip_address_lane",
|
|
||||||
toolGateReason: "non_domain_query_indexed",
|
|
||||||
livingMode: "chat",
|
|
||||||
livingReason: "non_domain_query_indexed",
|
|
||||||
orchestrationContract: {
|
|
||||||
schema_version: "assistant_orchestration_contract_v1",
|
|
||||||
hard_meta_mode: "non_domain",
|
|
||||||
address_mode: resolvedModeDetection.mode,
|
|
||||||
address_mode_confidence: resolvedModeDetection.confidence,
|
|
||||||
address_intent: resolvedIntentResolution.intent,
|
|
||||||
address_intent_confidence: resolvedIntentResolution.confidence,
|
|
||||||
strong_data_signal_detected: strongDataSignal,
|
|
||||||
data_retrieval_signal_detected: dataRetrievalSignal,
|
|
||||||
followup_context_detected: Boolean(followupContext),
|
|
||||||
unsupported_address_intent_fallback_to_deep: false,
|
|
||||||
final_decision: {
|
|
||||||
run_address_lane: false,
|
|
||||||
tool_gate_decision: "skip_address_lane",
|
|
||||||
tool_gate_reason: "non_domain_query_indexed",
|
|
||||||
living_mode: "chat",
|
|
||||||
living_reason: "non_domain_query_indexed"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const metaAnswerFollowupSignal = hasMetaAnswerFollowupSignal(rawUserMessage) ||
|
|
||||||
hasMetaAnswerFollowupSignal(repairedRawUserMessage) ||
|
|
||||||
hasMetaAnswerFollowupSignal(effectiveAddressUserMessage) ||
|
|
||||||
hasMetaAnswerFollowupSignal(repairedEffectiveAddressUserMessage);
|
|
||||||
const baseToolGate = resolveAddressToolGateDecision(effectiveAddressUserMessage, followupContext, llmPreDecomposeMeta, rawUserMessage);
|
|
||||||
const preserveAddressLaneSignal = Boolean((llmPreDecomposeMeta?.llmCanonicalCandidateDetected &&
|
|
||||||
llmPreDecomposeMeta?.applied &&
|
|
||||||
llmContractMode === "address_query") ||
|
|
||||||
hasSameDateAccountFollowupSignalForPredecompose(rawUserMessage) ||
|
|
||||||
hasSameDateAccountFollowupSignalForPredecompose(effectiveAddressUserMessage) ||
|
|
||||||
hasSameDateAccountFollowupSignalForPredecompose(repairedRawUserMessage) ||
|
|
||||||
hasSameDateAccountFollowupSignalForPredecompose(repairedEffectiveAddressUserMessage) ||
|
|
||||||
hasLooseAllTimeAddressLookupSignal(rawUserMessage) ||
|
|
||||||
hasLooseAllTimeAddressLookupSignal(effectiveAddressUserMessage) ||
|
|
||||||
hasLooseAllTimeAddressLookupSignal(repairedRawUserMessage) ||
|
|
||||||
hasLooseAllTimeAddressLookupSignal(repairedEffectiveAddressUserMessage) ||
|
|
||||||
hasAddressFollowupContextSignal(rawUserMessage) ||
|
|
||||||
hasAddressFollowupContextSignal(effectiveAddressUserMessage) ||
|
|
||||||
hasAddressFollowupContextSignal(repairedRawUserMessage) ||
|
|
||||||
hasAddressFollowupContextSignal(repairedEffectiveAddressUserMessage) ||
|
|
||||||
hasShortDebtMirrorFollowupSignal(rawUserMessage) ||
|
|
||||||
hasShortDebtMirrorFollowupSignal(effectiveAddressUserMessage) ||
|
|
||||||
hasShortDebtMirrorFollowupSignal(repairedRawUserMessage) ||
|
|
||||||
hasShortDebtMirrorFollowupSignal(repairedEffectiveAddressUserMessage));
|
|
||||||
const supportedAddressIntentDetected = (!strictDeepInvestigationCueDetected || strictDeepInvestigationBypassAllowed) &&
|
|
||||||
Boolean((resolvedIntentResolution.intent && ADDRESS_INTENTS_KEEP_ADDRESS_LANE.has(resolvedIntentResolution.intent)) ||
|
|
||||||
(llmContractIntent && ADDRESS_INTENTS_KEEP_ADDRESS_LANE.has(llmContractIntent)) ||
|
|
||||||
openContractsAddressSignal);
|
|
||||||
const semanticGuardHints = semanticExtractionContract?.guard_hints &&
|
|
||||||
typeof semanticExtractionContract.guard_hints === "object"
|
|
||||||
? semanticExtractionContract.guard_hints
|
|
||||||
: null;
|
|
||||||
const semanticExtraction = semanticExtractionContract?.extraction &&
|
|
||||||
typeof semanticExtractionContract.extraction === "object"
|
|
||||||
? semanticExtractionContract.extraction
|
|
||||||
: null;
|
|
||||||
const semanticDeepInvestigationHintDetected = semanticGuardHints?.deep_investigation_signal_detected === true;
|
|
||||||
const semanticAggregateShapeDetected = semanticExtraction?.query_shape === "AGGREGATE_LOOKUP" ||
|
|
||||||
semanticExtraction?.aggregation_profile === "management_profile";
|
|
||||||
const rootContextOnlyFollowup = Boolean(followupContext && followupContext.root_context_only === true);
|
|
||||||
const followupSemanticOverrideToDeepAllowed = Boolean(followupContext &&
|
|
||||||
!supportedAddressIntentDetected &&
|
|
||||||
(rootContextOnlyFollowup ||
|
|
||||||
llmContractMode === "unsupported" ||
|
|
||||||
semanticAggregateShapeDetected ||
|
|
||||||
semanticDeepInvestigationHintDetected ||
|
|
||||||
!semanticApplyCanonicalRecommended));
|
|
||||||
const unsupportedIntentOrMode = (resolvedModeDetection.mode !== "address_query" && resolvedIntentResolution.intent === "unknown") ||
|
|
||||||
llmContractMode === "unsupported" ||
|
|
||||||
(rootContextOnlyFollowup &&
|
|
||||||
resolvedIntentResolution.intent === "unknown" &&
|
|
||||||
(!llmContractIntent || llmContractIntent === "unknown"));
|
|
||||||
const unsupportedAddressIntentFallbackToDeep = Boolean(baseToolGate?.runAddressLane &&
|
|
||||||
!llmRuntimeUnavailableDetected &&
|
|
||||||
unsupportedIntentOrMode &&
|
|
||||||
strongDataSignal &&
|
|
||||||
(rootContextOnlyFollowup ||
|
|
||||||
llmContractMode === "deep_analysis" ||
|
|
||||||
!dataRetrievalSignal ||
|
|
||||||
strictDeepInvestigationCueDetected ||
|
|
||||||
semanticDeepInvestigationHintDetected ||
|
|
||||||
aggregateBusinessAnalyticsSignal) &&
|
|
||||||
!preserveAddressLaneSignal &&
|
|
||||||
!keepAddressLaneByIntent &&
|
|
||||||
!supportedAddressIntentDetected &&
|
|
||||||
(!followupContext || followupSemanticOverrideToDeepAllowed));
|
|
||||||
const deepAnalysisPreferenceDetected = Boolean(hasDeepAnalysisPreferenceSignal(rawUserMessage) ||
|
|
||||||
hasDeepAnalysisPreferenceSignal(repairedRawUserMessage) ||
|
|
||||||
hasDeepAnalysisPreferenceSignal(effectiveAddressUserMessage) ||
|
|
||||||
hasDeepAnalysisPreferenceSignal(repairedEffectiveAddressUserMessage) ||
|
|
||||||
hasDirectDeepAnalysisSignal(rawUserMessage) ||
|
|
||||||
hasDirectDeepAnalysisSignal(repairedRawUserMessage) ||
|
|
||||||
hasDirectDeepAnalysisSignal(effectiveAddressUserMessage) ||
|
|
||||||
hasDirectDeepAnalysisSignal(repairedEffectiveAddressUserMessage));
|
|
||||||
const vatExplainFollowupSignal = Boolean(followupContext &&
|
|
||||||
toNonEmptyString(followupContext.previous_intent) === "vat_payable_forecast" &&
|
|
||||||
/(?:\u043f\u043e\u0447\u0435\u043c\u0443|why).*(?:\u043f\u0440\u043e\u0433\u043d\u043e\u0437|forecast).*(?:\u0443\u043f\u043b\u0430\u0442|payable|\b0\b)/iu.test(compactWhitespace(`${repairedRawUserMessage} ${repairedEffectiveAddressUserMessage}`)));
|
|
||||||
const vatEvaluativeFollowupSignal = Boolean(followupContext &&
|
|
||||||
toNonEmptyString(followupContext.previous_intent) === "vat_payable_forecast" &&
|
|
||||||
/(?:^|\s)(?:это\s+)?много\s+или\s+мало(?:\?|$)|(?:^|\s)(?:это\s+)?нормально(?:\?|$)|(?:^|\s)(?:это\s+)?плохо(?:\?|$)|(?:^|\s)(?:это\s+)?хорошо(?:\?|$)/iu.test(compactWhitespace(`${repairedRawUserMessage} ${repairedEffectiveAddressUserMessage}`)));
|
|
||||||
const deepAnalysisSignalFallbackToDeep = Boolean(baseToolGate?.runAddressLane &&
|
|
||||||
!llmRuntimeUnavailableDetected &&
|
|
||||||
(deepAnalysisPreferenceDetected || semanticDeepInvestigationHintDetected) &&
|
|
||||||
!keepAddressLaneByIntent &&
|
|
||||||
!supportedAddressIntentDetected &&
|
|
||||||
!vatExplainFollowupSignal &&
|
|
||||||
(!followupContext || !dataRetrievalSignal || followupSemanticOverrideToDeepAllowed));
|
|
||||||
const aggregateAnalyticsFallbackToDeep = Boolean(baseToolGate?.runAddressLane &&
|
|
||||||
!llmRuntimeUnavailableDetected &&
|
|
||||||
aggregateBusinessAnalyticsSignal &&
|
|
||||||
!keepAddressLaneByIntent &&
|
|
||||||
!supportedAddressIntentDetected &&
|
|
||||||
(!followupContext ||
|
|
||||||
llmContractMode === "unsupported" ||
|
|
||||||
semanticAggregateShapeDetected ||
|
|
||||||
!semanticApplyCanonicalRecommended ||
|
|
||||||
standaloneAddressTopicSignal));
|
|
||||||
const deepSessionContinuationFallbackToDeep = Boolean(!followupContext &&
|
|
||||||
baseToolGate?.runAddressLane &&
|
|
||||||
!llmRuntimeUnavailableDetected &&
|
|
||||||
hasDeepSessionContinuationSignal({
|
|
||||||
rawUserMessage,
|
|
||||||
repairedRawUserMessage,
|
|
||||||
effectiveAddressUserMessage,
|
|
||||||
repairedEffectiveAddressUserMessage,
|
|
||||||
sessionItems
|
|
||||||
}));
|
|
||||||
const hasPriorAddressAnswerContext = Boolean(lastGroundedAddressDebug || toNonEmptyString(followupContext?.previous_intent));
|
|
||||||
const metaFollowupOverGroundedAnswer = Boolean(followupContext &&
|
|
||||||
hasPriorAddressAnswerContext &&
|
|
||||||
(metaAnswerFollowupSignal || vatEvaluativeFollowupSignal) &&
|
|
||||||
!dataScopeMetaQuery &&
|
|
||||||
!capabilityMetaQuery &&
|
|
||||||
!aggregateBusinessAnalyticsSignal &&
|
|
||||||
!dataRetrievalSignal &&
|
|
||||||
!strongDataSignal &&
|
|
||||||
resolvedModeDetection.mode !== "address_query" &&
|
|
||||||
resolvedIntentResolution.intent === "unknown" &&
|
|
||||||
(!llmContractIntent || llmContractIntent === "unknown") &&
|
|
||||||
llmContractMode !== "address_query");
|
|
||||||
let runAddressLane = Boolean(baseToolGate?.runAddressLane);
|
|
||||||
let toolGateDecision = String(baseToolGate?.decision ?? "skip_address_lane");
|
|
||||||
let toolGateReason = String(baseToolGate?.reason ?? "no_address_signal_after_l0");
|
|
||||||
if (unsupportedAddressIntentFallbackToDeep) {
|
|
||||||
runAddressLane = false;
|
|
||||||
toolGateDecision = "skip_address_lane";
|
|
||||||
toolGateReason = "address_signal_unsupported_intent_fallback_to_deep";
|
|
||||||
}
|
|
||||||
if (deepAnalysisSignalFallbackToDeep && !unsupportedAddressIntentFallbackToDeep) {
|
|
||||||
runAddressLane = false;
|
|
||||||
toolGateDecision = "skip_address_lane";
|
|
||||||
toolGateReason = "deep_analysis_signal_fallback_to_deep";
|
|
||||||
}
|
|
||||||
if (aggregateAnalyticsFallbackToDeep &&
|
|
||||||
!unsupportedAddressIntentFallbackToDeep &&
|
|
||||||
!deepAnalysisSignalFallbackToDeep) {
|
|
||||||
runAddressLane = false;
|
|
||||||
toolGateDecision = "skip_address_lane";
|
|
||||||
toolGateReason = "aggregate_analytics_signal_fallback_to_deep";
|
|
||||||
}
|
|
||||||
if (deepSessionContinuationFallbackToDeep) {
|
|
||||||
runAddressLane = false;
|
|
||||||
toolGateDecision = "skip_address_lane";
|
|
||||||
toolGateReason = "deep_session_continuation_fallback_to_deep";
|
|
||||||
}
|
|
||||||
if (metaFollowupOverGroundedAnswer) {
|
|
||||||
runAddressLane = false;
|
|
||||||
toolGateDecision = "skip_address_lane";
|
|
||||||
toolGateReason = "meta_followup_over_grounded_answer";
|
|
||||||
}
|
|
||||||
let livingDecision = resolveLivingAssistantModeDecision({
|
|
||||||
userMessage: rawUserMessage,
|
|
||||||
addressLaneTriggered: runAddressLane,
|
|
||||||
useMock,
|
|
||||||
predecomposeMode: llmPreDecomposeMeta?.predecomposeContract?.mode ?? null,
|
|
||||||
predecomposeModeConfidence: llmPreDecomposeMeta?.predecomposeContract?.mode_confidence ?? null
|
|
||||||
});
|
|
||||||
if (unsupportedAddressIntentFallbackToDeep) {
|
|
||||||
livingDecision = {
|
|
||||||
mode: "deep_analysis",
|
|
||||||
reason: "unsupported_address_intent_fallback_to_deep"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (deepAnalysisSignalFallbackToDeep && !unsupportedAddressIntentFallbackToDeep) {
|
|
||||||
livingDecision = {
|
|
||||||
mode: "deep_analysis",
|
|
||||||
reason: "deep_analysis_signal_fallback_to_deep"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (aggregateAnalyticsFallbackToDeep &&
|
|
||||||
!unsupportedAddressIntentFallbackToDeep &&
|
|
||||||
!deepAnalysisSignalFallbackToDeep) {
|
|
||||||
livingDecision = {
|
|
||||||
mode: "deep_analysis",
|
|
||||||
reason: "aggregate_analytics_signal_fallback_to_deep"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (deepSessionContinuationFallbackToDeep) {
|
|
||||||
livingDecision = {
|
|
||||||
mode: "deep_analysis",
|
|
||||||
reason: "deep_session_continuation_fallback_to_deep"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (metaFollowupOverGroundedAnswer) {
|
|
||||||
livingDecision = {
|
|
||||||
mode: "chat",
|
|
||||||
reason: "meta_followup_over_grounded_answer"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
runAddressLane,
|
|
||||||
toolGateDecision,
|
|
||||||
toolGateReason,
|
|
||||||
livingMode: livingDecision.mode,
|
|
||||||
livingReason: livingDecision.reason,
|
|
||||||
orchestrationContract: {
|
|
||||||
schema_version: "assistant_orchestration_contract_v1",
|
|
||||||
hard_meta_mode: null,
|
|
||||||
address_mode: resolvedModeDetection.mode,
|
|
||||||
address_mode_confidence: resolvedModeDetection.confidence,
|
|
||||||
address_intent: resolvedIntentResolution.intent,
|
|
||||||
address_intent_confidence: resolvedIntentResolution.confidence,
|
|
||||||
strong_data_signal_detected: strongDataSignal,
|
|
||||||
data_retrieval_signal_detected: dataRetrievalSignal,
|
|
||||||
semantic_contract_valid: semanticContractValid,
|
|
||||||
semantic_apply_canonical_recommended: semanticApplyCanonicalRecommended,
|
|
||||||
semantic_reason_codes: semanticReasonCodes,
|
|
||||||
semantic_route_arbitration: {
|
|
||||||
supported_address_intent_detected: supportedAddressIntentDetected,
|
|
||||||
strict_deep_investigation_bypass_allowed: strictDeepInvestigationBypassAllowed,
|
|
||||||
semantic_deep_investigation_hint_detected: semanticDeepInvestigationHintDetected,
|
|
||||||
semantic_aggregate_shape_detected: semanticAggregateShapeDetected,
|
|
||||||
followup_semantic_override_to_deep_allowed: followupSemanticOverrideToDeepAllowed
|
|
||||||
},
|
|
||||||
followup_context_detected: Boolean(followupContext),
|
|
||||||
unsupported_address_intent_fallback_to_deep: unsupportedAddressIntentFallbackToDeep,
|
|
||||||
deep_analysis_signal_fallback_to_deep: deepAnalysisSignalFallbackToDeep,
|
|
||||||
aggregate_analytics_signal_fallback_to_deep: aggregateAnalyticsFallbackToDeep,
|
|
||||||
deep_session_continuation_fallback_to_deep: deepSessionContinuationFallbackToDeep,
|
|
||||||
final_decision: {
|
|
||||||
run_address_lane: runAddressLane,
|
|
||||||
tool_gate_decision: toolGateDecision,
|
|
||||||
tool_gate_reason: toolGateReason,
|
|
||||||
living_mode: livingDecision.mode,
|
|
||||||
living_reason: livingDecision.reason
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasStrongDataIntentSignal(text) {
|
function hasStrongDataIntentSignal(text) {
|
||||||
const lower = String(text ?? "").toLowerCase();
|
return assistantLivingModePolicy.hasStrongDataIntentSignal(text);
|
||||||
return /(база|док|документ|проводк|контрагент|договор|контракт|счет|сч[её]т|остат|сальдо|хвост|платеж|плат[её]ж|операц|поставщик|клиент|заказчик|дебитор|кредитор|оборот|баланс|период|месяц|год|инн|аванс|предоплат|отгруз|задолж|долг|склад|товар|номенклат|материал|mcp|bank|counterparty|contract|document|ledger|posting|account|organization|company|advance|prepay|shipment|receivab|payab|warehouse|inventory|stock|item|организац|компан|контор|фирм)/i.test(lower);
|
|
||||||
}
|
}
|
||||||
function hasDataRetrievalRequestSignal(text) {
|
function hasDataRetrievalRequestSignal(text) {
|
||||||
const lower = compactWhitespace(String(text ?? "").toLowerCase());
|
return assistantLivingModePolicy.hasDataRetrievalRequestSignal(text);
|
||||||
if (!lower) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const hasBroadInterrogative = /(?:\u0433\u0434\u0435|\u0432\s+\u043a\u0430\u043a\u0438\u0445|\u043f\u043e\s+\u043a\u0430\u043a\u0438\u043c|\u043f\u043e\s+\u043a\u043e\u043c\u0443|\u043a\u0430\u043a\u0438\u0435|\u043a\u0430\u043a\u043e\u0439|\u043a\u0442\u043e|\u0441\u043a\u043e\u043b\u044c\u043a\u043e|where|which|who|how\s+many)/iu.test(lower);
|
|
||||||
const hasBroadBusinessObject = /(?:\u0430\u0432\u0430\u043d\u0441|\u043f\u0440\u0435\u0434\u043e\u043f\u043b\u0430\u0442|\u043e\u0442\u0433\u0440\u0443\u0437|\u0437\u0430\u0434\u043e\u043b\u0436|\u0434\u043e\u043b\u0433|\u0441\u0430\u043b\u044c\u0434\u043e|\u043e\u043f\u043b\u0430\u0442|\u043f\u043b\u0430\u0442(?:\u0435|\u0451)\u0436|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442|\u0434\u043e\u0433\u043e\u0432\u043e\u0440|\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442|\u0441\u0447(?:\u0435|\u0451)\u0442|\u043e\u0431\u043e\u0440\u043e\u0442|\u043f\u0435\u0440\u0438\u043e\u0434|\u043c\u0435\u0441\u044f\u0446|\u0433\u043e\u0434|\u0441\u043a\u043b\u0430\u0434|\u0442\u043e\u0432\u0430\u0440|\u043d\u043e\u043c\u0435\u043d\u043a\u043b\u0430\u0442|\u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b|advance|prepay|shipment|receivab|payab|counterparty|contract|document|account|balance|turnover|warehouse|inventory|stock|item)/iu.test(lower);
|
|
||||||
if (hasBroadInterrogative && hasBroadBusinessObject) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const hasRussianRetrievalAction = /(?:^|\s)(?:\u043f\u043e\u043a\u0430\u0436\u0438|\u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c|\u043d\u0430\u0439\u0434\u0438|\u0432\u044b\u0432\u0435\u0434\u0438|\u0434\u0430\u0439|\u0440\u0430\u0441\u043a\u0440\u043e\u0439|\u0441\u043f\u0438\u0441\u043e\u043a|\u043f\u0440\u043e\u0432\u0435\u0440\u044c|\u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c)(?:$|[\s,.!?;:])/iu.test(lower);
|
|
||||||
const hasRussianRetrievalObject = /(?:\u0434\u043e\u0433\u043e\u0432\u043e\u0440|\u043a\u043e\u043d\u0442\u0440\u0430\u043a\u0442|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442|\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442|\u0441\u0447(?:\u0435|\u0451)\u0442|\u043e\u0441\u0442\u0430\u0442|\u0441\u0430\u043b\u044c\u0434\u043e|\u043e\u0431\u043e\u0440\u043e\u0442|\u043f\u043b\u0430\u0442(?:\u0435|\u0451)\u0436|\u043e\u043f\u0435\u0440\u0430\u0446|\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|\u043a\u043b\u0438\u0435\u043d\u0442|\u0433\u043e\u0434|\u043f\u0435\u0440\u0438\u043e\u0434|\u043c\u0435\u0441\u044f\u0446|\u0430\u0432\u0430\u043d\u0441|\u043f\u0440\u0435\u0434\u043e\u043f\u043b\u0430\u0442|\u043e\u0442\u0433\u0440\u0443\u0437|\u0437\u0430\u0434\u043e\u043b\u0436|\u0434\u043e\u043b\u0433|\u0441\u043a\u043b\u0430\u0434|\u0442\u043e\u0432\u0430\u0440|\u043d\u043e\u043c\u0435\u043d\u043a\u043b\u0430\u0442|\u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b)/iu.test(lower);
|
|
||||||
if (hasRussianRetrievalAction && hasRussianRetrievalObject) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const hasExplicitRetrievalAction = /(?:\bпокажи\b|\bпоказать\b|\bвыведи\b|\bнайди\b|\bсписок\b|\bдай\b|\bраскрой\b|\bshow\b|\blist\b|\bfind\b|\bcount\b)/i.test(lower);
|
|
||||||
const hasInterrogativeRetrievalAction = /(?:\bсколько\b|\bкакой\b|\bкакая\b|\bкакое\b|\bкакую\b|\bкакие\b|\bкто\b|\bгде\b|\bпо\s+каким\b|\bпо\s+кому\b|\bу\s+кого\b|\bwhich\b|\bwho\b|\bwhere\b)/i.test(lower);
|
|
||||||
if (!hasExplicitRetrievalAction && !hasInterrogativeRetrievalAction) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const hasRetrievalObject = /(1с|база|док|документ|контрагент|договор|контракт|счет|сч[её]т|остат|сальдо|хвост|платеж|плат[её]ж|операц|поставщик|клиент|заказчик|дебитор|кредитор|период|месяц|год|инн|аванс|предоплат|отгруз|задолж|долг|склад|товар|номенклат|материал|bank|counterparty|contract|document|account|balance|ledger|posting|advance|prepay|shipment|receivab|payab|warehouse|inventory|stock|item|организац|компан|контор|фирм|возраст|дата\s+регистрац|регистрац|основан)/i.test(lower);
|
|
||||||
if (!hasRetrievalObject) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (hasExplicitRetrievalAction) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const hasMetaCapabilityShape = /(?:мож(?:ем|ешь|ете|но)|уме(?:ешь|ете)|доступ|подключ|чья|как\s+называ(?:ет|ется)|работ(?:ать|аем|аешь|аете)|в\s+тебе|у\s+тебя)/i.test(lower);
|
|
||||||
return !hasMetaCapabilityShape;
|
|
||||||
}
|
}
|
||||||
function hasOrganizationFactLookupSignal(text) {
|
function hasOrganizationFactLookupSignal(text) {
|
||||||
const repaired = repairAddressMojibake(String(text ?? ""));
|
return assistantLivingModePolicy.hasOrganizationFactLookupSignal(text);
|
||||||
const normalized = compactWhitespace(repaired.toLowerCase()).replace(/ё/g, "е");
|
|
||||||
if (!normalized) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const hasFactCue = /(?:возраст|сколько\s+лет|дата\s+регистрац|когда\s+(?:зарегистр|создан|основан)|год\s+регистрац|год\s+основан|с\s+какого\s+года|when\s+was\s+(?:it\s+)?(?:registered|founded|created))/i.test(normalized);
|
|
||||||
if (!hasFactCue) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return /(?:организац|компан|контор|фирм|ооо|ао|зао|ип|альтернатив|лайсвуд|райм|organization|company)/i.test(normalized);
|
|
||||||
}
|
}
|
||||||
function findLastAssistantLivingChatDebug(items) {
|
function findLastAssistantLivingChatDebug(items) {
|
||||||
if (!Array.isArray(items)) {
|
if (!Array.isArray(items)) {
|
||||||
|
|
@ -4888,67 +4285,13 @@ function findLastOrganizationClarificationAddressDebug(items) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
function hasMetaAnswerFollowupSignal(userMessage) {
|
function hasMetaAnswerFollowupSignal(userMessage) {
|
||||||
const rawText = compactWhitespace(String(userMessage ?? "").toLowerCase());
|
return assistantLivingModePolicy.hasMetaAnswerFollowupSignal(userMessage);
|
||||||
const repairedText = compactWhitespace(repairAddressMojibake(String(userMessage ?? "")).toLowerCase());
|
|
||||||
const samples = [rawText, repairedText]
|
|
||||||
.filter((item) => item.length > 0)
|
|
||||||
.map((item) => item.replace(/ё/g, "е"));
|
|
||||||
if (samples.length === 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const hasReflectionCue = samples.some((sample) => sample.includes("дума") ||
|
|
||||||
sample.includes("скаж") ||
|
|
||||||
sample.includes("мнение") ||
|
|
||||||
sample.includes("как тебе") ||
|
|
||||||
sample.includes("норм") ||
|
|
||||||
sample.includes("стран") ||
|
|
||||||
sample.includes("логич") ||
|
|
||||||
sample.includes("смуща") ||
|
|
||||||
sample.includes("выгляд"));
|
|
||||||
const hasTopicPointerCue = samples.some((sample) => sample.includes("на эту тему") ||
|
|
||||||
sample.includes("по этому поводу") ||
|
|
||||||
sample.includes("об этом") ||
|
|
||||||
(sample.includes("это") && hasReferentialPointer(sample)));
|
|
||||||
const hasEvaluationCue = samples.some((sample) => /\b(?:много|мало|нормально|хорошо|плохо|критично|перебор|слабо)\b/iu.test(sample));
|
|
||||||
if (!((hasReflectionCue || hasEvaluationCue) &&
|
|
||||||
(hasTopicPointerCue || (hasEvaluationCue && samples.some((sample) => /^(?:это|ну это)\b/iu.test(sample)))))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return !samples.some((sample) => hasAssistantDataScopeMetaQuestionSignal(sample) ||
|
|
||||||
shouldHandleAsAssistantCapabilityMetaQuery(sample) ||
|
|
||||||
hasDataRetrievalRequestSignal(sample) ||
|
|
||||||
hasStrongDataIntentSignal(sample));
|
|
||||||
}
|
}
|
||||||
function hasConversationMemoryRecallFollowupSignal(userMessage) {
|
function hasConversationMemoryRecallFollowupSignal(userMessage) {
|
||||||
const rawText = compactWhitespace(String(userMessage ?? "").toLowerCase());
|
return assistantLivingModePolicy.hasConversationMemoryRecallFollowupSignal(userMessage);
|
||||||
const repairedText = compactWhitespace(repairAddressMojibake(String(userMessage ?? "")).toLowerCase());
|
|
||||||
const samples = [rawText, repairedText]
|
|
||||||
.filter((item) => item.length > 0)
|
|
||||||
.map((item) => item.replace(/ё/g, "е"));
|
|
||||||
if (samples.length === 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const hasMemoryCue = samples.some((sample) => /(?:помни(?:шь|те|м)?|remember|recall)/iu.test(sample));
|
|
||||||
const hasDiscussionCue = samples.some((sample) => /(?:обсуждал[аи]?|говорил[аи]?|смотрел[аи]?|разбирал[аи]?|спрашивал[аи]?)/iu.test(sample));
|
|
||||||
if (!hasMemoryCue || !hasDiscussionCue) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return !samples.some((sample) => hasAssistantDataScopeMetaQuestionSignal(sample) ||
|
|
||||||
shouldHandleAsAssistantCapabilityMetaQuery(sample) ||
|
|
||||||
hasDataRetrievalRequestSignal(sample) ||
|
|
||||||
hasStrongDataIntentSignal(sample));
|
|
||||||
}
|
}
|
||||||
function hasHistoricalCapabilityFollowupSignal(text) {
|
function hasHistoricalCapabilityFollowupSignal(text) {
|
||||||
const repaired = repairAddressMojibake(String(text ?? ""));
|
return assistantLivingModePolicy.hasHistoricalCapabilityFollowupSignal(text);
|
||||||
const normalized = compactWhitespace(repaired.toLowerCase()).replace(/ё/g, "е");
|
|
||||||
if (!normalized) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const hasHistoryCue = /(?:историческ|история|архив|прошл(?:ый|ые|ую|ых)?|раньше|ретро|старые\s+данные)/iu.test(normalized);
|
|
||||||
if (!hasHistoryCue) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return /(?:мож(?:ешь|ете|но)|уме(?:ешь|ете)|показ|вывед|дай|раскрой)/iu.test(normalized);
|
|
||||||
}
|
}
|
||||||
function isGroundedInventoryContextDebug(debug) {
|
function isGroundedInventoryContextDebug(debug) {
|
||||||
if (!debug || typeof debug !== "object") {
|
if (!debug || typeof debug !== "object") {
|
||||||
|
|
@ -4965,56 +4308,10 @@ function isGroundedInventoryContextDebug(debug) {
|
||||||
rootIntent === "inventory_on_hand_as_of_date";
|
rootIntent === "inventory_on_hand_as_of_date";
|
||||||
}
|
}
|
||||||
function hasOrganizationFactFollowupSignal(userMessage, items) {
|
function hasOrganizationFactFollowupSignal(userMessage, items) {
|
||||||
const repaired = repairAddressMojibake(String(userMessage ?? ""));
|
return assistantLivingModePolicy.hasOrganizationFactFollowupSignal(userMessage, items);
|
||||||
const normalized = compactWhitespace(repaired.toLowerCase()).replace(/ё/g, "е");
|
|
||||||
if (!normalized) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (hasOrganizationFactLookupSignal(normalized)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const hasFollowupCue = /(?:^|\s)(?:давай|го|погнали|ок(?:ей)?|хорошо|принято|подтверждаю|запрашивай|запроси|проверь|продолжай|ну\s+давай|да\s+давай)(?=$|[\s,.!?;:])/iu.test(normalized);
|
|
||||||
if (!hasFollowupCue) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const lastDebug = findLastAssistantLivingChatDebug(items);
|
|
||||||
const lastSource = toNonEmptyString(lastDebug?.living_chat_response_source);
|
|
||||||
const lastGuardReason = toNonEmptyString(lastDebug?.living_chat_grounding_guard_reason);
|
|
||||||
const inOrganizationFactBoundary = lastSource === "deterministic_organization_fact_boundary" ||
|
|
||||||
lastSource === "deterministic_organization_fact_boundary_followup" ||
|
|
||||||
lastGuardReason === "organization_fact_without_live_source_blocked";
|
|
||||||
return inOrganizationFactBoundary;
|
|
||||||
}
|
}
|
||||||
function shouldEmitOrganizationSelectionReply(userMessage, selectedOrganization) {
|
function shouldEmitOrganizationSelectionReply(userMessage, selectedOrganization) {
|
||||||
const selected = normalizeOrganizationScopeValue(selectedOrganization);
|
return assistantLivingModePolicy.shouldEmitOrganizationSelectionReply(userMessage, selectedOrganization);
|
||||||
if (!selected) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const repaired = repairAddressMojibake(String(userMessage ?? ""));
|
|
||||||
const normalized = compactWhitespace(repaired.toLowerCase()).replace(/ё/g, "е");
|
|
||||||
if (!normalized) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (hasOrganizationFactLookupSignal(normalized) || hasDataRetrievalRequestSignal(normalized) || hasStrongDataIntentSignal(normalized)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const hasAnalyticalCue = /(?:какой|какая|какие|когда|сколько|кто|почему|зачем|возраст|дата|регистрац|ндс|налог|контракт|договор|документ|операц|оборот|сумм|остат|сальдо|founded|registered|created)/i.test(normalized);
|
|
||||||
if (hasAnalyticalCue) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const hasSelectionCue = /(?:давай|го|погнали|ок(?:ей)?|хорошо|отлично|берем|выберем|выбираем|переключ(?:им|аем|ай)|фиксир|работаем|обсудим|тогда)\b/i.test(normalized);
|
|
||||||
if (hasSelectionCue) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const hasAffectiveReactionCue = /(?:^|[\s,.;:!?()\-])(?:ну|мда|ох|ах|офигеть|офигенно|ахуеть|охуеть|пиздец|пизда|нихуя|хуево|хуёво|ебать|ебан|бля|блять|fuck|shit|damn)(?=$|[\s,.;:!?()\-])/iu.test(normalized) ||
|
|
||||||
normalized.includes("\u0430\u0445\u0443") ||
|
|
||||||
normalized.includes("\u043e\u0445\u0443") ||
|
|
||||||
normalized.includes("\u043f\u0438\u0437\u0434") ||
|
|
||||||
normalized.includes("\u0431\u043b\u044f");
|
|
||||||
if (hasAffectiveReactionCue) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return normalized.length <= 36 && !/[?]/.test(String(userMessage ?? ""));
|
|
||||||
}
|
}
|
||||||
function hasOperationalAdminActionRequestSignal(text) {
|
function hasOperationalAdminActionRequestSignal(text) {
|
||||||
const lower = compactWhitespace(String(text ?? "").toLowerCase()).replace(/ё/g, "е");
|
const lower = compactWhitespace(String(text ?? "").toLowerCase()).replace(/ё/g, "е");
|
||||||
|
|
@ -5090,78 +4387,13 @@ function hasAssistantCapabilityQuestionSignal(text) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
function hasAssistantDataScopeMetaQuestionSignal(text) {
|
function hasAssistantDataScopeMetaQuestionSignal(text) {
|
||||||
const repaired = repairAddressMojibake(String(text ?? ""));
|
return assistantLivingModePolicy.hasAssistantDataScopeMetaQuestionSignal(text);
|
||||||
const lower = compactWhitespace(repaired.toLowerCase()).replace(/ё/g, "е");
|
|
||||||
const normalized = lower.replace(/\b1\s*[cс]\b/giu, "1с");
|
|
||||||
if (!normalized) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const hasDirectSlangScopeLead = /(?:по\s+каким\s+(?:контор(?:ам|ы|а)?|кантор(?:ам|ы|а)?|компан(?:иям|ии|ию|ия)|организац(?:иям|ии|ию|ия))\s+мож(?:ем|но)\s+(?:общат|работ)|база\s+какой\s+(?:контор|компан|организац|фирм)|какая\s+база\s+(?:подключ|подруб|актив))/iu.test(normalized);
|
|
||||||
if (hasDirectSlangScopeLead) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const hasSlangScopeQuestion = /(?:\u043f\u043e\s+\u043a\u0430\u043a\u0438\u043c\s+(?:\u043a\u043e\u043d\u0442\u043e\u0440(?:\u0430\u043c|\u044b|\u0430)?|\u043a\u043e\u043c\u043f\u0430\u043d(?:\u0438\u044f\u043c|\u0438\u0438|\u0438\u044e|\u0438\u044f)|\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446(?:\u0438\u044f\u043c|\u0438\u0438|\u0438\u044e|\u0438\u044f)|\u0444\u0438\u0440\u043c(?:\u0430\u043c|\u0435|\u0443|\u0430)).*(?:\u043c\u043e\u0436(?:\u0435\u043c|\u043d\u043e)|\u0440\u0430\u0431\u043e\u0442|\u043e\u0431\u0449\u0430\u0442|\u043f\u043e\u0434\u0440\u0443\u0431|\u043f\u043e\u0434\u043a\u043b\u044e\u0447)|(?:\u0431\u0430\u0437\u0430\s+\u043a\u0430\u043a\u043e\u0439\s+(?:\u043a\u043e\u043d\u0442\u043e\u0440|\u043a\u043e\u043c\u043f\u0430\u043d|\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446|\u0444\u0438\u0440\u043c))|(?:\u043a\u0430\u043a\u0430\u044f\s+\u0431\u0430\u0437\u0430\s+(?:\u043f\u043e\u0434\u043a\u043b\u044e\u0447|\u0430\u043a\u0442\u0438\u0432)))/iu.test(normalized);
|
|
||||||
if (hasSlangScopeQuestion) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const hasBaseOrTenantObject = /(?:баз(?:а|е|у|ы)?|тенант|tenant|контур)/i.test(normalized);
|
|
||||||
const hasCompanyObject = /(?:компан(?:ия|ии|ию|ией)|компин(?:ия|ии|ию|ией)?|компини(?:я|и|ю|ей)?|компани[яеию]|организац(?:ия|ии|ию|ией)|контор(?:а|ы|у|ой)?|фирм(?:а|ы|у|ой)?)/i.test(normalized);
|
|
||||||
const hasConnectionCue = /(?:подключен(?:а|о|ы)?|подруб|воткнут|активн(?:ый|ая)\s+канал|mcp-?канал|канал)/i.test(normalized);
|
|
||||||
const hasNamingCue = /(?:как\s+называ(?:ет|ется)|что\s+за\s+(?:контор|компан|организац|фирм))/i.test(normalized);
|
|
||||||
const hasWorkabilityCue = /(?:мож(?:ем|ешь|ете|но)\s+работ|работ(?:ать|аем|аешь|аете))/i.test(normalized);
|
|
||||||
const hasScopeObject = hasBaseOrTenantObject || hasCompanyObject || hasConnectionCue;
|
|
||||||
if (!hasScopeObject) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const hasMetaPerspective = /(?:ты|тебе|твой|у\s+тебя|в\s+тебе|мы|нам|наш(?:а|е|и|у|ей)?|сейчас|щас)/i.test(normalized);
|
|
||||||
const hasScopedInterrogativePair = /(?:^|\s)(?:по\s+какой|с\s+какой|какая|какой|какие)\s+(?:баз|компан|компин|компини|компани|организац|контор|фирм|тенант|контур)/i.test(normalized);
|
|
||||||
const hasScopeQuestion = /(?:чья|чье|чьи|доступн|подключен|подруб|воткнут|какая\s+баз|какой\s+баз)/i.test(normalized) ||
|
|
||||||
hasNamingCue ||
|
|
||||||
hasWorkabilityCue ||
|
|
||||||
hasScopedInterrogativePair;
|
|
||||||
const hasInterrogativeScopeLead = /(?:^|\s)(?:по\s+какой|с\s+какой|чья|чье|чьи|which|who|what)/i.test(normalized);
|
|
||||||
const isQuestionLike = /[?]/.test(String(text ?? "")) || hasInterrogativeScopeLead || hasScopedInterrogativePair;
|
|
||||||
const hasExplicitScopeContext = hasBaseOrTenantObject || hasConnectionCue || hasWorkabilityCue || hasNamingCue;
|
|
||||||
const hasRetrievalSignal = hasDataRetrievalRequestSignal(normalized);
|
|
||||||
const hasContractAnalyticsCue = /(?:договор|контракт|contract).*(?:топ|сам(?:ый|ая|ое|ые)|крупн|жирн|оборот|бюджет|сумм|стоим|value|turnover|all\s+time|всю\s+истори|за\s+вс[её]\s+время)/iu.test(normalized);
|
|
||||||
if (hasContractAnalyticsCue) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (hasRetrievalSignal && !hasExplicitScopeContext) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const hasEligibleScopeObject = hasBaseOrTenantObject || (hasCompanyObject && (hasConnectionCue || hasWorkabilityCue || hasNamingCue || hasMetaPerspective));
|
|
||||||
return hasEligibleScopeObject && hasScopeQuestion && (hasMetaPerspective || isQuestionLike || hasExplicitScopeContext);
|
|
||||||
}
|
}
|
||||||
function shouldHandleAsAssistantCapabilityMetaQuery(text) {
|
function shouldHandleAsAssistantCapabilityMetaQuery(text) {
|
||||||
const raw = String(text ?? "");
|
return assistantLivingModePolicy.shouldHandleAsAssistantCapabilityMetaQuery(text);
|
||||||
const repaired = repairAddressMojibake(raw);
|
|
||||||
const hasScopeMetaSignal = hasAssistantDataScopeMetaQuestionSignal(raw) || hasAssistantDataScopeMetaQuestionSignal(repaired);
|
|
||||||
if (hasScopeMetaSignal) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const hasCapabilitySignal = hasAssistantCapabilityQuestionSignal(raw) ||
|
|
||||||
hasAssistantCapabilityQuestionSignal(repaired) ||
|
|
||||||
hasOperationalAdminActionRequestSignal(raw) ||
|
|
||||||
hasOperationalAdminActionRequestSignal(repaired);
|
|
||||||
const hasRetrievalSignal = hasDataRetrievalRequestSignal(raw) || hasDataRetrievalRequestSignal(repaired);
|
|
||||||
return hasCapabilitySignal && !hasRetrievalSignal;
|
|
||||||
}
|
}
|
||||||
function hasLivingChatSignal(text) {
|
function hasLivingChatSignal(text) {
|
||||||
const lower = compactWhitespace(String(text ?? "").toLowerCase());
|
return assistantLivingModePolicy.hasLivingChatSignal(text);
|
||||||
if (!lower) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (/^(?:а\s+)?(?:тут|здесь|там|сюда|туда)[\s!?.,:;\-]*$/iu.test(lower)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (/^(ага|угу|ок|окей|ясно|понял|поняла|принято|спасибо|благодарю|супер|класс|норм|го|давай|погнали|привет|хай|йо|yo|че\s+там|ч[её]\s+как|че\s+как|hello|hi|thanks?)$/i.test(lower)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (/(как дела|как ты|что нового|расскажи о себе|чем можешь помочь|давай поговорим|поговорим|обсудим|посоветуй|что думаешь)/i.test(lower)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return hasSmallTalkSignal(lower);
|
|
||||||
}
|
}
|
||||||
function buildAssistantCapabilityContractReply() {
|
function buildAssistantCapabilityContractReply() {
|
||||||
return (0, capabilitiesRegistry_1.buildCapabilityContractReplyFromRegistry)();
|
return (0, capabilitiesRegistry_1.buildCapabilityContractReplyFromRegistry)();
|
||||||
|
|
@ -5265,6 +4497,55 @@ function normalizeOrganizationScopeValue(value) {
|
||||||
.trim();
|
.trim();
|
||||||
return unwrapped ? unwrapped : null;
|
return unwrapped ? unwrapped : null;
|
||||||
}
|
}
|
||||||
|
const assistantLivingModePolicy = (0, assistantLivingModePolicy_1.createAssistantLivingModePolicy)({
|
||||||
|
featureAssistantLivingChatRouterV1: config_1.FEATURE_ASSISTANT_LIVING_CHAT_ROUTER_V1,
|
||||||
|
compactWhitespace,
|
||||||
|
repairAddressMojibake,
|
||||||
|
toNonEmptyString,
|
||||||
|
normalizeOrganizationScopeValue,
|
||||||
|
hasReferentialPointer,
|
||||||
|
hasSmallTalkSignal,
|
||||||
|
hasAssistantCapabilityQuestionSignal,
|
||||||
|
hasOperationalAdminActionRequestSignal
|
||||||
|
});
|
||||||
|
const assistantRoutePolicy = (0, assistantRoutePolicy_1.createAssistantRoutePolicy)({
|
||||||
|
repairAddressMojibake,
|
||||||
|
findLastGroundedAddressAnswerDebug,
|
||||||
|
findLastOrganizationClarificationAddressDebug,
|
||||||
|
mergeKnownOrganizations,
|
||||||
|
normalizeOrganizationScopeValue,
|
||||||
|
resolveOrganizationSelectionFromMessage,
|
||||||
|
hasAssistantDataScopeMetaQuestionSignal: assistantLivingModePolicy.hasAssistantDataScopeMetaQuestionSignal,
|
||||||
|
shouldHandleAsAssistantCapabilityMetaQuery: assistantLivingModePolicy.shouldHandleAsAssistantCapabilityMetaQuery,
|
||||||
|
hasDataRetrievalRequestSignal: assistantLivingModePolicy.hasDataRetrievalRequestSignal,
|
||||||
|
hasAggregateBusinessAnalyticsSignal,
|
||||||
|
hasStandaloneAddressTopicSignal,
|
||||||
|
hasOpenContractsAddressSignal,
|
||||||
|
detectAddressQuestionMode: addressQueryClassifier_1.detectAddressQuestionMode,
|
||||||
|
resolveAddressIntent: addressIntentResolver_1.resolveAddressIntent,
|
||||||
|
toNonEmptyString,
|
||||||
|
hasStrictDeepInvestigationCue,
|
||||||
|
hasStrongDataIntentSignal: assistantLivingModePolicy.hasStrongDataIntentSignal,
|
||||||
|
hasAccountingSignal,
|
||||||
|
hasDangerOrCoercionSignal,
|
||||||
|
hasAddressFollowupContextSignal,
|
||||||
|
hasShortDebtMirrorFollowupSignal,
|
||||||
|
isInventorySelectedObjectIntent,
|
||||||
|
hasShortInventoryObjectFollowupSignal,
|
||||||
|
hasHistoricalCapabilityFollowupSignal: assistantLivingModePolicy.hasHistoricalCapabilityFollowupSignal,
|
||||||
|
isGroundedInventoryContextDebug,
|
||||||
|
hasConversationMemoryRecallFollowupSignal: assistantLivingModePolicy.hasConversationMemoryRecallFollowupSignal,
|
||||||
|
findLastAddressAssistantItem,
|
||||||
|
hasMetaAnswerFollowupSignal: assistantLivingModePolicy.hasMetaAnswerFollowupSignal,
|
||||||
|
resolveAddressToolGateDecision,
|
||||||
|
hasSameDateAccountFollowupSignalForPredecompose,
|
||||||
|
hasLooseAllTimeAddressLookupSignal,
|
||||||
|
hasDeepAnalysisPreferenceSignal,
|
||||||
|
hasDirectDeepAnalysisSignal,
|
||||||
|
compactWhitespace,
|
||||||
|
hasDeepSessionContinuationSignal,
|
||||||
|
resolveLivingAssistantModeDecision: assistantLivingModePolicy.resolveLivingAssistantModeDecision
|
||||||
|
});
|
||||||
function normalizeOrganizationScopeSearchText(value) {
|
function normalizeOrganizationScopeSearchText(value) {
|
||||||
const source = normalizeScopeKey(value);
|
const source = normalizeScopeKey(value);
|
||||||
return source
|
return source
|
||||||
|
|
@ -6063,67 +5344,7 @@ function applyLivingChatGroundingGuard(input) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
export function resolveLivingAssistantModeDecision(input) {
|
export function resolveLivingAssistantModeDecision(input) {
|
||||||
const userMessage = String(input?.userMessage ?? "");
|
return assistantLivingModePolicy.resolveLivingAssistantModeDecision(input);
|
||||||
if (input?.addressLaneTriggered) {
|
|
||||||
return {
|
|
||||||
mode: "address_data",
|
|
||||||
reason: "address_lane_triggered"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (!config_1.FEATURE_ASSISTANT_LIVING_CHAT_ROUTER_V1) {
|
|
||||||
return {
|
|
||||||
mode: "deep_analysis",
|
|
||||||
reason: "living_chat_router_disabled"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (Boolean(input?.useMock)) {
|
|
||||||
return {
|
|
||||||
mode: "deep_analysis",
|
|
||||||
reason: "mock_mode_keeps_deep_pipeline"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (hasAssistantDataScopeMetaQuestionSignal(userMessage)) {
|
|
||||||
return {
|
|
||||||
mode: "chat",
|
|
||||||
reason: "assistant_data_scope_query_detected"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (shouldHandleAsAssistantCapabilityMetaQuery(userMessage)) {
|
|
||||||
return {
|
|
||||||
mode: "chat",
|
|
||||||
reason: "assistant_capability_query_detected"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (hasOrganizationFactLookupSignal(userMessage) || hasOrganizationFactFollowupSignal(userMessage)) {
|
|
||||||
return {
|
|
||||||
mode: "chat",
|
|
||||||
reason: "organization_fact_lookup_signal_detected"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (hasStrongDataIntentSignal(userMessage)) {
|
|
||||||
return {
|
|
||||||
mode: "deep_analysis",
|
|
||||||
reason: "strong_data_signal_detected"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (hasLivingChatSignal(userMessage)) {
|
|
||||||
return {
|
|
||||||
mode: "chat",
|
|
||||||
reason: "living_chat_signal_detected"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const predecomposeMode = toNonEmptyString(input?.predecomposeMode);
|
|
||||||
const predecomposeConfidence = toNonEmptyString(input?.predecomposeModeConfidence);
|
|
||||||
if (predecomposeMode === "unsupported" && (predecomposeConfidence === "low" || predecomposeConfidence === "medium")) {
|
|
||||||
return {
|
|
||||||
mode: "deep_analysis",
|
|
||||||
reason: "predecompose_unsupported_mode_fallback_to_deep"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
mode: "deep_analysis",
|
|
||||||
reason: "default_deep_pipeline"
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
export class AssistantService {
|
export class AssistantService {
|
||||||
normalizerService;
|
normalizerService;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { createAssistantLivingModePolicy } from "../src/services/assistantLivingModePolicy";
|
||||||
|
|
||||||
|
function buildPolicy() {
|
||||||
|
return createAssistantLivingModePolicy({
|
||||||
|
featureAssistantLivingChatRouterV1: true,
|
||||||
|
compactWhitespace: (text: string) => text.replace(/\s+/g, " ").trim(),
|
||||||
|
repairAddressMojibake: (text: string) => text,
|
||||||
|
toNonEmptyString: (value: unknown) => {
|
||||||
|
if (value === null || value === undefined) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const text = String(value).trim();
|
||||||
|
return text.length > 0 ? text : null;
|
||||||
|
},
|
||||||
|
normalizeOrganizationScopeValue: (value: unknown) => {
|
||||||
|
if (value === null || value === undefined) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const text = String(value).trim().replace(/^"+|"+$/g, "").replace(/^'+|'+$/g, "");
|
||||||
|
return text.length > 0 ? text : null;
|
||||||
|
},
|
||||||
|
hasReferentialPointer: (text: string) =>
|
||||||
|
/(по этому|по тому|это же|этой|этим|этому|этого|этот|эту|этом|это|эти|этих|из этого|из них|из этих|из тех|в этом|тот же|same thing|that one|po etomu|po tomu)/i.test(
|
||||||
|
text.toLowerCase()
|
||||||
|
),
|
||||||
|
hasSmallTalkSignal: (text: string) => /(привет|как дела|спасибо|благодарю|thanks|thank you|hello|hi)\b/i.test(text.toLowerCase()),
|
||||||
|
hasAssistantCapabilityQuestionSignal: (text: string) =>
|
||||||
|
/(?:кто ты|что ты можешь|какие фичи|полный список возможностей|чем ты можешь помочь|что ты умеешь)/i.test(text),
|
||||||
|
hasOperationalAdminActionRequestSignal: (text: string) =>
|
||||||
|
/(?:настро|установ|подключ|обнов|почин|исправ|удал|снеси|delete\s+database|drop\s+database)/i.test(text)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("assistantLivingModePolicy", () => {
|
||||||
|
it("routes data-scope question to chat mode", () => {
|
||||||
|
const policy = buildPolicy();
|
||||||
|
|
||||||
|
const decision = policy.resolveLivingAssistantModeDecision({
|
||||||
|
userMessage: "по какой компании мы можем работать?",
|
||||||
|
addressLaneTriggered: false,
|
||||||
|
useMock: false,
|
||||||
|
predecomposeMode: "unsupported",
|
||||||
|
predecomposeModeConfidence: "low"
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(decision.mode).toBe("chat");
|
||||||
|
expect(decision.reason).toBe("assistant_data_scope_query_detected");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps explicit accounting question in deep mode", () => {
|
||||||
|
const policy = buildPolicy();
|
||||||
|
|
||||||
|
const decision = policy.resolveLivingAssistantModeDecision({
|
||||||
|
userMessage: "покажи документы по сверке за 2020",
|
||||||
|
addressLaneTriggered: false,
|
||||||
|
useMock: false,
|
||||||
|
predecomposeMode: "unsupported",
|
||||||
|
predecomposeModeConfidence: "low"
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(decision.mode).toBe("deep_analysis");
|
||||||
|
expect(decision.reason).toBe("strong_data_signal_detected");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("detects organization fact follow-up after prior boundary reply", () => {
|
||||||
|
const policy = buildPolicy();
|
||||||
|
|
||||||
|
const detected = policy.hasOrganizationFactFollowupSignal("давай", [
|
||||||
|
{
|
||||||
|
role: "assistant",
|
||||||
|
debug: {
|
||||||
|
living_chat_response_source: "deterministic_organization_fact_boundary",
|
||||||
|
living_chat_grounding_guard_reason: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(detected).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,147 @@
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { createAssistantRoutePolicy } from "../src/services/assistantRoutePolicy";
|
||||||
|
|
||||||
|
function toNonEmptyString(value: unknown): string | null {
|
||||||
|
if (value === null || value === undefined) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const text = String(value).trim();
|
||||||
|
return text.length > 0 ? text : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeOrganizationScopeValue(value: unknown): string | null {
|
||||||
|
const text = toNonEmptyString(value);
|
||||||
|
if (!text) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return text.replace(/^"+|"+$/g, "").replace(/^'+|'+$/g, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildPolicy(overrides: Record<string, unknown> = {}) {
|
||||||
|
return createAssistantRoutePolicy({
|
||||||
|
repairAddressMojibake: (text: string) => text,
|
||||||
|
findLastGroundedAddressAnswerDebug: () => null,
|
||||||
|
findLastOrganizationClarificationAddressDebug: () => null,
|
||||||
|
mergeKnownOrganizations: (values: unknown[]) =>
|
||||||
|
Array.from(
|
||||||
|
new Set(
|
||||||
|
(Array.isArray(values) ? values : [])
|
||||||
|
.map((item) => normalizeOrganizationScopeValue(item))
|
||||||
|
.filter((item): item is string => Boolean(item))
|
||||||
|
)
|
||||||
|
),
|
||||||
|
normalizeOrganizationScopeValue,
|
||||||
|
resolveOrganizationSelectionFromMessage: () => null,
|
||||||
|
hasAssistantDataScopeMetaQuestionSignal: (text: string) => /по какой компании|какая база|по каким конторам/i.test(text),
|
||||||
|
shouldHandleAsAssistantCapabilityMetaQuery: (text: string) => /что ты можешь|что ты умеешь/i.test(text),
|
||||||
|
hasDataRetrievalRequestSignal: () => false,
|
||||||
|
hasAggregateBusinessAnalyticsSignal: () => false,
|
||||||
|
hasStandaloneAddressTopicSignal: () => false,
|
||||||
|
hasOpenContractsAddressSignal: () => false,
|
||||||
|
detectAddressQuestionMode: () => ({ mode: "unsupported", confidence: "low" }),
|
||||||
|
resolveAddressIntent: () => ({ intent: "unknown", confidence: "low" }),
|
||||||
|
toNonEmptyString,
|
||||||
|
hasStrictDeepInvestigationCue: () => false,
|
||||||
|
hasStrongDataIntentSignal: () => false,
|
||||||
|
hasAccountingSignal: () => false,
|
||||||
|
hasDangerOrCoercionSignal: () => false,
|
||||||
|
hasAddressFollowupContextSignal: () => false,
|
||||||
|
hasShortDebtMirrorFollowupSignal: () => false,
|
||||||
|
isInventorySelectedObjectIntent: (intent: unknown) => /inventory/i.test(String(intent ?? "")),
|
||||||
|
hasShortInventoryObjectFollowupSignal: () => false,
|
||||||
|
hasHistoricalCapabilityFollowupSignal: () => false,
|
||||||
|
isGroundedInventoryContextDebug: (debug: unknown) => Boolean(debug),
|
||||||
|
hasConversationMemoryRecallFollowupSignal: () => false,
|
||||||
|
findLastAddressAssistantItem: () => null,
|
||||||
|
hasMetaAnswerFollowupSignal: () => false,
|
||||||
|
resolveAddressToolGateDecision: () => ({
|
||||||
|
runAddressLane: false,
|
||||||
|
decision: "skip_address_lane",
|
||||||
|
reason: "no_address_signal_after_l0"
|
||||||
|
}),
|
||||||
|
hasSameDateAccountFollowupSignalForPredecompose: () => false,
|
||||||
|
hasLooseAllTimeAddressLookupSignal: () => false,
|
||||||
|
hasDeepAnalysisPreferenceSignal: () => false,
|
||||||
|
hasDirectDeepAnalysisSignal: () => false,
|
||||||
|
compactWhitespace: (text: string) => String(text ?? "").replace(/\s+/g, " ").trim(),
|
||||||
|
hasDeepSessionContinuationSignal: () => false,
|
||||||
|
resolveLivingAssistantModeDecision: (input: { addressLaneTriggered?: boolean }) =>
|
||||||
|
input.addressLaneTriggered
|
||||||
|
? { mode: "address_data", reason: "address_lane_triggered" }
|
||||||
|
: { mode: "chat", reason: "living_chat_signal_detected" },
|
||||||
|
...overrides
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("assistantRoutePolicy", () => {
|
||||||
|
it("routes data-scope meta question to chat contract", () => {
|
||||||
|
const policy = buildPolicy();
|
||||||
|
|
||||||
|
const decision = policy.resolveAssistantOrchestrationDecision({
|
||||||
|
rawUserMessage: "по какой компании мы можем работать?",
|
||||||
|
effectiveAddressUserMessage: "по какой компании мы можем работать?",
|
||||||
|
followupContext: null,
|
||||||
|
llmPreDecomposeMeta: null,
|
||||||
|
useMock: false
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(decision.runAddressLane).toBe(false);
|
||||||
|
expect(decision.toolGateReason).toBe("assistant_data_scope_query_detected");
|
||||||
|
expect(decision.livingMode).toBe("chat");
|
||||||
|
expect(decision.orchestrationContract?.hard_meta_mode).toBe("data_scope");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps supported address intent in address lane", () => {
|
||||||
|
const policy = buildPolicy({
|
||||||
|
detectAddressQuestionMode: () => ({ mode: "address_query", confidence: "high" }),
|
||||||
|
resolveAddressIntent: () => ({ intent: "inventory_on_hand_as_of_date", confidence: "high" }),
|
||||||
|
resolveAddressToolGateDecision: () => ({
|
||||||
|
runAddressLane: true,
|
||||||
|
decision: "run_address_lane",
|
||||||
|
reason: "address_mode_classifier_detected"
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const decision = policy.resolveAssistantOrchestrationDecision({
|
||||||
|
rawUserMessage: "какие товары сейчас лежат на складе",
|
||||||
|
effectiveAddressUserMessage: "какие товары сейчас лежат на складе",
|
||||||
|
followupContext: null,
|
||||||
|
llmPreDecomposeMeta: null,
|
||||||
|
useMock: false
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(decision.runAddressLane).toBe(true);
|
||||||
|
expect(decision.toolGateReason).toBe("address_mode_classifier_detected");
|
||||||
|
expect(decision.livingMode).toBe("address_data");
|
||||||
|
expect(decision.orchestrationContract?.semantic_route_arbitration?.supported_address_intent_detected).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("routes memory recap follow-up over grounded answer to chat", () => {
|
||||||
|
const policy = buildPolicy({
|
||||||
|
hasConversationMemoryRecallFollowupSignal: () => true,
|
||||||
|
findLastGroundedAddressAnswerDebug: () => ({ execution_lane: "address_query" })
|
||||||
|
});
|
||||||
|
|
||||||
|
const decision = policy.resolveAssistantOrchestrationDecision({
|
||||||
|
rawUserMessage: "а ты помнишь что мы обсуждали?",
|
||||||
|
effectiveAddressUserMessage: "а ты помнишь что мы обсуждали?",
|
||||||
|
followupContext: null,
|
||||||
|
llmPreDecomposeMeta: {
|
||||||
|
applied: false,
|
||||||
|
reason: "normalized_fragment_rejected_semantic_guard",
|
||||||
|
predecomposeContract: {
|
||||||
|
mode: "unsupported",
|
||||||
|
mode_confidence: "low",
|
||||||
|
intent: "unknown",
|
||||||
|
intent_confidence: "low"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
useMock: false
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(decision.runAddressLane).toBe(false);
|
||||||
|
expect(decision.toolGateReason).toBe("memory_recap_followup_detected");
|
||||||
|
expect(decision.livingMode).toBe("chat");
|
||||||
|
expect(decision.livingReason).toBe("memory_recap_followup_detected");
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue