ГЛОБАЛЬНЫЙ РЕФАКТОРИНГ АРХИТЕКТУРЫ - Рефакторинг этапов 3.1 - Добавлен строгий контракт семантического извлечения (валидность, качество, reason-codes, рекомендация применять canonical). Подключен semantic guard в predecompose/runtime: мягкое отклонение плохого canonical: отдельный safe-кейс для сырого полезного raw-фрагмента; fallback теперь выигрывает только когда это оправдано. Подключty semantic arbitration в tool-gate
This commit is contained in:
@@ -2714,12 +2714,19 @@ function hasSameDateAccountFollowupSignalForPredecompose(text) {
|
||||
}
|
||||
function attachAddressPredecomposeContract(meta, sourceMessage) {
|
||||
const canonicalMessage = toNonEmptyString(meta?.effectiveMessage) ?? String(sourceMessage ?? "");
|
||||
const predecomposeContract = (0, predecomposeContract_1.buildAddressLlmPredecomposeContractV1)({
|
||||
sourceMessage: String(sourceMessage ?? ""),
|
||||
canonicalMessage
|
||||
});
|
||||
const semanticExtractionContract = (0, predecomposeContract_1.buildAddressSemanticExtractionContractV1)({
|
||||
sourceMessage: String(sourceMessage ?? ""),
|
||||
canonicalMessage,
|
||||
predecomposeContract
|
||||
});
|
||||
return {
|
||||
...meta,
|
||||
predecomposeContract: (0, predecomposeContract_1.buildAddressLlmPredecomposeContractV1)({
|
||||
sourceMessage: String(sourceMessage ?? ""),
|
||||
canonicalMessage
|
||||
})
|
||||
predecomposeContract,
|
||||
semanticExtractionContract
|
||||
};
|
||||
}
|
||||
async function runAddressLlmPreDecompose(normalizerService, payload, userMessage) {
|
||||
@@ -2910,6 +2917,63 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage
|
||||
}, userMessage);
|
||||
}
|
||||
}
|
||||
const semanticContractForCandidate = (0, predecomposeContract_1.buildAddressSemanticExtractionContractV1)({
|
||||
sourceMessage: String(userMessage ?? ""),
|
||||
canonicalMessage: candidate
|
||||
});
|
||||
if (!semanticContractForCandidate.apply_canonical_recommended) {
|
||||
const sourceDataSignalDetected = Boolean(semanticContractForCandidate?.guard_hints?.source_data_signal_detected);
|
||||
const rawFragmentCandidatePreferred = Boolean(sourceDataSignalDetected &&
|
||||
candidateFromNormalized &&
|
||||
candidateFromNormalized === candidate &&
|
||||
toNonEmptyString(candidate));
|
||||
if (rawFragmentCandidatePreferred) {
|
||||
return attachAddressPredecomposeContract({
|
||||
...baseMeta,
|
||||
attempted: true,
|
||||
applied: true,
|
||||
traceId: normalized?.trace_id ?? null,
|
||||
llmCanonicalCandidateDetected: true,
|
||||
effectiveMessage: candidate,
|
||||
reason: "normalized_fragment_semantic_guard_raw_fragment_preferred",
|
||||
fallbackRuleHit: null,
|
||||
sanitizedUserMessage
|
||||
}, userMessage);
|
||||
}
|
||||
if (fallbackCandidate) {
|
||||
const fallbackSemanticContract = (0, predecomposeContract_1.buildAddressSemanticExtractionContractV1)({
|
||||
sourceMessage: String(userMessage ?? ""),
|
||||
canonicalMessage: String(fallbackCandidate.candidate ?? "")
|
||||
});
|
||||
const fallbackCompact = compactWhitespace(String(fallbackCandidate.candidate ?? "").toLowerCase());
|
||||
const sourceCompactForFallback = compactWhitespace(String(userMessage ?? "").toLowerCase());
|
||||
const fallbackApplied = fallbackCompact.length > 0 && fallbackCompact !== sourceCompactForFallback;
|
||||
if (fallbackApplied && fallbackSemanticContract.apply_canonical_recommended && !sourceDataSignalDetected) {
|
||||
return attachAddressPredecomposeContract({
|
||||
...baseMeta,
|
||||
attempted: true,
|
||||
applied: true,
|
||||
traceId: normalized?.trace_id ?? null,
|
||||
llmCanonicalCandidateDetected: true,
|
||||
effectiveMessage: String(fallbackCandidate.candidate ?? ""),
|
||||
reason: "fallback_rule_preferred_over_llm_candidate_semantic_guard",
|
||||
fallbackRuleHit: fallbackCandidate.rule,
|
||||
sanitizedUserMessage
|
||||
}, userMessage);
|
||||
}
|
||||
}
|
||||
return attachAddressPredecomposeContract({
|
||||
...baseMeta,
|
||||
attempted: true,
|
||||
applied: false,
|
||||
traceId: normalized?.trace_id ?? null,
|
||||
llmCanonicalCandidateDetected: true,
|
||||
effectiveMessage: userMessage,
|
||||
reason: "normalized_fragment_rejected_semantic_guard",
|
||||
fallbackRuleHit: null,
|
||||
sanitizedUserMessage
|
||||
}, userMessage);
|
||||
}
|
||||
const sourceCompact = compactWhitespace(String(userMessage ?? "").toLowerCase());
|
||||
const candidateCompact = compactWhitespace(candidate.toLowerCase());
|
||||
const applied = sourceCompact !== candidateCompact;
|
||||
@@ -2986,13 +3050,20 @@ function resolveAddressToolGateDecision(addressInputMessage, followupContext, ll
|
||||
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 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 = Boolean(llmPreDecomposeMeta?.llmCanonicalCandidateDetected) &&
|
||||
const hasLlmCanonicalSignal = semanticCanonicalRecommended &&
|
||||
Boolean(llmPreDecomposeMeta?.llmCanonicalCandidateDetected) &&
|
||||
((llmContractMode === "address_query" && llmContractModeConfidence !== "low") ||
|
||||
(llmCanonicalAppliedSignal &&
|
||||
(hasStrongDataIntentSignal(repairedInputMessage) || llmCanonicalEntitySignal)));
|
||||
const hasLlmCanonicalDataSignal = Boolean(llmPreDecomposeMeta?.llmCanonicalCandidateDetected) &&
|
||||
const hasLlmCanonicalDataSignal = semanticCanonicalRecommended &&
|
||||
Boolean(llmPreDecomposeMeta?.llmCanonicalCandidateDetected) &&
|
||||
Boolean(llmPreDecomposeMeta?.applied) &&
|
||||
(llmContractMode === "address_query" || llmContractMode === "unsupported" || llmContractMode === null) &&
|
||||
hasStrongDataIntentSignal(repairedInputMessage);
|
||||
@@ -3013,6 +3084,17 @@ function resolveAddressToolGateDecision(addressInputMessage, followupContext, ll
|
||||
const strongDataSignalFromEffectiveMessage = hasStrongDataIntentSignal(repairedInputMessage) ||
|
||||
hasAccountingSignal(repairedInputMessage) ||
|
||||
hasDataRetrievalRequestSignal(repairedInputMessage);
|
||||
if (!semanticCanonicalRecommended &&
|
||||
llmContractIntent === "unknown" &&
|
||||
!followupContext &&
|
||||
!hasClassifierSignal &&
|
||||
!strongDataSignalFromRawMessage) {
|
||||
return {
|
||||
runAddressLane: false,
|
||||
decision: "skip_address_lane",
|
||||
reason: "llm_predecompose_semantic_guard_rejected"
|
||||
};
|
||||
}
|
||||
if (hasUnsupportedLowConfidencePredecomposeSignal && !followupContext &&
|
||||
!hasAnyAddressSignal &&
|
||||
!strongDataSignalFromRawMessage &&
|
||||
@@ -3198,11 +3280,21 @@ export function resolveAssistantOrchestrationDecision(input) {
|
||||
const modeDetection = (0, addressQueryClassifier_1.detectAddressQuestionMode)(modeSample);
|
||||
const intentResolution = (0, addressIntentResolver_1.resolveAddressIntent)(modeSample);
|
||||
const llmContractIntent = toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.intent);
|
||||
const semanticExtractionContract = llmPreDecomposeMeta?.semanticExtractionContract &&
|
||||
typeof llmPreDecomposeMeta.semanticExtractionContract === "object"
|
||||
? llmPreDecomposeMeta.semanticExtractionContract
|
||||
: null;
|
||||
const semanticContractValid = semanticExtractionContract?.valid !== false;
|
||||
const semanticApplyCanonicalRecommended = semanticExtractionContract?.apply_canonical_recommended !== false;
|
||||
const semanticReasonCodes = Array.isArray(semanticExtractionContract?.reason_codes)
|
||||
? semanticExtractionContract.reason_codes
|
||||
: [];
|
||||
const strictDeepInvestigationCueDetected = hasStrictDeepInvestigationCue(rawUserMessage) ||
|
||||
hasStrictDeepInvestigationCue(repairedRawUserMessage) ||
|
||||
hasStrictDeepInvestigationCue(effectiveAddressUserMessage) ||
|
||||
hasStrictDeepInvestigationCue(repairedEffectiveAddressUserMessage);
|
||||
const keepAddressLaneByIntent = Boolean((intentResolution.intent && ADDRESS_INTENTS_KEEP_ADDRESS_LANE.has(intentResolution.intent)) ||
|
||||
const keepAddressLaneByIntent = semanticApplyCanonicalRecommended &&
|
||||
Boolean((intentResolution.intent && ADDRESS_INTENTS_KEEP_ADDRESS_LANE.has(intentResolution.intent)) ||
|
||||
(llmContractIntent && ADDRESS_INTENTS_KEEP_ADDRESS_LANE.has(llmContractIntent))) &&
|
||||
!strictDeepInvestigationCueDetected;
|
||||
const strongDataSignal = hasStrongDataIntentSignal(rawUserMessage) ||
|
||||
@@ -3402,6 +3494,9 @@ export function resolveAssistantOrchestrationDecision(input) {
|
||||
address_intent_confidence: intentResolution.confidence,
|
||||
strong_data_signal_detected: strongDataSignal,
|
||||
data_retrieval_signal_detected: dataRetrievalSignal,
|
||||
semantic_contract_valid: semanticContractValid,
|
||||
semantic_apply_canonical_recommended: semanticApplyCanonicalRecommended,
|
||||
semantic_reason_codes: semanticReasonCodes,
|
||||
followup_context_detected: Boolean(followupContext),
|
||||
unsupported_address_intent_fallback_to_deep: unsupportedAddressIntentFallbackToDeep,
|
||||
deep_analysis_signal_fallback_to_deep: deepAnalysisSignalFallbackToDeep,
|
||||
|
||||
Reference in New Issue
Block a user