ГЛОБАЛЬНЫЙ РЕФАКТОРИНГ АРХИТЕКТУРЫ - Рефакторинг этапов 2.25 финализацию address-ветки в отдельный adapter \ Переподключил assistantService на новый adapter (без смены поведения)
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { finalizeAssistantAddressTurn } from "../src/services/assistantAddressTurnFinalizeRuntimeAdapter";
|
||||
|
||||
function buildLaneDebug() {
|
||||
return {
|
||||
detected_mode: "address_query",
|
||||
detected_mode_confidence: "high",
|
||||
query_shape: "DOCUMENT_LIST",
|
||||
query_shape_confidence: "high",
|
||||
detected_intent: "list_documents_by_counterparty",
|
||||
detected_intent_confidence: "high",
|
||||
extracted_filters: { counterparty: "свк", limit: 20 },
|
||||
missing_required_filters: [],
|
||||
selected_recipe: "address_documents_by_counterparty_v1",
|
||||
mcp_call_status_legacy: "matched_non_empty",
|
||||
account_scope_mode: "preferred",
|
||||
account_scope_fallback_applied: false,
|
||||
anchor_type: "counterparty",
|
||||
anchor_value_raw: "свк",
|
||||
anchor_value_resolved: "Группа СВК",
|
||||
resolver_confidence: "medium",
|
||||
ambiguity_count: 0,
|
||||
match_failure_stage: "none",
|
||||
match_failure_reason: null,
|
||||
mcp_call_status: "matched_non_empty",
|
||||
rows_fetched: 20,
|
||||
raw_rows_received: 20,
|
||||
rows_after_account_scope: 5,
|
||||
rows_after_recipe_filter: 3,
|
||||
rows_materialized: 5,
|
||||
rows_matched: 3,
|
||||
raw_row_keys_sample: [],
|
||||
materialization_drop_reason: "none",
|
||||
account_token_raw: null,
|
||||
account_token_normalized: null,
|
||||
account_scope_fields_checked: ["account_dt", "account_kt", "registrator", "analytics"],
|
||||
account_scope_match_strategy: "account_code_regex_plus_alias_map_v1",
|
||||
account_scope_drop_reason: "not_applicable",
|
||||
runtime_readiness: "LIVE_QUERYABLE_WITH_LIMITS",
|
||||
limited_reason_category: null,
|
||||
response_type: "FACTUAL_LIST",
|
||||
limitations: [],
|
||||
reasons: []
|
||||
} as any;
|
||||
}
|
||||
|
||||
describe("assistant address turn finalize runtime adapter", () => {
|
||||
it("builds assistant item and passes expected log details into commit runtime", () => {
|
||||
const laneDebug = buildLaneDebug();
|
||||
const commitCalls: Array<Record<string, unknown>> = [];
|
||||
const output = finalizeAssistantAddressTurn({
|
||||
sessionId: "asst-1",
|
||||
userMessage: "покажи документы по свк",
|
||||
effectiveAddressUserMessage: "документы по контрагенту свк",
|
||||
assistantReply: "Собран список документов.",
|
||||
replyType: "factual",
|
||||
addressLaneDebug: laneDebug,
|
||||
debug: { trace_id: "address-trace-1", x: 1 } as any,
|
||||
carryoverMeta: {
|
||||
previousAddressIntent: "list_documents_by_counterparty",
|
||||
previousAddressAnchor: "Группа СВК"
|
||||
},
|
||||
llmPreDecomposeMeta: {
|
||||
attempted: true,
|
||||
applied: true,
|
||||
provider: "openai",
|
||||
traceId: "predec-1",
|
||||
reason: "llm_contract_ok",
|
||||
fallbackRuleHit: null,
|
||||
sanitizedUserMessage: "покажи документы по свк",
|
||||
toolGateDecision: "run_address_lane",
|
||||
toolGateReason: "address_mode_classifier_detected",
|
||||
dialogContinuationContract: {
|
||||
decision: "new_topic",
|
||||
target_intent: null
|
||||
},
|
||||
addressRetryAudit: {
|
||||
attempted: false,
|
||||
reason: null,
|
||||
initial_limited_category: null,
|
||||
retry_result_category: null
|
||||
},
|
||||
predecomposeContract: {
|
||||
intent: "list_documents_by_counterparty",
|
||||
aggregation_profile: "list_lookup",
|
||||
period: {
|
||||
scope: "year"
|
||||
}
|
||||
}
|
||||
},
|
||||
appendItem: () => {},
|
||||
getSession: () => null,
|
||||
persistSession: () => {},
|
||||
cloneConversation: () => [],
|
||||
logEvent: () => {},
|
||||
nowIso: () => "2026-04-10T12:00:00.000Z",
|
||||
messageIdFactory: () => "msg-fixed-1",
|
||||
commitFn: ((input: Record<string, unknown>) => {
|
||||
commitCalls.push(input);
|
||||
return {
|
||||
currentSession: null,
|
||||
conversation: []
|
||||
};
|
||||
}) as any
|
||||
});
|
||||
|
||||
expect(output.assistantItem.message_id).toBe("msg-fixed-1");
|
||||
expect(output.assistantItem.created_at).toBe("2026-04-10T12:00:00.000Z");
|
||||
expect(output.assistantItem.trace_id).toBe("address-trace-1");
|
||||
expect(commitCalls).toHaveLength(1);
|
||||
expect(commitCalls[0]?.["eventType"]).toBe("assistant_message_address");
|
||||
const logDetails = commitCalls[0]?.["logDetails"] as Record<string, unknown>;
|
||||
expect(logDetails?.["session_id"]).toBe("asst-1");
|
||||
expect(logDetails?.["effective_address_user_message"]).toBe("документы по контрагенту свк");
|
||||
expect(logDetails?.["address_followup_context_applied"]).toBe(true);
|
||||
expect(logDetails?.["address_llm_predecompose_attempted"]).toBe(true);
|
||||
expect(logDetails?.["address_dialog_continuation_decision"]).toBe("new_topic");
|
||||
expect(logDetails?.["assistant_reply"]).toBe("Собран список документов.");
|
||||
expect(output.response.assistant_reply).toBe("Собран список документов.");
|
||||
expect(output.response.reply_type).toBe("factual");
|
||||
});
|
||||
|
||||
it("uses default commit runtime and returns conversation from stored session", () => {
|
||||
const laneDebug = buildLaneDebug();
|
||||
let appendCalls = 0;
|
||||
let persistCalls = 0;
|
||||
let logCalls = 0;
|
||||
let storedSession: any = null;
|
||||
const output = finalizeAssistantAddressTurn({
|
||||
sessionId: "asst-2",
|
||||
userMessage: "покажи документы",
|
||||
effectiveAddressUserMessage: "документы",
|
||||
assistantReply: "Готово",
|
||||
replyType: "partial_coverage",
|
||||
addressLaneDebug: laneDebug,
|
||||
debug: { trace_id: "address-trace-2" } as any,
|
||||
appendItem: (_sessionId, item) => {
|
||||
appendCalls += 1;
|
||||
storedSession = {
|
||||
session_id: "asst-2",
|
||||
updated_at: "2026-04-10T12:00:00.000Z",
|
||||
items: [item],
|
||||
investigation_state: null
|
||||
};
|
||||
},
|
||||
getSession: () => storedSession,
|
||||
persistSession: () => {
|
||||
persistCalls += 1;
|
||||
},
|
||||
cloneConversation: (items) => items.map((item) => ({ ...item })),
|
||||
logEvent: () => {
|
||||
logCalls += 1;
|
||||
},
|
||||
messageIdFactory: () => "msg-fixed-2",
|
||||
nowIso: () => "2026-04-10T12:10:00.000Z"
|
||||
});
|
||||
|
||||
expect(appendCalls).toBe(1);
|
||||
expect(persistCalls).toBe(1);
|
||||
expect(logCalls).toBe(1);
|
||||
expect(output.response.ok).toBe(true);
|
||||
expect(output.response.session_id).toBe("asst-2");
|
||||
expect(output.response.reply_type).toBe("partial_coverage");
|
||||
expect(output.response.conversation).toHaveLength(1);
|
||||
expect(output.response.conversation_item.message_id).toBe("msg-fixed-2");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,112 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { finalizeAssistantDeepTurn } from "../src/services/assistantDeepTurnFinalizeRuntimeAdapter";
|
||||
|
||||
describe("assistant deep turn finalize runtime adapter", () => {
|
||||
it("commits assistant turn and builds response with committed conversation", () => {
|
||||
const callOrder: string[] = [];
|
||||
const conversation = [
|
||||
{
|
||||
message_id: "msg-1",
|
||||
session_id: "asst-1",
|
||||
role: "assistant",
|
||||
text: "ok",
|
||||
reply_type: "factual",
|
||||
created_at: "2026-04-10T10:00:00.000Z",
|
||||
trace_id: "trace-1",
|
||||
debug: null
|
||||
}
|
||||
] as any;
|
||||
|
||||
const output = finalizeAssistantDeepTurn({
|
||||
sessionId: "asst-1",
|
||||
assistantReply: "safe-reply",
|
||||
replyType: "factual",
|
||||
assistantItem: conversation[0],
|
||||
debug: { d: 1 },
|
||||
deepAnalysisLogDetails: { stage: "deep" },
|
||||
appendItem: () => {},
|
||||
getSession: () => null,
|
||||
persistSession: () => {},
|
||||
cloneConversation: () => [],
|
||||
logEvent: () => {},
|
||||
commitFn: ((input: Record<string, unknown>) => {
|
||||
callOrder.push("commit");
|
||||
expect(input.eventType).toBe("assistant_message");
|
||||
expect(input.sessionId).toBe("asst-1");
|
||||
expect(input.logDetails).toEqual({ stage: "deep" });
|
||||
return {
|
||||
currentSession: null,
|
||||
conversation
|
||||
};
|
||||
}) as any,
|
||||
buildResponseFn: ((input: Record<string, unknown>) => {
|
||||
callOrder.push("response");
|
||||
expect(input.conversation).toBe(conversation);
|
||||
return {
|
||||
ok: true,
|
||||
session_id: "asst-1",
|
||||
assistant_reply: "safe-reply",
|
||||
reply_type: "factual",
|
||||
conversation_item: conversation[0],
|
||||
debug: { d: 1 },
|
||||
conversation
|
||||
};
|
||||
}) as any
|
||||
});
|
||||
|
||||
expect(callOrder).toEqual(["commit", "response"]);
|
||||
expect(output.commitResult.conversation).toBe(conversation);
|
||||
expect(output.response.assistant_reply).toBe("safe-reply");
|
||||
expect(output.response.conversation).toBe(conversation as any);
|
||||
});
|
||||
|
||||
it("uses default commit/response functions when custom hooks are not provided", () => {
|
||||
const assistantItem = {
|
||||
message_id: "msg-1",
|
||||
session_id: "asst-1",
|
||||
role: "assistant",
|
||||
text: "ok",
|
||||
reply_type: "factual",
|
||||
created_at: "2026-04-10T10:00:00.000Z",
|
||||
trace_id: "trace-1",
|
||||
debug: null
|
||||
} as any;
|
||||
const storedSession = {
|
||||
session_id: "asst-1",
|
||||
updated_at: "2026-04-10T10:00:00.000Z",
|
||||
items: [assistantItem],
|
||||
investigation_state: null
|
||||
};
|
||||
let appendCalled = 0;
|
||||
let persistCalled = 0;
|
||||
let logCalled = 0;
|
||||
|
||||
const output = finalizeAssistantDeepTurn({
|
||||
sessionId: "asst-1",
|
||||
assistantReply: "safe-reply",
|
||||
replyType: "factual",
|
||||
assistantItem,
|
||||
debug: { debug: true },
|
||||
deepAnalysisLogDetails: { info: "x" },
|
||||
appendItem: () => {
|
||||
appendCalled += 1;
|
||||
},
|
||||
getSession: () => storedSession as any,
|
||||
persistSession: () => {
|
||||
persistCalled += 1;
|
||||
},
|
||||
cloneConversation: (items) => items.map((item) => ({ ...item })),
|
||||
logEvent: () => {
|
||||
logCalled += 1;
|
||||
}
|
||||
});
|
||||
|
||||
expect(appendCalled).toBe(1);
|
||||
expect(persistCalled).toBe(1);
|
||||
expect(logCalled).toBe(1);
|
||||
expect(output.response.ok).toBe(true);
|
||||
expect(output.response.session_id).toBe("asst-1");
|
||||
expect(output.response.reply_type).toBe("factual");
|
||||
expect(output.response.conversation.length).toBe(1);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user