ARCH: продолжить metadata continuity для MCP discovery
This commit is contained in:
parent
561b4ea45c
commit
d66e2bfb01
|
|
@ -112,6 +112,29 @@ function localizeLine(value) {
|
||||||
if (/^Counterparty monthly net value-flow breakdown was grouped by month over confirmed incoming and outgoing 1C rows$/i.test(value)) {
|
if (/^Counterparty monthly net value-flow breakdown was grouped by month over confirmed incoming and outgoing 1C rows$/i.test(value)) {
|
||||||
return "Помесячная нетто-раскладка сгруппирована только по подтвержденным входящим и исходящим строкам 1С.";
|
return "Помесячная нетто-раскладка сгруппирована только по подтвержденным входящим и исходящим строкам 1С.";
|
||||||
}
|
}
|
||||||
|
const metadataSurfaceMatch = value.match(/^Confirmed 1C metadata surface(?: for scope "([^"]+)")?: (\d+) rows and (\d+) matching objects$/i);
|
||||||
|
if (metadataSurfaceMatch) {
|
||||||
|
const scopePart = metadataSurfaceMatch[1] ? ` по области "${metadataSurfaceMatch[1]}"` : "";
|
||||||
|
return `В 1С подтверждена metadata-поверхность${scopePart}: ${metadataSurfaceMatch[2]} строк metadata-ответа и ${metadataSurfaceMatch[3]} совпавших объекта(ов).`;
|
||||||
|
}
|
||||||
|
const metadataObjectSetsMatch = value.match(/^Available metadata object sets: (.+)$/i);
|
||||||
|
if (metadataObjectSetsMatch) {
|
||||||
|
return `Доступные типы metadata-объектов: ${metadataObjectSetsMatch[1]}.`;
|
||||||
|
}
|
||||||
|
const metadataFieldsMatch = value.match(/^Available metadata fields\/sections: (.+)$/i);
|
||||||
|
if (metadataFieldsMatch) {
|
||||||
|
return `Доступные metadata-поля/секции: ${metadataFieldsMatch[1]}.`;
|
||||||
|
}
|
||||||
|
if (/^Detailed metadata fields were not returned by this MCP metadata probe$/i.test(value)) {
|
||||||
|
return "Эта MCP-проверка metadata не вернула детальный список полей.";
|
||||||
|
}
|
||||||
|
const noMatchingMetadataScopeMatch = value.match(/^No matching 1C metadata objects were confirmed for scope "([^"]+)"$/i);
|
||||||
|
if (noMatchingMetadataScopeMatch) {
|
||||||
|
return `В 1С не подтверждены metadata-объекты по области "${noMatchingMetadataScopeMatch[1]}".`;
|
||||||
|
}
|
||||||
|
if (/^No matching 1C metadata objects were confirmed by this MCP metadata probe$/i.test(value)) {
|
||||||
|
return "В 1С эта MCP-проверка не подтвердила подходящих metadata-объектов.";
|
||||||
|
}
|
||||||
if (/^Legal registration date is not proven by this MCP discovery pilot$/i.test(value)) {
|
if (/^Legal registration date is not proven by this MCP discovery pilot$/i.test(value)) {
|
||||||
return "Юридическая дата регистрации этим поиском не подтверждена.";
|
return "Юридическая дата регистрации этим поиском не подтверждена.";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -141,6 +141,13 @@ function mapPilotScopeToFollowupMeaning(pilotScope) {
|
||||||
unsupported: "counterparty_bidirectional_value_flow_or_netting"
|
unsupported: "counterparty_bidirectional_value_flow_or_netting"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (pilotScope === "metadata_inspection_v1") {
|
||||||
|
return {
|
||||||
|
domain: "metadata",
|
||||||
|
action: "inspect_catalog",
|
||||||
|
unsupported: "1c_metadata_surface"
|
||||||
|
};
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
domain: null,
|
domain: null,
|
||||||
action: null,
|
action: null,
|
||||||
|
|
@ -183,6 +190,7 @@ function collectFollowupDiscoverySeed(followupContext) {
|
||||||
const mapped = mapPilotScopeToFollowupMeaning(pilotScope).domain !== null
|
const mapped = mapPilotScopeToFollowupMeaning(pilotScope).domain !== null
|
||||||
? mapPilotScopeToFollowupMeaning(pilotScope)
|
? mapPilotScopeToFollowupMeaning(pilotScope)
|
||||||
: mapAddressIntentToFollowupMeaning(previousIntent);
|
: mapAddressIntentToFollowupMeaning(previousIntent);
|
||||||
|
const discoveryEntities = collectEntityCandidates(followupContext?.previous_discovery_entity_candidates);
|
||||||
const counterparty = toNonEmptyString(previousFilters?.counterparty) ??
|
const counterparty = toNonEmptyString(previousFilters?.counterparty) ??
|
||||||
toNonEmptyString(rootFilters?.counterparty) ??
|
toNonEmptyString(rootFilters?.counterparty) ??
|
||||||
(toNonEmptyString(followupContext?.previous_anchor_type) === "counterparty"
|
(toNonEmptyString(followupContext?.previous_anchor_type) === "counterparty"
|
||||||
|
|
@ -201,6 +209,7 @@ function collectFollowupDiscoverySeed(followupContext) {
|
||||||
action: mapped.action,
|
action: mapped.action,
|
||||||
unsupported: mapped.unsupported,
|
unsupported: mapped.unsupported,
|
||||||
counterparty,
|
counterparty,
|
||||||
|
discoveryEntity: discoveryEntities[0] ?? null,
|
||||||
organization,
|
organization,
|
||||||
dateScope
|
dateScope
|
||||||
};
|
};
|
||||||
|
|
@ -227,6 +236,9 @@ function hasMetadataSignal(text) {
|
||||||
return (/(?:\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u044b|\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b|\u0441\u043f\u0440\u0430\u0432\u043e\u0447\u043d\u0438\u043a\u0438|\u043f\u043e\u043b(?:\u0435|\u044f)|registers?|documents?|catalogs?|fields?)/iu.test(text) &&
|
return (/(?:\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u044b|\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b|\u0441\u043f\u0440\u0430\u0432\u043e\u0447\u043d\u0438\u043a\u0438|\u043f\u043e\u043b(?:\u0435|\u044f)|registers?|documents?|catalogs?|fields?)/iu.test(text) &&
|
||||||
/(?:\u0435\u0441\u0442\u044c|\u0434\u043e\u0441\u0442\u0443\u043f\u043d|\u0432\s+1\u0441|available|exist)/iu.test(text));
|
/(?:\u0435\u0441\u0442\u044c|\u0434\u043e\u0441\u0442\u0443\u043f\u043d|\u0432\s+1\u0441|available|exist)/iu.test(text));
|
||||||
}
|
}
|
||||||
|
function hasMetadataObjectHint(text) {
|
||||||
|
return /(?:\u0440\u0435\u0433\u0438\u0441\u0442\u0440(?:\u044b)?|\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442(?:\u044b)?|\u0441\u043f\u0440\u0430\u0432\u043e\u0447\u043d\u0438\u043a(?:\u0438)?|\u043f\u043e\u043b(?:\u0435|\u044f)|registers?|documents?|catalogs?|fields?)/iu.test(text);
|
||||||
|
}
|
||||||
function metadataActionFromRawText(text) {
|
function metadataActionFromRawText(text) {
|
||||||
if (/(?:\u043f\u043e\u043b(?:\u0435|\u044f)|field)/iu.test(text)) {
|
if (/(?:\u043f\u043e\u043b(?:\u0435|\u044f)|field)/iu.test(text)) {
|
||||||
return "inspect_fields";
|
return "inspect_fields";
|
||||||
|
|
@ -322,9 +334,13 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
||||||
!rawLifecycleSignal &&
|
!rawLifecycleSignal &&
|
||||||
!rawValueFlowSignal &&
|
!rawValueFlowSignal &&
|
||||||
(monthlyAggregationSignal || explicitDateScopeLiteralDetected || predecomposeDateScope));
|
(monthlyAggregationSignal || explicitDateScopeLiteralDetected || predecomposeDateScope));
|
||||||
const seededDomain = followupDiscoverySeedApplicable ? followupSeed.domain : null;
|
const metadataFollowupSeedApplicable = Boolean(followupSeed.domain === "metadata" &&
|
||||||
const seededAction = followupDiscoverySeedApplicable ? followupSeed.action : null;
|
!rawLifecycleSignal &&
|
||||||
const seededUnsupported = followupDiscoverySeedApplicable ? followupSeed.unsupported : null;
|
!rawValueFlowSignal &&
|
||||||
|
hasMetadataObjectHint(rawText));
|
||||||
|
const seededDomain = followupDiscoverySeedApplicable || metadataFollowupSeedApplicable ? followupSeed.domain : null;
|
||||||
|
const seededAction = followupDiscoverySeedApplicable || metadataFollowupSeedApplicable ? followupSeed.action : null;
|
||||||
|
const seededUnsupported = followupDiscoverySeedApplicable || metadataFollowupSeedApplicable ? followupSeed.unsupported : null;
|
||||||
const lifecycleSignal = rawLifecycleSignal || seededDomain === "counterparty_lifecycle";
|
const lifecycleSignal = rawLifecycleSignal || seededDomain === "counterparty_lifecycle";
|
||||||
const bidirectionalValueFlowSignal = !lifecycleSignal &&
|
const bidirectionalValueFlowSignal = !lifecycleSignal &&
|
||||||
(rawBidirectionalValueFlowSignal || seededAction === "net_value_flow");
|
(rawBidirectionalValueFlowSignal || seededAction === "net_value_flow");
|
||||||
|
|
@ -338,11 +354,14 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
||||||
unsupported: unsupported ?? seededUnsupported,
|
unsupported: unsupported ?? seededUnsupported,
|
||||||
lifecycleSignal,
|
lifecycleSignal,
|
||||||
valueFlowSignal,
|
valueFlowSignal,
|
||||||
metadataSignal: rawMetadataSignal
|
metadataSignal: rawMetadataSignal || metadataFollowupSeedApplicable
|
||||||
});
|
});
|
||||||
const entityCandidates = collectEntityCandidates(assistantTurnMeaning?.explicit_entity_candidates);
|
const entityCandidates = collectEntityCandidates(assistantTurnMeaning?.explicit_entity_candidates);
|
||||||
pushUnique(entityCandidates, predecomposeEntities.counterparty);
|
pushUnique(entityCandidates, predecomposeEntities.counterparty);
|
||||||
pushUnique(entityCandidates, followupSeed.counterparty);
|
pushUnique(entityCandidates, followupSeed.counterparty);
|
||||||
|
if ((rawMetadataSignal || metadataFollowupSeedApplicable) && !followupSeed.counterparty) {
|
||||||
|
pushUnique(entityCandidates, followupSeed.discoveryEntity);
|
||||||
|
}
|
||||||
if (valueFlowSignal && !predecomposeEntities.counterparty && !followupSeed.counterparty) {
|
if (valueFlowSignal && !predecomposeEntities.counterparty && !followupSeed.counterparty) {
|
||||||
pushUnique(entityCandidates, predecomposeEntities.organization);
|
pushUnique(entityCandidates, predecomposeEntities.organization);
|
||||||
pushUnique(entityCandidates, followupSeed.organization);
|
pushUnique(entityCandidates, followupSeed.organization);
|
||||||
|
|
@ -356,7 +375,7 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
||||||
? "counterparty_lifecycle"
|
? "counterparty_lifecycle"
|
||||||
: valueFlowSignal
|
: valueFlowSignal
|
||||||
? "counterparty_value"
|
? "counterparty_value"
|
||||||
: rawMetadataSignal
|
: rawMetadataSignal || metadataFollowupSeedApplicable
|
||||||
? "metadata"
|
? "metadata"
|
||||||
: rawDomain ?? seededDomain,
|
: rawDomain ?? seededDomain,
|
||||||
asked_action_family: lifecycleSignal
|
asked_action_family: lifecycleSignal
|
||||||
|
|
@ -367,8 +386,8 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
||||||
: payoutSignal
|
: payoutSignal
|
||||||
? "payout"
|
? "payout"
|
||||||
: rawAction ?? seededAction ?? "turnover"
|
: rawAction ?? seededAction ?? "turnover"
|
||||||
: rawMetadataSignal
|
: rawMetadataSignal || metadataFollowupSeedApplicable
|
||||||
? metadataActionFromRawText(rawText)
|
? metadataActionFromRawText(rawText) ?? seededAction
|
||||||
: rawAction ?? seededAction,
|
: rawAction ?? seededAction,
|
||||||
asked_aggregation_axis: monthlyAggregationSignal ? "month" : rawAggregationAxis,
|
asked_aggregation_axis: monthlyAggregationSignal ? "month" : rawAggregationAxis,
|
||||||
explicit_entity_candidates: entityCandidates,
|
explicit_entity_candidates: entityCandidates,
|
||||||
|
|
@ -383,7 +402,7 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
||||||
: payoutSignal
|
: payoutSignal
|
||||||
? "counterparty_payouts_or_outflow"
|
? "counterparty_payouts_or_outflow"
|
||||||
: seededUnsupported ?? "counterparty_value_or_turnover"
|
: seededUnsupported ?? "counterparty_value_or_turnover"
|
||||||
: rawMetadataSignal
|
: rawMetadataSignal || metadataFollowupSeedApplicable
|
||||||
? "1c_metadata_surface"
|
? "1c_metadata_surface"
|
||||||
: followupDiscoverySeedApplicable
|
: followupDiscoverySeedApplicable
|
||||||
? seededUnsupported
|
? seededUnsupported
|
||||||
|
|
@ -393,6 +412,7 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
||||||
lifecycleSignal ||
|
lifecycleSignal ||
|
||||||
valueFlowSignal ||
|
valueFlowSignal ||
|
||||||
rawMetadataSignal ||
|
rawMetadataSignal ||
|
||||||
|
metadataFollowupSeedApplicable ||
|
||||||
followupDiscoverySeedApplicable)
|
followupDiscoverySeedApplicable)
|
||||||
};
|
};
|
||||||
const cleanTurnMeaning = {};
|
const cleanTurnMeaning = {};
|
||||||
|
|
@ -424,15 +444,15 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
||||||
unsupported: unsupported ?? seededUnsupported,
|
unsupported: unsupported ?? seededUnsupported,
|
||||||
lifecycleSignal,
|
lifecycleSignal,
|
||||||
valueFlowSignal,
|
valueFlowSignal,
|
||||||
metadataSignal: rawMetadataSignal,
|
metadataSignal: rawMetadataSignal || metadataFollowupSeedApplicable,
|
||||||
semanticDataNeed,
|
semanticDataNeed,
|
||||||
explicitIntentCandidate,
|
explicitIntentCandidate,
|
||||||
followupDiscoverySeedApplicable
|
followupDiscoverySeedApplicable: followupDiscoverySeedApplicable || metadataFollowupSeedApplicable
|
||||||
});
|
});
|
||||||
const hasTurnMeaning = Object.keys(cleanTurnMeaning).length > 0;
|
const hasTurnMeaning = Object.keys(cleanTurnMeaning).length > 0;
|
||||||
const sourceSignal = assistantTurnMeaning
|
const sourceSignal = assistantTurnMeaning
|
||||||
? "assistant_turn_meaning"
|
? "assistant_turn_meaning"
|
||||||
: followupDiscoverySeedApplicable
|
: followupDiscoverySeedApplicable || metadataFollowupSeedApplicable
|
||||||
? "followup_context"
|
? "followup_context"
|
||||||
: predecomposeContract
|
: predecomposeContract
|
||||||
? "predecompose_contract"
|
? "predecompose_contract"
|
||||||
|
|
@ -440,7 +460,7 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
||||||
? "raw_text"
|
? "raw_text"
|
||||||
: valueFlowSignal
|
: valueFlowSignal
|
||||||
? "raw_text"
|
? "raw_text"
|
||||||
: rawMetadataSignal
|
: rawMetadataSignal || metadataFollowupSeedApplicable
|
||||||
? "raw_text"
|
? "raw_text"
|
||||||
: "none";
|
: "none";
|
||||||
if (lifecycleSignal) {
|
if (lifecycleSignal) {
|
||||||
|
|
@ -464,6 +484,9 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
||||||
if (followupDiscoverySeedApplicable) {
|
if (followupDiscoverySeedApplicable) {
|
||||||
pushReason(reasonCodes, "mcp_discovery_seeded_from_followup_context");
|
pushReason(reasonCodes, "mcp_discovery_seeded_from_followup_context");
|
||||||
}
|
}
|
||||||
|
if (metadataFollowupSeedApplicable) {
|
||||||
|
pushReason(reasonCodes, "mcp_discovery_metadata_seeded_from_followup_context");
|
||||||
|
}
|
||||||
if (unsupported) {
|
if (unsupported) {
|
||||||
pushReason(reasonCodes, "mcp_discovery_unsupported_but_understood_turn");
|
pushReason(reasonCodes, "mcp_discovery_unsupported_but_understood_turn");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,30 @@ function periodPartForRecap(scopedDate) {
|
||||||
}
|
}
|
||||||
return /^\d{2}\.\d{2}\.\d{4}$/.test(scopedDate) ? ` на ${scopedDate}` : ` за период ${scopedDate}`;
|
return /^\d{2}\.\d{2}\.\d{4}$/.test(scopedDate) ? ` на ${scopedDate}` : ` за период ${scopedDate}`;
|
||||||
}
|
}
|
||||||
|
function readDiscoveryMetadataScope(debug) {
|
||||||
|
const discoveryEntry = toRecordObject(debug?.assistant_mcp_discovery_entry_point_v1);
|
||||||
|
const bridge = toRecordObject(discoveryEntry?.bridge);
|
||||||
|
const pilot = toRecordObject(bridge?.pilot);
|
||||||
|
const surface = toRecordObject(pilot?.derived_metadata_surface);
|
||||||
|
const surfaceScope = toNonEmptyString(surface?.metadata_scope);
|
||||||
|
if (surfaceScope) {
|
||||||
|
return surfaceScope;
|
||||||
|
}
|
||||||
|
const turnInput = toRecordObject(discoveryEntry?.turn_input);
|
||||||
|
const turnMeaningRef = toRecordObject(turnInput?.turn_meaning_ref);
|
||||||
|
const entityCandidates = Array.isArray(turnMeaningRef?.explicit_entity_candidates)
|
||||||
|
? turnMeaningRef.explicit_entity_candidates
|
||||||
|
: [];
|
||||||
|
for (const candidate of entityCandidates) {
|
||||||
|
const text = toNonEmptyString(candidate);
|
||||||
|
if (text) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
function buildDiscoveryRecapFactLine(input) {
|
function buildDiscoveryRecapFactLine(input) {
|
||||||
if (!input.debug || !input.counterparty) {
|
if (!input.debug) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const pilotScope = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryPilotScope)(input.debug, toNonEmptyString);
|
const pilotScope = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryPilotScope)(input.debug, toNonEmptyString);
|
||||||
|
|
@ -43,6 +65,34 @@ function buildDiscoveryRecapFactLine(input) {
|
||||||
const bridge = toRecordObject(discoveryEntry?.bridge);
|
const bridge = toRecordObject(discoveryEntry?.bridge);
|
||||||
const pilot = toRecordObject(bridge?.pilot);
|
const pilot = toRecordObject(bridge?.pilot);
|
||||||
const periodPart = periodPartForRecap(input.scopedDate);
|
const periodPart = periodPartForRecap(input.scopedDate);
|
||||||
|
if (pilotScope === "metadata_inspection_v1") {
|
||||||
|
const metadataScope = readDiscoveryMetadataScope(input.debug);
|
||||||
|
const surface = toRecordObject(pilot?.derived_metadata_surface);
|
||||||
|
const entitySets = Array.isArray(surface?.available_entity_sets)
|
||||||
|
? surface.available_entity_sets
|
||||||
|
.map((item) => toNonEmptyString(item))
|
||||||
|
.filter((item) => Boolean(item))
|
||||||
|
: [];
|
||||||
|
const fields = Array.isArray(surface?.available_fields)
|
||||||
|
? surface.available_fields
|
||||||
|
.map((item) => toNonEmptyString(item))
|
||||||
|
.filter((item) => Boolean(item))
|
||||||
|
: [];
|
||||||
|
const objects = Array.isArray(surface?.matched_objects)
|
||||||
|
? surface.matched_objects
|
||||||
|
.map((item) => toNonEmptyString(item))
|
||||||
|
.filter((item) => Boolean(item))
|
||||||
|
: [];
|
||||||
|
const rows = Number(surface?.matched_rows ?? 0);
|
||||||
|
const scopePart = metadataScope ? ` по области «${metadataScope}»` : "";
|
||||||
|
const objectsPart = objects.length > 0 ? `, нашли объекты ${objects.slice(0, 4).join(", ")}` : "";
|
||||||
|
const entitySetsPart = entitySets.length > 0 ? `, видны типы ${entitySets.slice(0, 4).join(", ")}` : "";
|
||||||
|
const fieldsPart = fields.length > 0 ? `, доступны поля/секции ${fields.slice(0, 5).join(", ")}` : "";
|
||||||
|
return `смотрели metadata-поверхность 1С${scopePart}${periodPart}: ${rows} подтвержденных строк${objectsPart}${entitySetsPart}${fieldsPart}`.trim();
|
||||||
|
}
|
||||||
|
if (!input.counterparty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
if (pilotScope === "counterparty_lifecycle_query_documents_v1") {
|
if (pilotScope === "counterparty_lifecycle_query_documents_v1") {
|
||||||
const activityPeriod = toRecordObject(pilot?.derived_activity_period);
|
const activityPeriod = toRecordObject(pilot?.derived_activity_period);
|
||||||
const duration = toNonEmptyString(activityPeriod?.duration_human_ru);
|
const duration = toNonEmptyString(activityPeriod?.duration_human_ru);
|
||||||
|
|
|
||||||
|
|
@ -146,6 +146,31 @@ function localizeLine(value: string): string {
|
||||||
if (/^Counterparty monthly net value-flow breakdown was grouped by month over confirmed incoming and outgoing 1C rows$/i.test(value)) {
|
if (/^Counterparty monthly net value-flow breakdown was grouped by month over confirmed incoming and outgoing 1C rows$/i.test(value)) {
|
||||||
return "Помесячная нетто-раскладка сгруппирована только по подтвержденным входящим и исходящим строкам 1С.";
|
return "Помесячная нетто-раскладка сгруппирована только по подтвержденным входящим и исходящим строкам 1С.";
|
||||||
}
|
}
|
||||||
|
const metadataSurfaceMatch = value.match(
|
||||||
|
/^Confirmed 1C metadata surface(?: for scope "([^"]+)")?: (\d+) rows and (\d+) matching objects$/i
|
||||||
|
);
|
||||||
|
if (metadataSurfaceMatch) {
|
||||||
|
const scopePart = metadataSurfaceMatch[1] ? ` по области "${metadataSurfaceMatch[1]}"` : "";
|
||||||
|
return `В 1С подтверждена metadata-поверхность${scopePart}: ${metadataSurfaceMatch[2]} строк metadata-ответа и ${metadataSurfaceMatch[3]} совпавших объекта(ов).`;
|
||||||
|
}
|
||||||
|
const metadataObjectSetsMatch = value.match(/^Available metadata object sets: (.+)$/i);
|
||||||
|
if (metadataObjectSetsMatch) {
|
||||||
|
return `Доступные типы metadata-объектов: ${metadataObjectSetsMatch[1]}.`;
|
||||||
|
}
|
||||||
|
const metadataFieldsMatch = value.match(/^Available metadata fields\/sections: (.+)$/i);
|
||||||
|
if (metadataFieldsMatch) {
|
||||||
|
return `Доступные metadata-поля/секции: ${metadataFieldsMatch[1]}.`;
|
||||||
|
}
|
||||||
|
if (/^Detailed metadata fields were not returned by this MCP metadata probe$/i.test(value)) {
|
||||||
|
return "Эта MCP-проверка metadata не вернула детальный список полей.";
|
||||||
|
}
|
||||||
|
const noMatchingMetadataScopeMatch = value.match(/^No matching 1C metadata objects were confirmed for scope "([^"]+)"$/i);
|
||||||
|
if (noMatchingMetadataScopeMatch) {
|
||||||
|
return `В 1С не подтверждены metadata-объекты по области "${noMatchingMetadataScopeMatch[1]}".`;
|
||||||
|
}
|
||||||
|
if (/^No matching 1C metadata objects were confirmed by this MCP metadata probe$/i.test(value)) {
|
||||||
|
return "В 1С эта MCP-проверка не подтвердила подходящих metadata-объектов.";
|
||||||
|
}
|
||||||
if (/^Legal registration date is not proven by this MCP discovery pilot$/i.test(value)) {
|
if (/^Legal registration date is not proven by this MCP discovery pilot$/i.test(value)) {
|
||||||
return "Юридическая дата регистрации этим поиском не подтверждена.";
|
return "Юридическая дата регистрации этим поиском не подтверждена.";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -190,6 +190,13 @@ function mapPilotScopeToFollowupMeaning(
|
||||||
unsupported: "counterparty_bidirectional_value_flow_or_netting"
|
unsupported: "counterparty_bidirectional_value_flow_or_netting"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (pilotScope === "metadata_inspection_v1") {
|
||||||
|
return {
|
||||||
|
domain: "metadata",
|
||||||
|
action: "inspect_catalog",
|
||||||
|
unsupported: "1c_metadata_surface"
|
||||||
|
};
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
domain: null,
|
domain: null,
|
||||||
action: null,
|
action: null,
|
||||||
|
|
@ -238,6 +245,7 @@ function collectFollowupDiscoverySeed(followupContext: Record<string, unknown> |
|
||||||
action: string | null;
|
action: string | null;
|
||||||
unsupported: string | null;
|
unsupported: string | null;
|
||||||
counterparty: string | null;
|
counterparty: string | null;
|
||||||
|
discoveryEntity: string | null;
|
||||||
organization: string | null;
|
organization: string | null;
|
||||||
dateScope: string | null;
|
dateScope: string | null;
|
||||||
} {
|
} {
|
||||||
|
|
@ -250,6 +258,7 @@ function collectFollowupDiscoverySeed(followupContext: Record<string, unknown> |
|
||||||
mapPilotScopeToFollowupMeaning(pilotScope).domain !== null
|
mapPilotScopeToFollowupMeaning(pilotScope).domain !== null
|
||||||
? mapPilotScopeToFollowupMeaning(pilotScope)
|
? mapPilotScopeToFollowupMeaning(pilotScope)
|
||||||
: mapAddressIntentToFollowupMeaning(previousIntent);
|
: mapAddressIntentToFollowupMeaning(previousIntent);
|
||||||
|
const discoveryEntities = collectEntityCandidates(followupContext?.previous_discovery_entity_candidates);
|
||||||
const counterparty =
|
const counterparty =
|
||||||
toNonEmptyString(previousFilters?.counterparty) ??
|
toNonEmptyString(previousFilters?.counterparty) ??
|
||||||
toNonEmptyString(rootFilters?.counterparty) ??
|
toNonEmptyString(rootFilters?.counterparty) ??
|
||||||
|
|
@ -271,6 +280,7 @@ function collectFollowupDiscoverySeed(followupContext: Record<string, unknown> |
|
||||||
action: mapped.action,
|
action: mapped.action,
|
||||||
unsupported: mapped.unsupported,
|
unsupported: mapped.unsupported,
|
||||||
counterparty,
|
counterparty,
|
||||||
|
discoveryEntity: discoveryEntities[0] ?? null,
|
||||||
organization,
|
organization,
|
||||||
dateScope
|
dateScope
|
||||||
};
|
};
|
||||||
|
|
@ -322,6 +332,12 @@ function hasMetadataSignal(text: string): boolean {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasMetadataObjectHint(text: string): boolean {
|
||||||
|
return /(?:\u0440\u0435\u0433\u0438\u0441\u0442\u0440(?:\u044b)?|\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442(?:\u044b)?|\u0441\u043f\u0440\u0430\u0432\u043e\u0447\u043d\u0438\u043a(?:\u0438)?|\u043f\u043e\u043b(?:\u0435|\u044f)|registers?|documents?|catalogs?|fields?)/iu.test(
|
||||||
|
text
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function metadataActionFromRawText(text: string): string {
|
function metadataActionFromRawText(text: string): string {
|
||||||
if (/(?:\u043f\u043e\u043b(?:\u0435|\u044f)|field)/iu.test(text)) {
|
if (/(?:\u043f\u043e\u043b(?:\u0435|\u044f)|field)/iu.test(text)) {
|
||||||
return "inspect_fields";
|
return "inspect_fields";
|
||||||
|
|
@ -443,9 +459,15 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||||
!rawValueFlowSignal &&
|
!rawValueFlowSignal &&
|
||||||
(monthlyAggregationSignal || explicitDateScopeLiteralDetected || predecomposeDateScope)
|
(monthlyAggregationSignal || explicitDateScopeLiteralDetected || predecomposeDateScope)
|
||||||
);
|
);
|
||||||
const seededDomain = followupDiscoverySeedApplicable ? followupSeed.domain : null;
|
const metadataFollowupSeedApplicable = Boolean(
|
||||||
const seededAction = followupDiscoverySeedApplicable ? followupSeed.action : null;
|
followupSeed.domain === "metadata" &&
|
||||||
const seededUnsupported = followupDiscoverySeedApplicable ? followupSeed.unsupported : null;
|
!rawLifecycleSignal &&
|
||||||
|
!rawValueFlowSignal &&
|
||||||
|
hasMetadataObjectHint(rawText)
|
||||||
|
);
|
||||||
|
const seededDomain = followupDiscoverySeedApplicable || metadataFollowupSeedApplicable ? followupSeed.domain : null;
|
||||||
|
const seededAction = followupDiscoverySeedApplicable || metadataFollowupSeedApplicable ? followupSeed.action : null;
|
||||||
|
const seededUnsupported = followupDiscoverySeedApplicable || metadataFollowupSeedApplicable ? followupSeed.unsupported : null;
|
||||||
const lifecycleSignal =
|
const lifecycleSignal =
|
||||||
rawLifecycleSignal || seededDomain === "counterparty_lifecycle";
|
rawLifecycleSignal || seededDomain === "counterparty_lifecycle";
|
||||||
const bidirectionalValueFlowSignal =
|
const bidirectionalValueFlowSignal =
|
||||||
|
|
@ -463,11 +485,14 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||||
unsupported: unsupported ?? seededUnsupported,
|
unsupported: unsupported ?? seededUnsupported,
|
||||||
lifecycleSignal,
|
lifecycleSignal,
|
||||||
valueFlowSignal,
|
valueFlowSignal,
|
||||||
metadataSignal: rawMetadataSignal
|
metadataSignal: rawMetadataSignal || metadataFollowupSeedApplicable
|
||||||
});
|
});
|
||||||
const entityCandidates = collectEntityCandidates(assistantTurnMeaning?.explicit_entity_candidates);
|
const entityCandidates = collectEntityCandidates(assistantTurnMeaning?.explicit_entity_candidates);
|
||||||
pushUnique(entityCandidates, predecomposeEntities.counterparty);
|
pushUnique(entityCandidates, predecomposeEntities.counterparty);
|
||||||
pushUnique(entityCandidates, followupSeed.counterparty);
|
pushUnique(entityCandidates, followupSeed.counterparty);
|
||||||
|
if ((rawMetadataSignal || metadataFollowupSeedApplicable) && !followupSeed.counterparty) {
|
||||||
|
pushUnique(entityCandidates, followupSeed.discoveryEntity);
|
||||||
|
}
|
||||||
if (valueFlowSignal && !predecomposeEntities.counterparty && !followupSeed.counterparty) {
|
if (valueFlowSignal && !predecomposeEntities.counterparty && !followupSeed.counterparty) {
|
||||||
pushUnique(entityCandidates, predecomposeEntities.organization);
|
pushUnique(entityCandidates, predecomposeEntities.organization);
|
||||||
pushUnique(entityCandidates, followupSeed.organization);
|
pushUnique(entityCandidates, followupSeed.organization);
|
||||||
|
|
@ -484,7 +509,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||||
? "counterparty_lifecycle"
|
? "counterparty_lifecycle"
|
||||||
: valueFlowSignal
|
: valueFlowSignal
|
||||||
? "counterparty_value"
|
? "counterparty_value"
|
||||||
: rawMetadataSignal
|
: rawMetadataSignal || metadataFollowupSeedApplicable
|
||||||
? "metadata"
|
? "metadata"
|
||||||
: rawDomain ?? seededDomain,
|
: rawDomain ?? seededDomain,
|
||||||
asked_action_family: lifecycleSignal
|
asked_action_family: lifecycleSignal
|
||||||
|
|
@ -495,8 +520,8 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||||
: payoutSignal
|
: payoutSignal
|
||||||
? "payout"
|
? "payout"
|
||||||
: rawAction ?? seededAction ?? "turnover"
|
: rawAction ?? seededAction ?? "turnover"
|
||||||
: rawMetadataSignal
|
: rawMetadataSignal || metadataFollowupSeedApplicable
|
||||||
? metadataActionFromRawText(rawText)
|
? metadataActionFromRawText(rawText) ?? seededAction
|
||||||
: rawAction ?? seededAction,
|
: rawAction ?? seededAction,
|
||||||
asked_aggregation_axis: monthlyAggregationSignal ? "month" : rawAggregationAxis,
|
asked_aggregation_axis: monthlyAggregationSignal ? "month" : rawAggregationAxis,
|
||||||
explicit_entity_candidates: entityCandidates,
|
explicit_entity_candidates: entityCandidates,
|
||||||
|
|
@ -512,7 +537,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||||
: payoutSignal
|
: payoutSignal
|
||||||
? "counterparty_payouts_or_outflow"
|
? "counterparty_payouts_or_outflow"
|
||||||
: seededUnsupported ?? "counterparty_value_or_turnover"
|
: seededUnsupported ?? "counterparty_value_or_turnover"
|
||||||
: rawMetadataSignal
|
: rawMetadataSignal || metadataFollowupSeedApplicable
|
||||||
? "1c_metadata_surface"
|
? "1c_metadata_surface"
|
||||||
: followupDiscoverySeedApplicable
|
: followupDiscoverySeedApplicable
|
||||||
? seededUnsupported
|
? seededUnsupported
|
||||||
|
|
@ -523,6 +548,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||||
lifecycleSignal ||
|
lifecycleSignal ||
|
||||||
valueFlowSignal ||
|
valueFlowSignal ||
|
||||||
rawMetadataSignal ||
|
rawMetadataSignal ||
|
||||||
|
metadataFollowupSeedApplicable ||
|
||||||
followupDiscoverySeedApplicable
|
followupDiscoverySeedApplicable
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
@ -557,15 +583,15 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||||
unsupported: unsupported ?? seededUnsupported,
|
unsupported: unsupported ?? seededUnsupported,
|
||||||
lifecycleSignal,
|
lifecycleSignal,
|
||||||
valueFlowSignal,
|
valueFlowSignal,
|
||||||
metadataSignal: rawMetadataSignal,
|
metadataSignal: rawMetadataSignal || metadataFollowupSeedApplicable,
|
||||||
semanticDataNeed,
|
semanticDataNeed,
|
||||||
explicitIntentCandidate,
|
explicitIntentCandidate,
|
||||||
followupDiscoverySeedApplicable
|
followupDiscoverySeedApplicable: followupDiscoverySeedApplicable || metadataFollowupSeedApplicable
|
||||||
});
|
});
|
||||||
const hasTurnMeaning = Object.keys(cleanTurnMeaning).length > 0;
|
const hasTurnMeaning = Object.keys(cleanTurnMeaning).length > 0;
|
||||||
const sourceSignal: AssistantMcpDiscoveryTurnInputSource = assistantTurnMeaning
|
const sourceSignal: AssistantMcpDiscoveryTurnInputSource = assistantTurnMeaning
|
||||||
? "assistant_turn_meaning"
|
? "assistant_turn_meaning"
|
||||||
: followupDiscoverySeedApplicable
|
: followupDiscoverySeedApplicable || metadataFollowupSeedApplicable
|
||||||
? "followup_context"
|
? "followup_context"
|
||||||
: predecomposeContract
|
: predecomposeContract
|
||||||
? "predecompose_contract"
|
? "predecompose_contract"
|
||||||
|
|
@ -573,7 +599,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||||
? "raw_text"
|
? "raw_text"
|
||||||
: valueFlowSignal
|
: valueFlowSignal
|
||||||
? "raw_text"
|
? "raw_text"
|
||||||
: rawMetadataSignal
|
: rawMetadataSignal || metadataFollowupSeedApplicable
|
||||||
? "raw_text"
|
? "raw_text"
|
||||||
: "none";
|
: "none";
|
||||||
|
|
||||||
|
|
@ -598,6 +624,9 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||||
if (followupDiscoverySeedApplicable) {
|
if (followupDiscoverySeedApplicable) {
|
||||||
pushReason(reasonCodes, "mcp_discovery_seeded_from_followup_context");
|
pushReason(reasonCodes, "mcp_discovery_seeded_from_followup_context");
|
||||||
}
|
}
|
||||||
|
if (metadataFollowupSeedApplicable) {
|
||||||
|
pushReason(reasonCodes, "mcp_discovery_metadata_seeded_from_followup_context");
|
||||||
|
}
|
||||||
if (unsupported) {
|
if (unsupported) {
|
||||||
pushReason(reasonCodes, "mcp_discovery_unsupported_but_understood_turn");
|
pushReason(reasonCodes, "mcp_discovery_unsupported_but_understood_turn");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -77,12 +77,35 @@ function periodPartForRecap(scopedDate: string | null): string {
|
||||||
return /^\d{2}\.\d{2}\.\d{4}$/.test(scopedDate) ? ` на ${scopedDate}` : ` за период ${scopedDate}`;
|
return /^\d{2}\.\d{2}\.\d{4}$/.test(scopedDate) ? ` на ${scopedDate}` : ` за период ${scopedDate}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function readDiscoveryMetadataScope(debug: Record<string, unknown> | null): string | null {
|
||||||
|
const discoveryEntry = toRecordObject(debug?.assistant_mcp_discovery_entry_point_v1);
|
||||||
|
const bridge = toRecordObject(discoveryEntry?.bridge);
|
||||||
|
const pilot = toRecordObject(bridge?.pilot);
|
||||||
|
const surface = toRecordObject(pilot?.derived_metadata_surface);
|
||||||
|
const surfaceScope = toNonEmptyString(surface?.metadata_scope);
|
||||||
|
if (surfaceScope) {
|
||||||
|
return surfaceScope;
|
||||||
|
}
|
||||||
|
const turnInput = toRecordObject(discoveryEntry?.turn_input);
|
||||||
|
const turnMeaningRef = toRecordObject(turnInput?.turn_meaning_ref);
|
||||||
|
const entityCandidates = Array.isArray(turnMeaningRef?.explicit_entity_candidates)
|
||||||
|
? turnMeaningRef.explicit_entity_candidates
|
||||||
|
: [];
|
||||||
|
for (const candidate of entityCandidates) {
|
||||||
|
const text = toNonEmptyString(candidate);
|
||||||
|
if (text) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
function buildDiscoveryRecapFactLine(input: {
|
function buildDiscoveryRecapFactLine(input: {
|
||||||
debug: Record<string, unknown> | null;
|
debug: Record<string, unknown> | null;
|
||||||
counterparty: string | null;
|
counterparty: string | null;
|
||||||
scopedDate: string | null;
|
scopedDate: string | null;
|
||||||
}): string | null {
|
}): string | null {
|
||||||
if (!input.debug || !input.counterparty) {
|
if (!input.debug) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const pilotScope = readAssistantMcpDiscoveryPilotScope(input.debug, toNonEmptyString);
|
const pilotScope = readAssistantMcpDiscoveryPilotScope(input.debug, toNonEmptyString);
|
||||||
|
|
@ -90,6 +113,36 @@ function buildDiscoveryRecapFactLine(input: {
|
||||||
const bridge = toRecordObject(discoveryEntry?.bridge);
|
const bridge = toRecordObject(discoveryEntry?.bridge);
|
||||||
const pilot = toRecordObject(bridge?.pilot);
|
const pilot = toRecordObject(bridge?.pilot);
|
||||||
const periodPart = periodPartForRecap(input.scopedDate);
|
const periodPart = periodPartForRecap(input.scopedDate);
|
||||||
|
if (pilotScope === "metadata_inspection_v1") {
|
||||||
|
const metadataScope = readDiscoveryMetadataScope(input.debug);
|
||||||
|
const surface = toRecordObject(pilot?.derived_metadata_surface);
|
||||||
|
const entitySets = Array.isArray(surface?.available_entity_sets)
|
||||||
|
? surface.available_entity_sets
|
||||||
|
.map((item) => toNonEmptyString(item))
|
||||||
|
.filter((item): item is string => Boolean(item))
|
||||||
|
: [];
|
||||||
|
const fields = Array.isArray(surface?.available_fields)
|
||||||
|
? surface.available_fields
|
||||||
|
.map((item) => toNonEmptyString(item))
|
||||||
|
.filter((item): item is string => Boolean(item))
|
||||||
|
: [];
|
||||||
|
const objects = Array.isArray(surface?.matched_objects)
|
||||||
|
? surface.matched_objects
|
||||||
|
.map((item) => toNonEmptyString(item))
|
||||||
|
.filter((item): item is string => Boolean(item))
|
||||||
|
: [];
|
||||||
|
const rows = Number(surface?.matched_rows ?? 0);
|
||||||
|
const scopePart = metadataScope ? ` по области «${metadataScope}»` : "";
|
||||||
|
const objectsPart = objects.length > 0 ? `, нашли объекты ${objects.slice(0, 4).join(", ")}` : "";
|
||||||
|
const entitySetsPart =
|
||||||
|
entitySets.length > 0 ? `, видны типы ${entitySets.slice(0, 4).join(", ")}` : "";
|
||||||
|
const fieldsPart =
|
||||||
|
fields.length > 0 ? `, доступны поля/секции ${fields.slice(0, 5).join(", ")}` : "";
|
||||||
|
return `смотрели metadata-поверхность 1С${scopePart}${periodPart}: ${rows} подтвержденных строк${objectsPart}${entitySetsPart}${fieldsPart}`.trim();
|
||||||
|
}
|
||||||
|
if (!input.counterparty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
if (pilotScope === "counterparty_lifecycle_query_documents_v1") {
|
if (pilotScope === "counterparty_lifecycle_query_documents_v1") {
|
||||||
const activityPeriod = toRecordObject(pilot?.derived_activity_period);
|
const activityPeriod = toRecordObject(pilot?.derived_activity_period);
|
||||||
const duration = toNonEmptyString(activityPeriod?.duration_human_ru);
|
const duration = toNonEmptyString(activityPeriod?.duration_human_ru);
|
||||||
|
|
|
||||||
|
|
@ -209,6 +209,39 @@ describe("assistant MCP discovery response candidate", () => {
|
||||||
expect(candidate.reply_text).not.toContain("broad probe hit the row limit");
|
expect(candidate.reply_text).not.toContain("broad probe hit the row limit");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("localizes metadata evidence without leaking raw MCP wording", () => {
|
||||||
|
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
|
||||||
|
entryPoint({
|
||||||
|
bridge: {
|
||||||
|
bridge_status: "answer_draft_ready",
|
||||||
|
user_facing_response_allowed: true,
|
||||||
|
business_fact_answer_allowed: true,
|
||||||
|
requires_user_clarification: false,
|
||||||
|
answer_draft: {
|
||||||
|
answer_mode: "confirmed_with_bounded_inference",
|
||||||
|
headline: "По данным 1С найдена подтвержденная metadata-поверхность.",
|
||||||
|
confirmed_lines: [
|
||||||
|
'Confirmed 1C metadata surface for scope "НДС": 7 rows and 3 matching objects',
|
||||||
|
"Available metadata object sets: accumulation_register, document",
|
||||||
|
"Available metadata fields/sections: amount, vat_rate, organization"
|
||||||
|
],
|
||||||
|
inference_lines: [],
|
||||||
|
unknown_lines: ['No matching 1C metadata objects were confirmed for scope "Прибыль"'],
|
||||||
|
limitation_lines: ["Detailed metadata fields were not returned by this MCP metadata probe"],
|
||||||
|
next_step_line: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(candidate.reply_text).toContain('В 1С подтверждена metadata-поверхность по области "НДС"');
|
||||||
|
expect(candidate.reply_text).toContain("Доступные типы metadata-объектов");
|
||||||
|
expect(candidate.reply_text).toContain("Доступные metadata-поля/секции");
|
||||||
|
expect(candidate.reply_text).toContain('В 1С не подтверждены metadata-объекты по области "Прибыль"');
|
||||||
|
expect(candidate.reply_text).toContain("Эта MCP-проверка metadata не вернула детальный список полей");
|
||||||
|
expect(candidate.reply_text).not.toContain("Confirmed 1C metadata surface");
|
||||||
|
});
|
||||||
|
|
||||||
it("returns not applicable when discovery was skipped for an exact supported route", () => {
|
it("returns not applicable when discovery was skipped for an exact supported route", () => {
|
||||||
const candidate = buildAssistantMcpDiscoveryResponseCandidate({
|
const candidate = buildAssistantMcpDiscoveryResponseCandidate({
|
||||||
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
|
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
|
||||||
|
|
|
||||||
|
|
@ -203,6 +203,34 @@ describe("assistant MCP discovery turn input adapter", () => {
|
||||||
expect(result.reason_codes).toContain("mcp_discovery_date_scope_from_followup_context");
|
expect(result.reason_codes).toContain("mcp_discovery_date_scope_from_followup_context");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("seeds short metadata follow-up from prior metadata discovery context", () => {
|
||||||
|
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||||
|
userMessage: "а по регистрам?",
|
||||||
|
followupContext: {
|
||||||
|
previous_discovery_pilot_scope: "metadata_inspection_v1",
|
||||||
|
previous_filters: {
|
||||||
|
counterparty: "НДС"
|
||||||
|
},
|
||||||
|
previous_anchor_type: "counterparty",
|
||||||
|
previous_anchor_value: "НДС"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.adapter_status).toBe("ready");
|
||||||
|
expect(result.should_run_discovery).toBe(true);
|
||||||
|
expect(result.source_signal).toBe("followup_context");
|
||||||
|
expect(result.semantic_data_need).toBe("1C metadata evidence");
|
||||||
|
expect(result.turn_meaning_ref).toMatchObject({
|
||||||
|
asked_domain_family: "metadata",
|
||||||
|
asked_action_family: "inspect_registers",
|
||||||
|
explicit_entity_candidates: ["НДС"],
|
||||||
|
unsupported_but_understood_family: "1c_metadata_surface",
|
||||||
|
stale_replay_forbidden: true
|
||||||
|
});
|
||||||
|
expect(result.reason_codes).toContain("mcp_discovery_metadata_seeded_from_followup_context");
|
||||||
|
expect(result.reason_codes).toContain("mcp_discovery_counterparty_from_followup_context");
|
||||||
|
});
|
||||||
|
|
||||||
it("switches the checked year on a short payout follow-up while keeping prior discovery counterparty", () => {
|
it("switches the checked year on a short payout follow-up while keeping prior discovery counterparty", () => {
|
||||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||||
userMessage: "а теперь за 2021?",
|
userMessage: "а теперь за 2021?",
|
||||||
|
|
|
||||||
|
|
@ -386,6 +386,64 @@ describe("assistantMemoryRecapPolicy", () => {
|
||||||
expect(reply).toContain("43 763 351,53 руб.");
|
expect(reply).toContain("43 763 351,53 руб.");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("builds deterministic recap summary from grounded MCP metadata discovery context", () => {
|
||||||
|
const sessionItems = [
|
||||||
|
{
|
||||||
|
role: "assistant",
|
||||||
|
debug: {
|
||||||
|
execution_lane: "living_chat",
|
||||||
|
mcp_discovery_response_applied: true,
|
||||||
|
assistant_mcp_discovery_entry_point_v1: {
|
||||||
|
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
|
||||||
|
entry_status: "bridge_executed",
|
||||||
|
turn_input: {
|
||||||
|
turn_meaning_ref: {
|
||||||
|
explicit_entity_candidates: ["НДС"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
bridge: {
|
||||||
|
bridge_status: "answer_draft_ready",
|
||||||
|
business_fact_answer_allowed: true,
|
||||||
|
answer_draft: {
|
||||||
|
answer_mode: "confirmed_with_bounded_inference"
|
||||||
|
},
|
||||||
|
pilot: {
|
||||||
|
pilot_scope: "metadata_inspection_v1",
|
||||||
|
derived_metadata_surface: {
|
||||||
|
metadata_scope: "НДС",
|
||||||
|
matched_rows: 7,
|
||||||
|
matched_objects: ["РегистрНакопления.НДСПокупок"],
|
||||||
|
available_entity_sets: ["accumulation_register", "document"],
|
||||||
|
available_fields: ["amount", "vat_rate", "organization"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const context = resolveAssistantLivingChatMemoryContext({
|
||||||
|
modeDecisionReason: "memory_recap_followup_detected",
|
||||||
|
sessionItems
|
||||||
|
});
|
||||||
|
|
||||||
|
const reply = buildAddressMemoryRecapReply({
|
||||||
|
organization: null,
|
||||||
|
addressDebug: context.lastMemoryAddressDebug,
|
||||||
|
sessionItems,
|
||||||
|
toNonEmptyString: (value: unknown) => {
|
||||||
|
const text = String(value ?? "").trim();
|
||||||
|
return text.length > 0 ? text : null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(context.contextualMemoryRecapFollowup).toBe(true);
|
||||||
|
expect(reply).toContain("НДС");
|
||||||
|
expect(reply).toContain("metadata-поверхность 1С");
|
||||||
|
expect(reply).toContain("amount");
|
||||||
|
expect(reply).toContain("accumulation_register");
|
||||||
|
});
|
||||||
|
|
||||||
it("builds deterministic broad business evaluation summary from recent grounded organization facts", () => {
|
it("builds deterministic broad business evaluation summary from recent grounded organization facts", () => {
|
||||||
const sessionItems = [
|
const sessionItems = [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue