282 lines
9.5 KiB
TypeScript
282 lines
9.5 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
||
import {
|
||
buildAddressMemoryRecapReply,
|
||
buildSelectedObjectAnswerInspectionReply,
|
||
createAssistantMemoryRecapPolicy,
|
||
resolveAssistantLivingChatMemoryContext
|
||
} from "../src/services/assistantMemoryRecapPolicy";
|
||
|
||
const policy = createAssistantMemoryRecapPolicy({
|
||
hasHistoricalCapabilityFollowupSignal: (text: unknown) =>
|
||
/историческ|архив|раньше/i.test(String(text ?? "")),
|
||
hasConversationMemoryRecallFollowupSignal: (text: unknown) =>
|
||
/помнишь|remember/i.test(String(text ?? "")),
|
||
isGroundedInventoryContextDebug: (debug: unknown) =>
|
||
String((debug as Record<string, unknown> | null)?.detected_intent ?? "") === "inventory_on_hand_as_of_date"
|
||
});
|
||
|
||
describe("assistantMemoryRecapPolicy", () => {
|
||
it("detects contextual historical capability follow-up", () => {
|
||
const signals = policy.resolveRouteMemorySignals({
|
||
rawUserMessage: "а исторические остатки тоже можешь?",
|
||
repairedRawUserMessage: "",
|
||
effectiveAddressUserMessage: "",
|
||
repairedEffectiveAddressUserMessage: "",
|
||
dataScopeMetaQuery: false,
|
||
capabilityMetaQuery: true,
|
||
dataRetrievalSignal: false,
|
||
strongDataSignal: false,
|
||
aggregateBusinessAnalyticsSignal: false,
|
||
lastGroundedAddressDebug: {
|
||
detected_intent: "inventory_on_hand_as_of_date"
|
||
},
|
||
hasPriorAddressDebug: true
|
||
});
|
||
|
||
expect(signals.contextualHistoricalCapabilityFollowupDetected).toBe(true);
|
||
expect(signals.contextualMemoryRecapFollowupDetected).toBe(false);
|
||
});
|
||
|
||
it("detects contextual memory recap over prior address debug", () => {
|
||
const signals = policy.resolveRouteMemorySignals({
|
||
rawUserMessage: "а ты помнишь что мы обсуждали?",
|
||
repairedRawUserMessage: "",
|
||
effectiveAddressUserMessage: "",
|
||
repairedEffectiveAddressUserMessage: "",
|
||
dataScopeMetaQuery: false,
|
||
capabilityMetaQuery: false,
|
||
dataRetrievalSignal: false,
|
||
strongDataSignal: false,
|
||
aggregateBusinessAnalyticsSignal: false,
|
||
lastGroundedAddressDebug: null,
|
||
hasPriorAddressDebug: true,
|
||
sessionItems: [
|
||
{
|
||
role: "assistant",
|
||
debug: {
|
||
execution_lane: "address_query",
|
||
answer_grounding_check: {
|
||
status: "grounded"
|
||
},
|
||
detected_intent: "list_documents_by_counterparty"
|
||
}
|
||
}
|
||
]
|
||
});
|
||
|
||
expect(signals.contextualHistoricalCapabilityFollowupDetected).toBe(false);
|
||
expect(signals.contextualMemoryRecapFollowupDetected).toBe(true);
|
||
});
|
||
|
||
it("treats explicit recap wording over selected-object phrasing as memory follow-up even when data cues are present", () => {
|
||
const signals = policy.resolveRouteMemorySignals({
|
||
rawUserMessage: "а ты помнишь, что мы по этой позиции уже выяснили?",
|
||
repairedRawUserMessage: "",
|
||
effectiveAddressUserMessage: "",
|
||
repairedEffectiveAddressUserMessage: "",
|
||
dataScopeMetaQuery: false,
|
||
capabilityMetaQuery: false,
|
||
dataRetrievalSignal: true,
|
||
strongDataSignal: true,
|
||
aggregateBusinessAnalyticsSignal: false,
|
||
lastGroundedAddressDebug: null,
|
||
hasPriorAddressDebug: true,
|
||
sessionItems: [
|
||
{
|
||
role: "assistant",
|
||
debug: {
|
||
execution_lane: "address_query",
|
||
answer_grounding_check: {
|
||
status: "grounded"
|
||
},
|
||
detected_intent: "inventory_purchase_provenance_for_item",
|
||
extracted_filters: {
|
||
item: "Рабочая станция",
|
||
as_of_date: "2022-02-28"
|
||
}
|
||
}
|
||
}
|
||
]
|
||
});
|
||
|
||
expect(signals.contextualHistoricalCapabilityFollowupDetected).toBe(false);
|
||
expect(signals.contextualMemoryRecapFollowupDetected).toBe(true);
|
||
});
|
||
|
||
it("does not trigger recap from ungrounded address history", () => {
|
||
const signals = policy.resolveRouteMemorySignals({
|
||
rawUserMessage: "а ты помнишь что мы обсуждали?",
|
||
repairedRawUserMessage: "",
|
||
effectiveAddressUserMessage: "",
|
||
repairedEffectiveAddressUserMessage: "",
|
||
dataScopeMetaQuery: false,
|
||
capabilityMetaQuery: false,
|
||
dataRetrievalSignal: false,
|
||
strongDataSignal: false,
|
||
aggregateBusinessAnalyticsSignal: false,
|
||
lastGroundedAddressDebug: null,
|
||
hasPriorAddressDebug: true,
|
||
sessionItems: [
|
||
{
|
||
role: "assistant",
|
||
debug: {
|
||
execution_lane: "address_query",
|
||
detected_intent: "inventory_purchase_documents_for_item",
|
||
extracted_filters: {
|
||
item: "Рабочая станция"
|
||
}
|
||
}
|
||
}
|
||
]
|
||
});
|
||
|
||
expect(signals.contextualMemoryRecapFollowupDetected).toBe(false);
|
||
});
|
||
|
||
it("builds deterministic recap summary from recent selected-object facts", () => {
|
||
const context = resolveAssistantLivingChatMemoryContext({
|
||
modeDecisionReason: "memory_recap_followup_detected",
|
||
sessionItems: [
|
||
{
|
||
role: "assistant",
|
||
debug: {
|
||
execution_lane: "address_query",
|
||
answer_grounding_check: {
|
||
status: "grounded"
|
||
},
|
||
anchor_type: "item",
|
||
anchor_value_resolved: "Рабочая станция",
|
||
extracted_filters: {
|
||
item: "Рабочая станция",
|
||
as_of_date: "2022-02-28"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
role: "assistant",
|
||
debug: {
|
||
execution_lane: "address_query",
|
||
answer_grounding_check: {
|
||
status: "grounded"
|
||
},
|
||
detected_intent: "inventory_purchase_provenance_for_item",
|
||
extracted_filters: {
|
||
item: "Рабочая станция",
|
||
as_of_date: "2022-02-28"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
role: "assistant",
|
||
debug: {
|
||
execution_lane: "address_query",
|
||
answer_grounding_check: {
|
||
status: "grounded"
|
||
},
|
||
detected_intent: "inventory_purchase_documents_for_item",
|
||
extracted_filters: {
|
||
item: "Рабочая станция",
|
||
as_of_date: "2022-02-28"
|
||
}
|
||
}
|
||
}
|
||
]
|
||
});
|
||
|
||
const reply = buildAddressMemoryRecapReply({
|
||
organization: null,
|
||
addressDebug: context.lastMemoryAddressDebug,
|
||
sessionItems: [
|
||
{
|
||
role: "assistant",
|
||
debug: {
|
||
execution_lane: "address_query",
|
||
answer_grounding_check: {
|
||
status: "grounded"
|
||
},
|
||
detected_intent: "inventory_on_hand_as_of_date",
|
||
extracted_filters: {
|
||
organization: "ООО Альтернатива Плюс",
|
||
as_of_date: "2022-02-28"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
role: "assistant",
|
||
debug: {
|
||
execution_lane: "address_query",
|
||
answer_grounding_check: {
|
||
status: "grounded"
|
||
},
|
||
detected_intent: "inventory_purchase_provenance_for_item",
|
||
extracted_filters: {
|
||
item: "Рабочая станция",
|
||
as_of_date: "2022-02-28"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
role: "assistant",
|
||
debug: {
|
||
execution_lane: "address_query",
|
||
answer_grounding_check: {
|
||
status: "grounded"
|
||
},
|
||
detected_intent: "inventory_purchase_documents_for_item",
|
||
extracted_filters: {
|
||
item: "Рабочая станция",
|
||
as_of_date: "2022-02-28"
|
||
}
|
||
}
|
||
}
|
||
],
|
||
toNonEmptyString: (value: unknown) => {
|
||
const text = String(value ?? "").trim();
|
||
return text.length > 0 ? text : null;
|
||
}
|
||
});
|
||
|
||
expect(context.contextualMemoryRecapFollowup).toBe(true);
|
||
expect(reply).toContain("Рабочая станция");
|
||
expect(reply).toContain("мы уже выяснили");
|
||
expect(reply).toContain("разобрали, кто поставлял");
|
||
expect(reply).toContain("подняли документы закупки");
|
||
});
|
||
|
||
it("resolves grounded answer inspection from shared memory context", () => {
|
||
const context = resolveAssistantLivingChatMemoryContext({
|
||
modeDecisionReason: "answer_inspection_followup_detected",
|
||
sessionItems: [
|
||
{
|
||
role: "assistant",
|
||
debug: {
|
||
execution_lane: "address_query",
|
||
answer_grounding_check: {
|
||
status: "grounded"
|
||
},
|
||
detected_intent: "inventory_sale_trace_for_item",
|
||
extracted_filters: {
|
||
item: "Рабочая станция",
|
||
organization: "ООО Альтернатива Плюс",
|
||
as_of_date: "2016-03-31"
|
||
}
|
||
}
|
||
}
|
||
]
|
||
});
|
||
|
||
const reply = buildSelectedObjectAnswerInspectionReply({
|
||
addressDebug: context.lastAnswerInspectionAddressDebug,
|
||
toNonEmptyString: (value: unknown) => {
|
||
const text = String(value ?? "").trim();
|
||
return text.length > 0 ? text : null;
|
||
}
|
||
});
|
||
|
||
expect(context.contextualAnswerInspectionFollowup).toBe(true);
|
||
expect(reply).toContain("не контрагент");
|
||
expect(reply).toContain("Рабочая станция");
|
||
expect(reply).toContain("Покупатель");
|
||
});
|
||
});
|