ARCH: восстановить память discovery-ответов в living chat

This commit is contained in:
2026-04-21 08:56:38 +03:00
parent cd3315e06d
commit b542b65b81
9 changed files with 719 additions and 24 deletions
@@ -108,11 +108,45 @@ describe("assistantContinuityPolicy organization authority", () => {
expect(facts).toEqual({
item: "Рабочая станция",
counterparty: null,
organization: 'ООО "Альтернатива Плюс"',
scopedDate: "31.03.2020"
});
});
it("reads counterparty, organization and period from grounded MCP discovery fallback", () => {
const facts = resolveAddressDebugContextFacts({
execution_lane: "living_chat",
mcp_discovery_response_applied: true,
assistant_active_organization: "ООО Альтернатива Плюс",
assistant_mcp_discovery_entry_point_v1: {
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
entry_status: "bridge_executed",
turn_input: {
turn_meaning_ref: {
explicit_entity_candidates: ["Группа СВК"],
explicit_organization_scope: "ООО Альтернатива Плюс",
explicit_date_scope: "2020"
}
},
bridge: {
bridge_status: "answer_draft_ready",
business_fact_answer_allowed: true,
answer_draft: {
answer_mode: "confirmed_with_bounded_inference"
}
}
}
});
expect(facts).toEqual({
item: null,
counterparty: "Группа СВК",
organization: "ООО Альтернатива Плюс",
scopedDate: "2020"
});
});
it("resolves navigation session context through one shared helper", () => {
const state = resolveNavigationSessionContextState({
session_context: {
@@ -366,6 +366,56 @@ describe("assistant living chat runtime adapter", () => {
expect(executeLlmChat).not.toHaveBeenCalled();
});
it("builds deterministic memory recap for prior grounded MCP discovery counterparty context", async () => {
const executeLlmChat = vi.fn(async () => "raw-llm");
const input = buildRuntimeInput({
userMessage: "а ты помнишь, что мы выяснили по свк?",
modeDecision: { mode: "chat", reason: "memory_recap_followup_detected" },
sessionItems: [
{
role: "assistant",
debug: {
execution_lane: "living_chat",
mcp_discovery_response_applied: true,
assistant_mcp_discovery_entry_point_v1: {
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
entry_status: "bridge_executed",
turn_input: {
turn_meaning_ref: {
explicit_entity_candidates: ["Группа СВК"],
explicit_organization_scope: "ООО Альтернатива Плюс",
explicit_date_scope: "2020"
}
},
bridge: {
bridge_status: "answer_draft_ready",
business_fact_answer_allowed: true,
answer_draft: {
answer_mode: "confirmed_with_bounded_inference"
},
pilot: {
pilot_scope: "counterparty_supplier_payout_query_movements_v1",
derived_value_flow: {
total_amount_human_ru: "43 763 351,53 руб."
}
}
}
}
}
}
],
executeLlmChat
});
const output = await runAssistantLivingChatRuntime(input);
expect(output.handled).toBe(true);
expect(output.chatText).toContain("Группа СВК");
expect(output.chatText).toContain("43 763 351,53 руб.");
expect(output.debug?.living_chat_response_source).toBe("deterministic_memory_recap_contract");
expect(executeLlmChat).not.toHaveBeenCalled();
});
it("uses continuity-backed active organization for organization-fact boundary even when session scope is empty", async () => {
const executeLlmChat = vi.fn(async () => "raw-llm");
const input = buildRuntimeInput({
@@ -434,4 +484,50 @@ describe("assistant living chat runtime adapter", () => {
expect(output.debug?.living_chat_response_source).toBe("deterministic_answer_inspection_contract");
expect(executeLlmChat).not.toHaveBeenCalled();
});
it("builds deterministic answer inspection reply over grounded MCP discovery net answer", async () => {
const executeLlmChat = vi.fn(async () => "raw-llm");
const input = buildRuntimeInput({
userMessage: "что ты имел в виду под нетто по свк?",
modeDecision: { mode: "chat", reason: "answer_inspection_followup_detected" },
sessionItems: [
{
role: "assistant",
debug: {
execution_lane: "living_chat",
mcp_discovery_response_applied: true,
assistant_mcp_discovery_entry_point_v1: {
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
entry_status: "bridge_executed",
turn_input: {
turn_meaning_ref: {
explicit_entity_candidates: ["Группа СВК"],
explicit_date_scope: "2020"
}
},
bridge: {
bridge_status: "answer_draft_ready",
business_fact_answer_allowed: true,
answer_draft: {
answer_mode: "confirmed_with_bounded_inference"
},
pilot: {
pilot_scope: "counterparty_bidirectional_value_flow_query_movements_v1"
}
}
}
}
}
],
executeLlmChat
});
const output = await runAssistantLivingChatRuntime(input);
expect(output.handled).toBe(true);
expect(output.chatText).toContain("Группа СВК");
expect(output.chatText).toContain("Нетто");
expect(output.debug?.living_chat_response_source).toBe("deterministic_answer_inspection_contract");
expect(executeLlmChat).not.toHaveBeenCalled();
});
});
@@ -133,6 +133,50 @@ describe("assistantMemoryRecapPolicy", () => {
expect(signals.contextualMemoryRecapFollowupDetected).toBe(false);
});
it("detects contextual memory recap over prior grounded MCP discovery answer", () => {
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: "living_chat",
mcp_discovery_response_applied: true,
assistant_mcp_discovery_entry_point_v1: {
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
entry_status: "bridge_executed",
turn_input: {
turn_meaning_ref: {
explicit_entity_candidates: ["Группа СВК"],
explicit_date_scope: "2020"
}
},
bridge: {
bridge_status: "answer_draft_ready",
business_fact_answer_allowed: true,
answer_draft: {
answer_mode: "confirmed_with_bounded_inference"
}
}
}
}
}
]
});
expect(signals.contextualMemoryRecapFollowupDetected).toBe(true);
});
it("builds deterministic recap summary from recent selected-object facts", () => {
const context = resolveAssistantLivingChatMemoryContext({
modeDecisionReason: "memory_recap_followup_detected",
@@ -278,4 +322,114 @@ describe("assistantMemoryRecapPolicy", () => {
expect(reply).toContain("Рабочая станция");
expect(reply).toContain("Покупатель");
});
it("builds deterministic recap summary from grounded MCP discovery counterparty context", () => {
const sessionItems = [
{
role: "assistant",
debug: {
execution_lane: "living_chat",
mcp_discovery_response_applied: true,
assistant_mcp_discovery_entry_point_v1: {
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
entry_status: "bridge_executed",
turn_input: {
turn_meaning_ref: {
explicit_entity_candidates: ["Группа СВК"],
explicit_organization_scope: "ООО Альтернатива Плюс",
explicit_date_scope: "2020"
}
},
bridge: {
bridge_status: "answer_draft_ready",
business_fact_answer_allowed: true,
answer_draft: {
answer_mode: "confirmed_with_bounded_inference"
},
pilot: {
pilot_scope: "counterparty_bidirectional_value_flow_query_movements_v1",
derived_bidirectional_value_flow: {
net_amount_human_ru: "3 865 501,50 руб.",
incoming_customer_revenue: {
total_amount_human_ru: "47 628 853,03 руб."
},
outgoing_supplier_payout: {
total_amount_human_ru: "43 763 351,53 руб."
}
}
}
}
}
}
}
];
const context = resolveAssistantLivingChatMemoryContext({
modeDecisionReason: "memory_recap_followup_detected",
sessionItems
});
const reply = buildAddressMemoryRecapReply({
organization: null,
addressDebug: context.lastMemoryAddressDebug,
sessionItems,
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("47 628 853,03 руб.");
expect(reply).toContain("43 763 351,53 руб.");
});
it("builds grounded answer inspection reply for MCP discovery net answer", () => {
const context = resolveAssistantLivingChatMemoryContext({
modeDecisionReason: "answer_inspection_followup_detected",
sessionItems: [
{
role: "assistant",
debug: {
execution_lane: "living_chat",
mcp_discovery_response_applied: true,
assistant_mcp_discovery_entry_point_v1: {
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
entry_status: "bridge_executed",
turn_input: {
turn_meaning_ref: {
explicit_entity_candidates: ["Группа СВК"],
explicit_date_scope: "2020"
}
},
bridge: {
bridge_status: "answer_draft_ready",
business_fact_answer_allowed: true,
answer_draft: {
answer_mode: "confirmed_with_bounded_inference"
},
pilot: {
pilot_scope: "counterparty_bidirectional_value_flow_query_movements_v1"
}
}
}
}
}
]
});
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("проверенному периоду");
});
});