319 lines
17 KiB
JavaScript
319 lines
17 KiB
JavaScript
"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 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 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,
|
||
toNonEmptyString: input.toNonEmptyString
|
||
});
|
||
activeOrganization = scopedOrganization ?? activeOrganization;
|
||
livingChatSource = "deterministic_memory_recap_contract";
|
||
}
|
||
else if (capabilityMetaQuery) {
|
||
chatText = input.buildAssistantCapabilityContractReply();
|
||
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";
|
||
}
|
||
}
|
||
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_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
|
||
};
|
||
}
|