АРЧ - Ассистент: отделить reference-срез от execution-окна в ответах по lifecycle follow-up
This commit is contained in:
@@ -57,6 +57,84 @@ export interface AssistantLivingChatRuntimeOutput {
|
||||
debug: Record<string, unknown> | null;
|
||||
}
|
||||
|
||||
function formatIsoDateForReply(value: unknown): string | null {
|
||||
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: unknown[]): Record<string, unknown> | null {
|
||||
if (!Array.isArray(items)) {
|
||||
return null;
|
||||
}
|
||||
for (let index = items.length - 1; index >= 0; index -= 1) {
|
||||
const item = items[index] as { role?: string; debug?: Record<string, unknown> } | null;
|
||||
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 as Record<string, unknown>)
|
||||
: 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 as Record<string, unknown>)
|
||||
: 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: {
|
||||
organization: string | null;
|
||||
addressDebug: Record<string, unknown> | null;
|
||||
toNonEmptyString: (value: unknown) => string | null;
|
||||
}): string {
|
||||
const rootFrameContext =
|
||||
input.addressDebug?.address_root_frame_context && typeof input.addressDebug.address_root_frame_context === "object"
|
||||
? (input.addressDebug.address_root_frame_context as Record<string, unknown>)
|
||||
: null;
|
||||
const extractedFilters =
|
||||
input.addressDebug?.extracted_filters && typeof input.addressDebug.extracted_filters === "object"
|
||||
? (input.addressDebug.extracted_filters as Record<string, unknown>)
|
||||
: 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");
|
||||
}
|
||||
|
||||
export async function runAssistantLivingChatRuntime(
|
||||
input: AssistantLivingChatRuntimeInput
|
||||
): Promise<AssistantLivingChatRuntimeOutput> {
|
||||
@@ -77,6 +155,11 @@ export async function runAssistantLivingChatRuntime(
|
||||
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();
|
||||
@@ -119,6 +202,15 @@ export async function runAssistantLivingChatRuntime(
|
||||
} else if (capabilityMetaQuery && operationalSignal && !input.hasAssistantCapabilityQuestionSignal(userMessage)) {
|
||||
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";
|
||||
|
||||
Reference in New Issue
Block a user