537 lines
25 KiB
TypeScript
537 lines
25 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
||
import { resolveAssistantOrchestrationDecision, resolveLivingAssistantModeDecision } from "../src/services/assistantService";
|
||
|
||
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("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("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(decision.toolGateReason).toBe("address_mode_classifier_detected");
|
||
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("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("routes profitability ranking query to deep analysis instead of 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(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 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("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 list_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: "list_open_contracts",
|
||
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 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);
|
||
});
|
||
});
|