ГЛОБАЛЬНЫЙ РЕФАКТОРИНГ АРХИТЕКТУРЫ - Рефакторинг этапов 2.25 финализацию address-ветки в отдельный adapter \ Переподключил assistantService на новый adapter (без смены поведения)

This commit is contained in:
2026-04-10 19:37:56 +03:00
parent 80d108e506
commit 205daeccc5
9 changed files with 794 additions and 209 deletions
@@ -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);
});
});