diff --git a/docs/TECH/1CLLMARCH-FACT.md b/docs/TECH/1CLLMARCH-FACT.md index b6546eb..125ca5f 100644 --- a/docs/TECH/1CLLMARCH-FACT.md +++ b/docs/TECH/1CLLMARCH-FACT.md @@ -996,7 +996,109 @@ Validation: - `assistantLivingRouter.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 completed)** +Implemented in current pass (Phase 2.30): +1. Extracted address tool-gate skip branch from `assistantService` into dedicated runtime adapter: + - `assistantAddressToolGateRuntimeAdapter.ts` + - introduced: + - `runAssistantAddressToolGateRuntime(...)` +2. Centralized tool-gate skip/runtime sequence (behavior-preserving): + - deterministic early noop when `runAddressLane=true`; + - structured `assistant_address_tool_gate_skip` logging payload projection; + - conditional living-chat fallback invocation when mode is `chat`. +3. Rewired `assistantService` to consume tool-gate runtime adapter output and preserve existing early-return contract for handled chat fallback. +4. Added focused unit tests: + - `assistantAddressToolGateRuntimeAdapter.test.ts` + +Validation: +1. `npm run build` passed. +2. Targeted living/address followup pack passed: + - `assistantAddressToolGateRuntimeAdapter.test.ts` + - `assistantAddressOrchestrationRuntimeAdapter.test.ts` + - `assistantAddressLaneRuntimeAdapter.test.ts` + - `assistantAddressFollowupContext.test.ts` + - `assistantLivingChatMode.test.ts` + - `assistantLivingRouter.test.ts` + - `assistantWave10SettlementCorrectiveRegression.test.ts` + +Implemented in current pass (Phase 2.31): +1. Extracted deep-lane followup binding + normalize bootstrap block from `assistantService` into dedicated runtime adapter: + - `assistantDeepTurnNormalizationRuntimeAdapter.ts` + - introduced: + - `buildAssistantDeepTurnNormalizationRuntime(...)` +2. Centralized deep normalization bootstrap sequence (behavior-preserving): + - followup state binding projection when feature flags are enabled; + - deterministic fallback to raw user question when followup binding is disabled/unavailable; + - normalize request payload assembly and normalizer invocation. +3. Rewired `assistantService` deep-lane bootstrap to consume normalization runtime adapter output. +4. Added focused unit tests: + - `assistantDeepTurnNormalizationRuntimeAdapter.test.ts` + +Validation: +1. `npm run build` passed. +2. Targeted living/address/deep followup pack passed: + - `assistantDeepTurnNormalizationRuntimeAdapter.test.ts` + - `assistantAddressToolGateRuntimeAdapter.test.ts` + - `assistantAddressOrchestrationRuntimeAdapter.test.ts` + - `assistantAddressLaneRuntimeAdapter.test.ts` + - `assistantAddressFollowupContext.test.ts` + - `assistantLivingChatMode.test.ts` + - `assistantLivingRouter.test.ts` + - `assistantWave10SettlementCorrectiveRegression.test.ts` + +Implemented in current pass (Phase 2.32): +1. Extracted deep-lane context/plan/retrieval/guard/grounding/composition orchestration block from `assistantService` into dedicated runtime adapter: + - `assistantDeepTurnAnalysisRuntimeAdapter.ts` + - introduced: + - `runAssistantDeepTurnAnalysisRuntime(...)` +2. Centralized deep analysis sequence wiring (behavior-preserving): + - runtime context stage output propagation; + - execution-plan, retrieval, guard and grounding stage chaining; + - composition stage input projection from grounded retrieval output. +3. Rewired `assistantService` deep-lane middle pipeline to consume analysis runtime adapter output while preserving existing packaging/finalization contracts. +4. Added focused unit tests: + - `assistantDeepTurnAnalysisRuntimeAdapter.test.ts` + +Validation: +1. `npm run build` passed. +2. Targeted living/address/deep followup pack passed: + - `assistantDeepTurnAnalysisRuntimeAdapter.test.ts` + - `assistantDeepTurnNormalizationRuntimeAdapter.test.ts` + - `assistantAddressToolGateRuntimeAdapter.test.ts` + - `assistantAddressOrchestrationRuntimeAdapter.test.ts` + - `assistantAddressLaneRuntimeAdapter.test.ts` + - `assistantAddressFollowupContext.test.ts` + - `assistantLivingChatMode.test.ts` + - `assistantLivingRouter.test.ts` + - `assistantWave10SettlementCorrectiveRegression.test.ts` + +Implemented in current pass (Phase 2.33): +1. Extracted deep-lane response tail (packaging + finalize) from `assistantService` into dedicated runtime adapter: + - `assistantDeepTurnResponseRuntimeAdapter.ts` + - introduced: + - `runAssistantDeepTurnResponseRuntime(...)` +2. Centralized deep response-tail sequence (behavior-preserving): + - packaging runtime invocation with full debug/contract payload projection; + - deep finalization invocation with packaged reply/debug artifacts; + - single response projection back to caller. +3. Rewired `assistantService` deep-lane tail to consume response runtime adapter output. +4. Added focused unit tests: + - `assistantDeepTurnResponseRuntimeAdapter.test.ts` + +Validation: +1. `npm run build` passed. +2. Targeted living/address/deep followup pack passed: + - `assistantDeepTurnResponseRuntimeAdapter.test.ts` + - `assistantDeepTurnAnalysisRuntimeAdapter.test.ts` + - `assistantDeepTurnNormalizationRuntimeAdapter.test.ts` + - `assistantAddressToolGateRuntimeAdapter.test.ts` + - `assistantAddressOrchestrationRuntimeAdapter.test.ts` + - `assistantAddressLaneRuntimeAdapter.test.ts` + - `assistantAddressFollowupContext.test.ts` + - `assistantLivingChatMode.test.ts` + - `assistantLivingRouter.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 completed)** ## Stage 3 (P2): Hybrid Semantic Layer (LLM + Deterministic Guards) diff --git a/llm_normalizer/backend/dist/services/assistantAddressToolGateRuntimeAdapter.js b/llm_normalizer/backend/dist/services/assistantAddressToolGateRuntimeAdapter.js new file mode 100644 index 0000000..5f835d3 --- /dev/null +++ b/llm_normalizer/backend/dist/services/assistantAddressToolGateRuntimeAdapter.js @@ -0,0 +1,56 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.runAssistantAddressToolGateRuntime = runAssistantAddressToolGateRuntime; +async function runAssistantAddressToolGateRuntime(input) { + if (Boolean(input.orchestrationDecision?.runAddressLane)) { + return { + handled: false, + response: null + }; + } + const runtimeMeta = input.addressRuntimeMeta && typeof input.addressRuntimeMeta === "object" + ? input.addressRuntimeMeta + : {}; + const predecomposeContract = runtimeMeta.predecomposeContract && typeof runtimeMeta.predecomposeContract === "object" + ? runtimeMeta.predecomposeContract + : null; + const predecomposePeriod = predecomposeContract?.period && typeof predecomposeContract.period === "object" + ? predecomposeContract.period + : null; + input.logEvent({ + timestamp: input.nowIso(), + level: "info", + service: "assistant_loop", + message: "assistant_address_tool_gate_skip", + sessionId: input.sessionId, + details: { + session_id: input.sessionId, + user_message: input.userMessage, + effective_address_user_message: input.addressInputMessage, + address_llm_predecompose_attempted: Boolean(runtimeMeta.attempted), + address_llm_predecompose_applied: Boolean(runtimeMeta.applied), + address_llm_predecompose_reason: runtimeMeta.reason ?? null, + address_fallback_rule_hit: runtimeMeta.fallbackRuleHit ?? null, + address_sanitized_user_message: runtimeMeta.sanitizedUserMessage ?? null, + assistant_orchestration_contract_v1: runtimeMeta.orchestrationContract ?? null, + address_tool_gate_decision: runtimeMeta.toolGateDecision ?? null, + address_tool_gate_reason: runtimeMeta.toolGateReason ?? null, + address_llm_predecompose_contract_intent: predecomposeContract?.intent ?? null, + address_llm_predecompose_contract_aggregation_profile: predecomposeContract?.aggregation_profile ?? null, + address_llm_predecompose_contract_period_scope: predecomposePeriod?.scope ?? null + } + }); + if (input.livingModeDecision?.mode === "chat") { + const chatHandled = await input.tryHandleLivingChat(input.livingModeDecision, runtimeMeta); + if (chatHandled) { + return { + handled: true, + response: chatHandled + }; + } + } + return { + handled: false, + response: null + }; +} diff --git a/llm_normalizer/backend/dist/services/assistantDeepTurnAnalysisRuntimeAdapter.js b/llm_normalizer/backend/dist/services/assistantDeepTurnAnalysisRuntimeAdapter.js new file mode 100644 index 0000000..4712e98 --- /dev/null +++ b/llm_normalizer/backend/dist/services/assistantDeepTurnAnalysisRuntimeAdapter.js @@ -0,0 +1,69 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.runAssistantDeepTurnAnalysisRuntime = runAssistantDeepTurnAnalysisRuntime; +async function runAssistantDeepTurnAnalysisRuntime(input) { + const contextRuntime = input.runContextRuntime(); + const executionPlanRuntime = input.runExecutionPlanRuntime({ + resolvedRouteSummary: contextRuntime.resolvedRouteSummary, + claimAnchorAudit: contextRuntime.claimAnchorAudit, + temporalGuard: contextRuntime.temporalGuard, + domainPolarityGuardInitial: contextRuntime.domainPolarityGuardInitial + }); + const retrievalRuntime = await input.runRetrievalRuntime({ + executionPlan: executionPlanRuntime.executionPlan, + liveTemporalHint: contextRuntime.liveTemporalHint + }); + const guardRuntime = input.runGuardRuntime({ + retrievalResults: retrievalRuntime.retrievalResults, + domainPolarityGuardInitial: contextRuntime.domainPolarityGuardInitial, + claimAnchorAudit: contextRuntime.claimAnchorAudit, + temporalGuard: contextRuntime.temporalGuard, + focusDomainForGuards: contextRuntime.focusDomainForGuards, + companyAnchors: contextRuntime.companyAnchors, + userMessage: input.userMessage + }); + const groundingRuntime = input.runGroundingRuntime({ + claimType: contextRuntime.claimAnchorAudit.claim_type, + retrievalResults: guardRuntime.retrievalResults, + rbpPlanAudit: executionPlanRuntime.rbpRoutePlanEnforcement.audit, + faPlanAudit: executionPlanRuntime.faRoutePlanEnforcement.audit, + routeSummary: contextRuntime.resolvedRouteSummary, + requirementExtraction: executionPlanRuntime.requirementExtraction, + temporalGuard: contextRuntime.temporalGuard, + polarityAudit: guardRuntime.polarityGuardResult.audit, + evidenceAudit: guardRuntime.evidenceGateResult.audit, + claimAnchorAudit: contextRuntime.claimAnchorAudit, + targetedEvidenceHitRate: guardRuntime.targetedEvidenceResult.audit.targeted_evidence_hit_rate, + businessScopeResolved: contextRuntime.businessScopeResolution.business_scope_resolved ?? null + }); + const compositionRuntime = input.runCompositionRuntime({ + resolvedRouteSummary: contextRuntime.resolvedRouteSummary, + retrievalResults: guardRuntime.retrievalResults, + requirements: groundingRuntime.coverageEvaluation.requirements, + coverageReport: groundingRuntime.coverageEvaluation.coverage, + groundingCheck: groundingRuntime.groundingCheck, + companyAnchors: contextRuntime.companyAnchors + }); + return { + companyAnchors: contextRuntime.companyAnchors, + temporalGuard: contextRuntime.temporalGuard, + claimAnchorAudit: contextRuntime.claimAnchorAudit, + businessScopeResolution: contextRuntime.businessScopeResolution, + resolvedRouteSummary: contextRuntime.resolvedRouteSummary, + requirementExtraction: executionPlanRuntime.requirementExtraction, + executionPlan: executionPlanRuntime.executionPlan, + retrievalCalls: retrievalRuntime.retrievalCalls, + retrievalResultsRaw: retrievalRuntime.retrievalResultsRaw, + retrievalResults: guardRuntime.retrievalResults, + polarityGuardResult: guardRuntime.polarityGuardResult, + targetedEvidenceResult: guardRuntime.targetedEvidenceResult, + evidenceGateResult: guardRuntime.evidenceGateResult, + rbpLiveRouteAudit: groundingRuntime.rbpLiveRouteAudit, + faLiveRouteAudit: groundingRuntime.faLiveRouteAudit, + coverageEvaluation: groundingRuntime.coverageEvaluation, + groundedAnswerEligibilityGuard: groundingRuntime.groundedAnswerEligibilityGuard, + groundingCheck: groundingRuntime.groundingCheck, + questionTypeClass: compositionRuntime.questionTypeClass, + composition: compositionRuntime.composition + }; +} diff --git a/llm_normalizer/backend/dist/services/assistantDeepTurnNormalizationRuntimeAdapter.js b/llm_normalizer/backend/dist/services/assistantDeepTurnNormalizationRuntimeAdapter.js new file mode 100644 index 0000000..1c99803 --- /dev/null +++ b/llm_normalizer/backend/dist/services/assistantDeepTurnNormalizationRuntimeAdapter.js @@ -0,0 +1,40 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.buildAssistantDeepTurnNormalizationRuntime = buildAssistantDeepTurnNormalizationRuntime; +async function buildAssistantDeepTurnNormalizationRuntime(input) { + const followupBinding = input.featureInvestigationStateV1 && + input.featureStateFollowupBindingV1 && + Boolean(input.sessionInvestigationState) + ? input.buildFollowupStateBinding({ + userMessage: input.userMessage, + payloadContext: input.payload.context, + investigationState: input.sessionInvestigationState + }) + : { + normalizedQuestion: input.userMessage, + mergedContext: input.payload.context, + usage: null + }; + const normalizePayload = { + llmProvider: input.payload.llmProvider, + apiKey: input.payload.apiKey, + model: input.payload.model, + baseUrl: input.payload.baseUrl, + temperature: input.payload.temperature, + maxOutputTokens: input.payload.maxOutputTokens, + promptVersion: input.payload.promptVersion ?? "address_query_runtime_v1", + systemPrompt: input.payload.systemPrompt, + developerPrompt: input.payload.developerPrompt, + domainPrompt: input.payload.domainPrompt, + fewShotExamples: input.payload.fewShotExamples, + userQuestion: followupBinding.normalizedQuestion, + context: followupBinding.mergedContext, + useMock: Boolean(input.payload.useMock) + }; + const normalized = await input.normalize(normalizePayload); + return { + followupBinding, + normalizePayload, + normalized + }; +} diff --git a/llm_normalizer/backend/dist/services/assistantDeepTurnResponseRuntimeAdapter.js b/llm_normalizer/backend/dist/services/assistantDeepTurnResponseRuntimeAdapter.js new file mode 100644 index 0000000..f456a9f --- /dev/null +++ b/llm_normalizer/backend/dist/services/assistantDeepTurnResponseRuntimeAdapter.js @@ -0,0 +1,68 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.runAssistantDeepTurnResponseRuntime = runAssistantDeepTurnResponseRuntime; +const assistantDeepTurnPackagingRuntimeAdapter_1 = require("./assistantDeepTurnPackagingRuntimeAdapter"); +const assistantDeepTurnFinalizeRuntimeAdapter_1 = require("./assistantDeepTurnFinalizeRuntimeAdapter"); +function runAssistantDeepTurnResponseRuntime(input) { + const runPackagingRuntimeSafe = input.runPackagingRuntime ?? assistantDeepTurnPackagingRuntimeAdapter_1.runAssistantDeepTurnPackagingRuntime; + const runFinalizeDeepTurnSafe = input.runFinalizeDeepTurn ?? assistantDeepTurnFinalizeRuntimeAdapter_1.finalizeAssistantDeepTurn; + const packagingRuntime = runPackagingRuntimeSafe({ + featureInvestigationStateV1: input.featureInvestigationStateV1, + sessionId: input.sessionId, + questionId: input.questionId, + userMessage: input.userMessage, + normalized: input.normalized, + normalizedQuestion: input.normalizedQuestion, + routeSummary: input.routeSummary, + executionPlan: input.executionPlan, + requirementExtractionRequirements: input.requirementExtractionRequirements, + coverageEvaluationRequirements: input.coverageEvaluationRequirements, + coverageReport: input.coverageReport, + groundingCheck: input.groundingCheck, + retrievalCalls: input.retrievalCalls, + retrievalResultsRaw: input.retrievalResultsRaw, + retrievalResults: input.retrievalResults, + questionTypeClass: input.questionTypeClass, + companyAnchors: input.companyAnchors, + runtimeAnalysisContext: input.runtimeAnalysisContext, + businessScopeResolution: input.businessScopeResolution, + temporalGuard: input.temporalGuard, + polarityAudit: input.polarityAudit, + claimAnchorAudit: input.claimAnchorAudit, + targetedEvidenceAudit: input.targetedEvidenceAudit, + evidenceAdmissibilityGateAudit: input.evidenceAdmissibilityGateAudit, + rbpLiveRouteAudit: input.rbpLiveRouteAudit, + faLiveRouteAudit: input.faLiveRouteAudit, + groundedAnswerEligibilityGuard: input.groundedAnswerEligibilityGuard, + followupStateUsage: input.followupStateUsage, + followupApplied: input.followupApplied, + composition: input.composition, + featureContractsV11: input.featureContractsV11, + featureAnswerPolicyV11: input.featureAnswerPolicyV11, + previousInvestigationState: input.previousInvestigationState ?? null, + addressRuntimeMetaForDeep: input.addressRuntimeMetaForDeep, + extractDroppedIntentSegments: input.extractDroppedIntentSegments, + buildDebugRoutes: input.buildDebugRoutes, + extractExecutionState: input.extractExecutionState, + sanitizeReply: input.sanitizeReply, + persistInvestigationState: input.persistInvestigationState, + messageIdFactory: input.messageIdFactory + }); + const finalization = runFinalizeDeepTurnSafe({ + sessionId: input.sessionId, + assistantReply: packagingRuntime.safeAssistantReply, + replyType: input.composition.reply_type, + assistantItem: packagingRuntime.assistantItem, + debug: packagingRuntime.debug, + deepAnalysisLogDetails: packagingRuntime.deepAnalysisLogDetails, + appendItem: input.appendItem, + getSession: input.getSession, + persistSession: input.persistSession, + cloneConversation: input.cloneConversation, + logEvent: input.logEvent + }); + return { + response: finalization.response, + debug: packagingRuntime.debug + }; +} diff --git a/llm_normalizer/backend/dist/services/assistantService.js b/llm_normalizer/backend/dist/services/assistantService.js index f5b3a0d..6c4dbfc 100644 --- a/llm_normalizer/backend/dist/services/assistantService.js +++ b/llm_normalizer/backend/dist/services/assistantService.js @@ -67,6 +67,7 @@ const capabilitiesRegistry_1 = __importStar(require("./capabilitiesRegistry")); const assistantCanon_1 = __importStar(require("./assistantCanon")); const assistantAddressTurnFinalizeRuntimeAdapter_1 = __importStar(require("./assistantAddressTurnFinalizeRuntimeAdapter")); const assistantCoverageGrounding_1 = __importStar(require("./assistantCoverageGrounding")); +const assistantDeepTurnAnalysisRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnAnalysisRuntimeAdapter")); const assistantDeepTurnCompositionRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnCompositionRuntimeAdapter")); const assistantDeepTurnContextRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnContextRuntimeAdapter")); const assistantDeepTurnFinalizeRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnFinalizeRuntimeAdapter")); @@ -74,9 +75,12 @@ const assistantDeepTurnGuardRuntimeAdapter_1 = __importStar(require("./assistant const assistantDeepTurnGroundingRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnGroundingRuntimeAdapter")); const assistantDeepTurnPackagingRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnPackagingRuntimeAdapter")); const assistantDeepTurnPlanRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnPlanRuntimeAdapter")); +const assistantDeepTurnNormalizationRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnNormalizationRuntimeAdapter")); +const assistantDeepTurnResponseRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnResponseRuntimeAdapter")); const assistantDeepTurnRetrievalRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnRetrievalRuntimeAdapter")); const assistantAddressLaneRuntimeAdapter_1 = __importStar(require("./assistantAddressLaneRuntimeAdapter")); const assistantAddressOrchestrationRuntimeAdapter_1 = __importStar(require("./assistantAddressOrchestrationRuntimeAdapter")); +const assistantAddressToolGateRuntimeAdapter_1 = __importStar(require("./assistantAddressToolGateRuntimeAdapter")); const assistantLivingChatTurnFinalizeRuntimeAdapter_1 = __importStar(require("./assistantLivingChatTurnFinalizeRuntimeAdapter")); const assistantLivingChatRuntimeAdapter_1 = __importStar(require("./assistantLivingChatRuntimeAdapter")); const assistantQueryPlanning_1 = __importStar(require("./assistantQueryPlanning")); @@ -4583,36 +4587,19 @@ class AssistantService { const addressRuntimeMeta = addressOrchestrationRuntime.addressRuntimeMeta; addressRuntimeMetaForDeep = addressRuntimeMeta; const livingModeDecision = addressOrchestrationRuntime.livingModeDecision; - if (!orchestrationDecision.runAddressLane) { - (0, log_1.logJson)({ - timestamp: new Date().toISOString(), - level: "info", - service: "assistant_loop", - message: "assistant_address_tool_gate_skip", - sessionId, - details: { - session_id: sessionId, - user_message: userMessage, - effective_address_user_message: addressInputMessage, - address_llm_predecompose_attempted: Boolean(addressRuntimeMeta?.attempted), - address_llm_predecompose_applied: Boolean(addressRuntimeMeta?.applied), - address_llm_predecompose_reason: addressRuntimeMeta?.reason ?? null, - address_fallback_rule_hit: addressRuntimeMeta?.fallbackRuleHit ?? null, - address_sanitized_user_message: addressRuntimeMeta?.sanitizedUserMessage ?? null, - assistant_orchestration_contract_v1: addressRuntimeMeta?.orchestrationContract ?? null, - address_tool_gate_decision: addressRuntimeMeta?.toolGateDecision ?? null, - address_tool_gate_reason: addressRuntimeMeta?.toolGateReason ?? null, - address_llm_predecompose_contract_intent: addressRuntimeMeta?.predecomposeContract?.intent ?? null, - address_llm_predecompose_contract_aggregation_profile: addressRuntimeMeta?.predecomposeContract?.aggregation_profile ?? null, - address_llm_predecompose_contract_period_scope: addressRuntimeMeta?.predecomposeContract?.period?.scope ?? null - } - }); - if (livingModeDecision.mode === "chat") { - const chatHandled = await tryHandleLivingChat(livingModeDecision, addressRuntimeMeta); - if (chatHandled) { - return chatHandled; - } - } + const toolGateRuntime = await (0, assistantAddressToolGateRuntimeAdapter_1.runAssistantAddressToolGateRuntime)({ + sessionId, + userMessage, + addressInputMessage, + orchestrationDecision, + livingModeDecision, + addressRuntimeMeta, + logEvent: (payload) => (0, log_1.logJson)(payload), + tryHandleLivingChat: (modeDecision, runtimeMeta) => tryHandleLivingChat(modeDecision, runtimeMeta), + nowIso: () => new Date().toISOString() + }); + if (toolGateRuntime.handled && toolGateRuntime.response) { + return toolGateRuntime.response; } if (orchestrationDecision.runAddressLane) { const shouldPreferContextualLane = Boolean(carryover?.followupContext); @@ -4648,145 +4635,125 @@ class AssistantService { } } } - const followupBinding = config_1.FEATURE_ASSISTANT_INVESTIGATION_STATE_V1 && - config_1.FEATURE_ASSISTANT_STATE_FOLLOWUP_BINDING_V1 && - session.investigation_state - ? buildFollowupStateBinding({ - userMessage, - payloadContext: payload.context, - investigationState: session.investigation_state - }) - : { - normalizedQuestion: userMessage, - mergedContext: payload.context, - usage: null - }; - const normalizePayload = { - llmProvider: payload.llmProvider, - apiKey: payload.apiKey, - model: payload.model, - baseUrl: payload.baseUrl, - temperature: payload.temperature, - maxOutputTokens: payload.maxOutputTokens, - promptVersion: payload.promptVersion ?? "address_query_runtime_v1", - systemPrompt: payload.systemPrompt, - developerPrompt: payload.developerPrompt, - domainPrompt: payload.domainPrompt, - fewShotExamples: payload.fewShotExamples, - userQuestion: followupBinding.normalizedQuestion, - context: followupBinding.mergedContext, - useMock: Boolean(payload.useMock) - }; - const normalized = await this.normalizerService.normalize(normalizePayload); - const contextRuntime = (0, assistantDeepTurnContextRuntimeAdapter_1.buildAssistantDeepTurnRuntimeContext)({ + const normalizationRuntime = await (0, assistantDeepTurnNormalizationRuntimeAdapter_1.buildAssistantDeepTurnNormalizationRuntime)({ userMessage, - normalizedPayload: normalized.normalized, - routeSummary: normalized.route_hint_summary, - runtimeAnalysisContext, - followupUsage: followupBinding.usage, - resolveCompanyAnchors: companyAnchorResolver_1.resolveCompanyAnchors, - resolveBusinessScopeAlignment, - inferP0DomainFromMessage, - resolveTemporalGuard: assistantRuntimeGuards_1.resolveTemporalGuard, - resolveDomainPolarityGuard: assistantRuntimeGuards_1.resolveDomainPolarityGuard, - resolveClaimBoundAnchors: assistantClaimBoundEvidence_1.resolveClaimBoundAnchors, - resolveBusinessScopeFromLiveContext - }); - const companyAnchors = contextRuntime.companyAnchors; - const focusDomainForGuards = contextRuntime.focusDomainForGuards; - const temporalGuard = contextRuntime.temporalGuard; - const domainPolarityGuardInitial = contextRuntime.domainPolarityGuardInitial; - const claimAnchorAudit = contextRuntime.claimAnchorAudit; - const businessScopeResolution = contextRuntime.businessScopeResolution; - const resolvedRouteSummary = contextRuntime.resolvedRouteSummary; - const liveTemporalHint = contextRuntime.liveTemporalHint; - const executionPlanRuntime = (0, assistantDeepTurnPlanRuntimeAdapter_1.buildAssistantDeepTurnExecutionPlan)({ - routeSummary: resolvedRouteSummary, - normalizedPayload: normalized.normalized, - userMessage, - claimType: claimAnchorAudit.claim_type, - temporalGuard, - domainPolarityGuardInitial, - extractRequirements, - toExecutionPlan, - enforceRbpLiveRoutePlan, - enforceFaLiveRoutePlan, - applyTemporalHintToExecutionPlan: assistantRuntimeGuards_1.applyTemporalHintToExecutionPlan, - applyPolarityHintToExecutionPlan: assistantRuntimeGuards_1.applyPolarityHintToExecutionPlan - }); - const requirementExtraction = executionPlanRuntime.requirementExtraction; - const rbpRoutePlanEnforcement = executionPlanRuntime.rbpRoutePlanEnforcement; - const faRoutePlanEnforcement = executionPlanRuntime.faRoutePlanEnforcement; - const executionPlan = executionPlanRuntime.executionPlan; - const retrievalRuntime = await (0, assistantDeepTurnRetrievalRuntimeAdapter_1.executeAssistantDeepTurnRetrievalPlan)({ - executionPlan, - liveTemporalHint, - executeRouteRuntime: (route, fragmentText, options) => this.dataLayer.executeRouteRuntime(route, fragmentText, options), - mapNoRouteReason, - buildSkippedResult - }); - const retrievalCalls = retrievalRuntime.retrievalCalls; - const retrievalResultsRaw = retrievalRuntime.retrievalResultsRaw; - let retrievalResults = retrievalRuntime.retrievalResults; - const guardRuntime = (0, assistantDeepTurnGuardRuntimeAdapter_1.applyAssistantDeepTurnRetrievalGuards)({ - retrievalResults, - domainPolarityGuardInitial, - claimAnchorAudit, - temporalGuard, - focusDomainForGuards, - companyAnchors, - userMessage - }); - retrievalResults = guardRuntime.retrievalResults; - const polarityGuardResult = guardRuntime.polarityGuardResult; - const targetedEvidenceResult = guardRuntime.targetedEvidenceResult; - const evidenceGateResult = guardRuntime.evidenceGateResult; - const groundingRuntime = (0, assistantDeepTurnGroundingRuntimeAdapter_1.runAssistantDeepTurnGroundingRuntime)({ - claimType: claimAnchorAudit.claim_type, - retrievalResults, - rbpPlanAudit: rbpRoutePlanEnforcement.audit, - faPlanAudit: faRoutePlanEnforcement.audit, - routeSummary: resolvedRouteSummary, - normalizedPayload: normalized.normalized, - userMessage, - requirementExtraction, - extractRequirements, - evaluateCoverage, - checkGrounding, - temporalGuard, - polarityAudit: polarityGuardResult.audit, - evidenceAudit: evidenceGateResult.audit, - claimAnchorAudit, - targetedEvidenceHitRate: targetedEvidenceResult.audit.targeted_evidence_hit_rate, - businessScopeResolved: businessScopeResolution.business_scope_resolved, - collectRbpLiveRouteAudit, - collectFaLiveRouteAudit - }); - const rbpLiveRouteAudit = groundingRuntime.rbpLiveRouteAudit; - const faLiveRouteAudit = groundingRuntime.faLiveRouteAudit; - const coverageEvaluation = groundingRuntime.coverageEvaluation; - const groundedAnswerEligibilityGuard = groundingRuntime.groundedAnswerEligibilityGuard; - const groundingCheck = groundingRuntime.groundingCheck; - const deepTurnComposition = (0, assistantDeepTurnCompositionRuntimeAdapter_1.buildAssistantDeepTurnComposition)({ - userMessage, - routeSummary: resolvedRouteSummary, - retrievalResults, - requirements: coverageEvaluation.requirements, - coverageReport: coverageEvaluation.coverage, - groundingCheck, - followupUsage: followupBinding.usage, - investigationState: session.investigation_state, - companyAnchors, - normalizedPayload: normalized.normalized, - 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, - hasExplicitPeriodAnchor: (normalizedPayload) => hasExplicitPeriodAnchorFromNormalized(normalizedPayload) - }); - const questionTypeClass = deepTurnComposition.questionTypeClass; - const composition = deepTurnComposition.composition; - const packagingRuntime = (0, assistantDeepTurnPackagingRuntimeAdapter_1.runAssistantDeepTurnPackagingRuntime)({ + payload, featureInvestigationStateV1: config_1.FEATURE_ASSISTANT_INVESTIGATION_STATE_V1, + featureStateFollowupBindingV1: config_1.FEATURE_ASSISTANT_STATE_FOLLOWUP_BINDING_V1, + sessionInvestigationState: session.investigation_state, + buildFollowupStateBinding, + normalize: (normalizePayload) => this.normalizerService.normalize(normalizePayload) + }); + const followupBinding = normalizationRuntime.followupBinding; + const normalized = normalizationRuntime.normalized; + const deepTurnAnalysisRuntime = await (0, assistantDeepTurnAnalysisRuntimeAdapter_1.runAssistantDeepTurnAnalysisRuntime)({ + userMessage, + runContextRuntime: () => (0, assistantDeepTurnContextRuntimeAdapter_1.buildAssistantDeepTurnRuntimeContext)({ + userMessage, + normalizedPayload: normalized.normalized, + routeSummary: normalized.route_hint_summary, + runtimeAnalysisContext, + followupUsage: followupBinding.usage, + resolveCompanyAnchors: companyAnchorResolver_1.resolveCompanyAnchors, + resolveBusinessScopeAlignment, + inferP0DomainFromMessage, + resolveTemporalGuard: assistantRuntimeGuards_1.resolveTemporalGuard, + resolveDomainPolarityGuard: assistantRuntimeGuards_1.resolveDomainPolarityGuard, + resolveClaimBoundAnchors: assistantClaimBoundEvidence_1.resolveClaimBoundAnchors, + resolveBusinessScopeFromLiveContext + }), + runExecutionPlanRuntime: ({ resolvedRouteSummary, claimAnchorAudit, temporalGuard, domainPolarityGuardInitial }) => (0, assistantDeepTurnPlanRuntimeAdapter_1.buildAssistantDeepTurnExecutionPlan)({ + routeSummary: resolvedRouteSummary, + normalizedPayload: normalized.normalized, + userMessage, + claimType: claimAnchorAudit.claim_type, + temporalGuard, + domainPolarityGuardInitial, + extractRequirements, + toExecutionPlan, + enforceRbpLiveRoutePlan, + enforceFaLiveRoutePlan, + applyTemporalHintToExecutionPlan: assistantRuntimeGuards_1.applyTemporalHintToExecutionPlan, + applyPolarityHintToExecutionPlan: assistantRuntimeGuards_1.applyPolarityHintToExecutionPlan + }), + runRetrievalRuntime: ({ executionPlan, liveTemporalHint }) => (0, assistantDeepTurnRetrievalRuntimeAdapter_1.executeAssistantDeepTurnRetrievalPlan)({ + executionPlan: executionPlan, + liveTemporalHint, + executeRouteRuntime: (route, fragmentText, options) => this.dataLayer.executeRouteRuntime(route, fragmentText, options), + mapNoRouteReason, + buildSkippedResult + }), + runGuardRuntime: ({ retrievalResults, domainPolarityGuardInitial, claimAnchorAudit, temporalGuard, focusDomainForGuards, companyAnchors, userMessage: runtimeUserMessage }) => (0, assistantDeepTurnGuardRuntimeAdapter_1.applyAssistantDeepTurnRetrievalGuards)({ + retrievalResults, + domainPolarityGuardInitial, + claimAnchorAudit, + temporalGuard, + focusDomainForGuards, + companyAnchors, + userMessage: runtimeUserMessage + }), + runGroundingRuntime: ({ claimType, retrievalResults, rbpPlanAudit, faPlanAudit, routeSummary, requirementExtraction, temporalGuard, polarityAudit, evidenceAudit, claimAnchorAudit, targetedEvidenceHitRate, businessScopeResolved }) => (0, assistantDeepTurnGroundingRuntimeAdapter_1.runAssistantDeepTurnGroundingRuntime)({ + claimType, + retrievalResults, + rbpPlanAudit, + faPlanAudit, + routeSummary, + normalizedPayload: normalized.normalized, + userMessage, + requirementExtraction, + extractRequirements, + evaluateCoverage, + checkGrounding, + temporalGuard, + polarityAudit, + evidenceAudit, + claimAnchorAudit, + targetedEvidenceHitRate, + businessScopeResolved, + collectRbpLiveRouteAudit, + collectFaLiveRouteAudit + }), + runCompositionRuntime: ({ resolvedRouteSummary, retrievalResults, requirements, coverageReport, groundingCheck, companyAnchors }) => (0, assistantDeepTurnCompositionRuntimeAdapter_1.buildAssistantDeepTurnComposition)({ + userMessage, + routeSummary: resolvedRouteSummary, + retrievalResults, + requirements, + coverageReport, + groundingCheck, + followupUsage: followupBinding.usage, + investigationState: session.investigation_state, + companyAnchors, + normalizedPayload: normalized.normalized, + 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, + hasExplicitPeriodAnchor: (normalizedPayload) => hasExplicitPeriodAnchorFromNormalized(normalizedPayload) + }) + }); + const companyAnchors = deepTurnAnalysisRuntime.companyAnchors; + const temporalGuard = deepTurnAnalysisRuntime.temporalGuard; + const claimAnchorAudit = deepTurnAnalysisRuntime.claimAnchorAudit; + const businessScopeResolution = deepTurnAnalysisRuntime.businessScopeResolution; + const resolvedRouteSummary = deepTurnAnalysisRuntime.resolvedRouteSummary; + const requirementExtraction = deepTurnAnalysisRuntime.requirementExtraction; + const executionPlan = deepTurnAnalysisRuntime.executionPlan; + const retrievalCalls = deepTurnAnalysisRuntime.retrievalCalls; + const retrievalResultsRaw = deepTurnAnalysisRuntime.retrievalResultsRaw; + const retrievalResults = deepTurnAnalysisRuntime.retrievalResults; + const polarityGuardResult = deepTurnAnalysisRuntime.polarityGuardResult; + const targetedEvidenceResult = deepTurnAnalysisRuntime.targetedEvidenceResult; + const evidenceGateResult = deepTurnAnalysisRuntime.evidenceGateResult; + const rbpLiveRouteAudit = deepTurnAnalysisRuntime.rbpLiveRouteAudit; + const faLiveRouteAudit = deepTurnAnalysisRuntime.faLiveRouteAudit; + const coverageEvaluation = deepTurnAnalysisRuntime.coverageEvaluation; + const groundedAnswerEligibilityGuard = deepTurnAnalysisRuntime.groundedAnswerEligibilityGuard; + const groundingCheck = deepTurnAnalysisRuntime.groundingCheck; + const questionTypeClass = deepTurnAnalysisRuntime.questionTypeClass; + const composition = deepTurnAnalysisRuntime.composition; + const deepTurnResponseRuntime = (0, assistantDeepTurnResponseRuntimeAdapter_1.runAssistantDeepTurnResponseRuntime)({ + featureInvestigationStateV1: config_1.FEATURE_ASSISTANT_INVESTIGATION_STATE_V1, + featureContractsV11: config_1.FEATURE_ASSISTANT_CONTRACTS_V11, + featureAnswerPolicyV11: config_1.FEATURE_ASSISTANT_ANSWER_POLICY_V11, sessionId, questionId: userItem.message_id, userMessage, @@ -4821,8 +4788,6 @@ class AssistantService { followupStateUsage: followupBinding.usage, followupApplied: Boolean(followupBinding.usage?.applied), composition, - featureContractsV11: config_1.FEATURE_ASSISTANT_CONTRACTS_V11, - featureAnswerPolicyV11: config_1.FEATURE_ASSISTANT_ANSWER_POLICY_V11, previousInvestigationState: session.investigation_state, addressRuntimeMetaForDeep, extractDroppedIntentSegments: (normalizedPayload) => extractDiscardedIntentSegments(normalizedPayload), @@ -4830,26 +4795,16 @@ class AssistantService { 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)}` - }); - const safeAssistantReply = packagingRuntime.safeAssistantReply; - const debug = packagingRuntime.debug; - const assistantItem = packagingRuntime.assistantItem; - const deepAnalysisLogDetails = packagingRuntime.deepAnalysisLogDetails; - const finalization = (0, assistantDeepTurnFinalizeRuntimeAdapter_1.finalizeAssistantDeepTurn)({ - sessionId, - assistantReply: safeAssistantReply, - replyType: composition.reply_type, - assistantItem, - debug, - deepAnalysisLogDetails, + 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) + logEvent: (payload) => (0, log_1.logJson)(payload), + runPackagingRuntime: (input) => (0, assistantDeepTurnPackagingRuntimeAdapter_1.runAssistantDeepTurnPackagingRuntime)(input), + runFinalizeDeepTurn: (input) => (0, assistantDeepTurnFinalizeRuntimeAdapter_1.finalizeAssistantDeepTurn)(input) }); - return finalization.response; + return deepTurnResponseRuntime.response; } } exports.AssistantService = AssistantService; diff --git a/llm_normalizer/backend/src/services/assistantAddressToolGateRuntimeAdapter.ts b/llm_normalizer/backend/src/services/assistantAddressToolGateRuntimeAdapter.ts new file mode 100644 index 0000000..c76b009 --- /dev/null +++ b/llm_normalizer/backend/src/services/assistantAddressToolGateRuntimeAdapter.ts @@ -0,0 +1,110 @@ +export interface AssistantAddressToolGateRuntimeInput { + sessionId: string; + userMessage: string; + addressInputMessage: string; + orchestrationDecision: { + runAddressLane?: boolean; + [key: string]: unknown; + }; + livingModeDecision: { + mode?: unknown; + [key: string]: unknown; + }; + addressRuntimeMeta: { + attempted?: unknown; + applied?: unknown; + reason?: unknown; + fallbackRuleHit?: unknown; + sanitizedUserMessage?: unknown; + orchestrationContract?: unknown; + toolGateDecision?: unknown; + toolGateReason?: unknown; + predecomposeContract?: { + intent?: unknown; + aggregation_profile?: unknown; + period?: { + scope?: unknown; + } | null; + } | null; + [key: string]: unknown; + } | null; + logEvent: (payload: Record) => void; + tryHandleLivingChat: ( + modeDecision: { mode?: unknown; reason?: unknown }, + addressRuntimeMeta: Record | null + ) => Promise; + nowIso: () => string; +} + +export interface AssistantAddressToolGateRuntimeOutput { + handled: boolean; + response: ResponseType | null; +} + +export async function runAssistantAddressToolGateRuntime( + input: AssistantAddressToolGateRuntimeInput +): Promise> { + if (Boolean(input.orchestrationDecision?.runAddressLane)) { + return { + handled: false, + response: null + }; + } + + const runtimeMeta = + input.addressRuntimeMeta && typeof input.addressRuntimeMeta === "object" + ? input.addressRuntimeMeta + : ({} as Record); + const predecomposeContract = + runtimeMeta.predecomposeContract && typeof runtimeMeta.predecomposeContract === "object" + ? (runtimeMeta.predecomposeContract as { + intent?: unknown; + aggregation_profile?: unknown; + period?: { scope?: unknown } | null; + }) + : null; + const predecomposePeriod = + predecomposeContract?.period && typeof predecomposeContract.period === "object" + ? predecomposeContract.period + : null; + + input.logEvent({ + timestamp: input.nowIso(), + level: "info", + service: "assistant_loop", + message: "assistant_address_tool_gate_skip", + sessionId: input.sessionId, + details: { + session_id: input.sessionId, + user_message: input.userMessage, + effective_address_user_message: input.addressInputMessage, + address_llm_predecompose_attempted: Boolean(runtimeMeta.attempted), + address_llm_predecompose_applied: Boolean(runtimeMeta.applied), + address_llm_predecompose_reason: runtimeMeta.reason ?? null, + address_fallback_rule_hit: runtimeMeta.fallbackRuleHit ?? null, + address_sanitized_user_message: runtimeMeta.sanitizedUserMessage ?? null, + assistant_orchestration_contract_v1: runtimeMeta.orchestrationContract ?? null, + address_tool_gate_decision: runtimeMeta.toolGateDecision ?? null, + address_tool_gate_reason: runtimeMeta.toolGateReason ?? null, + address_llm_predecompose_contract_intent: predecomposeContract?.intent ?? null, + address_llm_predecompose_contract_aggregation_profile: predecomposeContract?.aggregation_profile ?? null, + address_llm_predecompose_contract_period_scope: predecomposePeriod?.scope ?? null + } + }); + + if (input.livingModeDecision?.mode === "chat") { + const chatHandled = await input.tryHandleLivingChat(input.livingModeDecision, runtimeMeta); + if (chatHandled) { + return { + handled: true, + response: chatHandled + }; + } + } + + return { + handled: false, + response: null + }; +} + diff --git a/llm_normalizer/backend/src/services/assistantDeepTurnAnalysisRuntimeAdapter.ts b/llm_normalizer/backend/src/services/assistantDeepTurnAnalysisRuntimeAdapter.ts new file mode 100644 index 0000000..c907df8 --- /dev/null +++ b/llm_normalizer/backend/src/services/assistantDeepTurnAnalysisRuntimeAdapter.ts @@ -0,0 +1,174 @@ +import type { AnswerGroundingCheck, AssistantRequirement, RequirementCoverageReport, UnifiedRetrievalResult } from "../types/assistant"; +import type { RouteHintSummary } from "../types/normalizer"; +import type { + AssistantPlanEnforcementAuditLike, + AssistantRequirementExtractionLike +} from "./assistantDeepTurnPlanRuntimeAdapter"; +import type { + AssistantDeepTurnRetrievalExecutionOutput, + AssistantLiveTemporalHint +} from "./assistantDeepTurnRetrievalRuntimeAdapter"; +import type { AssistantDeepTurnRetrievalGuardPipelineOutput } from "./assistantDeepTurnGuardRuntimeAdapter"; +import type { AssistantDeepTurnGroundingRuntimeOutput } from "./assistantDeepTurnGroundingRuntimeAdapter"; +import type { AssistantDeepTurnCompositionOutput } from "./assistantDeepTurnCompositionRuntimeAdapter"; + +export interface AssistantDeepTurnRuntimeContextLike { + companyAnchors: unknown; + focusDomainForGuards: string | null; + temporalGuard: unknown; + domainPolarityGuardInitial: unknown; + claimAnchorAudit: { + claim_type: string; + [key: string]: unknown; + }; + businessScopeResolution: { + business_scope_resolved?: string[] | null; + [key: string]: unknown; + }; + resolvedRouteSummary: RouteHintSummary | null; + liveTemporalHint: AssistantLiveTemporalHint | null; +} + +export interface RunAssistantDeepTurnAnalysisRuntimeInput { + userMessage: string; + runContextRuntime: () => AssistantDeepTurnRuntimeContextLike; + runExecutionPlanRuntime: (input: { + resolvedRouteSummary: RouteHintSummary | null; + claimAnchorAudit: AssistantDeepTurnRuntimeContextLike["claimAnchorAudit"]; + temporalGuard: unknown; + domainPolarityGuardInitial: unknown; + }) => { + requirementExtraction: AssistantRequirementExtractionLike; + executionPlan: unknown[]; + rbpRoutePlanEnforcement: AssistantPlanEnforcementAuditLike; + faRoutePlanEnforcement: AssistantPlanEnforcementAuditLike; + }; + runRetrievalRuntime: (input: { + executionPlan: unknown[]; + liveTemporalHint: AssistantLiveTemporalHint | null; + }) => Promise; + runGuardRuntime: (input: { + retrievalResults: UnifiedRetrievalResult[]; + domainPolarityGuardInitial: unknown; + claimAnchorAudit: AssistantDeepTurnRuntimeContextLike["claimAnchorAudit"]; + temporalGuard: unknown; + focusDomainForGuards: string | null; + companyAnchors: unknown; + userMessage: string; + }) => AssistantDeepTurnRetrievalGuardPipelineOutput; + runGroundingRuntime: (input: { + claimType: string; + retrievalResults: UnifiedRetrievalResult[]; + rbpPlanAudit: unknown; + faPlanAudit: unknown; + routeSummary: RouteHintSummary | null; + requirementExtraction: AssistantRequirementExtractionLike; + temporalGuard: unknown; + polarityAudit: unknown; + evidenceAudit: unknown; + claimAnchorAudit: AssistantDeepTurnRuntimeContextLike["claimAnchorAudit"]; + targetedEvidenceHitRate?: number | null; + businessScopeResolved?: string[] | null; + }) => AssistantDeepTurnGroundingRuntimeOutput; + runCompositionRuntime: (input: { + resolvedRouteSummary: RouteHintSummary | null; + retrievalResults: UnifiedRetrievalResult[]; + requirements: AssistantRequirement[]; + coverageReport: RequirementCoverageReport; + groundingCheck: AnswerGroundingCheck; + companyAnchors: unknown; + }) => AssistantDeepTurnCompositionOutput; +} + +export interface RunAssistantDeepTurnAnalysisRuntimeOutput { + companyAnchors: unknown; + temporalGuard: unknown; + claimAnchorAudit: AssistantDeepTurnRuntimeContextLike["claimAnchorAudit"]; + businessScopeResolution: AssistantDeepTurnRuntimeContextLike["businessScopeResolution"]; + resolvedRouteSummary: RouteHintSummary | null; + requirementExtraction: AssistantRequirementExtractionLike; + executionPlan: unknown[]; + retrievalCalls: AssistantDeepTurnRetrievalExecutionOutput["retrievalCalls"]; + retrievalResultsRaw: AssistantDeepTurnRetrievalExecutionOutput["retrievalResultsRaw"]; + retrievalResults: UnifiedRetrievalResult[]; + polarityGuardResult: AssistantDeepTurnRetrievalGuardPipelineOutput["polarityGuardResult"]; + targetedEvidenceResult: AssistantDeepTurnRetrievalGuardPipelineOutput["targetedEvidenceResult"]; + evidenceGateResult: AssistantDeepTurnRetrievalGuardPipelineOutput["evidenceGateResult"]; + rbpLiveRouteAudit: AssistantDeepTurnGroundingRuntimeOutput["rbpLiveRouteAudit"]; + faLiveRouteAudit: AssistantDeepTurnGroundingRuntimeOutput["faLiveRouteAudit"]; + coverageEvaluation: AssistantDeepTurnGroundingRuntimeOutput["coverageEvaluation"]; + groundedAnswerEligibilityGuard: AssistantDeepTurnGroundingRuntimeOutput["groundedAnswerEligibilityGuard"]; + groundingCheck: AssistantDeepTurnGroundingRuntimeOutput["groundingCheck"]; + questionTypeClass: AssistantDeepTurnCompositionOutput["questionTypeClass"]; + composition: AssistantDeepTurnCompositionOutput["composition"]; +} + +export async function runAssistantDeepTurnAnalysisRuntime( + input: RunAssistantDeepTurnAnalysisRuntimeInput +): Promise { + const contextRuntime = input.runContextRuntime(); + const executionPlanRuntime = input.runExecutionPlanRuntime({ + resolvedRouteSummary: contextRuntime.resolvedRouteSummary, + claimAnchorAudit: contextRuntime.claimAnchorAudit, + temporalGuard: contextRuntime.temporalGuard, + domainPolarityGuardInitial: contextRuntime.domainPolarityGuardInitial + }); + const retrievalRuntime = await input.runRetrievalRuntime({ + executionPlan: executionPlanRuntime.executionPlan, + liveTemporalHint: contextRuntime.liveTemporalHint + }); + const guardRuntime = input.runGuardRuntime({ + retrievalResults: retrievalRuntime.retrievalResults, + domainPolarityGuardInitial: contextRuntime.domainPolarityGuardInitial, + claimAnchorAudit: contextRuntime.claimAnchorAudit, + temporalGuard: contextRuntime.temporalGuard, + focusDomainForGuards: contextRuntime.focusDomainForGuards, + companyAnchors: contextRuntime.companyAnchors, + userMessage: input.userMessage + }); + const groundingRuntime = input.runGroundingRuntime({ + claimType: contextRuntime.claimAnchorAudit.claim_type, + retrievalResults: guardRuntime.retrievalResults, + rbpPlanAudit: executionPlanRuntime.rbpRoutePlanEnforcement.audit, + faPlanAudit: executionPlanRuntime.faRoutePlanEnforcement.audit, + routeSummary: contextRuntime.resolvedRouteSummary, + requirementExtraction: executionPlanRuntime.requirementExtraction, + temporalGuard: contextRuntime.temporalGuard, + polarityAudit: guardRuntime.polarityGuardResult.audit, + evidenceAudit: guardRuntime.evidenceGateResult.audit, + claimAnchorAudit: contextRuntime.claimAnchorAudit, + targetedEvidenceHitRate: guardRuntime.targetedEvidenceResult.audit.targeted_evidence_hit_rate, + businessScopeResolved: contextRuntime.businessScopeResolution.business_scope_resolved ?? null + }); + const compositionRuntime = input.runCompositionRuntime({ + resolvedRouteSummary: contextRuntime.resolvedRouteSummary, + retrievalResults: guardRuntime.retrievalResults, + requirements: groundingRuntime.coverageEvaluation.requirements, + coverageReport: groundingRuntime.coverageEvaluation.coverage, + groundingCheck: groundingRuntime.groundingCheck, + companyAnchors: contextRuntime.companyAnchors + }); + + return { + companyAnchors: contextRuntime.companyAnchors, + temporalGuard: contextRuntime.temporalGuard, + claimAnchorAudit: contextRuntime.claimAnchorAudit, + businessScopeResolution: contextRuntime.businessScopeResolution, + resolvedRouteSummary: contextRuntime.resolvedRouteSummary, + requirementExtraction: executionPlanRuntime.requirementExtraction, + executionPlan: executionPlanRuntime.executionPlan, + retrievalCalls: retrievalRuntime.retrievalCalls, + retrievalResultsRaw: retrievalRuntime.retrievalResultsRaw, + retrievalResults: guardRuntime.retrievalResults, + polarityGuardResult: guardRuntime.polarityGuardResult, + targetedEvidenceResult: guardRuntime.targetedEvidenceResult, + evidenceGateResult: guardRuntime.evidenceGateResult, + rbpLiveRouteAudit: groundingRuntime.rbpLiveRouteAudit, + faLiveRouteAudit: groundingRuntime.faLiveRouteAudit, + coverageEvaluation: groundingRuntime.coverageEvaluation, + groundedAnswerEligibilityGuard: groundingRuntime.groundedAnswerEligibilityGuard, + groundingCheck: groundingRuntime.groundingCheck, + questionTypeClass: compositionRuntime.questionTypeClass, + composition: compositionRuntime.composition + }; +} diff --git a/llm_normalizer/backend/src/services/assistantDeepTurnNormalizationRuntimeAdapter.ts b/llm_normalizer/backend/src/services/assistantDeepTurnNormalizationRuntimeAdapter.ts new file mode 100644 index 0000000..84f9802 --- /dev/null +++ b/llm_normalizer/backend/src/services/assistantDeepTurnNormalizationRuntimeAdapter.ts @@ -0,0 +1,72 @@ +import type { AssistantMessageRequestPayload } from "../types/assistant"; +import type { NormalizeRequestPayload, NormalizeResponsePayload } from "../types/normalizer"; + +export interface AssistantDeepTurnFollowupBinding { + normalizedQuestion: string; + mergedContext: NormalizeRequestPayload["context"] | undefined; + usage: unknown | null; +} + +export interface BuildAssistantDeepTurnNormalizationRuntimeInput { + userMessage: string; + payload: AssistantMessageRequestPayload; + featureInvestigationStateV1: boolean; + featureStateFollowupBindingV1: boolean; + sessionInvestigationState: unknown | null | undefined; + buildFollowupStateBinding: (input: { + userMessage: string; + payloadContext: NormalizeRequestPayload["context"] | undefined; + investigationState: unknown; + }) => AssistantDeepTurnFollowupBinding; + normalize: (payload: NormalizeRequestPayload) => Promise; +} + +export interface BuildAssistantDeepTurnNormalizationRuntimeOutput { + followupBinding: AssistantDeepTurnFollowupBinding; + normalizePayload: NormalizeRequestPayload; + normalized: NormalizeResponsePayload; +} + +export async function buildAssistantDeepTurnNormalizationRuntime( + input: BuildAssistantDeepTurnNormalizationRuntimeInput +): Promise { + const followupBinding = + input.featureInvestigationStateV1 && + input.featureStateFollowupBindingV1 && + Boolean(input.sessionInvestigationState) + ? input.buildFollowupStateBinding({ + userMessage: input.userMessage, + payloadContext: input.payload.context, + investigationState: input.sessionInvestigationState as unknown + }) + : { + normalizedQuestion: input.userMessage, + mergedContext: input.payload.context, + usage: null + }; + + const normalizePayload: NormalizeRequestPayload = { + llmProvider: input.payload.llmProvider, + apiKey: input.payload.apiKey, + model: input.payload.model, + baseUrl: input.payload.baseUrl, + temperature: input.payload.temperature, + maxOutputTokens: input.payload.maxOutputTokens, + promptVersion: input.payload.promptVersion ?? "address_query_runtime_v1", + systemPrompt: input.payload.systemPrompt, + developerPrompt: input.payload.developerPrompt, + domainPrompt: input.payload.domainPrompt, + fewShotExamples: input.payload.fewShotExamples, + userQuestion: followupBinding.normalizedQuestion, + context: followupBinding.mergedContext, + useMock: Boolean(input.payload.useMock) + }; + + const normalized = await input.normalize(normalizePayload); + + return { + followupBinding, + normalizePayload, + normalized + }; +} diff --git a/llm_normalizer/backend/src/services/assistantDeepTurnResponseRuntimeAdapter.ts b/llm_normalizer/backend/src/services/assistantDeepTurnResponseRuntimeAdapter.ts new file mode 100644 index 0000000..b09cea5 --- /dev/null +++ b/llm_normalizer/backend/src/services/assistantDeepTurnResponseRuntimeAdapter.ts @@ -0,0 +1,148 @@ +import type { AssistantMessageResponsePayload, AssistantReplyType } from "../types/assistant"; +import type { NormalizeResponsePayload, RouteHintSummary } from "../types/normalizer"; +import type { InvestigationStateWithProblemUnits } from "../types/stage2ProblemUnits"; +import { + runAssistantDeepTurnPackagingRuntime, + type AssistantDeepTurnPackagingRuntimeInput, + type AssistantDeepTurnPackagingRuntimeOutput +} from "./assistantDeepTurnPackagingRuntimeAdapter"; +import { + finalizeAssistantDeepTurn, + type FinalizeAssistantDeepTurnInput +} from "./assistantDeepTurnFinalizeRuntimeAdapter"; + +type CompositionLike = { + reply_type: AssistantReplyType; + [key: string]: unknown; +}; + +export interface RunAssistantDeepTurnResponseRuntimeInput { + featureInvestigationStateV1: boolean; + featureContractsV11: boolean; + featureAnswerPolicyV11: boolean; + sessionId: string; + questionId: string; + userMessage: string; + normalized: { + trace_id: string; + prompt_version: string; + schema_version: string; + normalized: NormalizeResponsePayload["normalized"]; + }; + normalizedQuestion: string; + routeSummary: RouteHintSummary | null; + executionPlan: unknown[]; + requirementExtractionRequirements: unknown[]; + coverageEvaluationRequirements: unknown[]; + coverageReport: unknown; + groundingCheck: unknown; + retrievalCalls: unknown[]; + retrievalResultsRaw: unknown[]; + retrievalResults: unknown[]; + questionTypeClass: string; + companyAnchors: unknown; + runtimeAnalysisContext: unknown; + businessScopeResolution: unknown; + temporalGuard: unknown; + polarityAudit: unknown; + claimAnchorAudit: unknown; + targetedEvidenceAudit: unknown; + evidenceAdmissibilityGateAudit: unknown; + rbpLiveRouteAudit: unknown; + faLiveRouteAudit: unknown; + groundedAnswerEligibilityGuard: unknown; + followupStateUsage: unknown; + followupApplied: boolean; + composition: CompositionLike; + previousInvestigationState: InvestigationStateWithProblemUnits | null | undefined; + addressRuntimeMetaForDeep: unknown; + extractDroppedIntentSegments: (normalizedPayload: NormalizeResponsePayload["normalized"]) => string[]; + buildDebugRoutes: (routeSummary: RouteHintSummary | null) => Array>; + extractExecutionState: (normalizedPayload: NormalizeResponsePayload["normalized"]) => unknown[]; + sanitizeReply: (value: string, fallback?: string) => string; + persistInvestigationState: (sessionId: string, snapshot: InvestigationStateWithProblemUnits) => void; + messageIdFactory: () => string; + appendItem: FinalizeAssistantDeepTurnInput["appendItem"]; + getSession: FinalizeAssistantDeepTurnInput["getSession"]; + persistSession: FinalizeAssistantDeepTurnInput["persistSession"]; + cloneConversation: FinalizeAssistantDeepTurnInput["cloneConversation"]; + logEvent: FinalizeAssistantDeepTurnInput["logEvent"]; + runPackagingRuntime?: ( + input: AssistantDeepTurnPackagingRuntimeInput + ) => AssistantDeepTurnPackagingRuntimeOutput; + runFinalizeDeepTurn?: (input: FinalizeAssistantDeepTurnInput) => { response: AssistantMessageResponsePayload }; +} + +export interface RunAssistantDeepTurnResponseRuntimeOutput { + response: AssistantMessageResponsePayload; + debug: Record; +} + +export function runAssistantDeepTurnResponseRuntime( + input: RunAssistantDeepTurnResponseRuntimeInput +): RunAssistantDeepTurnResponseRuntimeOutput { + const runPackagingRuntimeSafe = input.runPackagingRuntime ?? runAssistantDeepTurnPackagingRuntime; + const runFinalizeDeepTurnSafe = input.runFinalizeDeepTurn ?? finalizeAssistantDeepTurn; + + const packagingRuntime = runPackagingRuntimeSafe({ + featureInvestigationStateV1: input.featureInvestigationStateV1, + sessionId: input.sessionId, + questionId: input.questionId, + userMessage: input.userMessage, + normalized: input.normalized, + normalizedQuestion: input.normalizedQuestion, + routeSummary: input.routeSummary, + executionPlan: input.executionPlan as any, + requirementExtractionRequirements: input.requirementExtractionRequirements as any, + coverageEvaluationRequirements: input.coverageEvaluationRequirements as any, + coverageReport: input.coverageReport as any, + groundingCheck: input.groundingCheck as any, + retrievalCalls: input.retrievalCalls as any, + retrievalResultsRaw: input.retrievalResultsRaw as any, + retrievalResults: input.retrievalResults as any, + questionTypeClass: input.questionTypeClass, + companyAnchors: input.companyAnchors, + runtimeAnalysisContext: input.runtimeAnalysisContext as any, + businessScopeResolution: input.businessScopeResolution as any, + temporalGuard: input.temporalGuard as any, + polarityAudit: input.polarityAudit as any, + claimAnchorAudit: input.claimAnchorAudit as any, + targetedEvidenceAudit: input.targetedEvidenceAudit as any, + evidenceAdmissibilityGateAudit: input.evidenceAdmissibilityGateAudit as any, + rbpLiveRouteAudit: input.rbpLiveRouteAudit as any, + faLiveRouteAudit: input.faLiveRouteAudit as any, + groundedAnswerEligibilityGuard: input.groundedAnswerEligibilityGuard as any, + followupStateUsage: input.followupStateUsage as any, + followupApplied: input.followupApplied, + composition: input.composition as any, + featureContractsV11: input.featureContractsV11, + featureAnswerPolicyV11: input.featureAnswerPolicyV11, + previousInvestigationState: input.previousInvestigationState ?? null, + addressRuntimeMetaForDeep: input.addressRuntimeMetaForDeep as any, + extractDroppedIntentSegments: input.extractDroppedIntentSegments, + buildDebugRoutes: input.buildDebugRoutes, + extractExecutionState: input.extractExecutionState, + sanitizeReply: input.sanitizeReply, + persistInvestigationState: input.persistInvestigationState, + messageIdFactory: input.messageIdFactory + }); + + const finalization = runFinalizeDeepTurnSafe({ + sessionId: input.sessionId, + assistantReply: packagingRuntime.safeAssistantReply, + replyType: input.composition.reply_type, + assistantItem: packagingRuntime.assistantItem, + debug: packagingRuntime.debug, + deepAnalysisLogDetails: packagingRuntime.deepAnalysisLogDetails, + appendItem: input.appendItem, + getSession: input.getSession, + persistSession: input.persistSession, + cloneConversation: input.cloneConversation, + logEvent: input.logEvent + }); + + return { + response: finalization.response, + debug: packagingRuntime.debug + }; +} diff --git a/llm_normalizer/backend/src/services/assistantService.ts b/llm_normalizer/backend/src/services/assistantService.ts index 337c7da..1d90317 100644 --- a/llm_normalizer/backend/src/services/assistantService.ts +++ b/llm_normalizer/backend/src/services/assistantService.ts @@ -21,6 +21,7 @@ import * as capabilitiesRegistry_1 from "./capabilitiesRegistry"; import * as assistantCanon_1 from "./assistantCanon"; import * as assistantAddressTurnFinalizeRuntimeAdapter_1 from "./assistantAddressTurnFinalizeRuntimeAdapter"; import * as assistantCoverageGrounding_1 from "./assistantCoverageGrounding"; +import * as assistantDeepTurnAnalysisRuntimeAdapter_1 from "./assistantDeepTurnAnalysisRuntimeAdapter"; import * as assistantDeepTurnCompositionRuntimeAdapter_1 from "./assistantDeepTurnCompositionRuntimeAdapter"; import * as assistantDeepTurnContextRuntimeAdapter_1 from "./assistantDeepTurnContextRuntimeAdapter"; import * as assistantDeepTurnFinalizeRuntimeAdapter_1 from "./assistantDeepTurnFinalizeRuntimeAdapter"; @@ -28,9 +29,12 @@ import * as assistantDeepTurnGuardRuntimeAdapter_1 from "./assistantDeepTurnGuar import * as assistantDeepTurnGroundingRuntimeAdapter_1 from "./assistantDeepTurnGroundingRuntimeAdapter"; import * as assistantDeepTurnPackagingRuntimeAdapter_1 from "./assistantDeepTurnPackagingRuntimeAdapter"; import * as assistantDeepTurnPlanRuntimeAdapter_1 from "./assistantDeepTurnPlanRuntimeAdapter"; +import * as assistantDeepTurnNormalizationRuntimeAdapter_1 from "./assistantDeepTurnNormalizationRuntimeAdapter"; +import * as assistantDeepTurnResponseRuntimeAdapter_1 from "./assistantDeepTurnResponseRuntimeAdapter"; import * as assistantDeepTurnRetrievalRuntimeAdapter_1 from "./assistantDeepTurnRetrievalRuntimeAdapter"; import * as assistantAddressLaneRuntimeAdapter_1 from "./assistantAddressLaneRuntimeAdapter"; import * as assistantAddressOrchestrationRuntimeAdapter_1 from "./assistantAddressOrchestrationRuntimeAdapter"; +import * as assistantAddressToolGateRuntimeAdapter_1 from "./assistantAddressToolGateRuntimeAdapter"; import * as assistantLivingChatTurnFinalizeRuntimeAdapter_1 from "./assistantLivingChatTurnFinalizeRuntimeAdapter"; import * as assistantLivingChatRuntimeAdapter_1 from "./assistantLivingChatRuntimeAdapter"; import * as assistantQueryPlanning_1 from "./assistantQueryPlanning"; @@ -4538,36 +4542,19 @@ export class AssistantService { const addressRuntimeMeta = addressOrchestrationRuntime.addressRuntimeMeta; addressRuntimeMetaForDeep = addressRuntimeMeta; const livingModeDecision = addressOrchestrationRuntime.livingModeDecision; - if (!orchestrationDecision.runAddressLane) { - (0, log_1.logJson)({ - timestamp: new Date().toISOString(), - level: "info", - service: "assistant_loop", - message: "assistant_address_tool_gate_skip", - sessionId, - details: { - session_id: sessionId, - user_message: userMessage, - effective_address_user_message: addressInputMessage, - address_llm_predecompose_attempted: Boolean(addressRuntimeMeta?.attempted), - address_llm_predecompose_applied: Boolean(addressRuntimeMeta?.applied), - address_llm_predecompose_reason: addressRuntimeMeta?.reason ?? null, - address_fallback_rule_hit: addressRuntimeMeta?.fallbackRuleHit ?? null, - address_sanitized_user_message: addressRuntimeMeta?.sanitizedUserMessage ?? null, - assistant_orchestration_contract_v1: addressRuntimeMeta?.orchestrationContract ?? null, - address_tool_gate_decision: addressRuntimeMeta?.toolGateDecision ?? null, - address_tool_gate_reason: addressRuntimeMeta?.toolGateReason ?? null, - address_llm_predecompose_contract_intent: addressRuntimeMeta?.predecomposeContract?.intent ?? null, - address_llm_predecompose_contract_aggregation_profile: addressRuntimeMeta?.predecomposeContract?.aggregation_profile ?? null, - address_llm_predecompose_contract_period_scope: addressRuntimeMeta?.predecomposeContract?.period?.scope ?? null - } - }); - if (livingModeDecision.mode === "chat") { - const chatHandled = await tryHandleLivingChat(livingModeDecision, addressRuntimeMeta); - if (chatHandled) { - return chatHandled; - } - } + const toolGateRuntime = await (0, assistantAddressToolGateRuntimeAdapter_1.runAssistantAddressToolGateRuntime)({ + sessionId, + userMessage, + addressInputMessage, + orchestrationDecision, + livingModeDecision, + addressRuntimeMeta, + logEvent: (payload) => (0, log_1.logJson)(payload), + tryHandleLivingChat: (modeDecision, runtimeMeta) => tryHandleLivingChat(modeDecision, runtimeMeta), + nowIso: () => new Date().toISOString() + }); + if (toolGateRuntime.handled && toolGateRuntime.response) { + return toolGateRuntime.response; } if (orchestrationDecision.runAddressLane) { const shouldPreferContextualLane = Boolean(carryover?.followupContext); @@ -4603,145 +4590,125 @@ export class AssistantService { } } } - const followupBinding = config_1.FEATURE_ASSISTANT_INVESTIGATION_STATE_V1 && - config_1.FEATURE_ASSISTANT_STATE_FOLLOWUP_BINDING_V1 && - session.investigation_state - ? buildFollowupStateBinding({ - userMessage, - payloadContext: payload.context, - investigationState: session.investigation_state - }) - : { - normalizedQuestion: userMessage, - mergedContext: payload.context, - usage: null - }; - const normalizePayload = { - llmProvider: payload.llmProvider, - apiKey: payload.apiKey, - model: payload.model, - baseUrl: payload.baseUrl, - temperature: payload.temperature, - maxOutputTokens: payload.maxOutputTokens, - promptVersion: payload.promptVersion ?? "address_query_runtime_v1", - systemPrompt: payload.systemPrompt, - developerPrompt: payload.developerPrompt, - domainPrompt: payload.domainPrompt, - fewShotExamples: payload.fewShotExamples, - userQuestion: followupBinding.normalizedQuestion, - context: followupBinding.mergedContext, - useMock: Boolean(payload.useMock) - }; - const normalized = await this.normalizerService.normalize(normalizePayload); - const contextRuntime = (0, assistantDeepTurnContextRuntimeAdapter_1.buildAssistantDeepTurnRuntimeContext)({ + const normalizationRuntime = await (0, assistantDeepTurnNormalizationRuntimeAdapter_1.buildAssistantDeepTurnNormalizationRuntime)({ userMessage, - normalizedPayload: normalized.normalized, - routeSummary: normalized.route_hint_summary, - runtimeAnalysisContext, - followupUsage: followupBinding.usage, - resolveCompanyAnchors: companyAnchorResolver_1.resolveCompanyAnchors, - resolveBusinessScopeAlignment, - inferP0DomainFromMessage, - resolveTemporalGuard: assistantRuntimeGuards_1.resolveTemporalGuard, - resolveDomainPolarityGuard: assistantRuntimeGuards_1.resolveDomainPolarityGuard, - resolveClaimBoundAnchors: assistantClaimBoundEvidence_1.resolveClaimBoundAnchors, - resolveBusinessScopeFromLiveContext - }); - const companyAnchors = contextRuntime.companyAnchors; - const focusDomainForGuards = contextRuntime.focusDomainForGuards; - const temporalGuard = contextRuntime.temporalGuard; - const domainPolarityGuardInitial = contextRuntime.domainPolarityGuardInitial; - const claimAnchorAudit = contextRuntime.claimAnchorAudit; - const businessScopeResolution = contextRuntime.businessScopeResolution; - const resolvedRouteSummary = contextRuntime.resolvedRouteSummary; - const liveTemporalHint = contextRuntime.liveTemporalHint; - const executionPlanRuntime = (0, assistantDeepTurnPlanRuntimeAdapter_1.buildAssistantDeepTurnExecutionPlan)({ - routeSummary: resolvedRouteSummary, - normalizedPayload: normalized.normalized, - userMessage, - claimType: claimAnchorAudit.claim_type, - temporalGuard, - domainPolarityGuardInitial, - extractRequirements, - toExecutionPlan, - enforceRbpLiveRoutePlan, - enforceFaLiveRoutePlan, - applyTemporalHintToExecutionPlan: assistantRuntimeGuards_1.applyTemporalHintToExecutionPlan, - applyPolarityHintToExecutionPlan: assistantRuntimeGuards_1.applyPolarityHintToExecutionPlan - }); - const requirementExtraction = executionPlanRuntime.requirementExtraction; - const rbpRoutePlanEnforcement = executionPlanRuntime.rbpRoutePlanEnforcement; - const faRoutePlanEnforcement = executionPlanRuntime.faRoutePlanEnforcement; - const executionPlan = executionPlanRuntime.executionPlan; - const retrievalRuntime = await (0, assistantDeepTurnRetrievalRuntimeAdapter_1.executeAssistantDeepTurnRetrievalPlan)({ - executionPlan, - liveTemporalHint, - executeRouteRuntime: (route, fragmentText, options) => this.dataLayer.executeRouteRuntime(route, fragmentText, options), - mapNoRouteReason, - buildSkippedResult - }); - const retrievalCalls = retrievalRuntime.retrievalCalls; - const retrievalResultsRaw = retrievalRuntime.retrievalResultsRaw; - let retrievalResults = retrievalRuntime.retrievalResults; - const guardRuntime = (0, assistantDeepTurnGuardRuntimeAdapter_1.applyAssistantDeepTurnRetrievalGuards)({ - retrievalResults, - domainPolarityGuardInitial, - claimAnchorAudit, - temporalGuard, - focusDomainForGuards, - companyAnchors, - userMessage - }); - retrievalResults = guardRuntime.retrievalResults; - const polarityGuardResult = guardRuntime.polarityGuardResult; - const targetedEvidenceResult = guardRuntime.targetedEvidenceResult; - const evidenceGateResult = guardRuntime.evidenceGateResult; - const groundingRuntime = (0, assistantDeepTurnGroundingRuntimeAdapter_1.runAssistantDeepTurnGroundingRuntime)({ - claimType: claimAnchorAudit.claim_type, - retrievalResults, - rbpPlanAudit: rbpRoutePlanEnforcement.audit, - faPlanAudit: faRoutePlanEnforcement.audit, - routeSummary: resolvedRouteSummary, - normalizedPayload: normalized.normalized, - userMessage, - requirementExtraction, - extractRequirements, - evaluateCoverage, - checkGrounding, - temporalGuard, - polarityAudit: polarityGuardResult.audit, - evidenceAudit: evidenceGateResult.audit, - claimAnchorAudit, - targetedEvidenceHitRate: targetedEvidenceResult.audit.targeted_evidence_hit_rate, - businessScopeResolved: businessScopeResolution.business_scope_resolved, - collectRbpLiveRouteAudit, - collectFaLiveRouteAudit - }); - const rbpLiveRouteAudit = groundingRuntime.rbpLiveRouteAudit; - const faLiveRouteAudit = groundingRuntime.faLiveRouteAudit; - const coverageEvaluation = groundingRuntime.coverageEvaluation; - const groundedAnswerEligibilityGuard = groundingRuntime.groundedAnswerEligibilityGuard; - const groundingCheck = groundingRuntime.groundingCheck; - const deepTurnComposition = (0, assistantDeepTurnCompositionRuntimeAdapter_1.buildAssistantDeepTurnComposition)({ - userMessage, - routeSummary: resolvedRouteSummary, - retrievalResults, - requirements: coverageEvaluation.requirements, - coverageReport: coverageEvaluation.coverage, - groundingCheck, - followupUsage: followupBinding.usage, - investigationState: session.investigation_state, - companyAnchors, - normalizedPayload: normalized.normalized, - 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, - hasExplicitPeriodAnchor: (normalizedPayload) => hasExplicitPeriodAnchorFromNormalized(normalizedPayload) - }); - const questionTypeClass = deepTurnComposition.questionTypeClass; - const composition = deepTurnComposition.composition; - const packagingRuntime = (0, assistantDeepTurnPackagingRuntimeAdapter_1.runAssistantDeepTurnPackagingRuntime)({ + payload, featureInvestigationStateV1: config_1.FEATURE_ASSISTANT_INVESTIGATION_STATE_V1, + featureStateFollowupBindingV1: config_1.FEATURE_ASSISTANT_STATE_FOLLOWUP_BINDING_V1, + sessionInvestigationState: session.investigation_state, + buildFollowupStateBinding, + normalize: (normalizePayload) => this.normalizerService.normalize(normalizePayload) + }); + const followupBinding = normalizationRuntime.followupBinding; + const normalized = normalizationRuntime.normalized; + const deepTurnAnalysisRuntime = await (0, assistantDeepTurnAnalysisRuntimeAdapter_1.runAssistantDeepTurnAnalysisRuntime)({ + userMessage, + runContextRuntime: () => (0, assistantDeepTurnContextRuntimeAdapter_1.buildAssistantDeepTurnRuntimeContext)({ + userMessage, + normalizedPayload: normalized.normalized, + routeSummary: normalized.route_hint_summary, + runtimeAnalysisContext, + followupUsage: followupBinding.usage, + resolveCompanyAnchors: companyAnchorResolver_1.resolveCompanyAnchors, + resolveBusinessScopeAlignment, + inferP0DomainFromMessage, + resolveTemporalGuard: assistantRuntimeGuards_1.resolveTemporalGuard, + resolveDomainPolarityGuard: assistantRuntimeGuards_1.resolveDomainPolarityGuard, + resolveClaimBoundAnchors: assistantClaimBoundEvidence_1.resolveClaimBoundAnchors, + resolveBusinessScopeFromLiveContext + }), + runExecutionPlanRuntime: ({ resolvedRouteSummary, claimAnchorAudit, temporalGuard, domainPolarityGuardInitial }) => (0, assistantDeepTurnPlanRuntimeAdapter_1.buildAssistantDeepTurnExecutionPlan)({ + routeSummary: resolvedRouteSummary, + normalizedPayload: normalized.normalized, + userMessage, + claimType: claimAnchorAudit.claim_type, + temporalGuard, + domainPolarityGuardInitial, + extractRequirements, + toExecutionPlan, + enforceRbpLiveRoutePlan, + enforceFaLiveRoutePlan, + applyTemporalHintToExecutionPlan: assistantRuntimeGuards_1.applyTemporalHintToExecutionPlan, + applyPolarityHintToExecutionPlan: assistantRuntimeGuards_1.applyPolarityHintToExecutionPlan + }), + runRetrievalRuntime: ({ executionPlan, liveTemporalHint }) => (0, assistantDeepTurnRetrievalRuntimeAdapter_1.executeAssistantDeepTurnRetrievalPlan)({ + executionPlan: executionPlan, + liveTemporalHint, + executeRouteRuntime: (route, fragmentText, options) => this.dataLayer.executeRouteRuntime(route, fragmentText, options), + mapNoRouteReason, + buildSkippedResult + }), + runGuardRuntime: ({ retrievalResults, domainPolarityGuardInitial, claimAnchorAudit, temporalGuard, focusDomainForGuards, companyAnchors, userMessage: runtimeUserMessage }) => (0, assistantDeepTurnGuardRuntimeAdapter_1.applyAssistantDeepTurnRetrievalGuards)({ + retrievalResults, + domainPolarityGuardInitial, + claimAnchorAudit, + temporalGuard, + focusDomainForGuards, + companyAnchors, + userMessage: runtimeUserMessage + }), + runGroundingRuntime: ({ claimType, retrievalResults, rbpPlanAudit, faPlanAudit, routeSummary, requirementExtraction, temporalGuard, polarityAudit, evidenceAudit, claimAnchorAudit, targetedEvidenceHitRate, businessScopeResolved }) => (0, assistantDeepTurnGroundingRuntimeAdapter_1.runAssistantDeepTurnGroundingRuntime)({ + claimType, + retrievalResults, + rbpPlanAudit, + faPlanAudit, + routeSummary, + normalizedPayload: normalized.normalized, + userMessage, + requirementExtraction, + extractRequirements, + evaluateCoverage, + checkGrounding, + temporalGuard, + polarityAudit, + evidenceAudit, + claimAnchorAudit, + targetedEvidenceHitRate, + businessScopeResolved, + collectRbpLiveRouteAudit, + collectFaLiveRouteAudit + }), + runCompositionRuntime: ({ resolvedRouteSummary, retrievalResults, requirements, coverageReport, groundingCheck, companyAnchors }) => (0, assistantDeepTurnCompositionRuntimeAdapter_1.buildAssistantDeepTurnComposition)({ + userMessage, + routeSummary: resolvedRouteSummary, + retrievalResults, + requirements, + coverageReport, + groundingCheck, + followupUsage: followupBinding.usage, + investigationState: session.investigation_state, + companyAnchors, + normalizedPayload: normalized.normalized, + 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, + hasExplicitPeriodAnchor: (normalizedPayload) => hasExplicitPeriodAnchorFromNormalized(normalizedPayload) + }) + }); + const companyAnchors = deepTurnAnalysisRuntime.companyAnchors; + const temporalGuard = deepTurnAnalysisRuntime.temporalGuard; + const claimAnchorAudit = deepTurnAnalysisRuntime.claimAnchorAudit; + const businessScopeResolution = deepTurnAnalysisRuntime.businessScopeResolution; + const resolvedRouteSummary = deepTurnAnalysisRuntime.resolvedRouteSummary; + const requirementExtraction = deepTurnAnalysisRuntime.requirementExtraction; + const executionPlan = deepTurnAnalysisRuntime.executionPlan; + const retrievalCalls = deepTurnAnalysisRuntime.retrievalCalls; + const retrievalResultsRaw = deepTurnAnalysisRuntime.retrievalResultsRaw; + const retrievalResults = deepTurnAnalysisRuntime.retrievalResults; + const polarityGuardResult = deepTurnAnalysisRuntime.polarityGuardResult; + const targetedEvidenceResult = deepTurnAnalysisRuntime.targetedEvidenceResult; + const evidenceGateResult = deepTurnAnalysisRuntime.evidenceGateResult; + const rbpLiveRouteAudit = deepTurnAnalysisRuntime.rbpLiveRouteAudit; + const faLiveRouteAudit = deepTurnAnalysisRuntime.faLiveRouteAudit; + const coverageEvaluation = deepTurnAnalysisRuntime.coverageEvaluation; + const groundedAnswerEligibilityGuard = deepTurnAnalysisRuntime.groundedAnswerEligibilityGuard; + const groundingCheck = deepTurnAnalysisRuntime.groundingCheck; + const questionTypeClass = deepTurnAnalysisRuntime.questionTypeClass; + const composition = deepTurnAnalysisRuntime.composition; + const deepTurnResponseRuntime = (0, assistantDeepTurnResponseRuntimeAdapter_1.runAssistantDeepTurnResponseRuntime)({ + featureInvestigationStateV1: config_1.FEATURE_ASSISTANT_INVESTIGATION_STATE_V1, + featureContractsV11: config_1.FEATURE_ASSISTANT_CONTRACTS_V11, + featureAnswerPolicyV11: config_1.FEATURE_ASSISTANT_ANSWER_POLICY_V11, sessionId, questionId: userItem.message_id, userMessage, @@ -4776,8 +4743,6 @@ export class AssistantService { followupStateUsage: followupBinding.usage, followupApplied: Boolean(followupBinding.usage?.applied), composition, - featureContractsV11: config_1.FEATURE_ASSISTANT_CONTRACTS_V11, - featureAnswerPolicyV11: config_1.FEATURE_ASSISTANT_ANSWER_POLICY_V11, previousInvestigationState: session.investigation_state, addressRuntimeMetaForDeep, extractDroppedIntentSegments: (normalizedPayload) => extractDiscardedIntentSegments(normalizedPayload), @@ -4785,26 +4750,16 @@ export class AssistantService { 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)}` - }); - const safeAssistantReply = packagingRuntime.safeAssistantReply; - const debug = packagingRuntime.debug; - const assistantItem = packagingRuntime.assistantItem; - const deepAnalysisLogDetails = packagingRuntime.deepAnalysisLogDetails; - const finalization = (0, assistantDeepTurnFinalizeRuntimeAdapter_1.finalizeAssistantDeepTurn)({ - sessionId, - assistantReply: safeAssistantReply, - replyType: composition.reply_type, - assistantItem, - debug, - deepAnalysisLogDetails, + 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) + logEvent: (payload) => (0, log_1.logJson)(payload), + runPackagingRuntime: (input) => (0, assistantDeepTurnPackagingRuntimeAdapter_1.runAssistantDeepTurnPackagingRuntime)(input), + runFinalizeDeepTurn: (input) => (0, assistantDeepTurnFinalizeRuntimeAdapter_1.finalizeAssistantDeepTurn)(input) }); - return finalization.response; + return deepTurnResponseRuntime.response; } } diff --git a/llm_normalizer/backend/tests/assistantAddressToolGateRuntimeAdapter.test.ts b/llm_normalizer/backend/tests/assistantAddressToolGateRuntimeAdapter.test.ts new file mode 100644 index 0000000..a8c78c7 --- /dev/null +++ b/llm_normalizer/backend/tests/assistantAddressToolGateRuntimeAdapter.test.ts @@ -0,0 +1,80 @@ +import { describe, expect, it, vi } from "vitest"; +import { runAssistantAddressToolGateRuntime } from "../src/services/assistantAddressToolGateRuntimeAdapter"; + +describe("assistant address tool-gate runtime adapter", () => { + it("does nothing when runAddressLane is true", async () => { + const logEvent = vi.fn(); + const tryHandleLivingChat = vi.fn(async () => "chat-response"); + + const result = await runAssistantAddressToolGateRuntime({ + sessionId: "asst-1", + userMessage: "вопрос", + addressInputMessage: "вопрос", + orchestrationDecision: { runAddressLane: true }, + livingModeDecision: { mode: "chat", reason: "x" }, + addressRuntimeMeta: {}, + logEvent, + tryHandleLivingChat, + nowIso: () => "2026-04-10T00:00:00.000Z" + }); + + expect(result.handled).toBe(false); + expect(result.response).toBeNull(); + expect(logEvent).not.toHaveBeenCalled(); + expect(tryHandleLivingChat).not.toHaveBeenCalled(); + }); + + it("logs skip and returns chat response when chat fallback handles", async () => { + const logEvent = vi.fn(); + const tryHandleLivingChat = vi.fn(async () => ({ ok: true })); + + const result = await runAssistantAddressToolGateRuntime({ + sessionId: "asst-2", + userMessage: "вопрос", + addressInputMessage: "канон", + orchestrationDecision: { runAddressLane: false }, + livingModeDecision: { mode: "chat", reason: "predecompose_unsupported_mode" }, + addressRuntimeMeta: { + attempted: true, + applied: false, + reason: "normalize_failed", + predecomposeContract: { + intent: "unknown", + aggregation_profile: "unknown", + period: { scope: "unspecified" } + } + }, + logEvent, + tryHandleLivingChat, + nowIso: () => "2026-04-10T00:00:00.000Z" + }); + + expect(result.handled).toBe(true); + expect(result.response).toEqual({ ok: true }); + expect(logEvent).toHaveBeenCalledTimes(1); + expect(tryHandleLivingChat).toHaveBeenCalledTimes(1); + }); + + it("logs skip and returns unhandled when mode is not chat", async () => { + const logEvent = vi.fn(); + const tryHandleLivingChat = vi.fn(async () => ({ ok: true })); + + const result = await runAssistantAddressToolGateRuntime({ + sessionId: "asst-3", + userMessage: "вопрос", + addressInputMessage: "канон", + orchestrationDecision: { runAddressLane: false }, + livingModeDecision: { mode: "deep_analysis", reason: "strong_data_signal_detected" }, + addressRuntimeMeta: {}, + logEvent, + tryHandleLivingChat, + nowIso: () => "2026-04-10T00:00:00.000Z" + }); + + expect(result.handled).toBe(false); + expect(result.response).toBeNull(); + expect(logEvent).toHaveBeenCalledTimes(1); + expect(tryHandleLivingChat).not.toHaveBeenCalled(); + }); +}); + diff --git a/llm_normalizer/backend/tests/assistantDeepTurnAnalysisRuntimeAdapter.test.ts b/llm_normalizer/backend/tests/assistantDeepTurnAnalysisRuntimeAdapter.test.ts new file mode 100644 index 0000000..c0c79a7 --- /dev/null +++ b/llm_normalizer/backend/tests/assistantDeepTurnAnalysisRuntimeAdapter.test.ts @@ -0,0 +1,164 @@ +import { describe, expect, it, vi } from "vitest"; +import { runAssistantDeepTurnAnalysisRuntime } from "../src/services/assistantDeepTurnAnalysisRuntimeAdapter"; + +describe("assistant deep turn analysis runtime adapter", () => { + it("orchestrates deep pipeline steps in deterministic order", async () => { + const callOrder: string[] = []; + + const runContextRuntime = vi.fn(() => { + callOrder.push("context"); + return { + companyAnchors: { accounts: ["60.01"] }, + focusDomainForGuards: "settlements_60_62", + temporalGuard: { primary_period_window: { from: "2020-07-01", to: "2020-07-31" } }, + domainPolarityGuardInitial: { polarity: "supplier_payable" }, + claimAnchorAudit: { claim_type: "prove_settlement_closure_state" }, + businessScopeResolution: { business_scope_resolved: ["company_specific_accounting"] }, + resolvedRouteSummary: { mode: "deterministic_v2", decisions: [] as any[] } as any, + liveTemporalHint: { + as_of_date: "2020-07-31", + period_from: null, + period_to: null, + source: "analysis_context" + } + }; + }); + const runExecutionPlanRuntime = vi.fn((input) => { + callOrder.push("plan"); + expect(input.claimAnchorAudit.claim_type).toBe("prove_settlement_closure_state"); + return { + requirementExtraction: { + requirements: [{ id: "R1" }], + byFragment: new Map([["F1", ["R1"]]]) + }, + executionPlan: [{ fragment_id: "F1" }], + rbpRoutePlanEnforcement: { executionPlan: [{ fragment_id: "F1" }], audit: { rbp: true } }, + faRoutePlanEnforcement: { executionPlan: [{ fragment_id: "F1" }], audit: { fa: true } } + } as any; + }); + const runRetrievalRuntime = vi.fn(async (input) => { + callOrder.push("retrieval"); + expect(input.liveTemporalHint?.as_of_date).toBe("2020-07-31"); + return { + retrievalCalls: [{ fragment_id: "F1", route: "store_canonical" }], + retrievalResultsRaw: [{ fragment_id: "F1", route: "store_canonical", raw_result: {} }], + retrievalResults: [{ fragment_id: "F1", requirement_ids: ["R1"] }] + } as any; + }); + const runGuardRuntime = vi.fn((input) => { + callOrder.push("guard"); + expect(input.retrievalResults[0].fragment_id).toBe("F1"); + return { + retrievalResults: [{ fragment_id: "F1-guarded", requirement_ids: ["R1"] }], + polarityGuardResult: { audit: { polarity: "supplier_payable" } }, + targetedEvidenceResult: { + audit: { + targeted_evidence_hit_rate: 0.5 + } + }, + evidenceGateResult: { + audit: { + admissible_evidence_count: 3 + } + } + } as any; + }); + const runGroundingRuntime = vi.fn((input) => { + callOrder.push("grounding"); + expect(input.claimType).toBe("prove_settlement_closure_state"); + expect(input.targetedEvidenceHitRate).toBe(0.5); + expect(input.businessScopeResolved).toEqual(["company_specific_accounting"]); + return { + rbpLiveRouteAudit: { routed: 1 }, + faLiveRouteAudit: { routed: 1 }, + coverageEvaluation: { + requirements: [{ id: "R1" }], + coverage: { requirements_total: 1, requirements_covered: 1 } + }, + groundedAnswerEligibilityGuard: { eligible: true }, + groundingCheck: { status: "grounded", reasons: [] } + } as any; + }); + const runCompositionRuntime = vi.fn((input) => { + callOrder.push("composition"); + expect(input.retrievalResults[0].fragment_id).toBe("F1-guarded"); + return { + questionTypeClass: "causal_trace", + composition: { reply_type: "factual", answer: "ok" } + } as any; + }); + + const runtime = await runAssistantDeepTurnAnalysisRuntime({ + userMessage: "where closure failed", + runContextRuntime, + runExecutionPlanRuntime, + runRetrievalRuntime, + runGuardRuntime, + runGroundingRuntime, + runCompositionRuntime + }); + + expect(callOrder).toEqual(["context", "plan", "retrieval", "guard", "grounding", "composition"]); + expect(runtime.retrievalResults[0].fragment_id).toBe("F1-guarded"); + expect(runtime.questionTypeClass).toBe("causal_trace"); + expect(runtime.composition.reply_type).toBe("factual"); + }); + + it("passes null business scope when not resolved", async () => { + const runGroundingRuntime = vi.fn(() => ({ + rbpLiveRouteAudit: {}, + faLiveRouteAudit: {}, + coverageEvaluation: { requirements: [], coverage: {} }, + groundedAnswerEligibilityGuard: {}, + groundingCheck: { status: "no_grounded_answer", reasons: [] } + })); + + await runAssistantDeepTurnAnalysisRuntime({ + userMessage: "question", + runContextRuntime: () => + ({ + companyAnchors: {}, + focusDomainForGuards: null, + temporalGuard: {}, + domainPolarityGuardInitial: {}, + claimAnchorAudit: { claim_type: "unknown" }, + businessScopeResolution: {}, + resolvedRouteSummary: null, + liveTemporalHint: null + }) as any, + runExecutionPlanRuntime: () => + ({ + requirementExtraction: { requirements: [], byFragment: new Map() }, + executionPlan: [], + rbpRoutePlanEnforcement: { executionPlan: [], audit: {} }, + faRoutePlanEnforcement: { executionPlan: [], audit: {} } + }) as any, + runRetrievalRuntime: async () => + ({ + retrievalCalls: [], + retrievalResultsRaw: [], + retrievalResults: [] + }) as any, + runGuardRuntime: () => + ({ + retrievalResults: [], + polarityGuardResult: { audit: {} }, + targetedEvidenceResult: { audit: { targeted_evidence_hit_rate: null } }, + evidenceGateResult: { audit: {} } + }) as any, + runGroundingRuntime: runGroundingRuntime as any, + runCompositionRuntime: () => + ({ + questionTypeClass: "single_fact_lookup", + composition: { reply_type: "partial_coverage" } + }) as any + }); + + expect(runGroundingRuntime).toHaveBeenCalledWith( + expect.objectContaining({ + routeSummary: null, + businessScopeResolved: null + }) + ); + }); +}); diff --git a/llm_normalizer/backend/tests/assistantDeepTurnNormalizationRuntimeAdapter.test.ts b/llm_normalizer/backend/tests/assistantDeepTurnNormalizationRuntimeAdapter.test.ts new file mode 100644 index 0000000..de4d526 --- /dev/null +++ b/llm_normalizer/backend/tests/assistantDeepTurnNormalizationRuntimeAdapter.test.ts @@ -0,0 +1,145 @@ +import { describe, expect, it, vi } from "vitest"; +import { buildAssistantDeepTurnNormalizationRuntime } from "../src/services/assistantDeepTurnNormalizationRuntimeAdapter"; + +describe("assistant deep turn normalization runtime adapter", () => { + it("uses followup state binding when feature flags are enabled and state exists", async () => { + const followupBinding = { + normalizedQuestion: "normalized question", + mergedContext: { + period_hint: "2020-07", + business_context: "ctx-from-followup" + }, + usage: { + applied: true + } + }; + const buildFollowupStateBinding = vi.fn(() => followupBinding); + const normalize = vi.fn(async (request) => ({ + trace_id: "trace-1", + ok: true, + normalized: { schema_version: "normalized_query_v2_0_2" } as any, + route_hint_summary: null, + raw_model_output: {}, + validation: { passed: true, errors: [] }, + usage: { input_tokens: 10, output_tokens: 20, total_tokens: 30 }, + latency_ms: 7, + prompt_version: String(request.promptVersion ?? ""), + schema_version: "normalized_query_v2_0_2", + request_count_for_case: 1 + })); + + const runtime = await buildAssistantDeepTurnNormalizationRuntime({ + userMessage: "raw question", + payload: { + llmProvider: "openai", + apiKey: "k", + model: "m", + baseUrl: "https://api.example.com", + temperature: 0.2, + maxOutputTokens: 333, + promptVersion: "normalizer_v2_0_2", + systemPrompt: "sys", + developerPrompt: "dev", + domainPrompt: "dom", + fewShotExamples: "few", + context: { + period_hint: "2020-06" + }, + useMock: true + }, + featureInvestigationStateV1: true, + featureStateFollowupBindingV1: true, + sessionInvestigationState: { + active_domain: "settlements_60_62" + }, + buildFollowupStateBinding, + normalize + }); + + expect(buildFollowupStateBinding).toHaveBeenCalledTimes(1); + expect(normalize).toHaveBeenCalledTimes(1); + expect(normalize).toHaveBeenCalledWith({ + llmProvider: "openai", + apiKey: "k", + model: "m", + baseUrl: "https://api.example.com", + temperature: 0.2, + maxOutputTokens: 333, + promptVersion: "normalizer_v2_0_2", + systemPrompt: "sys", + developerPrompt: "dev", + domainPrompt: "dom", + fewShotExamples: "few", + userQuestion: "normalized question", + context: { + period_hint: "2020-07", + business_context: "ctx-from-followup" + }, + useMock: true + }); + expect(runtime.followupBinding).toBe(followupBinding); + expect(runtime.normalizePayload.userQuestion).toBe("normalized question"); + }); + + it("falls back to raw user message when followup binding is disabled", async () => { + const buildFollowupStateBinding = vi.fn(); + const normalize = vi.fn(async () => ({ + trace_id: "trace-2", + ok: true, + normalized: { schema_version: "normalized_query_v2_0_2" } as any, + route_hint_summary: null, + raw_model_output: {}, + validation: { passed: true, errors: [] }, + usage: { input_tokens: 1, output_tokens: 1, total_tokens: 2 }, + latency_ms: 1, + prompt_version: "address_query_runtime_v1", + schema_version: "normalized_query_v2_0_2", + request_count_for_case: 1 + })); + + const runtime = await buildAssistantDeepTurnNormalizationRuntime({ + userMessage: "raw fallback", + payload: { + llmProvider: "openai", + context: { + business_context: "payload-context" + }, + useMock: undefined + }, + featureInvestigationStateV1: false, + featureStateFollowupBindingV1: true, + sessionInvestigationState: { + some: "state" + }, + buildFollowupStateBinding, + normalize + }); + + expect(buildFollowupStateBinding).not.toHaveBeenCalled(); + expect(normalize).toHaveBeenCalledWith({ + llmProvider: "openai", + apiKey: undefined, + model: undefined, + baseUrl: undefined, + temperature: undefined, + maxOutputTokens: undefined, + promptVersion: "address_query_runtime_v1", + systemPrompt: undefined, + developerPrompt: undefined, + domainPrompt: undefined, + fewShotExamples: undefined, + userQuestion: "raw fallback", + context: { + business_context: "payload-context" + }, + useMock: false + }); + expect(runtime.followupBinding).toEqual({ + normalizedQuestion: "raw fallback", + mergedContext: { + business_context: "payload-context" + }, + usage: null + }); + }); +}); diff --git a/llm_normalizer/backend/tests/assistantDeepTurnResponseRuntimeAdapter.test.ts b/llm_normalizer/backend/tests/assistantDeepTurnResponseRuntimeAdapter.test.ts new file mode 100644 index 0000000..0f379dc --- /dev/null +++ b/llm_normalizer/backend/tests/assistantDeepTurnResponseRuntimeAdapter.test.ts @@ -0,0 +1,163 @@ +import { describe, expect, it, vi } from "vitest"; +import { runAssistantDeepTurnResponseRuntime } from "../src/services/assistantDeepTurnResponseRuntimeAdapter"; + +function buildBaseInput(overrides: Record = {}) { + return { + featureInvestigationStateV1: true, + featureContractsV11: true, + featureAnswerPolicyV11: true, + sessionId: "asst-1", + questionId: "msg-q1", + userMessage: "question", + normalized: { + trace_id: "trace-1", + prompt_version: "normalizer_v2_0_2", + schema_version: "normalized_query_v2_0_2", + normalized: { schema_version: "normalized_query_v2_0_2" } + }, + normalizedQuestion: "normalized-question", + routeSummary: { mode: "deterministic_v2", decisions: [] as any[] }, + executionPlan: [], + requirementExtractionRequirements: [], + coverageEvaluationRequirements: [], + coverageReport: {}, + groundingCheck: { status: "no_grounded_answer", reasons: [] }, + retrievalCalls: [], + retrievalResultsRaw: [], + retrievalResults: [], + questionTypeClass: "single_fact_lookup", + companyAnchors: {}, + runtimeAnalysisContext: {}, + businessScopeResolution: {}, + temporalGuard: {}, + polarityAudit: {}, + claimAnchorAudit: {}, + targetedEvidenceAudit: {}, + evidenceAdmissibilityGateAudit: {}, + rbpLiveRouteAudit: {}, + faLiveRouteAudit: {}, + groundedAnswerEligibilityGuard: {}, + followupStateUsage: null, + followupApplied: false, + composition: { + reply_type: "factual", + assistant_reply: "assistant answer" + }, + previousInvestigationState: null, + addressRuntimeMetaForDeep: null, + extractDroppedIntentSegments: () => [], + buildDebugRoutes: () => [], + extractExecutionState: () => [], + sanitizeReply: (value: string) => value, + persistInvestigationState: () => {}, + messageIdFactory: () => "msg-a1", + appendItem: () => {}, + getSession: () => ({ session_id: "asst-1", items: [] }), + persistSession: () => {}, + cloneConversation: () => [], + logEvent: () => {}, + ...overrides + } as any; +} + +describe("assistant deep turn response runtime adapter", () => { + it("wires packaging output into deep finalization", () => { + const runPackagingRuntime = vi.fn(() => ({ + messageId: "msg-a1", + investigationStateSnapshot: null, + droppedIntentSegments: [], + analysisContextForContract: null, + routesForDebug: [], + resolvedExecutionState: [], + safeAssistantReplyBase: "base", + safeAssistantReply: "safe-reply", + debug: { trace_id: "trace-1" }, + assistantItem: { + message_id: "msg-a1", + session_id: "asst-1", + role: "assistant", + text: "safe-reply", + reply_type: "factual", + created_at: "2026-04-10T00:00:00.000Z", + trace_id: "trace-1", + debug: null + }, + deepAnalysisLogDetails: { info: "ok" } + })); + const responsePayload = { + ok: true, + session_id: "asst-1", + conversation: [], + debug: { trace_id: "trace-1" } + }; + const runFinalizeDeepTurn = vi.fn(() => ({ + response: responsePayload + })); + + const runtime = runAssistantDeepTurnResponseRuntime( + buildBaseInput({ + runPackagingRuntime, + runFinalizeDeepTurn + }) + ); + + expect(runPackagingRuntime).toHaveBeenCalledTimes(1); + expect(runFinalizeDeepTurn).toHaveBeenCalledWith( + expect.objectContaining({ + sessionId: "asst-1", + assistantReply: "safe-reply", + replyType: "factual" + }) + ); + expect(runtime.response).toEqual(responsePayload); + expect(runtime.debug).toEqual({ trace_id: "trace-1" }); + }); + + it("passes feature flags and followup flags into packaging stage", () => { + const runPackagingRuntime = vi.fn(() => ({ + messageId: "msg-a1", + investigationStateSnapshot: null, + droppedIntentSegments: [], + analysisContextForContract: null, + routesForDebug: [], + resolvedExecutionState: [], + safeAssistantReplyBase: "base", + safeAssistantReply: "safe-reply", + debug: {}, + assistantItem: { + message_id: "msg-a1", + session_id: "asst-1", + role: "assistant", + text: "safe-reply", + reply_type: "factual", + created_at: "2026-04-10T00:00:00.000Z", + trace_id: "trace-1", + debug: null + }, + deepAnalysisLogDetails: {} + })); + + runAssistantDeepTurnResponseRuntime( + buildBaseInput({ + featureInvestigationStateV1: false, + followupApplied: true, + runPackagingRuntime, + runFinalizeDeepTurn: () => ({ + response: { + ok: true, + session_id: "asst-1", + conversation: [], + debug: null + } + }) + }) + ); + + expect(runPackagingRuntime).toHaveBeenCalledWith( + expect.objectContaining({ + featureInvestigationStateV1: false, + followupApplied: true + }) + ); + }); +}); diff --git a/llm_normalizer/data/autorun_annotations/annotations.json b/llm_normalizer/data/autorun_annotations/annotations.json index 66c4fa7..d025ed6 100644 --- a/llm_normalizer/data/autorun_annotations/annotations.json +++ b/llm_normalizer/data/autorun_annotations/annotations.json @@ -171,11 +171,11 @@ "comment": "вопрос не отрабатываем но нужно обратить внимание что ассистент выдал список активности - то есть не понял контекст и дал совершенновне контекста выдачу", "manual_case_decision": "out_of_scope_but_answer_softly", "annotation_author": "manual_reviewer", - "resolved": false, - "resolved_at": null, - "resolved_by": null, + "resolved": true, + "resolved_at": "2026-04-10T17:28:38.335Z", + "resolved_by": "manual_reviewer", "created_at": "2026-04-10T09:11:53.544Z", - "updated_at": "2026-04-10T09:11:53.544Z", + "updated_at": "2026-04-10T17:28:38.335Z", "context": { "message_id": "msg-RDpaUn4py2", "trace_id": "address-pQYUVty09a", @@ -225,11 +225,11 @@ "comment": "мы разве не можем прокинуть маршрут по не закрытиым авансам на актами на дату рассмотрения???", "manual_case_decision": "needs_routing_extension", "annotation_author": "manual_reviewer", - "resolved": false, - "resolved_at": null, - "resolved_by": null, + "resolved": true, + "resolved_at": "2026-04-10T17:28:00.389Z", + "resolved_by": "manual_reviewer", "created_at": "2026-04-10T09:14:16.180Z", - "updated_at": "2026-04-10T09:14:16.180Z", + "updated_at": "2026-04-10T17:28:00.389Z", "context": { "message_id": "msg-XjaJdWuftN", "trace_id": "address-m9ZE6DBr7R", @@ -252,11 +252,11 @@ "comment": "вопрос не актуален", "manual_case_decision": "out_of_scope_but_answer_softly", "annotation_author": "manual_reviewer", - "resolved": false, - "resolved_at": null, - "resolved_by": null, + "resolved": true, + "resolved_at": "2026-04-10T17:27:36.886Z", + "resolved_by": "manual_reviewer", "created_at": "2026-04-10T09:14:39.227Z", - "updated_at": "2026-04-10T09:14:39.227Z", + "updated_at": "2026-04-10T17:27:36.886Z", "context": { "message_id": "msg-6N2xTrZA3p", "trace_id": "address-XQrnJFW_v1", @@ -306,11 +306,11 @@ "comment": "тут надо вывести самых доходном контр агентов - мы уже это умееем но запрос распознался не правильно? где наша ллс констекстная аналитика? тут явно сильныо торчащий контекст", "manual_case_decision": "covered_but_bad_answer", "annotation_author": "manual_reviewer", - "resolved": false, - "resolved_at": null, - "resolved_by": null, + "resolved": true, + "resolved_at": "2026-04-10T17:26:51.525Z", + "resolved_by": "manual_reviewer", "created_at": "2026-04-10T09:17:19.668Z", - "updated_at": "2026-04-10T09:17:19.668Z", + "updated_at": "2026-04-10T17:26:51.525Z", "context": { "message_id": "msg-7Fi7216TqF", "trace_id": "address-pL6IUlwjAP", @@ -360,11 +360,11 @@ "comment": "какой еще инн? юзер задает максимально конкретыный вопрос. типа авансы закрыты дибо не закрыты - нужна конкретная адресная проверка по базе 1с", "manual_case_decision": "needs_routing_extension", "annotation_author": "manual_reviewer", - "resolved": false, - "resolved_at": null, - "resolved_by": null, + "resolved": true, + "resolved_at": "2026-04-10T17:26:16.682Z", + "resolved_by": "manual_reviewer", "created_at": "2026-04-10T09:19:30.315Z", - "updated_at": "2026-04-10T09:19:30.315Z", + "updated_at": "2026-04-10T17:26:16.682Z", "context": { "message_id": "msg-qyrc6Jek0h", "trace_id": "address-K97ICzNyLf", @@ -387,11 +387,11 @@ "comment": "не отрабатываем - почему выскачела техничка???? нужен мягкий отказ", "manual_case_decision": "out_of_scope_but_answer_softly", "annotation_author": "manual_reviewer", - "resolved": false, - "resolved_at": null, - "resolved_by": null, + "resolved": true, + "resolved_at": "2026-04-10T17:25:55.103Z", + "resolved_by": "manual_reviewer", "created_at": "2026-04-10T09:19:59.184Z", - "updated_at": "2026-04-10T09:20:29.316Z", + "updated_at": "2026-04-10T17:25:55.103Z", "context": { "message_id": "msg-2BXINs6O5A", "trace_id": "iUWtcr5ZfOqMvU", @@ -441,11 +441,11 @@ "comment": "нельзя вообще даваать такие ответы - нужно технически отработать этот марштрут", "manual_case_decision": "candidate_for_implementation", "annotation_author": "manual_reviewer", - "resolved": false, - "resolved_at": null, - "resolved_by": null, + "resolved": true, + "resolved_at": "2026-04-10T17:24:56.029Z", + "resolved_by": "manual_reviewer", "created_at": "2026-04-10T09:23:01.330Z", - "updated_at": "2026-04-10T09:23:01.330Z", + "updated_at": "2026-04-10T17:24:56.029Z", "context": { "message_id": "msg-CBXmhYfjcw", "trace_id": "XHuO-nGyt2K1Sc", @@ -495,11 +495,11 @@ "comment": "мы точно отработали этот кейс маршрутом - схуя он опять упал в шаблонный ответ - мы уже точно умеем выводить топ по даходам и суммам контрактов", "manual_case_decision": "covered_but_bad_answer", "annotation_author": "manual_reviewer", - "resolved": false, - "resolved_at": null, - "resolved_by": null, + "resolved": true, + "resolved_at": "2026-04-10T17:25:11.918Z", + "resolved_by": "manual_reviewer", "created_at": "2026-04-10T09:26:31.131Z", - "updated_at": "2026-04-10T09:26:31.131Z", + "updated_at": "2026-04-10T17:25:11.918Z", "context": { "message_id": "msg-WmJg1Kp0tt", "trace_id": "address-jDze8Tv0JS", @@ -549,11 +549,11 @@ "comment": "почему показаны актиные заказчики? вопрос был про не закрытые актими и приходами денег договора на момент рассмотрения", "manual_case_decision": "covered_but_bad_answer", "annotation_author": "manual_reviewer", - "resolved": false, - "resolved_at": null, - "resolved_by": null, + "resolved": true, + "resolved_at": "2026-04-10T17:28:51.491Z", + "resolved_by": "manual_reviewer", "created_at": "2026-04-10T09:29:32.681Z", - "updated_at": "2026-04-10T09:29:32.681Z", + "updated_at": "2026-04-10T17:28:51.491Z", "context": { "message_id": "msg-XsyFjMyJS2", "trace_id": "address-xexv2UQ5mP",