Архитектура: перенести base address tool gate из assistantService в assistantRoutePolicy и выровнять top-level orchestration owner

This commit is contained in:
dctouch 2026-04-19 13:42:55 +03:00
parent d1ad8e2c1b
commit ac3757bc59
6 changed files with 329 additions and 279 deletions

View File

@ -466,6 +466,17 @@ Still open after the accepted phase12 replay:
- continuity now also owns `resolveFollowupTargetIntent(...)`, so `carryover target intent` precedence for purchase-date VAT bridge, selected-object retarget, root-context carryover, same-date pivot, displayed-entity retarget, and plain previous-intent fallback is expressed in one shared helper instead of an inline ternary tower;
- this matters because root-pivot semantics and target-intent precedence are among the heaviest remaining orchestration decisions in the follow-up path, and keeping them under one shared continuity layer reduces another chance that future domain expansion reintroduces drift between carryover state and target route selection;
- targeted `assistantContinuityPolicy` and `assistantTransitionPolicy` suites are green after the move, and a fresh live rerun of `address_truth_harness_phase12_wider_saved_session_pool` on `2026-04-19` is accepted `20/20`, which is the critical proof that the flagship mixed replay still survives after the decision-block extraction.
- the next top-level orchestration pass now removes one more heavy owner from `assistantService` and makes route arbitration more self-contained:
- the base `address tool gate` decision no longer lives as a service-local block in `assistantService` and is no longer injected into route arbitration as an external callback-only source of truth;
- `assistantRoutePolicy` now owns the default `resolveBaseAddressToolGateDecision(...)` path itself, including:
- meta/capability skip logic;
- classifier/intent/LLM-canonical exact-route signals;
- lexical/address-data fallback signals;
- unsupported predecompose semantic guard;
- `followup_context_detected` fallback when no direct message signal exists;
- this matters because the top-level `run address lane vs keep chat` gate is now structurally closer to the same route-policy owner that already arbitrates memory/meta/follow-up/deep transitions, instead of remaining split across `assistantService` glue and route policy heuristics;
- the route policy still accepts an override in tests, so regression coverage remains narrow and controllable, but the production runtime no longer depends on a duplicate service-local decision block;
- targeted `assistantRoutePolicy`, `assistantContinuityPolicy`, and `assistantTransitionPolicy` suites are green after the move, and a fresh live rerun of `address_truth_harness_phase12_wider_saved_session_pool` on `2026-04-19` remains accepted `20/20`, which is the proof that the flagship mixed path survives after lifting the gate out of `assistantService`.
## Next Execution Slice (2026-04-18)

View File

