238 lines
10 KiB
TypeScript
238 lines
10 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 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("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("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
|
||
);
|
||
});
|
||
});
|