АРЧ - Ассистент: отделить reference-срез от execution-окна в ответах по lifecycle follow-up
This commit is contained in:
@@ -1352,6 +1352,10 @@ function hasSelectedObjectInventoryPurchaseDocumentsSignal(text) {
|
||||
return (hasSelectedObjectInventoryCue(text) &&
|
||||
/(?:по\s+каким\s+документам\s+(?:это|его|этот\s+товар|эту\s+позицию)\s+купили|по\s+каким\s+документам\s+(?:был\s+)?куплен|какими\s+документами\s+(?:это|его|этот\s+товар|эту\s+позицию)\s+купили|какими\s+документами\s+(?:был\s+)?куплен|purchase\s+documents|documents\s+of\s+purchase|through\s+which\s+documents)/iu.test(text));
|
||||
}
|
||||
function hasSelectedObjectInventorySaleTraceSignal(text) {
|
||||
return (hasSelectedObjectInventoryCue(text) &&
|
||||
/(?:кому\s+(?:в\s+итоге\s+)?(?:мы\s+)?продали|кому\s+был\s+продан|куда\s+(?:в\s+итоге\s+)?(?:мы\s+)?продали(?:\s+(?:это|его|этот\s+товар|эту\s+позицию))?|куда\s+(?:была\s+)?реализована\s+(?:позиция|номенклатура|продукция)|кто\s+купил|buyer|sale\s+trace|trace\s+of\s+sale)/iu.test(text));
|
||||
}
|
||||
function hasInventoryProvenanceSignalV2(text) {
|
||||
const hasItemCue = /(?:товар|номенклатур|sku|item|product|остат(?:ок|ки)|склад)/iu.test(text);
|
||||
const hasSupplierCue = /(?:от\s+какого\s+поставщика|у\s+какого\s+поставщика|от\s+кого\s+куплен|у\s+кого\s+купили|у\s+кого\s+куплено|где\s+(?:мы\s+)?купили(?:\s+(?:это|его|товар|позицию))?|где\s+куплено|кто\s+(?:нам\s+)?поставил|кем\s+поставлен|поставщик|supplier|vendor)/iu.test(text);
|
||||
@@ -1369,8 +1373,8 @@ function hasInventoryPurchaseDocumentsSignalV2(text) {
|
||||
return hasItemCue && hasPurchaseDocCue;
|
||||
}
|
||||
function hasInventorySaleTraceSignalV2(text) {
|
||||
const hasItemCue = /(?:товар|номенклатур|sku|item|product)/iu.test(text);
|
||||
const hasTraceCue = /(?:кому\s+(?:в\s+итоге\s+)?(?:мы\s+)?продали|кому\s+был\s+продан|кто\s+купил|buyer|sale\s+trace|trace\s+of\s+sale|через\s+какие\s+документы\s+прош[её]л\s+путь\s+товара|закупк.*склад.*продаж|purchase[\s-]?to[\s-]?sale|purchase\s*->\s*warehouse\s*->\s*sale|purchase\s*->\s*stock\s*->\s*sale)/iu.test(text);
|
||||
const hasItemCue = /(?:товар|номенклатур|sku|item|product|позици(?:я|ю|и)|продукци(?:я|ю|и))/iu.test(text);
|
||||
const hasTraceCue = /(?:кому\s+(?:в\s+итоге\s+)?(?:мы\s+)?продали|кому\s+был\s+продан|куда\s+(?:в\s+итоге\s+)?(?:мы\s+)?продали(?:\s+(?:это|его|товар|позицию))?|куда\s+(?:была\s+)?реализована\s+(?:позиция|номенклатура|продукция)|кто\s+купил|buyer|sale\s+trace|trace\s+of\s+sale|через\s+какие\s+документы\s+прош[её]л\s+путь\s+товара|закупк.*склад.*продаж|purchase[\s-]?to[\s-]?sale|purchase\s*->\s*warehouse\s*->\s*sale|purchase\s*->\s*stock\s*->\s*sale)/iu.test(text);
|
||||
return hasItemCue && hasTraceCue;
|
||||
}
|
||||
function hasInventorySupplierStockOverlapSignal(text) {
|
||||
@@ -1588,6 +1592,13 @@ function resolveAddressIntent(userMessage) {
|
||||
reasons: ["inventory_purchase_documents_signal_detected"]
|
||||
};
|
||||
}
|
||||
if (hasSelectedObjectInventorySaleTraceSignal(text)) {
|
||||
return {
|
||||
intent: "inventory_sale_trace_for_item",
|
||||
confidence: "medium",
|
||||
reasons: ["inventory_selected_object_sale_trace_signal_detected"]
|
||||
};
|
||||
}
|
||||
if (hasInventorySaleTraceSignalV2(text)) {
|
||||
return {
|
||||
intent: "inventory_sale_trace_for_item",
|
||||
|
||||
@@ -21,7 +21,14 @@ const DISPLAY_ENTITY_TYPE_BY_INTENT = {
|
||||
list_documents_by_contract: "document_ref",
|
||||
bank_operations_by_counterparty: "document_ref",
|
||||
bank_operations_by_contract: "document_ref",
|
||||
open_items_by_counterparty_or_contract: "counterparty"
|
||||
open_items_by_counterparty_or_contract: "counterparty",
|
||||
inventory_on_hand_as_of_date: "item",
|
||||
inventory_purchase_provenance_for_item: "item",
|
||||
inventory_purchase_documents_for_item: "item",
|
||||
inventory_supplier_stock_overlap_as_of_date: "item",
|
||||
inventory_sale_trace_for_item: "item",
|
||||
inventory_purchase_to_sale_chain: "item",
|
||||
inventory_aging_by_purchase_date: "item"
|
||||
};
|
||||
const RESULT_SET_TYPE_BY_INTENT = {
|
||||
counterparty_activity_lifecycle: "counterparty_list",
|
||||
@@ -39,6 +46,13 @@ const RESULT_SET_TYPE_BY_INTENT = {
|
||||
bank_operations_by_counterparty: "bank_operations_list",
|
||||
bank_operations_by_contract: "bank_operations_list",
|
||||
open_items_by_counterparty_or_contract: "open_items_list",
|
||||
inventory_on_hand_as_of_date: "inventory_snapshot",
|
||||
inventory_purchase_provenance_for_item: "inventory_trace",
|
||||
inventory_purchase_documents_for_item: "inventory_trace",
|
||||
inventory_supplier_stock_overlap_as_of_date: "inventory_trace",
|
||||
inventory_sale_trace_for_item: "inventory_trace",
|
||||
inventory_purchase_to_sale_chain: "inventory_trace",
|
||||
inventory_aging_by_purchase_date: "inventory_trace",
|
||||
period_coverage_profile: "profile_summary",
|
||||
document_type_and_account_section_profile: "profile_summary",
|
||||
counterparty_population_and_roles: "profile_summary",
|
||||
@@ -64,7 +78,13 @@ function toAddressFocusObjectType(value) {
|
||||
if (!normalized) {
|
||||
return "unknown";
|
||||
}
|
||||
if (normalized === "counterparty" || normalized === "contract" || normalized === "document_ref" || normalized === "account") {
|
||||
if (normalized === "counterparty" ||
|
||||
normalized === "contract" ||
|
||||
normalized === "document_ref" ||
|
||||
normalized === "account" ||
|
||||
normalized === "item" ||
|
||||
normalized === "organization" ||
|
||||
normalized === "warehouse") {
|
||||
return normalized;
|
||||
}
|
||||
return "unknown";
|
||||
@@ -127,6 +147,38 @@ function extractEntityRefsFromAssistantReply(replyText, intent, limit = MAX_ENTI
|
||||
}
|
||||
return Array.from(dedup.values());
|
||||
}
|
||||
function extractOrganizationsFromAssistantReply(replyText, limit = 10) {
|
||||
const dedup = new Map();
|
||||
const lines = String(replyText ?? "").split(/\r?\n/);
|
||||
for (const line of lines) {
|
||||
const match = line.match(/(?:^|\|)\s*организац(?:ия|ии)\s*:\s*([^|]+)/iu);
|
||||
if (!match) {
|
||||
continue;
|
||||
}
|
||||
const organization = toNonEmptyString(match[1]);
|
||||
if (!organization) {
|
||||
continue;
|
||||
}
|
||||
const key = organization.toLowerCase();
|
||||
if (!dedup.has(key)) {
|
||||
dedup.set(key, organization);
|
||||
}
|
||||
if (dedup.size >= limit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return Array.from(dedup.values());
|
||||
}
|
||||
function resolveDerivedOrganizationScope(debug, filters, replyText) {
|
||||
const rootFrameContext = toObject(debug.address_root_frame_context) ?? {};
|
||||
const candidates = [
|
||||
toNonEmptyString(filters.organization),
|
||||
toNonEmptyString(rootFrameContext.organization),
|
||||
...extractOrganizationsFromAssistantReply(replyText)
|
||||
].filter((value) => Boolean(value));
|
||||
const dedup = Array.from(new Map(candidates.map((value) => [value.toLowerCase(), value])).values());
|
||||
return dedup.length === 1 ? dedup[0] : null;
|
||||
}
|
||||
function cloneFocusObject(value) {
|
||||
if (!value) {
|
||||
return null;
|
||||
@@ -345,6 +397,13 @@ function evolveAddressNavigationStateWithAssistantItem(state, item, turnIndex) {
|
||||
const resultSetId = `rs-${item.message_id}`;
|
||||
const routeId = toNonEmptyString(debug.selected_recipe);
|
||||
const filters = normalizeFilters(debug.extracted_filters);
|
||||
const derivedOrganizationScope = resolveDerivedOrganizationScope(debug, filters, item.text);
|
||||
const filtersWithDerivedScope = derivedOrganizationScope && !toNonEmptyString(filters.organization)
|
||||
? {
|
||||
...filters,
|
||||
organization: derivedOrganizationScope
|
||||
}
|
||||
: filters;
|
||||
const sourceRefs = routeId ? [routeId] : [];
|
||||
const entityRefs = extractEntityRefsFromAssistantReply(item.text, intent);
|
||||
const resultSet = {
|
||||
@@ -352,7 +411,7 @@ function evolveAddressNavigationStateWithAssistantItem(state, item, turnIndex) {
|
||||
type: inferResultSetType(intent),
|
||||
intent,
|
||||
route_id: routeId,
|
||||
filters,
|
||||
filters: filtersWithDerivedScope,
|
||||
source_refs: sourceRefs,
|
||||
entity_refs: entityRefs,
|
||||
created_from_turn: turnIndex,
|
||||
@@ -371,11 +430,11 @@ function evolveAddressNavigationStateWithAssistantItem(state, item, turnIndex) {
|
||||
created_at: createdAt
|
||||
};
|
||||
const normalizedDateScope = {
|
||||
as_of_date: toNonEmptyString(filters.as_of_date),
|
||||
period_from: toNonEmptyString(filters.period_from),
|
||||
period_to: toNonEmptyString(filters.period_to)
|
||||
as_of_date: toNonEmptyString(filtersWithDerivedScope.as_of_date),
|
||||
period_from: toNonEmptyString(filtersWithDerivedScope.period_from),
|
||||
period_to: toNonEmptyString(filtersWithDerivedScope.period_to)
|
||||
};
|
||||
const organizationScope = toNonEmptyString(filters.organization);
|
||||
const organizationScope = toNonEmptyString(filtersWithDerivedScope.organization);
|
||||
const nextResultSets = capResultSets([...state.result_sets.filter((itemSet) => itemSet.result_set_id !== resultSetId), resultSet].sort((left, right) => left.created_from_turn - right.created_from_turn));
|
||||
const nextEvents = capNavigationEvents([...state.navigation_history, navigationEvent]);
|
||||
return {
|
||||
|
||||
+178
-2
@@ -1219,8 +1219,32 @@ function applyPreExecutionOrganizationScopeGrounding(input) {
|
||||
input.semanticFrame.anchor_value = resolvedOrganizationFromMessage;
|
||||
}
|
||||
}
|
||||
if (!input.filters.organization && !activeOrganization && !resolvedOrganizationFromMessage && candidateOrganizations.length === 1) {
|
||||
input.filters.organization = candidateOrganizations[0];
|
||||
if (!input.warnings.includes("organization_auto_selected_from_single_scope_candidate")) {
|
||||
input.warnings.push("organization_auto_selected_from_single_scope_candidate");
|
||||
}
|
||||
if (!input.baseReasons.includes("organization_auto_selected_from_single_scope_candidate")) {
|
||||
input.baseReasons.push("organization_auto_selected_from_single_scope_candidate");
|
||||
}
|
||||
if (input.semanticFrame?.anchor_kind === "organization") {
|
||||
input.semanticFrame.anchor_value = candidateOrganizations[0];
|
||||
}
|
||||
}
|
||||
return resolvedOrganizationFromMessage;
|
||||
}
|
||||
function isOrganizationScopedInventoryIntent(intent) {
|
||||
return (intent === "inventory_on_hand_as_of_date" ||
|
||||
intent === "inventory_purchase_provenance_for_item" ||
|
||||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_supplier_stock_overlap_as_of_date" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain" ||
|
||||
intent === "inventory_aging_by_purchase_date");
|
||||
}
|
||||
function collectOrganizationCandidatesFromRows(rows) {
|
||||
return (0, assistantOrganizationMatcher_1.mergeKnownOrganizations)(rows.map((row) => row.organization).filter((value) => Boolean(value)));
|
||||
}
|
||||
function isHeuristicCandidatesIntent(intent) {
|
||||
return (intent === "list_receivables_counterparties" ||
|
||||
intent === "list_payables_counterparties" ||
|
||||
@@ -1587,7 +1611,21 @@ function shouldBoostAutoBroadenedLimit(intent) {
|
||||
intent === "inventory_aging_by_purchase_date");
|
||||
}
|
||||
function shouldClearAsOfDateForHistoryRecovery(intent) {
|
||||
return intent === "inventory_purchase_provenance_for_item" || intent === "inventory_purchase_documents_for_item";
|
||||
return (intent === "inventory_purchase_provenance_for_item" ||
|
||||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain");
|
||||
}
|
||||
function shouldDetachLifecycleExecutionFromSnapshotContext(intent, reasons) {
|
||||
if (intent !== "inventory_purchase_provenance_for_item" &&
|
||||
intent !== "inventory_purchase_documents_for_item" &&
|
||||
intent !== "inventory_sale_trace_for_item" &&
|
||||
intent !== "inventory_purchase_to_sale_chain") {
|
||||
return false;
|
||||
}
|
||||
return (reasons.includes("as_of_date_from_followup_context") ||
|
||||
reasons.includes("period_from_followup_context") ||
|
||||
reasons.includes("as_of_date_from_open_items_followup_context"));
|
||||
}
|
||||
function invertSort(sort) {
|
||||
return sort === "period_asc" ? "period_desc" : "period_asc";
|
||||
@@ -2241,6 +2279,48 @@ function buildLimitedExecutionResult(input) {
|
||||
}
|
||||
};
|
||||
}
|
||||
function composeOrganizationClarificationReply(organizations) {
|
||||
const normalizedOrganizations = (0, assistantOrganizationMatcher_1.mergeKnownOrganizations)(organizations).slice(0, 10);
|
||||
const lines = [
|
||||
normalizedOrganizations.length > 1
|
||||
? "Нужно уточнить организацию, чтобы не смешивать компании в одном ответе."
|
||||
: "Нужно уточнить организацию, чтобы продолжить запрос.",
|
||||
normalizedOrganizations.length > 0
|
||||
? "Сейчас в доступном контуре вижу такие организации:"
|
||||
: "Уточни, по какой организации продолжать."
|
||||
];
|
||||
for (const organization of normalizedOrganizations) {
|
||||
lines.push(`- ${organization}`);
|
||||
}
|
||||
lines.push("Можешь ответить просто названием компании, и я продолжу этот же запрос.");
|
||||
return lines.join("\n");
|
||||
}
|
||||
function buildOrganizationClarificationExecutionResult(input) {
|
||||
const result = buildLimitedExecutionResult({
|
||||
mode: input.mode,
|
||||
shape: input.shape,
|
||||
intent: input.intent,
|
||||
filters: input.filters,
|
||||
missingRequiredFilters: ["organization"],
|
||||
selectedRecipe: null,
|
||||
anchor: input.anchor,
|
||||
mcpCallStatus: "skipped",
|
||||
rowsFetched: 0,
|
||||
rowsMatched: 0,
|
||||
category: "missing_anchor",
|
||||
reasonText: "не указана организация, а в доступном контуре найдено несколько компаний",
|
||||
nextStep: "уточните организацию из списка, и я продолжу этот же запрос",
|
||||
limitations: ["organization_clarification_required", "multiple_known_organizations_detected"],
|
||||
reasons: [...input.reasons, "organization_clarification_required", "multiple_known_organizations_detected"],
|
||||
semanticFrame: input.semanticFrame,
|
||||
capabilityAudit: input.capabilityAudit,
|
||||
shadowRouteAudit: input.shadowRouteAudit,
|
||||
routeExpectationAudit: input.routeExpectationAudit
|
||||
});
|
||||
result.reply_text = composeOrganizationClarificationReply(input.organizations);
|
||||
result.debug.organization_candidates = (0, assistantOrganizationMatcher_1.mergeKnownOrganizations)(input.organizations);
|
||||
return result;
|
||||
}
|
||||
class AddressQueryService {
|
||||
async tryHandle(userMessage, options = {}) {
|
||||
if (!config_1.FEATURE_ASSISTANT_ADDRESS_QUERY_V1) {
|
||||
@@ -2277,6 +2357,29 @@ class AddressQueryService {
|
||||
activeOrganization: options.activeOrganization ?? null,
|
||||
knownOrganizations: options.knownOrganizations ?? []
|
||||
});
|
||||
const knownOrganizations = (0, assistantOrganizationMatcher_1.mergeKnownOrganizations)(options.knownOrganizations ?? []);
|
||||
const activeOrganization = (0, assistantOrganizationMatcher_1.normalizeOrganizationScopeValue)(options.activeOrganization ?? null);
|
||||
if (isOrganizationScopedInventoryIntent(intent.intent) &&
|
||||
!toNonEmptyFilterValue(filters.extracted_filters.organization) &&
|
||||
!activeOrganization &&
|
||||
!resolvedOrganizationFromMessage &&
|
||||
knownOrganizations.length > 1) {
|
||||
return buildOrganizationClarificationExecutionResult({
|
||||
mode,
|
||||
shape,
|
||||
intent,
|
||||
filters: filters.extracted_filters,
|
||||
organizations: knownOrganizations,
|
||||
reasons: [...baseReasons, "organization_candidates_from_scope_context"],
|
||||
semanticFrame,
|
||||
capabilityAudit: buildCapabilityAudit(intent.intent),
|
||||
shadowRouteAudit: buildShadowRouteAudit({
|
||||
intent: intent.intent,
|
||||
requestedResultMode: resolveRequestedResultMode(intent.intent, filters.extracted_filters, semanticFrame),
|
||||
filters: filters.extracted_filters
|
||||
})
|
||||
});
|
||||
}
|
||||
const requestedResultMode = resolveRequestedResultMode(intent.intent, filters.extracted_filters, semanticFrame);
|
||||
const confirmedBalancePayablesIntent = (intent.intent === "list_payables_counterparties" || intent.intent === "payables_confirmed_as_of_date") &&
|
||||
requestedResultMode === "confirmed_balance";
|
||||
@@ -2336,6 +2439,44 @@ class AddressQueryService {
|
||||
baseReasons.push("as_of_date_derived_for_inventory_on_hand");
|
||||
}
|
||||
}
|
||||
if (shouldDetachLifecycleExecutionFromSnapshotContext(intent.intent, baseReasons)) {
|
||||
const detachedExecutionFilters = { ...executionFilters };
|
||||
let periodDetached = false;
|
||||
let asOfDetached = false;
|
||||
if (toNonEmptyFilterValue(detachedExecutionFilters.period_from) ||
|
||||
toNonEmptyFilterValue(detachedExecutionFilters.period_to)) {
|
||||
delete detachedExecutionFilters.period_from;
|
||||
delete detachedExecutionFilters.period_to;
|
||||
periodDetached = true;
|
||||
}
|
||||
if (toNonEmptyFilterValue(detachedExecutionFilters.as_of_date)) {
|
||||
delete detachedExecutionFilters.as_of_date;
|
||||
asOfDetached = true;
|
||||
}
|
||||
if (periodDetached || asOfDetached) {
|
||||
executionFilters = detachedExecutionFilters;
|
||||
if (periodDetached && !filters.warnings.includes("period_window_detached_for_lifecycle_execution")) {
|
||||
filters.warnings.push("period_window_detached_for_lifecycle_execution");
|
||||
}
|
||||
if (periodDetached && !baseReasons.includes("period_window_detached_for_lifecycle_execution")) {
|
||||
baseReasons.push("period_window_detached_for_lifecycle_execution");
|
||||
}
|
||||
if ((periodDetached || asOfDetached) &&
|
||||
!filters.warnings.includes("lifecycle_execution_detached_from_snapshot_date")) {
|
||||
filters.warnings.push("lifecycle_execution_detached_from_snapshot_date");
|
||||
}
|
||||
if ((periodDetached || asOfDetached) &&
|
||||
!baseReasons.includes("lifecycle_execution_detached_from_snapshot_date")) {
|
||||
baseReasons.push("lifecycle_execution_detached_from_snapshot_date");
|
||||
}
|
||||
if (asOfDetached && !filters.warnings.includes("as_of_date_cleared_for_history_recovery")) {
|
||||
filters.warnings.push("as_of_date_cleared_for_history_recovery");
|
||||
}
|
||||
if (asOfDetached && !baseReasons.includes("as_of_date_cleared_for_history_recovery")) {
|
||||
baseReasons.push("as_of_date_cleared_for_history_recovery");
|
||||
}
|
||||
}
|
||||
}
|
||||
const capabilityDecision = (0, addressCapabilityPolicy_1.resolveAddressCapabilityRouteDecision)(intent.intent);
|
||||
const capabilityAudit = buildCapabilityAudit(intent.intent);
|
||||
const shadowRouteAudit = buildShadowRouteAudit({
|
||||
@@ -2789,6 +2930,41 @@ class AddressQueryService {
|
||||
baseReasons.push("organization_scope_live_grounding_recovered_rows");
|
||||
}
|
||||
}
|
||||
if (filteredRows.length > 0 &&
|
||||
isOrganizationScopedInventoryIntent(intent.intent) &&
|
||||
!toNonEmptyFilterValue(filters.extracted_filters.organization)) {
|
||||
const observedOrganizations = collectOrganizationCandidatesFromRows(filteredRows);
|
||||
if (observedOrganizations.length === 1) {
|
||||
filters.extracted_filters = {
|
||||
...filters.extracted_filters,
|
||||
organization: observedOrganizations[0]
|
||||
};
|
||||
executionFilters = {
|
||||
...executionFilters,
|
||||
organization: observedOrganizations[0]
|
||||
};
|
||||
if (!filters.warnings.includes("organization_grounded_from_observed_rows")) {
|
||||
filters.warnings.push("organization_grounded_from_observed_rows");
|
||||
}
|
||||
if (!baseReasons.includes("organization_grounded_from_observed_rows")) {
|
||||
baseReasons.push("organization_grounded_from_observed_rows");
|
||||
}
|
||||
}
|
||||
else if (observedOrganizations.length > 1) {
|
||||
return buildOrganizationClarificationExecutionResult({
|
||||
mode,
|
||||
shape,
|
||||
intent,
|
||||
filters: filters.extracted_filters,
|
||||
anchor,
|
||||
organizations: observedOrganizations,
|
||||
reasons: [...baseReasons, "organization_candidates_from_observed_rows"],
|
||||
semanticFrame,
|
||||
capabilityAudit,
|
||||
shadowRouteAudit
|
||||
});
|
||||
}
|
||||
}
|
||||
if (filteredRows.length === 0 && intent.intent === "list_documents_by_contract" && filterByAnchors.length > 0) {
|
||||
const recoveredBankRows = applyIntentSpecificFilter("bank_operations_by_contract", filterByAnchors);
|
||||
const recoveredRows = recoveredBankRows.length > 0 ? recoveredBankRows : filterByAnchors;
|
||||
@@ -2989,7 +3165,7 @@ class AddressQueryService {
|
||||
const broadenedAdjustments = [];
|
||||
delete autoBroadenedFilters.period_from;
|
||||
delete autoBroadenedFilters.period_to;
|
||||
if (stageStatus === "no_raw_rows" && shouldClearAsOfDateForHistoryRecovery(intent.intent) && toNonEmptyFilterValue(autoBroadenedFilters.as_of_date)) {
|
||||
if (shouldClearAsOfDateForHistoryRecovery(intent.intent) && toNonEmptyFilterValue(autoBroadenedFilters.as_of_date)) {
|
||||
delete autoBroadenedFilters.as_of_date;
|
||||
broadenedAdjustments.push("as_of_date_cleared_for_history_recovery");
|
||||
}
|
||||
|
||||
@@ -34,7 +34,8 @@ const INVENTORY_MOVEMENTS_QUERY_TEMPLATE = `
|
||||
ПРЕДСТАВЛЕНИЕ(Движения.СубконтоДт3) КАК СубконтоДт3,
|
||||
ПРЕДСТАВЛЕНИЕ(Движения.СубконтоКт1) КАК СубконтоКт1,
|
||||
ПРЕДСТАВЛЕНИЕ(Движения.СубконтоКт2) КАК СубконтоКт2,
|
||||
ПРЕДСТАВЛЕНИЕ(Движения.СубконтоКт3) КАК СубконтоКт3
|
||||
ПРЕДСТАВЛЕНИЕ(Движения.СубконтоКт3) КАК СубконтоКт3,
|
||||
ПРЕДСТАВЛЕНИЕ(Движения.Организация) КАК Организация
|
||||
ИЗ
|
||||
РегистрБухгалтерии.Хозрасчетный.ДвиженияССубконто КАК Движения
|
||||
__WHERE_CLAUSE__
|
||||
@@ -934,6 +935,18 @@ function toDateTimeExpr(isoDate, endOfDay) {
|
||||
const second = endOfDay ? 59 : 0;
|
||||
return `ДАТАВРЕМЯ(${year}, ${month}, ${day}, ${hour}, ${minute}, ${second})`;
|
||||
}
|
||||
function toQueryStringLiteral(value) {
|
||||
return String(value ?? "").replace(/"/g, '""');
|
||||
}
|
||||
function buildOrganizationPresentationCondition(filters, fieldPath) {
|
||||
const organization = typeof filters.organization === "string" && filters.organization.trim().length > 0
|
||||
? filters.organization.trim()
|
||||
: "";
|
||||
if (!organization) {
|
||||
return null;
|
||||
}
|
||||
return `ПРЕДСТАВЛЕНИЕ(${fieldPath}) = "${toQueryStringLiteral(organization)}"`;
|
||||
}
|
||||
function buildWhereClause(filters, fieldPath, extraConditions = []) {
|
||||
const periodFromExpr = typeof filters.period_from === "string" && filters.period_from.trim().length > 0
|
||||
? toDateTimeExpr(filters.period_from, false)
|
||||
@@ -1074,9 +1087,10 @@ function buildInventoryMovementQuery(filters, resolvedLimit, side) {
|
||||
: side === "kt"
|
||||
? creditPredicate
|
||||
: `(${debitPredicate} ИЛИ ${creditPredicate})`;
|
||||
const organizationCondition = buildOrganizationPresentationCondition(filters, "Движения.Организация");
|
||||
return INVENTORY_MOVEMENTS_QUERY_TEMPLATE
|
||||
.replace("__LIMIT__", String(resolvedLimit))
|
||||
.replace("__WHERE_CLAUSE__", buildWhereClause(filters, "Движения.Период", [inventoryCondition]))
|
||||
.replace("__WHERE_CLAUSE__", buildWhereClause(filters, "Движения.Период", [inventoryCondition, organizationCondition].filter((item) => Boolean(item))))
|
||||
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort));
|
||||
}
|
||||
function shouldBoostLimitForAllTimeCounterparty(filters) {
|
||||
|
||||
@@ -264,6 +264,12 @@ function isInventoryDrilldownFrameIntent(intent) {
|
||||
intent === "inventory_purchase_to_sale_chain" ||
|
||||
intent === "inventory_aging_by_purchase_date");
|
||||
}
|
||||
function isInventoryLifecycleHistoryIntent(intent) {
|
||||
return (intent === "inventory_purchase_provenance_for_item" ||
|
||||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain");
|
||||
}
|
||||
function buildInventoryRootFollowupContext(followupContext) {
|
||||
if (!followupContext || !followupContext.root_intent || !followupContext.root_filters) {
|
||||
return followupContext;
|
||||
@@ -412,7 +418,7 @@ function hasBareInventoryPurchaseDateFollowupCue(text) {
|
||||
return /(?:^|\s)(?:когда|а\s+когда|ну\s+когда)(?=$|[\s,.;:!?])/iu.test(normalized) && normalized.split(/\s+/).filter(Boolean).length <= 3;
|
||||
}
|
||||
function hasInventorySaleFollowupCue(text) {
|
||||
return /(?:кому\s+(?:в\s+итоге\s+)?(?:мы\s+)?продали|кому\s+был\s+продан|кому\s+дальше\s+продали|кто\s+купил|buyer|покупател)/iu.test(String(text ?? ""));
|
||||
return /(?:кому\s+(?:в\s+итоге\s+)?(?:мы\s+)?продали|кому\s+был\s+продан|кому\s+дальше\s+продали|куда\s+(?:в\s+итоге\s+)?(?:мы\s+)?продали|куда\s+ушла\s+позиция|куда\s+ушел\s+товар|кто\s+купил|buyer|покупател)/iu.test(String(text ?? ""));
|
||||
}
|
||||
function hasInventoryPurchaseToSaleChainFollowupCue(text) {
|
||||
return /(?:через\s+какие\s+документы\s+прош[её]л\s+путь\s+товара|закупк.*склад.*продаж|purchase[\s-]?to[\s-]?sale|purchase\s*->\s*(?:warehouse|stock)\s*->\s*sale)/iu.test(String(text ?? ""));
|
||||
@@ -618,20 +624,18 @@ function mergeFollowupFilters(current, intent, userMessage, followupContext) {
|
||||
}
|
||||
}
|
||||
if (!sameDateRequested &&
|
||||
(intent === "inventory_purchase_provenance_for_item" ||
|
||||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain" ||
|
||||
intent === "inventory_aging_by_purchase_date") &&
|
||||
(intent === "inventory_aging_by_purchase_date" || isInventoryLifecycleHistoryIntent(intent)) &&
|
||||
!hasExplicitPeriodLiteral(userMessage) &&
|
||||
!hasExplicitCurrentDateHint(userMessage)) {
|
||||
const inheritedAsOfDate = previousAsOfDate ?? previousPeriodTo ?? previousPeriodFrom;
|
||||
const currentAsOfDate = toNonEmptyString(merged.as_of_date);
|
||||
const todayIso = new Date().toISOString().slice(0, 10);
|
||||
const currentLooksDefaultedToToday = currentAsOfDate === todayIso;
|
||||
if (inheritedAsOfDate && (!currentAsOfDate || currentLooksDefaultedToToday) && currentAsOfDate !== inheritedAsOfDate) {
|
||||
merged.as_of_date = inheritedAsOfDate;
|
||||
reasons.push("as_of_date_from_followup_context");
|
||||
if (intent === "inventory_aging_by_purchase_date") {
|
||||
const inheritedAsOfDate = previousAsOfDate ?? previousPeriodTo ?? previousPeriodFrom;
|
||||
const currentAsOfDate = toNonEmptyString(merged.as_of_date);
|
||||
const todayIso = new Date().toISOString().slice(0, 10);
|
||||
const currentLooksDefaultedToToday = currentAsOfDate === todayIso;
|
||||
if (inheritedAsOfDate && (!currentAsOfDate || currentLooksDefaultedToToday) && currentAsOfDate !== inheritedAsOfDate) {
|
||||
merged.as_of_date = inheritedAsOfDate;
|
||||
reasons.push("as_of_date_from_followup_context");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!sameDateRequested &&
|
||||
@@ -706,6 +710,7 @@ function mergeFollowupFilters(current, intent, userMessage, followupContext) {
|
||||
const hasFollowupSignal = hasAddressFollowupContextSignal(userMessage);
|
||||
const hasExplicitPeriodInMessage = hasExplicitPeriodLiteral(userMessage);
|
||||
const hasExplicitCurrentDateInMessage = hasExplicitCurrentDateHint(userMessage);
|
||||
const inventoryLifecycleHistoryIntent = isInventoryLifecycleHistoryIntent(intent);
|
||||
const asOfPrimaryIntent = intent === "account_balance_snapshot" ||
|
||||
intent === "documents_forming_balance" ||
|
||||
intent === "open_contracts_confirmed_as_of_date" ||
|
||||
@@ -739,7 +744,11 @@ function mergeFollowupFilters(current, intent, userMessage, followupContext) {
|
||||
reasons.push("period_from_followup_context");
|
||||
}
|
||||
}
|
||||
if (!currentHasPeriod && previousHasPeriod && hasFollowupSignal && !hasExplicitPeriodInMessage) {
|
||||
if (!currentHasPeriod &&
|
||||
previousHasPeriod &&
|
||||
hasFollowupSignal &&
|
||||
!hasExplicitPeriodInMessage &&
|
||||
!inventoryLifecycleHistoryIntent) {
|
||||
if (previousPeriodFrom) {
|
||||
merged.period_from = previousPeriodFrom;
|
||||
}
|
||||
|
||||
@@ -108,6 +108,8 @@ async function runAssistantAddressAttemptRuntime(input) {
|
||||
sessionId: input.sessionId,
|
||||
userMessage: input.userMessage,
|
||||
sessionItems: input.sessionItems,
|
||||
sessionAddressNavigationState: input.sessionAddressNavigationState,
|
||||
sessionScope: input.sessionScope,
|
||||
payload: input.payload,
|
||||
featureAddressLlmPredecomposeV1: input.featureAddressLlmPredecomposeV1,
|
||||
runAddressLlmPreDecompose: input.runAddressLlmPreDecompose,
|
||||
|
||||
+7
-1
@@ -151,7 +151,13 @@ function runAssistantAddressLaneResponseRuntime(input) {
|
||||
if (followupOffer) {
|
||||
debug.address_followup_offer = followupOffer;
|
||||
}
|
||||
const debugKnownOrganizations = input.mergeKnownOrganizations(input.knownOrganizations);
|
||||
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;
|
||||
|
||||
+4
-3
@@ -5,7 +5,7 @@ function hasSelectedObjectInventorySignal(text) {
|
||||
return /(?:по\s+выбранному\s+объекту|по\s+этой\s+позиции|по\s+этому\s+товару|selected\s+object)/iu.test(String(text ?? ""));
|
||||
}
|
||||
function hasSelectedObjectInventoryActionCue(text) {
|
||||
return /(?:кому[\s\S]{0,80}продал[аи]?|кому[\s\S]{0,80}реализова[нлт][а-я]*|кому\s+был\s+продан|кто[\s\S]{0,40}купил|кто\s+это\s+поставил|кто\s+поставил|у\s+кого\s+купили|у\s+кого\s+куплено|где\s+мы\s+купили|где\s+куплено|по\s+каким\s+документам|какими\s+документами|покажи\s+документы|документы\s+закупки|buyer|sale\s+trace|supplier|vendor|purchase\s+documents|purchase[\s-]?to[\s-]?sale|old\s+purchase|aged\s+stock)/iu.test(String(text ?? ""));
|
||||
return /(?:кому[\s\S]{0,80}продал[аи]?|кому[\s\S]{0,80}реализова[нлт][а-я]*|кому\s+был\s+продан|куда[\s\S]{0,80}продал[аи]?|куда[\s\S]{0,80}реализова[нлт][а-я]*|кто[\s\S]{0,40}купил|кто\s+это\s+поставил|кто\s+поставил|у\s+кого\s+купили|у\s+кого\s+куплено|где\s+мы\s+купили|где\s+куплено|по\s+каким\s+документам|какими\s+документами|покажи\s+документы|документы\s+закупки|buyer|sale\s+trace|supplier|vendor|purchase\s+documents|purchase[\s-]?to[\s-]?sale|old\s+purchase|aged\s+stock)/iu.test(String(text ?? ""));
|
||||
}
|
||||
function isGenericCanonicalDriftIntent(intent) {
|
||||
return (intent === "open_items_by_counterparty_or_contract" ||
|
||||
@@ -62,7 +62,7 @@ async function buildAssistantAddressOrchestrationRuntime(input) {
|
||||
: fallbackAddressPreDecompose(input.userMessage, input.llmProvider, input.buildAddressLlmPredecomposeContractV1, input.sanitizeAddressMessageForFallback);
|
||||
let addressPreDecompose = initialAddressPreDecompose;
|
||||
let addressInputMessage = input.toNonEmptyString(addressPreDecompose?.effectiveMessage) ?? input.userMessage;
|
||||
let carryover = input.resolveAddressFollowupCarryoverContext(input.userMessage, input.sessionItems, addressInputMessage, addressPreDecompose);
|
||||
let carryover = input.resolveAddressFollowupCarryoverContext(input.userMessage, input.sessionItems, addressInputMessage, addressPreDecompose, input.sessionAddressNavigationState);
|
||||
if (shouldPreferRawFollowupMessage(input.userMessage, addressInputMessage, carryover, addressPreDecompose, input.toNonEmptyString)) {
|
||||
addressInputMessage = input.userMessage;
|
||||
addressPreDecompose = {
|
||||
@@ -75,7 +75,7 @@ async function buildAssistantAddressOrchestrationRuntime(input) {
|
||||
canonicalMessage: input.userMessage
|
||||
})
|
||||
};
|
||||
carryover = input.resolveAddressFollowupCarryoverContext(input.userMessage, input.sessionItems, addressInputMessage, addressPreDecompose);
|
||||
carryover = input.resolveAddressFollowupCarryoverContext(input.userMessage, input.sessionItems, addressInputMessage, addressPreDecompose, input.sessionAddressNavigationState);
|
||||
}
|
||||
const followupContext = carryover?.followupContext ?? null;
|
||||
const orchestrationDecision = input.resolveAssistantOrchestrationDecision({
|
||||
@@ -84,6 +84,7 @@ async function buildAssistantAddressOrchestrationRuntime(input) {
|
||||
followupContext,
|
||||
llmPreDecomposeMeta: addressPreDecompose,
|
||||
sessionItems: input.sessionItems,
|
||||
sessionOrganizationScope: input.sessionOrganizationScope ?? null,
|
||||
useMock: input.useMock
|
||||
});
|
||||
const dialogContinuationContract = input.buildAddressDialogContinuationContractV2(input.userMessage, addressInputMessage, carryover, addressPreDecompose);
|
||||
|
||||
@@ -18,6 +18,8 @@ async function runAssistantAddressRuntime(input) {
|
||||
const addressOrchestrationRuntime = await runAddressOrchestrationRuntimeSafe({
|
||||
userMessage: input.userMessage,
|
||||
sessionItems: input.sessionItems,
|
||||
sessionAddressNavigationState: input.sessionAddressNavigationState,
|
||||
sessionOrganizationScope: input.sessionOrganizationScope ?? null,
|
||||
llmProvider: input.llmProvider,
|
||||
useMock: input.useMock,
|
||||
featureAddressLlmPredecomposeV1: input.featureAddressLlmPredecomposeV1,
|
||||
|
||||
@@ -7,6 +7,8 @@ function buildAssistantAddressRuntimeInput(input) {
|
||||
sessionId: input.sessionId,
|
||||
userMessage: input.userMessage,
|
||||
sessionItems: input.sessionItems,
|
||||
sessionAddressNavigationState: input.sessionAddressNavigationState,
|
||||
sessionOrganizationScope: input.sessionScope,
|
||||
llmProvider: input.payload.llmProvider,
|
||||
useMock: Boolean(input.payload.useMock),
|
||||
featureAddressLlmPredecomposeV1: input.featureAddressLlmPredecomposeV1,
|
||||
|
||||
@@ -1,6 +1,70 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.runAssistantLivingChatRuntime = runAssistantLivingChatRuntime;
|
||||
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 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");
|
||||
}
|
||||
async function runAssistantLivingChatRuntime(input) {
|
||||
const userMessage = String(input.userMessage ?? "");
|
||||
const dataScopeMetaQuery = input.hasAssistantDataScopeMetaQuestionSignal(userMessage);
|
||||
@@ -18,6 +82,10 @@ async function runAssistantLivingChatRuntime(input) {
|
||||
let knownOrganizations = input.mergeKnownOrganizations(input.sessionScope.knownOrganizations ?? []);
|
||||
let selectedOrganization = input.toNonEmptyString(input.sessionScope.selectedOrganization);
|
||||
let activeOrganization = input.toNonEmptyString(input.sessionScope.activeOrganization);
|
||||
const contextualInventoryHistoryCapabilityFollowup = input.modeDecision?.reason === "inventory_history_capability_followup_detected";
|
||||
const lastGroundedInventoryAddressDebug = contextualInventoryHistoryCapabilityFollowup
|
||||
? findLastGroundedInventoryAddressDebug(input.sessionItems)
|
||||
: null;
|
||||
if (capabilityMetaQuery && (destructiveSignal || dangerSignal)) {
|
||||
chatText = input.buildAssistantSafetyRefusalReply();
|
||||
livingChatSource = "deterministic_safety_refusal";
|
||||
@@ -61,6 +129,16 @@ async function runAssistantLivingChatRuntime(input) {
|
||||
chatText = input.buildAssistantOperationalBoundaryReply();
|
||||
livingChatSource = "deterministic_operational_boundary";
|
||||
}
|
||||
else if (contextualInventoryHistoryCapabilityFollowup) {
|
||||
const scopedOrganization = selectedOrganization ?? activeOrganization ?? null;
|
||||
chatText = buildInventoryHistoryCapabilityFollowupReply({
|
||||
organization: scopedOrganization,
|
||||
addressDebug: lastGroundedInventoryAddressDebug,
|
||||
toNonEmptyString: input.toNonEmptyString
|
||||
});
|
||||
activeOrganization = scopedOrganization ?? activeOrganization;
|
||||
livingChatSource = "deterministic_inventory_history_capability_contract";
|
||||
}
|
||||
else if (capabilityMetaQuery) {
|
||||
chatText = input.buildAssistantCapabilityContractReply();
|
||||
livingChatSource = "deterministic_capability_contract";
|
||||
|
||||
@@ -65,7 +65,7 @@ function normalizeOrganizationScopeValue(value) {
|
||||
if (!normalized) {
|
||||
return null;
|
||||
}
|
||||
let unwrapped = normalized.replace(/^\\+|\\+$/g, "").trim();
|
||||
let unwrapped = normalized.trim();
|
||||
if ((unwrapped.startsWith('"') && unwrapped.endsWith('"')) ||
|
||||
(unwrapped.startsWith("'") && unwrapped.endsWith("'"))) {
|
||||
unwrapped = unwrapped.slice(1, -1).trim();
|
||||
|
||||
+42
-2
@@ -2,11 +2,41 @@
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.resolveSessionOrganizationScopeContextRuntime = resolveSessionOrganizationScopeContextRuntime;
|
||||
exports.mergeFollowupContextWithOrganizationScopeRuntime = mergeFollowupContextWithOrganizationScopeRuntime;
|
||||
function extractOrganizationsFromNavigationState(addressNavigationState, normalizeOrganizationScopeValue) {
|
||||
if (!addressNavigationState || typeof addressNavigationState !== "object") {
|
||||
return [];
|
||||
}
|
||||
const collected = [];
|
||||
const directOrganization = normalizeOrganizationScopeValue(addressNavigationState.session_context?.organization_scope);
|
||||
if (directOrganization) {
|
||||
collected.push(directOrganization);
|
||||
}
|
||||
for (const resultSet of Array.isArray(addressNavigationState.result_sets) ? addressNavigationState.result_sets : []) {
|
||||
const scopedOrganization = normalizeOrganizationScopeValue(resultSet?.filters?.organization);
|
||||
if (scopedOrganization) {
|
||||
collected.push(scopedOrganization);
|
||||
}
|
||||
}
|
||||
return Array.from(new Map(collected.map((value) => [value.toLowerCase(), value])).values());
|
||||
}
|
||||
function resolveActiveOrganizationFromNavigationState(addressNavigationState, normalizeOrganizationScopeValue) {
|
||||
if (!addressNavigationState || typeof addressNavigationState !== "object") {
|
||||
return null;
|
||||
}
|
||||
return normalizeOrganizationScopeValue(addressNavigationState.session_context?.organization_scope);
|
||||
}
|
||||
function resolveSessionOrganizationScopeContextRuntime(input) {
|
||||
const knownOrganizations = input.extractKnownOrganizationsFromHistory(input.items);
|
||||
const knownOrganizations = Array.from(new Map([
|
||||
...extractOrganizationsFromNavigationState(input.addressNavigationState, input.normalizeOrganizationScopeValue),
|
||||
...input.extractKnownOrganizationsFromHistory(input.items)
|
||||
].map((value) => [String(value).toLowerCase(), value])).values());
|
||||
const selectedOrganization = input.resolveOrganizationSelectionFromMessage(input.userMessage, knownOrganizations);
|
||||
const lastActiveOrganization = input.findLastAssistantActiveOrganization(input.items);
|
||||
const activeOrganization = selectedOrganization ?? input.normalizeOrganizationScopeValue(lastActiveOrganization);
|
||||
const navigationActiveOrganization = resolveActiveOrganizationFromNavigationState(input.addressNavigationState, input.normalizeOrganizationScopeValue);
|
||||
const activeOrganization = selectedOrganization ??
|
||||
navigationActiveOrganization ??
|
||||
input.normalizeOrganizationScopeValue(lastActiveOrganization) ??
|
||||
(knownOrganizations.length === 1 ? knownOrganizations[0] : null);
|
||||
return {
|
||||
knownOrganizations,
|
||||
selectedOrganization,
|
||||
@@ -28,5 +58,15 @@ function mergeFollowupContextWithOrganizationScopeRuntime(input) {
|
||||
previousFilters.organization = normalizedOrganization;
|
||||
}
|
||||
base.previous_filters = previousFilters;
|
||||
const rootFiltersRaw = base.root_filters;
|
||||
const rootFilters = rootFiltersRaw && typeof rootFiltersRaw === "object"
|
||||
? { ...rootFiltersRaw }
|
||||
: {};
|
||||
if (!input.toNonEmptyString(rootFilters.organization)) {
|
||||
rootFilters.organization = normalizedOrganization;
|
||||
}
|
||||
if (Object.keys(rootFilters).length > 0) {
|
||||
base.root_filters = rootFilters;
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
+346
-12
@@ -1473,6 +1473,7 @@ function buildAddressDebugPayload(addressDebug, llmPreDecomposeMeta = null) {
|
||||
account_scope_drop_reason: addressDebug.account_scope_drop_reason,
|
||||
runtime_readiness: addressDebug.runtime_readiness,
|
||||
limited_reason_category: addressDebug.limited_reason_category,
|
||||
organization_candidates: addressDebug.organization_candidates ?? undefined,
|
||||
response_type: addressDebug.response_type,
|
||||
requested_result_mode: addressDebug.requested_result_mode ?? undefined,
|
||||
result_mode: addressDebug.result_mode ?? undefined,
|
||||
@@ -2790,9 +2791,18 @@ function resolveDebtRoleSwapFollowupIntent(userMessage, previousIntent) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function resolveAddressFollowupCarryoverContext(userMessage, items, alternateMessage = null, llmPreDecomposeMeta = null) {
|
||||
function resolveAddressFollowupCarryoverContext(userMessage, items, alternateMessage = null, llmPreDecomposeMeta = null, addressNavigationState = null) {
|
||||
const previousAddressItem = findLastAddressAssistantItem(items);
|
||||
const previousAddressDebug = previousAddressItem?.debug ?? null;
|
||||
const lastOrganizationClarificationDebug = findLastOrganizationClarificationAddressDebug(items);
|
||||
const organizationClarificationCandidates = Array.isArray(lastOrganizationClarificationDebug?.organization_candidates)
|
||||
? mergeKnownOrganizations(lastOrganizationClarificationDebug.organization_candidates)
|
||||
: [];
|
||||
const organizationClarificationSelection = resolveOrganizationSelectionFromMessage(userMessage, organizationClarificationCandidates) ??
|
||||
(toNonEmptyString(alternateMessage)
|
||||
? resolveOrganizationSelectionFromMessage(String(alternateMessage ?? ""), organizationClarificationCandidates)
|
||||
: null);
|
||||
const hasOrganizationClarificationContinuation = Boolean(lastOrganizationClarificationDebug && organizationClarificationSelection);
|
||||
const followupOffer = previousAddressDebug ? buildAddressFollowupOffer(previousAddressDebug) : null;
|
||||
const hasImplicitContinuationSignal = Boolean(previousAddressDebug) &&
|
||||
Boolean(followupOffer?.enabled) &&
|
||||
@@ -2823,10 +2833,15 @@ function resolveAddressFollowupCarryoverContext(userMessage, items, alternateMes
|
||||
!hasPrimaryFollowupSignal &&
|
||||
!hasAlternateFollowupSignal &&
|
||||
!hasImplicitContinuationSignal &&
|
||||
!hasOrganizationClarificationContinuation &&
|
||||
!hasIndexReferenceSignal) {
|
||||
return null;
|
||||
}
|
||||
if (!hasPrimaryFollowupSignal && !hasAlternateFollowupSignal && !hasImplicitContinuationSignal && !hasIndexReferenceSignal) {
|
||||
if (!hasPrimaryFollowupSignal &&
|
||||
!hasAlternateFollowupSignal &&
|
||||
!hasImplicitContinuationSignal &&
|
||||
!hasOrganizationClarificationContinuation &&
|
||||
!hasIndexReferenceSignal) {
|
||||
return null;
|
||||
}
|
||||
if (!previousAddressDebug) {
|
||||
@@ -2854,7 +2869,45 @@ function resolveAddressFollowupCarryoverContext(userMessage, items, alternateMes
|
||||
readAddressFilterString(previousAddressDebug, "counterparty") ??
|
||||
readAddressFilterString(previousAddressDebug, "account") ??
|
||||
readAddressFilterString(previousAddressDebug, "contract");
|
||||
const inventoryRootFrame = findRecentInventoryRootFrame(items);
|
||||
const navigationSessionContext = addressNavigationState && typeof addressNavigationState === "object"
|
||||
? (addressNavigationState.session_context && typeof addressNavigationState.session_context === "object"
|
||||
? addressNavigationState.session_context
|
||||
: null)
|
||||
: null;
|
||||
const navigationDateScope = navigationSessionContext && typeof navigationSessionContext.date_scope === "object"
|
||||
? navigationSessionContext.date_scope
|
||||
: null;
|
||||
const navigationOrganization = normalizeOrganizationScopeValue(navigationSessionContext?.organization_scope);
|
||||
const navigationFocusObject = navigationSessionContext && typeof navigationSessionContext.active_focus_object === "object"
|
||||
? navigationSessionContext.active_focus_object
|
||||
: null;
|
||||
const navigationFocusObjectType = toNonEmptyString(navigationFocusObject?.object_type);
|
||||
const navigationFocusObjectLabel = toNonEmptyString(navigationFocusObject?.label);
|
||||
const hasSelectedObjectInventorySignalPrimary = /(?:по\s+выбранному\s+объекту|по\s+этой\s+позиции|по\s+этому\s+товару|selected\s+object)/iu.test(String(userMessage ?? ""));
|
||||
const hasSelectedObjectInventorySignalAlternate = toNonEmptyString(alternateMessage)
|
||||
? /(?:по\s+выбранному\s+объекту|по\s+этой\s+позиции|по\s+этому\s+товару|selected\s+object)/iu.test(String(alternateMessage ?? ""))
|
||||
: false;
|
||||
let inventoryRootFrame = findRecentInventoryRootFrame(items);
|
||||
if (inventoryRootFrame && navigationOrganization && !toNonEmptyString(inventoryRootFrame.filters?.organization)) {
|
||||
inventoryRootFrame = {
|
||||
...inventoryRootFrame,
|
||||
filters: {
|
||||
...(inventoryRootFrame.filters ?? {}),
|
||||
organization: navigationOrganization
|
||||
}
|
||||
};
|
||||
}
|
||||
if (inventoryRootFrame && navigationDateScope) {
|
||||
inventoryRootFrame = {
|
||||
...inventoryRootFrame,
|
||||
filters: {
|
||||
...(inventoryRootFrame.filters ?? {}),
|
||||
as_of_date: toNonEmptyString(inventoryRootFrame.filters?.as_of_date) ?? toNonEmptyString(navigationDateScope.as_of_date) ?? undefined,
|
||||
period_from: toNonEmptyString(inventoryRootFrame.filters?.period_from) ?? toNonEmptyString(navigationDateScope.period_from) ?? undefined,
|
||||
period_to: toNonEmptyString(inventoryRootFrame.filters?.period_to) ?? toNonEmptyString(navigationDateScope.period_to) ?? undefined
|
||||
}
|
||||
};
|
||||
}
|
||||
const currentFrameKind = inventoryRootFrame
|
||||
? isInventoryDrilldownFrameIntent(sourceIntent)
|
||||
? "inventory_drilldown"
|
||||
@@ -2885,6 +2938,21 @@ function resolveAddressFollowupCarryoverContext(userMessage, items, alternateMes
|
||||
previousFilters.organization = historicalOrganization;
|
||||
}
|
||||
}
|
||||
if (!toNonEmptyString(previousFilters.organization) && navigationOrganization) {
|
||||
previousFilters.organization = navigationOrganization;
|
||||
}
|
||||
if (!toNonEmptyString(previousFilters.organization) && organizationClarificationSelection) {
|
||||
previousFilters.organization = organizationClarificationSelection;
|
||||
}
|
||||
if (!toNonEmptyString(previousFilters.as_of_date) && toNonEmptyString(navigationDateScope?.as_of_date)) {
|
||||
previousFilters.as_of_date = toNonEmptyString(navigationDateScope?.as_of_date);
|
||||
}
|
||||
if (!toNonEmptyString(previousFilters.period_from) && toNonEmptyString(navigationDateScope?.period_from)) {
|
||||
previousFilters.period_from = toNonEmptyString(navigationDateScope?.period_from);
|
||||
}
|
||||
if (!toNonEmptyString(previousFilters.period_to) && toNonEmptyString(navigationDateScope?.period_to)) {
|
||||
previousFilters.period_to = toNonEmptyString(navigationDateScope?.period_to);
|
||||
}
|
||||
const displayedEntityType = inferDisplayedEntityTypeFromIntent(sourceIntent);
|
||||
const displayedEntities = extractDisplayedAddressEntityCandidates(toNonEmptyString(previousAddressItem?.text) ?? "", displayedEntityType);
|
||||
const resolvedEntityFromFollowup = resolveDisplayedAddressEntityMention(userMessage, displayedEntities) ??
|
||||
@@ -2912,6 +2980,36 @@ function resolveAddressFollowupCarryoverContext(userMessage, items, alternateMes
|
||||
followupSelectionMode = "carry_referenced_entity";
|
||||
}
|
||||
}
|
||||
if (!toNonEmptyString(previousFilters.item) &&
|
||||
navigationFocusObjectType === "item" &&
|
||||
navigationFocusObjectLabel &&
|
||||
(sourceIntentHint === "inventory_on_hand_as_of_date" ||
|
||||
sourceIntentHint === "inventory_purchase_provenance_for_item" ||
|
||||
sourceIntentHint === "inventory_purchase_documents_for_item" ||
|
||||
sourceIntentHint === "inventory_sale_trace_for_item" ||
|
||||
sourceIntentHint === "inventory_purchase_to_sale_chain" ||
|
||||
sourceIntentHint === "inventory_aging_by_purchase_date" ||
|
||||
hasSelectedObjectInventorySignalPrimary ||
|
||||
hasSelectedObjectInventorySignalAlternate)) {
|
||||
previousFilters.item = navigationFocusObjectLabel;
|
||||
if (!previousAnchor) {
|
||||
previousAnchorType = "item";
|
||||
previousAnchor = navigationFocusObjectLabel;
|
||||
}
|
||||
}
|
||||
if (organizationClarificationSelection && !previousAnchor) {
|
||||
previousAnchorType = "organization";
|
||||
previousAnchor = organizationClarificationSelection;
|
||||
}
|
||||
if (inventoryRootFrame && organizationClarificationSelection && !toNonEmptyString(inventoryRootFrame.filters?.organization)) {
|
||||
inventoryRootFrame = {
|
||||
...inventoryRootFrame,
|
||||
filters: {
|
||||
...(inventoryRootFrame.filters ?? {}),
|
||||
organization: organizationClarificationSelection
|
||||
}
|
||||
};
|
||||
}
|
||||
if (!previousIntent && !previousAnchor && Object.keys(previousFilters).length === 0) {
|
||||
return null;
|
||||
}
|
||||
@@ -4036,6 +4134,28 @@ function resolveAssistantOrchestrationDecision(input) {
|
||||
const llmPreDecomposeMeta = input?.llmPreDecomposeMeta ?? null;
|
||||
const useMock = Boolean(input?.useMock);
|
||||
const sessionItems = Array.isArray(input?.sessionItems) ? input.sessionItems : null;
|
||||
const sessionOrganizationScope = input?.sessionOrganizationScope && typeof input.sessionOrganizationScope === "object"
|
||||
? input.sessionOrganizationScope
|
||||
: null;
|
||||
const lastGroundedAddressDebug = findLastGroundedAddressAnswerDebug(sessionItems);
|
||||
const lastOrganizationClarificationDebug = findLastOrganizationClarificationAddressDebug(sessionItems);
|
||||
const organizationClarificationCandidates = Array.isArray(lastOrganizationClarificationDebug?.organization_candidates)
|
||||
? mergeKnownOrganizations([
|
||||
...lastOrganizationClarificationDebug.organization_candidates,
|
||||
...((Array.isArray(sessionOrganizationScope?.knownOrganizations)
|
||||
? sessionOrganizationScope.knownOrganizations
|
||||
: []))
|
||||
])
|
||||
: [];
|
||||
const organizationClarificationSelectionFromScope = normalizeOrganizationScopeValue(sessionOrganizationScope?.selectedOrganization);
|
||||
const organizationClarificationSelection = resolveOrganizationSelectionFromMessage(rawUserMessage, organizationClarificationCandidates) ??
|
||||
resolveOrganizationSelectionFromMessage(repairedRawUserMessage, organizationClarificationCandidates) ??
|
||||
resolveOrganizationSelectionFromMessage(effectiveAddressUserMessage, organizationClarificationCandidates) ??
|
||||
resolveOrganizationSelectionFromMessage(repairedEffectiveAddressUserMessage, organizationClarificationCandidates) ??
|
||||
(organizationClarificationSelectionFromScope &&
|
||||
organizationClarificationCandidates.some((candidate) => normalizeOrganizationScopeValue(candidate) === organizationClarificationSelectionFromScope)
|
||||
? organizationClarificationSelectionFromScope
|
||||
: null);
|
||||
const dataScopeMetaQuery = hasAssistantDataScopeMetaQuestionSignal(rawUserMessage) ||
|
||||
hasAssistantDataScopeMetaQuestionSignal(repairedRawUserMessage) ||
|
||||
hasAssistantDataScopeMetaQuestionSignal(effectiveAddressUserMessage) ||
|
||||
@@ -4123,6 +4243,12 @@ function resolveAssistantOrchestrationDecision(input) {
|
||||
hasShortInventoryObjectFollowupSignal(repairedRawUserMessage) ||
|
||||
hasShortInventoryObjectFollowupSignal(effectiveAddressUserMessage) ||
|
||||
hasShortInventoryObjectFollowupSignal(repairedEffectiveAddressUserMessage)));
|
||||
const organizationClarificationContinuationDetected = Boolean(followupContext &&
|
||||
lastOrganizationClarificationDebug &&
|
||||
organizationClarificationSelection &&
|
||||
!dataScopeMetaQuery &&
|
||||
!capabilityMetaQuery &&
|
||||
!dataRetrievalSignal);
|
||||
const effectiveAddressFollowupSignal = explicitAddressFollowupSignal && !dangerOrCoercionSignal;
|
||||
const deterministicNonDomainGuard = Boolean(!dataScopeMetaQuery &&
|
||||
!capabilityMetaQuery &&
|
||||
@@ -4133,7 +4259,16 @@ function resolveAssistantOrchestrationDecision(input) {
|
||||
const nonDomainQueryIndexed = Boolean(!llmFirstAddressCandidate &&
|
||||
deterministicNonDomainGuard &&
|
||||
(llmFirstUnsupportedCandidate || llmContractMode === null) &&
|
||||
!protectedInventoryShortFollowup);
|
||||
!protectedInventoryShortFollowup &&
|
||||
!organizationClarificationContinuationDetected);
|
||||
const contextualHistoricalCapabilityFollowupDetected = Boolean(capabilityMetaQuery &&
|
||||
!dataScopeMetaQuery &&
|
||||
!dataRetrievalSignal &&
|
||||
(hasHistoricalCapabilityFollowupSignal(rawUserMessage) ||
|
||||
hasHistoricalCapabilityFollowupSignal(repairedRawUserMessage) ||
|
||||
hasHistoricalCapabilityFollowupSignal(effectiveAddressUserMessage) ||
|
||||
hasHistoricalCapabilityFollowupSignal(repairedEffectiveAddressUserMessage)) &&
|
||||
isGroundedInventoryContextDebug(lastGroundedAddressDebug));
|
||||
const hardMetaMode = dataScopeMetaQuery
|
||||
? "data_scope"
|
||||
: capabilityMetaQuery && !dataRetrievalSignal
|
||||
@@ -4168,6 +4303,34 @@ function resolveAssistantOrchestrationDecision(input) {
|
||||
};
|
||||
}
|
||||
if (hardMetaMode === "capability") {
|
||||
if (contextualHistoricalCapabilityFollowupDetected) {
|
||||
return {
|
||||
runAddressLane: false,
|
||||
toolGateDecision: "skip_address_lane",
|
||||
toolGateReason: "inventory_history_capability_followup_detected",
|
||||
livingMode: "chat",
|
||||
livingReason: "inventory_history_capability_followup_detected",
|
||||
orchestrationContract: {
|
||||
schema_version: "assistant_orchestration_contract_v1",
|
||||
hard_meta_mode: "capability",
|
||||
address_mode: resolvedModeDetection.mode,
|
||||
address_mode_confidence: resolvedModeDetection.confidence,
|
||||
address_intent: resolvedIntentResolution.intent,
|
||||
address_intent_confidence: resolvedIntentResolution.confidence,
|
||||
strong_data_signal_detected: strongDataSignal,
|
||||
data_retrieval_signal_detected: dataRetrievalSignal,
|
||||
followup_context_detected: Boolean(followupContext || lastGroundedAddressDebug),
|
||||
unsupported_address_intent_fallback_to_deep: false,
|
||||
final_decision: {
|
||||
run_address_lane: false,
|
||||
tool_gate_decision: "skip_address_lane",
|
||||
tool_gate_reason: "inventory_history_capability_followup_detected",
|
||||
living_mode: "chat",
|
||||
living_reason: "inventory_history_capability_followup_detected"
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
return {
|
||||
runAddressLane: false,
|
||||
toolGateDecision: "skip_address_lane",
|
||||
@@ -4223,6 +4386,10 @@ function resolveAssistantOrchestrationDecision(input) {
|
||||
}
|
||||
};
|
||||
}
|
||||
const metaAnswerFollowupSignal = hasMetaAnswerFollowupSignal(rawUserMessage) ||
|
||||
hasMetaAnswerFollowupSignal(repairedRawUserMessage) ||
|
||||
hasMetaAnswerFollowupSignal(effectiveAddressUserMessage) ||
|
||||
hasMetaAnswerFollowupSignal(repairedEffectiveAddressUserMessage);
|
||||
const baseToolGate = resolveAddressToolGateDecision(effectiveAddressUserMessage, followupContext, llmPreDecomposeMeta, rawUserMessage);
|
||||
const preserveAddressLaneSignal = Boolean((llmPreDecomposeMeta?.llmCanonicalCandidateDetected &&
|
||||
llmPreDecomposeMeta?.applied &&
|
||||
@@ -4317,6 +4484,19 @@ function resolveAssistantOrchestrationDecision(input) {
|
||||
repairedEffectiveAddressUserMessage,
|
||||
sessionItems
|
||||
}));
|
||||
const hasPriorAddressAnswerContext = Boolean(lastGroundedAddressDebug || toNonEmptyString(followupContext?.previous_intent));
|
||||
const metaFollowupOverGroundedAnswer = Boolean(followupContext &&
|
||||
hasPriorAddressAnswerContext &&
|
||||
metaAnswerFollowupSignal &&
|
||||
!dataScopeMetaQuery &&
|
||||
!capabilityMetaQuery &&
|
||||
!aggregateBusinessAnalyticsSignal &&
|
||||
!dataRetrievalSignal &&
|
||||
!strongDataSignal &&
|
||||
resolvedModeDetection.mode !== "address_query" &&
|
||||
resolvedIntentResolution.intent === "unknown" &&
|
||||
(!llmContractIntent || llmContractIntent === "unknown") &&
|
||||
llmContractMode !== "address_query");
|
||||
let runAddressLane = Boolean(baseToolGate?.runAddressLane);
|
||||
let toolGateDecision = String(baseToolGate?.decision ?? "skip_address_lane");
|
||||
let toolGateReason = String(baseToolGate?.reason ?? "no_address_signal_after_l0");
|
||||
@@ -4342,6 +4522,11 @@ function resolveAssistantOrchestrationDecision(input) {
|
||||
toolGateDecision = "skip_address_lane";
|
||||
toolGateReason = "deep_session_continuation_fallback_to_deep";
|
||||
}
|
||||
if (metaFollowupOverGroundedAnswer) {
|
||||
runAddressLane = false;
|
||||
toolGateDecision = "skip_address_lane";
|
||||
toolGateReason = "meta_followup_over_grounded_answer";
|
||||
}
|
||||
let livingDecision = resolveLivingAssistantModeDecision({
|
||||
userMessage: rawUserMessage,
|
||||
addressLaneTriggered: runAddressLane,
|
||||
@@ -4375,6 +4560,12 @@ function resolveAssistantOrchestrationDecision(input) {
|
||||
reason: "deep_session_continuation_fallback_to_deep"
|
||||
};
|
||||
}
|
||||
if (metaFollowupOverGroundedAnswer) {
|
||||
livingDecision = {
|
||||
mode: "chat",
|
||||
reason: "meta_followup_over_grounded_answer"
|
||||
};
|
||||
}
|
||||
return {
|
||||
runAddressLane,
|
||||
toolGateDecision,
|
||||
@@ -4476,6 +4667,105 @@ function findLastAssistantLivingChatDebug(items) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function findLastGroundedAddressAnswerDebug(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 (debug.execution_lane !== "address_query") {
|
||||
continue;
|
||||
}
|
||||
const groundingStatus = toNonEmptyString(debug.answer_grounding_check?.status);
|
||||
if (groundingStatus === "grounded") {
|
||||
return debug;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function findLastOrganizationClarificationAddressDebug(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 (debug.execution_lane !== "address_query" && debug.detected_mode !== "address_query") {
|
||||
continue;
|
||||
}
|
||||
const limitedCategory = toNonEmptyString(debug.limited_reason_category);
|
||||
const candidates = Array.isArray(debug.organization_candidates)
|
||||
? mergeKnownOrganizations(debug.organization_candidates)
|
||||
: [];
|
||||
if (limitedCategory === "missing_anchor" && candidates.length > 0) {
|
||||
return debug;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function hasMetaAnswerFollowupSignal(userMessage) {
|
||||
const rawText = compactWhitespace(String(userMessage ?? "").toLowerCase());
|
||||
const repairedText = compactWhitespace(repairAddressMojibake(String(userMessage ?? "")).toLowerCase());
|
||||
const samples = [rawText, repairedText]
|
||||
.filter((item) => item.length > 0)
|
||||
.map((item) => item.replace(/ё/g, "е"));
|
||||
if (samples.length === 0) {
|
||||
return false;
|
||||
}
|
||||
const hasReflectionCue = samples.some((sample) => sample.includes("дума") ||
|
||||
sample.includes("скаж") ||
|
||||
sample.includes("мнение") ||
|
||||
sample.includes("как тебе") ||
|
||||
sample.includes("норм") ||
|
||||
sample.includes("стран") ||
|
||||
sample.includes("логич") ||
|
||||
sample.includes("смуща") ||
|
||||
sample.includes("выгляд"));
|
||||
const hasTopicPointerCue = samples.some((sample) => sample.includes("на эту тему") ||
|
||||
sample.includes("по этому поводу") ||
|
||||
sample.includes("об этом") ||
|
||||
(sample.includes("это") && hasReferentialPointer(sample)));
|
||||
if (!(hasReflectionCue && hasTopicPointerCue)) {
|
||||
return false;
|
||||
}
|
||||
return !samples.some((sample) => hasAssistantDataScopeMetaQuestionSignal(sample) ||
|
||||
shouldHandleAsAssistantCapabilityMetaQuery(sample) ||
|
||||
hasDataRetrievalRequestSignal(sample) ||
|
||||
hasStrongDataIntentSignal(sample));
|
||||
}
|
||||
function hasHistoricalCapabilityFollowupSignal(text) {
|
||||
const repaired = repairAddressMojibake(String(text ?? ""));
|
||||
const normalized = compactWhitespace(repaired.toLowerCase()).replace(/ё/g, "е");
|
||||
if (!normalized) {
|
||||
return false;
|
||||
}
|
||||
const hasHistoryCue = /(?:историческ|история|архив|прошл(?:ый|ые|ую|ых)?|раньше|ретро|старые\s+данные)/iu.test(normalized);
|
||||
if (!hasHistoryCue) {
|
||||
return false;
|
||||
}
|
||||
return /(?:мож(?:ешь|ете|но)|уме(?:ешь|ете)|показ|вывед|дай|раскрой)/iu.test(normalized);
|
||||
}
|
||||
function isGroundedInventoryContextDebug(debug) {
|
||||
if (!debug || typeof debug !== "object") {
|
||||
return false;
|
||||
}
|
||||
const detectedIntent = toNonEmptyString(debug.detected_intent);
|
||||
const capabilityId = toNonEmptyString(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 = toNonEmptyString(rootFrameContext?.root_intent);
|
||||
return detectedIntent === "inventory_on_hand_as_of_date" ||
|
||||
capabilityId === "confirmed_inventory_on_hand_as_of_date" ||
|
||||
rootIntent === "inventory_on_hand_as_of_date";
|
||||
}
|
||||
function hasOrganizationFactFollowupSignal(userMessage, items) {
|
||||
const repaired = repairAddressMojibake(String(userMessage ?? ""));
|
||||
const normalized = compactWhitespace(repaired.toLowerCase()).replace(/ё/g, "е");
|
||||
@@ -4764,7 +5054,6 @@ function normalizeOrganizationScopeValue(value) {
|
||||
return null;
|
||||
}
|
||||
const unwrapped = normalized
|
||||
.replace(/^\\+|\\+$/g, "")
|
||||
.replace(/^"+|"+$/g, "")
|
||||
.replace(/^'+|'+$/g, "")
|
||||
.trim();
|
||||
@@ -4905,8 +5194,34 @@ function mergeKnownOrganizations(values) {
|
||||
}
|
||||
return Array.from(dedup.values()).slice(0, 20);
|
||||
}
|
||||
function extractKnownOrganizationsFromHistory(items) {
|
||||
function extractKnownOrganizationsFromNavigationState(addressNavigationState) {
|
||||
if (!addressNavigationState || typeof addressNavigationState !== "object") {
|
||||
return [];
|
||||
}
|
||||
const collected = [];
|
||||
const sessionContext = addressNavigationState.session_context && typeof addressNavigationState.session_context === "object"
|
||||
? addressNavigationState.session_context
|
||||
: null;
|
||||
const directOrganization = normalizeOrganizationScopeValue(sessionContext?.organization_scope);
|
||||
if (directOrganization) {
|
||||
collected.push(directOrganization);
|
||||
}
|
||||
const resultSets = Array.isArray(addressNavigationState.result_sets) ? addressNavigationState.result_sets : [];
|
||||
for (const resultSet of resultSets) {
|
||||
const filters = resultSet?.filters && typeof resultSet.filters === "object" ? resultSet.filters : null;
|
||||
const scopedOrganization = normalizeOrganizationScopeValue(filters?.organization);
|
||||
if (scopedOrganization) {
|
||||
collected.push(scopedOrganization);
|
||||
}
|
||||
}
|
||||
return mergeKnownOrganizations(collected);
|
||||
}
|
||||
function extractKnownOrganizationsFromHistory(items, addressNavigationState = null) {
|
||||
const collected = [];
|
||||
const navigationOrganizations = extractKnownOrganizationsFromNavigationState(addressNavigationState);
|
||||
if (navigationOrganizations.length > 0) {
|
||||
collected.push(...navigationOrganizations);
|
||||
}
|
||||
for (let index = (Array.isArray(items) ? items.length : 0) - 1; index >= 0; index -= 1) {
|
||||
const item = items[index];
|
||||
if (!item || item.role !== "assistant") {
|
||||
@@ -4920,8 +5235,17 @@ function extractKnownOrganizationsFromHistory(items) {
|
||||
const knownFromDebug = Array.isArray(debug.assistant_known_organizations)
|
||||
? debug.assistant_known_organizations
|
||||
: [];
|
||||
if (directFromProbe.length > 0 || knownFromDebug.length > 0) {
|
||||
collected.push(...directFromProbe, ...knownFromDebug);
|
||||
const directFromCandidates = Array.isArray(debug.organization_candidates)
|
||||
? debug.organization_candidates
|
||||
: [];
|
||||
const directFromResolved = [
|
||||
normalizeOrganizationScopeValue(debug.assistant_active_organization),
|
||||
normalizeOrganizationScopeValue(debug.living_chat_selected_organization),
|
||||
normalizeOrganizationScopeValue(debug.extracted_filters?.organization),
|
||||
normalizeOrganizationScopeValue(debug.address_root_frame_context?.organization)
|
||||
].filter(Boolean);
|
||||
if (directFromProbe.length > 0 || knownFromDebug.length > 0 || directFromCandidates.length > 0 || directFromResolved.length > 0) {
|
||||
collected.push(...directFromProbe, ...knownFromDebug, ...directFromCandidates, ...directFromResolved);
|
||||
}
|
||||
}
|
||||
const parsedFromText = parseOrganizationsFromDataScopeAssistantText(item.text);
|
||||
@@ -4934,7 +5258,16 @@ function extractKnownOrganizationsFromHistory(items) {
|
||||
}
|
||||
return mergeKnownOrganizations(collected);
|
||||
}
|
||||
function findLastAssistantActiveOrganization(items) {
|
||||
function findLastAssistantActiveOrganization(items, addressNavigationState = null) {
|
||||
const sessionContext = addressNavigationState && typeof addressNavigationState === "object"
|
||||
? (addressNavigationState.session_context && typeof addressNavigationState.session_context === "object"
|
||||
? addressNavigationState.session_context
|
||||
: null)
|
||||
: null;
|
||||
const navigationOrganization = normalizeOrganizationScopeValue(sessionContext?.organization_scope);
|
||||
if (navigationOrganization) {
|
||||
return navigationOrganization;
|
||||
}
|
||||
for (let index = (Array.isArray(items) ? items.length : 0) - 1; index >= 0; index -= 1) {
|
||||
const item = items[index];
|
||||
if (!item || item.role !== "assistant" || !item.debug || typeof item.debug !== "object") {
|
||||
@@ -4980,10 +5313,11 @@ function resolveOrganizationSelectionFromMessage(userMessage, knownOrganizations
|
||||
}
|
||||
return best.organization;
|
||||
}
|
||||
function resolveSessionOrganizationScopeContext(userMessage, items) {
|
||||
function resolveSessionOrganizationScopeContext(userMessage, items, addressNavigationState = null) {
|
||||
return (0, assistantOrganizationScopeRuntimeAdapter_1.resolveSessionOrganizationScopeContextRuntime)({
|
||||
userMessage,
|
||||
items,
|
||||
addressNavigationState,
|
||||
extractKnownOrganizationsFromHistory,
|
||||
resolveOrganizationSelectionFromMessage,
|
||||
findLastAssistantActiveOrganization,
|
||||
@@ -4998,8 +5332,8 @@ function mergeFollowupContextWithOrganizationScope(followupContext, organization
|
||||
toNonEmptyString
|
||||
});
|
||||
}
|
||||
function resolveSessionOrganizationScopeContextForTests(userMessage, items) {
|
||||
return resolveSessionOrganizationScopeContext(userMessage, items);
|
||||
function resolveSessionOrganizationScopeContextForTests(userMessage, items, addressNavigationState = null) {
|
||||
return resolveSessionOrganizationScopeContext(userMessage, items, addressNavigationState);
|
||||
}
|
||||
function normalizeGuidValue(value) {
|
||||
const source = normalizeScopeLabel(value);
|
||||
|
||||
@@ -8,6 +8,7 @@ function buildAssistantTurnAttemptAddressRuntimeInput(input) {
|
||||
sessionId: input.userTurn.sessionId,
|
||||
userMessage: input.userTurn.userMessage,
|
||||
sessionItems: input.userTurn.session.items,
|
||||
sessionAddressNavigationState: input.userTurn.session.address_navigation_state ?? null,
|
||||
runtimeAnalysisContext: input.userTurn.runtimeAnalysisContext,
|
||||
sessionOrganizationScope: input.sessionOrganizationScope
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@ exports.runAssistantTurnAttemptRuntime = runAssistantTurnAttemptRuntime;
|
||||
const assistantTurnAttemptInputBuilder_1 = require("./assistantTurnAttemptInputBuilder");
|
||||
async function runAssistantTurnAttemptRuntime(input) {
|
||||
const userTurn = input.runUserTurnBootstrapRuntime(input.payload);
|
||||
const sessionOrganizationScope = input.resolveSessionOrganizationScopeContext(userTurn.userMessage, userTurn.session.items);
|
||||
const sessionOrganizationScope = input.resolveSessionOrganizationScopeContext(userTurn.userMessage, userTurn.session.items, userTurn.session.address_navigation_state ?? null);
|
||||
const addressRuntime = await input.runAddressAttemptRuntime((0, assistantTurnAttemptInputBuilder_1.buildAssistantTurnAttemptAddressRuntimeInput)({
|
||||
payload: input.payload,
|
||||
userTurn,
|
||||
|
||||
@@ -23,6 +23,7 @@ function buildAssistantAddressAttemptRuntimeInput(runtimeInput, deps) {
|
||||
sessionId: runtimeInput.sessionId,
|
||||
userMessage: runtimeInput.userMessage,
|
||||
sessionItems: runtimeInput.sessionItems,
|
||||
sessionAddressNavigationState: runtimeInput.sessionAddressNavigationState,
|
||||
payload: runtimeInput.payload,
|
||||
sessionScope: {
|
||||
knownOrganizations: runtimeInput.sessionOrganizationScope.knownOrganizations,
|
||||
|
||||
Reference in New Issue
Block a user