Post-F: закрепить семантическую целостность MCP-цепочек
This commit is contained in:
@@ -82,6 +82,15 @@ function userFacingUnknowns(values: string[]): string[] {
|
||||
return uniqueStrings(values).filter((value) => !isInternalMechanicsLine(value));
|
||||
}
|
||||
|
||||
function rankedValueFlowUnknownLines(pilot: AssistantMcpDiscoveryPilotExecutionContract): string[] {
|
||||
if (!pilot.derived_ranked_value_flow) {
|
||||
return userFacingUnknowns(pilot.evidence.unknown_facts);
|
||||
}
|
||||
const ranking = pilot.derived_ranked_value_flow;
|
||||
const period = ranking.period_scope ? `периода ${ranking.period_scope}` : "проверенного окна";
|
||||
return [`Полный рейтинг контрагентов вне ${period} этим поиском не подтвержден.`];
|
||||
}
|
||||
|
||||
function userFacingLimitations(values: string[]): string[] {
|
||||
return uniqueStrings(values).filter((value) => !isInternalMechanicsLine(value));
|
||||
}
|
||||
@@ -856,7 +865,9 @@ export function buildAssistantMcpDiscoveryAnswerDraft(
|
||||
if (monthlyConfirmedLines.length > 0) {
|
||||
pushReason(reasonCodes, "answer_contains_monthly_breakdown");
|
||||
}
|
||||
const confirmedLines = derivedValueLine
|
||||
const confirmedLines = pilot.derived_ranked_value_flow && derivedValueLine
|
||||
? [derivedValueLine]
|
||||
: derivedValueLine
|
||||
? [...pilot.evidence.confirmed_facts, derivedValueLine, ...monthlyConfirmedLines]
|
||||
: derivedEntityResolutionLine
|
||||
? [...pilot.evidence.confirmed_facts, derivedEntityResolutionLine]
|
||||
@@ -871,7 +882,7 @@ export function buildAssistantMcpDiscoveryAnswerDraft(
|
||||
headline: headlineFor(mode, pilot),
|
||||
confirmed_lines: uniqueStrings(confirmedLines),
|
||||
inference_lines: uniqueStrings(inferenceLines),
|
||||
unknown_lines: userFacingUnknowns(pilot.evidence.unknown_facts),
|
||||
unknown_lines: rankedValueFlowUnknownLines(pilot),
|
||||
limitation_lines: userFacingLimitations([...pilot.query_limitations, ...pilot.evidence.query_limitations]),
|
||||
next_step_line: nextStepFor(mode, pilot),
|
||||
internal_mechanics_allowed: false,
|
||||
|
||||
@@ -102,9 +102,46 @@ function isReferentialEntityPlaceholder(value: string): boolean {
|
||||
]).has(normalizeRussianComparableText(value));
|
||||
}
|
||||
|
||||
function isReferentialOrganizationPlaceholder(value: string | null): boolean {
|
||||
if (!value) {
|
||||
return false;
|
||||
}
|
||||
return new Set([
|
||||
"эта организация",
|
||||
"этой организации",
|
||||
"этой организацией",
|
||||
"эту организацию",
|
||||
"эта компания",
|
||||
"этой компании",
|
||||
"этой компанией",
|
||||
"эту компанию",
|
||||
"наша организация",
|
||||
"нашей организации",
|
||||
"нашей компанией"
|
||||
]).has(normalizeRussianComparableText(value));
|
||||
}
|
||||
|
||||
function isValueFlowPredicateEntityCandidate(value: string | null): boolean {
|
||||
if (!value) {
|
||||
return false;
|
||||
}
|
||||
const text = compactLower(value);
|
||||
const looksLikeRankingPredicate =
|
||||
/(?:прин[её]с|принес|выручк|доходн|больше\s+всего|наибольш)/iu.test(text) &&
|
||||
/(?:организац|компан|ден[её]г|выручк|поступлен|плат[её]ж)/iu.test(text);
|
||||
if (!looksLikeRankingPredicate) {
|
||||
return false;
|
||||
}
|
||||
return !/(?<!\p{L})(?:ооо|ип|ао|пао|зао|llc|inc|corp)(?!\p{L})/iu.test(text);
|
||||
}
|
||||
|
||||
function isInvalidEntityCandidate(value: string | null): boolean {
|
||||
return Boolean(value && (isReferentialEntityPlaceholder(value) || isValueFlowPredicateEntityCandidate(value)));
|
||||
}
|
||||
|
||||
function normalizeFollowupCounterpartyCandidate(value: unknown): string | null {
|
||||
const text = candidateValue(value);
|
||||
if (!text || isReferentialEntityPlaceholder(text)) {
|
||||
if (!text || isInvalidEntityCandidate(text)) {
|
||||
return null;
|
||||
}
|
||||
return text;
|
||||
@@ -119,7 +156,7 @@ function pushScopedEntityCandidate(
|
||||
if (!text) {
|
||||
return;
|
||||
}
|
||||
if (groundedFollowupEntity && isReferentialEntityPlaceholder(text)) {
|
||||
if ((groundedFollowupEntity && isReferentialEntityPlaceholder(text)) || isValueFlowPredicateEntityCandidate(text)) {
|
||||
return;
|
||||
}
|
||||
pushUnique(target, text);
|
||||
@@ -138,7 +175,7 @@ function pushNormalizedEntityResolutionCandidate(target: string[], value: unknow
|
||||
return;
|
||||
}
|
||||
const normalized = canonicalizeEntityResolutionCandidate(text);
|
||||
if (normalized && !target.includes(normalized)) {
|
||||
if (normalized && !isInvalidEntityCandidate(normalized) && !target.includes(normalized)) {
|
||||
target.push(normalized);
|
||||
}
|
||||
}
|
||||
@@ -175,11 +212,17 @@ function collectEntityCandidates(value: unknown): string[] {
|
||||
const result: string[] = [];
|
||||
if (Array.isArray(value)) {
|
||||
for (const item of value) {
|
||||
pushUnique(result, candidateValue(item));
|
||||
const candidate = candidateValue(item);
|
||||
if (!isInvalidEntityCandidate(candidate)) {
|
||||
pushUnique(result, candidate);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
pushUnique(result, candidateValue(value));
|
||||
const candidate = candidateValue(value);
|
||||
if (!isInvalidEntityCandidate(candidate)) {
|
||||
pushUnique(result, candidate);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -188,9 +231,10 @@ function collectPredecomposeEntities(predecompose: Record<string, unknown> | nul
|
||||
organization: string | null;
|
||||
} {
|
||||
const entities = toRecordObject(predecompose?.entities);
|
||||
const organization = toNonEmptyString(entities?.organization);
|
||||
return {
|
||||
counterparty: toNonEmptyString(entities?.counterparty),
|
||||
organization: toNonEmptyString(entities?.organization)
|
||||
organization: isReferentialOrganizationPlaceholder(organization) ? null : organization
|
||||
};
|
||||
}
|
||||
|
||||
@@ -767,7 +811,7 @@ function hasMetadataDownstreamContinuationSignal(text: string): boolean {
|
||||
function hasEntityResolutionSignal(text: string): boolean {
|
||||
const hasSearchVerb = /(?:найд(?:и|ите|ем|у)|поищ(?:и|ите|ем)|найти|поиск|search|find|look\s*up)/iu.test(text);
|
||||
const hasEntityNoun =
|
||||
/(?:контрагент(?:а|ов)?|поставщик(?:а|ов)?|клиент(?:а|ов)?|counterpart(?:y|ies)|supplier(?:s)?|customer(?:s)?)/iu.test(
|
||||
/(?:контрагент(?:а|ов|у|ом|е)?|поставщик(?:а|ов|у|ом|е)?|клиент(?:а|ов|у|ом|е)?|counterpart(?:y|ies)|supplier(?:s)?|customer(?:s)?)/iu.test(
|
||||
text
|
||||
);
|
||||
return hasSearchVerb && hasEntityNoun;
|
||||
@@ -777,8 +821,9 @@ function normalizeEntityResolutionCandidate(value: string): string {
|
||||
return value
|
||||
.replace(/^(?:в\s*1с\s+|в\s+1c\s+|по\s+имени\s+)/iu, "")
|
||||
.replace(/[?!.]+$/gu, "")
|
||||
.replace(/^(?:контрагент(?:а|ов)?|поставщик(?:а|ов)?|клиент(?:а|ов)?)\s+/iu, "")
|
||||
.replace(/^(?:контрагент(?:а|ов|у|ом|е)?|поставщик(?:а|ов|у|ом|е)?|клиент(?:а|ов|у|ом|е)?)\s+/iu, "")
|
||||
.replace(/^(?:counterpart(?:y|ies)|supplier(?:s)?|customer(?:s)?)\s+/iu, "")
|
||||
.replace(/^группу\s+/iu, "Группа ")
|
||||
.replace(/^[«"'\s]+|[»"'\s]+$/gu, "")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
@@ -786,8 +831,9 @@ function normalizeEntityResolutionCandidate(value: string): string {
|
||||
|
||||
function rawEntityResolutionCandidate(text: string): string | null {
|
||||
const patterns = [
|
||||
/(?:найд(?:и|ите|ем|у)|поищ(?:и|ите|ем)|найти|search|find|look\s*up)\s+(?:в\s*1с\s+|в\s+1c\s+)?(?:контрагент(?:а|ов)?|поставщик(?:а|ов)?|клиент(?:а|ов)?|counterpart(?:y|ies)|supplier(?:s)?|customer(?:s)?)\s+(.+)$/iu,
|
||||
/(?:контрагент(?:а|ов)?|поставщик(?:а|ов)?|клиент(?:а|ов)?|counterpart(?:y|ies)|supplier(?:s)?|customer(?:s)?)\s+(.+?)\s+(?:найд(?:и|ите|ем|у)|поищ(?:и|ите|ем)|найти|search|find|look\s*up)\b/iu
|
||||
/(?:найд(?:и|ите|ем|у)|поищ(?:и|ите|ем)|найти|search|find|look\s*up)\s+(?:в\s*1с\s+|в\s+1c\s+)?(?:контрагент(?:а|ов|у|ом|е)?|поставщик(?:а|ов|у|ом|е)?|клиент(?:а|ов|у|ом|е)?|counterpart(?:y|ies)|supplier(?:s)?|customer(?:s)?)\s+(.+)$/iu,
|
||||
/(?:найд(?:и|ите|ем|у)|поищ(?:и|ите|ем)|найти|search|find|look\s*up)\s+(?:в\s*1с\s+|в\s+1c\s+)(.+)$/iu,
|
||||
/(?:контрагент(?:а|ов|у|ом|е)?|поставщик(?:а|ов|у|ом|е)?|клиент(?:а|ов|у|ом|е)?|counterpart(?:y|ies)|supplier(?:s)?|customer(?:s)?)\s+(.+?)\s+(?:найд(?:и|ите|ем|у)|поищ(?:и|ите|ем)|найти|search|find|look\s*up)\b/iu
|
||||
];
|
||||
for (const pattern of patterns) {
|
||||
const match = text.match(pattern);
|
||||
@@ -1085,6 +1131,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||
: null;
|
||||
const entityResolutionSignal =
|
||||
rawEntityResolutionSignal || Boolean(rawEntityCandidate) || Boolean(entityResolutionClarificationCandidate);
|
||||
const rawEntitySearchOverridesStaleScope = Boolean(rawEntityCandidate && entityResolutionSignal);
|
||||
const rawDomain = toNonEmptyString(assistantTurnMeaning?.asked_domain_family);
|
||||
const rawAction = toNonEmptyString(assistantTurnMeaning?.asked_action_family);
|
||||
const rawAggregationAxis = toNonEmptyString(assistantTurnMeaning?.asked_aggregation_axis);
|
||||
@@ -1105,11 +1152,17 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||
hasMovementEvidenceFollowupSignal(rawText) ||
|
||||
hasPronounMovementEvidenceFollowupSignal(rawText);
|
||||
const assistantTurnMeaningDateScope = toNonEmptyString(assistantTurnMeaning?.explicit_date_scope);
|
||||
const assistantTurnMeaningOrganizationScope = toNonEmptyString(assistantTurnMeaning?.explicit_organization_scope);
|
||||
const rawAssistantTurnMeaningOrganizationScope = toNonEmptyString(assistantTurnMeaning?.explicit_organization_scope);
|
||||
const assistantTurnMeaningOrganizationScope = isReferentialOrganizationPlaceholder(
|
||||
rawAssistantTurnMeaningOrganizationScope
|
||||
)
|
||||
? null
|
||||
: rawAssistantTurnMeaningOrganizationScope;
|
||||
const rawOrganizationMentionSignal = hasOrganizationScopeSignalUtf8(rawText);
|
||||
const rawOrganizationScope = extractOrganizationScopeFromRawText(rawUserText ?? rawEffectiveText ?? rawSignalSourceText);
|
||||
const currentTurnFreshOrganizationScope = rawOrganizationScope ?? predecomposeEntities.organization;
|
||||
const currentTurnOrganizationScope =
|
||||
rawOrganizationScope ?? predecomposeEntities.organization ?? assistantTurnMeaningOrganizationScope;
|
||||
currentTurnFreshOrganizationScope ?? assistantTurnMeaningOrganizationScope;
|
||||
const explicitOrganizationScopeSignal = Boolean(rawOrganizationMentionSignal && currentTurnOrganizationScope);
|
||||
const organizationClarificationFollowupApplicable = Boolean(
|
||||
followupSeed.domain === "counterparty_value" &&
|
||||
@@ -1492,7 +1545,9 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||
metadataGroundedMovementLaneApplicable)
|
||||
);
|
||||
const metadataLaneScopeHint =
|
||||
explicitCurrentCounterpartyOverridesFollowupEntity
|
||||
rawEntitySearchOverridesStaleScope
|
||||
? null
|
||||
: explicitCurrentCounterpartyOverridesFollowupEntity
|
||||
? null
|
||||
: rawMetadataScopeHint ??
|
||||
followupSeed.metadataScopeHint ??
|
||||
@@ -1506,6 +1561,8 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||
);
|
||||
const groundedFollowupEntity = metadataScopedLaneWithoutSubject
|
||||
? null
|
||||
: rawEntitySearchOverridesStaleScope
|
||||
? null
|
||||
: explicitCurrentCounterpartyOverridesFollowupEntity
|
||||
? null
|
||||
: followupSeed.counterparty ?? followupSeed.discoveryEntity;
|
||||
@@ -1514,10 +1571,16 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||
pushNormalizedEntityResolutionCandidate(entityCandidates, entityResolutionClarificationCandidate);
|
||||
pushNormalizedEntityResolutionCandidate(entityCandidates, rawEntityCandidate);
|
||||
for (const candidate of collectEntityCandidates(assistantTurnMeaning?.explicit_entity_candidates)) {
|
||||
pushNormalizedEntityResolutionCandidate(entityCandidates, candidate);
|
||||
if (!rawEntitySearchOverridesStaleScope || sameScopedName(candidate, rawEntityCandidate)) {
|
||||
pushNormalizedEntityResolutionCandidate(entityCandidates, candidate);
|
||||
}
|
||||
}
|
||||
if (!rawEntitySearchOverridesStaleScope || sameScopedName(normalizedPredecomposeCounterparty, rawEntityCandidate)) {
|
||||
pushNormalizedEntityResolutionCandidate(entityCandidates, normalizedPredecomposeCounterparty);
|
||||
}
|
||||
if (!rawEntitySearchOverridesStaleScope) {
|
||||
pushNormalizedEntityResolutionCandidate(entityCandidates, followupSeed.counterparty);
|
||||
}
|
||||
pushNormalizedEntityResolutionCandidate(entityCandidates, normalizedPredecomposeCounterparty);
|
||||
pushNormalizedEntityResolutionCandidate(entityCandidates, followupSeed.counterparty);
|
||||
} else {
|
||||
if (groundedFollowupEntity) {
|
||||
pushUnique(entityCandidates, groundedFollowupEntity);
|
||||
@@ -1562,8 +1625,11 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||
pushUnique(entityCandidates, followupSeed.organization);
|
||||
}
|
||||
const explicitOrganizationScope =
|
||||
valueFlowOrganizationStaysScope || !openScopeValueFlowWithoutCounterparty
|
||||
? currentTurnOrganizationScope ?? followupSeed.organization
|
||||
rawEntitySearchOverridesStaleScope && !currentTurnFreshOrganizationScope
|
||||
? null
|
||||
: valueFlowOrganizationStaysScope || !openScopeValueFlowWithoutCounterparty
|
||||
? (rawEntitySearchOverridesStaleScope ? currentTurnFreshOrganizationScope : currentTurnOrganizationScope) ??
|
||||
followupSeed.organization
|
||||
: null;
|
||||
if (
|
||||
explicitCurrentCounterpartyCandidate &&
|
||||
@@ -1609,13 +1675,18 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||
(valueFlowOrganizationStaysScope && (Boolean(followupSeed.rankingNeed) || bidirectionalValueFlowSignal)))
|
||||
);
|
||||
const normalizedPredecomposeDateScope =
|
||||
suppressImplicitCurrentDateScope && isImplicitCurrentDateScope(predecomposeDateScope) ? null : predecomposeDateScope;
|
||||
(rawEntitySearchOverridesStaleScope && !currentTurnCarriesExplicitPeriod) ||
|
||||
(suppressImplicitCurrentDateScope && isImplicitCurrentDateScope(predecomposeDateScope))
|
||||
? null
|
||||
: predecomposeDateScope;
|
||||
const normalizedAssistantTurnMeaningDateScope =
|
||||
suppressImplicitCurrentDateScope && isImplicitCurrentDateScope(assistantTurnMeaningDateScope)
|
||||
rawEntitySearchOverridesStaleScope ||
|
||||
(suppressImplicitCurrentDateScope && isImplicitCurrentDateScope(assistantTurnMeaningDateScope))
|
||||
? null
|
||||
: assistantTurnMeaningDateScope;
|
||||
const normalizedFollowupDateScope =
|
||||
suppressImplicitCurrentDateScope && isImplicitCurrentDateScope(followupSeed.dateScope)
|
||||
rawEntitySearchOverridesStaleScope ||
|
||||
(suppressImplicitCurrentDateScope && isImplicitCurrentDateScope(followupSeed.dateScope))
|
||||
? null
|
||||
: followupSeed.dateScope;
|
||||
const explicitDateScope =
|
||||
@@ -1670,7 +1741,9 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||
: rawAction ?? seededAction,
|
||||
asked_aggregation_axis: monthlyAggregationSignal ? "month" : rawAggregationAxis,
|
||||
seeded_ranking_need:
|
||||
valueFlowSignal && followupSeed.rankingNeed ? followupSeed.rankingNeed : undefined,
|
||||
valueFlowSignal && followupSeed.rankingNeed && !rawEntitySearchOverridesStaleScope
|
||||
? followupSeed.rankingNeed
|
||||
: undefined,
|
||||
explicit_entity_candidates: entityCandidates,
|
||||
metadata_ambiguity_entity_sets:
|
||||
metadataAmbiguityLaneClarificationApplicable && followupSeed.metadataAmbiguityEntitySets.length > 0
|
||||
@@ -1782,9 +1855,11 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||
groundedValueFlowFollowupApplicable
|
||||
});
|
||||
const hasTurnMeaning = Object.keys(cleanTurnMeaning).length > 0;
|
||||
const sourceSignal: AssistantMcpDiscoveryTurnInputSource = assistantTurnMeaning
|
||||
? "assistant_turn_meaning"
|
||||
: followupDiscoverySeedApplicable ||
|
||||
const sourceSignal: AssistantMcpDiscoveryTurnInputSource = rawEntitySearchOverridesStaleScope
|
||||
? "raw_text"
|
||||
: assistantTurnMeaning
|
||||
? "assistant_turn_meaning"
|
||||
: followupDiscoverySeedApplicable ||
|
||||
Boolean(entityResolutionClarificationCandidate) ||
|
||||
effectiveMetadataFollowupSeedApplicable ||
|
||||
metadataAmbiguityLaneClarificationApplicable
|
||||
@@ -1925,7 +2000,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||
) {
|
||||
pushReason(reasonCodes, "mcp_discovery_counterparty_from_predecompose");
|
||||
}
|
||||
if (followupSeed.counterparty) {
|
||||
if (followupSeed.counterparty && !rawEntitySearchOverridesStaleScope) {
|
||||
pushReason(reasonCodes, "mcp_discovery_counterparty_from_followup_context");
|
||||
}
|
||||
if (followupDateScopeApplied) {
|
||||
|
||||
@@ -1888,7 +1888,7 @@ function textMojibakeScoreForAddress(value) {
|
||||
const source = String(value ?? "");
|
||||
const cyrillic = (source.match(/[А-Яа-яЁё]/g) ?? []).length;
|
||||
const latin = (source.match(/[A-Za-z]/g) ?? []).length;
|
||||
const hardMarkers = (source.match(/[Ѓѓ‚„…†‡€‰‹ЉЊЌЋЏ�?’“”•–—™љ›њќћџ]/g) ?? []).length;
|
||||
const hardMarkers = (source.match(/[Ѓѓ‚„…†‡€‰‹ЉЊЌЋЏ\uFFFD?’“”•–—™љ›њќћџ]/g) ?? []).length;
|
||||
const pairMarkers = (source.match(/(?:Р.|С.|Ð.|Ñ.)/g) ?? []).length;
|
||||
const doubleEncodedMarkers = (source.match(/(?:Г[Ђ-џ]|В[Ђ-џ]|Ã.|Â.)/gu) ?? []).length;
|
||||
return cyrillic + latin - hardMarkers * 3 - pairMarkers * 2 - doubleEncodedMarkers * 2;
|
||||
@@ -1898,7 +1898,7 @@ function looksLikeMojibakeForAddress(value) {
|
||||
if (!source.trim()) {
|
||||
return false;
|
||||
}
|
||||
if (/[Ѓѓ‚„…†‡€‰‹ЉЊЌЋЏ�?’“”•–—™љ›њќћџ]/.test(source)) {
|
||||
if (/[Ѓѓ‚„…†‡€‰‹ЉЊЌЋЏ\uFFFD?’“”•–—™љ›њќћџ]/.test(source)) {
|
||||
return true;
|
||||
}
|
||||
if ((source.match(/(?:Р.|С.|Ð.|Ñ.)/g) ?? []).length >= 2) {
|
||||
@@ -2129,7 +2129,7 @@ function normalizeCounterpartyForFollowupMatch(value) {
|
||||
return compactWhitespace(repairAddressMojibake(String(value ?? ""))
|
||||
.toLowerCase()
|
||||
.replace(/ё/g, "е")
|
||||
.replace(/[«»"'`“”„’�?]/g, " ")
|
||||
.replace(/[«»"'`“”„’\uFFFD?]/g, " ")
|
||||
.replace(/[^a-zа-я0-9\s._-]+/giu, " "));
|
||||
}
|
||||
function normalizeCounterpartyTokenForFollowupMatch(value) {
|
||||
@@ -2175,7 +2175,7 @@ function extractDisplayedAddressEntityCandidates(replyText, entityType = "unknow
|
||||
if (parts.length >= 2 && /^\d{4}-\d{2}-\d{2}/.test(parts[0] ?? "")) {
|
||||
counterpartyCandidate = parts[1] ?? counterpartyCandidate;
|
||||
}
|
||||
const cleanedCandidate = compactWhitespace(counterpartyCandidate.replace(/^["'«»“”„`’�?]+|["'«»“”„`’�?]+$/gu, ""));
|
||||
const cleanedCandidate = compactWhitespace(counterpartyCandidate.replace(/^["'«»“”„`’\uFFFD?]+|["'«»“”„`’\uFFFD?]+$/gu, ""));
|
||||
if (!cleanedCandidate || cleanedCandidate.length < 2) {
|
||||
continue;
|
||||
}
|
||||
@@ -3224,6 +3224,11 @@ function hasSameDateAccountFollowupSignalForPredecompose(text) {
|
||||
/(?:^|\s)по\s+\d{2}(?:[.,]\d{1,2})?(?=$|[\s,.;:!?])/iu.test(source) ||
|
||||
/\b\d{2}(?:[.,]\d{1,2})\b/u.test(source));
|
||||
}
|
||||
function isCounterpartyDrilldownIntentForPredecompose(intent) {
|
||||
return intent === "list_documents_by_counterparty" ||
|
||||
intent === "bank_operations_by_counterparty" ||
|
||||
intent === "list_contracts_by_counterparty";
|
||||
}
|
||||
function hasPredecomposeDiagnosticUncertaintyLead(text) {
|
||||
const normalized = compactWhitespace(repairAddressMojibake(String(text ?? "")).toLowerCase());
|
||||
if (!normalized) {
|
||||
@@ -3442,8 +3447,16 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage
|
||||
const sourceHasExplicitAccountAnchor = (0, addressIntentResolver_1.hasAccountNumberAnchor)(repairedSourceMessage || userMessage) ||
|
||||
(0, addressIntentResolver_1.hasCompactAccountCodeToken)(repairedSourceMessage || userMessage);
|
||||
const candidateInjectsAccountAnchor = Boolean(toNonEmptyString(candidatePredecomposeContract?.entities?.account));
|
||||
if (sourceIntentResolution.intent === "inventory_on_hand_as_of_date" &&
|
||||
candidateIntentResolution.intent === "inventory_on_hand_as_of_date" &&
|
||||
const sourceAnchorQuality = evaluateAddressAnchorQuality(repairedSourceMessage || userMessage);
|
||||
const candidateAccountInjectedIntoCounterpartyAnchor = isCounterpartyDrilldownIntentForPredecompose(sourceIntentResolution.intent) &&
|
||||
sourceIntentResolution.intent === candidateIntentResolution.intent &&
|
||||
sourceAnchorQuality.anchorType === "counterparty" &&
|
||||
sourceAnchorQuality.quality >= 2 &&
|
||||
!sourceHasExplicitAccountAnchor &&
|
||||
candidateInjectsAccountAnchor;
|
||||
if (((sourceIntentResolution.intent === "inventory_on_hand_as_of_date" &&
|
||||
candidateIntentResolution.intent === "inventory_on_hand_as_of_date") ||
|
||||
candidateAccountInjectedIntoCounterpartyAnchor) &&
|
||||
!sourceHasExplicitAccountAnchor &&
|
||||
candidateInjectsAccountAnchor) {
|
||||
return attachAddressPredecomposeContract({
|
||||
@@ -3456,10 +3469,9 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage
|
||||
reason: "normalized_fragment_rejected_anchor_injection",
|
||||
fallbackRuleHit: null,
|
||||
sanitizedUserMessage,
|
||||
semanticHints: candidateMeta?.semanticHints ?? null
|
||||
semanticHints: null
|
||||
}, userMessage);
|
||||
}
|
||||
const sourceAnchorQuality = evaluateAddressAnchorQuality(repairedSourceMessage || userMessage);
|
||||
const candidateAnchorQuality = evaluateAddressAnchorQuality(candidate);
|
||||
const sameIntentForAnchorSafety = sourceAnchorQuality.intent !== "unknown" && sourceAnchorQuality.intent === candidateAnchorQuality.intent;
|
||||
const sourceSelectedObjectItemAnchorValue = toNonEmptyString((0, addressFilterExtractor_1.extractSelectedObjectQuotedValue)(userMessage)) ??
|
||||
|
||||
Reference in New Issue
Block a user