АРЧ - UI и domain loop: синхронизировать локальную LLM-модель через shared config + КВИН 3
This commit is contained in:
@@ -245,6 +245,60 @@ describe("inventory selected-object follow-up", () => {
|
||||
expect(String(result?.reply_text ?? "")).toContain("ООО \\Производство мебели\\");
|
||||
});
|
||||
|
||||
it("handles selected-object colloquial supplier wording 'у кого куплено' as provenance follow-up", async () => {
|
||||
executeAddressMcpQueryMock.mockResolvedValueOnce({
|
||||
fetched_rows: 1,
|
||||
matched_rows: 1,
|
||||
raw_rows: [
|
||||
{
|
||||
Period: "2020-06-18T00:00:00Z",
|
||||
Registrator: "Поступление товаров и услуг 00000000101 от 18.06.2020 0:00:00",
|
||||
AccountDt: "41.01",
|
||||
AccountKt: "60.01",
|
||||
Amount: 498472.5,
|
||||
SubcontoDt1: "Конструкция трансформер рабочей станции 1300*900*2000",
|
||||
SubcontoDt3: "Основной склад",
|
||||
SubcontoKt1: "ООО \\Гамма-мебель\\",
|
||||
SubcontoKt2: "Договор поставки № 11 от 15.06.2020",
|
||||
Organization: "ООО \\Альтернатива Плюс\\"
|
||||
}
|
||||
],
|
||||
rows: [],
|
||||
error: null
|
||||
});
|
||||
|
||||
const service = new AddressQueryService();
|
||||
const result = await service.tryHandle(
|
||||
'По выбранному объекту "Конструкция трансформер рабочей станции 1300*900*2000": у кого куплено',
|
||||
{
|
||||
followupContext: {
|
||||
previous_intent: "inventory_on_hand_as_of_date",
|
||||
previous_filters: {
|
||||
as_of_date: "2020-06-30",
|
||||
period_from: "2020-06-01",
|
||||
period_to: "2020-06-30",
|
||||
warehouse: "Основной склад",
|
||||
organization: "ООО \\Альтернатива Плюс\\"
|
||||
},
|
||||
previous_anchor_type: "unknown",
|
||||
previous_anchor_value: null
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
expect(result?.handled).toBe(true);
|
||||
expect(result?.response_type).toBe("FACTUAL_SUMMARY");
|
||||
expect(result?.debug.detected_intent).toBe("inventory_purchase_provenance_for_item");
|
||||
expect(result?.debug.selected_recipe).toBe("address_inventory_purchase_provenance_for_item_v1");
|
||||
expect(result?.debug.extracted_filters?.item).toBe("Конструкция трансформер рабочей станции 1300*900*2000");
|
||||
expect(result?.debug.extracted_filters?.as_of_date).toBe("2020-06-30");
|
||||
expect(result?.debug.extracted_filters?.period_from).toBe("2020-06-01");
|
||||
expect(result?.debug.extracted_filters?.period_to).toBe("2020-06-30");
|
||||
expect(result?.debug.capability_id).toBe("inventory_inventory_purchase_provenance_for_item");
|
||||
expect(result?.debug.capability_route_mode).toBe("exact");
|
||||
expect(String(result?.reply_text ?? "")).toContain("ООО \\Гамма-мебель\\");
|
||||
});
|
||||
|
||||
it("handles selected-object wording 'где мы купили это' as provenance follow-up", async () => {
|
||||
executeAddressMcpQueryMock.mockResolvedValueOnce({
|
||||
fetched_rows: 1,
|
||||
|
||||
@@ -373,6 +373,112 @@ describe("assistant address follow-up carryover", () => {
|
||||
expect(normalizerService.normalize).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("keeps historical stock date window for selected-object supplier wording 'у кого куплено'", async () => {
|
||||
const calls: Array<{ message: string; options?: any }> = [];
|
||||
const rootMessage = 'какие у нас остатки на складе на июнь 2020';
|
||||
const followupMessage = 'По выбранному объекту "Конструкция трансформер рабочей станции 1300*900*2000": у кого куплено';
|
||||
|
||||
const addressQueryService = {
|
||||
tryHandle: vi.fn(async (message: string, options?: any) => {
|
||||
calls.push({ message, options });
|
||||
if (message === rootMessage) {
|
||||
return {
|
||||
handled: true,
|
||||
reply_text:
|
||||
"На 30.06.2020 на складе подтверждено 12 позиций с остатком на 755.392,33 ₽.\n1. Конструкция трансформер рабочей станции 1300*900*2000 | склад: Основной склад",
|
||||
reply_type: "factual",
|
||||
response_type: "FACTUAL_LIST",
|
||||
debug: {
|
||||
detected_mode: "address_query",
|
||||
detected_intent: "inventory_on_hand_as_of_date",
|
||||
detected_intent_confidence: "high",
|
||||
extracted_filters: {
|
||||
as_of_date: "2020-06-30",
|
||||
period_from: "2020-06-01",
|
||||
period_to: "2020-06-30",
|
||||
warehouse: "Основной склад",
|
||||
organization: "ООО \\Альтернатива Плюс\\"
|
||||
},
|
||||
selected_recipe: "address_inventory_on_hand_as_of_date_v1",
|
||||
capability_id: "confirmed_inventory_on_hand_as_of_date",
|
||||
capability_route_mode: "exact",
|
||||
anchor_type: "unknown",
|
||||
anchor_value_raw: null,
|
||||
anchor_value_resolved: null,
|
||||
reasons: ["address_action_detected", "address_entity_detected"]
|
||||
}
|
||||
};
|
||||
}
|
||||
return {
|
||||
handled: true,
|
||||
reply_text:
|
||||
"Поставщик по позиции Конструкция трансформер рабочей станции 1300*900*2000: ООО \\Гамма-мебель\\.",
|
||||
reply_type: "factual",
|
||||
response_type: "FACTUAL_SUMMARY",
|
||||
debug: {
|
||||
detected_mode: "address_query",
|
||||
detected_intent: "inventory_purchase_provenance_for_item",
|
||||
detected_intent_confidence: "medium",
|
||||
extracted_filters: {
|
||||
item: "Конструкция трансформер рабочей станции 1300*900*2000",
|
||||
as_of_date: "2020-06-30",
|
||||
period_from: "2020-06-01",
|
||||
period_to: "2020-06-30"
|
||||
},
|
||||
selected_recipe: "address_inventory_purchase_provenance_for_item_v1",
|
||||
capability_id: "inventory_inventory_purchase_provenance_for_item",
|
||||
capability_route_mode: "exact",
|
||||
reasons: ["address_action_detected", "address_entity_detected", "address_followup_context_applied"]
|
||||
},
|
||||
...(options?.followupContext ? {} : {})
|
||||
};
|
||||
})
|
||||
} as any;
|
||||
|
||||
const normalizerService = {
|
||||
normalize: vi.fn(async () => ({
|
||||
assistant_reply: "normalizer_fallback_should_not_be_used",
|
||||
reply_type: "partial_coverage",
|
||||
debug: {}
|
||||
}))
|
||||
} as any;
|
||||
|
||||
const sessions = new AssistantSessionStore();
|
||||
const service = new AssistantService(
|
||||
normalizerService,
|
||||
sessions as any,
|
||||
{} as any,
|
||||
{ persistSession: vi.fn() } as any,
|
||||
addressQueryService
|
||||
);
|
||||
|
||||
const sessionId = `asst-address-followup-u-kogo-kupleno-${Date.now()}`;
|
||||
const first = await service.handleMessage({
|
||||
session_id: sessionId,
|
||||
user_message: rootMessage,
|
||||
useMock: true
|
||||
} as any);
|
||||
expect(first.ok).toBe(true);
|
||||
expect(first.reply_type).toBe("factual");
|
||||
|
||||
const second = await service.handleMessage({
|
||||
session_id: sessionId,
|
||||
user_message: followupMessage,
|
||||
useMock: true
|
||||
} as any);
|
||||
|
||||
expect(second.ok).toBe(true);
|
||||
expect(second.reply_type).toBe("factual");
|
||||
expect(calls).toHaveLength(2);
|
||||
expect(calls[1].message).toBe(followupMessage);
|
||||
expect(calls[1].options?.followupContext?.previous_intent).toBe("inventory_on_hand_as_of_date");
|
||||
expect(calls[1].options?.followupContext?.previous_filters?.as_of_date).toBe("2020-06-30");
|
||||
expect(calls[1].options?.followupContext?.previous_filters?.period_from).toBe("2020-06-01");
|
||||
expect(calls[1].options?.followupContext?.previous_filters?.period_to).toBe("2020-06-30");
|
||||
expect(calls[1].options?.followupContext?.previous_filters?.warehouse).toBe("Основной склад");
|
||||
expect(normalizerService.normalize).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("treats typo imperative 'показывыай' as implicit continuation and switches to suggested follow-up intent", async () => {
|
||||
const calls: Array<{ message: string; options?: any }> = [];
|
||||
const firstMessage = "покажи документы по свк за 2020";
|
||||
|
||||
Reference in New Issue
Block a user