АДРЕСНЫЙ РЕЖИМ - стабилизация LLM fallback якорей и периода
This commit is contained in:
@@ -1828,9 +1828,24 @@ const ADDRESS_PREDECOMPOSE_NOISE_TOKENS = new Set([
|
||||
"show",
|
||||
"list",
|
||||
"выведи",
|
||||
"что",
|
||||
"чо",
|
||||
"которые",
|
||||
"какие",
|
||||
"какой",
|
||||
"были",
|
||||
"был",
|
||||
"была",
|
||||
"было",
|
||||
"ли",
|
||||
"списания",
|
||||
"списание",
|
||||
"поступления",
|
||||
"поступление",
|
||||
"расчетного",
|
||||
"расчётного",
|
||||
"счета",
|
||||
"счёта",
|
||||
"есть",
|
||||
"est",
|
||||
"kakie",
|
||||
@@ -1910,6 +1925,8 @@ const ADDRESS_MONTH_ALIAS_MAP = {
|
||||
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_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) {
|
||||
@@ -1975,14 +1992,21 @@ function extractAddressFallbackYear(text) {
|
||||
return fullYearMatch[1];
|
||||
}
|
||||
const shortYearMatch = source.match(/(?:^|[^0-9])(\d{2})\s*(?:г(?:од|ода)?|г|год|year|god)(?=$|[^a-zа-яё0-9])/iu);
|
||||
if (!shortYearMatch) {
|
||||
if (shortYearMatch) {
|
||||
const shortYear = Number(shortYearMatch[1]);
|
||||
if (Number.isFinite(shortYear) && shortYear >= 0 && shortYear <= 99) {
|
||||
return String(2000 + shortYear);
|
||||
}
|
||||
}
|
||||
const shortOrdinalYearMatch = source.match(/(?:^|[^0-9])(\d{2})\s*(?:[-\s]?(?:й|ый|ой|th))(?=$|[^a-zа-яё0-9])/iu);
|
||||
if (!shortOrdinalYearMatch) {
|
||||
return null;
|
||||
}
|
||||
const shortYear = Number(shortYearMatch[1]);
|
||||
if (!Number.isFinite(shortYear) || shortYear < 0 || shortYear > 99) {
|
||||
const shortOrdinalYear = Number(shortOrdinalYearMatch[1]);
|
||||
if (!Number.isFinite(shortOrdinalYear) || shortOrdinalYear < 0 || shortOrdinalYear > 99) {
|
||||
return null;
|
||||
}
|
||||
return String(2000 + shortYear);
|
||||
return String(2000 + shortOrdinalYear);
|
||||
}
|
||||
function extractAddressFallbackMonthYear(text) {
|
||||
const source = String(text ?? "");
|
||||
@@ -1998,14 +2022,14 @@ function extractAddressFallbackMonthYear(text) {
|
||||
const year = numericMonthYear[2];
|
||||
return `${year}-${month}`;
|
||||
}
|
||||
const namedMonthYear = source.match(/\b([a-zа-яё]+)\s+(20\d{2})\b/iu);
|
||||
const namedMonthYear = source.match(/(?:^|[^a-zа-яё0-9])([a-zа-яё]+)\s+(20\d{2})(?=$|[^a-zа-яё0-9])/iu);
|
||||
if (namedMonthYear) {
|
||||
const month = normalizeAddressMonthAliasToken(namedMonthYear[1]);
|
||||
if (month) {
|
||||
return `${namedMonthYear[2]}-${month}`;
|
||||
}
|
||||
}
|
||||
const yearNamedMonth = source.match(/\b(20\d{2})\s+([a-zа-яё]+)\b/iu);
|
||||
const yearNamedMonth = source.match(/(?:^|[^a-zа-яё0-9])(20\d{2})\s+([a-zа-яё]+)(?=$|[^a-zа-яё0-9])/iu);
|
||||
if (yearNamedMonth) {
|
||||
const month = normalizeAddressMonthAliasToken(yearNamedMonth[2]);
|
||||
if (month) {
|
||||
@@ -2014,7 +2038,42 @@ function extractAddressFallbackMonthYear(text) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function extractAddressFallbackAccountToken(text) {
|
||||
const source = String(text ?? "");
|
||||
const explicitMatch = source.match(/(?:сч[её]т(?:а|у|ом|е)?|account)\D{0,12}(\d{2}(?:[.,]\d{1,2})?)/iu);
|
||||
if (explicitMatch && explicitMatch[1]) {
|
||||
return String(explicitMatch[1]).replace(",", ".");
|
||||
}
|
||||
const tokenPattern = /\b(\d{2}(?:[.,]\d{1,2})?)\b/giu;
|
||||
let match = tokenPattern.exec(source);
|
||||
while (match) {
|
||||
const raw = String(match[1] ?? "");
|
||||
const start = match.index;
|
||||
const end = start + raw.length;
|
||||
const prev = start > 0 ? source[start - 1] : " ";
|
||||
const next = end < source.length ? source[end] : " ";
|
||||
if (!/[./-]/.test(prev) && !/[./-]/.test(next) && !/\d/.test(prev) && !/\d/.test(next)) {
|
||||
return raw.replace(",", ".");
|
||||
}
|
||||
match = tokenPattern.exec(source);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function pickAddressFallbackCounterpartyToken(text) {
|
||||
const source = String(text ?? "");
|
||||
const byAnchor = source.match(/(?:^|[\s,.;:!?()\-])(?:по|от)\s+([a-zа-яё][a-zа-яё0-9._-]{1,})(?=$|[\s,.;:!?()\-])/iu);
|
||||
if (byAnchor && byAnchor[1]) {
|
||||
const byToken = String(byAnchor[1]).trim();
|
||||
const normalizedByToken = byToken.toLowerCase();
|
||||
if (byToken &&
|
||||
!ADDRESS_PREDECOMPOSE_NOISE_TOKENS.has(normalizedByToken) &&
|
||||
!/^\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)) {
|
||||
return byToken;
|
||||
}
|
||||
}
|
||||
const candidates = extractAddressAnchorTokens(text);
|
||||
for (const token of candidates) {
|
||||
const normalized = String(token ?? "").toLowerCase();
|
||||
@@ -2027,10 +2086,44 @@ function pickAddressFallbackCounterpartyToken(text) {
|
||||
if (/^(?:янв|фев|мар|апр|май|июн|июл|авг|сен|сент|окт|ноя|дек|january|february|march|april|may|june|july|august|september|october|november|december)/i.test(normalized)) {
|
||||
continue;
|
||||
}
|
||||
if (/^(?:договор|договора|договору|договором|договоре|contract)$/.test(normalized)) {
|
||||
continue;
|
||||
}
|
||||
return token;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
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
|
||||
];
|
||||
for (const pattern of patterns) {
|
||||
const match = pattern.exec(source);
|
||||
if (!match || !match[1]) {
|
||||
continue;
|
||||
}
|
||||
const candidate = String(match[1]).replace(/^[^a-zа-я0-9]+|[^a-zа-я0-9/_-]+$/giu, "");
|
||||
if (!candidate || candidate.length < 2) {
|
||||
continue;
|
||||
}
|
||||
if (/^(?:19|20)\d{2}$/.test(candidate)) {
|
||||
continue;
|
||||
}
|
||||
if (/^(?:19|20)\d{2}[./-](?:0?[1-9]|1[0-2])(?:[./-](?:0?[1-9]|[12]\d|3[01]))?$/.test(candidate)) {
|
||||
continue;
|
||||
}
|
||||
return candidate;
|
||||
}
|
||||
if (ADDRESS_CONTRACT_SIGNAL_PATTERN.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];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function resolveAddressDeterministicFallback(userMessage, sanitizedUserMessage) {
|
||||
const sourceRaw = compactWhitespace(repairAddressMojibake(String(userMessage ?? "")));
|
||||
const source = compactWhitespace(String(sanitizedUserMessage ?? sourceRaw).toLowerCase());
|
||||
@@ -2040,9 +2133,10 @@ function resolveAddressDeterministicFallback(userMessage, sanitizedUserMessage)
|
||||
const monthYear = extractAddressFallbackMonthYear(source);
|
||||
const year = extractAddressFallbackYear(source);
|
||||
const allTime = ADDRESS_ALL_TIME_PATTERN.test(source);
|
||||
const accountMatch = source.match(/\b(\d{2}(?:[.,]\d{1,2})?)\b/);
|
||||
const account = accountMatch ? String(accountMatch[1]).replace(",", ".") : null;
|
||||
const account = extractAddressFallbackAccountToken(source);
|
||||
const docsSignal = ADDRESS_DOCS_SIGNAL_PATTERN.test(source);
|
||||
const bankSignal = ADDRESS_BANK_SIGNAL_PATTERN.test(source);
|
||||
const contractSignal = ADDRESS_CONTRACT_SIGNAL_PATTERN.test(source);
|
||||
const balanceSignal = ADDRESS_BALANCE_SIGNAL_PATTERN.test(source);
|
||||
if (balanceSignal && account) {
|
||||
let periodClause = "";
|
||||
@@ -2064,28 +2158,59 @@ function resolveAddressDeterministicFallback(userMessage, sanitizedUserMessage)
|
||||
}
|
||||
}
|
||||
if (docsSignal) {
|
||||
const counterparty = pickAddressFallbackCounterpartyToken(source);
|
||||
if (counterparty) {
|
||||
let periodClause = "";
|
||||
let rule = "documents_counterparty_rewrite";
|
||||
if (allTime) {
|
||||
periodClause = " за все время";
|
||||
rule = "documents_counterparty_all_time_rewrite";
|
||||
if (contractSignal) {
|
||||
const contract = extractAddressFallbackContractToken(sourceRaw || source);
|
||||
if (contract) {
|
||||
let periodClause = "";
|
||||
let rule = bankSignal ? "bank_operations_contract_rewrite" : "documents_contract_rewrite";
|
||||
if (allTime) {
|
||||
periodClause = " за все время";
|
||||
rule = bankSignal ? "bank_operations_contract_all_time_rewrite" : "documents_contract_all_time_rewrite";
|
||||
}
|
||||
else if (monthYear) {
|
||||
periodClause = ` за ${monthYear}`;
|
||||
rule = bankSignal ? "bank_operations_contract_month_rewrite" : "documents_contract_month_rewrite";
|
||||
}
|
||||
else if (year) {
|
||||
periodClause = ` за ${year} год`;
|
||||
rule = bankSignal ? "bank_operations_contract_year_rewrite" : "documents_contract_year_rewrite";
|
||||
}
|
||||
const subject = bankSignal ? "банковские операции" : "документы";
|
||||
const candidate = compactWhitespace(`${subject} по договору ${contract}${periodClause}`);
|
||||
if (candidate && candidate !== sourceRaw.toLowerCase()) {
|
||||
return {
|
||||
candidate,
|
||||
rule
|
||||
};
|
||||
}
|
||||
}
|
||||
else if (monthYear) {
|
||||
periodClause = ` за ${monthYear}`;
|
||||
rule = "documents_counterparty_month_rewrite";
|
||||
}
|
||||
else if (year) {
|
||||
periodClause = ` за ${year} год`;
|
||||
rule = "documents_counterparty_year_rewrite";
|
||||
}
|
||||
const candidate = compactWhitespace(`документы по контрагенту ${counterparty}${periodClause}`);
|
||||
if (candidate && candidate !== sourceRaw.toLowerCase()) {
|
||||
return {
|
||||
candidate,
|
||||
rule
|
||||
};
|
||||
}
|
||||
else {
|
||||
const counterparty = pickAddressFallbackCounterpartyToken(source);
|
||||
if (counterparty) {
|
||||
let periodClause = "";
|
||||
const subject = bankSignal ? "банковские операции" : "документы";
|
||||
const rulePrefix = bankSignal ? "bank_operations_counterparty" : "documents_counterparty";
|
||||
let rule = `${rulePrefix}_rewrite`;
|
||||
if (allTime) {
|
||||
periodClause = " за все время";
|
||||
rule = `${rulePrefix}_all_time_rewrite`;
|
||||
}
|
||||
else if (monthYear) {
|
||||
periodClause = ` за ${monthYear}`;
|
||||
rule = `${rulePrefix}_month_rewrite`;
|
||||
}
|
||||
else if (year) {
|
||||
periodClause = ` за ${year} год`;
|
||||
rule = `${rulePrefix}_year_rewrite`;
|
||||
}
|
||||
const candidate = compactWhitespace(`${subject} по контрагенту ${counterparty}${periodClause}`);
|
||||
if (candidate && candidate !== sourceRaw.toLowerCase()) {
|
||||
return {
|
||||
candidate,
|
||||
rule
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2620,12 +2745,13 @@ export class AssistantService {
|
||||
};
|
||||
this.sessions.appendItem(sessionId, userItem);
|
||||
const finalizeAddressLaneResponse = (addressLane, effectiveAddressUserMessage, carryoverMeta = null, llmPreDecomposeMeta = null) => {
|
||||
const safeAddressReply = String((0, answerComposer_1.sanitizeAssistantReplyForUserFacing)(addressLane.reply_text) ?? "").trim() || String(addressLane.reply_text ?? "");
|
||||
const debug = buildAddressDebugPayload(addressLane.debug, llmPreDecomposeMeta);
|
||||
const assistantItem = {
|
||||
message_id: `msg-${(0, nanoid_1.nanoid)(10)}`,
|
||||
session_id: sessionId,
|
||||
role: "assistant",
|
||||
text: addressLane.reply_text,
|
||||
text: safeAddressReply,
|
||||
reply_type: addressLane.reply_type,
|
||||
created_at: new Date().toISOString(),
|
||||
trace_id: debug.trace_id,
|
||||
|
||||
Reference in New Issue
Block a user