@ -47,7 +47,149 @@ 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, resolveMetaSignalSet, resolveHardMetaMode, isMetaFollowupOverGroundedAnswer, isAnswerInspectionFollowupOverGroundedAnswer, hasDataRetrievalRequestSignal, hasOrganizationFactLookupSignal, hasOrganizationFactFollowupSignal, hasAggregateBusinessAnalyticsSignal, hasStandaloneAddressTopicSignal, hasOpenContractsAddressSignal, detectAddressQuestionMode, resolveAddressIntent, toNonEmptyString, hasStrictDeepInvestigationCue, hasStrongDataIntentSignal, hasAccountingSignal, hasDangerOrCoercionSignal, hasAddressFollowupContextSignal, hasShortDebtMirrorFollowupSignal, isInventorySelectedObjectIntent, hasShortInventoryObjectFollowupSignal, isGroundedInventoryContextDebug, resolveRouteMemorySignals, findLastAddressAssistantItem, resolveAddressToolGateDecision, hasSameDateAccountFollowupSignalForPredecompose, hasLooseAllTimeAddressLookupSignal, hasDeepAnalysisPreferenceSignal, hasDirectDeepAnalysisSignal, compactWhitespace, hasDeepSessionContinuationSignal, resolveLivingAssistantModeDecision, resolveProviderExecutionState } = deps;
const { repairAddressMojibake, findLastGroundedAddressAnswerDebug, findLastOrganizationClarificationAddressDebug, mergeKnownOrganizations, normalizeOrganizationScopeValue, resolveOrganizationSelectionFromMessage, resolveMetaSignalSet, resolveHardMetaMode, isMetaFollowupOverGroundedAnswer, isAnswerInspectionFollowupOverGroundedAnswer, hasDataRetrievalRequestSignal, hasOrganizationFactLookupSignal, hasOrganizationFactFollowupSignal, hasAggregateBusinessAnalyticsSignal, hasStandaloneAddressTopicSignal, hasOpenContractsAddressSignal, detectAddressQuestionMode, resolveAddressIntent, toNonEmptyString, hasStrictDeepInvestigationCue, hasStrongDataIntentSignal, hasAccountingSignal, hasDangerOrCoercionSignal, hasAddressFollowupContextSignal, hasShortDebtMirrorFollowupSignal, isInventorySelectedObjectIntent, hasShortInventoryObjectFollowupSignal, isGroundedInventoryContextDebug, resolveRouteMemorySignals, findLastAddressAssistantItem, resolveAddressToolGateDecision: resolveAddressToolGateDecisionOverride, hasAddressLlmPreDecomposeCandidate, hasSameDateAccountFollowupSignalForPredecompose, hasLooseAllTimeAddressLookupSignal, hasDeepAnalysisPreferenceSignal, hasDirectDeepAnalysisSignal, compactWhitespace, hasDeepSessionContinuationSignal, resolveLivingAssistantModeDecision, resolveProviderExecutionState } = deps;
function resolveBaseAddressToolGateDecision(addressInputMessage, followupContext, llmPreDecomposeMeta = null, rawUserMessage = null) {
const repairedInputMessage = repairAddressMojibake(String(addressInputMessage ?? ""));
const rawMessageForGate = String(rawUserMessage ?? addressInputMessage ?? "");
const repairedRawMessageForGate = repairAddressMojibake(rawMessageForGate);
const rawMetaSignals = resolveMetaSignalSet({
rawUserMessage: rawMessageForGate,
repairedRawUserMessage: repairedRawMessageForGate,
effectiveAddressUserMessage: rawMessageForGate,
repairedEffectiveAddressUserMessage: repairedRawMessageForGate
});
const effectiveMetaSignals = resolveMetaSignalSet({
rawUserMessage: rawMessageForGate,
repairedRawUserMessage: repairedRawMessageForGate,
effectiveAddressUserMessage: String(addressInputMessage ?? ""),
repairedEffectiveAddressUserMessage: repairedInputMessage
});
const dataScopeMetaQuery = Boolean(rawMetaSignals.dataScopeMetaQuery || effectiveMetaSignals.dataScopeMetaQuery);
const rawCapabilityMetaQuery = Boolean(rawMetaSignals.capabilityMetaQuery);
const capabilityMetaQuery = Boolean(rawCapabilityMetaQuery || effectiveMetaSignals.capabilityMetaQuery);
const rawDataRetrievalSignal = hasDataRetrievalRequestSignal(rawMessageForGate) ||
hasDataRetrievalRequestSignal(repairedRawMessageForGate);
const dataRetrievalSignal = rawDataRetrievalSignal || hasDataRetrievalRequestSignal(repairedInputMessage);
const rawCapabilityMetaOverride = rawCapabilityMetaQuery && !rawDataRetrievalSignal;
if (dataScopeMetaQuery || rawCapabilityMetaOverride || (capabilityMetaQuery && !dataRetrievalSignal)) {
return {
runAddressLane: false,
decision: "skip_address_lane",
reason: dataScopeMetaQuery ? "assistant_data_scope_query_detected" : "assistant_capability_query_detected"
};
}
const modeDetection = detectAddressQuestionMode(repairedInputMessage || addressInputMessage);
const modeDetectionRaw = detectAddressQuestionMode(String(addressInputMessage ?? ""));
const hasClassifierSignal = modeDetection.mode === "address_query" || modeDetectionRaw.mode === "address_query";
const intentResolution = resolveAddressIntent(repairedInputMessage || addressInputMessage);
const intentResolutionRaw = resolveAddressIntent(String(addressInputMessage ?? ""));
const hasIntentSignal = intentResolution.intent !== "unknown" || intentResolutionRaw.intent !== "unknown";
const llmContractMode = toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.mode);
const llmContractModeConfidence = toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.mode_confidence);
const llmContractIntent = toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.intent);
const semanticExtractionContract = llmPreDecomposeMeta?.semanticExtractionContract &&
typeof llmPreDecomposeMeta.semanticExtractionContract === "object"
? llmPreDecomposeMeta.semanticExtractionContract
: null;
const semanticCanonicalRecommended = semanticExtractionContract?.apply_canonical_recommended !== false;
const llmSupportedDeepAddressIntentSignal = llmContractMode === "deep_analysis" &&
/^(?:inventory_purchase_provenance_for_item|inventory_purchase_documents_for_item|inventory_sale_trace_for_item|inventory_profitability_for_item|inventory_purchase_to_sale_chain)$/u.test(llmContractIntent ?? "") &&
semanticCanonicalRecommended;
const llmCanonicalEntitySignal = /(?:заказчик|поставщик|контрагент|компан|customer|supplier|counterparty|company|vendor|client)/iu.test(compactWhitespace(repairedInputMessage.toLowerCase()));
const llmCanonicalAppliedSignal = Boolean(llmPreDecomposeMeta?.applied) && llmContractMode !== "deep_analysis";
const hasLlmCanonicalSignal = semanticCanonicalRecommended &&
Boolean(llmPreDecomposeMeta?.llmCanonicalCandidateDetected) &&
((llmContractMode === "address_query" && llmContractModeConfidence !== "low") ||
(llmCanonicalAppliedSignal &&
(hasStrongDataIntentSignal(repairedInputMessage) || llmCanonicalEntitySignal)));
const hasLlmCanonicalDataSignal = semanticCanonicalRecommended &&
Boolean(llmPreDecomposeMeta?.llmCanonicalCandidateDetected) &&
Boolean(llmPreDecomposeMeta?.applied) &&
(llmContractMode === "address_query" || llmContractMode === "unsupported" || llmContractMode === null) &&
hasStrongDataIntentSignal(repairedInputMessage);
const hasBusinessRankingAddressSignal = /(?:кто\s+(?:нам\s+)?(?:больше(?:\s+всего)?\s+принес|принес\s+больше(?:\s+всего)?).*(?:денег)?|who\s+brought\s+(?:us\s+)?(?:the\s+)?most\s+money)/iu.test(compactWhitespace(repairedInputMessage.toLowerCase()));
const sameDateAccountFollowupSignal = hasSameDateAccountFollowupSignalForPredecompose(rawMessageForGate) ||
hasSameDateAccountFollowupSignalForPredecompose(repairedInputMessage);
const hasLexicalAddressSignal = hasAddressLlmPreDecomposeCandidate(addressInputMessage) ||
hasAddressLlmPreDecomposeCandidate(repairedInputMessage) ||
hasAccountingSignal(addressInputMessage) ||
hasAccountingSignal(repairedInputMessage) ||
hasShortDebtMirrorFollowupSignal(rawMessageForGate) ||
hasShortDebtMirrorFollowupSignal(repairedInputMessage) ||
hasBusinessRankingAddressSignal ||
sameDateAccountFollowupSignal;
const hasUnsupportedLowConfidencePredecomposeSignal = llmContractMode === "unsupported" &&
(llmContractModeConfidence === "low" || llmContractModeConfidence === "medium") &&
llmContractIntent === "unknown";
const hasAnyAddressSignal = hasClassifierSignal ||
hasIntentSignal ||
hasLlmCanonicalSignal ||
hasLlmCanonicalDataSignal ||
hasLexicalAddressSignal ||
llmSupportedDeepAddressIntentSignal;
const strongDataSignalFromRawMessage = hasStrongDataIntentSignal(rawMessageForGate) ||
hasDataRetrievalRequestSignal(rawMessageForGate) ||
hasAccountingSignal(rawMessageForGate) ||
hasSameDateAccountFollowupSignalForPredecompose(rawMessageForGate);
const strongDataSignalFromEffectiveMessage = hasStrongDataIntentSignal(repairedInputMessage) ||
hasAccountingSignal(repairedInputMessage) ||
hasDataRetrievalRequestSignal(repairedInputMessage);
if (!semanticCanonicalRecommended &&
llmContractIntent === "unknown" &&
!followupContext &&
!hasClassifierSignal &&
!hasIntentSignal &&
!hasLexicalAddressSignal &&
!llmSupportedDeepAddressIntentSignal &&
!strongDataSignalFromRawMessage &&
!strongDataSignalFromEffectiveMessage) {
return {
runAddressLane: false,
decision: "skip_address_lane",
reason: "llm_predecompose_semantic_guard_rejected"
};
}
if (hasUnsupportedLowConfidencePredecomposeSignal &&
!followupContext &&
!hasAnyAddressSignal &&
!llmSupportedDeepAddressIntentSignal &&
!strongDataSignalFromRawMessage &&
!strongDataSignalFromEffectiveMessage) {
return {
runAddressLane: false,
decision: "skip_address_lane",
reason: "llm_predecompose_unsupported_mode"
};
}
const hasMessageSignal = hasAnyAddressSignal || strongDataSignalFromRawMessage || strongDataSignalFromEffectiveMessage;
if (hasMessageSignal) {
return {
runAddressLane: true,
decision: "run_address_lane",
reason: hasClassifierSignal
? "address_mode_classifier_detected"
: hasIntentSignal
? "address_intent_resolver_detected"
: hasLlmCanonicalSignal
? "llm_canonical_candidate_detected"
: hasLlmCanonicalDataSignal
? "llm_canonical_data_signal_detected"
: "address_signal_detected"
};
}
if (followupContext) {
return {
runAddressLane: true,
decision: "run_address_lane",
reason: "followup_context_detected"
};
}
return {
runAddressLane: false,
decision: "skip_address_lane",
reason: "no_address_signal_after_l0"
};
}
function hasInventoryRootRestatementFollowupSignal(text) {
const normalized = compactWhitespace(repairAddressMojibake(String(text ?? "")).toLowerCase()).replace(/ё/g, "е");
if (!normalized) {
@ -210,7 +352,9 @@ function createAssistantRoutePolicy(deps) {
!capabilityMetaQuery &&
!dataRetrievalSignal);
const effectiveAddressFollowupSignal = explicitAddressFollowupSignal && !dangerOrCoercionSignal;
const baseToolGate = resolveAddressToolGateDecision(effectiveAddressUserMessage, followupContext, llmPreDecomposeMeta, rawUserMessage);
const baseToolGate = typeof resolveAddressToolGateDecisionOverride === "function"
? resolveAddressToolGateDecisionOverride(effectiveAddressUserMessage, followupContext, llmPreDecomposeMeta, rawUserMessage)
: resolveBaseAddressToolGateDecision(effectiveAddressUserMessage, followupContext, llmPreDecomposeMeta, rawUserMessage);
const deterministicNonDomainGuard = Boolean(!dataScopeMetaQuery &&
!capabilityMetaQuery &&
!dataRetrievalSignal &&

View File

@ -3693,142 +3693,6 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage
}, userMessage);
}
}
function resolveAddressToolGateDecision(addressInputMessage, followupContext, llmPreDecomposeMeta = null, rawUserMessage = null) {
const repairedInputMessage = repairAddressMojibake(String(addressInputMessage ?? ""));
const rawMessageForGate = String(rawUserMessage ?? addressInputMessage ?? "");
const repairedRawMessageForGate = repairAddressMojibake(rawMessageForGate);
const dataScopeMetaQuery = hasAssistantDataScopeMetaQuestionSignal(rawMessageForGate) ||
hasAssistantDataScopeMetaQuestionSignal(repairedRawMessageForGate) ||
hasAssistantDataScopeMetaQuestionSignal(repairedInputMessage);
const rawCapabilityMetaQuery = shouldHandleAsAssistantCapabilityMetaQuery(rawMessageForGate) ||
shouldHandleAsAssistantCapabilityMetaQuery(repairedRawMessageForGate);
const capabilityMetaQuery = rawCapabilityMetaQuery ||
shouldHandleAsAssistantCapabilityMetaQuery(repairedInputMessage);
const rawDataRetrievalSignal = hasDataRetrievalRequestSignal(rawMessageForGate) ||
hasDataRetrievalRequestSignal(repairedRawMessageForGate);
const dataRetrievalSignal = rawDataRetrievalSignal ||
hasDataRetrievalRequestSignal(repairedInputMessage);
const rawCapabilityMetaOverride = rawCapabilityMetaQuery && !rawDataRetrievalSignal;
if (dataScopeMetaQuery || rawCapabilityMetaOverride || (capabilityMetaQuery && !dataRetrievalSignal)) {
return {
runAddressLane: false,
decision: "skip_address_lane",
reason: dataScopeMetaQuery ? "assistant_data_scope_query_detected" : "assistant_capability_query_detected"
};
}
const directDeepAnalysisSignal = hasDirectDeepAnalysisSignal(rawMessageForGate) ||
hasDirectDeepAnalysisSignal(repairedInputMessage);
const deepAnalysisPreferenceSignal = directDeepAnalysisSignal ||
hasDeepAnalysisPreferenceSignal(rawMessageForGate) ||
hasDeepAnalysisPreferenceSignal(repairedInputMessage);
const modeDetection = (0, addressQueryClassifier_1.detectAddressQuestionMode)(repairedInputMessage || addressInputMessage);
const modeDetectionRaw = (0, addressQueryClassifier_1.detectAddressQuestionMode)(String(addressInputMessage ?? ""));
const hasClassifierSignal = modeDetection.mode === "address_query" || modeDetectionRaw.mode === "address_query";
const intentResolution = (0, addressIntentResolver_1.resolveAddressIntent)(repairedInputMessage || addressInputMessage);
const intentResolutionRaw = (0, addressIntentResolver_1.resolveAddressIntent)(String(addressInputMessage ?? ""));
const hasIntentSignal = intentResolution.intent !== "unknown" || intentResolutionRaw.intent !== "unknown";
const llmContractMode = toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.mode);
const llmContractModeConfidence = toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.mode_confidence);
const llmContractIntent = toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.intent);
const semanticExtractionContract = llmPreDecomposeMeta?.semanticExtractionContract &&
typeof llmPreDecomposeMeta.semanticExtractionContract === "object"
? llmPreDecomposeMeta.semanticExtractionContract
: null;
const semanticCanonicalRecommended = semanticExtractionContract?.apply_canonical_recommended !== false;
const llmSupportedDeepAddressIntentSignal = llmContractMode === "deep_analysis" &&
/^(?:inventory_purchase_provenance_for_item|inventory_purchase_documents_for_item|inventory_sale_trace_for_item|inventory_profitability_for_item|inventory_purchase_to_sale_chain)$/u.test(llmContractIntent ?? "") &&
semanticCanonicalRecommended;
const llmCanonicalEntitySignal = /(?:\u0437\u0430\u043a\u0430\u0437\u0447\u0438\u043a|\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442|\u043a\u043e\u043c\u043f\u0430\u043d|customer|supplier|counterparty|company|vendor|client)/iu.test(compactWhitespace(repairedInputMessage.toLowerCase()));
const llmCanonicalAppliedSignal = Boolean(llmPreDecomposeMeta?.applied) && llmContractMode !== "deep_analysis";
const hasLlmCanonicalSignal = semanticCanonicalRecommended &&
Boolean(llmPreDecomposeMeta?.llmCanonicalCandidateDetected) &&
((llmContractMode === "address_query" && llmContractModeConfidence !== "low") ||
(llmCanonicalAppliedSignal &&
(hasStrongDataIntentSignal(repairedInputMessage) || llmCanonicalEntitySignal)));
const hasLlmCanonicalDataSignal = semanticCanonicalRecommended &&
Boolean(llmPreDecomposeMeta?.llmCanonicalCandidateDetected) &&
Boolean(llmPreDecomposeMeta?.applied) &&
(llmContractMode === "address_query" || llmContractMode === "unsupported" || llmContractMode === null) &&
hasStrongDataIntentSignal(repairedInputMessage);
const hasBusinessRankingAddressSignal = /(?:\u043a\u0442\u043e\s+(?:\u043d\u0430\u043c\s+)?(?:\u0431\u043e\u043b\u044c\u0448\u0435(?:\s+\u0432\u0441\u0435\u0433\u043e)?\s+\u043f\u0440\u0438\u043d\u0435\u0441|\u043f\u0440\u0438\u043d\u0435\u0441\s+\u0431\u043e\u043b\u044c\u0448\u0435(?:\s+\u0432\u0441\u0435\u0433\u043e)?).*(?:\u0434\u0435\u043d\u0435\u0433)?|who\s+brought\s+(?:us\s+)?(?:the\s+)?most\s+money)/iu.test(compactWhitespace(repairedInputMessage.toLowerCase()));
const sameDateAccountFollowupSignal = hasSameDateAccountFollowupSignalForPredecompose(rawMessageForGate) ||
hasSameDateAccountFollowupSignalForPredecompose(repairedInputMessage);
const hasLexicalAddressSignal = isAddressLlmPreDecomposeCandidate(addressInputMessage) ||
isAddressLlmPreDecomposeCandidate(repairedInputMessage) ||
hasAccountingSignal(addressInputMessage) ||
hasAccountingSignal(repairedInputMessage) ||
hasShortDebtMirrorFollowupSignal(rawMessageForGate) ||
hasShortDebtMirrorFollowupSignal(repairedInputMessage) ||
hasBusinessRankingAddressSignal ||
sameDateAccountFollowupSignal;
const hasUnsupportedLowConfidencePredecomposeSignal = llmContractMode === "unsupported" &&
(llmContractModeConfidence === "low" || llmContractModeConfidence === "medium") &&
llmContractIntent === "unknown";
const hasAnyAddressSignal = hasClassifierSignal || hasIntentSignal || hasLlmCanonicalSignal || hasLlmCanonicalDataSignal || hasLexicalAddressSignal || llmSupportedDeepAddressIntentSignal;
const strongDataSignalFromRawMessage = hasStrongDataIntentSignal(rawMessageForGate) ||
hasDataRetrievalRequestSignal(rawMessageForGate) ||
hasAccountingSignal(rawMessageForGate) ||
hasSameDateAccountFollowupSignalForPredecompose(rawMessageForGate);
const strongDataSignalFromEffectiveMessage = hasStrongDataIntentSignal(repairedInputMessage) ||
hasAccountingSignal(repairedInputMessage) ||
hasDataRetrievalRequestSignal(repairedInputMessage);
if (!semanticCanonicalRecommended &&
llmContractIntent === "unknown" &&
!followupContext &&
!hasClassifierSignal &&
!hasIntentSignal &&
!hasLexicalAddressSignal &&
!llmSupportedDeepAddressIntentSignal &&
!strongDataSignalFromRawMessage &&
!strongDataSignalFromEffectiveMessage) {
return {
runAddressLane: false,
decision: "skip_address_lane",
reason: "llm_predecompose_semantic_guard_rejected"
};
}
if (hasUnsupportedLowConfidencePredecomposeSignal && !followupContext &&
!hasAnyAddressSignal &&
!llmSupportedDeepAddressIntentSignal &&
!strongDataSignalFromRawMessage &&
!strongDataSignalFromEffectiveMessage) {
return {
runAddressLane: false,
decision: "skip_address_lane",
reason: "llm_predecompose_unsupported_mode"
};
}
const hasMessageSignal = hasAnyAddressSignal || strongDataSignalFromRawMessage || strongDataSignalFromEffectiveMessage;
if (hasMessageSignal) {
return {
runAddressLane: true,
decision: "run_address_lane",
reason: hasClassifierSignal
? "address_mode_classifier_detected"
: hasIntentSignal
? "address_intent_resolver_detected"
: hasLlmCanonicalSignal
? "llm_canonical_candidate_detected"
: hasLlmCanonicalDataSignal
? "llm_canonical_data_signal_detected"
: llmSupportedDeepAddressIntentSignal
? "address_signal_detected"
: "address_signal_detected"
};
}
if (followupContext) {
return {
runAddressLane: true,
decision: "run_address_lane",
reason: "followup_context_detected"
};
}
return {
runAddressLane: false,
decision: "skip_address_lane",
reason: "no_address_signal_after_l0"
};
}
function hasLooseAllTimeAddressLookupSignal(text) {
const repaired = repairAddressMojibake(String(text ?? ""));
const normalized = compactWhitespace(repaired.toLowerCase());
@ -4223,7 +4087,7 @@ const assistantRoutePolicy = (0, assistantRoutePolicy_1.createAssistantRoutePoli
hasShortInventoryObjectFollowupSignal,
resolveRouteMemorySignals: assistantMemoryRecapPolicy.resolveRouteMemorySignals,
findLastAddressAssistantItem,
resolveAddressToolGateDecision,
hasAddressLlmPreDecomposeCandidate: isAddressLlmPreDecomposeCandidate,
hasSameDateAccountFollowupSignalForPredecompose,
hasLooseAllTimeAddressLookupSignal,
hasDeepAnalysisPreferenceSignal,

View File

@ -78,7 +78,8 @@ export function createAssistantRoutePolicy(deps) {
isGroundedInventoryContextDebug,
resolveRouteMemorySignals,
findLastAddressAssistantItem,
resolveAddressToolGateDecision,
resolveAddressToolGateDecision: resolveAddressToolGateDecisionOverride,
hasAddressLlmPreDecomposeCandidate,
hasSameDateAccountFollowupSignalForPredecompose,
hasLooseAllTimeAddressLookupSignal,
hasDeepAnalysisPreferenceSignal,
@ -88,6 +89,148 @@ export function createAssistantRoutePolicy(deps) {
resolveLivingAssistantModeDecision,
resolveProviderExecutionState
} = deps;
function resolveBaseAddressToolGateDecision(addressInputMessage, followupContext, llmPreDecomposeMeta = null, rawUserMessage = null) {
const repairedInputMessage = repairAddressMojibake(String(addressInputMessage ?? ""));
const rawMessageForGate = String(rawUserMessage ?? addressInputMessage ?? "");
const repairedRawMessageForGate = repairAddressMojibake(rawMessageForGate);
const rawMetaSignals = resolveMetaSignalSet({
rawUserMessage: rawMessageForGate,
repairedRawUserMessage: repairedRawMessageForGate,
effectiveAddressUserMessage: rawMessageForGate,
repairedEffectiveAddressUserMessage: repairedRawMessageForGate
});
const effectiveMetaSignals = resolveMetaSignalSet({
rawUserMessage: rawMessageForGate,
repairedRawUserMessage: repairedRawMessageForGate,
effectiveAddressUserMessage: String(addressInputMessage ?? ""),
repairedEffectiveAddressUserMessage: repairedInputMessage
});
const dataScopeMetaQuery = Boolean(rawMetaSignals.dataScopeMetaQuery || effectiveMetaSignals.dataScopeMetaQuery);
const rawCapabilityMetaQuery = Boolean(rawMetaSignals.capabilityMetaQuery);
const capabilityMetaQuery = Boolean(rawCapabilityMetaQuery || effectiveMetaSignals.capabilityMetaQuery);
const rawDataRetrievalSignal = hasDataRetrievalRequestSignal(rawMessageForGate) ||
hasDataRetrievalRequestSignal(repairedRawMessageForGate);
const dataRetrievalSignal = rawDataRetrievalSignal || hasDataRetrievalRequestSignal(repairedInputMessage);
const rawCapabilityMetaOverride = rawCapabilityMetaQuery && !rawDataRetrievalSignal;
if (dataScopeMetaQuery || rawCapabilityMetaOverride || (capabilityMetaQuery && !dataRetrievalSignal)) {
return {
runAddressLane: false,
decision: "skip_address_lane",
reason: dataScopeMetaQuery ? "assistant_data_scope_query_detected" : "assistant_capability_query_detected"
};
}
const modeDetection = detectAddressQuestionMode(repairedInputMessage || addressInputMessage);
const modeDetectionRaw = detectAddressQuestionMode(String(addressInputMessage ?? ""));
const hasClassifierSignal = modeDetection.mode === "address_query" || modeDetectionRaw.mode === "address_query";
const intentResolution = resolveAddressIntent(repairedInputMessage || addressInputMessage);
const intentResolutionRaw = resolveAddressIntent(String(addressInputMessage ?? ""));
const hasIntentSignal = intentResolution.intent !== "unknown" || intentResolutionRaw.intent !== "unknown";
const llmContractMode = toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.mode);
const llmContractModeConfidence = toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.mode_confidence);
const llmContractIntent = toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.intent);
const semanticExtractionContract = llmPreDecomposeMeta?.semanticExtractionContract &&
typeof llmPreDecomposeMeta.semanticExtractionContract === "object"
? llmPreDecomposeMeta.semanticExtractionContract
: null;
const semanticCanonicalRecommended = semanticExtractionContract?.apply_canonical_recommended !== false;
const llmSupportedDeepAddressIntentSignal = llmContractMode === "deep_analysis" &&
/^(?:inventory_purchase_provenance_for_item|inventory_purchase_documents_for_item|inventory_sale_trace_for_item|inventory_profitability_for_item|inventory_purchase_to_sale_chain)$/u.test(llmContractIntent ?? "") &&
semanticCanonicalRecommended;
const llmCanonicalEntitySignal = /(?:заказчик|поставщик|контрагент|компан|customer|supplier|counterparty|company|vendor|client)/iu.test(compactWhitespace(repairedInputMessage.toLowerCase()));
const llmCanonicalAppliedSignal = Boolean(llmPreDecomposeMeta?.applied) && llmContractMode !== "deep_analysis";
const hasLlmCanonicalSignal = semanticCanonicalRecommended &&
Boolean(llmPreDecomposeMeta?.llmCanonicalCandidateDetected) &&
((llmContractMode === "address_query" && llmContractModeConfidence !== "low") ||
(llmCanonicalAppliedSignal &&
(hasStrongDataIntentSignal(repairedInputMessage) || llmCanonicalEntitySignal)));
const hasLlmCanonicalDataSignal = semanticCanonicalRecommended &&
Boolean(llmPreDecomposeMeta?.llmCanonicalCandidateDetected) &&
Boolean(llmPreDecomposeMeta?.applied) &&
(llmContractMode === "address_query" || llmContractMode === "unsupported" || llmContractMode === null) &&
hasStrongDataIntentSignal(repairedInputMessage);
const hasBusinessRankingAddressSignal = /(?:кто\s+(?:нам\s+)?(?:больше(?:\s+всего)?\s+принес|принес\s+больше(?:\s+всего)?).*(?:денег)?|who\s+brought\s+(?:us\s+)?(?:the\s+)?most\s+money)/iu.test(compactWhitespace(repairedInputMessage.toLowerCase()));
const sameDateAccountFollowupSignal = hasSameDateAccountFollowupSignalForPredecompose(rawMessageForGate) ||
hasSameDateAccountFollowupSignalForPredecompose(repairedInputMessage);
const hasLexicalAddressSignal = hasAddressLlmPreDecomposeCandidate(addressInputMessage) ||
hasAddressLlmPreDecomposeCandidate(repairedInputMessage) ||
hasAccountingSignal(addressInputMessage) ||
hasAccountingSignal(repairedInputMessage) ||
hasShortDebtMirrorFollowupSignal(rawMessageForGate) ||
hasShortDebtMirrorFollowupSignal(repairedInputMessage) ||
hasBusinessRankingAddressSignal ||
sameDateAccountFollowupSignal;
const hasUnsupportedLowConfidencePredecomposeSignal = llmContractMode === "unsupported" &&
(llmContractModeConfidence === "low" || llmContractModeConfidence === "medium") &&
llmContractIntent === "unknown";
const hasAnyAddressSignal = hasClassifierSignal ||
hasIntentSignal ||
hasLlmCanonicalSignal ||
hasLlmCanonicalDataSignal ||
hasLexicalAddressSignal ||
llmSupportedDeepAddressIntentSignal;
const strongDataSignalFromRawMessage = hasStrongDataIntentSignal(rawMessageForGate) ||
hasDataRetrievalRequestSignal(rawMessageForGate) ||
hasAccountingSignal(rawMessageForGate) ||
hasSameDateAccountFollowupSignalForPredecompose(rawMessageForGate);
const strongDataSignalFromEffectiveMessage = hasStrongDataIntentSignal(repairedInputMessage) ||
hasAccountingSignal(repairedInputMessage) ||
hasDataRetrievalRequestSignal(repairedInputMessage);
if (!semanticCanonicalRecommended &&
llmContractIntent === "unknown" &&
!followupContext &&
!hasClassifierSignal &&
!hasIntentSignal &&
!hasLexicalAddressSignal &&
!llmSupportedDeepAddressIntentSignal &&
!strongDataSignalFromRawMessage &&
!strongDataSignalFromEffectiveMessage) {
return {
runAddressLane: false,
decision: "skip_address_lane",
reason: "llm_predecompose_semantic_guard_rejected"
};
}
if (hasUnsupportedLowConfidencePredecomposeSignal &&
!followupContext &&
!hasAnyAddressSignal &&
!llmSupportedDeepAddressIntentSignal &&
!strongDataSignalFromRawMessage &&
!strongDataSignalFromEffectiveMessage) {
return {
runAddressLane: false,
decision: "skip_address_lane",
reason: "llm_predecompose_unsupported_mode"
};
}
const hasMessageSignal = hasAnyAddressSignal || strongDataSignalFromRawMessage || strongDataSignalFromEffectiveMessage;
if (hasMessageSignal) {
return {
runAddressLane: true,
decision: "run_address_lane",
reason: hasClassifierSignal
? "address_mode_classifier_detected"
: hasIntentSignal
? "address_intent_resolver_detected"
: hasLlmCanonicalSignal
? "llm_canonical_candidate_detected"
: hasLlmCanonicalDataSignal
? "llm_canonical_data_signal_detected"
: "address_signal_detected"
};
}
if (followupContext) {
return {
runAddressLane: true,
decision: "run_address_lane",
reason: "followup_context_detected"
};
}
return {
runAddressLane: false,
decision: "skip_address_lane",
reason: "no_address_signal_after_l0"
};
}
function hasInventoryRootRestatementFollowupSignal(text) {
const normalized = compactWhitespace(repairAddressMojibake(String(text ?? "")).toLowerCase()).replace(/ё/g, "е");
if (!normalized) {
@ -250,7 +393,9 @@ export function createAssistantRoutePolicy(deps) {
!capabilityMetaQuery &&
!dataRetrievalSignal);
const effectiveAddressFollowupSignal = explicitAddressFollowupSignal && !dangerOrCoercionSignal;
const baseToolGate = resolveAddressToolGateDecision(effectiveAddressUserMessage, followupContext, llmPreDecomposeMeta, rawUserMessage);
const baseToolGate = typeof resolveAddressToolGateDecisionOverride === "function"
? resolveAddressToolGateDecisionOverride(effectiveAddressUserMessage, followupContext, llmPreDecomposeMeta, rawUserMessage)
: resolveBaseAddressToolGateDecision(effectiveAddressUserMessage, followupContext, llmPreDecomposeMeta, rawUserMessage);
const deterministicNonDomainGuard = Boolean(!dataScopeMetaQuery &&
!capabilityMetaQuery &&
!dataRetrievalSignal &&

View File

@ -3649,143 +3649,6 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage
}, userMessage);
}
}
function resolveAddressToolGateDecision(addressInputMessage, followupContext, llmPreDecomposeMeta = null, rawUserMessage = null) {
const repairedInputMessage = repairAddressMojibake(String(addressInputMessage ?? ""));
const rawMessageForGate = String(rawUserMessage ?? addressInputMessage ?? "");
const repairedRawMessageForGate = repairAddressMojibake(rawMessageForGate);
const dataScopeMetaQuery = hasAssistantDataScopeMetaQuestionSignal(rawMessageForGate) ||
hasAssistantDataScopeMetaQuestionSignal(repairedRawMessageForGate) ||
hasAssistantDataScopeMetaQuestionSignal(repairedInputMessage);
const rawCapabilityMetaQuery = shouldHandleAsAssistantCapabilityMetaQuery(rawMessageForGate) ||
shouldHandleAsAssistantCapabilityMetaQuery(repairedRawMessageForGate);
const capabilityMetaQuery = rawCapabilityMetaQuery ||
shouldHandleAsAssistantCapabilityMetaQuery(repairedInputMessage);
const rawDataRetrievalSignal = hasDataRetrievalRequestSignal(rawMessageForGate) ||
hasDataRetrievalRequestSignal(repairedRawMessageForGate);
const dataRetrievalSignal = rawDataRetrievalSignal ||
hasDataRetrievalRequestSignal(repairedInputMessage);
const rawCapabilityMetaOverride = rawCapabilityMetaQuery && !rawDataRetrievalSignal;
if (dataScopeMetaQuery || rawCapabilityMetaOverride || (capabilityMetaQuery && !dataRetrievalSignal)) {
return {
runAddressLane: false,
decision: "skip_address_lane",
reason: dataScopeMetaQuery ? "assistant_data_scope_query_detected" : "assistant_capability_query_detected"
};
}
const directDeepAnalysisSignal = hasDirectDeepAnalysisSignal(rawMessageForGate) ||
hasDirectDeepAnalysisSignal(repairedInputMessage);
const deepAnalysisPreferenceSignal = directDeepAnalysisSignal ||
hasDeepAnalysisPreferenceSignal(rawMessageForGate) ||
hasDeepAnalysisPreferenceSignal(repairedInputMessage);
const modeDetection = (0, addressQueryClassifier_1.detectAddressQuestionMode)(repairedInputMessage || addressInputMessage);
const modeDetectionRaw = (0, addressQueryClassifier_1.detectAddressQuestionMode)(String(addressInputMessage ?? ""));
const hasClassifierSignal = modeDetection.mode === "address_query" || modeDetectionRaw.mode === "address_query";
const intentResolution = (0, addressIntentResolver_1.resolveAddressIntent)(repairedInputMessage || addressInputMessage);
const intentResolutionRaw = (0, addressIntentResolver_1.resolveAddressIntent)(String(addressInputMessage ?? ""));
const hasIntentSignal = intentResolution.intent !== "unknown" || intentResolutionRaw.intent !== "unknown";
const llmContractMode = toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.mode);
const llmContractModeConfidence = toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.mode_confidence);
const llmContractIntent = toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.intent);
const semanticExtractionContract = llmPreDecomposeMeta?.semanticExtractionContract &&
typeof llmPreDecomposeMeta.semanticExtractionContract === "object"
? llmPreDecomposeMeta.semanticExtractionContract
: null;
const semanticCanonicalRecommended = semanticExtractionContract?.apply_canonical_recommended !== false;
const llmSupportedDeepAddressIntentSignal = llmContractMode === "deep_analysis" &&
/^(?:inventory_purchase_provenance_for_item|inventory_purchase_documents_for_item|inventory_sale_trace_for_item|inventory_profitability_for_item|inventory_purchase_to_sale_chain)$/u.test(llmContractIntent ?? "") &&
semanticCanonicalRecommended;
const llmCanonicalEntitySignal = /(?:\u0437\u0430\u043a\u0430\u0437\u0447\u0438\u043a|\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442|\u043a\u043e\u043c\u043f\u0430\u043d|customer|supplier|counterparty|company|vendor|client)/iu.test(compactWhitespace(repairedInputMessage.toLowerCase()));
const llmCanonicalAppliedSignal = Boolean(llmPreDecomposeMeta?.applied) && llmContractMode !== "deep_analysis";
const hasLlmCanonicalSignal = semanticCanonicalRecommended &&
Boolean(llmPreDecomposeMeta?.llmCanonicalCandidateDetected) &&
((llmContractMode === "address_query" && llmContractModeConfidence !== "low") ||
(llmCanonicalAppliedSignal &&
(hasStrongDataIntentSignal(repairedInputMessage) || llmCanonicalEntitySignal)));
const hasLlmCanonicalDataSignal = semanticCanonicalRecommended &&
Boolean(llmPreDecomposeMeta?.llmCanonicalCandidateDetected) &&
Boolean(llmPreDecomposeMeta?.applied) &&
(llmContractMode === "address_query" || llmContractMode === "unsupported" || llmContractMode === null) &&
hasStrongDataIntentSignal(repairedInputMessage);
const hasBusinessRankingAddressSignal = /(?:\u043a\u0442\u043e\s+(?:\u043d\u0430\u043c\s+)?(?:\u0431\u043e\u043b\u044c\u0448\u0435(?:\s+\u0432\u0441\u0435\u0433\u043e)?\s+\u043f\u0440\u0438\u043d\u0435\u0441|\u043f\u0440\u0438\u043d\u0435\u0441\s+\u0431\u043e\u043b\u044c\u0448\u0435(?:\s+\u0432\u0441\u0435\u0433\u043e)?).*(?:\u0434\u0435\u043d\u0435\u0433)?|who\s+brought\s+(?:us\s+)?(?:the\s+)?most\s+money)/iu.test(compactWhitespace(repairedInputMessage.toLowerCase()));
const sameDateAccountFollowupSignal = hasSameDateAccountFollowupSignalForPredecompose(rawMessageForGate) ||
hasSameDateAccountFollowupSignalForPredecompose(repairedInputMessage);
const hasLexicalAddressSignal = isAddressLlmPreDecomposeCandidate(addressInputMessage) ||
isAddressLlmPreDecomposeCandidate(repairedInputMessage) ||
hasAccountingSignal(addressInputMessage) ||
hasAccountingSignal(repairedInputMessage) ||
hasShortDebtMirrorFollowupSignal(rawMessageForGate) ||
hasShortDebtMirrorFollowupSignal(repairedInputMessage) ||
hasBusinessRankingAddressSignal ||
sameDateAccountFollowupSignal;
const hasUnsupportedLowConfidencePredecomposeSignal = llmContractMode === "unsupported" &&
(llmContractModeConfidence === "low" || llmContractModeConfidence === "medium") &&
llmContractIntent === "unknown";
const hasAnyAddressSignal =
hasClassifierSignal || hasIntentSignal || hasLlmCanonicalSignal || hasLlmCanonicalDataSignal || hasLexicalAddressSignal || llmSupportedDeepAddressIntentSignal;
const strongDataSignalFromRawMessage = hasStrongDataIntentSignal(rawMessageForGate) ||
hasDataRetrievalRequestSignal(rawMessageForGate) ||
hasAccountingSignal(rawMessageForGate) ||
hasSameDateAccountFollowupSignalForPredecompose(rawMessageForGate);
const strongDataSignalFromEffectiveMessage = hasStrongDataIntentSignal(repairedInputMessage) ||
hasAccountingSignal(repairedInputMessage) ||
hasDataRetrievalRequestSignal(repairedInputMessage);
if (!semanticCanonicalRecommended &&
llmContractIntent === "unknown" &&
!followupContext &&
!hasClassifierSignal &&
!hasIntentSignal &&
!hasLexicalAddressSignal &&
!llmSupportedDeepAddressIntentSignal &&
!strongDataSignalFromRawMessage &&
!strongDataSignalFromEffectiveMessage) {
return {
runAddressLane: false,
decision: "skip_address_lane",
reason: "llm_predecompose_semantic_guard_rejected"
};
}
if (hasUnsupportedLowConfidencePredecomposeSignal && !followupContext &&
!hasAnyAddressSignal &&
!llmSupportedDeepAddressIntentSignal &&
!strongDataSignalFromRawMessage &&
!strongDataSignalFromEffectiveMessage) {
return {
runAddressLane: false,
decision: "skip_address_lane",
reason: "llm_predecompose_unsupported_mode"
};
}
const hasMessageSignal = hasAnyAddressSignal || strongDataSignalFromRawMessage || strongDataSignalFromEffectiveMessage;
if (hasMessageSignal) {
return {
runAddressLane: true,
decision: "run_address_lane",
reason: hasClassifierSignal
? "address_mode_classifier_detected"
: hasIntentSignal
? "address_intent_resolver_detected"
: hasLlmCanonicalSignal
? "llm_canonical_candidate_detected"
: hasLlmCanonicalDataSignal
? "llm_canonical_data_signal_detected"
: llmSupportedDeepAddressIntentSignal
? "address_signal_detected"
: "address_signal_detected"
};
}
if (followupContext) {
return {
runAddressLane: true,
decision: "run_address_lane",
reason: "followup_context_detected"
};
}
return {
runAddressLane: false,
decision: "skip_address_lane",
reason: "no_address_signal_after_l0"
};
}
function hasLooseAllTimeAddressLookupSignal(text) {
const repaired = repairAddressMojibake(String(text ?? ""));
const normalized = compactWhitespace(repaired.toLowerCase());
@ -4181,7 +4044,7 @@ const assistantRoutePolicy = (0, assistantRoutePolicy_1.createAssistantRoutePoli
hasShortInventoryObjectFollowupSignal,
resolveRouteMemorySignals: assistantMemoryRecapPolicy.resolveRouteMemorySignals,
findLastAddressAssistantItem,
resolveAddressToolGateDecision,
hasAddressLlmPreDecomposeCandidate: isAddressLlmPreDecomposeCandidate,
hasSameDateAccountFollowupSignalForPredecompose,
hasLooseAllTimeAddressLookupSignal,
hasDeepAnalysisPreferenceSignal,

View File

@ -85,6 +85,7 @@ function buildPolicy(overrides: Record<string, unknown> = {}) {
contextualMemoryRecapFollowupDetected: false
}),
findLastAddressAssistantItem: () => null,
hasAddressLlmPreDecomposeCandidate: () => false,
resolveAddressToolGateDecision: () => ({
runAddressLane: false,
decision: "skip_address_lane",
@ -159,6 +160,28 @@ describe("assistantRoutePolicy", () => {
expect(decision.orchestrationContract?.semantic_route_arbitration?.supported_address_intent_detected).toBe(true);
});
it("uses internal route-policy tool gate when no external override is provided", () => {
const policy = buildPolicy({
resolveAddressToolGateDecision: undefined,
detectAddressQuestionMode: () => ({ mode: "address_query", confidence: "high" }),
resolveAddressIntent: () => ({ intent: "inventory_on_hand_as_of_date", confidence: "high" }),
hasAddressLlmPreDecomposeCandidate: () => true
});
const decision = policy.resolveAssistantOrchestrationDecision({
rawUserMessage: "какие остатки на складе",
effectiveAddressUserMessage: "какие остатки на складе",
followupContext: null,
llmPreDecomposeMeta: null,
useMock: false
});
expect(decision.runAddressLane).toBe(true);
expect(decision.toolGateDecision).toBe("run_address_lane");
expect(decision.toolGateReason).toBe("address_mode_classifier_detected");
expect(decision.livingMode).toBe("address_data");
});
it("does not let deep session continuation override an exact VAT period route", () => {
const policy = buildPolicy({
detectAddressQuestionMode: () => ({ mode: "address_query", confidence: "high" }),