Архитектура: перенести base address tool gate из assistantService в assistantRoutePolicy и выровнять top-level orchestration owner
This commit is contained in:
parent
d1ad8e2c1b
commit
ac3757bc59
|
|
@ -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;
|
- 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;
|
- 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.
|
- 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)
|
## Next Execution Slice (2026-04-18)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,149 @@ function shouldBypassStrictDeepInvestigationCueForAddressIntent(intent) {
|
||||||
return Boolean(intent && ADDRESS_INTENTS_ALLOW_STRICT_DEEP_INVESTIGATION_BYPASS.has(intent));
|
return Boolean(intent && ADDRESS_INTENTS_ALLOW_STRICT_DEEP_INVESTIGATION_BYPASS.has(intent));
|
||||||
}
|
}
|
||||||
function createAssistantRoutePolicy(deps) {
|
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) {
|
function hasInventoryRootRestatementFollowupSignal(text) {
|
||||||
const normalized = compactWhitespace(repairAddressMojibake(String(text ?? "")).toLowerCase()).replace(/ё/g, "е");
|
const normalized = compactWhitespace(repairAddressMojibake(String(text ?? "")).toLowerCase()).replace(/ё/g, "е");
|
||||||
if (!normalized) {
|
if (!normalized) {
|
||||||
|
|
@ -210,7 +352,9 @@ function createAssistantRoutePolicy(deps) {
|
||||||
!capabilityMetaQuery &&
|
!capabilityMetaQuery &&
|
||||||
!dataRetrievalSignal);
|
!dataRetrievalSignal);
|
||||||
const effectiveAddressFollowupSignal = explicitAddressFollowupSignal && !dangerOrCoercionSignal;
|
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 &&
|
const deterministicNonDomainGuard = Boolean(!dataScopeMetaQuery &&
|
||||||
!capabilityMetaQuery &&
|
!capabilityMetaQuery &&
|
||||||
!dataRetrievalSignal &&
|
!dataRetrievalSignal &&
|
||||||
|
|
|
||||||
|
|
@ -3693,142 +3693,6 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage
|
||||||
}, 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) {
|
function hasLooseAllTimeAddressLookupSignal(text) {
|
||||||
const repaired = repairAddressMojibake(String(text ?? ""));
|
const repaired = repairAddressMojibake(String(text ?? ""));
|
||||||
const normalized = compactWhitespace(repaired.toLowerCase());
|
const normalized = compactWhitespace(repaired.toLowerCase());
|
||||||
|
|
@ -4223,7 +4087,7 @@ const assistantRoutePolicy = (0, assistantRoutePolicy_1.createAssistantRoutePoli
|
||||||
hasShortInventoryObjectFollowupSignal,
|
hasShortInventoryObjectFollowupSignal,
|
||||||
resolveRouteMemorySignals: assistantMemoryRecapPolicy.resolveRouteMemorySignals,
|
resolveRouteMemorySignals: assistantMemoryRecapPolicy.resolveRouteMemorySignals,
|
||||||
findLastAddressAssistantItem,
|
findLastAddressAssistantItem,
|
||||||
resolveAddressToolGateDecision,
|
hasAddressLlmPreDecomposeCandidate: isAddressLlmPreDecomposeCandidate,
|
||||||
hasSameDateAccountFollowupSignalForPredecompose,
|
hasSameDateAccountFollowupSignalForPredecompose,
|
||||||
hasLooseAllTimeAddressLookupSignal,
|
hasLooseAllTimeAddressLookupSignal,
|
||||||
hasDeepAnalysisPreferenceSignal,
|
hasDeepAnalysisPreferenceSignal,
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,8 @@ export function createAssistantRoutePolicy(deps) {
|
||||||
isGroundedInventoryContextDebug,
|
isGroundedInventoryContextDebug,
|
||||||
resolveRouteMemorySignals,
|
resolveRouteMemorySignals,
|
||||||
findLastAddressAssistantItem,
|
findLastAddressAssistantItem,
|
||||||
resolveAddressToolGateDecision,
|
resolveAddressToolGateDecision: resolveAddressToolGateDecisionOverride,
|
||||||
|
hasAddressLlmPreDecomposeCandidate,
|
||||||
hasSameDateAccountFollowupSignalForPredecompose,
|
hasSameDateAccountFollowupSignalForPredecompose,
|
||||||
hasLooseAllTimeAddressLookupSignal,
|
hasLooseAllTimeAddressLookupSignal,
|
||||||
hasDeepAnalysisPreferenceSignal,
|
hasDeepAnalysisPreferenceSignal,
|
||||||
|
|
@ -88,6 +89,148 @@ export function createAssistantRoutePolicy(deps) {
|
||||||
resolveLivingAssistantModeDecision,
|
resolveLivingAssistantModeDecision,
|
||||||
resolveProviderExecutionState
|
resolveProviderExecutionState
|
||||||
} = deps;
|
} = 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) {
|
function hasInventoryRootRestatementFollowupSignal(text) {
|
||||||
const normalized = compactWhitespace(repairAddressMojibake(String(text ?? "")).toLowerCase()).replace(/ё/g, "е");
|
const normalized = compactWhitespace(repairAddressMojibake(String(text ?? "")).toLowerCase()).replace(/ё/g, "е");
|
||||||
if (!normalized) {
|
if (!normalized) {
|
||||||
|
|
@ -250,7 +393,9 @@ export function createAssistantRoutePolicy(deps) {
|
||||||
!capabilityMetaQuery &&
|
!capabilityMetaQuery &&
|
||||||
!dataRetrievalSignal);
|
!dataRetrievalSignal);
|
||||||
const effectiveAddressFollowupSignal = explicitAddressFollowupSignal && !dangerOrCoercionSignal;
|
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 &&
|
const deterministicNonDomainGuard = Boolean(!dataScopeMetaQuery &&
|
||||||
!capabilityMetaQuery &&
|
!capabilityMetaQuery &&
|
||||||
!dataRetrievalSignal &&
|
!dataRetrievalSignal &&
|
||||||
|
|
|
||||||
|
|
@ -3649,143 +3649,6 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage
|
||||||
}, 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) {
|
function hasLooseAllTimeAddressLookupSignal(text) {
|
||||||
const repaired = repairAddressMojibake(String(text ?? ""));
|
const repaired = repairAddressMojibake(String(text ?? ""));
|
||||||
const normalized = compactWhitespace(repaired.toLowerCase());
|
const normalized = compactWhitespace(repaired.toLowerCase());
|
||||||
|
|
@ -4181,7 +4044,7 @@ const assistantRoutePolicy = (0, assistantRoutePolicy_1.createAssistantRoutePoli
|
||||||
hasShortInventoryObjectFollowupSignal,
|
hasShortInventoryObjectFollowupSignal,
|
||||||
resolveRouteMemorySignals: assistantMemoryRecapPolicy.resolveRouteMemorySignals,
|
resolveRouteMemorySignals: assistantMemoryRecapPolicy.resolveRouteMemorySignals,
|
||||||
findLastAddressAssistantItem,
|
findLastAddressAssistantItem,
|
||||||
resolveAddressToolGateDecision,
|
hasAddressLlmPreDecomposeCandidate: isAddressLlmPreDecomposeCandidate,
|
||||||
hasSameDateAccountFollowupSignalForPredecompose,
|
hasSameDateAccountFollowupSignalForPredecompose,
|
||||||
hasLooseAllTimeAddressLookupSignal,
|
hasLooseAllTimeAddressLookupSignal,
|
||||||
hasDeepAnalysisPreferenceSignal,
|
hasDeepAnalysisPreferenceSignal,
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,7 @@ function buildPolicy(overrides: Record<string, unknown> = {}) {
|
||||||
contextualMemoryRecapFollowupDetected: false
|
contextualMemoryRecapFollowupDetected: false
|
||||||
}),
|
}),
|
||||||
findLastAddressAssistantItem: () => null,
|
findLastAddressAssistantItem: () => null,
|
||||||
|
hasAddressLlmPreDecomposeCandidate: () => false,
|
||||||
resolveAddressToolGateDecision: () => ({
|
resolveAddressToolGateDecision: () => ({
|
||||||
runAddressLane: false,
|
runAddressLane: false,
|
||||||
decision: "skip_address_lane",
|
decision: "skip_address_lane",
|
||||||
|
|
@ -159,6 +160,28 @@ describe("assistantRoutePolicy", () => {
|
||||||
expect(decision.orchestrationContract?.semantic_route_arbitration?.supported_address_intent_detected).toBe(true);
|
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", () => {
|
it("does not let deep session continuation override an exact VAT period route", () => {
|
||||||
const policy = buildPolicy({
|
const policy = buildPolicy({
|
||||||
detectAddressQuestionMode: () => ({ mode: "address_query", confidence: "high" }),
|
detectAddressQuestionMode: () => ({ mode: "address_query", confidence: "high" }),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue