ГЛОБАЛЬНЫЙ РЕФАКТОРИНГ АРХИТЕКТУРЫ - Рефакторинг этапов Stage 3.4: расширен soft-refusal в deep/living, чтобы при слабом/непокрытом домене не падать в шаблонный Коротко/Что сломано.

This commit is contained in:
2026-04-11 17:41:07 +03:00
parent a0d0f95dde
commit 88d5561fad
5 changed files with 386 additions and 15 deletions
@@ -1895,6 +1895,17 @@ function formatNarrativeDomainLabel(domain: P0NarrativeDomain): string {
return "доступного учетного контура";
}
function pickDeterministicBoundaryVariant(seed: string, variants: string[]): string {
if (variants.length === 0) {
return "";
}
let score = 0;
for (const char of String(seed ?? "")) {
score = (score + char.charCodeAt(0)) % 104_729;
}
return variants[score % variants.length];
}
function pickBoundaryCapabilityLines(userMessage: string, limit = 3): string[] {
const text = String(userMessage ?? "").toLowerCase();
const scored = BOUNDARY_CAPABILITY_SUGGESTIONS.map((item, index) => ({
@@ -1910,6 +1921,17 @@ function pickBoundaryCapabilityLines(userMessage: string, limit = 3): string[] {
return uniqueStrings(selected.map((item) => `${item.label}: ${item.helpText}`), limit);
}
function buildBoundaryQuickActionLine(capabilities: string[]): string | null {
const actions = capabilities
.slice(0, 2)
.map((item) => item.replace(/:\s*/u, " ").trim())
.filter((item) => item.length > 0);
if (actions.length === 0) {
return null;
}
return `Что могу сделать сейчас: ${actions.join("; ")}.`;
}
function buildNaturalClarificationHints(input: {
missingAnchors: MissingAnchors;
coverageReport: RequirementCoverageReport;
@@ -1942,11 +1964,15 @@ function shouldUseBoundaryFallbackReply(input: {
coverageReport: RequirementCoverageReport;
okResultsCount: number;
partialResultsCount: number;
focusDomain: P0NarrativeDomain;
focusDomainGroundingBlocked: boolean;
aggregateEvidenceConfidence: EvidenceConfidence;
hasCriticalEvidenceLimitation: boolean;
}): boolean {
if (input.mode === "out_of_scope") {
return true;
}
if (input.mode !== "clarification_required" && input.mode !== "no_grounded") {
if (input.mode !== "clarification_required" && input.mode !== "no_grounded" && input.mode !== "broad_partial") {
return false;
}
const hasNoEvidenceRoutes = input.okResultsCount === 0 && input.partialResultsCount === 0;
@@ -1957,7 +1983,19 @@ function shouldUseBoundaryFallbackReply(input: {
input.groundingCheck.status === "no_grounded_answer" ||
input.groundingCheck.status === "partial" ||
input.groundingCheck.status === "route_mismatch_blocked";
return hasNoEvidenceRoutes && hasNoConfirmedCoverage && groundingBlocked;
if (hasNoEvidenceRoutes && hasNoConfirmedCoverage && groundingBlocked) {
return true;
}
const domainNotCovered = input.focusDomain === null || input.focusDomainGroundingBlocked;
const weakEvidenceEnvelope =
input.okResultsCount === 0 &&
(input.partialResultsCount === 0 ||
input.aggregateEvidenceConfidence === "low" ||
input.hasCriticalEvidenceLimitation);
if (domainNotCovered && hasNoConfirmedCoverage && groundingBlocked && weakEvidenceEnvelope) {
return true;
}
return false;
}
function buildBoundaryFallbackReply(input: {
@@ -1967,11 +2005,20 @@ function buildBoundaryFallbackReply(input: {
coverageReport: RequirementCoverageReport;
}): string {
const nearbyCapabilities = pickBoundaryCapabilityLines(input.userMessage, 3);
const quickActionLine = buildBoundaryQuickActionLine(nearbyCapabilities);
if (input.focusDomain === null) {
return sanitizeUserFacingReply(
const heading = pickDeterministicBoundaryVariant(
input.userMessage,
[
"По этому запросу у меня нет надежного доменного покрытия, поэтому даю мягкий отказ вместо технического шаблона.",
"Сейчас у меня нет надежного доменного маршрута по этому запросу, поэтому даю мягкий отказ вместо шаблонной технички."
]
);
return sanitizeUserFacingReply(
[
heading,
nearbyCapabilities.length > 0 ? `Что могу сделать рядом по смыслу:\n${formatList(nearbyCapabilities)}` : "",
quickActionLine ?? "",
"Переформулируй вопрос через один из вариантов выше, и я сразу перейду к проверке по данным 1С."
]
.filter(Boolean)
@@ -1983,11 +2030,19 @@ function buildBoundaryFallbackReply(input: {
missingAnchors: input.missingAnchors,
coverageReport: input.coverageReport
});
return sanitizeUserFacingReply(
const domainHeading = pickDeterministicBoundaryVariant(
`${input.userMessage}|${input.focusDomain}`,
[
`Сейчас не могу надежно ответить по сценарию ${formatNarrativeDomainLabel(input.focusDomain)}: не хватает опоры.`,
`По сценарию ${formatNarrativeDomainLabel(input.focusDomain)} пока не хватает подтвержденной опоры для надежного вывода.`
]
);
return sanitizeUserFacingReply(
[
domainHeading,
clarificationHints.length > 0 ? `Чтобы сразу перейти к проверке, уточни:\n${formatList(clarificationHints)}` : "",
nearbyCapabilities.length > 0 ? `Если удобнее, могу начать с близкого сценария:\n${formatList(nearbyCapabilities.slice(0, 2))}` : ""
nearbyCapabilities.length > 0 ? `Если удобнее, могу начать с близкого сценария:\n${formatList(nearbyCapabilities.slice(0, 2))}` : "",
quickActionLine ?? ""
]
.filter(Boolean)
.join("\n\n")
@@ -4364,7 +4419,11 @@ function composeAssistantAnswerV11(input: ComposeAnswerInput): ComposeAnswerOutp
groundingCheck: input.groundingCheck,
coverageReport: input.coverageReport,
okResultsCount: okResults.length,
partialResultsCount: partialResults.length
partialResultsCount: partialResults.length,
focusDomain: focusNarrativeDomain,
focusDomainGroundingBlocked,
aggregateEvidenceConfidence,
hasCriticalEvidenceLimitation
});
const hasProblemWeakSignal =
policySignals.narrowing_strength !== "strong" ||