АДРЕСНЫЙ РЕЖИМ - ADDRESS: стабилизация интерпретации сленга и mixed RU/EN, расширение intent/mode-роутинга, регресс-тесты и live stress 102/102
This commit is contained in:
@@ -13,6 +13,8 @@ import * as companyAnchorResolver_1 from "./companyAnchorResolver";
|
||||
import * as assistantRuntimeGuards_1 from "./assistantRuntimeGuards";
|
||||
import * as assistantClaimBoundEvidence_1 from "./assistantClaimBoundEvidence";
|
||||
import * as addressQueryService_1 from "./addressQueryService";
|
||||
import * as addressQueryClassifier_1 from "./addressQueryClassifier";
|
||||
import * as addressIntentResolver_1 from "./addressIntentResolver";
|
||||
import iconv from "iconv-lite";
|
||||
function retrievalSummaryForRoute(route) {
|
||||
if (route === "store_canonical")
|
||||
@@ -1924,9 +1926,9 @@ const ADDRESS_MONTH_ALIAS_MAP = {
|
||||
december: "12",
|
||||
dec: "12"
|
||||
};
|
||||
const ADDRESS_DOCS_SIGNAL_PATTERN = /(?:док|доки|документ|документы|документов|docs?|documents?|bank|выписк|плат[её]ж|оплат|поступлен|списан|операц)/i;
|
||||
const ADDRESS_BANK_SIGNAL_PATTERN = /(?:bank|банк|банков|выписк|плат[её]ж|оплат|поступлен|списан|операц|расчетн)/i;
|
||||
const ADDRESS_CONTRACT_SIGNAL_PATTERN = /(?:договор(?:а|у|ом|е)?|\bcontract\b)/iu;
|
||||
const ADDRESS_DOCS_SIGNAL_PATTERN = /(?:док|доки|документ|документы|документов|docs?|documents?|bank|выписк|плат[её]ж|оплат|поступлен|списан|операц|опер|transaction)/i;
|
||||
const ADDRESS_BANK_SIGNAL_PATTERN = /(?:bank|банк|банков|выписк|плат[её]ж|оплат|поступлен|списан|операц|опер|расчетн|транзак)/i;
|
||||
const ADDRESS_CONTRACT_SIGNAL_PATTERN = /(?:договор(?:а|у|ом|е)?|(?:^|[^\p{L}\p{N}_])(?:дог\.?|[dд][oо][gг]\.?|dog\.?)(?=$|[^\p{L}\p{N}_])|contract|dogovor)/iu;
|
||||
const ADDRESS_BALANCE_SIGNAL_PATTERN = /(?:остат|сальдо|баланс|взаиморасч|долг|saldo|balance)/i;
|
||||
const ADDRESS_ALL_TIME_PATTERN = /(?:за\s+вс[её]\s+время|за\s+весь\s+период|за\s+всю\s+истори(?:ю|и)|for\s+all\s+time|all\s+time|entire\s+period|full\s+history)/iu;
|
||||
function normalizeAddressMonthAliasToken(token) {
|
||||
@@ -1964,6 +1966,10 @@ function sanitizeAddressMessageForFallback(userMessage) {
|
||||
.replace(/\bpokezh\b/giu, "покажи")
|
||||
.replace(/\bpokazh(?:i)?\b/giu, "покажи")
|
||||
.replace(/\bpokaji\b/giu, "покажи")
|
||||
.replace(/\bop(?:er|ers?)\b/giu, "операции")
|
||||
.replace(/(^|[^\p{L}\p{N}_])опер(?:аци[яиюе]|ы|)?(?=$|[^\p{L}\p{N}_])/giu, "$1операции")
|
||||
.replace(/(^|[^\p{L}\p{N}_])дог\.?(?=$|[^\p{L}\p{N}_])/giu, "$1договор")
|
||||
.replace(/(^|[^\p{L}\p{N}_])dog\.?(?=$|[^\p{L}\p{N}_])/giu, "$1contract")
|
||||
.replace(/\bdok(?:i|y)?\b/giu, "доки")
|
||||
.replace(/\bdocuments?\b/giu, "документы")
|
||||
.replace(/\bdocs?\b/giu, "документы")
|
||||
@@ -2070,7 +2076,7 @@ function pickAddressFallbackCounterpartyToken(text) {
|
||||
!/^\d{2}(?:\.\d{1,2})?$/.test(normalizedByToken) &&
|
||||
!/^(?:19|20)\d{2}$/.test(normalizedByToken) &&
|
||||
!/^(?:янв|фев|мар|апр|май|июн|июл|авг|сен|сент|окт|ноя|дек|january|february|march|april|may|june|july|august|september|october|november|december)/i.test(normalizedByToken) &&
|
||||
!/^(?:договор|договора|договору|договором|договоре|contract)$/.test(normalizedByToken)) {
|
||||
!/^(?:договор|договора|договору|договором|договоре|contract|dogovor|dog|дог|d[oо]g|д[oо]г)$/.test(normalizedByToken)) {
|
||||
return byToken;
|
||||
}
|
||||
}
|
||||
@@ -2086,7 +2092,7 @@ function pickAddressFallbackCounterpartyToken(text) {
|
||||
if (/^(?:янв|фев|мар|апр|май|июн|июл|авг|сен|сент|окт|ноя|дек|january|february|march|april|may|june|july|august|september|october|november|december)/i.test(normalized)) {
|
||||
continue;
|
||||
}
|
||||
if (/^(?:договор|договора|договору|договором|договоре|contract)$/.test(normalized)) {
|
||||
if (/^(?:договор|договора|договору|договором|договоре|contract|dogovor|dog|дог|d[oо]g|д[oо]г)$/.test(normalized)) {
|
||||
continue;
|
||||
}
|
||||
return token;
|
||||
@@ -2096,8 +2102,8 @@ function pickAddressFallbackCounterpartyToken(text) {
|
||||
function extractAddressFallbackContractToken(text) {
|
||||
const source = String(text ?? "");
|
||||
const patterns = [
|
||||
/(?:договор(?:а|у|ом|е)?|\bcontract\b)\s*(?:№|#|n|no\.?)?\s*([a-zа-я0-9][a-zа-я0-9/_-]{1,})/iu,
|
||||
/(?:№|#|n|no\.?)\s*([a-zа-я0-9][a-zа-я0-9/_-]{1,})\s*(?:договор(?:а|у|ом|е)?|\bcontract\b)/iu
|
||||
/(?:договор(?:а|у|ом|е)?|дог\.?|[dд][oо][gг]\.?|contract|dogovor|dog\.?)\s*(?:№|#|n|no\.?)?\s*([a-zа-я0-9][a-zа-я0-9/_-]{1,})/iu,
|
||||
/(?:№|#|n|no\.?)\s*([a-zа-я0-9][a-zа-я0-9/_-]{1,})\s*(?:договор(?:а|у|ом|е)?|дог\.?|[dд][oо][gг]\.?|contract|dogovor|dog\.?)/iu
|
||||
];
|
||||
for (const pattern of patterns) {
|
||||
const match = pattern.exec(source);
|
||||
@@ -2116,7 +2122,7 @@ function extractAddressFallbackContractToken(text) {
|
||||
}
|
||||
return candidate;
|
||||
}
|
||||
if (ADDRESS_CONTRACT_SIGNAL_PATTERN.test(source)) {
|
||||
if (ADDRESS_CONTRACT_SIGNAL_PATTERN.test(source) || /(?:^|[^\p{L}\p{N}_])(?:[dд][oо][gг]|dogovor)(?=$|[^\p{L}\p{N}_])/iu.test(source)) {
|
||||
const generic = source.match(/\b([a-zа-я0-9]{1,10}[/-][a-zа-я0-9]{1,10}(?:[/-][a-zа-я0-9]{1,10})?)\b/iu);
|
||||
if (generic && generic[1]) {
|
||||
return generic[1];
|
||||
@@ -2157,9 +2163,36 @@ function resolveAddressDeterministicFallback(userMessage, sanitizedUserMessage)
|
||||
};
|
||||
}
|
||||
}
|
||||
if (!docsSignal && !contractSignal && !balanceSignal) {
|
||||
const counterparty = pickAddressFallbackCounterpartyToken(source);
|
||||
const genericLookupSignal = /(?:\bесть\b|\bпокажи\b|\bвыведи\b|\bч[её]\b|\bчто\b)/iu.test(source);
|
||||
if (counterparty && (allTime || monthYear || year) && genericLookupSignal) {
|
||||
let periodClause = "";
|
||||
let rule = "documents_counterparty_rewrite_from_generic_lookup";
|
||||
if (allTime) {
|
||||
periodClause = " за все время";
|
||||
rule = "documents_counterparty_all_time_rewrite_from_generic_lookup";
|
||||
}
|
||||
else if (monthYear) {
|
||||
periodClause = ` за ${monthYear}`;
|
||||
rule = "documents_counterparty_month_rewrite_from_generic_lookup";
|
||||
}
|
||||
else if (year) {
|
||||
periodClause = ` за ${year} год`;
|
||||
rule = "documents_counterparty_year_rewrite_from_generic_lookup";
|
||||
}
|
||||
const candidate = compactWhitespace(`документы по контрагенту ${counterparty}${periodClause}`);
|
||||
if (candidate && candidate !== sourceRaw.toLowerCase()) {
|
||||
return {
|
||||
candidate,
|
||||
rule
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
if (docsSignal) {
|
||||
if (contractSignal) {
|
||||
const contract = extractAddressFallbackContractToken(sourceRaw || source);
|
||||
const contract = extractAddressFallbackContractToken(sourceRaw || source);
|
||||
if (contractSignal || contract) {
|
||||
if (contract) {
|
||||
let periodClause = "";
|
||||
let rule = bankSignal ? "bank_operations_contract_rewrite" : "documents_contract_rewrite";
|
||||
@@ -2642,6 +2675,32 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage
|
||||
reason: normalized?.ok ? "no_usable_fragment" : "normalize_failed"
|
||||
};
|
||||
}
|
||||
const repairedSourceMessage = repairAddressMojibake(userMessage);
|
||||
const sourceIntentResolution = (0, addressIntentResolver_1.resolveAddressIntent)(repairedSourceMessage || userMessage);
|
||||
const candidateIntentResolution = (0, addressIntentResolver_1.resolveAddressIntent)(candidate);
|
||||
const sourceIntentKnown = sourceIntentResolution.intent !== "unknown";
|
||||
const candidateIntentKnown = candidateIntentResolution.intent !== "unknown";
|
||||
const intentConflict = sourceIntentKnown &&
|
||||
candidateIntentKnown &&
|
||||
sourceIntentResolution.intent !== candidateIntentResolution.intent;
|
||||
const intentDroppedByCandidate = sourceIntentKnown && !candidateIntentKnown;
|
||||
const rejectCandidateForIntentSafety = intentDroppedByCandidate ||
|
||||
(intentConflict &&
|
||||
(sourceIntentResolution.confidence === "high" || candidateIntentResolution.confidence !== "high"));
|
||||
if (rejectCandidateForIntentSafety) {
|
||||
return {
|
||||
...baseMeta,
|
||||
attempted: true,
|
||||
applied: false,
|
||||
traceId: normalized?.trace_id ?? null,
|
||||
effectiveMessage: userMessage,
|
||||
reason: intentDroppedByCandidate
|
||||
? "normalized_fragment_rejected_intent_drop"
|
||||
: "normalized_fragment_rejected_intent_conflict",
|
||||
fallbackRuleHit: null,
|
||||
sanitizedUserMessage
|
||||
};
|
||||
}
|
||||
const sourceCompact = compactWhitespace(String(userMessage ?? "").toLowerCase());
|
||||
const candidateCompact = compactWhitespace(candidate.toLowerCase());
|
||||
const applied = sourceCompact !== candidateCompact;
|
||||
@@ -2692,12 +2751,19 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage
|
||||
}
|
||||
}
|
||||
function resolveAddressToolGateDecision(addressInputMessage, followupContext) {
|
||||
const hasMessageSignal = isAddressLlmPreDecomposeCandidate(addressInputMessage) || hasAccountingSignal(addressInputMessage);
|
||||
const repairedInputMessage = repairAddressMojibake(String(addressInputMessage ?? ""));
|
||||
const modeDetection = (0, addressQueryClassifier_1.detectAddressQuestionMode)(repairedInputMessage || addressInputMessage);
|
||||
const hasClassifierSignal = modeDetection.mode === "address_query";
|
||||
const hasMessageSignal = hasClassifierSignal ||
|
||||
isAddressLlmPreDecomposeCandidate(addressInputMessage) ||
|
||||
isAddressLlmPreDecomposeCandidate(repairedInputMessage) ||
|
||||
hasAccountingSignal(addressInputMessage) ||
|
||||
hasAccountingSignal(repairedInputMessage);
|
||||
if (hasMessageSignal) {
|
||||
return {
|
||||
runAddressLane: true,
|
||||
decision: "run_address_lane",
|
||||
reason: "address_signal_detected"
|
||||
reason: hasClassifierSignal ? "address_mode_classifier_detected" : "address_signal_detected"
|
||||
};
|
||||
}
|
||||
if (followupContext) {
|
||||
|
||||
Reference in New Issue
Block a user