Post-F: закрепить семантическую целостность MCP-цепочек

This commit is contained in:
2026-04-24 18:35:12 +03:00
parent 830e05fe97
commit 796a9a93f2
24 changed files with 2408 additions and 104 deletions
@@ -52,6 +52,14 @@ function isInternalMechanicsLine(value) {
function userFacingUnknowns(values) {
return uniqueStrings(values).filter((value) => !isInternalMechanicsLine(value));
}
function rankedValueFlowUnknownLines(pilot) {
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) {
return uniqueStrings(values).filter((value) => !isInternalMechanicsLine(value));
}
@@ -718,13 +726,15 @@ function buildAssistantMcpDiscoveryAnswerDraft(pilot) {
if (monthlyConfirmedLines.length > 0) {
pushReason(reasonCodes, "answer_contains_monthly_breakdown");
}
const confirmedLines = derivedValueLine
? [...pilot.evidence.confirmed_facts, derivedValueLine, ...monthlyConfirmedLines]
: derivedEntityResolutionLine
? [...pilot.evidence.confirmed_facts, derivedEntityResolutionLine]
: derivedMetadataLine
? [...pilot.evidence.confirmed_facts, derivedMetadataLine]
: pilot.evidence.confirmed_facts;
const confirmedLines = pilot.derived_ranked_value_flow && derivedValueLine
? [derivedValueLine]
: derivedValueLine
? [...pilot.evidence.confirmed_facts, derivedValueLine, ...monthlyConfirmedLines]
: derivedEntityResolutionLine
? [...pilot.evidence.confirmed_facts, derivedEntityResolutionLine]
: derivedMetadataLine
? [...pilot.evidence.confirmed_facts, derivedMetadataLine]
: pilot.evidence.confirmed_facts;
return {
schema_version: exports.ASSISTANT_MCP_DISCOVERY_ANSWER_DRAFT_SCHEMA_VERSION,
policy_owner: "assistantMcpDiscoveryAnswerAdapter",
@@ -732,7 +742,7 @@ function buildAssistantMcpDiscoveryAnswerDraft(pilot) {
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,
@@ -59,9 +59,42 @@ function isReferentialEntityPlaceholder(value) {
"этом"
]).has((0, addressTextRepair_1.normalizeRussianComparableText)(value));
}
function isReferentialOrganizationPlaceholder(value) {
if (!value) {
return false;
}
return new Set([
"эта организация",
"этой организации",
"этой организацией",
"эту организацию",
"эта компания",
"этой компании",
"этой компанией",
"эту компанию",
"наша организация",
"нашей организации",
"нашей компанией"
]).has((0, addressTextRepair_1.normalizeRussianComparableText)(value));
}
function isValueFlowPredicateEntityCandidate(value) {
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) {
return Boolean(value && (isReferentialEntityPlaceholder(value) || isValueFlowPredicateEntityCandidate(value)));
}
function normalizeFollowupCounterpartyCandidate(value) {
const text = candidateValue(value);
if (!text || isReferentialEntityPlaceholder(text)) {
if (!text || isInvalidEntityCandidate(text)) {
return null;
}
return text;
@@ -71,7 +104,7 @@ function pushScopedEntityCandidate(target, value, groundedFollowupEntity) {
if (!text) {
return;
}
if (groundedFollowupEntity && isReferentialEntityPlaceholder(text)) {
if ((groundedFollowupEntity && isReferentialEntityPlaceholder(text)) || isValueFlowPredicateEntityCandidate(text)) {
return;
}
pushUnique(target, text);
@@ -88,7 +121,7 @@ function pushNormalizedEntityResolutionCandidate(target, value) {
return;
}
const normalized = canonicalizeEntityResolutionCandidate(text);
if (normalized && !target.includes(normalized)) {
if (normalized && !isInvalidEntityCandidate(normalized) && !target.includes(normalized)) {
target.push(normalized);
}
}
@@ -119,18 +152,25 @@ function collectEntityCandidates(value) {
const result = [];
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;
}
function collectPredecomposeEntities(predecompose) {
const entities = toRecordObject(predecompose?.entities);
const organization = toNonEmptyString(entities?.organization);
return {
counterparty: toNonEmptyString(entities?.counterparty),
organization: toNonEmptyString(entities?.organization)
organization: isReferentialOrganizationPlaceholder(organization) ? null : organization
};
}
function collectDateScope(predecompose) {
@@ -541,23 +581,25 @@ function hasMetadataDownstreamContinuationSignal(text) {
}
function hasEntityResolutionSignal(text) {
const hasSearchVerb = /(?:найд(?:и|ите|ем|у)|поищ(?:и|ите|ем)|найти|поиск|search|find|look\s*up)/iu.test(text);
const hasEntityNoun = /(?:контрагент(?:а|ов)?|поставщик(?:а|ов)?|клиент(?:а|ов)?|counterpart(?:y|ies)|supplier(?:s)?|customer(?:s)?)/iu.test(text);
const hasEntityNoun = /(?:контрагент(?:а|ов|у|ом|е)?|поставщик(?:а|ов|у|ом|е)?|клиент(?:а|ов|у|ом|е)?|counterpart(?:y|ies)|supplier(?:s)?|customer(?:s)?)/iu.test(text);
return hasSearchVerb && hasEntityNoun;
}
function normalizeEntityResolutionCandidate(value) {
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();
}
function rawEntityResolutionCandidate(text) {
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);
@@ -797,6 +839,7 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
? resolveEntityResolutionAmbiguityChoice(rawEntitySourceText, followupSeed.entityResolutionAmbiguityCandidates)
: 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);
@@ -815,10 +858,14 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
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 currentTurnOrganizationScope = rawOrganizationScope ?? predecomposeEntities.organization ?? assistantTurnMeaningOrganizationScope;
const currentTurnFreshOrganizationScope = rawOrganizationScope ?? predecomposeEntities.organization;
const currentTurnOrganizationScope = currentTurnFreshOrganizationScope ?? assistantTurnMeaningOrganizationScope;
const explicitOrganizationScopeSignal = Boolean(rawOrganizationMentionSignal && currentTurnOrganizationScope);
const organizationClarificationFollowupApplicable = Boolean(followupSeed.domain === "counterparty_value" &&
!followupSeed.counterparty &&
@@ -1130,30 +1177,40 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
lifecycleSignal ||
metadataGroundedDocumentLaneApplicable ||
metadataGroundedMovementLaneApplicable));
const metadataLaneScopeHint = explicitCurrentCounterpartyOverridesFollowupEntity
const metadataLaneScopeHint = rawEntitySearchOverridesStaleScope
? null
: rawMetadataScopeHint ??
followupSeed.metadataScopeHint ??
followupSeed.discoveryEntity ??
followupSeed.metadataSelectedEntitySet ??
null;
: explicitCurrentCounterpartyOverridesFollowupEntity
? null
: rawMetadataScopeHint ??
followupSeed.metadataScopeHint ??
followupSeed.discoveryEntity ??
followupSeed.metadataSelectedEntitySet ??
null;
const metadataScopedLaneWithoutSubject = Boolean((metadataGroundedMovementLaneApplicable || metadataGroundedDocumentLaneApplicable) &&
!followupSeed.counterparty &&
metadataLaneCarryoverAvailable);
const groundedFollowupEntity = metadataScopedLaneWithoutSubject
? null
: explicitCurrentCounterpartyOverridesFollowupEntity
: rawEntitySearchOverridesStaleScope
? null
: followupSeed.counterparty ?? followupSeed.discoveryEntity;
: explicitCurrentCounterpartyOverridesFollowupEntity
? null
: followupSeed.counterparty ?? followupSeed.discoveryEntity;
const entityCandidates = entityResolutionSignal ? [] : [];
if (entityResolutionSignal) {
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) {
@@ -1190,9 +1247,12 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
pushUnique(entityCandidates, predecomposeEntities.organization);
pushUnique(entityCandidates, followupSeed.organization);
}
const explicitOrganizationScope = valueFlowOrganizationStaysScope || !openScopeValueFlowWithoutCounterparty
? currentTurnOrganizationScope ?? followupSeed.organization
: null;
const explicitOrganizationScope = rawEntitySearchOverridesStaleScope && !currentTurnFreshOrganizationScope
? null
: valueFlowOrganizationStaysScope || !openScopeValueFlowWithoutCounterparty
? (rawEntitySearchOverridesStaleScope ? currentTurnFreshOrganizationScope : currentTurnOrganizationScope) ??
followupSeed.organization
: null;
if (explicitCurrentCounterpartyCandidate &&
(valueFlowSignal || lifecycleSignal || metadataGroundedDocumentLaneApplicable || metadataGroundedMovementLaneApplicable)) {
for (let index = entityCandidates.length - 1; index >= 0; index -= 1) {
@@ -1226,11 +1286,16 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
(clarificationLoopStillNeedsPeriod ||
openScopeValueFlowWithoutResolvedCounterparty ||
(valueFlowOrganizationStaysScope && (Boolean(followupSeed.rankingNeed) || bidirectionalValueFlowSignal))));
const normalizedPredecomposeDateScope = suppressImplicitCurrentDateScope && isImplicitCurrentDateScope(predecomposeDateScope) ? null : predecomposeDateScope;
const normalizedAssistantTurnMeaningDateScope = suppressImplicitCurrentDateScope && isImplicitCurrentDateScope(assistantTurnMeaningDateScope)
const normalizedPredecomposeDateScope = (rawEntitySearchOverridesStaleScope && !currentTurnCarriesExplicitPeriod) ||
(suppressImplicitCurrentDateScope && isImplicitCurrentDateScope(predecomposeDateScope))
? null
: predecomposeDateScope;
const normalizedAssistantTurnMeaningDateScope = rawEntitySearchOverridesStaleScope ||
(suppressImplicitCurrentDateScope && isImplicitCurrentDateScope(assistantTurnMeaningDateScope))
? null
: assistantTurnMeaningDateScope;
const normalizedFollowupDateScope = suppressImplicitCurrentDateScope && isImplicitCurrentDateScope(followupSeed.dateScope)
const normalizedFollowupDateScope = rawEntitySearchOverridesStaleScope ||
(suppressImplicitCurrentDateScope && isImplicitCurrentDateScope(followupSeed.dateScope))
? null
: followupSeed.dateScope;
const explicitDateScope = rawAllTimeScopeSignal
@@ -1277,7 +1342,9 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
? metadataActionFromRawText(rawText) ?? seededAction
: rawAction ?? seededAction,
asked_aggregation_axis: monthlyAggregationSignal ? "month" : rawAggregationAxis,
seeded_ranking_need: valueFlowSignal && followupSeed.rankingNeed ? followupSeed.rankingNeed : undefined,
seeded_ranking_need: valueFlowSignal && followupSeed.rankingNeed && !rawEntitySearchOverridesStaleScope
? followupSeed.rankingNeed
: undefined,
explicit_entity_candidates: entityCandidates,
metadata_ambiguity_entity_sets: metadataAmbiguityLaneClarificationApplicable && followupSeed.metadataAmbiguityEntitySets.length > 0
? followupSeed.metadataAmbiguityEntitySets
@@ -1381,28 +1448,30 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
groundedValueFlowFollowupApplicable
});
const hasTurnMeaning = Object.keys(cleanTurnMeaning).length > 0;
const sourceSignal = assistantTurnMeaning
? "assistant_turn_meaning"
: followupDiscoverySeedApplicable ||
Boolean(entityResolutionClarificationCandidate) ||
effectiveMetadataFollowupSeedApplicable ||
metadataAmbiguityLaneClarificationApplicable
? "followup_context"
: metadataGroundedMovementLaneApplicable
const sourceSignal = rawEntitySearchOverridesStaleScope
? "raw_text"
: assistantTurnMeaning
? "assistant_turn_meaning"
: followupDiscoverySeedApplicable ||
Boolean(entityResolutionClarificationCandidate) ||
effectiveMetadataFollowupSeedApplicable ||
metadataAmbiguityLaneClarificationApplicable
? "followup_context"
: metadataGroundedDocumentLaneApplicable
: metadataGroundedMovementLaneApplicable
? "followup_context"
: predecomposeContract
? "predecompose_contract"
: lifecycleSignal
? "raw_text"
: valueFlowSignal
: metadataGroundedDocumentLaneApplicable
? "followup_context"
: predecomposeContract
? "predecompose_contract"
: lifecycleSignal
? "raw_text"
: entityResolutionSignal
: valueFlowSignal
? "raw_text"
: rawMetadataSignal || effectiveMetadataFollowupSeedApplicable
: entityResolutionSignal
? "raw_text"
: "none";
: rawMetadataSignal || effectiveMetadataFollowupSeedApplicable
? "raw_text"
: "none";
if (lifecycleSignal) {
pushReason(reasonCodes, "mcp_discovery_lifecycle_signal_detected");
}
@@ -1521,7 +1590,7 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
normalizedPredecomposeCounterparty) {
pushReason(reasonCodes, "mcp_discovery_counterparty_from_predecompose");
}
if (followupSeed.counterparty) {
if (followupSeed.counterparty && !rawEntitySearchOverridesStaleScope) {
pushReason(reasonCodes, "mcp_discovery_counterparty_from_followup_context");
}
if (followupDateScopeApplied) {
+20 -8
View File
@@ -1932,7 +1932,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;
@@ -1942,7 +1942,7 @@ function looksLikeMojibakeForAddress(value) {
if (!source.trim()) {
return false;
}
if (/[Ѓѓ‚„…†‡€‰‹ЉЊЌЋЏ?’“”•–—™љ›њќћџ]/.test(source)) {
if (/[Ѓѓ‚„…†‡€‰‹ЉЊЌЋЏ\uFFFD?’“”•–—™љ›њќћџ]/.test(source)) {
return true;
}
if ((source.match(/(?:Р.|С.|Ð.|Ñ.)/g) ?? []).length >= 2) {
@@ -2173,7 +2173,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) {
@@ -2219,7 +2219,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;
}
@@ -3268,6 +3268,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) {
@@ -3486,8 +3491,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({
@@ -3500,10 +3513,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)) ??
@@ -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)) ??
@@ -1,4 +1,4 @@
import { describe, expect, it, vi } from "vitest";
import { describe, expect, it, vi } from "vitest";
import { AssistantService } from "../src/services/assistantService";
import { AssistantSessionStore } from "../src/services/assistantSessionStore";
@@ -1444,6 +1444,102 @@ describe("assistant address llm pre-decompose candidate preference", () => {
expect(response.debug?.fallback_rule_hit).toBe("documents_counterparty_year_rewrite");
});
it("rejects account injection when LLM truncates a numeric counterparty suffix", async () => {
const calls: Array<{ message: string }> = [];
const addressQueryService = {
tryHandle: vi.fn(async (message: string) => {
calls.push({ message });
return buildAddressLaneResult(message);
})
} as any;
const normalizerService = {
normalize: vi.fn(async () => ({
trace_id: "norm-predecompose-counterparty-suffix-account-injection",
ok: true,
normalized: {
schema_version: "normalized_query_v2_0_2",
user_message_raw: "Покажи документы по Жуковке 51.",
message_in_scope: true,
scope_confidence: "high",
contains_multiple_tasks: false,
fragments: [
{
fragment_id: "F1",
raw_fragment_text: "Покажи документы по Жуковке 51.",
normalized_fragment_text: "Показать документы, связанные с контрагентом Жуковка по счету 51",
domain_relevance: "in_scope",
business_scope: "company_specific_accounting",
entity_hints: ["Жуковка"],
account_hints: ["51"],
document_hints: ["документы"],
register_hints: [],
semantic_hints: {
scope_target_kind: "counterparty",
scope_target_text: "Жуковка",
date_scope_kind: "implicit_current",
self_scope_detected: false,
selected_object_scope_detected: false
},
time_scope: { type: "unspecified", value: null, confidence: "low" },
flags: {
has_multi_entity_scope: false,
asks_for_chain_explanation: false,
asks_for_ranking_or_top: false,
asks_for_period_summary: false,
asks_for_rule_check: false,
asks_for_anomaly_scan: false,
asks_for_exact_object_trace: false,
asks_for_evidence: false,
mentions_period_close_context: false
},
candidate_labels: ["simple_factual"],
confidence: "high",
execution_readiness: "executable",
clarification_reason: null,
soft_assumption_used: [],
route_status: "routed",
no_route_reason: null
}
],
discarded_fragments: [],
global_notes: { needs_clarification: false, clarification_reason: null }
},
raw_model_output: null,
validation: { passed: true, errors: [] },
usage: { input_tokens: 1, output_tokens: 1, total_tokens: 2 },
latency_ms: 10,
prompt_version: "normalizer_v2_0_2",
schema_version: "v2_0_2",
request_count_for_case: 1
}))
} as any;
const sessions = new AssistantSessionStore();
const service = new AssistantService(
normalizerService,
sessions as any,
{} as any,
{ persistSession: vi.fn() } as any,
addressQueryService
);
const response = await service.handleMessage({
session_id: `asst-predecompose-counterparty-suffix-${Date.now()}`,
user_message: "Покажи документы по Жуковке 51.",
llmProvider: "local",
useMock: false
} as any);
expect(response.ok).toBe(true);
expect(calls).toHaveLength(1);
expect(calls[0].message).toBe("Покажи документы по Жуковке 51.");
expect(response.debug?.llm_decomposition_applied).toBe(false);
expect(response.debug?.llm_decomposition_reason).toBe("normalized_fragment_rejected_anchor_injection");
expect(response.debug?.llm_predecompose_contract?.entities?.account).toBeNull();
expect(response.debug?.llm_predecompose_contract?.entities?.counterparty).toBe("Жуковке 51");
});
it("rewrites payment-style counterparty phrasing to bank operations", async () => {
const calls: Array<{ message: string }> = [];
const addressQueryService = {
@@ -341,6 +341,62 @@ describe("assistant MCP discovery answer adapter", () => {
expect(draft.next_step_line).toContain("организац");
});
it("renders confirmed ranked value-flow without raw technical evidence lines", async () => {
const planner = planAssistantMcpDiscovery({
dataNeedGraph: {
schema_version: "assistant_data_need_graph_v1",
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
subject_candidates: [],
business_fact_family: "value_flow",
action_family: "turnover",
aggregation_need: null,
time_scope_need: "explicit_period",
comparison_need: null,
ranking_need: "top_desc",
proof_expectation: "coverage_checked_fact",
clarification_gaps: [],
decomposition_candidates: ["collect_scoped_movements", "aggregate_ranked_axis_values", "probe_coverage"],
forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"],
reason_codes: ["data_need_graph_built", "data_need_graph_ranking_top_desc"]
},
turnMeaning: {
asked_domain_family: "counterparty_value",
asked_action_family: "turnover",
explicit_organization_scope: "\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441",
explicit_date_scope: "2020",
unsupported_but_understood_family: "counterparty_value_or_turnover"
}
});
const pilot = await executeAssistantMcpDiscoveryPilot(
planner,
buildDeps([
{
Period: "2020-01-15T00:00:00",
Amount: 12000,
Counterparty: "\u0421\u0411\u0415\u0420\u0411\u0410\u041d\u041a, \u041f\u0410\u041e",
Organization: "\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441"
},
{
Period: "2020-02-20T00:00:00",
Amount: 5000,
Counterparty: "\u0413\u0440\u0443\u043f\u043f\u0430 \u0421\u0412\u041a",
Organization: "\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441"
}
])
);
const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot);
const userText = [...draft.confirmed_lines, ...draft.inference_lines, ...draft.unknown_lines].join("\n");
expect(draft.answer_mode).toBe("confirmed_with_bounded_inference");
expect(draft.confirmed_lines).toHaveLength(1);
expect(userText).toContain("\u0411\u043e\u043b\u044c\u0448\u0435 \u0432\u0441\u0435\u0433\u043e \u0434\u0435\u043d\u0435\u0433 \u043f\u0440\u0438\u043d\u0451\u0441 \u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442");
expect(userText).toContain("\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441");
expect(userText).not.toContain("1C incoming value-flow");
expect(userText).not.toContain("Full ranking outside");
expect(draft.unknown_lines[0]).toContain("\u041f\u043e\u043b\u043d\u044b\u0439 \u0440\u0435\u0439\u0442\u0438\u043d\u0433");
});
it("asks for both organization and period when an open total still misses both axes", async () => {
const planner = planAssistantMcpDiscovery({
dataNeedGraph: {
@@ -1426,6 +1426,45 @@ describe("assistant MCP discovery turn input adapter", () => {
);
});
it("lets an explicit current entity search override stale ranking follow-up scope", () => {
const staleEntity = "\u043f\u0440\u0438\u043d\u0451\u0441 \u043d\u0430\u0438\u0431\u043e\u043b\u044c\u0448\u0443\u044e \u0432\u044b\u0440\u0443\u0447\u043a\u0443 \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u0432";
const result = buildAssistantMcpDiscoveryTurnInput({
userMessage:
"\u0422\u0435\u043f\u0435\u0440\u044c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0430\u044f \u0442\u0435\u043c\u0430 \u043f\u043e \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u043c\u0443 \u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442\u0443. \u041d\u0430\u0439\u0434\u0438 \u0432 1\u0421 \u0413\u0440\u0443\u043f\u043f\u0443 \u0421\u0412\u041a.",
assistantTurnMeaning: {
asked_domain_family: "entity_resolution",
asked_action_family: "search_business_entity",
explicit_entity_candidates: [staleEntity],
explicit_organization_scope: "\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441",
explicit_date_scope: "2021",
unsupported_but_understood_family: "entity_resolution",
stale_replay_forbidden: true
},
followupContext: {
previous_discovery_pilot_scope: "counterparty_value_flow_query_movements_v1",
previous_discovery_ranking_need: "top_desc",
previous_filters: {
organization: "\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441",
counterparty: staleEntity,
period_from: "2021-01-01",
period_to: "2021-12-31"
}
}
});
expect(result.adapter_status).toBe("ready");
expect(result.should_run_discovery).toBe(true);
expect(result.source_signal).toBe("raw_text");
expect(result.turn_meaning_ref?.explicit_entity_candidates).toEqual(["\u0413\u0440\u0443\u043f\u043f\u0430 \u0421\u0412\u041a"]);
expect(result.turn_meaning_ref?.metadata_scope_hint).toBeUndefined();
expect(result.turn_meaning_ref?.explicit_organization_scope).toBeUndefined();
expect(result.turn_meaning_ref?.explicit_date_scope).toBeUndefined();
expect(result.turn_meaning_ref?.seeded_ranking_need).toBeUndefined();
expect(result.data_need_graph?.subject_candidates).toEqual(["\u0413\u0440\u0443\u043f\u043f\u0430 \u0421\u0412\u041a"]);
expect(result.reason_codes).toContain("mcp_discovery_entity_scope_from_raw_entity_search");
expect(result.reason_codes).not.toContain("mcp_discovery_counterparty_from_followup_context");
});
it("marks top-value wording as a ranking data need without inventing a missing subject gap", () => {
const result = buildAssistantMcpDiscoveryTurnInput({
userMessage: "кто больше всего принес денег в 2020"
@@ -1444,6 +1483,46 @@ describe("assistant MCP discovery turn input adapter", () => {
]);
});
it("keeps open organization ranking subjectless when assistant meaning invents a predicate-shaped entity", () => {
const result = buildAssistantMcpDiscoveryTurnInput({
userMessage:
"\u0418 \u043a\u0442\u043e \u0431\u043e\u043b\u044c\u0448\u0435 \u0432\u0441\u0435\u0433\u043e \u043f\u0440\u0438\u043d\u0435\u0441 \u0434\u0435\u043d\u0435\u0433 \u044d\u0442\u043e\u0439 \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u0432 2020 \u0433\u043e\u0434\u0443?",
assistantTurnMeaning: {
asked_domain_family: "counterparty_value",
asked_action_family: "turnover",
explicit_entity_candidates: [
"\u043f\u0440\u0438\u043d\u0451\u0441 \u043d\u0430\u0438\u0431\u043e\u043b\u044c\u0448\u0443\u044e \u0432\u044b\u0440\u0443\u0447\u043a\u0443 \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u0432"
],
explicit_organization_scope: "\u044d\u0442\u0430 \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u044f",
explicit_date_scope: "2020",
unsupported_but_understood_family: "counterparty_value_or_turnover",
stale_replay_forbidden: true
},
predecomposeContract: {
entities: {
organization: "\u044d\u0442\u0430 \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u044f"
}
},
followupContext: {
previous_discovery_pilot_scope: "counterparty_value_flow_query_movements_v1",
previous_filters: {
organization: "\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441"
}
}
});
expect(result.adapter_status).toBe("ready");
expect(result.should_run_discovery).toBe(true);
expect(result.turn_meaning_ref?.explicit_entity_candidates).toBeUndefined();
expect(result.turn_meaning_ref?.explicit_organization_scope).toBe(
"\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441"
);
expect(result.turn_meaning_ref?.explicit_date_scope).toBe("2020");
expect(result.data_need_graph?.subject_candidates).toEqual([]);
expect(result.data_need_graph?.ranking_need).toBe("top_desc");
expect(result.data_need_graph?.decomposition_candidates).toContain("aggregate_ranked_axis_values");
});
it("keeps organization as scope for open bidirectional comparison wording instead of inventing a subject candidate", () => {
const result = buildAssistantMcpDiscoveryTurnInput({
userMessage: "что больше: входящие или исходящие деньги за 2020 год по ООО Альтернатива Плюс?",
@@ -1,4 +1,172 @@
[
{
"generation_id": "gen-ag04241406-abe4d8",
"created_at": "2026-04-24T14:06:30+00:00",
"mode": "saved_user_sessions",
"title": "AGENT | Post-F cross-stage semantic integrity canary",
"count": 24,
"domain": "address_post_f_cross_stage_canary_agent",
"questions": [
"Мне нужно понять, где в 1С по НДС вообще лежат данные. Какие объекты стоит смотреть по НДС?",
"Хорошо, тогда покажи движения по ООО Альтернатива Плюс.",
"За 2020 год.",
"А теперь по документам?",
"А теперь за 2021 год?",
"А теперь за все время?",
"С НДС закончили. Новая тема: покажи документы по Жуковке 51.",
"Хорошо, а теперь платежи по нему тоже покажи.",
"А по нему договоры?",
"А по нему документы?",
"А за 2021?",
"С Жуковкой закончили. Теперь другая задача: быстрый денежный срез по одной организации. Если для ответа нужна организация, просто уточни ее. Сколько вообще входящих денег было за 2020 год?",
"По ООО Альтернатива Плюс.",
"Понял, тогда за все время.",
"Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?",
"И кто больше всего принес денег этой организации в 2020 году?",
"А в 2021 году?",
"Теперь отдельная тема по конкретному контрагенту. Найди в 1С Группу СВК.",
"Сколько получили по нему за 2020 год?",
"А теперь сколько заплатили?",
"А какое нетто?",
"А по документам?",
"А по движениям?",
"А теперь тот же смысл за 2021 год."
],
"generated_by": "codex_agent",
"saved_case_set_file": "assistant_autogen_saved_user_sessions_20260424140630_gen-ag04241406-abe4d8.json",
"context": {
"llm_provider": null,
"model": null,
"assistant_prompt_version": null,
"decomposition_prompt_version": null,
"prompt_fingerprint": null,
"autogen_personality_id": null,
"autogen_personality_prompt": null,
"source_session_id": null,
"saved_session_file": "assistant_saved_session_20260424140630_gen-ag04241406-abe4d8.json",
"saved_case_set_kind": "agent_semantic_scenario",
"agent_run": true,
"agent_focus": "cross-stage canary: VAT metadata, numeric counterparty suffix, open organization value-flow, SVK grounded reset",
"architecture_phase": "Post-F Semantic Integrity Hardening",
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_post_f_cross_stage_canary_agent_20260424.json",
"scenario_id": "address_truth_harness_post_f_cross_stage_canary_agent_20260424",
"semantic_tags": [
"account_injection_guard",
"all_time_followup",
"bidirectional_value_flow",
"clarification_resume",
"contracts_followup",
"counterparty_pronoun_resolution",
"cross_stage_canary",
"document_lane_continuity",
"document_pivot_after_movement",
"documents_followup",
"documents_pivot",
"grounded_counterparty",
"incoming_value_flow",
"legacy_phase39",
"legacy_phase64",
"legacy_phase67",
"metadata_surface",
"movement_execution",
"movement_lane_after_metadata",
"movements_pivot",
"net_value_flow",
"numeric_counterparty_suffix",
"open_scope_total",
"organization_clarification",
"organization_scope",
"payments_followup",
"payout_value_flow",
"period_clarification_resume",
"post_f",
"repeated_pivot",
"scope_reuse",
"topic_reset",
"value_flow_ranking",
"vat_orientation",
"year_switch",
"year_switch_after_document_pivot",
"year_switch_after_repeated_pivot"
]
}
},
{
"generation_id": "gen-ag04231844-8e552a",
"created_at": "2026-04-23T18:44:25+00:00",
"mode": "saved_user_sessions",
"title": "AGENT | ARCH: Post-F Semantic Integrity Hardening | Смешанный живой диалог: repeated pivots, орг-срез и СВК",
"count": 19,
"domain": "address_phase82_human_mixed_integrity_status_dialog",
"questions": [
"Покажи документы по Жуковке 51.",
"Хорошо, а теперь платежи по нему тоже покажи.",
"А по нему договоры?",
"А по нему документы?",
"А по нему платежи?",
"А за 2021?",
"С Жуковкой закончили. Теперь нужна другая задача: быстрый денежный срез по одной организации. Если для ответа нужна организация, просто уточни ее. Сколько вообще входящих денег было за 2020 год?",
"По ООО Альтернатива Плюс.",
"Понял, тогда за все время.",
"Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?",
"А что по ООО Альтернатива Плюс больше уже за 2021 год: входящие или исходящие деньги?",
"И кто больше всего принес денег этой организации в 2020 году?",
"А в 2021 году?",
"Теперь отдельная тема по конкретному контрагенту. Найди в 1С Группу СВК.",
"Сколько получили по нему за 2020 год?",
"А теперь сколько заплатили?",
"А какое нетто?",
"А по документам?",
"А по движениям?"
],
"generated_by": "codex_agent",
"saved_case_set_file": "assistant_autogen_saved_user_sessions_20260423184425_gen-ag04231844-8e552a.json",
"context": {
"llm_provider": null,
"model": null,
"assistant_prompt_version": null,
"decomposition_prompt_version": null,
"prompt_fingerprint": null,
"autogen_personality_id": null,
"autogen_personality_prompt": null,
"source_session_id": null,
"saved_session_file": "assistant_saved_session_20260423184425_gen-ag04231844-8e552a.json",
"saved_case_set_kind": "agent_semantic_scenario",
"agent_run": true,
"agent_focus": "Post-F repeated pivots + open-scope organization money + grounded SVK counterparty chain",
"architecture_phase": "turnaround_11",
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase82_human_mixed_integrity_status_dialog.json",
"scenario_id": "address_truth_harness_phase82_human_mixed_integrity_status_dialog",
"semantic_tags": [
"all_time_followup",
"contracts_followup",
"counterparty_pronoun_resolution",
"documents_by_counterparty",
"documents_followup",
"documents_pivot",
"fourth_pivot",
"grounded_counterparty",
"human_dialog",
"incoming_value_flow",
"movements_pivot",
"net_value_flow",
"open_scope_total",
"organization_clarification",
"organization_scope",
"payments_followup",
"payout_value_flow",
"pivot_seed",
"post_f_integrity_hardening",
"second_pivot",
"third_pivot",
"topic_reset",
"value_flow_comparison",
"value_flow_ranking",
"year_switch",
"year_switch_after_fourth_pivot"
]
}
},
{
"generation_id": "gen-ag04231336-3d4cc9",
"created_at": "2026-04-23T13:36:22+00:00",
@@ -0,0 +1,281 @@
{
"saved_at": "2026-04-23T18:44:25+00:00",
"generation_id": "gen-ag04231844-8e552a",
"mode": "saved_user_sessions",
"title": "AGENT | ARCH: Post-F Semantic Integrity Hardening | Смешанный живой диалог: repeated pivots, орг-срез и СВК",
"agent_run": true,
"questions": [
"Покажи документы по Жуковке 51.",
"Хорошо, а теперь платежи по нему тоже покажи.",
"А по нему договоры?",
"А по нему документы?",
"А по нему платежи?",
"А за 2021?",
"С Жуковкой закончили. Теперь нужна другая задача: быстрый денежный срез по одной организации. Если для ответа нужна организация, просто уточни ее. Сколько вообще входящих денег было за 2020 год?",
"По ООО Альтернатива Плюс.",
"Понял, тогда за все время.",
"Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?",
"А что по ООО Альтернатива Плюс больше уже за 2021 год: входящие или исходящие деньги?",
"И кто больше всего принес денег этой организации в 2020 году?",
"А в 2021 году?",
"Теперь отдельная тема по конкретному контрагенту. Найди в 1С Группу СВК.",
"Сколько получили по нему за 2020 год?",
"А теперь сколько заплатили?",
"А какое нетто?",
"А по документам?",
"А по движениям?"
],
"metadata": {
"assistant_prompt_version": null,
"decomposition_prompt_version": null,
"prompt_fingerprint": null,
"agent_focus": "Post-F repeated pivots + open-scope organization money + grounded SVK counterparty chain",
"architecture_phase": "turnaround_11",
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase82_human_mixed_integrity_status_dialog.json",
"scenario_id": "address_truth_harness_phase82_human_mixed_integrity_status_dialog",
"semantic_tags": [
"all_time_followup",
"contracts_followup",
"counterparty_pronoun_resolution",
"documents_by_counterparty",
"documents_followup",
"documents_pivot",
"fourth_pivot",
"grounded_counterparty",
"human_dialog",
"incoming_value_flow",
"movements_pivot",
"net_value_flow",
"open_scope_total",
"organization_clarification",
"organization_scope",
"payments_followup",
"payout_value_flow",
"pivot_seed",
"post_f_integrity_hardening",
"second_pivot",
"third_pivot",
"topic_reset",
"value_flow_comparison",
"value_flow_ranking",
"year_switch",
"year_switch_after_fourth_pivot"
]
},
"source_session_id": null,
"session": {
"session_id": null,
"mode": "agent_semantic_run",
"items": [
{
"message_id": "agent-user-001",
"role": "user",
"text": "Покажи документы по Жуковке 51.",
"created_at": "2026-04-23T18:44:25+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-002",
"role": "user",
"text": "Хорошо, а теперь платежи по нему тоже покажи.",
"created_at": "2026-04-23T18:44:25+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-003",
"role": "user",
"text": "А по нему договоры?",
"created_at": "2026-04-23T18:44:25+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-004",
"role": "user",
"text": "А по нему документы?",
"created_at": "2026-04-23T18:44:25+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-005",
"role": "user",
"text": "А по нему платежи?",
"created_at": "2026-04-23T18:44:25+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-006",
"role": "user",
"text": "А за 2021?",
"created_at": "2026-04-23T18:44:25+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-007",
"role": "user",
"text": "С Жуковкой закончили. Теперь нужна другая задача: быстрый денежный срез по одной организации. Если для ответа нужна организация, просто уточни ее. Сколько вообще входящих денег было за 2020 год?",
"created_at": "2026-04-23T18:44:25+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-008",
"role": "user",
"text": "По ООО Альтернатива Плюс.",
"created_at": "2026-04-23T18:44:25+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-009",
"role": "user",
"text": "Понял, тогда за все время.",
"created_at": "2026-04-23T18:44:25+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-010",
"role": "user",
"text": "Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?",
"created_at": "2026-04-23T18:44:25+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-011",
"role": "user",
"text": "А что по ООО Альтернатива Плюс больше уже за 2021 год: входящие или исходящие деньги?",
"created_at": "2026-04-23T18:44:25+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-012",
"role": "user",
"text": "И кто больше всего принес денег этой организации в 2020 году?",
"created_at": "2026-04-23T18:44:25+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-013",
"role": "user",
"text": "А в 2021 году?",
"created_at": "2026-04-23T18:44:25+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-014",
"role": "user",
"text": "Теперь отдельная тема по конкретному контрагенту. Найди в 1С Группу СВК.",
"created_at": "2026-04-23T18:44:25+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-015",
"role": "user",
"text": "Сколько получили по нему за 2020 год?",
"created_at": "2026-04-23T18:44:25+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-016",
"role": "user",
"text": "А теперь сколько заплатили?",
"created_at": "2026-04-23T18:44:25+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-017",
"role": "user",
"text": "А какое нетто?",
"created_at": "2026-04-23T18:44:25+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-018",
"role": "user",
"text": "А по документам?",
"created_at": "2026-04-23T18:44:25+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-019",
"role": "user",
"text": "А по движениям?",
"created_at": "2026-04-23T18:44:25+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
}
],
"agent_run": true,
"metadata": {
"assistant_prompt_version": null,
"decomposition_prompt_version": null,
"prompt_fingerprint": null,
"agent_focus": "Post-F repeated pivots + open-scope organization money + grounded SVK counterparty chain",
"architecture_phase": "turnaround_11",
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase82_human_mixed_integrity_status_dialog.json",
"scenario_id": "address_truth_harness_phase82_human_mixed_integrity_status_dialog",
"semantic_tags": [
"all_time_followup",
"contracts_followup",
"counterparty_pronoun_resolution",
"documents_by_counterparty",
"documents_followup",
"documents_pivot",
"fourth_pivot",
"grounded_counterparty",
"human_dialog",
"incoming_value_flow",
"movements_pivot",
"net_value_flow",
"open_scope_total",
"organization_clarification",
"organization_scope",
"payments_followup",
"payout_value_flow",
"pivot_seed",
"post_f_integrity_hardening",
"second_pivot",
"third_pivot",
"topic_reset",
"value_flow_comparison",
"value_flow_ranking",
"year_switch",
"year_switch_after_fourth_pivot"
]
}
}
}
@@ -0,0 +1,353 @@
{
"saved_at": "2026-04-24T14:06:30+00:00",
"generation_id": "gen-ag04241406-abe4d8",
"mode": "saved_user_sessions",
"title": "AGENT | Post-F cross-stage semantic integrity canary",
"agent_run": true,
"questions": [
"Мне нужно понять, где в 1С по НДС вообще лежат данные. Какие объекты стоит смотреть по НДС?",
"Хорошо, тогда покажи движения по ООО Альтернатива Плюс.",
"За 2020 год.",
"А теперь по документам?",
"А теперь за 2021 год?",
"А теперь за все время?",
"С НДС закончили. Новая тема: покажи документы по Жуковке 51.",
"Хорошо, а теперь платежи по нему тоже покажи.",
"А по нему договоры?",
"А по нему документы?",
"А за 2021?",
"С Жуковкой закончили. Теперь другая задача: быстрый денежный срез по одной организации. Если для ответа нужна организация, просто уточни ее. Сколько вообще входящих денег было за 2020 год?",
"По ООО Альтернатива Плюс.",
"Понял, тогда за все время.",
"Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?",
"И кто больше всего принес денег этой организации в 2020 году?",
"А в 2021 году?",
"Теперь отдельная тема по конкретному контрагенту. Найди в 1С Группу СВК.",
"Сколько получили по нему за 2020 год?",
"А теперь сколько заплатили?",
"А какое нетто?",
"А по документам?",
"А по движениям?",
"А теперь тот же смысл за 2021 год."
],
"metadata": {
"assistant_prompt_version": null,
"decomposition_prompt_version": null,
"prompt_fingerprint": null,
"agent_focus": "cross-stage canary: VAT metadata, numeric counterparty suffix, open organization value-flow, SVK grounded reset",
"architecture_phase": "Post-F Semantic Integrity Hardening",
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_post_f_cross_stage_canary_agent_20260424.json",
"scenario_id": "address_truth_harness_post_f_cross_stage_canary_agent_20260424",
"semantic_tags": [
"account_injection_guard",
"all_time_followup",
"bidirectional_value_flow",
"clarification_resume",
"contracts_followup",
"counterparty_pronoun_resolution",
"cross_stage_canary",
"document_lane_continuity",
"document_pivot_after_movement",
"documents_followup",
"documents_pivot",
"grounded_counterparty",
"incoming_value_flow",
"legacy_phase39",
"legacy_phase64",
"legacy_phase67",
"metadata_surface",
"movement_execution",
"movement_lane_after_metadata",
"movements_pivot",
"net_value_flow",
"numeric_counterparty_suffix",
"open_scope_total",
"organization_clarification",
"organization_scope",
"payments_followup",
"payout_value_flow",
"period_clarification_resume",
"post_f",
"repeated_pivot",
"scope_reuse",
"topic_reset",
"value_flow_ranking",
"vat_orientation",
"year_switch",
"year_switch_after_document_pivot",
"year_switch_after_repeated_pivot"
]
},
"source_session_id": null,
"session": {
"session_id": null,
"mode": "agent_semantic_run",
"items": [
{
"message_id": "agent-user-001",
"role": "user",
"text": "Мне нужно понять, где в 1С по НДС вообще лежат данные. Какие объекты стоит смотреть по НДС?",
"created_at": "2026-04-24T14:06:30+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-002",
"role": "user",
"text": "Хорошо, тогда покажи движения по ООО Альтернатива Плюс.",
"created_at": "2026-04-24T14:06:30+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-003",
"role": "user",
"text": "За 2020 год.",
"created_at": "2026-04-24T14:06:30+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-004",
"role": "user",
"text": "А теперь по документам?",
"created_at": "2026-04-24T14:06:30+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-005",
"role": "user",
"text": "А теперь за 2021 год?",
"created_at": "2026-04-24T14:06:30+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-006",
"role": "user",
"text": "А теперь за все время?",
"created_at": "2026-04-24T14:06:30+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-007",
"role": "user",
"text": "С НДС закончили. Новая тема: покажи документы по Жуковке 51.",
"created_at": "2026-04-24T14:06:30+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-008",
"role": "user",
"text": "Хорошо, а теперь платежи по нему тоже покажи.",
"created_at": "2026-04-24T14:06:30+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-009",
"role": "user",
"text": "А по нему договоры?",
"created_at": "2026-04-24T14:06:30+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-010",
"role": "user",
"text": "А по нему документы?",
"created_at": "2026-04-24T14:06:30+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-011",
"role": "user",
"text": "А за 2021?",
"created_at": "2026-04-24T14:06:30+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-012",
"role": "user",
"text": "С Жуковкой закончили. Теперь другая задача: быстрый денежный срез по одной организации. Если для ответа нужна организация, просто уточни ее. Сколько вообще входящих денег было за 2020 год?",
"created_at": "2026-04-24T14:06:30+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-013",
"role": "user",
"text": "По ООО Альтернатива Плюс.",
"created_at": "2026-04-24T14:06:30+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-014",
"role": "user",
"text": "Понял, тогда за все время.",
"created_at": "2026-04-24T14:06:30+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-015",
"role": "user",
"text": "Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?",
"created_at": "2026-04-24T14:06:30+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-016",
"role": "user",
"text": "И кто больше всего принес денег этой организации в 2020 году?",
"created_at": "2026-04-24T14:06:30+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-017",
"role": "user",
"text": "А в 2021 году?",
"created_at": "2026-04-24T14:06:30+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-018",
"role": "user",
"text": "Теперь отдельная тема по конкретному контрагенту. Найди в 1С Группу СВК.",
"created_at": "2026-04-24T14:06:30+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-019",
"role": "user",
"text": "Сколько получили по нему за 2020 год?",
"created_at": "2026-04-24T14:06:30+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-020",
"role": "user",
"text": "А теперь сколько заплатили?",
"created_at": "2026-04-24T14:06:30+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-021",
"role": "user",
"text": "А какое нетто?",
"created_at": "2026-04-24T14:06:30+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-022",
"role": "user",
"text": "А по документам?",
"created_at": "2026-04-24T14:06:30+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-023",
"role": "user",
"text": "А по движениям?",
"created_at": "2026-04-24T14:06:30+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-024",
"role": "user",
"text": "А теперь тот же смысл за 2021 год.",
"created_at": "2026-04-24T14:06:30+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
}
],
"agent_run": true,
"metadata": {
"assistant_prompt_version": null,
"decomposition_prompt_version": null,
"prompt_fingerprint": null,
"agent_focus": "cross-stage canary: VAT metadata, numeric counterparty suffix, open organization value-flow, SVK grounded reset",
"architecture_phase": "Post-F Semantic Integrity Hardening",
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_post_f_cross_stage_canary_agent_20260424.json",
"scenario_id": "address_truth_harness_post_f_cross_stage_canary_agent_20260424",
"semantic_tags": [
"account_injection_guard",
"all_time_followup",
"bidirectional_value_flow",
"clarification_resume",
"contracts_followup",
"counterparty_pronoun_resolution",
"cross_stage_canary",
"document_lane_continuity",
"document_pivot_after_movement",
"documents_followup",
"documents_pivot",
"grounded_counterparty",
"incoming_value_flow",
"legacy_phase39",
"legacy_phase64",
"legacy_phase67",
"metadata_surface",
"movement_execution",
"movement_lane_after_metadata",
"movements_pivot",
"net_value_flow",
"numeric_counterparty_suffix",
"open_scope_total",
"organization_clarification",
"organization_scope",
"payments_followup",
"payout_value_flow",
"period_clarification_resume",
"post_f",
"repeated_pivot",
"scope_reuse",
"topic_reset",
"value_flow_ranking",
"vat_orientation",
"year_switch",
"year_switch_after_document_pivot",
"year_switch_after_repeated_pivot"
]
}
}
}
@@ -0,0 +1,82 @@
{
"suite_id": "assistant_saved_session_gen-ag04231844-8e552a",
"suite_version": "0.1.0",
"schema_version": "assistant_saved_session_suite_v0_1",
"generated_at": "2026-04-23T18:44:25+00:00",
"generation_id": "gen-ag04231844-8e552a",
"mode": "saved_user_sessions",
"title": "AGENT | ARCH: Post-F Semantic Integrity Hardening | Смешанный живой диалог: repeated pivots, орг-срез и СВК",
"domain": "address_phase82_human_mixed_integrity_status_dialog",
"scenario_count": 1,
"case_ids": [
"SAVED-001"
],
"cases": [
{
"case_id": "SAVED-001",
"scenario_tag": "agent_saved_user_sessions",
"title": "AGENT | ARCH: Post-F Semantic Integrity Hardening | Смешанный живой диалог: repeated pivots, орг-срез и СВК",
"question_type": "followup",
"broadness_level": "medium",
"turns": [
{
"user_message": "Покажи документы по Жуковке 51."
},
{
"user_message": "Хорошо, а теперь платежи по нему тоже покажи."
},
{
"user_message": "А по нему договоры?"
},
{
"user_message": "А по нему документы?"
},
{
"user_message": "А по нему платежи?"
},
{
"user_message": "А за 2021?"
},
{
"user_message": "С Жуковкой закончили. Теперь нужна другая задача: быстрый денежный срез по одной организации. Если для ответа нужна организация, просто уточни ее. Сколько вообще входящих денег было за 2020 год?"
},
{
"user_message": "По ООО Альтернатива Плюс."
},
{
"user_message": "Понял, тогда за все время."
},
{
"user_message": "Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?"
},
{
"user_message": "А что по ООО Альтернатива Плюс больше уже за 2021 год: входящие или исходящие деньги?"
},
{
"user_message": "И кто больше всего принес денег этой организации в 2020 году?"
},
{
"user_message": "А в 2021 году?"
},
{
"user_message": "Теперь отдельная тема по конкретному контрагенту. Найди в 1С Группу СВК."
},
{
"user_message": "Сколько получили по нему за 2020 год?"
},
{
"user_message": "А теперь сколько заплатили?"
},
{
"user_message": "А какое нетто?"
},
{
"user_message": "А по документам?"
},
{
"user_message": "А по движениям?"
}
]
}
]
}
@@ -0,0 +1,97 @@
{
"suite_id": "assistant_saved_session_gen-ag04241406-abe4d8",
"suite_version": "0.1.0",
"schema_version": "assistant_saved_session_suite_v0_1",
"generated_at": "2026-04-24T14:06:30+00:00",
"generation_id": "gen-ag04241406-abe4d8",
"mode": "saved_user_sessions",
"title": "AGENT | Post-F cross-stage semantic integrity canary",
"domain": "address_post_f_cross_stage_canary_agent",
"scenario_count": 1,
"case_ids": [
"SAVED-001"
],
"cases": [
{
"case_id": "SAVED-001",
"scenario_tag": "agent_saved_user_sessions",
"title": "AGENT | Post-F cross-stage semantic integrity canary",
"question_type": "followup",
"broadness_level": "medium",
"turns": [
{
"user_message": "Мне нужно понять, где в 1С по НДС вообще лежат данные. Какие объекты стоит смотреть по НДС?"
},
{
"user_message": "Хорошо, тогда покажи движения по ООО Альтернатива Плюс."
},
{
"user_message": "За 2020 год."
},
{
"user_message": "А теперь по документам?"
},
{
"user_message": "А теперь за 2021 год?"
},
{
"user_message": "А теперь за все время?"
},
{
"user_message": "С НДС закончили. Новая тема: покажи документы по Жуковке 51."
},
{
"user_message": "Хорошо, а теперь платежи по нему тоже покажи."
},
{
"user_message": "А по нему договоры?"
},
{
"user_message": "А по нему документы?"
},
{
"user_message": "А за 2021?"
},
{
"user_message": "С Жуковкой закончили. Теперь другая задача: быстрый денежный срез по одной организации. Если для ответа нужна организация, просто уточни ее. Сколько вообще входящих денег было за 2020 год?"
},
{
"user_message": "По ООО Альтернатива Плюс."
},
{
"user_message": "Понял, тогда за все время."
},
{
"user_message": "Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?"
},
{
"user_message": "И кто больше всего принес денег этой организации в 2020 году?"
},
{
"user_message": "А в 2021 году?"
},
{
"user_message": "Теперь отдельная тема по конкретному контрагенту. Найди в 1С Группу СВК."
},
{
"user_message": "Сколько получили по нему за 2020 год?"
},
{
"user_message": "А теперь сколько заплатили?"
},
{
"user_message": "А какое нетто?"
},
{
"user_message": "А по документам?"
},
{
"user_message": "А по движениям?"
},
{
"user_message": "А теперь тот же смысл за 2021 год."
}
]
}
]
}
@@ -0,0 +1,78 @@
{
"suite_id": "assistant_saved_session_runtime_job-C4l-4Yfuqm",
"suite_version": "0.1.0",
"schema_version": "assistant_saved_session_runtime_v0_1",
"title": "AGENT | ARCH: Post-F Semantic Integrity Hardening | Смешанный живой диалог: repeated pivots, орг-срез и СВК",
"scenario_count": 1,
"case_ids": [
"SAVED-001"
],
"cases": [
{
"case_id": "SAVED-001",
"scenario_tag": "saved_user_sessions_runtime",
"title": "AGENT | ARCH: Post-F Semantic Integrity Hardening | Смешанный живой диалог: repeated pivots, орг-срез и СВК",
"question_type": "followup",
"broadness_level": "medium",
"turns": [
{
"user_message": "Покажи документы по Жуковке 51."
},
{
"user_message": "Хорошо, а теперь платежи по нему тоже покажи."
},
{
"user_message": "А по нему договоры?"
},
{
"user_message": "А по нему документы?"
},
{
"user_message": "А по нему платежи?"
},
{
"user_message": "А за 2021?"
},
{
"user_message": "С Жуковкой закончили. Теперь нужна другая задача: быстрый денежный срез по одной организации. Если для ответа нужна организация, просто уточни ее. Сколько вообще входящих денег было за 2020 год?"
},
{
"user_message": "По ООО Альтернатива Плюс."
},
{
"user_message": "Понял, тогда за все время."
},
{
"user_message": "Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?"
},
{
"user_message": "А что по ООО Альтернатива Плюс больше уже за 2021 год: входящие или исходящие деньги?"
},
{
"user_message": "И кто больше всего принес денег этой организации в 2020 году?"
},
{
"user_message": "А в 2021 году?"
},
{
"user_message": "Теперь отдельная тема по конкретному контрагенту. Найди в 1С Группу СВК."
},
{
"user_message": "Сколько получили по нему за 2020 год?"
},
{
"user_message": "А теперь сколько заплатили?"
},
{
"user_message": "А какое нетто?"
},
{
"user_message": "А по документам?"
},
{
"user_message": "А по движениям?"
}
]
}
]
}
@@ -0,0 +1,123 @@
{
"suite_id": "assistant_saved_session_runtime_job-FvKPQG152c",
"suite_version": "0.1.0",
"schema_version": "assistant_saved_session_runtime_v0_1",
"title": "БОЛЬШОЙ ОБЩИЙ Ручная сессия 16.04.2026, 21:26:06",
"scenario_count": 1,
"case_ids": [
"SAVED-001"
],
"cases": [
{
"case_id": "SAVED-001",
"scenario_tag": "saved_user_sessions_runtime",
"title": "БОЛЬШОЙ ОБЩИЙ Ручная сессия 16.04.2026, 21:26:06",
"question_type": "followup",
"broadness_level": "medium",
"turns": [
{
"user_message": "приветик - че как там дела"
},
{
"user_message": "расскажи что можешь интересного"
},
{
"user_message": "кайф - что там на складе по остаткам?"
},
{
"user_message": "АЛЬТЕРНАТИВА"
},
{
"user_message": "а исторические остатки на другие даты умеешь?"
},
{
"user_message": "давай на июль 2017"
},
{
"user_message": "март 2016"
},
{
"user_message": "По выбранному объекту \"Рабочая станция универсального специалиста (индивидуальное изготовление)\": где взяли это?"
},
{
"user_message": "а кому продали?"
},
{
"user_message": "у тебя написано кто контрагент: рабочая станция - это ошибка?"
},
{
"user_message": "ндс можешь прикинуть на дату покупки рабочей станции?"
},
{
"user_message": "а какой ндс мы должны сгрузить на март 2020?"
},
{
"user_message": "прикинь какой ндс нам надо заплатить на февраль 2017"
},
{
"user_message": "кто у нас самый доходный клиент за все время"
},
{
"user_message": "кто нам должен денег на май 2017"
},
{
"user_message": "а какой ндс мы должны примерно заплатить за этот период?"
},
{
"user_message": "мы должны комуто денег на сегодня?"
},
{
"user_message": "а нам?"
},
{
"user_message": "какой у нас самый доходный год"
},
{
"user_message": "а за 2017 мы скок заработали?"
},
{
"user_message": "сколько вообще денег мы заработали за все время?"
},
{
"user_message": "ты умеешь считать дельту по договорам?"
},
{
"user_message": "по чепурнову покажи все доки"
},
{
"user_message": "а по свк"
},
{
"user_message": "а сейчас у нас есть что на складе?"
},
{
"user_message": "что нам отгружал чепурнов? какой товар или услугу?"
},
{
"user_message": "какие остатки на складе на сегодня"
},
{
"user_message": "остатки на март 2016"
},
{
"user_message": "хвосты покажи по счету 60 на август 2022"
},
{
"user_message": "Есть ли остатки товара, которые закупались очень давно"
},
{
"user_message": "Какие конкретно номенклатуры формируют остаток по складу на май 2020"
},
{
"user_message": "а по Альтернативе Плюс сколько лет активности в базе 1С?"
},
{
"user_message": "Как ты оценишь деятельность компании?"
},
{
"user_message": "какое нетто по деньгам с Группа СВК за 2020 год: сколько получили и сколько заплатили?"
}
]
}
]
}
@@ -0,0 +1,42 @@
{
"suite_id": "assistant_saved_session_runtime_job-IYhHuIIG_n",
"suite_version": "0.1.0",
"schema_version": "assistant_saved_session_runtime_v0_1",
"title": "AGENT | Живой диалог по организации: денежный срез, сравнение и рейтинг",
"scenario_count": 1,
"case_ids": [
"SAVED-001"
],
"cases": [
{
"case_id": "SAVED-001",
"scenario_tag": "saved_user_sessions_runtime",
"title": "AGENT | Живой диалог по организации: денежный срез, сравнение и рейтинг",
"question_type": "followup",
"broadness_level": "medium",
"turns": [
{
"user_message": "Хочу быстрый денежный срез по одной организации без привязки к контрагенту. Сколько вообще входящих денег было за 2020 год?"
},
{
"user_message": "По ООО Альтернатива Плюс."
},
{
"user_message": "Понял, тогда за все время."
},
{
"user_message": "Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?"
},
{
"user_message": "А что по ООО Альтернатива Плюс больше уже за 2021 год: входящие или исходящие деньги?"
},
{
"user_message": "И кто больше всего принес денег этой организации в 2020 году?"
},
{
"user_message": "А в 2021 году?"
}
]
}
]
}
@@ -0,0 +1,123 @@
{
"suite_id": "assistant_saved_session_runtime_job-LndQtjGZU3",
"suite_version": "0.1.0",
"schema_version": "assistant_saved_session_runtime_v0_1",
"title": "БОЛЬШОЙ ОБЩИЙ Ручная сессия 16.04.2026, 21:26:06",
"scenario_count": 1,
"case_ids": [
"SAVED-001"
],
"cases": [
{
"case_id": "SAVED-001",
"scenario_tag": "saved_user_sessions_runtime",
"title": "БОЛЬШОЙ ОБЩИЙ Ручная сессия 16.04.2026, 21:26:06",
"question_type": "followup",
"broadness_level": "medium",
"turns": [
{
"user_message": "приветик - че как там дела"
},
{
"user_message": "расскажи что можешь интересного"
},
{
"user_message": "кайф - что там на складе по остаткам?"
},
{
"user_message": "АЛЬТЕРНАТИВА"
},
{
"user_message": "а исторические остатки на другие даты умеешь?"
},
{
"user_message": "давай на июль 2017"
},
{
"user_message": "март 2016"
},
{
"user_message": "По выбранному объекту \"Рабочая станция универсального специалиста (индивидуальное изготовление)\": где взяли это?"
},
{
"user_message": "а кому продали?"
},
{
"user_message": "у тебя написано кто контрагент: рабочая станция - это ошибка?"
},
{
"user_message": "ндс можешь прикинуть на дату покупки рабочей станции?"
},
{
"user_message": "а какой ндс мы должны сгрузить на март 2020?"
},
{
"user_message": "прикинь какой ндс нам надо заплатить на февраль 2017"
},
{
"user_message": "кто у нас самый доходный клиент за все время"
},
{
"user_message": "кто нам должен денег на май 2017"
},
{
"user_message": "а какой ндс мы должны примерно заплатить за этот период?"
},
{
"user_message": "мы должны комуто денег на сегодня?"
},
{
"user_message": "а нам?"
},
{
"user_message": "какой у нас самый доходный год"
},
{
"user_message": "а за 2017 мы скок заработали?"
},
{
"user_message": "сколько вообще денег мы заработали за все время?"
},
{
"user_message": "ты умеешь считать дельту по договорам?"
},
{
"user_message": "по чепурнову покажи все доки"
},
{
"user_message": "а по свк"
},
{
"user_message": "а сейчас у нас есть что на складе?"
},
{
"user_message": "что нам отгружал чепурнов? какой товар или услугу?"
},
{
"user_message": "какие остатки на складе на сегодня"
},
{
"user_message": "остатки на март 2016"
},
{
"user_message": "хвосты покажи по счету 60 на август 2022"
},
{
"user_message": "Есть ли остатки товара, которые закупались очень давно"
},
{
"user_message": "Какие конкретно номенклатуры формируют остаток по складу на май 2020"
},
{
"user_message": "а по Альтернативе Плюс сколько лет активности в базе 1С?"
},
{
"user_message": "Как ты оценишь деятельность компании?"
},
{
"user_message": "какое нетто по деньгам с Группа СВК за 2020 год: сколько получили и сколько заплатили?"
}
]
}
]
}
@@ -0,0 +1,42 @@
{
"suite_id": "assistant_saved_session_runtime_job-X1z7AFW_Nq",
"suite_version": "0.1.0",
"schema_version": "assistant_saved_session_runtime_v0_1",
"title": "AGENT | Живой диалог по СВК: деньги, нетто, документы и движения",
"scenario_count": 1,
"case_ids": [
"SAVED-001"
],
"cases": [
{
"case_id": "SAVED-001",
"scenario_tag": "saved_user_sessions_runtime",
"title": "AGENT | Живой диалог по СВК: деньги, нетто, документы и движения",
"question_type": "followup",
"broadness_level": "medium",
"turns": [
{
"user_message": "Хочу проверить одного контрагента. Найди в 1С Группу СВК."
},
{
"user_message": "Посмотри, сколько денег мы получили от него за 2020 год."
},
{
"user_message": "А теперь сколько мы ему заплатили?"
},
{
"user_message": "А какое получилось нетто?"
},
{
"user_message": "А по документам?"
},
{
"user_message": "А по движениям?"
},
{
"user_message": "А теперь за 2021 год?"
}
]
}
]
}