ГЛОБАЛЬНЫЙ РЕФАКТОРИНГ АРХИТЕКТУРЫ - Рефакторинг этапов 2.39: вынос bootstrap пользовательского хода (нормализация текста + append/persist user-item) из handleMessage в отдельный runtime-адаптер. Это безопасно и дополнительно разгружает assistantService.
This commit is contained in:
parent
0cc8f71068
commit
5520dbccbc
|
|
@ -1235,7 +1235,33 @@ Validation:
|
|||
- `assistantDeepTurnPackagingRuntimeAdapter.test.ts`
|
||||
- `assistantWave10SettlementCorrectiveRegression.test.ts`
|
||||
|
||||
Status: **In progress (Phase 2.1 + 2.2 + 2.3 + 2.4 + 2.5 + 2.6 + 2.7 + 2.8 + 2.9 + 2.10 + 2.11 + 2.12 + 2.13 + 2.14 + 2.15 + 2.16 + 2.17 + 2.18 + 2.19 + 2.20 + 2.21 + 2.22 + 2.23 + 2.24 + 2.25 + 2.26 + 2.27 + 2.28 + 2.29 + 2.30 + 2.31 + 2.32 + 2.33 + 2.34 + 2.35 + 2.36 + 2.37 + 2.38 completed)**
|
||||
Implemented in current pass (Phase 2.39):
|
||||
1. Extracted user-turn bootstrap sequence from `assistantService` into dedicated runtime adapter:
|
||||
- `assistantUserTurnBootstrapRuntimeAdapter.ts`
|
||||
- introduced:
|
||||
- `runAssistantUserTurnBootstrapRuntime(...)`
|
||||
2. Centralized user-turn bootstrap flow (behavior-preserving):
|
||||
- session ensure + user message normalization/repair;
|
||||
- user item append + session persistence;
|
||||
- runtime analysis context projection.
|
||||
3. Rewired `assistantService.handleMessage(...)` to consume bootstrap runtime output and preserve downstream `questionId` contract usage.
|
||||
4. Added focused unit tests:
|
||||
- `assistantUserTurnBootstrapRuntimeAdapter.test.ts`
|
||||
|
||||
Validation:
|
||||
1. `npm run build` passed.
|
||||
2. Targeted living/address/deep followup pack passed:
|
||||
- `assistantUserTurnBootstrapRuntimeAdapter.test.ts`
|
||||
- `assistantLivingChatLlmRuntimeAdapter.test.ts`
|
||||
- `assistantLivingChatHandlerRuntimeAdapter.test.ts`
|
||||
- `assistantLivingChatRuntimeAdapter.test.ts`
|
||||
- `assistantAddressRuntimeAdapter.test.ts`
|
||||
- `assistantAddressLaneResponseRuntimeAdapter.test.ts`
|
||||
- `assistantDeepTurnResponseRuntimeAdapter.test.ts`
|
||||
- `assistantDeepTurnPackagingRuntimeAdapter.test.ts`
|
||||
- `assistantWave10SettlementCorrectiveRegression.test.ts`
|
||||
|
||||
Status: **In progress (Phase 2.1 + 2.2 + 2.3 + 2.4 + 2.5 + 2.6 + 2.7 + 2.8 + 2.9 + 2.10 + 2.11 + 2.12 + 2.13 + 2.14 + 2.15 + 2.16 + 2.17 + 2.18 + 2.19 + 2.20 + 2.21 + 2.22 + 2.23 + 2.24 + 2.25 + 2.26 + 2.27 + 2.28 + 2.29 + 2.30 + 2.31 + 2.32 + 2.33 + 2.34 + 2.35 + 2.36 + 2.37 + 2.38 + 2.39 completed)**
|
||||
|
||||
## Stage 3 (P2): Hybrid Semantic Layer (LLM + Deterministic Guards)
|
||||
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ const assistantDeepTurnRetrievalRuntimeAdapter_1 = __importStar(require("./assis
|
|||
const assistantAddressRuntimeAdapter_1 = __importStar(require("./assistantAddressRuntimeAdapter"));
|
||||
const assistantLivingChatHandlerRuntimeAdapter_1 = __importStar(require("./assistantLivingChatHandlerRuntimeAdapter"));
|
||||
const assistantLivingChatLlmRuntimeAdapter_1 = __importStar(require("./assistantLivingChatLlmRuntimeAdapter"));
|
||||
const assistantUserTurnBootstrapRuntimeAdapter_1 = __importStar(require("./assistantUserTurnBootstrapRuntimeAdapter"));
|
||||
const assistantQueryPlanning_1 = __importStar(require("./assistantQueryPlanning"));
|
||||
const iconv_lite_1 = __importDefault(require("iconv-lite"));
|
||||
const DATA_SCOPE_CACHE_TTL_MS = 60_000;
|
||||
|
|
@ -4384,27 +4385,18 @@ class AssistantService {
|
|||
return this.sessions.getSession(sessionId);
|
||||
}
|
||||
async handleMessage(payload) {
|
||||
const session = this.sessions.ensureSession(payload.session_id);
|
||||
const sessionId = session.session_id;
|
||||
const userMessageRaw = String(payload.user_message ?? payload.message ?? "").trim();
|
||||
const repairedUserMessage = compactWhitespace(repairAddressMojibake(userMessageRaw));
|
||||
const userMessage = repairedUserMessage || userMessageRaw;
|
||||
const runtimeAnalysisContext = resolveRuntimeAnalysisContext(payload?.context);
|
||||
const userItem = {
|
||||
message_id: `msg-${(0, nanoid_1.nanoid)(10)}`,
|
||||
session_id: sessionId,
|
||||
role: "user",
|
||||
text: userMessage,
|
||||
reply_type: null,
|
||||
created_at: new Date().toISOString(),
|
||||
trace_id: null,
|
||||
debug: null
|
||||
};
|
||||
this.sessions.appendItem(sessionId, userItem);
|
||||
const sessionAfterUserAppend = this.sessions.getSession(sessionId);
|
||||
if (sessionAfterUserAppend) {
|
||||
this.sessionLogger.persistSession(sessionAfterUserAppend);
|
||||
}
|
||||
const { session, sessionId, userMessage, runtimeAnalysisContext, userItem } = (0, assistantUserTurnBootstrapRuntimeAdapter_1.runAssistantUserTurnBootstrapRuntime)({
|
||||
payload,
|
||||
ensureSession: (targetSessionId) => this.sessions.ensureSession(targetSessionId),
|
||||
appendItem: (targetSessionId, item) => this.sessions.appendItem(targetSessionId, item),
|
||||
getSession: (targetSessionId) => this.sessions.getSession(targetSessionId),
|
||||
persistSession: (sessionState) => this.sessionLogger.persistSession(sessionState),
|
||||
compactWhitespace,
|
||||
repairAddressMojibake,
|
||||
resolveRuntimeAnalysisContext,
|
||||
messageIdFactory: () => `msg-${(0, nanoid_1.nanoid)(10)}`,
|
||||
nowIso: () => new Date().toISOString()
|
||||
});
|
||||
const sessionOrganizationScope = resolveSessionOrganizationScopeContext(userMessage, session.items);
|
||||
const finalizeAddressLaneResponse = (addressLane, effectiveAddressUserMessage, carryoverMeta = null, llmPreDecomposeMeta = null) => {
|
||||
const runtime = (0, assistantAddressLaneResponseRuntimeAdapter_1.runAssistantAddressLaneResponseRuntime)({
|
||||
|
|
|
|||
34
llm_normalizer/backend/dist/services/assistantUserTurnBootstrapRuntimeAdapter.js
vendored
Normal file
34
llm_normalizer/backend/dist/services/assistantUserTurnBootstrapRuntimeAdapter.js
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.runAssistantUserTurnBootstrapRuntime = runAssistantUserTurnBootstrapRuntime;
|
||||
function runAssistantUserTurnBootstrapRuntime(input) {
|
||||
const session = input.ensureSession(String(input.payload.session_id ?? ""));
|
||||
const sessionId = session.session_id;
|
||||
const userMessageRaw = String(input.payload.user_message ?? input.payload.message ?? "").trim();
|
||||
const repairedUserMessage = input.compactWhitespace(input.repairAddressMojibake(userMessageRaw));
|
||||
const userMessage = repairedUserMessage || userMessageRaw;
|
||||
const runtimeAnalysisContext = input.resolveRuntimeAnalysisContext(input.payload?.context);
|
||||
const userItem = {
|
||||
message_id: (input.messageIdFactory ?? (() => "msg-unknown"))(),
|
||||
session_id: sessionId,
|
||||
role: "user",
|
||||
text: userMessage,
|
||||
reply_type: null,
|
||||
created_at: (input.nowIso ?? (() => new Date().toISOString()))(),
|
||||
trace_id: null,
|
||||
debug: null
|
||||
};
|
||||
input.appendItem(sessionId, userItem);
|
||||
const sessionAfterUserAppend = input.getSession(sessionId);
|
||||
if (sessionAfterUserAppend) {
|
||||
input.persistSession(sessionAfterUserAppend);
|
||||
}
|
||||
return {
|
||||
session,
|
||||
sessionId,
|
||||
userMessageRaw,
|
||||
userMessage,
|
||||
runtimeAnalysisContext,
|
||||
userItem
|
||||
};
|
||||
}
|
||||
|
|
@ -35,6 +35,7 @@ import * as assistantDeepTurnRetrievalRuntimeAdapter_1 from "./assistantDeepTurn
|
|||
import * as assistantAddressRuntimeAdapter_1 from "./assistantAddressRuntimeAdapter";
|
||||
import * as assistantLivingChatHandlerRuntimeAdapter_1 from "./assistantLivingChatHandlerRuntimeAdapter";
|
||||
import * as assistantLivingChatLlmRuntimeAdapter_1 from "./assistantLivingChatLlmRuntimeAdapter";
|
||||
import * as assistantUserTurnBootstrapRuntimeAdapter_1 from "./assistantUserTurnBootstrapRuntimeAdapter";
|
||||
import * as assistantQueryPlanning_1 from "./assistantQueryPlanning";
|
||||
import iconv from "iconv-lite";
|
||||
const DATA_SCOPE_CACHE_TTL_MS = 60_000;
|
||||
|
|
@ -4339,27 +4340,18 @@ export class AssistantService {
|
|||
return this.sessions.getSession(sessionId);
|
||||
}
|
||||
async handleMessage(payload) {
|
||||
const session = this.sessions.ensureSession(payload.session_id);
|
||||
const sessionId = session.session_id;
|
||||
const userMessageRaw = String(payload.user_message ?? payload.message ?? "").trim();
|
||||
const repairedUserMessage = compactWhitespace(repairAddressMojibake(userMessageRaw));
|
||||
const userMessage = repairedUserMessage || userMessageRaw;
|
||||
const runtimeAnalysisContext = resolveRuntimeAnalysisContext(payload?.context);
|
||||
const userItem = {
|
||||
message_id: `msg-${(0, nanoid_1.nanoid)(10)}`,
|
||||
session_id: sessionId,
|
||||
role: "user",
|
||||
text: userMessage,
|
||||
reply_type: null,
|
||||
created_at: new Date().toISOString(),
|
||||
trace_id: null,
|
||||
debug: null
|
||||
};
|
||||
this.sessions.appendItem(sessionId, userItem);
|
||||
const sessionAfterUserAppend = this.sessions.getSession(sessionId);
|
||||
if (sessionAfterUserAppend) {
|
||||
this.sessionLogger.persistSession(sessionAfterUserAppend);
|
||||
}
|
||||
const { session, sessionId, userMessage, runtimeAnalysisContext, userItem } = (0, assistantUserTurnBootstrapRuntimeAdapter_1.runAssistantUserTurnBootstrapRuntime)({
|
||||
payload,
|
||||
ensureSession: (targetSessionId) => this.sessions.ensureSession(targetSessionId),
|
||||
appendItem: (targetSessionId, item) => this.sessions.appendItem(targetSessionId, item),
|
||||
getSession: (targetSessionId) => this.sessions.getSession(targetSessionId),
|
||||
persistSession: (sessionState) => this.sessionLogger.persistSession(sessionState),
|
||||
compactWhitespace,
|
||||
repairAddressMojibake,
|
||||
resolveRuntimeAnalysisContext,
|
||||
messageIdFactory: () => `msg-${(0, nanoid_1.nanoid)(10)}`,
|
||||
nowIso: () => new Date().toISOString()
|
||||
});
|
||||
const sessionOrganizationScope = resolveSessionOrganizationScopeContext(userMessage, session.items);
|
||||
const finalizeAddressLaneResponse = (addressLane, effectiveAddressUserMessage, carryoverMeta = null, llmPreDecomposeMeta = null) => {
|
||||
const runtime = (0, assistantAddressLaneResponseRuntimeAdapter_1.runAssistantAddressLaneResponseRuntime)({
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
import type { AssistantConversationItem, AssistantSessionState } from "../types/assistant";
|
||||
|
||||
export interface AssistantUserTurnBootstrapPayloadLike {
|
||||
session_id?: unknown;
|
||||
user_message?: unknown;
|
||||
message?: unknown;
|
||||
context?: unknown;
|
||||
}
|
||||
|
||||
export interface RunAssistantUserTurnBootstrapRuntimeInput {
|
||||
payload: AssistantUserTurnBootstrapPayloadLike;
|
||||
ensureSession: (sessionId: string) => AssistantSessionState;
|
||||
appendItem: (sessionId: string, item: AssistantConversationItem) => void;
|
||||
getSession: (sessionId: string) => AssistantSessionState | null;
|
||||
persistSession: (session: AssistantSessionState) => void;
|
||||
compactWhitespace: (value: unknown) => string;
|
||||
repairAddressMojibake: (value: unknown) => string;
|
||||
resolveRuntimeAnalysisContext: (value: unknown) => { as_of_date: string | null };
|
||||
messageIdFactory?: () => string;
|
||||
nowIso?: () => string;
|
||||
}
|
||||
|
||||
export interface RunAssistantUserTurnBootstrapRuntimeOutput {
|
||||
session: AssistantSessionState;
|
||||
sessionId: string;
|
||||
userMessageRaw: string;
|
||||
userMessage: string;
|
||||
runtimeAnalysisContext: { as_of_date: string | null };
|
||||
userItem: AssistantConversationItem;
|
||||
}
|
||||
|
||||
export function runAssistantUserTurnBootstrapRuntime(
|
||||
input: RunAssistantUserTurnBootstrapRuntimeInput
|
||||
): RunAssistantUserTurnBootstrapRuntimeOutput {
|
||||
const session = input.ensureSession(String(input.payload.session_id ?? ""));
|
||||
const sessionId = session.session_id;
|
||||
const userMessageRaw = String(input.payload.user_message ?? input.payload.message ?? "").trim();
|
||||
const repairedUserMessage = input.compactWhitespace(input.repairAddressMojibake(userMessageRaw));
|
||||
const userMessage = repairedUserMessage || userMessageRaw;
|
||||
const runtimeAnalysisContext = input.resolveRuntimeAnalysisContext(input.payload?.context);
|
||||
const userItem: AssistantConversationItem = {
|
||||
message_id: (input.messageIdFactory ?? (() => "msg-unknown"))(),
|
||||
session_id: sessionId,
|
||||
role: "user",
|
||||
text: userMessage,
|
||||
reply_type: null,
|
||||
created_at: (input.nowIso ?? (() => new Date().toISOString()))(),
|
||||
trace_id: null,
|
||||
debug: null
|
||||
};
|
||||
input.appendItem(sessionId, userItem);
|
||||
const sessionAfterUserAppend = input.getSession(sessionId);
|
||||
if (sessionAfterUserAppend) {
|
||||
input.persistSession(sessionAfterUserAppend);
|
||||
}
|
||||
return {
|
||||
session,
|
||||
sessionId,
|
||||
userMessageRaw,
|
||||
userMessage,
|
||||
runtimeAnalysisContext,
|
||||
userItem
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
import { describe, expect, it, vi } from "vitest";
|
||||
import { runAssistantUserTurnBootstrapRuntime } from "../src/services/assistantUserTurnBootstrapRuntimeAdapter";
|
||||
|
||||
describe("assistant user turn bootstrap runtime adapter", () => {
|
||||
it("normalizes user message, appends user turn and persists session", () => {
|
||||
const session = {
|
||||
session_id: "asst-1",
|
||||
items: []
|
||||
} as any;
|
||||
const appendItem = vi.fn();
|
||||
const getSession = vi.fn(() => session);
|
||||
const persistSession = vi.fn();
|
||||
|
||||
const output = runAssistantUserTurnBootstrapRuntime({
|
||||
payload: {
|
||||
session_id: "asst-1",
|
||||
user_message: " source "
|
||||
},
|
||||
ensureSession: () => session,
|
||||
appendItem,
|
||||
getSession,
|
||||
persistSession,
|
||||
compactWhitespace: (value: unknown) => String(value ?? "").replace(/\s+/g, " ").trim(),
|
||||
repairAddressMojibake: () => " fixed user text ",
|
||||
resolveRuntimeAnalysisContext: () => ({ as_of_date: "2020-07-31" }),
|
||||
messageIdFactory: () => "msg-fixed",
|
||||
nowIso: () => "2026-04-10T00:00:00.000Z"
|
||||
});
|
||||
|
||||
expect(output.session).toBe(session);
|
||||
expect(output.sessionId).toBe("asst-1");
|
||||
expect(output.userMessageRaw).toBe("source");
|
||||
expect(output.userMessage).toBe("fixed user text");
|
||||
expect(output.runtimeAnalysisContext).toEqual({ as_of_date: "2020-07-31" });
|
||||
expect(appendItem).toHaveBeenCalledWith(
|
||||
"asst-1",
|
||||
expect.objectContaining({
|
||||
message_id: "msg-fixed",
|
||||
role: "user",
|
||||
text: "fixed user text",
|
||||
created_at: "2026-04-10T00:00:00.000Z"
|
||||
})
|
||||
);
|
||||
expect(getSession).toHaveBeenCalledWith("asst-1");
|
||||
expect(persistSession).toHaveBeenCalledWith(session);
|
||||
});
|
||||
|
||||
it("falls back to raw message when repaired text is empty", () => {
|
||||
const session = {
|
||||
session_id: "asst-2",
|
||||
items: []
|
||||
} as any;
|
||||
|
||||
const output = runAssistantUserTurnBootstrapRuntime({
|
||||
payload: {
|
||||
session_id: "asst-2",
|
||||
message: " raw fallback "
|
||||
},
|
||||
ensureSession: () => session,
|
||||
appendItem: () => undefined,
|
||||
getSession: () => null,
|
||||
persistSession: () => undefined,
|
||||
compactWhitespace: () => "",
|
||||
repairAddressMojibake: () => "",
|
||||
resolveRuntimeAnalysisContext: () => ({ as_of_date: null })
|
||||
});
|
||||
|
||||
expect(output.userMessageRaw).toBe("raw fallback");
|
||||
expect(output.userMessage).toBe("raw fallback");
|
||||
expect(output.runtimeAnalysisContext).toEqual({ as_of_date: null });
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue