ARCH: сохранять metadata ambiguity через clarification follow-up
This commit is contained in:
parent
40cf71d118
commit
007369a78a
|
|
@ -73,6 +73,10 @@ function isMovementPilot(pilot) {
|
|||
function isMetadataPilot(pilot) {
|
||||
return pilot.pilot_scope === "metadata_inspection_v1";
|
||||
}
|
||||
function isMetadataLaneChoiceClarification(pilot) {
|
||||
return (pilot.reason_codes.includes("planner_selected_metadata_lane_clarification_recipe") ||
|
||||
pilot.dry_run.reason_codes.includes("planner_selected_metadata_lane_clarification_recipe"));
|
||||
}
|
||||
function metadataRouteFamilyLabelRu(routeFamily) {
|
||||
if (routeFamily === "document_evidence") {
|
||||
return "контур документов";
|
||||
|
|
@ -127,6 +131,12 @@ function headlineFor(mode, pilot) {
|
|||
if (mode === "bounded_inference_only") {
|
||||
return "Точный факт не подтвержден, но есть ограниченная оценка по найденной активности в 1С.";
|
||||
}
|
||||
if (mode === "needs_clarification" && isMetadataLaneChoiceClarification(pilot)) {
|
||||
return "По подтвержденной metadata-поверхности видно несколько конкурирующих data-lane, и без явного выбора дальше идти нельзя.";
|
||||
}
|
||||
if (mode === "needs_clarification" && isMetadataLaneChoiceClarification(pilot)) {
|
||||
return "Уточните, в какой контур идти дальше: по документам или по движениям/регистрам.";
|
||||
}
|
||||
if (mode === "needs_clarification") {
|
||||
return "Нужно уточнить контекст перед поиском в 1С.";
|
||||
}
|
||||
|
|
@ -136,6 +146,9 @@ function headlineFor(mode, pilot) {
|
|||
return "Я проверил доступный контур, но подтвержденного факта для ответа не получил.";
|
||||
}
|
||||
function nextStepFor(mode, pilot) {
|
||||
if (mode === "needs_clarification" && isMetadataLaneChoiceClarification(pilot)) {
|
||||
return "Уточните, в какой контур идти дальше: по документам или по движениям/регистрам.";
|
||||
}
|
||||
if (mode === "needs_clarification") {
|
||||
return "Уточните контрагента, период или организацию, и я смогу выполнить проверку по 1С.";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,6 +82,15 @@ function recipeFor(input) {
|
|||
const axes = [];
|
||||
const requestedAggregationAxis = aggregationAxis(meaning);
|
||||
addScopeAxes(axes, meaning);
|
||||
if (includesAny(combined, ["metadata_lane_choice_clarification", "resolve_next_lane"])) {
|
||||
pushUnique(axes, "lane_family_choice");
|
||||
return {
|
||||
semanticDataNeed: "metadata lane clarification",
|
||||
primitives: [],
|
||||
axes,
|
||||
reason: "planner_selected_metadata_lane_clarification_recipe"
|
||||
};
|
||||
}
|
||||
if (includesAny(combined, ["turnover", "revenue", "payment", "payout", "value", "net", "netting", "balance", "cashflow"])) {
|
||||
pushUnique(axes, "aggregate_axis");
|
||||
pushUnique(axes, "amount");
|
||||
|
|
|
|||
|
|
@ -441,6 +441,17 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
!metadataDocumentHintSignal &&
|
||||
!metadataMovementHintSignal &&
|
||||
hasMetadataDownstreamContinuationSignal(rawText));
|
||||
const metadataAmbiguityLaneClarificationApplicable = Boolean(followupSeed.pilotScope === "metadata_inspection_v1" &&
|
||||
followupSeed.metadataAmbiguityDetected &&
|
||||
!metadataAmbiguityCollapsesToDocumentLane(followupSeed.metadataAmbiguityEntitySets) &&
|
||||
!metadataAmbiguityCollapsesToMovementLane(followupSeed.metadataAmbiguityEntitySets) &&
|
||||
followupSeed.counterparty &&
|
||||
!rawLifecycleSignal &&
|
||||
!rawValueFlowSignal &&
|
||||
!rawMetadataSignal &&
|
||||
!metadataDocumentHintSignal &&
|
||||
!metadataMovementHintSignal &&
|
||||
hasMetadataDownstreamContinuationSignal(rawText));
|
||||
const metadataGroundedDocumentLaneApplicable = metadataGroundedDocumentFollowupApplicable ||
|
||||
metadataAmbiguityResolvedDocumentFollowupApplicable ||
|
||||
(metadataGroundedLaneContinuationApplicable && followupSeed.metadataRouteFamily === "document_evidence") ||
|
||||
|
|
@ -450,29 +461,36 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
(metadataGroundedLaneContinuationApplicable && followupSeed.metadataRouteFamily === "movement_evidence") ||
|
||||
metadataAmbiguityCollapsedMovementLaneContinuationApplicable;
|
||||
const effectiveMetadataFollowupSeedApplicable = metadataFollowupSeedApplicable &&
|
||||
!metadataAmbiguityLaneClarificationApplicable &&
|
||||
!metadataGroundedDocumentLaneApplicable &&
|
||||
!metadataGroundedMovementLaneApplicable;
|
||||
const seededDomain = metadataGroundedDocumentLaneApplicable
|
||||
? "documents"
|
||||
: metadataGroundedMovementLaneApplicable
|
||||
? "movements"
|
||||
: followupDiscoverySeedApplicable || effectiveMetadataFollowupSeedApplicable
|
||||
? followupSeed.domain
|
||||
: null;
|
||||
const seededAction = metadataGroundedDocumentLaneApplicable
|
||||
? "list_documents"
|
||||
: metadataGroundedMovementLaneApplicable
|
||||
? "list_movements"
|
||||
: followupDiscoverySeedApplicable || effectiveMetadataFollowupSeedApplicable
|
||||
? followupSeed.action
|
||||
: null;
|
||||
const seededUnsupported = metadataGroundedDocumentLaneApplicable
|
||||
? "document_evidence"
|
||||
: metadataGroundedMovementLaneApplicable
|
||||
? "movement_evidence"
|
||||
: followupDiscoverySeedApplicable || effectiveMetadataFollowupSeedApplicable
|
||||
? followupSeed.unsupported
|
||||
: null;
|
||||
const seededDomain = metadataAmbiguityLaneClarificationApplicable
|
||||
? "metadata"
|
||||
: metadataGroundedDocumentLaneApplicable
|
||||
? "documents"
|
||||
: metadataGroundedMovementLaneApplicable
|
||||
? "movements"
|
||||
: followupDiscoverySeedApplicable || effectiveMetadataFollowupSeedApplicable
|
||||
? followupSeed.domain
|
||||
: null;
|
||||
const seededAction = metadataAmbiguityLaneClarificationApplicable
|
||||
? "resolve_next_lane"
|
||||
: metadataGroundedDocumentLaneApplicable
|
||||
? "list_documents"
|
||||
: metadataGroundedMovementLaneApplicable
|
||||
? "list_movements"
|
||||
: followupDiscoverySeedApplicable || effectiveMetadataFollowupSeedApplicable
|
||||
? followupSeed.action
|
||||
: null;
|
||||
const seededUnsupported = metadataAmbiguityLaneClarificationApplicable
|
||||
? "metadata_lane_choice_clarification"
|
||||
: metadataGroundedDocumentLaneApplicable
|
||||
? "document_evidence"
|
||||
: metadataGroundedMovementLaneApplicable
|
||||
? "movement_evidence"
|
||||
: followupDiscoverySeedApplicable || effectiveMetadataFollowupSeedApplicable
|
||||
? followupSeed.unsupported
|
||||
: null;
|
||||
const lifecycleSignal = rawLifecycleSignal || seededDomain === "counterparty_lifecycle";
|
||||
const bidirectionalValueFlowSignal = !lifecycleSignal &&
|
||||
(rawBidirectionalValueFlowSignal || seededAction === "net_value_flow");
|
||||
|
|
@ -482,14 +500,16 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
const payoutSignal = valueFlowSignal &&
|
||||
!bidirectionalValueFlowSignal &&
|
||||
(rawPayoutSignal || seededAction === "payout");
|
||||
const semanticDataNeed = semanticNeedFor({
|
||||
domain: rawDomain ?? seededDomain,
|
||||
action: rawAction ?? seededAction,
|
||||
unsupported: unsupported ?? seededUnsupported,
|
||||
lifecycleSignal,
|
||||
valueFlowSignal,
|
||||
metadataSignal: rawMetadataSignal || effectiveMetadataFollowupSeedApplicable
|
||||
});
|
||||
const semanticDataNeed = metadataAmbiguityLaneClarificationApplicable
|
||||
? "metadata lane clarification"
|
||||
: semanticNeedFor({
|
||||
domain: rawDomain ?? seededDomain,
|
||||
action: rawAction ?? seededAction,
|
||||
unsupported: unsupported ?? seededUnsupported,
|
||||
lifecycleSignal,
|
||||
valueFlowSignal,
|
||||
metadataSignal: rawMetadataSignal || effectiveMetadataFollowupSeedApplicable
|
||||
});
|
||||
const entityCandidates = collectEntityCandidates(assistantTurnMeaning?.explicit_entity_candidates);
|
||||
pushUnique(entityCandidates, predecomposeEntities.counterparty);
|
||||
pushUnique(entityCandidates, followupSeed.counterparty);
|
||||
|
|
@ -533,6 +553,9 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
: rawAction ?? seededAction,
|
||||
asked_aggregation_axis: monthlyAggregationSignal ? "month" : rawAggregationAxis,
|
||||
explicit_entity_candidates: entityCandidates,
|
||||
metadata_ambiguity_entity_sets: metadataAmbiguityLaneClarificationApplicable && followupSeed.metadataAmbiguityEntitySets.length > 0
|
||||
? followupSeed.metadataAmbiguityEntitySets
|
||||
: undefined,
|
||||
explicit_organization_scope: explicitOrganizationScope,
|
||||
explicit_date_scope: explicitDateScope,
|
||||
unsupported_but_understood_family: unsupported ??
|
||||
|
|
@ -548,17 +571,20 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
? "movement_evidence"
|
||||
: metadataGroundedDocumentLaneApplicable
|
||||
? "document_evidence"
|
||||
: rawMetadataSignal || effectiveMetadataFollowupSeedApplicable
|
||||
? "1c_metadata_surface"
|
||||
: followupDiscoverySeedApplicable
|
||||
? seededUnsupported
|
||||
: null),
|
||||
: metadataAmbiguityLaneClarificationApplicable
|
||||
? "metadata_lane_choice_clarification"
|
||||
: rawMetadataSignal || effectiveMetadataFollowupSeedApplicable
|
||||
? "1c_metadata_surface"
|
||||
: followupDiscoverySeedApplicable
|
||||
? seededUnsupported
|
||||
: null),
|
||||
stale_replay_forbidden: Boolean(assistantTurnMeaning?.stale_replay_forbidden ||
|
||||
unsupported ||
|
||||
lifecycleSignal ||
|
||||
valueFlowSignal ||
|
||||
metadataGroundedMovementLaneApplicable ||
|
||||
metadataGroundedDocumentLaneApplicable ||
|
||||
metadataAmbiguityLaneClarificationApplicable ||
|
||||
rawMetadataSignal ||
|
||||
effectiveMetadataFollowupSeedApplicable ||
|
||||
followupDiscoverySeedApplicable)
|
||||
|
|
@ -576,6 +602,9 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
if ((turnMeaning.explicit_entity_candidates?.length ?? 0) > 0) {
|
||||
cleanTurnMeaning.explicit_entity_candidates = turnMeaning.explicit_entity_candidates;
|
||||
}
|
||||
if ((turnMeaning.metadata_ambiguity_entity_sets?.length ?? 0) > 0) {
|
||||
cleanTurnMeaning.metadata_ambiguity_entity_sets = turnMeaning.metadata_ambiguity_entity_sets;
|
||||
}
|
||||
if (toNonEmptyString(turnMeaning.explicit_organization_scope)) {
|
||||
cleanTurnMeaning.explicit_organization_scope = turnMeaning.explicit_organization_scope;
|
||||
}
|
||||
|
|
@ -597,13 +626,14 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
explicitIntentCandidate,
|
||||
followupDiscoverySeedApplicable: followupDiscoverySeedApplicable ||
|
||||
effectiveMetadataFollowupSeedApplicable ||
|
||||
metadataAmbiguityLaneClarificationApplicable ||
|
||||
metadataGroundedMovementLaneApplicable ||
|
||||
metadataGroundedDocumentLaneApplicable
|
||||
});
|
||||
const hasTurnMeaning = Object.keys(cleanTurnMeaning).length > 0;
|
||||
const sourceSignal = assistantTurnMeaning
|
||||
? "assistant_turn_meaning"
|
||||
: followupDiscoverySeedApplicable || effectiveMetadataFollowupSeedApplicable
|
||||
: followupDiscoverySeedApplicable || effectiveMetadataFollowupSeedApplicable || metadataAmbiguityLaneClarificationApplicable
|
||||
? "followup_context"
|
||||
: metadataGroundedMovementLaneApplicable
|
||||
? "followup_context"
|
||||
|
|
@ -663,6 +693,9 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
if (metadataAmbiguityCollapsedMovementLaneContinuationApplicable) {
|
||||
pushReason(reasonCodes, "mcp_discovery_metadata_ambiguity_collapsed_to_movement_lane");
|
||||
}
|
||||
if (metadataAmbiguityLaneClarificationApplicable) {
|
||||
pushReason(reasonCodes, "mcp_discovery_metadata_ambiguity_requires_lane_choice");
|
||||
}
|
||||
if (unsupported) {
|
||||
pushReason(reasonCodes, "mcp_discovery_unsupported_but_understood_turn");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -142,6 +142,17 @@ function readAssistantMcpDiscoveryTurnMeaning(
|
|||
return toRecordObject(turnInput?.turn_meaning_ref);
|
||||
}
|
||||
|
||||
function readAssistantMcpDiscoveryTurnMeaningMetadataAmbiguityEntitySets(
|
||||
debug: Record<string, unknown> | null,
|
||||
toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString
|
||||
): string[] {
|
||||
const values = readAssistantMcpDiscoveryTurnMeaning(debug)?.metadata_ambiguity_entity_sets;
|
||||
if (!Array.isArray(values)) {
|
||||
return [];
|
||||
}
|
||||
return values.map((item) => toNonEmptyString(item)).filter((item): item is string => Boolean(item));
|
||||
}
|
||||
|
||||
function readAssistantMcpDiscoveryActionFamily(
|
||||
debug: Record<string, unknown> | null,
|
||||
toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString
|
||||
|
|
@ -189,7 +200,10 @@ export function readAssistantMcpDiscoveryMetadataSelectedEntitySet(
|
|||
export function readAssistantMcpDiscoveryMetadataAmbiguityDetected(
|
||||
debug: Record<string, unknown> | null
|
||||
): boolean {
|
||||
return readAssistantMcpDiscoveryDerivedMetadataSurface(debug)?.ambiguity_detected === true;
|
||||
return (
|
||||
readAssistantMcpDiscoveryDerivedMetadataSurface(debug)?.ambiguity_detected === true ||
|
||||
readAssistantMcpDiscoveryTurnMeaningMetadataAmbiguityEntitySets(debug).length > 0
|
||||
);
|
||||
}
|
||||
|
||||
export function readAssistantMcpDiscoveryMetadataAmbiguityEntitySets(
|
||||
|
|
@ -197,10 +211,10 @@ export function readAssistantMcpDiscoveryMetadataAmbiguityEntitySets(
|
|||
toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString
|
||||
): string[] {
|
||||
const values = readAssistantMcpDiscoveryDerivedMetadataSurface(debug)?.ambiguity_entity_sets;
|
||||
if (!Array.isArray(values)) {
|
||||
return [];
|
||||
if (Array.isArray(values)) {
|
||||
return values.map((item) => toNonEmptyString(item)).filter((item): item is string => Boolean(item));
|
||||
}
|
||||
return values.map((item) => toNonEmptyString(item)).filter((item): item is string => Boolean(item));
|
||||
return readAssistantMcpDiscoveryTurnMeaningMetadataAmbiguityEntitySets(debug, toNonEmptyString);
|
||||
}
|
||||
|
||||
function mapAssistantMcpDiscoveryPilotScopeToAddressIntent(
|
||||
|
|
|
|||
|
|
@ -1562,6 +1562,9 @@ function buildEmptyEvidence(
|
|||
}
|
||||
|
||||
function pilotScopeForPlanner(planner: AssistantMcpDiscoveryPlannerContract): AssistantMcpDiscoveryPilotScope {
|
||||
if (planner.reason_codes.includes("planner_selected_metadata_lane_clarification_recipe")) {
|
||||
return "metadata_inspection_v1";
|
||||
}
|
||||
if (isMetadataPilotEligible(planner)) {
|
||||
return "metadata_inspection_v1";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ export interface AssistantMcpDiscoveryTurnMeaningRef {
|
|||
asked_action_family?: string | null;
|
||||
asked_aggregation_axis?: string | null;
|
||||
explicit_entity_candidates?: string[];
|
||||
metadata_ambiguity_entity_sets?: string[];
|
||||
explicit_organization_scope?: string | null;
|
||||
explicit_date_scope?: string | null;
|
||||
meaning_confidence?: number | null;
|
||||
|
|
@ -170,6 +171,7 @@ function normalizeTurnMeaning(
|
|||
const dateScope = toNonEmptyString(value.explicit_date_scope);
|
||||
const unsupported = toNonEmptyString(value.unsupported_but_understood_family);
|
||||
const entities = toStringList(value.explicit_entity_candidates);
|
||||
const metadataAmbiguityEntitySets = toStringList(value.metadata_ambiguity_entity_sets);
|
||||
if (domain) {
|
||||
result.asked_domain_family = domain;
|
||||
}
|
||||
|
|
@ -182,6 +184,9 @@ function normalizeTurnMeaning(
|
|||
if (entities.length > 0) {
|
||||
result.explicit_entity_candidates = entities;
|
||||
}
|
||||
if (metadataAmbiguityEntitySets.length > 0) {
|
||||
result.metadata_ambiguity_entity_sets = metadataAmbiguityEntitySets;
|
||||
}
|
||||
if (organization) {
|
||||
result.explicit_organization_scope = organization;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -725,6 +725,10 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
: rawAction ?? seededAction,
|
||||
asked_aggregation_axis: monthlyAggregationSignal ? "month" : rawAggregationAxis,
|
||||
explicit_entity_candidates: entityCandidates,
|
||||
metadata_ambiguity_entity_sets:
|
||||
metadataAmbiguityLaneClarificationApplicable && followupSeed.metadataAmbiguityEntitySets.length > 0
|
||||
? followupSeed.metadataAmbiguityEntitySets
|
||||
: undefined,
|
||||
explicit_organization_scope: explicitOrganizationScope,
|
||||
explicit_date_scope: explicitDateScope,
|
||||
unsupported_but_understood_family:
|
||||
|
|
@ -775,6 +779,9 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
if ((turnMeaning.explicit_entity_candidates?.length ?? 0) > 0) {
|
||||
cleanTurnMeaning.explicit_entity_candidates = turnMeaning.explicit_entity_candidates;
|
||||
}
|
||||
if ((turnMeaning.metadata_ambiguity_entity_sets?.length ?? 0) > 0) {
|
||||
cleanTurnMeaning.metadata_ambiguity_entity_sets = turnMeaning.metadata_ambiguity_entity_sets;
|
||||
}
|
||||
if (toNonEmptyString(turnMeaning.explicit_organization_scope)) {
|
||||
cleanTurnMeaning.explicit_organization_scope = turnMeaning.explicit_organization_scope;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -595,6 +595,7 @@ describe("assistant MCP discovery turn input adapter", () => {
|
|||
asked_domain_family: "metadata",
|
||||
asked_action_family: "resolve_next_lane",
|
||||
explicit_entity_candidates: ["SVK"],
|
||||
metadata_ambiguity_entity_sets: ["Документ", "РегистрНакопления"],
|
||||
explicit_date_scope: "2020",
|
||||
unsupported_but_understood_family: "metadata_lane_choice_clarification",
|
||||
stale_replay_forbidden: true
|
||||
|
|
|
|||
|
|
@ -1227,6 +1227,61 @@ describe("assistantTransitionPolicy", () => {
|
|||
"РегистрНакопления"
|
||||
]);
|
||||
});
|
||||
it("preserves metadata ambiguity choice sets through a clarification assistant turn", () => {
|
||||
const policy = buildPolicy({
|
||||
findLastAddressAssistantItem: () => ({
|
||||
text: "уточните: по документам или по движениям?",
|
||||
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: ["SVK"],
|
||||
metadata_ambiguity_entity_sets: ["Документ", "РегистрНакопления"],
|
||||
explicit_date_scope: "2020",
|
||||
unsupported_but_understood_family: "metadata_lane_choice_clarification"
|
||||
}
|
||||
},
|
||||
bridge: {
|
||||
bridge_status: "needs_clarification",
|
||||
business_fact_answer_allowed: false,
|
||||
pilot: {
|
||||
pilot_scope: "metadata_inspection_v1"
|
||||
},
|
||||
answer_draft: {
|
||||
answer_mode: "needs_clarification"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
hasAddressFollowupContextSignal: () => true,
|
||||
hasReferentialPointer: () => false,
|
||||
resolveAddressIntent: () => ({ intent: "unknown" }),
|
||||
resolveAddressIntentFamily: () => null,
|
||||
resolveAssistantTurnMeaning: () => null
|
||||
});
|
||||
|
||||
const carryover = policy.resolveAddressFollowupCarryoverContext(
|
||||
"по движениям",
|
||||
[{ kind: "assistant", text: "уточните: по документам или по движениям?" }],
|
||||
"по движениям",
|
||||
{ predecomposeContract: { intent: "unknown" } },
|
||||
null
|
||||
);
|
||||
|
||||
expect(carryover?.followupContext?.previous_discovery_pilot_scope).toBe("metadata_inspection_v1");
|
||||
expect(carryover?.followupContext?.previous_discovery_metadata_ambiguity_detected).toBe(true);
|
||||
expect(carryover?.followupContext?.previous_discovery_metadata_ambiguity_entity_sets).toEqual([
|
||||
"Документ",
|
||||
"РегистрНакопления"
|
||||
]);
|
||||
});
|
||||
it("switches to VAT tax-period intent while preserving carried period filters", () => {
|
||||
const policy = buildPolicy({
|
||||
findLastAddressAssistantItem: () => ({
|
||||
|
|
|
|||
Loading…
Reference in New Issue