ГЛОБАЛЬНЫЙ РЕФАКТОРИНГ АРХИТЕКТУРЫ - Рефакторинг этапов 2.34 вынос address-ветку (orchestration + lane + finalize) в единый runtime-оркестратор, чтобы handleMessage стал почти плоским.
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { runAssistantAddressLaneResponseRuntime } from "../src/services/assistantAddressLaneResponseRuntimeAdapter";
|
||||
|
||||
describe("assistant address lane response runtime adapter", () => {
|
||||
it("builds debug payload and finalizes address turn", () => {
|
||||
const finalizeAddressTurn = vi.fn(() => ({
|
||||
response: {
|
||||
ok: true
|
||||
}
|
||||
}));
|
||||
|
||||
const runtime = runAssistantAddressLaneResponseRuntime({
|
||||
sessionId: "asst-1",
|
||||
userMessage: "raw",
|
||||
effectiveAddressUserMessage: "canon",
|
||||
addressLane: {
|
||||
handled: true,
|
||||
reply_text: "answer",
|
||||
reply_type: "factual",
|
||||
debug: {
|
||||
extracted_filters: {
|
||||
organization: "ООО Ромашка"
|
||||
}
|
||||
}
|
||||
},
|
||||
carryoverMeta: {
|
||||
followupContext: {
|
||||
previous_intent: "list_documents"
|
||||
}
|
||||
},
|
||||
llmPreDecomposeMeta: {
|
||||
attempted: true
|
||||
},
|
||||
knownOrganizations: ["ООО Ромашка", "ООО Лютик"],
|
||||
activeOrganization: "ООО Ромашка",
|
||||
sanitizeOutgoingAssistantText: (text) => String(text ?? "").trim(),
|
||||
buildAddressDebugPayload: (addressDebug) => ({ ...(addressDebug as Record<string, unknown>) }),
|
||||
buildAddressFollowupOffer: () => ({ suggestion: "continue_previous" }),
|
||||
mergeKnownOrganizations: (items) => Array.from(new Set(items)),
|
||||
toNonEmptyString: (value) => (typeof value === "string" && value.trim() ? value.trim() : null),
|
||||
appendItem: () => {},
|
||||
getSession: () => ({ session_id: "asst-1", updated_at: "", items: [], investigation_state: null } as any),
|
||||
persistSession: () => {},
|
||||
cloneConversation: (items) => items,
|
||||
logEvent: () => {},
|
||||
messageIdFactory: () => "msg-1",
|
||||
finalizeAddressTurn
|
||||
});
|
||||
|
||||
expect(finalizeAddressTurn).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
assistantReply: "answer",
|
||||
replyType: "factual",
|
||||
llmPreDecomposeMeta: {
|
||||
attempted: true
|
||||
}
|
||||
})
|
||||
);
|
||||
expect(runtime.response).toEqual({ ok: true });
|
||||
expect(runtime.debug).toEqual(
|
||||
expect.objectContaining({
|
||||
assistant_known_organizations: ["ООО Ромашка", "ООО Лютик"],
|
||||
assistant_active_organization: "ООО Ромашка",
|
||||
address_followup_offer: { suggestion: "continue_previous" }
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps debug minimal when optional enrichment is absent", () => {
|
||||
const runtime = runAssistantAddressLaneResponseRuntime({
|
||||
sessionId: "asst-2",
|
||||
userMessage: "raw",
|
||||
effectiveAddressUserMessage: "raw",
|
||||
addressLane: {
|
||||
handled: true,
|
||||
reply_text: "answer",
|
||||
reply_type: "partial_coverage",
|
||||
debug: {}
|
||||
},
|
||||
knownOrganizations: [],
|
||||
activeOrganization: null,
|
||||
sanitizeOutgoingAssistantText: (text) => String(text ?? ""),
|
||||
buildAddressDebugPayload: () => ({}),
|
||||
buildAddressFollowupOffer: () => null,
|
||||
mergeKnownOrganizations: (items) => items,
|
||||
toNonEmptyString: () => null,
|
||||
appendItem: () => {},
|
||||
getSession: () => ({ session_id: "asst-2", updated_at: "", items: [], investigation_state: null } as any),
|
||||
persistSession: () => {},
|
||||
cloneConversation: (items) => items,
|
||||
logEvent: () => {},
|
||||
messageIdFactory: () => "msg-2",
|
||||
finalizeAddressTurn: () => ({
|
||||
response: {
|
||||
ok: true
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
expect(runtime.debug).toEqual({});
|
||||
expect(runtime.response).toEqual({ ok: true });
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,196 @@
|
||||
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");
|
||||
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
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user