ГЛОБАЛЬНЫЙ РЕФАКТОРИНГ АРХИТЕКТУРЫ - Рефакторинг этапов 2.47: вынесен верхний orchestration-скелет handleMessage (bootstrap -> address attempt -> deep attempt) в единый turn runtime adapter без изменения поведения.

This commit is contained in:
2026-04-11 00:31:17 +03:00
parent 5f4e898c7c
commit 90d529f79b
6 changed files with 514 additions and 237 deletions
@@ -19,6 +19,7 @@ import * as assistantCanon_1 from "./assistantCanon";
import * as assistantAddressAttemptRuntimeAdapter_1 from "./assistantAddressAttemptRuntimeAdapter";
import * as assistantCoverageGrounding_1 from "./assistantCoverageGrounding";
import * as assistantDeepTurnAttemptRuntimeAdapter_1 from "./assistantDeepTurnAttemptRuntimeAdapter";
import * as assistantTurnAttemptRuntimeAdapter_1 from "./assistantTurnAttemptRuntimeAdapter";
import * as assistantUserTurnBootstrapRuntimeAdapter_1 from "./assistantUserTurnBootstrapRuntimeAdapter";
import * as assistantQueryPlanning_1 from "./assistantQueryPlanning";
import iconv from "iconv-lite";
@@ -4324,125 +4325,124 @@ export class AssistantService {
return this.sessions.getSession(sessionId);
}
async handleMessage(payload) {
const { session, sessionId, userMessage, runtimeAnalysisContext, userItem } = (0, assistantUserTurnBootstrapRuntimeAdapter_1.runAssistantUserTurnBootstrapRuntime)({
const turnRuntime = await (0, assistantTurnAttemptRuntimeAdapter_1.runAssistantTurnAttemptRuntime)({
payload,
ensureSession: (targetSessionId) => this.sessions.ensureSession(targetSessionId),
appendItem: (targetSessionId, item) => this.sessions.appendItem(targetSessionId, item),
getSession: (targetSessionId) => this.sessions.getSession(targetSessionId),
persistSession: (sessionState) => this.sessionLogger.persistSession(sessionState),
compactWhitespace,
repairAddressMojibake,
resolveRuntimeAnalysisContext,
messageIdFactory: () => `msg-${(0, nanoid_1.nanoid)(10)}`,
nowIso: () => new Date().toISOString()
runUserTurnBootstrapRuntime: (runtimePayload) => (0, assistantUserTurnBootstrapRuntimeAdapter_1.runAssistantUserTurnBootstrapRuntime)({
payload: runtimePayload,
ensureSession: (targetSessionId) => this.sessions.ensureSession(targetSessionId),
appendItem: (targetSessionId, item) => this.sessions.appendItem(targetSessionId, item),
getSession: (targetSessionId) => this.sessions.getSession(targetSessionId),
persistSession: (sessionState) => this.sessionLogger.persistSession(sessionState),
compactWhitespace,
repairAddressMojibake,
resolveRuntimeAnalysisContext,
messageIdFactory: () => `msg-${(0, nanoid_1.nanoid)(10)}`,
nowIso: () => new Date().toISOString()
}),
resolveSessionOrganizationScopeContext: (runtimeUserMessage, sessionItems) => resolveSessionOrganizationScopeContext(runtimeUserMessage, sessionItems),
runAddressAttemptRuntime: async (runtimeInput) => (0, assistantAddressAttemptRuntimeAdapter_1.runAssistantAddressAttemptRuntime)({
featureAssistantAddressQueryV1: config_1.FEATURE_ASSISTANT_ADDRESS_QUERY_V1,
sessionId: runtimeInput.sessionId,
userMessage: runtimeInput.userMessage,
sessionItems: runtimeInput.sessionItems,
payload: runtimeInput.payload,
sessionScope: {
knownOrganizations: runtimeInput.sessionOrganizationScope.knownOrganizations,
selectedOrganization: runtimeInput.sessionOrganizationScope.selectedOrganization,
activeOrganization: runtimeInput.sessionOrganizationScope.activeOrganization
},
featureAddressLlmPredecomposeV1: config_1.FEATURE_ASSISTANT_ADDRESS_QUERY_LLM_PREDECOMPOSE_V1,
runAddressLlmPreDecompose: async () => runAddressLlmPreDecompose(this.normalizerService, runtimeInput.payload, runtimeInput.userMessage),
buildAddressLlmPredecomposeContractV1: predecomposeContract_1.buildAddressLlmPredecomposeContractV1,
sanitizeAddressMessageForFallback,
toNonEmptyString,
resolveAddressFollowupCarryoverContext,
resolveAssistantOrchestrationDecision,
buildAddressDialogContinuationContractV2,
runtimeAnalysisContextAsOfDate: runtimeInput.runtimeAnalysisContext.as_of_date,
compactWhitespace,
mergeFollowupContextWithOrganizationScope,
runAddressQueryTryHandle: (laneMessageUsed, options) => this.addressQueryService.tryHandle(laneMessageUsed, options),
isRetryableAddressLimitedResult,
mergeKnownOrganizations,
hasAssistantDataScopeMetaQuestionSignal,
shouldHandleAsAssistantCapabilityMetaQuery,
hasDestructiveDataActionSignal,
hasDangerOrCoercionSignal,
hasOperationalAdminActionRequestSignal,
hasOrganizationFactLookupSignal,
hasOrganizationFactFollowupSignal,
shouldEmitOrganizationSelectionReply,
hasAssistantCapabilityQuestionSignal,
resolveDataScopeProbe: () => resolveAssistantDataScopeProbe(),
applyScriptGuard: (chatText, runtimeUserMessage) => applyLivingChatScriptGuard(chatText, runtimeUserMessage),
applyGroundingGuard: (guardInput) => applyLivingChatGroundingGuard(guardInput),
buildAssistantSafetyRefusalReply,
buildAssistantDataScopeContractReply,
buildAssistantOrganizationFactBoundaryReply,
buildAssistantDataScopeSelectionReply,
buildAssistantOperationalBoundaryReply,
buildAssistantCapabilityContractReply,
chatClient: this.chatClient,
loadAssistantCanonExcerpt: assistantCanon_1.loadAssistantCanonExcerpt,
sanitizeOutgoingAssistantText,
defaultModel: config_1.DEFAULT_MODEL,
defaultBaseUrl: config_1.DEFAULT_OPENAI_BASE_URL,
defaultApiKey: process.env.OPENAI_API_KEY ?? "",
buildAddressDebugPayload,
buildAddressFollowupOffer,
appendItem: (targetSessionId, item) => this.sessions.appendItem(targetSessionId, item),
getSession: (targetSessionId) => this.sessions.getSession(targetSessionId),
persistSession: (sessionState) => this.sessionLogger.persistSession(sessionState),
cloneConversation: (items) => cloneItems(items),
logEvent: (runtimePayload) => (0, log_1.logJson)(runtimePayload),
messageIdFactory: () => `msg-${(0, nanoid_1.nanoid)(10)}`,
nowIso: () => new Date().toISOString()
}),
runDeepTurnAttemptRuntime: async (runtimeInput) => (0, assistantDeepTurnAttemptRuntimeAdapter_1.runAssistantDeepTurnAttemptRuntime)({
sessionId: runtimeInput.sessionId,
questionId: runtimeInput.questionId,
userMessage: runtimeInput.userMessage,
payload: runtimeInput.payload,
runtimeAnalysisContext: runtimeInput.runtimeAnalysisContext,
sessionInvestigationState: runtimeInput.sessionInvestigationState,
addressRuntimeMetaForDeep: runtimeInput.addressRuntimeMetaForDeep,
featureInvestigationStateV1: config_1.FEATURE_ASSISTANT_INVESTIGATION_STATE_V1,
featureStateFollowupBindingV1: config_1.FEATURE_ASSISTANT_STATE_FOLLOWUP_BINDING_V1,
featureContractsV11: config_1.FEATURE_ASSISTANT_CONTRACTS_V11,
featureAnswerPolicyV11: config_1.FEATURE_ASSISTANT_ANSWER_POLICY_V11,
featureProblemCentricAnswerV1: config_1.FEATURE_ASSISTANT_PROBLEM_CENTRIC_ANSWER_V1,
featureLifecycleAnswerV1: config_1.FEATURE_ASSISTANT_LIFECYCLE_ANSWER_V1,
buildFollowupStateBinding,
normalize: (normalizePayload) => this.normalizerService.normalize(normalizePayload),
resolveBusinessScopeAlignment,
inferP0DomainFromMessage,
resolveBusinessScopeFromLiveContext,
extractRequirements,
toExecutionPlan,
enforceRbpLiveRoutePlan,
enforceFaLiveRoutePlan,
executeRouteRuntime: (route, fragmentText, options) => this.dataLayer.executeRouteRuntime(route, fragmentText, options),
mapNoRouteReason,
buildSkippedResult,
evaluateCoverage,
checkGrounding,
collectRbpLiveRouteAudit,
collectFaLiveRouteAudit,
hasExplicitPeriodAnchor: (normalizedPayload) => hasExplicitPeriodAnchorFromNormalized(normalizedPayload),
extractDroppedIntentSegments: (normalizedPayload) => extractDiscardedIntentSegments(normalizedPayload),
buildDebugRoutes: (routeSummary) => toDebugRoutes(routeSummary),
extractExecutionState: (normalizedPayload) => extractExecutionState(normalizedPayload),
sanitizeReply: (value, fallback) => sanitizeOutgoingAssistantText(value, fallback),
persistInvestigationState: (targetSessionId, snapshot) => this.sessions.setInvestigationState(targetSessionId, snapshot),
messageIdFactory: () => `msg-${(0, nanoid_1.nanoid)(10)}`,
appendItem: (targetSessionId, item) => this.sessions.appendItem(targetSessionId, item),
getSession: (targetSessionId) => this.sessions.getSession(targetSessionId),
persistSession: (sessionState) => this.sessionLogger.persistSession(sessionState),
cloneConversation: (items) => cloneItems(items),
logEvent: (runtimePayload) => (0, log_1.logJson)(runtimePayload)
})
});
const sessionOrganizationScope = resolveSessionOrganizationScopeContext(userMessage, session.items);
const addressRuntime = await (0, assistantAddressAttemptRuntimeAdapter_1.runAssistantAddressAttemptRuntime)({
featureAssistantAddressQueryV1: config_1.FEATURE_ASSISTANT_ADDRESS_QUERY_V1,
sessionId,
userMessage,
sessionItems: session.items,
payload,
sessionScope: {
knownOrganizations: sessionOrganizationScope.knownOrganizations,
selectedOrganization: sessionOrganizationScope.selectedOrganization,
activeOrganization: sessionOrganizationScope.activeOrganization
},
featureAddressLlmPredecomposeV1: config_1.FEATURE_ASSISTANT_ADDRESS_QUERY_LLM_PREDECOMPOSE_V1,
runAddressLlmPreDecompose: async () => runAddressLlmPreDecompose(this.normalizerService, payload, userMessage),
buildAddressLlmPredecomposeContractV1: predecomposeContract_1.buildAddressLlmPredecomposeContractV1,
sanitizeAddressMessageForFallback,
toNonEmptyString,
resolveAddressFollowupCarryoverContext,
resolveAssistantOrchestrationDecision,
buildAddressDialogContinuationContractV2,
runtimeAnalysisContextAsOfDate: runtimeAnalysisContext.as_of_date,
compactWhitespace,
mergeFollowupContextWithOrganizationScope,
runAddressQueryTryHandle: (laneMessageUsed, options) => this.addressQueryService.tryHandle(laneMessageUsed, options),
isRetryableAddressLimitedResult,
mergeKnownOrganizations,
hasAssistantDataScopeMetaQuestionSignal,
shouldHandleAsAssistantCapabilityMetaQuery,
hasDestructiveDataActionSignal,
hasDangerOrCoercionSignal,
hasOperationalAdminActionRequestSignal,
hasOrganizationFactLookupSignal,
hasOrganizationFactFollowupSignal,
shouldEmitOrganizationSelectionReply,
hasAssistantCapabilityQuestionSignal,
resolveDataScopeProbe: () => resolveAssistantDataScopeProbe(),
applyScriptGuard: (chatText, runtimeUserMessage) => applyLivingChatScriptGuard(chatText, runtimeUserMessage),
applyGroundingGuard: (guardInput) => applyLivingChatGroundingGuard(guardInput),
buildAssistantSafetyRefusalReply,
buildAssistantDataScopeContractReply,
buildAssistantOrganizationFactBoundaryReply,
buildAssistantDataScopeSelectionReply,
buildAssistantOperationalBoundaryReply,
buildAssistantCapabilityContractReply,
chatClient: this.chatClient,
loadAssistantCanonExcerpt: assistantCanon_1.loadAssistantCanonExcerpt,
sanitizeOutgoingAssistantText,
defaultModel: config_1.DEFAULT_MODEL,
defaultBaseUrl: config_1.DEFAULT_OPENAI_BASE_URL,
defaultApiKey: process.env.OPENAI_API_KEY ?? "",
buildAddressDebugPayload,
buildAddressFollowupOffer,
appendItem: (targetSessionId, item) => this.sessions.appendItem(targetSessionId, item),
getSession: (targetSessionId) => this.sessions.getSession(targetSessionId),
persistSession: (sessionState) => this.sessionLogger.persistSession(sessionState),
cloneConversation: (items) => cloneItems(items),
logEvent: (payload) => (0, log_1.logJson)(payload),
messageIdFactory: () => `msg-${(0, nanoid_1.nanoid)(10)}`,
nowIso: () => new Date().toISOString()
});
const addressRuntimeMetaForDeep = addressRuntime.addressRuntimeMetaForDeep;
if (addressRuntime.handled && addressRuntime.response) {
return addressRuntime.response;
}
const deepTurnRuntime = await (0, assistantDeepTurnAttemptRuntimeAdapter_1.runAssistantDeepTurnAttemptRuntime)({
sessionId,
questionId: userItem.message_id,
userMessage,
payload,
runtimeAnalysisContext,
sessionInvestigationState: session.investigation_state,
addressRuntimeMetaForDeep,
featureInvestigationStateV1: config_1.FEATURE_ASSISTANT_INVESTIGATION_STATE_V1,
featureStateFollowupBindingV1: config_1.FEATURE_ASSISTANT_STATE_FOLLOWUP_BINDING_V1,
featureContractsV11: config_1.FEATURE_ASSISTANT_CONTRACTS_V11,
featureAnswerPolicyV11: config_1.FEATURE_ASSISTANT_ANSWER_POLICY_V11,
featureProblemCentricAnswerV1: config_1.FEATURE_ASSISTANT_PROBLEM_CENTRIC_ANSWER_V1,
featureLifecycleAnswerV1: config_1.FEATURE_ASSISTANT_LIFECYCLE_ANSWER_V1,
buildFollowupStateBinding,
normalize: (normalizePayload) => this.normalizerService.normalize(normalizePayload),
resolveBusinessScopeAlignment,
inferP0DomainFromMessage,
resolveBusinessScopeFromLiveContext,
extractRequirements,
toExecutionPlan,
enforceRbpLiveRoutePlan,
enforceFaLiveRoutePlan,
executeRouteRuntime: (route, fragmentText, options) => this.dataLayer.executeRouteRuntime(route, fragmentText, options),
mapNoRouteReason,
buildSkippedResult,
evaluateCoverage,
checkGrounding,
collectRbpLiveRouteAudit,
collectFaLiveRouteAudit,
hasExplicitPeriodAnchor: (normalizedPayload) => hasExplicitPeriodAnchorFromNormalized(normalizedPayload),
extractDroppedIntentSegments: (normalizedPayload) => extractDiscardedIntentSegments(normalizedPayload),
buildDebugRoutes: (routeSummary) => toDebugRoutes(routeSummary),
extractExecutionState: (normalizedPayload) => extractExecutionState(normalizedPayload),
sanitizeReply: (value, fallback) => sanitizeOutgoingAssistantText(value, fallback),
persistInvestigationState: (targetSessionId, snapshot) => this.sessions.setInvestigationState(targetSessionId, snapshot),
messageIdFactory: () => `msg-${(0, nanoid_1.nanoid)(10)}`,
appendItem: (targetSessionId, item) => this.sessions.appendItem(targetSessionId, item),
getSession: (targetSessionId) => this.sessions.getSession(targetSessionId),
persistSession: (sessionState) => this.sessionLogger.persistSession(sessionState),
cloneConversation: (items) => cloneItems(items),
logEvent: (payload) => (0, log_1.logJson)(payload)
});
return deepTurnRuntime.response;
return turnRuntime.response;
}
}
@@ -0,0 +1,97 @@
import type { RunAssistantAddressRuntimeOutput } from "./assistantAddressRuntimeAdapter";
import type { RunAssistantUserTurnBootstrapRuntimeOutput } from "./assistantUserTurnBootstrapRuntimeAdapter";
export interface AssistantSessionOrganizationScopeContext {
knownOrganizations: string[];
selectedOrganization: string | null;
activeOrganization: string | null;
}
export interface RunAssistantTurnAttemptRuntimeAddressInput<PayloadType = unknown> {
payload: PayloadType;
sessionId: string;
userMessage: string;
sessionItems: unknown[];
runtimeAnalysisContext: { as_of_date: string | null };
sessionOrganizationScope: AssistantSessionOrganizationScopeContext;
}
export interface RunAssistantTurnAttemptRuntimeDeepInput<PayloadType = unknown> {
payload: PayloadType;
sessionId: string;
questionId: string;
userMessage: string;
runtimeAnalysisContext: unknown;
sessionInvestigationState: unknown;
addressRuntimeMetaForDeep: Record<string, unknown> | null;
}
export interface RunAssistantTurnAttemptRuntimeInput<ResponseType = unknown, PayloadType = unknown> {
payload: PayloadType;
runUserTurnBootstrapRuntime: (payload: PayloadType) => RunAssistantUserTurnBootstrapRuntimeOutput;
resolveSessionOrganizationScopeContext: (
userMessage: string,
sessionItems: unknown[]
) => AssistantSessionOrganizationScopeContext;
runAddressAttemptRuntime: (
input: RunAssistantTurnAttemptRuntimeAddressInput<PayloadType>
) => Promise<RunAssistantAddressRuntimeOutput<ResponseType>>;
runDeepTurnAttemptRuntime: (
input: RunAssistantTurnAttemptRuntimeDeepInput<PayloadType>
) => Promise<{ response: ResponseType }>;
}
export interface RunAssistantTurnAttemptRuntimeOutput<ResponseType = unknown> {
response: ResponseType;
source: "address" | "deep";
addressRuntimeMetaForDeep: Record<string, unknown> | null;
userTurn: RunAssistantUserTurnBootstrapRuntimeOutput;
sessionOrganizationScope: AssistantSessionOrganizationScopeContext;
}
export async function runAssistantTurnAttemptRuntime<ResponseType = unknown, PayloadType = unknown>(
input: RunAssistantTurnAttemptRuntimeInput<ResponseType, PayloadType>
): Promise<RunAssistantTurnAttemptRuntimeOutput<ResponseType>> {
const userTurn = input.runUserTurnBootstrapRuntime(input.payload);
const sessionOrganizationScope = input.resolveSessionOrganizationScopeContext(
userTurn.userMessage,
userTurn.session.items
);
const addressRuntime = await input.runAddressAttemptRuntime({
payload: input.payload,
sessionId: userTurn.sessionId,
userMessage: userTurn.userMessage,
sessionItems: userTurn.session.items,
runtimeAnalysisContext: userTurn.runtimeAnalysisContext,
sessionOrganizationScope
});
const addressRuntimeMetaForDeep = addressRuntime.addressRuntimeMetaForDeep ?? null;
if (addressRuntime.handled && addressRuntime.response) {
return {
response: addressRuntime.response,
source: "address",
addressRuntimeMetaForDeep,
userTurn,
sessionOrganizationScope
};
}
const deepTurnRuntime = await input.runDeepTurnAttemptRuntime({
payload: input.payload,
sessionId: userTurn.sessionId,
questionId: userTurn.userItem.message_id,
userMessage: userTurn.userMessage,
runtimeAnalysisContext: userTurn.runtimeAnalysisContext,
sessionInvestigationState: userTurn.session.investigation_state,
addressRuntimeMetaForDeep
});
return {
response: deepTurnRuntime.response,
source: "deep",
addressRuntimeMetaForDeep,
userTurn,
sessionOrganizationScope
};
}