ГЛОБАЛЬНЫЙ РЕФАКТОРИНГ АРХИТЕКТУРЫ - Этап 4.3: унификация контракта ответов и добавление теста формы Stage 4
This commit is contained in:
@@ -0,0 +1,220 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { composeAssistantAnswer } from "../src/services/answerComposer";
|
||||
import type { UnifiedRetrievalResult } from "../src/types/assistant";
|
||||
|
||||
function routeSummary() {
|
||||
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: "none" as const,
|
||||
message: null
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function expectStage4Blocks(reply: string) {
|
||||
expect(reply).toContain("Коротко:");
|
||||
expect(reply).toContain("Что именно проверено:");
|
||||
expect(reply).toContain("Что найдено:");
|
||||
expect(reply).toContain("Что пока не доказано:");
|
||||
expect(reply).not.toContain("Что сломано:");
|
||||
expect(reply).not.toContain("Почему это похоже на проблему:");
|
||||
expect(reply).not.toContain("На чем это основано:");
|
||||
expect(reply).not.toContain("Ограничения:");
|
||||
}
|
||||
|
||||
describe("assistant stage4 answer contract shape", () => {
|
||||
it("uses Stage 4 blocks for focused grounded policy reply", () => {
|
||||
const retrieval: UnifiedRetrievalResult = {
|
||||
fragment_id: "F1",
|
||||
requirement_ids: ["R1"],
|
||||
route: "store_feature_risk",
|
||||
status: "ok",
|
||||
result_type: "summary",
|
||||
items: [{ doc: "A-1" }],
|
||||
summary: {
|
||||
broad_query_detected: false,
|
||||
broad_result_flag: false,
|
||||
minimum_evidence_failed: false,
|
||||
narrowing_strength: "strong"
|
||||
},
|
||||
evidence: [
|
||||
{
|
||||
evidence_id: "ev-1",
|
||||
claim_ref: "requirement:R1",
|
||||
source_type: "retrieval_item",
|
||||
source_ref: {
|
||||
schema_version: "evidence_source_ref_v1",
|
||||
namespace: "snapshot_2020",
|
||||
entity: "document",
|
||||
id: "A-1",
|
||||
period: "2020-06",
|
||||
canonical_ref: "evidence_source_ref_v1|snapshot_2020|document|A-1|2020-06"
|
||||
},
|
||||
pointer: {
|
||||
fragment_id: "F1",
|
||||
route: "store_feature_risk",
|
||||
source: {
|
||||
namespace: "snapshot_2020",
|
||||
entity: "document",
|
||||
id: "A-1",
|
||||
period: "2020-06"
|
||||
}
|
||||
},
|
||||
evidence_kind: "mechanism_link",
|
||||
mechanism_note: "Переход подтвержден документом и проводкой.",
|
||||
confidence: "high",
|
||||
payload: {
|
||||
amount: 100
|
||||
}
|
||||
}
|
||||
],
|
||||
why_included: ["релевантная запись"],
|
||||
selection_reason: ["сильное совпадение"],
|
||||
risk_factors: [],
|
||||
business_interpretation: [],
|
||||
confidence: "high",
|
||||
limitations: [],
|
||||
errors: []
|
||||
};
|
||||
|
||||
const output = composeAssistantAnswer({
|
||||
userMessage: "Проверь документ A-1 за июнь 2020.",
|
||||
routeSummary: routeSummary(),
|
||||
retrievalResults: [retrieval],
|
||||
requirements: [
|
||||
{
|
||||
requirement_id: "R1",
|
||||
source_fragment_id: "F1",
|
||||
requirement_text: "проверить документ",
|
||||
subject_tokens: [],
|
||||
status: "covered",
|
||||
route: "store_feature_risk"
|
||||
}
|
||||
],
|
||||
coverageReport: {
|
||||
requirements_total: 1,
|
||||
requirements_covered: 1,
|
||||
requirements_uncovered: [],
|
||||
requirements_partially_covered: [],
|
||||
clarification_needed_for: [],
|
||||
out_of_scope_requirements: []
|
||||
},
|
||||
groundingCheck: {
|
||||
status: "grounded",
|
||||
route_subject_match: true,
|
||||
missing_requirements: [],
|
||||
reasons: [],
|
||||
why_included_summary: ["релевантная запись"],
|
||||
selection_reason_summary: ["сильное совпадение"]
|
||||
},
|
||||
enableAnswerPolicyV11: true
|
||||
});
|
||||
|
||||
expect(output.reply_type).toBe("factual_with_explanation");
|
||||
expect(output.assistant_reply).toContain("Что проверить первым:");
|
||||
expectStage4Blocks(output.assistant_reply);
|
||||
});
|
||||
|
||||
it("uses Stage 4 blocks for weak broad-partial soft reply", () => {
|
||||
const retrieval: UnifiedRetrievalResult = {
|
||||
fragment_id: "F1",
|
||||
requirement_ids: ["R1"],
|
||||
route: "store_feature_risk",
|
||||
status: "partial",
|
||||
result_type: "summary",
|
||||
items: [{ note: "weak candidate" }],
|
||||
summary: {
|
||||
broad_query_detected: true,
|
||||
broad_result_flag: true,
|
||||
minimum_evidence_failed: true,
|
||||
narrowing_strength: "weak"
|
||||
},
|
||||
evidence: [],
|
||||
why_included: [],
|
||||
selection_reason: [],
|
||||
risk_factors: [],
|
||||
business_interpretation: [],
|
||||
confidence: "low",
|
||||
limitations: ["Weak evidence envelope"],
|
||||
errors: []
|
||||
};
|
||||
|
||||
const output = composeAssistantAnswer({
|
||||
userMessage: "Покажи общую картину рисков и аномалий по документам.",
|
||||
routeSummary: routeSummary(),
|
||||
retrievalResults: [retrieval],
|
||||
requirements: [
|
||||
{
|
||||
requirement_id: "R1",
|
||||
source_fragment_id: "F1",
|
||||
requirement_text: "общая картина рисков",
|
||||
subject_tokens: [],
|
||||
status: "partially_covered",
|
||||
route: "store_feature_risk"
|
||||
}
|
||||
],
|
||||
coverageReport: {
|
||||
requirements_total: 1,
|
||||
requirements_covered: 0,
|
||||
requirements_uncovered: [],
|
||||
requirements_partially_covered: ["R1"],
|
||||
clarification_needed_for: [],
|
||||
out_of_scope_requirements: []
|
||||
},
|
||||
groundingCheck: {
|
||||
status: "partial",
|
||||
route_subject_match: true,
|
||||
missing_requirements: ["R1"],
|
||||
reasons: ["insufficient_detail"],
|
||||
why_included_summary: [],
|
||||
selection_reason_summary: []
|
||||
},
|
||||
enableAnswerPolicyV11: true
|
||||
});
|
||||
|
||||
expect(output.reply_type).toBe("partial_coverage");
|
||||
expect(output.assistant_reply).toContain("Что могу сделать сейчас:");
|
||||
expectStage4Blocks(output.assistant_reply);
|
||||
});
|
||||
|
||||
it("uses Stage 4 blocks for boundary fallback reply", () => {
|
||||
const output = composeAssistantAnswer({
|
||||
userMessage: "Скажи курс доллара на завтра и дай прогноз инфляции.",
|
||||
routeSummary: routeSummary(),
|
||||
retrievalResults: [],
|
||||
requirements: [],
|
||||
coverageReport: {
|
||||
requirements_total: 0,
|
||||
requirements_covered: 0,
|
||||
requirements_uncovered: [],
|
||||
requirements_partially_covered: [],
|
||||
clarification_needed_for: [],
|
||||
out_of_scope_requirements: []
|
||||
},
|
||||
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).toContain("Что могу сделать сейчас:");
|
||||
expectStage4Blocks(output.assistant_reply);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user