ARCH: защитить document→payments pivot от discovery override

This commit is contained in:
2026-04-23 19:35:10 +03:00
parent 65d15c156b
commit 19137a698f
8 changed files with 495 additions and 8 deletions
@@ -274,6 +274,27 @@ function hasMatchedFactualAddressContinuationTarget(input, entryPoint) {
const targetIntent = toNonEmptyString(dialogContinuationContract?.target_intent);
return Boolean(detectedIntent && targetIntent && detectedIntent === targetIntent);
}
function hasMatchedFactualSuggestedIntentPivotTarget(input, entryPoint) {
if (!isDiscoveryReadyAddressCandidate(input, entryPoint)) {
return false;
}
if (!hasEffectivelyFactualAddressReply(input)) {
return false;
}
const detectedIntent = toNonEmptyString(input.addressRuntimeMeta?.detected_intent);
const dialogContinuationContract = toRecordObject(input.addressRuntimeMeta?.dialogContinuationContract) ??
toRecordObject(input.addressRuntimeMeta?.dialog_continuation_contract_v2);
const targetIntent = toNonEmptyString(dialogContinuationContract?.target_intent);
const decision = toNonEmptyString(dialogContinuationContract?.decision);
const selectionMode = toNonEmptyString(dialogContinuationContract?.intent_selection_mode);
const suggestedPivotSignal = dialogContinuationContract?.suggested_intent_pivot_signal === true;
return Boolean(detectedIntent &&
targetIntent &&
detectedIntent === targetIntent &&
(decision === "switch_to_suggested" ||
selectionMode === "switch_to_suggested_intent" ||
suggestedPivotSignal));
}
function hasFullConfirmedFactualAddressReply(input, entryPoint) {
if (!isDiscoveryReadyAddressCandidate(input, entryPoint)) {
return false;
@@ -309,6 +330,7 @@ function applyAssistantMcpDiscoveryResponsePolicy(input) {
const alignedFactualAddressReply = hasAlignedFactualAddressReply(input, entryPoint);
const semanticConflictWithDiscoveryTurnMeaning = hasSemanticConflictWithDiscoveryTurnMeaning(input, entryPoint);
const matchedFactualAddressContinuationTarget = hasMatchedFactualAddressContinuationTarget(input, entryPoint);
const matchedFactualSuggestedIntentPivotTarget = hasMatchedFactualSuggestedIntentPivotTarget(input, entryPoint);
const fullConfirmedFactualAddressReply = hasFullConfirmedFactualAddressReply(input, entryPoint);
const runtimeAdjustedExactReply = hasRuntimeAdjustedExactReply(input, entryPoint);
if (!entryPoint) {
@@ -335,6 +357,9 @@ function applyAssistantMcpDiscoveryResponsePolicy(input) {
if (matchedFactualAddressContinuationTarget) {
pushReason(reasonCodes, "mcp_discovery_response_policy_keep_factual_address_continuation_target");
}
if (matchedFactualSuggestedIntentPivotTarget) {
pushReason(reasonCodes, "mcp_discovery_response_policy_keep_factual_suggested_intent_pivot_target");
}
if (fullConfirmedFactualAddressReply) {
pushReason(reasonCodes, "mcp_discovery_response_policy_keep_full_confirmed_factual_address_reply");
}
@@ -360,6 +385,7 @@ function applyAssistantMcpDiscoveryResponsePolicy(input) {
(unsupportedBoundary || discoveryReadyChatCandidate || discoveryReadyDeepCandidate || discoveryReadyAddressCandidate) &&
!alignedFactualAddressReply &&
!matchedFactualAddressContinuationTarget &&
!matchedFactualSuggestedIntentPivotTarget &&
!fullConfirmedFactualAddressReply &&
!runtimeAdjustedExactReply &&
!(deterministicBroadBusinessEvaluationReply && candidate.candidate_status === "clarification_candidate") &&
@@ -7,6 +7,78 @@ function createAssistantTransitionPolicy(deps) {
function normalizeFollowupText(value) {
return deps.compactWhitespace(deps.repairAddressMojibake(String(value ?? "")).toLowerCase()).replace(/ё/g, "е");
}
function hasBankOperationsPivotCue(text) {
const normalized = normalizeFollowupText(text);
if (!normalized) {
return false;
}
return /(?:платеж|платёж|банк|банковск|операц|выписк|поступлен|списан)/iu.test(normalized);
}
function hasContractsPivotCue(text) {
const normalized = normalizeFollowupText(text);
if (!normalized) {
return false;
}
return /(?:РґРѕРіРѕРІРѕСЂ)/iu.test(normalized);
}
function hasDocumentsPivotCue(text) {
const normalized = normalizeFollowupText(text);
if (!normalized) {
return false;
}
return /(?:документ|счет|счёт|накладн|акт)/iu.test(normalized);
}
function hasReadableBankOperationsPivotCue(text) {
const normalized = normalizeFollowupText(text).replace(/ё/g, "е");
if (!normalized) {
return false;
}
return /(?:платеж|оплат|банк|банковск|операц|поступлен|списан|выписк|перевод|payment|bank|transaction)/iu.test(normalized);
}
function hasReadableContractsPivotCue(text) {
const normalized = normalizeFollowupText(text).replace(/ё/g, "е");
if (!normalized) {
return false;
}
return /(?:договор|контракт|соглашен|contract|agreement)/iu.test(normalized);
}
function hasReadableDocumentsPivotCue(text) {
const normalized = normalizeFollowupText(text).replace(/ё/g, "е");
if (!normalized) {
return false;
}
return /(?:документ|счет|счет-фактур|накладн|акт|реализац|document|invoice|receipt)/iu.test(normalized);
}
function selectSuggestedIntentByPivotCue(suggestedIntents, userMessage, alternateMessage = null) {
if (!Array.isArray(suggestedIntents) || suggestedIntents.length === 0) {
return null;
}
const samples = [userMessage, alternateMessage].filter((item) => deps.toNonEmptyString(item));
if (samples.length === 0) {
return null;
}
if (suggestedIntents.includes("bank_operations_by_counterparty") &&
samples.some((sample) => hasBankOperationsPivotCue(sample) || hasReadableBankOperationsPivotCue(sample))) {
return "bank_operations_by_counterparty";
}
if (suggestedIntents.includes("bank_operations_by_contract") &&
samples.some((sample) => hasBankOperationsPivotCue(sample) || hasReadableBankOperationsPivotCue(sample))) {
return "bank_operations_by_contract";
}
if (suggestedIntents.includes("list_contracts_by_counterparty") &&
samples.some((sample) => hasContractsPivotCue(sample) || hasReadableContractsPivotCue(sample))) {
return "list_contracts_by_counterparty";
}
if (suggestedIntents.includes("list_documents_by_counterparty") &&
samples.some((sample) => hasDocumentsPivotCue(sample) || hasReadableDocumentsPivotCue(sample))) {
return "list_documents_by_counterparty";
}
if (suggestedIntents.includes("list_documents_by_contract") &&
samples.some((sample) => hasDocumentsPivotCue(sample) || hasReadableDocumentsPivotCue(sample))) {
return "list_documents_by_contract";
}
return null;
}
function hasSelectedObjectInventoryScopeSignal(text) {
const normalized = normalizeFollowupText(text);
if (!normalized) {
@@ -352,6 +424,10 @@ function createAssistantTransitionPolicy(deps) {
const carryoverSourceDebug = previousAddressDebug ??
(hasOrganizationClarificationContinuation ? lastOrganizationClarificationDebug : null);
const followupOffer = carryoverSourceDebug ? deps.buildAddressFollowupOffer(carryoverSourceDebug) : null;
const suggestedIntentFromPivotCue = selectSuggestedIntentByPivotCue(Array.isArray(followupOffer?.suggested_intents) ? followupOffer.suggested_intents : [], userMessage, alternateMessage);
const hasSuggestedIntentPivotSignal = Boolean(previousAddressDebug) &&
Boolean(followupOffer?.enabled) &&
Boolean(suggestedIntentFromPivotCue);
const hasImplicitContinuationSignal = Boolean(previousAddressDebug) &&
Boolean(followupOffer?.enabled) &&
(deps.isImplicitAddressContinuationByLlm(userMessage, llmPreDecomposeMeta) ||
@@ -423,6 +499,7 @@ function createAssistantTransitionPolicy(deps) {
hasAlternateIndexReferenceSignal ||
hasOrganizationClarificationContinuation ||
hasImplicitContinuationSignal ||
hasSuggestedIntentPivotSignal ||
inventoryShortFollowupPrimary ||
inventoryShortFollowupAlternate ||
hasInventoryRootTemporalFollowupPrimary ||
@@ -575,9 +652,9 @@ function createAssistantTransitionPolicy(deps) {
if (debtRoleSwapIntent) {
previousIntent = debtRoleSwapIntent;
}
if (hasImplicitContinuationSignal) {
if (hasImplicitContinuationSignal || hasSuggestedIntentPivotSignal) {
const suggestedIntent = Array.isArray(followupOffer?.suggested_intents)
? deps.toNonEmptyString(followupOffer.suggested_intents[0])
? suggestedIntentFromPivotCue ?? deps.toNonEmptyString(followupOffer.suggested_intents[0])
: null;
const keepPreviousIntent = shouldKeepPreviousIntentForShortCounterpartyRetargetV2(userMessage, sourceIntent);
if (suggestedIntent && !keepPreviousIntent) {
@@ -789,7 +866,8 @@ function createAssistantTransitionPolicy(deps) {
previousAddressAnchor: previousAnchor,
previousSourceIntent: sourceIntent,
followupSelectionMode,
hasImplicitContinuationSignal
hasImplicitContinuationSignal,
hasSuggestedIntentPivotSignal
};
}
function buildAddressDialogContinuationContractV2(userMessage, effectiveMessage, carryoverMeta, llmPreDecomposeMeta) {
@@ -809,6 +887,7 @@ function createAssistantTransitionPolicy(deps) {
? carryoverTargetIntent ?? rootIntent ?? explicitIntent ?? null
: carryoverTargetIntent ?? explicitIntent ?? deps.toNonEmptyString(carryoverMeta?.previousAddressIntent) ?? null;
const hasImplicitContinuationSignal = Boolean(carryoverMeta?.hasImplicitContinuationSignal);
const hasSuggestedIntentPivotSignal = Boolean(carryoverMeta?.hasSuggestedIntentPivotSignal);
const rewrittenByPredecompose = deps.compactWhitespace(sourceMessage.toLowerCase()) !== deps.compactWhitespace(canonicalMessage.toLowerCase());
const hasExplicitIntent = Boolean(explicitIntent);
const decision = !hasFollowupContext
@@ -823,6 +902,9 @@ function createAssistantTransitionPolicy(deps) {
if (hasImplicitContinuationSignal) {
reasons.push("implicit_continuation_by_llm");
}
if (hasSuggestedIntentPivotSignal) {
reasons.push("suggested_intent_followup_pivot");
}
if (rewrittenByPredecompose) {
reasons.push("effective_message_rewritten_by_predecompose");
}
@@ -847,7 +929,8 @@ function createAssistantTransitionPolicy(deps) {
intent_selection_mode: selectionMode,
anchor_type: carryoverMeta?.followupContext?.previous_anchor_type ?? null,
anchor_value: carryoverMeta?.followupContext?.previous_anchor_value ?? null,
implicit_continuation_signal: hasImplicitContinuationSignal
implicit_continuation_signal: hasImplicitContinuationSignal,
suggested_intent_pivot_signal: hasSuggestedIntentPivotSignal
};
}
return {