АРЧ АП11 - Исправить маршрутизацию selected-object и явных root-вопросов после складового контекста

This commit is contained in:
2026-04-16 00:12:39 +03:00
parent 918feaf06e
commit a493e2fd69
13 changed files with 191 additions and 16 deletions
@@ -62,4 +62,53 @@ describe("inventory root frame regressions", () => {
result?.intent.reasons.includes("inventory_selected_object_provenance_signal_detected")
).toBe(true);
});
it("does not restore inventory root when the follow-up is an explicit receivables root query", () => {
const result = runAddressDecomposeStage("покажи кто нам должен денег на май 2017", {
previous_intent: "inventory_sale_trace_for_item",
previous_filters: {
item: "Четки Пост (84*117)",
as_of_date: "2020-05-31",
period_from: "2020-05-01",
period_to: "2020-05-31"
},
previous_anchor_type: "item",
previous_anchor_value: "Четки Пост (84*117)",
root_intent: "inventory_on_hand_as_of_date",
root_filters: {
period_from: "2020-05-01",
period_to: "2020-05-31",
as_of_date: "2020-05-31"
},
root_anchor_type: "unknown",
root_anchor_value: null,
current_frame_kind: "inventory_drilldown"
});
expect(result).not.toBeNull();
expect(result?.intent.intent).toBe("receivables_confirmed_as_of_date");
expect(result?.intent.reasons).not.toContain("intent_restored_to_inventory_root_frame");
expect(result?.filters.extracted_filters.as_of_date).toBe("2017-05-31");
});
it("keeps selected-object loose documents wording in the inventory document contour", () => {
const result = runAddressDecomposeStage(
'По выбранному объекту "Четки Пост (84*117)": меня четки интересуют покажи документы все по ним и все операции',
{
previous_intent: "inventory_on_hand_as_of_date",
previous_filters: {
as_of_date: "2020-05-31",
period_from: "2020-05-01",
period_to: "2020-05-31"
},
previous_anchor_type: "unknown",
previous_anchor_value: null
}
);
expect(result).not.toBeNull();
expect(result?.intent.intent).toBe("inventory_purchase_documents_for_item");
expect(result?.intent.intent).not.toBe("bank_operations_by_counterparty");
expect(result?.filters.extracted_filters.item).toBe("Четки Пост (84*117)");
});
});
@@ -75,6 +75,40 @@ describe("inventory sale trace selected-object regressions", () => {
expect(query).not.toContain('Номенклатура.Наименование = "Кромка"');
});
it("routes selected-object delivery wording 'кому мы это поставили' into sale trace", async () => {
executeAddressMcpQueryMock.mockResolvedValueOnce({
fetched_rows: 1,
matched_rows: 1,
raw_rows: [saleRow],
rows: [],
error: null
});
const service = new AddressQueryService();
const result = await service.tryHandle(
'По выбранному объекту "Кромка с клеем 33 дуб ниагара 137 м": кому мы это поставили',
{
followupContext: {
previous_intent: "inventory_on_hand_as_of_date",
previous_filters: {
as_of_date: "2021-03-31",
period_from: "2021-03-01",
period_to: "2021-03-31",
organization: "ООО \\Альтернатива Плюс\\"
},
previous_anchor_type: "unknown",
previous_anchor_value: null
}
}
);
expect(result?.handled).toBe(true);
expect(result?.response_type).toBe("FACTUAL_LIST");
expect(result?.debug.detected_intent).toBe("inventory_sale_trace_for_item");
expect(result?.debug.extracted_filters?.item).toBe("Кромка с клеем 33 дуб ниагара 137 м");
expect(String(result?.reply_text ?? "")).toContain("ООО \\Покупатель\\");
});
it("keeps the full selected item for canonical selected-object buyer wording", async () => {
executeAddressMcpQueryMock.mockResolvedValueOnce({
fetched_rows: 1,
@@ -314,4 +314,67 @@ describe("assistant address orchestration runtime adapter", () => {
})
);
});
it("prefers raw selected-object delivery wording over generic canonical drift intent", async () => {
const resolveAddressFollowupCarryoverContext = vi.fn(() => ({
followupContext: {
previous_intent: "inventory_on_hand_as_of_date",
previous_filters: {
as_of_date: "2021-03-31",
period_from: "2021-03-01",
period_to: "2021-03-31"
}
}
}));
const resolveAssistantOrchestrationDecision = vi.fn(() => ({
runAddressLane: true,
livingMode: "address_data",
livingReason: "address_lane_triggered",
toolGateDecision: "run_address_lane",
toolGateReason: "address_mode_classifier_detected",
orchestrationContract: { schema_version: "assistant_orchestration_contract_v1" }
}));
const buildAddressLlmPredecomposeContractV1 = vi.fn(({ sourceMessage, canonicalMessage }: { sourceMessage: string; canonicalMessage: string }) => ({
schema_version: "address_llm_predecompose_contract_v1",
source_message: sourceMessage,
canonical_message: canonicalMessage,
mode: "address_query",
intent: "unknown"
}));
const rawMessage = 'По выбранному объекту "Кромка с клеем 33 дуб ниагара 137 м": кому мы это поставили';
const output = await buildAssistantAddressOrchestrationRuntime(
buildInput({
userMessage: rawMessage,
runAddressLlmPreDecompose: vi.fn(async () => ({
attempted: true,
applied: true,
effectiveMessage: "Покажи взаиморасчеты с контрагентом по выбранной позиции",
reason: "normalized_fragment_applied",
predecomposeContract: {
mode: "address_query",
intent: "bank_operations_by_counterparty",
semantics: {
selected_object_scope_detected: true
}
}
})),
buildAddressLlmPredecomposeContractV1,
resolveAddressFollowupCarryoverContext,
resolveAssistantOrchestrationDecision
})
);
expect(output.addressInputMessage).toBe(rawMessage);
expect(output.addressPreDecompose.applied).toBe(false);
expect(output.addressPreDecompose.reason).toBe("followup_raw_message_preferred_over_llm_rewrite");
expect(resolveAddressFollowupCarryoverContext).toHaveBeenCalledTimes(2);
expect(resolveAssistantOrchestrationDecision).toHaveBeenCalledWith(
expect.objectContaining({
rawUserMessage: rawMessage,
effectiveAddressUserMessage: rawMessage
})
);
});
});