NODEDC_1C/llm_normalizer/backend/tests/assistantAddressRuntimeAdap...

280 lines
9.9 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import { runAssistantAddressRuntime } from "../src/services/assistantAddressRuntimeAdapter";
describe("assistant address runtime adapter", () => {
it("returns unhandled when address feature is disabled", async () => {
const result = await runAssistantAddressRuntime({
featureAssistantAddressQueryV1: false,
sessionId: "asst-1",
userMessage: "question",
sessionItems: [],
llmProvider: "openai",
useMock: false,
featureAddressLlmPredecomposeV1: true,
runAddressLlmPreDecompose: async () => ({}),
buildAddressLlmPredecomposeContractV1: () => ({}),
sanitizeAddressMessageForFallback: (value) => value,
toNonEmptyString: () => null,
resolveAddressFollowupCarryoverContext: () => null,
resolveAssistantOrchestrationDecision: () => ({}),
buildAddressDialogContinuationContractV2: () => ({}),
runtimeAnalysisContextAsOfDate: null,
payloadContextPeriodHint: null,
compactWhitespace: (value) => value.trim(),
runAddressLaneAttempt: async () => null,
isRetryableAddressLimitedResult: () => false,
finalizeAddressLaneResponse: () => ({ ok: true }),
tryHandleLivingChat: async () => null,
logEvent: () => {},
nowIso: () => "2026-04-10T00:00:00.000Z"
});
expect(result).toEqual({
handled: false,
response: null,
addressRuntimeMetaForDeep: null
});
});
it("returns early when tool-gate chat fallback handles", async () => {
const runAddressOrchestrationRuntime = vi.fn(async () => ({
addressPreDecompose: {},
addressInputMessage: "canon",
carryover: null,
orchestrationDecision: { runAddressLane: false },
addressRuntimeMeta: { attempted: true },
livingModeDecision: { mode: "chat", reason: "x" }
}));
const runAddressToolGateRuntime = vi.fn(async () => ({
handled: true,
response: { ok: "chat" }
}));
const runAddressLaneRuntime = vi.fn(async () => ({
handled: false,
selection: null,
retryAudit: {
attempted: false,
reason: null,
initial_limited_category: null,
retry_message: null,
retry_used_followup_context: false,
retry_result_category: null
}
}));
const result = await runAssistantAddressRuntime({
featureAssistantAddressQueryV1: true,
sessionId: "asst-2",
userMessage: "question",
sessionItems: [],
llmProvider: "openai",
useMock: false,
featureAddressLlmPredecomposeV1: true,
runAddressLlmPreDecompose: async () => ({}),
buildAddressLlmPredecomposeContractV1: () => ({}),
sanitizeAddressMessageForFallback: (value) => value,
toNonEmptyString: () => null,
resolveAddressFollowupCarryoverContext: () => null,
resolveAssistantOrchestrationDecision: () => ({}),
buildAddressDialogContinuationContractV2: () => ({}),
runtimeAnalysisContextAsOfDate: null,
payloadContextPeriodHint: null,
compactWhitespace: (value) => value.trim(),
runAddressLaneAttempt: async () => null,
isRetryableAddressLimitedResult: () => false,
finalizeAddressLaneResponse: () => ({ ok: true }),
tryHandleLivingChat: async () => ({ ok: true }),
logEvent: () => {},
nowIso: () => "2026-04-10T00:00:00.000Z",
runAddressOrchestrationRuntime,
runAddressToolGateRuntime,
runAddressLaneRuntime
});
expect(result.handled).toBe(true);
expect(result.response).toEqual({ ok: "chat" });
expect(result.addressRuntimeMetaForDeep).toEqual({ attempted: true });
expect(runAddressLaneRuntime).not.toHaveBeenCalled();
});
it("finalizes address lane when lane runtime resolves handled selection", async () => {
const runAddressLaneAttempt = vi.fn(async () => ({
handled: true
}));
const finalizeAddressLaneResponse = vi.fn(() => ({ ok: "address" }));
const runAddressLaneRuntime = vi.fn(async (input) => {
await input.runAddressLaneAttempt("canon", null);
return {
handled: true,
selection: {
addressLane: {
handled: true
},
messageUsed: "canon",
carryMeta: null
},
retryAudit: {
attempted: true,
reason: "limited_result_retry_with_raw_message",
initial_limited_category: "missing_anchor",
retry_message: "raw",
retry_used_followup_context: false,
retry_result_category: null
}
};
});
const result = await runAssistantAddressRuntime({
featureAssistantAddressQueryV1: true,
sessionId: "asst-3",
userMessage: "raw question",
sessionItems: [],
llmProvider: "openai",
useMock: false,
featureAddressLlmPredecomposeV1: true,
runAddressLlmPreDecompose: async () => ({}),
buildAddressLlmPredecomposeContractV1: () => ({}),
sanitizeAddressMessageForFallback: (value) => value,
toNonEmptyString: (value) => (typeof value === "string" && value.trim() ? value.trim() : null),
resolveAddressFollowupCarryoverContext: () => ({
followupContext: {
intent: "x"
}
}),
resolveAssistantOrchestrationDecision: () => ({}),
buildAddressDialogContinuationContractV2: () => ({}),
runtimeAnalysisContextAsOfDate: null,
payloadContextPeriodHint: "2020-07-31",
compactWhitespace: (value) => value.replace(/\s+/g, " ").trim(),
runAddressLaneAttempt,
isRetryableAddressLimitedResult: () => false,
finalizeAddressLaneResponse,
tryHandleLivingChat: async () => null,
logEvent: () => {},
nowIso: () => "2026-04-10T00:00:00.000Z",
runAddressOrchestrationRuntime: async () => ({
addressPreDecompose: {},
addressInputMessage: "canon",
carryover: {
followupContext: {
intent: "x"
}
},
orchestrationDecision: { runAddressLane: true },
addressRuntimeMeta: {
attempted: true
},
livingModeDecision: { mode: "deep_analysis", reason: "x" }
}),
runAddressToolGateRuntime: async () => ({
handled: false,
response: null
}),
runAddressLaneRuntime
});
expect(runAddressLaneAttempt).toHaveBeenCalledWith("canon", null, "2020-07-31", null);
expect(finalizeAddressLaneResponse).toHaveBeenCalledWith(
{ handled: true },
"canon",
null,
expect.objectContaining({
attempted: true,
addressRetryAudit: expect.objectContaining({
attempted: true
})
})
);
expect(result).toEqual({
handled: true,
response: { ok: "address" },
addressRuntimeMetaForDeep: {
attempted: true
}
});
});
it("passes llm semantic hints from orchestration metadata into lane attempts", async () => {
const runAddressLaneAttempt = vi.fn(async () => ({
handled: true
}));
const result = await runAssistantAddressRuntime({
featureAssistantAddressQueryV1: true,
sessionId: "asst-4",
userMessage: "что на складе конторы альтернатива",
sessionItems: [],
llmProvider: "local",
useMock: false,
featureAddressLlmPredecomposeV1: true,
runAddressLlmPreDecompose: async () => ({}),
buildAddressLlmPredecomposeContractV1: () => ({}),
sanitizeAddressMessageForFallback: (value) => value,
toNonEmptyString: (value) => (typeof value === "string" && value.trim() ? value.trim() : null),
resolveAddressFollowupCarryoverContext: () => null,
resolveAssistantOrchestrationDecision: () => ({}),
buildAddressDialogContinuationContractV2: () => ({}),
runtimeAnalysisContextAsOfDate: null,
payloadContextPeriodHint: null,
compactWhitespace: (value) => value.replace(/\s+/g, " ").trim(),
runAddressLaneAttempt,
isRetryableAddressLimitedResult: () => false,
finalizeAddressLaneResponse: () => ({ ok: "address" }),
tryHandleLivingChat: async () => null,
logEvent: () => {},
nowIso: () => "2026-04-10T00:00:00.000Z",
runAddressOrchestrationRuntime: async () => ({
addressPreDecompose: {},
addressInputMessage: "что на складе конторы альтернатива",
carryover: null,
orchestrationDecision: { runAddressLane: true },
addressRuntimeMeta: {
attempted: true,
semanticHints: {
scope_target_kind: "organization",
scope_target_text: "Альтернатива",
date_scope_kind: "implicit_current",
self_scope_detected: false,
selected_object_scope_detected: false
}
},
livingModeDecision: { mode: "address_data", reason: "address_lane_triggered" }
}),
runAddressToolGateRuntime: async () => ({
handled: false,
response: null
}),
runAddressLaneRuntime: async (input) => {
const addressLane = await input.runAddressLaneAttempt(input.addressInputMessage, null, input.llmSemanticHints ?? null);
return {
handled: true,
selection: {
addressLane: addressLane ?? { handled: true },
messageUsed: input.addressInputMessage,
carryMeta: null
},
retryAudit: {
attempted: false,
reason: null,
initial_limited_category: null,
retry_message: null,
retry_used_followup_context: false,
retry_result_category: null
}
};
}
});
expect(runAddressLaneAttempt).toHaveBeenCalledWith(
"что на складе конторы альтернатива",
null,
null,
expect.objectContaining({
scope_target_kind: "organization",
scope_target_text: "Альтернатива"
})
);
expect(result.handled).toBe(true);
});
});