NODEDC_1C/llm_normalizer/backend/tests/assistantAddressLaneRespons...

226 lines
8.3 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, vi } from "vitest";
import { runAssistantAddressLaneResponseRuntime } from "../src/services/assistantAddressLaneResponseRuntimeAdapter";
describe("assistant address lane response runtime adapter", () => {
it("builds debug payload and finalizes address turn", () => {
const finalizeAddressTurn = vi.fn(() => ({
response: {
ok: true
}
}));
const runtime = runAssistantAddressLaneResponseRuntime({
sessionId: "asst-1",
userMessage: "raw",
effectiveAddressUserMessage: "canon",
addressLane: {
handled: true,
reply_text: "answer",
reply_type: "factual",
debug: {
extracted_filters: {
organization: "ООО Ромашка"
}
}
},
carryoverMeta: {
followupContext: {
previous_intent: "list_documents"
}
},
llmPreDecomposeMeta: {
attempted: true
},
knownOrganizations: ["ООО Ромашка", "ООО Лютик"],
activeOrganization: "ООО Ромашка",
sanitizeOutgoingAssistantText: (text) => String(text ?? "").trim(),
buildAddressDebugPayload: (addressDebug) => ({ ...(addressDebug as Record<string, unknown>) }),
buildAddressFollowupOffer: () => ({ suggestion: "continue_previous" }),
mergeKnownOrganizations: (items) => Array.from(new Set(items)),
toNonEmptyString: (value) => (typeof value === "string" && value.trim() ? value.trim() : null),
appendItem: () => {},
getSession: () => ({ session_id: "asst-1", updated_at: "", items: [], investigation_state: null } as any),
persistSession: () => {},
cloneConversation: (items) => items,
logEvent: () => {},
messageIdFactory: () => "msg-1",
finalizeAddressTurn
});
expect(finalizeAddressTurn).toHaveBeenCalledWith(
expect.objectContaining({
assistantReply: "answer",
replyType: "factual",
llmPreDecomposeMeta: {
attempted: true
}
})
);
expect(runtime.response).toEqual({ ok: true });
expect(runtime.debug).toEqual(
expect.objectContaining({
assistant_known_organizations: ["ООО Ромашка", "ООО Лютик"],
assistant_active_organization: "ООО Ромашка",
address_followup_offer: { suggestion: "continue_previous" },
assistant_runtime_contract_v1: expect.objectContaining({
schema_version: "assistant_runtime_contracts_v1"
}),
assistant_truth_answer_policy_v1: expect.objectContaining({
schema_version: "assistant_truth_answer_policy_runtime_v1",
policy_owner: "assistantTruthAnswerPolicyRuntimeAdapter"
}),
coverage_gate_contract: expect.objectContaining({
schema_version: "assistant_truth_answer_policy_runtime_v1"
}),
answer_shape_contract: expect.objectContaining({
reply_type: "factual"
}),
assistant_state_transition_v1: expect.objectContaining({
schema_version: "assistant_state_transition_runtime_v1",
state_owner: "assistantStateTransitionRuntimeAdapter"
}),
state_transition_contract: expect.objectContaining({
schema_version: "assistant_state_transition_runtime_v1"
}),
assistant_capability_binding_v1: expect.objectContaining({
schema_version: "assistant_capability_runtime_binding_v1",
binding_owner: "assistantCapabilityRuntimeBindingAdapter"
}),
capability_binding_contract: expect.objectContaining({
schema_version: "assistant_capability_runtime_binding_v1"
}),
capability_binding_response_guard: expect.objectContaining({
schema_version: "assistant_capability_binding_response_guard_v1",
applied: false
})
})
);
});
it("keeps debug bounded to shadow contracts when optional enrichment is absent", () => {
const runtime = runAssistantAddressLaneResponseRuntime({
sessionId: "asst-2",
userMessage: "raw",
effectiveAddressUserMessage: "raw",
addressLane: {
handled: true,
reply_text: "answer",
reply_type: "partial_coverage",
debug: {}
},
knownOrganizations: [],
activeOrganization: null,
sanitizeOutgoingAssistantText: (text) => String(text ?? ""),
buildAddressDebugPayload: () => ({}),
buildAddressFollowupOffer: () => null,
mergeKnownOrganizations: (items) => items,
toNonEmptyString: () => null,
appendItem: () => {},
getSession: () => ({ session_id: "asst-2", updated_at: "", items: [], investigation_state: null } as any),
persistSession: () => {},
cloneConversation: (items) => items,
logEvent: () => {},
messageIdFactory: () => "msg-2",
finalizeAddressTurn: () => ({
response: {
ok: true
}
})
});
expect(runtime.debug).toEqual(
expect.objectContaining({
assistant_runtime_contract_v1: expect.objectContaining({
transition_contract_id: null,
capability_contract_id: null,
truth_gate_contract_status: "unknown"
}),
assistant_truth_answer_policy_v1: expect.objectContaining({
schema_version: "assistant_truth_answer_policy_runtime_v1"
}),
coverage_gate_contract: expect.objectContaining({
coverage_status: "blocked",
truth_mode: "unsupported"
}),
answer_shape_contract: expect.objectContaining({
answer_shape: "blocked_no_answer",
reply_type: "partial_coverage"
}),
assistant_state_transition_v1: expect.objectContaining({
application_status: "unresolved",
effective_carryover_depth: "none"
}),
transition_contract_id: null,
capability_contract_id: null,
truth_gate_contract_status: "unknown",
carryover_eligibility: "none",
state_transition_id: null,
state_transition_status: "unresolved",
effective_carryover_depth: "none",
capability_binding_status: "not_applicable",
capability_binding_action: "observe_only",
capability_binding_violations: [],
capability_binding_response_guard: expect.objectContaining({
applied: false,
action: "observe_only"
})
})
);
expect(runtime.response).toEqual({ ok: true });
});
it("guards blocked capability binding responses before finalizing the turn", () => {
const finalizeAddressTurn = vi.fn(() => ({
response: {
ok: true
}
}));
runAssistantAddressLaneResponseRuntime({
sessionId: "asst-3",
userMessage: "кто поставщик?",
effectiveAddressUserMessage: "кто поставщик?",
addressLane: {
handled: true,
reply_text: "unsafe factual answer",
reply_type: "factual",
debug: {
capability_id: "inventory_inventory_purchase_provenance_for_item",
limited_reason_category: "missing_anchor",
missing_required_filters: ["item"]
}
},
knownOrganizations: [],
activeOrganization: null,
sanitizeOutgoingAssistantText: (text) => String(text ?? ""),
buildAddressDebugPayload: (addressDebug) => ({ ...(addressDebug as Record<string, unknown>) }),
buildAddressFollowupOffer: () => null,
mergeKnownOrganizations: (items) => items,
toNonEmptyString: (value) => (typeof value === "string" && value.trim() ? value.trim() : null),
appendItem: () => {},
getSession: () => ({ session_id: "asst-3", updated_at: "", items: [], investigation_state: null } as any),
persistSession: () => {},
cloneConversation: (items) => items,
logEvent: () => {},
messageIdFactory: () => "msg-3",
finalizeAddressTurn
});
expect(finalizeAddressTurn).toHaveBeenCalledWith(
expect.objectContaining({
assistantReply: expect.stringContaining("Нужно уточнение"),
replyType: "partial_coverage",
debug: expect.objectContaining({
capability_binding_status: "blocked",
capability_binding_action: "clarify",
capability_binding_response_guard: expect.objectContaining({
applied: true,
action: "clarify",
guarded_reply_type: "partial_coverage"
})
})
})
);
});
});