ЮИ - редактура вопросов в АВТОПРОГОНАХ
This commit is contained in:
@@ -66,6 +66,7 @@ async function runAssistantAddressAttemptRuntime(input) {
|
||||
hasOperationalAdminActionRequestSignal: input.hasOperationalAdminActionRequestSignal,
|
||||
hasOrganizationFactLookupSignal: input.hasOrganizationFactLookupSignal,
|
||||
hasOrganizationFactFollowupSignal: input.hasOrganizationFactFollowupSignal,
|
||||
hasLivingChatSignal: input.hasLivingChatSignal,
|
||||
shouldEmitOrganizationSelectionReply: input.shouldEmitOrganizationSelectionReply,
|
||||
hasAssistantCapabilityQuestionSignal: input.hasAssistantCapabilityQuestionSignal,
|
||||
resolveDataScopeProbe: input.resolveDataScopeProbe,
|
||||
@@ -73,6 +74,7 @@ async function runAssistantAddressAttemptRuntime(input) {
|
||||
applyGroundingGuard: input.applyGroundingGuard,
|
||||
buildAssistantSafetyRefusalReply: input.buildAssistantSafetyRefusalReply,
|
||||
buildAssistantDataScopeContractReply: input.buildAssistantDataScopeContractReply,
|
||||
buildAssistantProactiveOrganizationOfferReply: input.buildAssistantProactiveOrganizationOfferReply,
|
||||
buildAssistantOrganizationFactBoundaryReply: input.buildAssistantOrganizationFactBoundaryReply,
|
||||
buildAssistantDataScopeSelectionReply: input.buildAssistantDataScopeSelectionReply,
|
||||
buildAssistantOperationalBoundaryReply: input.buildAssistantOperationalBoundaryReply,
|
||||
|
||||
@@ -51,6 +51,28 @@ function createAssistantBoundaryPolicy(deps) {
|
||||
"Если в нем несколько организаций, скажите, по какой смотреть данные."
|
||||
].join(" ");
|
||||
}
|
||||
function buildAssistantProactiveOrganizationOfferReply(scopeProbe = null) {
|
||||
const organizations = Array.isArray(scopeProbe?.organizations)
|
||||
? scopeProbe.organizations
|
||||
.map((item) => normalizeSelectedOrganization(item, deps.normalizeOrganizationScopeValue))
|
||||
.filter((item) => item.length > 0)
|
||||
.filter((item, index, array) => array.indexOf(item) === index)
|
||||
: [];
|
||||
if (organizations.length === 1) {
|
||||
return [
|
||||
`Если дальше пойдём в данные 1С, могу сразу держать в контуре ${organizations[0]}.`,
|
||||
"Можно просто писать вопрос по документам, остаткам, НДС или контрагентам."
|
||||
].join(" ");
|
||||
}
|
||||
if (organizations.length > 1) {
|
||||
const preview = organizations.slice(0, 10).join(", ");
|
||||
return [
|
||||
`Если дальше пойдём в данные 1С, могу сразу зафиксировать организацию: ${preview}.`,
|
||||
"Просто напишите название компании, и дальше буду держать её активной в этой сессии."
|
||||
].join(" ");
|
||||
}
|
||||
return "";
|
||||
}
|
||||
function buildAssistantDataScopeSelectionReply(organization) {
|
||||
const selected = normalizeSelectedOrganization(organization, deps.normalizeOrganizationScopeValue);
|
||||
return [
|
||||
@@ -161,6 +183,7 @@ function createAssistantBoundaryPolicy(deps) {
|
||||
}
|
||||
return {
|
||||
buildAssistantDataScopeContractReply,
|
||||
buildAssistantProactiveOrganizationOfferReply,
|
||||
buildAssistantDataScopeSelectionReply,
|
||||
buildAssistantOrganizationFactBoundaryReply,
|
||||
buildAssistantOperationalBoundaryReply,
|
||||
|
||||
+2
@@ -23,6 +23,7 @@ function buildAssistantLivingChatAttemptRuntimeInput(input) {
|
||||
hasOperationalAdminActionRequestSignal: input.hasOperationalAdminActionRequestSignal,
|
||||
hasOrganizationFactLookupSignal: input.hasOrganizationFactLookupSignal,
|
||||
hasOrganizationFactFollowupSignal: input.hasOrganizationFactFollowupSignal,
|
||||
hasLivingChatSignal: input.hasLivingChatSignal,
|
||||
shouldEmitOrganizationSelectionReply: input.shouldEmitOrganizationSelectionReply,
|
||||
hasAssistantCapabilityQuestionSignal: input.hasAssistantCapabilityQuestionSignal,
|
||||
resolveDataScopeProbe: input.resolveDataScopeProbe,
|
||||
@@ -30,6 +31,7 @@ function buildAssistantLivingChatAttemptRuntimeInput(input) {
|
||||
applyGroundingGuard: input.applyGroundingGuard,
|
||||
buildAssistantSafetyRefusalReply: input.buildAssistantSafetyRefusalReply,
|
||||
buildAssistantDataScopeContractReply: input.buildAssistantDataScopeContractReply,
|
||||
buildAssistantProactiveOrganizationOfferReply: input.buildAssistantProactiveOrganizationOfferReply,
|
||||
buildAssistantOrganizationFactBoundaryReply: input.buildAssistantOrganizationFactBoundaryReply,
|
||||
buildAssistantDataScopeSelectionReply: input.buildAssistantDataScopeSelectionReply,
|
||||
buildAssistantOperationalBoundaryReply: input.buildAssistantOperationalBoundaryReply,
|
||||
|
||||
+2
@@ -38,6 +38,7 @@ async function runAssistantLivingChatAttemptRuntime(input) {
|
||||
hasOperationalAdminActionRequestSignal: input.hasOperationalAdminActionRequestSignal,
|
||||
hasOrganizationFactLookupSignal: input.hasOrganizationFactLookupSignal,
|
||||
hasOrganizationFactFollowupSignal: input.hasOrganizationFactFollowupSignal,
|
||||
hasLivingChatSignal: input.hasLivingChatSignal,
|
||||
shouldEmitOrganizationSelectionReply: input.shouldEmitOrganizationSelectionReply,
|
||||
hasAssistantCapabilityQuestionSignal: input.hasAssistantCapabilityQuestionSignal,
|
||||
resolveDataScopeProbe: input.resolveDataScopeProbe,
|
||||
@@ -46,6 +47,7 @@ async function runAssistantLivingChatAttemptRuntime(input) {
|
||||
applyGroundingGuard: input.applyGroundingGuard,
|
||||
buildAssistantSafetyRefusalReply: input.buildAssistantSafetyRefusalReply,
|
||||
buildAssistantDataScopeContractReply: input.buildAssistantDataScopeContractReply,
|
||||
buildAssistantProactiveOrganizationOfferReply: input.buildAssistantProactiveOrganizationOfferReply,
|
||||
buildAssistantOrganizationFactBoundaryReply: input.buildAssistantOrganizationFactBoundaryReply,
|
||||
buildAssistantDataScopeSelectionReply: input.buildAssistantDataScopeSelectionReply,
|
||||
buildAssistantOperationalBoundaryReply: input.buildAssistantOperationalBoundaryReply,
|
||||
|
||||
+2
@@ -33,6 +33,7 @@ function buildAssistantLivingChatHandlerRuntimeInput(input) {
|
||||
hasOperationalAdminActionRequestSignal: input.hasOperationalAdminActionRequestSignal,
|
||||
hasOrganizationFactLookupSignal: input.hasOrganizationFactLookupSignal,
|
||||
hasOrganizationFactFollowupSignal: input.hasOrganizationFactFollowupSignal,
|
||||
hasLivingChatSignal: input.hasLivingChatSignal,
|
||||
shouldEmitOrganizationSelectionReply: input.shouldEmitOrganizationSelectionReply,
|
||||
hasAssistantCapabilityQuestionSignal: input.hasAssistantCapabilityQuestionSignal,
|
||||
resolveDataScopeProbe: input.resolveDataScopeProbe,
|
||||
@@ -41,6 +42,7 @@ function buildAssistantLivingChatHandlerRuntimeInput(input) {
|
||||
applyGroundingGuard: input.applyGroundingGuard,
|
||||
buildAssistantSafetyRefusalReply: input.buildAssistantSafetyRefusalReply,
|
||||
buildAssistantDataScopeContractReply: input.buildAssistantDataScopeContractReply,
|
||||
buildAssistantProactiveOrganizationOfferReply: input.buildAssistantProactiveOrganizationOfferReply,
|
||||
buildAssistantOrganizationFactBoundaryReply: input.buildAssistantOrganizationFactBoundaryReply,
|
||||
buildAssistantDataScopeSelectionReply: input.buildAssistantDataScopeSelectionReply,
|
||||
buildAssistantOperationalBoundaryReply: input.buildAssistantOperationalBoundaryReply,
|
||||
|
||||
+2
@@ -23,6 +23,7 @@ async function tryHandleAssistantLivingChatRuntime(input) {
|
||||
hasOperationalAdminActionRequestSignal: input.hasOperationalAdminActionRequestSignal,
|
||||
hasOrganizationFactLookupSignal: input.hasOrganizationFactLookupSignal,
|
||||
hasOrganizationFactFollowupSignal: input.hasOrganizationFactFollowupSignal,
|
||||
hasLivingChatSignal: input.hasLivingChatSignal,
|
||||
shouldEmitOrganizationSelectionReply: input.shouldEmitOrganizationSelectionReply,
|
||||
hasAssistantCapabilityQuestionSignal: input.hasAssistantCapabilityQuestionSignal,
|
||||
resolveDataScopeProbe: input.resolveDataScopeProbe,
|
||||
@@ -31,6 +32,7 @@ async function tryHandleAssistantLivingChatRuntime(input) {
|
||||
applyGroundingGuard: input.applyGroundingGuard,
|
||||
buildAssistantSafetyRefusalReply: input.buildAssistantSafetyRefusalReply,
|
||||
buildAssistantDataScopeContractReply: input.buildAssistantDataScopeContractReply,
|
||||
buildAssistantProactiveOrganizationOfferReply: input.buildAssistantProactiveOrganizationOfferReply,
|
||||
buildAssistantOrganizationFactBoundaryReply: input.buildAssistantOrganizationFactBoundaryReply,
|
||||
buildAssistantDataScopeSelectionReply: input.buildAssistantDataScopeSelectionReply,
|
||||
buildAssistantOperationalBoundaryReply: input.buildAssistantOperationalBoundaryReply,
|
||||
|
||||
@@ -65,6 +65,12 @@ function findLastAddressDebugWithItem(items) {
|
||||
}
|
||||
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;
|
||||
@@ -157,6 +163,7 @@ async function runAssistantLivingChatRuntime(input) {
|
||||
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);
|
||||
@@ -256,6 +263,31 @@ async function runAssistantLivingChatRuntime(input) {
|
||||
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 {
|
||||
@@ -288,6 +320,7 @@ async function runAssistantLivingChatRuntime(input) {
|
||||
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)
|
||||
|
||||
@@ -5023,6 +5023,7 @@ class AssistantService {
|
||||
hasOperationalAdminActionRequestSignal,
|
||||
hasOrganizationFactLookupSignal,
|
||||
hasOrganizationFactFollowupSignal,
|
||||
hasLivingChatSignal,
|
||||
shouldEmitOrganizationSelectionReply,
|
||||
hasAssistantCapabilityQuestionSignal,
|
||||
resolveDataScopeProbe: () => resolveAssistantDataScopeProbe(),
|
||||
@@ -5030,6 +5031,7 @@ class AssistantService {
|
||||
applyGroundingGuard: applyLivingChatGroundingGuardFromPolicy,
|
||||
buildAssistantSafetyRefusalReply: buildAssistantSafetyRefusalReplyFromPolicy,
|
||||
buildAssistantDataScopeContractReply: buildAssistantDataScopeContractReplyFromPolicy,
|
||||
buildAssistantProactiveOrganizationOfferReply: assistantBoundaryPolicy.buildAssistantProactiveOrganizationOfferReply,
|
||||
buildAssistantOrganizationFactBoundaryReply: buildAssistantOrganizationFactBoundaryReplyFromPolicy,
|
||||
buildAssistantDataScopeSelectionReply: buildAssistantDataScopeSelectionReplyFromPolicy,
|
||||
buildAssistantOperationalBoundaryReply: buildAssistantOperationalBoundaryReplyFromPolicy,
|
||||
|
||||
@@ -35,6 +35,28 @@ function createAssistantTransitionPolicy(deps) {
|
||||
(hasRestatementCue || hasBareSnapshotSameDateCue || hasBareSnapshotSameDatePhraseCue) &&
|
||||
!deps.hasForeignAccountingPivotOverInventoryMessage(normalized));
|
||||
}
|
||||
function hasExplicitInventorySameDatePivotSignal(userMessage) {
|
||||
const normalized = deps
|
||||
.compactWhitespace(deps.repairAddressMojibake(String(userMessage ?? "")).toLowerCase())
|
||||
.replace(/ё/g, "е");
|
||||
if (!normalized) {
|
||||
return false;
|
||||
}
|
||||
const hasInventoryLexeme = /(?:остат|склад|товар|номенклатур|позиц)/iu.test(normalized);
|
||||
if (!hasInventoryLexeme) {
|
||||
return false;
|
||||
}
|
||||
const sameDatePhrases = [
|
||||
"на ту же дат",
|
||||
"на эту же дат",
|
||||
"на эту дат",
|
||||
"эту дат",
|
||||
"та же дата",
|
||||
"тот же период",
|
||||
"этот же период"
|
||||
];
|
||||
return sameDatePhrases.some((phrase) => normalized.includes(phrase));
|
||||
}
|
||||
function shouldKeepPreviousIntentForShortCounterpartyRetarget(userMessage, sourceIntent) {
|
||||
const normalized = deps.compactWhitespace(deps.repairAddressMojibake(String(userMessage ?? "")).toLowerCase());
|
||||
if (!normalized || deps.countTokens(normalized) > 4) {
|
||||
@@ -170,6 +192,10 @@ function createAssistantTransitionPolicy(deps) {
|
||||
const hasInventoryRootRestatementAlternate = deps.toNonEmptyString(alternateMessage)
|
||||
? hasInventoryRootRestatementLikeSignal(String(alternateMessage ?? ""), sourceIntentHint, Boolean(recentInventoryRootFrame))
|
||||
: false;
|
||||
const hasExplicitInventorySameDatePivotPrimary = hasExplicitInventorySameDatePivotSignal(userMessage);
|
||||
const hasExplicitInventorySameDatePivotAlternate = deps.toNonEmptyString(alternateMessage)
|
||||
? hasExplicitInventorySameDatePivotSignal(String(alternateMessage ?? ""))
|
||||
: false;
|
||||
let hasStrongFollowupReference = hasPrimaryIndexReferenceSignal ||
|
||||
hasAlternateIndexReferenceSignal ||
|
||||
hasOrganizationClarificationContinuation ||
|
||||
@@ -451,6 +477,9 @@ function createAssistantTransitionPolicy(deps) {
|
||||
currentFrameKind === "generic") &&
|
||||
(hasInventoryRootRestatementPrimary || hasInventoryRootRestatementAlternate) &&
|
||||
!deps.hasForeignAccountingPivotOverInventoryMessage(userMessage, alternateMessage));
|
||||
const explicitInventorySameDatePivot = Boolean(!inventoryRootFrame &&
|
||||
(hasExplicitInventorySameDatePivotPrimary || hasExplicitInventorySameDatePivotAlternate) &&
|
||||
!deps.hasForeignAccountingPivotOverInventoryMessage(userMessage, alternateMessage));
|
||||
const rootScopedPivot = rootContextOnlyPivot || inventoryRootTemporalPivot || inventoryRootRestatementPivot;
|
||||
if (rootScopedPivot) {
|
||||
previousIntent = null;
|
||||
@@ -540,7 +569,9 @@ function createAssistantTransitionPolicy(deps) {
|
||||
hasSelectedObjectInventorySignalAlternate));
|
||||
const carryoverTargetIntent = followupSelectionMode === "carry_root_context"
|
||||
? inventoryRootFrame?.intent ?? displayedEntityTargetIntent ?? explicitIntent ?? previousIntent ?? undefined
|
||||
: displayedEntityTargetIntent ?? explicitIntent ?? previousIntent ?? undefined;
|
||||
: explicitInventorySameDatePivot
|
||||
? "inventory_on_hand_as_of_date"
|
||||
: displayedEntityTargetIntent ?? explicitIntent ?? previousIntent ?? undefined;
|
||||
return {
|
||||
followupContext: {
|
||||
previous_intent: previousIntent ?? undefined,
|
||||
|
||||
@@ -51,6 +51,7 @@ function buildAssistantAddressAttemptRuntimeInput(runtimeInput, deps) {
|
||||
hasOperationalAdminActionRequestSignal: deps.hasOperationalAdminActionRequestSignal,
|
||||
hasOrganizationFactLookupSignal: deps.hasOrganizationFactLookupSignal,
|
||||
hasOrganizationFactFollowupSignal: deps.hasOrganizationFactFollowupSignal,
|
||||
hasLivingChatSignal: deps.hasLivingChatSignal,
|
||||
shouldEmitOrganizationSelectionReply: deps.shouldEmitOrganizationSelectionReply,
|
||||
hasAssistantCapabilityQuestionSignal: deps.hasAssistantCapabilityQuestionSignal,
|
||||
resolveDataScopeProbe: deps.resolveDataScopeProbe,
|
||||
@@ -58,6 +59,7 @@ function buildAssistantAddressAttemptRuntimeInput(runtimeInput, deps) {
|
||||
applyGroundingGuard: deps.applyGroundingGuard,
|
||||
buildAssistantSafetyRefusalReply: deps.buildAssistantSafetyRefusalReply,
|
||||
buildAssistantDataScopeContractReply: deps.buildAssistantDataScopeContractReply,
|
||||
buildAssistantProactiveOrganizationOfferReply: deps.buildAssistantProactiveOrganizationOfferReply,
|
||||
buildAssistantOrganizationFactBoundaryReply: deps.buildAssistantOrganizationFactBoundaryReply,
|
||||
buildAssistantDataScopeSelectionReply: deps.buildAssistantDataScopeSelectionReply,
|
||||
buildAssistantOperationalBoundaryReply: deps.buildAssistantOperationalBoundaryReply,
|
||||
|
||||
@@ -62,6 +62,7 @@ export interface RunAssistantAddressAttemptRuntimeInput<ResponseType = unknown>
|
||||
hasOperationalAdminActionRequestSignal: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["hasOperationalAdminActionRequestSignal"];
|
||||
hasOrganizationFactLookupSignal: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["hasOrganizationFactLookupSignal"];
|
||||
hasOrganizationFactFollowupSignal: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["hasOrganizationFactFollowupSignal"];
|
||||
hasLivingChatSignal: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["hasLivingChatSignal"];
|
||||
shouldEmitOrganizationSelectionReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["shouldEmitOrganizationSelectionReply"];
|
||||
hasAssistantCapabilityQuestionSignal: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["hasAssistantCapabilityQuestionSignal"];
|
||||
resolveDataScopeProbe: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["resolveDataScopeProbe"];
|
||||
@@ -69,6 +70,8 @@ export interface RunAssistantAddressAttemptRuntimeInput<ResponseType = unknown>
|
||||
applyGroundingGuard: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["applyGroundingGuard"];
|
||||
buildAssistantSafetyRefusalReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantSafetyRefusalReply"];
|
||||
buildAssistantDataScopeContractReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantDataScopeContractReply"];
|
||||
buildAssistantProactiveOrganizationOfferReply:
|
||||
RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantProactiveOrganizationOfferReply"];
|
||||
buildAssistantOrganizationFactBoundaryReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantOrganizationFactBoundaryReply"];
|
||||
buildAssistantDataScopeSelectionReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantDataScopeSelectionReply"];
|
||||
buildAssistantOperationalBoundaryReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantOperationalBoundaryReply"];
|
||||
@@ -179,6 +182,7 @@ export async function runAssistantAddressAttemptRuntime<ResponseType = unknown>(
|
||||
hasOperationalAdminActionRequestSignal: input.hasOperationalAdminActionRequestSignal,
|
||||
hasOrganizationFactLookupSignal: input.hasOrganizationFactLookupSignal,
|
||||
hasOrganizationFactFollowupSignal: input.hasOrganizationFactFollowupSignal,
|
||||
hasLivingChatSignal: input.hasLivingChatSignal,
|
||||
shouldEmitOrganizationSelectionReply: input.shouldEmitOrganizationSelectionReply,
|
||||
hasAssistantCapabilityQuestionSignal: input.hasAssistantCapabilityQuestionSignal,
|
||||
resolveDataScopeProbe: input.resolveDataScopeProbe,
|
||||
@@ -186,6 +190,7 @@ export async function runAssistantAddressAttemptRuntime<ResponseType = unknown>(
|
||||
applyGroundingGuard: input.applyGroundingGuard,
|
||||
buildAssistantSafetyRefusalReply: input.buildAssistantSafetyRefusalReply,
|
||||
buildAssistantDataScopeContractReply: input.buildAssistantDataScopeContractReply,
|
||||
buildAssistantProactiveOrganizationOfferReply: input.buildAssistantProactiveOrganizationOfferReply,
|
||||
buildAssistantOrganizationFactBoundaryReply: input.buildAssistantOrganizationFactBoundaryReply,
|
||||
buildAssistantDataScopeSelectionReply: input.buildAssistantDataScopeSelectionReply,
|
||||
buildAssistantOperationalBoundaryReply: input.buildAssistantOperationalBoundaryReply,
|
||||
|
||||
@@ -19,6 +19,7 @@ export interface AssistantBoundaryPolicyGroundingGuardInput {
|
||||
|
||||
export interface AssistantBoundaryPolicy {
|
||||
buildAssistantDataScopeContractReply: (scopeProbe?: Record<string, unknown> | null) => string;
|
||||
buildAssistantProactiveOrganizationOfferReply: (scopeProbe?: Record<string, unknown> | null) => string;
|
||||
buildAssistantDataScopeSelectionReply: (organization: unknown) => string;
|
||||
buildAssistantOrganizationFactBoundaryReply: (organization: unknown) => string;
|
||||
buildAssistantOperationalBoundaryReply: () => string;
|
||||
@@ -90,6 +91,32 @@ export function createAssistantBoundaryPolicy(deps: AssistantBoundaryPolicyDeps)
|
||||
].join(" ");
|
||||
}
|
||||
|
||||
function buildAssistantProactiveOrganizationOfferReply(scopeProbe: Record<string, unknown> | null = null): string {
|
||||
const organizations = Array.isArray(scopeProbe?.organizations)
|
||||
? scopeProbe.organizations
|
||||
.map((item) => normalizeSelectedOrganization(item, deps.normalizeOrganizationScopeValue))
|
||||
.filter((item) => item.length > 0)
|
||||
.filter((item, index, array) => array.indexOf(item) === index)
|
||||
: [];
|
||||
|
||||
if (organizations.length === 1) {
|
||||
return [
|
||||
`Если дальше пойдём в данные 1С, могу сразу держать в контуре ${organizations[0]}.`,
|
||||
"Можно просто писать вопрос по документам, остаткам, НДС или контрагентам."
|
||||
].join(" ");
|
||||
}
|
||||
|
||||
if (organizations.length > 1) {
|
||||
const preview = organizations.slice(0, 10).join(", ");
|
||||
return [
|
||||
`Если дальше пойдём в данные 1С, могу сразу зафиксировать организацию: ${preview}.`,
|
||||
"Просто напишите название компании, и дальше буду держать её активной в этой сессии."
|
||||
].join(" ");
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
function buildAssistantDataScopeSelectionReply(organization: unknown): string {
|
||||
const selected = normalizeSelectedOrganization(organization, deps.normalizeOrganizationScopeValue);
|
||||
return [
|
||||
@@ -219,6 +246,7 @@ export function createAssistantBoundaryPolicy(deps: AssistantBoundaryPolicyDeps)
|
||||
|
||||
return {
|
||||
buildAssistantDataScopeContractReply,
|
||||
buildAssistantProactiveOrganizationOfferReply,
|
||||
buildAssistantDataScopeSelectionReply,
|
||||
buildAssistantOrganizationFactBoundaryReply,
|
||||
buildAssistantOperationalBoundaryReply,
|
||||
|
||||
@@ -22,6 +22,7 @@ export interface BuildAssistantLivingChatAttemptRuntimeInputInput<ResponseType =
|
||||
hasOperationalAdminActionRequestSignal: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["hasOperationalAdminActionRequestSignal"];
|
||||
hasOrganizationFactLookupSignal: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["hasOrganizationFactLookupSignal"];
|
||||
hasOrganizationFactFollowupSignal: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["hasOrganizationFactFollowupSignal"];
|
||||
hasLivingChatSignal: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["hasLivingChatSignal"];
|
||||
shouldEmitOrganizationSelectionReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["shouldEmitOrganizationSelectionReply"];
|
||||
hasAssistantCapabilityQuestionSignal: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["hasAssistantCapabilityQuestionSignal"];
|
||||
resolveDataScopeProbe: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["resolveDataScopeProbe"];
|
||||
@@ -29,6 +30,8 @@ export interface BuildAssistantLivingChatAttemptRuntimeInputInput<ResponseType =
|
||||
applyGroundingGuard: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["applyGroundingGuard"];
|
||||
buildAssistantSafetyRefusalReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantSafetyRefusalReply"];
|
||||
buildAssistantDataScopeContractReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantDataScopeContractReply"];
|
||||
buildAssistantProactiveOrganizationOfferReply:
|
||||
RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantProactiveOrganizationOfferReply"];
|
||||
buildAssistantOrganizationFactBoundaryReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantOrganizationFactBoundaryReply"];
|
||||
buildAssistantDataScopeSelectionReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantDataScopeSelectionReply"];
|
||||
buildAssistantOperationalBoundaryReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantOperationalBoundaryReply"];
|
||||
@@ -73,6 +76,7 @@ export function buildAssistantLivingChatAttemptRuntimeInput<ResponseType = unkno
|
||||
hasOperationalAdminActionRequestSignal: input.hasOperationalAdminActionRequestSignal,
|
||||
hasOrganizationFactLookupSignal: input.hasOrganizationFactLookupSignal,
|
||||
hasOrganizationFactFollowupSignal: input.hasOrganizationFactFollowupSignal,
|
||||
hasLivingChatSignal: input.hasLivingChatSignal,
|
||||
shouldEmitOrganizationSelectionReply: input.shouldEmitOrganizationSelectionReply,
|
||||
hasAssistantCapabilityQuestionSignal: input.hasAssistantCapabilityQuestionSignal,
|
||||
resolveDataScopeProbe: input.resolveDataScopeProbe,
|
||||
@@ -80,6 +84,7 @@ export function buildAssistantLivingChatAttemptRuntimeInput<ResponseType = unkno
|
||||
applyGroundingGuard: input.applyGroundingGuard,
|
||||
buildAssistantSafetyRefusalReply: input.buildAssistantSafetyRefusalReply,
|
||||
buildAssistantDataScopeContractReply: input.buildAssistantDataScopeContractReply,
|
||||
buildAssistantProactiveOrganizationOfferReply: input.buildAssistantProactiveOrganizationOfferReply,
|
||||
buildAssistantOrganizationFactBoundaryReply: input.buildAssistantOrganizationFactBoundaryReply,
|
||||
buildAssistantDataScopeSelectionReply: input.buildAssistantDataScopeSelectionReply,
|
||||
buildAssistantOperationalBoundaryReply: input.buildAssistantOperationalBoundaryReply,
|
||||
|
||||
@@ -75,6 +75,7 @@ export async function runAssistantLivingChatAttemptRuntime<ResponseType = unknow
|
||||
hasOperationalAdminActionRequestSignal: input.hasOperationalAdminActionRequestSignal,
|
||||
hasOrganizationFactLookupSignal: input.hasOrganizationFactLookupSignal,
|
||||
hasOrganizationFactFollowupSignal: input.hasOrganizationFactFollowupSignal,
|
||||
hasLivingChatSignal: input.hasLivingChatSignal,
|
||||
shouldEmitOrganizationSelectionReply: input.shouldEmitOrganizationSelectionReply,
|
||||
hasAssistantCapabilityQuestionSignal: input.hasAssistantCapabilityQuestionSignal,
|
||||
resolveDataScopeProbe: input.resolveDataScopeProbe,
|
||||
@@ -83,6 +84,7 @@ export async function runAssistantLivingChatAttemptRuntime<ResponseType = unknow
|
||||
applyGroundingGuard: input.applyGroundingGuard,
|
||||
buildAssistantSafetyRefusalReply: input.buildAssistantSafetyRefusalReply,
|
||||
buildAssistantDataScopeContractReply: input.buildAssistantDataScopeContractReply,
|
||||
buildAssistantProactiveOrganizationOfferReply: input.buildAssistantProactiveOrganizationOfferReply,
|
||||
buildAssistantOrganizationFactBoundaryReply: input.buildAssistantOrganizationFactBoundaryReply,
|
||||
buildAssistantDataScopeSelectionReply: input.buildAssistantDataScopeSelectionReply,
|
||||
buildAssistantOperationalBoundaryReply: input.buildAssistantOperationalBoundaryReply,
|
||||
|
||||
@@ -56,6 +56,7 @@ export function buildAssistantLivingChatHandlerRuntimeInput<ResponseType = unkno
|
||||
hasOperationalAdminActionRequestSignal: input.hasOperationalAdminActionRequestSignal,
|
||||
hasOrganizationFactLookupSignal: input.hasOrganizationFactLookupSignal,
|
||||
hasOrganizationFactFollowupSignal: input.hasOrganizationFactFollowupSignal,
|
||||
hasLivingChatSignal: input.hasLivingChatSignal,
|
||||
shouldEmitOrganizationSelectionReply: input.shouldEmitOrganizationSelectionReply,
|
||||
hasAssistantCapabilityQuestionSignal: input.hasAssistantCapabilityQuestionSignal,
|
||||
resolveDataScopeProbe: input.resolveDataScopeProbe,
|
||||
@@ -64,6 +65,7 @@ export function buildAssistantLivingChatHandlerRuntimeInput<ResponseType = unkno
|
||||
applyGroundingGuard: input.applyGroundingGuard,
|
||||
buildAssistantSafetyRefusalReply: input.buildAssistantSafetyRefusalReply,
|
||||
buildAssistantDataScopeContractReply: input.buildAssistantDataScopeContractReply,
|
||||
buildAssistantProactiveOrganizationOfferReply: input.buildAssistantProactiveOrganizationOfferReply,
|
||||
buildAssistantOrganizationFactBoundaryReply: input.buildAssistantOrganizationFactBoundaryReply,
|
||||
buildAssistantDataScopeSelectionReply: input.buildAssistantDataScopeSelectionReply,
|
||||
buildAssistantOperationalBoundaryReply: input.buildAssistantOperationalBoundaryReply,
|
||||
|
||||
@@ -25,6 +25,7 @@ export interface TryHandleAssistantLivingChatRuntimeInput<ResponseType = unknown
|
||||
hasOperationalAdminActionRequestSignal: AssistantLivingChatRuntimeInput["hasOperationalAdminActionRequestSignal"];
|
||||
hasOrganizationFactLookupSignal: AssistantLivingChatRuntimeInput["hasOrganizationFactLookupSignal"];
|
||||
hasOrganizationFactFollowupSignal: AssistantLivingChatRuntimeInput["hasOrganizationFactFollowupSignal"];
|
||||
hasLivingChatSignal: AssistantLivingChatRuntimeInput["hasLivingChatSignal"];
|
||||
shouldEmitOrganizationSelectionReply: AssistantLivingChatRuntimeInput["shouldEmitOrganizationSelectionReply"];
|
||||
hasAssistantCapabilityQuestionSignal: AssistantLivingChatRuntimeInput["hasAssistantCapabilityQuestionSignal"];
|
||||
resolveDataScopeProbe: AssistantLivingChatRuntimeInput["resolveDataScopeProbe"];
|
||||
@@ -33,6 +34,7 @@ export interface TryHandleAssistantLivingChatRuntimeInput<ResponseType = unknown
|
||||
applyGroundingGuard: AssistantLivingChatRuntimeInput["applyGroundingGuard"];
|
||||
buildAssistantSafetyRefusalReply: AssistantLivingChatRuntimeInput["buildAssistantSafetyRefusalReply"];
|
||||
buildAssistantDataScopeContractReply: AssistantLivingChatRuntimeInput["buildAssistantDataScopeContractReply"];
|
||||
buildAssistantProactiveOrganizationOfferReply: AssistantLivingChatRuntimeInput["buildAssistantProactiveOrganizationOfferReply"];
|
||||
buildAssistantOrganizationFactBoundaryReply: AssistantLivingChatRuntimeInput["buildAssistantOrganizationFactBoundaryReply"];
|
||||
buildAssistantDataScopeSelectionReply: AssistantLivingChatRuntimeInput["buildAssistantDataScopeSelectionReply"];
|
||||
buildAssistantOperationalBoundaryReply: AssistantLivingChatRuntimeInput["buildAssistantOperationalBoundaryReply"];
|
||||
@@ -76,6 +78,7 @@ export async function tryHandleAssistantLivingChatRuntime<ResponseType = unknown
|
||||
hasOperationalAdminActionRequestSignal: input.hasOperationalAdminActionRequestSignal,
|
||||
hasOrganizationFactLookupSignal: input.hasOrganizationFactLookupSignal,
|
||||
hasOrganizationFactFollowupSignal: input.hasOrganizationFactFollowupSignal,
|
||||
hasLivingChatSignal: input.hasLivingChatSignal,
|
||||
shouldEmitOrganizationSelectionReply: input.shouldEmitOrganizationSelectionReply,
|
||||
hasAssistantCapabilityQuestionSignal: input.hasAssistantCapabilityQuestionSignal,
|
||||
resolveDataScopeProbe: input.resolveDataScopeProbe,
|
||||
@@ -84,6 +87,7 @@ export async function tryHandleAssistantLivingChatRuntime<ResponseType = unknown
|
||||
applyGroundingGuard: input.applyGroundingGuard,
|
||||
buildAssistantSafetyRefusalReply: input.buildAssistantSafetyRefusalReply,
|
||||
buildAssistantDataScopeContractReply: input.buildAssistantDataScopeContractReply,
|
||||
buildAssistantProactiveOrganizationOfferReply: input.buildAssistantProactiveOrganizationOfferReply,
|
||||
buildAssistantOrganizationFactBoundaryReply: input.buildAssistantOrganizationFactBoundaryReply,
|
||||
buildAssistantDataScopeSelectionReply: input.buildAssistantDataScopeSelectionReply,
|
||||
buildAssistantOperationalBoundaryReply: input.buildAssistantOperationalBoundaryReply,
|
||||
|
||||
@@ -31,6 +31,7 @@ export interface AssistantLivingChatRuntimeInput {
|
||||
hasOperationalAdminActionRequestSignal: (message: string) => boolean;
|
||||
hasOrganizationFactLookupSignal: (message: string) => boolean;
|
||||
hasOrganizationFactFollowupSignal: (message: string, items: unknown[]) => boolean;
|
||||
hasLivingChatSignal: (message: string) => boolean;
|
||||
shouldEmitOrganizationSelectionReply: (message: string, activeOrganization: string | null) => boolean;
|
||||
hasAssistantCapabilityQuestionSignal: (message: string) => boolean;
|
||||
resolveDataScopeProbe: () => Promise<Record<string, unknown> | null>;
|
||||
@@ -51,6 +52,7 @@ export interface AssistantLivingChatRuntimeInput {
|
||||
};
|
||||
buildAssistantSafetyRefusalReply: () => string;
|
||||
buildAssistantDataScopeContractReply: (scopeProbe: Record<string, unknown> | null) => string;
|
||||
buildAssistantProactiveOrganizationOfferReply: (scopeProbe: Record<string, unknown> | null) => string;
|
||||
buildAssistantOrganizationFactBoundaryReply: (organization: string | null) => string;
|
||||
buildAssistantDataScopeSelectionReply: (organization: string | null) => string;
|
||||
buildAssistantOperationalBoundaryReply: () => string;
|
||||
@@ -134,6 +136,13 @@ function findLastAddressDebugWithItem(items: unknown[]): Record<string, unknown>
|
||||
return null;
|
||||
}
|
||||
|
||||
function hasPriorAssistantTurn(items: unknown[]): boolean {
|
||||
if (!Array.isArray(items)) {
|
||||
return false;
|
||||
}
|
||||
return items.some((item) => item && typeof item === "object" && (item as { role?: string }).role === "assistant");
|
||||
}
|
||||
|
||||
function findLastAddressDebug(items: unknown[]): Record<string, unknown> | null {
|
||||
if (!Array.isArray(items)) {
|
||||
return null;
|
||||
@@ -252,6 +261,7 @@ export async function runAssistantLivingChatRuntime(
|
||||
let livingChatScriptGuardReason: string | null = null;
|
||||
let livingChatGroundingGuardApplied = false;
|
||||
let livingChatGroundingGuardReason: string | null = 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);
|
||||
@@ -348,6 +358,33 @@ export async function runAssistantLivingChatRuntime(
|
||||
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 as unknown[]) : [])
|
||||
]);
|
||||
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) {
|
||||
@@ -385,6 +422,7 @@ export async function runAssistantLivingChatRuntime(
|
||||
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)
|
||||
|
||||
@@ -4981,6 +4981,7 @@ export class AssistantService {
|
||||
hasOperationalAdminActionRequestSignal,
|
||||
hasOrganizationFactLookupSignal,
|
||||
hasOrganizationFactFollowupSignal,
|
||||
hasLivingChatSignal,
|
||||
shouldEmitOrganizationSelectionReply,
|
||||
hasAssistantCapabilityQuestionSignal,
|
||||
resolveDataScopeProbe: () => resolveAssistantDataScopeProbe(),
|
||||
@@ -4988,6 +4989,7 @@ export class AssistantService {
|
||||
applyGroundingGuard: applyLivingChatGroundingGuardFromPolicy,
|
||||
buildAssistantSafetyRefusalReply: buildAssistantSafetyRefusalReplyFromPolicy,
|
||||
buildAssistantDataScopeContractReply: buildAssistantDataScopeContractReplyFromPolicy,
|
||||
buildAssistantProactiveOrganizationOfferReply: assistantBoundaryPolicy.buildAssistantProactiveOrganizationOfferReply,
|
||||
buildAssistantOrganizationFactBoundaryReply: buildAssistantOrganizationFactBoundaryReplyFromPolicy,
|
||||
buildAssistantDataScopeSelectionReply: buildAssistantDataScopeSelectionReplyFromPolicy,
|
||||
buildAssistantOperationalBoundaryReply: buildAssistantOperationalBoundaryReplyFromPolicy,
|
||||
|
||||
@@ -43,6 +43,29 @@ export function createAssistantTransitionPolicy(deps) {
|
||||
);
|
||||
}
|
||||
|
||||
function hasExplicitInventorySameDatePivotSignal(userMessage) {
|
||||
const normalized = deps
|
||||
.compactWhitespace(deps.repairAddressMojibake(String(userMessage ?? "")).toLowerCase())
|
||||
.replace(/ё/g, "е");
|
||||
if (!normalized) {
|
||||
return false;
|
||||
}
|
||||
const hasInventoryLexeme = /(?:остат|склад|товар|номенклатур|позиц)/iu.test(normalized);
|
||||
if (!hasInventoryLexeme) {
|
||||
return false;
|
||||
}
|
||||
const sameDatePhrases = [
|
||||
"на ту же дат",
|
||||
"на эту же дат",
|
||||
"на эту дат",
|
||||
"эту дат",
|
||||
"та же дата",
|
||||
"тот же период",
|
||||
"этот же период"
|
||||
];
|
||||
return sameDatePhrases.some((phrase) => normalized.includes(phrase));
|
||||
}
|
||||
|
||||
function shouldKeepPreviousIntentForShortCounterpartyRetarget(userMessage, sourceIntent) {
|
||||
const normalized = deps.compactWhitespace(
|
||||
deps.repairAddressMojibake(String(userMessage ?? "")).toLowerCase()
|
||||
@@ -227,6 +250,10 @@ export function createAssistantTransitionPolicy(deps) {
|
||||
Boolean(recentInventoryRootFrame)
|
||||
)
|
||||
: false;
|
||||
const hasExplicitInventorySameDatePivotPrimary = hasExplicitInventorySameDatePivotSignal(userMessage);
|
||||
const hasExplicitInventorySameDatePivotAlternate = deps.toNonEmptyString(alternateMessage)
|
||||
? hasExplicitInventorySameDatePivotSignal(String(alternateMessage ?? ""))
|
||||
: false;
|
||||
let hasStrongFollowupReference =
|
||||
hasPrimaryIndexReferenceSignal ||
|
||||
hasAlternateIndexReferenceSignal ||
|
||||
@@ -550,6 +577,11 @@ export function createAssistantTransitionPolicy(deps) {
|
||||
(hasInventoryRootRestatementPrimary || hasInventoryRootRestatementAlternate) &&
|
||||
!deps.hasForeignAccountingPivotOverInventoryMessage(userMessage, alternateMessage)
|
||||
);
|
||||
const explicitInventorySameDatePivot = Boolean(
|
||||
!inventoryRootFrame &&
|
||||
(hasExplicitInventorySameDatePivotPrimary || hasExplicitInventorySameDatePivotAlternate) &&
|
||||
!deps.hasForeignAccountingPivotOverInventoryMessage(userMessage, alternateMessage)
|
||||
);
|
||||
const rootScopedPivot = rootContextOnlyPivot || inventoryRootTemporalPivot || inventoryRootRestatementPivot;
|
||||
if (rootScopedPivot) {
|
||||
previousIntent = null;
|
||||
@@ -651,7 +683,9 @@ export function createAssistantTransitionPolicy(deps) {
|
||||
const carryoverTargetIntent =
|
||||
followupSelectionMode === "carry_root_context"
|
||||
? inventoryRootFrame?.intent ?? displayedEntityTargetIntent ?? explicitIntent ?? previousIntent ?? undefined
|
||||
: displayedEntityTargetIntent ?? explicitIntent ?? previousIntent ?? undefined;
|
||||
: explicitInventorySameDatePivot
|
||||
? "inventory_on_hand_as_of_date"
|
||||
: displayedEntityTargetIntent ?? explicitIntent ?? previousIntent ?? undefined;
|
||||
return {
|
||||
followupContext: {
|
||||
previous_intent: previousIntent ?? undefined,
|
||||
|
||||
@@ -67,6 +67,7 @@ export interface AssistantTurnRuntimeBuilderDeps<ResponseType = unknown> {
|
||||
AddressAttemptRuntimeInput<ResponseType>["hasOperationalAdminActionRequestSignal"];
|
||||
hasOrganizationFactLookupSignal: AddressAttemptRuntimeInput<ResponseType>["hasOrganizationFactLookupSignal"];
|
||||
hasOrganizationFactFollowupSignal: AddressAttemptRuntimeInput<ResponseType>["hasOrganizationFactFollowupSignal"];
|
||||
hasLivingChatSignal: AddressAttemptRuntimeInput<ResponseType>["hasLivingChatSignal"];
|
||||
shouldEmitOrganizationSelectionReply:
|
||||
AddressAttemptRuntimeInput<ResponseType>["shouldEmitOrganizationSelectionReply"];
|
||||
hasAssistantCapabilityQuestionSignal:
|
||||
@@ -77,6 +78,8 @@ export interface AssistantTurnRuntimeBuilderDeps<ResponseType = unknown> {
|
||||
buildAssistantSafetyRefusalReply: AddressAttemptRuntimeInput<ResponseType>["buildAssistantSafetyRefusalReply"];
|
||||
buildAssistantDataScopeContractReply:
|
||||
AddressAttemptRuntimeInput<ResponseType>["buildAssistantDataScopeContractReply"];
|
||||
buildAssistantProactiveOrganizationOfferReply:
|
||||
AddressAttemptRuntimeInput<ResponseType>["buildAssistantProactiveOrganizationOfferReply"];
|
||||
buildAssistantOrganizationFactBoundaryReply:
|
||||
AddressAttemptRuntimeInput<ResponseType>["buildAssistantOrganizationFactBoundaryReply"];
|
||||
buildAssistantDataScopeSelectionReply:
|
||||
@@ -164,6 +167,7 @@ export function buildAssistantAddressAttemptRuntimeInput<ResponseType = unknown>
|
||||
hasOperationalAdminActionRequestSignal: deps.hasOperationalAdminActionRequestSignal,
|
||||
hasOrganizationFactLookupSignal: deps.hasOrganizationFactLookupSignal,
|
||||
hasOrganizationFactFollowupSignal: deps.hasOrganizationFactFollowupSignal,
|
||||
hasLivingChatSignal: deps.hasLivingChatSignal,
|
||||
shouldEmitOrganizationSelectionReply: deps.shouldEmitOrganizationSelectionReply,
|
||||
hasAssistantCapabilityQuestionSignal: deps.hasAssistantCapabilityQuestionSignal,
|
||||
resolveDataScopeProbe: deps.resolveDataScopeProbe,
|
||||
@@ -171,6 +175,7 @@ export function buildAssistantAddressAttemptRuntimeInput<ResponseType = unknown>
|
||||
applyGroundingGuard: deps.applyGroundingGuard,
|
||||
buildAssistantSafetyRefusalReply: deps.buildAssistantSafetyRefusalReply,
|
||||
buildAssistantDataScopeContractReply: deps.buildAssistantDataScopeContractReply,
|
||||
buildAssistantProactiveOrganizationOfferReply: deps.buildAssistantProactiveOrganizationOfferReply,
|
||||
buildAssistantOrganizationFactBoundaryReply: deps.buildAssistantOrganizationFactBoundaryReply,
|
||||
buildAssistantDataScopeSelectionReply: deps.buildAssistantDataScopeSelectionReply,
|
||||
buildAssistantOperationalBoundaryReply: deps.buildAssistantOperationalBoundaryReply,
|
||||
|
||||
@@ -56,6 +56,23 @@ describe("assistantBoundaryPolicy", () => {
|
||||
expect(reply).not.toContain("\\");
|
||||
});
|
||||
|
||||
it("builds proactive organization offer without technical labels", () => {
|
||||
const policy = createPolicy();
|
||||
|
||||
const reply = policy.buildAssistantProactiveOrganizationOfferReply({
|
||||
status: "resolved",
|
||||
channel: "default",
|
||||
organizations: ["ООО Альтернатива Плюс", "ООО Лайсвуд", "РАЙМ"]
|
||||
});
|
||||
|
||||
expect(reply).toContain("Если дальше пойдём в данные 1С");
|
||||
expect(reply).toContain("ООО Альтернатива Плюс");
|
||||
expect(reply).toContain("ООО Лайсвуд");
|
||||
expect(reply).toContain("РАЙМ");
|
||||
expect(reply).not.toContain("MCP");
|
||||
expect(reply.toLowerCase()).not.toContain("snapshot");
|
||||
});
|
||||
|
||||
it("strips unexpected CJK fragments from live chat reply", () => {
|
||||
const policy = createPolicy();
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ function buildInput(overrides: Record<string, unknown> = {}) {
|
||||
hasOperationalAdminActionRequestSignal: () => false,
|
||||
hasOrganizationFactLookupSignal: () => false,
|
||||
hasOrganizationFactFollowupSignal: () => false,
|
||||
hasLivingChatSignal: () => true,
|
||||
shouldEmitOrganizationSelectionReply: () => false,
|
||||
hasAssistantCapabilityQuestionSignal: () => false,
|
||||
resolveDataScopeProbe: async () => null,
|
||||
|
||||
@@ -37,6 +37,7 @@ function buildRuntimeInput(overrides: Record<string, unknown> = {}) {
|
||||
hasOperationalAdminActionRequestSignal: () => false,
|
||||
hasOrganizationFactLookupSignal: () => false,
|
||||
hasOrganizationFactFollowupSignal: () => false,
|
||||
hasLivingChatSignal: () => true,
|
||||
shouldEmitOrganizationSelectionReply: () => false,
|
||||
hasAssistantCapabilityQuestionSignal: () => false,
|
||||
resolveDataScopeProbe,
|
||||
@@ -53,6 +54,7 @@ function buildRuntimeInput(overrides: Record<string, unknown> = {}) {
|
||||
}),
|
||||
buildAssistantSafetyRefusalReply: () => "safety",
|
||||
buildAssistantDataScopeContractReply: () => "scope",
|
||||
buildAssistantProactiveOrganizationOfferReply: () => "",
|
||||
buildAssistantOrganizationFactBoundaryReply: () => "org-boundary",
|
||||
buildAssistantDataScopeSelectionReply: () => "org-selection",
|
||||
buildAssistantOperationalBoundaryReply: () => "ops",
|
||||
@@ -134,6 +136,56 @@ describe("assistant living chat runtime adapter", () => {
|
||||
expect(executeLlmChat).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("adds proactive organization offer on first smalltalk turn when multiple organizations are available", async () => {
|
||||
const resolveDataScopeProbe = vi.fn(async () => ({
|
||||
status: "resolved",
|
||||
channel: "default",
|
||||
organizations: ["ООО Альтернатива Плюс", "ООО Лайсвуд", "РАЙМ"],
|
||||
error: null
|
||||
}));
|
||||
const input = buildRuntimeInput({
|
||||
userMessage: "привет, как дела?",
|
||||
resolveDataScopeProbe,
|
||||
buildAssistantProactiveOrganizationOfferReply: (scopeProbe: Record<string, unknown> | null) => {
|
||||
const organizations = Array.isArray(scopeProbe?.organizations) ? scopeProbe.organizations.join(", ") : "";
|
||||
return `offer:${organizations}`;
|
||||
}
|
||||
});
|
||||
|
||||
const output = await runAssistantLivingChatRuntime(input);
|
||||
|
||||
expect(output.handled).toBe(true);
|
||||
expect(output.chatText).toContain("llm-text");
|
||||
expect(output.chatText).toContain("offer:ООО Альтернатива Плюс, ООО Лайсвуд, РАЙМ");
|
||||
expect(output.debug?.living_chat_response_source).toBe("llm_chat_with_proactive_scope_offer");
|
||||
expect(output.debug?.living_chat_proactive_scope_offer_applied).toBe(true);
|
||||
expect(output.debug?.living_chat_data_scope_probe_org_count).toBe(3);
|
||||
expect(output.debug?.assistant_known_organizations).toEqual(["ООО Альтернатива Плюс", "ООО Лайсвуд", "РАЙМ"]);
|
||||
});
|
||||
|
||||
it("does not add proactive organization offer after the session already has assistant context", async () => {
|
||||
const resolveDataScopeProbe = vi.fn(async () => ({
|
||||
status: "resolved",
|
||||
channel: "default",
|
||||
organizations: ["ООО Альтернатива Плюс", "ООО Лайсвуд"],
|
||||
error: null
|
||||
}));
|
||||
const input = buildRuntimeInput({
|
||||
userMessage: "привет еще раз",
|
||||
sessionItems: [{ role: "assistant", text: "Ранее уже отвечал." }],
|
||||
resolveDataScopeProbe,
|
||||
buildAssistantProactiveOrganizationOfferReply: () => "offer"
|
||||
});
|
||||
|
||||
const output = await runAssistantLivingChatRuntime(input);
|
||||
|
||||
expect(output.handled).toBe(true);
|
||||
expect(output.chatText).toBe("llm-text");
|
||||
expect(output.debug?.living_chat_response_source).toBe("llm_chat");
|
||||
expect(output.debug?.living_chat_proactive_scope_offer_applied).toBe(false);
|
||||
expect(resolveDataScopeProbe).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("builds deterministic memory recap for prior selected-object address context", async () => {
|
||||
const executeLlmChat = vi.fn(async () => "raw-llm");
|
||||
const input = buildRuntimeInput({
|
||||
@@ -144,6 +196,9 @@ describe("assistant living chat runtime adapter", () => {
|
||||
role: "assistant",
|
||||
debug: {
|
||||
execution_lane: "address_query",
|
||||
answer_grounding_check: {
|
||||
status: "grounded"
|
||||
},
|
||||
detected_intent: "inventory_purchase_provenance_for_item",
|
||||
extracted_filters: {
|
||||
item: "Зеркало для инвалидов поворотное травмобезопасное",
|
||||
@@ -160,7 +215,7 @@ describe("assistant living chat runtime adapter", () => {
|
||||
|
||||
expect(output.handled).toBe(true);
|
||||
expect(output.chatText).toContain("Зеркало для инвалидов поворотное травмобезопасное");
|
||||
expect(output.chatText).toContain("кто поставил");
|
||||
expect(output.chatText).toContain("кто поставлял");
|
||||
expect(output.debug?.living_chat_response_source).toBe("deterministic_memory_recap_contract");
|
||||
expect(executeLlmChat).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -212,6 +212,48 @@ describe("assistantTransitionPolicy", () => {
|
||||
expect(contract.decision).toBe("continue_previous");
|
||||
});
|
||||
|
||||
it("retargets same-date inventory follow-up away from receivables intent", () => {
|
||||
const policy = buildPolicy({
|
||||
findLastAddressAssistantItem: () => ({
|
||||
text: "Подтвержденная дебиторская задолженность на 31.03.2020 собрана.",
|
||||
debug: {
|
||||
detected_intent: "receivables_confirmed_as_of_date",
|
||||
extracted_filters: {
|
||||
organization: 'ООО "Альтернатива Плюс"',
|
||||
as_of_date: "2020-03-31",
|
||||
period_from: "2020-03-01",
|
||||
period_to: "2020-03-31"
|
||||
},
|
||||
anchor_type: "organization",
|
||||
anchor_value_resolved: 'ООО "Альтернатива Плюс"'
|
||||
}
|
||||
}),
|
||||
hasAddressFollowupContextSignal: () => true,
|
||||
hasReferentialPointer: () => true,
|
||||
findRecentInventoryRootFrame: () => null,
|
||||
resolveAddressIntent: () => ({ intent: "unknown" })
|
||||
});
|
||||
|
||||
const carryover = policy.resolveAddressFollowupCarryoverContext(
|
||||
"остатки по складу на эту же дату",
|
||||
[],
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
expect(carryover?.followupSelectionMode).toBe("carry_previous_intent");
|
||||
expect(carryover?.followupContext?.previous_intent).toBe("receivables_confirmed_as_of_date");
|
||||
expect(carryover?.followupContext?.target_intent).toBe("inventory_on_hand_as_of_date");
|
||||
expect(carryover?.followupContext?.previous_filters).toMatchObject({
|
||||
organization: 'ООО "Альтернатива Плюс"',
|
||||
as_of_date: "2020-03-31",
|
||||
period_from: "2020-03-01",
|
||||
period_to: "2020-03-31"
|
||||
});
|
||||
expect(carryover?.followupContext?.root_context_only).toBeUndefined();
|
||||
});
|
||||
|
||||
it("drops stale carryover for a fresh standalone topic from another intent family", () => {
|
||||
const policy = buildPolicy({
|
||||
findLastAddressAssistantItem: () => ({
|
||||
|
||||
Reference in New Issue
Block a user