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

336 lines
20 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");
const assistantContinuityPolicy_1 = require("./assistantContinuityPolicy");
function hasPriorAssistantTurn(items) {
if (!Array.isArray(items)) {
return false;
}
return items.some((item) => item && typeof item === "object" && item.role === "assistant");
}
function buildDeterministicSmalltalkLeadReply() {
return "\u041f\u0440\u0438\u0432\u0435\u0442! \u0412\u0441\u0451 \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u043e.";
}
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 = (0, assistantContinuityPolicy_1.formatIsoDateForReply)(rootFrameContext?.as_of_date) ??
(0, assistantContinuityPolicy_1.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 = (0, assistantContinuityPolicy_1.formatIsoDateForReply)(extractedFilters?.as_of_date) ??
(0, assistantContinuityPolicy_1.formatIsoDateForReply)(rootFrameContext?.as_of_date) ??
(0, assistantContinuityPolicy_1.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 "Да, помню предыдущий адресный контур. Могу кратко напомнить, что мы уже подтвердили, или сразу продолжить следующий шаг.";
}
function buildSelectedObjectAnswerInspectionReply(input) {
const extractedFilters = input.addressDebug?.extracted_filters && typeof input.addressDebug.extracted_filters === "object"
? input.addressDebug.extracted_filters
: 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 detectedIntent = String(input.addressDebug?.detected_intent ?? "");
const itemLabel = item ?? "эта позиция";
if (detectedIntent === "inventory_sale_trace_for_item") {
return [
`Да, если так прозвучало, это ошибка чтения ответа. «${itemLabel}» здесь не контрагент, а сама позиция, по которой мы смотрели продажу.`,
"В предыдущем ответе я показывал документы выбытия по этой позиции. Покупатель в доступных данных отдельно не выделен, поэтому назвать контрагента-покупателя я там не мог.",
"Если хочешь, следующим шагом могу отдельно проверить, можно ли вытащить покупателя по связанным документам реализации."
].join(" ");
}
if (detectedIntent === "inventory_purchase_provenance_for_item" ||
detectedIntent === "inventory_purchase_documents_for_item") {
return [
`Да, если так прозвучало, это ошибка чтения ответа. «${itemLabel}» здесь не контрагент, а сама позиция / номенклатура.`,
"В предыдущем ответе речь шла о закупке этой позиции: я перечислял поставщиков или закупочные документы по ней, а не называл саму позицию контрагентом."
].join(" ");
}
return [
`Да, если так прозвучало, это ошибка чтения ответа. «${itemLabel}» здесь не контрагент, а выбранный объект разбора.`,
"Я сейчас уточняю именно смысл предыдущего grounded-ответа по этой позиции, а не запускаю новый адресный поиск."
].join(" ");
}
async function runAssistantLivingChatRuntime(input) {
const userMessage = String(input.userMessage ?? "");
const organizationAuthority = (0, assistantContinuityPolicy_1.resolveAssistantOrganizationAuthority)({
sessionItems: input.sessionItems,
sessionKnownOrganizations: Array.isArray(input.sessionScope.knownOrganizations)
? input.sessionScope.knownOrganizations
: [],
sessionSelectedOrganization: input.sessionScope.selectedOrganization,
sessionActiveOrganization: input.sessionScope.activeOrganization,
toNonEmptyString: input.toNonEmptyString,
normalizeOrganizationScopeValue: input.toNonEmptyString,
mergeKnownOrganizations: input.mergeKnownOrganizations
});
const continuitySnapshot = organizationAuthority.continuitySnapshot;
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;
const continuityActiveOrganization = organizationAuthority.continuityActiveOrganization;
let knownOrganizations = [...organizationAuthority.knownOrganizations];
let selectedOrganization = organizationAuthority.selectedOrganization;
let activeOrganization = organizationAuthority.activeOrganization;
const memoryRecapContext = (0, assistantMemoryRecapPolicy_1.resolveAssistantLivingChatMemoryContext)({
modeDecisionReason: input.modeDecision?.reason ?? null,
sessionItems: input.sessionItems
});
const contextualInventoryHistoryCapabilityFollowup = memoryRecapContext.contextualInventoryHistoryCapabilityFollowup;
const contextualMemoryRecapFollowup = memoryRecapContext.contextualMemoryRecapFollowup;
const contextualAnswerInspectionFollowup = memoryRecapContext.contextualAnswerInspectionFollowup;
const lastGroundedInventoryAddressDebug = memoryRecapContext.lastGroundedInventoryAddressDebug;
const lastMemoryAddressDebug = memoryRecapContext.lastMemoryAddressDebug;
const lastAnswerInspectionAddressDebug = memoryRecapContext.lastAnswerInspectionAddressDebug;
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 (contextualAnswerInspectionFollowup) {
chatText = (0, assistantMemoryRecapPolicy_1.buildSelectedObjectAnswerInspectionReply)({
addressDebug: lastAnswerInspectionAddressDebug,
toNonEmptyString: input.toNonEmptyString
});
livingChatSource = "deterministic_answer_inspection_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 &&
!continuitySnapshot.hasGroundedAddressContext &&
!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 = [buildDeterministicSmalltalkLeadReply(), proactiveOffer]
.filter((part) => String(part ?? "").trim().length > 0)
.join(" ");
livingChatProactiveScopeOfferApplied = true;
livingChatSource = "deterministic_smalltalk_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_continuity_grounded_context_detected: continuitySnapshot.hasGroundedAddressContext,
living_chat_continuity_active_organization: continuityActiveOrganization,
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
};
}