NODEDC_1C/llm_normalizer/backend/src/services/assistantCapabilityBindingR...

119 lines
4.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import type { AssistantReplyType } from "../types/assistant";
import type {
AssistantCapabilityBindingAction,
AssistantCapabilityRuntimeBindingContract
} from "../types/assistantRuntimeContracts";
export interface ApplyAssistantCapabilityBindingResponseGuardInput {
assistantReply: string;
replyType: AssistantReplyType;
capabilityBinding: AssistantCapabilityRuntimeBindingContract | null | undefined;
}
export interface AssistantCapabilityBindingResponseGuardAudit {
schema_version: "assistant_capability_binding_response_guard_v1";
guard_owner: "assistantCapabilityBindingResponseGuard";
applied: boolean;
action: AssistantCapabilityBindingAction | "none";
original_reply_type: AssistantReplyType;
guarded_reply_type: AssistantReplyType;
reason_codes: string[];
}
export interface ApplyAssistantCapabilityBindingResponseGuardOutput {
assistantReply: string;
replyType: AssistantReplyType;
audit: AssistantCapabilityBindingResponseGuardAudit;
}
function formatMissingAnchors(anchors: string[]): string {
if (anchors.length === 0) {
return "нужный объект, период или организацию";
}
return anchors.join(", ");
}
function buildClarificationReply(binding: AssistantCapabilityRuntimeBindingContract): string {
if (binding.capability_contract_id === "inventory_inventory_margin_ranking_for_nomenclature") {
return [
"Для рейтинга прибыльности номенклатуры нужен период.",
"Могу посчитать по номенклатуре: выручку без НДС, себестоимость реализации, валовую прибыль и маржинальность.",
"Уточните период: месяц, квартал, год или весь доступный период."
].join("\n\n");
}
return [
"Нужно уточнение, чтобы не подставить неподтвержденный объект в расчет.",
`Не хватает: ${formatMissingAnchors(binding.missing_anchors)}.`,
"Уточните это, и я продолжу тот же сценарий."
].join("\n");
}
function buildBlockedReply(binding: AssistantCapabilityRuntimeBindingContract): string {
const reasons = binding.violations.length > 0 ? binding.violations.join(", ") : "runtime_binding_blocked";
return [
"Не могу надежно подтвердить ответ в текущем контексте.",
`Проверка сценария остановила ответ: ${reasons}.`,
"Лучше уточнить объект или перезапустить вопрос от корневого запроса, чем выдавать это как подтвержденный факт."
].join("\n");
}
export function applyAssistantCapabilityBindingResponseGuard(
input: ApplyAssistantCapabilityBindingResponseGuardInput
): ApplyAssistantCapabilityBindingResponseGuardOutput {
const binding = input.capabilityBinding;
const baseAudit: AssistantCapabilityBindingResponseGuardAudit = {
schema_version: "assistant_capability_binding_response_guard_v1",
guard_owner: "assistantCapabilityBindingResponseGuard",
applied: false,
action: binding?.binding_action ?? "none",
original_reply_type: input.replyType,
guarded_reply_type: input.replyType,
reason_codes: binding?.reason_codes ?? []
};
if (!binding || binding.binding_action === "allow" || binding.binding_action === "observe_only") {
return {
assistantReply: input.assistantReply,
replyType: input.replyType,
audit: baseAudit
};
}
if (binding.binding_action === "clarify") {
return {
assistantReply: buildClarificationReply(binding),
replyType: "partial_coverage",
audit: {
...baseAudit,
applied: true,
guarded_reply_type: "partial_coverage",
reason_codes: [...baseAudit.reason_codes, "capability_binding_guard_clarification_reply"]
}
};
}
if (binding.binding_action === "block") {
return {
assistantReply: buildBlockedReply(binding),
replyType: "partial_coverage",
audit: {
...baseAudit,
applied: true,
guarded_reply_type: "partial_coverage",
reason_codes: [...baseAudit.reason_codes, "capability_binding_guard_blocked_reply"]
}
};
}
return {
assistantReply: input.assistantReply,
replyType: input.replyType === "factual" ? "partial_coverage" : input.replyType,
audit: {
...baseAudit,
applied: input.replyType === "factual",
guarded_reply_type: input.replyType === "factual" ? "partial_coverage" : input.replyType,
reason_codes: [...baseAudit.reason_codes, "capability_binding_guard_limited_reply_type"]
}
};
}