ГЛОБАЛЬНЫЙ РЕФАКТОРИНГ АРХИТЕКТУРЫ - Рефакторинг этапов Stage 3.6 Усилина оркестрация и детект data-scope для живого сленга. Убрана шаблонность soft-refusal в limited-ответах. Подрезана словарнаю жирность в фильтр-экстракторе
This commit is contained in:
@@ -998,6 +998,27 @@ function countTokens(text) {
|
||||
function hasPeriodLiteral(text) {
|
||||
return /\b(20\d{2}(?:[-/.](?:0[1-9]|1[0-2]))?)\b/.test(text);
|
||||
}
|
||||
function hasStandaloneAddressTopicSignal(text) {
|
||||
const normalized = compactWhitespace(repairAddressMojibake(String(text ?? "")).toLowerCase());
|
||||
if (!normalized) {
|
||||
return false;
|
||||
}
|
||||
if (hasFollowupMarker(normalized) || hasReferentialPointer(normalized)) {
|
||||
return false;
|
||||
}
|
||||
const hasRequestCue = /(?:^|[\s,.;:!?()\-])(?:покажи|показать|выведи|дай|найди|список|какие|какой|какая|каких|сколько|где|show|list|find|which|what)/iu.test(normalized);
|
||||
if (!hasRequestCue) {
|
||||
return false;
|
||||
}
|
||||
const hasBusinessObject = /(?:договор|контракт|контрагент|поставщик|покупател|клиент|документ|платеж|оплат|сальдо|остатк|сч[её]т|оборот|выруч|доход|прибыл|ндс|дебитор|кредитор|организац|компан|контор|contract|counterparty|supplier|customer|document|payment|turnover|revenue|profit|balance|account|vat)/iu.test(normalized);
|
||||
if (!hasBusinessObject) {
|
||||
return false;
|
||||
}
|
||||
const hasStructuredAnchor = hasPeriodLiteral(normalized) ||
|
||||
/\b\d{2}(?:[.,]\d{1,2})?\b/.test(normalized) ||
|
||||
/(?:альтернатива|лайсвуд|райм|ооо\s+[a-zа-яё])/iu.test(normalized);
|
||||
return hasStructuredAnchor || countTokens(normalized) >= 6;
|
||||
}
|
||||
function extractNormalizedPeriodLiteral(text) {
|
||||
const monthly = text.match(/\b(20\d{2})[-/.](0[1-9]|1[0-2])\b/);
|
||||
if (monthly) {
|
||||
@@ -1304,6 +1325,35 @@ function buildAddressCoverageReport() {
|
||||
out_of_scope_requirements: []
|
||||
};
|
||||
}
|
||||
function buildAssistantBackendErrorDebugPayload(errorMessage) {
|
||||
return {
|
||||
trace_id: `chat-${(0, nanoid_1.nanoid)(10)}`,
|
||||
prompt_version: "assistant_backend_error_fallback_v1",
|
||||
schema_version: "assistant_backend_error_fallback_v1",
|
||||
fallback_type: "unknown",
|
||||
route_summary: null,
|
||||
fragments: [],
|
||||
requirements_extracted: [],
|
||||
coverage_report: buildAddressCoverageReport(),
|
||||
routes: [],
|
||||
retrieval_status: [],
|
||||
retrieval_results: [],
|
||||
answer_grounding_check: {
|
||||
status: "no_grounded_answer",
|
||||
route_subject_match: true,
|
||||
missing_requirements: [],
|
||||
reasons: [
|
||||
`backend_error:${String(errorMessage ?? "unknown_error").slice(0, 280)}`
|
||||
],
|
||||
why_included_summary: [],
|
||||
selection_reason_summary: []
|
||||
},
|
||||
dropped_intent_segments: []
|
||||
};
|
||||
}
|
||||
function buildAssistantBackendErrorReply() {
|
||||
return "Сейчас не удалось завершить разбор из-за внутренней ошибки контуров LLM. Могу продолжить в адресном режиме: проверить документы, договоры и операции по нужному периоду или контрагенту.";
|
||||
}
|
||||
function buildAddressDebugPayload(addressDebug, llmPreDecomposeMeta = null) {
|
||||
const grounded = addressDebug.response_type === "LIMITED_WITH_REASON" ? "partial" : "grounded";
|
||||
const llmMeta = llmPreDecomposeMeta && typeof llmPreDecomposeMeta === "object" ? llmPreDecomposeMeta : null;
|
||||
@@ -2170,6 +2220,9 @@ function hasAddressFollowupContextSignal(userMessage) {
|
||||
if (!text) {
|
||||
return false;
|
||||
}
|
||||
if (hasStandaloneAddressTopicSignal(text)) {
|
||||
return false;
|
||||
}
|
||||
if (shouldHandleAsAssistantCapabilityMetaQuery(text)) {
|
||||
return false;
|
||||
}
|
||||
@@ -2228,6 +2281,11 @@ function resolveAddressFollowupCarryoverContext(userMessage, items, alternateMes
|
||||
const hasAlternateFollowupSignal = toNonEmptyString(alternateMessage)
|
||||
? hasAddressFollowupContextSignal(alternateMessage)
|
||||
: false;
|
||||
const hasStandaloneAddressTopic = hasStandaloneAddressTopicSignal(userMessage) ||
|
||||
(toNonEmptyString(alternateMessage) ? hasStandaloneAddressTopicSignal(alternateMessage) : false);
|
||||
if (hasStandaloneAddressTopic && !hasImplicitContinuationSignal) {
|
||||
return null;
|
||||
}
|
||||
if (!hasPrimaryFollowupSignal && !hasAlternateFollowupSignal && !hasImplicitContinuationSignal) {
|
||||
return null;
|
||||
}
|
||||
@@ -2859,6 +2917,29 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage
|
||||
const sourceAnchorQuality = evaluateAddressAnchorQuality(repairedSourceMessage || userMessage);
|
||||
const candidateAnchorQuality = evaluateAddressAnchorQuality(candidate);
|
||||
const sameIntentForAnchorSafety = sourceAnchorQuality.intent !== "unknown" && sourceAnchorQuality.intent === candidateAnchorQuality.intent;
|
||||
const counterpartyAnchorSubstitutedByCandidate = sameIntentForAnchorSafety &&
|
||||
sourceAnchorQuality.anchorType === "counterparty" &&
|
||||
sourceAnchorQuality.quality >= 2 &&
|
||||
Boolean(sourceAnchorQuality.anchorValue) &&
|
||||
((candidateAnchorQuality.anchorType === "counterparty" &&
|
||||
candidateAnchorQuality.quality >= 2 &&
|
||||
Boolean(candidateAnchorQuality.anchorValue) &&
|
||||
hasCounterpartyAnchorSubstitution(sourceAnchorQuality.anchorValue ?? "", candidateAnchorQuality.anchorValue ?? "")) ||
|
||||
(candidateAnchorQuality.quality < sourceAnchorQuality.quality &&
|
||||
hasCounterpartyAnchorSubstitution(sourceAnchorQuality.anchorValue ?? "", candidate)));
|
||||
if (counterpartyAnchorSubstitutedByCandidate) {
|
||||
return attachAddressPredecomposeContract({
|
||||
...baseMeta,
|
||||
attempted: true,
|
||||
applied: false,
|
||||
traceId: normalized?.trace_id ?? null,
|
||||
llmCanonicalCandidateDetected: true,
|
||||
effectiveMessage: userMessage,
|
||||
reason: "normalized_fragment_rejected_anchor_substitution",
|
||||
fallbackRuleHit: null,
|
||||
sanitizedUserMessage
|
||||
}, userMessage);
|
||||
}
|
||||
const anchorDegradedByCandidate = sameIntentForAnchorSafety &&
|
||||
sourceAnchorQuality.anchorType &&
|
||||
sourceAnchorQuality.quality >= 2 &&
|
||||
@@ -2876,27 +2957,6 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage
|
||||
sanitizedUserMessage
|
||||
}, userMessage);
|
||||
}
|
||||
const counterpartyAnchorSubstitutedByCandidate = sameIntentForAnchorSafety &&
|
||||
sourceAnchorQuality.anchorType === "counterparty" &&
|
||||
candidateAnchorQuality.anchorType === "counterparty" &&
|
||||
sourceAnchorQuality.quality >= 2 &&
|
||||
candidateAnchorQuality.quality >= 2 &&
|
||||
Boolean(sourceAnchorQuality.anchorValue) &&
|
||||
Boolean(candidateAnchorQuality.anchorValue) &&
|
||||
hasCounterpartyAnchorSubstitution(sourceAnchorQuality.anchorValue ?? "", candidateAnchorQuality.anchorValue ?? "");
|
||||
if (counterpartyAnchorSubstitutedByCandidate) {
|
||||
return attachAddressPredecomposeContract({
|
||||
...baseMeta,
|
||||
attempted: true,
|
||||
applied: false,
|
||||
traceId: normalized?.trace_id ?? null,
|
||||
llmCanonicalCandidateDetected: true,
|
||||
effectiveMessage: userMessage,
|
||||
reason: "normalized_fragment_rejected_anchor_substitution",
|
||||
fallbackRuleHit: null,
|
||||
sanitizedUserMessage
|
||||
}, userMessage);
|
||||
}
|
||||
if (fallbackCandidate) {
|
||||
const fallbackAnchorQuality = evaluateAddressAnchorQuality(String(fallbackCandidate.candidate ?? ""));
|
||||
const fallbackPreferredForAnchorSafety = sameIntentForAnchorSafety &&
|
||||
@@ -3079,6 +3139,7 @@ function resolveAddressToolGateDecision(addressInputMessage, followupContext, ll
|
||||
llmContractIntent === "unknown";
|
||||
const hasAnyAddressSignal = hasClassifierSignal || hasLlmCanonicalSignal || hasLlmCanonicalDataSignal || hasLexicalAddressSignal;
|
||||
const strongDataSignalFromRawMessage = hasStrongDataIntentSignal(rawMessageForGate) ||
|
||||
hasDataRetrievalRequestSignal(rawMessageForGate) ||
|
||||
hasAccountingSignal(rawMessageForGate) ||
|
||||
hasSameDateAccountFollowupSignalForPredecompose(rawMessageForGate);
|
||||
const strongDataSignalFromEffectiveMessage = hasStrongDataIntentSignal(repairedInputMessage) ||
|
||||
@@ -3105,7 +3166,7 @@ function resolveAddressToolGateDecision(addressInputMessage, followupContext, ll
|
||||
reason: "llm_predecompose_unsupported_mode"
|
||||
};
|
||||
}
|
||||
const hasMessageSignal = hasAnyAddressSignal;
|
||||
const hasMessageSignal = hasAnyAddressSignal || strongDataSignalFromRawMessage || strongDataSignalFromEffectiveMessage;
|
||||
if (hasMessageSignal) {
|
||||
return {
|
||||
runAddressLane: true,
|
||||
@@ -3190,7 +3251,9 @@ function hasDeepAnalysisPreferenceSignal(text) {
|
||||
}
|
||||
const riskOrAnomalySignal = /(?:\u0440\u0438\u0441\u043a|risk|\u0430\u043d\u043e\u043c\u0430\u043b|anomal|\u043f\u0440\u043e\u0442\u0438\u0432\u043e\u0440\u0435\u0447|\u043a\u043e\u043d\u0444\u043b\u0438\u043a\u0442|conflict|deviation|\u043e\u0442\u043a\u043b\u043e\u043d\u0435\u043d|\u043d\u0435\u0441\u044b\u043a\u043e\u0432\u043a|\u043d\u0435\u0441\u0445\u043e\u0434|\u043e\u0448\u0438\u0431|error|issue|\u043f\u0440\u043e\u0431\u043b\u0435\u043c)/iu.test(lower);
|
||||
const chainSignal = /(?:\u0446\u0435\u043f\u043e\u0447\u043a|chain|trace\s*chain|lifecycle|\u0436\u0438\u0437\u043d\u0435\u043d\u043d[\u0430-\u044f]+\s+\u0446\u0438\u043a\u043b|state\s+transition|\u0440\u0430\u0437\u0440\u044b\u0432[\u0430-\u044f]*)/iu.test(lower);
|
||||
const diagnosticsSignal = /(?:\u0440\u0430\u0437\u043b\u043e\u0436\u0438|\u0434\u0435\u043a\u043e\u043c\u043f\u043e\u0437|\u0440\u0430\u0437\u0431\u0435\u0440\u0438|\u043f\u043e\u0447\u0435\u043c\u0443|why|\u043a\u043e\u0440\u043d\u0435\u0432[\u0430-\u044f]+\s+\u043f\u0440\u0438\u0447\u0438\u043d|root\s*cause|\u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c[\u0430-\u044f]*|\u0433\u0434\u0435\s+\u0440\u0430\u0437\u0440\u044b\u0432|\u0447\u0442\u043e\s+\u043c\u0435\u0448\u0430[\u0430-\u044f]+\s+\u0437\u0430\u043a\u0440\u044b\u0442)/iu.test(lower);
|
||||
const diagnosticsKeywordSignal = /(?:\u0440\u0430\u0437\u043b\u043e\u0436\u0438|\u0434\u0435\u043a\u043e\u043c\u043f\u043e\u0437|\u0440\u0430\u0437\u0431\u0435\u0440\u0438|\u043f\u043e\u0447\u0435\u043c\u0443|why|audit|scan|\u043a\u043e\u0440\u043d\u0435\u0432[\u0430-\u044f]+\s+\u043f\u0440\u0438\u0447\u0438\u043d|root\s*cause|\u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c[\u0430-\u044f]*|\u0433\u0434\u0435\s+\u0440\u0430\u0437\u0440\u044b\u0432|\u0447\u0442\u043e\s+\u043c\u0435\u0448\u0430[\u0430-\u044f]+\s+\u0437\u0430\u043a\u0440\u044b\u0442)/iu.test(lower);
|
||||
const diagnosticsCheckVerbSignal = /(?:^|[\s,.;:!?()\-])\u043f\u0440\u043e\u0432\u0435\u0440(?:\u044c|\u0438\u0442\u044c|\u043a\u0443|\u0438\u043c|\u043a\u0430)(?:$|[\s,.;:!?()\-])/iu.test(lower);
|
||||
const diagnosticsSignal = diagnosticsKeywordSignal || diagnosticsCheckVerbSignal;
|
||||
const closureSignal = /(?:\u0437\u0430\u043a\u0440\u044b\u0442\u0438[\u0435\u044f]\s+\u043f\u0435\u0440\u0438\u043e\u0434|period\s*close|\u043d\u0435\s+\u0437\u0430\u043a\u0440\u044b\u043b[\u0430-\u044f]*|\u0445\u0432\u043e\u0441\u0442[\u0430-\u044f]*)/iu.test(lower);
|
||||
const closureIntentSignal = /(?:\u0437\u0430\u043a\u0440\u044b\u0442[\u0430-\u044f]*|period\s*close|close\s+period)/iu.test(lower);
|
||||
const closureDiagnosticPhraseSignal = /(?:\u0447\u0442\u043e(?:\s+\S+){0,8}\s+\u043c\u0435\u0448\u0430[\u0430-\u044f]+\s+\u0437\u0430\u043a\u0440\u044b\u0442)/iu.test(lower);
|
||||
@@ -3198,13 +3261,12 @@ function hasDeepAnalysisPreferenceSignal(text) {
|
||||
const lifecycleMismatchSignal = /(?:\u043d\u0435\s+\u0442\u0435\u043c\s+\u0442\u0438\u043f(?:\u043e\u043c)?\s+\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442|\u043e\u0436\u0438\u0434\u0430\u0435\u043c[\u0430-\u044f]+\s+\u043f\u0435\u0440\u0435\u0445\u043e\u0434[\u0430-\u044f]*\s+\u043d\u0435\s+\u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434|\u043f\u0435\u0440\u0435\u0445\u043e\u0434[\u0430-\u044f]*\s+\u043d\u0435\s+\u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434|wrong\s+closing\s+document|expected\s+transition)/iu.test(lower);
|
||||
const lifecycleTransitionGapSignal = /(?:\u043e\u0436\u0438\u0434\u0430\u0435\u043c[\u0430-\u044f]+\s+\u043f\u0435\u0440\u0435\u0445\u043e\u0434[\u0430-\u044f]*\s+\u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432|\u043f\u0435\u0440\u0435\u0445\u043e\u0434[\u0430-\u044f]*\s+\u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432|\u0441\u0442\u0430\u0434\u0438[\u0438\u044f\u0435]\s+.*\u043f\u0440\u043e\u0439\u0434\u0435\u043d.*\u043f\u0435\u0440\u0435\u0445\u043e\u0434)/iu.test(lower);
|
||||
const expectedActualMismatchSignal = /(?:\u0444\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a[\u0430-\u044f]+\s+\u0441\u043e\u0441\u0442\u043e\u044f\u043d[\u0438\u0435\u044f]+\s+.*\u0440\u0430\u0441\u0445\u043e\u0434[\u0430-\u044f]*\s+\u0441\s+\u043e\u0436\u0438\u0434\u0430\u0435\u043c|\u043e\u0436\u0438\u0434\u0430\u0435\u043c[\u0430-\u044f]+\s+\u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d[\u0430-\u044f]*\s+\u0441\u043f\u0438\u0441\u0430\u043d)/iu.test(lower);
|
||||
return riskOrAnomalySignal ||
|
||||
lifecycleMismatchSignal ||
|
||||
return lifecycleMismatchSignal ||
|
||||
(chainSignal && lifecycleTransitionGapSignal) ||
|
||||
expectedActualMismatchSignal ||
|
||||
(chainSignal && diagnosticsSignal) ||
|
||||
(riskOrAnomalySignal && (chainSignal || closureSignal || diagnosticsSignal || closureIntentSignal)) ||
|
||||
(diagnosticsSignal && closureIntentSignal) ||
|
||||
(riskOrAnomalySignal && (chainSignal || diagnosticsSignal || lifecycleTransitionGapSignal)) ||
|
||||
(diagnosticsSignal && (closureSignal || closureIntentSignal)) ||
|
||||
closureDiagnosticPhraseSignal ||
|
||||
signalVsNoiseDiagnostic;
|
||||
}
|
||||
@@ -3213,7 +3275,7 @@ function hasDirectDeepAnalysisSignal(text) {
|
||||
if (!normalized) {
|
||||
return false;
|
||||
}
|
||||
return /(?:\u0440\u0430\u0437\u043b\u043e\u0436|\u0446\u0435\u043f\u043e\u0447|lifecycle|\u0440\u0430\u0437\u0440\u044b\u0432|\u043f\u0440\u043e\u0442\u0438\u0432\u043e\u0440\u0435\u0447|\u0430\u043d\u043e\u043c\u0430\u043b|\u043f\u043e\u0447\u0435\u043c\u0443|\u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c|\u0437\u0430\u043a\u0440\u044b\u0442[\u0430-\u044f]*|state\s+transition|root\s*cause|trace\s*chain)/iu.test(normalized);
|
||||
return /(?:\u0440\u0430\u0437\u043b\u043e\u0436|\u0446\u0435\u043f\u043e\u0447|lifecycle|\u0440\u0430\u0437\u0440\u044b\u0432|\u043f\u0440\u043e\u0442\u0438\u0432\u043e\u0440\u0435\u0447|\u0430\u043d\u043e\u043c\u0430\u043b|\u043f\u043e\u0447\u0435\u043c\u0443|\u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c|\u0437\u0430\u043a\u0440\u044b\u0442\u0438[\u0435\u044f]\s+\u043f\u0435\u0440\u0438\u043e\u0434|period\s*close|close\s+period|\u0447\u0442\u043e\s+\u043c\u0435\u0448\u0430[\u0430-\u044f]+\s+\u0437\u0430\u043a\u0440\u044b\u0442|state\s+transition|root\s*cause|trace\s*chain)/iu.test(normalized);
|
||||
}
|
||||
function hasStrictDeepInvestigationCue(text) {
|
||||
const normalized = compactWhitespace(repairAddressMojibake(String(text ?? "")).toLowerCase());
|
||||
@@ -3239,6 +3301,23 @@ function hasAggregateBusinessAnalyticsSignal(text) {
|
||||
const hasPeriodAggregateCue = /(?:\u043f\u043e\s+\u0433\u043e\u0434\u0430\u043c|\u0437\u0430\s+\d{4}\s+\u0433\u043e\u0434|\u0433\u043e\u0434(?:\u0430|\u0443|\u044b)?|year|years|\u043a\u0432\u0430\u0440\u0442\u0430\u043b|\u043c\u0435\u0441\u044f\u0446|\u043f\u0435\u0440\u0438\u043e\u0434)/iu.test(normalized);
|
||||
return hasRankingOrTrendCue || hasPeriodAggregateCue;
|
||||
}
|
||||
function hasOpenContractsAddressSignal(text) {
|
||||
const normalized = compactWhitespace(repairAddressMojibake(String(text ?? "")).toLowerCase());
|
||||
if (!normalized) {
|
||||
return false;
|
||||
}
|
||||
const hasContractCue = /(?:договор|контракт|contract)/iu.test(normalized);
|
||||
if (!hasContractCue) {
|
||||
return false;
|
||||
}
|
||||
const hasOpenCue = /(?:незакрыт|не\s+закрыт|открыт|open\s+contract|open\s+item|open)/iu.test(normalized);
|
||||
if (!hasOpenCue) {
|
||||
return false;
|
||||
}
|
||||
const hasRequestCue = /(?:покажи|показать|список|какие|какой|show|list|find|на\s+дату|as\s+of)/iu.test(normalized);
|
||||
const hasTemporalCue = hasPeriodLiteral(normalized) || /\b\d{4}[-/.]\d{2}[-/.]\d{2}\b/.test(normalized);
|
||||
return hasRequestCue || hasTemporalCue;
|
||||
}
|
||||
const ADDRESS_INTENTS_KEEP_ADDRESS_LANE = new Set([
|
||||
"list_open_contracts",
|
||||
"open_items_by_counterparty_or_contract",
|
||||
@@ -3276,10 +3355,21 @@ export function resolveAssistantOrchestrationDecision(input) {
|
||||
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 intentResolution = (0, addressIntentResolver_1.resolveAddressIntent)(modeSample);
|
||||
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
|
||||
@@ -3295,7 +3385,8 @@ export function resolveAssistantOrchestrationDecision(input) {
|
||||
hasStrictDeepInvestigationCue(repairedEffectiveAddressUserMessage);
|
||||
const keepAddressLaneByIntent = semanticApplyCanonicalRecommended &&
|
||||
Boolean((intentResolution.intent && ADDRESS_INTENTS_KEEP_ADDRESS_LANE.has(intentResolution.intent)) ||
|
||||
(llmContractIntent && ADDRESS_INTENTS_KEEP_ADDRESS_LANE.has(llmContractIntent))) &&
|
||||
(llmContractIntent && ADDRESS_INTENTS_KEEP_ADDRESS_LANE.has(llmContractIntent)) ||
|
||||
openContractsAddressSignal) &&
|
||||
!strictDeepInvestigationCueDetected;
|
||||
const strongDataSignal = hasStrongDataIntentSignal(rawUserMessage) ||
|
||||
hasStrongDataIntentSignal(repairedRawUserMessage) ||
|
||||
@@ -3387,7 +3478,8 @@ export function resolveAssistantOrchestrationDecision(input) {
|
||||
hasAddressFollowupContextSignal(repairedEffectiveAddressUserMessage));
|
||||
const supportedAddressIntentDetected = !strictDeepInvestigationCueDetected &&
|
||||
Boolean((intentResolution.intent && ADDRESS_INTENTS_KEEP_ADDRESS_LANE.has(intentResolution.intent)) ||
|
||||
(llmContractIntent && ADDRESS_INTENTS_KEEP_ADDRESS_LANE.has(llmContractIntent)));
|
||||
(llmContractIntent && ADDRESS_INTENTS_KEEP_ADDRESS_LANE.has(llmContractIntent)) ||
|
||||
openContractsAddressSignal);
|
||||
const semanticGuardHints = semanticExtractionContract?.guard_hints &&
|
||||
typeof semanticExtractionContract.guard_hints === "object"
|
||||
? semanticExtractionContract.guard_hints
|
||||
@@ -3408,8 +3500,14 @@ export function resolveAssistantOrchestrationDecision(input) {
|
||||
const unsupportedIntentOrMode = (modeDetection.mode !== "address_query" && intentResolution.intent === "unknown") ||
|
||||
llmContractMode === "unsupported";
|
||||
const unsupportedAddressIntentFallbackToDeep = Boolean(baseToolGate?.runAddressLane &&
|
||||
!llmRuntimeUnavailableDetected &&
|
||||
unsupportedIntentOrMode &&
|
||||
strongDataSignal &&
|
||||
(llmContractMode === "deep_analysis" ||
|
||||
!dataRetrievalSignal ||
|
||||
strictDeepInvestigationCueDetected ||
|
||||
semanticDeepInvestigationHintDetected ||
|
||||
aggregateBusinessAnalyticsSignal) &&
|
||||
!preserveAddressLaneSignal &&
|
||||
!keepAddressLaneByIntent &&
|
||||
!supportedAddressIntentDetected &&
|
||||
@@ -3426,21 +3524,25 @@ export function resolveAssistantOrchestrationDecision(input) {
|
||||
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 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));
|
||||
!semanticApplyCanonicalRecommended ||
|
||||
standaloneAddressTopicSignal));
|
||||
const deepSessionContinuationFallbackToDeep = Boolean(!followupContext &&
|
||||
baseToolGate?.runAddressLane &&
|
||||
!llmRuntimeUnavailableDetected &&
|
||||
hasDeepSessionContinuationSignal({
|
||||
rawUserMessage,
|
||||
repairedRawUserMessage,
|
||||
@@ -3547,24 +3649,29 @@ export function resolveAssistantOrchestrationDecision(input) {
|
||||
}
|
||||
function hasStrongDataIntentSignal(text) {
|
||||
const lower = String(text ?? "").toLowerCase();
|
||||
return /(база|док|документ|проводк|контрагент|договор|контракт|счет|сч[её]т|остат|сальдо|хвост|платеж|плат[её]ж|операц|поставщик|клиент|заказчик|дебитор|кредитор|оборот|баланс|период|месяц|год|инн|mcp|bank|counterparty|contract|document|ledger|posting|account|организац|компан|контор|фирм)/i.test(lower);
|
||||
return /(база|док|документ|проводк|контрагент|договор|контракт|счет|сч[её]т|остат|сальдо|хвост|платеж|плат[её]ж|операц|поставщик|клиент|заказчик|дебитор|кредитор|оборот|баланс|период|месяц|год|инн|аванс|предоплат|отгруз|задолж|долг|mcp|bank|counterparty|contract|document|ledger|posting|account|organization|company|advance|prepay|shipment|receivab|payab|организац|компан|контор|фирм)/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|advance|prepay|shipment|receivab|payab|counterparty|contract|document|account|balance|turnover)/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)(?:$|[\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)/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|\bwhich\b|\bwho\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|организац|компан|контор|фирм|возраст|дата\s+регистрац|регистрац|основан)/i.test(lower);
|
||||
const hasRetrievalObject = /(1с|база|док|документ|контрагент|договор|контракт|счет|сч[её]т|остат|сальдо|хвост|платеж|плат[её]ж|операц|поставщик|клиент|заказчик|дебитор|кредитор|период|месяц|год|инн|аванс|предоплат|отгруз|задолж|долг|bank|counterparty|contract|document|account|balance|ledger|posting|advance|prepay|shipment|receivab|payab|организац|компан|контор|фирм|возраст|дата\s+регистрац|регистрац|основан)/i.test(lower);
|
||||
if (!hasRetrievalObject) {
|
||||
return false;
|
||||
}
|
||||
@@ -3725,6 +3832,10 @@ function hasAssistantDataScopeMetaQuestionSignal(text) {
|
||||
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;
|
||||
@@ -4771,13 +4882,55 @@ export class AssistantService {
|
||||
extractExecutionState
|
||||
}
|
||||
});
|
||||
const turnRuntime = await (0, assistantTurnAttemptRuntimeAdapter_1.runAssistantTurnAttemptRuntime)({
|
||||
payload,
|
||||
runUserTurnBootstrapRuntime: (runtimePayload) => (0, assistantUserTurnBootstrapRuntimeAdapter_1.runAssistantUserTurnBootstrapRuntime)((0, assistantTurnRuntimeInputBuilder_1.buildAssistantUserTurnBootstrapRuntimeInput)(runtimePayload, turnRuntimeDeps)),
|
||||
resolveSessionOrganizationScopeContext: (runtimeUserMessage, sessionItems) => resolveSessionOrganizationScopeContext(runtimeUserMessage, sessionItems),
|
||||
runAddressAttemptRuntime: async (runtimeInput) => (0, assistantAddressAttemptRuntimeAdapter_1.runAssistantAddressAttemptRuntime)((0, assistantTurnRuntimeInputBuilder_1.buildAssistantAddressAttemptRuntimeInput)(runtimeInput, turnRuntimeDeps)),
|
||||
runDeepTurnAttemptRuntime: async (runtimeInput) => (0, assistantDeepTurnAttemptRuntimeAdapter_1.runAssistantDeepTurnAttemptRuntime)((0, assistantTurnRuntimeInputBuilder_1.buildAssistantDeepTurnAttemptRuntimeInput)(runtimeInput, turnRuntimeDeps))
|
||||
});
|
||||
return turnRuntime.response;
|
||||
try {
|
||||
const turnRuntime = await (0, assistantTurnAttemptRuntimeAdapter_1.runAssistantTurnAttemptRuntime)({
|
||||
payload,
|
||||
runUserTurnBootstrapRuntime: (runtimePayload) => (0, assistantUserTurnBootstrapRuntimeAdapter_1.runAssistantUserTurnBootstrapRuntime)((0, assistantTurnRuntimeInputBuilder_1.buildAssistantUserTurnBootstrapRuntimeInput)(runtimePayload, turnRuntimeDeps)),
|
||||
resolveSessionOrganizationScopeContext: (runtimeUserMessage, sessionItems) => resolveSessionOrganizationScopeContext(runtimeUserMessage, sessionItems),
|
||||
runAddressAttemptRuntime: async (runtimeInput) => (0, assistantAddressAttemptRuntimeAdapter_1.runAssistantAddressAttemptRuntime)((0, assistantTurnRuntimeInputBuilder_1.buildAssistantAddressAttemptRuntimeInput)(runtimeInput, turnRuntimeDeps)),
|
||||
runDeepTurnAttemptRuntime: async (runtimeInput) => (0, assistantDeepTurnAttemptRuntimeAdapter_1.runAssistantDeepTurnAttemptRuntime)((0, assistantTurnRuntimeInputBuilder_1.buildAssistantDeepTurnAttemptRuntimeInput)(runtimeInput, turnRuntimeDeps))
|
||||
});
|
||||
return turnRuntime.response;
|
||||
}
|
||||
catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
const sessionId = String(payload?.session_id ?? payload?.sessionId ?? "").trim() || `asst-${(0, nanoid_1.nanoid)(10)}`;
|
||||
const ensuredSession = this.sessions.ensureSession(sessionId);
|
||||
const existingAssistant = [...ensuredSession.items].reverse().find((item) => item.role === "assistant") ?? null;
|
||||
if (existingAssistant) {
|
||||
return {
|
||||
ok: true,
|
||||
session_id: sessionId,
|
||||
assistant_reply: existingAssistant.text,
|
||||
reply_type: existingAssistant.reply_type ?? "backend_error",
|
||||
conversation_item: existingAssistant,
|
||||
debug: existingAssistant.debug ?? buildAssistantBackendErrorDebugPayload(errorMessage),
|
||||
conversation: cloneItems(ensuredSession.items)
|
||||
};
|
||||
}
|
||||
const createdAt = new Date().toISOString();
|
||||
const debugPayload = buildAssistantBackendErrorDebugPayload(errorMessage);
|
||||
const assistantItem = this.sessions.appendItem(sessionId, {
|
||||
message_id: `msg-${(0, nanoid_1.nanoid)(10)}`,
|
||||
session_id: sessionId,
|
||||
role: "assistant",
|
||||
text: buildAssistantBackendErrorReply(),
|
||||
reply_type: "backend_error",
|
||||
created_at: createdAt,
|
||||
trace_id: debugPayload.trace_id ?? null,
|
||||
debug: debugPayload
|
||||
});
|
||||
const sessionSnapshot = this.sessions.getSession(sessionId) ?? this.sessions.ensureSession(sessionId);
|
||||
this.sessionLogger.persistSession(sessionSnapshot);
|
||||
return {
|
||||
ok: true,
|
||||
session_id: sessionId,
|
||||
assistant_reply: assistantItem.text,
|
||||
reply_type: "backend_error",
|
||||
conversation_item: assistantItem,
|
||||
debug: debugPayload,
|
||||
conversation: cloneItems(sessionSnapshot.items)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user