NODEDC_1C/llm_normalizer/backend/tests/assistantBoundaryFallbackRe...

108 lines
3.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

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

import { 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("Что сломано:");
});
});