NODEDC_1C/llm_normalizer/backend/dist/services/assistantAddressLaneRespons...

387 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.runAssistantAddressLaneResponseRuntime = runAssistantAddressLaneResponseRuntime;
const assistantAddressTurnFinalizeRuntimeAdapter_1 = require("./assistantAddressTurnFinalizeRuntimeAdapter");
const assistantCapabilityBindingResponseGuard_1 = require("./assistantCapabilityBindingResponseGuard");
const assistantCapabilityRuntimeBindingAdapter_1 = require("./assistantCapabilityRuntimeBindingAdapter");
const assistantMcpDiscoveryDebugAttachment_1 = require("./assistantMcpDiscoveryDebugAttachment");
const assistantMcpDiscoveryResponsePolicy_1 = require("./assistantMcpDiscoveryResponsePolicy");
const assistantRuntimeContractResolver_1 = require("./assistantRuntimeContractResolver");
const assistantStateTransitionRuntimeAdapter_1 = require("./assistantStateTransitionRuntimeAdapter");
const assistantTruthAnswerPolicyRuntimeAdapter_1 = require("./assistantTruthAnswerPolicyRuntimeAdapter");
function toRecordObject(value) {
if (!value || typeof value !== "object") {
return null;
}
return value;
}
function toNullableString(value) {
if (typeof value !== "string") {
return null;
}
const trimmed = value.trim();
return trimmed.length > 0 ? trimmed : null;
}
function toNullableBoolean(value) {
if (typeof value === "boolean") {
return value;
}
return undefined;
}
function normalizeAddressReplyType(value) {
return value === "factual" || value === "partial_coverage" ? value : "partial_coverage";
}
function sameBusinessLabel(left, right) {
const normalizedLeft = toNullableString(left)?.toLocaleLowerCase("ru-RU").replace(/ё/g, "е");
const normalizedRight = toNullableString(right)?.toLocaleLowerCase("ru-RU").replace(/ё/g, "е");
return Boolean(normalizedLeft &&
normalizedRight &&
(normalizedLeft === normalizedRight ||
normalizedLeft.includes(normalizedRight) ||
normalizedRight.includes(normalizedLeft)));
}
function firstString(values) {
for (const value of values) {
const text = toNullableString(value);
if (text) {
return text;
}
}
return null;
}
function legalOrganizationLabelFromClarification(value) {
const text = toNullableString(value);
if (!text) {
return null;
}
const compact = text.replace(/\s+/gu, " ").replace(/[.!?]+$/u, "").trim();
if (compact.length > 120 || !/^(?:ООО|ПАО|АО|ИП)\s+\S/iu.test(compact)) {
return null;
}
return compact;
}
function cleanComparisonScopeCompanyLine(line, organization) {
let clean = String(line ?? "")
.replace(/\bcompany-level\b/giu, "общий по компании")
.replace(/\breusable bundle\b/giu, "сохраненный подтвержденный срез");
if (organization) {
clean = clean.replace(/по компании\s+Альтернатива Плюс/iu, `по компании ${organization}`);
}
return clean.trim();
}
function buildComparisonScopeProofReply(input) {
const entryPoint = toRecordObject(input.debug.assistant_mcp_discovery_entry_point_v1);
const turnInput = toRecordObject(entryPoint?.turn_input);
const turnMeaning = toRecordObject(turnInput?.turn_meaning_ref);
const isBusinessOverview = toNullableString(turnMeaning?.asked_domain_family) === "business_overview";
if (!isBusinessOverview) {
return null;
}
const separateCandidates = Array.isArray(turnMeaning?.business_overview_separate_entity_candidates)
? turnMeaning.business_overview_separate_entity_candidates
: [];
const separateSubject = firstString([...separateCandidates, turnMeaning?.metadata_scope_hint]);
if (!separateSubject) {
return null;
}
const sessionRecord = toRecordObject(input.session);
const addressNavigationState = toRecordObject(sessionRecord?.address_navigation_state);
const sessionContext = toRecordObject(addressNavigationState?.session_context);
const comparisonScope = toRecordObject(sessionContext?.comparison_scope);
const comparisonCounterparty = toRecordObject(comparisonScope?.counterparty);
const proofBundles = toRecordObject(comparisonScope?.proof_bundles);
const valueBundle = toRecordObject(proofBundles?.counterparty_value_flow_bundle);
const documentBundle = toRecordObject(proofBundles?.counterparty_document_bundle);
if (!valueBundle || !sameBusinessLabel(separateSubject, valueBundle.counterparty ?? comparisonCounterparty?.label)) {
return null;
}
const incoming = toRecordObject(valueBundle.incoming_customer_revenue);
const outgoing = toRecordObject(valueBundle.outgoing_supplier_payout);
const incomingAmount = toNullableString(incoming?.total_amount_human_ru);
const outgoingAmount = toNullableString(outgoing?.total_amount_human_ru);
const netAmount = toNullableString(valueBundle.net_amount_human_ru);
if (!incomingAmount && !outgoingAmount && !netAmount) {
return null;
}
const organization = legalOrganizationLabelFromClarification(input.userMessage)
?? toNullableString(turnMeaning?.explicit_organization_scope)
?? toNullableString(toRecordObject(comparisonScope?.organization)?.label);
const documentCount = Number(toRecordObject(documentBundle)?.document_count);
const documentText = Number.isFinite(documentCount) && documentCount > 0 ? `, документы: ${documentCount}` : "";
const lines = String(input.baseReply ?? "")
.split(/\r?\n/u)
.map((line) => line.trim())
.filter(Boolean);
const companyLine = cleanComparisonScopeCompanyLine(lines[0] ?? `Коротко: по компании ${organization ?? "выбранной организации"} подтвержден общий денежный срез.`, organization);
const netDirection = valueBundle.net_direction === "net_outgoing" ? "нетто в минус" : "нетто в нашу сторону";
const counterparty = toNullableString(valueBundle.counterparty) ?? separateSubject;
return {
reply: [
`${companyLine}; отдельно по ${counterparty}: получили ${incomingAmount ?? "0 руб."}, заплатили ${outgoingAmount ?? "0 руб."}, ${netDirection} ${netAmount ?? "0 руб."}${documentText}.`,
`Отдельно по контрагенту ${counterparty}: это ранее подтвержденный контрагентский срез, а не перенос общих сумм компании на контрагента.`,
`Нельзя утверждать: чистую прибыль, полноценный финрезультат, юридические роли клиентов/поставщиков и выводы по ${counterparty} из общих сумм компании без отдельного контрагентского среза.`
].join("\n"),
audit: {
applied: true,
source: "address_navigation_state.comparison_scope.proof_bundles",
counterparty,
organization: organization ?? null,
document_count: Number.isFinite(documentCount) && documentCount > 0 ? documentCount : null
}
};
}
function buildAppliedMcpDiscoveryRoutePatch(debug, applied) {
if (!applied) {
return {};
}
const selectedChain = toNullableString(debug.mcp_discovery_selected_chain_id);
if (selectedChain === "business_overview") {
return {
detected_intent: "business_overview",
detected_intent_confidence: "high",
selected_recipe: "business_overview",
response_type: "LIMITED_WITH_REASON",
mcp_discovery_effective_response_route: "business_overview"
};
}
return {};
}
function normalizeAddressLaneDebug(value) {
return (toRecordObject(value) ?? {});
}
function normalizeCarryoverMeta(value) {
const source = toRecordObject(value);
if (!source) {
return null;
}
const followupContext = toRecordObject(source.followupContext);
const previousAddressIntent = toNullableString(source.previousAddressIntent) ??
toNullableString(followupContext?.previous_intent);
const previousAddressAnchor = toNullableString(source.previousAddressAnchor) ??
toNullableString(followupContext?.previous_anchor);
if (!previousAddressIntent && !previousAddressAnchor) {
return null;
}
return {
previousAddressIntent,
previousAddressAnchor
};
}
function normalizeLlmPreDecomposeMeta(value) {
const source = toRecordObject(value);
if (!source) {
return null;
}
const dialogContinuationContractRaw = toRecordObject(source.dialogContinuationContract);
const addressRetryAuditRaw = toRecordObject(source.addressRetryAudit);
const predecomposeContractRaw = toRecordObject(source.predecomposeContract);
const predecomposePeriodRaw = toRecordObject(predecomposeContractRaw?.period);
const semanticExtractionContractRaw = toRecordObject(source.semanticExtractionContract);
const normalized = {};
const attempted = toNullableBoolean(source.attempted);
if (attempted !== undefined)
normalized.attempted = attempted;
const applied = toNullableBoolean(source.applied);
if (applied !== undefined)
normalized.applied = applied;
const provider = toNullableString(source.provider);
if (provider)
normalized.provider = provider;
const traceId = toNullableString(source.traceId);
if (traceId)
normalized.traceId = traceId;
const reason = toNullableString(source.reason);
if (reason)
normalized.reason = reason;
const fallbackRuleHit = toNullableString(source.fallbackRuleHit);
if (fallbackRuleHit)
normalized.fallbackRuleHit = fallbackRuleHit;
const sanitizedUserMessage = toNullableString(source.sanitizedUserMessage);
if (sanitizedUserMessage)
normalized.sanitizedUserMessage = sanitizedUserMessage;
const toolGateDecision = toNullableString(source.toolGateDecision);
if (toolGateDecision)
normalized.toolGateDecision = toolGateDecision;
const toolGateReason = toNullableString(source.toolGateReason);
if (toolGateReason)
normalized.toolGateReason = toolGateReason;
if (dialogContinuationContractRaw) {
const decision = toNullableString(dialogContinuationContractRaw.decision);
const targetIntent = toNullableString(dialogContinuationContractRaw.target_intent);
if (decision || targetIntent) {
normalized.dialogContinuationContract = {
decision,
target_intent: targetIntent
};
}
}
if (addressRetryAuditRaw) {
const retryAttempted = toNullableBoolean(addressRetryAuditRaw.attempted);
const retryReason = toNullableString(addressRetryAuditRaw.reason);
const initialLimitedCategory = toNullableString(addressRetryAuditRaw.initial_limited_category);
const retryResultCategory = toNullableString(addressRetryAuditRaw.retry_result_category);
if (retryAttempted !== undefined ||
retryReason ||
initialLimitedCategory ||
retryResultCategory) {
normalized.addressRetryAudit = {
attempted: retryAttempted,
reason: retryReason,
initial_limited_category: initialLimitedCategory,
retry_result_category: retryResultCategory
};
}
}
if (predecomposeContractRaw) {
const intent = toNullableString(predecomposeContractRaw.intent);
const aggregationProfile = toNullableString(predecomposeContractRaw.aggregation_profile);
const periodScope = toNullableString(predecomposePeriodRaw?.scope);
if (intent || aggregationProfile || periodScope) {
normalized.predecomposeContract = {
intent,
aggregation_profile: aggregationProfile,
period: periodScope ? { scope: periodScope } : null
};
}
}
if (semanticExtractionContractRaw) {
const valid = toNullableBoolean(semanticExtractionContractRaw.valid);
const quality = toNullableString(semanticExtractionContractRaw.quality);
const applyCanonicalRecommended = toNullableBoolean(semanticExtractionContractRaw.apply_canonical_recommended);
const reasonCodes = Array.isArray(semanticExtractionContractRaw.reason_codes)
? semanticExtractionContractRaw.reason_codes
.map((item) => toNullableString(item))
.filter((item) => Boolean(item))
: [];
if (valid !== undefined || quality || applyCanonicalRecommended !== undefined || reasonCodes.length > 0) {
normalized.semanticExtractionContract = {
valid: valid ?? null,
quality,
apply_canonical_recommended: applyCanonicalRecommended ?? null,
reason_codes: reasonCodes
};
}
}
const hasUsefulField = Object.values(normalized).some((item) => item !== undefined && item !== null);
return hasUsefulField ? normalized : null;
}
function runAssistantAddressLaneResponseRuntime(input) {
const finalizeAddressTurnSafe = input.finalizeAddressTurn ?? assistantAddressTurnFinalizeRuntimeAdapter_1.finalizeAssistantAddressTurn;
const safeAddressReply = input.sanitizeOutgoingAssistantText(input.addressLane.reply_text);
const debug = input.buildAddressDebugPayload(input.addressLane.debug, input.llmPreDecomposeMeta);
const followupOffer = input.buildAddressFollowupOffer(debug);
if (followupOffer) {
debug.address_followup_offer = followupOffer;
}
const laneOrganizationCandidates = Array.isArray(input.addressLane.debug?.organization_candidates)
? input.addressLane.debug.organization_candidates
: [];
const debugKnownOrganizations = input.mergeKnownOrganizations([
...input.knownOrganizations,
...laneOrganizationCandidates
]);
const debugFilters = debug?.extracted_filters && typeof debug.extracted_filters === "object"
? debug.extracted_filters
: null;
const debugActiveOrganization = input.toNonEmptyString(debugFilters?.organization) ??
input.toNonEmptyString(input.activeOrganization);
const followupContextSource = input.carryoverMeta?.followupContext && typeof input.carryoverMeta.followupContext === "object"
? input.carryoverMeta.followupContext
: null;
if (debugKnownOrganizations.length > 0) {
debug.assistant_known_organizations = debugKnownOrganizations;
}
if (debugActiveOrganization) {
debug.assistant_active_organization = debugActiveOrganization;
}
const rootIntent = input.toNonEmptyString(followupContextSource?.root_intent);
const currentFrameKind = input.toNonEmptyString(followupContextSource?.current_frame_kind);
const rootFilters = followupContextSource?.root_filters && typeof followupContextSource.root_filters === "object"
? followupContextSource.root_filters
: null;
if (rootIntent || currentFrameKind) {
debug.address_root_frame_context = {
root_intent: rootIntent,
current_frame_kind: currentFrameKind,
organization: input.toNonEmptyString(rootFilters?.organization),
as_of_date: input.toNonEmptyString(rootFilters?.as_of_date),
period_from: input.toNonEmptyString(rootFilters?.period_from),
period_to: input.toNonEmptyString(rootFilters?.period_to)
};
}
const debugWithRuntimeContracts = (0, assistantRuntimeContractResolver_1.attachAssistantRuntimeContractShadow)(debug, {
userMessage: input.userMessage,
addressRuntimeMeta: input.llmPreDecomposeMeta
});
const debugWithTruthAnswerPolicy = (0, assistantTruthAnswerPolicyRuntimeAdapter_1.attachAssistantTruthAnswerPolicy)(debugWithRuntimeContracts, {
addressRuntimeMeta: input.llmPreDecomposeMeta,
replyType: normalizeAddressReplyType(input.addressLane.reply_type)
});
const debugWithStateTransition = (0, assistantStateTransitionRuntimeAdapter_1.attachAssistantStateTransition)(debugWithTruthAnswerPolicy, {
addressRuntimeMeta: input.llmPreDecomposeMeta,
replyType: normalizeAddressReplyType(input.addressLane.reply_type)
});
const debugWithCapabilityBinding = (0, assistantCapabilityRuntimeBindingAdapter_1.attachAssistantCapabilityRuntimeBinding)(debugWithStateTransition, {
addressRuntimeMeta: input.llmPreDecomposeMeta,
replyType: normalizeAddressReplyType(input.addressLane.reply_type)
});
const debugWithMcpDiscovery = (0, assistantMcpDiscoveryDebugAttachment_1.attachAssistantMcpDiscoveryDebug)(debugWithCapabilityBinding, {
addressRuntimeMeta: input.llmPreDecomposeMeta
});
const guardedResponse = (0, assistantCapabilityBindingResponseGuard_1.applyAssistantCapabilityBindingResponseGuard)({
assistantReply: safeAddressReply,
replyType: normalizeAddressReplyType(input.addressLane.reply_type),
capabilityBinding: debugWithMcpDiscovery.assistant_capability_binding_v1
});
const debugWithResponseGuard = {
...debugWithMcpDiscovery,
capability_binding_response_guard: guardedResponse.audit
};
const mcpDiscoveryResponsePolicy = (0, assistantMcpDiscoveryResponsePolicy_1.applyAssistantMcpDiscoveryResponsePolicy)({
currentReply: guardedResponse.assistantReply,
currentReplySource: "address_query_runtime_v1",
currentReplyType: guardedResponse.replyType,
addressRuntimeMeta: debugWithResponseGuard
});
const finalAssistantReply = mcpDiscoveryResponsePolicy.applied
? mcpDiscoveryResponsePolicy.reply_text
: guardedResponse.assistantReply;
const comparisonScopeProofReply = buildComparisonScopeProofReply({
baseReply: finalAssistantReply,
debug: debugWithResponseGuard,
session: input.getSession(input.sessionId),
userMessage: input.userMessage
});
const finalAssistantReplyWithComparisonProof = comparisonScopeProofReply?.reply ?? finalAssistantReply;
const finalReplyType = mcpDiscoveryResponsePolicy.applied ? "partial_coverage" : guardedResponse.replyType;
const finalDebug = {
...debugWithResponseGuard,
mcp_discovery_response_policy_v1: mcpDiscoveryResponsePolicy,
mcp_discovery_response_candidate_v1: mcpDiscoveryResponsePolicy.candidate,
mcp_discovery_response_applied: mcpDiscoveryResponsePolicy.applied,
comparison_scope_response_augmentation_v1: comparisonScopeProofReply?.audit ?? null,
...buildAppliedMcpDiscoveryRoutePatch(debugWithResponseGuard, mcpDiscoveryResponsePolicy.applied)
};
const finalization = finalizeAddressTurnSafe({
sessionId: input.sessionId,
userMessage: input.userMessage,
effectiveAddressUserMessage: input.effectiveAddressUserMessage,
assistantReply: finalAssistantReplyWithComparisonProof,
replyType: finalReplyType,
addressLaneDebug: normalizeAddressLaneDebug(input.addressLane.debug),
debug: finalDebug,
carryoverMeta: normalizeCarryoverMeta(input.carryoverMeta),
llmPreDecomposeMeta: normalizeLlmPreDecomposeMeta(input.llmPreDecomposeMeta),
appendItem: input.appendItem,
getSession: input.getSession,
persistSession: input.persistSession,
cloneConversation: input.cloneConversation,
logEvent: input.logEvent,
messageIdFactory: input.messageIdFactory
});
return {
response: finalization.response,
debug: finalDebug
};
}