ARCH: замкнуть multi-hop ranking clarification loop
This commit is contained in:
@@ -267,15 +267,29 @@ function dryRunMissingAxis(pilot: AssistantMcpDiscoveryPilotExecutionContract, a
|
||||
);
|
||||
}
|
||||
|
||||
function queryPlanClarificationGaps(pilot: AssistantMcpDiscoveryPilotExecutionContract): string[] {
|
||||
const values = pilot.evidence.query_plan.clarification_gaps;
|
||||
return Array.isArray(values) ? uniqueStrings(values) : [];
|
||||
}
|
||||
|
||||
function clarificationGapMissing(pilot: AssistantMcpDiscoveryPilotExecutionContract, axis: string): boolean {
|
||||
const gaps = queryPlanClarificationGaps(pilot);
|
||||
if (gaps.length > 0) {
|
||||
return gaps.includes(axis);
|
||||
}
|
||||
return dryRunMissingAxis(pilot, axis);
|
||||
}
|
||||
|
||||
function clarificationNeedRu(
|
||||
pilot: AssistantMcpDiscoveryPilotExecutionContract
|
||||
): { subject: string; verb: string } {
|
||||
const needsPeriod = clarificationGapMissing(pilot, "period");
|
||||
const organizationScopedOpenTotal =
|
||||
pilot.reason_codes.includes("data_need_graph_open_scope_total_needs_organization") ||
|
||||
pilot.dry_run.reason_codes.includes("data_need_graph_open_scope_total_needs_organization") ||
|
||||
pilot.reason_codes.includes("planner_requires_organization_scope_from_data_need_graph") ||
|
||||
pilot.dry_run.reason_codes.includes("planner_requires_organization_scope_from_data_need_graph");
|
||||
if (organizationScopedOpenTotal) {
|
||||
if (organizationScopedOpenTotal && !needsPeriod) {
|
||||
return {
|
||||
subject: "\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u044e",
|
||||
verb: "\u043d\u0443\u0436\u043d\u043e"
|
||||
@@ -283,8 +297,7 @@ function clarificationNeedRu(
|
||||
}
|
||||
const hasCounterparty = dryRunHasAxis(pilot, "counterparty");
|
||||
const hasAccount = dryRunHasAxis(pilot, "account");
|
||||
const needsPeriod = dryRunMissingAxis(pilot, "period");
|
||||
const needsOrganization = !hasCounterparty && !hasAccount && dryRunMissingAxis(pilot, "organization");
|
||||
const needsOrganization = !hasCounterparty && !hasAccount && clarificationGapMissing(pilot, "organization");
|
||||
if (needsPeriod && needsOrganization) {
|
||||
return { subject: "проверяемый период и организацию", verb: "нужно" };
|
||||
}
|
||||
@@ -306,8 +319,8 @@ function clarificationNextStepLine(
|
||||
pilot.dry_run.reason_codes.includes("data_need_graph_open_scope_total_needs_organization") ||
|
||||
pilot.reason_codes.includes("planner_requires_organization_scope_from_data_need_graph") ||
|
||||
pilot.dry_run.reason_codes.includes("planner_requires_organization_scope_from_data_need_graph");
|
||||
const needsPeriod = dryRunMissingAxis(pilot, "period");
|
||||
const needsOrganization = dryRunMissingAxis(pilot, "organization");
|
||||
const needsPeriod = clarificationGapMissing(pilot, "period");
|
||||
const needsOrganization = clarificationGapMissing(pilot, "organization");
|
||||
const scopeSuffix = laneScopeSuffix(pilot);
|
||||
if (organizationScopedOpenTotal && !needsPeriod) {
|
||||
return `Уточните организацию, и я продолжу поиск по ${laneLabel}${scopeSuffix} в 1С.`;
|
||||
|
||||
@@ -852,6 +852,7 @@ export function planAssistantMcpDiscovery(
|
||||
turnMeaning: input.turnMeaning,
|
||||
proposedPrimitives: recipe.primitives,
|
||||
requiredAxes: recipe.axes,
|
||||
clarificationGaps: dataNeedGraph?.clarification_gaps ?? [],
|
||||
maxProbeCount: budgetOverride.maxProbeCount
|
||||
});
|
||||
const review = reviewAssistantMcpDiscoveryPlanAgainstCatalog(plan);
|
||||
|
||||
@@ -48,6 +48,7 @@ export interface AssistantMcpDiscoveryPlanContract {
|
||||
allowed_primitives: AssistantMcpDiscoveryPrimitive[];
|
||||
rejected_primitives: string[];
|
||||
required_axes: string[];
|
||||
clarification_gaps: string[];
|
||||
execution_budget: AssistantMcpDiscoveryExecutionBudget;
|
||||
requires_evidence_gate: true;
|
||||
answer_may_use_raw_model_claims: false;
|
||||
@@ -59,6 +60,7 @@ export interface BuildAssistantMcpDiscoveryPlanInput {
|
||||
turnMeaning?: AssistantMcpDiscoveryTurnMeaningRef | null;
|
||||
proposedPrimitives?: string[] | null;
|
||||
requiredAxes?: string[] | null;
|
||||
clarificationGaps?: string[] | null;
|
||||
maxProbeCount?: number | null;
|
||||
maxRowsPerProbe?: number | null;
|
||||
}
|
||||
@@ -237,6 +239,7 @@ export function buildAssistantMcpDiscoveryPlan(
|
||||
const semanticDataNeed = toNonEmptyString(input.semanticDataNeed);
|
||||
const turnMeaning = normalizeTurnMeaning(input.turnMeaning);
|
||||
const requiredAxes = toStringList(input.requiredAxes);
|
||||
const clarificationGaps = toStringList(input.clarificationGaps);
|
||||
const proposed = toStringList(input.proposedPrimitives);
|
||||
const reasonCodes: string[] = [];
|
||||
const allowedPrimitives: AssistantMcpDiscoveryPrimitive[] = [];
|
||||
@@ -297,6 +300,7 @@ export function buildAssistantMcpDiscoveryPlan(
|
||||
allowed_primitives: allowedPrimitives,
|
||||
rejected_primitives: rejectedPrimitives,
|
||||
required_axes: requiredAxes,
|
||||
clarification_gaps: clarificationGaps,
|
||||
execution_budget: {
|
||||
max_probe_count: clampInteger(input.maxProbeCount, DEFAULT_DISCOVERY_BUDGET.max_probe_count, 1, MAX_PROBE_COUNT),
|
||||
max_rows_per_probe: clampInteger(
|
||||
|
||||
@@ -169,6 +169,7 @@ function buildLoopState(
|
||||
pilot: AssistantMcpDiscoveryPilotExecutionContract,
|
||||
bridgeStatus: AssistantMcpDiscoveryRuntimeBridgeStatus
|
||||
): AssistantMcpDiscoveryLoopStateContract {
|
||||
const plannerClarificationGaps = planner.discovery_plan.clarification_gaps ?? [];
|
||||
return {
|
||||
schema_version: ASSISTANT_MCP_DISCOVERY_LOOP_STATE_SCHEMA_VERSION,
|
||||
policy_owner: "assistantMcpDiscoveryRuntimeBridge",
|
||||
@@ -180,7 +181,7 @@ function buildLoopState(
|
||||
unsupported_but_understood_family:
|
||||
planner.discovery_plan.turn_meaning_ref?.unsupported_but_understood_family ?? null,
|
||||
ranking_need: planner.data_need_graph?.ranking_need ?? planner.discovery_plan.turn_meaning_ref?.seeded_ranking_need ?? null,
|
||||
pending_axes: flattenAxes(pilot, "missing_axis_options"),
|
||||
pending_axes: plannerClarificationGaps.length > 0 ? plannerClarificationGaps : flattenAxes(pilot, "missing_axis_options"),
|
||||
provided_axes: flattenAxes(pilot, "provided_axes"),
|
||||
explicit_entity_candidates: entityCandidatesFromPlanner(planner),
|
||||
explicit_organization_scope: planner.discovery_plan.turn_meaning_ref?.explicit_organization_scope ?? null,
|
||||
|
||||
@@ -881,6 +881,20 @@ function collectDateScopeFromRawText(text: string): string | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
function currentIsoDate(): string {
|
||||
return new Date().toISOString().slice(0, 10);
|
||||
}
|
||||
|
||||
function hasRelativeCurrentDateHint(text: string): boolean {
|
||||
return /(?:\bсегодня\b|\bна\s+сегодня\b|\bсегодняшн(?:ий|его|ем)\b|\btoday\b|\bas\s+of\s+today\b|\bcurrent\s+date\b)/iu.test(
|
||||
text
|
||||
);
|
||||
}
|
||||
|
||||
function isImplicitCurrentDateScope(value: string | null): boolean {
|
||||
return Boolean(value && /^\d{4}-\d{2}-\d{2}$/.test(value) && value === currentIsoDate());
|
||||
}
|
||||
|
||||
function semanticNeedFor(input: {
|
||||
domain: string | null;
|
||||
action: string | null;
|
||||
@@ -979,6 +993,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||
const monthlyAggregationSignal = hasMonthlyAggregationSignal(rawText);
|
||||
const rawAllTimeScopeSignal = hasAllTimeScopeHint(rawText);
|
||||
const explicitDateScopeLiteralDetected = hasExplicitDateScopeLiteral(rawText);
|
||||
const relativeCurrentDateHintDetected = hasRelativeCurrentDateHint(rawText);
|
||||
const rawDateScope = collectDateScopeFromRawText(rawText);
|
||||
const rawMetadataScopeHint = rawMetadataSignal ? metadataScopeHintFromRawText(rawText) : null;
|
||||
const rawEntityCandidate = rawEntityResolutionSignal ? rawEntityResolutionCandidate(rawEntitySourceText) : null;
|
||||
@@ -1356,6 +1371,9 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||
organizationClarificationFollowupApplicable ||
|
||||
followupSeed.organization
|
||||
);
|
||||
const openScopeValueFlowWithoutResolvedCounterparty = Boolean(
|
||||
valueFlowSignal && !normalizedPredecomposeCounterparty && !followupSeed.counterparty
|
||||
);
|
||||
if (openScopeValueFlowWithoutCounterparty && !valueFlowOrganizationStaysScope) {
|
||||
pushUnique(entityCandidates, predecomposeEntities.organization);
|
||||
pushUnique(entityCandidates, followupSeed.organization);
|
||||
@@ -1371,16 +1389,44 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||
}
|
||||
}
|
||||
}
|
||||
const clarificationLoopStillNeedsPeriod = Boolean(
|
||||
followupSeed.loopStatus === "awaiting_clarification" && followupSeed.loopPendingAxes.includes("period")
|
||||
);
|
||||
const currentTurnCarriesExplicitPeriod = Boolean(
|
||||
explicitDateScopeLiteralDetected ||
|
||||
rawDateScope ||
|
||||
relativeCurrentDateHintDetected ||
|
||||
(predecomposeDateScope && !isImplicitCurrentDateScope(predecomposeDateScope))
|
||||
);
|
||||
const suppressImplicitCurrentDateScope = Boolean(
|
||||
!currentTurnCarriesExplicitPeriod &&
|
||||
(clarificationLoopStillNeedsPeriod ||
|
||||
openScopeValueFlowWithoutResolvedCounterparty ||
|
||||
(valueFlowOrganizationStaysScope && (Boolean(followupSeed.rankingNeed) || bidirectionalValueFlowSignal)))
|
||||
);
|
||||
const normalizedPredecomposeDateScope =
|
||||
suppressImplicitCurrentDateScope && isImplicitCurrentDateScope(predecomposeDateScope) ? null : predecomposeDateScope;
|
||||
const normalizedAssistantTurnMeaningDateScope =
|
||||
suppressImplicitCurrentDateScope && isImplicitCurrentDateScope(assistantTurnMeaningDateScope)
|
||||
? null
|
||||
: assistantTurnMeaningDateScope;
|
||||
const normalizedFollowupDateScope =
|
||||
suppressImplicitCurrentDateScope && isImplicitCurrentDateScope(followupSeed.dateScope)
|
||||
? null
|
||||
: followupSeed.dateScope;
|
||||
const explicitDateScope =
|
||||
rawAllTimeScopeSignal
|
||||
? null
|
||||
: assistantTurnMeaningDateScope ?? predecomposeDateScope ?? rawDateScope ?? followupSeed.dateScope;
|
||||
: normalizedAssistantTurnMeaningDateScope ??
|
||||
normalizedPredecomposeDateScope ??
|
||||
rawDateScope ??
|
||||
normalizedFollowupDateScope;
|
||||
const followupDateScopeApplied = Boolean(
|
||||
!rawAllTimeScopeSignal &&
|
||||
!assistantTurnMeaningDateScope &&
|
||||
!predecomposeDateScope &&
|
||||
!normalizedAssistantTurnMeaningDateScope &&
|
||||
!normalizedPredecomposeDateScope &&
|
||||
!rawDateScope &&
|
||||
followupSeed.dateScope
|
||||
normalizedFollowupDateScope
|
||||
);
|
||||
const clarificationLoopSeedApplied = Boolean(
|
||||
followupSeed.loopStatus === "awaiting_clarification" && followupSeed.loopSelectedChainId
|
||||
|
||||
Reference in New Issue
Block a user