From dedf1935427278fb9d2c297585534f069e87450b Mon Sep 17 00:00:00 2001 From: dctouch Date: Sat, 11 Apr 2026 10:35:29 +0300 Subject: [PATCH] =?UTF-8?q?=D0=93=D0=9B=D0=9E=D0=91=D0=90=D0=9B=D0=AC?= =?UTF-8?q?=D0=9D=D0=AB=D0=99=20=D0=A0=D0=95=D0=A4=D0=90=D0=9A=D0=A2=D0=9E?= =?UTF-8?q?=D0=A0=D0=98=D0=9D=D0=93=20=D0=90=D0=A0=D0=A5=D0=98=D0=A2=D0=95?= =?UTF-8?q?=D0=9A=D0=A2=D0=A3=D0=A0=D0=AB=20-=20=D0=A0=D0=B5=D1=84=D0=B0?= =?UTF-8?q?=D0=BA=D1=82=D0=BE=D1=80=D0=B8=D0=BD=D0=B3=20=D1=8D=D1=82=D0=B0?= =?UTF-8?q?=D0=BF=D0=BE=D0=B2=20=202.772.80=20-=20=D0=A3=D0=B1=D1=80=D0=B0?= =?UTF-8?q?=D0=BD=D0=BE=20as=20any=20=D0=B8=D0=B7=20=D0=B0=D0=B4=D1=80?= =?UTF-8?q?=D0=B5=D1=81=D0=BD=D0=BE=D0=B3=D0=BE=20response-runtime=20?= =?UTF-8?q?=D0=B8=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20=D1=8F?= =?UTF-8?q?=D0=B2=D0=BD=D1=83=D1=8E=20=D0=BD=D0=BE=D1=80=D0=BC=D0=B0=D0=BB?= =?UTF-8?q?=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8E=20=D0=BF=D0=B5=D1=80=D0=B5?= =?UTF-8?q?=D0=B4=20=D1=84=D0=B8=D0=BD=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D0=B5=D0=B9=20/=20=D0=A3=D0=B1=D1=80=D0=B0=D0=BD=20cast?= =?UTF-8?q?=20=D0=B2=20builder=D0=B5=20deep-response=20composition?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/TECH/1CLLMARCH-FACT.md | 32 +++- ...istantAddressLaneResponseRuntimeAdapter.js | 130 ++++++++++++- ...assistantDeepTurnResponseRuntimeAdapter.js | 112 ++++++++++-- ...istantAddressLaneResponseRuntimeAdapter.ts | 144 ++++++++++++++- ...assistantDeepTurnResponseRuntimeAdapter.ts | 171 ++++++++++++++---- ...tantDeepTurnResponseRuntimeInputBuilder.ts | 2 +- 6 files changed, 532 insertions(+), 59 deletions(-) diff --git a/docs/TECH/1CLLMARCH-FACT.md b/docs/TECH/1CLLMARCH-FACT.md index cde9f0c..dc3ea1a 100644 --- a/docs/TECH/1CLLMARCH-FACT.md +++ b/docs/TECH/1CLLMARCH-FACT.md @@ -1890,7 +1890,37 @@ Validation: - `assistantTurnRuntimeDepsAdapter.test.ts` - `assistantTurnAttemptRuntimeAdapter.test.ts` -Status: **In progress (Phase 2.1 + 2.2 + 2.3 + 2.4 + 2.5 + 2.6 + 2.7 + 2.8 + 2.9 + 2.10 + 2.11 + 2.12 + 2.13 + 2.14 + 2.15 + 2.16 + 2.17 + 2.18 + 2.19 + 2.20 + 2.21 + 2.22 + 2.23 + 2.24 + 2.25 + 2.26 + 2.27 + 2.28 + 2.29 + 2.30 + 2.31 + 2.32 + 2.33 + 2.34 + 2.35 + 2.36 + 2.37 + 2.38 + 2.39 + 2.40 + 2.41 + 2.42 + 2.43 + 2.44 + 2.45 + 2.46 + 2.47 + 2.48 + 2.49 + 2.50 + 2.51 + 2.52 + 2.53 + 2.54 + 2.55 + 2.56 + 2.57 + 2.58 + 2.59 + 2.60 + 2.61 + 2.62 + 2.63 + 2.64 + 2.65 + 2.66 + 2.67 + 2.68 + 2.69 + 2.70 + 2.71 + 2.72 + 2.73 + 2.74 + 2.75 + 2.76 completed)** +Implemented in current pass (Phase 2.77 + 2.78 + 2.79 + 2.80): +1. Removed unsafe casts from address lane response runtime finalization path: + - `assistantAddressLaneResponseRuntimeAdapter.ts` + - introduced explicit normalization for: + - `replyType` (fallback-safe); + - `carryoverMeta` extraction from followup context; + - `llmPreDecomposeMeta` sparse contract mapping; + - address debug payload handoff into finalize stage. +2. Hardened deep response runtime bridge to packaging stage: + - `assistantDeepTurnResponseRuntimeAdapter.ts` + - replaced `as any` payload handoff with typed normalizers for: + - execution plan; + - runtime analysis context; + - business scope resolution; + - record/audit buckets; + - address-runtime meta. +3. Updated deep response runtime input builder to pass typed composition contract directly: + - `assistantDeepTurnResponseRuntimeInputBuilder.ts` + - removed composition cast in analysis->response mapping. + +Validation: +1. `npm run build` passed. +2. Targeted response/address/deep pack passed: + - `assistantAddressLaneResponseRuntimeAdapter.test.ts` + - `assistantAddressLaneResponseAttemptRuntimeAdapter.test.ts` + - `assistantDeepTurnResponseRuntimeInputBuilder.test.ts` + - `assistantDeepTurnResponseRuntimeAdapter.test.ts` + - `assistantDeepTurnResponseAttemptRuntimeAdapter.test.ts` + - `assistantDeepTurnAttemptRuntimeAdapter.test.ts` + +Status: **In progress (Phase 2.1 + 2.2 + 2.3 + 2.4 + 2.5 + 2.6 + 2.7 + 2.8 + 2.9 + 2.10 + 2.11 + 2.12 + 2.13 + 2.14 + 2.15 + 2.16 + 2.17 + 2.18 + 2.19 + 2.20 + 2.21 + 2.22 + 2.23 + 2.24 + 2.25 + 2.26 + 2.27 + 2.28 + 2.29 + 2.30 + 2.31 + 2.32 + 2.33 + 2.34 + 2.35 + 2.36 + 2.37 + 2.38 + 2.39 + 2.40 + 2.41 + 2.42 + 2.43 + 2.44 + 2.45 + 2.46 + 2.47 + 2.48 + 2.49 + 2.50 + 2.51 + 2.52 + 2.53 + 2.54 + 2.55 + 2.56 + 2.57 + 2.58 + 2.59 + 2.60 + 2.61 + 2.62 + 2.63 + 2.64 + 2.65 + 2.66 + 2.67 + 2.68 + 2.69 + 2.70 + 2.71 + 2.72 + 2.73 + 2.74 + 2.75 + 2.76 + 2.77 + 2.78 + 2.79 + 2.80 completed)** ## Stage 3 (P2): Hybrid Semantic Layer (LLM + Deterministic Guards) diff --git a/llm_normalizer/backend/dist/services/assistantAddressLaneResponseRuntimeAdapter.js b/llm_normalizer/backend/dist/services/assistantAddressLaneResponseRuntimeAdapter.js index 69edee9..e95ff04 100644 --- a/llm_normalizer/backend/dist/services/assistantAddressLaneResponseRuntimeAdapter.js +++ b/llm_normalizer/backend/dist/services/assistantAddressLaneResponseRuntimeAdapter.js @@ -2,6 +2,128 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.runAssistantAddressLaneResponseRuntime = runAssistantAddressLaneResponseRuntime; const assistantAddressTurnFinalizeRuntimeAdapter_1 = require("./assistantAddressTurnFinalizeRuntimeAdapter"); +function toRecordObject(value) { + if (!value || typeof value !== "object") { + return null; + } + return value; +} +function toNullableString(value) { + if (typeof value !== "string") { + return null; + } + const trimmed = value.trim(); + return trimmed.length > 0 ? trimmed : null; +} +function toNullableBoolean(value) { + if (typeof value === "boolean") { + return value; + } + return undefined; +} +function normalizeAddressReplyType(value) { + return value === "factual" || value === "partial_coverage" ? value : "partial_coverage"; +} +function normalizeAddressLaneDebug(value) { + return (toRecordObject(value) ?? {}); +} +function normalizeCarryoverMeta(value) { + const source = toRecordObject(value); + if (!source) { + return null; + } + const followupContext = toRecordObject(source.followupContext); + const previousAddressIntent = toNullableString(source.previousAddressIntent) ?? + toNullableString(followupContext?.previous_intent); + const previousAddressAnchor = toNullableString(source.previousAddressAnchor) ?? + toNullableString(followupContext?.previous_anchor); + if (!previousAddressIntent && !previousAddressAnchor) { + return null; + } + return { + previousAddressIntent, + previousAddressAnchor + }; +} +function normalizeLlmPreDecomposeMeta(value) { + const source = toRecordObject(value); + if (!source) { + return null; + } + const dialogContinuationContractRaw = toRecordObject(source.dialogContinuationContract); + const addressRetryAuditRaw = toRecordObject(source.addressRetryAudit); + const predecomposeContractRaw = toRecordObject(source.predecomposeContract); + const predecomposePeriodRaw = toRecordObject(predecomposeContractRaw?.period); + const normalized = {}; + const attempted = toNullableBoolean(source.attempted); + if (attempted !== undefined) + normalized.attempted = attempted; + const applied = toNullableBoolean(source.applied); + if (applied !== undefined) + normalized.applied = applied; + const provider = toNullableString(source.provider); + if (provider) + normalized.provider = provider; + const traceId = toNullableString(source.traceId); + if (traceId) + normalized.traceId = traceId; + const reason = toNullableString(source.reason); + if (reason) + normalized.reason = reason; + const fallbackRuleHit = toNullableString(source.fallbackRuleHit); + if (fallbackRuleHit) + normalized.fallbackRuleHit = fallbackRuleHit; + const sanitizedUserMessage = toNullableString(source.sanitizedUserMessage); + if (sanitizedUserMessage) + normalized.sanitizedUserMessage = sanitizedUserMessage; + const toolGateDecision = toNullableString(source.toolGateDecision); + if (toolGateDecision) + normalized.toolGateDecision = toolGateDecision; + const toolGateReason = toNullableString(source.toolGateReason); + if (toolGateReason) + normalized.toolGateReason = toolGateReason; + if (dialogContinuationContractRaw) { + const decision = toNullableString(dialogContinuationContractRaw.decision); + const targetIntent = toNullableString(dialogContinuationContractRaw.target_intent); + if (decision || targetIntent) { + normalized.dialogContinuationContract = { + decision, + target_intent: targetIntent + }; + } + } + if (addressRetryAuditRaw) { + const retryAttempted = toNullableBoolean(addressRetryAuditRaw.attempted); + const retryReason = toNullableString(addressRetryAuditRaw.reason); + const initialLimitedCategory = toNullableString(addressRetryAuditRaw.initial_limited_category); + const retryResultCategory = toNullableString(addressRetryAuditRaw.retry_result_category); + if (retryAttempted !== undefined || + retryReason || + initialLimitedCategory || + retryResultCategory) { + normalized.addressRetryAudit = { + attempted: retryAttempted, + reason: retryReason, + initial_limited_category: initialLimitedCategory, + retry_result_category: retryResultCategory + }; + } + } + if (predecomposeContractRaw) { + const intent = toNullableString(predecomposeContractRaw.intent); + const aggregationProfile = toNullableString(predecomposeContractRaw.aggregation_profile); + const periodScope = toNullableString(predecomposePeriodRaw?.scope); + if (intent || aggregationProfile || periodScope) { + normalized.predecomposeContract = { + intent, + aggregation_profile: aggregationProfile, + period: periodScope ? { scope: periodScope } : null + }; + } + } + const hasUsefulField = Object.values(normalized).some((item) => item !== undefined && item !== null); + return hasUsefulField ? normalized : null; +} function runAssistantAddressLaneResponseRuntime(input) { const finalizeAddressTurnSafe = input.finalizeAddressTurn ?? assistantAddressTurnFinalizeRuntimeAdapter_1.finalizeAssistantAddressTurn; const safeAddressReply = input.sanitizeOutgoingAssistantText(input.addressLane.reply_text); @@ -27,11 +149,11 @@ function runAssistantAddressLaneResponseRuntime(input) { userMessage: input.userMessage, effectiveAddressUserMessage: input.effectiveAddressUserMessage, assistantReply: safeAddressReply, - replyType: input.addressLane.reply_type, - addressLaneDebug: (input.addressLane.debug ?? null), + replyType: normalizeAddressReplyType(input.addressLane.reply_type), + addressLaneDebug: normalizeAddressLaneDebug(input.addressLane.debug), debug, - carryoverMeta: (input.carryoverMeta ?? null), - llmPreDecomposeMeta: (input.llmPreDecomposeMeta ?? null), + carryoverMeta: normalizeCarryoverMeta(input.carryoverMeta), + llmPreDecomposeMeta: normalizeLlmPreDecomposeMeta(input.llmPreDecomposeMeta), appendItem: input.appendItem, getSession: input.getSession, persistSession: input.persistSession, diff --git a/llm_normalizer/backend/dist/services/assistantDeepTurnResponseRuntimeAdapter.js b/llm_normalizer/backend/dist/services/assistantDeepTurnResponseRuntimeAdapter.js index f456a9f..cda1ab6 100644 --- a/llm_normalizer/backend/dist/services/assistantDeepTurnResponseRuntimeAdapter.js +++ b/llm_normalizer/backend/dist/services/assistantDeepTurnResponseRuntimeAdapter.js @@ -3,6 +3,94 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.runAssistantDeepTurnResponseRuntime = runAssistantDeepTurnResponseRuntime; const assistantDeepTurnPackagingRuntimeAdapter_1 = require("./assistantDeepTurnPackagingRuntimeAdapter"); const assistantDeepTurnFinalizeRuntimeAdapter_1 = require("./assistantDeepTurnFinalizeRuntimeAdapter"); +function toRecordObject(value) { + if (!value || typeof value !== "object") { + return null; + } + return value; +} +function toNullableString(value) { + if (typeof value !== "string") { + return null; + } + const trimmed = value.trim(); + return trimmed.length > 0 ? trimmed : null; +} +function toStringArray(value) { + if (!Array.isArray(value)) { + return []; + } + return value + .map((item) => (typeof item === "string" ? item.trim() : "")) + .filter((item) => item.length > 0); +} +function toSnapshotMode(value) { + return value === "force_snapshot" || value === "force_live" ? value : "auto"; +} +function normalizeExecutionPlan(value) { + if (!Array.isArray(value)) { + return []; + } + return value.map((item, index) => { + const source = toRecordObject(item); + return { + fragment_id: toNullableString(source?.fragment_id) ?? `fragment_${index + 1}`, + requirement_ids: toStringArray(source?.requirement_ids), + route: toNullableString(source?.route) ?? "unknown_route", + should_execute: Boolean(source?.should_execute), + no_route_reason: toNullableString(source?.no_route_reason), + clarification_reason: toNullableString(source?.clarification_reason) + }; + }); +} +function normalizeRecordArray(value) { + if (!Array.isArray(value)) { + return []; + } + return value + .map((item) => toRecordObject(item)) + .filter((item) => Boolean(item)); +} +function normalizeRecord(value) { + return toRecordObject(value) ?? {}; +} +function normalizeRuntimeAnalysisContext(value) { + const source = toRecordObject(value); + return { + active: Boolean(source?.active), + as_of_date: toNullableString(source?.as_of_date), + period_from: toNullableString(source?.period_from), + period_to: toNullableString(source?.period_to), + source: toNullableString(source?.source), + snapshot_mode: toSnapshotMode(source?.snapshot_mode) + }; +} +function normalizeBusinessScopeResolution(value) { + const source = toRecordObject(value); + return { + business_scope_raw: toStringArray(source?.business_scope_raw), + business_scope_resolved: toStringArray(source?.business_scope_resolved), + company_grounding_applied: Boolean(source?.company_grounding_applied), + scope_resolution_reason: toStringArray(source?.scope_resolution_reason) + }; +} +function normalizeAddressRuntimeMetaForDeep(value) { + const source = toRecordObject(value); + if (!source) { + return null; + } + return { + attempted: typeof source.attempted === "boolean" ? source.attempted : undefined, + applied: typeof source.applied === "boolean" ? source.applied : undefined, + reason: toNullableString(source.reason), + provider: toNullableString(source.provider), + fallbackRuleHit: toNullableString(source.fallbackRuleHit), + toolGateDecision: toNullableString(source.toolGateDecision), + toolGateReason: toNullableString(source.toolGateReason), + predecomposeContract: toRecordObject(source.predecomposeContract), + orchestrationContract: toRecordObject(source.orchestrationContract) + }; +} function runAssistantDeepTurnResponseRuntime(input) { const runPackagingRuntimeSafe = input.runPackagingRuntime ?? assistantDeepTurnPackagingRuntimeAdapter_1.runAssistantDeepTurnPackagingRuntime; const runFinalizeDeepTurnSafe = input.runFinalizeDeepTurn ?? assistantDeepTurnFinalizeRuntimeAdapter_1.finalizeAssistantDeepTurn; @@ -14,33 +102,33 @@ function runAssistantDeepTurnResponseRuntime(input) { normalized: input.normalized, normalizedQuestion: input.normalizedQuestion, routeSummary: input.routeSummary, - executionPlan: input.executionPlan, + executionPlan: normalizeExecutionPlan(input.executionPlan), requirementExtractionRequirements: input.requirementExtractionRequirements, coverageEvaluationRequirements: input.coverageEvaluationRequirements, coverageReport: input.coverageReport, groundingCheck: input.groundingCheck, - retrievalCalls: input.retrievalCalls, - retrievalResultsRaw: input.retrievalResultsRaw, + retrievalCalls: normalizeRecordArray(input.retrievalCalls), + retrievalResultsRaw: Array.isArray(input.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, + runtimeAnalysisContext: normalizeRuntimeAnalysisContext(input.runtimeAnalysisContext), + businessScopeResolution: normalizeBusinessScopeResolution(input.businessScopeResolution), + temporalGuard: normalizeRecord(input.temporalGuard), + polarityAudit: normalizeRecord(input.polarityAudit), + claimAnchorAudit: normalizeRecord(input.claimAnchorAudit), targetedEvidenceAudit: input.targetedEvidenceAudit, evidenceAdmissibilityGateAudit: input.evidenceAdmissibilityGateAudit, - rbpLiveRouteAudit: input.rbpLiveRouteAudit, - faLiveRouteAudit: input.faLiveRouteAudit, - groundedAnswerEligibilityGuard: input.groundedAnswerEligibilityGuard, + rbpLiveRouteAudit: input.rbpLiveRouteAudit ?? null, + faLiveRouteAudit: input.faLiveRouteAudit ?? null, + groundedAnswerEligibilityGuard: normalizeRecord(input.groundedAnswerEligibilityGuard), followupStateUsage: input.followupStateUsage, followupApplied: input.followupApplied, composition: input.composition, featureContractsV11: input.featureContractsV11, featureAnswerPolicyV11: input.featureAnswerPolicyV11, previousInvestigationState: input.previousInvestigationState ?? null, - addressRuntimeMetaForDeep: input.addressRuntimeMetaForDeep, + addressRuntimeMetaForDeep: normalizeAddressRuntimeMetaForDeep(input.addressRuntimeMetaForDeep), extractDroppedIntentSegments: input.extractDroppedIntentSegments, buildDebugRoutes: input.buildDebugRoutes, extractExecutionState: input.extractExecutionState, diff --git a/llm_normalizer/backend/src/services/assistantAddressLaneResponseRuntimeAdapter.ts b/llm_normalizer/backend/src/services/assistantAddressLaneResponseRuntimeAdapter.ts index bcba12e..cce4324 100644 --- a/llm_normalizer/backend/src/services/assistantAddressLaneResponseRuntimeAdapter.ts +++ b/llm_normalizer/backend/src/services/assistantAddressLaneResponseRuntimeAdapter.ts @@ -1,7 +1,10 @@ import type { AssistantAddressLaneLike, AssistantAddressFollowupCarryoverLike } from "./assistantAddressLaneRuntimeAdapter"; -import type { AssistantMessageResponsePayload } from "../types/assistant"; +import type { AssistantMessageResponsePayload, AssistantReplyType } from "../types/assistant"; +import type { AddressExecutionDebug } from "../types/addressQuery"; import { finalizeAssistantAddressTurn, + type AddressCarryoverMetaLogInput, + type AddressLlmPreDecomposeMetaLogInput, type FinalizeAssistantAddressTurnInput } from "./assistantAddressTurnFinalizeRuntimeAdapter"; @@ -40,6 +43,137 @@ export interface RunAssistantAddressLaneResponseRuntimeOutput; } +function toRecordObject(value: unknown): Record | null { + if (!value || typeof value !== "object") { + return null; + } + return value as Record; +} + +function toNullableString(value: unknown): string | null { + if (typeof value !== "string") { + return null; + } + const trimmed = value.trim(); + return trimmed.length > 0 ? trimmed : null; +} + +function toNullableBoolean(value: unknown): boolean | undefined { + if (typeof value === "boolean") { + return value; + } + return undefined; +} + +function normalizeAddressReplyType(value: unknown): AssistantReplyType { + return value === "factual" || value === "partial_coverage" ? value : "partial_coverage"; +} + +function normalizeAddressLaneDebug(value: unknown): AddressExecutionDebug { + return (toRecordObject(value) ?? {}) as unknown as AddressExecutionDebug; +} + +function normalizeCarryoverMeta(value: unknown): AddressCarryoverMetaLogInput | null { + const source = toRecordObject(value); + if (!source) { + return null; + } + const followupContext = toRecordObject(source.followupContext); + const previousAddressIntent = + toNullableString(source.previousAddressIntent) ?? + toNullableString(followupContext?.previous_intent); + const previousAddressAnchor = + toNullableString(source.previousAddressAnchor) ?? + toNullableString(followupContext?.previous_anchor); + if (!previousAddressIntent && !previousAddressAnchor) { + return null; + } + return { + previousAddressIntent, + previousAddressAnchor + }; +} + +function normalizeLlmPreDecomposeMeta(value: unknown): AddressLlmPreDecomposeMetaLogInput | null { + const source = toRecordObject(value); + if (!source) { + return null; + } + + const dialogContinuationContractRaw = toRecordObject(source.dialogContinuationContract); + const addressRetryAuditRaw = toRecordObject(source.addressRetryAudit); + const predecomposeContractRaw = toRecordObject(source.predecomposeContract); + const predecomposePeriodRaw = toRecordObject(predecomposeContractRaw?.period); + + const normalized: AddressLlmPreDecomposeMetaLogInput = {}; + + const attempted = toNullableBoolean(source.attempted); + if (attempted !== undefined) normalized.attempted = attempted; + const applied = toNullableBoolean(source.applied); + if (applied !== undefined) normalized.applied = applied; + const provider = toNullableString(source.provider); + if (provider) normalized.provider = provider; + const traceId = toNullableString(source.traceId); + if (traceId) normalized.traceId = traceId; + const reason = toNullableString(source.reason); + if (reason) normalized.reason = reason; + const fallbackRuleHit = toNullableString(source.fallbackRuleHit); + if (fallbackRuleHit) normalized.fallbackRuleHit = fallbackRuleHit; + const sanitizedUserMessage = toNullableString(source.sanitizedUserMessage); + if (sanitizedUserMessage) normalized.sanitizedUserMessage = sanitizedUserMessage; + const toolGateDecision = toNullableString(source.toolGateDecision); + if (toolGateDecision) normalized.toolGateDecision = toolGateDecision; + const toolGateReason = toNullableString(source.toolGateReason); + if (toolGateReason) normalized.toolGateReason = toolGateReason; + + if (dialogContinuationContractRaw) { + const decision = toNullableString(dialogContinuationContractRaw.decision); + const targetIntent = toNullableString(dialogContinuationContractRaw.target_intent); + if (decision || targetIntent) { + normalized.dialogContinuationContract = { + decision, + target_intent: targetIntent + }; + } + } + + if (addressRetryAuditRaw) { + const retryAttempted = toNullableBoolean(addressRetryAuditRaw.attempted); + const retryReason = toNullableString(addressRetryAuditRaw.reason); + const initialLimitedCategory = toNullableString(addressRetryAuditRaw.initial_limited_category); + const retryResultCategory = toNullableString(addressRetryAuditRaw.retry_result_category); + if ( + retryAttempted !== undefined || + retryReason || + initialLimitedCategory || + retryResultCategory + ) { + normalized.addressRetryAudit = { + attempted: retryAttempted, + reason: retryReason, + initial_limited_category: initialLimitedCategory, + retry_result_category: retryResultCategory + }; + } + } + + if (predecomposeContractRaw) { + const intent = toNullableString(predecomposeContractRaw.intent); + const aggregationProfile = toNullableString(predecomposeContractRaw.aggregation_profile); + const periodScope = toNullableString(predecomposePeriodRaw?.scope); + if (intent || aggregationProfile || periodScope) { + normalized.predecomposeContract = { + intent, + aggregation_profile: aggregationProfile, + period: periodScope ? { scope: periodScope } : null + }; + } + } + + const hasUsefulField = Object.values(normalized).some((item) => item !== undefined && item !== null); + return hasUsefulField ? normalized : null; +} + export function runAssistantAddressLaneResponseRuntime( input: RunAssistantAddressLaneResponseRuntimeInput ): RunAssistantAddressLaneResponseRuntimeOutput { @@ -69,11 +203,11 @@ export function runAssistantAddressLaneResponseRuntime string[]; buildDebugRoutes: (routeSummary: RouteHintSummary | null) => Array>; @@ -78,6 +73,110 @@ export interface RunAssistantDeepTurnResponseRuntimeOutput { debug: Record; } +function toRecordObject(value: unknown): Record | null { + if (!value || typeof value !== "object") { + return null; + } + return value as Record; +} + +function toNullableString(value: unknown): string | null { + if (typeof value !== "string") { + return null; + } + const trimmed = value.trim(); + return trimmed.length > 0 ? trimmed : null; +} + +function toStringArray(value: unknown): string[] { + if (!Array.isArray(value)) { + return []; + } + return value + .map((item) => (typeof item === "string" ? item.trim() : "")) + .filter((item) => item.length > 0); +} + +function toSnapshotMode(value: unknown): "auto" | "force_snapshot" | "force_live" { + return value === "force_snapshot" || value === "force_live" ? value : "auto"; +} + +function normalizeExecutionPlan(value: unknown[]): AssistantDeepTurnPackagingRuntimeInput["executionPlan"] { + if (!Array.isArray(value)) { + return []; + } + return value.map((item, index) => { + const source = toRecordObject(item); + return { + fragment_id: toNullableString(source?.fragment_id) ?? `fragment_${index + 1}`, + requirement_ids: toStringArray(source?.requirement_ids), + route: toNullableString(source?.route) ?? "unknown_route", + should_execute: Boolean(source?.should_execute), + no_route_reason: toNullableString(source?.no_route_reason), + clarification_reason: toNullableString(source?.clarification_reason) + }; + }); +} + +function normalizeRecordArray(value: unknown[]): Array> { + if (!Array.isArray(value)) { + return []; + } + return value + .map((item) => toRecordObject(item)) + .filter((item): item is Record => Boolean(item)); +} + +function normalizeRecord(value: unknown): Record { + return toRecordObject(value) ?? {}; +} + +function normalizeRuntimeAnalysisContext( + value: unknown +): AssistantDeepTurnPackagingRuntimeInput["runtimeAnalysisContext"] { + const source = toRecordObject(value); + return { + active: Boolean(source?.active), + as_of_date: toNullableString(source?.as_of_date), + period_from: toNullableString(source?.period_from), + period_to: toNullableString(source?.period_to), + source: toNullableString(source?.source), + snapshot_mode: toSnapshotMode(source?.snapshot_mode) + }; +} + +function normalizeBusinessScopeResolution( + value: unknown +): AssistantDeepTurnPackagingRuntimeInput["businessScopeResolution"] { + const source = toRecordObject(value); + return { + business_scope_raw: toStringArray(source?.business_scope_raw), + business_scope_resolved: toStringArray(source?.business_scope_resolved), + company_grounding_applied: Boolean(source?.company_grounding_applied), + scope_resolution_reason: toStringArray(source?.scope_resolution_reason) + }; +} + +function normalizeAddressRuntimeMetaForDeep( + value: unknown +): AssistantDeepTurnPackagingRuntimeInput["addressRuntimeMetaForDeep"] { + const source = toRecordObject(value); + if (!source) { + return null; + } + return { + attempted: typeof source.attempted === "boolean" ? source.attempted : undefined, + applied: typeof source.applied === "boolean" ? source.applied : undefined, + reason: toNullableString(source.reason), + provider: toNullableString(source.provider), + fallbackRuleHit: toNullableString(source.fallbackRuleHit), + toolGateDecision: toNullableString(source.toolGateDecision), + toolGateReason: toNullableString(source.toolGateReason), + predecomposeContract: toRecordObject(source.predecomposeContract), + orchestrationContract: toRecordObject(source.orchestrationContract) + }; +} + export function runAssistantDeepTurnResponseRuntime( input: RunAssistantDeepTurnResponseRuntimeInput ): RunAssistantDeepTurnResponseRuntimeOutput { @@ -92,33 +191,33 @@ export function runAssistantDeepTurnResponseRuntime( 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, + executionPlan: normalizeExecutionPlan(input.executionPlan), + requirementExtractionRequirements: input.requirementExtractionRequirements, + coverageEvaluationRequirements: input.coverageEvaluationRequirements, + coverageReport: input.coverageReport, + groundingCheck: input.groundingCheck, + retrievalCalls: normalizeRecordArray(input.retrievalCalls), + retrievalResultsRaw: Array.isArray(input.retrievalResultsRaw) ? input.retrievalResultsRaw : [], + retrievalResults: input.retrievalResults, 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, + runtimeAnalysisContext: normalizeRuntimeAnalysisContext(input.runtimeAnalysisContext), + businessScopeResolution: normalizeBusinessScopeResolution(input.businessScopeResolution), + temporalGuard: normalizeRecord(input.temporalGuard), + polarityAudit: normalizeRecord(input.polarityAudit), + claimAnchorAudit: normalizeRecord(input.claimAnchorAudit), + targetedEvidenceAudit: input.targetedEvidenceAudit, + evidenceAdmissibilityGateAudit: input.evidenceAdmissibilityGateAudit, + rbpLiveRouteAudit: input.rbpLiveRouteAudit ?? null, + faLiveRouteAudit: input.faLiveRouteAudit ?? null, + groundedAnswerEligibilityGuard: normalizeRecord(input.groundedAnswerEligibilityGuard), + followupStateUsage: input.followupStateUsage, followupApplied: input.followupApplied, - composition: input.composition as any, + composition: input.composition, featureContractsV11: input.featureContractsV11, featureAnswerPolicyV11: input.featureAnswerPolicyV11, previousInvestigationState: input.previousInvestigationState ?? null, - addressRuntimeMetaForDeep: input.addressRuntimeMetaForDeep as any, + addressRuntimeMetaForDeep: normalizeAddressRuntimeMetaForDeep(input.addressRuntimeMetaForDeep), extractDroppedIntentSegments: input.extractDroppedIntentSegments, buildDebugRoutes: input.buildDebugRoutes, extractExecutionState: input.extractExecutionState, diff --git a/llm_normalizer/backend/src/services/assistantDeepTurnResponseRuntimeInputBuilder.ts b/llm_normalizer/backend/src/services/assistantDeepTurnResponseRuntimeInputBuilder.ts index 04588da..d1a5dc5 100644 --- a/llm_normalizer/backend/src/services/assistantDeepTurnResponseRuntimeInputBuilder.ts +++ b/llm_normalizer/backend/src/services/assistantDeepTurnResponseRuntimeInputBuilder.ts @@ -67,7 +67,7 @@ export function buildAssistantDeepTurnResponseRuntimeInput( groundedAnswerEligibilityGuard: analysis.groundedAnswerEligibilityGuard, followupStateUsage: input.followupStateUsage, followupApplied: input.followupApplied, - composition: analysis.composition as any, + composition: analysis.composition, previousInvestigationState: input.previousInvestigationState ?? null, addressRuntimeMetaForDeep: input.addressRuntimeMetaForDeep, extractDroppedIntentSegments: input.extractDroppedIntentSegments,