NODEDC_1C/llm_normalizer/backend/tests/assistantLivingRouter.test.ts

1438 lines
62 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { describe, expect, it } from "vitest";
import { resolveAssistantOrchestrationDecision, resolveLivingAssistantModeDecision } from "../src/services/assistantService";
import {
buildAddressLlmPredecomposeContractV1,
buildAddressSemanticExtractionContractV1
} from "../src/services/address_runtime/predecomposeContract";
describe("assistant living router mode decision", () => {
it("returns address_data when address lane already triggered", () => {
const decision = resolveLivingAssistantModeDecision({
userMessage: "давай",
addressLaneTriggered: true,
useMock: false,
predecomposeMode: "address_query",
predecomposeModeConfidence: "high"
});
expect(decision.mode).toBe("address_data");
expect(decision.reason).toBe("address_lane_triggered");
});
it("keeps deep pipeline in mock mode to avoid test-env network calls", () => {
const decision = resolveLivingAssistantModeDecision({
userMessage: "привет",
addressLaneTriggered: false,
useMock: true,
predecomposeMode: "unsupported",
predecomposeModeConfidence: "low"
});
expect(decision.mode).toBe("deep_analysis");
expect(decision.reason).toBe("mock_mode_keeps_deep_pipeline");
});
it("routes casual non-data phrase to chat mode", () => {
const decision = resolveLivingAssistantModeDecision({
userMessage: "привет, как дела?",
addressLaneTriggered: false,
useMock: false,
predecomposeMode: "unsupported",
predecomposeModeConfidence: "low"
});
expect(decision.mode).toBe("chat");
});
it("keeps deep mode for strong data signal", () => {
const decision = resolveLivingAssistantModeDecision({
userMessage: "покажи документы по свк за 2020",
addressLaneTriggered: false,
useMock: false,
predecomposeMode: "unsupported",
predecomposeModeConfidence: "low"
});
expect(decision.mode).toBe("deep_analysis");
expect(decision.reason).toBe("strong_data_signal_detected");
});
it("keeps deep mode for accumulated advances query even when predecompose mode is unsupported", () => {
const decision = resolveLivingAssistantModeDecision({
userMessage: "Где у нас накопились авансы к отгрузкам, которые уже давно пора закрыть?",
addressLaneTriggered: false,
useMock: false,
predecomposeMode: "unsupported",
predecomposeModeConfidence: "low"
});
expect(decision.mode).toBe("deep_analysis");
expect(decision.reason).toBe("strong_data_signal_detected");
});
it("routes short unsupported predecompose prompts to deep fallback instead of chat", () => {
const decision = resolveLivingAssistantModeDecision({
userMessage: "без воды?",
addressLaneTriggered: false,
useMock: false,
predecomposeMode: "unsupported",
predecomposeModeConfidence: "low"
});
expect(decision.mode).toBe("deep_analysis");
expect(decision.reason).toBe("predecompose_unsupported_mode_fallback_to_deep");
});
it("routes ultra-short deictic follow-up ('тут?') to chat mode", () => {
const decision = resolveLivingAssistantModeDecision({
userMessage: "тут?",
addressLaneTriggered: false,
useMock: false,
predecomposeMode: "unsupported",
predecomposeModeConfidence: "low"
});
expect(decision.mode).toBe("chat");
expect(decision.reason).toBe("living_chat_signal_detected");
});
it("routes capability question to chat even when phrase contains 1С", () => {
const decision = resolveLivingAssistantModeDecision({
userMessage: "и 1с можешь настроить?",
addressLaneTriggered: false,
useMock: false,
predecomposeMode: "unsupported",
predecomposeModeConfidence: "low"
});
expect(decision.mode).toBe("chat");
expect(decision.reason).toBe("assistant_capability_query_detected");
});
it("routes capability question 'ok - what can you do in 1c' to chat", () => {
const decision = resolveLivingAssistantModeDecision({
userMessage: "\u043e\u043a - \u0447\u0442\u043e \u043c\u043e\u0436\u0435\u0448\u044c \u043f\u043e 1\u0441",
addressLaneTriggered: false,
useMock: false,
predecomposeMode: "unsupported",
predecomposeModeConfidence: "low"
});
expect(decision.mode).toBe("chat");
expect(decision.reason).toBe("assistant_capability_query_detected");
});
it("routes feature-capability wording to chat", () => {
const decision = resolveLivingAssistantModeDecision({
userMessage: "а какие фичи по работе с 1с у тебя отработаны максимально?",
addressLaneTriggered: false,
useMock: false,
predecomposeMode: "unsupported",
predecomposeModeConfidence: "low"
});
expect(decision.mode).toBe("chat");
expect(decision.reason).toBe("assistant_capability_query_detected");
});
it("routes data-scope question to chat instead of address lane", () => {
const decision = resolveLivingAssistantModeDecision({
userMessage: "по какой компании мы можем работать?",
addressLaneTriggered: false,
useMock: false,
predecomposeMode: "unsupported",
predecomposeModeConfidence: "low"
});
expect(decision.mode).toBe("chat");
expect(decision.reason).toBe("assistant_data_scope_query_detected");
});
it("routes 'whose base is this' style question to chat", () => {
const decision = resolveLivingAssistantModeDecision({
userMessage: "ну база в тебе чья? как называется контора?",
addressLaneTriggered: false,
useMock: false,
predecomposeMode: "unsupported",
predecomposeModeConfidence: "low"
});
expect(decision.mode).toBe("chat");
expect(decision.reason).toBe("assistant_data_scope_query_detected");
});
it("routes 'какая база подрублена?' to data-scope chat mode", () => {
const decision = resolveLivingAssistantModeDecision({
userMessage: "какая база подрублена?",
addressLaneTriggered: false,
useMock: false,
predecomposeMode: "unsupported",
predecomposeModeConfidence: "low"
});
expect(decision.mode).toBe("chat");
expect(decision.reason).toBe("assistant_data_scope_query_detected");
});
it("routes typo data-scope wording with misspelled company token to chat", () => {
const decision = resolveLivingAssistantModeDecision({
userMessage: "подскажи плиз с какой компинией можем поработать?",
addressLaneTriggered: false,
useMock: false,
predecomposeMode: "unsupported",
predecomposeModeConfidence: "low"
});
expect(decision.mode).toBe("chat");
expect(decision.reason).toBe("assistant_data_scope_query_detected");
});
it("routes slang data-scope wording 'по каким конторам можем общаться' to chat", () => {
const decision = resolveLivingAssistantModeDecision({
userMessage: "\u043f\u043e \u043a\u0430\u043a\u0438\u043c \u043a\u043e\u043d\u0442\u043e\u0440\u0430\u043c \u043c\u043e\u0436\u0435\u043c \u043e\u0431\u0449\u0430\u0442\u044c\u0441\u044f?",
addressLaneTriggered: false,
useMock: false,
predecomposeMode: "unsupported",
predecomposeModeConfidence: "low"
});
expect(decision.mode).toBe("chat");
expect(decision.reason).toBe("assistant_data_scope_query_detected");
});
it("routes data-scope wording without question mark when interrogative token is present", () => {
const decision = resolveLivingAssistantModeDecision({
userMessage: "каза какой компании подключена к 1с",
addressLaneTriggered: false,
useMock: false,
predecomposeMode: "unsupported",
predecomposeModeConfidence: "low"
});
expect(decision.mode).toBe("chat");
expect(decision.reason).toBe("assistant_data_scope_query_detected");
});
it("does not treat contract ranking data query as data-scope meta question", () => {
const decision = resolveLivingAssistantModeDecision({
userMessage: "по альтернативе вопрос. какой самый жирный контракт за всю историю у нее?",
addressLaneTriggered: false,
useMock: false,
predecomposeMode: "unsupported",
predecomposeModeConfidence: "low"
});
expect(decision.mode).toBe("deep_analysis");
expect(decision.reason).toBe("strong_data_signal_detected");
});
});
describe("assistant orchestration contract", () => {
it("routes non-domain emotional follow-up to indexed chat path instead of address lane", () => {
const decision = resolveAssistantOrchestrationDecision({
rawUserMessage: "бля хочу выпилиться от этого ебаного 1с",
effectiveAddressUserMessage: "бля хочу выпилиться от этого ебаного 1с",
followupContext: {
previous_intent: "payables_confirmed_as_of_date",
previous_filters: {
period_from: "2021-05-01",
period_to: "2021-05-31",
as_of_date: "2021-05-31"
}
},
llmPreDecomposeMeta: {
applied: false,
reason: "no_usable_fragment",
predecomposeContract: {
mode: "unsupported",
mode_confidence: "low",
intent: "unknown",
intent_confidence: "low"
}
} as any,
useMock: false
});
expect(decision.runAddressLane).toBe(false);
expect(decision.toolGateDecision).toBe("skip_address_lane");
expect(decision.toolGateReason).toBe("non_domain_query_indexed");
expect(decision.livingMode).toBe("chat");
expect(decision.livingReason).toBe("non_domain_query_indexed");
expect(decision.orchestrationContract?.hard_meta_mode).toBe("non_domain");
});
it("keeps address lane for a short net follow-up over grounded value-flow context", () => {
const decision = resolveAssistantOrchestrationDecision({
rawUserMessage: "А какое получилось нетто?",
effectiveAddressUserMessage: "А какое получилось нетто?",
followupContext: {
previous_intent: "customer_revenue_and_payments",
previous_filters: {
counterparty: "Группа СВК",
period_from: "2020-01-01",
period_to: "2020-12-31"
},
previous_anchor_type: "counterparty",
previous_anchor_value: "Группа СВК",
previous_discovery_pilot_scope: "counterparty_supplier_payout_query_movements_v1"
},
llmPreDecomposeMeta: {
applied: false,
reason: "no_usable_fragment",
predecomposeContract: {
mode: "unsupported",
mode_confidence: "low",
intent: "unknown",
intent_confidence: "low"
}
} as any,
useMock: false
});
expect(decision.runAddressLane).toBe(true);
expect(decision.toolGateDecision).toBe("run_address_lane");
expect(decision.toolGateReason).not.toBe("non_domain_query_indexed");
expect(decision.livingMode).toBe("address_data");
});
it("routes historical capability follow-up over grounded inventory answer to contextual chat", () => {
const decision = resolveAssistantOrchestrationDecision({
rawUserMessage: "а исторические данные ты можешь же показать?",
effectiveAddressUserMessage: "а исторические данные ты можешь же показать?",
followupContext: null,
llmPreDecomposeMeta: {
applied: false,
reason: "normalized_fragment_rejected_semantic_guard",
predecomposeContract: {
mode: "unsupported",
mode_confidence: "low",
intent: "unknown",
intent_confidence: "low"
},
semanticExtractionContract: {
valid: false,
apply_canonical_recommended: false
}
} as any,
sessionItems: [
{
role: "assistant",
debug: {
execution_lane: "address_query",
answer_grounding_check: {
status: "grounded"
},
detected_intent: "inventory_on_hand_as_of_date",
capability_id: "confirmed_inventory_on_hand_as_of_date",
assistant_active_organization: "альтернатива",
address_root_frame_context: {
root_intent: "inventory_on_hand_as_of_date",
current_frame_kind: "inventory_root",
organization: "альтернатива",
as_of_date: "2026-04-15"
}
}
}
],
useMock: false
} as any);
expect(decision.runAddressLane).toBe(false);
expect(decision.toolGateDecision).toBe("skip_address_lane");
expect(decision.toolGateReason).toBe("inventory_history_capability_followup_detected");
expect(decision.livingMode).toBe("chat");
expect(decision.livingReason).toBe("inventory_history_capability_followup_detected");
expect(decision.orchestrationContract?.followup_context_detected).toBe(true);
});
it("blocks organization age lookup from re-entering inventory lane on follow-up context", () => {
const decision = resolveAssistantOrchestrationDecision({
rawUserMessage: "а какой возраст у Альтернативы Плюс?",
effectiveAddressUserMessage: "Какой возраст у контрагента Альтернатива Плюс?",
followupContext: {
previous_intent: "inventory_on_hand_as_of_date",
previous_filters: {
organization: "ООО \"Альтернатива Плюс\"",
as_of_date: "2021-03-31",
period_from: "2021-03-01",
period_to: "2021-03-31"
},
previous_anchor_type: "item",
previous_anchor_value: "Модуль прямоугольный 1400*110*750"
},
llmPreDecomposeMeta: {
applied: true,
reason: "normalized_fragment_applied",
predecomposeContract: buildAddressLlmPredecomposeContractV1({
sourceMessage: "а какой возраст у Альтернативы Плюс?",
canonicalMessage: "Какой возраст у контрагента Альтернатива Плюс?",
mode: "address_query",
modeConfidence: "high",
intent: "unknown",
intentConfidence: "low",
entities: {
counterparty: "Альтернатива Плюс"
}
}),
semanticExtractionContract: buildAddressSemanticExtractionContractV1({
valid: true,
applyCanonicalRecommended: true
}),
llmCanonicalCandidateDetected: true
} as any,
useMock: false
} as any);
expect(decision.runAddressLane).toBe(false);
expect(decision.toolGateDecision).toBe("skip_address_lane");
expect(decision.toolGateReason).toBe("organization_fact_lookup_signal_detected");
expect(decision.livingMode).toBe("chat");
expect(decision.livingReason).toBe("organization_fact_lookup_signal_detected");
});
it("keeps organization activity-age lookup in address lane when the question is about 1C evidence", () => {
const decision = resolveAssistantOrchestrationDecision({
rawUserMessage: "а по Альтернативе Плюс сколько лет активности в базе 1С?",
effectiveAddressUserMessage: "а по Альтернативе Плюс сколько лет активности в базе 1С?",
followupContext: {
previous_intent: "inventory_on_hand_as_of_date",
previous_filters: {
organization: 'ООО "Альтернатива Плюс"',
counterparty: "Альтернатива Плюс",
as_of_date: "2021-03-31",
period_from: "2021-03-01",
period_to: "2021-03-31"
},
previous_anchor_type: "counterparty",
previous_anchor_value: "Альтернатива Плюс"
},
llmPreDecomposeMeta: null,
useMock: false
} as any);
expect(decision.runAddressLane).toBe(true);
expect(decision.toolGateDecision).toBe("run_address_lane");
expect(["address_intent_resolver_detected", "address_mode_classifier_detected", "followup_context_detected"]).toContain(
String(decision.toolGateReason)
);
expect(decision.livingMode).toBe("address_data");
expect(decision.livingReason).toBe("address_lane_triggered");
});
it("keeps inventory root restatement after company selection in address lane", () => {
const decision = resolveAssistantOrchestrationDecision({
rawUserMessage: "тогда покажи остатки на март 2021",
effectiveAddressUserMessage: "тогда покажи остатки на март 2021",
followupContext: {
previous_intent: "inventory_on_hand_as_of_date",
previous_filters: {
organization: 'ООО "Альтернатива Плюс"',
counterparty: "Альтернатива Плюс",
as_of_date: "2021-03-31",
period_from: "2021-03-01",
period_to: "2021-03-31"
},
previous_anchor_type: "counterparty",
previous_anchor_value: "Альтернатива Плюс",
root_intent: "inventory_on_hand_as_of_date",
root_filters: {
organization: 'ООО "Альтернатива Плюс"',
counterparty: "Альтернатива Плюс",
as_of_date: "2021-03-31",
period_from: "2021-03-01",
period_to: "2021-03-31"
},
current_frame_kind: "inventory_root",
root_context_only: true
},
llmPreDecomposeMeta: null,
useMock: false
} as any);
expect(decision.runAddressLane).toBe(true);
expect(decision.toolGateDecision).toBe("run_address_lane");
expect(["followup_context_detected", "address_mode_classifier_detected"]).toContain(String(decision.toolGateReason));
expect(decision.livingMode).toBe("address_data");
expect(decision.livingReason).toBe("address_lane_triggered");
});
it("keeps VAT payable forecast query in address lane", () => {
const decision = resolveAssistantOrchestrationDecision({
rawUserMessage: "какой прогноз оплаты ндс за 12 мая 2020",
effectiveAddressUserMessage: "какой прогноз оплаты ндс за 12 мая 2020",
followupContext: null,
llmPreDecomposeMeta: null,
useMock: false
});
expect(decision.runAddressLane).toBe(true);
expect(decision.toolGateDecision).toBe("run_address_lane");
expect(decision.toolGateReason).toBe("address_mode_classifier_detected");
expect(decision.livingMode).toBe("address_data");
expect(decision.livingReason).toBe("address_lane_triggered");
expect(decision.orchestrationContract?.unsupported_address_intent_fallback_to_deep).toBe(false);
});
it("keeps supported contract analytics query in address lane", () => {
const decision = resolveAssistantOrchestrationDecision({
rawUserMessage: "по альтернативе вопрос. какой самый жирный контракт за всю историю у нее?",
effectiveAddressUserMessage: "по альтернативе вопрос. какой самый жирный контракт за всю историю у нее?",
followupContext: null,
llmPreDecomposeMeta: null,
useMock: false
});
expect(decision.runAddressLane).toBe(true);
expect(decision.toolGateDecision).toBe("run_address_lane");
expect(["address_mode_classifier_detected", "address_intent_resolver_detected"]).toContain(String(decision.toolGateReason));
expect(decision.livingMode).toBe("address_data");
expect(decision.livingReason).toBe("address_lane_triggered");
});
it("keeps inventory provenance and sale-trace queries in address lane", () => {
const decision = resolveAssistantOrchestrationDecision({
rawUserMessage: "От какого поставщика куплен товар Диван трехместный из текущего остатка на складе Основной склад",
effectiveAddressUserMessage: "От какого поставщика куплен товар Диван трехместный из текущего остатка на складе Основной склад",
followupContext: {
previous_intent: "inventory_purchase_provenance_for_item",
previous_filters: {
as_of_date: "2020-03-31"
}
},
llmPreDecomposeMeta: {
applied: true,
llmCanonicalCandidateDetected: true,
predecomposeContract: {
mode: "address_query",
mode_confidence: "high",
intent: "inventory_purchase_provenance_for_item",
intent_confidence: "high"
}
} as any,
useMock: false
});
expect(decision.runAddressLane).toBe(true);
expect(decision.toolGateDecision).toBe("run_address_lane");
expect(["address_mode_classifier_detected", "address_intent_resolver_detected"]).toContain(String(decision.toolGateReason));
expect(decision.livingMode).toBe("address_data");
expect(decision.livingReason).toBe("address_lane_triggered");
});
it("keeps short inventory follow-up 'когда' in address lane when a selected-item provenance context exists", () => {
const decision = resolveAssistantOrchestrationDecision({
rawUserMessage: "когда",
effectiveAddressUserMessage: "когда",
followupContext: {
previous_intent: "inventory_purchase_provenance_for_item",
previous_filters: {
item: "Столешница 600*3050*26 дуб ниагара",
warehouse: "Основной склад",
as_of_date: "2019-03-31"
}
},
llmPreDecomposeMeta: {
applied: false,
reason: "normalized_fragment_rejected_semantic_guard",
llmCanonicalCandidateDetected: true,
predecomposeContract: {
mode: "unsupported",
mode_confidence: "low",
intent: "unknown",
intent_confidence: "low"
},
semanticExtractionContract: {
valid: false,
apply_canonical_recommended: false,
reason_codes: ["unsupported_low_confidence_contract"]
}
} as any,
useMock: false
});
expect(decision.runAddressLane).toBe(true);
expect(decision.toolGateDecision).toBe("run_address_lane");
expect(decision.livingMode).toBe("address_data");
expect(decision.livingReason).toBe("address_lane_triggered");
});
it("keeps customer-value ranking question in address lane even when LLM semantic guard rejects canonical rewrite", () => {
const decision = resolveAssistantOrchestrationDecision({
rawUserMessage: "кто больше всего принес денег в 2020",
effectiveAddressUserMessage: "кто больше всего принес денег в 2020",
followupContext: null,
llmPreDecomposeMeta: {
applied: false,
reason: "normalized_fragment_rejected_semantic_guard",
llmCanonicalCandidateDetected: true,
predecomposeContract: {
mode: "unsupported",
mode_confidence: "low",
intent: "unknown",
intent_confidence: "low"
},
semanticExtractionContract: {
valid: false,
apply_canonical_recommended: false,
reason_codes: ["unsupported_low_confidence_contract"]
}
} as any,
useMock: false
});
expect(decision.runAddressLane).toBe(true);
expect(decision.toolGateDecision).toBe("run_address_lane");
expect(["address_intent_resolver_detected", "address_mode_classifier_detected", "address_signal_detected"]).toContain(
String(decision.toolGateReason)
);
expect(decision.livingMode).toBe("address_data");
expect(decision.livingReason).toBe("address_lane_triggered");
});
it("keeps colloquial 'кто нам больше денег принес' in address lane", () => {
const decision = resolveAssistantOrchestrationDecision({
rawUserMessage: "\u043a\u0442\u043e \u043d\u0430\u043c \u0431\u043e\u043b\u044c\u0448\u0435 \u0434\u0435\u043d\u0435\u0433 \u043f\u0440\u0438\u043d\u0435\u0441",
effectiveAddressUserMessage: "\u043a\u0442\u043e \u043d\u0430\u043c \u0431\u043e\u043b\u044c\u0448\u0435 \u0434\u0435\u043d\u0435\u0433 \u043f\u0440\u0438\u043d\u0435\u0441",
followupContext: null,
llmPreDecomposeMeta: null,
useMock: false
});
expect(decision.runAddressLane).toBe(true);
expect(decision.toolGateDecision).toBe("run_address_lane");
expect(["address_intent_resolver_detected", "address_mode_classifier_detected", "address_signal_detected"]).toContain(
String(decision.toolGateReason)
);
expect(decision.livingMode).toBe("address_data");
expect(decision.livingReason).toBe("address_lane_triggered");
});
it("keeps short mirror follow-up 'a мы кому' in address lane instead of non-domain chat", () => {
const decision = resolveAssistantOrchestrationDecision({
rawUserMessage: "a мы кому",
effectiveAddressUserMessage: "a мы кому",
followupContext: null,
llmPreDecomposeMeta: {
applied: false,
reason: "normalized_fragment_rejected_semantic_guard",
llmCanonicalCandidateDetected: true,
predecomposeContract: {
mode: "unsupported",
mode_confidence: "low",
intent: "unknown",
intent_confidence: "low"
},
semanticExtractionContract: {
valid: false,
apply_canonical_recommended: false,
reason_codes: ["unsupported_low_confidence_contract"]
}
} as any,
useMock: false
});
expect(decision.runAddressLane).toBe(true);
expect(decision.toolGateDecision).toBe("run_address_lane");
expect(decision.livingMode).toBe("address_data");
expect(decision.livingReason).toBe("address_lane_triggered");
});
it("routes unsupported turnover-by-organization query to deep analysis", () => {
const decision = resolveAssistantOrchestrationDecision({
rawUserMessage: "\u043a\u0430\u043a\u0438\u0435 \u043e\u0431\u043e\u0440\u043e\u0442\u044b \u043f\u043e \u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0435 \u0437\u0430 2020 \u0433\u043e\u0434",
effectiveAddressUserMessage: "\u041e\u0431\u043e\u0440\u043e\u0442\u044b \u043f\u043e \u0441\u0447\u0435\u0442\u0443 '\u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430' \u0437\u0430 2020 \u0433\u043e\u0434.",
followupContext: null,
llmPreDecomposeMeta: {
applied: true,
llmCanonicalCandidateDetected: true,
predecomposeContract: {
mode: "unsupported",
mode_confidence: "low",
intent: "account_balance_snapshot",
intent_confidence: "high"
}
} as any,
useMock: false
});
expect(decision.runAddressLane).toBe(false);
expect(decision.toolGateDecision).toBe("skip_address_lane");
expect(decision.livingMode).toBe("deep_analysis");
expect([
"address_signal_unsupported_intent_fallback_to_deep",
"aggregate_analytics_signal_fallback_to_deep"
]).toContain(String(decision.toolGateReason));
expect([
"unsupported_address_intent_fallback_to_deep",
"aggregate_analytics_signal_fallback_to_deep"
]).toContain(String(decision.livingReason));
});
it("does not route advances-to-shipment risk query to chat when semantic guard rejects canonical rewrite", () => {
const decision = resolveAssistantOrchestrationDecision({
rawUserMessage:
"Где у нас накопились авансы к отгрузкам, которые уже давно пора закрыть или хотя бы перепроверить?",
effectiveAddressUserMessage:
"Где у нас накопились авансы к отгрузкам, которые уже давно пора закрыть или хотя бы перепроверить?",
followupContext: null,
llmPreDecomposeMeta: {
applied: false,
llmCanonicalCandidateDetected: false,
reason: "normalized_fragment_rejected_semantic_guard",
predecomposeContract: {
mode: "unsupported",
mode_confidence: "low",
intent: "unknown",
intent_confidence: "low"
},
semanticExtractionContract: {
valid: false,
apply_canonical_recommended: false,
reason_codes: ["unsupported_low_confidence_contract"]
}
} as any,
useMock: false
});
expect(decision.livingMode).toBe("address_data");
expect(decision.toolGateDecision).toBe("run_address_lane");
expect(["address_signal_detected", "address_intent_resolver_detected", "address_mode_classifier_detected"]).toContain(
String(decision.toolGateReason)
);
expect(decision.livingReason).toBe("address_lane_triggered");
});
it("routes unsupported turnover query to deep even with followup context carryover", () => {
const decision = resolveAssistantOrchestrationDecision({
rawUserMessage: "\u043a\u0430\u043a\u0438\u0435 \u043e\u0431\u043e\u0440\u043e\u0442\u044b \u043f\u043e \u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0435 \u0437\u0430 2020 \u0433\u043e\u0434",
effectiveAddressUserMessage: "\u041e\u0431\u043e\u0440\u043e\u0442\u044b \u043f\u043e \u0441\u0447\u0435\u0442\u0443 '\u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430' \u0437\u0430 2020 \u0433\u043e\u0434.",
followupContext: {
previous_intent: "list_documents_by_contract",
previous_filters: {
organization: "\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441"
}
},
llmPreDecomposeMeta: {
applied: true,
llmCanonicalCandidateDetected: true,
predecomposeContract: {
mode: "unsupported",
mode_confidence: "low",
intent: "account_balance_snapshot",
intent_confidence: "high"
},
semanticExtractionContract: {
extraction: {
query_shape: "AGGREGATE_LOOKUP",
aggregation_profile: "management_profile"
},
guard_hints: {
deep_investigation_signal_detected: false
}
}
} as any,
useMock: false
});
expect(decision.runAddressLane).toBe(false);
expect(decision.toolGateDecision).toBe("skip_address_lane");
expect(decision.livingMode).toBe("deep_analysis");
expect([
"address_signal_unsupported_intent_fallback_to_deep",
"aggregate_analytics_signal_fallback_to_deep"
]).toContain(String(decision.toolGateReason));
expect(
decision.orchestrationContract?.semantic_route_arbitration?.followup_semantic_override_to_deep_allowed
).toBe(true);
});
it("routes standalone aggregate query to deep even when stale followup context exists and LLM predecompose is unavailable", () => {
const decision = resolveAssistantOrchestrationDecision({
rawUserMessage: "какие обороты по альтернативе за 2020 год",
effectiveAddressUserMessage: "какие обороты по альтернативе за 2020 год",
followupContext: {
previous_intent: "list_documents_by_contract",
previous_filters: {
organization: "ООО Альтернатива Плюс"
}
},
llmPreDecomposeMeta: null as any,
useMock: true
} as any);
expect(decision.runAddressLane).toBe(false);
expect(decision.toolGateDecision).toBe("skip_address_lane");
expect(decision.toolGateReason).toBe("aggregate_analytics_signal_fallback_to_deep");
expect(decision.livingMode).toBe("deep_analysis");
expect(decision.livingReason).toBe("aggregate_analytics_signal_fallback_to_deep");
expect(decision.orchestrationContract?.aggregate_analytics_signal_fallback_to_deep).toBe(true);
});
it("keeps profitability ranking query in address lane", () => {
const decision = resolveAssistantOrchestrationDecision({
rawUserMessage: "\u043a\u0430\u043a\u043e\u0439 \u0441\u0430\u043c\u044b\u0439 \u0434\u043e\u0445\u043e\u0434\u043d\u044b\u0439 \u0433\u043e\u0434?",
effectiveAddressUserMessage: "\u043a\u0430\u043a\u043e\u0439 \u0441\u0430\u043c\u044b\u0439 \u0434\u043e\u0445\u043e\u0434\u043d\u044b\u0439 \u0433\u043e\u0434?",
followupContext: null,
llmPreDecomposeMeta: null as any,
useMock: false
} as any);
expect(decision.runAddressLane).toBe(true);
expect(decision.toolGateDecision).toBe("run_address_lane");
expect(["address_intent_resolver_detected", "address_mode_classifier_detected", "address_signal_detected"]).toContain(
String(decision.toolGateReason)
);
expect(decision.livingMode).toBe("address_data");
expect(decision.livingReason).toBe("address_lane_triggered");
expect(decision.orchestrationContract?.aggregate_analytics_signal_fallback_to_deep).toBe(false);
});
it("keeps all-time top customer ranking in address lane instead of stale deep problem answer", () => {
const decision = resolveAssistantOrchestrationDecision({
rawUserMessage: "кто у нас самый доходный клиент за все время",
effectiveAddressUserMessage: "определить самого доходного клиента за весь период",
followupContext: {
previous_intent: "counterparty_activity_lifecycle",
previous_filters: {
organization: "ООО Альтернатива Плюс"
}
},
llmPreDecomposeMeta: {
applied: true,
llmCanonicalCandidateDetected: true,
predecomposeContract: {
mode: "address_query",
mode_confidence: "medium",
intent: "customer_revenue_and_payments",
intent_confidence: "medium"
},
semanticExtractionContract: {
valid: false,
apply_canonical_recommended: false,
extraction: {
query_shape: "AGGREGATE_LOOKUP",
aggregation_profile: "management_profile"
},
guard_hints: {},
reason_codes: ["ranking_semantic_guard_rejected"]
}
} as any,
useMock: false
} as any);
expect(decision.runAddressLane).toBe(true);
expect(decision.toolGateDecision).toBe("run_address_lane");
expect(decision.livingMode).toBe("address_data");
expect(decision.livingReason).toBe("address_lane_triggered");
expect(decision.orchestrationContract?.aggregate_analytics_signal_fallback_to_deep).toBe(false);
});
it("keeps unsupported retrieval query in address lane when LLM runtime is unavailable", () => {
const decision = resolveAssistantOrchestrationDecision({
rawUserMessage:
"\u0413\u0434\u0435 \u0443 \u043d\u0430\u0441 \u043d\u0430\u043a\u043e\u043f\u0438\u043b\u0438\u0441\u044c \u0430\u0432\u0430\u043d\u0441\u044b \u043a \u043e\u0442\u0433\u0440\u0443\u0437\u043a\u0430\u043c, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0434\u0430\u0432\u043d\u043e \u043f\u043e\u0440\u0430 \u0437\u0430\u043a\u0440\u044b\u0442\u044c?",
effectiveAddressUserMessage:
"\u0413\u0434\u0435 \u0443 \u043d\u0430\u0441 \u043d\u0430\u043a\u043e\u043f\u0438\u043b\u0438\u0441\u044c \u0430\u0432\u0430\u043d\u0441\u044b \u043a \u043e\u0442\u0433\u0440\u0443\u0437\u043a\u0430\u043c, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0434\u0430\u0432\u043d\u043e \u043f\u043e\u0440\u0430 \u0437\u0430\u043a\u0440\u044b\u0442\u044c?",
followupContext: null,
llmPreDecomposeMeta: {
applied: false,
reason: "error:OpenAI API key is missing.",
llmCanonicalCandidateDetected: false,
predecomposeContract: {
mode: "unsupported",
mode_confidence: "low",
intent: "unknown",
intent_confidence: "low"
},
semanticExtractionContract: {
valid: false,
apply_canonical_recommended: false,
reason_codes: ["unsupported_low_confidence_contract", "deep_investigation_signal_detected"],
guard_hints: {
deep_investigation_signal_detected: true
},
extraction: {
query_shape: "VERIFY_FACTUAL",
aggregation_profile: "unknown"
}
}
} as any,
useMock: false
});
expect(decision.runAddressLane).toBe(true);
expect(decision.toolGateDecision).toBe("run_address_lane");
expect(decision.livingMode).toBe("address_data");
expect(decision.livingReason).toBe("address_lane_triggered");
expect(decision.orchestrationContract?.unsupported_address_intent_fallback_to_deep).toBe(false);
expect(decision.orchestrationContract?.deep_analysis_signal_fallback_to_deep).toBe(false);
});
it("keeps VAT explain follow-up in address lane when followup context is present", () => {
const decision = resolveAssistantOrchestrationDecision({
rawUserMessage: "почему прогноз к уплате 0?",
effectiveAddressUserMessage: "почему прогноз к уплате 0?",
followupContext: {
previous_intent: "vat_payable_forecast",
previous_filters: {
period_from: "2020-03-01",
period_to: "2020-03-31"
},
previous_anchor_type: "unknown",
previous_anchor_value: null
},
llmPreDecomposeMeta: null,
useMock: false
});
expect(decision.runAddressLane).toBe(true);
expect(decision.toolGateDecision).toBe("run_address_lane");
expect(decision.toolGateReason).toBe("followup_context_detected");
expect(decision.livingMode).toBe("address_data");
expect(decision.livingReason).toBe("address_lane_triggered");
});
it("routes meta follow-up over grounded inventory answer to chat instead of rerunning address lane", () => {
const decision = resolveAssistantOrchestrationDecision({
rawUserMessage: "\u0447\u0435 \u0434\u0443\u043c\u0430\u0435\u0448\u044c \u043d\u0430 \u044d\u0442\u0443 \u0442\u0435\u043c\u0443",
effectiveAddressUserMessage: "\u0447\u0435 \u0434\u0443\u043c\u0430\u0435\u0448\u044c \u043d\u0430 \u044d\u0442\u0443 \u0442\u0435\u043c\u0443",
followupContext: {
previous_intent: "inventory_on_hand_as_of_date",
previous_filters: {
as_of_date: "2016-06-30",
organization: "alt"
},
previous_anchor_type: "counterparty",
previous_anchor_value: "ALT"
},
llmPreDecomposeMeta: {
applied: false,
reason: "normalized_fragment_rejected_semantic_guard",
llmCanonicalCandidateDetected: true,
predecomposeContract: {
mode: "unsupported",
mode_confidence: "low",
intent: "unknown",
intent_confidence: "low"
},
semanticExtractionContract: {
valid: false,
apply_canonical_recommended: false
}
} as any,
sessionItems: [
{
role: "assistant",
debug: {
execution_lane: "address_query",
answer_grounding_check: {
status: "grounded"
}
}
}
],
useMock: false
} as any);
expect(decision.runAddressLane).toBe(false);
expect(decision.toolGateDecision).toBe("skip_address_lane");
expect(decision.toolGateReason).toBe("meta_followup_over_grounded_answer");
expect(decision.livingMode).toBe("chat");
expect(decision.livingReason).toBe("meta_followup_over_grounded_answer");
});
it("routes evaluative VAT follow-up 'это много или мало' to contextual chat instead of replaying address lane", () => {
const decision = resolveAssistantOrchestrationDecision({
rawUserMessage: "это много или мало?",
effectiveAddressUserMessage: "это много или мало?",
followupContext: {
previous_intent: "vat_payable_forecast",
previous_filters: {
period_from: "2020-03-01",
period_to: "2020-03-31"
},
previous_anchor_type: "unknown",
previous_anchor_value: null
},
llmPreDecomposeMeta: {
applied: false,
reason: "normalized_fragment_rejected_semantic_guard",
llmCanonicalCandidateDetected: true,
predecomposeContract: {
mode: "unsupported",
mode_confidence: "low",
intent: "unknown",
intent_confidence: "low"
},
semanticExtractionContract: {
valid: false,
apply_canonical_recommended: false
}
} as any,
sessionItems: [
{
role: "assistant",
debug: {
execution_lane: "address_query",
answer_grounding_check: {
status: "grounded"
},
detected_intent: "vat_payable_forecast"
}
}
],
useMock: false
} as any);
expect(decision.runAddressLane).toBe(false);
expect(decision.toolGateDecision).toBe("skip_address_lane");
expect(decision.toolGateReason).toBe("meta_followup_over_grounded_answer");
expect(decision.livingMode).toBe("chat");
expect(decision.livingReason).toBe("meta_followup_over_grounded_answer");
});
it("routes memory recap follow-up over prior address context to deterministic chat instead of generic non-domain chat", () => {
const decision = resolveAssistantOrchestrationDecision({
rawUserMessage: "а ты помнишь мы зеркало обсуждали?",
effectiveAddressUserMessage: "а ты помнишь мы зеркало обсуждали?",
followupContext: null,
llmPreDecomposeMeta: {
applied: false,
reason: "normalized_fragment_rejected_semantic_guard",
llmCanonicalCandidateDetected: false,
predecomposeContract: {
mode: "unsupported",
mode_confidence: "low",
intent: "unknown",
intent_confidence: "low"
}
} as any,
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"
}
}
}
],
useMock: false
} as any);
expect(decision.runAddressLane).toBe(false);
expect(decision.toolGateDecision).toBe("skip_address_lane");
expect(decision.toolGateReason).toBe("memory_recap_followup_detected");
expect(decision.livingMode).toBe("chat");
expect(decision.livingReason).toBe("memory_recap_followup_detected");
});
it("keeps documentary inventory chain verification in address lane for supported exact intent", () => {
const question =
"Есть ли документально подтвержденная цепочка: поставщик Гамма-мебель, ООО -> товар Шкаф картотечный 1000*400*2100 -> покупатель Департамент капитального ремонта города Москвы";
const decision = resolveAssistantOrchestrationDecision({
rawUserMessage: question,
effectiveAddressUserMessage: question,
followupContext: null,
llmPreDecomposeMeta: {
applied: true,
llmCanonicalCandidateDetected: true,
predecomposeContract: {
mode: "deep_analysis",
mode_confidence: "high",
intent: "inventory_purchase_to_sale_chain",
intent_confidence: "medium"
},
semanticExtractionContract: {
valid: true,
apply_canonical_recommended: true,
reason_codes: ["deep_investigation_signal_detected"],
guard_hints: {
deep_investigation_signal_detected: true
},
extraction: {
query_shape: "VERIFY_FACTUAL",
aggregation_profile: "unknown"
}
}
} as any,
useMock: false
});
expect(decision.runAddressLane).toBe(true);
expect(decision.toolGateDecision).toBe("run_address_lane");
expect(decision.livingMode).toBe("address_data");
expect(decision.livingReason).toBe("address_lane_triggered");
expect(decision.orchestrationContract?.deep_analysis_signal_fallback_to_deep).toBe(false);
expect(decision.orchestrationContract?.semantic_route_arbitration?.supported_address_intent_detected).toBe(true);
expect(decision.orchestrationContract?.semantic_route_arbitration?.strict_deep_investigation_bypass_allowed).toBe(true);
});
it("keeps selected-object purchase-to-sale chain wording in address lane even when LLM predecompose calls it deep", () => {
const question =
'По выбранному объекту "Шкаф картотечный 1000*400*2100": через какие документы прошел путь товара: закупка -> склад -> продажа';
const decision = resolveAssistantOrchestrationDecision({
rawUserMessage: question,
effectiveAddressUserMessage: question,
followupContext: null,
llmPreDecomposeMeta: {
applied: true,
llmCanonicalCandidateDetected: true,
predecomposeContract: {
mode: "deep_analysis",
mode_confidence: "high",
intent: "list_documents_by_counterparty",
intent_confidence: "high"
},
semanticExtractionContract: {
valid: true,
apply_canonical_recommended: true,
reason_codes: ["deep_investigation_signal_detected"],
guard_hints: {
deep_investigation_signal_detected: true
},
extraction: {
query_shape: "DOCUMENT_LIST",
aggregation_profile: "list_lookup"
}
}
} as any,
useMock: false
});
expect(decision.runAddressLane).toBe(true);
expect(decision.toolGateDecision).toBe("run_address_lane");
expect(decision.livingMode).toBe("address_data");
expect(decision.orchestrationContract?.deep_analysis_signal_fallback_to_deep).toBe(false);
expect(decision.orchestrationContract?.address_intent).toBe("inventory_purchase_to_sale_chain");
});
it("keeps 'a na tekushuyu datu' follow-up in address lane when previous VAT context exists", () => {
const decision = resolveAssistantOrchestrationDecision({
rawUserMessage: "а на текущую дату",
effectiveAddressUserMessage: "а на текущую дату",
followupContext: {
previous_intent: "vat_payable_confirmed_as_of_date",
previous_filters: {
period_from: "2016-03-01",
period_to: "2016-03-31",
as_of_date: "2016-03-31"
},
previous_anchor_type: "unknown",
previous_anchor_value: null
},
llmPreDecomposeMeta: {
applied: false,
reason: "normalized_fragment_rejected_semantic_guard",
llmCanonicalCandidateDetected: true,
predecomposeContract: {
mode: "unsupported",
mode_confidence: "low",
intent: "unknown",
intent_confidence: "low"
}
} as any,
useMock: false
});
expect(decision.runAddressLane).toBe(true);
expect(decision.toolGateDecision).toBe("run_address_lane");
expect(decision.livingMode).toBe("address_data");
expect(decision.livingReason).toBe("address_lane_triggered");
});
it("keeps explicit address-mode unknown-intent data query in address lane", () => {
const decision = resolveAssistantOrchestrationDecision({
rawUserMessage:
"Покажи контрагентов, по которым сальдо скорее всего не совпадет с их актом сверки. Может, стоит поторопиться и запросить сверку?",
effectiveAddressUserMessage:
"Показать контрагентов с вероятным несогласием между сальдо и актом сверки. Рекомендовать запросить сверку.",
followupContext: null,
llmPreDecomposeMeta: {
applied: true,
llmCanonicalCandidateDetected: true,
predecomposeContract: {
mode: "address_query",
mode_confidence: "high",
intent: "unknown",
intent_confidence: "low"
}
} as any,
useMock: false
});
expect(decision.runAddressLane).toBe(true);
expect(decision.toolGateDecision).toBe("run_address_lane");
expect(decision.livingMode).toBe("address_data");
expect(decision.livingReason).toBe("address_lane_triggered");
expect(decision.orchestrationContract?.unsupported_address_intent_fallback_to_deep).toBe(false);
});
it("keeps confirmed open-contracts query in address lane despite 'unclosed' wording", () => {
const decision = resolveAssistantOrchestrationDecision({
rawUserMessage: "\u041f\u043e\u043a\u0430\u0436\u0438 \u043d\u0435\u0437\u0430\u043a\u0440\u044b\u0442\u044b\u0435 \u0434\u043e\u0433\u043e\u0432\u043e\u0440\u044b \u043d\u0430 2020-12-31",
effectiveAddressUserMessage: "\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u043d\u0435\u0437\u0430\u043a\u0440\u044b\u0442\u044b\u0435 \u0434\u043e\u0433\u043e\u0432\u043e\u0440\u044b \u043f\u043e \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044e \u043d\u0430 \u043a\u043e\u043d\u0435\u0446 \u0434\u0435\u043a\u0430\u0431\u0440\u044f 2020 \u0433\u043e\u0434\u0430.",
followupContext: {
previous_intent: "month_close_costs_20_44"
},
llmPreDecomposeMeta: {
applied: true,
llmCanonicalCandidateDetected: true,
predecomposeContract: {
mode: "address_query",
mode_confidence: "high",
intent: "open_contracts_confirmed_as_of_date",
intent_confidence: "medium"
}
} as any,
useMock: false
} as any);
expect(decision.runAddressLane).toBe(true);
expect(decision.toolGateDecision).toBe("run_address_lane");
expect(decision.livingMode).toBe("address_data");
expect(decision.livingReason).toBe("address_lane_triggered");
expect(decision.toolGateReason).toBe("address_mode_classifier_detected");
});
it("keeps inventory-on-hand query in address lane", () => {
const decision = resolveAssistantOrchestrationDecision({
rawUserMessage: "Какие товары сейчас лежат на складе",
effectiveAddressUserMessage: "Какие товары сейчас лежат на складе",
followupContext: null,
llmPreDecomposeMeta: null as any,
useMock: true
} as any);
expect(decision.runAddressLane).toBe(true);
expect(decision.toolGateDecision).toBe("run_address_lane");
expect(["address_intent_resolver_detected", "address_mode_classifier_detected", "address_signal_detected"]).toContain(
String(decision.toolGateReason)
);
expect(decision.livingMode).toBe("address_data");
expect(decision.livingReason).toBe("address_lane_triggered");
});
it("keeps slang stock-state query with organization scope in address lane instead of deep fallback", () => {
const rawUserMessage = "чекни плиз чо там на складе альтернативы происходит";
const effectiveAddressUserMessage = "проверь, что происходит на складе у компании 'альтернатива'";
const predecomposeContract = buildAddressLlmPredecomposeContractV1({
sourceMessage: rawUserMessage,
canonicalMessage: effectiveAddressUserMessage,
semanticHints: {
scope_target_kind: "organization",
scope_target_text: "альтернатива",
date_scope_kind: "implicit_current",
self_scope_detected: false,
selected_object_scope_detected: false
}
});
const semanticExtractionContract = buildAddressSemanticExtractionContractV1({
sourceMessage: rawUserMessage,
canonicalMessage: effectiveAddressUserMessage,
predecomposeContract
});
const decision = resolveAssistantOrchestrationDecision({
rawUserMessage,
effectiveAddressUserMessage,
followupContext: null,
llmPreDecomposeMeta: {
applied: true,
llmCanonicalCandidateDetected: true,
predecomposeContract,
semanticExtractionContract
} as any,
useMock: false
});
expect(decision.runAddressLane).toBe(true);
expect(decision.toolGateDecision).toBe("run_address_lane");
expect(decision.livingMode).toBe("address_data");
expect(decision.livingReason).toBe("address_lane_triggered");
expect(decision.orchestrationContract?.unsupported_address_intent_fallback_to_deep).toBe(false);
expect(decision.orchestrationContract?.deep_analysis_signal_fallback_to_deep).toBe(false);
expect(decision.orchestrationContract?.semantic_route_arbitration?.supported_address_intent_detected).toBe(true);
});
it("keeps short colloquial stock query with organization scope in address lane instead of chat fallback", () => {
const rawUserMessage = "че на складах альтернативы";
const effectiveAddressUserMessage = "что находится на складах у компании 'альтернатива'";
const predecomposeContract = buildAddressLlmPredecomposeContractV1({
sourceMessage: rawUserMessage,
canonicalMessage: effectiveAddressUserMessage,
semanticHints: {
scope_target_kind: "organization",
scope_target_text: "альтернатива",
date_scope_kind: "implicit_current",
self_scope_detected: false,
selected_object_scope_detected: false
}
});
const semanticExtractionContract = buildAddressSemanticExtractionContractV1({
sourceMessage: rawUserMessage,
canonicalMessage: effectiveAddressUserMessage,
predecomposeContract
});
const decision = resolveAssistantOrchestrationDecision({
rawUserMessage,
effectiveAddressUserMessage,
followupContext: null,
llmPreDecomposeMeta: {
applied: true,
llmCanonicalCandidateDetected: true,
predecomposeContract,
semanticExtractionContract
} as any,
useMock: false
});
expect(decision.runAddressLane).toBe(true);
expect(decision.toolGateDecision).toBe("run_address_lane");
expect([
"address_intent_resolver_detected",
"address_mode_classifier_detected",
"llm_canonical_data_signal_detected",
"address_signal_detected"
]).toContain(
String(decision.toolGateReason)
);
expect(decision.livingMode).toBe("address_data");
expect(decision.livingReason).toBe("address_lane_triggered");
});
it("keeps exact open-items lookup in address lane even when semantic guard overflags deep investigation", () => {
const rawUserMessage = "хвосты покажи по счету 60 на август 2022";
const effectiveAddressUserMessage = "хвосты по счету 60 на август 2022";
const predecomposeContract = buildAddressLlmPredecomposeContractV1({
sourceMessage: rawUserMessage,
canonicalMessage: effectiveAddressUserMessage,
semanticHints: {
exact_data_request_detected: true,
account_scope_kind: "explicit",
account_scope_text: "60",
date_scope_kind: "explicit_period",
date_scope_text: "август 2022"
}
});
const semanticExtractionContract = buildAddressSemanticExtractionContractV1({
sourceMessage: rawUserMessage,
canonicalMessage: effectiveAddressUserMessage,
predecomposeContract
});
const decision = resolveAssistantOrchestrationDecision({
rawUserMessage,
effectiveAddressUserMessage,
followupContext: null,
llmPreDecomposeMeta: {
applied: true,
llmCanonicalCandidateDetected: true,
predecomposeContract,
semanticExtractionContract: {
...semanticExtractionContract,
guard_hints: {
...(semanticExtractionContract.guard_hints ?? {}),
deep_investigation_signal_detected: true
}
}
} as any,
useMock: false
});
expect(decision.runAddressLane).toBe(true);
expect(decision.toolGateDecision).toBe("run_address_lane");
expect(decision.livingMode).toBe("address_data");
expect(decision.livingReason).toBe("address_lane_triggered");
expect(decision.orchestrationContract?.deep_analysis_signal_fallback_to_deep).toBe(false);
expect(
decision.orchestrationContract?.semantic_route_arbitration?.exact_address_intent_protected_from_semantic_deep_hint
).toBe(true);
});
it("keeps open-contracts request in address lane even with stale deep followup context when LLM contract is absent", () => {
const decision = resolveAssistantOrchestrationDecision({
rawUserMessage: "Покажи незакрытые договоры на 2020-12-31",
effectiveAddressUserMessage: "Покажи незакрытые договоры на 2020-12-31",
followupContext: {
previous_intent: "month_close_costs_20_44"
},
llmPreDecomposeMeta: null as any,
useMock: true
} as any);
expect(decision.runAddressLane).toBe(true);
expect(decision.toolGateDecision).toBe("run_address_lane");
expect(decision.livingMode).toBe("address_data");
expect(decision.livingReason).toBe("address_lane_triggered");
});
it("routes 'по каким конторам можем общаться?' to chat/data-scope in orchestration contract", () => {
const decision = resolveAssistantOrchestrationDecision({
rawUserMessage: "по каким конторам можем общаться?",
effectiveAddressUserMessage: "по каким контрагентам у нас есть активность или договоры?",
followupContext: null,
llmPreDecomposeMeta: {
applied: true,
llmCanonicalCandidateDetected: true,
predecomposeContract: {
mode: "address_query",
mode_confidence: "medium",
intent: "list_documents_by_contract",
intent_confidence: "medium"
}
} as any,
useMock: false
} as any);
expect(decision.runAddressLane).toBe(false);
expect(decision.toolGateDecision).toBe("skip_address_lane");
expect(decision.toolGateReason).toBe("assistant_data_scope_query_detected");
expect(decision.livingMode).toBe("chat");
expect(decision.livingReason).toBe("assistant_data_scope_query_detected");
});
it("does not force address lane for deep-analysis unknown intent query with date-like token", () => {
const decision = resolveAssistantOrchestrationDecision({
rawUserMessage: "найди какие либо ошибки на 21 мая 2022 года",
effectiveAddressUserMessage: "Найти ошибки в бухгалтерии за 21 мая 2022 года.",
followupContext: null,
llmPreDecomposeMeta: {
applied: true,
llmCanonicalCandidateDetected: true,
predecomposeContract: {
mode: "deep_analysis",
mode_confidence: "high",
intent: "unknown",
intent_confidence: "low"
}
} as any,
useMock: false
});
expect(decision.runAddressLane).toBe(false);
expect(decision.toolGateDecision).toBe("skip_address_lane");
expect(decision.livingMode).toBe("deep_analysis");
expect(["address_signal_unsupported_intent_fallback_to_deep", "no_address_signal_after_l0"]).toContain(
decision.toolGateReason
);
});
it("routes risk/anomaly analytics wording to deep pipeline", () => {
const decision = resolveAssistantOrchestrationDecision({
rawUserMessage: "Проверь НДС по счету 19 за 2020-06 и рискованные записи по документам.",
effectiveAddressUserMessage: "Проверь НДС по счету 19 за 2020-06 и рискованные записи по документам.",
followupContext: null,
llmPreDecomposeMeta: null,
useMock: false
});
expect(decision.runAddressLane).toBe(false);
expect(decision.toolGateDecision).toBe("skip_address_lane");
expect(decision.toolGateReason).toBe("deep_analysis_signal_fallback_to_deep");
expect(decision.livingMode).toBe("deep_analysis");
expect(decision.livingReason).toBe("deep_analysis_signal_fallback_to_deep");
expect(decision.orchestrationContract?.deep_analysis_signal_fallback_to_deep).toBe(true);
});
it("routes settlement closure verification wording to deep pipeline", () => {
const decision = resolveAssistantOrchestrationDecision({
rawUserMessage:
"По оплате поставщику на счете 60 в июле 2020 остался хвост. Проверь закрытие по договору и объекту расчетов.",
effectiveAddressUserMessage:
"По оплате поставщику на счете 60 в июле 2020 остался хвост. Проверь закрытие по договору и объекту расчетов.",
followupContext: null,
llmPreDecomposeMeta: null,
useMock: false
});
expect(decision.runAddressLane).toBe(false);
expect(decision.toolGateDecision).toBe("skip_address_lane");
expect(decision.toolGateReason).toBe("deep_analysis_signal_fallback_to_deep");
expect(decision.livingMode).toBe("deep_analysis");
expect(decision.livingReason).toBe("deep_analysis_signal_fallback_to_deep");
expect(decision.orchestrationContract?.deep_analysis_signal_fallback_to_deep).toBe(true);
});
});