ARCH: замкнуть metadata-scoped clarification recovery loops
This commit is contained in:
parent
c96c9bab86
commit
cddd6667fb
|
|
@ -0,0 +1,75 @@
|
|||
{
|
||||
"schema_version": "domain_truth_harness_spec_v1",
|
||||
"scenario_id": "address_truth_harness_phase50_metadata_movement_org_period_clarification_loop",
|
||||
"domain": "address_phase50_metadata_movement_org_period_clarification_loop",
|
||||
"title": "Phase 50 metadata movement organization and period clarification loop",
|
||||
"description": "Targeted AGENT replay for Big Block F where a metadata-born movement lane must keep both remaining gaps visible, then preserve the same loop after an organization-only clarification and ask only for the period.",
|
||||
"bindings": {},
|
||||
"steps": [
|
||||
{
|
||||
"step_id": "step_01_metadata_ambiguity_surface",
|
||||
"title": "Metadata ambiguity is surfaced honestly for VAT",
|
||||
"question": "какие объекты 1С есть по НДС?",
|
||||
"allowed_reply_types": ["partial_coverage", "factual_with_explanation"],
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)metadata|метадан",
|
||||
"(?i)ндс",
|
||||
"(?i)документ|регистр"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"(?i)получили",
|
||||
"(?i)заплатили",
|
||||
"(?i)нетто"
|
||||
],
|
||||
"criticality": "critical",
|
||||
"semantic_tags": ["metadata_surface", "mixed_ambiguity"]
|
||||
},
|
||||
{
|
||||
"step_id": "step_02_neutral_followup_requires_lane_choice",
|
||||
"title": "Neutral follow-up still requires lane choice",
|
||||
"question": "давай дальше",
|
||||
"allowed_reply_types": ["clarification_required", "partial_coverage"],
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)документ",
|
||||
"(?i)движени|регистр",
|
||||
"(?i)уточн|выб(ери|рать)|какой контур"
|
||||
],
|
||||
"criticality": "critical",
|
||||
"semantic_tags": ["metadata_lane_choice_clarification", "neutral_followup"]
|
||||
},
|
||||
{
|
||||
"step_id": "step_03_movement_lane_requires_org_and_period",
|
||||
"title": "Movement lane keeps both remaining gaps visible",
|
||||
"question": "по движениям",
|
||||
"allowed_reply_types": ["clarification_required", "partial_coverage"],
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)движени|регистр",
|
||||
"(?i)организац",
|
||||
"(?i)период"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"(?i)уточните контрагента",
|
||||
"(?i)не найден контрагент"
|
||||
],
|
||||
"criticality": "critical",
|
||||
"semantic_tags": ["movement_lane_after_clarification", "remaining_gaps", "organization_scope", "period_scope"]
|
||||
},
|
||||
{
|
||||
"step_id": "step_04_org_only_clarification_keeps_same_movement_loop",
|
||||
"title": "Organization-only clarification preserves the same movement loop and asks only for the period",
|
||||
"question": "по ООО Альтернатива Плюс",
|
||||
"allowed_reply_types": ["clarification_required", "partial_coverage"],
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)движени|регистр",
|
||||
"(?i)период"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"(?i)организац",
|
||||
"(?i)уточните контрагента",
|
||||
"(?i)не найден контрагент"
|
||||
],
|
||||
"criticality": "critical",
|
||||
"semantic_tags": ["movement_lane_after_clarification", "organization_followup_reuse", "period_scope"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
{
|
||||
"schema_version": "domain_truth_harness_spec_v1",
|
||||
"scenario_id": "address_truth_harness_phase51_metadata_lane_choice_with_org_inline",
|
||||
"domain": "address_phase51_metadata_lane_choice_with_org_inline",
|
||||
"title": "Phase 51 metadata lane choice with inline organization clarification",
|
||||
"description": "Targeted AGENT replay for Big Block F where the user resolves metadata ambiguity and supplies the organization in the same follow-up, so the movement loop should keep only the period gap alive.",
|
||||
"bindings": {},
|
||||
"steps": [
|
||||
{
|
||||
"step_id": "step_01_metadata_ambiguity_surface",
|
||||
"title": "Metadata ambiguity is surfaced honestly for VAT",
|
||||
"question": "какие объекты 1С есть по НДС?",
|
||||
"allowed_reply_types": ["partial_coverage", "factual_with_explanation"],
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)metadata|метадан",
|
||||
"(?i)ндс",
|
||||
"(?i)документ|регистр"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"(?i)получили",
|
||||
"(?i)заплатили",
|
||||
"(?i)нетто"
|
||||
],
|
||||
"criticality": "critical",
|
||||
"semantic_tags": ["metadata_surface", "mixed_ambiguity"]
|
||||
},
|
||||
{
|
||||
"step_id": "step_02_neutral_followup_requires_lane_choice",
|
||||
"title": "Neutral follow-up still requires lane choice",
|
||||
"question": "давай дальше",
|
||||
"allowed_reply_types": ["clarification_required", "partial_coverage"],
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)документ",
|
||||
"(?i)движени|регистр",
|
||||
"(?i)уточн|выб(ери|рать)|какой контур"
|
||||
],
|
||||
"criticality": "critical",
|
||||
"semantic_tags": ["metadata_lane_choice_clarification", "neutral_followup"]
|
||||
},
|
||||
{
|
||||
"step_id": "step_03_inline_lane_choice_with_org_keeps_only_period_gap",
|
||||
"title": "Movement lane plus organization in one follow-up leaves only the period gap",
|
||||
"question": "по движениям по ООО Альтернатива Плюс",
|
||||
"allowed_reply_types": ["clarification_required", "partial_coverage"],
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)движени|регистр",
|
||||
"(?i)период"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"(?i)уточните .*организац",
|
||||
"(?i)нужн[ао].*организац",
|
||||
"(?i)уточните .*контрагента",
|
||||
"(?i)не найден контрагент"
|
||||
],
|
||||
"criticality": "critical",
|
||||
"semantic_tags": [
|
||||
"movement_lane_after_clarification",
|
||||
"inline_organization_clarification",
|
||||
"remaining_period_gap_only"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -12,6 +12,8 @@ exports.readAssistantMcpDiscoveryLoopProvidedAxes = readAssistantMcpDiscoveryLoo
|
|||
exports.readAssistantMcpDiscoveryLoopAskedDomainFamily = readAssistantMcpDiscoveryLoopAskedDomainFamily;
|
||||
exports.readAssistantMcpDiscoveryLoopAskedActionFamily = readAssistantMcpDiscoveryLoopAskedActionFamily;
|
||||
exports.readAssistantMcpDiscoveryLoopUnsupportedFamily = readAssistantMcpDiscoveryLoopUnsupportedFamily;
|
||||
exports.readAssistantMcpDiscoveryLoopMetadataScopeHint = readAssistantMcpDiscoveryLoopMetadataScopeHint;
|
||||
exports.readAssistantMcpDiscoveryLoopSubjectResolutionOptional = readAssistantMcpDiscoveryLoopSubjectResolutionOptional;
|
||||
exports.readAssistantMcpDiscoveryMetadataRouteFamily = readAssistantMcpDiscoveryMetadataRouteFamily;
|
||||
exports.readAssistantMcpDiscoveryMetadataRouteFamilySelectionBasis = readAssistantMcpDiscoveryMetadataRouteFamilySelectionBasis;
|
||||
exports.readAssistantMcpDiscoveryMetadataSelectedEntitySet = readAssistantMcpDiscoveryMetadataSelectedEntitySet;
|
||||
|
|
@ -194,6 +196,16 @@ function readAssistantMcpDiscoveryLoopAskedActionFamily(debug, toNonEmptyString
|
|||
function readAssistantMcpDiscoveryLoopUnsupportedFamily(debug, toNonEmptyString = fallbackToNonEmptyString) {
|
||||
return toNonEmptyString(readAssistantMcpDiscoveryLoopState(debug)?.unsupported_but_understood_family);
|
||||
}
|
||||
function readAssistantMcpDiscoveryLoopMetadataScopeHint(debug, toNonEmptyString = fallbackToNonEmptyString) {
|
||||
return (toNonEmptyString(readAssistantMcpDiscoveryLoopState(debug)?.metadata_scope_hint) ??
|
||||
toNonEmptyString(readAssistantMcpDiscoveryTurnMeaning(debug)?.metadata_scope_hint) ??
|
||||
toNonEmptyString(readAssistantMcpDiscoveryDataNeedGraph(debug)?.metadata_scope_hint));
|
||||
}
|
||||
function readAssistantMcpDiscoveryLoopSubjectResolutionOptional(debug) {
|
||||
return (readAssistantMcpDiscoveryLoopState(debug)?.subject_resolution_optional === true ||
|
||||
readAssistantMcpDiscoveryTurnMeaning(debug)?.subject_resolution_optional === true ||
|
||||
readAssistantMcpDiscoveryDataNeedGraph(debug)?.subject_resolution_optional === true);
|
||||
}
|
||||
function readAssistantMcpDiscoveryMetadataRouteFamily(debug, toNonEmptyString = fallbackToNonEmptyString) {
|
||||
return toNonEmptyString(readAssistantMcpDiscoveryDerivedMetadataSurface(debug)?.downstream_route_family);
|
||||
}
|
||||
|
|
@ -361,6 +373,12 @@ function readAddressDebugCounterparty(debug, toNonEmptyString = fallbackToNonEmp
|
|||
if (String(debug?.anchor_type ?? "") === "counterparty") {
|
||||
return toNonEmptyString(debug?.anchor_value_resolved) ?? toNonEmptyString(debug?.anchor_value_raw);
|
||||
}
|
||||
const discoveryPilotScope = readAssistantMcpDiscoveryPilotScope(debug, toNonEmptyString);
|
||||
const suppressDiscoveryEntityCarryover = discoveryPilotScope === "metadata_inspection_v1" ||
|
||||
readAssistantMcpDiscoveryLoopSubjectResolutionOptional(debug);
|
||||
if (suppressDiscoveryEntityCarryover) {
|
||||
return null;
|
||||
}
|
||||
const discoveryEntities = collectAssistantMcpDiscoveryEntityCandidates(debug, toNonEmptyString);
|
||||
for (const entity of discoveryEntities) {
|
||||
const text = toNonEmptyString(entity);
|
||||
|
|
|
|||
|
|
@ -131,6 +131,10 @@ function allowsOpenScopeWithoutSubject(input) {
|
|||
}
|
||||
return Boolean(supportsOrganizationScopedOpenTotal(input.action) && (input.organizationScope || input.oneSidedOpenScopeTotalHint));
|
||||
}
|
||||
function allowsMetadataScopedOpenLaneWithoutSubject(input) {
|
||||
return Boolean(input.subjectResolutionOptional &&
|
||||
(input.family === "movement_evidence" || input.family === "document_evidence"));
|
||||
}
|
||||
function rankingNeedFromRawUtterance(value) {
|
||||
const text = lower(value);
|
||||
if (!text) {
|
||||
|
|
@ -206,13 +210,17 @@ function decompositionCandidatesFor(input) {
|
|||
return result;
|
||||
}
|
||||
if (input.family === "movement_evidence") {
|
||||
pushUnique(result, "resolve_entity_reference");
|
||||
if (!input.metadataScopedOpenLaneWithoutSubject) {
|
||||
pushUnique(result, "resolve_entity_reference");
|
||||
}
|
||||
pushUnique(result, "fetch_scoped_movements");
|
||||
pushUnique(result, "probe_coverage");
|
||||
return result;
|
||||
}
|
||||
if (input.family === "document_evidence") {
|
||||
pushUnique(result, "resolve_entity_reference");
|
||||
if (!input.metadataScopedOpenLaneWithoutSubject) {
|
||||
pushUnique(result, "resolve_entity_reference");
|
||||
}
|
||||
pushUnique(result, "fetch_scoped_documents");
|
||||
pushUnique(result, "probe_coverage");
|
||||
return result;
|
||||
|
|
@ -252,6 +260,8 @@ function buildAssistantMcpDiscoveryDataNeedGraph(input) {
|
|||
const seededRankingNeed = toNonEmptyString(turnMeaning?.seeded_ranking_need);
|
||||
const explicitDateScope = toNonEmptyString(turnMeaning?.explicit_date_scope);
|
||||
const explicitOrganizationScope = toNonEmptyString(turnMeaning?.explicit_organization_scope);
|
||||
const metadataScopeHint = toNonEmptyString(turnMeaning?.metadata_scope_hint);
|
||||
const subjectResolutionOptional = turnMeaning?.subject_resolution_optional === true;
|
||||
const subjectCandidates = (turnMeaning?.explicit_entity_candidates ?? [])
|
||||
.map((item) => toNonEmptyString(item))
|
||||
.filter((item) => Boolean(item));
|
||||
|
|
@ -275,6 +285,11 @@ function buildAssistantMcpDiscoveryDataNeedGraph(input) {
|
|||
rankingNeed,
|
||||
oneSidedOpenScopeTotalHint
|
||||
});
|
||||
const metadataScopedOpenLaneWithoutSubject = subjectCandidates.length === 0 &&
|
||||
allowsMetadataScopedOpenLaneWithoutSubject({
|
||||
family: businessFactFamily,
|
||||
subjectResolutionOptional
|
||||
});
|
||||
const clarificationGaps = [];
|
||||
if (unsupported === "metadata_lane_choice_clarification" || action === "resolve_next_lane") {
|
||||
pushUnique(clarificationGaps, "lane_family_choice");
|
||||
|
|
@ -285,7 +300,15 @@ function buildAssistantMcpDiscoveryDataNeedGraph(input) {
|
|||
!explicitOrganizationScope) {
|
||||
pushUnique(clarificationGaps, "organization");
|
||||
}
|
||||
else if (subjectCandidates.length === 0 && businessFactFamily !== "schema_surface" && !openScopeWithoutSubject) {
|
||||
else if (subjectCandidates.length === 0 &&
|
||||
metadataScopedOpenLaneWithoutSubject &&
|
||||
!explicitOrganizationScope) {
|
||||
pushUnique(clarificationGaps, "organization");
|
||||
}
|
||||
else if (subjectCandidates.length === 0 &&
|
||||
businessFactFamily !== "schema_surface" &&
|
||||
!openScopeWithoutSubject &&
|
||||
!metadataScopedOpenLaneWithoutSubject) {
|
||||
pushUnique(clarificationGaps, "subject");
|
||||
}
|
||||
const timeScopeNeed = timeScopeNeedFor({
|
||||
|
|
@ -302,7 +325,8 @@ function buildAssistantMcpDiscoveryDataNeedGraph(input) {
|
|||
aggregationNeed,
|
||||
comparisonNeed,
|
||||
rankingNeed,
|
||||
openScopeWithoutSubject
|
||||
openScopeWithoutSubject,
|
||||
metadataScopedOpenLaneWithoutSubject
|
||||
});
|
||||
const reasonCodes = [];
|
||||
pushReason(reasonCodes, "data_need_graph_built");
|
||||
|
|
@ -324,6 +348,9 @@ function buildAssistantMcpDiscoveryDataNeedGraph(input) {
|
|||
if (openScopeWithoutSubject && !rankingNeed && !comparisonNeed) {
|
||||
pushReason(reasonCodes, "data_need_graph_open_scope_total_without_subject");
|
||||
}
|
||||
if (metadataScopedOpenLaneWithoutSubject) {
|
||||
pushReason(reasonCodes, "data_need_graph_metadata_scoped_open_lane_without_subject");
|
||||
}
|
||||
if (allTimeScopeHint) {
|
||||
pushReason(reasonCodes, "data_need_graph_all_time_scope_hint");
|
||||
}
|
||||
|
|
@ -337,6 +364,8 @@ function buildAssistantMcpDiscoveryDataNeedGraph(input) {
|
|||
schema_version: exports.ASSISTANT_MCP_DISCOVERY_DATA_NEED_GRAPH_SCHEMA_VERSION,
|
||||
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
|
||||
subject_candidates: subjectCandidates,
|
||||
metadata_scope_hint: metadataScopeHint,
|
||||
subject_resolution_optional: subjectResolutionOptional || undefined,
|
||||
business_fact_family: businessFactFamily,
|
||||
action_family: toNonEmptyString(turnMeaning?.asked_action_family),
|
||||
aggregation_need: aggregationNeed,
|
||||
|
|
|
|||
|
|
@ -41,6 +41,10 @@ function hasEntity(meaning) {
|
|||
function hasSubjectCandidates(graph) {
|
||||
return (graph?.subject_candidates.length ?? 0) > 0;
|
||||
}
|
||||
function hasMetadataScopedOpenLane(graph, meaning) {
|
||||
return Boolean(graph?.subject_resolution_optional === true ||
|
||||
meaning?.subject_resolution_optional === true);
|
||||
}
|
||||
function hasReasonCode(graph, reasonCode) {
|
||||
return (graph?.reason_codes ?? []).includes(reasonCode);
|
||||
}
|
||||
|
|
@ -58,6 +62,11 @@ function addScopeAxes(axes, meaning) {
|
|||
pushUnique(axes, "period");
|
||||
}
|
||||
}
|
||||
function addMetadataScopeAxis(axes, meaning) {
|
||||
if (toNonEmptyString(meaning?.metadata_scope_hint)) {
|
||||
pushUnique(axes, "metadata_scope");
|
||||
}
|
||||
}
|
||||
function addTimeScopeAxes(axes, dataNeedGraph) {
|
||||
if (dataNeedGraph?.time_scope_need === "all_time_scope") {
|
||||
pushUnique(axes, "all_time_scope");
|
||||
|
|
@ -273,7 +282,7 @@ function recipeFor(input) {
|
|||
const graphAction = lower(dataNeedGraph?.action_family);
|
||||
const graphAggregation = lower(dataNeedGraph?.aggregation_need);
|
||||
const graphClarificationGaps = (dataNeedGraph?.clarification_gaps ?? []).map((item) => lower(item));
|
||||
const organizationScope = toNonEmptyString(meaning?.explicit_organization_scope);
|
||||
const metadataScopedOpenLane = hasMetadataScopedOpenLane(dataNeedGraph, meaning);
|
||||
const openScopeTotalWithoutSubject = graphFactFamily === "value_flow" &&
|
||||
!hasSubjectCandidates(dataNeedGraph) &&
|
||||
hasReasonCode(dataNeedGraph, "data_need_graph_open_scope_total_without_subject");
|
||||
|
|
@ -281,6 +290,7 @@ function recipeFor(input) {
|
|||
const axes = [];
|
||||
const requestedAggregationAxis = aggregationAxis(meaning);
|
||||
addScopeAxes(axes, meaning);
|
||||
addMetadataScopeAxis(axes, meaning);
|
||||
addTimeScopeAxes(axes, dataNeedGraph);
|
||||
if (graphClarificationGaps.includes("lane_family_choice")) {
|
||||
pushUnique(axes, "lane_family_choice");
|
||||
|
|
@ -494,6 +504,26 @@ function recipeFor(input) {
|
|||
};
|
||||
}
|
||||
if (graphFactFamily === "movement_evidence") {
|
||||
if (metadataScopedOpenLane) {
|
||||
pushUnique(axes, "organization");
|
||||
pushUnique(axes, "coverage_target");
|
||||
const primitiveSelection = selectPrimitivesFromGraphAndCatalog({
|
||||
dataNeedGraph,
|
||||
fallbackPrimitives: ["query_movements", "probe_coverage"],
|
||||
requiredAxes: axes,
|
||||
metadataSurface: input.metadataSurface,
|
||||
actionFamily: action
|
||||
});
|
||||
return {
|
||||
semanticDataNeed: "movement evidence",
|
||||
chainId: "movement_evidence",
|
||||
chainSummary: "Keep the metadata-scoped movement lane, ask only for the remaining business scope, then fetch scoped movement rows and probe coverage without pretending there is a grounded counterparty.",
|
||||
primitives: primitiveSelection.primitives,
|
||||
axes,
|
||||
reason: "planner_selected_metadata_scoped_movement_from_data_need_graph",
|
||||
extraReasons: primitiveSelection.reasonCodes
|
||||
};
|
||||
}
|
||||
pushUnique(axes, "coverage_target");
|
||||
const primitiveSelection = selectPrimitivesFromGraphAndCatalog({
|
||||
dataNeedGraph,
|
||||
|
|
@ -513,6 +543,26 @@ function recipeFor(input) {
|
|||
};
|
||||
}
|
||||
if (graphFactFamily === "document_evidence") {
|
||||
if (metadataScopedOpenLane) {
|
||||
pushUnique(axes, "organization");
|
||||
pushUnique(axes, "coverage_target");
|
||||
const primitiveSelection = selectPrimitivesFromGraphAndCatalog({
|
||||
dataNeedGraph,
|
||||
fallbackPrimitives: ["query_documents", "probe_coverage"],
|
||||
requiredAxes: axes,
|
||||
metadataSurface: input.metadataSurface,
|
||||
actionFamily: action
|
||||
});
|
||||
return {
|
||||
semanticDataNeed: "document evidence",
|
||||
chainId: "document_evidence",
|
||||
chainSummary: "Keep the metadata-scoped document lane, ask only for the remaining business scope, then fetch scoped document rows and probe coverage without pretending there is a grounded counterparty.",
|
||||
primitives: primitiveSelection.primitives,
|
||||
axes,
|
||||
reason: "planner_selected_metadata_scoped_document_from_data_need_graph",
|
||||
extraReasons: primitiveSelection.reasonCodes
|
||||
};
|
||||
}
|
||||
pushUnique(axes, "coverage_target");
|
||||
const primitiveSelection = selectPrimitivesFromGraphAndCatalog({
|
||||
dataNeedGraph,
|
||||
|
|
|
|||
|
|
@ -98,6 +98,11 @@ function buildLoopState(planner, pilot, bridgeStatus) {
|
|||
pending_axes: plannerClarificationGaps.length > 0 ? plannerClarificationGaps : flattenAxes(pilot, "missing_axis_options"),
|
||||
provided_axes: flattenAxes(pilot, "provided_axes"),
|
||||
explicit_entity_candidates: entityCandidatesFromPlanner(planner),
|
||||
metadata_scope_hint: planner.discovery_plan.turn_meaning_ref?.metadata_scope_hint ??
|
||||
planner.data_need_graph?.metadata_scope_hint ??
|
||||
null,
|
||||
subject_resolution_optional: planner.discovery_plan.turn_meaning_ref?.subject_resolution_optional === true ||
|
||||
planner.data_need_graph?.subject_resolution_optional === true,
|
||||
explicit_organization_scope: planner.discovery_plan.turn_meaning_ref?.explicit_organization_scope ?? null,
|
||||
explicit_date_scope: planner.discovery_plan.turn_meaning_ref?.explicit_date_scope ?? null
|
||||
};
|
||||
|
|
|
|||
|
|
@ -321,6 +321,8 @@ function collectFollowupDiscoverySeed(followupContext) {
|
|||
const loopAskedDomainFamily = toNonEmptyString(followupContext?.previous_discovery_loop_asked_domain_family);
|
||||
const loopAskedActionFamily = toNonEmptyString(followupContext?.previous_discovery_loop_asked_action_family);
|
||||
const loopUnsupportedFamily = toNonEmptyString(followupContext?.previous_discovery_loop_unsupported_family);
|
||||
const loopMetadataScopeHint = toNonEmptyString(followupContext?.previous_discovery_loop_metadata_scope_hint);
|
||||
const loopSubjectResolutionOptional = followupContext?.previous_discovery_loop_subject_resolution_optional === true;
|
||||
const previousIntent = toNonEmptyString(followupContext?.target_intent) ?? toNonEmptyString(followupContext?.previous_intent);
|
||||
const loopMapped = loopStatus === "awaiting_clarification"
|
||||
? mapLoopClarificationSeedToFollowupMeaning({
|
||||
|
|
@ -343,13 +345,16 @@ function collectFollowupDiscoverySeed(followupContext) {
|
|||
const discoveryEntities = collectEntityCandidates(followupContext?.previous_discovery_entity_candidates);
|
||||
const entityResolutionStatus = toNonEmptyString(followupContext?.previous_discovery_entity_resolution_status);
|
||||
const entityResolutionAmbiguityCandidates = collectEntityCandidates(followupContext?.previous_discovery_entity_ambiguity_candidates);
|
||||
const ambiguityBlocksImplicitGrounding = pilotScope === "entity_resolution_search_v1" && entityResolutionStatus === "ambiguous";
|
||||
const ambiguityBlocksImplicitGrounding = effectivePilotScope === "entity_resolution_search_v1" && entityResolutionStatus === "ambiguous";
|
||||
const metadataPilotCarriesScopeOnly = effectivePilotScope === "metadata_inspection_v1" || loopSubjectResolutionOptional;
|
||||
const metadataScopeHint = loopMetadataScopeHint ??
|
||||
(loopSubjectResolutionOptional ? discoveryEntities[0] ?? null : null);
|
||||
const counterparty = toNonEmptyString(previousFilters?.counterparty) ??
|
||||
toNonEmptyString(rootFilters?.counterparty) ??
|
||||
(toNonEmptyString(followupContext?.previous_anchor_type) === "counterparty"
|
||||
? toNonEmptyString(followupContext?.previous_anchor_value)
|
||||
: null) ??
|
||||
(ambiguityBlocksImplicitGrounding ? null : discoveryEntities[0] ?? null);
|
||||
(ambiguityBlocksImplicitGrounding || metadataPilotCarriesScopeOnly ? null : discoveryEntities[0] ?? null);
|
||||
const organization = toNonEmptyString(previousFilters?.organization) ??
|
||||
toNonEmptyString(rootFilters?.organization) ??
|
||||
(toNonEmptyString(followupContext?.previous_anchor_type) === "organization"
|
||||
|
|
@ -367,12 +372,14 @@ function collectFollowupDiscoverySeed(followupContext) {
|
|||
loopPendingAxes,
|
||||
loopProvidedAxes,
|
||||
counterparty,
|
||||
discoveryEntity: ambiguityBlocksImplicitGrounding ? null : discoveryEntities[0] ?? null,
|
||||
discoveryEntity: ambiguityBlocksImplicitGrounding || loopSubjectResolutionOptional ? null : discoveryEntities[0] ?? null,
|
||||
entityResolutionStatus,
|
||||
entityResolutionAmbiguityCandidates,
|
||||
rankingNeed: toNonEmptyString(followupContext?.previous_discovery_ranking_need),
|
||||
organization,
|
||||
dateScope,
|
||||
metadataScopeHint,
|
||||
subjectResolutionOptional: loopSubjectResolutionOptional,
|
||||
metadataRouteFamily: normalizeMetadataRouteFamily(followupContext?.previous_discovery_metadata_route_family),
|
||||
metadataRouteFamilySelectionBasis: normalizeMetadataRouteFamilySelectionBasis(followupContext?.previous_discovery_metadata_route_family_selection_basis),
|
||||
metadataSelectedEntitySet: toNonEmptyString(followupContext?.previous_discovery_metadata_selected_entity_set),
|
||||
|
|
@ -793,10 +800,15 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
!rawLifecycleSignal &&
|
||||
!rawValueFlowSignal &&
|
||||
hasMetadataObjectHint(rawText));
|
||||
const metadataLaneCarryoverAvailable = Boolean(followupSeed.counterparty ||
|
||||
followupSeed.discoveryEntity ||
|
||||
followupSeed.metadataScopeHint ||
|
||||
followupSeed.metadataSelectedEntitySet ||
|
||||
followupSeed.metadataSelectedSurfaceObjects.length > 0);
|
||||
const metadataGroundedDocumentFollowupApplicable = Boolean(followupSeed.pilotScope === "metadata_inspection_v1" &&
|
||||
followupSeed.metadataRouteFamily === "document_evidence" &&
|
||||
!followupSeed.metadataAmbiguityDetected &&
|
||||
followupSeed.counterparty &&
|
||||
metadataLaneCarryoverAvailable &&
|
||||
!rawLifecycleSignal &&
|
||||
!rawValueFlowSignal &&
|
||||
metadataDocumentHintSignal);
|
||||
|
|
@ -804,21 +816,21 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
followupSeed.metadataAmbiguityDetected &&
|
||||
(followupSeed.metadataAmbiguityEntitySets.length === 0 ||
|
||||
metadataEntitySetsSuggestDocumentLane(followupSeed.metadataAmbiguityEntitySets)) &&
|
||||
followupSeed.counterparty &&
|
||||
metadataLaneCarryoverAvailable &&
|
||||
!rawLifecycleSignal &&
|
||||
!rawValueFlowSignal &&
|
||||
metadataDocumentHintSignal);
|
||||
const metadataGroundedMovementFollowupApplicable = Boolean(followupSeed.pilotScope === "metadata_inspection_v1" &&
|
||||
followupSeed.metadataRouteFamily === "movement_evidence" &&
|
||||
!followupSeed.metadataAmbiguityDetected &&
|
||||
followupSeed.counterparty &&
|
||||
metadataLaneCarryoverAvailable &&
|
||||
!rawLifecycleSignal &&
|
||||
metadataMovementHintSignal);
|
||||
const metadataAmbiguityResolvedMovementFollowupApplicable = Boolean(followupSeed.pilotScope === "metadata_inspection_v1" &&
|
||||
followupSeed.metadataAmbiguityDetected &&
|
||||
(followupSeed.metadataAmbiguityEntitySets.length === 0 ||
|
||||
metadataEntitySetsSuggestMovementLane(followupSeed.metadataAmbiguityEntitySets)) &&
|
||||
followupSeed.counterparty &&
|
||||
metadataLaneCarryoverAvailable &&
|
||||
!rawLifecycleSignal &&
|
||||
metadataMovementHintSignal);
|
||||
const entityResolutionGroundedDocumentFollowupApplicable = Boolean(followupSeed.pilotScope === "entity_resolution_search_v1" &&
|
||||
|
|
@ -886,7 +898,7 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
(followupSeed.metadataRouteFamily === "document_evidence" ||
|
||||
followupSeed.metadataRouteFamily === "movement_evidence") &&
|
||||
!followupSeed.metadataAmbiguityDetected &&
|
||||
followupSeed.counterparty &&
|
||||
metadataLaneCarryoverAvailable &&
|
||||
!rawLifecycleSignal &&
|
||||
!rawValueFlowSignal &&
|
||||
!rawMetadataSignal &&
|
||||
|
|
@ -905,7 +917,7 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
const metadataAmbiguityCollapsedDocumentLaneContinuationApplicable = Boolean(followupSeed.pilotScope === "metadata_inspection_v1" &&
|
||||
followupSeed.metadataAmbiguityDetected &&
|
||||
metadataAmbiguityCollapsesToDocumentLane(followupSeed.metadataAmbiguityEntitySets) &&
|
||||
followupSeed.counterparty &&
|
||||
metadataLaneCarryoverAvailable &&
|
||||
!rawLifecycleSignal &&
|
||||
!rawValueFlowSignal &&
|
||||
!rawMetadataSignal &&
|
||||
|
|
@ -915,7 +927,7 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
const metadataAmbiguityCollapsedMovementLaneContinuationApplicable = Boolean(followupSeed.pilotScope === "metadata_inspection_v1" &&
|
||||
followupSeed.metadataAmbiguityDetected &&
|
||||
metadataAmbiguityCollapsesToMovementLane(followupSeed.metadataAmbiguityEntitySets) &&
|
||||
followupSeed.counterparty &&
|
||||
metadataLaneCarryoverAvailable &&
|
||||
!rawLifecycleSignal &&
|
||||
!rawValueFlowSignal &&
|
||||
!rawMetadataSignal &&
|
||||
|
|
@ -926,7 +938,7 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
followupSeed.metadataAmbiguityDetected &&
|
||||
!metadataAmbiguityCollapsesToDocumentLane(followupSeed.metadataAmbiguityEntitySets) &&
|
||||
!metadataAmbiguityCollapsesToMovementLane(followupSeed.metadataAmbiguityEntitySets) &&
|
||||
followupSeed.counterparty &&
|
||||
metadataLaneCarryoverAvailable &&
|
||||
!rawLifecycleSignal &&
|
||||
!rawValueFlowSignal &&
|
||||
!rawMetadataSignal &&
|
||||
|
|
@ -935,6 +947,14 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
hasMetadataDownstreamContinuationSignal(rawText));
|
||||
const metadataGroundedDocumentLaneApplicable = metadataGroundedDocumentFollowupApplicable ||
|
||||
metadataAmbiguityResolvedDocumentFollowupApplicable ||
|
||||
(followupSeed.subjectResolutionOptional &&
|
||||
!followupSeed.counterparty &&
|
||||
metadataLaneCarryoverAvailable &&
|
||||
!rawLifecycleSignal &&
|
||||
!rawValueFlowSignal &&
|
||||
!rawMetadataSignal &&
|
||||
followupSeed.domain === "documents" &&
|
||||
followupSeed.action === "list_documents") ||
|
||||
entityResolutionGroundedDocumentFollowupApplicable ||
|
||||
entityResolutionClarifiedDocumentFollowupApplicable ||
|
||||
valueFlowGroundedDocumentFollowupApplicable ||
|
||||
|
|
@ -943,6 +963,14 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
metadataAmbiguityCollapsedDocumentLaneContinuationApplicable;
|
||||
const metadataGroundedMovementLaneApplicable = metadataGroundedMovementFollowupApplicable ||
|
||||
metadataAmbiguityResolvedMovementFollowupApplicable ||
|
||||
(followupSeed.subjectResolutionOptional &&
|
||||
!followupSeed.counterparty &&
|
||||
metadataLaneCarryoverAvailable &&
|
||||
!rawLifecycleSignal &&
|
||||
!rawValueFlowSignal &&
|
||||
!rawMetadataSignal &&
|
||||
followupSeed.domain === "movements" &&
|
||||
followupSeed.action === "list_movements") ||
|
||||
entityResolutionGroundedMovementFollowupApplicable ||
|
||||
entityResolutionClarifiedMovementFollowupApplicable ||
|
||||
valueFlowGroundedMovementFollowupApplicable ||
|
||||
|
|
@ -1009,7 +1037,17 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
!metadataGroundedDocumentLaneApplicable &&
|
||||
!metadataGroundedMovementLaneApplicable
|
||||
});
|
||||
const groundedFollowupEntity = followupSeed.counterparty ?? followupSeed.discoveryEntity;
|
||||
const metadataLaneScopeHint = rawMetadataScopeHint ??
|
||||
followupSeed.metadataScopeHint ??
|
||||
followupSeed.discoveryEntity ??
|
||||
followupSeed.metadataSelectedEntitySet ??
|
||||
null;
|
||||
const metadataScopedLaneWithoutSubject = Boolean((metadataGroundedMovementLaneApplicable || metadataGroundedDocumentLaneApplicable) &&
|
||||
!followupSeed.counterparty &&
|
||||
metadataLaneCarryoverAvailable);
|
||||
const groundedFollowupEntity = metadataScopedLaneWithoutSubject
|
||||
? null
|
||||
: followupSeed.counterparty ?? followupSeed.discoveryEntity;
|
||||
const entityCandidates = entityResolutionSignal ? [] : [];
|
||||
if (entityResolutionSignal) {
|
||||
pushNormalizedEntityResolutionCandidate(entityCandidates, entityResolutionClarificationCandidate);
|
||||
|
|
@ -1030,11 +1068,15 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
pushScopedEntityCandidate(entityCandidates, normalizedPredecomposeCounterparty, groundedFollowupEntity);
|
||||
if (!groundedFollowupEntity) {
|
||||
pushScopedEntityCandidate(entityCandidates, followupSeed.counterparty, null);
|
||||
pushScopedEntityCandidate(entityCandidates, followupSeed.discoveryEntity, null);
|
||||
if (!metadataScopedLaneWithoutSubject) {
|
||||
pushScopedEntityCandidate(entityCandidates, followupSeed.discoveryEntity, null);
|
||||
}
|
||||
}
|
||||
pushScopedEntityCandidate(entityCandidates, rawEntityCandidate, groundedFollowupEntity);
|
||||
}
|
||||
if ((rawMetadataSignal || metadataFollowupSeedApplicable) && !groundedFollowupEntity) {
|
||||
if ((rawMetadataSignal || metadataFollowupSeedApplicable) &&
|
||||
!groundedFollowupEntity &&
|
||||
!metadataScopedLaneWithoutSubject) {
|
||||
pushUnique(entityCandidates, followupSeed.discoveryEntity);
|
||||
pushUnique(entityCandidates, rawMetadataScopeHint);
|
||||
}
|
||||
|
|
@ -1126,8 +1168,10 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
metadata_ambiguity_entity_sets: metadataAmbiguityLaneClarificationApplicable && followupSeed.metadataAmbiguityEntitySets.length > 0
|
||||
? followupSeed.metadataAmbiguityEntitySets
|
||||
: undefined,
|
||||
metadata_scope_hint: metadataLaneScopeHint,
|
||||
explicit_organization_scope: explicitOrganizationScope,
|
||||
explicit_date_scope: explicitDateScope,
|
||||
subject_resolution_optional: metadataScopedLaneWithoutSubject || undefined,
|
||||
unsupported_but_understood_family: unsupported ??
|
||||
(lifecycleSignal
|
||||
? "counterparty_lifecycle"
|
||||
|
|
@ -1181,12 +1225,18 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
if ((turnMeaning.metadata_ambiguity_entity_sets?.length ?? 0) > 0) {
|
||||
cleanTurnMeaning.metadata_ambiguity_entity_sets = turnMeaning.metadata_ambiguity_entity_sets;
|
||||
}
|
||||
if (toNonEmptyString(turnMeaning.metadata_scope_hint)) {
|
||||
cleanTurnMeaning.metadata_scope_hint = turnMeaning.metadata_scope_hint;
|
||||
}
|
||||
if (toNonEmptyString(turnMeaning.explicit_organization_scope)) {
|
||||
cleanTurnMeaning.explicit_organization_scope = turnMeaning.explicit_organization_scope;
|
||||
}
|
||||
if (toNonEmptyString(turnMeaning.explicit_date_scope)) {
|
||||
cleanTurnMeaning.explicit_date_scope = turnMeaning.explicit_date_scope;
|
||||
}
|
||||
if (turnMeaning.subject_resolution_optional) {
|
||||
cleanTurnMeaning.subject_resolution_optional = true;
|
||||
}
|
||||
if (toNonEmptyString(turnMeaning.unsupported_but_understood_family)) {
|
||||
cleanTurnMeaning.unsupported_but_understood_family = turnMeaning.unsupported_but_understood_family;
|
||||
}
|
||||
|
|
@ -1299,6 +1349,9 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
if (metadataAmbiguityResolvedMovementFollowupApplicable) {
|
||||
pushReason(reasonCodes, "mcp_discovery_metadata_ambiguity_resolved_to_movement_lane");
|
||||
}
|
||||
if (metadataScopedLaneWithoutSubject) {
|
||||
pushReason(reasonCodes, "mcp_discovery_metadata_scoped_lane_without_subject");
|
||||
}
|
||||
if (entityResolutionGroundedDocumentFollowupApplicable) {
|
||||
pushReason(reasonCodes, "mcp_discovery_entity_resolution_grounded_document_followup");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -515,6 +515,8 @@ function createAssistantTransitionPolicy(deps) {
|
|||
const sourceDiscoveryLoopAskedDomainFamily = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryLoopAskedDomainFamily)(carryoverSourceDebug, deps.toNonEmptyString);
|
||||
const sourceDiscoveryLoopAskedActionFamily = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryLoopAskedActionFamily)(carryoverSourceDebug, deps.toNonEmptyString);
|
||||
const sourceDiscoveryLoopUnsupportedFamily = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryLoopUnsupportedFamily)(carryoverSourceDebug, deps.toNonEmptyString);
|
||||
const sourceDiscoveryLoopMetadataScopeHint = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryLoopMetadataScopeHint)(carryoverSourceDebug, deps.toNonEmptyString);
|
||||
const sourceDiscoveryLoopSubjectResolutionOptional = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryLoopSubjectResolutionOptional)(carryoverSourceDebug);
|
||||
const sourceDiscoveryRankingNeed = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryRankingNeed)(carryoverSourceDebug, deps.toNonEmptyString);
|
||||
const sourceDiscoveryEntityAmbiguityCandidates = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryEntityAmbiguityCandidates)(carryoverSourceDebug, deps.toNonEmptyString);
|
||||
const llmExplicitIntent = deps.toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.intent);
|
||||
|
|
@ -760,6 +762,8 @@ function createAssistantTransitionPolicy(deps) {
|
|||
previous_discovery_loop_asked_domain_family: sourceDiscoveryLoopAskedDomainFamily ?? undefined,
|
||||
previous_discovery_loop_asked_action_family: sourceDiscoveryLoopAskedActionFamily ?? undefined,
|
||||
previous_discovery_loop_unsupported_family: sourceDiscoveryLoopUnsupportedFamily ?? undefined,
|
||||
previous_discovery_loop_metadata_scope_hint: sourceDiscoveryLoopMetadataScopeHint ?? undefined,
|
||||
previous_discovery_loop_subject_resolution_optional: sourceDiscoveryLoopSubjectResolutionOptional || undefined,
|
||||
previous_discovery_ranking_need: sourceDiscoveryRankingNeed ?? undefined,
|
||||
previous_discovery_entity_ambiguity_candidates: sourceDiscoveryEntityAmbiguityCandidates.length > 0
|
||||
? sourceDiscoveryEntityAmbiguityCandidates
|
||||
|
|
|
|||
|
|
@ -329,6 +329,27 @@ export function readAssistantMcpDiscoveryLoopUnsupportedFamily(
|
|||
return toNonEmptyString(readAssistantMcpDiscoveryLoopState(debug)?.unsupported_but_understood_family);
|
||||
}
|
||||
|
||||
export function readAssistantMcpDiscoveryLoopMetadataScopeHint(
|
||||
debug: Record<string, unknown> | null,
|
||||
toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString
|
||||
): string | null {
|
||||
return (
|
||||
toNonEmptyString(readAssistantMcpDiscoveryLoopState(debug)?.metadata_scope_hint) ??
|
||||
toNonEmptyString(readAssistantMcpDiscoveryTurnMeaning(debug)?.metadata_scope_hint) ??
|
||||
toNonEmptyString(readAssistantMcpDiscoveryDataNeedGraph(debug)?.metadata_scope_hint)
|
||||
);
|
||||
}
|
||||
|
||||
export function readAssistantMcpDiscoveryLoopSubjectResolutionOptional(
|
||||
debug: Record<string, unknown> | null
|
||||
): boolean {
|
||||
return (
|
||||
readAssistantMcpDiscoveryLoopState(debug)?.subject_resolution_optional === true ||
|
||||
readAssistantMcpDiscoveryTurnMeaning(debug)?.subject_resolution_optional === true ||
|
||||
readAssistantMcpDiscoveryDataNeedGraph(debug)?.subject_resolution_optional === true
|
||||
);
|
||||
}
|
||||
|
||||
export function readAssistantMcpDiscoveryMetadataRouteFamily(
|
||||
debug: Record<string, unknown> | null,
|
||||
toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString
|
||||
|
|
@ -557,6 +578,13 @@ export function readAddressDebugCounterparty(
|
|||
if (String(debug?.anchor_type ?? "") === "counterparty") {
|
||||
return toNonEmptyString(debug?.anchor_value_resolved) ?? toNonEmptyString(debug?.anchor_value_raw);
|
||||
}
|
||||
const discoveryPilotScope = readAssistantMcpDiscoveryPilotScope(debug, toNonEmptyString);
|
||||
const suppressDiscoveryEntityCarryover =
|
||||
discoveryPilotScope === "metadata_inspection_v1" ||
|
||||
readAssistantMcpDiscoveryLoopSubjectResolutionOptional(debug);
|
||||
if (suppressDiscoveryEntityCarryover) {
|
||||
return null;
|
||||
}
|
||||
const discoveryEntities = collectAssistantMcpDiscoveryEntityCandidates(debug, toNonEmptyString);
|
||||
for (const entity of discoveryEntities) {
|
||||
const text = toNonEmptyString(entity);
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ export interface AssistantMcpDiscoveryDataNeedGraphContract {
|
|||
schema_version: typeof ASSISTANT_MCP_DISCOVERY_DATA_NEED_GRAPH_SCHEMA_VERSION;
|
||||
policy_owner: "assistantMcpDiscoveryDataNeedGraph";
|
||||
subject_candidates: string[];
|
||||
metadata_scope_hint?: string | null;
|
||||
subject_resolution_optional?: boolean;
|
||||
business_fact_family: string | null;
|
||||
action_family: string | null;
|
||||
aggregation_need: string | null;
|
||||
|
|
@ -205,6 +207,16 @@ function allowsOpenScopeWithoutSubject(input: {
|
|||
);
|
||||
}
|
||||
|
||||
function allowsMetadataScopedOpenLaneWithoutSubject(input: {
|
||||
family: string | null;
|
||||
subjectResolutionOptional: boolean;
|
||||
}): boolean {
|
||||
return Boolean(
|
||||
input.subjectResolutionOptional &&
|
||||
(input.family === "movement_evidence" || input.family === "document_evidence")
|
||||
);
|
||||
}
|
||||
|
||||
function rankingNeedFromRawUtterance(value: string): string | null {
|
||||
const text = lower(value);
|
||||
if (!text) {
|
||||
|
|
@ -249,6 +261,7 @@ function decompositionCandidatesFor(input: {
|
|||
comparisonNeed: string | null;
|
||||
rankingNeed: string | null;
|
||||
openScopeWithoutSubject: boolean;
|
||||
metadataScopedOpenLaneWithoutSubject: boolean;
|
||||
}): string[] {
|
||||
const result: string[] = [];
|
||||
if (input.family === "schema_surface") {
|
||||
|
|
@ -295,13 +308,17 @@ function decompositionCandidatesFor(input: {
|
|||
return result;
|
||||
}
|
||||
if (input.family === "movement_evidence") {
|
||||
pushUnique(result, "resolve_entity_reference");
|
||||
if (!input.metadataScopedOpenLaneWithoutSubject) {
|
||||
pushUnique(result, "resolve_entity_reference");
|
||||
}
|
||||
pushUnique(result, "fetch_scoped_movements");
|
||||
pushUnique(result, "probe_coverage");
|
||||
return result;
|
||||
}
|
||||
if (input.family === "document_evidence") {
|
||||
pushUnique(result, "resolve_entity_reference");
|
||||
if (!input.metadataScopedOpenLaneWithoutSubject) {
|
||||
pushUnique(result, "resolve_entity_reference");
|
||||
}
|
||||
pushUnique(result, "fetch_scoped_documents");
|
||||
pushUnique(result, "probe_coverage");
|
||||
return result;
|
||||
|
|
@ -345,6 +362,8 @@ export function buildAssistantMcpDiscoveryDataNeedGraph(
|
|||
const seededRankingNeed = toNonEmptyString(turnMeaning?.seeded_ranking_need);
|
||||
const explicitDateScope = toNonEmptyString(turnMeaning?.explicit_date_scope);
|
||||
const explicitOrganizationScope = toNonEmptyString(turnMeaning?.explicit_organization_scope);
|
||||
const metadataScopeHint = toNonEmptyString(turnMeaning?.metadata_scope_hint);
|
||||
const subjectResolutionOptional = turnMeaning?.subject_resolution_optional === true;
|
||||
const subjectCandidates = (turnMeaning?.explicit_entity_candidates ?? [])
|
||||
.map((item) => toNonEmptyString(item))
|
||||
.filter((item): item is string => Boolean(item));
|
||||
|
|
@ -369,6 +388,12 @@ export function buildAssistantMcpDiscoveryDataNeedGraph(
|
|||
rankingNeed,
|
||||
oneSidedOpenScopeTotalHint
|
||||
});
|
||||
const metadataScopedOpenLaneWithoutSubject =
|
||||
subjectCandidates.length === 0 &&
|
||||
allowsMetadataScopedOpenLaneWithoutSubject({
|
||||
family: businessFactFamily,
|
||||
subjectResolutionOptional
|
||||
});
|
||||
const clarificationGaps: string[] = [];
|
||||
if (unsupported === "metadata_lane_choice_clarification" || action === "resolve_next_lane") {
|
||||
pushUnique(clarificationGaps, "lane_family_choice");
|
||||
|
|
@ -380,7 +405,18 @@ export function buildAssistantMcpDiscoveryDataNeedGraph(
|
|||
!explicitOrganizationScope
|
||||
) {
|
||||
pushUnique(clarificationGaps, "organization");
|
||||
} else if (subjectCandidates.length === 0 && businessFactFamily !== "schema_surface" && !openScopeWithoutSubject) {
|
||||
} else if (
|
||||
subjectCandidates.length === 0 &&
|
||||
metadataScopedOpenLaneWithoutSubject &&
|
||||
!explicitOrganizationScope
|
||||
) {
|
||||
pushUnique(clarificationGaps, "organization");
|
||||
} else if (
|
||||
subjectCandidates.length === 0 &&
|
||||
businessFactFamily !== "schema_surface" &&
|
||||
!openScopeWithoutSubject &&
|
||||
!metadataScopedOpenLaneWithoutSubject
|
||||
) {
|
||||
pushUnique(clarificationGaps, "subject");
|
||||
}
|
||||
const timeScopeNeed = timeScopeNeedFor({
|
||||
|
|
@ -393,12 +429,13 @@ export function buildAssistantMcpDiscoveryDataNeedGraph(
|
|||
}
|
||||
const decompositionCandidates = decompositionCandidatesFor({
|
||||
family: businessFactFamily,
|
||||
action,
|
||||
aggregationNeed,
|
||||
comparisonNeed,
|
||||
rankingNeed,
|
||||
openScopeWithoutSubject
|
||||
});
|
||||
action,
|
||||
aggregationNeed,
|
||||
comparisonNeed,
|
||||
rankingNeed,
|
||||
openScopeWithoutSubject,
|
||||
metadataScopedOpenLaneWithoutSubject
|
||||
});
|
||||
const reasonCodes: string[] = [];
|
||||
pushReason(reasonCodes, "data_need_graph_built");
|
||||
if (businessFactFamily) {
|
||||
|
|
@ -418,6 +455,9 @@ export function buildAssistantMcpDiscoveryDataNeedGraph(
|
|||
if (openScopeWithoutSubject && !rankingNeed && !comparisonNeed) {
|
||||
pushReason(reasonCodes, "data_need_graph_open_scope_total_without_subject");
|
||||
}
|
||||
if (metadataScopedOpenLaneWithoutSubject) {
|
||||
pushReason(reasonCodes, "data_need_graph_metadata_scoped_open_lane_without_subject");
|
||||
}
|
||||
if (allTimeScopeHint) {
|
||||
pushReason(reasonCodes, "data_need_graph_all_time_scope_hint");
|
||||
}
|
||||
|
|
@ -432,6 +472,8 @@ export function buildAssistantMcpDiscoveryDataNeedGraph(
|
|||
schema_version: ASSISTANT_MCP_DISCOVERY_DATA_NEED_GRAPH_SCHEMA_VERSION,
|
||||
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
|
||||
subject_candidates: subjectCandidates,
|
||||
metadata_scope_hint: metadataScopeHint,
|
||||
subject_resolution_optional: subjectResolutionOptional || undefined,
|
||||
business_fact_family: businessFactFamily,
|
||||
action_family: toNonEmptyString(turnMeaning?.asked_action_family),
|
||||
aggregation_need: aggregationNeed,
|
||||
|
|
|
|||
|
|
@ -127,6 +127,16 @@ function hasSubjectCandidates(graph: AssistantMcpDiscoveryDataNeedGraphContract
|
|||
return (graph?.subject_candidates.length ?? 0) > 0;
|
||||
}
|
||||
|
||||
function hasMetadataScopedOpenLane(
|
||||
graph: AssistantMcpDiscoveryDataNeedGraphContract | null | undefined,
|
||||
meaning: AssistantMcpDiscoveryTurnMeaningRef | null | undefined
|
||||
): boolean {
|
||||
return Boolean(
|
||||
graph?.subject_resolution_optional === true ||
|
||||
meaning?.subject_resolution_optional === true
|
||||
);
|
||||
}
|
||||
|
||||
function hasReasonCode(
|
||||
graph: AssistantMcpDiscoveryDataNeedGraphContract | null | undefined,
|
||||
reasonCode: string
|
||||
|
|
@ -150,6 +160,12 @@ function addScopeAxes(axes: string[], meaning: AssistantMcpDiscoveryTurnMeaningR
|
|||
}
|
||||
}
|
||||
|
||||
function addMetadataScopeAxis(axes: string[], meaning: AssistantMcpDiscoveryTurnMeaningRef | null | undefined): void {
|
||||
if (toNonEmptyString(meaning?.metadata_scope_hint)) {
|
||||
pushUnique(axes, "metadata_scope");
|
||||
}
|
||||
}
|
||||
|
||||
function addTimeScopeAxes(
|
||||
axes: string[],
|
||||
dataNeedGraph: AssistantMcpDiscoveryDataNeedGraphContract | null | undefined
|
||||
|
|
@ -417,7 +433,7 @@ function recipeFor(input: AssistantMcpDiscoveryPlannerInput): PlannerRecipe {
|
|||
const graphAction = lower(dataNeedGraph?.action_family);
|
||||
const graphAggregation = lower(dataNeedGraph?.aggregation_need);
|
||||
const graphClarificationGaps = (dataNeedGraph?.clarification_gaps ?? []).map((item) => lower(item));
|
||||
const organizationScope = toNonEmptyString(meaning?.explicit_organization_scope);
|
||||
const metadataScopedOpenLane = hasMetadataScopedOpenLane(dataNeedGraph, meaning);
|
||||
const openScopeTotalWithoutSubject =
|
||||
graphFactFamily === "value_flow" &&
|
||||
!hasSubjectCandidates(dataNeedGraph) &&
|
||||
|
|
@ -426,6 +442,7 @@ function recipeFor(input: AssistantMcpDiscoveryPlannerInput): PlannerRecipe {
|
|||
const axes: string[] = [];
|
||||
const requestedAggregationAxis = aggregationAxis(meaning);
|
||||
addScopeAxes(axes, meaning);
|
||||
addMetadataScopeAxis(axes, meaning);
|
||||
addTimeScopeAxes(axes, dataNeedGraph);
|
||||
|
||||
if (graphClarificationGaps.includes("lane_family_choice")) {
|
||||
|
|
@ -654,6 +671,27 @@ function recipeFor(input: AssistantMcpDiscoveryPlannerInput): PlannerRecipe {
|
|||
}
|
||||
|
||||
if (graphFactFamily === "movement_evidence") {
|
||||
if (metadataScopedOpenLane) {
|
||||
pushUnique(axes, "organization");
|
||||
pushUnique(axes, "coverage_target");
|
||||
const primitiveSelection = selectPrimitivesFromGraphAndCatalog({
|
||||
dataNeedGraph,
|
||||
fallbackPrimitives: ["query_movements", "probe_coverage"],
|
||||
requiredAxes: axes,
|
||||
metadataSurface: input.metadataSurface,
|
||||
actionFamily: action
|
||||
});
|
||||
return {
|
||||
semanticDataNeed: "movement evidence",
|
||||
chainId: "movement_evidence",
|
||||
chainSummary:
|
||||
"Keep the metadata-scoped movement lane, ask only for the remaining business scope, then fetch scoped movement rows and probe coverage without pretending there is a grounded counterparty.",
|
||||
primitives: primitiveSelection.primitives,
|
||||
axes,
|
||||
reason: "planner_selected_metadata_scoped_movement_from_data_need_graph",
|
||||
extraReasons: primitiveSelection.reasonCodes
|
||||
};
|
||||
}
|
||||
pushUnique(axes, "coverage_target");
|
||||
const primitiveSelection = selectPrimitivesFromGraphAndCatalog({
|
||||
dataNeedGraph,
|
||||
|
|
@ -674,6 +712,27 @@ function recipeFor(input: AssistantMcpDiscoveryPlannerInput): PlannerRecipe {
|
|||
}
|
||||
|
||||
if (graphFactFamily === "document_evidence") {
|
||||
if (metadataScopedOpenLane) {
|
||||
pushUnique(axes, "organization");
|
||||
pushUnique(axes, "coverage_target");
|
||||
const primitiveSelection = selectPrimitivesFromGraphAndCatalog({
|
||||
dataNeedGraph,
|
||||
fallbackPrimitives: ["query_documents", "probe_coverage"],
|
||||
requiredAxes: axes,
|
||||
metadataSurface: input.metadataSurface,
|
||||
actionFamily: action
|
||||
});
|
||||
return {
|
||||
semanticDataNeed: "document evidence",
|
||||
chainId: "document_evidence",
|
||||
chainSummary:
|
||||
"Keep the metadata-scoped document lane, ask only for the remaining business scope, then fetch scoped document rows and probe coverage without pretending there is a grounded counterparty.",
|
||||
primitives: primitiveSelection.primitives,
|
||||
axes,
|
||||
reason: "planner_selected_metadata_scoped_document_from_data_need_graph",
|
||||
extraReasons: primitiveSelection.reasonCodes
|
||||
};
|
||||
}
|
||||
pushUnique(axes, "coverage_target");
|
||||
const primitiveSelection = selectPrimitivesFromGraphAndCatalog({
|
||||
dataNeedGraph,
|
||||
|
|
|
|||
|
|
@ -27,8 +27,10 @@ export interface AssistantMcpDiscoveryTurnMeaningRef {
|
|||
seeded_ranking_need?: string | null;
|
||||
explicit_entity_candidates?: string[];
|
||||
metadata_ambiguity_entity_sets?: string[];
|
||||
metadata_scope_hint?: string | null;
|
||||
explicit_organization_scope?: string | null;
|
||||
explicit_date_scope?: string | null;
|
||||
subject_resolution_optional?: boolean | null;
|
||||
meaning_confidence?: number | null;
|
||||
unsupported_but_understood_family?: string | null;
|
||||
stale_replay_forbidden?: boolean | null;
|
||||
|
|
|
|||
|
|
@ -53,6 +53,8 @@ export interface AssistantMcpDiscoveryLoopStateContract {
|
|||
pending_axes: string[];
|
||||
provided_axes: string[];
|
||||
explicit_entity_candidates: string[];
|
||||
metadata_scope_hint: string | null;
|
||||
subject_resolution_optional: boolean;
|
||||
explicit_organization_scope: string | null;
|
||||
explicit_date_scope: string | null;
|
||||
}
|
||||
|
|
@ -184,6 +186,13 @@ function buildLoopState(
|
|||
pending_axes: plannerClarificationGaps.length > 0 ? plannerClarificationGaps : flattenAxes(pilot, "missing_axis_options"),
|
||||
provided_axes: flattenAxes(pilot, "provided_axes"),
|
||||
explicit_entity_candidates: entityCandidatesFromPlanner(planner),
|
||||
metadata_scope_hint:
|
||||
planner.discovery_plan.turn_meaning_ref?.metadata_scope_hint ??
|
||||
planner.data_need_graph?.metadata_scope_hint ??
|
||||
null,
|
||||
subject_resolution_optional:
|
||||
planner.discovery_plan.turn_meaning_ref?.subject_resolution_optional === true ||
|
||||
planner.data_need_graph?.subject_resolution_optional === true,
|
||||
explicit_organization_scope: planner.discovery_plan.turn_meaning_ref?.explicit_organization_scope ?? null,
|
||||
explicit_date_scope: planner.discovery_plan.turn_meaning_ref?.explicit_date_scope ?? null
|
||||
};
|
||||
|
|
|
|||
|
|
@ -427,6 +427,8 @@ function collectFollowupDiscoverySeed(followupContext: Record<string, unknown> |
|
|||
rankingNeed: string | null;
|
||||
organization: string | null;
|
||||
dateScope: string | null;
|
||||
metadataScopeHint: string | null;
|
||||
subjectResolutionOptional: boolean;
|
||||
metadataRouteFamily: AssistantMcpDiscoveryMetadataRouteFamily | null;
|
||||
metadataRouteFamilySelectionBasis: AssistantMcpDiscoveryMetadataSurfaceRef["route_family_selection_basis"];
|
||||
metadataSelectedEntitySet: string | null;
|
||||
|
|
@ -445,6 +447,9 @@ function collectFollowupDiscoverySeed(followupContext: Record<string, unknown> |
|
|||
const loopAskedDomainFamily = toNonEmptyString(followupContext?.previous_discovery_loop_asked_domain_family);
|
||||
const loopAskedActionFamily = toNonEmptyString(followupContext?.previous_discovery_loop_asked_action_family);
|
||||
const loopUnsupportedFamily = toNonEmptyString(followupContext?.previous_discovery_loop_unsupported_family);
|
||||
const loopMetadataScopeHint = toNonEmptyString(followupContext?.previous_discovery_loop_metadata_scope_hint);
|
||||
const loopSubjectResolutionOptional =
|
||||
followupContext?.previous_discovery_loop_subject_resolution_optional === true;
|
||||
const previousIntent =
|
||||
toNonEmptyString(followupContext?.target_intent) ?? toNonEmptyString(followupContext?.previous_intent);
|
||||
const loopMapped =
|
||||
|
|
@ -474,14 +479,19 @@ function collectFollowupDiscoverySeed(followupContext: Record<string, unknown> |
|
|||
followupContext?.previous_discovery_entity_ambiguity_candidates
|
||||
);
|
||||
const ambiguityBlocksImplicitGrounding =
|
||||
pilotScope === "entity_resolution_search_v1" && entityResolutionStatus === "ambiguous";
|
||||
effectivePilotScope === "entity_resolution_search_v1" && entityResolutionStatus === "ambiguous";
|
||||
const metadataPilotCarriesScopeOnly =
|
||||
effectivePilotScope === "metadata_inspection_v1" || loopSubjectResolutionOptional;
|
||||
const metadataScopeHint =
|
||||
loopMetadataScopeHint ??
|
||||
(loopSubjectResolutionOptional ? discoveryEntities[0] ?? null : null);
|
||||
const counterparty =
|
||||
toNonEmptyString(previousFilters?.counterparty) ??
|
||||
toNonEmptyString(rootFilters?.counterparty) ??
|
||||
(toNonEmptyString(followupContext?.previous_anchor_type) === "counterparty"
|
||||
? toNonEmptyString(followupContext?.previous_anchor_value)
|
||||
: null) ??
|
||||
(ambiguityBlocksImplicitGrounding ? null : discoveryEntities[0] ?? null);
|
||||
(ambiguityBlocksImplicitGrounding || metadataPilotCarriesScopeOnly ? null : discoveryEntities[0] ?? null);
|
||||
const organization =
|
||||
toNonEmptyString(previousFilters?.organization) ??
|
||||
toNonEmptyString(rootFilters?.organization) ??
|
||||
|
|
@ -501,12 +511,15 @@ function collectFollowupDiscoverySeed(followupContext: Record<string, unknown> |
|
|||
loopPendingAxes,
|
||||
loopProvidedAxes,
|
||||
counterparty,
|
||||
discoveryEntity: ambiguityBlocksImplicitGrounding ? null : discoveryEntities[0] ?? null,
|
||||
discoveryEntity:
|
||||
ambiguityBlocksImplicitGrounding || loopSubjectResolutionOptional ? null : discoveryEntities[0] ?? null,
|
||||
entityResolutionStatus,
|
||||
entityResolutionAmbiguityCandidates,
|
||||
rankingNeed: toNonEmptyString(followupContext?.previous_discovery_ranking_need),
|
||||
organization,
|
||||
dateScope,
|
||||
metadataScopeHint,
|
||||
subjectResolutionOptional: loopSubjectResolutionOptional,
|
||||
metadataRouteFamily: normalizeMetadataRouteFamily(followupContext?.previous_discovery_metadata_route_family),
|
||||
metadataRouteFamilySelectionBasis: normalizeMetadataRouteFamilySelectionBasis(
|
||||
followupContext?.previous_discovery_metadata_route_family_selection_basis
|
||||
|
|
@ -1084,11 +1097,18 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
!rawValueFlowSignal &&
|
||||
hasMetadataObjectHint(rawText)
|
||||
);
|
||||
const metadataLaneCarryoverAvailable = Boolean(
|
||||
followupSeed.counterparty ||
|
||||
followupSeed.discoveryEntity ||
|
||||
followupSeed.metadataScopeHint ||
|
||||
followupSeed.metadataSelectedEntitySet ||
|
||||
followupSeed.metadataSelectedSurfaceObjects.length > 0
|
||||
);
|
||||
const metadataGroundedDocumentFollowupApplicable = Boolean(
|
||||
followupSeed.pilotScope === "metadata_inspection_v1" &&
|
||||
followupSeed.metadataRouteFamily === "document_evidence" &&
|
||||
!followupSeed.metadataAmbiguityDetected &&
|
||||
followupSeed.counterparty &&
|
||||
metadataLaneCarryoverAvailable &&
|
||||
!rawLifecycleSignal &&
|
||||
!rawValueFlowSignal &&
|
||||
metadataDocumentHintSignal
|
||||
|
|
@ -1098,7 +1118,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
followupSeed.metadataAmbiguityDetected &&
|
||||
(followupSeed.metadataAmbiguityEntitySets.length === 0 ||
|
||||
metadataEntitySetsSuggestDocumentLane(followupSeed.metadataAmbiguityEntitySets)) &&
|
||||
followupSeed.counterparty &&
|
||||
metadataLaneCarryoverAvailable &&
|
||||
!rawLifecycleSignal &&
|
||||
!rawValueFlowSignal &&
|
||||
metadataDocumentHintSignal
|
||||
|
|
@ -1107,7 +1127,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
followupSeed.pilotScope === "metadata_inspection_v1" &&
|
||||
followupSeed.metadataRouteFamily === "movement_evidence" &&
|
||||
!followupSeed.metadataAmbiguityDetected &&
|
||||
followupSeed.counterparty &&
|
||||
metadataLaneCarryoverAvailable &&
|
||||
!rawLifecycleSignal &&
|
||||
metadataMovementHintSignal
|
||||
);
|
||||
|
|
@ -1116,7 +1136,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
followupSeed.metadataAmbiguityDetected &&
|
||||
(followupSeed.metadataAmbiguityEntitySets.length === 0 ||
|
||||
metadataEntitySetsSuggestMovementLane(followupSeed.metadataAmbiguityEntitySets)) &&
|
||||
followupSeed.counterparty &&
|
||||
metadataLaneCarryoverAvailable &&
|
||||
!rawLifecycleSignal &&
|
||||
metadataMovementHintSignal
|
||||
);
|
||||
|
|
@ -1206,7 +1226,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
(followupSeed.metadataRouteFamily === "document_evidence" ||
|
||||
followupSeed.metadataRouteFamily === "movement_evidence") &&
|
||||
!followupSeed.metadataAmbiguityDetected &&
|
||||
followupSeed.counterparty &&
|
||||
metadataLaneCarryoverAvailable &&
|
||||
!rawLifecycleSignal &&
|
||||
!rawValueFlowSignal &&
|
||||
!rawMetadataSignal &&
|
||||
|
|
@ -1229,7 +1249,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
followupSeed.pilotScope === "metadata_inspection_v1" &&
|
||||
followupSeed.metadataAmbiguityDetected &&
|
||||
metadataAmbiguityCollapsesToDocumentLane(followupSeed.metadataAmbiguityEntitySets) &&
|
||||
followupSeed.counterparty &&
|
||||
metadataLaneCarryoverAvailable &&
|
||||
!rawLifecycleSignal &&
|
||||
!rawValueFlowSignal &&
|
||||
!rawMetadataSignal &&
|
||||
|
|
@ -1241,7 +1261,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
followupSeed.pilotScope === "metadata_inspection_v1" &&
|
||||
followupSeed.metadataAmbiguityDetected &&
|
||||
metadataAmbiguityCollapsesToMovementLane(followupSeed.metadataAmbiguityEntitySets) &&
|
||||
followupSeed.counterparty &&
|
||||
metadataLaneCarryoverAvailable &&
|
||||
!rawLifecycleSignal &&
|
||||
!rawValueFlowSignal &&
|
||||
!rawMetadataSignal &&
|
||||
|
|
@ -1254,7 +1274,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
followupSeed.metadataAmbiguityDetected &&
|
||||
!metadataAmbiguityCollapsesToDocumentLane(followupSeed.metadataAmbiguityEntitySets) &&
|
||||
!metadataAmbiguityCollapsesToMovementLane(followupSeed.metadataAmbiguityEntitySets) &&
|
||||
followupSeed.counterparty &&
|
||||
metadataLaneCarryoverAvailable &&
|
||||
!rawLifecycleSignal &&
|
||||
!rawValueFlowSignal &&
|
||||
!rawMetadataSignal &&
|
||||
|
|
@ -1265,6 +1285,14 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
const metadataGroundedDocumentLaneApplicable =
|
||||
metadataGroundedDocumentFollowupApplicable ||
|
||||
metadataAmbiguityResolvedDocumentFollowupApplicable ||
|
||||
(followupSeed.subjectResolutionOptional &&
|
||||
!followupSeed.counterparty &&
|
||||
metadataLaneCarryoverAvailable &&
|
||||
!rawLifecycleSignal &&
|
||||
!rawValueFlowSignal &&
|
||||
!rawMetadataSignal &&
|
||||
followupSeed.domain === "documents" &&
|
||||
followupSeed.action === "list_documents") ||
|
||||
entityResolutionGroundedDocumentFollowupApplicable ||
|
||||
entityResolutionClarifiedDocumentFollowupApplicable ||
|
||||
valueFlowGroundedDocumentFollowupApplicable ||
|
||||
|
|
@ -1274,6 +1302,14 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
const metadataGroundedMovementLaneApplicable =
|
||||
metadataGroundedMovementFollowupApplicable ||
|
||||
metadataAmbiguityResolvedMovementFollowupApplicable ||
|
||||
(followupSeed.subjectResolutionOptional &&
|
||||
!followupSeed.counterparty &&
|
||||
metadataLaneCarryoverAvailable &&
|
||||
!rawLifecycleSignal &&
|
||||
!rawValueFlowSignal &&
|
||||
!rawMetadataSignal &&
|
||||
followupSeed.domain === "movements" &&
|
||||
followupSeed.action === "list_movements") ||
|
||||
entityResolutionGroundedMovementFollowupApplicable ||
|
||||
entityResolutionClarifiedMovementFollowupApplicable ||
|
||||
valueFlowGroundedMovementFollowupApplicable ||
|
||||
|
|
@ -1346,7 +1382,20 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
!metadataGroundedDocumentLaneApplicable &&
|
||||
!metadataGroundedMovementLaneApplicable
|
||||
});
|
||||
const groundedFollowupEntity = followupSeed.counterparty ?? followupSeed.discoveryEntity;
|
||||
const metadataLaneScopeHint =
|
||||
rawMetadataScopeHint ??
|
||||
followupSeed.metadataScopeHint ??
|
||||
followupSeed.discoveryEntity ??
|
||||
followupSeed.metadataSelectedEntitySet ??
|
||||
null;
|
||||
const metadataScopedLaneWithoutSubject = Boolean(
|
||||
(metadataGroundedMovementLaneApplicable || metadataGroundedDocumentLaneApplicable) &&
|
||||
!followupSeed.counterparty &&
|
||||
metadataLaneCarryoverAvailable
|
||||
);
|
||||
const groundedFollowupEntity = metadataScopedLaneWithoutSubject
|
||||
? null
|
||||
: followupSeed.counterparty ?? followupSeed.discoveryEntity;
|
||||
const entityCandidates = entityResolutionSignal ? [] : [];
|
||||
if (entityResolutionSignal) {
|
||||
pushNormalizedEntityResolutionCandidate(entityCandidates, entityResolutionClarificationCandidate);
|
||||
|
|
@ -1366,11 +1415,17 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
pushScopedEntityCandidate(entityCandidates, normalizedPredecomposeCounterparty, groundedFollowupEntity);
|
||||
if (!groundedFollowupEntity) {
|
||||
pushScopedEntityCandidate(entityCandidates, followupSeed.counterparty, null);
|
||||
pushScopedEntityCandidate(entityCandidates, followupSeed.discoveryEntity, null);
|
||||
if (!metadataScopedLaneWithoutSubject) {
|
||||
pushScopedEntityCandidate(entityCandidates, followupSeed.discoveryEntity, null);
|
||||
}
|
||||
}
|
||||
pushScopedEntityCandidate(entityCandidates, rawEntityCandidate, groundedFollowupEntity);
|
||||
}
|
||||
if ((rawMetadataSignal || metadataFollowupSeedApplicable) && !groundedFollowupEntity) {
|
||||
if (
|
||||
(rawMetadataSignal || metadataFollowupSeedApplicable) &&
|
||||
!groundedFollowupEntity &&
|
||||
!metadataScopedLaneWithoutSubject
|
||||
) {
|
||||
pushUnique(entityCandidates, followupSeed.discoveryEntity);
|
||||
pushUnique(entityCandidates, rawMetadataScopeHint);
|
||||
}
|
||||
|
|
@ -1487,8 +1542,10 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
metadataAmbiguityLaneClarificationApplicable && followupSeed.metadataAmbiguityEntitySets.length > 0
|
||||
? followupSeed.metadataAmbiguityEntitySets
|
||||
: undefined,
|
||||
metadata_scope_hint: metadataLaneScopeHint,
|
||||
explicit_organization_scope: explicitOrganizationScope,
|
||||
explicit_date_scope: explicitDateScope,
|
||||
subject_resolution_optional: metadataScopedLaneWithoutSubject || undefined,
|
||||
unsupported_but_understood_family:
|
||||
unsupported ??
|
||||
(lifecycleSignal
|
||||
|
|
@ -1546,12 +1603,18 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
if ((turnMeaning.metadata_ambiguity_entity_sets?.length ?? 0) > 0) {
|
||||
cleanTurnMeaning.metadata_ambiguity_entity_sets = turnMeaning.metadata_ambiguity_entity_sets;
|
||||
}
|
||||
if (toNonEmptyString(turnMeaning.metadata_scope_hint)) {
|
||||
cleanTurnMeaning.metadata_scope_hint = turnMeaning.metadata_scope_hint;
|
||||
}
|
||||
if (toNonEmptyString(turnMeaning.explicit_organization_scope)) {
|
||||
cleanTurnMeaning.explicit_organization_scope = turnMeaning.explicit_organization_scope;
|
||||
}
|
||||
if (toNonEmptyString(turnMeaning.explicit_date_scope)) {
|
||||
cleanTurnMeaning.explicit_date_scope = turnMeaning.explicit_date_scope;
|
||||
}
|
||||
if (turnMeaning.subject_resolution_optional) {
|
||||
cleanTurnMeaning.subject_resolution_optional = true;
|
||||
}
|
||||
if (toNonEmptyString(turnMeaning.unsupported_but_understood_family)) {
|
||||
cleanTurnMeaning.unsupported_but_understood_family = turnMeaning.unsupported_but_understood_family;
|
||||
}
|
||||
|
|
@ -1668,6 +1731,9 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
if (metadataAmbiguityResolvedMovementFollowupApplicable) {
|
||||
pushReason(reasonCodes, "mcp_discovery_metadata_ambiguity_resolved_to_movement_lane");
|
||||
}
|
||||
if (metadataScopedLaneWithoutSubject) {
|
||||
pushReason(reasonCodes, "mcp_discovery_metadata_scoped_lane_without_subject");
|
||||
}
|
||||
if (entityResolutionGroundedDocumentFollowupApplicable) {
|
||||
pushReason(reasonCodes, "mcp_discovery_entity_resolution_grounded_document_followup");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ import {
|
|||
readAssistantMcpDiscoveryLoopAskedDomainFamily,
|
||||
readAssistantMcpDiscoveryLoopAskedActionFamily,
|
||||
readAssistantMcpDiscoveryLoopUnsupportedFamily,
|
||||
readAssistantMcpDiscoveryLoopMetadataScopeHint,
|
||||
readAssistantMcpDiscoveryLoopSubjectResolutionOptional,
|
||||
readAddressDebugTemporalScope,
|
||||
readAssistantMcpDiscoveryPilotScope,
|
||||
resolveOrganizationClarificationContinuation,
|
||||
|
|
@ -727,6 +729,12 @@ export function createAssistantTransitionPolicy(deps) {
|
|||
carryoverSourceDebug,
|
||||
deps.toNonEmptyString
|
||||
);
|
||||
const sourceDiscoveryLoopMetadataScopeHint = readAssistantMcpDiscoveryLoopMetadataScopeHint(
|
||||
carryoverSourceDebug,
|
||||
deps.toNonEmptyString
|
||||
);
|
||||
const sourceDiscoveryLoopSubjectResolutionOptional =
|
||||
readAssistantMcpDiscoveryLoopSubjectResolutionOptional(carryoverSourceDebug);
|
||||
const sourceDiscoveryRankingNeed = readAssistantMcpDiscoveryRankingNeed(
|
||||
carryoverSourceDebug,
|
||||
deps.toNonEmptyString
|
||||
|
|
@ -1083,6 +1091,9 @@ export function createAssistantTransitionPolicy(deps) {
|
|||
previous_discovery_loop_asked_domain_family: sourceDiscoveryLoopAskedDomainFamily ?? undefined,
|
||||
previous_discovery_loop_asked_action_family: sourceDiscoveryLoopAskedActionFamily ?? undefined,
|
||||
previous_discovery_loop_unsupported_family: sourceDiscoveryLoopUnsupportedFamily ?? undefined,
|
||||
previous_discovery_loop_metadata_scope_hint: sourceDiscoveryLoopMetadataScopeHint ?? undefined,
|
||||
previous_discovery_loop_subject_resolution_optional:
|
||||
sourceDiscoveryLoopSubjectResolutionOptional || undefined,
|
||||
previous_discovery_ranking_need: sourceDiscoveryRankingNeed ?? undefined,
|
||||
previous_discovery_entity_ambiguity_candidates:
|
||||
sourceDiscoveryEntityAmbiguityCandidates.length > 0
|
||||
|
|
|
|||
|
|
@ -229,4 +229,26 @@ describe("assistant MCP discovery data need graph", () => {
|
|||
expect(result.reason_codes).toContain("data_need_graph_open_scope_total_without_subject");
|
||||
expect(result.reason_codes).toContain("data_need_graph_all_time_scope_hint");
|
||||
});
|
||||
|
||||
it("treats metadata-scoped movement evidence as subjectless and asks only for organization plus period", () => {
|
||||
const result = buildAssistantMcpDiscoveryDataNeedGraph({
|
||||
semanticDataNeed: "movement evidence",
|
||||
rawUtterance: "по движениям",
|
||||
turnMeaning: {
|
||||
asked_domain_family: "movements",
|
||||
asked_action_family: "list_movements",
|
||||
metadata_scope_hint: "НДС",
|
||||
subject_resolution_optional: true,
|
||||
unsupported_but_understood_family: "movement_evidence"
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.business_fact_family).toBe("movement_evidence");
|
||||
expect(result.subject_candidates).toEqual([]);
|
||||
expect(result.metadata_scope_hint).toBe("НДС");
|
||||
expect(result.subject_resolution_optional).toBe(true);
|
||||
expect(result.clarification_gaps).toEqual(["organization", "period"]);
|
||||
expect(result.decomposition_candidates).toEqual(["fetch_scoped_movements", "probe_coverage"]);
|
||||
expect(result.reason_codes).toContain("data_need_graph_metadata_scoped_open_lane_without_subject");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -823,4 +823,86 @@ describe("assistant MCP discovery planner", () => {
|
|||
expect(result.catalog_review.review_status).toBe("catalog_compatible");
|
||||
expect(result.reason_codes).toContain("planner_selected_open_scope_value_flow_total_from_data_need_graph");
|
||||
});
|
||||
|
||||
it("keeps metadata-scoped movement evidence in clarification instead of forcing entity resolution", () => {
|
||||
const result = planAssistantMcpDiscovery({
|
||||
dataNeedGraph: {
|
||||
schema_version: "assistant_data_need_graph_v1",
|
||||
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
|
||||
subject_candidates: [],
|
||||
metadata_scope_hint: "НДС",
|
||||
subject_resolution_optional: true,
|
||||
business_fact_family: "movement_evidence",
|
||||
action_family: "list_movements",
|
||||
aggregation_need: null,
|
||||
time_scope_need: "period_required",
|
||||
comparison_need: null,
|
||||
ranking_need: null,
|
||||
proof_expectation: "clarification_required",
|
||||
clarification_gaps: ["organization", "period"],
|
||||
decomposition_candidates: ["fetch_scoped_movements", "probe_coverage"],
|
||||
forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"],
|
||||
reason_codes: [
|
||||
"data_need_graph_built",
|
||||
"data_need_graph_metadata_scoped_open_lane_without_subject"
|
||||
]
|
||||
},
|
||||
turnMeaning: {
|
||||
asked_domain_family: "movements",
|
||||
asked_action_family: "list_movements",
|
||||
metadata_scope_hint: "НДС",
|
||||
subject_resolution_optional: true,
|
||||
unsupported_but_understood_family: "movement_evidence"
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.planner_status).toBe("needs_clarification");
|
||||
expect(result.selected_chain_id).toBe("movement_evidence");
|
||||
expect(result.proposed_primitives).toEqual(["query_movements", "probe_coverage"]);
|
||||
expect(result.required_axes).toEqual(["metadata_scope", "organization", "coverage_target"]);
|
||||
expect(result.reason_codes).toContain("planner_selected_metadata_scoped_movement_from_data_need_graph");
|
||||
expect(result.selected_chain_id).not.toBe("entity_resolution");
|
||||
});
|
||||
|
||||
it("keeps metadata-scoped movement evidence execution-ready once organization and period are known", () => {
|
||||
const result = planAssistantMcpDiscovery({
|
||||
dataNeedGraph: {
|
||||
schema_version: "assistant_data_need_graph_v1",
|
||||
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
|
||||
subject_candidates: [],
|
||||
metadata_scope_hint: "НДС",
|
||||
subject_resolution_optional: true,
|
||||
business_fact_family: "movement_evidence",
|
||||
action_family: "list_movements",
|
||||
aggregation_need: null,
|
||||
time_scope_need: "explicit_period",
|
||||
comparison_need: null,
|
||||
ranking_need: null,
|
||||
proof_expectation: "coverage_checked_fact",
|
||||
clarification_gaps: [],
|
||||
decomposition_candidates: ["fetch_scoped_movements", "probe_coverage"],
|
||||
forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"],
|
||||
reason_codes: [
|
||||
"data_need_graph_built",
|
||||
"data_need_graph_metadata_scoped_open_lane_without_subject"
|
||||
]
|
||||
},
|
||||
turnMeaning: {
|
||||
asked_domain_family: "movements",
|
||||
asked_action_family: "list_movements",
|
||||
metadata_scope_hint: "НДС",
|
||||
subject_resolution_optional: true,
|
||||
explicit_organization_scope: "ООО Альтернатива Плюс",
|
||||
explicit_date_scope: "2020",
|
||||
unsupported_but_understood_family: "movement_evidence"
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.planner_status).toBe("ready_for_execution");
|
||||
expect(result.selected_chain_id).toBe("movement_evidence");
|
||||
expect(result.proposed_primitives).toEqual(["query_movements", "probe_coverage"]);
|
||||
expect(result.required_axes).toEqual(["organization", "period", "metadata_scope", "coverage_target"]);
|
||||
expect(result.catalog_review.review_status).toBe("catalog_compatible");
|
||||
expect(result.reason_codes).toContain("planner_selected_metadata_scoped_movement_from_data_need_graph");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -429,4 +429,44 @@ describe("assistant MCP discovery runtime bridge", () => {
|
|||
"\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442\u0430"
|
||||
);
|
||||
});
|
||||
it("persists metadata scope and subject-optional flags in the resumable loop state", async () => {
|
||||
const result = await runAssistantMcpDiscoveryRuntimeBridge({
|
||||
dataNeedGraph: {
|
||||
schema_version: "assistant_data_need_graph_v1",
|
||||
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
|
||||
subject_candidates: [],
|
||||
metadata_scope_hint: "\u041d\u0414\u0421",
|
||||
subject_resolution_optional: true,
|
||||
business_fact_family: "movement_evidence",
|
||||
action_family: "list_movements",
|
||||
aggregation_need: null,
|
||||
time_scope_need: "period_required",
|
||||
comparison_need: null,
|
||||
ranking_need: null,
|
||||
proof_expectation: "clarification_required",
|
||||
clarification_gaps: ["organization", "period"],
|
||||
decomposition_candidates: ["fetch_scoped_movements", "probe_coverage"],
|
||||
forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"],
|
||||
reason_codes: ["data_need_graph_built", "data_need_graph_metadata_scoped_open_lane_without_subject"]
|
||||
},
|
||||
turnMeaning: {
|
||||
asked_domain_family: "movements",
|
||||
asked_action_family: "list_movements",
|
||||
metadata_scope_hint: "\u041d\u0414\u0421",
|
||||
subject_resolution_optional: true,
|
||||
unsupported_but_understood_family: "movement_evidence"
|
||||
},
|
||||
deps: buildDeps([])
|
||||
});
|
||||
|
||||
expect(result.bridge_status).toBe("needs_clarification");
|
||||
expect(result.loop_state).toMatchObject({
|
||||
loop_status: "awaiting_clarification",
|
||||
selected_chain_id: "movement_evidence",
|
||||
metadata_scope_hint: "\u041d\u0414\u0421",
|
||||
subject_resolution_optional: true
|
||||
});
|
||||
expect(result.loop_state.pending_axes).toEqual(["organization", "period"]);
|
||||
expect(result.loop_state.explicit_entity_candidates).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1877,4 +1877,144 @@ describe("assistant MCP discovery turn input adapter", () => {
|
|||
);
|
||||
expect(result.reason_codes).not.toContain("mcp_discovery_not_applicable_for_supported_exact_turn");
|
||||
});
|
||||
|
||||
it.skip("keeps metadata-born movement lane subjectless and asks for organization plus period", () => {
|
||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||
userMessage: "по движениям",
|
||||
followupContext: {
|
||||
previous_discovery_loop_status: "awaiting_clarification",
|
||||
previous_discovery_loop_selected_chain_id: "metadata_lane_clarification",
|
||||
previous_discovery_loop_pending_axes: ["lane_family_choice"],
|
||||
previous_discovery_loop_asked_domain_family: "metadata",
|
||||
previous_discovery_loop_asked_action_family: "resolve_next_lane",
|
||||
previous_discovery_loop_unsupported_family: "metadata_lane_choice_clarification",
|
||||
previous_discovery_entity_candidates: ["НДС"],
|
||||
previous_discovery_metadata_ambiguity_detected: true,
|
||||
previous_discovery_metadata_ambiguity_entity_sets: ["Документ", "РегистрНакопления"]
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.adapter_status).toBe("ready");
|
||||
expect(result.should_run_discovery).toBe(true);
|
||||
expect(result.semantic_data_need).toBe("movement evidence");
|
||||
expect(result.turn_meaning_ref).toMatchObject({
|
||||
asked_domain_family: "movements",
|
||||
asked_action_family: "list_movements",
|
||||
metadata_scope_hint: "НДС",
|
||||
subject_resolution_optional: true,
|
||||
unsupported_but_understood_family: "movement_evidence",
|
||||
stale_replay_forbidden: true
|
||||
});
|
||||
expect(result.turn_meaning_ref?.explicit_entity_candidates).toBeUndefined();
|
||||
expect(result.data_need_graph?.clarification_gaps).toEqual(["organization", "period"]);
|
||||
expect(result.reason_codes).toContain("mcp_discovery_metadata_ambiguity_resolved_to_movement_lane");
|
||||
expect(result.reason_codes).toContain("mcp_discovery_metadata_scoped_lane_without_subject");
|
||||
});
|
||||
|
||||
it.skip("keeps metadata scope through organization-only clarification and leaves only period pending", () => {
|
||||
const orgName = "ООО Альтернатива Плюс";
|
||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||
userMessage: "по ООО Альтернатива Плюс",
|
||||
predecomposeContract: {
|
||||
entities: { organization: orgName }
|
||||
},
|
||||
followupContext: {
|
||||
previous_discovery_loop_status: "awaiting_clarification",
|
||||
previous_discovery_loop_selected_chain_id: "movement_evidence",
|
||||
previous_discovery_loop_pending_axes: ["organization", "period"],
|
||||
previous_discovery_loop_asked_domain_family: "movements",
|
||||
previous_discovery_loop_asked_action_family: "list_movements",
|
||||
previous_discovery_loop_unsupported_family: "movement_evidence",
|
||||
previous_discovery_entity_candidates: ["НДС"]
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.adapter_status).toBe("ready");
|
||||
expect(result.should_run_discovery).toBe(true);
|
||||
expect(result.semantic_data_need).toBe("movement evidence");
|
||||
expect(result.turn_meaning_ref).toMatchObject({
|
||||
asked_domain_family: "movements",
|
||||
asked_action_family: "list_movements",
|
||||
metadata_scope_hint: "НДС",
|
||||
subject_resolution_optional: true,
|
||||
explicit_organization_scope: orgName,
|
||||
unsupported_but_understood_family: "movement_evidence",
|
||||
stale_replay_forbidden: true
|
||||
});
|
||||
expect(result.turn_meaning_ref?.explicit_entity_candidates).toBeUndefined();
|
||||
expect(result.data_need_graph?.clarification_gaps).toEqual(["period"]);
|
||||
});
|
||||
it("keeps metadata-born movement lane subjectless and asks for organization plus period (utf8-safe)", () => {
|
||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||
userMessage: "\u043f\u043e \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u044f\u043c",
|
||||
followupContext: {
|
||||
previous_discovery_loop_status: "awaiting_clarification",
|
||||
previous_discovery_loop_selected_chain_id: "metadata_lane_clarification",
|
||||
previous_discovery_loop_pending_axes: ["lane_family_choice"],
|
||||
previous_discovery_loop_asked_domain_family: "metadata",
|
||||
previous_discovery_loop_asked_action_family: "resolve_next_lane",
|
||||
previous_discovery_loop_unsupported_family: "metadata_lane_choice_clarification",
|
||||
previous_discovery_entity_candidates: ["\u041d\u0414\u0421"],
|
||||
previous_discovery_metadata_ambiguity_detected: true,
|
||||
previous_discovery_metadata_ambiguity_entity_sets: [
|
||||
"\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442",
|
||||
"\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u041d\u0430\u043a\u043e\u043f\u043b\u0435\u043d\u0438\u044f"
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.adapter_status).toBe("ready");
|
||||
expect(result.should_run_discovery).toBe(true);
|
||||
expect(result.semantic_data_need).toBe("movement evidence");
|
||||
expect(result.turn_meaning_ref).toMatchObject({
|
||||
asked_domain_family: "movements",
|
||||
asked_action_family: "list_movements",
|
||||
metadata_scope_hint: "\u041d\u0414\u0421",
|
||||
subject_resolution_optional: true,
|
||||
unsupported_but_understood_family: "movement_evidence",
|
||||
stale_replay_forbidden: true
|
||||
});
|
||||
expect(result.turn_meaning_ref?.explicit_entity_candidates).toBeUndefined();
|
||||
expect(result.data_need_graph?.clarification_gaps).toEqual(["organization", "period"]);
|
||||
expect(result.reason_codes).toContain("mcp_discovery_metadata_ambiguity_resolved_to_movement_lane");
|
||||
expect(result.reason_codes).toContain("mcp_discovery_metadata_scoped_lane_without_subject");
|
||||
});
|
||||
|
||||
it("keeps metadata scope through organization-only clarification and leaves only period pending (utf8-safe)", () => {
|
||||
const orgName =
|
||||
"\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441";
|
||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||
userMessage:
|
||||
"\u043f\u043e \u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441",
|
||||
predecomposeContract: {
|
||||
entities: { organization: orgName }
|
||||
},
|
||||
followupContext: {
|
||||
previous_discovery_loop_status: "awaiting_clarification",
|
||||
previous_discovery_loop_selected_chain_id: "movement_evidence",
|
||||
previous_discovery_loop_pending_axes: ["organization", "period"],
|
||||
previous_discovery_loop_asked_domain_family: "movements",
|
||||
previous_discovery_loop_asked_action_family: "list_movements",
|
||||
previous_discovery_loop_unsupported_family: "movement_evidence",
|
||||
previous_discovery_loop_metadata_scope_hint: "\u041d\u0414\u0421",
|
||||
previous_discovery_loop_subject_resolution_optional: true,
|
||||
previous_discovery_entity_candidates: ["\u041d\u0414\u0421"]
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.adapter_status).toBe("ready");
|
||||
expect(result.should_run_discovery).toBe(true);
|
||||
expect(result.semantic_data_need).toBe("movement evidence");
|
||||
expect(result.turn_meaning_ref).toMatchObject({
|
||||
asked_domain_family: "movements",
|
||||
asked_action_family: "list_movements",
|
||||
metadata_scope_hint: "\u041d\u0414\u0421",
|
||||
subject_resolution_optional: true,
|
||||
explicit_organization_scope: orgName,
|
||||
unsupported_but_understood_family: "movement_evidence",
|
||||
stale_replay_forbidden: true
|
||||
});
|
||||
expect(result.turn_meaning_ref?.explicit_entity_candidates).toBeUndefined();
|
||||
expect(result.data_need_graph?.clarification_gaps).toEqual(["period"]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1647,4 +1647,156 @@ describe("assistantTransitionPolicy", () => {
|
|||
period_to: "2017-05-31"
|
||||
});
|
||||
});
|
||||
it("carries metadata-scoped subjectless loop state through follow-up context", () => {
|
||||
const policy = buildPolicy({
|
||||
findLastAddressAssistantItem: () => ({
|
||||
text: "\u043d\u0443\u0436\u043d\u044b \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u044f \u0438 \u043f\u0435\u0440\u0438\u043e\u0434",
|
||||
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: {
|
||||
asked_domain_family: "movements",
|
||||
asked_action_family: "list_movements",
|
||||
metadata_scope_hint: "\u041d\u0414\u0421",
|
||||
subject_resolution_optional: true,
|
||||
unsupported_but_understood_family: "movement_evidence",
|
||||
stale_replay_forbidden: true
|
||||
}
|
||||
},
|
||||
bridge: {
|
||||
bridge_status: "needs_clarification",
|
||||
business_fact_answer_allowed: false,
|
||||
pilot: {
|
||||
pilot_scope: "counterparty_movement_evidence_query_movements_v1"
|
||||
},
|
||||
loop_state: {
|
||||
schema_version: "assistant_mcp_discovery_loop_state_v1",
|
||||
policy_owner: "assistantMcpDiscoveryRuntimeBridge",
|
||||
loop_status: "awaiting_clarification",
|
||||
selected_chain_id: "movement_evidence",
|
||||
pilot_scope: "counterparty_movement_evidence_query_movements_v1",
|
||||
asked_domain_family: "movements",
|
||||
asked_action_family: "list_movements",
|
||||
unsupported_but_understood_family: "movement_evidence",
|
||||
ranking_need: null,
|
||||
pending_axes: ["organization", "period"],
|
||||
provided_axes: [],
|
||||
explicit_entity_candidates: [],
|
||||
metadata_scope_hint: "\u041d\u0414\u0421",
|
||||
subject_resolution_optional: true,
|
||||
explicit_organization_scope: null,
|
||||
explicit_date_scope: null
|
||||
},
|
||||
answer_draft: {
|
||||
answer_mode: "needs_clarification"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
hasAddressFollowupContextSignal: () => true,
|
||||
hasReferentialPointer: () => false,
|
||||
resolveAddressIntent: () => ({ intent: "unknown" }),
|
||||
resolveAddressIntentFamily: () => null,
|
||||
resolveAssistantTurnMeaning: () => null
|
||||
});
|
||||
|
||||
const carryover = policy.resolveAddressFollowupCarryoverContext(
|
||||
"\u043f\u043e \u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441",
|
||||
[{ kind: "assistant", text: "\u043d\u0443\u0436\u043d\u044b \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u044f \u0438 \u043f\u0435\u0440\u0438\u043e\u0434" }],
|
||||
"\u043f\u043e \u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441",
|
||||
{ predecomposeContract: { intent: "unknown" } },
|
||||
null
|
||||
);
|
||||
|
||||
expect(carryover?.followupContext?.previous_discovery_loop_selected_chain_id).toBe("movement_evidence");
|
||||
expect(carryover?.followupContext?.previous_discovery_loop_pending_axes).toEqual([
|
||||
"organization",
|
||||
"period"
|
||||
]);
|
||||
expect(carryover?.followupContext?.previous_discovery_loop_metadata_scope_hint).toBe(
|
||||
"\u041d\u0414\u0421"
|
||||
);
|
||||
expect(carryover?.followupContext?.previous_discovery_loop_subject_resolution_optional).toBe(true);
|
||||
});
|
||||
it("does not backfill metadata scope into counterparty carryover during lane choice follow-up", () => {
|
||||
const policy = buildPolicy({
|
||||
findLastAddressAssistantItem: () => ({
|
||||
text: "\u0443\u0442\u043e\u0447\u043d\u0438\u0442\u0435: \u043f\u043e \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u043c \u0438\u043b\u0438 \u043f\u043e \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u044f\u043c?",
|
||||
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: {
|
||||
asked_domain_family: "metadata",
|
||||
asked_action_family: "resolve_next_lane",
|
||||
explicit_entity_candidates: ["\u041d\u0414\u0421"],
|
||||
metadata_scope_hint: "\u041d\u0414\u0421",
|
||||
metadata_ambiguity_entity_sets: [
|
||||
"\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442",
|
||||
"\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u041d\u0430\u043a\u043e\u043f\u043b\u0435\u043d\u0438\u044f"
|
||||
],
|
||||
unsupported_but_understood_family: "metadata_lane_choice_clarification",
|
||||
stale_replay_forbidden: true
|
||||
}
|
||||
},
|
||||
bridge: {
|
||||
bridge_status: "needs_clarification",
|
||||
business_fact_answer_allowed: false,
|
||||
pilot: {
|
||||
pilot_scope: "metadata_inspection_v1"
|
||||
},
|
||||
loop_state: {
|
||||
schema_version: "assistant_mcp_discovery_loop_state_v1",
|
||||
policy_owner: "assistantMcpDiscoveryRuntimeBridge",
|
||||
loop_status: "awaiting_clarification",
|
||||
selected_chain_id: "metadata_lane_clarification",
|
||||
pilot_scope: "metadata_inspection_v1",
|
||||
asked_domain_family: "metadata",
|
||||
asked_action_family: "resolve_next_lane",
|
||||
unsupported_but_understood_family: "metadata_lane_choice_clarification",
|
||||
ranking_need: null,
|
||||
pending_axes: ["lane_family_choice"],
|
||||
provided_axes: [],
|
||||
explicit_entity_candidates: ["\u041d\u0414\u0421"],
|
||||
metadata_scope_hint: "\u041d\u0414\u0421",
|
||||
subject_resolution_optional: false,
|
||||
explicit_organization_scope: null,
|
||||
explicit_date_scope: null
|
||||
},
|
||||
answer_draft: {
|
||||
answer_mode: "needs_clarification"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
hasAddressFollowupContextSignal: () => true,
|
||||
hasReferentialPointer: () => false,
|
||||
resolveAddressIntent: () => ({ intent: "unknown" }),
|
||||
resolveAddressIntentFamily: () => null,
|
||||
resolveAssistantTurnMeaning: () => null
|
||||
});
|
||||
|
||||
const carryover = policy.resolveAddressFollowupCarryoverContext(
|
||||
"\u043f\u043e \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u044f\u043c",
|
||||
[{ kind: "assistant", text: "\u0443\u0442\u043e\u0447\u043d\u0438\u0442\u0435: \u043f\u043e \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u043c \u0438\u043b\u0438 \u043f\u043e \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u044f\u043c?" }],
|
||||
"\u043f\u043e \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u044f\u043c",
|
||||
{ predecomposeContract: { intent: "unknown" } },
|
||||
null
|
||||
);
|
||||
|
||||
expect(carryover?.followupContext?.previous_filters?.counterparty).toBeUndefined();
|
||||
expect(carryover?.followupContext?.previous_anchor_type).toBeUndefined();
|
||||
expect(carryover?.followupContext?.previous_anchor_value).toBeNull();
|
||||
expect(carryover?.followupContext?.previous_discovery_entity_candidates).toEqual(["\u041d\u0414\u0421"]);
|
||||
expect(carryover?.followupContext?.previous_discovery_pilot_scope).toBe("metadata_inspection_v1");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue