NODEDC_1C/llm_normalizer/backend/tests/assistantDeepTurnNormalizat...

254 lines
8.4 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import { buildAssistantDeepTurnNormalizationRuntime } from "../src/services/assistantDeepTurnNormalizationRuntimeAdapter";
describe("assistant deep turn normalization runtime adapter", () => {
it("uses followup state binding when feature flags are enabled and state exists", async () => {
const followupBinding = {
normalizedQuestion: "normalized question",
mergedContext: {
period_hint: "2020-07",
business_context: "ctx-from-followup"
},
usage: {
applied: true
}
};
const buildFollowupStateBinding = vi.fn(() => followupBinding);
const normalize = vi.fn(async (request) => ({
trace_id: "trace-1",
ok: true,
normalized: { schema_version: "normalized_query_v2_0_2" } as any,
route_hint_summary: null,
raw_model_output: {},
validation: { passed: true, errors: [] },
usage: { input_tokens: 10, output_tokens: 20, total_tokens: 30 },
latency_ms: 7,
prompt_version: String(request.promptVersion ?? ""),
schema_version: "normalized_query_v2_0_2",
request_count_for_case: 1
}));
const runtime = await buildAssistantDeepTurnNormalizationRuntime({
userMessage: "raw question",
payload: {
llmProvider: "openai",
apiKey: "k",
model: "m",
baseUrl: "https://api.example.com",
temperature: 0.2,
maxOutputTokens: 333,
promptVersion: "normalizer_v2_0_2",
systemPrompt: "sys",
developerPrompt: "dev",
domainPrompt: "dom",
fewShotExamples: "few",
context: {
period_hint: "2020-06"
},
useMock: true
},
featureInvestigationStateV1: true,
featureStateFollowupBindingV1: true,
sessionInvestigationState: {
active_domain: "settlements_60_62"
},
buildFollowupStateBinding,
normalize
});
expect(buildFollowupStateBinding).toHaveBeenCalledTimes(1);
expect(normalize).toHaveBeenCalledTimes(1);
expect(normalize).toHaveBeenCalledWith({
llmProvider: "openai",
apiKey: "k",
model: "m",
baseUrl: "https://api.example.com",
temperature: 0.2,
maxOutputTokens: 333,
promptVersion: "normalizer_v2_0_2",
schemaVersion: "v2_0_2",
systemPrompt: "sys",
developerPrompt: "dev",
domainPrompt: "dom",
fewShotExamples: "few",
userQuestion: "normalized question",
context: {
period_hint: "2020-07",
business_context: "ctx-from-followup"
},
useMock: true
});
expect(runtime.followupBinding).toBe(followupBinding);
expect(runtime.normalizePayload.userQuestion).toBe("normalized question");
});
it("falls back to raw user message when followup binding is disabled", async () => {
const buildFollowupStateBinding = vi.fn();
const normalize = vi.fn(async () => ({
trace_id: "trace-2",
ok: true,
normalized: { schema_version: "normalized_query_v2_0_2" } as any,
route_hint_summary: null,
raw_model_output: {},
validation: { passed: true, errors: [] },
usage: { input_tokens: 1, output_tokens: 1, total_tokens: 2 },
latency_ms: 1,
prompt_version: "address_query_runtime_v1",
schema_version: "normalized_query_v2_0_2",
request_count_for_case: 1
}));
const runtime = await buildAssistantDeepTurnNormalizationRuntime({
userMessage: "raw fallback",
payload: {
llmProvider: "openai",
context: {
business_context: "payload-context"
},
useMock: undefined
},
featureInvestigationStateV1: false,
featureStateFollowupBindingV1: true,
sessionInvestigationState: {
some: "state"
},
buildFollowupStateBinding,
normalize
});
expect(buildFollowupStateBinding).not.toHaveBeenCalled();
expect(normalize).toHaveBeenCalledWith({
llmProvider: "openai",
apiKey: undefined,
model: undefined,
baseUrl: undefined,
temperature: undefined,
maxOutputTokens: undefined,
promptVersion: "normalizer_v2_0_2",
schemaVersion: "v2_0_2",
systemPrompt: undefined,
developerPrompt: undefined,
domainPrompt: undefined,
fewShotExamples: undefined,
userQuestion: "raw fallback",
context: {
business_context: "payload-context"
},
useMock: false
});
expect(runtime.followupBinding).toEqual({
normalizedQuestion: "raw fallback",
mergedContext: {
business_context: "payload-context"
},
usage: null
});
});
it("synthesizes route summary from normalized payload when normalize omits it", async () => {
const normalize = vi.fn(async () => ({
trace_id: "trace-3",
ok: true,
normalized: {
schema_version: "normalized_query_v2_0_2",
user_message_raw: "Разложи хвосты по поставщикам",
message_in_scope: true,
scope_confidence: "high",
contains_multiple_tasks: false,
discarded_fragments: [],
global_notes: {
needs_clarification: false,
clarification_reason: null
},
fragments: [
{
fragment_id: "F1",
raw_fragment_text: "Разложи хвосты по поставщикам: где разрыв между оплатой и документами выглядит системным.",
normalized_fragment_text:
"Разложи хвосты по поставщикам: где разрыв между оплатой и документами выглядит системным.",
domain_relevance: "in_scope",
business_scope: "company_specific_accounting",
entity_hints: ["контрагент", "документ"],
account_hints: ["60"],
document_hints: ["документ"],
register_hints: [],
time_scope: {
type: "missing",
value: null,
confidence: "low"
},
flags: {
has_multi_entity_scope: true,
asks_for_chain_explanation: true,
asks_for_ranking_or_top: false,
asks_for_period_summary: false,
asks_for_rule_check: false,
asks_for_anomaly_scan: true,
asks_for_exact_object_trace: false,
asks_for_evidence: true,
mentions_period_close_context: false
},
semantic_hints: {
scope_target_kind: "none",
scope_target_text: null,
date_scope_kind: "missing",
self_scope_detected: false,
selected_object_scope_detected: false
},
candidate_labels: ["cross_entity", "anomaly_probe"],
confidence: "medium",
execution_readiness: "executable_with_soft_assumptions",
clarification_reason: null,
soft_assumption_used: ["problem_scan_mode_enabled"],
route_status: "routed",
no_route_reason: null
}
]
},
route_hint_summary: null,
raw_model_output: {},
validation: { passed: true, errors: [] },
usage: { input_tokens: 1, output_tokens: 1, total_tokens: 2 },
latency_ms: 1,
prompt_version: "normalizer_v2_0_2",
schema_version: "normalized_query_v2_0_2",
request_count_for_case: 1
}));
const runtime = await buildAssistantDeepTurnNormalizationRuntime({
userMessage: "Разложи хвосты по поставщикам",
payload: {
llmProvider: "openai",
promptVersion: "address_query_runtime_v1",
useMock: true
},
featureInvestigationStateV1: false,
featureStateFollowupBindingV1: false,
sessionInvestigationState: null,
buildFollowupStateBinding: vi.fn(),
normalize
});
expect(runtime.normalized.route_hint_summary).toEqual(
expect.objectContaining({
mode: "deterministic_v2",
fallback: expect.objectContaining({
type: "none"
}),
decisions: [
expect.objectContaining({
fragment_id: "F1",
route: "hybrid_store_plus_live"
})
]
})
);
expect(normalize).toHaveBeenCalledWith(
expect.objectContaining({
promptVersion: "normalizer_v2_0_2",
schemaVersion: "v2_0_2"
})
);
});
});