ГЛОБАЛЬНЫЙ РЕФАКТОРИНГ АРХИТЕКТУРЫ - Рефакторинг этапов 2.25 финализацию address-ветки в отдельный adapter \ Переподключил assistantService на новый adapter (без смены поведения)
This commit is contained in:
parent
80d108e506
commit
205daeccc5
|
|
@ -826,7 +826,76 @@ Validation:
|
|||
- `assistantMcpRuntimeBridge.test.ts`
|
||||
- `assistantAddressFollowupContext.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 completed)**
|
||||
Implemented in current pass (Phase 2.24):
|
||||
1. Extracted deep-lane finalization/response tail from `assistantService` into dedicated runtime adapter:
|
||||
- `assistantDeepTurnFinalizeRuntimeAdapter.ts`
|
||||
- introduced:
|
||||
- `finalizeAssistantDeepTurn(...)`
|
||||
2. Centralized finalization runtime sequence (behavior-preserving):
|
||||
- assistant turn commit + processed-event logging;
|
||||
- deep-turn API success response assembly from committed conversation state.
|
||||
3. Rewired `assistantService` deep-lane to consume finalization adapter output (behavior-preserving).
|
||||
4. Added focused unit tests:
|
||||
- `assistantDeepTurnFinalizeRuntimeAdapter.test.ts`
|
||||
|
||||
Validation:
|
||||
1. `npm run build` passed.
|
||||
2. Targeted Stage 2 assembler/adapter pack passed:
|
||||
- `assistantOrchestrationContracts.test.ts`
|
||||
- `assistantOrchestrationRuntimeAdapter.test.ts`
|
||||
- `assistantAnswerPackageBuilder.test.ts`
|
||||
- `assistantCoverageGrounding.test.ts`
|
||||
- `assistantQueryPlanning.test.ts`
|
||||
- `assistantEvidenceBundleAssembler.test.ts`
|
||||
- `assistantDebugPayloadAssembler.test.ts`
|
||||
- `assistantMessageLogAssembler.test.ts`
|
||||
- `assistantContractsBundleAssembler.test.ts`
|
||||
- `assistantDeepResponseAssembler.test.ts`
|
||||
- `assistantDeepTurnPackaging.test.ts`
|
||||
- `assistantDeepTurnInputBuilder.test.ts`
|
||||
- `assistantInvestigationStateRuntimeAdapter.test.ts`
|
||||
- `assistantTurnCommitRuntimeAdapter.test.ts`
|
||||
- `assistantDeepTurnPrePackagingContext.test.ts`
|
||||
- `assistantDeepTurnResponseBuilder.test.ts`
|
||||
- `assistantDeepTurnCompositionRuntimeAdapter.test.ts`
|
||||
- `assistantDeepTurnGuardRuntimeAdapter.test.ts`
|
||||
- `assistantDeepTurnRetrievalRuntimeAdapter.test.ts`
|
||||
- `assistantDeepTurnPlanRuntimeAdapter.test.ts`
|
||||
- `assistantDeepTurnContextRuntimeAdapter.test.ts`
|
||||
- `assistantDeepTurnGroundingRuntimeAdapter.test.ts`
|
||||
- `assistantDeepTurnPackagingRuntimeAdapter.test.ts`
|
||||
- `assistantDeepTurnFinalizeRuntimeAdapter.test.ts`
|
||||
3. Additional safety regressions passed:
|
||||
- `assistantWave10SettlementCorrectiveRegression.test.ts`
|
||||
- `assistantMcpRuntimeBridge.test.ts`
|
||||
- `assistantAddressFollowupContext.test.ts`
|
||||
|
||||
Implemented in current pass (Phase 2.25):
|
||||
1. Extracted address-lane finalization/response tail from `assistantService` into dedicated runtime adapter:
|
||||
- `assistantAddressTurnFinalizeRuntimeAdapter.ts`
|
||||
- introduced:
|
||||
- `finalizeAssistantAddressTurn(...)`
|
||||
2. Centralized address finalization runtime sequence (behavior-preserving):
|
||||
- assistant item creation for address lane;
|
||||
- structured `assistant_message_address` processed-event payload build;
|
||||
- turn commit/persist/log via shared commit runtime adapter;
|
||||
- API success response assembly from committed conversation state.
|
||||
3. Rewired `assistantService` address-lane finalize path to consume adapter output (behavior-preserving).
|
||||
4. Added focused unit tests:
|
||||
- `assistantAddressTurnFinalizeRuntimeAdapter.test.ts`
|
||||
|
||||
Validation:
|
||||
1. `npm run build` passed.
|
||||
2. Targeted Stage 2 adapter/finalization pack passed:
|
||||
- `assistantAddressTurnFinalizeRuntimeAdapter.test.ts`
|
||||
- `assistantDeepTurnFinalizeRuntimeAdapter.test.ts`
|
||||
- `assistantTurnCommitRuntimeAdapter.test.ts`
|
||||
3. Additional safety regressions passed:
|
||||
- `assistantWave10SettlementCorrectiveRegression.test.ts`
|
||||
- `assistantMcpRuntimeBridge.test.ts`
|
||||
- `assistantAddressFollowupContext.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 completed)**
|
||||
|
||||
## Stage 3 (P2): Hybrid Semantic Layer (LLM + Deterministic Guards)
|
||||
|
||||
|
|
|
|||
118
llm_normalizer/backend/dist/services/assistantAddressTurnFinalizeRuntimeAdapter.js
vendored
Normal file
118
llm_normalizer/backend/dist/services/assistantAddressTurnFinalizeRuntimeAdapter.js
vendored
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.finalizeAssistantAddressTurn = finalizeAssistantAddressTurn;
|
||||
const nanoid_1 = require("nanoid");
|
||||
const assistantTurnCommitRuntimeAdapter_1 = require("./assistantTurnCommitRuntimeAdapter");
|
||||
function toTraceId(debug) {
|
||||
const value = debug?.trace_id;
|
||||
if (typeof value !== "string") {
|
||||
return null;
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
return trimmed.length > 0 ? trimmed : null;
|
||||
}
|
||||
function buildAddressProcessedLogDetails(input, assistantItem) {
|
||||
const laneDebug = input.addressLaneDebug;
|
||||
const llmMeta = input.llmPreDecomposeMeta;
|
||||
const carryover = input.carryoverMeta;
|
||||
return {
|
||||
session_id: input.sessionId,
|
||||
message_id: assistantItem.message_id,
|
||||
user_message: input.userMessage,
|
||||
effective_address_user_message: input.effectiveAddressUserMessage,
|
||||
address_followup_context_applied: Boolean(carryover),
|
||||
address_followup_context_previous_intent: carryover?.previousAddressIntent ?? null,
|
||||
address_followup_context_previous_anchor: carryover?.previousAddressAnchor ?? null,
|
||||
address_llm_predecompose_attempted: Boolean(llmMeta?.attempted),
|
||||
address_llm_predecompose_applied: Boolean(llmMeta?.applied),
|
||||
address_llm_predecompose_provider: llmMeta?.provider ?? null,
|
||||
address_llm_predecompose_trace_id: llmMeta?.traceId ?? null,
|
||||
address_llm_predecompose_reason: llmMeta?.reason ?? null,
|
||||
address_fallback_rule_hit: llmMeta?.fallbackRuleHit ?? null,
|
||||
address_sanitized_user_message: llmMeta?.sanitizedUserMessage ?? null,
|
||||
address_tool_gate_decision: llmMeta?.toolGateDecision ?? null,
|
||||
address_tool_gate_reason: llmMeta?.toolGateReason ?? null,
|
||||
address_dialog_continuation_decision: llmMeta?.dialogContinuationContract?.decision ?? null,
|
||||
address_dialog_continuation_target_intent: llmMeta?.dialogContinuationContract?.target_intent ?? null,
|
||||
address_retry_attempted: Boolean(llmMeta?.addressRetryAudit?.attempted),
|
||||
address_retry_reason: llmMeta?.addressRetryAudit?.reason ?? null,
|
||||
address_retry_initial_limited_category: llmMeta?.addressRetryAudit?.initial_limited_category ?? null,
|
||||
address_retry_result_category: llmMeta?.addressRetryAudit?.retry_result_category ?? null,
|
||||
address_llm_predecompose_contract_intent: llmMeta?.predecomposeContract?.intent ?? null,
|
||||
address_llm_predecompose_contract_aggregation_profile: llmMeta?.predecomposeContract?.aggregation_profile ?? null,
|
||||
address_llm_predecompose_contract_period_scope: llmMeta?.predecomposeContract?.period?.scope ?? null,
|
||||
detected_mode: laneDebug.detected_mode,
|
||||
query_shape: laneDebug.query_shape,
|
||||
detected_intent: laneDebug.detected_intent,
|
||||
extracted_filters: laneDebug.extracted_filters,
|
||||
selected_recipe: laneDebug.selected_recipe,
|
||||
mcp_call_status_legacy: laneDebug.mcp_call_status_legacy,
|
||||
account_scope_mode: laneDebug.account_scope_mode,
|
||||
account_scope_fallback_applied: laneDebug.account_scope_fallback_applied,
|
||||
anchor_type: laneDebug.anchor_type,
|
||||
resolver_confidence: laneDebug.resolver_confidence,
|
||||
match_failure_stage: laneDebug.match_failure_stage,
|
||||
match_failure_reason: laneDebug.match_failure_reason,
|
||||
mcp_call_status: laneDebug.mcp_call_status,
|
||||
rows_fetched: laneDebug.rows_fetched,
|
||||
raw_rows_received: laneDebug.raw_rows_received,
|
||||
rows_after_account_scope: laneDebug.rows_after_account_scope,
|
||||
rows_after_recipe_filter: laneDebug.rows_after_recipe_filter,
|
||||
rows_materialized: laneDebug.rows_materialized,
|
||||
rows_matched: laneDebug.rows_matched,
|
||||
materialization_drop_reason: laneDebug.materialization_drop_reason,
|
||||
account_token_raw: laneDebug.account_token_raw,
|
||||
account_token_normalized: laneDebug.account_token_normalized,
|
||||
account_scope_fields_checked: laneDebug.account_scope_fields_checked,
|
||||
account_scope_match_strategy: laneDebug.account_scope_match_strategy,
|
||||
account_scope_drop_reason: laneDebug.account_scope_drop_reason,
|
||||
runtime_readiness: laneDebug.runtime_readiness,
|
||||
limited_reason_category: laneDebug.limited_reason_category,
|
||||
response_type: laneDebug.response_type,
|
||||
limitations: laneDebug.limitations,
|
||||
assistant_reply: assistantItem.text,
|
||||
reply_type: assistantItem.reply_type,
|
||||
trace_id: assistantItem.trace_id
|
||||
};
|
||||
}
|
||||
function finalizeAssistantAddressTurn(input) {
|
||||
const nowIso = input.nowIso ?? (() => new Date().toISOString());
|
||||
const messageIdFactory = input.messageIdFactory ?? (() => `msg-${(0, nanoid_1.nanoid)(10)}`);
|
||||
const commitSafe = input.commitFn ?? assistantTurnCommitRuntimeAdapter_1.commitAssistantTurnAndLog;
|
||||
const assistantItem = {
|
||||
message_id: messageIdFactory(),
|
||||
session_id: input.sessionId,
|
||||
role: "assistant",
|
||||
text: input.assistantReply,
|
||||
reply_type: input.replyType,
|
||||
created_at: nowIso(),
|
||||
trace_id: toTraceId(input.debug),
|
||||
debug: input.debug
|
||||
};
|
||||
const logDetails = buildAddressProcessedLogDetails(input, assistantItem);
|
||||
const commitResult = commitSafe({
|
||||
sessionId: input.sessionId,
|
||||
assistantItem,
|
||||
eventType: "assistant_message_address",
|
||||
logDetails,
|
||||
appendItem: input.appendItem,
|
||||
getSession: input.getSession,
|
||||
persistSession: input.persistSession,
|
||||
cloneConversation: input.cloneConversation,
|
||||
logEvent: input.logEvent
|
||||
});
|
||||
const response = {
|
||||
ok: true,
|
||||
session_id: input.sessionId,
|
||||
assistant_reply: assistantItem.text,
|
||||
reply_type: assistantItem.reply_type,
|
||||
conversation_item: assistantItem,
|
||||
debug: input.debug,
|
||||
conversation: commitResult.conversation
|
||||
};
|
||||
return {
|
||||
assistantItem,
|
||||
commitResult,
|
||||
response
|
||||
};
|
||||
}
|
||||
32
llm_normalizer/backend/dist/services/assistantDeepTurnFinalizeRuntimeAdapter.js
vendored
Normal file
32
llm_normalizer/backend/dist/services/assistantDeepTurnFinalizeRuntimeAdapter.js
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.finalizeAssistantDeepTurn = finalizeAssistantDeepTurn;
|
||||
const assistantDeepTurnResponseBuilder_1 = require("./assistantDeepTurnResponseBuilder");
|
||||
const assistantTurnCommitRuntimeAdapter_1 = require("./assistantTurnCommitRuntimeAdapter");
|
||||
function finalizeAssistantDeepTurn(input) {
|
||||
const commitSafe = input.commitFn ?? assistantTurnCommitRuntimeAdapter_1.commitAssistantTurnAndLog;
|
||||
const buildResponseSafe = input.buildResponseFn ?? assistantDeepTurnResponseBuilder_1.buildAssistantDeepTurnSuccessResponse;
|
||||
const commitResult = commitSafe({
|
||||
sessionId: input.sessionId,
|
||||
assistantItem: input.assistantItem,
|
||||
eventType: "assistant_message",
|
||||
logDetails: input.deepAnalysisLogDetails,
|
||||
appendItem: input.appendItem,
|
||||
getSession: input.getSession,
|
||||
persistSession: input.persistSession,
|
||||
cloneConversation: input.cloneConversation,
|
||||
logEvent: input.logEvent
|
||||
});
|
||||
const response = buildResponseSafe({
|
||||
sessionId: input.sessionId,
|
||||
assistantReply: input.assistantReply,
|
||||
replyType: input.replyType,
|
||||
conversationItem: input.assistantItem,
|
||||
debug: input.debug,
|
||||
conversation: commitResult.conversation
|
||||
});
|
||||
return {
|
||||
commitResult,
|
||||
response
|
||||
};
|
||||
}
|
||||
|
|
@ -65,17 +65,17 @@ const openaiResponsesClient_1 = __importStar(require("./openaiResponsesClient"))
|
|||
const addressMcpClient_1 = __importStar(require("./addressMcpClient"));
|
||||
const capabilitiesRegistry_1 = __importStar(require("./capabilitiesRegistry"));
|
||||
const assistantCanon_1 = __importStar(require("./assistantCanon"));
|
||||
const assistantAddressTurnFinalizeRuntimeAdapter_1 = __importStar(require("./assistantAddressTurnFinalizeRuntimeAdapter"));
|
||||
const assistantCoverageGrounding_1 = __importStar(require("./assistantCoverageGrounding"));
|
||||
const assistantDeepTurnResponseBuilder_1 = __importStar(require("./assistantDeepTurnResponseBuilder"));
|
||||
const assistantDeepTurnCompositionRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnCompositionRuntimeAdapter"));
|
||||
const assistantDeepTurnContextRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnContextRuntimeAdapter"));
|
||||
const assistantDeepTurnFinalizeRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnFinalizeRuntimeAdapter"));
|
||||
const assistantDeepTurnGuardRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnGuardRuntimeAdapter"));
|
||||
const assistantDeepTurnGroundingRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnGroundingRuntimeAdapter"));
|
||||
const assistantDeepTurnPackagingRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnPackagingRuntimeAdapter"));
|
||||
const assistantDeepTurnPlanRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnPlanRuntimeAdapter"));
|
||||
const assistantDeepTurnRetrievalRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnRetrievalRuntimeAdapter"));
|
||||
const assistantQueryPlanning_1 = __importStar(require("./assistantQueryPlanning"));
|
||||
const assistantTurnCommitRuntimeAdapter_1 = __importStar(require("./assistantTurnCommitRuntimeAdapter"));
|
||||
const iconv_lite_1 = __importDefault(require("iconv-lite"));
|
||||
const DATA_SCOPE_CACHE_TTL_MS = 60_000;
|
||||
const dataScopeProbeCache = new Map();
|
||||
|
|
@ -4439,98 +4439,24 @@ class AssistantService {
|
|||
if (debugActiveOrganization) {
|
||||
debug.assistant_active_organization = debugActiveOrganization;
|
||||
}
|
||||
const assistantItem = {
|
||||
message_id: `msg-${(0, nanoid_1.nanoid)(10)}`,
|
||||
session_id: sessionId,
|
||||
role: "assistant",
|
||||
text: safeAddressReply,
|
||||
reply_type: addressLane.reply_type,
|
||||
created_at: new Date().toISOString(),
|
||||
trace_id: debug.trace_id,
|
||||
debug
|
||||
};
|
||||
this.sessions.appendItem(sessionId, assistantItem);
|
||||
const current = this.sessions.getSession(sessionId);
|
||||
if (current) {
|
||||
this.sessionLogger.persistSession(current);
|
||||
}
|
||||
const conversation = cloneItems(current?.items ?? []);
|
||||
(0, log_1.logJson)({
|
||||
timestamp: new Date().toISOString(),
|
||||
level: "info",
|
||||
service: "assistant_loop",
|
||||
message: "assistant_message_processed",
|
||||
const finalization = (0, assistantAddressTurnFinalizeRuntimeAdapter_1.finalizeAssistantAddressTurn)({
|
||||
sessionId,
|
||||
eventType: "assistant_message_address",
|
||||
details: {
|
||||
session_id: sessionId,
|
||||
message_id: assistantItem.message_id,
|
||||
user_message: userMessage,
|
||||
effective_address_user_message: effectiveAddressUserMessage,
|
||||
address_followup_context_applied: Boolean(carryoverMeta),
|
||||
address_followup_context_previous_intent: carryoverMeta?.previousAddressIntent ?? null,
|
||||
address_followup_context_previous_anchor: carryoverMeta?.previousAddressAnchor ?? null,
|
||||
address_llm_predecompose_attempted: Boolean(llmPreDecomposeMeta?.attempted),
|
||||
address_llm_predecompose_applied: Boolean(llmPreDecomposeMeta?.applied),
|
||||
address_llm_predecompose_provider: llmPreDecomposeMeta?.provider ?? null,
|
||||
address_llm_predecompose_trace_id: llmPreDecomposeMeta?.traceId ?? null,
|
||||
address_llm_predecompose_reason: llmPreDecomposeMeta?.reason ?? null,
|
||||
address_fallback_rule_hit: llmPreDecomposeMeta?.fallbackRuleHit ?? null,
|
||||
address_sanitized_user_message: llmPreDecomposeMeta?.sanitizedUserMessage ?? null,
|
||||
address_tool_gate_decision: llmPreDecomposeMeta?.toolGateDecision ?? null,
|
||||
address_tool_gate_reason: llmPreDecomposeMeta?.toolGateReason ?? null,
|
||||
address_dialog_continuation_decision: llmPreDecomposeMeta?.dialogContinuationContract?.decision ?? null,
|
||||
address_dialog_continuation_target_intent: llmPreDecomposeMeta?.dialogContinuationContract?.target_intent ?? null,
|
||||
address_retry_attempted: Boolean(llmPreDecomposeMeta?.addressRetryAudit?.attempted),
|
||||
address_retry_reason: llmPreDecomposeMeta?.addressRetryAudit?.reason ?? null,
|
||||
address_retry_initial_limited_category: llmPreDecomposeMeta?.addressRetryAudit?.initial_limited_category ?? null,
|
||||
address_retry_result_category: llmPreDecomposeMeta?.addressRetryAudit?.retry_result_category ?? null,
|
||||
address_llm_predecompose_contract_intent: llmPreDecomposeMeta?.predecomposeContract?.intent ?? null,
|
||||
address_llm_predecompose_contract_aggregation_profile: llmPreDecomposeMeta?.predecomposeContract?.aggregation_profile ?? null,
|
||||
address_llm_predecompose_contract_period_scope: llmPreDecomposeMeta?.predecomposeContract?.period?.scope ?? null,
|
||||
detected_mode: addressLane.debug.detected_mode,
|
||||
query_shape: addressLane.debug.query_shape,
|
||||
detected_intent: addressLane.debug.detected_intent,
|
||||
extracted_filters: addressLane.debug.extracted_filters,
|
||||
selected_recipe: addressLane.debug.selected_recipe,
|
||||
mcp_call_status_legacy: addressLane.debug.mcp_call_status_legacy,
|
||||
account_scope_mode: addressLane.debug.account_scope_mode,
|
||||
account_scope_fallback_applied: addressLane.debug.account_scope_fallback_applied,
|
||||
anchor_type: addressLane.debug.anchor_type,
|
||||
resolver_confidence: addressLane.debug.resolver_confidence,
|
||||
match_failure_stage: addressLane.debug.match_failure_stage,
|
||||
match_failure_reason: addressLane.debug.match_failure_reason,
|
||||
mcp_call_status: addressLane.debug.mcp_call_status,
|
||||
rows_fetched: addressLane.debug.rows_fetched,
|
||||
raw_rows_received: addressLane.debug.raw_rows_received,
|
||||
rows_after_account_scope: addressLane.debug.rows_after_account_scope,
|
||||
rows_after_recipe_filter: addressLane.debug.rows_after_recipe_filter,
|
||||
rows_materialized: addressLane.debug.rows_materialized,
|
||||
rows_matched: addressLane.debug.rows_matched,
|
||||
materialization_drop_reason: addressLane.debug.materialization_drop_reason,
|
||||
account_token_raw: addressLane.debug.account_token_raw,
|
||||
account_token_normalized: addressLane.debug.account_token_normalized,
|
||||
account_scope_fields_checked: addressLane.debug.account_scope_fields_checked,
|
||||
account_scope_match_strategy: addressLane.debug.account_scope_match_strategy,
|
||||
account_scope_drop_reason: addressLane.debug.account_scope_drop_reason,
|
||||
runtime_readiness: addressLane.debug.runtime_readiness,
|
||||
limited_reason_category: addressLane.debug.limited_reason_category,
|
||||
response_type: addressLane.debug.response_type,
|
||||
limitations: addressLane.debug.limitations,
|
||||
assistant_reply: assistantItem.text,
|
||||
reply_type: assistantItem.reply_type,
|
||||
trace_id: assistantItem.trace_id
|
||||
}
|
||||
});
|
||||
return {
|
||||
ok: true,
|
||||
session_id: sessionId,
|
||||
assistant_reply: assistantItem.text,
|
||||
reply_type: assistantItem.reply_type,
|
||||
conversation_item: assistantItem,
|
||||
userMessage,
|
||||
effectiveAddressUserMessage,
|
||||
assistantReply: safeAddressReply,
|
||||
replyType: addressLane.reply_type,
|
||||
addressLaneDebug: addressLane.debug,
|
||||
debug,
|
||||
conversation
|
||||
};
|
||||
carryoverMeta,
|
||||
llmPreDecomposeMeta,
|
||||
appendItem: (targetSessionId, item) => this.sessions.appendItem(targetSessionId, item),
|
||||
getSession: (targetSessionId) => this.sessions.getSession(targetSessionId),
|
||||
persistSession: (sessionState) => this.sessionLogger.persistSession(sessionState),
|
||||
cloneConversation: (items) => cloneItems(items),
|
||||
logEvent: (payload) => (0, log_1.logJson)(payload),
|
||||
messageIdFactory: () => `msg-${(0, nanoid_1.nanoid)(10)}`
|
||||
});
|
||||
return finalization.response;
|
||||
};
|
||||
const tryHandleLivingChat = async (modeDecision, addressRuntimeMeta = null) => {
|
||||
try {
|
||||
|
|
@ -5098,26 +5024,20 @@ class AssistantService {
|
|||
const debug = packagingRuntime.debug;
|
||||
const assistantItem = packagingRuntime.assistantItem;
|
||||
const deepAnalysisLogDetails = packagingRuntime.deepAnalysisLogDetails;
|
||||
const commitResult = (0, assistantTurnCommitRuntimeAdapter_1.commitAssistantTurnAndLog)({
|
||||
const finalization = (0, assistantDeepTurnFinalizeRuntimeAdapter_1.finalizeAssistantDeepTurn)({
|
||||
sessionId,
|
||||
assistantReply: safeAssistantReply,
|
||||
replyType: composition.reply_type,
|
||||
assistantItem,
|
||||
eventType: "assistant_message",
|
||||
logDetails: deepAnalysisLogDetails,
|
||||
debug,
|
||||
deepAnalysisLogDetails,
|
||||
appendItem: (targetSessionId, item) => this.sessions.appendItem(targetSessionId, item),
|
||||
getSession: (targetSessionId) => this.sessions.getSession(targetSessionId),
|
||||
persistSession: (sessionState) => this.sessionLogger.persistSession(sessionState),
|
||||
cloneConversation: (items) => cloneItems(items),
|
||||
logEvent: (payload) => (0, log_1.logJson)(payload)
|
||||
});
|
||||
const conversation = commitResult.conversation;
|
||||
return (0, assistantDeepTurnResponseBuilder_1.buildAssistantDeepTurnSuccessResponse)({
|
||||
sessionId,
|
||||
assistantReply: safeAssistantReply,
|
||||
replyType: composition.reply_type,
|
||||
conversationItem: assistantItem,
|
||||
debug,
|
||||
conversation
|
||||
});
|
||||
return finalization.response;
|
||||
}
|
||||
}
|
||||
exports.AssistantService = AssistantService;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,190 @@
|
|||
import { nanoid } from "nanoid";
|
||||
import type {
|
||||
AssistantConversationItem,
|
||||
AssistantDebugPayload,
|
||||
AssistantMessageResponsePayload,
|
||||
AssistantReplyType
|
||||
} from "../types/assistant";
|
||||
import type { AddressExecutionDebug } from "../types/addressQuery";
|
||||
import type { CommitAssistantTurnAndLogOutput } from "./assistantTurnCommitRuntimeAdapter";
|
||||
import { commitAssistantTurnAndLog } from "./assistantTurnCommitRuntimeAdapter";
|
||||
|
||||
export interface AddressCarryoverMetaLogInput {
|
||||
previousAddressIntent?: string | null;
|
||||
previousAddressAnchor?: string | null;
|
||||
}
|
||||
|
||||
export interface AddressLlmPreDecomposeMetaLogInput {
|
||||
attempted?: boolean;
|
||||
applied?: boolean;
|
||||
provider?: string | null;
|
||||
traceId?: string | null;
|
||||
reason?: string | null;
|
||||
fallbackRuleHit?: string | null;
|
||||
sanitizedUserMessage?: string | null;
|
||||
toolGateDecision?: string | null;
|
||||
toolGateReason?: string | null;
|
||||
dialogContinuationContract?: {
|
||||
decision?: string | null;
|
||||
target_intent?: string | null;
|
||||
} | null;
|
||||
addressRetryAudit?: {
|
||||
attempted?: boolean;
|
||||
reason?: string | null;
|
||||
initial_limited_category?: string | null;
|
||||
retry_result_category?: string | null;
|
||||
} | null;
|
||||
predecomposeContract?: {
|
||||
intent?: string | null;
|
||||
aggregation_profile?: string | null;
|
||||
period?: {
|
||||
scope?: string | null;
|
||||
} | null;
|
||||
} | null;
|
||||
}
|
||||
|
||||
export interface FinalizeAssistantAddressTurnInput {
|
||||
sessionId: string;
|
||||
userMessage: string;
|
||||
effectiveAddressUserMessage: string;
|
||||
assistantReply: string;
|
||||
replyType: AssistantReplyType;
|
||||
addressLaneDebug: AddressExecutionDebug;
|
||||
debug: AssistantDebugPayload | Record<string, unknown>;
|
||||
carryoverMeta?: AddressCarryoverMetaLogInput | null;
|
||||
llmPreDecomposeMeta?: AddressLlmPreDecomposeMetaLogInput | null;
|
||||
appendItem: Parameters<typeof commitAssistantTurnAndLog>[0]["appendItem"];
|
||||
getSession: Parameters<typeof commitAssistantTurnAndLog>[0]["getSession"];
|
||||
persistSession: Parameters<typeof commitAssistantTurnAndLog>[0]["persistSession"];
|
||||
cloneConversation: Parameters<typeof commitAssistantTurnAndLog>[0]["cloneConversation"];
|
||||
logEvent: Parameters<typeof commitAssistantTurnAndLog>[0]["logEvent"];
|
||||
nowIso?: () => string;
|
||||
messageIdFactory?: () => string;
|
||||
commitFn?: typeof commitAssistantTurnAndLog;
|
||||
}
|
||||
|
||||
export interface FinalizeAssistantAddressTurnOutput {
|
||||
assistantItem: AssistantConversationItem;
|
||||
commitResult: CommitAssistantTurnAndLogOutput;
|
||||
response: AssistantMessageResponsePayload;
|
||||
}
|
||||
|
||||
function toTraceId(debug: AssistantDebugPayload | Record<string, unknown>): string | null {
|
||||
const value = (debug as Record<string, unknown> | null | undefined)?.trace_id;
|
||||
if (typeof value !== "string") {
|
||||
return null;
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
return trimmed.length > 0 ? trimmed : null;
|
||||
}
|
||||
|
||||
function buildAddressProcessedLogDetails(input: FinalizeAssistantAddressTurnInput, assistantItem: AssistantConversationItem) {
|
||||
const laneDebug = input.addressLaneDebug;
|
||||
const llmMeta = input.llmPreDecomposeMeta;
|
||||
const carryover = input.carryoverMeta;
|
||||
|
||||
return {
|
||||
session_id: input.sessionId,
|
||||
message_id: assistantItem.message_id,
|
||||
user_message: input.userMessage,
|
||||
effective_address_user_message: input.effectiveAddressUserMessage,
|
||||
address_followup_context_applied: Boolean(carryover),
|
||||
address_followup_context_previous_intent: carryover?.previousAddressIntent ?? null,
|
||||
address_followup_context_previous_anchor: carryover?.previousAddressAnchor ?? null,
|
||||
address_llm_predecompose_attempted: Boolean(llmMeta?.attempted),
|
||||
address_llm_predecompose_applied: Boolean(llmMeta?.applied),
|
||||
address_llm_predecompose_provider: llmMeta?.provider ?? null,
|
||||
address_llm_predecompose_trace_id: llmMeta?.traceId ?? null,
|
||||
address_llm_predecompose_reason: llmMeta?.reason ?? null,
|
||||
address_fallback_rule_hit: llmMeta?.fallbackRuleHit ?? null,
|
||||
address_sanitized_user_message: llmMeta?.sanitizedUserMessage ?? null,
|
||||
address_tool_gate_decision: llmMeta?.toolGateDecision ?? null,
|
||||
address_tool_gate_reason: llmMeta?.toolGateReason ?? null,
|
||||
address_dialog_continuation_decision: llmMeta?.dialogContinuationContract?.decision ?? null,
|
||||
address_dialog_continuation_target_intent: llmMeta?.dialogContinuationContract?.target_intent ?? null,
|
||||
address_retry_attempted: Boolean(llmMeta?.addressRetryAudit?.attempted),
|
||||
address_retry_reason: llmMeta?.addressRetryAudit?.reason ?? null,
|
||||
address_retry_initial_limited_category: llmMeta?.addressRetryAudit?.initial_limited_category ?? null,
|
||||
address_retry_result_category: llmMeta?.addressRetryAudit?.retry_result_category ?? null,
|
||||
address_llm_predecompose_contract_intent: llmMeta?.predecomposeContract?.intent ?? null,
|
||||
address_llm_predecompose_contract_aggregation_profile: llmMeta?.predecomposeContract?.aggregation_profile ?? null,
|
||||
address_llm_predecompose_contract_period_scope: llmMeta?.predecomposeContract?.period?.scope ?? null,
|
||||
detected_mode: laneDebug.detected_mode,
|
||||
query_shape: laneDebug.query_shape,
|
||||
detected_intent: laneDebug.detected_intent,
|
||||
extracted_filters: laneDebug.extracted_filters,
|
||||
selected_recipe: laneDebug.selected_recipe,
|
||||
mcp_call_status_legacy: laneDebug.mcp_call_status_legacy,
|
||||
account_scope_mode: laneDebug.account_scope_mode,
|
||||
account_scope_fallback_applied: laneDebug.account_scope_fallback_applied,
|
||||
anchor_type: laneDebug.anchor_type,
|
||||
resolver_confidence: laneDebug.resolver_confidence,
|
||||
match_failure_stage: laneDebug.match_failure_stage,
|
||||
match_failure_reason: laneDebug.match_failure_reason,
|
||||
mcp_call_status: laneDebug.mcp_call_status,
|
||||
rows_fetched: laneDebug.rows_fetched,
|
||||
raw_rows_received: laneDebug.raw_rows_received,
|
||||
rows_after_account_scope: laneDebug.rows_after_account_scope,
|
||||
rows_after_recipe_filter: laneDebug.rows_after_recipe_filter,
|
||||
rows_materialized: laneDebug.rows_materialized,
|
||||
rows_matched: laneDebug.rows_matched,
|
||||
materialization_drop_reason: laneDebug.materialization_drop_reason,
|
||||
account_token_raw: laneDebug.account_token_raw,
|
||||
account_token_normalized: laneDebug.account_token_normalized,
|
||||
account_scope_fields_checked: laneDebug.account_scope_fields_checked,
|
||||
account_scope_match_strategy: laneDebug.account_scope_match_strategy,
|
||||
account_scope_drop_reason: laneDebug.account_scope_drop_reason,
|
||||
runtime_readiness: laneDebug.runtime_readiness,
|
||||
limited_reason_category: laneDebug.limited_reason_category,
|
||||
response_type: laneDebug.response_type,
|
||||
limitations: laneDebug.limitations,
|
||||
assistant_reply: assistantItem.text,
|
||||
reply_type: assistantItem.reply_type,
|
||||
trace_id: assistantItem.trace_id
|
||||
};
|
||||
}
|
||||
|
||||
export function finalizeAssistantAddressTurn(
|
||||
input: FinalizeAssistantAddressTurnInput
|
||||
): FinalizeAssistantAddressTurnOutput {
|
||||
const nowIso = input.nowIso ?? (() => new Date().toISOString());
|
||||
const messageIdFactory = input.messageIdFactory ?? (() => `msg-${nanoid(10)}`);
|
||||
const commitSafe = input.commitFn ?? commitAssistantTurnAndLog;
|
||||
const assistantItem: AssistantConversationItem = {
|
||||
message_id: messageIdFactory(),
|
||||
session_id: input.sessionId,
|
||||
role: "assistant",
|
||||
text: input.assistantReply,
|
||||
reply_type: input.replyType,
|
||||
created_at: nowIso(),
|
||||
trace_id: toTraceId(input.debug),
|
||||
debug: input.debug as AssistantDebugPayload
|
||||
};
|
||||
const logDetails = buildAddressProcessedLogDetails(input, assistantItem);
|
||||
const commitResult = commitSafe({
|
||||
sessionId: input.sessionId,
|
||||
assistantItem,
|
||||
eventType: "assistant_message_address",
|
||||
logDetails,
|
||||
appendItem: input.appendItem,
|
||||
getSession: input.getSession,
|
||||
persistSession: input.persistSession,
|
||||
cloneConversation: input.cloneConversation,
|
||||
logEvent: input.logEvent
|
||||
});
|
||||
const response: AssistantMessageResponsePayload = {
|
||||
ok: true,
|
||||
session_id: input.sessionId,
|
||||
assistant_reply: assistantItem.text,
|
||||
reply_type: assistantItem.reply_type as AssistantReplyType,
|
||||
conversation_item: assistantItem,
|
||||
debug: input.debug as AssistantDebugPayload,
|
||||
conversation: commitResult.conversation
|
||||
};
|
||||
|
||||
return {
|
||||
assistantItem,
|
||||
commitResult,
|
||||
response
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
import type { AssistantConversationItem, AssistantDebugPayload, AssistantMessageResponsePayload, AssistantReplyType } from "../types/assistant";
|
||||
import { buildAssistantDeepTurnSuccessResponse } from "./assistantDeepTurnResponseBuilder";
|
||||
import type { CommitAssistantTurnAndLogOutput } from "./assistantTurnCommitRuntimeAdapter";
|
||||
import { commitAssistantTurnAndLog } from "./assistantTurnCommitRuntimeAdapter";
|
||||
|
||||
export interface FinalizeAssistantDeepTurnInput {
|
||||
sessionId: string;
|
||||
assistantReply: string;
|
||||
replyType: AssistantReplyType;
|
||||
assistantItem: AssistantConversationItem;
|
||||
debug: AssistantDebugPayload | Record<string, unknown>;
|
||||
deepAnalysisLogDetails: Record<string, unknown>;
|
||||
appendItem: Parameters<typeof commitAssistantTurnAndLog>[0]["appendItem"];
|
||||
getSession: Parameters<typeof commitAssistantTurnAndLog>[0]["getSession"];
|
||||
persistSession: Parameters<typeof commitAssistantTurnAndLog>[0]["persistSession"];
|
||||
cloneConversation: Parameters<typeof commitAssistantTurnAndLog>[0]["cloneConversation"];
|
||||
logEvent: Parameters<typeof commitAssistantTurnAndLog>[0]["logEvent"];
|
||||
commitFn?: typeof commitAssistantTurnAndLog;
|
||||
buildResponseFn?: typeof buildAssistantDeepTurnSuccessResponse;
|
||||
}
|
||||
|
||||
export interface FinalizeAssistantDeepTurnOutput {
|
||||
commitResult: CommitAssistantTurnAndLogOutput;
|
||||
response: AssistantMessageResponsePayload;
|
||||
}
|
||||
|
||||
export function finalizeAssistantDeepTurn(
|
||||
input: FinalizeAssistantDeepTurnInput
|
||||
): FinalizeAssistantDeepTurnOutput {
|
||||
const commitSafe = input.commitFn ?? commitAssistantTurnAndLog;
|
||||
const buildResponseSafe = input.buildResponseFn ?? buildAssistantDeepTurnSuccessResponse;
|
||||
|
||||
const commitResult = commitSafe({
|
||||
sessionId: input.sessionId,
|
||||
assistantItem: input.assistantItem,
|
||||
eventType: "assistant_message",
|
||||
logDetails: input.deepAnalysisLogDetails,
|
||||
appendItem: input.appendItem,
|
||||
getSession: input.getSession,
|
||||
persistSession: input.persistSession,
|
||||
cloneConversation: input.cloneConversation,
|
||||
logEvent: input.logEvent
|
||||
});
|
||||
const response = buildResponseSafe({
|
||||
sessionId: input.sessionId,
|
||||
assistantReply: input.assistantReply,
|
||||
replyType: input.replyType,
|
||||
conversationItem: input.assistantItem,
|
||||
debug: input.debug,
|
||||
conversation: commitResult.conversation
|
||||
});
|
||||
|
||||
return {
|
||||
commitResult,
|
||||
response
|
||||
};
|
||||
}
|
||||
|
|
@ -19,17 +19,17 @@ import * as openaiResponsesClient_1 from "./openaiResponsesClient";
|
|||
import * as addressMcpClient_1 from "./addressMcpClient";
|
||||
import * as capabilitiesRegistry_1 from "./capabilitiesRegistry";
|
||||
import * as assistantCanon_1 from "./assistantCanon";
|
||||
import * as assistantAddressTurnFinalizeRuntimeAdapter_1 from "./assistantAddressTurnFinalizeRuntimeAdapter";
|
||||
import * as assistantCoverageGrounding_1 from "./assistantCoverageGrounding";
|
||||
import * as assistantDeepTurnResponseBuilder_1 from "./assistantDeepTurnResponseBuilder";
|
||||
import * as assistantDeepTurnCompositionRuntimeAdapter_1 from "./assistantDeepTurnCompositionRuntimeAdapter";
|
||||
import * as assistantDeepTurnContextRuntimeAdapter_1 from "./assistantDeepTurnContextRuntimeAdapter";
|
||||
import * as assistantDeepTurnFinalizeRuntimeAdapter_1 from "./assistantDeepTurnFinalizeRuntimeAdapter";
|
||||
import * as assistantDeepTurnGuardRuntimeAdapter_1 from "./assistantDeepTurnGuardRuntimeAdapter";
|
||||
import * as assistantDeepTurnGroundingRuntimeAdapter_1 from "./assistantDeepTurnGroundingRuntimeAdapter";
|
||||
import * as assistantDeepTurnPackagingRuntimeAdapter_1 from "./assistantDeepTurnPackagingRuntimeAdapter";
|
||||
import * as assistantDeepTurnPlanRuntimeAdapter_1 from "./assistantDeepTurnPlanRuntimeAdapter";
|
||||
import * as assistantDeepTurnRetrievalRuntimeAdapter_1 from "./assistantDeepTurnRetrievalRuntimeAdapter";
|
||||
import * as assistantQueryPlanning_1 from "./assistantQueryPlanning";
|
||||
import * as assistantTurnCommitRuntimeAdapter_1 from "./assistantTurnCommitRuntimeAdapter";
|
||||
import iconv from "iconv-lite";
|
||||
const DATA_SCOPE_CACHE_TTL_MS = 60_000;
|
||||
const dataScopeProbeCache = new Map();
|
||||
|
|
@ -4394,98 +4394,24 @@ export class AssistantService {
|
|||
if (debugActiveOrganization) {
|
||||
debug.assistant_active_organization = debugActiveOrganization;
|
||||
}
|
||||
const assistantItem = {
|
||||
message_id: `msg-${(0, nanoid_1.nanoid)(10)}`,
|
||||
session_id: sessionId,
|
||||
role: "assistant",
|
||||
text: safeAddressReply,
|
||||
reply_type: addressLane.reply_type,
|
||||
created_at: new Date().toISOString(),
|
||||
trace_id: debug.trace_id,
|
||||
debug
|
||||
};
|
||||
this.sessions.appendItem(sessionId, assistantItem);
|
||||
const current = this.sessions.getSession(sessionId);
|
||||
if (current) {
|
||||
this.sessionLogger.persistSession(current);
|
||||
}
|
||||
const conversation = cloneItems(current?.items ?? []);
|
||||
(0, log_1.logJson)({
|
||||
timestamp: new Date().toISOString(),
|
||||
level: "info",
|
||||
service: "assistant_loop",
|
||||
message: "assistant_message_processed",
|
||||
const finalization = (0, assistantAddressTurnFinalizeRuntimeAdapter_1.finalizeAssistantAddressTurn)({
|
||||
sessionId,
|
||||
eventType: "assistant_message_address",
|
||||
details: {
|
||||
session_id: sessionId,
|
||||
message_id: assistantItem.message_id,
|
||||
user_message: userMessage,
|
||||
effective_address_user_message: effectiveAddressUserMessage,
|
||||
address_followup_context_applied: Boolean(carryoverMeta),
|
||||
address_followup_context_previous_intent: carryoverMeta?.previousAddressIntent ?? null,
|
||||
address_followup_context_previous_anchor: carryoverMeta?.previousAddressAnchor ?? null,
|
||||
address_llm_predecompose_attempted: Boolean(llmPreDecomposeMeta?.attempted),
|
||||
address_llm_predecompose_applied: Boolean(llmPreDecomposeMeta?.applied),
|
||||
address_llm_predecompose_provider: llmPreDecomposeMeta?.provider ?? null,
|
||||
address_llm_predecompose_trace_id: llmPreDecomposeMeta?.traceId ?? null,
|
||||
address_llm_predecompose_reason: llmPreDecomposeMeta?.reason ?? null,
|
||||
address_fallback_rule_hit: llmPreDecomposeMeta?.fallbackRuleHit ?? null,
|
||||
address_sanitized_user_message: llmPreDecomposeMeta?.sanitizedUserMessage ?? null,
|
||||
address_tool_gate_decision: llmPreDecomposeMeta?.toolGateDecision ?? null,
|
||||
address_tool_gate_reason: llmPreDecomposeMeta?.toolGateReason ?? null,
|
||||
address_dialog_continuation_decision: llmPreDecomposeMeta?.dialogContinuationContract?.decision ?? null,
|
||||
address_dialog_continuation_target_intent: llmPreDecomposeMeta?.dialogContinuationContract?.target_intent ?? null,
|
||||
address_retry_attempted: Boolean(llmPreDecomposeMeta?.addressRetryAudit?.attempted),
|
||||
address_retry_reason: llmPreDecomposeMeta?.addressRetryAudit?.reason ?? null,
|
||||
address_retry_initial_limited_category: llmPreDecomposeMeta?.addressRetryAudit?.initial_limited_category ?? null,
|
||||
address_retry_result_category: llmPreDecomposeMeta?.addressRetryAudit?.retry_result_category ?? null,
|
||||
address_llm_predecompose_contract_intent: llmPreDecomposeMeta?.predecomposeContract?.intent ?? null,
|
||||
address_llm_predecompose_contract_aggregation_profile: llmPreDecomposeMeta?.predecomposeContract?.aggregation_profile ?? null,
|
||||
address_llm_predecompose_contract_period_scope: llmPreDecomposeMeta?.predecomposeContract?.period?.scope ?? null,
|
||||
detected_mode: addressLane.debug.detected_mode,
|
||||
query_shape: addressLane.debug.query_shape,
|
||||
detected_intent: addressLane.debug.detected_intent,
|
||||
extracted_filters: addressLane.debug.extracted_filters,
|
||||
selected_recipe: addressLane.debug.selected_recipe,
|
||||
mcp_call_status_legacy: addressLane.debug.mcp_call_status_legacy,
|
||||
account_scope_mode: addressLane.debug.account_scope_mode,
|
||||
account_scope_fallback_applied: addressLane.debug.account_scope_fallback_applied,
|
||||
anchor_type: addressLane.debug.anchor_type,
|
||||
resolver_confidence: addressLane.debug.resolver_confidence,
|
||||
match_failure_stage: addressLane.debug.match_failure_stage,
|
||||
match_failure_reason: addressLane.debug.match_failure_reason,
|
||||
mcp_call_status: addressLane.debug.mcp_call_status,
|
||||
rows_fetched: addressLane.debug.rows_fetched,
|
||||
raw_rows_received: addressLane.debug.raw_rows_received,
|
||||
rows_after_account_scope: addressLane.debug.rows_after_account_scope,
|
||||
rows_after_recipe_filter: addressLane.debug.rows_after_recipe_filter,
|
||||
rows_materialized: addressLane.debug.rows_materialized,
|
||||
rows_matched: addressLane.debug.rows_matched,
|
||||
materialization_drop_reason: addressLane.debug.materialization_drop_reason,
|
||||
account_token_raw: addressLane.debug.account_token_raw,
|
||||
account_token_normalized: addressLane.debug.account_token_normalized,
|
||||
account_scope_fields_checked: addressLane.debug.account_scope_fields_checked,
|
||||
account_scope_match_strategy: addressLane.debug.account_scope_match_strategy,
|
||||
account_scope_drop_reason: addressLane.debug.account_scope_drop_reason,
|
||||
runtime_readiness: addressLane.debug.runtime_readiness,
|
||||
limited_reason_category: addressLane.debug.limited_reason_category,
|
||||
response_type: addressLane.debug.response_type,
|
||||
limitations: addressLane.debug.limitations,
|
||||
assistant_reply: assistantItem.text,
|
||||
reply_type: assistantItem.reply_type,
|
||||
trace_id: assistantItem.trace_id
|
||||
}
|
||||
});
|
||||
return {
|
||||
ok: true,
|
||||
session_id: sessionId,
|
||||
assistant_reply: assistantItem.text,
|
||||
reply_type: assistantItem.reply_type,
|
||||
conversation_item: assistantItem,
|
||||
userMessage,
|
||||
effectiveAddressUserMessage,
|
||||
assistantReply: safeAddressReply,
|
||||
replyType: addressLane.reply_type,
|
||||
addressLaneDebug: addressLane.debug,
|
||||
debug,
|
||||
conversation
|
||||
};
|
||||
carryoverMeta,
|
||||
llmPreDecomposeMeta,
|
||||
appendItem: (targetSessionId, item) => this.sessions.appendItem(targetSessionId, item),
|
||||
getSession: (targetSessionId) => this.sessions.getSession(targetSessionId),
|
||||
persistSession: (sessionState) => this.sessionLogger.persistSession(sessionState),
|
||||
cloneConversation: (items) => cloneItems(items),
|
||||
logEvent: (payload) => (0, log_1.logJson)(payload),
|
||||
messageIdFactory: () => `msg-${(0, nanoid_1.nanoid)(10)}`
|
||||
});
|
||||
return finalization.response;
|
||||
};
|
||||
const tryHandleLivingChat = async (modeDecision, addressRuntimeMeta = null) => {
|
||||
try {
|
||||
|
|
@ -5053,26 +4979,20 @@ export class AssistantService {
|
|||
const debug = packagingRuntime.debug;
|
||||
const assistantItem = packagingRuntime.assistantItem;
|
||||
const deepAnalysisLogDetails = packagingRuntime.deepAnalysisLogDetails;
|
||||
const commitResult = (0, assistantTurnCommitRuntimeAdapter_1.commitAssistantTurnAndLog)({
|
||||
const finalization = (0, assistantDeepTurnFinalizeRuntimeAdapter_1.finalizeAssistantDeepTurn)({
|
||||
sessionId,
|
||||
assistantReply: safeAssistantReply,
|
||||
replyType: composition.reply_type,
|
||||
assistantItem,
|
||||
eventType: "assistant_message",
|
||||
logDetails: deepAnalysisLogDetails,
|
||||
debug,
|
||||
deepAnalysisLogDetails,
|
||||
appendItem: (targetSessionId, item) => this.sessions.appendItem(targetSessionId, item),
|
||||
getSession: (targetSessionId) => this.sessions.getSession(targetSessionId),
|
||||
persistSession: (sessionState) => this.sessionLogger.persistSession(sessionState),
|
||||
cloneConversation: (items) => cloneItems(items),
|
||||
logEvent: (payload) => (0, log_1.logJson)(payload)
|
||||
});
|
||||
const conversation = commitResult.conversation;
|
||||
return (0, assistantDeepTurnResponseBuilder_1.buildAssistantDeepTurnSuccessResponse)({
|
||||
sessionId,
|
||||
assistantReply: safeAssistantReply,
|
||||
replyType: composition.reply_type,
|
||||
conversationItem: assistantItem,
|
||||
debug,
|
||||
conversation
|
||||
});
|
||||
return finalization.response;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,167 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { finalizeAssistantAddressTurn } from "../src/services/assistantAddressTurnFinalizeRuntimeAdapter";
|
||||
|
||||
function buildLaneDebug() {
|
||||
return {
|
||||
detected_mode: "address_query",
|
||||
detected_mode_confidence: "high",
|
||||
query_shape: "DOCUMENT_LIST",
|
||||
query_shape_confidence: "high",
|
||||
detected_intent: "list_documents_by_counterparty",
|
||||
detected_intent_confidence: "high",
|
||||
extracted_filters: { counterparty: "свк", limit: 20 },
|
||||
missing_required_filters: [],
|
||||
selected_recipe: "address_documents_by_counterparty_v1",
|
||||
mcp_call_status_legacy: "matched_non_empty",
|
||||
account_scope_mode: "preferred",
|
||||
account_scope_fallback_applied: false,
|
||||
anchor_type: "counterparty",
|
||||
anchor_value_raw: "свк",
|
||||
anchor_value_resolved: "Группа СВК",
|
||||
resolver_confidence: "medium",
|
||||
ambiguity_count: 0,
|
||||
match_failure_stage: "none",
|
||||
match_failure_reason: null,
|
||||
mcp_call_status: "matched_non_empty",
|
||||
rows_fetched: 20,
|
||||
raw_rows_received: 20,
|
||||
rows_after_account_scope: 5,
|
||||
rows_after_recipe_filter: 3,
|
||||
rows_materialized: 5,
|
||||
rows_matched: 3,
|
||||
raw_row_keys_sample: [],
|
||||
materialization_drop_reason: "none",
|
||||
account_token_raw: null,
|
||||
account_token_normalized: null,
|
||||
account_scope_fields_checked: ["account_dt", "account_kt", "registrator", "analytics"],
|
||||
account_scope_match_strategy: "account_code_regex_plus_alias_map_v1",
|
||||
account_scope_drop_reason: "not_applicable",
|
||||
runtime_readiness: "LIVE_QUERYABLE_WITH_LIMITS",
|
||||
limited_reason_category: null,
|
||||
response_type: "FACTUAL_LIST",
|
||||
limitations: [],
|
||||
reasons: []
|
||||
} as any;
|
||||
}
|
||||
|
||||
describe("assistant address turn finalize runtime adapter", () => {
|
||||
it("builds assistant item and passes expected log details into commit runtime", () => {
|
||||
const laneDebug = buildLaneDebug();
|
||||
const commitCalls: Array<Record<string, unknown>> = [];
|
||||
const output = finalizeAssistantAddressTurn({
|
||||
sessionId: "asst-1",
|
||||
userMessage: "покажи документы по свк",
|
||||
effectiveAddressUserMessage: "документы по контрагенту свк",
|
||||
assistantReply: "Собран список документов.",
|
||||
replyType: "factual",
|
||||
addressLaneDebug: laneDebug,
|
||||
debug: { trace_id: "address-trace-1", x: 1 } as any,
|
||||
carryoverMeta: {
|
||||
previousAddressIntent: "list_documents_by_counterparty",
|
||||
previousAddressAnchor: "Группа СВК"
|
||||
},
|
||||
llmPreDecomposeMeta: {
|
||||
attempted: true,
|
||||
applied: true,
|
||||
provider: "openai",
|
||||
traceId: "predec-1",
|
||||
reason: "llm_contract_ok",
|
||||
fallbackRuleHit: null,
|
||||
sanitizedUserMessage: "покажи документы по свк",
|
||||
toolGateDecision: "run_address_lane",
|
||||
toolGateReason: "address_mode_classifier_detected",
|
||||
dialogContinuationContract: {
|
||||
decision: "new_topic",
|
||||
target_intent: null
|
||||
},
|
||||
addressRetryAudit: {
|
||||
attempted: false,
|
||||
reason: null,
|
||||
initial_limited_category: null,
|
||||
retry_result_category: null
|
||||
},
|
||||
predecomposeContract: {
|
||||
intent: "list_documents_by_counterparty",
|
||||
aggregation_profile: "list_lookup",
|
||||
period: {
|
||||
scope: "year"
|
||||
}
|
||||
}
|
||||
},
|
||||
appendItem: () => {},
|
||||
getSession: () => null,
|
||||
persistSession: () => {},
|
||||
cloneConversation: () => [],
|
||||
logEvent: () => {},
|
||||
nowIso: () => "2026-04-10T12:00:00.000Z",
|
||||
messageIdFactory: () => "msg-fixed-1",
|
||||
commitFn: ((input: Record<string, unknown>) => {
|
||||
commitCalls.push(input);
|
||||
return {
|
||||
currentSession: null,
|
||||
conversation: []
|
||||
};
|
||||
}) as any
|
||||
});
|
||||
|
||||
expect(output.assistantItem.message_id).toBe("msg-fixed-1");
|
||||
expect(output.assistantItem.created_at).toBe("2026-04-10T12:00:00.000Z");
|
||||
expect(output.assistantItem.trace_id).toBe("address-trace-1");
|
||||
expect(commitCalls).toHaveLength(1);
|
||||
expect(commitCalls[0]?.["eventType"]).toBe("assistant_message_address");
|
||||
const logDetails = commitCalls[0]?.["logDetails"] as Record<string, unknown>;
|
||||
expect(logDetails?.["session_id"]).toBe("asst-1");
|
||||
expect(logDetails?.["effective_address_user_message"]).toBe("документы по контрагенту свк");
|
||||
expect(logDetails?.["address_followup_context_applied"]).toBe(true);
|
||||
expect(logDetails?.["address_llm_predecompose_attempted"]).toBe(true);
|
||||
expect(logDetails?.["address_dialog_continuation_decision"]).toBe("new_topic");
|
||||
expect(logDetails?.["assistant_reply"]).toBe("Собран список документов.");
|
||||
expect(output.response.assistant_reply).toBe("Собран список документов.");
|
||||
expect(output.response.reply_type).toBe("factual");
|
||||
});
|
||||
|
||||
it("uses default commit runtime and returns conversation from stored session", () => {
|
||||
const laneDebug = buildLaneDebug();
|
||||
let appendCalls = 0;
|
||||
let persistCalls = 0;
|
||||
let logCalls = 0;
|
||||
let storedSession: any = null;
|
||||
const output = finalizeAssistantAddressTurn({
|
||||
sessionId: "asst-2",
|
||||
userMessage: "покажи документы",
|
||||
effectiveAddressUserMessage: "документы",
|
||||
assistantReply: "Готово",
|
||||
replyType: "partial_coverage",
|
||||
addressLaneDebug: laneDebug,
|
||||
debug: { trace_id: "address-trace-2" } as any,
|
||||
appendItem: (_sessionId, item) => {
|
||||
appendCalls += 1;
|
||||
storedSession = {
|
||||
session_id: "asst-2",
|
||||
updated_at: "2026-04-10T12:00:00.000Z",
|
||||
items: [item],
|
||||
investigation_state: null
|
||||
};
|
||||
},
|
||||
getSession: () => storedSession,
|
||||
persistSession: () => {
|
||||
persistCalls += 1;
|
||||
},
|
||||
cloneConversation: (items) => items.map((item) => ({ ...item })),
|
||||
logEvent: () => {
|
||||
logCalls += 1;
|
||||
},
|
||||
messageIdFactory: () => "msg-fixed-2",
|
||||
nowIso: () => "2026-04-10T12:10:00.000Z"
|
||||
});
|
||||
|
||||
expect(appendCalls).toBe(1);
|
||||
expect(persistCalls).toBe(1);
|
||||
expect(logCalls).toBe(1);
|
||||
expect(output.response.ok).toBe(true);
|
||||
expect(output.response.session_id).toBe("asst-2");
|
||||
expect(output.response.reply_type).toBe("partial_coverage");
|
||||
expect(output.response.conversation).toHaveLength(1);
|
||||
expect(output.response.conversation_item.message_id).toBe("msg-fixed-2");
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { finalizeAssistantDeepTurn } from "../src/services/assistantDeepTurnFinalizeRuntimeAdapter";
|
||||
|
||||
describe("assistant deep turn finalize runtime adapter", () => {
|
||||
it("commits assistant turn and builds response with committed conversation", () => {
|
||||
const callOrder: string[] = [];
|
||||
const conversation = [
|
||||
{
|
||||
message_id: "msg-1",
|
||||
session_id: "asst-1",
|
||||
role: "assistant",
|
||||
text: "ok",
|
||||
reply_type: "factual",
|
||||
created_at: "2026-04-10T10:00:00.000Z",
|
||||
trace_id: "trace-1",
|
||||
debug: null
|
||||
}
|
||||
] as any;
|
||||
|
||||
const output = finalizeAssistantDeepTurn({
|
||||
sessionId: "asst-1",
|
||||
assistantReply: "safe-reply",
|
||||
replyType: "factual",
|
||||
assistantItem: conversation[0],
|
||||
debug: { d: 1 },
|
||||
deepAnalysisLogDetails: { stage: "deep" },
|
||||
appendItem: () => {},
|
||||
getSession: () => null,
|
||||
persistSession: () => {},
|
||||
cloneConversation: () => [],
|
||||
logEvent: () => {},
|
||||
commitFn: ((input: Record<string, unknown>) => {
|
||||
callOrder.push("commit");
|
||||
expect(input.eventType).toBe("assistant_message");
|
||||
expect(input.sessionId).toBe("asst-1");
|
||||
expect(input.logDetails).toEqual({ stage: "deep" });
|
||||
return {
|
||||
currentSession: null,
|
||||
conversation
|
||||
};
|
||||
}) as any,
|
||||
buildResponseFn: ((input: Record<string, unknown>) => {
|
||||
callOrder.push("response");
|
||||
expect(input.conversation).toBe(conversation);
|
||||
return {
|
||||
ok: true,
|
||||
session_id: "asst-1",
|
||||
assistant_reply: "safe-reply",
|
||||
reply_type: "factual",
|
||||
conversation_item: conversation[0],
|
||||
debug: { d: 1 },
|
||||
conversation
|
||||
};
|
||||
}) as any
|
||||
});
|
||||
|
||||
expect(callOrder).toEqual(["commit", "response"]);
|
||||
expect(output.commitResult.conversation).toBe(conversation);
|
||||
expect(output.response.assistant_reply).toBe("safe-reply");
|
||||
expect(output.response.conversation).toBe(conversation as any);
|
||||
});
|
||||
|
||||
it("uses default commit/response functions when custom hooks are not provided", () => {
|
||||
const assistantItem = {
|
||||
message_id: "msg-1",
|
||||
session_id: "asst-1",
|
||||
role: "assistant",
|
||||
text: "ok",
|
||||
reply_type: "factual",
|
||||
created_at: "2026-04-10T10:00:00.000Z",
|
||||
trace_id: "trace-1",
|
||||
debug: null
|
||||
} as any;
|
||||
const storedSession = {
|
||||
session_id: "asst-1",
|
||||
updated_at: "2026-04-10T10:00:00.000Z",
|
||||
items: [assistantItem],
|
||||
investigation_state: null
|
||||
};
|
||||
let appendCalled = 0;
|
||||
let persistCalled = 0;
|
||||
let logCalled = 0;
|
||||
|
||||
const output = finalizeAssistantDeepTurn({
|
||||
sessionId: "asst-1",
|
||||
assistantReply: "safe-reply",
|
||||
replyType: "factual",
|
||||
assistantItem,
|
||||
debug: { debug: true },
|
||||
deepAnalysisLogDetails: { info: "x" },
|
||||
appendItem: () => {
|
||||
appendCalled += 1;
|
||||
},
|
||||
getSession: () => storedSession as any,
|
||||
persistSession: () => {
|
||||
persistCalled += 1;
|
||||
},
|
||||
cloneConversation: (items) => items.map((item) => ({ ...item })),
|
||||
logEvent: () => {
|
||||
logCalled += 1;
|
||||
}
|
||||
});
|
||||
|
||||
expect(appendCalled).toBe(1);
|
||||
expect(persistCalled).toBe(1);
|
||||
expect(logCalled).toBe(1);
|
||||
expect(output.response.ok).toBe(true);
|
||||
expect(output.response.session_id).toBe("asst-1");
|
||||
expect(output.response.reply_type).toBe("factual");
|
||||
expect(output.response.conversation.length).toBe(1);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue