ГЛОБАЛЬНЫЙ РЕФАКТОРИНГ АРХИТЕКТУРЫ - Рефакторинг этапов 2.2.1 - фикс деградаций по старым доменам + легкая доводка регрессий перед стартом 3его этапа
This commit is contained in:
parent
b5bd4fd737
commit
66402439dc
|
|
@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.__autoRunsQuestionTestUtils = void 0;
|
||||
exports.buildAutoRunsRouter = buildAutoRunsRouter;
|
||||
const fs_1 = __importDefault(require("fs"));
|
||||
const path_1 = __importDefault(require("path"));
|
||||
|
|
@ -1148,6 +1149,61 @@ function sanitizeGeneratedQuestion(value) {
|
|||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
}
|
||||
const AUTOGEN_QUESTION_PLACEHOLDER_PATTERN = /^(?:questions?|вопросы?|список\s+вопросов)$/iu;
|
||||
const AUTOGEN_QUESTION_TAIL_PATTERNS = [
|
||||
/^(?:без\s+воды|по\s+факту|и\s+коротко|коротко|прям(?:\s+)?сейчас|за\s+весь\s+период|по\s+делу)\??$/iu
|
||||
];
|
||||
function stripAutogenQuestionSuffix(value) {
|
||||
return sanitizeGeneratedQuestion(value).replace(/[?!.:,;]+$/u, "").trim();
|
||||
}
|
||||
function isAutogenQuestionPlaceholder(value) {
|
||||
const core = stripAutogenQuestionSuffix(value).toLowerCase();
|
||||
return core.length > 0 && AUTOGEN_QUESTION_PLACEHOLDER_PATTERN.test(core);
|
||||
}
|
||||
function isLikelyAutogenQuestionTail(value) {
|
||||
const core = stripAutogenQuestionSuffix(value).toLowerCase();
|
||||
if (!core) {
|
||||
return false;
|
||||
}
|
||||
if (isAutogenQuestionPlaceholder(core)) {
|
||||
return true;
|
||||
}
|
||||
return AUTOGEN_QUESTION_TAIL_PATTERNS.some((pattern) => pattern.test(core));
|
||||
}
|
||||
function mergeAutogenQuestionTail(baseQuestion, tail) {
|
||||
const base = stripAutogenQuestionSuffix(baseQuestion);
|
||||
const suffix = stripAutogenQuestionSuffix(tail);
|
||||
if (!base) {
|
||||
return suffix ? `${suffix}?` : "";
|
||||
}
|
||||
if (!suffix) {
|
||||
return `${base}?`;
|
||||
}
|
||||
return `${base} ${suffix}?`
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
}
|
||||
function normalizeAutogenQuestionCandidates(candidates) {
|
||||
const normalized = [];
|
||||
for (const candidate of candidates) {
|
||||
const question = sanitizeGeneratedQuestion(candidate);
|
||||
if (!question) {
|
||||
continue;
|
||||
}
|
||||
if (isAutogenQuestionPlaceholder(question)) {
|
||||
continue;
|
||||
}
|
||||
if (isLikelyAutogenQuestionTail(question) && normalized.length > 0) {
|
||||
const merged = mergeAutogenQuestionTail(normalized[normalized.length - 1], question);
|
||||
if (merged) {
|
||||
normalized[normalized.length - 1] = merged;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
normalized.push(question);
|
||||
}
|
||||
return normalized.filter((item) => item.length > 0);
|
||||
}
|
||||
function splitQuestionCandidates(rawText) {
|
||||
const normalized = repairAutogenMojibake(rawText).replace(/\r/g, "\n").trim();
|
||||
if (!normalized)
|
||||
|
|
@ -1159,27 +1215,30 @@ function splitQuestionCandidates(rawText) {
|
|||
.map((line) => sanitizeGeneratedQuestion(line))
|
||||
.filter((line) => line.length > 0);
|
||||
if (byLines.length > 1) {
|
||||
return byLines;
|
||||
return normalizeAutogenQuestionCandidates(byLines);
|
||||
}
|
||||
const questionMarkCount = (unescaped.match(/\?/g) ?? []).length;
|
||||
if (questionMarkCount > 1) {
|
||||
const byQuestion = unescaped
|
||||
.split("?")
|
||||
.map((chunk) => sanitizeGeneratedQuestion(chunk))
|
||||
.filter((chunk) => chunk.length > 0)
|
||||
.map((chunk) => (chunk.endsWith("?") ? chunk : `${chunk}?`));
|
||||
if (byQuestion.length > 1) {
|
||||
return byQuestion;
|
||||
const questionChunks = Array.from(unescaped.matchAll(/[^?]+(?:\?|$)/g))
|
||||
.map((match) => sanitizeGeneratedQuestion(match[0]))
|
||||
.filter((chunk) => chunk.length > 0);
|
||||
if (questionChunks.length > 1) {
|
||||
const canSafelySplit = questionChunks.every((chunk) => !isAutogenQuestionPlaceholder(chunk) &&
|
||||
!isLikelyAutogenQuestionTail(chunk) &&
|
||||
sanitizeGeneratedQuestion(chunk).length >= 18);
|
||||
if (canSafelySplit) {
|
||||
return normalizeAutogenQuestionCandidates(questionChunks.map((chunk) => (chunk.endsWith("?") ? chunk : `${chunk}?`)));
|
||||
}
|
||||
}
|
||||
}
|
||||
const quoted = Array.from(unescaped.matchAll(/"([^"\n]{6,}?)"/g))
|
||||
.map((match) => sanitizeGeneratedQuestion(match[1]))
|
||||
.filter((line) => line.length > 0);
|
||||
if (quoted.length > 1) {
|
||||
return quoted;
|
||||
return normalizeAutogenQuestionCandidates(quoted);
|
||||
}
|
||||
const cleaned = sanitizeGeneratedQuestion(unescaped);
|
||||
return cleaned ? [cleaned] : [];
|
||||
return cleaned ? normalizeAutogenQuestionCandidates([cleaned]) : [];
|
||||
}
|
||||
function parseAutogenOutputJson(rawText) {
|
||||
const cleaned = repairAutogenMojibake(rawText)
|
||||
|
|
@ -1225,7 +1284,8 @@ function collectQuestionsFromCandidate(value, depth = 0) {
|
|||
return [];
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
return value.flatMap((item) => collectQuestionsFromCandidate(item, depth + 1));
|
||||
const expanded = value.flatMap((item) => collectQuestionsFromCandidate(item, depth + 1));
|
||||
return normalizeAutogenQuestionCandidates(expanded);
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
const text = value.trim();
|
||||
|
|
@ -1271,6 +1331,10 @@ function extractQuestionsFromAutogenOutput(rawText) {
|
|||
}
|
||||
return collectQuestionsFromCandidate(rawText);
|
||||
}
|
||||
exports.__autoRunsQuestionTestUtils = {
|
||||
splitQuestionCandidates,
|
||||
extractQuestionsFromAutogenOutput
|
||||
};
|
||||
async function generateQwenSeedQuestionsLive(input) {
|
||||
const seedExamples = collectCanonicalQuestions(40);
|
||||
const fallbackExamples = fallbackDomainTemplates(input.domain);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.__evalRouteTestUtils = void 0;
|
||||
exports.buildEvalRouter = buildEvalRouter;
|
||||
const fs_1 = __importDefault(require("fs"));
|
||||
const path_1 = __importDefault(require("path"));
|
||||
|
|
@ -35,6 +36,61 @@ function normalizeQuestionChunk(value) {
|
|||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
}
|
||||
const RUNTIME_QUESTION_PLACEHOLDER_PATTERN = /^(?:questions?|вопросы?|список\s+вопросов)$/iu;
|
||||
const RUNTIME_QUESTION_TAIL_PATTERNS = [
|
||||
/^(?:без\s+воды|по\s+факту|и\s+коротко|коротко|прям(?:\s+)?сейчас|за\s+весь\s+период|по\s+делу)\??$/iu
|
||||
];
|
||||
function stripQuestionSuffix(value) {
|
||||
return normalizeQuestionChunk(value).replace(/[?!.:,;]+$/u, "").trim();
|
||||
}
|
||||
function isRuntimeQuestionPlaceholder(value) {
|
||||
const core = stripQuestionSuffix(value).toLowerCase();
|
||||
return core.length > 0 && RUNTIME_QUESTION_PLACEHOLDER_PATTERN.test(core);
|
||||
}
|
||||
function isLikelyRuntimeQuestionTail(value) {
|
||||
const core = stripQuestionSuffix(value).toLowerCase();
|
||||
if (!core) {
|
||||
return false;
|
||||
}
|
||||
if (isRuntimeQuestionPlaceholder(core)) {
|
||||
return true;
|
||||
}
|
||||
return RUNTIME_QUESTION_TAIL_PATTERNS.some((pattern) => pattern.test(core));
|
||||
}
|
||||
function mergeRuntimeQuestionTail(baseQuestion, tail) {
|
||||
const base = stripQuestionSuffix(baseQuestion);
|
||||
const suffix = stripQuestionSuffix(tail);
|
||||
if (!base) {
|
||||
return suffix ? `${suffix}?` : "";
|
||||
}
|
||||
if (!suffix) {
|
||||
return `${base}?`;
|
||||
}
|
||||
return `${base} ${suffix}?`
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
}
|
||||
function normalizeRuntimeQuestionList(items) {
|
||||
const normalized = [];
|
||||
for (const item of items) {
|
||||
const chunk = normalizeQuestionChunk(item);
|
||||
if (!chunk) {
|
||||
continue;
|
||||
}
|
||||
if (isRuntimeQuestionPlaceholder(chunk)) {
|
||||
continue;
|
||||
}
|
||||
if (isLikelyRuntimeQuestionTail(chunk) && normalized.length > 0) {
|
||||
const merged = mergeRuntimeQuestionTail(normalized[normalized.length - 1], chunk);
|
||||
if (merged) {
|
||||
normalized[normalized.length - 1] = merged;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
normalized.push(chunk);
|
||||
}
|
||||
return normalized.filter((item) => item.length > 0);
|
||||
}
|
||||
function splitQuestionCandidate(raw) {
|
||||
const normalized = String(raw ?? "").replace(/\r/g, "\n").trim();
|
||||
if (!normalized) {
|
||||
|
|
@ -47,18 +103,30 @@ function splitQuestionCandidate(raw) {
|
|||
const source = byLines.length > 1 ? byLines : [normalized];
|
||||
const chunks = [];
|
||||
for (const line of source) {
|
||||
const normalizedLine = normalizeQuestionChunk(line);
|
||||
if (!normalizedLine || isRuntimeQuestionPlaceholder(normalizedLine)) {
|
||||
continue;
|
||||
}
|
||||
const questionLike = Array.from(line.matchAll(/[^?]+(?:\?|$)/g))
|
||||
.map((match) => normalizeQuestionChunk(match[0]))
|
||||
.filter((item) => item.length > 0);
|
||||
if (questionLike.length > 1) {
|
||||
for (const item of questionLike) {
|
||||
chunks.push(item.endsWith("?") ? item : `${item}?`);
|
||||
const canSafelySplit = questionLike.every((item) => !isRuntimeQuestionPlaceholder(item) &&
|
||||
!isLikelyRuntimeQuestionTail(item) &&
|
||||
normalizeQuestionChunk(item).length >= 18);
|
||||
if (canSafelySplit) {
|
||||
for (const item of questionLike) {
|
||||
chunks.push(item.endsWith("?") ? item : `${item}?`);
|
||||
}
|
||||
}
|
||||
else {
|
||||
chunks.push(normalizedLine);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
chunks.push(normalizeQuestionChunk(line));
|
||||
chunks.push(normalizedLine);
|
||||
}
|
||||
return chunks.filter((item) => item.length > 0);
|
||||
return normalizeRuntimeQuestionList(chunks);
|
||||
}
|
||||
function normalizeRuntimeQuestions(value) {
|
||||
const raw = toArray(value)
|
||||
|
|
@ -67,7 +135,7 @@ function normalizeRuntimeQuestions(value) {
|
|||
if (raw.length === 0) {
|
||||
return [];
|
||||
}
|
||||
const expanded = raw.flatMap((item) => splitQuestionCandidate(item));
|
||||
const expanded = normalizeRuntimeQuestionList(raw.flatMap((item) => splitQuestionCandidate(item)));
|
||||
const deduped = [];
|
||||
const seen = new Set();
|
||||
for (const item of expanded) {
|
||||
|
|
@ -81,6 +149,10 @@ function normalizeRuntimeQuestions(value) {
|
|||
}
|
||||
return deduped;
|
||||
}
|
||||
exports.__evalRouteTestUtils = {
|
||||
splitQuestionCandidate,
|
||||
normalizeRuntimeQuestions
|
||||
};
|
||||
function normalizeCaseIds(value) {
|
||||
if (!Array.isArray(value)) {
|
||||
return undefined;
|
||||
|
|
|
|||
|
|
@ -1569,6 +1569,112 @@ function buildAnswerSummary(mode) {
|
|||
return "Недостаточно опоры для обоснованного ответа.";
|
||||
return "Не удалось собрать обоснованный ответ по текущему запросу.";
|
||||
}
|
||||
const BOUNDARY_CAPABILITY_SUGGESTIONS = [
|
||||
{
|
||||
key: "settlements_60_62",
|
||||
label: "Взаиморасчеты 60/62",
|
||||
helpText: "найти хвосты, незакрытые оплаты и рисковые связки по контрагентам.",
|
||||
signals: /(контраг|долг|сальдо|взаиморасчет|оплат|аванс|покупат|поставщ|банк|выписк|\b60\b|\b62\b|\b76\b)/iu
|
||||
},
|
||||
{
|
||||
key: "vat_document_register_book",
|
||||
label: "НДС 19/68",
|
||||
helpText: "проверить цепочку документ -> счет-фактура -> регистр -> книга.",
|
||||
signals: /(ндс|сч[её]т[-\s]?фактур|регистр|книга\s+покуп|книга\s+продаж|декларац|\b19\b|\b68\b)/iu
|
||||
},
|
||||
{
|
||||
key: "month_close_costs_20_44",
|
||||
label: "Закрытие месяца 20/44",
|
||||
helpText: "проверить распределение затрат и остатки после регламентных операций.",
|
||||
signals: /(закрыти[ея]|месяц|затрат|распределени|рбп|аморт|основн|ос\b|\b20\b|\b25\b|\b26\b|\b44\b)/iu
|
||||
}
|
||||
];
|
||||
function formatNarrativeDomainLabel(domain) {
|
||||
if (domain === "settlements_60_62") {
|
||||
return "взаиморасчетов 60/62";
|
||||
}
|
||||
if (domain === "vat_document_register_book") {
|
||||
return "НДС-контура 19/68";
|
||||
}
|
||||
if (domain === "month_close_costs_20_44") {
|
||||
return "закрытия месяца (20/44)";
|
||||
}
|
||||
return "доступного учетного контура";
|
||||
}
|
||||
function pickBoundaryCapabilityLines(userMessage, limit = 3) {
|
||||
const text = String(userMessage ?? "").toLowerCase();
|
||||
const scored = BOUNDARY_CAPABILITY_SUGGESTIONS.map((item, index) => ({
|
||||
item,
|
||||
score: (text.match(item.signals) ?? []).length,
|
||||
order: index
|
||||
}));
|
||||
const ranked = scored
|
||||
.slice()
|
||||
.sort((left, right) => right.score - left.score || left.order - right.order)
|
||||
.map((entry) => entry.item);
|
||||
const selected = ranked.slice(0, Math.max(2, limit));
|
||||
return uniqueStrings(selected.map((item) => `${item.label}: ${item.helpText}`), limit);
|
||||
}
|
||||
function buildNaturalClarificationHints(input) {
|
||||
const hints = [];
|
||||
if (input.missingAnchors.period) {
|
||||
hints.push("Укажи период проверки (например, июль 2020).");
|
||||
}
|
||||
if (input.missingAnchors.account) {
|
||||
hints.push("Укажи счет или связку счетов (например, 60/62, 19/68 или 20/44).");
|
||||
}
|
||||
if (input.missingAnchors.counterparty) {
|
||||
hints.push("Добавь контрагента или договор, чтобы зафиксировать контур проверки.");
|
||||
}
|
||||
if (input.missingAnchors.documentOrObject) {
|
||||
hints.push("Укажи документ или объект, от которого строить проверку цепочки.");
|
||||
}
|
||||
if (input.missingAnchors.anomalyType) {
|
||||
hints.push("Уточни тип отклонения: разрыв цепочки, неверное закрытие или аномальный хвост.");
|
||||
}
|
||||
if (input.coverageReport.clarification_needed_for.length > 0) {
|
||||
hints.push(`Закрой уточнения для требований: ${input.coverageReport.clarification_needed_for.join(", ")}.`);
|
||||
}
|
||||
return uniqueStrings(hints, 5);
|
||||
}
|
||||
function shouldUseBoundaryFallbackReply(input) {
|
||||
if (input.mode === "out_of_scope") {
|
||||
return true;
|
||||
}
|
||||
if (input.mode !== "clarification_required" && input.mode !== "no_grounded") {
|
||||
return false;
|
||||
}
|
||||
const hasNoEvidenceRoutes = input.okResultsCount === 0 && input.partialResultsCount === 0;
|
||||
const hasNoConfirmedCoverage = input.coverageReport.requirements_covered === 0 &&
|
||||
input.coverageReport.requirements_partially_covered.length === 0;
|
||||
const groundingBlocked = input.groundingCheck.status === "no_grounded_answer" ||
|
||||
input.groundingCheck.status === "partial" ||
|
||||
input.groundingCheck.status === "route_mismatch_blocked";
|
||||
return hasNoEvidenceRoutes && hasNoConfirmedCoverage && groundingBlocked;
|
||||
}
|
||||
function buildBoundaryFallbackReply(input) {
|
||||
const nearbyCapabilities = pickBoundaryCapabilityLines(input.userMessage, 3);
|
||||
if (input.focusDomain === null) {
|
||||
return sanitizeUserFacingReply([
|
||||
"По этому запросу у меня нет надежного доменного покрытия, поэтому даю мягкий отказ вместо технического шаблона.",
|
||||
nearbyCapabilities.length > 0 ? `Что могу сделать рядом по смыслу:\n${formatList(nearbyCapabilities)}` : "",
|
||||
"Переформулируй вопрос через один из вариантов выше, и я сразу перейду к проверке по данным 1С."
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n\n"));
|
||||
}
|
||||
const clarificationHints = buildNaturalClarificationHints({
|
||||
missingAnchors: input.missingAnchors,
|
||||
coverageReport: input.coverageReport
|
||||
});
|
||||
return sanitizeUserFacingReply([
|
||||
`Сейчас не могу надежно ответить по сценарию ${formatNarrativeDomainLabel(input.focusDomain)}: не хватает опоры.`,
|
||||
clarificationHints.length > 0 ? `Чтобы сразу перейти к проверке, уточни:\n${formatList(clarificationHints)}` : "",
|
||||
nearbyCapabilities.length > 0 ? `Если удобнее, могу начать с близкого сценария:\n${formatList(nearbyCapabilities.slice(0, 2))}` : ""
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n\n"));
|
||||
}
|
||||
function ensureSentence(value) {
|
||||
const sanitized = sanitizeUserText(value) ?? String(value ?? "").trim();
|
||||
const normalized = sanitized.replace(/\s+/g, " ").trim();
|
||||
|
|
@ -3548,6 +3654,13 @@ function composeAssistantAnswerV11(input) {
|
|||
normalizationPeriodExplicit: Boolean(input.normalizationPeriodExplicit),
|
||||
companyAnchors: input.companyAnchors ?? null
|
||||
});
|
||||
const useBoundaryFallbackReply = shouldUseBoundaryFallbackReply({
|
||||
mode: guardedDecision.mode,
|
||||
groundingCheck: input.groundingCheck,
|
||||
coverageReport: input.coverageReport,
|
||||
okResultsCount: okResults.length,
|
||||
partialResultsCount: partialResults.length
|
||||
});
|
||||
const hasProblemWeakSignal = policySignals.narrowing_strength !== "strong" ||
|
||||
policySignals.minimum_evidence_failed ||
|
||||
limitationReasonCodes.includes("missing_mechanism") ||
|
||||
|
|
@ -3563,6 +3676,7 @@ function composeAssistantAnswerV11(input) {
|
|||
guardedDecision.mode === "clarification_required" ||
|
||||
(guardedDecision.mode === "focused_grounded" && hasProblemWeakSignal);
|
||||
const shouldUseProblemCentricAnswer = Boolean(input.enableProblemCentricAnswerV1) &&
|
||||
!useBoundaryFallbackReply &&
|
||||
!hardBlockedMode &&
|
||||
problemCentricModeEligible &&
|
||||
(!focusedStrong || hasProblemWeakSignal) &&
|
||||
|
|
@ -3689,13 +3803,21 @@ function composeAssistantAnswerV11(input) {
|
|||
clarification_questions: clarificationQuestions
|
||||
}
|
||||
};
|
||||
return {
|
||||
assistant_reply: renderPolicyReply(answerStructure, {
|
||||
const finalAssistantReply = useBoundaryFallbackReply
|
||||
? buildBoundaryFallbackReply({
|
||||
userMessage: input.userMessage,
|
||||
focusDomain: focusNarrativeDomain,
|
||||
missingAnchors,
|
||||
coverageReport: input.coverageReport
|
||||
})
|
||||
: renderPolicyReply(answerStructure, {
|
||||
questionType,
|
||||
focusDomain: focusNarrativeDomain,
|
||||
anchors: anchorUsage,
|
||||
userMessage: input.userMessage
|
||||
}),
|
||||
});
|
||||
return {
|
||||
assistant_reply: finalAssistantReply,
|
||||
fallback_type: guardedDecision.fallback_type,
|
||||
reply_type: guardedDecision.reply_type,
|
||||
answer_structure_v11: answerStructure,
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ async function buildAssistantAddressOrchestrationRuntime(input) {
|
|||
effectiveAddressUserMessage: addressInputMessage,
|
||||
followupContext,
|
||||
llmPreDecomposeMeta: addressPreDecompose,
|
||||
sessionItems: input.sessionItems,
|
||||
useMock: input.useMock
|
||||
});
|
||||
const dialogContinuationContract = input.buildAddressDialogContinuationContractV2(input.userMessage, addressInputMessage, carryover, addressPreDecompose);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.resolveTemporalGuard = resolveTemporalGuard;
|
||||
exports.applyTemporalHintToExecutionPlan = applyTemporalHintToExecutionPlan;
|
||||
|
|
@ -8,6 +11,7 @@ exports.applyDomainPolarityGuardToRetrievalResults = applyDomainPolarityGuardToR
|
|||
exports.applyEvidenceAdmissibilityGate = applyEvidenceAdmissibilityGate;
|
||||
exports.evaluateGroundedAnswerEligibility = evaluateGroundedAnswerEligibility;
|
||||
exports.applyEligibilityToGroundingCheck = applyEligibilityToGroundingCheck;
|
||||
const iconv_lite_1 = __importDefault(require("iconv-lite"));
|
||||
const JULY_YEAR = "2020";
|
||||
const JULY_MONTH = "07";
|
||||
const JULY_WINDOW = {
|
||||
|
|
@ -747,8 +751,65 @@ function applyTemporalHintToExecutionPlan(executionPlan, temporal) {
|
|||
};
|
||||
});
|
||||
}
|
||||
function mojibakeScoreForRuntimeGuards(value) {
|
||||
const source = String(value ?? "");
|
||||
const cyrillic = (source.match(/[А-Яа-яЁё]/g) ?? []).length;
|
||||
const latin = (source.match(/[A-Za-z]/g) ?? []).length;
|
||||
const hardMarkers = (source.match(/[ѓ“‚„…†‡€‰‹‰ЉЊ‹Џ‘’“”•–—™љ›њћџ]/g) ?? []).length;
|
||||
const pairMarkers = (source.match(/(?:Р.|С.|Гђ.|Г‘.)/g) ?? []).length;
|
||||
const doubleEncodedMarkers = (source.match(/(?:Р“.|Р’.|Гѓ.|Г‚.)/gu) ?? []).length;
|
||||
return cyrillic + latin - hardMarkers * 3 - pairMarkers * 2 - doubleEncodedMarkers * 2;
|
||||
}
|
||||
function looksLikeMojibakeForRuntimeGuards(value) {
|
||||
const source = String(value ?? "");
|
||||
if (!source.trim()) {
|
||||
return false;
|
||||
}
|
||||
if (/[ѓ“‚„…†‡€‰‹‰ЉЊ‹Џ‘’“”•–—™љ›њћџ]/.test(source)) {
|
||||
return true;
|
||||
}
|
||||
if ((source.match(/(?:Р.|С.|Гђ.|Г‘.)/g) ?? []).length >= 2) {
|
||||
return true;
|
||||
}
|
||||
return (source.match(/(?:Р“.|Р’.|Гѓ.|Г‚.)/gu) ?? []).length >= 2;
|
||||
}
|
||||
function repairRuntimeGuardsMojibake(value) {
|
||||
const source = String(value ?? "");
|
||||
if (!looksLikeMojibakeForRuntimeGuards(source)) {
|
||||
return source;
|
||||
}
|
||||
let candidate = source;
|
||||
for (let pass = 0; pass < 3; pass += 1) {
|
||||
let improved = false;
|
||||
try {
|
||||
const fromWin1251 = iconv_lite_1.default.encode(candidate, "win1251").toString("utf8");
|
||||
if (mojibakeScoreForRuntimeGuards(fromWin1251) > mojibakeScoreForRuntimeGuards(candidate)) {
|
||||
candidate = fromWin1251;
|
||||
improved = true;
|
||||
}
|
||||
}
|
||||
catch (_error) {
|
||||
// noop
|
||||
}
|
||||
try {
|
||||
const fromLatin1 = Buffer.from(candidate, "latin1").toString("utf8");
|
||||
if (mojibakeScoreForRuntimeGuards(fromLatin1) > mojibakeScoreForRuntimeGuards(candidate)) {
|
||||
candidate = fromLatin1;
|
||||
improved = true;
|
||||
}
|
||||
}
|
||||
catch (_error) {
|
||||
// noop
|
||||
}
|
||||
if (!improved) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return candidate;
|
||||
}
|
||||
function resolveDomainPolarityGuard(input) {
|
||||
const lower = String(input.userMessage ?? "").toLowerCase();
|
||||
const repairedMessage = repairRuntimeGuardsMojibake(String(input.userMessage ?? ""));
|
||||
const lower = repairedMessage.toLowerCase();
|
||||
const accountExtraction = extractAccountsFromTextDetailed(lower);
|
||||
const accounts = uniqueStrings([...(input.companyAnchors?.accounts ?? []), ...accountExtraction.resolved_account_anchors]);
|
||||
const prefixes = new Set(accounts.map((item) => accountPrefix(item)).filter((item) => Boolean(item)));
|
||||
|
|
@ -1397,7 +1458,7 @@ function applyEligibilityToGroundingCheck(groundingCheck, eligibility) {
|
|||
const reasonMap = {
|
||||
admissible_evidence_count_zero: "Недостаточно подтвержденных данных для уверенного ответа.",
|
||||
critical_domain_or_account_contradiction: "Есть противоречие по выбранному домену или контуру счета.",
|
||||
temporal_guard_failed_out_of_snapshot_window: "Запрошенный период выходит за доступный срез данных.",
|
||||
temporal_guard_failed_out_of_snapshot_window: "Запрошенный период выходит за доступный срез данных. Temporal anchor outside snapshot window.",
|
||||
temporal_guard_ambiguous_limited: "Период в вопросе определен недостаточно точно.",
|
||||
business_scope_generic_unresolved: "Не удалось надежно привязать вопрос к конкретному бизнес-контексту.",
|
||||
polarity_guard_limited_unresolved_polarity: "Не удалось однозначно определить сторону расчета (нам должны или мы должны).",
|
||||
|
|
|
|||
|
|
@ -1086,7 +1086,12 @@ function hasCrossScopeConflictWithState(userMessage, state) {
|
|||
const inferredDomain = inferP0DomainFromMessage(userMessage);
|
||||
const stateDomain = compactWhitespace(state.followup_context?.active_domain ?? state.focus.domain ?? "");
|
||||
if (inferredDomain && stateDomain && inferredDomain !== stateDomain) {
|
||||
return true;
|
||||
const followupDomainRefinement = hasFollowupMarker(userMessage) ||
|
||||
hasReferentialPointer(userMessage) ||
|
||||
hasPeriodLiteral(userMessage);
|
||||
if (!followupDomainRefinement) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
const explicitAccounts = extractAccountTokens(userMessage);
|
||||
const fallbackAccounts = explicitAccounts.length > 0 ? explicitAccounts : extractFollowupAccountAnchorsLoose(userMessage);
|
||||
|
|
@ -1116,9 +1121,11 @@ function inferP0DomainFromMessage(text) {
|
|||
return null;
|
||||
}
|
||||
function hasStrongFollowupAnchors(userMessage, state) {
|
||||
const normalizedMessage = compactWhitespace(repairAddressMojibake(String(userMessage ?? "")).toLowerCase());
|
||||
const periodRefinementCue = /(?:^(?:\u0430\s+)?\u0435\u0441\u043b\u0438|\u0442\u043e\u043b\u044c\u043a\u043e\s+\u0437\u0430|\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c|\u043f\u043e\s+\u043f\u0435\u0440\u0438\u043e\u0434\u0443|\u0437\u0430\s+\u0438\u044e\u043d\u044c|\u0437\u0430\s+\u0438\u044e\u043b\u044c)/iu.test(normalizedMessage);
|
||||
const explicitPeriod = extractNormalizedPeriodLiteral(userMessage);
|
||||
if (explicitPeriod && state.focus.period && explicitPeriod !== state.focus.period) {
|
||||
const periodLooksLikeFollowupRefinement = hasFollowupMarker(userMessage) || hasReferentialPointer(userMessage);
|
||||
const periodLooksLikeFollowupRefinement = hasFollowupMarker(userMessage) || hasReferentialPointer(userMessage) || periodRefinementCue;
|
||||
if (!periodLooksLikeFollowupRefinement) {
|
||||
return true;
|
||||
}
|
||||
|
|
@ -3013,26 +3020,33 @@ function resolveAddressToolGateDecision(addressInputMessage, followupContext, ll
|
|||
reason: dataScopeMetaQuery ? "assistant_data_scope_query_detected" : "assistant_capability_query_detected"
|
||||
};
|
||||
}
|
||||
const directDeepAnalysisSignal = hasDirectDeepAnalysisSignal(rawMessageForGate) ||
|
||||
hasDirectDeepAnalysisSignal(repairedInputMessage);
|
||||
const deepAnalysisPreferenceSignal = directDeepAnalysisSignal ||
|
||||
hasDeepAnalysisPreferenceSignal(rawMessageForGate) ||
|
||||
hasDeepAnalysisPreferenceSignal(repairedInputMessage);
|
||||
const modeDetection = (0, addressQueryClassifier_1.detectAddressQuestionMode)(repairedInputMessage || addressInputMessage);
|
||||
const hasClassifierSignal = modeDetection.mode === "address_query";
|
||||
const llmContractMode = toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.mode);
|
||||
const llmContractModeConfidence = toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.mode_confidence);
|
||||
const llmContractIntent = toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.intent);
|
||||
const llmContractIntentConfidence = toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.intent_confidence);
|
||||
const llmCanonicalEntitySignal = /(?:\u0437\u0430\u043a\u0430\u0437\u0447\u0438\u043a|\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442|\u043a\u043e\u043c\u043f\u0430\u043d|customer|supplier|counterparty|company|vendor|client)/iu.test(compactWhitespace(repairedInputMessage.toLowerCase()));
|
||||
const llmCanonicalAppliedSignal = Boolean(llmPreDecomposeMeta?.applied) && llmContractMode !== "deep_analysis";
|
||||
const hasLlmCanonicalSignal = Boolean(llmPreDecomposeMeta?.llmCanonicalCandidateDetected) &&
|
||||
llmContractMode === "address_query" &&
|
||||
llmContractModeConfidence !== "low" &&
|
||||
llmContractIntent !== null &&
|
||||
llmContractIntent !== "unknown" &&
|
||||
llmContractIntentConfidence !== "low";
|
||||
((llmContractMode === "address_query" && llmContractModeConfidence !== "low") ||
|
||||
(llmCanonicalAppliedSignal &&
|
||||
(hasStrongDataIntentSignal(repairedInputMessage) || llmCanonicalEntitySignal)));
|
||||
const hasLlmCanonicalDataSignal = Boolean(llmPreDecomposeMeta?.llmCanonicalCandidateDetected) &&
|
||||
Boolean(llmPreDecomposeMeta?.applied) &&
|
||||
llmContractMode === "address_query" &&
|
||||
(llmContractMode === "address_query" || llmContractMode === "unsupported" || llmContractMode === null) &&
|
||||
hasStrongDataIntentSignal(repairedInputMessage);
|
||||
const sameDateAccountFollowupSignal = hasSameDateAccountFollowupSignalForPredecompose(rawMessageForGate) ||
|
||||
hasSameDateAccountFollowupSignalForPredecompose(repairedInputMessage);
|
||||
const hasLexicalAddressSignal = isAddressLlmPreDecomposeCandidate(addressInputMessage) ||
|
||||
isAddressLlmPreDecomposeCandidate(repairedInputMessage) ||
|
||||
hasAccountingSignal(addressInputMessage) ||
|
||||
hasAccountingSignal(repairedInputMessage);
|
||||
hasAccountingSignal(repairedInputMessage) ||
|
||||
sameDateAccountFollowupSignal;
|
||||
const hasUnsupportedLowConfidencePredecomposeSignal = llmContractMode === "unsupported" &&
|
||||
(llmContractModeConfidence === "low" || llmContractModeConfidence === "medium") &&
|
||||
llmContractIntent === "unknown";
|
||||
|
|
@ -3080,6 +3094,125 @@ function resolveAddressToolGateDecision(addressInputMessage, followupContext, ll
|
|||
reason: "no_address_signal_after_l0"
|
||||
};
|
||||
}
|
||||
function hasLooseAllTimeAddressLookupSignal(text) {
|
||||
const repaired = repairAddressMojibake(String(text ?? ""));
|
||||
const normalized = compactWhitespace(repaired.toLowerCase());
|
||||
if (!normalized) {
|
||||
return false;
|
||||
}
|
||||
if (shouldHandleAsAssistantCapabilityMetaQuery(normalized) || hasAssistantDataScopeMetaQuestionSignal(normalized)) {
|
||||
return false;
|
||||
}
|
||||
const hasAllTimeSignal = /(?:\u0437\u0430\s+\u0432\u0435\u0441\u044c\s+\u043f\u0435\u0440\u0438\u043e\u0434|\u0437\u0430\s+\u0432\u0441\u0435\s+\u0432\u0440\u0435\u043c\u044f|\u0437\u0430\s+\u0432\u0441\u044e\s+\u0438\u0441\u0442\u043e\u0440\u0438(?:\u044e|\u0438)|for\s+all\s+time|all\s+time|entire\s+period|full\s+period)/iu.test(normalized);
|
||||
if (!hasAllTimeSignal) {
|
||||
return false;
|
||||
}
|
||||
return /(?:\u0447\u0442\u043e\s+\u0435\u0441\u0442\u044c|\u0447[\u0435\u0451]\s+\u0435\u0441\u0442\u044c|\u043f\u043e\u043a\u0430\u0436\u0438|\u0432\u044b\u0432\u0435\u0434\u0438|\u0434\u0430\u0439|show|list|find)/iu.test(normalized);
|
||||
}
|
||||
function hasDeepSessionContinuationSignal(input) {
|
||||
const sessionItems = Array.isArray(input?.sessionItems) ? input.sessionItems : [];
|
||||
if (sessionItems.length === 0) {
|
||||
return false;
|
||||
}
|
||||
const previousDebug = findLastAssistantLivingChatDebug(sessionItems);
|
||||
if (!previousDebug || typeof previousDebug !== "object") {
|
||||
return false;
|
||||
}
|
||||
const investigationState = previousDebug.investigation_state_snapshot;
|
||||
if (!investigationState || typeof investigationState !== "object") {
|
||||
return false;
|
||||
}
|
||||
const candidateTexts = [
|
||||
input?.rawUserMessage,
|
||||
input?.repairedRawUserMessage,
|
||||
input?.effectiveAddressUserMessage,
|
||||
input?.repairedEffectiveAddressUserMessage
|
||||
]
|
||||
.map((value) => compactWhitespace(repairAddressMojibake(String(value ?? "")).toLowerCase()))
|
||||
.filter((value) => value.length > 0);
|
||||
if (candidateTexts.length === 0) {
|
||||
return false;
|
||||
}
|
||||
return candidateTexts.some((text) => {
|
||||
const hasContinuationCue = /^(?:\u0438|\u0430|\u0442\u0430\u043a\u0436\u0435|\u0435\u0449[\u0435\u0451]|\u0434\u043e\u0431\u0430\u0432\u044c|\u0434\u043e\u043f\u043e\u043b\u043d\u0438|\u0443\u0442\u043e\u0447\u043d\u0438|\u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0438|\u0442\u0435\u043f\u0435\u0440\u044c|then|also|and)\b/iu.test(text) ||
|
||||
/(?:\u043f\u043e\s+\u0442\u043e\u043c\u0443\s+\u0436\u0435|\u043f\u043e\s+\u044d\u0442\u043e\u043c\u0443|\u0432\s+\u044d\u0442\u043e\u043c\s+\u0436\u0435|\u0438\s+\u043f\u043e\s+\u043f\u0435\u0440\u0438\u043e\u0434\u0443|\u0434\u043e\u0431\u0430\u0432\u044c\s+\u0443\u0442\u043e\u0447\u043d\u0435\u043d\u0438\u0435|\u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u043c|\u0430\s+\u0435\u0441\u043b\u0438|\u0435\u0441\u043b\u0438\s+\u0442\u043e\u043b\u044c\u043a\u043e|\u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e)/iu.test(text);
|
||||
const hasAccountOrPeriodCue = /(?:\u0441\u0447[\u0435\u0451]\u0442|account|\b\d{2}(?:[.,]\d{1,2})?\b|\b20\d{2}(?:[-/.]\d{1,2})?\b|\u043f\u0435\u0440\u0438\u043e\u0434|\u043c\u0435\u0441\u044f\u0446)/iu.test(text);
|
||||
const hasDeepRebindCue = /(?:\u0430\u043c\u043e\u0440\u0442\u0438\u0437|fixed\s*asset|\u043e\u0441\b|\u043d\u0434\u0441|vat|\u0440\u0430\u0437\u0440\u044b\u0432|\u0446\u0435\u043f\u043e\u0447\u043a|\u0430\u043d\u043e\u043c\u0430\u043b|lifecycle|\u043f\u0440\u043e\u0442\u0438\u0432\u043e\u0440\u0435\u0447)/iu.test(text);
|
||||
if (hasContinuationCue && (hasAccountOrPeriodCue || hasDeepRebindCue)) {
|
||||
return true;
|
||||
}
|
||||
return hasDeepRebindCue && hasAccountOrPeriodCue;
|
||||
});
|
||||
}
|
||||
function hasDeepAnalysisPreferenceSignal(text) {
|
||||
const repaired = repairAddressMojibake(String(text ?? ""));
|
||||
const lower = compactWhitespace(repaired.toLowerCase());
|
||||
if (!lower) {
|
||||
return false;
|
||||
}
|
||||
const riskOrAnomalySignal = /(?:\u0440\u0438\u0441\u043a|risk|\u0430\u043d\u043e\u043c\u0430\u043b|anomal|\u043f\u0440\u043e\u0442\u0438\u0432\u043e\u0440\u0435\u0447|\u043a\u043e\u043d\u0444\u043b\u0438\u043a\u0442|conflict|deviation|\u043e\u0442\u043a\u043b\u043e\u043d\u0435\u043d|\u043d\u0435\u0441\u044b\u043a\u043e\u0432\u043a|\u043d\u0435\u0441\u0445\u043e\u0434|\u043e\u0448\u0438\u0431|error|issue|\u043f\u0440\u043e\u0431\u043b\u0435\u043c)/iu.test(lower);
|
||||
const chainSignal = /(?:\u0446\u0435\u043f\u043e\u0447\u043a|chain|trace\s*chain|lifecycle|\u0436\u0438\u0437\u043d\u0435\u043d\u043d[\u0430-\u044f]+\s+\u0446\u0438\u043a\u043b|state\s+transition|\u0440\u0430\u0437\u0440\u044b\u0432[\u0430-\u044f]*)/iu.test(lower);
|
||||
const diagnosticsSignal = /(?:\u0440\u0430\u0437\u043b\u043e\u0436\u0438|\u0434\u0435\u043a\u043e\u043c\u043f\u043e\u0437|\u0440\u0430\u0437\u0431\u0435\u0440\u0438|\u043f\u043e\u0447\u0435\u043c\u0443|why|\u043a\u043e\u0440\u043d\u0435\u0432[\u0430-\u044f]+\s+\u043f\u0440\u0438\u0447\u0438\u043d|root\s*cause|\u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c[\u0430-\u044f]*|\u0433\u0434\u0435\s+\u0440\u0430\u0437\u0440\u044b\u0432|\u0447\u0442\u043e\s+\u043c\u0435\u0448\u0430[\u0430-\u044f]+\s+\u0437\u0430\u043a\u0440\u044b\u0442)/iu.test(lower);
|
||||
const closureSignal = /(?:\u0437\u0430\u043a\u0440\u044b\u0442\u0438[\u0435\u044f]\s+\u043f\u0435\u0440\u0438\u043e\u0434|period\s*close|\u043d\u0435\s+\u0437\u0430\u043a\u0440\u044b\u043b[\u0430-\u044f]*|\u0445\u0432\u043e\u0441\u0442[\u0430-\u044f]*)/iu.test(lower);
|
||||
const closureIntentSignal = /(?:\u0437\u0430\u043a\u0440\u044b\u0442[\u0430-\u044f]*|period\s*close|close\s+period)/iu.test(lower);
|
||||
const closureDiagnosticPhraseSignal = /(?:\u0447\u0442\u043e(?:\s+\S+){0,8}\s+\u043c\u0435\u0448\u0430[\u0430-\u044f]+\s+\u0437\u0430\u043a\u0440\u044b\u0442)/iu.test(lower);
|
||||
const signalVsNoiseDiagnostic = /(?:\u043d\u0435\s+\u043f\u0440\u043e\u0441\u0442\u043e\s+(?:\u043d\u0430\s+)?\u0448\u0443\u043c|\u043f\u043e\u0445\u043e\u0436[\u0438\u0435]\s+(?:\u0438\u043c\u0435\u043d\u043d\u043e\s+)?\u043d\u0430\s+\u043f\u0440\u043e\u0431\u043b\u0435\u043c)/iu.test(lower);
|
||||
const lifecycleMismatchSignal = /(?:\u043d\u0435\s+\u0442\u0435\u043c\s+\u0442\u0438\u043f(?:\u043e\u043c)?\s+\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442|\u043e\u0436\u0438\u0434\u0430\u0435\u043c[\u0430-\u044f]+\s+\u043f\u0435\u0440\u0435\u0445\u043e\u0434[\u0430-\u044f]*\s+\u043d\u0435\s+\u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434|\u043f\u0435\u0440\u0435\u0445\u043e\u0434[\u0430-\u044f]*\s+\u043d\u0435\s+\u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434|wrong\s+closing\s+document|expected\s+transition)/iu.test(lower);
|
||||
const lifecycleTransitionGapSignal = /(?:\u043e\u0436\u0438\u0434\u0430\u0435\u043c[\u0430-\u044f]+\s+\u043f\u0435\u0440\u0435\u0445\u043e\u0434[\u0430-\u044f]*\s+\u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432|\u043f\u0435\u0440\u0435\u0445\u043e\u0434[\u0430-\u044f]*\s+\u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432|\u0441\u0442\u0430\u0434\u0438[\u0438\u044f\u0435]\s+.*\u043f\u0440\u043e\u0439\u0434\u0435\u043d.*\u043f\u0435\u0440\u0435\u0445\u043e\u0434)/iu.test(lower);
|
||||
const expectedActualMismatchSignal = /(?:\u0444\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a[\u0430-\u044f]+\s+\u0441\u043e\u0441\u0442\u043e\u044f\u043d[\u0438\u0435\u044f]+\s+.*\u0440\u0430\u0441\u0445\u043e\u0434[\u0430-\u044f]*\s+\u0441\s+\u043e\u0436\u0438\u0434\u0430\u0435\u043c|\u043e\u0436\u0438\u0434\u0430\u0435\u043c[\u0430-\u044f]+\s+\u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d[\u0430-\u044f]*\s+\u0441\u043f\u0438\u0441\u0430\u043d)/iu.test(lower);
|
||||
return riskOrAnomalySignal ||
|
||||
lifecycleMismatchSignal ||
|
||||
(chainSignal && lifecycleTransitionGapSignal) ||
|
||||
expectedActualMismatchSignal ||
|
||||
(chainSignal && diagnosticsSignal) ||
|
||||
(riskOrAnomalySignal && (chainSignal || closureSignal || diagnosticsSignal || closureIntentSignal)) ||
|
||||
(diagnosticsSignal && closureIntentSignal) ||
|
||||
closureDiagnosticPhraseSignal ||
|
||||
signalVsNoiseDiagnostic;
|
||||
}
|
||||
function hasDirectDeepAnalysisSignal(text) {
|
||||
const normalized = compactWhitespace(repairAddressMojibake(String(text ?? "")).toLowerCase());
|
||||
if (!normalized) {
|
||||
return false;
|
||||
}
|
||||
return /(?:\u0440\u0430\u0437\u043b\u043e\u0436|\u0446\u0435\u043f\u043e\u0447|lifecycle|\u0440\u0430\u0437\u0440\u044b\u0432|\u043f\u0440\u043e\u0442\u0438\u0432\u043e\u0440\u0435\u0447|\u0430\u043d\u043e\u043c\u0430\u043b|\u043f\u043e\u0447\u0435\u043c\u0443|\u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c|\u0437\u0430\u043a\u0440\u044b\u0442[\u0430-\u044f]*|state\s+transition|root\s*cause|trace\s*chain)/iu.test(normalized);
|
||||
}
|
||||
function hasStrictDeepInvestigationCue(text) {
|
||||
const normalized = compactWhitespace(repairAddressMojibake(String(text ?? "")).toLowerCase());
|
||||
if (!normalized) {
|
||||
return false;
|
||||
}
|
||||
const hasInvestigativeVerb = /(?:\u043f\u0440\u043e\u0432\u0435\u0440(?:\u044c|\u0438\u0442\u044c)|\u0440\u0430\u0437\u0431\u0435\u0440(?:\u0438|\u0430\u0442\u044c)|\u0440\u0430\u0437\u043b\u043e\u0436(?:\u0438|\u0438\u0442\u044c)|\u043f\u043e\u0447\u0435\u043c\u0443|\u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c|root\s*cause|trace\s*chain)/iu.test(normalized);
|
||||
if (!hasInvestigativeVerb) {
|
||||
return false;
|
||||
}
|
||||
return /(?:\u0445\u0432\u043e\u0441\u0442|\u0440\u0430\u0437\u0440\u044b\u0432|\u0446\u0435\u043f\u043e\u0447|\u0430\u043d\u043e\u043c\u0430\u043b|\u043f\u0440\u043e\u0442\u0438\u0432\u043e\u0440\u0435\u0447|\u0437\u0430\u043a\u0440\u044b\u0442\u0438[\u0435\u044f]|\u043e\u0431\u044a\u0435\u043a\u0442(?:\u0443)?\s+\u0440\u0430\u0441\u0447(?:\u0435|\u0451)\u0442|lifecycle|state\s+transition)/iu.test(normalized);
|
||||
}
|
||||
function hasAggregateBusinessAnalyticsSignal(text) {
|
||||
const normalized = compactWhitespace(repairAddressMojibake(String(text ?? "")).toLowerCase());
|
||||
if (!normalized) {
|
||||
return false;
|
||||
}
|
||||
const hasMetricCue = /(?:\u043e\u0431\u043e\u0440\u043e\u0442|\u0432\u044b\u0440\u0443\u0447|\u0434\u043e\u0445\u043e\u0434|\u043f\u0440\u0438\u0431\u044b\u043b|\u043c\u0430\u0440\u0436|\u0440\u0435\u043d\u0442\u0430\u0431\u0435\u043b|\u043f\u043e\u043a\u0430\u0437\u0430\u0442\u0435\u043b|turnover|revenue|profit|margin)/iu.test(normalized);
|
||||
if (!hasMetricCue) {
|
||||
return false;
|
||||
}
|
||||
const hasRankingOrTrendCue = /(?:\u0441\u0430\u043c(?:\u044b\u0439|\u0430\u044f|\u043e\u0435|\u044b\u0435)|\u0442\u043e\u043f|\u043b\u0443\u0447\u0448|\u0445\u0443\u0434\u0448|\u043c\u0430\u043a\u0441(?:\u0438\u043c\u0443\u043c)?|\u043c\u0438\u043d(?:\u0438\u043c\u0443\u043c)?|\u0434\u0438\u043d\u0430\u043c|\u0442\u0440\u0435\u043d\u0434|\u0441\u0440\u0430\u0432\u043d|ranking|top|best|worst)/iu.test(normalized);
|
||||
const hasPeriodAggregateCue = /(?:\u043f\u043e\s+\u0433\u043e\u0434\u0430\u043c|\u0437\u0430\s+\d{4}\s+\u0433\u043e\u0434|\u0433\u043e\u0434(?:\u0430|\u0443|\u044b)?|year|years|\u043a\u0432\u0430\u0440\u0442\u0430\u043b|\u043c\u0435\u0441\u044f\u0446|\u043f\u0435\u0440\u0438\u043e\u0434)/iu.test(normalized);
|
||||
return hasRankingOrTrendCue || hasPeriodAggregateCue;
|
||||
}
|
||||
const ADDRESS_INTENTS_KEEP_ADDRESS_LANE = new Set([
|
||||
"list_open_contracts",
|
||||
"open_items_by_counterparty_or_contract",
|
||||
"list_documents_by_contract",
|
||||
"bank_operations_by_contract",
|
||||
"list_documents_by_counterparty",
|
||||
"bank_operations_by_counterparty",
|
||||
"list_contracts_by_counterparty",
|
||||
"contract_usage_overview",
|
||||
"contract_usage_and_value",
|
||||
"vat_payable_forecast"
|
||||
]);
|
||||
function resolveAssistantOrchestrationDecision(input) {
|
||||
const rawUserMessage = String(input?.rawUserMessage ?? input?.userMessage ?? "");
|
||||
const effectiveAddressUserMessage = String(input?.effectiveAddressUserMessage ?? rawUserMessage);
|
||||
|
|
@ -3088,6 +3221,7 @@ function resolveAssistantOrchestrationDecision(input) {
|
|||
const followupContext = input?.followupContext ?? null;
|
||||
const llmPreDecomposeMeta = input?.llmPreDecomposeMeta ?? null;
|
||||
const useMock = Boolean(input?.useMock);
|
||||
const sessionItems = Array.isArray(input?.sessionItems) ? input.sessionItems : null;
|
||||
const dataScopeMetaQuery = hasAssistantDataScopeMetaQuestionSignal(rawUserMessage) ||
|
||||
hasAssistantDataScopeMetaQuestionSignal(repairedRawUserMessage) ||
|
||||
hasAssistantDataScopeMetaQuestionSignal(effectiveAddressUserMessage) ||
|
||||
|
|
@ -3100,9 +3234,21 @@ function resolveAssistantOrchestrationDecision(input) {
|
|||
hasDataRetrievalRequestSignal(repairedRawUserMessage) ||
|
||||
hasDataRetrievalRequestSignal(effectiveAddressUserMessage) ||
|
||||
hasDataRetrievalRequestSignal(repairedEffectiveAddressUserMessage);
|
||||
const aggregateBusinessAnalyticsSignal = hasAggregateBusinessAnalyticsSignal(rawUserMessage) ||
|
||||
hasAggregateBusinessAnalyticsSignal(repairedRawUserMessage) ||
|
||||
hasAggregateBusinessAnalyticsSignal(effectiveAddressUserMessage) ||
|
||||
hasAggregateBusinessAnalyticsSignal(repairedEffectiveAddressUserMessage);
|
||||
const modeSample = repairedEffectiveAddressUserMessage || effectiveAddressUserMessage;
|
||||
const modeDetection = (0, addressQueryClassifier_1.detectAddressQuestionMode)(modeSample);
|
||||
const intentResolution = (0, addressIntentResolver_1.resolveAddressIntent)(modeSample);
|
||||
const llmContractIntent = toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.intent);
|
||||
const strictDeepInvestigationCueDetected = hasStrictDeepInvestigationCue(rawUserMessage) ||
|
||||
hasStrictDeepInvestigationCue(repairedRawUserMessage) ||
|
||||
hasStrictDeepInvestigationCue(effectiveAddressUserMessage) ||
|
||||
hasStrictDeepInvestigationCue(repairedEffectiveAddressUserMessage);
|
||||
const keepAddressLaneByIntent = Boolean((intentResolution.intent && ADDRESS_INTENTS_KEEP_ADDRESS_LANE.has(intentResolution.intent)) ||
|
||||
(llmContractIntent && ADDRESS_INTENTS_KEEP_ADDRESS_LANE.has(llmContractIntent))) &&
|
||||
!strictDeepInvestigationCueDetected;
|
||||
const strongDataSignal = hasStrongDataIntentSignal(rawUserMessage) ||
|
||||
hasStrongDataIntentSignal(repairedRawUserMessage) ||
|
||||
hasStrongDataIntentSignal(effectiveAddressUserMessage) ||
|
||||
|
|
@ -3175,11 +3321,58 @@ function resolveAssistantOrchestrationDecision(input) {
|
|||
};
|
||||
}
|
||||
const baseToolGate = resolveAddressToolGateDecision(effectiveAddressUserMessage, followupContext, llmPreDecomposeMeta, rawUserMessage);
|
||||
const llmContractMode = toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.mode);
|
||||
const preserveAddressLaneSignal = Boolean((llmPreDecomposeMeta?.llmCanonicalCandidateDetected &&
|
||||
llmPreDecomposeMeta?.applied &&
|
||||
llmContractMode === "address_query") ||
|
||||
hasSameDateAccountFollowupSignalForPredecompose(rawUserMessage) ||
|
||||
hasSameDateAccountFollowupSignalForPredecompose(effectiveAddressUserMessage) ||
|
||||
hasSameDateAccountFollowupSignalForPredecompose(repairedRawUserMessage) ||
|
||||
hasSameDateAccountFollowupSignalForPredecompose(repairedEffectiveAddressUserMessage) ||
|
||||
hasLooseAllTimeAddressLookupSignal(rawUserMessage) ||
|
||||
hasLooseAllTimeAddressLookupSignal(effectiveAddressUserMessage) ||
|
||||
hasLooseAllTimeAddressLookupSignal(repairedRawUserMessage) ||
|
||||
hasLooseAllTimeAddressLookupSignal(repairedEffectiveAddressUserMessage) ||
|
||||
hasAddressFollowupContextSignal(rawUserMessage) ||
|
||||
hasAddressFollowupContextSignal(effectiveAddressUserMessage) ||
|
||||
hasAddressFollowupContextSignal(repairedRawUserMessage) ||
|
||||
hasAddressFollowupContextSignal(repairedEffectiveAddressUserMessage));
|
||||
const unsupportedIntentOrMode = modeDetection.mode !== "address_query" &&
|
||||
(intentResolution.intent === "unknown" || llmContractMode === "unsupported");
|
||||
const unsupportedAddressIntentFallbackToDeep = Boolean(!followupContext &&
|
||||
baseToolGate?.runAddressLane &&
|
||||
modeDetection.mode !== "address_query" &&
|
||||
intentResolution.intent === "unknown" &&
|
||||
strongDataSignal);
|
||||
unsupportedIntentOrMode &&
|
||||
strongDataSignal &&
|
||||
!preserveAddressLaneSignal);
|
||||
const deepAnalysisPreferenceDetected = Boolean(hasDeepAnalysisPreferenceSignal(rawUserMessage) ||
|
||||
hasDeepAnalysisPreferenceSignal(repairedRawUserMessage) ||
|
||||
hasDeepAnalysisPreferenceSignal(effectiveAddressUserMessage) ||
|
||||
hasDeepAnalysisPreferenceSignal(repairedEffectiveAddressUserMessage) ||
|
||||
hasDirectDeepAnalysisSignal(rawUserMessage) ||
|
||||
hasDirectDeepAnalysisSignal(repairedRawUserMessage) ||
|
||||
hasDirectDeepAnalysisSignal(effectiveAddressUserMessage) ||
|
||||
hasDirectDeepAnalysisSignal(repairedEffectiveAddressUserMessage));
|
||||
const vatExplainFollowupSignal = Boolean(followupContext &&
|
||||
toNonEmptyString(followupContext.previous_intent) === "vat_payable_forecast" &&
|
||||
/(?:\u043f\u043e\u0447\u0435\u043c\u0443|why).*(?:\u043f\u0440\u043e\u0433\u043d\u043e\u0437|forecast).*(?:\u0443\u043f\u043b\u0430\u0442|payable|\b0\b)/iu.test(compactWhitespace(`${repairedRawUserMessage} ${repairedEffectiveAddressUserMessage}`)));
|
||||
const deepAnalysisSignalFallbackToDeep = Boolean(baseToolGate?.runAddressLane &&
|
||||
deepAnalysisPreferenceDetected &&
|
||||
!keepAddressLaneByIntent &&
|
||||
!vatExplainFollowupSignal &&
|
||||
(!followupContext || !dataRetrievalSignal));
|
||||
const aggregateAnalyticsFallbackToDeep = Boolean(baseToolGate?.runAddressLane &&
|
||||
aggregateBusinessAnalyticsSignal &&
|
||||
!keepAddressLaneByIntent &&
|
||||
!followupContext);
|
||||
const deepSessionContinuationFallbackToDeep = Boolean(!followupContext &&
|
||||
baseToolGate?.runAddressLane &&
|
||||
hasDeepSessionContinuationSignal({
|
||||
rawUserMessage,
|
||||
repairedRawUserMessage,
|
||||
effectiveAddressUserMessage,
|
||||
repairedEffectiveAddressUserMessage,
|
||||
sessionItems
|
||||
}));
|
||||
let runAddressLane = Boolean(baseToolGate?.runAddressLane);
|
||||
let toolGateDecision = String(baseToolGate?.decision ?? "skip_address_lane");
|
||||
let toolGateReason = String(baseToolGate?.reason ?? "no_address_signal_after_l0");
|
||||
|
|
@ -3188,6 +3381,23 @@ function resolveAssistantOrchestrationDecision(input) {
|
|||
toolGateDecision = "skip_address_lane";
|
||||
toolGateReason = "address_signal_unsupported_intent_fallback_to_deep";
|
||||
}
|
||||
if (deepAnalysisSignalFallbackToDeep && !unsupportedAddressIntentFallbackToDeep) {
|
||||
runAddressLane = false;
|
||||
toolGateDecision = "skip_address_lane";
|
||||
toolGateReason = "deep_analysis_signal_fallback_to_deep";
|
||||
}
|
||||
if (aggregateAnalyticsFallbackToDeep &&
|
||||
!unsupportedAddressIntentFallbackToDeep &&
|
||||
!deepAnalysisSignalFallbackToDeep) {
|
||||
runAddressLane = false;
|
||||
toolGateDecision = "skip_address_lane";
|
||||
toolGateReason = "aggregate_analytics_signal_fallback_to_deep";
|
||||
}
|
||||
if (deepSessionContinuationFallbackToDeep) {
|
||||
runAddressLane = false;
|
||||
toolGateDecision = "skip_address_lane";
|
||||
toolGateReason = "deep_session_continuation_fallback_to_deep";
|
||||
}
|
||||
let livingDecision = resolveLivingAssistantModeDecision({
|
||||
userMessage: rawUserMessage,
|
||||
addressLaneTriggered: runAddressLane,
|
||||
|
|
@ -3201,6 +3411,26 @@ function resolveAssistantOrchestrationDecision(input) {
|
|||
reason: "unsupported_address_intent_fallback_to_deep"
|
||||
};
|
||||
}
|
||||
if (deepAnalysisSignalFallbackToDeep && !unsupportedAddressIntentFallbackToDeep) {
|
||||
livingDecision = {
|
||||
mode: "deep_analysis",
|
||||
reason: "deep_analysis_signal_fallback_to_deep"
|
||||
};
|
||||
}
|
||||
if (aggregateAnalyticsFallbackToDeep &&
|
||||
!unsupportedAddressIntentFallbackToDeep &&
|
||||
!deepAnalysisSignalFallbackToDeep) {
|
||||
livingDecision = {
|
||||
mode: "deep_analysis",
|
||||
reason: "aggregate_analytics_signal_fallback_to_deep"
|
||||
};
|
||||
}
|
||||
if (deepSessionContinuationFallbackToDeep) {
|
||||
livingDecision = {
|
||||
mode: "deep_analysis",
|
||||
reason: "deep_session_continuation_fallback_to_deep"
|
||||
};
|
||||
}
|
||||
return {
|
||||
runAddressLane,
|
||||
toolGateDecision,
|
||||
|
|
@ -3218,6 +3448,9 @@ function resolveAssistantOrchestrationDecision(input) {
|
|||
data_retrieval_signal_detected: dataRetrievalSignal,
|
||||
followup_context_detected: Boolean(followupContext),
|
||||
unsupported_address_intent_fallback_to_deep: unsupportedAddressIntentFallbackToDeep,
|
||||
deep_analysis_signal_fallback_to_deep: deepAnalysisSignalFallbackToDeep,
|
||||
aggregate_analytics_signal_fallback_to_deep: aggregateAnalyticsFallbackToDeep,
|
||||
deep_session_continuation_fallback_to_deep: deepSessionContinuationFallbackToDeep,
|
||||
final_decision: {
|
||||
run_address_lane: runAddressLane,
|
||||
tool_gate_decision: toolGateDecision,
|
||||
|
|
@ -3237,6 +3470,11 @@ function hasDataRetrievalRequestSignal(text) {
|
|||
if (!lower) {
|
||||
return false;
|
||||
}
|
||||
const hasRussianRetrievalAction = /(?:^|\s)(?:\u043f\u043e\u043a\u0430\u0436\u0438|\u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c|\u043d\u0430\u0439\u0434\u0438|\u0432\u044b\u0432\u0435\u0434\u0438|\u0434\u0430\u0439|\u0440\u0430\u0441\u043a\u0440\u043e\u0439|\u0441\u043f\u0438\u0441\u043e\u043a)(?:$|[\s,.!?;:])/iu.test(lower);
|
||||
const hasRussianRetrievalObject = /(?:\u0434\u043e\u0433\u043e\u0432\u043e\u0440|\u043a\u043e\u043d\u0442\u0440\u0430\u043a\u0442|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442|\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442|\u0441\u0447(?:\u0435|\u0451)\u0442|\u043e\u0441\u0442\u0430\u0442|\u0441\u0430\u043b\u044c\u0434\u043e|\u043e\u0431\u043e\u0440\u043e\u0442|\u043f\u043b\u0430\u0442(?:\u0435|\u0451)\u0436|\u043e\u043f\u0435\u0440\u0430\u0446|\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|\u043a\u043b\u0438\u0435\u043d\u0442|\u0433\u043e\u0434|\u043f\u0435\u0440\u0438\u043e\u0434|\u043c\u0435\u0441\u044f\u0446)/iu.test(lower);
|
||||
if (hasRussianRetrievalAction && hasRussianRetrievalObject) {
|
||||
return true;
|
||||
}
|
||||
const hasExplicitRetrievalAction = /(?:\bпокажи\b|\bпоказать\b|\bвыведи\b|\bнайди\b|\bсписок\b|\bдай\b|\bраскрой\b|\bshow\b|\blist\b|\bfind\b|\bcount\b)/i.test(lower);
|
||||
const hasInterrogativeRetrievalAction = /(?:\bсколько\b|\bкакой\b|\bкакая\b|\bкакое\b|\bкакую\b|\bкакие\b|\bкто\b|\bwhich\b|\bwho\b)/i.test(lower);
|
||||
if (!hasExplicitRetrievalAction && !hasInterrogativeRetrievalAction) {
|
||||
|
|
@ -3403,6 +3641,10 @@ function hasAssistantDataScopeMetaQuestionSignal(text) {
|
|||
if (!normalized) {
|
||||
return false;
|
||||
}
|
||||
const hasSlangScopeQuestion = /(?:\u043f\u043e\s+\u043a\u0430\u043a\u0438\u043c\s+(?:\u043a\u043e\u043d\u0442\u043e\u0440(?:\u0430\u043c|\u044b|\u0430)?|\u043a\u043e\u043c\u043f\u0430\u043d(?:\u0438\u044f\u043c|\u0438\u0438|\u0438\u044e|\u0438\u044f)|\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446(?:\u0438\u044f\u043c|\u0438\u0438|\u0438\u044e|\u0438\u044f)|\u0444\u0438\u0440\u043c(?:\u0430\u043c|\u0435|\u0443|\u0430)).*(?:\u043c\u043e\u0436(?:\u0435\u043c|\u043d\u043e)|\u0440\u0430\u0431\u043e\u0442|\u043e\u0431\u0449\u0430\u0442|\u043f\u043e\u0434\u0440\u0443\u0431|\u043f\u043e\u0434\u043a\u043b\u044e\u0447)|(?:\u0431\u0430\u0437\u0430\s+\u043a\u0430\u043a\u043e\u0439\s+(?:\u043a\u043e\u043d\u0442\u043e\u0440|\u043a\u043e\u043c\u043f\u0430\u043d|\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446|\u0444\u0438\u0440\u043c))|(?:\u043a\u0430\u043a\u0430\u044f\s+\u0431\u0430\u0437\u0430\s+(?:\u043f\u043e\u0434\u043a\u043b\u044e\u0447|\u0430\u043a\u0442\u0438\u0432)))/iu.test(normalized);
|
||||
if (hasSlangScopeQuestion) {
|
||||
return true;
|
||||
}
|
||||
const hasBaseOrTenantObject = /(?:баз(?:а|е|у|ы)?|тенант|tenant|контур)/i.test(normalized);
|
||||
const hasCompanyObject = /(?:компан(?:ия|ии|ию|ией)|компин(?:ия|ии|ию|ией)?|компини(?:я|и|ю|ей)?|компани[яеию]|организац(?:ия|ии|ию|ией)|контор(?:а|ы|у|ой)?|фирм(?:а|ы|у|ой)?)/i.test(normalized);
|
||||
const hasConnectionCue = /(?:подключен(?:а|о|ы)?|подруб|воткнут|активн(?:ый|ая)\s+канал|mcp-?канал|канал)/i.test(normalized);
|
||||
|
|
|
|||
|
|
@ -22,6 +22,9 @@ function redactObject(value) {
|
|||
return value;
|
||||
}
|
||||
function logJson(entry) {
|
||||
if (process.env.NODE_ENV === "test" && process.env.FEATURE_JSON_STDOUT_LOGS_IN_TESTS !== "1") {
|
||||
return;
|
||||
}
|
||||
const safe = {
|
||||
...entry,
|
||||
details: redactObject(entry.details)
|
||||
|
|
|
|||
|
|
@ -1415,6 +1415,67 @@ function sanitizeGeneratedQuestion(value: string): string {
|
|||
.trim();
|
||||
}
|
||||
|
||||
const AUTOGEN_QUESTION_PLACEHOLDER_PATTERN = /^(?:questions?|вопросы?|список\s+вопросов)$/iu;
|
||||
const AUTOGEN_QUESTION_TAIL_PATTERNS: RegExp[] = [
|
||||
/^(?:без\s+воды|по\s+факту|и\s+коротко|коротко|прям(?:\s+)?сейчас|за\s+весь\s+период|по\s+делу)\??$/iu
|
||||
];
|
||||
|
||||
function stripAutogenQuestionSuffix(value: string): string {
|
||||
return sanitizeGeneratedQuestion(value).replace(/[?!.:,;]+$/u, "").trim();
|
||||
}
|
||||
|
||||
function isAutogenQuestionPlaceholder(value: string): boolean {
|
||||
const core = stripAutogenQuestionSuffix(value).toLowerCase();
|
||||
return core.length > 0 && AUTOGEN_QUESTION_PLACEHOLDER_PATTERN.test(core);
|
||||
}
|
||||
|
||||
function isLikelyAutogenQuestionTail(value: string): boolean {
|
||||
const core = stripAutogenQuestionSuffix(value).toLowerCase();
|
||||
if (!core) {
|
||||
return false;
|
||||
}
|
||||
if (isAutogenQuestionPlaceholder(core)) {
|
||||
return true;
|
||||
}
|
||||
return AUTOGEN_QUESTION_TAIL_PATTERNS.some((pattern) => pattern.test(core));
|
||||
}
|
||||
|
||||
function mergeAutogenQuestionTail(baseQuestion: string, tail: string): string {
|
||||
const base = stripAutogenQuestionSuffix(baseQuestion);
|
||||
const suffix = stripAutogenQuestionSuffix(tail);
|
||||
if (!base) {
|
||||
return suffix ? `${suffix}?` : "";
|
||||
}
|
||||
if (!suffix) {
|
||||
return `${base}?`;
|
||||
}
|
||||
return `${base} ${suffix}?`
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
}
|
||||
|
||||
function normalizeAutogenQuestionCandidates(candidates: string[]): string[] {
|
||||
const normalized: string[] = [];
|
||||
for (const candidate of candidates) {
|
||||
const question = sanitizeGeneratedQuestion(candidate);
|
||||
if (!question) {
|
||||
continue;
|
||||
}
|
||||
if (isAutogenQuestionPlaceholder(question)) {
|
||||
continue;
|
||||
}
|
||||
if (isLikelyAutogenQuestionTail(question) && normalized.length > 0) {
|
||||
const merged = mergeAutogenQuestionTail(normalized[normalized.length - 1], question);
|
||||
if (merged) {
|
||||
normalized[normalized.length - 1] = merged;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
normalized.push(question);
|
||||
}
|
||||
return normalized.filter((item) => item.length > 0);
|
||||
}
|
||||
|
||||
function splitQuestionCandidates(rawText: string): string[] {
|
||||
const normalized = repairAutogenMojibake(rawText).replace(/\r/g, "\n").trim();
|
||||
if (!normalized) return [];
|
||||
|
|
@ -1426,18 +1487,26 @@ function splitQuestionCandidates(rawText: string): string[] {
|
|||
.map((line) => sanitizeGeneratedQuestion(line))
|
||||
.filter((line) => line.length > 0);
|
||||
if (byLines.length > 1) {
|
||||
return byLines;
|
||||
return normalizeAutogenQuestionCandidates(byLines);
|
||||
}
|
||||
|
||||
const questionMarkCount = (unescaped.match(/\?/g) ?? []).length;
|
||||
if (questionMarkCount > 1) {
|
||||
const byQuestion = unescaped
|
||||
.split("?")
|
||||
.map((chunk) => sanitizeGeneratedQuestion(chunk))
|
||||
.filter((chunk) => chunk.length > 0)
|
||||
.map((chunk) => (chunk.endsWith("?") ? chunk : `${chunk}?`));
|
||||
if (byQuestion.length > 1) {
|
||||
return byQuestion;
|
||||
const questionChunks = Array.from(unescaped.matchAll(/[^?]+(?:\?|$)/g))
|
||||
.map((match) => sanitizeGeneratedQuestion(match[0]))
|
||||
.filter((chunk) => chunk.length > 0);
|
||||
if (questionChunks.length > 1) {
|
||||
const canSafelySplit = questionChunks.every(
|
||||
(chunk) =>
|
||||
!isAutogenQuestionPlaceholder(chunk) &&
|
||||
!isLikelyAutogenQuestionTail(chunk) &&
|
||||
sanitizeGeneratedQuestion(chunk).length >= 18
|
||||
);
|
||||
if (canSafelySplit) {
|
||||
return normalizeAutogenQuestionCandidates(
|
||||
questionChunks.map((chunk) => (chunk.endsWith("?") ? chunk : `${chunk}?`))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1445,11 +1514,11 @@ function splitQuestionCandidates(rawText: string): string[] {
|
|||
.map((match) => sanitizeGeneratedQuestion(match[1]))
|
||||
.filter((line) => line.length > 0);
|
||||
if (quoted.length > 1) {
|
||||
return quoted;
|
||||
return normalizeAutogenQuestionCandidates(quoted);
|
||||
}
|
||||
|
||||
const cleaned = sanitizeGeneratedQuestion(unescaped);
|
||||
return cleaned ? [cleaned] : [];
|
||||
return cleaned ? normalizeAutogenQuestionCandidates([cleaned]) : [];
|
||||
}
|
||||
|
||||
function parseAutogenOutputJson(rawText: string): unknown | null {
|
||||
|
|
@ -1496,7 +1565,8 @@ function collectQuestionsFromCandidate(value: unknown, depth = 0): string[] {
|
|||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return value.flatMap((item) => collectQuestionsFromCandidate(item, depth + 1));
|
||||
const expanded = value.flatMap((item) => collectQuestionsFromCandidate(item, depth + 1));
|
||||
return normalizeAutogenQuestionCandidates(expanded);
|
||||
}
|
||||
|
||||
if (typeof value === "string") {
|
||||
|
|
@ -1549,6 +1619,11 @@ function extractQuestionsFromAutogenOutput(rawText: string): string[] {
|
|||
return collectQuestionsFromCandidate(rawText);
|
||||
}
|
||||
|
||||
export const __autoRunsQuestionTestUtils = {
|
||||
splitQuestionCandidates,
|
||||
extractQuestionsFromAutogenOutput
|
||||
};
|
||||
|
||||
async function generateQwenSeedQuestionsLive(input: {
|
||||
count: number;
|
||||
domain: string | null;
|
||||
|
|
|
|||
|
|
@ -73,6 +73,67 @@ function normalizeQuestionChunk(value: string): string {
|
|||
.trim();
|
||||
}
|
||||
|
||||
const RUNTIME_QUESTION_PLACEHOLDER_PATTERN = /^(?:questions?|вопросы?|список\s+вопросов)$/iu;
|
||||
const RUNTIME_QUESTION_TAIL_PATTERNS: RegExp[] = [
|
||||
/^(?:без\s+воды|по\s+факту|и\s+коротко|коротко|прям(?:\s+)?сейчас|за\s+весь\s+период|по\s+делу)\??$/iu
|
||||
];
|
||||
|
||||
function stripQuestionSuffix(value: string): string {
|
||||
return normalizeQuestionChunk(value).replace(/[?!.:,;]+$/u, "").trim();
|
||||
}
|
||||
|
||||
function isRuntimeQuestionPlaceholder(value: string): boolean {
|
||||
const core = stripQuestionSuffix(value).toLowerCase();
|
||||
return core.length > 0 && RUNTIME_QUESTION_PLACEHOLDER_PATTERN.test(core);
|
||||
}
|
||||
|
||||
function isLikelyRuntimeQuestionTail(value: string): boolean {
|
||||
const core = stripQuestionSuffix(value).toLowerCase();
|
||||
if (!core) {
|
||||
return false;
|
||||
}
|
||||
if (isRuntimeQuestionPlaceholder(core)) {
|
||||
return true;
|
||||
}
|
||||
return RUNTIME_QUESTION_TAIL_PATTERNS.some((pattern) => pattern.test(core));
|
||||
}
|
||||
|
||||
function mergeRuntimeQuestionTail(baseQuestion: string, tail: string): string {
|
||||
const base = stripQuestionSuffix(baseQuestion);
|
||||
const suffix = stripQuestionSuffix(tail);
|
||||
if (!base) {
|
||||
return suffix ? `${suffix}?` : "";
|
||||
}
|
||||
if (!suffix) {
|
||||
return `${base}?`;
|
||||
}
|
||||
return `${base} ${suffix}?`
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
}
|
||||
|
||||
function normalizeRuntimeQuestionList(items: string[]): string[] {
|
||||
const normalized: string[] = [];
|
||||
for (const item of items) {
|
||||
const chunk = normalizeQuestionChunk(item);
|
||||
if (!chunk) {
|
||||
continue;
|
||||
}
|
||||
if (isRuntimeQuestionPlaceholder(chunk)) {
|
||||
continue;
|
||||
}
|
||||
if (isLikelyRuntimeQuestionTail(chunk) && normalized.length > 0) {
|
||||
const merged = mergeRuntimeQuestionTail(normalized[normalized.length - 1], chunk);
|
||||
if (merged) {
|
||||
normalized[normalized.length - 1] = merged;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
normalized.push(chunk);
|
||||
}
|
||||
return normalized.filter((item) => item.length > 0);
|
||||
}
|
||||
|
||||
function splitQuestionCandidate(raw: string): string[] {
|
||||
const normalized = String(raw ?? "").replace(/\r/g, "\n").trim();
|
||||
if (!normalized) {
|
||||
|
|
@ -87,18 +148,32 @@ function splitQuestionCandidate(raw: string): string[] {
|
|||
|
||||
const chunks: string[] = [];
|
||||
for (const line of source) {
|
||||
const normalizedLine = normalizeQuestionChunk(line);
|
||||
if (!normalizedLine || isRuntimeQuestionPlaceholder(normalizedLine)) {
|
||||
continue;
|
||||
}
|
||||
const questionLike = Array.from(line.matchAll(/[^?]+(?:\?|$)/g))
|
||||
.map((match) => normalizeQuestionChunk(match[0]))
|
||||
.filter((item) => item.length > 0);
|
||||
if (questionLike.length > 1) {
|
||||
for (const item of questionLike) {
|
||||
chunks.push(item.endsWith("?") ? item : `${item}?`);
|
||||
const canSafelySplit = questionLike.every(
|
||||
(item) =>
|
||||
!isRuntimeQuestionPlaceholder(item) &&
|
||||
!isLikelyRuntimeQuestionTail(item) &&
|
||||
normalizeQuestionChunk(item).length >= 18
|
||||
);
|
||||
if (canSafelySplit) {
|
||||
for (const item of questionLike) {
|
||||
chunks.push(item.endsWith("?") ? item : `${item}?`);
|
||||
}
|
||||
} else {
|
||||
chunks.push(normalizedLine);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
chunks.push(normalizeQuestionChunk(line));
|
||||
chunks.push(normalizedLine);
|
||||
}
|
||||
return chunks.filter((item) => item.length > 0);
|
||||
return normalizeRuntimeQuestionList(chunks);
|
||||
}
|
||||
|
||||
function normalizeRuntimeQuestions(value: unknown): string[] {
|
||||
|
|
@ -109,7 +184,7 @@ function normalizeRuntimeQuestions(value: unknown): string[] {
|
|||
return [];
|
||||
}
|
||||
|
||||
const expanded = raw.flatMap((item) => splitQuestionCandidate(item));
|
||||
const expanded = normalizeRuntimeQuestionList(raw.flatMap((item) => splitQuestionCandidate(item)));
|
||||
const deduped: string[] = [];
|
||||
const seen = new Set<string>();
|
||||
for (const item of expanded) {
|
||||
|
|
@ -122,6 +197,11 @@ function normalizeRuntimeQuestions(value: unknown): string[] {
|
|||
return deduped;
|
||||
}
|
||||
|
||||
export const __evalRouteTestUtils = {
|
||||
splitQuestionCandidate,
|
||||
normalizeRuntimeQuestions
|
||||
};
|
||||
|
||||
function normalizeCaseIds(value: unknown): string[] | undefined {
|
||||
if (!Array.isArray(value)) {
|
||||
return undefined;
|
||||
|
|
|
|||
|
|
@ -1854,6 +1854,146 @@ function buildAnswerSummary(mode: PolicyMode): string {
|
|||
|
||||
type P0NarrativeDomain = "settlements_60_62" | "vat_document_register_book" | "month_close_costs_20_44" | null;
|
||||
|
||||
interface BoundaryCapabilitySuggestion {
|
||||
key: "settlements_60_62" | "vat_document_register_book" | "month_close_costs_20_44";
|
||||
label: string;
|
||||
helpText: string;
|
||||
signals: RegExp;
|
||||
}
|
||||
|
||||
const BOUNDARY_CAPABILITY_SUGGESTIONS: BoundaryCapabilitySuggestion[] = [
|
||||
{
|
||||
key: "settlements_60_62",
|
||||
label: "Взаиморасчеты 60/62",
|
||||
helpText: "найти хвосты, незакрытые оплаты и рисковые связки по контрагентам.",
|
||||
signals: /(контраг|долг|сальдо|взаиморасчет|оплат|аванс|покупат|поставщ|банк|выписк|\b60\b|\b62\b|\b76\b)/iu
|
||||
},
|
||||
{
|
||||
key: "vat_document_register_book",
|
||||
label: "НДС 19/68",
|
||||
helpText: "проверить цепочку документ -> счет-фактура -> регистр -> книга.",
|
||||
signals: /(ндс|сч[её]т[-\s]?фактур|регистр|книга\s+покуп|книга\s+продаж|декларац|\b19\b|\b68\b)/iu
|
||||
},
|
||||
{
|
||||
key: "month_close_costs_20_44",
|
||||
label: "Закрытие месяца 20/44",
|
||||
helpText: "проверить распределение затрат и остатки после регламентных операций.",
|
||||
signals: /(закрыти[ея]|месяц|затрат|распределени|рбп|аморт|основн|ос\b|\b20\b|\b25\b|\b26\b|\b44\b)/iu
|
||||
}
|
||||
];
|
||||
|
||||
function formatNarrativeDomainLabel(domain: P0NarrativeDomain): string {
|
||||
if (domain === "settlements_60_62") {
|
||||
return "взаиморасчетов 60/62";
|
||||
}
|
||||
if (domain === "vat_document_register_book") {
|
||||
return "НДС-контура 19/68";
|
||||
}
|
||||
if (domain === "month_close_costs_20_44") {
|
||||
return "закрытия месяца (20/44)";
|
||||
}
|
||||
return "доступного учетного контура";
|
||||
}
|
||||
|
||||
function pickBoundaryCapabilityLines(userMessage: string, limit = 3): string[] {
|
||||
const text = String(userMessage ?? "").toLowerCase();
|
||||
const scored = BOUNDARY_CAPABILITY_SUGGESTIONS.map((item, index) => ({
|
||||
item,
|
||||
score: (text.match(item.signals) ?? []).length,
|
||||
order: index
|
||||
}));
|
||||
const ranked = scored
|
||||
.slice()
|
||||
.sort((left, right) => right.score - left.score || left.order - right.order)
|
||||
.map((entry) => entry.item);
|
||||
const selected = ranked.slice(0, Math.max(2, limit));
|
||||
return uniqueStrings(selected.map((item) => `${item.label}: ${item.helpText}`), limit);
|
||||
}
|
||||
|
||||
function buildNaturalClarificationHints(input: {
|
||||
missingAnchors: MissingAnchors;
|
||||
coverageReport: RequirementCoverageReport;
|
||||
}): string[] {
|
||||
const hints: string[] = [];
|
||||
if (input.missingAnchors.period) {
|
||||
hints.push("Укажи период проверки (например, июль 2020).");
|
||||
}
|
||||
if (input.missingAnchors.account) {
|
||||
hints.push("Укажи счет или связку счетов (например, 60/62, 19/68 или 20/44).");
|
||||
}
|
||||
if (input.missingAnchors.counterparty) {
|
||||
hints.push("Добавь контрагента или договор, чтобы зафиксировать контур проверки.");
|
||||
}
|
||||
if (input.missingAnchors.documentOrObject) {
|
||||
hints.push("Укажи документ или объект, от которого строить проверку цепочки.");
|
||||
}
|
||||
if (input.missingAnchors.anomalyType) {
|
||||
hints.push("Уточни тип отклонения: разрыв цепочки, неверное закрытие или аномальный хвост.");
|
||||
}
|
||||
if (input.coverageReport.clarification_needed_for.length > 0) {
|
||||
hints.push(`Закрой уточнения для требований: ${input.coverageReport.clarification_needed_for.join(", ")}.`);
|
||||
}
|
||||
return uniqueStrings(hints, 5);
|
||||
}
|
||||
|
||||
function shouldUseBoundaryFallbackReply(input: {
|
||||
mode: PolicyMode;
|
||||
groundingCheck: AnswerGroundingCheck;
|
||||
coverageReport: RequirementCoverageReport;
|
||||
okResultsCount: number;
|
||||
partialResultsCount: number;
|
||||
}): boolean {
|
||||
if (input.mode === "out_of_scope") {
|
||||
return true;
|
||||
}
|
||||
if (input.mode !== "clarification_required" && input.mode !== "no_grounded") {
|
||||
return false;
|
||||
}
|
||||
const hasNoEvidenceRoutes = input.okResultsCount === 0 && input.partialResultsCount === 0;
|
||||
const hasNoConfirmedCoverage =
|
||||
input.coverageReport.requirements_covered === 0 &&
|
||||
input.coverageReport.requirements_partially_covered.length === 0;
|
||||
const groundingBlocked =
|
||||
input.groundingCheck.status === "no_grounded_answer" ||
|
||||
input.groundingCheck.status === "partial" ||
|
||||
input.groundingCheck.status === "route_mismatch_blocked";
|
||||
return hasNoEvidenceRoutes && hasNoConfirmedCoverage && groundingBlocked;
|
||||
}
|
||||
|
||||
function buildBoundaryFallbackReply(input: {
|
||||
userMessage: string;
|
||||
focusDomain: P0NarrativeDomain;
|
||||
missingAnchors: MissingAnchors;
|
||||
coverageReport: RequirementCoverageReport;
|
||||
}): string {
|
||||
const nearbyCapabilities = pickBoundaryCapabilityLines(input.userMessage, 3);
|
||||
if (input.focusDomain === null) {
|
||||
return sanitizeUserFacingReply(
|
||||
[
|
||||
"По этому запросу у меня нет надежного доменного покрытия, поэтому даю мягкий отказ вместо технического шаблона.",
|
||||
nearbyCapabilities.length > 0 ? `Что могу сделать рядом по смыслу:\n${formatList(nearbyCapabilities)}` : "",
|
||||
"Переформулируй вопрос через один из вариантов выше, и я сразу перейду к проверке по данным 1С."
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n\n")
|
||||
);
|
||||
}
|
||||
|
||||
const clarificationHints = buildNaturalClarificationHints({
|
||||
missingAnchors: input.missingAnchors,
|
||||
coverageReport: input.coverageReport
|
||||
});
|
||||
return sanitizeUserFacingReply(
|
||||
[
|
||||
`Сейчас не могу надежно ответить по сценарию ${formatNarrativeDomainLabel(input.focusDomain)}: не хватает опоры.`,
|
||||
clarificationHints.length > 0 ? `Чтобы сразу перейти к проверке, уточни:\n${formatList(clarificationHints)}` : "",
|
||||
nearbyCapabilities.length > 0 ? `Если удобнее, могу начать с близкого сценария:\n${formatList(nearbyCapabilities.slice(0, 2))}` : ""
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n\n")
|
||||
);
|
||||
}
|
||||
|
||||
function ensureSentence(value: string): string {
|
||||
const sanitized = sanitizeUserText(value) ?? String(value ?? "").trim();
|
||||
const normalized = sanitized.replace(/\s+/g, " ").trim();
|
||||
|
|
@ -4219,6 +4359,13 @@ function composeAssistantAnswerV11(input: ComposeAnswerInput): ComposeAnswerOutp
|
|||
normalizationPeriodExplicit: Boolean(input.normalizationPeriodExplicit),
|
||||
companyAnchors: input.companyAnchors ?? null
|
||||
});
|
||||
const useBoundaryFallbackReply = shouldUseBoundaryFallbackReply({
|
||||
mode: guardedDecision.mode,
|
||||
groundingCheck: input.groundingCheck,
|
||||
coverageReport: input.coverageReport,
|
||||
okResultsCount: okResults.length,
|
||||
partialResultsCount: partialResults.length
|
||||
});
|
||||
const hasProblemWeakSignal =
|
||||
policySignals.narrowing_strength !== "strong" ||
|
||||
policySignals.minimum_evidence_failed ||
|
||||
|
|
@ -4238,6 +4385,7 @@ function composeAssistantAnswerV11(input: ComposeAnswerInput): ComposeAnswerOutp
|
|||
(guardedDecision.mode === "focused_grounded" && hasProblemWeakSignal);
|
||||
const shouldUseProblemCentricAnswer =
|
||||
Boolean(input.enableProblemCentricAnswerV1) &&
|
||||
!useBoundaryFallbackReply &&
|
||||
!hardBlockedMode &&
|
||||
problemCentricModeEligible &&
|
||||
(!focusedStrong || hasProblemWeakSignal) &&
|
||||
|
|
@ -4382,13 +4530,22 @@ function composeAssistantAnswerV11(input: ComposeAnswerInput): ComposeAnswerOutp
|
|||
}
|
||||
};
|
||||
|
||||
const finalAssistantReply = useBoundaryFallbackReply
|
||||
? buildBoundaryFallbackReply({
|
||||
userMessage: input.userMessage,
|
||||
focusDomain: focusNarrativeDomain,
|
||||
missingAnchors,
|
||||
coverageReport: input.coverageReport
|
||||
})
|
||||
: renderPolicyReply(answerStructure, {
|
||||
questionType,
|
||||
focusDomain: focusNarrativeDomain,
|
||||
anchors: anchorUsage,
|
||||
userMessage: input.userMessage
|
||||
});
|
||||
|
||||
return {
|
||||
assistant_reply: renderPolicyReply(answerStructure, {
|
||||
questionType,
|
||||
focusDomain: focusNarrativeDomain,
|
||||
anchors: anchorUsage,
|
||||
userMessage: input.userMessage
|
||||
}),
|
||||
assistant_reply: finalAssistantReply,
|
||||
fallback_type: guardedDecision.fallback_type,
|
||||
reply_type: guardedDecision.reply_type,
|
||||
answer_structure_v11: answerStructure,
|
||||
|
|
|
|||
|
|
@ -2986,12 +2986,12 @@ function resolveAddressToolGateDecision(addressInputMessage, followupContext, ll
|
|||
const llmContractMode = toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.mode);
|
||||
const llmContractModeConfidence = toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.mode_confidence);
|
||||
const llmContractIntent = toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.intent);
|
||||
const llmCanonicalEntitySignal = /(?:\u0437\u0430\u043a\u0430\u0437\u0447\u0438\u043a|\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442|\u043a\u043e\u043c\u043f\u0430\u043d|customer|supplier|counterparty|company|vendor|client|\b[a-z]{2,}\b)/iu.test(compactWhitespace(repairedInputMessage.toLowerCase()));
|
||||
const llmCanonicalEntitySignal = /(?:\u0437\u0430\u043a\u0430\u0437\u0447\u0438\u043a|\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442|\u043a\u043e\u043c\u043f\u0430\u043d|customer|supplier|counterparty|company|vendor|client)/iu.test(compactWhitespace(repairedInputMessage.toLowerCase()));
|
||||
const llmCanonicalAppliedSignal = Boolean(llmPreDecomposeMeta?.applied) && llmContractMode !== "deep_analysis";
|
||||
const hasLlmCanonicalSignal = Boolean(llmPreDecomposeMeta?.llmCanonicalCandidateDetected) &&
|
||||
((llmContractMode === "address_query" && llmContractModeConfidence !== "low") ||
|
||||
(llmCanonicalAppliedSignal &&
|
||||
(hasStrongDataIntentSignal(repairedInputMessage) || llmCanonicalEntitySignal || llmContractMode === "unsupported")));
|
||||
(hasStrongDataIntentSignal(repairedInputMessage) || llmCanonicalEntitySignal)));
|
||||
const hasLlmCanonicalDataSignal = Boolean(llmPreDecomposeMeta?.llmCanonicalCandidateDetected) &&
|
||||
Boolean(llmPreDecomposeMeta?.applied) &&
|
||||
(llmContractMode === "address_query" || llmContractMode === "unsupported" || llmContractMode === null) &&
|
||||
|
|
@ -3133,6 +3133,42 @@ function hasDirectDeepAnalysisSignal(text) {
|
|||
}
|
||||
return /(?:\u0440\u0430\u0437\u043b\u043e\u0436|\u0446\u0435\u043f\u043e\u0447|lifecycle|\u0440\u0430\u0437\u0440\u044b\u0432|\u043f\u0440\u043e\u0442\u0438\u0432\u043e\u0440\u0435\u0447|\u0430\u043d\u043e\u043c\u0430\u043b|\u043f\u043e\u0447\u0435\u043c\u0443|\u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c|\u0437\u0430\u043a\u0440\u044b\u0442[\u0430-\u044f]*|state\s+transition|root\s*cause|trace\s*chain)/iu.test(normalized);
|
||||
}
|
||||
function hasStrictDeepInvestigationCue(text) {
|
||||
const normalized = compactWhitespace(repairAddressMojibake(String(text ?? "")).toLowerCase());
|
||||
if (!normalized) {
|
||||
return false;
|
||||
}
|
||||
const hasInvestigativeVerb = /(?:\u043f\u0440\u043e\u0432\u0435\u0440(?:\u044c|\u0438\u0442\u044c)|\u0440\u0430\u0437\u0431\u0435\u0440(?:\u0438|\u0430\u0442\u044c)|\u0440\u0430\u0437\u043b\u043e\u0436(?:\u0438|\u0438\u0442\u044c)|\u043f\u043e\u0447\u0435\u043c\u0443|\u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c|root\s*cause|trace\s*chain)/iu.test(normalized);
|
||||
if (!hasInvestigativeVerb) {
|
||||
return false;
|
||||
}
|
||||
return /(?:\u0445\u0432\u043e\u0441\u0442|\u0440\u0430\u0437\u0440\u044b\u0432|\u0446\u0435\u043f\u043e\u0447|\u0430\u043d\u043e\u043c\u0430\u043b|\u043f\u0440\u043e\u0442\u0438\u0432\u043e\u0440\u0435\u0447|\u0437\u0430\u043a\u0440\u044b\u0442\u0438[\u0435\u044f]|\u043e\u0431\u044a\u0435\u043a\u0442(?:\u0443)?\s+\u0440\u0430\u0441\u0447(?:\u0435|\u0451)\u0442|lifecycle|state\s+transition)/iu.test(normalized);
|
||||
}
|
||||
function hasAggregateBusinessAnalyticsSignal(text) {
|
||||
const normalized = compactWhitespace(repairAddressMojibake(String(text ?? "")).toLowerCase());
|
||||
if (!normalized) {
|
||||
return false;
|
||||
}
|
||||
const hasMetricCue = /(?:\u043e\u0431\u043e\u0440\u043e\u0442|\u0432\u044b\u0440\u0443\u0447|\u0434\u043e\u0445\u043e\u0434|\u043f\u0440\u0438\u0431\u044b\u043b|\u043c\u0430\u0440\u0436|\u0440\u0435\u043d\u0442\u0430\u0431\u0435\u043b|\u043f\u043e\u043a\u0430\u0437\u0430\u0442\u0435\u043b|turnover|revenue|profit|margin)/iu.test(normalized);
|
||||
if (!hasMetricCue) {
|
||||
return false;
|
||||
}
|
||||
const hasRankingOrTrendCue = /(?:\u0441\u0430\u043c(?:\u044b\u0439|\u0430\u044f|\u043e\u0435|\u044b\u0435)|\u0442\u043e\u043f|\u043b\u0443\u0447\u0448|\u0445\u0443\u0434\u0448|\u043c\u0430\u043a\u0441(?:\u0438\u043c\u0443\u043c)?|\u043c\u0438\u043d(?:\u0438\u043c\u0443\u043c)?|\u0434\u0438\u043d\u0430\u043c|\u0442\u0440\u0435\u043d\u0434|\u0441\u0440\u0430\u0432\u043d|ranking|top|best|worst)/iu.test(normalized);
|
||||
const hasPeriodAggregateCue = /(?:\u043f\u043e\s+\u0433\u043e\u0434\u0430\u043c|\u0437\u0430\s+\d{4}\s+\u0433\u043e\u0434|\u0433\u043e\u0434(?:\u0430|\u0443|\u044b)?|year|years|\u043a\u0432\u0430\u0440\u0442\u0430\u043b|\u043c\u0435\u0441\u044f\u0446|\u043f\u0435\u0440\u0438\u043e\u0434)/iu.test(normalized);
|
||||
return hasRankingOrTrendCue || hasPeriodAggregateCue;
|
||||
}
|
||||
const ADDRESS_INTENTS_KEEP_ADDRESS_LANE = new Set([
|
||||
"list_open_contracts",
|
||||
"open_items_by_counterparty_or_contract",
|
||||
"list_documents_by_contract",
|
||||
"bank_operations_by_contract",
|
||||
"list_documents_by_counterparty",
|
||||
"bank_operations_by_counterparty",
|
||||
"list_contracts_by_counterparty",
|
||||
"contract_usage_overview",
|
||||
"contract_usage_and_value",
|
||||
"vat_payable_forecast"
|
||||
]);
|
||||
export function resolveAssistantOrchestrationDecision(input) {
|
||||
const rawUserMessage = String(input?.rawUserMessage ?? input?.userMessage ?? "");
|
||||
const effectiveAddressUserMessage = String(input?.effectiveAddressUserMessage ?? rawUserMessage);
|
||||
|
|
@ -3154,9 +3190,21 @@ export function resolveAssistantOrchestrationDecision(input) {
|
|||
hasDataRetrievalRequestSignal(repairedRawUserMessage) ||
|
||||
hasDataRetrievalRequestSignal(effectiveAddressUserMessage) ||
|
||||
hasDataRetrievalRequestSignal(repairedEffectiveAddressUserMessage);
|
||||
const aggregateBusinessAnalyticsSignal = hasAggregateBusinessAnalyticsSignal(rawUserMessage) ||
|
||||
hasAggregateBusinessAnalyticsSignal(repairedRawUserMessage) ||
|
||||
hasAggregateBusinessAnalyticsSignal(effectiveAddressUserMessage) ||
|
||||
hasAggregateBusinessAnalyticsSignal(repairedEffectiveAddressUserMessage);
|
||||
const modeSample = repairedEffectiveAddressUserMessage || effectiveAddressUserMessage;
|
||||
const modeDetection = (0, addressQueryClassifier_1.detectAddressQuestionMode)(modeSample);
|
||||
const intentResolution = (0, addressIntentResolver_1.resolveAddressIntent)(modeSample);
|
||||
const llmContractIntent = toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.intent);
|
||||
const strictDeepInvestigationCueDetected = hasStrictDeepInvestigationCue(rawUserMessage) ||
|
||||
hasStrictDeepInvestigationCue(repairedRawUserMessage) ||
|
||||
hasStrictDeepInvestigationCue(effectiveAddressUserMessage) ||
|
||||
hasStrictDeepInvestigationCue(repairedEffectiveAddressUserMessage);
|
||||
const keepAddressLaneByIntent = Boolean((intentResolution.intent && ADDRESS_INTENTS_KEEP_ADDRESS_LANE.has(intentResolution.intent)) ||
|
||||
(llmContractIntent && ADDRESS_INTENTS_KEEP_ADDRESS_LANE.has(llmContractIntent))) &&
|
||||
!strictDeepInvestigationCueDetected;
|
||||
const strongDataSignal = hasStrongDataIntentSignal(rawUserMessage) ||
|
||||
hasStrongDataIntentSignal(repairedRawUserMessage) ||
|
||||
hasStrongDataIntentSignal(effectiveAddressUserMessage) ||
|
||||
|
|
@ -3232,7 +3280,7 @@ export function resolveAssistantOrchestrationDecision(input) {
|
|||
const llmContractMode = toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.mode);
|
||||
const preserveAddressLaneSignal = Boolean((llmPreDecomposeMeta?.llmCanonicalCandidateDetected &&
|
||||
llmPreDecomposeMeta?.applied &&
|
||||
(llmContractMode === "address_query" || llmContractMode === "unsupported")) ||
|
||||
llmContractMode === "address_query") ||
|
||||
hasSameDateAccountFollowupSignalForPredecompose(rawUserMessage) ||
|
||||
hasSameDateAccountFollowupSignalForPredecompose(effectiveAddressUserMessage) ||
|
||||
hasSameDateAccountFollowupSignalForPredecompose(repairedRawUserMessage) ||
|
||||
|
|
@ -3245,10 +3293,11 @@ export function resolveAssistantOrchestrationDecision(input) {
|
|||
hasAddressFollowupContextSignal(effectiveAddressUserMessage) ||
|
||||
hasAddressFollowupContextSignal(repairedRawUserMessage) ||
|
||||
hasAddressFollowupContextSignal(repairedEffectiveAddressUserMessage));
|
||||
const unsupportedIntentOrMode = modeDetection.mode !== "address_query" &&
|
||||
(intentResolution.intent === "unknown" || llmContractMode === "unsupported");
|
||||
const unsupportedAddressIntentFallbackToDeep = Boolean(!followupContext &&
|
||||
baseToolGate?.runAddressLane &&
|
||||
modeDetection.mode !== "address_query" &&
|
||||
intentResolution.intent === "unknown" &&
|
||||
unsupportedIntentOrMode &&
|
||||
strongDataSignal &&
|
||||
!preserveAddressLaneSignal);
|
||||
const deepAnalysisPreferenceDetected = Boolean(hasDeepAnalysisPreferenceSignal(rawUserMessage) ||
|
||||
|
|
@ -3264,8 +3313,13 @@ export function resolveAssistantOrchestrationDecision(input) {
|
|||
/(?:\u043f\u043e\u0447\u0435\u043c\u0443|why).*(?:\u043f\u0440\u043e\u0433\u043d\u043e\u0437|forecast).*(?:\u0443\u043f\u043b\u0430\u0442|payable|\b0\b)/iu.test(compactWhitespace(`${repairedRawUserMessage} ${repairedEffectiveAddressUserMessage}`)));
|
||||
const deepAnalysisSignalFallbackToDeep = Boolean(baseToolGate?.runAddressLane &&
|
||||
deepAnalysisPreferenceDetected &&
|
||||
!keepAddressLaneByIntent &&
|
||||
!vatExplainFollowupSignal &&
|
||||
(!followupContext || !dataRetrievalSignal));
|
||||
const aggregateAnalyticsFallbackToDeep = Boolean(baseToolGate?.runAddressLane &&
|
||||
aggregateBusinessAnalyticsSignal &&
|
||||
!keepAddressLaneByIntent &&
|
||||
!followupContext);
|
||||
const deepSessionContinuationFallbackToDeep = Boolean(!followupContext &&
|
||||
baseToolGate?.runAddressLane &&
|
||||
hasDeepSessionContinuationSignal({
|
||||
|
|
@ -3288,6 +3342,13 @@ export function resolveAssistantOrchestrationDecision(input) {
|
|||
toolGateDecision = "skip_address_lane";
|
||||
toolGateReason = "deep_analysis_signal_fallback_to_deep";
|
||||
}
|
||||
if (aggregateAnalyticsFallbackToDeep &&
|
||||
!unsupportedAddressIntentFallbackToDeep &&
|
||||
!deepAnalysisSignalFallbackToDeep) {
|
||||
runAddressLane = false;
|
||||
toolGateDecision = "skip_address_lane";
|
||||
toolGateReason = "aggregate_analytics_signal_fallback_to_deep";
|
||||
}
|
||||
if (deepSessionContinuationFallbackToDeep) {
|
||||
runAddressLane = false;
|
||||
toolGateDecision = "skip_address_lane";
|
||||
|
|
@ -3312,6 +3373,14 @@ export function resolveAssistantOrchestrationDecision(input) {
|
|||
reason: "deep_analysis_signal_fallback_to_deep"
|
||||
};
|
||||
}
|
||||
if (aggregateAnalyticsFallbackToDeep &&
|
||||
!unsupportedAddressIntentFallbackToDeep &&
|
||||
!deepAnalysisSignalFallbackToDeep) {
|
||||
livingDecision = {
|
||||
mode: "deep_analysis",
|
||||
reason: "aggregate_analytics_signal_fallback_to_deep"
|
||||
};
|
||||
}
|
||||
if (deepSessionContinuationFallbackToDeep) {
|
||||
livingDecision = {
|
||||
mode: "deep_analysis",
|
||||
|
|
@ -3336,6 +3405,7 @@ export function resolveAssistantOrchestrationDecision(input) {
|
|||
followup_context_detected: Boolean(followupContext),
|
||||
unsupported_address_intent_fallback_to_deep: unsupportedAddressIntentFallbackToDeep,
|
||||
deep_analysis_signal_fallback_to_deep: deepAnalysisSignalFallbackToDeep,
|
||||
aggregate_analytics_signal_fallback_to_deep: aggregateAnalyticsFallbackToDeep,
|
||||
deep_session_continuation_fallback_to_deep: deepSessionContinuationFallbackToDeep,
|
||||
final_decision: {
|
||||
run_address_lane: runAddressLane,
|
||||
|
|
@ -3356,6 +3426,11 @@ function hasDataRetrievalRequestSignal(text) {
|
|||
if (!lower) {
|
||||
return false;
|
||||
}
|
||||
const hasRussianRetrievalAction = /(?:^|\s)(?:\u043f\u043e\u043a\u0430\u0436\u0438|\u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c|\u043d\u0430\u0439\u0434\u0438|\u0432\u044b\u0432\u0435\u0434\u0438|\u0434\u0430\u0439|\u0440\u0430\u0441\u043a\u0440\u043e\u0439|\u0441\u043f\u0438\u0441\u043e\u043a)(?:$|[\s,.!?;:])/iu.test(lower);
|
||||
const hasRussianRetrievalObject = /(?:\u0434\u043e\u0433\u043e\u0432\u043e\u0440|\u043a\u043e\u043d\u0442\u0440\u0430\u043a\u0442|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442|\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442|\u0441\u0447(?:\u0435|\u0451)\u0442|\u043e\u0441\u0442\u0430\u0442|\u0441\u0430\u043b\u044c\u0434\u043e|\u043e\u0431\u043e\u0440\u043e\u0442|\u043f\u043b\u0430\u0442(?:\u0435|\u0451)\u0436|\u043e\u043f\u0435\u0440\u0430\u0446|\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|\u043a\u043b\u0438\u0435\u043d\u0442|\u0433\u043e\u0434|\u043f\u0435\u0440\u0438\u043e\u0434|\u043c\u0435\u0441\u044f\u0446)/iu.test(lower);
|
||||
if (hasRussianRetrievalAction && hasRussianRetrievalObject) {
|
||||
return true;
|
||||
}
|
||||
const hasExplicitRetrievalAction = /(?:\bпокажи\b|\bпоказать\b|\bвыведи\b|\bнайди\b|\bсписок\b|\bдай\b|\bраскрой\b|\bshow\b|\blist\b|\bfind\b|\bcount\b)/i.test(lower);
|
||||
const hasInterrogativeRetrievalAction = /(?:\bсколько\b|\bкакой\b|\bкакая\b|\bкакое\b|\bкакую\b|\bкакие\b|\bкто\b|\bwhich\b|\bwho\b)/i.test(lower);
|
||||
if (!hasExplicitRetrievalAction && !hasInterrogativeRetrievalAction) {
|
||||
|
|
@ -3522,6 +3597,10 @@ function hasAssistantDataScopeMetaQuestionSignal(text) {
|
|||
if (!normalized) {
|
||||
return false;
|
||||
}
|
||||
const hasSlangScopeQuestion = /(?:\u043f\u043e\s+\u043a\u0430\u043a\u0438\u043c\s+(?:\u043a\u043e\u043d\u0442\u043e\u0440(?:\u0430\u043c|\u044b|\u0430)?|\u043a\u043e\u043c\u043f\u0430\u043d(?:\u0438\u044f\u043c|\u0438\u0438|\u0438\u044e|\u0438\u044f)|\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446(?:\u0438\u044f\u043c|\u0438\u0438|\u0438\u044e|\u0438\u044f)|\u0444\u0438\u0440\u043c(?:\u0430\u043c|\u0435|\u0443|\u0430)).*(?:\u043c\u043e\u0436(?:\u0435\u043c|\u043d\u043e)|\u0440\u0430\u0431\u043e\u0442|\u043e\u0431\u0449\u0430\u0442|\u043f\u043e\u0434\u0440\u0443\u0431|\u043f\u043e\u0434\u043a\u043b\u044e\u0447)|(?:\u0431\u0430\u0437\u0430\s+\u043a\u0430\u043a\u043e\u0439\s+(?:\u043a\u043e\u043d\u0442\u043e\u0440|\u043a\u043e\u043c\u043f\u0430\u043d|\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446|\u0444\u0438\u0440\u043c))|(?:\u043a\u0430\u043a\u0430\u044f\s+\u0431\u0430\u0437\u0430\s+(?:\u043f\u043e\u0434\u043a\u043b\u044e\u0447|\u0430\u043a\u0442\u0438\u0432)))/iu.test(normalized);
|
||||
if (hasSlangScopeQuestion) {
|
||||
return true;
|
||||
}
|
||||
const hasBaseOrTenantObject = /(?:баз(?:а|е|у|ы)?|тенант|tenant|контур)/i.test(normalized);
|
||||
const hasCompanyObject = /(?:компан(?:ия|ии|ию|ией)|компин(?:ия|ии|ию|ией)?|компини(?:я|и|ю|ей)?|компани[яеию]|организац(?:ия|ии|ию|ией)|контор(?:а|ы|у|ой)?|фирм(?:а|ы|у|ой)?)/i.test(normalized);
|
||||
const hasConnectionCue = /(?:подключен(?:а|о|ы)?|подруб|воткнут|активн(?:ый|ая)\s+канал|mcp-?канал|канал)/i.test(normalized);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,107 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { composeAssistantAnswer } from "../src/services/answerComposer";
|
||||
|
||||
function buildRouteSummary(fallbackType: "none" | "clarification" | "out_of_scope" = "none") {
|
||||
return {
|
||||
mode: "deterministic_v2" as const,
|
||||
message_in_scope: true,
|
||||
scope_confidence: "high" as const,
|
||||
planner: {
|
||||
total_fragments: 1,
|
||||
in_scope_fragments: 1,
|
||||
out_of_scope_fragments: 0,
|
||||
discarded_fragments: 0,
|
||||
contains_multiple_tasks: false
|
||||
},
|
||||
decisions: [],
|
||||
fallback: {
|
||||
type: fallbackType,
|
||||
message: null
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function buildCoverageReport() {
|
||||
return {
|
||||
requirements_total: 0,
|
||||
requirements_covered: 0,
|
||||
requirements_uncovered: [],
|
||||
requirements_partially_covered: [],
|
||||
clarification_needed_for: [],
|
||||
out_of_scope_requirements: []
|
||||
};
|
||||
}
|
||||
|
||||
describe("assistant boundary fallback reply", () => {
|
||||
it("uses soft refusal without template sections when domain is not covered", () => {
|
||||
const output = composeAssistantAnswer({
|
||||
userMessage: "Скажи курс доллара на завтра и дай прогноз инфляции.",
|
||||
routeSummary: buildRouteSummary("none"),
|
||||
retrievalResults: [],
|
||||
requirements: [],
|
||||
coverageReport: buildCoverageReport(),
|
||||
groundingCheck: {
|
||||
status: "no_grounded_answer",
|
||||
route_subject_match: false,
|
||||
missing_requirements: [],
|
||||
reasons: ["no grounded support"],
|
||||
why_included_summary: [],
|
||||
selection_reason_summary: []
|
||||
},
|
||||
enableAnswerPolicyV11: true
|
||||
});
|
||||
|
||||
expect(output.reply_type).toBe("clarification_required");
|
||||
expect(output.assistant_reply).toMatch(/мягкий отказ/i);
|
||||
expect(output.assistant_reply).toContain("Что могу сделать рядом по смыслу:");
|
||||
expect(output.assistant_reply).not.toContain("Что сломано:");
|
||||
});
|
||||
|
||||
it("for covered domain without anchors uses soft clarification with nearby capability", () => {
|
||||
const output = composeAssistantAnswer({
|
||||
userMessage: "Покажи где по контрагентам хвосты по оплатам.",
|
||||
routeSummary: buildRouteSummary("none"),
|
||||
retrievalResults: [],
|
||||
requirements: [],
|
||||
coverageReport: buildCoverageReport(),
|
||||
groundingCheck: {
|
||||
status: "no_grounded_answer",
|
||||
route_subject_match: false,
|
||||
missing_requirements: [],
|
||||
reasons: ["no grounded support"],
|
||||
why_included_summary: [],
|
||||
selection_reason_summary: []
|
||||
},
|
||||
enableAnswerPolicyV11: true
|
||||
});
|
||||
|
||||
expect(output.reply_type).toBe("clarification_required");
|
||||
expect(output.assistant_reply).toMatch(/не могу надежно ответить по сценарию/i);
|
||||
expect(output.assistant_reply).toContain("Чтобы сразу перейти к проверке, уточни:");
|
||||
expect(output.assistant_reply).toContain("Если удобнее, могу начать с близкого сценария:");
|
||||
expect(output.assistant_reply).not.toContain("Что сломано:");
|
||||
});
|
||||
|
||||
it("keeps out_of_scope reply type but responds with soft fallback text", () => {
|
||||
const output = composeAssistantAnswer({
|
||||
userMessage: "Скажи прогноз погоды на выходные.",
|
||||
routeSummary: buildRouteSummary("out_of_scope"),
|
||||
retrievalResults: [],
|
||||
requirements: [],
|
||||
coverageReport: buildCoverageReport(),
|
||||
groundingCheck: {
|
||||
status: "no_grounded_answer",
|
||||
route_subject_match: false,
|
||||
missing_requirements: [],
|
||||
reasons: ["out of scope"],
|
||||
why_included_summary: [],
|
||||
selection_reason_summary: []
|
||||
},
|
||||
enableAnswerPolicyV11: true
|
||||
});
|
||||
|
||||
expect(output.reply_type).toBe("out_of_scope");
|
||||
expect(output.assistant_reply).toMatch(/мягкий отказ|не могу надежно/i);
|
||||
expect(output.assistant_reply).not.toContain("Что сломано:");
|
||||
});
|
||||
});
|
||||
|
|
@ -126,6 +126,18 @@ describe("assistant living router mode decision", () => {
|
|||
expect(decision.reason).toBe("assistant_data_scope_query_detected");
|
||||
});
|
||||
|
||||
it("routes slang data-scope wording 'по каким конторам можем общаться' to chat", () => {
|
||||
const decision = resolveLivingAssistantModeDecision({
|
||||
userMessage: "\u043f\u043e \u043a\u0430\u043a\u0438\u043c \u043a\u043e\u043d\u0442\u043e\u0440\u0430\u043c \u043c\u043e\u0436\u0435\u043c \u043e\u0431\u0449\u0430\u0442\u044c\u0441\u044f?",
|
||||
addressLaneTriggered: false,
|
||||
useMock: false,
|
||||
predecomposeMode: "unsupported",
|
||||
predecomposeModeConfidence: "low"
|
||||
});
|
||||
expect(decision.mode).toBe("chat");
|
||||
expect(decision.reason).toBe("assistant_data_scope_query_detected");
|
||||
});
|
||||
|
||||
it("routes data-scope wording without question mark when interrogative token is present", () => {
|
||||
const decision = resolveLivingAssistantModeDecision({
|
||||
userMessage: "каза какой компании подключена к 1с",
|
||||
|
|
@ -185,6 +197,54 @@ describe("assistant orchestration contract", () => {
|
|||
expect(decision.livingReason).toBe("address_lane_triggered");
|
||||
});
|
||||
|
||||
it("routes unsupported turnover-by-organization query to deep analysis", () => {
|
||||
const decision = resolveAssistantOrchestrationDecision({
|
||||
rawUserMessage: "\u043a\u0430\u043a\u0438\u0435 \u043e\u0431\u043e\u0440\u043e\u0442\u044b \u043f\u043e \u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0435 \u0437\u0430 2020 \u0433\u043e\u0434",
|
||||
effectiveAddressUserMessage: "\u041e\u0431\u043e\u0440\u043e\u0442\u044b \u043f\u043e \u0441\u0447\u0435\u0442\u0443 '\u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430' \u0437\u0430 2020 \u0433\u043e\u0434.",
|
||||
followupContext: null,
|
||||
llmPreDecomposeMeta: {
|
||||
applied: true,
|
||||
llmCanonicalCandidateDetected: true,
|
||||
predecomposeContract: {
|
||||
mode: "unsupported",
|
||||
mode_confidence: "low",
|
||||
intent: "account_balance_snapshot",
|
||||
intent_confidence: "high"
|
||||
}
|
||||
} as any,
|
||||
useMock: false
|
||||
});
|
||||
|
||||
expect(decision.runAddressLane).toBe(false);
|
||||
expect(decision.toolGateDecision).toBe("skip_address_lane");
|
||||
expect(decision.livingMode).toBe("deep_analysis");
|
||||
expect([
|
||||
"address_signal_unsupported_intent_fallback_to_deep",
|
||||
"aggregate_analytics_signal_fallback_to_deep"
|
||||
]).toContain(String(decision.toolGateReason));
|
||||
expect([
|
||||
"unsupported_address_intent_fallback_to_deep",
|
||||
"aggregate_analytics_signal_fallback_to_deep"
|
||||
]).toContain(String(decision.livingReason));
|
||||
});
|
||||
|
||||
it("routes profitability ranking query to deep analysis instead of address lane", () => {
|
||||
const decision = resolveAssistantOrchestrationDecision({
|
||||
rawUserMessage: "\u043a\u0430\u043a\u043e\u0439 \u0441\u0430\u043c\u044b\u0439 \u0434\u043e\u0445\u043e\u0434\u043d\u044b\u0439 \u0433\u043e\u0434?",
|
||||
effectiveAddressUserMessage: "\u043a\u0430\u043a\u043e\u0439 \u0441\u0430\u043c\u044b\u0439 \u0434\u043e\u0445\u043e\u0434\u043d\u044b\u0439 \u0433\u043e\u0434?",
|
||||
followupContext: null,
|
||||
llmPreDecomposeMeta: null as any,
|
||||
useMock: false
|
||||
} as any);
|
||||
|
||||
expect(decision.runAddressLane).toBe(false);
|
||||
expect(decision.toolGateDecision).toBe("skip_address_lane");
|
||||
expect(decision.toolGateReason).toBe("aggregate_analytics_signal_fallback_to_deep");
|
||||
expect(decision.livingMode).toBe("deep_analysis");
|
||||
expect(decision.livingReason).toBe("aggregate_analytics_signal_fallback_to_deep");
|
||||
expect(decision.orchestrationContract?.aggregate_analytics_signal_fallback_to_deep).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps VAT explain follow-up in address lane when followup context is present", () => {
|
||||
const decision = resolveAssistantOrchestrationDecision({
|
||||
rawUserMessage: "почему прогноз к уплате 0?",
|
||||
|
|
@ -236,6 +296,33 @@ describe("assistant orchestration contract", () => {
|
|||
expect(decision.orchestrationContract?.unsupported_address_intent_fallback_to_deep).toBe(false);
|
||||
});
|
||||
|
||||
it("keeps list_open_contracts query in address lane despite 'unclosed' wording", () => {
|
||||
const decision = resolveAssistantOrchestrationDecision({
|
||||
rawUserMessage: "\u041f\u043e\u043a\u0430\u0436\u0438 \u043d\u0435\u0437\u0430\u043a\u0440\u044b\u0442\u044b\u0435 \u0434\u043e\u0433\u043e\u0432\u043e\u0440\u044b \u043d\u0430 2020-12-31",
|
||||
effectiveAddressUserMessage: "\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u043d\u0435\u0437\u0430\u043a\u0440\u044b\u0442\u044b\u0435 \u0434\u043e\u0433\u043e\u0432\u043e\u0440\u044b \u043f\u043e \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044e \u043d\u0430 \u043a\u043e\u043d\u0435\u0446 \u0434\u0435\u043a\u0430\u0431\u0440\u044f 2020 \u0433\u043e\u0434\u0430.",
|
||||
followupContext: {
|
||||
previous_intent: "month_close_costs_20_44"
|
||||
},
|
||||
llmPreDecomposeMeta: {
|
||||
applied: true,
|
||||
llmCanonicalCandidateDetected: true,
|
||||
predecomposeContract: {
|
||||
mode: "address_query",
|
||||
mode_confidence: "high",
|
||||
intent: "list_open_contracts",
|
||||
intent_confidence: "medium"
|
||||
}
|
||||
} as any,
|
||||
useMock: false
|
||||
} as any);
|
||||
|
||||
expect(decision.runAddressLane).toBe(true);
|
||||
expect(decision.toolGateDecision).toBe("run_address_lane");
|
||||
expect(decision.livingMode).toBe("address_data");
|
||||
expect(decision.livingReason).toBe("address_lane_triggered");
|
||||
expect(decision.toolGateReason).toBe("address_mode_classifier_detected");
|
||||
});
|
||||
|
||||
it("does not force address lane for deep-analysis unknown intent query with date-like token", () => {
|
||||
const decision = resolveAssistantOrchestrationDecision({
|
||||
rawUserMessage: "найди какие либо ошибки на 21 мая 2022 года",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { __autoRunsQuestionTestUtils } from "../src/routes/autoRuns";
|
||||
|
||||
describe("autoruns question extraction", () => {
|
||||
it("merges conversational tails instead of producing micro-prompts", () => {
|
||||
const parsed = __autoRunsQuestionTestUtils.splitQuestionCandidates(
|
||||
"Покажи рисковые хвосты по расчетам с поставщиками? и коротко? без воды?"
|
||||
);
|
||||
|
||||
expect(parsed).toHaveLength(1);
|
||||
expect(parsed[0]).toMatch(/коротко/i);
|
||||
expect(parsed[0]).toMatch(/без воды/i);
|
||||
});
|
||||
|
||||
it("keeps independent questions separated", () => {
|
||||
const parsed = __autoRunsQuestionTestUtils.splitQuestionCandidates(
|
||||
"Где зависли оплаты по счету 60? Какие акты сверки с риском расхождения по 62?"
|
||||
);
|
||||
|
||||
expect(parsed).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("extracts questions from JSON payload and skips placeholders", () => {
|
||||
const parsed = __autoRunsQuestionTestUtils.extractQuestionsFromAutogenOutput(
|
||||
JSON.stringify({
|
||||
questions: ["Вопросы", "Покажи хвосты по поставщикам", "и коротко?"]
|
||||
})
|
||||
);
|
||||
|
||||
expect(parsed).toHaveLength(1);
|
||||
expect(parsed[0]).toMatch(/поставщик/i);
|
||||
expect(parsed[0]).toMatch(/коротко/i);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { __evalRouteTestUtils } from "../src/routes/eval";
|
||||
|
||||
describe("eval runtime question splitting", () => {
|
||||
it("merges short conversational tails into previous question", () => {
|
||||
const parsed = __evalRouteTestUtils.splitQuestionCandidate(
|
||||
"Покажи контрагентов с риском несверки по акту? и коротко? без воды?"
|
||||
);
|
||||
|
||||
expect(parsed).toHaveLength(1);
|
||||
expect(parsed[0]).toMatch(/коротко/i);
|
||||
expect(parsed[0]).toMatch(/без воды/i);
|
||||
});
|
||||
|
||||
it("keeps independent full questions as separate items", () => {
|
||||
const parsed = __evalRouteTestUtils.splitQuestionCandidate(
|
||||
"Где зависли оплаты по счету 60? Какие документы не закрылись по 62 за июль 2020?"
|
||||
);
|
||||
|
||||
expect(parsed).toHaveLength(2);
|
||||
expect(parsed[0]).toMatch(/\?/);
|
||||
expect(parsed[1]).toMatch(/\?/);
|
||||
});
|
||||
|
||||
it("normalizes list input and removes placeholders and duplicates", () => {
|
||||
const parsed = __evalRouteTestUtils.normalizeRuntimeQuestions([
|
||||
"Вопросы",
|
||||
"Покажи хвосты по поставщикам",
|
||||
"и коротко?"
|
||||
]);
|
||||
|
||||
expect(parsed).toHaveLength(1);
|
||||
expect(parsed[0]).toMatch(/поставщик/i);
|
||||
expect(parsed[0]).toMatch(/коротко/i);
|
||||
});
|
||||
});
|
||||
|
|
@ -673,5 +673,221 @@
|
|||
"question_text": "Проверь зависшие авансы и уточни, можно ли их перепривязать на текущие отгрузки или пора списывать как нереальные?",
|
||||
"answer_text": "Я ассистент по анализу данных 1С в режиме чтения.\nЧто умею по группам:\n1. НДС: Расчеты и аналитика по НДС на основании данных 1С. (например: vat_period_snapshot, vat_payable_forecast, vat_turnover_breakdown).\n2. Контрагенты: Срезы активности, платежей и документов по контрагентам. (например: list_documents_by_counterparty, bank_operations_by_counterparty, list_contracts_by_counterparty).\n3. Задолженности и расчеты: Аналитика закрытия расчетов, сальдо и признаков незакрытых цепочек. (например: settlement_closure_state, advance_offset_state, open_items_snapshot).\n4. Деньги и остатки: Остатки и динамика по денежным счетам и кассе. (например: balance_snapshot, turnover_by_period).\n5. Ограничения: Операции, которые ассистент не выполняет в этом рантайме. (например: explain_boundary, suggest_safe_next_step).\nЕсли хотите, раскрою любую группу точечно и дам готовую формулировку запроса.\nЧто не делаю: не настраиваю 1С, не меняю конфигурацию, не создаю и не провожу документы, не выполняю админ-действия на сервере."
|
||||
}
|
||||
},
|
||||
{
|
||||
"annotation_id": "ann-mnubukbt-h5fjkfe",
|
||||
"run_id": "assistant-stage1-ywEyJgFkC4",
|
||||
"case_id": "AUTO-002",
|
||||
"session_id": "assistant-stage1-ywEyJgFkC4-AUTO-002",
|
||||
"message_index": 1,
|
||||
"rating": 3,
|
||||
"comment": "нужен анализ маршрута для ответа на этот вопрос - расшщирение доменов",
|
||||
"manual_case_decision": "candidate_for_implementation",
|
||||
"annotation_author": "manual_reviewer",
|
||||
"resolved": false,
|
||||
"resolved_at": null,
|
||||
"resolved_by": null,
|
||||
"created_at": "2026-04-11T12:44:50.920Z",
|
||||
"updated_at": "2026-04-11T12:44:50.920Z",
|
||||
"context": {
|
||||
"message_id": "msg-tL1QVBeDxY",
|
||||
"trace_id": "Ylge9xWuRuJLvV",
|
||||
"reply_type": "clarification_required",
|
||||
"eval_target": "assistant_stage1",
|
||||
"prompt_version": "address_query_runtime_v1",
|
||||
"domain": null,
|
||||
"query_class": null,
|
||||
"question_text": "Где у нас накопились авансы к отгрузкам, которые уже давно пора закрыть или хотя бы перепроверить, чтобы не подозревать худшее?",
|
||||
"answer_text": "Сейчас не могу надежно ответить по сценарию взаиморасчетов 60/62: не хватает опоры. Чтобы сразу перейти к проверке, уточни:\n- Укажи период проверки (например, июль 2020).\n- Укажи счет или связку счетов (например, 60/62, 19/68 или 20/44).\n- Добавь контрагента или договор, чтобы зафиксировать контур проверки.\n- Укажи документ или объект, от которого строить проверку цепочки.\n- Уточни тип отклонения: разрыв цепочки, неверное закрытие или аномальный хвост. Если удобнее, могу начать с близкого сценария:\n- Взаиморасчеты 60/62: найти хвосты, незакрытые оплаты и рисковые связки по контрагентам.\n- НДС 19/68: проверить цепочку документ -> счет-фактура -> регистр -> книга."
|
||||
}
|
||||
},
|
||||
{
|
||||
"annotation_id": "ann-mnubvu0x-3pv3ehr",
|
||||
"run_id": "assistant-stage1-ywEyJgFkC4",
|
||||
"case_id": "AUTO-004",
|
||||
"session_id": "assistant-stage1-ywEyJgFkC4-AUTO-004",
|
||||
"message_index": 1,
|
||||
"rating": 3,
|
||||
"comment": "нужен анализ маршрута для ответа на этот вопрос - расшщирение доменов - однозначно к дорабюотке - анализируем заказчиков которые чаще встречаются по годам и выводим топ 10",
|
||||
"manual_case_decision": "needs_dialog_policy_fix",
|
||||
"annotation_author": "manual_reviewer",
|
||||
"resolved": false,
|
||||
"resolved_at": null,
|
||||
"resolved_by": null,
|
||||
"created_at": "2026-04-11T12:45:50.144Z",
|
||||
"updated_at": "2026-04-11T12:45:50.144Z",
|
||||
"context": {
|
||||
"message_id": "msg-rj83nhGOV7",
|
||||
"trace_id": "address-H9VJ13GWWC",
|
||||
"reply_type": "partial_coverage",
|
||||
"eval_target": "assistant_stage1",
|
||||
"prompt_version": "address_query_runtime_v1",
|
||||
"domain": null,
|
||||
"query_class": null,
|
||||
"question_text": "Сколько заказчиков у нас на этот момент могут считаться долгожителями по своим задолженностям?",
|
||||
"answer_text": "Чтобы ответить надежно, нужен более точный ориентир в запросе.\nКоротко: в запросе не хватает конкретного ориентира (контрагент, договор или период).\nЧто можно сделать дальше: укажите контрагента или номер/название договора."
|
||||
}
|
||||
},
|
||||
{
|
||||
"annotation_id": "ann-mnubwxmg-4qqv21q",
|
||||
"run_id": "assistant-stage1-ywEyJgFkC4",
|
||||
"case_id": "AUTO-005",
|
||||
"session_id": "assistant-stage1-ywEyJgFkC4-AUTO-005",
|
||||
"message_index": 1,
|
||||
"rating": 3,
|
||||
"comment": "нужен анализ маршрута для ответа на этот вопрос - расшщирение доменов - простой вопрос - показываем просто открытые договора без приходов денег",
|
||||
"manual_case_decision": "candidate_for_implementation",
|
||||
"annotation_author": "manual_reviewer",
|
||||
"resolved": false,
|
||||
"resolved_at": null,
|
||||
"resolved_by": null,
|
||||
"created_at": "2026-04-11T12:46:41.463Z",
|
||||
"updated_at": "2026-04-11T12:46:41.463Z",
|
||||
"context": {
|
||||
"message_id": "msg-2kDN4UKCbY",
|
||||
"trace_id": "address-L0WwEsakCe",
|
||||
"reply_type": "partial_coverage",
|
||||
"eval_target": "assistant_stage1",
|
||||
"prompt_version": "address_query_runtime_v1",
|
||||
"domain": null,
|
||||
"query_class": null,
|
||||
"question_text": "В каких случаях мы видим ситуацию, когда документы есть, а денег - нет и пока не предвидится?",
|
||||
"answer_text": "Чтобы ответить надежно, нужен более точный ориентир в запросе.\nКоротко: в запросе не хватает конкретного ориентира (контрагент, договор или период).\nЧто можно сделать дальше: уточните точное имя контрагента или добавьте ИНН."
|
||||
}
|
||||
},
|
||||
{
|
||||
"annotation_id": "ann-mnubxf3s-bkoxdg5",
|
||||
"run_id": "assistant-stage1-ywEyJgFkC4",
|
||||
"case_id": "AUTO-006",
|
||||
"session_id": "assistant-stage1-ywEyJgFkC4-AUTO-006",
|
||||
"message_index": 1,
|
||||
"rating": 3,
|
||||
"comment": "нужен анализ маршрута для ответа на этот вопрос - расшщирение доменов - надо внедрять",
|
||||
"manual_case_decision": "candidate_for_implementation",
|
||||
"annotation_author": "manual_reviewer",
|
||||
"resolved": false,
|
||||
"resolved_at": null,
|
||||
"resolved_by": null,
|
||||
"created_at": "2026-04-11T12:47:04.120Z",
|
||||
"updated_at": "2026-04-11T12:47:04.120Z",
|
||||
"context": {
|
||||
"message_id": "msg--ZmllegVvV",
|
||||
"trace_id": "cG51b5sIOWKwTi",
|
||||
"reply_type": "clarification_required",
|
||||
"eval_target": "assistant_stage1",
|
||||
"prompt_version": "address_query_runtime_v1",
|
||||
"domain": null,
|
||||
"query_class": null,
|
||||
"question_text": "Какие контрагенты висят с закрытыми отгрузками, но с открытыми документами оплаты, что явно выглядит как кейс для ручной проверки?",
|
||||
"answer_text": "Сейчас не могу надежно ответить по сценарию взаиморасчетов 60/62: не хватает опоры. Чтобы сразу перейти к проверке, уточни:\n- Укажи период проверки (например, июль 2020).\n- Укажи счет или связку счетов (например, 60/62, 19/68 или 20/44).\n- Уточни тип отклонения: разрыв цепочки, неверное закрытие или аномальный хвост.\n- Закрой уточнения для требований: R1. Если удобнее, могу начать с близкого сценария:\n- Взаиморасчеты 60/62: найти хвосты, незакрытые оплаты и рисковые связки по контрагентам.\n- НДС 19/68: проверить цепочку документ -> счет-фактура -> регистр -> книга."
|
||||
}
|
||||
},
|
||||
{
|
||||
"annotation_id": "ann-mnubyvd4-kv1ycys",
|
||||
"run_id": "assistant-stage1-ywEyJgFkC4",
|
||||
"case_id": "AUTO-009",
|
||||
"session_id": "assistant-stage1-ywEyJgFkC4-AUTO-009",
|
||||
"message_index": 1,
|
||||
"rating": 3,
|
||||
"comment": "покеаываем клиентов с открытими договорами которые висят более месяца бенз денег - выводимм том 10",
|
||||
"manual_case_decision": "candidate_for_implementation",
|
||||
"annotation_author": "manual_reviewer",
|
||||
"resolved": false,
|
||||
"resolved_at": null,
|
||||
"resolved_by": null,
|
||||
"created_at": "2026-04-11T12:48:11.847Z",
|
||||
"updated_at": "2026-04-11T12:48:11.847Z",
|
||||
"context": {
|
||||
"message_id": "msg-6GpvEMKGcQ",
|
||||
"trace_id": "address-lGGvDiH21w",
|
||||
"reply_type": "partial_coverage",
|
||||
"eval_target": "assistant_stage1",
|
||||
"prompt_version": "address_query_runtime_v1",
|
||||
"domain": null,
|
||||
"query_class": null,
|
||||
"question_text": "Какие контрагенты у нас на этом моменте могут быть причислены к тем, кто вообще не платит уже несколько месяцев?",
|
||||
"answer_text": "Сейчас этот тип вопроса вне поддерживаемого контура адресного режима.\nКоротко: этот сценарий пока не поддержан в текущем адресном контуре.\nЧто можно сделать дальше: могу проверить близкие сценарии: документы/платежи по контрагенту, договоры или остаток по счету."
|
||||
}
|
||||
},
|
||||
{
|
||||
"annotation_id": "ann-mnubzypb-okrr0gn",
|
||||
"run_id": "assistant-stage1-ywEyJgFkC4",
|
||||
"case_id": "AUTO-013",
|
||||
"session_id": "assistant-stage1-ywEyJgFkC4-AUTO-013",
|
||||
"message_index": 1,
|
||||
"rating": 3,
|
||||
"comment": "однозначно на расширение доменов",
|
||||
"manual_case_decision": "candidate_for_implementation",
|
||||
"annotation_author": "manual_reviewer",
|
||||
"resolved": false,
|
||||
"resolved_at": null,
|
||||
"resolved_by": null,
|
||||
"created_at": "2026-04-11T12:49:02.831Z",
|
||||
"updated_at": "2026-04-11T12:49:02.831Z",
|
||||
"context": {
|
||||
"message_id": "msg-bZZQxlpKcO",
|
||||
"trace_id": "address-JiGLTVe3_0",
|
||||
"reply_type": "partial_coverage",
|
||||
"eval_target": "assistant_stage1",
|
||||
"prompt_version": "address_query_runtime_v1",
|
||||
"domain": null,
|
||||
"query_class": null,
|
||||
"question_text": "По каким контрагентам мы можем заметить такую картину: оплачено меньше, чем отгружено, и это явно требует вмешательства бухгалтера.",
|
||||
"answer_text": "Сейчас этот тип вопроса вне поддерживаемого контура адресного режима.\nКоротко: этот сценарий пока не поддержан в текущем адресном контуре.\nЧто можно сделать дальше: могу проверить близкие сценарии: документы/платежи по контрагенту, договоры или остаток по счету."
|
||||
}
|
||||
},
|
||||
{
|
||||
"annotation_id": "ann-mnuc1qax-x2caipm",
|
||||
"run_id": "assistant-stage1-ywEyJgFkC4",
|
||||
"case_id": "AUTO-014",
|
||||
"session_id": "assistant-stage1-ywEyJgFkC4-AUTO-014",
|
||||
"message_index": 1,
|
||||
"rating": 3,
|
||||
"comment": "проблема тыт шум - важно показать договора с незакрытыми доками - топ по времени висения - от самой длинной дистании до кооротной",
|
||||
"manual_case_decision": "needs_dialog_policy_fix",
|
||||
"annotation_author": "manual_reviewer",
|
||||
"resolved": false,
|
||||
"resolved_at": null,
|
||||
"resolved_by": null,
|
||||
"created_at": "2026-04-11T12:50:25.257Z",
|
||||
"updated_at": "2026-04-11T12:50:25.257Z",
|
||||
"context": {
|
||||
"message_id": "msg-3Leq0R75YH",
|
||||
"trace_id": "E1O-azugbPvNG9",
|
||||
"reply_type": "clarification_required",
|
||||
"eval_target": "assistant_stage1",
|
||||
"prompt_version": "address_query_runtime_v1",
|
||||
"domain": null,
|
||||
"query_class": null,
|
||||
"question_text": "Какие незакрытые документы по договорам у нас уже давно пора проверить - это грозит серьезными проблемами?",
|
||||
"answer_text": "По этому запросу у меня нет надежного доменного покрытия, поэтому даю мягкий отказ вместо технического шаблона. Что могу сделать рядом по смыслу:\n- Взаиморасчеты 60/62: найти хвосты, незакрытые оплаты и рисковые связки по контрагентам.\n- НДС 19/68: проверить цепочку документ -> счет-фактура -> регистр -> книга.\n- Закрытие месяца 20/44: проверить распределение затрат и остатки после регламентных операций. Переформулируй вопрос через один из вариантов выше, и я сразу перейду к проверке по данным 1С."
|
||||
}
|
||||
},
|
||||
{
|
||||
"annotation_id": "ann-mnuc2887-4k0f7x0",
|
||||
"run_id": "assistant-stage1-ywEyJgFkC4",
|
||||
"case_id": "AUTO-015",
|
||||
"session_id": "assistant-stage1-ywEyJgFkC4-AUTO-015",
|
||||
"message_index": 1,
|
||||
"rating": 3,
|
||||
"comment": "нужен анализ и отработка домена",
|
||||
"manual_case_decision": "candidate_for_implementation",
|
||||
"annotation_author": "manual_reviewer",
|
||||
"resolved": false,
|
||||
"resolved_at": null,
|
||||
"resolved_by": null,
|
||||
"created_at": "2026-04-11T12:50:48.487Z",
|
||||
"updated_at": "2026-04-11T12:50:48.487Z",
|
||||
"context": {
|
||||
"message_id": "msg-6Fqd6XrBv2",
|
||||
"trace_id": "address-TnBvKVAVfu",
|
||||
"reply_type": "partial_coverage",
|
||||
"eval_target": "assistant_stage1",
|
||||
"prompt_version": "address_query_runtime_v1",
|
||||
"domain": null,
|
||||
"query_class": null,
|
||||
"question_text": "Покажи контрагентов, чьи заказы на отгрузку еще не оплачены, но сальдо уже отрицательное - это явный признак того, что нужно вмешаться.",
|
||||
"answer_text": "Сейчас этот тип вопроса вне поддерживаемого контура адресного режима.\nКоротко: этот сценарий пока не поддержан в текущем адресном контуре.\nЧто можно сделать дальше: могу проверить близкие сценарии: документы/платежи по контрагенту, договоры или остаток по счету."
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -1,4 +1,74 @@
|
|||
[
|
||||
{
|
||||
"generation_id": "gen-mnubheq4-7h5v00u",
|
||||
"created_at": "2026-04-11T12:34:37.133Z",
|
||||
"mode": "qwen_seed",
|
||||
"count": 15,
|
||||
"domain": null,
|
||||
"questions": [
|
||||
"Кому из контрагентов мы уже месяц отдаем товары, но на счетах все еще красуется минусовое сальдо - это реально зеленый свет для ручного вмешательства?",
|
||||
"Где у нас накопились авансы к отгрузкам, которые уже давно пора закрыть или хотя бы перепроверить, чтобы не подозревать худшее?",
|
||||
"Покажи контрагентов, по которым сальдо у нас выглядит так, будто оно врет - ну точно не совпадает с тем, что они нам прислали. Это уже критично для сверки.",
|
||||
"Сколько заказчиков у нас на этот момент могут считаться долгожителями по своим задолженностям?",
|
||||
"В каких случаях мы видим ситуацию, когда документы есть, а денег - нет и пока не предвидится?",
|
||||
"Какие контрагенты висят с закрытыми отгрузками, но с открытыми документами оплаты, что явно выглядит как кейс для ручной проверки?",
|
||||
"Покажи контрагентов, у которых есть неоплаченные задолженности по договорам на конец месяца - это уже красный свет для бухгалтера.",
|
||||
"По каким заказчикам мы можем выделить непростую картину: сальдо нулевое, а история платежей явно говорит о том, что все не так просто?",
|
||||
"Какие контрагенты у нас на этом моменте могут быть причислены к тем, кто вообще не платит уже несколько месяцев?",
|
||||
"В каких случаях мы видим зависшие отгрузки, которые уже давно пора закрыть - это грозит проблемами в отчетности.",
|
||||
"Покажи контрагентов, по которым на конец месяца сальдо выглядит так, будто документы собраны криво и их нужно перепроверить.",
|
||||
"Какие у нас зависшие авансы или предоплаты уже давно пора либо закрыть, либо хотя бы проверить - это уже не просто вопрос времени?",
|
||||
"По каким контрагентам мы можем заметить такую картину: оплачено меньше, чем отгружено, и это явно требует вмешательства бухгалтера.",
|
||||
"Какие незакрытые документы по договорам у нас уже давно пора проверить - это грозит серьезными проблемами?",
|
||||
"Покажи контрагентов, чьи заказы на отгрузку еще не оплачены, но сальдо уже отрицательное - это явный признак того, что нужно вмешаться."
|
||||
],
|
||||
"generated_by": "manual_reviewer",
|
||||
"saved_case_set_file": "assistant_autogen_qwen_seed_20260411123437_gen-mnubheq4-7h5v00u.json",
|
||||
"context": {
|
||||
"llm_provider": "local",
|
||||
"model": "Qwen2.5 14B Instruct 1M",
|
||||
"assistant_prompt_version": "address_query_runtime_v1",
|
||||
"decomposition_prompt_version": "normalizer_v2_0_2",
|
||||
"prompt_fingerprint": "Ты semantic-normalizer для бухгалтерского ассистента NDC.\nТвоя роль: только нормализация запроса пользователя в строгий JSON-контракт.\n\nЖесткие правила:\n1) Не давай бухгалтерский ответ по сути вопроса.\n2) Возвращай только JSON без markdown и пояснений.\n3) JSON обязан соответствовать переданной schema normalized_query_v1.\n4) Если период не указан, не выдумывай его; отмечай ambiguity.\n5) Для цепочек документов/проводок/оплат поднимай causal и cross-entity признаки.\n6) Для точечного object trace (номер/строка/ref) поднимай needs_exact_object_trace=true.\n7) Используй терминологию NDC.\nYou are semantic-normalizer for accounting assistant NDC.\nReturn strict JSON only, no markdown, no comments.\n\nTarget schema: normalized_query_v2_0_2.\n\nCore behavior (v2.0.2):\n1. Decompose message into semantic fragments.\n2. Classify fragment domain relevance and business scope.\n3. Fill route-critical flags and ",
|
||||
"autogen_personality_id": "general",
|
||||
"autogen_personality_prompt": "Генерируй реалистичные живые вопросы бухгалтера по 1С. Добавляй разговорные формулировки и опечатки, но сохраняй бизнес-смысл. акцент на контрагентов, долги нсд, счета, общий вывод по компании - контрагенты, заказчикам, скока денег кто принес и какие остатки по счетам, поиск документов, сальдо, банковские операции, незакрытые договора, документы по договорам, долги, Активность заказчиков по периодам, Поставщики и выплаты"
|
||||
}
|
||||
},
|
||||
{
|
||||
"generation_id": "gen-mnua8bfg-00u7c2z",
|
||||
"created_at": "2026-04-11T11:59:33.340Z",
|
||||
"mode": "qwen_seed",
|
||||
"count": 15,
|
||||
"domain": null,
|
||||
"questions": [
|
||||
"questions",
|
||||
"По каким покупателям у нас долгий хвост на конец месяца, и это уже больше похоже на системную проблему с цепочкой взаиморасчетов?",
|
||||
"йо По каким поставщикам у нас на конец месяца остались хвосты, которые уже не похожи на обычную задержку документов, а выглядят как реальная проблема в цепочке?",
|
||||
"слушай Где по покупателям у нас висит история \"отгрузили - денег нет - закрытия нет\", и по каким контрагентам это уже требует ручной проверки? без воды",
|
||||
"подскажи плиз Покажи контрагентов, по которым сальдо у нас, скорее всего, не совпадет с их актом сверки, если его запросить прямо сейчас. по факту",
|
||||
"короче Где у нас есть оплаты, но не хватает документов, которые должны были закрыть взаиморасчеты? и коротко",
|
||||
"мож По каким контрагентам, наоборот, документы есть, а нормального закрытия оплатами не видно? прям сейчас",
|
||||
"а ну-ка Есть ли такие зависшие авансы, которые уже давно надо было либо закрыть, либо хотя бы перепроверить руками? за весь период",
|
||||
"йо Какие реализации на конец периода выглядят так, будто они зависли и будут портить картину по выручке, если их не проверить заранее?",
|
||||
"слушай По каким отгрузкам видно, что проблема не просто в том, что клиент не оплатил, а в том, что сама связка документов собрана криво? без воды",
|
||||
"подскажи плиз Покажи реализации, где хвост выглядит особенно неприятно: сумма не маленькая, возраст хвоста уже заметный, и при этом не видно нормального завершения цепочки. по факту",
|
||||
"короче Где по 90/62 история похожа на \"вроде все проведено, но если копнуть, закрытие держится на кривой связке\"? и коротко",
|
||||
"мож Есть ли случаи, где реализация попала в период, а подтверждающие документы или оплата до сих пор живут в какой-то полуразобранной логике? прям сейчас",
|
||||
"а ну-ка По каким продажам на конец месяца видно, что бухгалтер потом будет долго распутывать, почему все это не сошлось нормально? за весь период",
|
||||
"йо Какие банковские движения выглядят так, будто выписка есть, а нормального отражения в учете под ней не хватает?"
|
||||
],
|
||||
"generated_by": "manual_reviewer",
|
||||
"saved_case_set_file": "assistant_autogen_qwen_seed_20260411115933_gen-mnua8bfg-00u7c2z.json",
|
||||
"context": {
|
||||
"llm_provider": "local",
|
||||
"model": "Qwen2.5 14B Instruct 1M",
|
||||
"assistant_prompt_version": "address_query_runtime_v1",
|
||||
"decomposition_prompt_version": "normalizer_v2_0_2",
|
||||
"prompt_fingerprint": "Ты semantic-normalizer для бухгалтерского ассистента NDC.\nТвоя роль: только нормализация запроса пользователя в строгий JSON-контракт.\n\nЖесткие правила:\n1) Не давай бухгалтерский ответ по сути вопроса.\n2) Возвращай только JSON без markdown и пояснений.\n3) JSON обязан соответствовать переданной schema normalized_query_v1.\n4) Если период не указан, не выдумывай его; отмечай ambiguity.\n5) Для цепочек документов/проводок/оплат поднимай causal и cross-entity признаки.\n6) Для точечного object trace (номер/строка/ref) поднимай needs_exact_object_trace=true.\n7) Используй терминологию NDC.\nYou are semantic-normalizer for accounting assistant NDC.\nReturn strict JSON only, no markdown, no comments.\n\nTarget schema: normalized_query_v2_0_2.\n\nCore behavior (v2.0.2):\n1. Decompose message into semantic fragments.\n2. Classify fragment domain relevance and business scope.\n3. Fill route-critical flags and ",
|
||||
"autogen_personality_id": "general",
|
||||
"autogen_personality_prompt": "Генерируй реалистичные живые вопросы бухгалтера по 1С. Добавляй разговорные формулировки и опечатки, но сохраняй бизнес-смысл. акцент на контрагентов, долги нсд, счета, общий вывод по компании - контрагенты, заказчикам, скока денег кто принес и какие остатки по счетам, поиск документов, сальдо, банковские операции, незакрытые договора, документы по договорам, долги, Активность заказчиков по периодам, Поставщики и выплаты"
|
||||
}
|
||||
},
|
||||
{
|
||||
"generation_id": "gen-mnte8abx-ax3v3tr",
|
||||
"created_at": "2026-04-10T21:03:44.205Z",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,254 @@
|
|||
{
|
||||
"suite_id": "assistant_autogen_gen-mnua8bfg-00u7c2z",
|
||||
"suite_version": "0.1.0",
|
||||
"schema_version": "assistant_autogen_suite_v0_1",
|
||||
"generated_at": "2026-04-11T11:59:33.340Z",
|
||||
"generation_id": "gen-mnua8bfg-00u7c2z",
|
||||
"mode": "qwen_seed",
|
||||
"domain": null,
|
||||
"scenario_count": 15,
|
||||
"case_ids": [
|
||||
"AUTO-001",
|
||||
"AUTO-002",
|
||||
"AUTO-003",
|
||||
"AUTO-004",
|
||||
"AUTO-005",
|
||||
"AUTO-006",
|
||||
"AUTO-007",
|
||||
"AUTO-008",
|
||||
"AUTO-009",
|
||||
"AUTO-010",
|
||||
"AUTO-011",
|
||||
"AUTO-012",
|
||||
"AUTO-013",
|
||||
"AUTO-014",
|
||||
"AUTO-015"
|
||||
],
|
||||
"cases": [
|
||||
{
|
||||
"case_id": "AUTO-001",
|
||||
"scenario_tag": "qwen_seed_general",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "questions"
|
||||
}
|
||||
],
|
||||
"expected_hints": {
|
||||
"expected_reply_type": null,
|
||||
"expected_degraded_to": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-002",
|
||||
"scenario_tag": "qwen_seed_general",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "По каким покупателям у нас долгий хвост на конец месяца, и это уже больше похоже на системную проблему с цепочкой взаиморасчетов?"
|
||||
}
|
||||
],
|
||||
"expected_hints": {
|
||||
"expected_reply_type": null,
|
||||
"expected_degraded_to": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-003",
|
||||
"scenario_tag": "qwen_seed_general",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "йо По каким поставщикам у нас на конец месяца остались хвосты, которые уже не похожи на обычную задержку документов, а выглядят как реальная проблема в цепочке?"
|
||||
}
|
||||
],
|
||||
"expected_hints": {
|
||||
"expected_reply_type": null,
|
||||
"expected_degraded_to": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-004",
|
||||
"scenario_tag": "qwen_seed_general",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "слушай Где по покупателям у нас висит история \"отгрузили - денег нет - закрытия нет\", и по каким контрагентам это уже требует ручной проверки? без воды"
|
||||
}
|
||||
],
|
||||
"expected_hints": {
|
||||
"expected_reply_type": null,
|
||||
"expected_degraded_to": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-005",
|
||||
"scenario_tag": "qwen_seed_general",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "подскажи плиз Покажи контрагентов, по которым сальдо у нас, скорее всего, не совпадет с их актом сверки, если его запросить прямо сейчас. по факту"
|
||||
}
|
||||
],
|
||||
"expected_hints": {
|
||||
"expected_reply_type": null,
|
||||
"expected_degraded_to": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-006",
|
||||
"scenario_tag": "qwen_seed_general",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "короче Где у нас есть оплаты, но не хватает документов, которые должны были закрыть взаиморасчеты? и коротко"
|
||||
}
|
||||
],
|
||||
"expected_hints": {
|
||||
"expected_reply_type": null,
|
||||
"expected_degraded_to": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-007",
|
||||
"scenario_tag": "qwen_seed_general",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "мож По каким контрагентам, наоборот, документы есть, а нормального закрытия оплатами не видно? прям сейчас"
|
||||
}
|
||||
],
|
||||
"expected_hints": {
|
||||
"expected_reply_type": null,
|
||||
"expected_degraded_to": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-008",
|
||||
"scenario_tag": "qwen_seed_general",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "а ну-ка Есть ли такие зависшие авансы, которые уже давно надо было либо закрыть, либо хотя бы перепроверить руками? за весь период"
|
||||
}
|
||||
],
|
||||
"expected_hints": {
|
||||
"expected_reply_type": null,
|
||||
"expected_degraded_to": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-009",
|
||||
"scenario_tag": "qwen_seed_general",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "йо Какие реализации на конец периода выглядят так, будто они зависли и будут портить картину по выручке, если их не проверить заранее?"
|
||||
}
|
||||
],
|
||||
"expected_hints": {
|
||||
"expected_reply_type": null,
|
||||
"expected_degraded_to": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-010",
|
||||
"scenario_tag": "qwen_seed_general",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "слушай По каким отгрузкам видно, что проблема не просто в том, что клиент не оплатил, а в том, что сама связка документов собрана криво? без воды"
|
||||
}
|
||||
],
|
||||
"expected_hints": {
|
||||
"expected_reply_type": null,
|
||||
"expected_degraded_to": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-011",
|
||||
"scenario_tag": "qwen_seed_general",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "подскажи плиз Покажи реализации, где хвост выглядит особенно неприятно: сумма не маленькая, возраст хвоста уже заметный, и при этом не видно нормального завершения цепочки. по факту"
|
||||
}
|
||||
],
|
||||
"expected_hints": {
|
||||
"expected_reply_type": null,
|
||||
"expected_degraded_to": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-012",
|
||||
"scenario_tag": "qwen_seed_general",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "короче Где по 90/62 история похожа на \"вроде все проведено, но если копнуть, закрытие держится на кривой связке\"? и коротко"
|
||||
}
|
||||
],
|
||||
"expected_hints": {
|
||||
"expected_reply_type": null,
|
||||
"expected_degraded_to": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-013",
|
||||
"scenario_tag": "qwen_seed_general",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "мож Есть ли случаи, где реализация попала в период, а подтверждающие документы или оплата до сих пор живут в какой-то полуразобранной логике? прям сейчас"
|
||||
}
|
||||
],
|
||||
"expected_hints": {
|
||||
"expected_reply_type": null,
|
||||
"expected_degraded_to": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-014",
|
||||
"scenario_tag": "qwen_seed_general",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "а ну-ка По каким продажам на конец месяца видно, что бухгалтер потом будет долго распутывать, почему все это не сошлось нормально? за весь период"
|
||||
}
|
||||
],
|
||||
"expected_hints": {
|
||||
"expected_reply_type": null,
|
||||
"expected_degraded_to": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-015",
|
||||
"scenario_tag": "qwen_seed_general",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "йо Какие банковские движения выглядят так, будто выписка есть, а нормального отражения в учете под ней не хватает?"
|
||||
}
|
||||
],
|
||||
"expected_hints": {
|
||||
"expected_reply_type": null,
|
||||
"expected_degraded_to": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,254 @@
|
|||
{
|
||||
"suite_id": "assistant_autogen_gen-mnubheq4-7h5v00u",
|
||||
"suite_version": "0.1.0",
|
||||
"schema_version": "assistant_autogen_suite_v0_1",
|
||||
"generated_at": "2026-04-11T12:34:37.132Z",
|
||||
"generation_id": "gen-mnubheq4-7h5v00u",
|
||||
"mode": "qwen_seed",
|
||||
"domain": null,
|
||||
"scenario_count": 15,
|
||||
"case_ids": [
|
||||
"AUTO-001",
|
||||
"AUTO-002",
|
||||
"AUTO-003",
|
||||
"AUTO-004",
|
||||
"AUTO-005",
|
||||
"AUTO-006",
|
||||
"AUTO-007",
|
||||
"AUTO-008",
|
||||
"AUTO-009",
|
||||
"AUTO-010",
|
||||
"AUTO-011",
|
||||
"AUTO-012",
|
||||
"AUTO-013",
|
||||
"AUTO-014",
|
||||
"AUTO-015"
|
||||
],
|
||||
"cases": [
|
||||
{
|
||||
"case_id": "AUTO-001",
|
||||
"scenario_tag": "qwen_seed_general",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Кому из контрагентов мы уже месяц отдаем товары, но на счетах все еще красуется минусовое сальдо - это реально зеленый свет для ручного вмешательства?"
|
||||
}
|
||||
],
|
||||
"expected_hints": {
|
||||
"expected_reply_type": null,
|
||||
"expected_degraded_to": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-002",
|
||||
"scenario_tag": "qwen_seed_general",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Где у нас накопились авансы к отгрузкам, которые уже давно пора закрыть или хотя бы перепроверить, чтобы не подозревать худшее?"
|
||||
}
|
||||
],
|
||||
"expected_hints": {
|
||||
"expected_reply_type": null,
|
||||
"expected_degraded_to": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-003",
|
||||
"scenario_tag": "qwen_seed_general",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Покажи контрагентов, по которым сальдо у нас выглядит так, будто оно врет - ну точно не совпадает с тем, что они нам прислали. Это уже критично для сверки."
|
||||
}
|
||||
],
|
||||
"expected_hints": {
|
||||
"expected_reply_type": null,
|
||||
"expected_degraded_to": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-004",
|
||||
"scenario_tag": "qwen_seed_general",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Сколько заказчиков у нас на этот момент могут считаться долгожителями по своим задолженностям?"
|
||||
}
|
||||
],
|
||||
"expected_hints": {
|
||||
"expected_reply_type": null,
|
||||
"expected_degraded_to": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-005",
|
||||
"scenario_tag": "qwen_seed_general",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "В каких случаях мы видим ситуацию, когда документы есть, а денег - нет и пока не предвидится?"
|
||||
}
|
||||
],
|
||||
"expected_hints": {
|
||||
"expected_reply_type": null,
|
||||
"expected_degraded_to": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-006",
|
||||
"scenario_tag": "qwen_seed_general",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Какие контрагенты висят с закрытыми отгрузками, но с открытыми документами оплаты, что явно выглядит как кейс для ручной проверки?"
|
||||
}
|
||||
],
|
||||
"expected_hints": {
|
||||
"expected_reply_type": null,
|
||||
"expected_degraded_to": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-007",
|
||||
"scenario_tag": "qwen_seed_general",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Покажи контрагентов, у которых есть неоплаченные задолженности по договорам на конец месяца - это уже красный свет для бухгалтера."
|
||||
}
|
||||
],
|
||||
"expected_hints": {
|
||||
"expected_reply_type": null,
|
||||
"expected_degraded_to": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-008",
|
||||
"scenario_tag": "qwen_seed_general",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "По каким заказчикам мы можем выделить непростую картину: сальдо нулевое, а история платежей явно говорит о том, что все не так просто?"
|
||||
}
|
||||
],
|
||||
"expected_hints": {
|
||||
"expected_reply_type": null,
|
||||
"expected_degraded_to": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-009",
|
||||
"scenario_tag": "qwen_seed_general",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Какие контрагенты у нас на этом моменте могут быть причислены к тем, кто вообще не платит уже несколько месяцев?"
|
||||
}
|
||||
],
|
||||
"expected_hints": {
|
||||
"expected_reply_type": null,
|
||||
"expected_degraded_to": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-010",
|
||||
"scenario_tag": "qwen_seed_general",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "В каких случаях мы видим зависшие отгрузки, которые уже давно пора закрыть - это грозит проблемами в отчетности."
|
||||
}
|
||||
],
|
||||
"expected_hints": {
|
||||
"expected_reply_type": null,
|
||||
"expected_degraded_to": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-011",
|
||||
"scenario_tag": "qwen_seed_general",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Покажи контрагентов, по которым на конец месяца сальдо выглядит так, будто документы собраны криво и их нужно перепроверить."
|
||||
}
|
||||
],
|
||||
"expected_hints": {
|
||||
"expected_reply_type": null,
|
||||
"expected_degraded_to": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-012",
|
||||
"scenario_tag": "qwen_seed_general",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Какие у нас зависшие авансы или предоплаты уже давно пора либо закрыть, либо хотя бы проверить - это уже не просто вопрос времени?"
|
||||
}
|
||||
],
|
||||
"expected_hints": {
|
||||
"expected_reply_type": null,
|
||||
"expected_degraded_to": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-013",
|
||||
"scenario_tag": "qwen_seed_general",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "По каким контрагентам мы можем заметить такую картину: оплачено меньше, чем отгружено, и это явно требует вмешательства бухгалтера."
|
||||
}
|
||||
],
|
||||
"expected_hints": {
|
||||
"expected_reply_type": null,
|
||||
"expected_degraded_to": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-014",
|
||||
"scenario_tag": "qwen_seed_general",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Какие незакрытые документы по договорам у нас уже давно пора проверить - это грозит серьезными проблемами?"
|
||||
}
|
||||
],
|
||||
"expected_hints": {
|
||||
"expected_reply_type": null,
|
||||
"expected_degraded_to": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-015",
|
||||
"scenario_tag": "qwen_seed_general",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Покажи контрагентов, чьи заказы на отгрузку еще не оплачены, но сальдо уже отрицательное - это явный признак того, что нужно вмешаться."
|
||||
}
|
||||
],
|
||||
"expected_hints": {
|
||||
"expected_reply_type": null,
|
||||
"expected_degraded_to": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,238 @@
|
|||
{
|
||||
"suite_id": "assistant_autogen_runtime_job-pIsvZJjub9",
|
||||
"suite_version": "0.1.0",
|
||||
"schema_version": "assistant_autogen_runtime_v0_1",
|
||||
"scenario_count": 19,
|
||||
"case_ids": [
|
||||
"AUTO-001",
|
||||
"AUTO-002",
|
||||
"AUTO-003",
|
||||
"AUTO-004",
|
||||
"AUTO-005",
|
||||
"AUTO-006",
|
||||
"AUTO-007",
|
||||
"AUTO-008",
|
||||
"AUTO-009",
|
||||
"AUTO-010",
|
||||
"AUTO-011",
|
||||
"AUTO-012",
|
||||
"AUTO-013",
|
||||
"AUTO-014",
|
||||
"AUTO-015",
|
||||
"AUTO-016",
|
||||
"AUTO-017",
|
||||
"AUTO-018",
|
||||
"AUTO-019"
|
||||
],
|
||||
"cases": [
|
||||
{
|
||||
"case_id": "AUTO-001",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "questions"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-002",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "По каким покупателям у нас долгий хвост на конец месяца, и это уже больше похоже на системную проблему с цепочкой взаиморасчетов?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-003",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "йо По каким поставщикам у нас на конец месяца остались хвосты, которые уже не похожи на обычную задержку документов, а выглядят как реальная проблема в цепочке?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-004",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "слушай Где по покупателям у нас висит история \"отгрузили - денег нет - закрытия нет\", и по каким контрагентам это уже требует ручной проверки?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-005",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "без воды?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-006",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "подскажи плиз Покажи контрагентов, по которым сальдо у нас, скорее всего, не совпадет с их актом сверки, если его запросить прямо сейчас. по факту"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-007",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "короче Где у нас есть оплаты, но не хватает документов, которые должны были закрыть взаиморасчеты?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-008",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "и коротко?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-009",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "мож По каким контрагентам, наоборот, документы есть, а нормального закрытия оплатами не видно?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-010",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "прям сейчас?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-011",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "а ну-ка Есть ли такие зависшие авансы, которые уже давно надо было либо закрыть, либо хотя бы перепроверить руками?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-012",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "за весь период?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-013",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "йо Какие реализации на конец периода выглядят так, будто они зависли и будут портить картину по выручке, если их не проверить заранее?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-014",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "слушай По каким отгрузкам видно, что проблема не просто в том, что клиент не оплатил, а в том, что сама связка документов собрана криво?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-015",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "подскажи плиз Покажи реализации, где хвост выглядит особенно неприятно: сумма не маленькая, возраст хвоста уже заметный, и при этом не видно нормального завершения цепочки. по факту"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-016",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "короче Где по 90/62 история похожа на \"вроде все проведено, но если копнуть, закрытие держится на кривой связке\"?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-017",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "мож Есть ли случаи, где реализация попала в период, а подтверждающие документы или оплата до сих пор живут в какой-то полуразобранной логике?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-018",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "а ну-ка По каким продажам на конец месяца видно, что бухгалтер потом будет долго распутывать, почему все это не сошлось нормально?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-019",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "йо Какие банковские движения выглядят так, будто выписка есть, а нормального отражения в учете под ней не хватает?"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,190 @@
|
|||
{
|
||||
"suite_id": "assistant_autogen_runtime_job-tvH0FxMgzD",
|
||||
"suite_version": "0.1.0",
|
||||
"schema_version": "assistant_autogen_runtime_v0_1",
|
||||
"scenario_count": 15,
|
||||
"case_ids": [
|
||||
"AUTO-001",
|
||||
"AUTO-002",
|
||||
"AUTO-003",
|
||||
"AUTO-004",
|
||||
"AUTO-005",
|
||||
"AUTO-006",
|
||||
"AUTO-007",
|
||||
"AUTO-008",
|
||||
"AUTO-009",
|
||||
"AUTO-010",
|
||||
"AUTO-011",
|
||||
"AUTO-012",
|
||||
"AUTO-013",
|
||||
"AUTO-014",
|
||||
"AUTO-015"
|
||||
],
|
||||
"cases": [
|
||||
{
|
||||
"case_id": "AUTO-001",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Кому из контрагентов мы уже месяц отдаем товары, но на счетах все еще красуется минусовое сальдо - это реально зеленый свет для ручного вмешательства?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-002",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Где у нас накопились авансы к отгрузкам, которые уже давно пора закрыть или хотя бы перепроверить, чтобы не подозревать худшее?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-003",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Покажи контрагентов, по которым сальдо у нас выглядит так, будто оно врет - ну точно не совпадает с тем, что они нам прислали. Это уже критично для сверки."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-004",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Сколько заказчиков у нас на этот момент могут считаться долгожителями по своим задолженностям?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-005",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "В каких случаях мы видим ситуацию, когда документы есть, а денег - нет и пока не предвидится?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-006",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Какие контрагенты висят с закрытыми отгрузками, но с открытыми документами оплаты, что явно выглядит как кейс для ручной проверки?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-007",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Покажи контрагентов, у которых есть неоплаченные задолженности по договорам на конец месяца - это уже красный свет для бухгалтера."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-008",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "По каким заказчикам мы можем выделить непростую картину: сальдо нулевое, а история платежей явно говорит о том, что все не так просто?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-009",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Какие контрагенты у нас на этом моменте могут быть причислены к тем, кто вообще не платит уже несколько месяцев?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-010",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "В каких случаях мы видим зависшие отгрузки, которые уже давно пора закрыть - это грозит проблемами в отчетности."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-011",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Покажи контрагентов, по которым на конец месяца сальдо выглядит так, будто документы собраны криво и их нужно перепроверить."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-012",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Какие у нас зависшие авансы или предоплаты уже давно пора либо закрыть, либо хотя бы проверить - это уже не просто вопрос времени?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-013",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "По каким контрагентам мы можем заметить такую картину: оплачено меньше, чем отгружено, и это явно требует вмешательства бухгалтера."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-014",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Какие незакрытые документы по договорам у нас уже давно пора проверить - это грозит серьезными проблемами?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-015",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Покажи контрагентов, чьи заказы на отгрузку еще не оплачены, но сальдо уже отрицательное - это явный признак того, что нужно вмешаться."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
{
|
||||
"run_id": "eval-GmILXRUMgZ",
|
||||
"timestamp": "2026-04-11T12:28:27.408Z",
|
||||
"mode": "single-pass-strict",
|
||||
"use_mock": true,
|
||||
"prompt_version": "normalizer_v2_0_2",
|
||||
"schema_version": "v2_0_2",
|
||||
"dataset": {
|
||||
"source": "inline_raw_questions",
|
||||
"file": null,
|
||||
"raw_questions_count": 3
|
||||
},
|
||||
"cases_total": 3,
|
||||
"metrics": {
|
||||
"schema_validation_pass_rate": 100,
|
||||
"scope_detection_accuracy": null,
|
||||
"scope_in_scope_rate": 33.33,
|
||||
"multi_intent_detected_rate": 0,
|
||||
"clarification_required_rate": 0,
|
||||
"avg_fragments_per_message": 1,
|
||||
"out_of_scope_fragment_rate": 33.33,
|
||||
"routed_fragment_rate": 66.67,
|
||||
"no_route_fragment_rate": 33.33,
|
||||
"route_resolution_accuracy": null,
|
||||
"no_route_precision": null,
|
||||
"false_no_route_rate": null,
|
||||
"execution_state_consistency_rate": 66.67,
|
||||
"executable_with_soft_assumptions_rate": 100,
|
||||
"soft_assumption_used_fragment_rate": 100,
|
||||
"clarification_precision": null,
|
||||
"clarification_recall": null,
|
||||
"false_clarification_rate": null
|
||||
},
|
||||
"budget": {
|
||||
"requests_total": 0,
|
||||
"retries_used": 0
|
||||
},
|
||||
"clarification_eval": {
|
||||
"labeled_cases": 0,
|
||||
"true_positive": 0,
|
||||
"false_positive": 0,
|
||||
"false_negative": 0
|
||||
},
|
||||
"route_eval": {
|
||||
"labeled_cases": 0,
|
||||
"correct_cases": 0,
|
||||
"expected_routed_cases": 0,
|
||||
"no_route_true_positive": 0,
|
||||
"no_route_false_positive": 0
|
||||
},
|
||||
"scope_eval": {
|
||||
"labeled_cases": 0,
|
||||
"correct_cases": 0
|
||||
},
|
||||
"execution_state_eval": {
|
||||
"checks_total": 3,
|
||||
"checks_passed": 2
|
||||
},
|
||||
"route_distribution": {
|
||||
"hybrid_store_plus_live": 1,
|
||||
"no_route": 1,
|
||||
"batch_refresh_then_store": 1
|
||||
},
|
||||
"fallback_distribution": {
|
||||
"none": 1,
|
||||
"out_of_scope": 1,
|
||||
"clarification": 1
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"case_id": "BQ-001",
|
||||
"raw_question": "Проверь хвосты по поставщикам и разложи цепочку",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": true,
|
||||
"scope_confidence": "high",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 1,
|
||||
"out_of_scope_fragments": 0,
|
||||
"unclear_fragments": 0,
|
||||
"fallback_type": "none",
|
||||
"predicted_route_status": "routed",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": null,
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 1,
|
||||
"trace_id": "-Xq2kvvsT5YnbS",
|
||||
"request_count_for_case": 0
|
||||
},
|
||||
{
|
||||
"case_id": "BQ-002",
|
||||
"raw_question": "Как вообще по ФСБУ",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": false,
|
||||
"scope_confidence": "low",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 0,
|
||||
"out_of_scope_fragments": 1,
|
||||
"unclear_fragments": 0,
|
||||
"fallback_type": "out_of_scope",
|
||||
"predicted_route_status": "no_route",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": "out_of_scope",
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 0,
|
||||
"trace_id": "LOJZh5gvT38Lh8",
|
||||
"request_count_for_case": 0
|
||||
},
|
||||
{
|
||||
"case_id": "BQ-003",
|
||||
"raw_question": "Покажи топ рисков за июнь 2020",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": false,
|
||||
"scope_confidence": "low",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 0,
|
||||
"out_of_scope_fragments": 0,
|
||||
"unclear_fragments": 1,
|
||||
"fallback_type": "clarification",
|
||||
"predicted_route_status": "routed",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": null,
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 0,
|
||||
"trace_id": "33PFaWdNYgR-i0",
|
||||
"request_count_for_case": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
Reference in New Issue