ГЛОБАЛЬНЫЙ РЕФАКТОРИНГ АРХИТЕКТУРЫ - Рефакторинг этапов 2.2 - Усилена оркестрацию в deep/address гейте и follow-up binding в assistantService.ts. Починен кейс UTF-8 follow-up refinement (теперь followup_state_usage.applied=true в нужном сценарии). Убраны регрессии по assistantLivingRouter и stage3 lifecycle probe. Корректное поведение для llm canonical candidate (чтобы не уезжало в clarification_required там, где должен быть address factual).

This commit is contained in:
2026-04-11 14:56:14 +03:00
parent 19f5f19d8e
commit b5bd4fd737
46 changed files with 2471 additions and 370 deletions
@@ -261,4 +261,40 @@ describe("assistant orchestration contract", () => {
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);
});
});
@@ -129,4 +129,89 @@ describe("assistant turn runtime deps adapter", () => {
expect(deps.compactWhitespace(" value ")).toBe("value");
expect(helperCompactWhitespace).toHaveBeenCalledWith(" value ");
});
it("preserves method context for stateful service instances", async () => {
class SessionStoreLike {
public calls: string[] = [];
public ensureSession(sessionId: string) {
this.calls.push(`ensure:${sessionId}`);
return { session_id: sessionId } as any;
}
public appendItem(sessionId: string) {
this.calls.push(`append:${sessionId}`);
}
public getSession(sessionId: string) {
this.calls.push(`get:${sessionId}`);
return null;
}
public setInvestigationState(sessionId: string) {
this.calls.push(`state:${sessionId}`);
return null;
}
}
class SessionLoggerLike {
public persisted = 0;
public persistSession() {
this.persisted += 1;
}
}
class NormalizerLike {
public normalized = 0;
public async normalize() {
this.normalized += 1;
return {};
}
}
const sessions = new SessionStoreLike();
const sessionLogger = new SessionLoggerLike();
const normalizerService = new NormalizerLike();
const deps = buildAssistantTurnRuntimeDeps({
sessions,
sessionLogger,
normalizerService,
dataLayer: {
executeRouteRuntime: async () => ({})
},
addressQueryService: {
tryHandle: async () => null
},
chatClient: {},
messageIdFactory: () => "msg-ctx",
nowIso: () => "2026-04-11T00:00:00.000Z",
defaultApiKey: "",
logEvent: () => {},
flags: {
featureAssistantAddressQueryV1: false,
featureAddressLlmPredecomposeV1: false,
featureInvestigationStateV1: false,
featureStateFollowupBindingV1: false,
featureContractsV11: false,
featureAnswerPolicyV11: false,
featureProblemCentricAnswerV1: false,
featureLifecycleAnswerV1: false
},
defaults: {
defaultModel: "model",
defaultBaseUrl: "base"
},
helpers: {
compactWhitespace: (value: unknown) => String(value ?? "")
} as any
});
deps.ensureSession("asst-ctx");
deps.appendItem("asst-ctx", { role: "user" } as any);
deps.getSession("asst-ctx");
deps.setInvestigationState("asst-ctx", null);
deps.persistSession({ session_id: "asst-ctx" } as any);
await deps.normalize({ user_message: "ctx" } as any);
expect(sessions.calls).toEqual(["ensure:asst-ctx", "append:asst-ctx", "get:asst-ctx", "state:asst-ctx"]);
expect(sessionLogger.persisted).toBe(1);
expect(normalizerService.normalized).toBe(1);
});
});