Semantic Gate: закрепить бизнес-аудит и executive summary

This commit is contained in:
2026-05-05 18:21:05 +03:00
parent 98afdd39c4
commit 9be3fb29b7
17 changed files with 604 additions and 54 deletions
@@ -426,6 +426,20 @@ describe("assistant living chat runtime adapter", () => {
userMessage: "а ты помнишь мы зеркало обсуждали?",
modeDecision: { mode: "chat", reason: "memory_recap_followup_detected" },
sessionItems: [
{
role: "assistant",
debug: {
execution_lane: "address_query",
answer_grounding_check: {
status: "grounded"
},
detected_intent: "list_documents_by_counterparty",
extracted_filters: {
organization: "ООО Альтернатива Плюс",
counterparty: "для"
}
}
},
{
role: "assistant",
debug: {
@@ -504,6 +518,81 @@ describe("assistant living chat runtime adapter", () => {
expect(executeLlmChat).not.toHaveBeenCalled();
});
it("builds executive summary from memory instead of running generic address lookup", async () => {
const executeLlmChat = vi.fn(async () => "raw-llm");
const input = buildRuntimeInput({
userMessage:
"Финально собери executive summary по всему диалогу: где подтверждено, где proxy и что смотреть руками.",
modeDecision: { mode: "chat", reason: "memory_recap_followup_detected" },
sessionItems: [
{
role: "assistant",
debug: {
execution_lane: "address_query",
answer_grounding_check: {
status: "grounded"
},
detected_intent: "list_documents_by_counterparty",
extracted_filters: {
organization: "ООО Альтернатива Плюс",
counterparty: "для"
}
}
},
{
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_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: {
period_scope: "2020",
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 руб."
}
}
}
}
}
}
}
],
executeLlmChat
});
const output = await runAssistantLivingChatRuntime(input);
expect(output.handled).toBe(true);
expect(output.chatText).toContain("Executive summary");
expect(output.chatText).toContain("Подтверждено");
expect(output.chatText).toContain("Proxy");
expect(output.chatText).toContain("3 865 501,50");
expect(output.chatText).toContain("директору смотреть руками");
expect(output.chatText).not.toContain("«для»");
expect(output.debug?.living_chat_response_source).toBe("deterministic_conversation_executive_summary_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({
@@ -50,6 +50,16 @@ describe("assistantLivingModePolicy", () => {
expect(policy.hasConversationMemoryRecallFollowupSignal("а что мы уже выяснили по этой позиции?")).toBe(true);
});
it("detects final executive summary wording as memory signal", () => {
const policy = buildPolicy();
expect(
policy.hasConversationMemoryRecallFollowupSignal(
"Финально собери executive summary по всему диалогу: где подтверждено, где proxy и что смотреть руками"
)
).toBe(true);
});
it("detects answer inspection wording for previous answer correction", () => {
const policy = buildPolicy();
@@ -104,6 +104,42 @@ describe("assistantMemoryRecapPolicy", () => {
expect(signals.contextualMemoryRecapFollowupDetected).toBe(true);
});
it("keeps final executive summary in memory lane even with strong data wording", () => {
const executivePolicy = createAssistantMemoryRecapPolicy({
hasHistoricalCapabilityFollowupSignal: () => false,
hasConversationMemoryRecallFollowupSignal: (text: unknown) => /executive summary/i.test(String(text ?? "")),
isGroundedInventoryContextDebug: () => false
});
const signals = executivePolicy.resolveRouteMemorySignals({
rawUserMessage:
"Финально собери executive summary по всему диалогу: где подтверждено, где proxy и где не хватило доказательств.",
repairedRawUserMessage: "",
effectiveAddressUserMessage: "",
repairedEffectiveAddressUserMessage: "",
dataScopeMetaQuery: false,
capabilityMetaQuery: false,
dataRetrievalSignal: false,
strongDataSignal: true,
aggregateBusinessAnalyticsSignal: false,
sessionItems: [
{
role: "assistant",
debug: {
execution_lane: "address_query",
answer_grounding_check: { status: "grounded" },
detected_intent: "receivables_confirmed_as_of_date",
extracted_filters: {
organization: "ООО Альтернатива Плюс"
}
}
}
]
});
expect(signals.contextualMemoryRecapFollowupDetected).toBe(true);
});
it("does not trigger recap from ungrounded address history", () => {
const signals = policy.resolveRouteMemorySignals({
rawUserMessage: "а ты помнишь что мы обсуждали?",
@@ -683,6 +683,45 @@ describe("assistantRoutePolicy", () => {
expect(decision.orchestrationContract?.unsupported_current_turn_family).toBe("broad_business_evaluation");
});
it("lets broad business-audit meaning override noisy capability wording", () => {
const policy = buildPolicy({
resolveMetaSignalSet: () => ({
dataScopeMetaQuery: false,
capabilityMetaQuery: true,
metaAnswerFollowupSignal: false,
answerInspectionFollowupSignal: false
}),
resolveAssistantTurnMeaning: () => ({
schema_version: "assistant_turn_meaning_v1",
asked_domain_family: "business_summary",
asked_action_family: "broad_evaluation",
explicit_intent_candidate: null,
unsupported_but_understood_family: "broad_business_evaluation",
stale_replay_forbidden: true,
reason_codes: ["broad_business_evaluation_current_turn_signal"]
})
});
const decision = policy.resolveAssistantOrchestrationDecision({
rawUserMessage:
"Собери это как нормальный бизнес-аудит: что уже можно сказать уверенно, что proxy и что директору проверить руками.",
effectiveAddressUserMessage:
"Собери это как нормальный бизнес-аудит: что уже можно сказать уверенно, что proxy и что директору проверить руками.",
followupContext: null,
llmPreDecomposeMeta: null,
useMock: false
});
expect(decision.runAddressLane).toBe(false);
expect(decision.toolGateReason).toBe("unsupported_current_turn_meaning_boundary");
expect(decision.livingReason).toBe("unsupported_current_turn_meaning_boundary");
expect(decision.orchestrationContract?.unsupported_current_turn_family).toBe("broad_business_evaluation");
expect(decision.orchestrationContract?.hard_meta_mode).toBeNull();
expect((decision.orchestrationContract as Record<string, unknown>)?.reason_codes).toContain(
"business_overview_meaning_overrides_capability_meta_noise"
);
});
it("recovers an address route from current-turn meaning when L0 resolver is noisy", () => {
const policy = buildPolicy({
resolveAddressToolGateDecision: undefined,