diff --git a/docs/TECH/1CLLMARCH-FACT.md b/docs/TECH/1CLLMARCH-FACT.md index 66c969b..3f50d47 100644 --- a/docs/TECH/1CLLMARCH-FACT.md +++ b/docs/TECH/1CLLMARCH-FACT.md @@ -1645,7 +1645,51 @@ Validation: - `assistantWave10SettlementCorrectiveRegression.test.ts` - `assistantLivingChatMode.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 + 2.50 + 2.51 completed)** +Implemented in current pass (Phase 2.52 + 2.53 + 2.54 + 2.55): +1. Removed remaining inline address-lane response input assembly from `assistantAddressAttemptRuntimeAdapter`: + - rewired `finalizeAddressLaneResponse` to `buildAssistantAddressLaneResponseAttemptRuntimeInput(...)`. +2. Added dedicated lane-attempt input builder and rewired adapter call-site: + - `assistantAddressLaneAttemptInputBuilder.ts` + - introduced: + - `buildAssistantAddressLaneAttemptRuntimeInput(...)` +3. Added dedicated address-runtime input builder and rewired final runtime invocation: + - `assistantAddressRuntimeInputBuilder.ts` + - introduced: + - `buildAssistantAddressRuntimeInput(...)` +4. Added focused builder tests: + - `assistantAddressLaneResponseAttemptInputBuilder.test.ts` + - `assistantAddressLaneAttemptInputBuilder.test.ts` + - `assistantAddressRuntimeInputBuilder.test.ts` + +Validation: +1. `npm run build` passed. +2. Targeted living/address/deep followup pack passed: + - `assistantAddressLaneResponseAttemptInputBuilder.test.ts` + - `assistantAddressLaneAttemptInputBuilder.test.ts` + - `assistantAddressRuntimeInputBuilder.test.ts` + - `assistantLivingChatAttemptInputBuilder.test.ts` + - `assistantAddressAttemptRuntimeAdapter.test.ts` + - `assistantLivingChatAttemptRuntimeAdapter.test.ts` + - `assistantLivingChatHandlerRuntimeAdapter.test.ts` + - `assistantLivingChatRuntimeAdapter.test.ts` + - `assistantTurnRuntimeDepsAdapter.test.ts` + - `assistantTurnRuntimeInputBuilder.test.ts` + - `assistantTurnAttemptRuntimeAdapter.test.ts` + - `assistantOrganizationScopeRuntimeAdapter.test.ts` + - `assistantAddressLaneAttemptRuntimeAdapter.test.ts` + - `assistantAddressLaneResponseAttemptRuntimeAdapter.test.ts` + - `assistantAddressRuntimeAdapter.test.ts` + - `assistantAddressLaneResponseRuntimeAdapter.test.ts` + - `assistantDeepTurnAttemptRuntimeAdapter.test.ts` + - `assistantDeepTurnResponseAttemptRuntimeAdapter.test.ts` + - `assistantDeepTurnAnalysisAttemptRuntimeAdapter.test.ts` + - `assistantDeepTurnAnalysisRuntimeAdapter.test.ts` + - `assistantDeepTurnResponseRuntimeAdapter.test.ts` + - `assistantDeepTurnPackagingRuntimeAdapter.test.ts` + - `assistantWave10SettlementCorrectiveRegression.test.ts` + - `assistantLivingChatMode.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 + 2.50 + 2.51 + 2.52 + 2.53 + 2.54 + 2.55 completed)** ## Stage 3 (P2): Hybrid Semantic Layer (LLM + Deterministic Guards) diff --git a/llm_normalizer/backend/dist/services/assistantAddressAttemptRuntimeAdapter.js b/llm_normalizer/backend/dist/services/assistantAddressAttemptRuntimeAdapter.js index 3947586..6b22f35 100644 --- a/llm_normalizer/backend/dist/services/assistantAddressAttemptRuntimeAdapter.js +++ b/llm_normalizer/backend/dist/services/assistantAddressAttemptRuntimeAdapter.js @@ -4,14 +4,17 @@ exports.runAssistantAddressAttemptRuntime = runAssistantAddressAttemptRuntime; const assistantAddressRuntimeAdapter_1 = require("./assistantAddressRuntimeAdapter"); const assistantAddressLaneAttemptRuntimeAdapter_1 = require("./assistantAddressLaneAttemptRuntimeAdapter"); const assistantAddressLaneResponseAttemptRuntimeAdapter_1 = require("./assistantAddressLaneResponseAttemptRuntimeAdapter"); +const assistantAddressLaneResponseAttemptInputBuilder_1 = require("./assistantAddressLaneResponseAttemptInputBuilder"); const assistantLivingChatAttemptRuntimeAdapter_1 = require("./assistantLivingChatAttemptRuntimeAdapter"); const assistantLivingChatAttemptInputBuilder_1 = require("./assistantLivingChatAttemptInputBuilder"); +const assistantAddressLaneAttemptInputBuilder_1 = require("./assistantAddressLaneAttemptInputBuilder"); +const assistantAddressRuntimeInputBuilder_1 = require("./assistantAddressRuntimeInputBuilder"); async function runAssistantAddressAttemptRuntime(input) { const runAddressRuntimeSafe = input.runAddressRuntime ?? assistantAddressRuntimeAdapter_1.runAssistantAddressRuntime; const runAddressLaneAttemptRuntimeSafe = input.runAddressLaneAttemptRuntime ?? assistantAddressLaneAttemptRuntimeAdapter_1.runAssistantAddressLaneAttemptRuntime; const runAddressLaneResponseAttemptRuntimeSafe = input.runAddressLaneResponseAttemptRuntime ?? assistantAddressLaneResponseAttemptRuntimeAdapter_1.runAssistantAddressLaneResponseAttemptRuntime; const runLivingChatAttemptRuntimeSafe = input.runLivingChatAttemptRuntime ?? assistantLivingChatAttemptRuntimeAdapter_1.runAssistantLivingChatAttemptRuntime; - const finalizeAddressLaneResponse = (addressLane, effectiveAddressUserMessage, carryoverMeta = null, llmPreDecomposeMeta = null) => runAddressLaneResponseAttemptRuntimeSafe({ + const finalizeAddressLaneResponse = (addressLane, effectiveAddressUserMessage, carryoverMeta = null, llmPreDecomposeMeta = null) => runAddressLaneResponseAttemptRuntimeSafe((0, assistantAddressLaneResponseAttemptInputBuilder_1.buildAssistantAddressLaneResponseAttemptRuntimeInput)({ sessionId: input.sessionId, userMessage: input.userMessage, effectiveAddressUserMessage, @@ -31,7 +34,7 @@ async function runAssistantAddressAttemptRuntime(input) { cloneConversation: input.cloneConversation, logEvent: input.logEvent, messageIdFactory: input.messageIdFactory - }); + })); const tryHandleLivingChat = async (modeDecision, addressRuntimeMeta = null) => runLivingChatAttemptRuntimeSafe((0, assistantLivingChatAttemptInputBuilder_1.buildAssistantLivingChatAttemptRuntimeInput)({ sessionId: input.sessionId, userMessage: input.userMessage, @@ -78,21 +81,20 @@ async function runAssistantAddressAttemptRuntime(input) { defaultBaseUrl: input.defaultBaseUrl, defaultApiKey: input.defaultApiKey })); - const runAddressLaneAttempt = async (messageUsed, carryMeta, analysisDateHint) => runAddressLaneAttemptRuntimeSafe({ + const runAddressLaneAttempt = async (messageUsed, carryMeta, analysisDateHint) => runAddressLaneAttemptRuntimeSafe((0, assistantAddressLaneAttemptInputBuilder_1.buildAssistantAddressLaneAttemptRuntimeInput)({ messageUsed, carryMeta, analysisDateHint, activeOrganization: input.sessionScope.activeOrganization, mergeFollowupContextWithOrganizationScope: input.mergeFollowupContextWithOrganizationScope, runAddressQueryTryHandle: input.runAddressQueryTryHandle - }); - return runAddressRuntimeSafe({ + })); + return runAddressRuntimeSafe((0, assistantAddressRuntimeInputBuilder_1.buildAssistantAddressRuntimeInput)({ featureAssistantAddressQueryV1: input.featureAssistantAddressQueryV1, sessionId: input.sessionId, userMessage: input.userMessage, sessionItems: input.sessionItems, - llmProvider: input.payload.llmProvider, - useMock: Boolean(input.payload.useMock), + payload: input.payload, featureAddressLlmPredecomposeV1: input.featureAddressLlmPredecomposeV1, runAddressLlmPreDecompose: input.runAddressLlmPreDecompose, buildAddressLlmPredecomposeContractV1: input.buildAddressLlmPredecomposeContractV1, @@ -102,7 +104,6 @@ async function runAssistantAddressAttemptRuntime(input) { resolveAssistantOrchestrationDecision: input.resolveAssistantOrchestrationDecision, buildAddressDialogContinuationContractV2: input.buildAddressDialogContinuationContractV2, runtimeAnalysisContextAsOfDate: input.runtimeAnalysisContextAsOfDate, - payloadContextPeriodHint: input.payload?.context?.period_hint, compactWhitespace: input.compactWhitespace, runAddressLaneAttempt, isRetryableAddressLimitedResult: input.isRetryableAddressLimitedResult, @@ -113,5 +114,5 @@ async function runAssistantAddressAttemptRuntime(input) { runAddressOrchestrationRuntime: input.runAddressOrchestrationRuntime, runAddressToolGateRuntime: input.runAddressToolGateRuntime, runAddressLaneRuntime: input.runAddressLaneRuntime - }); + })); } diff --git a/llm_normalizer/backend/dist/services/assistantAddressLaneAttemptInputBuilder.js b/llm_normalizer/backend/dist/services/assistantAddressLaneAttemptInputBuilder.js new file mode 100644 index 0000000..3c38728 --- /dev/null +++ b/llm_normalizer/backend/dist/services/assistantAddressLaneAttemptInputBuilder.js @@ -0,0 +1,13 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.buildAssistantAddressLaneAttemptRuntimeInput = buildAssistantAddressLaneAttemptRuntimeInput; +function buildAssistantAddressLaneAttemptRuntimeInput(input) { + return { + messageUsed: input.messageUsed, + carryMeta: input.carryMeta, + analysisDateHint: input.analysisDateHint, + activeOrganization: input.activeOrganization, + mergeFollowupContextWithOrganizationScope: input.mergeFollowupContextWithOrganizationScope, + runAddressQueryTryHandle: input.runAddressQueryTryHandle + }; +} diff --git a/llm_normalizer/backend/dist/services/assistantAddressLaneResponseAttemptInputBuilder.js b/llm_normalizer/backend/dist/services/assistantAddressLaneResponseAttemptInputBuilder.js new file mode 100644 index 0000000..ac840e9 --- /dev/null +++ b/llm_normalizer/backend/dist/services/assistantAddressLaneResponseAttemptInputBuilder.js @@ -0,0 +1,26 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.buildAssistantAddressLaneResponseAttemptRuntimeInput = buildAssistantAddressLaneResponseAttemptRuntimeInput; +function buildAssistantAddressLaneResponseAttemptRuntimeInput(input) { + return { + sessionId: input.sessionId, + userMessage: input.userMessage, + effectiveAddressUserMessage: input.effectiveAddressUserMessage, + addressLane: input.addressLane, + carryoverMeta: input.carryoverMeta ?? null, + llmPreDecomposeMeta: input.llmPreDecomposeMeta ?? null, + knownOrganizations: input.knownOrganizations, + activeOrganization: input.activeOrganization, + sanitizeOutgoingAssistantText: input.sanitizeOutgoingAssistantText, + buildAddressDebugPayload: input.buildAddressDebugPayload, + buildAddressFollowupOffer: input.buildAddressFollowupOffer, + mergeKnownOrganizations: input.mergeKnownOrganizations, + toNonEmptyString: input.toNonEmptyString, + appendItem: input.appendItem, + getSession: input.getSession, + persistSession: input.persistSession, + cloneConversation: input.cloneConversation, + logEvent: input.logEvent, + messageIdFactory: input.messageIdFactory + }; +} diff --git a/llm_normalizer/backend/dist/services/assistantAddressRuntimeInputBuilder.js b/llm_normalizer/backend/dist/services/assistantAddressRuntimeInputBuilder.js new file mode 100644 index 0000000..08e609f --- /dev/null +++ b/llm_normalizer/backend/dist/services/assistantAddressRuntimeInputBuilder.js @@ -0,0 +1,33 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.buildAssistantAddressRuntimeInput = buildAssistantAddressRuntimeInput; +function buildAssistantAddressRuntimeInput(input) { + return { + featureAssistantAddressQueryV1: input.featureAssistantAddressQueryV1, + sessionId: input.sessionId, + userMessage: input.userMessage, + sessionItems: input.sessionItems, + llmProvider: input.payload.llmProvider, + useMock: Boolean(input.payload.useMock), + featureAddressLlmPredecomposeV1: input.featureAddressLlmPredecomposeV1, + runAddressLlmPreDecompose: input.runAddressLlmPreDecompose, + buildAddressLlmPredecomposeContractV1: input.buildAddressLlmPredecomposeContractV1, + sanitizeAddressMessageForFallback: input.sanitizeAddressMessageForFallback, + toNonEmptyString: input.toNonEmptyString, + resolveAddressFollowupCarryoverContext: input.resolveAddressFollowupCarryoverContext, + resolveAssistantOrchestrationDecision: input.resolveAssistantOrchestrationDecision, + buildAddressDialogContinuationContractV2: input.buildAddressDialogContinuationContractV2, + runtimeAnalysisContextAsOfDate: input.runtimeAnalysisContextAsOfDate, + payloadContextPeriodHint: input.payload?.context?.period_hint, + compactWhitespace: input.compactWhitespace, + runAddressLaneAttempt: input.runAddressLaneAttempt, + isRetryableAddressLimitedResult: input.isRetryableAddressLimitedResult, + finalizeAddressLaneResponse: input.finalizeAddressLaneResponse, + tryHandleLivingChat: input.tryHandleLivingChat, + logEvent: input.logEvent, + nowIso: input.nowIso, + runAddressOrchestrationRuntime: input.runAddressOrchestrationRuntime, + runAddressToolGateRuntime: input.runAddressToolGateRuntime, + runAddressLaneRuntime: input.runAddressLaneRuntime + }; +} diff --git a/llm_normalizer/backend/src/services/assistantAddressAttemptRuntimeAdapter.ts b/llm_normalizer/backend/src/services/assistantAddressAttemptRuntimeAdapter.ts index 080f27b..3822be5 100644 --- a/llm_normalizer/backend/src/services/assistantAddressAttemptRuntimeAdapter.ts +++ b/llm_normalizer/backend/src/services/assistantAddressAttemptRuntimeAdapter.ts @@ -11,11 +11,14 @@ import { runAssistantAddressLaneResponseAttemptRuntime, type RunAssistantAddressLaneResponseAttemptRuntimeInput } from "./assistantAddressLaneResponseAttemptRuntimeAdapter"; +import { buildAssistantAddressLaneResponseAttemptRuntimeInput } from "./assistantAddressLaneResponseAttemptInputBuilder"; import { runAssistantLivingChatAttemptRuntime, type RunAssistantLivingChatAttemptRuntimeInput } from "./assistantLivingChatAttemptRuntimeAdapter"; import { buildAssistantLivingChatAttemptRuntimeInput } from "./assistantLivingChatAttemptInputBuilder"; +import { buildAssistantAddressLaneAttemptRuntimeInput } from "./assistantAddressLaneAttemptInputBuilder"; +import { buildAssistantAddressRuntimeInput } from "./assistantAddressRuntimeInputBuilder"; interface AddressAttemptPayload { llmProvider?: unknown; @@ -110,27 +113,29 @@ export async function runAssistantAddressAttemptRuntime( carryoverMeta = null, llmPreDecomposeMeta = null ) => - runAddressLaneResponseAttemptRuntimeSafe({ - sessionId: input.sessionId, - userMessage: input.userMessage, - effectiveAddressUserMessage, - addressLane, - carryoverMeta, - llmPreDecomposeMeta, - knownOrganizations: input.sessionScope.knownOrganizations, - activeOrganization: input.sessionScope.activeOrganization, - sanitizeOutgoingAssistantText: input.sanitizeOutgoingAssistantText, - buildAddressDebugPayload: input.buildAddressDebugPayload, - buildAddressFollowupOffer: input.buildAddressFollowupOffer, - mergeKnownOrganizations: input.mergeKnownOrganizations as any, - toNonEmptyString: input.toNonEmptyString, - appendItem: input.appendItem, - getSession: input.getSession, - persistSession: input.persistSession, - cloneConversation: input.cloneConversation, - logEvent: input.logEvent, - messageIdFactory: input.messageIdFactory - } as RunAssistantAddressLaneResponseAttemptRuntimeInput); + runAddressLaneResponseAttemptRuntimeSafe( + buildAssistantAddressLaneResponseAttemptRuntimeInput({ + sessionId: input.sessionId, + userMessage: input.userMessage, + effectiveAddressUserMessage, + addressLane, + carryoverMeta, + llmPreDecomposeMeta, + knownOrganizations: input.sessionScope.knownOrganizations, + activeOrganization: input.sessionScope.activeOrganization, + sanitizeOutgoingAssistantText: input.sanitizeOutgoingAssistantText, + buildAddressDebugPayload: input.buildAddressDebugPayload, + buildAddressFollowupOffer: input.buildAddressFollowupOffer, + mergeKnownOrganizations: input.mergeKnownOrganizations, + toNonEmptyString: input.toNonEmptyString, + appendItem: input.appendItem, + getSession: input.getSession, + persistSession: input.persistSession, + cloneConversation: input.cloneConversation, + logEvent: input.logEvent, + messageIdFactory: input.messageIdFactory + }) + ); const tryHandleLivingChat: RunAssistantAddressRuntimeInput["tryHandleLivingChat"] = async ( modeDecision, @@ -190,41 +195,43 @@ export async function runAssistantAddressAttemptRuntime( carryMeta, analysisDateHint ) => - runAddressLaneAttemptRuntimeSafe({ - messageUsed, - carryMeta, - analysisDateHint, - activeOrganization: input.sessionScope.activeOrganization, - mergeFollowupContextWithOrganizationScope: input.mergeFollowupContextWithOrganizationScope, - runAddressQueryTryHandle: input.runAddressQueryTryHandle - }); + runAddressLaneAttemptRuntimeSafe( + buildAssistantAddressLaneAttemptRuntimeInput({ + messageUsed, + carryMeta, + analysisDateHint, + activeOrganization: input.sessionScope.activeOrganization, + mergeFollowupContextWithOrganizationScope: input.mergeFollowupContextWithOrganizationScope, + runAddressQueryTryHandle: input.runAddressQueryTryHandle + }) + ); - return runAddressRuntimeSafe({ - featureAssistantAddressQueryV1: input.featureAssistantAddressQueryV1, - sessionId: input.sessionId, - userMessage: input.userMessage, - sessionItems: input.sessionItems, - llmProvider: input.payload.llmProvider, - useMock: Boolean(input.payload.useMock), - featureAddressLlmPredecomposeV1: input.featureAddressLlmPredecomposeV1, - runAddressLlmPreDecompose: input.runAddressLlmPreDecompose, - buildAddressLlmPredecomposeContractV1: input.buildAddressLlmPredecomposeContractV1, - sanitizeAddressMessageForFallback: input.sanitizeAddressMessageForFallback, - toNonEmptyString: input.toNonEmptyString, - resolveAddressFollowupCarryoverContext: input.resolveAddressFollowupCarryoverContext, - resolveAssistantOrchestrationDecision: input.resolveAssistantOrchestrationDecision, - buildAddressDialogContinuationContractV2: input.buildAddressDialogContinuationContractV2, - runtimeAnalysisContextAsOfDate: input.runtimeAnalysisContextAsOfDate, - payloadContextPeriodHint: input.payload?.context?.period_hint, - compactWhitespace: input.compactWhitespace, - runAddressLaneAttempt, - isRetryableAddressLimitedResult: input.isRetryableAddressLimitedResult, - finalizeAddressLaneResponse, - tryHandleLivingChat, - logEvent: input.logEvent, - nowIso: input.nowIso, - runAddressOrchestrationRuntime: input.runAddressOrchestrationRuntime, - runAddressToolGateRuntime: input.runAddressToolGateRuntime, - runAddressLaneRuntime: input.runAddressLaneRuntime - }); + return runAddressRuntimeSafe( + buildAssistantAddressRuntimeInput({ + featureAssistantAddressQueryV1: input.featureAssistantAddressQueryV1, + sessionId: input.sessionId, + userMessage: input.userMessage, + sessionItems: input.sessionItems, + payload: input.payload, + featureAddressLlmPredecomposeV1: input.featureAddressLlmPredecomposeV1, + runAddressLlmPreDecompose: input.runAddressLlmPreDecompose, + buildAddressLlmPredecomposeContractV1: input.buildAddressLlmPredecomposeContractV1, + sanitizeAddressMessageForFallback: input.sanitizeAddressMessageForFallback, + toNonEmptyString: input.toNonEmptyString, + resolveAddressFollowupCarryoverContext: input.resolveAddressFollowupCarryoverContext, + resolveAssistantOrchestrationDecision: input.resolveAssistantOrchestrationDecision, + buildAddressDialogContinuationContractV2: input.buildAddressDialogContinuationContractV2, + runtimeAnalysisContextAsOfDate: input.runtimeAnalysisContextAsOfDate, + compactWhitespace: input.compactWhitespace, + runAddressLaneAttempt, + isRetryableAddressLimitedResult: input.isRetryableAddressLimitedResult, + finalizeAddressLaneResponse, + tryHandleLivingChat, + logEvent: input.logEvent, + nowIso: input.nowIso, + runAddressOrchestrationRuntime: input.runAddressOrchestrationRuntime, + runAddressToolGateRuntime: input.runAddressToolGateRuntime, + runAddressLaneRuntime: input.runAddressLaneRuntime + }) + ); } diff --git a/llm_normalizer/backend/src/services/assistantAddressLaneAttemptInputBuilder.ts b/llm_normalizer/backend/src/services/assistantAddressLaneAttemptInputBuilder.ts new file mode 100644 index 0000000..52daea9 --- /dev/null +++ b/llm_normalizer/backend/src/services/assistantAddressLaneAttemptInputBuilder.ts @@ -0,0 +1,24 @@ +import type { RunAssistantAddressLaneAttemptRuntimeInput } from "./assistantAddressLaneAttemptRuntimeAdapter"; + +export interface BuildAssistantAddressLaneAttemptRuntimeInputInput { + messageUsed: RunAssistantAddressLaneAttemptRuntimeInput["messageUsed"]; + carryMeta: RunAssistantAddressLaneAttemptRuntimeInput["carryMeta"]; + analysisDateHint: RunAssistantAddressLaneAttemptRuntimeInput["analysisDateHint"]; + activeOrganization: RunAssistantAddressLaneAttemptRuntimeInput["activeOrganization"]; + mergeFollowupContextWithOrganizationScope: + RunAssistantAddressLaneAttemptRuntimeInput["mergeFollowupContextWithOrganizationScope"]; + runAddressQueryTryHandle: RunAssistantAddressLaneAttemptRuntimeInput["runAddressQueryTryHandle"]; +} + +export function buildAssistantAddressLaneAttemptRuntimeInput( + input: BuildAssistantAddressLaneAttemptRuntimeInputInput +): RunAssistantAddressLaneAttemptRuntimeInput { + return { + messageUsed: input.messageUsed, + carryMeta: input.carryMeta, + analysisDateHint: input.analysisDateHint, + activeOrganization: input.activeOrganization, + mergeFollowupContextWithOrganizationScope: input.mergeFollowupContextWithOrganizationScope, + runAddressQueryTryHandle: input.runAddressQueryTryHandle + }; +} diff --git a/llm_normalizer/backend/src/services/assistantAddressLaneResponseAttemptInputBuilder.ts b/llm_normalizer/backend/src/services/assistantAddressLaneResponseAttemptInputBuilder.ts new file mode 100644 index 0000000..f302268 --- /dev/null +++ b/llm_normalizer/backend/src/services/assistantAddressLaneResponseAttemptInputBuilder.ts @@ -0,0 +1,53 @@ +import type { RunAssistantAddressLaneResponseAttemptRuntimeInput } from "./assistantAddressLaneResponseAttemptRuntimeAdapter"; + +export interface BuildAssistantAddressLaneResponseAttemptRuntimeInputInput { + sessionId: string; + userMessage: string; + effectiveAddressUserMessage: string; + addressLane: RunAssistantAddressLaneResponseAttemptRuntimeInput["addressLane"]; + carryoverMeta?: RunAssistantAddressLaneResponseAttemptRuntimeInput["carryoverMeta"]; + llmPreDecomposeMeta?: RunAssistantAddressLaneResponseAttemptRuntimeInput["llmPreDecomposeMeta"]; + knownOrganizations: string[]; + activeOrganization: string | null; + sanitizeOutgoingAssistantText: + RunAssistantAddressLaneResponseAttemptRuntimeInput["sanitizeOutgoingAssistantText"]; + buildAddressDebugPayload: + RunAssistantAddressLaneResponseAttemptRuntimeInput["buildAddressDebugPayload"]; + buildAddressFollowupOffer: + RunAssistantAddressLaneResponseAttemptRuntimeInput["buildAddressFollowupOffer"]; + mergeKnownOrganizations: + RunAssistantAddressLaneResponseAttemptRuntimeInput["mergeKnownOrganizations"]; + toNonEmptyString: RunAssistantAddressLaneResponseAttemptRuntimeInput["toNonEmptyString"]; + appendItem: RunAssistantAddressLaneResponseAttemptRuntimeInput["appendItem"]; + getSession: RunAssistantAddressLaneResponseAttemptRuntimeInput["getSession"]; + persistSession: RunAssistantAddressLaneResponseAttemptRuntimeInput["persistSession"]; + cloneConversation: RunAssistantAddressLaneResponseAttemptRuntimeInput["cloneConversation"]; + logEvent: RunAssistantAddressLaneResponseAttemptRuntimeInput["logEvent"]; + messageIdFactory?: RunAssistantAddressLaneResponseAttemptRuntimeInput["messageIdFactory"]; +} + +export function buildAssistantAddressLaneResponseAttemptRuntimeInput( + input: BuildAssistantAddressLaneResponseAttemptRuntimeInputInput +): RunAssistantAddressLaneResponseAttemptRuntimeInput { + return { + sessionId: input.sessionId, + userMessage: input.userMessage, + effectiveAddressUserMessage: input.effectiveAddressUserMessage, + addressLane: input.addressLane, + carryoverMeta: input.carryoverMeta ?? null, + llmPreDecomposeMeta: input.llmPreDecomposeMeta ?? null, + knownOrganizations: input.knownOrganizations, + activeOrganization: input.activeOrganization, + sanitizeOutgoingAssistantText: input.sanitizeOutgoingAssistantText, + buildAddressDebugPayload: input.buildAddressDebugPayload, + buildAddressFollowupOffer: input.buildAddressFollowupOffer, + mergeKnownOrganizations: input.mergeKnownOrganizations, + toNonEmptyString: input.toNonEmptyString, + appendItem: input.appendItem, + getSession: input.getSession, + persistSession: input.persistSession, + cloneConversation: input.cloneConversation, + logEvent: input.logEvent, + messageIdFactory: input.messageIdFactory + }; +} diff --git a/llm_normalizer/backend/src/services/assistantAddressRuntimeInputBuilder.ts b/llm_normalizer/backend/src/services/assistantAddressRuntimeInputBuilder.ts new file mode 100644 index 0000000..7388fb5 --- /dev/null +++ b/llm_normalizer/backend/src/services/assistantAddressRuntimeInputBuilder.ts @@ -0,0 +1,47 @@ +import type { RunAssistantAddressRuntimeInput } from "./assistantAddressRuntimeAdapter"; + +interface AssistantAddressAttemptPayloadLike { + llmProvider?: unknown; + useMock?: unknown; + context?: { + period_hint?: unknown; + } | null; +} + +export interface BuildAssistantAddressRuntimeInputInput + extends Omit, "llmProvider" | "useMock" | "payloadContextPeriodHint"> { + payload: AssistantAddressAttemptPayloadLike; +} + +export function buildAssistantAddressRuntimeInput( + input: BuildAssistantAddressRuntimeInputInput +): RunAssistantAddressRuntimeInput { + return { + featureAssistantAddressQueryV1: input.featureAssistantAddressQueryV1, + sessionId: input.sessionId, + userMessage: input.userMessage, + sessionItems: input.sessionItems, + llmProvider: input.payload.llmProvider, + useMock: Boolean(input.payload.useMock), + featureAddressLlmPredecomposeV1: input.featureAddressLlmPredecomposeV1, + runAddressLlmPreDecompose: input.runAddressLlmPreDecompose, + buildAddressLlmPredecomposeContractV1: input.buildAddressLlmPredecomposeContractV1, + sanitizeAddressMessageForFallback: input.sanitizeAddressMessageForFallback, + toNonEmptyString: input.toNonEmptyString, + resolveAddressFollowupCarryoverContext: input.resolveAddressFollowupCarryoverContext, + resolveAssistantOrchestrationDecision: input.resolveAssistantOrchestrationDecision, + buildAddressDialogContinuationContractV2: input.buildAddressDialogContinuationContractV2, + runtimeAnalysisContextAsOfDate: input.runtimeAnalysisContextAsOfDate, + payloadContextPeriodHint: input.payload?.context?.period_hint, + compactWhitespace: input.compactWhitespace, + runAddressLaneAttempt: input.runAddressLaneAttempt, + isRetryableAddressLimitedResult: input.isRetryableAddressLimitedResult, + finalizeAddressLaneResponse: input.finalizeAddressLaneResponse, + tryHandleLivingChat: input.tryHandleLivingChat, + logEvent: input.logEvent, + nowIso: input.nowIso, + runAddressOrchestrationRuntime: input.runAddressOrchestrationRuntime, + runAddressToolGateRuntime: input.runAddressToolGateRuntime, + runAddressLaneRuntime: input.runAddressLaneRuntime + }; +} diff --git a/llm_normalizer/backend/tests/assistantAddressLaneAttemptInputBuilder.test.ts b/llm_normalizer/backend/tests/assistantAddressLaneAttemptInputBuilder.test.ts new file mode 100644 index 0000000..078abbe --- /dev/null +++ b/llm_normalizer/backend/tests/assistantAddressLaneAttemptInputBuilder.test.ts @@ -0,0 +1,53 @@ +import { describe, expect, it, vi } from "vitest"; +import { buildAssistantAddressLaneAttemptRuntimeInput } from "../src/services/assistantAddressLaneAttemptInputBuilder"; + +function buildInput(overrides: Record = {}) { + const mergeFollowupContextWithOrganizationScope = vi.fn((followupContext: Record | null) => + followupContext + ); + const runAddressQueryTryHandle = vi.fn(async () => ({ response_type: "READY" })); + return { + messageUsed: "Show overdue docs", + carryMeta: { followupContext: { previous_intent: "docs_by_counterparty" } }, + analysisDateHint: "2020-08-31", + activeOrganization: "Org A", + mergeFollowupContextWithOrganizationScope, + runAddressQueryTryHandle, + ...overrides + } as any; +} + +describe("assistant address lane attempt input builder", () => { + it("builds lane-attempt runtime input with message, carry meta and analysis date", () => { + const runtimeInput = buildAssistantAddressLaneAttemptRuntimeInput(buildInput()); + + expect(runtimeInput.messageUsed).toBe("Show overdue docs"); + expect(runtimeInput.analysisDateHint).toBe("2020-08-31"); + expect(runtimeInput.activeOrganization).toBe("Org A"); + expect(runtimeInput.carryMeta).toEqual({ + followupContext: { previous_intent: "docs_by_counterparty" } + }); + }); + + it("preserves injected callbacks and nullable fields", async () => { + const mergeFollowupContextWithOrganizationScope = vi.fn(() => null); + const runAddressQueryTryHandle = vi.fn(async () => ({ response_type: "NEEDS_CONTEXT" })); + const runtimeInput = buildAssistantAddressLaneAttemptRuntimeInput( + buildInput({ + carryMeta: null, + analysisDateHint: null, + activeOrganization: null, + mergeFollowupContextWithOrganizationScope, + runAddressQueryTryHandle + }) + ); + + await runtimeInput.runAddressQueryTryHandle("message", { analysisDateHint: null }); + runtimeInput.mergeFollowupContextWithOrganizationScope(null, null); + + expect(runAddressQueryTryHandle).toHaveBeenCalledWith("message", { analysisDateHint: null }); + expect(mergeFollowupContextWithOrganizationScope).toHaveBeenCalledWith(null, null); + expect(runtimeInput.carryMeta).toBeNull(); + expect(runtimeInput.analysisDateHint).toBeNull(); + }); +}); diff --git a/llm_normalizer/backend/tests/assistantAddressLaneResponseAttemptInputBuilder.test.ts b/llm_normalizer/backend/tests/assistantAddressLaneResponseAttemptInputBuilder.test.ts new file mode 100644 index 0000000..a3890cb --- /dev/null +++ b/llm_normalizer/backend/tests/assistantAddressLaneResponseAttemptInputBuilder.test.ts @@ -0,0 +1,66 @@ +import { describe, expect, it, vi } from "vitest"; +import { buildAssistantAddressLaneResponseAttemptRuntimeInput } from "../src/services/assistantAddressLaneResponseAttemptInputBuilder"; + +function buildInput(overrides: Record = {}) { + return { + sessionId: "asst-1", + userMessage: "Show overdue docs", + effectiveAddressUserMessage: "Show overdue docs for Org A", + addressLane: { + handled: true, + reply_text: "ok", + reply_type: "factual_with_explanation", + debug: {} + }, + knownOrganizations: ["Org A"], + activeOrganization: "Org A", + sanitizeOutgoingAssistantText: (value: unknown, fallback = "") => + typeof value === "string" && value.trim().length > 0 ? value.trim() : fallback, + buildAddressDebugPayload: () => ({}), + buildAddressFollowupOffer: () => null, + mergeKnownOrganizations: (organizations: string[]) => organizations, + toNonEmptyString: (value: unknown) => + typeof value === "string" && value.trim().length > 0 ? value.trim() : null, + appendItem: vi.fn(), + getSession: vi.fn(), + persistSession: vi.fn(), + cloneConversation: vi.fn((items: unknown[]) => items), + logEvent: vi.fn(), + messageIdFactory: vi.fn(() => "msg-1"), + ...overrides + } as any; +} + +describe("assistant address lane response attempt input builder", () => { + it("normalizes optional carryover and predecompose meta to null", () => { + const runtimeInput = buildAssistantAddressLaneResponseAttemptRuntimeInput( + buildInput({ + carryoverMeta: undefined, + llmPreDecomposeMeta: undefined + }) + ); + + expect(runtimeInput.carryoverMeta).toBeNull(); + expect(runtimeInput.llmPreDecomposeMeta).toBeNull(); + expect(runtimeInput.effectiveAddressUserMessage).toBe("Show overdue docs for Org A"); + expect(runtimeInput.knownOrganizations).toEqual(["Org A"]); + }); + + it("preserves explicit metadata and callback wiring", () => { + const logEvent = vi.fn(); + const runtimeInput = buildAssistantAddressLaneResponseAttemptRuntimeInput( + buildInput({ + carryoverMeta: { followupContext: { previous_intent: "docs_by_counterparty" } }, + llmPreDecomposeMeta: { mode: "supported" }, + logEvent + }) + ); + + expect(runtimeInput.carryoverMeta).toEqual({ + followupContext: { previous_intent: "docs_by_counterparty" } + }); + expect(runtimeInput.llmPreDecomposeMeta).toEqual({ mode: "supported" }); + runtimeInput.logEvent({ event: "address_done" }); + expect(logEvent).toHaveBeenCalledWith({ event: "address_done" }); + }); +}); diff --git a/llm_normalizer/backend/tests/assistantAddressRuntimeInputBuilder.test.ts b/llm_normalizer/backend/tests/assistantAddressRuntimeInputBuilder.test.ts new file mode 100644 index 0000000..3536486 --- /dev/null +++ b/llm_normalizer/backend/tests/assistantAddressRuntimeInputBuilder.test.ts @@ -0,0 +1,70 @@ +import { describe, expect, it, vi } from "vitest"; +import { buildAssistantAddressRuntimeInput } from "../src/services/assistantAddressRuntimeInputBuilder"; + +function buildInput(overrides: Record = {}) { + return { + featureAssistantAddressQueryV1: true, + sessionId: "asst-1", + userMessage: "Show overdue docs", + sessionItems: [], + payload: { + llmProvider: "openai", + useMock: 1, + context: { + period_hint: "2020-07-31" + } + }, + featureAddressLlmPredecomposeV1: true, + runAddressLlmPreDecompose: vi.fn(async () => ({})), + buildAddressLlmPredecomposeContractV1: vi.fn(() => ({})), + sanitizeAddressMessageForFallback: vi.fn((value: string) => value), + toNonEmptyString: vi.fn((value: unknown) => + typeof value === "string" && value.trim().length > 0 ? value.trim() : null + ), + resolveAddressFollowupCarryoverContext: vi.fn(() => null), + resolveAssistantOrchestrationDecision: vi.fn(() => ({ mode: "address_query", runAddressLane: true })), + buildAddressDialogContinuationContractV2: vi.fn(() => ({})), + runtimeAnalysisContextAsOfDate: "2020-07-31", + compactWhitespace: vi.fn((value: string) => value.trim()), + runAddressLaneAttempt: vi.fn(async () => null), + isRetryableAddressLimitedResult: vi.fn(() => false), + finalizeAddressLaneResponse: vi.fn(() => ({ kind: "address" })), + tryHandleLivingChat: vi.fn(async () => null), + logEvent: vi.fn(), + nowIso: vi.fn(() => "2026-04-11T00:00:00.000Z"), + runAddressOrchestrationRuntime: vi.fn(async () => ({})), + runAddressToolGateRuntime: vi.fn(async () => ({ handled: false, response: null })), + runAddressLaneRuntime: vi.fn(async () => ({ handled: false, selection: null, retryAudit: {} })), + ...overrides + } as any; +} + +describe("assistant address runtime input builder", () => { + it("maps payload provider/useMock/period hint into address runtime input", () => { + const runtimeInput = buildAssistantAddressRuntimeInput(buildInput()); + + expect(runtimeInput.llmProvider).toBe("openai"); + expect(runtimeInput.useMock).toBe(true); + expect(runtimeInput.payloadContextPeriodHint).toBe("2020-07-31"); + expect(runtimeInput.sessionId).toBe("asst-1"); + expect(runtimeInput.userMessage).toBe("Show overdue docs"); + }); + + it("keeps empty payload fields safe and preserves runtime callbacks", () => { + const runAddressLaneAttempt = vi.fn(async () => null); + const tryHandleLivingChat = vi.fn(async () => null); + const runtimeInput = buildAssistantAddressRuntimeInput( + buildInput({ + payload: {}, + runAddressLaneAttempt, + tryHandleLivingChat + }) + ); + + expect(runtimeInput.llmProvider).toBeUndefined(); + expect(runtimeInput.useMock).toBe(false); + expect(runtimeInput.payloadContextPeriodHint).toBeUndefined(); + expect(runtimeInput.runAddressLaneAttempt).toBe(runAddressLaneAttempt); + expect(runtimeInput.tryHandleLivingChat).toBe(tryHandleLivingChat); + }); +});