NODEDC_1C/llm_normalizer/backend/dist/services/assistantLivingChatRuntimeA...

353 lines
18 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.runAssistantLivingChatRuntime = runAssistantLivingChatRuntime;
const assistantMemoryRecapPolicy_1 = require("./assistantMemoryRecapPolicy");
function formatIsoDateForReply(value) {
const source = String(value ?? "").trim();
const match = source.match(/^(\d{4})-(\d{2})-(\d{2})$/);
if (!match) {
return null;
}
return `${match[3]}.${match[2]}.${match[1]}`;
}
function findLastGroundedInventoryAddressDebug(items) {
if (!Array.isArray(items)) {
return null;
}
for (let index = items.length - 1; index >= 0; index -= 1) {
const item = items[index];
if (!item || item.role !== "assistant" || !item.debug || typeof item.debug !== "object") {
continue;
}
const debug = item.debug;
const answerGroundingCheck = debug.answer_grounding_check && typeof debug.answer_grounding_check === "object"
? debug.answer_grounding_check
: null;
const groundingStatus = String(answerGroundingCheck?.status ?? "");
const detectedIntent = String(debug.detected_intent ?? "");
const capabilityId = String(debug.capability_id ?? "");
const rootFrameContext = debug.address_root_frame_context && typeof debug.address_root_frame_context === "object"
? debug.address_root_frame_context
: null;
const rootIntent = String(rootFrameContext?.root_intent ?? "");
const isInventoryContext = detectedIntent === "inventory_on_hand_as_of_date" ||
capabilityId === "confirmed_inventory_on_hand_as_of_date" ||
rootIntent === "inventory_on_hand_as_of_date";
if (groundingStatus === "grounded" && isInventoryContext) {
return debug;
}
}
return null;
}
function findLastAddressDebugWithItem(items) {
if (!Array.isArray(items)) {
return null;
}
for (let index = items.length - 1; index >= 0; index -= 1) {
const item = items[index];
if (!item || item.role !== "assistant" || !item.debug || typeof item.debug !== "object") {
continue;
}
const debug = item.debug;
if (String(debug.execution_lane ?? "") !== "address_query") {
continue;
}
const extractedFilters = debug.extracted_filters && typeof debug.extracted_filters === "object"
? debug.extracted_filters
: null;
const itemLabel = String(extractedFilters?.item ?? "").trim() ||
(String(debug.anchor_type ?? "") === "item"
? String(debug.anchor_value_resolved ?? debug.anchor_value_raw ?? "").trim()
: "");
if (itemLabel) {
return debug;
}
}
return null;
}
function hasPriorAssistantTurn(items) {
if (!Array.isArray(items)) {
return false;
}
return items.some((item) => item && typeof item === "object" && item.role === "assistant");
}
function findLastAddressDebug(items) {
if (!Array.isArray(items)) {
return null;
}
for (let index = items.length - 1; index >= 0; index -= 1) {
const item = items[index];
if (!item || item.role !== "assistant" || !item.debug || typeof item.debug !== "object") {
continue;
}
if (String(item.debug.execution_lane ?? "") === "address_query") {
return item.debug;
}
}
return null;
}
function buildInventoryHistoryCapabilityFollowupReply(input) {
const rootFrameContext = input.addressDebug?.address_root_frame_context && typeof input.addressDebug.address_root_frame_context === "object"
? input.addressDebug.address_root_frame_context
: null;
const extractedFilters = input.addressDebug?.extracted_filters && typeof input.addressDebug.extracted_filters === "object"
? input.addressDebug.extracted_filters
: null;
const organization = input.organization ??
input.toNonEmptyString(rootFrameContext?.organization) ??
input.toNonEmptyString(extractedFilters?.organization);
const lastAsOfDate = formatIsoDateForReply(rootFrameContext?.as_of_date) ??
formatIsoDateForReply(extractedFilters?.as_of_date);
const organizationPart = organization ? ` по компании «${organization}»` : "";
const referenceLine = lastAsOfDate
? `Да, могу. Сейчас мы уже смотрели складской срез${organizationPart} на ${lastAsOfDate}.`
: `Да, могу показать исторические данные${organizationPart} в этом же складском контуре.`;
return [
referenceLine,
`Могу показать исторические остатки${organizationPart} за нужный месяц, дату или год.`,
"Например:",
"- `на март 2020`",
"- `на июнь 2016`",
"- `за 2017 год`",
"- `сравни июнь 2016 с текущим срезом`",
"Если хочешь, сразу покажу нужный исторический период."
].join("\n");
}
function buildAddressMemoryRecapReply(input) {
const extractedFilters = input.addressDebug?.extracted_filters && typeof input.addressDebug.extracted_filters === "object"
? input.addressDebug.extracted_filters
: null;
const rootFrameContext = input.addressDebug?.address_root_frame_context && typeof input.addressDebug.address_root_frame_context === "object"
? input.addressDebug.address_root_frame_context
: null;
const item = input.toNonEmptyString(extractedFilters?.item) ??
(String(input.addressDebug?.anchor_type ?? "") === "item"
? input.toNonEmptyString(input.addressDebug?.anchor_value_resolved) ??
input.toNonEmptyString(input.addressDebug?.anchor_value_raw)
: null);
const organization = input.organization ??
input.toNonEmptyString(extractedFilters?.organization) ??
input.toNonEmptyString(rootFrameContext?.organization);
const scopedDate = formatIsoDateForReply(extractedFilters?.as_of_date) ??
formatIsoDateForReply(rootFrameContext?.as_of_date) ??
formatIsoDateForReply(extractedFilters?.period_to);
if (item) {
const datePart = scopedDate ? ` в срезе на ${scopedDate}` : "";
const organizationPart = organization ? ` по компании «${organization}»` : "";
return [
`Да, помню. Мы обсуждали позицию «${item}»${organizationPart}${datePart}.`,
"Могу продолжить по ней без переписывания сущности: кто поставил, когда купили, по каким документам или кому продали."
].join(" ");
}
if (organization || scopedDate) {
const organizationPart = organization ? ` по компании «${organization}»` : "";
const datePart = scopedDate ? ` на ${scopedDate}` : "";
return [
`Да, помню. Мы уже смотрели адресный контур${organizationPart}${datePart}.`,
"Могу кратко напомнить контекст или сразу продолжить следующий шаг по этому же сценарию."
].join(" ");
}
return "Да, помню предыдущий адресный контур. Могу кратко напомнить, что мы уже подтвердили, или сразу продолжить следующий шаг.";
}
async function runAssistantLivingChatRuntime(input) {
const userMessage = String(input.userMessage ?? "");
const dataScopeMetaQuery = input.hasAssistantDataScopeMetaQuestionSignal(userMessage);
const capabilityMetaQuery = input.shouldHandleAsAssistantCapabilityMetaQuery(userMessage);
const destructiveSignal = input.hasDestructiveDataActionSignal(userMessage);
const dangerSignal = input.hasDangerOrCoercionSignal(userMessage);
const operationalSignal = input.hasOperationalAdminActionRequestSignal(userMessage);
let dataScopeProbe = null;
let chatText = "";
let livingChatSource = "llm_chat";
let livingChatScriptGuardApplied = false;
let livingChatScriptGuardReason = null;
let livingChatGroundingGuardApplied = false;
let livingChatGroundingGuardReason = null;
let livingChatProactiveScopeOfferApplied = false;
let knownOrganizations = input.mergeKnownOrganizations(input.sessionScope.knownOrganizations ?? []);
let selectedOrganization = input.toNonEmptyString(input.sessionScope.selectedOrganization);
let activeOrganization = input.toNonEmptyString(input.sessionScope.activeOrganization);
const memoryRecapContext = (0, assistantMemoryRecapPolicy_1.resolveAssistantLivingChatMemoryContext)({
modeDecisionReason: input.modeDecision?.reason ?? null,
sessionItems: input.sessionItems
});
const contextualInventoryHistoryCapabilityFollowup = memoryRecapContext.contextualInventoryHistoryCapabilityFollowup;
const contextualMemoryRecapFollowup = memoryRecapContext.contextualMemoryRecapFollowup;
const lastGroundedInventoryAddressDebug = memoryRecapContext.lastGroundedInventoryAddressDebug;
const lastMemoryAddressDebug = memoryRecapContext.lastMemoryAddressDebug;
if (capabilityMetaQuery && (destructiveSignal || dangerSignal)) {
chatText = input.buildAssistantSafetyRefusalReply();
livingChatSource = "deterministic_safety_refusal";
}
else if (dataScopeMetaQuery) {
dataScopeProbe = await input.resolveDataScopeProbe();
chatText = input.buildAssistantDataScopeContractReply(dataScopeProbe);
knownOrganizations = input.mergeKnownOrganizations([
...knownOrganizations,
...(Array.isArray(dataScopeProbe?.organizations) ? dataScopeProbe.organizations : [])
]);
if (!activeOrganization && knownOrganizations.length === 1) {
activeOrganization = knownOrganizations[0];
}
livingChatSource =
dataScopeProbe?.status === "resolved"
? "deterministic_data_scope_contract_live"
: "deterministic_data_scope_contract";
}
else if ((selectedOrganization || activeOrganization) && input.hasOrganizationFactLookupSignal(userMessage)) {
const scopedOrganization = selectedOrganization ?? activeOrganization ?? null;
chatText = input.buildAssistantOrganizationFactBoundaryReply(scopedOrganization);
activeOrganization = scopedOrganization ?? activeOrganization;
livingChatSource = "deterministic_organization_fact_boundary";
}
else if ((selectedOrganization || activeOrganization) &&
input.hasOrganizationFactFollowupSignal(userMessage, input.sessionItems)) {
const scopedOrganization = selectedOrganization ?? activeOrganization ?? null;
chatText = input.buildAssistantOrganizationFactBoundaryReply(scopedOrganization);
activeOrganization = scopedOrganization ?? activeOrganization;
livingChatSource = "deterministic_organization_fact_boundary_followup";
}
else if (!capabilityMetaQuery &&
input.shouldEmitOrganizationSelectionReply(userMessage, selectedOrganization ?? activeOrganization)) {
const scopedOrganization = selectedOrganization ?? activeOrganization ?? null;
chatText = input.buildAssistantDataScopeSelectionReply(scopedOrganization);
activeOrganization = scopedOrganization ?? activeOrganization;
livingChatSource = "deterministic_data_scope_selection_contract";
}
else if (capabilityMetaQuery && operationalSignal && !input.hasAssistantCapabilityQuestionSignal(userMessage)) {
chatText = input.buildAssistantOperationalBoundaryReply();
livingChatSource = "deterministic_operational_boundary";
}
else if (contextualInventoryHistoryCapabilityFollowup) {
const scopedOrganization = selectedOrganization ?? activeOrganization ?? null;
chatText = (0, assistantMemoryRecapPolicy_1.buildInventoryHistoryCapabilityFollowupReply)({
organization: scopedOrganization,
addressDebug: lastGroundedInventoryAddressDebug,
toNonEmptyString: input.toNonEmptyString
});
activeOrganization = scopedOrganization ?? activeOrganization;
livingChatSource = "deterministic_inventory_history_capability_contract";
}
else if (contextualMemoryRecapFollowup) {
const scopedOrganization = selectedOrganization ?? activeOrganization ?? null;
chatText = (0, assistantMemoryRecapPolicy_1.buildAddressMemoryRecapReply)({
organization: scopedOrganization,
addressDebug: lastMemoryAddressDebug,
sessionItems: input.sessionItems,
toNonEmptyString: input.toNonEmptyString
});
activeOrganization = scopedOrganization ?? activeOrganization;
livingChatSource = "deterministic_memory_recap_contract";
}
else if (capabilityMetaQuery) {
chatText = input.buildAssistantCapabilityContractReply(userMessage);
livingChatSource = "deterministic_capability_contract";
}
else {
chatText = await input.executeLlmChat();
const scriptGuard = input.applyScriptGuard(chatText, userMessage);
chatText = scriptGuard.text;
if (scriptGuard.applied) {
livingChatScriptGuardApplied = true;
livingChatScriptGuardReason = scriptGuard.reason;
livingChatSource = "llm_chat_script_guard";
}
const groundingGuard = input.applyGroundingGuard({
userMessage,
chatText,
organization: activeOrganization ?? selectedOrganization ?? null
});
chatText = groundingGuard.text;
if (groundingGuard.applied) {
livingChatGroundingGuardApplied = true;
livingChatGroundingGuardReason = groundingGuard.reason;
livingChatSource = "llm_chat_grounding_guard";
}
const shouldOfferProactiveOrganizationScope = !selectedOrganization &&
!activeOrganization &&
!hasPriorAssistantTurn(input.sessionItems) &&
input.modeDecision?.mode === "chat" &&
input.hasLivingChatSignal(userMessage);
if (shouldOfferProactiveOrganizationScope) {
const proactiveScopeProbe = await input.resolveDataScopeProbe();
const mergedKnownOrganizations = input.mergeKnownOrganizations([
...knownOrganizations,
...(Array.isArray(proactiveScopeProbe?.organizations) ? proactiveScopeProbe.organizations : [])
]);
knownOrganizations = mergedKnownOrganizations;
if (!activeOrganization && mergedKnownOrganizations.length === 1) {
activeOrganization = mergedKnownOrganizations[0];
}
const proactiveOffer = input.buildAssistantProactiveOrganizationOfferReply(proactiveScopeProbe);
if (proactiveOffer) {
chatText = [chatText, proactiveOffer].filter((part) => String(part ?? "").trim().length > 0).join(" ");
livingChatProactiveScopeOfferApplied = true;
livingChatSource = "llm_chat_with_proactive_scope_offer";
if (!dataScopeProbe) {
dataScopeProbe = proactiveScopeProbe;
}
}
}
}
if (!chatText) {
return {
handled: false,
chatText: "",
debug: null
};
}
const addressRuntimeMeta = (input.addressRuntimeMeta && typeof input.addressRuntimeMeta === "object"
? input.addressRuntimeMeta
: {});
const predecomposeContract = addressRuntimeMeta.predecomposeContract && typeof addressRuntimeMeta.predecomposeContract === "object"
? addressRuntimeMeta.predecomposeContract
: null;
const semanticExtractionContract = addressRuntimeMeta.semanticExtractionContract && typeof addressRuntimeMeta.semanticExtractionContract === "object"
? addressRuntimeMeta.semanticExtractionContract
: null;
const debug = {
trace_id: input.traceIdFactory(),
prompt_version: "living_chat_router_v1",
schema_version: "living_chat_router_v1",
fallback_type: "none",
detected_mode: "chat",
detected_mode_confidence: "high",
execution_lane: "living_chat",
living_router_mode: input.modeDecision?.mode ?? "chat",
living_router_reason: input.modeDecision?.reason ?? "living_chat_signal_detected",
living_chat_response_source: livingChatSource,
living_chat_script_guard_applied: livingChatScriptGuardApplied,
living_chat_script_guard_reason: livingChatScriptGuardReason,
living_chat_grounding_guard_applied: livingChatGroundingGuardApplied,
living_chat_grounding_guard_reason: livingChatGroundingGuardReason,
living_chat_proactive_scope_offer_applied: livingChatProactiveScopeOfferApplied,
living_chat_data_scope_probe_status: dataScopeProbe?.status ?? null,
living_chat_data_scope_probe_channel: dataScopeProbe?.channel ?? null,
living_chat_data_scope_probe_org_count: Array.isArray(dataScopeProbe?.organizations)
? dataScopeProbe.organizations.length
: 0,
living_chat_data_scope_probe_organizations: Array.isArray(dataScopeProbe?.organizations)
? input.mergeKnownOrganizations(dataScopeProbe.organizations)
: [],
living_chat_data_scope_probe_error: dataScopeProbe?.error ?? null,
living_chat_selected_organization: selectedOrganization ?? null,
assistant_known_organizations: knownOrganizations,
assistant_active_organization: activeOrganization ?? null,
address_llm_predecompose_attempted: Boolean(addressRuntimeMeta.attempted),
address_llm_predecompose_applied: Boolean(addressRuntimeMeta.applied),
address_llm_predecompose_reason: addressRuntimeMeta.reason ?? null,
address_llm_predecompose_contract: predecomposeContract,
address_semantic_extraction_contract: semanticExtractionContract,
orchestration_contract_v1: addressRuntimeMeta.orchestrationContract ?? null,
tool_gate_decision: addressRuntimeMeta.toolGateDecision ?? null,
tool_gate_reason: addressRuntimeMeta.toolGateReason ?? null,
normalized: null,
normalizer_output: null
};
return {
handled: true,
chatText,
debug
};
}