Planner Autonomy: добавить verdict выравнивания catalog chain

This commit is contained in:
2026-05-01 14:43:05 +03:00
parent ea96df9ca1
commit 417b51096e
11 changed files with 92 additions and 4 deletions
@@ -46,6 +46,7 @@ function buildAssistantMcpDiscoveryDebugAttachmentFields(input) {
const entryPoint = resolveEntryPoint(input);
const bridge = toRecordObject(entryPoint?.bridge);
const planner = toRecordObject(bridge?.planner);
const chainAlignment = toRecordObject(planner?.catalog_chain_template_alignment);
const answerDraft = toRecordObject(bridge?.answer_draft);
return {
assistant_mcp_discovery_entry_point_v1: entryPoint,
@@ -55,6 +56,8 @@ function buildAssistantMcpDiscoveryDebugAttachmentFields(input) {
mcp_discovery_bridge_status: toNonEmptyString(bridge?.bridge_status),
mcp_discovery_selected_chain_id: toNonEmptyString(planner?.selected_chain_id),
mcp_discovery_catalog_chain_template_matches: toStringArray(planner?.catalog_chain_template_matches),
mcp_discovery_catalog_chain_top_match: toNonEmptyString(chainAlignment?.top_chain_template_match),
mcp_discovery_catalog_chain_selected_matches_top: chainAlignment?.selected_chain_matches_top === true,
mcp_discovery_answer_mode: toNonEmptyString(answerDraft?.answer_mode),
mcp_discovery_business_fact_answer_allowed: bridge?.business_fact_answer_allowed === true,
mcp_discovery_user_facing_response_allowed: bridge?.user_facing_response_allowed === true,
@@ -359,6 +359,17 @@ function catalogChainTemplateMatchesForContract(input, recipe) {
aggregation_need: dataNeedGraph.aggregation_need
});
}
function catalogChainTemplateAlignmentForContract(recipe, matches) {
const selectedChainIsCatalogTemplate = recipe.chainId !== "metadata_lane_clarification";
const selectedIndex = matches.indexOf(recipe.chainId);
return {
top_chain_template_match: matches[0] ?? null,
selected_chain_template_rank: selectedIndex >= 0 ? selectedIndex + 1 : null,
selected_chain_is_catalog_template: selectedChainIsCatalogTemplate,
selected_chain_in_catalog_matches: selectedIndex >= 0,
selected_chain_matches_top: selectedIndex === 0
};
}
function routeFamilyFromThinMetadataSurfaceInput(input) {
const surface = input.metadataSurface ?? null;
const surfaceRouteFamily = routeFamilyFromMetadataSurfaceRef(surface);
@@ -974,6 +985,7 @@ function planAssistantMcpDiscovery(input) {
const dataNeedGraph = input.dataNeedGraph ?? null;
const metadataSurface = input.metadataSurface ?? null;
const catalogChainTemplateMatches = catalogChainTemplateMatchesForContract(input, recipe);
const catalogChainTemplateAlignment = catalogChainTemplateAlignmentForContract(recipe, catalogChainTemplateMatches);
const reasonCodes = [];
pushReason(reasonCodes, recipe.reason);
for (const reason of recipe.extraReasons ?? []) {
@@ -1037,6 +1049,7 @@ function planAssistantMcpDiscovery(input) {
selected_chain_id: recipe.chainId,
selected_chain_summary: recipe.chainSummary,
catalog_chain_template_matches: catalogChainTemplateMatches,
catalog_chain_template_alignment: catalogChainTemplateAlignment,
proposed_primitives: recipe.primitives,
required_axes: recipe.axes,
discovery_plan: plan,
@@ -91,6 +91,7 @@ function buildLoopState(planner, pilot, bridgeStatus) {
loop_status: loopStatusFor(bridgeStatus),
selected_chain_id: planner.selected_chain_id,
catalog_chain_template_matches: [...planner.catalog_chain_template_matches],
catalog_chain_template_alignment: planner.catalog_chain_template_alignment,
pilot_scope: pilot.pilot_scope,
asked_domain_family: planner.discovery_plan.turn_meaning_ref?.asked_domain_family ?? null,
asked_action_family: planner.discovery_plan.turn_meaning_ref?.asked_action_family ?? null,
@@ -8,6 +8,8 @@ export interface AssistantMcpDiscoveryDebugAttachmentFields {
mcp_discovery_bridge_status: string | null;
mcp_discovery_selected_chain_id: string | null;
mcp_discovery_catalog_chain_template_matches: string[];
mcp_discovery_catalog_chain_top_match: string | null;
mcp_discovery_catalog_chain_selected_matches_top: boolean;
mcp_discovery_answer_mode: string | null;
mcp_discovery_business_fact_answer_allowed: boolean;
mcp_discovery_user_facing_response_allowed: boolean;
@@ -73,6 +75,7 @@ export function buildAssistantMcpDiscoveryDebugAttachmentFields(
const entryPoint = resolveEntryPoint(input);
const bridge = toRecordObject(entryPoint?.bridge);
const planner = toRecordObject(bridge?.planner);
const chainAlignment = toRecordObject(planner?.catalog_chain_template_alignment);
const answerDraft = toRecordObject(bridge?.answer_draft);
return {
@@ -83,6 +86,8 @@ export function buildAssistantMcpDiscoveryDebugAttachmentFields(
mcp_discovery_bridge_status: toNonEmptyString(bridge?.bridge_status),
mcp_discovery_selected_chain_id: toNonEmptyString(planner?.selected_chain_id),
mcp_discovery_catalog_chain_template_matches: toStringArray(planner?.catalog_chain_template_matches),
mcp_discovery_catalog_chain_top_match: toNonEmptyString(chainAlignment?.top_chain_template_match),
mcp_discovery_catalog_chain_selected_matches_top: chainAlignment?.selected_chain_matches_top === true,
mcp_discovery_answer_mode: toNonEmptyString(answerDraft?.answer_mode),
mcp_discovery_business_fact_answer_allowed: bridge?.business_fact_answer_allowed === true,
mcp_discovery_user_facing_response_allowed: bridge?.user_facing_response_allowed === true,
@@ -38,6 +38,14 @@ export interface AssistantMcpDiscoveryMetadataSurfaceRef {
ambiguity_entity_sets: string[];
}
export interface AssistantMcpDiscoveryCatalogChainTemplateAlignment {
top_chain_template_match: AssistantMcpCatalogChainTemplateId | null;
selected_chain_template_rank: number | null;
selected_chain_is_catalog_template: boolean;
selected_chain_in_catalog_matches: boolean;
selected_chain_matches_top: boolean;
}
export type AssistantMcpDiscoveryChainId =
| "metadata_inspection"
| "catalog_drilldown"
@@ -71,6 +79,7 @@ export interface AssistantMcpDiscoveryPlannerContract {
selected_chain_id: AssistantMcpDiscoveryChainId;
selected_chain_summary: string;
catalog_chain_template_matches: AssistantMcpCatalogChainTemplateId[];
catalog_chain_template_alignment: AssistantMcpDiscoveryCatalogChainTemplateAlignment;
proposed_primitives: AssistantMcpDiscoveryPrimitive[];
required_axes: string[];
discovery_plan: AssistantMcpDiscoveryPlanContract;
@@ -555,6 +564,21 @@ function catalogChainTemplateMatchesForContract(
});
}
function catalogChainTemplateAlignmentForContract(
recipe: PlannerRecipe,
matches: AssistantMcpCatalogChainTemplateId[]
): AssistantMcpDiscoveryCatalogChainTemplateAlignment {
const selectedChainIsCatalogTemplate = recipe.chainId !== "metadata_lane_clarification";
const selectedIndex = matches.indexOf(recipe.chainId as AssistantMcpCatalogChainTemplateId);
return {
top_chain_template_match: matches[0] ?? null,
selected_chain_template_rank: selectedIndex >= 0 ? selectedIndex + 1 : null,
selected_chain_is_catalog_template: selectedChainIsCatalogTemplate,
selected_chain_in_catalog_matches: selectedIndex >= 0,
selected_chain_matches_top: selectedIndex === 0
};
}
function routeFamilyFromThinMetadataSurfaceInput(
input: AssistantMcpDiscoveryPlannerInput
): AssistantMcpDiscoveryMetadataRouteFamily | null {
@@ -1216,6 +1240,7 @@ export function planAssistantMcpDiscovery(
const dataNeedGraph = input.dataNeedGraph ?? null;
const metadataSurface = input.metadataSurface ?? null;
const catalogChainTemplateMatches = catalogChainTemplateMatchesForContract(input, recipe);
const catalogChainTemplateAlignment = catalogChainTemplateAlignmentForContract(recipe, catalogChainTemplateMatches);
const reasonCodes: string[] = [];
pushReason(reasonCodes, recipe.reason);
for (const reason of recipe.extraReasons ?? []) {
@@ -1283,6 +1308,7 @@ export function planAssistantMcpDiscovery(
selected_chain_id: recipe.chainId,
selected_chain_summary: recipe.chainSummary,
catalog_chain_template_matches: catalogChainTemplateMatches,
catalog_chain_template_alignment: catalogChainTemplateAlignment,
proposed_primitives: recipe.primitives,
required_axes: recipe.axes,
discovery_plan: plan,
@@ -46,6 +46,7 @@ export interface AssistantMcpDiscoveryLoopStateContract {
loop_status: AssistantMcpDiscoveryLoopStatus;
selected_chain_id: AssistantMcpDiscoveryChainId;
catalog_chain_template_matches: AssistantMcpDiscoveryPlannerContract["catalog_chain_template_matches"];
catalog_chain_template_alignment: AssistantMcpDiscoveryPlannerContract["catalog_chain_template_alignment"];
pilot_scope: AssistantMcpDiscoveryPilotExecutionContract["pilot_scope"];
asked_domain_family: string | null;
asked_action_family: string | null;
@@ -179,6 +180,7 @@ function buildLoopState(
loop_status: loopStatusFor(bridgeStatus),
selected_chain_id: planner.selected_chain_id,
catalog_chain_template_matches: [...planner.catalog_chain_template_matches],
catalog_chain_template_alignment: planner.catalog_chain_template_alignment,
pilot_scope: pilot.pilot_scope,
asked_domain_family: planner.discovery_plan.turn_meaning_ref?.asked_domain_family ?? null,
asked_action_family: planner.discovery_plan.turn_meaning_ref?.asked_action_family ?? null,
@@ -16,7 +16,14 @@ function entryPointContract(overrides: Record<string, unknown> = {}) {
requires_user_clarification: false,
planner: {
selected_chain_id: "value_flow_ranking",
catalog_chain_template_matches: ["value_flow_ranking", "value_flow"]
catalog_chain_template_matches: ["value_flow_ranking", "value_flow"],
catalog_chain_template_alignment: {
top_chain_template_match: "value_flow_ranking",
selected_chain_template_rank: 1,
selected_chain_is_catalog_template: true,
selected_chain_in_catalog_matches: true,
selected_chain_matches_top: true
}
},
answer_draft: {
answer_mode: "confirmed_with_bounded_inference"
@@ -43,6 +50,8 @@ describe("assistant MCP discovery debug attachment", () => {
expect(debug.mcp_discovery_bridge_status).toBe("answer_draft_ready");
expect(debug.mcp_discovery_selected_chain_id).toBe("value_flow_ranking");
expect(debug.mcp_discovery_catalog_chain_template_matches).toEqual(["value_flow_ranking", "value_flow"]);
expect(debug.mcp_discovery_catalog_chain_top_match).toBe("value_flow_ranking");
expect(debug.mcp_discovery_catalog_chain_selected_matches_top).toBe(true);
expect(debug.mcp_discovery_answer_mode).toBe("confirmed_with_bounded_inference");
expect(debug.mcp_discovery_business_fact_answer_allowed).toBe(true);
expect(debug.mcp_discovery_user_facing_response_allowed).toBe(true);
@@ -62,6 +71,8 @@ describe("assistant MCP discovery debug attachment", () => {
expect(debug.mcp_discovery_bridge_status).toBeNull();
expect(debug.mcp_discovery_selected_chain_id).toBeNull();
expect(debug.mcp_discovery_catalog_chain_template_matches).toEqual([]);
expect(debug.mcp_discovery_catalog_chain_top_match).toBeNull();
expect(debug.mcp_discovery_catalog_chain_selected_matches_top).toBe(false);
expect(debug.mcp_discovery_answer_mode).toBeNull();
expect(debug.mcp_discovery_business_fact_answer_allowed).toBe(false);
expect(debug.mcp_discovery_user_facing_response_allowed).toBe(false);
@@ -48,6 +48,13 @@ describe("assistant MCP discovery planner", () => {
expect(result.discovery_plan.answer_may_use_raw_model_claims).toBe(false);
expect(result.data_need_graph?.business_fact_family).toBe("value_flow");
expect(result.catalog_chain_template_matches[0]).toBe("value_flow");
expect(result.catalog_chain_template_alignment).toMatchObject({
top_chain_template_match: "value_flow",
selected_chain_template_rank: 1,
selected_chain_is_catalog_template: true,
selected_chain_in_catalog_matches: true,
selected_chain_matches_top: true
});
expect(result.discovery_plan.execution_budget.max_probe_count).toBe(30);
expect(result.reason_codes).toContain("planner_enabled_chunked_coverage_probe_budget");
expect(result.reason_codes).toContain("planner_consumed_data_need_graph_v1");
@@ -150,6 +157,7 @@ describe("assistant MCP discovery planner", () => {
expect(result.reason_codes).toContain("planner_instantiated_catalog_chain_template_document_evidence");
expect(result.reason_codes).toContain("planner_catalog_chain_template_search_top_document_evidence");
expect(result.catalog_chain_template_matches[0]).toBe("document_evidence");
expect(result.catalog_chain_template_alignment.selected_chain_matches_top).toBe(true);
expect(result.reason_codes).not.toContain("planner_fell_back_to_recipe_primitives_after_empty_catalog_search");
});
@@ -564,6 +572,7 @@ describe("assistant MCP discovery planner", () => {
expect(result.reason_codes).toContain("planner_selected_bidirectional_value_flow_comparison_from_data_need_graph");
expect(result.reason_codes).toContain("planner_instantiated_catalog_chain_template_value_flow_comparison");
expect(result.catalog_chain_template_matches[0]).toBe("value_flow_comparison");
expect(result.catalog_chain_template_alignment.selected_chain_matches_top).toBe(true);
});
it("does not collapse a ranking-shaped value graph into entity-resolution just because no subject is preselected", () => {
@@ -635,6 +644,7 @@ describe("assistant MCP discovery planner", () => {
expect(result.reason_codes).toContain("planner_selected_top_ranked_value_flow_from_data_need_graph");
expect(result.reason_codes).toContain("planner_instantiated_catalog_chain_template_value_flow_ranking");
expect(result.catalog_chain_template_matches[0]).toBe("value_flow_ranking");
expect(result.catalog_chain_template_alignment.selected_chain_matches_top).toBe(true);
});
it("does not collapse incoming-vs-outgoing comparison into entity-resolution when no counterparty is preselected", () => {
@@ -942,6 +952,12 @@ describe("assistant MCP discovery planner", () => {
expect(result.planner_status).toBe("needs_clarification");
expect(result.discovery_plan.plan_status).toBe("needs_clarification");
expect(result.catalog_chain_template_matches).toEqual([]);
expect(result.catalog_chain_template_alignment).toMatchObject({
top_chain_template_match: null,
selected_chain_template_rank: null,
selected_chain_in_catalog_matches: false,
selected_chain_matches_top: false
});
expect(result.reason_codes).toContain("planner_needs_more_user_or_scope_context");
});
@@ -148,6 +148,7 @@ describe("assistant MCP discovery runtime bridge", () => {
expect(result.loop_state.pending_axes).toContain("organization");
expect(result.loop_state.provided_axes).toContain("aggregate_axis");
expect(result.loop_state.catalog_chain_template_matches[0]).toBe("value_flow_ranking");
expect(result.loop_state.catalog_chain_template_alignment.selected_chain_matches_top).toBe(true);
expect(result.reason_codes).toContain("runtime_bridge_loop_state_awaiting_clarification");
});