From ca467cdecc8b87b24c3a917b06b6ae55fd955879 Mon Sep 17 00:00:00 2001 From: dctouch Date: Sat, 11 Apr 2026 00:47:38 +0300 Subject: [PATCH] =?UTF-8?q?=D0=93=D0=9B=D0=9E=D0=91=D0=90=D0=9B=D0=AC?= =?UTF-8?q?=D0=9D=D0=AB=D0=99=20=D0=A0=D0=95=D0=A4=D0=90=D0=9A=D0=A2=D0=9E?= =?UTF-8?q?=D0=A0=D0=98=D0=9D=D0=93=20=D0=90=D0=A0=D0=A5=D0=98=D0=A2=D0=95?= =?UTF-8?q?=D0=9A=D0=A2=D0=A3=D0=A0=D0=AB=20-=20=D0=A0=D0=B5=D1=84=D0=B0?= =?UTF-8?q?=D0=BA=D1=82=D0=BE=D1=80=D0=B8=D0=BD=D0=B3=20=D1=8D=D1=82=D0=B0?= =?UTF-8?q?=D0=BF=D0=BE=D0=B2=202.49:=20=D0=B2=D1=8B=D0=BD=D0=BE=D1=81=20t?= =?UTF-8?q?urnRuntimeDeps=20(=D1=84=D0=B0=D0=B1=D1=80=D0=B8=D0=BA=D1=83=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=B2=D0=B8=D1=81=D0=B8=D0=BC=D0=BE=D1=81=D1=82?= =?UTF-8?q?=D0=B5=D0=B9)=20=D0=B8=D0=B7=20assistantService=20=D0=B2=20?= =?UTF-8?q?=D0=BE=D1=82=D0=B4=D0=B5=D0=BB=D1=8C=D0=BD=D1=8B=D0=B9=20deps-a?= =?UTF-8?q?dapter,=20=D1=87=D1=82=D0=BE=D0=B1=D1=8B=20handleMessage=20?= =?UTF-8?q?=D1=81=D1=82=D0=B0=D0=BB=20=D1=81=D0=BE=D0=B2=D1=81=D0=B5=D0=BC?= =?UTF-8?q?=20=D1=82=D0=BE=D0=BD=D0=BA=D0=B8=D0=BC.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/TECH/1CLLMARCH-FACT.md | 37 ++++- .../backend/dist/services/assistantService.js | 150 +++++++++--------- .../assistantTurnRuntimeDepsAdapter.js | 31 ++++ .../backend/src/services/assistantService.ts | 150 +++++++++--------- .../assistantTurnRuntimeDepsAdapter.ts | 112 +++++++++++++ .../assistantTurnRuntimeDepsAdapter.test.ts | 132 +++++++++++++++ 6 files changed, 465 insertions(+), 147 deletions(-) create mode 100644 llm_normalizer/backend/dist/services/assistantTurnRuntimeDepsAdapter.js create mode 100644 llm_normalizer/backend/src/services/assistantTurnRuntimeDepsAdapter.ts create mode 100644 llm_normalizer/backend/tests/assistantTurnRuntimeDepsAdapter.test.ts diff --git a/docs/TECH/1CLLMARCH-FACT.md b/docs/TECH/1CLLMARCH-FACT.md index b349fd7..d540fb1 100644 --- a/docs/TECH/1CLLMARCH-FACT.md +++ b/docs/TECH/1CLLMARCH-FACT.md @@ -1537,7 +1537,42 @@ Validation: - `assistantDeepTurnPackagingRuntimeAdapter.test.ts` - `assistantWave10SettlementCorrectiveRegression.test.ts` -Status: **In progress (Phase 2.1 + 2.2 + 2.3 + 2.4 + 2.5 + 2.6 + 2.7 + 2.8 + 2.9 + 2.10 + 2.11 + 2.12 + 2.13 + 2.14 + 2.15 + 2.16 + 2.17 + 2.18 + 2.19 + 2.20 + 2.21 + 2.22 + 2.23 + 2.24 + 2.25 + 2.26 + 2.27 + 2.28 + 2.29 + 2.30 + 2.31 + 2.32 + 2.33 + 2.34 + 2.35 + 2.36 + 2.37 + 2.38 + 2.39 + 2.40 + 2.41 + 2.42 + 2.43 + 2.44 + 2.45 + 2.46 + 2.47 + 2.48 completed)** +Implemented in current pass (Phase 2.49): +1. Extracted turn runtime dependency factory from `assistantService` into dedicated adapter: + - `assistantTurnRuntimeDepsAdapter.ts` + - introduced: + - `buildAssistantTurnRuntimeDeps(...)` +2. Rewired `assistantService.handleMessage` to construct runtime deps via adapter (behavior-preserving): + - service-level wrappers for `sessions`, `sessionLogger`, `normalizerService`, `dataLayer`, `addressQueryService` moved under deps-adapter boundary; + - flags/defaults/helpers remain unchanged semantically and are passed as structured groups (`flags`, `defaults`, `helpers`). +3. Added focused unit tests: + - `assistantTurnRuntimeDepsAdapter.test.ts` + +Validation: +1. `npm run build` passed. +2. Targeted living/address/deep followup pack passed: + - `assistantTurnRuntimeDepsAdapter.test.ts` + - `assistantTurnRuntimeInputBuilder.test.ts` + - `assistantTurnAttemptRuntimeAdapter.test.ts` + - `assistantAddressAttemptRuntimeAdapter.test.ts` + - `assistantDeepTurnAttemptRuntimeAdapter.test.ts` + - `assistantDeepTurnResponseAttemptRuntimeAdapter.test.ts` + - `assistantDeepTurnAnalysisAttemptRuntimeAdapter.test.ts` + - `assistantDeepTurnAnalysisRuntimeAdapter.test.ts` + - `assistantAddressLaneResponseAttemptRuntimeAdapter.test.ts` + - `assistantLivingChatAttemptRuntimeAdapter.test.ts` + - `assistantAddressLaneAttemptRuntimeAdapter.test.ts` + - `assistantUserTurnBootstrapRuntimeAdapter.test.ts` + - `assistantLivingChatLlmRuntimeAdapter.test.ts` + - `assistantLivingChatHandlerRuntimeAdapter.test.ts` + - `assistantLivingChatRuntimeAdapter.test.ts` + - `assistantAddressRuntimeAdapter.test.ts` + - `assistantAddressLaneResponseRuntimeAdapter.test.ts` + - `assistantDeepTurnResponseRuntimeAdapter.test.ts` + - `assistantDeepTurnPackagingRuntimeAdapter.test.ts` + - `assistantWave10SettlementCorrectiveRegression.test.ts` + +Status: **In progress (Phase 2.1 + 2.2 + 2.3 + 2.4 + 2.5 + 2.6 + 2.7 + 2.8 + 2.9 + 2.10 + 2.11 + 2.12 + 2.13 + 2.14 + 2.15 + 2.16 + 2.17 + 2.18 + 2.19 + 2.20 + 2.21 + 2.22 + 2.23 + 2.24 + 2.25 + 2.26 + 2.27 + 2.28 + 2.29 + 2.30 + 2.31 + 2.32 + 2.33 + 2.34 + 2.35 + 2.36 + 2.37 + 2.38 + 2.39 + 2.40 + 2.41 + 2.42 + 2.43 + 2.44 + 2.45 + 2.46 + 2.47 + 2.48 + 2.49 completed)** ## Stage 3 (P2): Hybrid Semantic Layer (LLM + Deterministic Guards) diff --git a/llm_normalizer/backend/dist/services/assistantService.js b/llm_normalizer/backend/dist/services/assistantService.js index 75ad4b2..c1699e6 100644 --- a/llm_normalizer/backend/dist/services/assistantService.js +++ b/llm_normalizer/backend/dist/services/assistantService.js @@ -66,6 +66,7 @@ const assistantAddressAttemptRuntimeAdapter_1 = __importStar(require("./assistan const assistantCoverageGrounding_1 = __importStar(require("./assistantCoverageGrounding")); const assistantDeepTurnAttemptRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnAttemptRuntimeAdapter")); const assistantTurnAttemptRuntimeAdapter_1 = __importStar(require("./assistantTurnAttemptRuntimeAdapter")); +const assistantTurnRuntimeDepsAdapter_1 = __importStar(require("./assistantTurnRuntimeDepsAdapter")); const assistantTurnRuntimeInputBuilder_1 = __importStar(require("./assistantTurnRuntimeInputBuilder")); const assistantUserTurnBootstrapRuntimeAdapter_1 = __importStar(require("./assistantUserTurnBootstrapRuntimeAdapter")); const assistantQueryPlanning_1 = __importStar(require("./assistantQueryPlanning")); @@ -4371,84 +4372,87 @@ class AssistantService { return this.sessions.getSession(sessionId); } async handleMessage(payload) { - const turnRuntimeDeps = { - 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), - setInvestigationState: (targetSessionId, snapshot) => this.sessions.setInvestigationState(targetSessionId, snapshot), - normalize: (normalizePayload) => this.normalizerService.normalize(normalizePayload), - executeRouteRuntime: (route, fragmentText, options) => this.dataLayer.executeRouteRuntime(route, fragmentText, options), - tryAddressQueryHandle: (laneMessageUsed, options) => this.addressQueryService.tryHandle(laneMessageUsed, options), + const turnRuntimeDeps = (0, assistantTurnRuntimeDepsAdapter_1.buildAssistantTurnRuntimeDeps)({ + sessions: this.sessions, + sessionLogger: this.sessionLogger, + normalizerService: this.normalizerService, + dataLayer: this.dataLayer, + addressQueryService: this.addressQueryService, chatClient: this.chatClient, messageIdFactory: () => `msg-${(0, nanoid_1.nanoid)(10)}`, nowIso: () => new Date().toISOString(), defaultApiKey: process.env.OPENAI_API_KEY ?? "", logEvent: (runtimePayload) => (0, log_1.logJson)(runtimePayload), - featureAssistantAddressQueryV1: config_1.FEATURE_ASSISTANT_ADDRESS_QUERY_V1, - featureAddressLlmPredecomposeV1: config_1.FEATURE_ASSISTANT_ADDRESS_QUERY_LLM_PREDECOMPOSE_V1, - 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, - defaultModel: config_1.DEFAULT_MODEL, - defaultBaseUrl: config_1.DEFAULT_OPENAI_BASE_URL, - compactWhitespace, - repairAddressMojibake, - resolveRuntimeAnalysisContext, - runAddressLlmPreDecompose: async (runtimePayload, runtimeUserMessage) => runAddressLlmPreDecompose(this.normalizerService, runtimePayload, runtimeUserMessage), - buildAddressLlmPredecomposeContractV1: predecomposeContract_1.buildAddressLlmPredecomposeContractV1, - sanitizeAddressMessageForFallback, - toNonEmptyString, - resolveAddressFollowupCarryoverContext, - resolveAssistantOrchestrationDecision, - buildAddressDialogContinuationContractV2, - mergeFollowupContextWithOrganizationScope, - 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, - loadAssistantCanonExcerpt: assistantCanon_1.loadAssistantCanonExcerpt, - sanitizeOutgoingAssistantText, - buildAddressDebugPayload, - buildAddressFollowupOffer, - buildFollowupStateBinding, - resolveBusinessScopeAlignment, - inferP0DomainFromMessage, - resolveBusinessScopeFromLiveContext, - extractRequirements, - toExecutionPlan, - enforceRbpLiveRoutePlan, - enforceFaLiveRoutePlan, - mapNoRouteReason, - buildSkippedResult, - evaluateCoverage, - checkGrounding, - collectRbpLiveRouteAudit, - collectFaLiveRouteAudit, - hasExplicitPeriodAnchorFromNormalized, - extractDroppedIntentSegments: (normalizedPayload) => extractDiscardedIntentSegments(normalizedPayload), - toDebugRoutes: (routeSummary) => toDebugRoutes(routeSummary), - extractExecutionState - }; + flags: { + featureAssistantAddressQueryV1: config_1.FEATURE_ASSISTANT_ADDRESS_QUERY_V1, + featureAddressLlmPredecomposeV1: config_1.FEATURE_ASSISTANT_ADDRESS_QUERY_LLM_PREDECOMPOSE_V1, + 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 + }, + defaults: { + defaultModel: config_1.DEFAULT_MODEL, + defaultBaseUrl: config_1.DEFAULT_OPENAI_BASE_URL + }, + helpers: { + compactWhitespace, + repairAddressMojibake, + resolveRuntimeAnalysisContext, + runAddressLlmPreDecompose: async (runtimePayload, runtimeUserMessage) => runAddressLlmPreDecompose(this.normalizerService, runtimePayload, runtimeUserMessage), + buildAddressLlmPredecomposeContractV1: predecomposeContract_1.buildAddressLlmPredecomposeContractV1, + sanitizeAddressMessageForFallback, + toNonEmptyString, + resolveAddressFollowupCarryoverContext, + resolveAssistantOrchestrationDecision, + buildAddressDialogContinuationContractV2, + mergeFollowupContextWithOrganizationScope, + 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, + loadAssistantCanonExcerpt: assistantCanon_1.loadAssistantCanonExcerpt, + sanitizeOutgoingAssistantText, + buildAddressDebugPayload, + buildAddressFollowupOffer, + buildFollowupStateBinding, + resolveBusinessScopeAlignment, + inferP0DomainFromMessage, + resolveBusinessScopeFromLiveContext, + extractRequirements, + toExecutionPlan, + enforceRbpLiveRoutePlan, + enforceFaLiveRoutePlan, + mapNoRouteReason, + buildSkippedResult, + evaluateCoverage, + checkGrounding, + collectRbpLiveRouteAudit, + collectFaLiveRouteAudit, + hasExplicitPeriodAnchorFromNormalized, + extractDroppedIntentSegments: (normalizedPayload) => extractDiscardedIntentSegments(normalizedPayload), + toDebugRoutes: (routeSummary) => toDebugRoutes(routeSummary), + extractExecutionState + } + }); const turnRuntime = await (0, assistantTurnAttemptRuntimeAdapter_1.runAssistantTurnAttemptRuntime)({ payload, runUserTurnBootstrapRuntime: (runtimePayload) => (0, assistantUserTurnBootstrapRuntimeAdapter_1.runAssistantUserTurnBootstrapRuntime)((0, assistantTurnRuntimeInputBuilder_1.buildAssistantUserTurnBootstrapRuntimeInput)(runtimePayload, turnRuntimeDeps)), diff --git a/llm_normalizer/backend/dist/services/assistantTurnRuntimeDepsAdapter.js b/llm_normalizer/backend/dist/services/assistantTurnRuntimeDepsAdapter.js new file mode 100644 index 0000000..6565eff --- /dev/null +++ b/llm_normalizer/backend/dist/services/assistantTurnRuntimeDepsAdapter.js @@ -0,0 +1,31 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.buildAssistantTurnRuntimeDeps = buildAssistantTurnRuntimeDeps; +function buildAssistantTurnRuntimeDeps(input) { + return { + ...input.helpers, + ensureSession: (sessionId) => input.sessions.ensureSession(sessionId), + appendItem: (sessionId, item) => input.sessions.appendItem(sessionId, item), + getSession: (sessionId) => input.sessions.getSession(sessionId), + persistSession: (sessionState) => input.sessionLogger.persistSession(sessionState), + setInvestigationState: (sessionId, snapshot) => input.sessions.setInvestigationState(sessionId, snapshot), + normalize: (payload) => input.normalizerService.normalize(payload), + executeRouteRuntime: (route, fragmentText, options) => input.dataLayer.executeRouteRuntime(route, fragmentText, options), + tryAddressQueryHandle: (messageUsed, options) => input.addressQueryService.tryHandle(messageUsed, options), + chatClient: input.chatClient, + messageIdFactory: input.messageIdFactory, + nowIso: input.nowIso, + defaultApiKey: input.defaultApiKey, + logEvent: input.logEvent, + featureAssistantAddressQueryV1: input.flags.featureAssistantAddressQueryV1, + featureAddressLlmPredecomposeV1: input.flags.featureAddressLlmPredecomposeV1, + featureInvestigationStateV1: input.flags.featureInvestigationStateV1, + featureStateFollowupBindingV1: input.flags.featureStateFollowupBindingV1, + featureContractsV11: input.flags.featureContractsV11, + featureAnswerPolicyV11: input.flags.featureAnswerPolicyV11, + featureProblemCentricAnswerV1: input.flags.featureProblemCentricAnswerV1, + featureLifecycleAnswerV1: input.flags.featureLifecycleAnswerV1, + defaultModel: input.defaults.defaultModel, + defaultBaseUrl: input.defaults.defaultBaseUrl + }; +} diff --git a/llm_normalizer/backend/src/services/assistantService.ts b/llm_normalizer/backend/src/services/assistantService.ts index b36099d..ff134d8 100644 --- a/llm_normalizer/backend/src/services/assistantService.ts +++ b/llm_normalizer/backend/src/services/assistantService.ts @@ -20,6 +20,7 @@ import * as assistantAddressAttemptRuntimeAdapter_1 from "./assistantAddressAtte import * as assistantCoverageGrounding_1 from "./assistantCoverageGrounding"; import * as assistantDeepTurnAttemptRuntimeAdapter_1 from "./assistantDeepTurnAttemptRuntimeAdapter"; import * as assistantTurnAttemptRuntimeAdapter_1 from "./assistantTurnAttemptRuntimeAdapter"; +import * as assistantTurnRuntimeDepsAdapter_1 from "./assistantTurnRuntimeDepsAdapter"; import * as assistantTurnRuntimeInputBuilder_1 from "./assistantTurnRuntimeInputBuilder"; import * as assistantUserTurnBootstrapRuntimeAdapter_1 from "./assistantUserTurnBootstrapRuntimeAdapter"; import * as assistantQueryPlanning_1 from "./assistantQueryPlanning"; @@ -4326,84 +4327,87 @@ export class AssistantService { return this.sessions.getSession(sessionId); } async handleMessage(payload) { - const turnRuntimeDeps = { - 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), - setInvestigationState: (targetSessionId, snapshot) => this.sessions.setInvestigationState(targetSessionId, snapshot), - normalize: (normalizePayload) => this.normalizerService.normalize(normalizePayload), - executeRouteRuntime: (route, fragmentText, options) => this.dataLayer.executeRouteRuntime(route, fragmentText, options), - tryAddressQueryHandle: (laneMessageUsed, options) => this.addressQueryService.tryHandle(laneMessageUsed, options), + const turnRuntimeDeps = (0, assistantTurnRuntimeDepsAdapter_1.buildAssistantTurnRuntimeDeps)({ + sessions: this.sessions, + sessionLogger: this.sessionLogger, + normalizerService: this.normalizerService, + dataLayer: this.dataLayer, + addressQueryService: this.addressQueryService, chatClient: this.chatClient, messageIdFactory: () => `msg-${(0, nanoid_1.nanoid)(10)}`, nowIso: () => new Date().toISOString(), defaultApiKey: process.env.OPENAI_API_KEY ?? "", logEvent: (runtimePayload) => (0, log_1.logJson)(runtimePayload), - featureAssistantAddressQueryV1: config_1.FEATURE_ASSISTANT_ADDRESS_QUERY_V1, - featureAddressLlmPredecomposeV1: config_1.FEATURE_ASSISTANT_ADDRESS_QUERY_LLM_PREDECOMPOSE_V1, - 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, - defaultModel: config_1.DEFAULT_MODEL, - defaultBaseUrl: config_1.DEFAULT_OPENAI_BASE_URL, - compactWhitespace, - repairAddressMojibake, - resolveRuntimeAnalysisContext, - runAddressLlmPreDecompose: async (runtimePayload, runtimeUserMessage) => runAddressLlmPreDecompose(this.normalizerService, runtimePayload, runtimeUserMessage), - buildAddressLlmPredecomposeContractV1: predecomposeContract_1.buildAddressLlmPredecomposeContractV1, - sanitizeAddressMessageForFallback, - toNonEmptyString, - resolveAddressFollowupCarryoverContext, - resolveAssistantOrchestrationDecision, - buildAddressDialogContinuationContractV2, - mergeFollowupContextWithOrganizationScope, - 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, - loadAssistantCanonExcerpt: assistantCanon_1.loadAssistantCanonExcerpt, - sanitizeOutgoingAssistantText, - buildAddressDebugPayload, - buildAddressFollowupOffer, - buildFollowupStateBinding, - resolveBusinessScopeAlignment, - inferP0DomainFromMessage, - resolveBusinessScopeFromLiveContext, - extractRequirements, - toExecutionPlan, - enforceRbpLiveRoutePlan, - enforceFaLiveRoutePlan, - mapNoRouteReason, - buildSkippedResult, - evaluateCoverage, - checkGrounding, - collectRbpLiveRouteAudit, - collectFaLiveRouteAudit, - hasExplicitPeriodAnchorFromNormalized, - extractDroppedIntentSegments: (normalizedPayload) => extractDiscardedIntentSegments(normalizedPayload), - toDebugRoutes: (routeSummary) => toDebugRoutes(routeSummary), - extractExecutionState - }; + flags: { + featureAssistantAddressQueryV1: config_1.FEATURE_ASSISTANT_ADDRESS_QUERY_V1, + featureAddressLlmPredecomposeV1: config_1.FEATURE_ASSISTANT_ADDRESS_QUERY_LLM_PREDECOMPOSE_V1, + 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 + }, + defaults: { + defaultModel: config_1.DEFAULT_MODEL, + defaultBaseUrl: config_1.DEFAULT_OPENAI_BASE_URL + }, + helpers: { + compactWhitespace, + repairAddressMojibake, + resolveRuntimeAnalysisContext, + runAddressLlmPreDecompose: async (runtimePayload, runtimeUserMessage) => runAddressLlmPreDecompose(this.normalizerService, runtimePayload, runtimeUserMessage), + buildAddressLlmPredecomposeContractV1: predecomposeContract_1.buildAddressLlmPredecomposeContractV1, + sanitizeAddressMessageForFallback, + toNonEmptyString, + resolveAddressFollowupCarryoverContext, + resolveAssistantOrchestrationDecision, + buildAddressDialogContinuationContractV2, + mergeFollowupContextWithOrganizationScope, + 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, + loadAssistantCanonExcerpt: assistantCanon_1.loadAssistantCanonExcerpt, + sanitizeOutgoingAssistantText, + buildAddressDebugPayload, + buildAddressFollowupOffer, + buildFollowupStateBinding, + resolveBusinessScopeAlignment, + inferP0DomainFromMessage, + resolveBusinessScopeFromLiveContext, + extractRequirements, + toExecutionPlan, + enforceRbpLiveRoutePlan, + enforceFaLiveRoutePlan, + mapNoRouteReason, + buildSkippedResult, + evaluateCoverage, + checkGrounding, + collectRbpLiveRouteAudit, + collectFaLiveRouteAudit, + hasExplicitPeriodAnchorFromNormalized, + extractDroppedIntentSegments: (normalizedPayload) => extractDiscardedIntentSegments(normalizedPayload), + toDebugRoutes: (routeSummary) => toDebugRoutes(routeSummary), + extractExecutionState + } + }); const turnRuntime = await (0, assistantTurnAttemptRuntimeAdapter_1.runAssistantTurnAttemptRuntime)({ payload, runUserTurnBootstrapRuntime: (runtimePayload) => (0, assistantUserTurnBootstrapRuntimeAdapter_1.runAssistantUserTurnBootstrapRuntime)((0, assistantTurnRuntimeInputBuilder_1.buildAssistantUserTurnBootstrapRuntimeInput)(runtimePayload, turnRuntimeDeps)), diff --git a/llm_normalizer/backend/src/services/assistantTurnRuntimeDepsAdapter.ts b/llm_normalizer/backend/src/services/assistantTurnRuntimeDepsAdapter.ts new file mode 100644 index 0000000..c59390f --- /dev/null +++ b/llm_normalizer/backend/src/services/assistantTurnRuntimeDepsAdapter.ts @@ -0,0 +1,112 @@ +import type { AssistantTurnRuntimeBuilderDeps } from "./assistantTurnRuntimeInputBuilder"; + +export interface AssistantTurnRuntimeDepsSessionsLike { + ensureSession: (sessionId: string) => unknown; + appendItem: (sessionId: string, item: unknown) => void; + getSession: (sessionId: string) => unknown; + setInvestigationState: (sessionId: string, snapshot: unknown) => void; +} + +export interface AssistantTurnRuntimeDepsSessionLoggerLike { + persistSession: (sessionState: unknown) => void; +} + +export interface AssistantTurnRuntimeDepsNormalizerLike { + normalize: (payload: unknown) => Promise; +} + +export interface AssistantTurnRuntimeDepsDataLayerLike { + executeRouteRuntime: (route: string, fragmentText: string, options?: unknown) => Promise; +} + +export interface AssistantTurnRuntimeDepsAddressQueryServiceLike { + tryHandle: (messageUsed: string, options?: unknown) => Promise; +} + +type BuilderDepsProvidedByAdapter = Pick< + AssistantTurnRuntimeBuilderDeps, + | "ensureSession" + | "appendItem" + | "getSession" + | "persistSession" + | "setInvestigationState" + | "normalize" + | "executeRouteRuntime" + | "tryAddressQueryHandle" + | "chatClient" + | "messageIdFactory" + | "nowIso" + | "defaultApiKey" + | "logEvent" + | "featureAssistantAddressQueryV1" + | "featureAddressLlmPredecomposeV1" + | "featureInvestigationStateV1" + | "featureStateFollowupBindingV1" + | "featureContractsV11" + | "featureAnswerPolicyV11" + | "featureProblemCentricAnswerV1" + | "featureLifecycleAnswerV1" + | "defaultModel" + | "defaultBaseUrl" +>; + +type BuilderDepsPassThrough = Omit; + +export interface BuildAssistantTurnRuntimeDepsInput { + sessions: AssistantTurnRuntimeDepsSessionsLike; + sessionLogger: AssistantTurnRuntimeDepsSessionLoggerLike; + normalizerService: AssistantTurnRuntimeDepsNormalizerLike; + dataLayer: AssistantTurnRuntimeDepsDataLayerLike; + addressQueryService: AssistantTurnRuntimeDepsAddressQueryServiceLike; + chatClient: unknown; + messageIdFactory: () => string; + nowIso: () => string; + defaultApiKey: string; + logEvent: (payload: Record) => void; + flags: { + featureAssistantAddressQueryV1: boolean; + featureAddressLlmPredecomposeV1: boolean; + featureInvestigationStateV1: boolean; + featureStateFollowupBindingV1: boolean; + featureContractsV11: boolean; + featureAnswerPolicyV11: boolean; + featureProblemCentricAnswerV1: boolean; + featureLifecycleAnswerV1: boolean; + }; + defaults: { + defaultModel: string; + defaultBaseUrl: string; + }; + helpers: BuilderDepsPassThrough; +} + +export function buildAssistantTurnRuntimeDeps( + input: BuildAssistantTurnRuntimeDepsInput +): AssistantTurnRuntimeBuilderDeps { + return { + ...input.helpers, + ensureSession: (sessionId) => input.sessions.ensureSession(sessionId) as any, + appendItem: (sessionId, item) => input.sessions.appendItem(sessionId, item as any), + getSession: (sessionId) => input.sessions.getSession(sessionId) as any, + persistSession: (sessionState) => input.sessionLogger.persistSession(sessionState as any), + setInvestigationState: (sessionId, snapshot) => input.sessions.setInvestigationState(sessionId, snapshot), + normalize: (payload) => input.normalizerService.normalize(payload), + executeRouteRuntime: (route, fragmentText, options) => input.dataLayer.executeRouteRuntime(route, fragmentText, options), + tryAddressQueryHandle: (messageUsed, options) => input.addressQueryService.tryHandle(messageUsed, options), + chatClient: input.chatClient, + messageIdFactory: input.messageIdFactory, + nowIso: input.nowIso, + defaultApiKey: input.defaultApiKey, + logEvent: input.logEvent, + featureAssistantAddressQueryV1: input.flags.featureAssistantAddressQueryV1, + featureAddressLlmPredecomposeV1: input.flags.featureAddressLlmPredecomposeV1, + featureInvestigationStateV1: input.flags.featureInvestigationStateV1, + featureStateFollowupBindingV1: input.flags.featureStateFollowupBindingV1, + featureContractsV11: input.flags.featureContractsV11, + featureAnswerPolicyV11: input.flags.featureAnswerPolicyV11, + featureProblemCentricAnswerV1: input.flags.featureProblemCentricAnswerV1, + featureLifecycleAnswerV1: input.flags.featureLifecycleAnswerV1, + defaultModel: input.defaults.defaultModel, + defaultBaseUrl: input.defaults.defaultBaseUrl + }; +} diff --git a/llm_normalizer/backend/tests/assistantTurnRuntimeDepsAdapter.test.ts b/llm_normalizer/backend/tests/assistantTurnRuntimeDepsAdapter.test.ts new file mode 100644 index 0000000..0a8d860 --- /dev/null +++ b/llm_normalizer/backend/tests/assistantTurnRuntimeDepsAdapter.test.ts @@ -0,0 +1,132 @@ +import { describe, expect, it, vi } from "vitest"; +import { buildAssistantTurnRuntimeDeps } from "../src/services/assistantTurnRuntimeDepsAdapter"; + +describe("assistant turn runtime deps adapter", () => { + it("builds runtime deps with service wrappers and static flags/defaults", async () => { + const sessions = { + ensureSession: vi.fn(() => ({ session_id: "asst-1" })), + appendItem: vi.fn(), + getSession: vi.fn(() => ({ session_id: "asst-1" })), + setInvestigationState: vi.fn() + }; + const sessionLogger = { + persistSession: vi.fn() + }; + const normalizerService = { + normalize: vi.fn(async () => ({ trace_id: "trace-1" })) + }; + const dataLayer = { + executeRouteRuntime: vi.fn(async () => ({ status: "ok" })) + }; + const addressQueryService = { + tryHandle: vi.fn(async () => ({ response_type: "READY" })) + }; + const logEvent = vi.fn(); + const helperFn = vi.fn(() => "ok"); + + const deps = buildAssistantTurnRuntimeDeps({ + sessions, + sessionLogger, + normalizerService, + dataLayer, + addressQueryService, + chatClient: { kind: "chat" }, + messageIdFactory: () => "msg-1", + nowIso: () => "2026-04-11T00:00:00.000Z", + defaultApiKey: "api-key", + logEvent, + flags: { + featureAssistantAddressQueryV1: true, + featureAddressLlmPredecomposeV1: true, + featureInvestigationStateV1: true, + featureStateFollowupBindingV1: false, + featureContractsV11: true, + featureAnswerPolicyV11: true, + featureProblemCentricAnswerV1: true, + featureLifecycleAnswerV1: false + }, + defaults: { + defaultModel: "gpt-5", + defaultBaseUrl: "http://localhost" + }, + helpers: { + compactWhitespace: helperFn + } as any + }); + + deps.ensureSession("asst-1"); + deps.appendItem("asst-1", { role: "assistant" } as any); + deps.getSession("asst-1"); + deps.persistSession({ session_id: "asst-1" } as any); + deps.setInvestigationState("asst-1", { scope: "x" }); + await deps.normalize({ user_message: "q" }); + await deps.executeRouteRuntime("store_canonical", "fragment"); + await deps.tryAddressQueryHandle("message", { analysisDateHint: "2020-07-31" }); + deps.logEvent({ event: "ok" }); + + expect(sessions.ensureSession).toHaveBeenCalledWith("asst-1"); + expect(sessions.appendItem).toHaveBeenCalledWith("asst-1", { role: "assistant" }); + expect(sessions.getSession).toHaveBeenCalledWith("asst-1"); + expect(sessionLogger.persistSession).toHaveBeenCalledWith({ session_id: "asst-1" }); + expect(sessions.setInvestigationState).toHaveBeenCalledWith("asst-1", { scope: "x" }); + expect(normalizerService.normalize).toHaveBeenCalledWith({ user_message: "q" }); + expect(dataLayer.executeRouteRuntime).toHaveBeenCalledWith("store_canonical", "fragment", undefined); + expect(addressQueryService.tryHandle).toHaveBeenCalledWith("message", { analysisDateHint: "2020-07-31" }); + expect(logEvent).toHaveBeenCalledWith({ event: "ok" }); + expect(deps.featureContractsV11).toBe(true); + expect(deps.featureLifecycleAnswerV1).toBe(false); + expect(deps.defaultModel).toBe("gpt-5"); + expect(deps.defaultBaseUrl).toBe("http://localhost"); + expect(deps.defaultApiKey).toBe("api-key"); + }); + + it("preserves helper functions in merged deps payload", () => { + const helperCompactWhitespace = vi.fn((value: unknown) => String(value ?? "").trim()); + + const deps = buildAssistantTurnRuntimeDeps({ + sessions: { + ensureSession: () => null, + appendItem: () => {}, + getSession: () => null, + setInvestigationState: () => {} + }, + sessionLogger: { + persistSession: () => {} + }, + normalizerService: { + normalize: async () => ({}) + }, + dataLayer: { + executeRouteRuntime: async () => ({}) + }, + addressQueryService: { + tryHandle: async () => null + }, + chatClient: {}, + messageIdFactory: () => "msg-2", + 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: helperCompactWhitespace + } as any + }); + + expect(deps.compactWhitespace(" value ")).toBe("value"); + expect(helperCompactWhitespace).toHaveBeenCalledWith(" value "); + }); +});