ГЛОБАЛЬНЫЙ РЕФАКТОРИНГ АРХИТЕКТУРЫ - Рефакторинг этапов 2.34 вынос address-ветку (orchestration + lane + finalize) в единый runtime-оркестратор, чтобы handleMessage стал почти плоским.
This commit is contained in:
parent
353cbc1763
commit
9c22460e8b
|
|
@ -1098,7 +1098,65 @@ Validation:
|
||||||
- `assistantLivingRouter.test.ts`
|
- `assistantLivingRouter.test.ts`
|
||||||
- `assistantWave10SettlementCorrectiveRegression.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)**
|
Implemented in current pass (Phase 2.34):
|
||||||
|
1. Extracted top-level address branch orchestration from `assistantService` into dedicated runtime adapter:
|
||||||
|
- `assistantAddressRuntimeAdapter.ts`
|
||||||
|
- introduced:
|
||||||
|
- `runAssistantAddressRuntime(...)`
|
||||||
|
2. Centralized full address-branch control flow (behavior-preserving):
|
||||||
|
- address bootstrap orchestration stage;
|
||||||
|
- tool-gate skip/chat fallback stage;
|
||||||
|
- lane execution/retry stage with analysis-date hint propagation;
|
||||||
|
- address finalize stage projection with retry audit merge.
|
||||||
|
3. Rewired `assistantService` address branch to a single runtime adapter invocation and preserved `addressRuntimeMetaForDeep` propagation contract.
|
||||||
|
4. Added focused unit tests:
|
||||||
|
- `assistantAddressRuntimeAdapter.test.ts`
|
||||||
|
|
||||||
|
Validation:
|
||||||
|
1. `npm run build` passed.
|
||||||
|
2. Targeted living/address/deep followup pack passed:
|
||||||
|
- `assistantAddressRuntimeAdapter.test.ts`
|
||||||
|
- `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`
|
||||||
|
|
||||||
|
Implemented in current pass (Phase 2.35):
|
||||||
|
1. Extracted address-lane response-tail (debug enrichment + finalize projection) from `assistantService` into dedicated runtime adapter:
|
||||||
|
- `assistantAddressLaneResponseRuntimeAdapter.ts`
|
||||||
|
- introduced:
|
||||||
|
- `runAssistantAddressLaneResponseRuntime(...)`
|
||||||
|
2. Centralized address response-tail sequence (behavior-preserving):
|
||||||
|
- reply sanitization and structured address debug payload assembly;
|
||||||
|
- followup-offer projection + known/active organization debug enrichment;
|
||||||
|
- address turn finalization through existing finalize adapter contract.
|
||||||
|
3. Rewired `assistantService` `finalizeAddressLaneResponse(...)` closure to consume response runtime adapter output.
|
||||||
|
4. Added focused unit tests:
|
||||||
|
- `assistantAddressLaneResponseRuntimeAdapter.test.ts`
|
||||||
|
|
||||||
|
Validation:
|
||||||
|
1. `npm run build` passed.
|
||||||
|
2. Targeted living/address/deep followup pack passed:
|
||||||
|
- `assistantAddressLaneResponseRuntimeAdapter.test.ts`
|
||||||
|
- `assistantAddressRuntimeAdapter.test.ts`
|
||||||
|
- `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 + 2.34 + 2.35 completed)**
|
||||||
|
|
||||||
## Stage 3 (P2): Hybrid Semantic Layer (LLM + Deterministic Guards)
|
## Stage 3 (P2): Hybrid Semantic Layer (LLM + Deterministic Guards)
|
||||||
|
|
||||||
|
|
|
||||||
46
llm_normalizer/backend/dist/services/assistantAddressLaneResponseRuntimeAdapter.js
vendored
Normal file
46
llm_normalizer/backend/dist/services/assistantAddressLaneResponseRuntimeAdapter.js
vendored
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.runAssistantAddressLaneResponseRuntime = runAssistantAddressLaneResponseRuntime;
|
||||||
|
const assistantAddressTurnFinalizeRuntimeAdapter_1 = require("./assistantAddressTurnFinalizeRuntimeAdapter");
|
||||||
|
function runAssistantAddressLaneResponseRuntime(input) {
|
||||||
|
const finalizeAddressTurnSafe = input.finalizeAddressTurn ?? assistantAddressTurnFinalizeRuntimeAdapter_1.finalizeAssistantAddressTurn;
|
||||||
|
const safeAddressReply = input.sanitizeOutgoingAssistantText(input.addressLane.reply_text);
|
||||||
|
const debug = input.buildAddressDebugPayload(input.addressLane.debug, input.llmPreDecomposeMeta);
|
||||||
|
const followupOffer = input.buildAddressFollowupOffer(debug);
|
||||||
|
if (followupOffer) {
|
||||||
|
debug.address_followup_offer = followupOffer;
|
||||||
|
}
|
||||||
|
const debugKnownOrganizations = input.mergeKnownOrganizations(input.knownOrganizations);
|
||||||
|
const debugFilters = debug?.extracted_filters && typeof debug.extracted_filters === "object"
|
||||||
|
? debug.extracted_filters
|
||||||
|
: null;
|
||||||
|
const debugActiveOrganization = input.toNonEmptyString(debugFilters?.organization) ??
|
||||||
|
input.toNonEmptyString(input.activeOrganization);
|
||||||
|
if (debugKnownOrganizations.length > 0) {
|
||||||
|
debug.assistant_known_organizations = debugKnownOrganizations;
|
||||||
|
}
|
||||||
|
if (debugActiveOrganization) {
|
||||||
|
debug.assistant_active_organization = debugActiveOrganization;
|
||||||
|
}
|
||||||
|
const finalization = finalizeAddressTurnSafe({
|
||||||
|
sessionId: input.sessionId,
|
||||||
|
userMessage: input.userMessage,
|
||||||
|
effectiveAddressUserMessage: input.effectiveAddressUserMessage,
|
||||||
|
assistantReply: safeAddressReply,
|
||||||
|
replyType: input.addressLane.reply_type,
|
||||||
|
addressLaneDebug: (input.addressLane.debug ?? null),
|
||||||
|
debug,
|
||||||
|
carryoverMeta: (input.carryoverMeta ?? null),
|
||||||
|
llmPreDecomposeMeta: (input.llmPreDecomposeMeta ?? null),
|
||||||
|
appendItem: input.appendItem,
|
||||||
|
getSession: input.getSession,
|
||||||
|
persistSession: input.persistSession,
|
||||||
|
cloneConversation: input.cloneConversation,
|
||||||
|
logEvent: input.logEvent,
|
||||||
|
messageIdFactory: input.messageIdFactory
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
response: finalization.response,
|
||||||
|
debug
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.runAssistantAddressRuntime = runAssistantAddressRuntime;
|
||||||
|
const assistantAddressOrchestrationRuntimeAdapter_1 = require("./assistantAddressOrchestrationRuntimeAdapter");
|
||||||
|
const assistantAddressLaneRuntimeAdapter_1 = require("./assistantAddressLaneRuntimeAdapter");
|
||||||
|
const assistantAddressToolGateRuntimeAdapter_1 = require("./assistantAddressToolGateRuntimeAdapter");
|
||||||
|
async function runAssistantAddressRuntime(input) {
|
||||||
|
if (!input.featureAssistantAddressQueryV1) {
|
||||||
|
return {
|
||||||
|
handled: false,
|
||||||
|
response: null,
|
||||||
|
addressRuntimeMetaForDeep: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const runAddressOrchestrationRuntimeSafe = input.runAddressOrchestrationRuntime ?? assistantAddressOrchestrationRuntimeAdapter_1.buildAssistantAddressOrchestrationRuntime;
|
||||||
|
const runAddressToolGateRuntimeSafe = input.runAddressToolGateRuntime ?? assistantAddressToolGateRuntimeAdapter_1.runAssistantAddressToolGateRuntime;
|
||||||
|
const runAddressLaneRuntimeSafe = input.runAddressLaneRuntime ?? assistantAddressLaneRuntimeAdapter_1.runAssistantAddressLaneRuntime;
|
||||||
|
const addressOrchestrationRuntime = await runAddressOrchestrationRuntimeSafe({
|
||||||
|
userMessage: input.userMessage,
|
||||||
|
sessionItems: input.sessionItems,
|
||||||
|
llmProvider: input.llmProvider,
|
||||||
|
useMock: input.useMock,
|
||||||
|
featureAddressLlmPredecomposeV1: input.featureAddressLlmPredecomposeV1,
|
||||||
|
runAddressLlmPreDecompose: input.runAddressLlmPreDecompose,
|
||||||
|
buildAddressLlmPredecomposeContractV1: input.buildAddressLlmPredecomposeContractV1,
|
||||||
|
sanitizeAddressMessageForFallback: input.sanitizeAddressMessageForFallback,
|
||||||
|
toNonEmptyString: input.toNonEmptyString,
|
||||||
|
resolveAddressFollowupCarryoverContext: input.resolveAddressFollowupCarryoverContext,
|
||||||
|
resolveAssistantOrchestrationDecision: input.resolveAssistantOrchestrationDecision,
|
||||||
|
buildAddressDialogContinuationContractV2: input.buildAddressDialogContinuationContractV2
|
||||||
|
});
|
||||||
|
const addressInputMessage = addressOrchestrationRuntime.addressInputMessage;
|
||||||
|
const carryover = addressOrchestrationRuntime.carryover;
|
||||||
|
const orchestrationDecision = addressOrchestrationRuntime.orchestrationDecision;
|
||||||
|
const addressRuntimeMeta = addressOrchestrationRuntime.addressRuntimeMeta;
|
||||||
|
const livingModeDecision = addressOrchestrationRuntime.livingModeDecision;
|
||||||
|
const addressRuntimeMetaForDeep = addressRuntimeMeta;
|
||||||
|
const toolGateRuntime = await runAddressToolGateRuntimeSafe({
|
||||||
|
sessionId: input.sessionId,
|
||||||
|
userMessage: input.userMessage,
|
||||||
|
addressInputMessage,
|
||||||
|
orchestrationDecision,
|
||||||
|
livingModeDecision,
|
||||||
|
addressRuntimeMeta,
|
||||||
|
logEvent: input.logEvent,
|
||||||
|
tryHandleLivingChat: input.tryHandleLivingChat,
|
||||||
|
nowIso: input.nowIso
|
||||||
|
});
|
||||||
|
if (toolGateRuntime.handled && toolGateRuntime.response) {
|
||||||
|
return {
|
||||||
|
handled: true,
|
||||||
|
response: toolGateRuntime.response,
|
||||||
|
addressRuntimeMetaForDeep
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (Boolean(orchestrationDecision.runAddressLane)) {
|
||||||
|
const shouldPreferContextualLane = Boolean(carryover?.followupContext);
|
||||||
|
const analysisDateHint = input.runtimeAnalysisContextAsOfDate ?? input.toNonEmptyString(input.payloadContextPeriodHint);
|
||||||
|
const canRetryWithRawUserMessage = input.compactWhitespace(String(addressInputMessage ?? "").toLowerCase()) !==
|
||||||
|
input.compactWhitespace(String(input.userMessage ?? "").toLowerCase());
|
||||||
|
const addressLaneRuntime = await runAddressLaneRuntimeSafe({
|
||||||
|
userMessage: input.userMessage,
|
||||||
|
addressInputMessage,
|
||||||
|
carryover,
|
||||||
|
shouldPreferContextualLane,
|
||||||
|
canRetryWithRawUserMessage,
|
||||||
|
runAddressLaneAttempt: (messageUsed, carryMeta) => input.runAddressLaneAttempt(messageUsed, carryMeta, analysisDateHint),
|
||||||
|
isRetryableAddressLimitedResult: input.isRetryableAddressLimitedResult
|
||||||
|
});
|
||||||
|
if (addressLaneRuntime.handled && addressLaneRuntime.selection) {
|
||||||
|
const response = input.finalizeAddressLaneResponse(addressLaneRuntime.selection.addressLane, addressLaneRuntime.selection.messageUsed, addressLaneRuntime.selection.carryMeta, {
|
||||||
|
...addressRuntimeMeta,
|
||||||
|
addressRetryAudit: { ...addressLaneRuntime.retryAudit }
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
handled: true,
|
||||||
|
response,
|
||||||
|
addressRuntimeMetaForDeep
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
handled: false,
|
||||||
|
response: null,
|
||||||
|
addressRuntimeMetaForDeep
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -65,7 +65,7 @@ const openaiResponsesClient_1 = __importStar(require("./openaiResponsesClient"))
|
||||||
const addressMcpClient_1 = __importStar(require("./addressMcpClient"));
|
const addressMcpClient_1 = __importStar(require("./addressMcpClient"));
|
||||||
const capabilitiesRegistry_1 = __importStar(require("./capabilitiesRegistry"));
|
const capabilitiesRegistry_1 = __importStar(require("./capabilitiesRegistry"));
|
||||||
const assistantCanon_1 = __importStar(require("./assistantCanon"));
|
const assistantCanon_1 = __importStar(require("./assistantCanon"));
|
||||||
const assistantAddressTurnFinalizeRuntimeAdapter_1 = __importStar(require("./assistantAddressTurnFinalizeRuntimeAdapter"));
|
const assistantAddressLaneResponseRuntimeAdapter_1 = __importStar(require("./assistantAddressLaneResponseRuntimeAdapter"));
|
||||||
const assistantCoverageGrounding_1 = __importStar(require("./assistantCoverageGrounding"));
|
const assistantCoverageGrounding_1 = __importStar(require("./assistantCoverageGrounding"));
|
||||||
const assistantDeepTurnAnalysisRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnAnalysisRuntimeAdapter"));
|
const assistantDeepTurnAnalysisRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnAnalysisRuntimeAdapter"));
|
||||||
const assistantDeepTurnCompositionRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnCompositionRuntimeAdapter"));
|
const assistantDeepTurnCompositionRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnCompositionRuntimeAdapter"));
|
||||||
|
|
@ -78,9 +78,7 @@ const assistantDeepTurnPlanRuntimeAdapter_1 = __importStar(require("./assistantD
|
||||||
const assistantDeepTurnNormalizationRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnNormalizationRuntimeAdapter"));
|
const assistantDeepTurnNormalizationRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnNormalizationRuntimeAdapter"));
|
||||||
const assistantDeepTurnResponseRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnResponseRuntimeAdapter"));
|
const assistantDeepTurnResponseRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnResponseRuntimeAdapter"));
|
||||||
const assistantDeepTurnRetrievalRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnRetrievalRuntimeAdapter"));
|
const assistantDeepTurnRetrievalRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnRetrievalRuntimeAdapter"));
|
||||||
const assistantAddressLaneRuntimeAdapter_1 = __importStar(require("./assistantAddressLaneRuntimeAdapter"));
|
const assistantAddressRuntimeAdapter_1 = __importStar(require("./assistantAddressRuntimeAdapter"));
|
||||||
const assistantAddressOrchestrationRuntimeAdapter_1 = __importStar(require("./assistantAddressOrchestrationRuntimeAdapter"));
|
|
||||||
const assistantAddressToolGateRuntimeAdapter_1 = __importStar(require("./assistantAddressToolGateRuntimeAdapter"));
|
|
||||||
const assistantLivingChatTurnFinalizeRuntimeAdapter_1 = __importStar(require("./assistantLivingChatTurnFinalizeRuntimeAdapter"));
|
const assistantLivingChatTurnFinalizeRuntimeAdapter_1 = __importStar(require("./assistantLivingChatTurnFinalizeRuntimeAdapter"));
|
||||||
const assistantLivingChatRuntimeAdapter_1 = __importStar(require("./assistantLivingChatRuntimeAdapter"));
|
const assistantLivingChatRuntimeAdapter_1 = __importStar(require("./assistantLivingChatRuntimeAdapter"));
|
||||||
const assistantQueryPlanning_1 = __importStar(require("./assistantQueryPlanning"));
|
const assistantQueryPlanning_1 = __importStar(require("./assistantQueryPlanning"));
|
||||||
|
|
@ -4432,31 +4430,20 @@ class AssistantService {
|
||||||
}
|
}
|
||||||
const sessionOrganizationScope = resolveSessionOrganizationScopeContext(userMessage, session.items);
|
const sessionOrganizationScope = resolveSessionOrganizationScopeContext(userMessage, session.items);
|
||||||
const finalizeAddressLaneResponse = (addressLane, effectiveAddressUserMessage, carryoverMeta = null, llmPreDecomposeMeta = null) => {
|
const finalizeAddressLaneResponse = (addressLane, effectiveAddressUserMessage, carryoverMeta = null, llmPreDecomposeMeta = null) => {
|
||||||
const safeAddressReply = sanitizeOutgoingAssistantText(addressLane.reply_text);
|
const runtime = (0, assistantAddressLaneResponseRuntimeAdapter_1.runAssistantAddressLaneResponseRuntime)({
|
||||||
const debug = buildAddressDebugPayload(addressLane.debug, llmPreDecomposeMeta);
|
|
||||||
const followupOffer = buildAddressFollowupOffer(debug);
|
|
||||||
if (followupOffer) {
|
|
||||||
debug.address_followup_offer = followupOffer;
|
|
||||||
}
|
|
||||||
const debugKnownOrganizations = mergeKnownOrganizations(sessionOrganizationScope.knownOrganizations);
|
|
||||||
const debugActiveOrganization = toNonEmptyString(debug?.extracted_filters?.organization) ??
|
|
||||||
toNonEmptyString(sessionOrganizationScope.activeOrganization);
|
|
||||||
if (debugKnownOrganizations.length > 0) {
|
|
||||||
debug.assistant_known_organizations = debugKnownOrganizations;
|
|
||||||
}
|
|
||||||
if (debugActiveOrganization) {
|
|
||||||
debug.assistant_active_organization = debugActiveOrganization;
|
|
||||||
}
|
|
||||||
const finalization = (0, assistantAddressTurnFinalizeRuntimeAdapter_1.finalizeAssistantAddressTurn)({
|
|
||||||
sessionId,
|
sessionId,
|
||||||
userMessage,
|
userMessage,
|
||||||
effectiveAddressUserMessage,
|
effectiveAddressUserMessage,
|
||||||
assistantReply: safeAddressReply,
|
addressLane,
|
||||||
replyType: addressLane.reply_type,
|
|
||||||
addressLaneDebug: addressLane.debug,
|
|
||||||
debug,
|
|
||||||
carryoverMeta,
|
carryoverMeta,
|
||||||
llmPreDecomposeMeta,
|
llmPreDecomposeMeta,
|
||||||
|
knownOrganizations: sessionOrganizationScope.knownOrganizations,
|
||||||
|
activeOrganization: sessionOrganizationScope.activeOrganization,
|
||||||
|
sanitizeOutgoingAssistantText,
|
||||||
|
buildAddressDebugPayload,
|
||||||
|
buildAddressFollowupOffer,
|
||||||
|
mergeKnownOrganizations,
|
||||||
|
toNonEmptyString,
|
||||||
appendItem: (targetSessionId, item) => this.sessions.appendItem(targetSessionId, item),
|
appendItem: (targetSessionId, item) => this.sessions.appendItem(targetSessionId, item),
|
||||||
getSession: (targetSessionId) => this.sessions.getSession(targetSessionId),
|
getSession: (targetSessionId) => this.sessions.getSession(targetSessionId),
|
||||||
persistSession: (sessionState) => this.sessionLogger.persistSession(sessionState),
|
persistSession: (sessionState) => this.sessionLogger.persistSession(sessionState),
|
||||||
|
|
@ -4464,7 +4451,7 @@ class AssistantService {
|
||||||
logEvent: (payload) => (0, log_1.logJson)(payload),
|
logEvent: (payload) => (0, log_1.logJson)(payload),
|
||||||
messageIdFactory: () => `msg-${(0, nanoid_1.nanoid)(10)}`
|
messageIdFactory: () => `msg-${(0, nanoid_1.nanoid)(10)}`
|
||||||
});
|
});
|
||||||
return finalization.response;
|
return runtime.response;
|
||||||
};
|
};
|
||||||
const tryHandleLivingChat = async (modeDecision, addressRuntimeMeta = null) => {
|
const tryHandleLivingChat = async (modeDecision, addressRuntimeMeta = null) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -4565,75 +4552,46 @@ class AssistantService {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let addressRuntimeMetaForDeep = null;
|
let addressRuntimeMetaForDeep = null;
|
||||||
if (config_1.FEATURE_ASSISTANT_ADDRESS_QUERY_V1) {
|
const runAddressLaneAttempt = async (messageUsed, carryMeta, analysisDateHint) => {
|
||||||
const addressOrchestrationRuntime = await (0, assistantAddressOrchestrationRuntimeAdapter_1.buildAssistantAddressOrchestrationRuntime)({
|
const scopedFollowupContext = mergeFollowupContextWithOrganizationScope(carryMeta?.followupContext ?? null, sessionOrganizationScope.activeOrganization);
|
||||||
userMessage,
|
if (scopedFollowupContext) {
|
||||||
sessionItems: session.items,
|
return this.addressQueryService.tryHandle(messageUsed, {
|
||||||
llmProvider: payload?.llmProvider,
|
followupContext: scopedFollowupContext,
|
||||||
useMock: Boolean(payload.useMock),
|
analysisDateHint
|
||||||
featureAddressLlmPredecomposeV1: config_1.FEATURE_ASSISTANT_ADDRESS_QUERY_LLM_PREDECOMPOSE_V1,
|
|
||||||
runAddressLlmPreDecompose: async () => runAddressLlmPreDecompose(this.normalizerService, payload, userMessage),
|
|
||||||
buildAddressLlmPredecomposeContractV1: predecomposeContract_1.buildAddressLlmPredecomposeContractV1,
|
|
||||||
sanitizeAddressMessageForFallback,
|
|
||||||
toNonEmptyString,
|
|
||||||
resolveAddressFollowupCarryoverContext,
|
|
||||||
resolveAssistantOrchestrationDecision,
|
|
||||||
buildAddressDialogContinuationContractV2
|
|
||||||
});
|
|
||||||
const addressPreDecompose = addressOrchestrationRuntime.addressPreDecompose;
|
|
||||||
const addressInputMessage = addressOrchestrationRuntime.addressInputMessage;
|
|
||||||
const carryover = addressOrchestrationRuntime.carryover;
|
|
||||||
const orchestrationDecision = addressOrchestrationRuntime.orchestrationDecision;
|
|
||||||
const addressRuntimeMeta = addressOrchestrationRuntime.addressRuntimeMeta;
|
|
||||||
addressRuntimeMetaForDeep = addressRuntimeMeta;
|
|
||||||
const livingModeDecision = addressOrchestrationRuntime.livingModeDecision;
|
|
||||||
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);
|
|
||||||
const analysisDateHint = runtimeAnalysisContext.as_of_date ?? toNonEmptyString(payload?.context?.period_hint);
|
|
||||||
const canRetryWithRawUserMessage = compactWhitespace(String(addressInputMessage ?? "").toLowerCase()) !==
|
|
||||||
compactWhitespace(String(userMessage ?? "").toLowerCase());
|
|
||||||
const runAddressLaneAttempt = async (messageUsed, carryMeta) => {
|
|
||||||
const scopedFollowupContext = mergeFollowupContextWithOrganizationScope(carryMeta?.followupContext ?? null, sessionOrganizationScope.activeOrganization);
|
|
||||||
if (scopedFollowupContext) {
|
|
||||||
return this.addressQueryService.tryHandle(messageUsed, {
|
|
||||||
followupContext: scopedFollowupContext,
|
|
||||||
analysisDateHint
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return this.addressQueryService.tryHandle(messageUsed, {
|
|
||||||
analysisDateHint
|
|
||||||
});
|
|
||||||
};
|
|
||||||
const addressLaneRuntime = await (0, assistantAddressLaneRuntimeAdapter_1.runAssistantAddressLaneRuntime)({
|
|
||||||
userMessage,
|
|
||||||
addressInputMessage,
|
|
||||||
carryover,
|
|
||||||
shouldPreferContextualLane,
|
|
||||||
canRetryWithRawUserMessage,
|
|
||||||
runAddressLaneAttempt,
|
|
||||||
isRetryableAddressLimitedResult
|
|
||||||
});
|
});
|
||||||
if (addressLaneRuntime.handled && addressLaneRuntime.selection) {
|
|
||||||
return finalizeAddressLaneResponse(addressLaneRuntime.selection.addressLane, addressLaneRuntime.selection.messageUsed, addressLaneRuntime.selection.carryMeta, {
|
|
||||||
...addressRuntimeMeta,
|
|
||||||
addressRetryAudit: { ...addressLaneRuntime.retryAudit }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return this.addressQueryService.tryHandle(messageUsed, {
|
||||||
|
analysisDateHint
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const addressRuntime = await (0, assistantAddressRuntimeAdapter_1.runAssistantAddressRuntime)({
|
||||||
|
featureAssistantAddressQueryV1: config_1.FEATURE_ASSISTANT_ADDRESS_QUERY_V1,
|
||||||
|
sessionId,
|
||||||
|
userMessage,
|
||||||
|
sessionItems: session.items,
|
||||||
|
llmProvider: payload?.llmProvider,
|
||||||
|
useMock: Boolean(payload.useMock),
|
||||||
|
featureAddressLlmPredecomposeV1: config_1.FEATURE_ASSISTANT_ADDRESS_QUERY_LLM_PREDECOMPOSE_V1,
|
||||||
|
runAddressLlmPreDecompose: async () => runAddressLlmPreDecompose(this.normalizerService, payload, userMessage),
|
||||||
|
buildAddressLlmPredecomposeContractV1: predecomposeContract_1.buildAddressLlmPredecomposeContractV1,
|
||||||
|
sanitizeAddressMessageForFallback,
|
||||||
|
toNonEmptyString,
|
||||||
|
resolveAddressFollowupCarryoverContext,
|
||||||
|
resolveAssistantOrchestrationDecision,
|
||||||
|
buildAddressDialogContinuationContractV2,
|
||||||
|
runtimeAnalysisContextAsOfDate: runtimeAnalysisContext.as_of_date,
|
||||||
|
payloadContextPeriodHint: payload?.context?.period_hint,
|
||||||
|
compactWhitespace,
|
||||||
|
runAddressLaneAttempt,
|
||||||
|
isRetryableAddressLimitedResult,
|
||||||
|
finalizeAddressLaneResponse,
|
||||||
|
tryHandleLivingChat: (modeDecision, runtimeMeta) => tryHandleLivingChat(modeDecision, runtimeMeta),
|
||||||
|
logEvent: (payload) => (0, log_1.logJson)(payload),
|
||||||
|
nowIso: () => new Date().toISOString()
|
||||||
|
});
|
||||||
|
addressRuntimeMetaForDeep = addressRuntime.addressRuntimeMetaForDeep;
|
||||||
|
if (addressRuntime.handled && addressRuntime.response) {
|
||||||
|
return addressRuntime.response;
|
||||||
}
|
}
|
||||||
const normalizationRuntime = await (0, assistantDeepTurnNormalizationRuntimeAdapter_1.buildAssistantDeepTurnNormalizationRuntime)({
|
const normalizationRuntime = await (0, assistantDeepTurnNormalizationRuntimeAdapter_1.buildAssistantDeepTurnNormalizationRuntime)({
|
||||||
userMessage,
|
userMessage,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
import type { AssistantAddressLaneLike, AssistantAddressFollowupCarryoverLike } from "./assistantAddressLaneRuntimeAdapter";
|
||||||
|
import type { AssistantMessageResponsePayload } from "../types/assistant";
|
||||||
|
import {
|
||||||
|
finalizeAssistantAddressTurn,
|
||||||
|
type FinalizeAssistantAddressTurnInput
|
||||||
|
} from "./assistantAddressTurnFinalizeRuntimeAdapter";
|
||||||
|
|
||||||
|
export interface RunAssistantAddressLaneResponseRuntimeInput<ResponseType = AssistantMessageResponsePayload> {
|
||||||
|
sessionId: string;
|
||||||
|
userMessage: string;
|
||||||
|
effectiveAddressUserMessage: string;
|
||||||
|
addressLane: AssistantAddressLaneLike;
|
||||||
|
carryoverMeta?: AssistantAddressFollowupCarryoverLike | null;
|
||||||
|
llmPreDecomposeMeta?: Record<string, unknown> | null;
|
||||||
|
knownOrganizations: string[];
|
||||||
|
activeOrganization: string | null;
|
||||||
|
sanitizeOutgoingAssistantText: (text: unknown, fallback?: string) => string;
|
||||||
|
buildAddressDebugPayload: (
|
||||||
|
addressDebug: unknown,
|
||||||
|
llmPreDecomposeMeta?: Record<string, unknown> | null
|
||||||
|
) => Record<string, unknown>;
|
||||||
|
buildAddressFollowupOffer: (addressDebug: Record<string, unknown>) => unknown;
|
||||||
|
mergeKnownOrganizations: (organizations: string[]) => string[];
|
||||||
|
toNonEmptyString: (value: unknown) => string | null;
|
||||||
|
appendItem: FinalizeAssistantAddressTurnInput["appendItem"];
|
||||||
|
getSession: FinalizeAssistantAddressTurnInput["getSession"];
|
||||||
|
persistSession: FinalizeAssistantAddressTurnInput["persistSession"];
|
||||||
|
cloneConversation: FinalizeAssistantAddressTurnInput["cloneConversation"];
|
||||||
|
logEvent: FinalizeAssistantAddressTurnInput["logEvent"];
|
||||||
|
messageIdFactory: FinalizeAssistantAddressTurnInput["messageIdFactory"];
|
||||||
|
finalizeAddressTurn?: (
|
||||||
|
input: FinalizeAssistantAddressTurnInput
|
||||||
|
) => {
|
||||||
|
response: ResponseType;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RunAssistantAddressLaneResponseRuntimeOutput<ResponseType = AssistantMessageResponsePayload> {
|
||||||
|
response: ResponseType;
|
||||||
|
debug: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function runAssistantAddressLaneResponseRuntime<ResponseType = AssistantMessageResponsePayload>(
|
||||||
|
input: RunAssistantAddressLaneResponseRuntimeInput<ResponseType>
|
||||||
|
): RunAssistantAddressLaneResponseRuntimeOutput<ResponseType> {
|
||||||
|
const finalizeAddressTurnSafe = input.finalizeAddressTurn ?? finalizeAssistantAddressTurn;
|
||||||
|
const safeAddressReply = input.sanitizeOutgoingAssistantText(input.addressLane.reply_text);
|
||||||
|
const debug = input.buildAddressDebugPayload(input.addressLane.debug, input.llmPreDecomposeMeta);
|
||||||
|
const followupOffer = input.buildAddressFollowupOffer(debug);
|
||||||
|
if (followupOffer) {
|
||||||
|
debug.address_followup_offer = followupOffer;
|
||||||
|
}
|
||||||
|
const debugKnownOrganizations = input.mergeKnownOrganizations(input.knownOrganizations);
|
||||||
|
const debugFilters =
|
||||||
|
debug?.extracted_filters && typeof debug.extracted_filters === "object"
|
||||||
|
? (debug.extracted_filters as Record<string, unknown>)
|
||||||
|
: null;
|
||||||
|
const debugActiveOrganization =
|
||||||
|
input.toNonEmptyString(debugFilters?.organization) ??
|
||||||
|
input.toNonEmptyString(input.activeOrganization);
|
||||||
|
if (debugKnownOrganizations.length > 0) {
|
||||||
|
debug.assistant_known_organizations = debugKnownOrganizations;
|
||||||
|
}
|
||||||
|
if (debugActiveOrganization) {
|
||||||
|
debug.assistant_active_organization = debugActiveOrganization;
|
||||||
|
}
|
||||||
|
const finalization = finalizeAddressTurnSafe({
|
||||||
|
sessionId: input.sessionId,
|
||||||
|
userMessage: input.userMessage,
|
||||||
|
effectiveAddressUserMessage: input.effectiveAddressUserMessage,
|
||||||
|
assistantReply: safeAddressReply,
|
||||||
|
replyType: input.addressLane.reply_type as any,
|
||||||
|
addressLaneDebug: (input.addressLane.debug ?? null) as any,
|
||||||
|
debug,
|
||||||
|
carryoverMeta: (input.carryoverMeta ?? null) as any,
|
||||||
|
llmPreDecomposeMeta: (input.llmPreDecomposeMeta ?? null) as any,
|
||||||
|
appendItem: input.appendItem,
|
||||||
|
getSession: input.getSession,
|
||||||
|
persistSession: input.persistSession,
|
||||||
|
cloneConversation: input.cloneConversation,
|
||||||
|
logEvent: input.logEvent,
|
||||||
|
messageIdFactory: input.messageIdFactory
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
response: finalization.response as ResponseType,
|
||||||
|
debug
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,189 @@
|
||||||
|
import {
|
||||||
|
buildAssistantAddressOrchestrationRuntime,
|
||||||
|
type AssistantAddressCarryoverLike,
|
||||||
|
type BuildAssistantAddressOrchestrationRuntimeInput,
|
||||||
|
type BuildAssistantAddressOrchestrationRuntimeOutput
|
||||||
|
} from "./assistantAddressOrchestrationRuntimeAdapter";
|
||||||
|
import {
|
||||||
|
runAssistantAddressLaneRuntime,
|
||||||
|
type AssistantAddressLaneLike,
|
||||||
|
type RunAssistantAddressLaneRuntimeOutput
|
||||||
|
} from "./assistantAddressLaneRuntimeAdapter";
|
||||||
|
import {
|
||||||
|
runAssistantAddressToolGateRuntime,
|
||||||
|
type AssistantAddressToolGateRuntimeOutput
|
||||||
|
} from "./assistantAddressToolGateRuntimeAdapter";
|
||||||
|
|
||||||
|
export interface RunAssistantAddressRuntimeInput<ResponseType = unknown> {
|
||||||
|
featureAssistantAddressQueryV1: boolean;
|
||||||
|
sessionId: string;
|
||||||
|
userMessage: string;
|
||||||
|
sessionItems: unknown[];
|
||||||
|
llmProvider: unknown;
|
||||||
|
useMock: boolean;
|
||||||
|
featureAddressLlmPredecomposeV1: boolean;
|
||||||
|
runAddressLlmPreDecompose: () => Promise<Record<string, unknown>>;
|
||||||
|
buildAddressLlmPredecomposeContractV1: BuildAssistantAddressOrchestrationRuntimeInput["buildAddressLlmPredecomposeContractV1"];
|
||||||
|
sanitizeAddressMessageForFallback: BuildAssistantAddressOrchestrationRuntimeInput["sanitizeAddressMessageForFallback"];
|
||||||
|
toNonEmptyString: (value: unknown) => string | null;
|
||||||
|
resolveAddressFollowupCarryoverContext: BuildAssistantAddressOrchestrationRuntimeInput["resolveAddressFollowupCarryoverContext"];
|
||||||
|
resolveAssistantOrchestrationDecision: BuildAssistantAddressOrchestrationRuntimeInput["resolveAssistantOrchestrationDecision"];
|
||||||
|
buildAddressDialogContinuationContractV2: BuildAssistantAddressOrchestrationRuntimeInput["buildAddressDialogContinuationContractV2"];
|
||||||
|
runtimeAnalysisContextAsOfDate: string | null;
|
||||||
|
payloadContextPeriodHint: unknown;
|
||||||
|
compactWhitespace: (value: string) => string;
|
||||||
|
runAddressLaneAttempt: (
|
||||||
|
messageUsed: string,
|
||||||
|
carryMeta: AssistantAddressCarryoverLike | null,
|
||||||
|
analysisDateHint: string | null
|
||||||
|
) => Promise<AssistantAddressLaneLike | null>;
|
||||||
|
isRetryableAddressLimitedResult: (addressLane: AssistantAddressLaneLike | null | undefined) => boolean;
|
||||||
|
finalizeAddressLaneResponse: (
|
||||||
|
addressLane: AssistantAddressLaneLike,
|
||||||
|
effectiveAddressUserMessage: string,
|
||||||
|
carryoverMeta?: AssistantAddressCarryoverLike | null,
|
||||||
|
llmPreDecomposeMeta?: Record<string, unknown> | null
|
||||||
|
) => ResponseType;
|
||||||
|
tryHandleLivingChat: (
|
||||||
|
modeDecision: { mode?: unknown; reason?: unknown },
|
||||||
|
addressRuntimeMeta: Record<string, unknown> | null
|
||||||
|
) => Promise<ResponseType | null>;
|
||||||
|
logEvent: (payload: Record<string, unknown>) => void;
|
||||||
|
nowIso: () => string;
|
||||||
|
runAddressOrchestrationRuntime?: (
|
||||||
|
input: BuildAssistantAddressOrchestrationRuntimeInput
|
||||||
|
) => Promise<BuildAssistantAddressOrchestrationRuntimeOutput>;
|
||||||
|
runAddressToolGateRuntime?: (
|
||||||
|
input: {
|
||||||
|
sessionId: string;
|
||||||
|
userMessage: string;
|
||||||
|
addressInputMessage: string;
|
||||||
|
orchestrationDecision: BuildAssistantAddressOrchestrationRuntimeOutput["orchestrationDecision"];
|
||||||
|
livingModeDecision: BuildAssistantAddressOrchestrationRuntimeOutput["livingModeDecision"];
|
||||||
|
addressRuntimeMeta: BuildAssistantAddressOrchestrationRuntimeOutput["addressRuntimeMeta"];
|
||||||
|
logEvent: (payload: Record<string, unknown>) => void;
|
||||||
|
tryHandleLivingChat: (
|
||||||
|
modeDecision: { mode?: unknown; reason?: unknown },
|
||||||
|
addressRuntimeMeta: Record<string, unknown> | null
|
||||||
|
) => Promise<ResponseType | null>;
|
||||||
|
nowIso: () => string;
|
||||||
|
}
|
||||||
|
) => Promise<AssistantAddressToolGateRuntimeOutput<ResponseType>>;
|
||||||
|
runAddressLaneRuntime?: (
|
||||||
|
input: {
|
||||||
|
userMessage: string;
|
||||||
|
addressInputMessage: string;
|
||||||
|
carryover: AssistantAddressCarryoverLike | null;
|
||||||
|
shouldPreferContextualLane: boolean;
|
||||||
|
canRetryWithRawUserMessage: boolean;
|
||||||
|
runAddressLaneAttempt: (
|
||||||
|
messageUsed: string,
|
||||||
|
carryMeta: AssistantAddressCarryoverLike | null
|
||||||
|
) => Promise<AssistantAddressLaneLike | null>;
|
||||||
|
isRetryableAddressLimitedResult: (addressLane: AssistantAddressLaneLike | null | undefined) => boolean;
|
||||||
|
}
|
||||||
|
) => Promise<RunAssistantAddressLaneRuntimeOutput>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RunAssistantAddressRuntimeOutput<ResponseType = unknown> {
|
||||||
|
handled: boolean;
|
||||||
|
response: ResponseType | null;
|
||||||
|
addressRuntimeMetaForDeep: Record<string, unknown> | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function runAssistantAddressRuntime<ResponseType = unknown>(
|
||||||
|
input: RunAssistantAddressRuntimeInput<ResponseType>
|
||||||
|
): Promise<RunAssistantAddressRuntimeOutput<ResponseType>> {
|
||||||
|
if (!input.featureAssistantAddressQueryV1) {
|
||||||
|
return {
|
||||||
|
handled: false,
|
||||||
|
response: null,
|
||||||
|
addressRuntimeMetaForDeep: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const runAddressOrchestrationRuntimeSafe =
|
||||||
|
input.runAddressOrchestrationRuntime ?? buildAssistantAddressOrchestrationRuntime;
|
||||||
|
const runAddressToolGateRuntimeSafe = input.runAddressToolGateRuntime ?? runAssistantAddressToolGateRuntime;
|
||||||
|
const runAddressLaneRuntimeSafe = input.runAddressLaneRuntime ?? runAssistantAddressLaneRuntime;
|
||||||
|
|
||||||
|
const addressOrchestrationRuntime = await runAddressOrchestrationRuntimeSafe({
|
||||||
|
userMessage: input.userMessage,
|
||||||
|
sessionItems: input.sessionItems,
|
||||||
|
llmProvider: input.llmProvider,
|
||||||
|
useMock: input.useMock,
|
||||||
|
featureAddressLlmPredecomposeV1: input.featureAddressLlmPredecomposeV1,
|
||||||
|
runAddressLlmPreDecompose: input.runAddressLlmPreDecompose,
|
||||||
|
buildAddressLlmPredecomposeContractV1: input.buildAddressLlmPredecomposeContractV1,
|
||||||
|
sanitizeAddressMessageForFallback: input.sanitizeAddressMessageForFallback,
|
||||||
|
toNonEmptyString: input.toNonEmptyString,
|
||||||
|
resolveAddressFollowupCarryoverContext: input.resolveAddressFollowupCarryoverContext,
|
||||||
|
resolveAssistantOrchestrationDecision: input.resolveAssistantOrchestrationDecision,
|
||||||
|
buildAddressDialogContinuationContractV2: input.buildAddressDialogContinuationContractV2
|
||||||
|
});
|
||||||
|
const addressInputMessage = addressOrchestrationRuntime.addressInputMessage;
|
||||||
|
const carryover = addressOrchestrationRuntime.carryover;
|
||||||
|
const orchestrationDecision = addressOrchestrationRuntime.orchestrationDecision;
|
||||||
|
const addressRuntimeMeta = addressOrchestrationRuntime.addressRuntimeMeta;
|
||||||
|
const livingModeDecision = addressOrchestrationRuntime.livingModeDecision;
|
||||||
|
const addressRuntimeMetaForDeep = addressRuntimeMeta;
|
||||||
|
|
||||||
|
const toolGateRuntime = await runAddressToolGateRuntimeSafe({
|
||||||
|
sessionId: input.sessionId,
|
||||||
|
userMessage: input.userMessage,
|
||||||
|
addressInputMessage,
|
||||||
|
orchestrationDecision,
|
||||||
|
livingModeDecision,
|
||||||
|
addressRuntimeMeta,
|
||||||
|
logEvent: input.logEvent,
|
||||||
|
tryHandleLivingChat: input.tryHandleLivingChat,
|
||||||
|
nowIso: input.nowIso
|
||||||
|
});
|
||||||
|
if (toolGateRuntime.handled && toolGateRuntime.response) {
|
||||||
|
return {
|
||||||
|
handled: true,
|
||||||
|
response: toolGateRuntime.response,
|
||||||
|
addressRuntimeMetaForDeep
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Boolean(orchestrationDecision.runAddressLane)) {
|
||||||
|
const shouldPreferContextualLane = Boolean(carryover?.followupContext);
|
||||||
|
const analysisDateHint = input.runtimeAnalysisContextAsOfDate ?? input.toNonEmptyString(input.payloadContextPeriodHint);
|
||||||
|
const canRetryWithRawUserMessage =
|
||||||
|
input.compactWhitespace(String(addressInputMessage ?? "").toLowerCase()) !==
|
||||||
|
input.compactWhitespace(String(input.userMessage ?? "").toLowerCase());
|
||||||
|
const addressLaneRuntime = await runAddressLaneRuntimeSafe({
|
||||||
|
userMessage: input.userMessage,
|
||||||
|
addressInputMessage,
|
||||||
|
carryover,
|
||||||
|
shouldPreferContextualLane,
|
||||||
|
canRetryWithRawUserMessage,
|
||||||
|
runAddressLaneAttempt: (messageUsed, carryMeta) =>
|
||||||
|
input.runAddressLaneAttempt(messageUsed, carryMeta, analysisDateHint),
|
||||||
|
isRetryableAddressLimitedResult: input.isRetryableAddressLimitedResult
|
||||||
|
});
|
||||||
|
if (addressLaneRuntime.handled && addressLaneRuntime.selection) {
|
||||||
|
const response = input.finalizeAddressLaneResponse(
|
||||||
|
addressLaneRuntime.selection.addressLane,
|
||||||
|
addressLaneRuntime.selection.messageUsed,
|
||||||
|
addressLaneRuntime.selection.carryMeta,
|
||||||
|
{
|
||||||
|
...addressRuntimeMeta,
|
||||||
|
addressRetryAudit: { ...addressLaneRuntime.retryAudit }
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
handled: true,
|
||||||
|
response,
|
||||||
|
addressRuntimeMetaForDeep
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
handled: false,
|
||||||
|
response: null,
|
||||||
|
addressRuntimeMetaForDeep
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -19,7 +19,7 @@ import * as openaiResponsesClient_1 from "./openaiResponsesClient";
|
||||||
import * as addressMcpClient_1 from "./addressMcpClient";
|
import * as addressMcpClient_1 from "./addressMcpClient";
|
||||||
import * as capabilitiesRegistry_1 from "./capabilitiesRegistry";
|
import * as capabilitiesRegistry_1 from "./capabilitiesRegistry";
|
||||||
import * as assistantCanon_1 from "./assistantCanon";
|
import * as assistantCanon_1 from "./assistantCanon";
|
||||||
import * as assistantAddressTurnFinalizeRuntimeAdapter_1 from "./assistantAddressTurnFinalizeRuntimeAdapter";
|
import * as assistantAddressLaneResponseRuntimeAdapter_1 from "./assistantAddressLaneResponseRuntimeAdapter";
|
||||||
import * as assistantCoverageGrounding_1 from "./assistantCoverageGrounding";
|
import * as assistantCoverageGrounding_1 from "./assistantCoverageGrounding";
|
||||||
import * as assistantDeepTurnAnalysisRuntimeAdapter_1 from "./assistantDeepTurnAnalysisRuntimeAdapter";
|
import * as assistantDeepTurnAnalysisRuntimeAdapter_1 from "./assistantDeepTurnAnalysisRuntimeAdapter";
|
||||||
import * as assistantDeepTurnCompositionRuntimeAdapter_1 from "./assistantDeepTurnCompositionRuntimeAdapter";
|
import * as assistantDeepTurnCompositionRuntimeAdapter_1 from "./assistantDeepTurnCompositionRuntimeAdapter";
|
||||||
|
|
@ -32,9 +32,7 @@ import * as assistantDeepTurnPlanRuntimeAdapter_1 from "./assistantDeepTurnPlanR
|
||||||
import * as assistantDeepTurnNormalizationRuntimeAdapter_1 from "./assistantDeepTurnNormalizationRuntimeAdapter";
|
import * as assistantDeepTurnNormalizationRuntimeAdapter_1 from "./assistantDeepTurnNormalizationRuntimeAdapter";
|
||||||
import * as assistantDeepTurnResponseRuntimeAdapter_1 from "./assistantDeepTurnResponseRuntimeAdapter";
|
import * as assistantDeepTurnResponseRuntimeAdapter_1 from "./assistantDeepTurnResponseRuntimeAdapter";
|
||||||
import * as assistantDeepTurnRetrievalRuntimeAdapter_1 from "./assistantDeepTurnRetrievalRuntimeAdapter";
|
import * as assistantDeepTurnRetrievalRuntimeAdapter_1 from "./assistantDeepTurnRetrievalRuntimeAdapter";
|
||||||
import * as assistantAddressLaneRuntimeAdapter_1 from "./assistantAddressLaneRuntimeAdapter";
|
import * as assistantAddressRuntimeAdapter_1 from "./assistantAddressRuntimeAdapter";
|
||||||
import * as assistantAddressOrchestrationRuntimeAdapter_1 from "./assistantAddressOrchestrationRuntimeAdapter";
|
|
||||||
import * as assistantAddressToolGateRuntimeAdapter_1 from "./assistantAddressToolGateRuntimeAdapter";
|
|
||||||
import * as assistantLivingChatTurnFinalizeRuntimeAdapter_1 from "./assistantLivingChatTurnFinalizeRuntimeAdapter";
|
import * as assistantLivingChatTurnFinalizeRuntimeAdapter_1 from "./assistantLivingChatTurnFinalizeRuntimeAdapter";
|
||||||
import * as assistantLivingChatRuntimeAdapter_1 from "./assistantLivingChatRuntimeAdapter";
|
import * as assistantLivingChatRuntimeAdapter_1 from "./assistantLivingChatRuntimeAdapter";
|
||||||
import * as assistantQueryPlanning_1 from "./assistantQueryPlanning";
|
import * as assistantQueryPlanning_1 from "./assistantQueryPlanning";
|
||||||
|
|
@ -4387,31 +4385,20 @@ export class AssistantService {
|
||||||
}
|
}
|
||||||
const sessionOrganizationScope = resolveSessionOrganizationScopeContext(userMessage, session.items);
|
const sessionOrganizationScope = resolveSessionOrganizationScopeContext(userMessage, session.items);
|
||||||
const finalizeAddressLaneResponse = (addressLane, effectiveAddressUserMessage, carryoverMeta = null, llmPreDecomposeMeta = null) => {
|
const finalizeAddressLaneResponse = (addressLane, effectiveAddressUserMessage, carryoverMeta = null, llmPreDecomposeMeta = null) => {
|
||||||
const safeAddressReply = sanitizeOutgoingAssistantText(addressLane.reply_text);
|
const runtime = (0, assistantAddressLaneResponseRuntimeAdapter_1.runAssistantAddressLaneResponseRuntime)({
|
||||||
const debug = buildAddressDebugPayload(addressLane.debug, llmPreDecomposeMeta);
|
|
||||||
const followupOffer = buildAddressFollowupOffer(debug);
|
|
||||||
if (followupOffer) {
|
|
||||||
debug.address_followup_offer = followupOffer;
|
|
||||||
}
|
|
||||||
const debugKnownOrganizations = mergeKnownOrganizations(sessionOrganizationScope.knownOrganizations);
|
|
||||||
const debugActiveOrganization = toNonEmptyString(debug?.extracted_filters?.organization) ??
|
|
||||||
toNonEmptyString(sessionOrganizationScope.activeOrganization);
|
|
||||||
if (debugKnownOrganizations.length > 0) {
|
|
||||||
debug.assistant_known_organizations = debugKnownOrganizations;
|
|
||||||
}
|
|
||||||
if (debugActiveOrganization) {
|
|
||||||
debug.assistant_active_organization = debugActiveOrganization;
|
|
||||||
}
|
|
||||||
const finalization = (0, assistantAddressTurnFinalizeRuntimeAdapter_1.finalizeAssistantAddressTurn)({
|
|
||||||
sessionId,
|
sessionId,
|
||||||
userMessage,
|
userMessage,
|
||||||
effectiveAddressUserMessage,
|
effectiveAddressUserMessage,
|
||||||
assistantReply: safeAddressReply,
|
addressLane,
|
||||||
replyType: addressLane.reply_type,
|
|
||||||
addressLaneDebug: addressLane.debug,
|
|
||||||
debug,
|
|
||||||
carryoverMeta,
|
carryoverMeta,
|
||||||
llmPreDecomposeMeta,
|
llmPreDecomposeMeta,
|
||||||
|
knownOrganizations: sessionOrganizationScope.knownOrganizations,
|
||||||
|
activeOrganization: sessionOrganizationScope.activeOrganization,
|
||||||
|
sanitizeOutgoingAssistantText,
|
||||||
|
buildAddressDebugPayload,
|
||||||
|
buildAddressFollowupOffer,
|
||||||
|
mergeKnownOrganizations,
|
||||||
|
toNonEmptyString,
|
||||||
appendItem: (targetSessionId, item) => this.sessions.appendItem(targetSessionId, item),
|
appendItem: (targetSessionId, item) => this.sessions.appendItem(targetSessionId, item),
|
||||||
getSession: (targetSessionId) => this.sessions.getSession(targetSessionId),
|
getSession: (targetSessionId) => this.sessions.getSession(targetSessionId),
|
||||||
persistSession: (sessionState) => this.sessionLogger.persistSession(sessionState),
|
persistSession: (sessionState) => this.sessionLogger.persistSession(sessionState),
|
||||||
|
|
@ -4419,7 +4406,7 @@ export class AssistantService {
|
||||||
logEvent: (payload) => (0, log_1.logJson)(payload),
|
logEvent: (payload) => (0, log_1.logJson)(payload),
|
||||||
messageIdFactory: () => `msg-${(0, nanoid_1.nanoid)(10)}`
|
messageIdFactory: () => `msg-${(0, nanoid_1.nanoid)(10)}`
|
||||||
});
|
});
|
||||||
return finalization.response;
|
return runtime.response;
|
||||||
};
|
};
|
||||||
const tryHandleLivingChat = async (modeDecision, addressRuntimeMeta = null) => {
|
const tryHandleLivingChat = async (modeDecision, addressRuntimeMeta = null) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -4520,75 +4507,46 @@ export class AssistantService {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let addressRuntimeMetaForDeep = null;
|
let addressRuntimeMetaForDeep = null;
|
||||||
if (config_1.FEATURE_ASSISTANT_ADDRESS_QUERY_V1) {
|
const runAddressLaneAttempt = async (messageUsed, carryMeta, analysisDateHint) => {
|
||||||
const addressOrchestrationRuntime = await (0, assistantAddressOrchestrationRuntimeAdapter_1.buildAssistantAddressOrchestrationRuntime)({
|
const scopedFollowupContext = mergeFollowupContextWithOrganizationScope(carryMeta?.followupContext ?? null, sessionOrganizationScope.activeOrganization);
|
||||||
userMessage,
|
if (scopedFollowupContext) {
|
||||||
sessionItems: session.items,
|
return this.addressQueryService.tryHandle(messageUsed, {
|
||||||
llmProvider: payload?.llmProvider,
|
followupContext: scopedFollowupContext,
|
||||||
useMock: Boolean(payload.useMock),
|
analysisDateHint
|
||||||
featureAddressLlmPredecomposeV1: config_1.FEATURE_ASSISTANT_ADDRESS_QUERY_LLM_PREDECOMPOSE_V1,
|
|
||||||
runAddressLlmPreDecompose: async () => runAddressLlmPreDecompose(this.normalizerService, payload, userMessage),
|
|
||||||
buildAddressLlmPredecomposeContractV1: predecomposeContract_1.buildAddressLlmPredecomposeContractV1,
|
|
||||||
sanitizeAddressMessageForFallback,
|
|
||||||
toNonEmptyString,
|
|
||||||
resolveAddressFollowupCarryoverContext,
|
|
||||||
resolveAssistantOrchestrationDecision,
|
|
||||||
buildAddressDialogContinuationContractV2
|
|
||||||
});
|
|
||||||
const addressPreDecompose = addressOrchestrationRuntime.addressPreDecompose;
|
|
||||||
const addressInputMessage = addressOrchestrationRuntime.addressInputMessage;
|
|
||||||
const carryover = addressOrchestrationRuntime.carryover;
|
|
||||||
const orchestrationDecision = addressOrchestrationRuntime.orchestrationDecision;
|
|
||||||
const addressRuntimeMeta = addressOrchestrationRuntime.addressRuntimeMeta;
|
|
||||||
addressRuntimeMetaForDeep = addressRuntimeMeta;
|
|
||||||
const livingModeDecision = addressOrchestrationRuntime.livingModeDecision;
|
|
||||||
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);
|
|
||||||
const analysisDateHint = runtimeAnalysisContext.as_of_date ?? toNonEmptyString(payload?.context?.period_hint);
|
|
||||||
const canRetryWithRawUserMessage = compactWhitespace(String(addressInputMessage ?? "").toLowerCase()) !==
|
|
||||||
compactWhitespace(String(userMessage ?? "").toLowerCase());
|
|
||||||
const runAddressLaneAttempt = async (messageUsed, carryMeta) => {
|
|
||||||
const scopedFollowupContext = mergeFollowupContextWithOrganizationScope(carryMeta?.followupContext ?? null, sessionOrganizationScope.activeOrganization);
|
|
||||||
if (scopedFollowupContext) {
|
|
||||||
return this.addressQueryService.tryHandle(messageUsed, {
|
|
||||||
followupContext: scopedFollowupContext,
|
|
||||||
analysisDateHint
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return this.addressQueryService.tryHandle(messageUsed, {
|
|
||||||
analysisDateHint
|
|
||||||
});
|
|
||||||
};
|
|
||||||
const addressLaneRuntime = await (0, assistantAddressLaneRuntimeAdapter_1.runAssistantAddressLaneRuntime)({
|
|
||||||
userMessage,
|
|
||||||
addressInputMessage,
|
|
||||||
carryover,
|
|
||||||
shouldPreferContextualLane,
|
|
||||||
canRetryWithRawUserMessage,
|
|
||||||
runAddressLaneAttempt,
|
|
||||||
isRetryableAddressLimitedResult
|
|
||||||
});
|
});
|
||||||
if (addressLaneRuntime.handled && addressLaneRuntime.selection) {
|
|
||||||
return finalizeAddressLaneResponse(addressLaneRuntime.selection.addressLane, addressLaneRuntime.selection.messageUsed, addressLaneRuntime.selection.carryMeta, {
|
|
||||||
...addressRuntimeMeta,
|
|
||||||
addressRetryAudit: { ...addressLaneRuntime.retryAudit }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return this.addressQueryService.tryHandle(messageUsed, {
|
||||||
|
analysisDateHint
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const addressRuntime = await (0, assistantAddressRuntimeAdapter_1.runAssistantAddressRuntime)({
|
||||||
|
featureAssistantAddressQueryV1: config_1.FEATURE_ASSISTANT_ADDRESS_QUERY_V1,
|
||||||
|
sessionId,
|
||||||
|
userMessage,
|
||||||
|
sessionItems: session.items,
|
||||||
|
llmProvider: payload?.llmProvider,
|
||||||
|
useMock: Boolean(payload.useMock),
|
||||||
|
featureAddressLlmPredecomposeV1: config_1.FEATURE_ASSISTANT_ADDRESS_QUERY_LLM_PREDECOMPOSE_V1,
|
||||||
|
runAddressLlmPreDecompose: async () => runAddressLlmPreDecompose(this.normalizerService, payload, userMessage),
|
||||||
|
buildAddressLlmPredecomposeContractV1: predecomposeContract_1.buildAddressLlmPredecomposeContractV1,
|
||||||
|
sanitizeAddressMessageForFallback,
|
||||||
|
toNonEmptyString,
|
||||||
|
resolveAddressFollowupCarryoverContext,
|
||||||
|
resolveAssistantOrchestrationDecision,
|
||||||
|
buildAddressDialogContinuationContractV2,
|
||||||
|
runtimeAnalysisContextAsOfDate: runtimeAnalysisContext.as_of_date,
|
||||||
|
payloadContextPeriodHint: payload?.context?.period_hint,
|
||||||
|
compactWhitespace,
|
||||||
|
runAddressLaneAttempt,
|
||||||
|
isRetryableAddressLimitedResult,
|
||||||
|
finalizeAddressLaneResponse,
|
||||||
|
tryHandleLivingChat: (modeDecision, runtimeMeta) => tryHandleLivingChat(modeDecision, runtimeMeta),
|
||||||
|
logEvent: (payload) => (0, log_1.logJson)(payload),
|
||||||
|
nowIso: () => new Date().toISOString()
|
||||||
|
});
|
||||||
|
addressRuntimeMetaForDeep = addressRuntime.addressRuntimeMetaForDeep;
|
||||||
|
if (addressRuntime.handled && addressRuntime.response) {
|
||||||
|
return addressRuntime.response;
|
||||||
}
|
}
|
||||||
const normalizationRuntime = await (0, assistantDeepTurnNormalizationRuntimeAdapter_1.buildAssistantDeepTurnNormalizationRuntime)({
|
const normalizationRuntime = await (0, assistantDeepTurnNormalizationRuntimeAdapter_1.buildAssistantDeepTurnNormalizationRuntime)({
|
||||||
userMessage,
|
userMessage,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,103 @@
|
||||||
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
import { runAssistantAddressLaneResponseRuntime } from "../src/services/assistantAddressLaneResponseRuntimeAdapter";
|
||||||
|
|
||||||
|
describe("assistant address lane response runtime adapter", () => {
|
||||||
|
it("builds debug payload and finalizes address turn", () => {
|
||||||
|
const finalizeAddressTurn = vi.fn(() => ({
|
||||||
|
response: {
|
||||||
|
ok: true
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
const runtime = runAssistantAddressLaneResponseRuntime({
|
||||||
|
sessionId: "asst-1",
|
||||||
|
userMessage: "raw",
|
||||||
|
effectiveAddressUserMessage: "canon",
|
||||||
|
addressLane: {
|
||||||
|
handled: true,
|
||||||
|
reply_text: "answer",
|
||||||
|
reply_type: "factual",
|
||||||
|
debug: {
|
||||||
|
extracted_filters: {
|
||||||
|
organization: "ООО Ромашка"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
carryoverMeta: {
|
||||||
|
followupContext: {
|
||||||
|
previous_intent: "list_documents"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
llmPreDecomposeMeta: {
|
||||||
|
attempted: true
|
||||||
|
},
|
||||||
|
knownOrganizations: ["ООО Ромашка", "ООО Лютик"],
|
||||||
|
activeOrganization: "ООО Ромашка",
|
||||||
|
sanitizeOutgoingAssistantText: (text) => String(text ?? "").trim(),
|
||||||
|
buildAddressDebugPayload: (addressDebug) => ({ ...(addressDebug as Record<string, unknown>) }),
|
||||||
|
buildAddressFollowupOffer: () => ({ suggestion: "continue_previous" }),
|
||||||
|
mergeKnownOrganizations: (items) => Array.from(new Set(items)),
|
||||||
|
toNonEmptyString: (value) => (typeof value === "string" && value.trim() ? value.trim() : null),
|
||||||
|
appendItem: () => {},
|
||||||
|
getSession: () => ({ session_id: "asst-1", updated_at: "", items: [], investigation_state: null } as any),
|
||||||
|
persistSession: () => {},
|
||||||
|
cloneConversation: (items) => items,
|
||||||
|
logEvent: () => {},
|
||||||
|
messageIdFactory: () => "msg-1",
|
||||||
|
finalizeAddressTurn
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(finalizeAddressTurn).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
assistantReply: "answer",
|
||||||
|
replyType: "factual",
|
||||||
|
llmPreDecomposeMeta: {
|
||||||
|
attempted: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(runtime.response).toEqual({ ok: true });
|
||||||
|
expect(runtime.debug).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
assistant_known_organizations: ["ООО Ромашка", "ООО Лютик"],
|
||||||
|
assistant_active_organization: "ООО Ромашка",
|
||||||
|
address_followup_offer: { suggestion: "continue_previous" }
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps debug minimal when optional enrichment is absent", () => {
|
||||||
|
const runtime = runAssistantAddressLaneResponseRuntime({
|
||||||
|
sessionId: "asst-2",
|
||||||
|
userMessage: "raw",
|
||||||
|
effectiveAddressUserMessage: "raw",
|
||||||
|
addressLane: {
|
||||||
|
handled: true,
|
||||||
|
reply_text: "answer",
|
||||||
|
reply_type: "partial_coverage",
|
||||||
|
debug: {}
|
||||||
|
},
|
||||||
|
knownOrganizations: [],
|
||||||
|
activeOrganization: null,
|
||||||
|
sanitizeOutgoingAssistantText: (text) => String(text ?? ""),
|
||||||
|
buildAddressDebugPayload: () => ({}),
|
||||||
|
buildAddressFollowupOffer: () => null,
|
||||||
|
mergeKnownOrganizations: (items) => items,
|
||||||
|
toNonEmptyString: () => null,
|
||||||
|
appendItem: () => {},
|
||||||
|
getSession: () => ({ session_id: "asst-2", updated_at: "", items: [], investigation_state: null } as any),
|
||||||
|
persistSession: () => {},
|
||||||
|
cloneConversation: (items) => items,
|
||||||
|
logEvent: () => {},
|
||||||
|
messageIdFactory: () => "msg-2",
|
||||||
|
finalizeAddressTurn: () => ({
|
||||||
|
response: {
|
||||||
|
ok: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(runtime.debug).toEqual({});
|
||||||
|
expect(runtime.response).toEqual({ ok: true });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,196 @@
|
||||||
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
import { runAssistantAddressRuntime } from "../src/services/assistantAddressRuntimeAdapter";
|
||||||
|
|
||||||
|
describe("assistant address runtime adapter", () => {
|
||||||
|
it("returns unhandled when address feature is disabled", async () => {
|
||||||
|
const result = await runAssistantAddressRuntime({
|
||||||
|
featureAssistantAddressQueryV1: false,
|
||||||
|
sessionId: "asst-1",
|
||||||
|
userMessage: "question",
|
||||||
|
sessionItems: [],
|
||||||
|
llmProvider: "openai",
|
||||||
|
useMock: false,
|
||||||
|
featureAddressLlmPredecomposeV1: true,
|
||||||
|
runAddressLlmPreDecompose: async () => ({}),
|
||||||
|
buildAddressLlmPredecomposeContractV1: () => ({}),
|
||||||
|
sanitizeAddressMessageForFallback: (value) => value,
|
||||||
|
toNonEmptyString: () => null,
|
||||||
|
resolveAddressFollowupCarryoverContext: () => null,
|
||||||
|
resolveAssistantOrchestrationDecision: () => ({}),
|
||||||
|
buildAddressDialogContinuationContractV2: () => ({}),
|
||||||
|
runtimeAnalysisContextAsOfDate: null,
|
||||||
|
payloadContextPeriodHint: null,
|
||||||
|
compactWhitespace: (value) => value.trim(),
|
||||||
|
runAddressLaneAttempt: async () => null,
|
||||||
|
isRetryableAddressLimitedResult: () => false,
|
||||||
|
finalizeAddressLaneResponse: () => ({ ok: true }),
|
||||||
|
tryHandleLivingChat: async () => null,
|
||||||
|
logEvent: () => {},
|
||||||
|
nowIso: () => "2026-04-10T00:00:00.000Z"
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
handled: false,
|
||||||
|
response: null,
|
||||||
|
addressRuntimeMetaForDeep: null
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns early when tool-gate chat fallback handles", async () => {
|
||||||
|
const runAddressOrchestrationRuntime = vi.fn(async () => ({
|
||||||
|
addressPreDecompose: {},
|
||||||
|
addressInputMessage: "canon",
|
||||||
|
carryover: null,
|
||||||
|
orchestrationDecision: { runAddressLane: false },
|
||||||
|
addressRuntimeMeta: { attempted: true },
|
||||||
|
livingModeDecision: { mode: "chat", reason: "x" }
|
||||||
|
}));
|
||||||
|
const runAddressToolGateRuntime = vi.fn(async () => ({
|
||||||
|
handled: true,
|
||||||
|
response: { ok: "chat" }
|
||||||
|
}));
|
||||||
|
const runAddressLaneRuntime = vi.fn(async () => ({
|
||||||
|
handled: false,
|
||||||
|
selection: null,
|
||||||
|
retryAudit: {
|
||||||
|
attempted: false,
|
||||||
|
reason: null,
|
||||||
|
initial_limited_category: null,
|
||||||
|
retry_message: null,
|
||||||
|
retry_used_followup_context: false,
|
||||||
|
retry_result_category: null
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
const result = await runAssistantAddressRuntime({
|
||||||
|
featureAssistantAddressQueryV1: true,
|
||||||
|
sessionId: "asst-2",
|
||||||
|
userMessage: "question",
|
||||||
|
sessionItems: [],
|
||||||
|
llmProvider: "openai",
|
||||||
|
useMock: false,
|
||||||
|
featureAddressLlmPredecomposeV1: true,
|
||||||
|
runAddressLlmPreDecompose: async () => ({}),
|
||||||
|
buildAddressLlmPredecomposeContractV1: () => ({}),
|
||||||
|
sanitizeAddressMessageForFallback: (value) => value,
|
||||||
|
toNonEmptyString: () => null,
|
||||||
|
resolveAddressFollowupCarryoverContext: () => null,
|
||||||
|
resolveAssistantOrchestrationDecision: () => ({}),
|
||||||
|
buildAddressDialogContinuationContractV2: () => ({}),
|
||||||
|
runtimeAnalysisContextAsOfDate: null,
|
||||||
|
payloadContextPeriodHint: null,
|
||||||
|
compactWhitespace: (value) => value.trim(),
|
||||||
|
runAddressLaneAttempt: async () => null,
|
||||||
|
isRetryableAddressLimitedResult: () => false,
|
||||||
|
finalizeAddressLaneResponse: () => ({ ok: true }),
|
||||||
|
tryHandleLivingChat: async () => ({ ok: true }),
|
||||||
|
logEvent: () => {},
|
||||||
|
nowIso: () => "2026-04-10T00:00:00.000Z",
|
||||||
|
runAddressOrchestrationRuntime,
|
||||||
|
runAddressToolGateRuntime,
|
||||||
|
runAddressLaneRuntime
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.handled).toBe(true);
|
||||||
|
expect(result.response).toEqual({ ok: "chat" });
|
||||||
|
expect(result.addressRuntimeMetaForDeep).toEqual({ attempted: true });
|
||||||
|
expect(runAddressLaneRuntime).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("finalizes address lane when lane runtime resolves handled selection", async () => {
|
||||||
|
const runAddressLaneAttempt = vi.fn(async () => ({
|
||||||
|
handled: true
|
||||||
|
}));
|
||||||
|
const finalizeAddressLaneResponse = vi.fn(() => ({ ok: "address" }));
|
||||||
|
const runAddressLaneRuntime = vi.fn(async (input) => {
|
||||||
|
await input.runAddressLaneAttempt("canon", null);
|
||||||
|
return {
|
||||||
|
handled: true,
|
||||||
|
selection: {
|
||||||
|
addressLane: {
|
||||||
|
handled: true
|
||||||
|
},
|
||||||
|
messageUsed: "canon",
|
||||||
|
carryMeta: null
|
||||||
|
},
|
||||||
|
retryAudit: {
|
||||||
|
attempted: true,
|
||||||
|
reason: "limited_result_retry_with_raw_message",
|
||||||
|
initial_limited_category: "missing_anchor",
|
||||||
|
retry_message: "raw",
|
||||||
|
retry_used_followup_context: false,
|
||||||
|
retry_result_category: null
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await runAssistantAddressRuntime({
|
||||||
|
featureAssistantAddressQueryV1: true,
|
||||||
|
sessionId: "asst-3",
|
||||||
|
userMessage: "raw question",
|
||||||
|
sessionItems: [],
|
||||||
|
llmProvider: "openai",
|
||||||
|
useMock: false,
|
||||||
|
featureAddressLlmPredecomposeV1: true,
|
||||||
|
runAddressLlmPreDecompose: async () => ({}),
|
||||||
|
buildAddressLlmPredecomposeContractV1: () => ({}),
|
||||||
|
sanitizeAddressMessageForFallback: (value) => value,
|
||||||
|
toNonEmptyString: (value) => (typeof value === "string" && value.trim() ? value.trim() : null),
|
||||||
|
resolveAddressFollowupCarryoverContext: () => ({
|
||||||
|
followupContext: {
|
||||||
|
intent: "x"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
resolveAssistantOrchestrationDecision: () => ({}),
|
||||||
|
buildAddressDialogContinuationContractV2: () => ({}),
|
||||||
|
runtimeAnalysisContextAsOfDate: null,
|
||||||
|
payloadContextPeriodHint: "2020-07-31",
|
||||||
|
compactWhitespace: (value) => value.replace(/\s+/g, " ").trim(),
|
||||||
|
runAddressLaneAttempt,
|
||||||
|
isRetryableAddressLimitedResult: () => false,
|
||||||
|
finalizeAddressLaneResponse,
|
||||||
|
tryHandleLivingChat: async () => null,
|
||||||
|
logEvent: () => {},
|
||||||
|
nowIso: () => "2026-04-10T00:00:00.000Z",
|
||||||
|
runAddressOrchestrationRuntime: async () => ({
|
||||||
|
addressPreDecompose: {},
|
||||||
|
addressInputMessage: "canon",
|
||||||
|
carryover: {
|
||||||
|
followupContext: {
|
||||||
|
intent: "x"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
orchestrationDecision: { runAddressLane: true },
|
||||||
|
addressRuntimeMeta: {
|
||||||
|
attempted: true
|
||||||
|
},
|
||||||
|
livingModeDecision: { mode: "deep_analysis", reason: "x" }
|
||||||
|
}),
|
||||||
|
runAddressToolGateRuntime: async () => ({
|
||||||
|
handled: false,
|
||||||
|
response: null
|
||||||
|
}),
|
||||||
|
runAddressLaneRuntime
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(runAddressLaneAttempt).toHaveBeenCalledWith("canon", null, "2020-07-31");
|
||||||
|
expect(finalizeAddressLaneResponse).toHaveBeenCalledWith(
|
||||||
|
{ handled: true },
|
||||||
|
"canon",
|
||||||
|
null,
|
||||||
|
expect.objectContaining({
|
||||||
|
attempted: true,
|
||||||
|
addressRetryAudit: expect.objectContaining({
|
||||||
|
attempted: true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(result).toEqual({
|
||||||
|
handled: true,
|
||||||
|
response: { ok: "address" },
|
||||||
|
addressRuntimeMetaForDeep: {
|
||||||
|
attempted: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue