ARCH: замкнуть multi-hop open total clarification loop
This commit is contained in:
@@ -296,6 +296,51 @@ describe("assistant MCP discovery answer adapter", () => {
|
||||
expect(draft.next_step_line).toContain("организац");
|
||||
});
|
||||
|
||||
it("asks for both organization and period when an open total still misses both axes", async () => {
|
||||
const planner = planAssistantMcpDiscovery({
|
||||
dataNeedGraph: {
|
||||
schema_version: "assistant_data_need_graph_v1",
|
||||
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
|
||||
subject_candidates: [],
|
||||
business_fact_family: "value_flow",
|
||||
action_family: "turnover",
|
||||
aggregation_need: null,
|
||||
time_scope_need: "period_required",
|
||||
comparison_need: null,
|
||||
ranking_need: null,
|
||||
proof_expectation: "clarification_required",
|
||||
clarification_gaps: ["organization", "period"],
|
||||
decomposition_candidates: ["collect_scoped_movements", "probe_coverage"],
|
||||
forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"],
|
||||
reason_codes: [
|
||||
"data_need_graph_built",
|
||||
"data_need_graph_open_scope_total_without_subject",
|
||||
"data_need_graph_open_scope_total_needs_organization",
|
||||
"data_need_graph_has_clarification_gaps"
|
||||
]
|
||||
},
|
||||
turnMeaning: {
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "turnover"
|
||||
}
|
||||
});
|
||||
const pilot = await executeAssistantMcpDiscoveryPilot(planner, buildDeps([]));
|
||||
|
||||
const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot);
|
||||
const periodToken = "\u043f\u0435\u0440\u0438\u043e\u0434";
|
||||
const organizationToken = "\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446";
|
||||
const counterpartyToken = "\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442";
|
||||
|
||||
expect(draft.answer_mode).toBe("needs_clarification");
|
||||
expect(draft.headline).toContain(periodToken);
|
||||
expect(draft.headline).toContain(organizationToken);
|
||||
expect(draft.next_step_line).toContain(periodToken);
|
||||
expect(draft.next_step_line).toContain(organizationToken);
|
||||
expect(draft.next_step_line).not.toContain(counterpartyToken);
|
||||
expect(draft.unknown_lines).toEqual([]);
|
||||
expect(draft.limitation_lines).toEqual([]);
|
||||
});
|
||||
|
||||
it("asks for organization rather than counterparty on open bidirectional comparison when only the period is known", async () => {
|
||||
const planner = planAssistantMcpDiscovery({
|
||||
dataNeedGraph: {
|
||||
|
||||
@@ -434,4 +434,34 @@ describe("assistant MCP discovery response candidate", () => {
|
||||
);
|
||||
expect(candidate.reply_text).not.toContain("Requested period hit the MCP row limit");
|
||||
});
|
||||
|
||||
it("filters MCP discovery scope-mechanics from clarification unknown and limitation blocks", () => {
|
||||
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
|
||||
entryPoint({
|
||||
bridge: {
|
||||
bridge_status: "needs_clarification",
|
||||
user_facing_response_allowed: true,
|
||||
business_fact_answer_allowed: false,
|
||||
requires_user_clarification: true,
|
||||
answer_draft: {
|
||||
answer_mode: "needs_clarification",
|
||||
headline:
|
||||
"Могу посчитать общий денежный поток в проверяемом окне, но для проверяемого поиска в 1С нужно проверяемый период и организацию.",
|
||||
confirmed_lines: [],
|
||||
inference_lines: [],
|
||||
unknown_lines: ["MCP discovery pilot needs more scope before execution"],
|
||||
limitation_lines: ["MCP discovery pilot needs more scope before execution"],
|
||||
next_step_line:
|
||||
"Уточните период и организацию, и я продолжу поиск по денежному потоку в 1С."
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
expect(candidate.reply_text).toContain("Уточните период и организацию");
|
||||
expect(candidate.reply_text).not.toContain("MCP discovery");
|
||||
expect(candidate.reply_text).not.toContain("needs more scope before execution");
|
||||
expect(candidate.reply_text).not.toContain("Что не подтверждено:");
|
||||
expect(candidate.reply_text).not.toContain("Ограничения проверки:");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -408,6 +408,87 @@ describe("assistant MCP discovery runtime entry point", () => {
|
||||
expect(result.bridge?.loop_state.pending_axes).toEqual([]);
|
||||
});
|
||||
|
||||
it("keeps the same open total loop after organization-only clarification when period is still missing", async () => {
|
||||
const periodToken = "\u043f\u0435\u0440\u0438\u043e\u0434";
|
||||
const organizationToken = "\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446";
|
||||
const result = await runAssistantMcpDiscoveryRuntimeEntryPoint({
|
||||
userMessage: "по ООО Альтернатива Плюс",
|
||||
predecomposeContract: {
|
||||
entities: { organization: "ООО Альтернатива Плюс" }
|
||||
},
|
||||
followupContext: {
|
||||
previous_discovery_loop_status: "awaiting_clarification",
|
||||
previous_discovery_loop_selected_chain_id: "value_flow",
|
||||
previous_discovery_loop_pending_axes: ["organization", "period"],
|
||||
previous_discovery_loop_provided_axes: ["amount", "coverage_target"],
|
||||
previous_discovery_loop_asked_domain_family: "counterparty_value",
|
||||
previous_discovery_loop_asked_action_family: "turnover",
|
||||
previous_discovery_loop_unsupported_family: "counterparty_value_or_turnover"
|
||||
},
|
||||
deps: buildDeps([])
|
||||
});
|
||||
|
||||
expect(result.entry_status).toBe("bridge_executed");
|
||||
expect(result.turn_input.turn_meaning_ref).toMatchObject({
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "turnover",
|
||||
explicit_organization_scope: "ООО Альтернатива Плюс"
|
||||
});
|
||||
expect(result.turn_input.turn_meaning_ref?.explicit_date_scope).toBeUndefined();
|
||||
expect(result.bridge?.bridge_status).toBe("needs_clarification");
|
||||
expect(result.bridge?.planner.selected_chain_id).toBe("value_flow");
|
||||
expect(result.bridge?.loop_state).toMatchObject({
|
||||
loop_status: "awaiting_clarification",
|
||||
selected_chain_id: "value_flow",
|
||||
explicit_organization_scope: "ООО Альтернатива Плюс",
|
||||
explicit_date_scope: null
|
||||
});
|
||||
expect(result.bridge?.loop_state.pending_axes).toContain("period");
|
||||
expect(result.bridge?.loop_state.pending_axes).not.toContain("organization");
|
||||
expect(result.bridge?.answer_draft.next_step_line).toContain(periodToken);
|
||||
expect(result.bridge?.answer_draft.next_step_line).not.toContain(organizationToken);
|
||||
});
|
||||
|
||||
it("completes the same open total loop after the second clarification provides the period", async () => {
|
||||
const result = await runAssistantMcpDiscoveryRuntimeEntryPoint({
|
||||
userMessage: "Р·Р° 2020 РіРѕРґ",
|
||||
followupContext: {
|
||||
previous_discovery_loop_status: "awaiting_clarification",
|
||||
previous_discovery_loop_selected_chain_id: "value_flow",
|
||||
previous_discovery_loop_pending_axes: ["period"],
|
||||
previous_discovery_loop_provided_axes: ["amount", "coverage_target", "organization"],
|
||||
previous_discovery_loop_asked_domain_family: "counterparty_value",
|
||||
previous_discovery_loop_asked_action_family: "turnover",
|
||||
previous_discovery_loop_unsupported_family: "counterparty_value_or_turnover",
|
||||
previous_filters: {
|
||||
organization: "ООО Альтернатива Плюс"
|
||||
}
|
||||
},
|
||||
deps: buildDeps([
|
||||
{ Period: "2020-01-10T00:00:00", Amount: 1200, Counterparty: "РЎР’Рљ-Рђ" },
|
||||
{ Period: "2020-03-11T00:00:00", Amount: 800, Counterparty: "РЎР’Рљ-Р‘" },
|
||||
{ Period: "2020-05-12T00:00:00", Amount: 900, Counterparty: "РЎР’Рљ-Рђ" }
|
||||
])
|
||||
});
|
||||
|
||||
expect(result.entry_status).toBe("bridge_executed");
|
||||
expect(result.turn_input.turn_meaning_ref).toMatchObject({
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "turnover",
|
||||
explicit_organization_scope: "ООО Альтернатива Плюс",
|
||||
explicit_date_scope: "2020"
|
||||
});
|
||||
expect(result.bridge?.bridge_status).toBe("answer_draft_ready");
|
||||
expect(result.bridge?.planner.selected_chain_id).toBe("value_flow");
|
||||
expect(result.bridge?.pilot.derived_value_flow).toMatchObject({
|
||||
counterparty: null,
|
||||
period_scope: "2020",
|
||||
total_amount: 2900
|
||||
});
|
||||
expect(result.bridge?.loop_state.loop_status).toBe("ready_for_next_hop");
|
||||
expect(result.bridge?.loop_state.pending_axes).toEqual([]);
|
||||
});
|
||||
|
||||
it.skip("keeps mirrored predecompose organization and counterparty out of the subject lane for open comparison (utf8-safe)", async () => {
|
||||
const orgName = "\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441";
|
||||
const result = await runAssistantMcpDiscoveryRuntimeEntryPoint({
|
||||
|
||||
@@ -1678,6 +1678,43 @@ describe("assistant MCP discovery turn input adapter", () => {
|
||||
expect(result.data_need_graph?.clarification_gaps).toEqual(["period"]);
|
||||
});
|
||||
|
||||
it("does not keep an implicit today date while an open total clarification loop still needs period", () => {
|
||||
const todayIso = new Date().toISOString().slice(0, 10);
|
||||
const orgName = "ООО Альтернатива Плюс";
|
||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||
userMessage: "по ООО Альтернатива Плюс",
|
||||
assistantTurnMeaning: {
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "turnover",
|
||||
explicit_date_scope: todayIso,
|
||||
explicit_intent_candidate: "customer_revenue_and_payments"
|
||||
},
|
||||
predecomposeContract: {
|
||||
entities: { organization: orgName }
|
||||
},
|
||||
followupContext: {
|
||||
previous_discovery_loop_status: "awaiting_clarification",
|
||||
previous_discovery_loop_selected_chain_id: "value_flow",
|
||||
previous_discovery_loop_pending_axes: ["organization", "period"],
|
||||
previous_discovery_loop_provided_axes: ["amount", "coverage_target"],
|
||||
previous_discovery_loop_asked_domain_family: "counterparty_value",
|
||||
previous_discovery_loop_asked_action_family: "turnover",
|
||||
previous_discovery_loop_unsupported_family: "counterparty_value_or_turnover",
|
||||
previous_filters: {
|
||||
period_to: todayIso
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.turn_meaning_ref).toMatchObject({
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "turnover",
|
||||
explicit_organization_scope: orgName
|
||||
});
|
||||
expect(result.turn_meaning_ref?.explicit_date_scope).toBeUndefined();
|
||||
expect(result.data_need_graph?.clarification_gaps).toEqual(["period"]);
|
||||
});
|
||||
|
||||
it("resolves metadata lane choice from saved loop state even without a previous pilot scope", () => {
|
||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||
userMessage: "по движениям",
|
||||
|
||||
Reference in New Issue
Block a user