ГЛОБАЛЬНЫЙ РЕФАКТОРИНГ АРХИТЕКТУРЫ - Рефакторинг этапов 2.41 - вынос склейки вызова live-chat из handleMessage в отдельный runtime-адаптер и переподключение на новый runtime bridge
This commit is contained in:
parent
bac6ebe701
commit
fbf2d6a19a
|
|
@ -1288,7 +1288,35 @@ Validation:
|
||||||
- `assistantDeepTurnPackagingRuntimeAdapter.test.ts`
|
- `assistantDeepTurnPackagingRuntimeAdapter.test.ts`
|
||||||
- `assistantWave10SettlementCorrectiveRegression.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 + 2.40 completed)**
|
Implemented in current pass (Phase 2.41):
|
||||||
|
1. Extracted living-chat attempt bridge (`tryHandleLivingChat`) from `assistantService` into dedicated runtime adapter:
|
||||||
|
- `assistantLivingChatAttemptRuntimeAdapter.ts`
|
||||||
|
- introduced:
|
||||||
|
- `runAssistantLivingChatAttemptRuntime(...)`
|
||||||
|
2. Centralized living-chat attempt handoff logic (behavior-preserving):
|
||||||
|
- delegated handler invocation (`tryHandleAssistantLivingChatRuntime(...)`);
|
||||||
|
- delegated LLM call bridge (`runAssistantLivingChatLlmRuntime(...)`) behind unified `executeLlmChat` contract;
|
||||||
|
- preserved all guard, scope and session finalization hooks.
|
||||||
|
3. Rewired `assistantService` to consume living-chat attempt runtime adapter.
|
||||||
|
4. Added focused unit tests:
|
||||||
|
- `assistantLivingChatAttemptRuntimeAdapter.test.ts`
|
||||||
|
|
||||||
|
Validation:
|
||||||
|
1. `npm run build` passed.
|
||||||
|
2. Targeted living/address/deep followup pack passed:
|
||||||
|
- `assistantLivingChatAttemptRuntimeAdapter.test.ts`
|
||||||
|
- `assistantAddressLaneAttemptRuntimeAdapter.test.ts`
|
||||||
|
- `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 + 2.40 + 2.41 completed)**
|
||||||
|
|
||||||
## Stage 3 (P2): Hybrid Semantic Layer (LLM + Deterministic Guards)
|
## Stage 3 (P2): Hybrid Semantic Layer (LLM + Deterministic Guards)
|
||||||
|
|
||||||
|
|
|
||||||
62
llm_normalizer/backend/dist/services/assistantLivingChatAttemptRuntimeAdapter.js
vendored
Normal file
62
llm_normalizer/backend/dist/services/assistantLivingChatAttemptRuntimeAdapter.js
vendored
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.runAssistantLivingChatAttemptRuntime = runAssistantLivingChatAttemptRuntime;
|
||||||
|
const assistantLivingChatHandlerRuntimeAdapter_1 = require("./assistantLivingChatHandlerRuntimeAdapter");
|
||||||
|
const assistantLivingChatLlmRuntimeAdapter_1 = require("./assistantLivingChatLlmRuntimeAdapter");
|
||||||
|
function buildExecuteLlmChat(input, runLivingChatLlmSafe) {
|
||||||
|
return async () => runLivingChatLlmSafe({
|
||||||
|
userMessage: input.userMessage,
|
||||||
|
sessionItems: input.sessionItems,
|
||||||
|
payload: input.payload,
|
||||||
|
chatClient: input.chatClient,
|
||||||
|
loadAssistantCanonExcerpt: input.loadAssistantCanonExcerpt,
|
||||||
|
sanitizeOutgoingAssistantText: input.sanitizeOutgoingAssistantText,
|
||||||
|
defaultModel: input.defaultModel,
|
||||||
|
defaultBaseUrl: input.defaultBaseUrl,
|
||||||
|
defaultApiKey: input.defaultApiKey
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async function runAssistantLivingChatAttemptRuntime(input) {
|
||||||
|
const runLivingChatHandlerSafe = input.runLivingChatHandler ?? assistantLivingChatHandlerRuntimeAdapter_1.tryHandleAssistantLivingChatRuntime;
|
||||||
|
const runLivingChatLlmSafe = input.runLivingChatLlm ?? assistantLivingChatLlmRuntimeAdapter_1.runAssistantLivingChatLlmRuntime;
|
||||||
|
const executeLlmChat = buildExecuteLlmChat(input, runLivingChatLlmSafe);
|
||||||
|
return runLivingChatHandlerSafe({
|
||||||
|
sessionId: input.sessionId,
|
||||||
|
userMessage: input.userMessage,
|
||||||
|
sessionItems: input.sessionItems,
|
||||||
|
modeDecision: input.modeDecision,
|
||||||
|
sessionScope: input.sessionScope,
|
||||||
|
addressRuntimeMeta: input.addressRuntimeMeta,
|
||||||
|
traceIdFactory: input.traceIdFactory,
|
||||||
|
toNonEmptyString: input.toNonEmptyString,
|
||||||
|
mergeKnownOrganizations: input.mergeKnownOrganizations,
|
||||||
|
hasAssistantDataScopeMetaQuestionSignal: input.hasAssistantDataScopeMetaQuestionSignal,
|
||||||
|
shouldHandleAsAssistantCapabilityMetaQuery: input.shouldHandleAsAssistantCapabilityMetaQuery,
|
||||||
|
hasDestructiveDataActionSignal: input.hasDestructiveDataActionSignal,
|
||||||
|
hasDangerOrCoercionSignal: input.hasDangerOrCoercionSignal,
|
||||||
|
hasOperationalAdminActionRequestSignal: input.hasOperationalAdminActionRequestSignal,
|
||||||
|
hasOrganizationFactLookupSignal: input.hasOrganizationFactLookupSignal,
|
||||||
|
hasOrganizationFactFollowupSignal: input.hasOrganizationFactFollowupSignal,
|
||||||
|
shouldEmitOrganizationSelectionReply: input.shouldEmitOrganizationSelectionReply,
|
||||||
|
hasAssistantCapabilityQuestionSignal: input.hasAssistantCapabilityQuestionSignal,
|
||||||
|
resolveDataScopeProbe: input.resolveDataScopeProbe,
|
||||||
|
executeLlmChat,
|
||||||
|
applyScriptGuard: input.applyScriptGuard,
|
||||||
|
applyGroundingGuard: input.applyGroundingGuard,
|
||||||
|
buildAssistantSafetyRefusalReply: input.buildAssistantSafetyRefusalReply,
|
||||||
|
buildAssistantDataScopeContractReply: input.buildAssistantDataScopeContractReply,
|
||||||
|
buildAssistantOrganizationFactBoundaryReply: input.buildAssistantOrganizationFactBoundaryReply,
|
||||||
|
buildAssistantDataScopeSelectionReply: input.buildAssistantDataScopeSelectionReply,
|
||||||
|
buildAssistantOperationalBoundaryReply: input.buildAssistantOperationalBoundaryReply,
|
||||||
|
buildAssistantCapabilityContractReply: input.buildAssistantCapabilityContractReply,
|
||||||
|
appendItem: input.appendItem,
|
||||||
|
getSession: input.getSession,
|
||||||
|
persistSession: input.persistSession,
|
||||||
|
cloneConversation: input.cloneConversation,
|
||||||
|
logEvent: input.logEvent,
|
||||||
|
messageIdFactory: input.messageIdFactory,
|
||||||
|
nowIso: input.nowIso,
|
||||||
|
runLivingChatRuntime: input.runLivingChatRuntime,
|
||||||
|
finalizeLivingChatTurn: input.finalizeLivingChatTurn
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -80,8 +80,7 @@ const assistantDeepTurnResponseRuntimeAdapter_1 = __importStar(require("./assist
|
||||||
const assistantDeepTurnRetrievalRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnRetrievalRuntimeAdapter"));
|
const assistantDeepTurnRetrievalRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnRetrievalRuntimeAdapter"));
|
||||||
const assistantAddressRuntimeAdapter_1 = __importStar(require("./assistantAddressRuntimeAdapter"));
|
const assistantAddressRuntimeAdapter_1 = __importStar(require("./assistantAddressRuntimeAdapter"));
|
||||||
const assistantAddressLaneAttemptRuntimeAdapter_1 = __importStar(require("./assistantAddressLaneAttemptRuntimeAdapter"));
|
const assistantAddressLaneAttemptRuntimeAdapter_1 = __importStar(require("./assistantAddressLaneAttemptRuntimeAdapter"));
|
||||||
const assistantLivingChatHandlerRuntimeAdapter_1 = __importStar(require("./assistantLivingChatHandlerRuntimeAdapter"));
|
const assistantLivingChatAttemptRuntimeAdapter_1 = __importStar(require("./assistantLivingChatAttemptRuntimeAdapter"));
|
||||||
const assistantLivingChatLlmRuntimeAdapter_1 = __importStar(require("./assistantLivingChatLlmRuntimeAdapter"));
|
|
||||||
const assistantUserTurnBootstrapRuntimeAdapter_1 = __importStar(require("./assistantUserTurnBootstrapRuntimeAdapter"));
|
const assistantUserTurnBootstrapRuntimeAdapter_1 = __importStar(require("./assistantUserTurnBootstrapRuntimeAdapter"));
|
||||||
const assistantQueryPlanning_1 = __importStar(require("./assistantQueryPlanning"));
|
const assistantQueryPlanning_1 = __importStar(require("./assistantQueryPlanning"));
|
||||||
const iconv_lite_1 = __importDefault(require("iconv-lite"));
|
const iconv_lite_1 = __importDefault(require("iconv-lite"));
|
||||||
|
|
@ -4423,59 +4422,53 @@ class AssistantService {
|
||||||
});
|
});
|
||||||
return runtime.response;
|
return runtime.response;
|
||||||
};
|
};
|
||||||
const tryHandleLivingChat = async (modeDecision, addressRuntimeMeta = null) => {
|
const tryHandleLivingChat = async (modeDecision, addressRuntimeMeta = null) => (0, assistantLivingChatAttemptRuntimeAdapter_1.runAssistantLivingChatAttemptRuntime)({
|
||||||
return (0, assistantLivingChatHandlerRuntimeAdapter_1.tryHandleAssistantLivingChatRuntime)({
|
sessionId,
|
||||||
sessionId,
|
userMessage,
|
||||||
userMessage,
|
sessionItems: session.items,
|
||||||
sessionItems: session.items,
|
modeDecision,
|
||||||
modeDecision,
|
sessionScope: {
|
||||||
sessionScope: {
|
knownOrganizations: sessionOrganizationScope.knownOrganizations,
|
||||||
knownOrganizations: sessionOrganizationScope.knownOrganizations,
|
selectedOrganization: sessionOrganizationScope.selectedOrganization,
|
||||||
selectedOrganization: sessionOrganizationScope.selectedOrganization,
|
activeOrganization: sessionOrganizationScope.activeOrganization
|
||||||
activeOrganization: sessionOrganizationScope.activeOrganization
|
},
|
||||||
},
|
addressRuntimeMeta,
|
||||||
addressRuntimeMeta,
|
traceIdFactory: () => `chat-${(0, nanoid_1.nanoid)(10)}`,
|
||||||
traceIdFactory: () => `chat-${(0, nanoid_1.nanoid)(10)}`,
|
toNonEmptyString,
|
||||||
toNonEmptyString,
|
mergeKnownOrganizations,
|
||||||
mergeKnownOrganizations,
|
hasAssistantDataScopeMetaQuestionSignal,
|
||||||
hasAssistantDataScopeMetaQuestionSignal,
|
shouldHandleAsAssistantCapabilityMetaQuery,
|
||||||
shouldHandleAsAssistantCapabilityMetaQuery,
|
hasDestructiveDataActionSignal,
|
||||||
hasDestructiveDataActionSignal,
|
hasDangerOrCoercionSignal,
|
||||||
hasDangerOrCoercionSignal,
|
hasOperationalAdminActionRequestSignal,
|
||||||
hasOperationalAdminActionRequestSignal,
|
hasOrganizationFactLookupSignal,
|
||||||
hasOrganizationFactLookupSignal,
|
hasOrganizationFactFollowupSignal,
|
||||||
hasOrganizationFactFollowupSignal,
|
shouldEmitOrganizationSelectionReply,
|
||||||
shouldEmitOrganizationSelectionReply,
|
hasAssistantCapabilityQuestionSignal,
|
||||||
hasAssistantCapabilityQuestionSignal,
|
resolveDataScopeProbe: () => resolveAssistantDataScopeProbe(),
|
||||||
resolveDataScopeProbe: () => resolveAssistantDataScopeProbe(),
|
applyScriptGuard: (chatText, runtimeUserMessage) => applyLivingChatScriptGuard(chatText, runtimeUserMessage),
|
||||||
executeLlmChat: async () => (0, assistantLivingChatLlmRuntimeAdapter_1.runAssistantLivingChatLlmRuntime)({
|
applyGroundingGuard: (guardInput) => applyLivingChatGroundingGuard(guardInput),
|
||||||
userMessage,
|
buildAssistantSafetyRefusalReply,
|
||||||
sessionItems: session.items,
|
buildAssistantDataScopeContractReply,
|
||||||
payload,
|
buildAssistantOrganizationFactBoundaryReply,
|
||||||
chatClient: this.chatClient,
|
buildAssistantDataScopeSelectionReply,
|
||||||
loadAssistantCanonExcerpt: assistantCanon_1.loadAssistantCanonExcerpt,
|
buildAssistantOperationalBoundaryReply,
|
||||||
sanitizeOutgoingAssistantText,
|
buildAssistantCapabilityContractReply,
|
||||||
defaultModel: config_1.DEFAULT_MODEL,
|
appendItem: (targetSessionId, item) => this.sessions.appendItem(targetSessionId, item),
|
||||||
defaultBaseUrl: config_1.DEFAULT_OPENAI_BASE_URL,
|
getSession: (targetSessionId) => this.sessions.getSession(targetSessionId),
|
||||||
defaultApiKey: process.env.OPENAI_API_KEY ?? ""
|
persistSession: (sessionState) => this.sessionLogger.persistSession(sessionState),
|
||||||
}),
|
cloneConversation: (items) => cloneItems(items),
|
||||||
applyScriptGuard: (chatText, runtimeUserMessage) => applyLivingChatScriptGuard(chatText, runtimeUserMessage),
|
logEvent: (payload) => (0, log_1.logJson)(payload),
|
||||||
applyGroundingGuard: (guardInput) => applyLivingChatGroundingGuard(guardInput),
|
messageIdFactory: () => `msg-${(0, nanoid_1.nanoid)(10)}`,
|
||||||
buildAssistantSafetyRefusalReply,
|
nowIso: () => new Date().toISOString(),
|
||||||
buildAssistantDataScopeContractReply,
|
payload,
|
||||||
buildAssistantOrganizationFactBoundaryReply,
|
chatClient: this.chatClient,
|
||||||
buildAssistantDataScopeSelectionReply,
|
loadAssistantCanonExcerpt: assistantCanon_1.loadAssistantCanonExcerpt,
|
||||||
buildAssistantOperationalBoundaryReply,
|
sanitizeOutgoingAssistantText,
|
||||||
buildAssistantCapabilityContractReply,
|
defaultModel: config_1.DEFAULT_MODEL,
|
||||||
appendItem: (targetSessionId, item) => this.sessions.appendItem(targetSessionId, item),
|
defaultBaseUrl: config_1.DEFAULT_OPENAI_BASE_URL,
|
||||||
getSession: (targetSessionId) => this.sessions.getSession(targetSessionId),
|
defaultApiKey: process.env.OPENAI_API_KEY ?? ""
|
||||||
persistSession: (sessionState) => this.sessionLogger.persistSession(sessionState),
|
});
|
||||||
cloneConversation: (items) => cloneItems(items),
|
|
||||||
logEvent: (payload) => (0, log_1.logJson)(payload),
|
|
||||||
messageIdFactory: () => `msg-${(0, nanoid_1.nanoid)(10)}`,
|
|
||||||
nowIso: () => new Date().toISOString()
|
|
||||||
});
|
|
||||||
};
|
|
||||||
let addressRuntimeMetaForDeep = null;
|
let addressRuntimeMetaForDeep = null;
|
||||||
const runAddressLaneAttempt = async (messageUsed, carryMeta, analysisDateHint) => (0, assistantAddressLaneAttemptRuntimeAdapter_1.runAssistantAddressLaneAttemptRuntime)({
|
const runAddressLaneAttempt = async (messageUsed, carryMeta, analysisDateHint) => (0, assistantAddressLaneAttemptRuntimeAdapter_1.runAssistantAddressLaneAttemptRuntime)({
|
||||||
messageUsed,
|
messageUsed,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
import {
|
||||||
|
tryHandleAssistantLivingChatRuntime,
|
||||||
|
type TryHandleAssistantLivingChatRuntimeInput
|
||||||
|
} from "./assistantLivingChatHandlerRuntimeAdapter";
|
||||||
|
import {
|
||||||
|
runAssistantLivingChatLlmRuntime,
|
||||||
|
type RunAssistantLivingChatLlmRuntimeInput
|
||||||
|
} from "./assistantLivingChatLlmRuntimeAdapter";
|
||||||
|
|
||||||
|
type AssistantLivingChatHandlerInput<ResponseType> = TryHandleAssistantLivingChatRuntimeInput<ResponseType>;
|
||||||
|
type AssistantLivingChatLlmInput = RunAssistantLivingChatLlmRuntimeInput;
|
||||||
|
|
||||||
|
export interface RunAssistantLivingChatAttemptRuntimeInput<ResponseType = unknown>
|
||||||
|
extends Omit<AssistantLivingChatHandlerInput<ResponseType>, "executeLlmChat"> {
|
||||||
|
payload: AssistantLivingChatLlmInput["payload"];
|
||||||
|
chatClient: AssistantLivingChatLlmInput["chatClient"];
|
||||||
|
loadAssistantCanonExcerpt: AssistantLivingChatLlmInput["loadAssistantCanonExcerpt"];
|
||||||
|
sanitizeOutgoingAssistantText: AssistantLivingChatLlmInput["sanitizeOutgoingAssistantText"];
|
||||||
|
defaultModel: AssistantLivingChatLlmInput["defaultModel"];
|
||||||
|
defaultBaseUrl: AssistantLivingChatLlmInput["defaultBaseUrl"];
|
||||||
|
defaultApiKey?: AssistantLivingChatLlmInput["defaultApiKey"];
|
||||||
|
runLivingChatHandler?: (
|
||||||
|
input: AssistantLivingChatHandlerInput<ResponseType>
|
||||||
|
) => Promise<ResponseType | null>;
|
||||||
|
runLivingChatLlm?: (
|
||||||
|
input: AssistantLivingChatLlmInput
|
||||||
|
) => Promise<string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildExecuteLlmChat<ResponseType>(
|
||||||
|
input: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>,
|
||||||
|
runLivingChatLlmSafe: (input: AssistantLivingChatLlmInput) => Promise<string>
|
||||||
|
): AssistantLivingChatHandlerInput<ResponseType>["executeLlmChat"] {
|
||||||
|
return async () =>
|
||||||
|
runLivingChatLlmSafe({
|
||||||
|
userMessage: input.userMessage,
|
||||||
|
sessionItems: input.sessionItems,
|
||||||
|
payload: input.payload,
|
||||||
|
chatClient: input.chatClient,
|
||||||
|
loadAssistantCanonExcerpt: input.loadAssistantCanonExcerpt,
|
||||||
|
sanitizeOutgoingAssistantText: input.sanitizeOutgoingAssistantText,
|
||||||
|
defaultModel: input.defaultModel,
|
||||||
|
defaultBaseUrl: input.defaultBaseUrl,
|
||||||
|
defaultApiKey: input.defaultApiKey
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function runAssistantLivingChatAttemptRuntime<ResponseType = unknown>(
|
||||||
|
input: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>
|
||||||
|
): Promise<ResponseType | null> {
|
||||||
|
const runLivingChatHandlerSafe = input.runLivingChatHandler ?? tryHandleAssistantLivingChatRuntime;
|
||||||
|
const runLivingChatLlmSafe = input.runLivingChatLlm ?? runAssistantLivingChatLlmRuntime;
|
||||||
|
const executeLlmChat = buildExecuteLlmChat(input, runLivingChatLlmSafe);
|
||||||
|
return runLivingChatHandlerSafe({
|
||||||
|
sessionId: input.sessionId,
|
||||||
|
userMessage: input.userMessage,
|
||||||
|
sessionItems: input.sessionItems,
|
||||||
|
modeDecision: input.modeDecision,
|
||||||
|
sessionScope: input.sessionScope,
|
||||||
|
addressRuntimeMeta: input.addressRuntimeMeta,
|
||||||
|
traceIdFactory: input.traceIdFactory,
|
||||||
|
toNonEmptyString: input.toNonEmptyString,
|
||||||
|
mergeKnownOrganizations: input.mergeKnownOrganizations,
|
||||||
|
hasAssistantDataScopeMetaQuestionSignal: input.hasAssistantDataScopeMetaQuestionSignal,
|
||||||
|
shouldHandleAsAssistantCapabilityMetaQuery: input.shouldHandleAsAssistantCapabilityMetaQuery,
|
||||||
|
hasDestructiveDataActionSignal: input.hasDestructiveDataActionSignal,
|
||||||
|
hasDangerOrCoercionSignal: input.hasDangerOrCoercionSignal,
|
||||||
|
hasOperationalAdminActionRequestSignal: input.hasOperationalAdminActionRequestSignal,
|
||||||
|
hasOrganizationFactLookupSignal: input.hasOrganizationFactLookupSignal,
|
||||||
|
hasOrganizationFactFollowupSignal: input.hasOrganizationFactFollowupSignal,
|
||||||
|
shouldEmitOrganizationSelectionReply: input.shouldEmitOrganizationSelectionReply,
|
||||||
|
hasAssistantCapabilityQuestionSignal: input.hasAssistantCapabilityQuestionSignal,
|
||||||
|
resolveDataScopeProbe: input.resolveDataScopeProbe,
|
||||||
|
executeLlmChat,
|
||||||
|
applyScriptGuard: input.applyScriptGuard,
|
||||||
|
applyGroundingGuard: input.applyGroundingGuard,
|
||||||
|
buildAssistantSafetyRefusalReply: input.buildAssistantSafetyRefusalReply,
|
||||||
|
buildAssistantDataScopeContractReply: input.buildAssistantDataScopeContractReply,
|
||||||
|
buildAssistantOrganizationFactBoundaryReply: input.buildAssistantOrganizationFactBoundaryReply,
|
||||||
|
buildAssistantDataScopeSelectionReply: input.buildAssistantDataScopeSelectionReply,
|
||||||
|
buildAssistantOperationalBoundaryReply: input.buildAssistantOperationalBoundaryReply,
|
||||||
|
buildAssistantCapabilityContractReply: input.buildAssistantCapabilityContractReply,
|
||||||
|
appendItem: input.appendItem,
|
||||||
|
getSession: input.getSession,
|
||||||
|
persistSession: input.persistSession,
|
||||||
|
cloneConversation: input.cloneConversation,
|
||||||
|
logEvent: input.logEvent,
|
||||||
|
messageIdFactory: input.messageIdFactory,
|
||||||
|
nowIso: input.nowIso,
|
||||||
|
runLivingChatRuntime: input.runLivingChatRuntime,
|
||||||
|
finalizeLivingChatTurn: input.finalizeLivingChatTurn
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -34,8 +34,7 @@ import * as assistantDeepTurnResponseRuntimeAdapter_1 from "./assistantDeepTurnR
|
||||||
import * as assistantDeepTurnRetrievalRuntimeAdapter_1 from "./assistantDeepTurnRetrievalRuntimeAdapter";
|
import * as assistantDeepTurnRetrievalRuntimeAdapter_1 from "./assistantDeepTurnRetrievalRuntimeAdapter";
|
||||||
import * as assistantAddressRuntimeAdapter_1 from "./assistantAddressRuntimeAdapter";
|
import * as assistantAddressRuntimeAdapter_1 from "./assistantAddressRuntimeAdapter";
|
||||||
import * as assistantAddressLaneAttemptRuntimeAdapter_1 from "./assistantAddressLaneAttemptRuntimeAdapter";
|
import * as assistantAddressLaneAttemptRuntimeAdapter_1 from "./assistantAddressLaneAttemptRuntimeAdapter";
|
||||||
import * as assistantLivingChatHandlerRuntimeAdapter_1 from "./assistantLivingChatHandlerRuntimeAdapter";
|
import * as assistantLivingChatAttemptRuntimeAdapter_1 from "./assistantLivingChatAttemptRuntimeAdapter";
|
||||||
import * as assistantLivingChatLlmRuntimeAdapter_1 from "./assistantLivingChatLlmRuntimeAdapter";
|
|
||||||
import * as assistantUserTurnBootstrapRuntimeAdapter_1 from "./assistantUserTurnBootstrapRuntimeAdapter";
|
import * as assistantUserTurnBootstrapRuntimeAdapter_1 from "./assistantUserTurnBootstrapRuntimeAdapter";
|
||||||
import * as assistantQueryPlanning_1 from "./assistantQueryPlanning";
|
import * as assistantQueryPlanning_1 from "./assistantQueryPlanning";
|
||||||
import iconv from "iconv-lite";
|
import iconv from "iconv-lite";
|
||||||
|
|
@ -4378,59 +4377,53 @@ export class AssistantService {
|
||||||
});
|
});
|
||||||
return runtime.response;
|
return runtime.response;
|
||||||
};
|
};
|
||||||
const tryHandleLivingChat = async (modeDecision, addressRuntimeMeta = null) => {
|
const tryHandleLivingChat = async (modeDecision, addressRuntimeMeta = null) => (0, assistantLivingChatAttemptRuntimeAdapter_1.runAssistantLivingChatAttemptRuntime)({
|
||||||
return (0, assistantLivingChatHandlerRuntimeAdapter_1.tryHandleAssistantLivingChatRuntime)({
|
sessionId,
|
||||||
sessionId,
|
userMessage,
|
||||||
userMessage,
|
sessionItems: session.items,
|
||||||
sessionItems: session.items,
|
modeDecision,
|
||||||
modeDecision,
|
sessionScope: {
|
||||||
sessionScope: {
|
knownOrganizations: sessionOrganizationScope.knownOrganizations,
|
||||||
knownOrganizations: sessionOrganizationScope.knownOrganizations,
|
selectedOrganization: sessionOrganizationScope.selectedOrganization,
|
||||||
selectedOrganization: sessionOrganizationScope.selectedOrganization,
|
activeOrganization: sessionOrganizationScope.activeOrganization
|
||||||
activeOrganization: sessionOrganizationScope.activeOrganization
|
},
|
||||||
},
|
addressRuntimeMeta,
|
||||||
addressRuntimeMeta,
|
traceIdFactory: () => `chat-${(0, nanoid_1.nanoid)(10)}`,
|
||||||
traceIdFactory: () => `chat-${(0, nanoid_1.nanoid)(10)}`,
|
toNonEmptyString,
|
||||||
toNonEmptyString,
|
mergeKnownOrganizations,
|
||||||
mergeKnownOrganizations,
|
hasAssistantDataScopeMetaQuestionSignal,
|
||||||
hasAssistantDataScopeMetaQuestionSignal,
|
shouldHandleAsAssistantCapabilityMetaQuery,
|
||||||
shouldHandleAsAssistantCapabilityMetaQuery,
|
hasDestructiveDataActionSignal,
|
||||||
hasDestructiveDataActionSignal,
|
hasDangerOrCoercionSignal,
|
||||||
hasDangerOrCoercionSignal,
|
hasOperationalAdminActionRequestSignal,
|
||||||
hasOperationalAdminActionRequestSignal,
|
hasOrganizationFactLookupSignal,
|
||||||
hasOrganizationFactLookupSignal,
|
hasOrganizationFactFollowupSignal,
|
||||||
hasOrganizationFactFollowupSignal,
|
shouldEmitOrganizationSelectionReply,
|
||||||
shouldEmitOrganizationSelectionReply,
|
hasAssistantCapabilityQuestionSignal,
|
||||||
hasAssistantCapabilityQuestionSignal,
|
resolveDataScopeProbe: () => resolveAssistantDataScopeProbe(),
|
||||||
resolveDataScopeProbe: () => resolveAssistantDataScopeProbe(),
|
applyScriptGuard: (chatText, runtimeUserMessage) => applyLivingChatScriptGuard(chatText, runtimeUserMessage),
|
||||||
executeLlmChat: async () => (0, assistantLivingChatLlmRuntimeAdapter_1.runAssistantLivingChatLlmRuntime)({
|
applyGroundingGuard: (guardInput) => applyLivingChatGroundingGuard(guardInput),
|
||||||
userMessage,
|
buildAssistantSafetyRefusalReply,
|
||||||
sessionItems: session.items,
|
buildAssistantDataScopeContractReply,
|
||||||
payload,
|
buildAssistantOrganizationFactBoundaryReply,
|
||||||
chatClient: this.chatClient,
|
buildAssistantDataScopeSelectionReply,
|
||||||
loadAssistantCanonExcerpt: assistantCanon_1.loadAssistantCanonExcerpt,
|
buildAssistantOperationalBoundaryReply,
|
||||||
sanitizeOutgoingAssistantText,
|
buildAssistantCapabilityContractReply,
|
||||||
defaultModel: config_1.DEFAULT_MODEL,
|
appendItem: (targetSessionId, item) => this.sessions.appendItem(targetSessionId, item),
|
||||||
defaultBaseUrl: config_1.DEFAULT_OPENAI_BASE_URL,
|
getSession: (targetSessionId) => this.sessions.getSession(targetSessionId),
|
||||||
defaultApiKey: process.env.OPENAI_API_KEY ?? ""
|
persistSession: (sessionState) => this.sessionLogger.persistSession(sessionState),
|
||||||
}),
|
cloneConversation: (items) => cloneItems(items),
|
||||||
applyScriptGuard: (chatText, runtimeUserMessage) => applyLivingChatScriptGuard(chatText, runtimeUserMessage),
|
logEvent: (payload) => (0, log_1.logJson)(payload),
|
||||||
applyGroundingGuard: (guardInput) => applyLivingChatGroundingGuard(guardInput),
|
messageIdFactory: () => `msg-${(0, nanoid_1.nanoid)(10)}`,
|
||||||
buildAssistantSafetyRefusalReply,
|
nowIso: () => new Date().toISOString(),
|
||||||
buildAssistantDataScopeContractReply,
|
payload,
|
||||||
buildAssistantOrganizationFactBoundaryReply,
|
chatClient: this.chatClient,
|
||||||
buildAssistantDataScopeSelectionReply,
|
loadAssistantCanonExcerpt: assistantCanon_1.loadAssistantCanonExcerpt,
|
||||||
buildAssistantOperationalBoundaryReply,
|
sanitizeOutgoingAssistantText,
|
||||||
buildAssistantCapabilityContractReply,
|
defaultModel: config_1.DEFAULT_MODEL,
|
||||||
appendItem: (targetSessionId, item) => this.sessions.appendItem(targetSessionId, item),
|
defaultBaseUrl: config_1.DEFAULT_OPENAI_BASE_URL,
|
||||||
getSession: (targetSessionId) => this.sessions.getSession(targetSessionId),
|
defaultApiKey: process.env.OPENAI_API_KEY ?? ""
|
||||||
persistSession: (sessionState) => this.sessionLogger.persistSession(sessionState),
|
});
|
||||||
cloneConversation: (items) => cloneItems(items),
|
|
||||||
logEvent: (payload) => (0, log_1.logJson)(payload),
|
|
||||||
messageIdFactory: () => `msg-${(0, nanoid_1.nanoid)(10)}`,
|
|
||||||
nowIso: () => new Date().toISOString()
|
|
||||||
});
|
|
||||||
};
|
|
||||||
let addressRuntimeMetaForDeep = null;
|
let addressRuntimeMetaForDeep = null;
|
||||||
const runAddressLaneAttempt = async (messageUsed, carryMeta, analysisDateHint) => (0, assistantAddressLaneAttemptRuntimeAdapter_1.runAssistantAddressLaneAttemptRuntime)({
|
const runAddressLaneAttempt = async (messageUsed, carryMeta, analysisDateHint) => (0, assistantAddressLaneAttemptRuntimeAdapter_1.runAssistantAddressLaneAttemptRuntime)({
|
||||||
messageUsed,
|
messageUsed,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,121 @@
|
||||||
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
import {
|
||||||
|
runAssistantLivingChatAttemptRuntime,
|
||||||
|
type RunAssistantLivingChatAttemptRuntimeInput
|
||||||
|
} from "../src/services/assistantLivingChatAttemptRuntimeAdapter";
|
||||||
|
|
||||||
|
type TestResponse = { ok: boolean; llm: string };
|
||||||
|
|
||||||
|
function buildInput(
|
||||||
|
overrides: Partial<RunAssistantLivingChatAttemptRuntimeInput<TestResponse>> = {}
|
||||||
|
): RunAssistantLivingChatAttemptRuntimeInput<TestResponse> {
|
||||||
|
return {
|
||||||
|
sessionId: "asst-1",
|
||||||
|
userMessage: "контрагенты со сверкой",
|
||||||
|
sessionItems: [{ role: "user", text: "контекст" }],
|
||||||
|
modeDecision: { mode: "chat", reason: "living_chat_signal_detected" },
|
||||||
|
sessionScope: {
|
||||||
|
knownOrganizations: [],
|
||||||
|
selectedOrganization: null,
|
||||||
|
activeOrganization: null
|
||||||
|
},
|
||||||
|
addressRuntimeMeta: null,
|
||||||
|
traceIdFactory: () => "chat-trace-1",
|
||||||
|
toNonEmptyString: (value: unknown) => (typeof value === "string" && value.trim() ? value.trim() : null),
|
||||||
|
mergeKnownOrganizations: (values: string[]) => values,
|
||||||
|
hasAssistantDataScopeMetaQuestionSignal: () => false,
|
||||||
|
shouldHandleAsAssistantCapabilityMetaQuery: () => false,
|
||||||
|
hasDestructiveDataActionSignal: () => false,
|
||||||
|
hasDangerOrCoercionSignal: () => false,
|
||||||
|
hasOperationalAdminActionRequestSignal: () => false,
|
||||||
|
hasOrganizationFactLookupSignal: () => false,
|
||||||
|
hasOrganizationFactFollowupSignal: () => false,
|
||||||
|
shouldEmitOrganizationSelectionReply: () => false,
|
||||||
|
hasAssistantCapabilityQuestionSignal: () => false,
|
||||||
|
resolveDataScopeProbe: async () => null,
|
||||||
|
applyScriptGuard: (text: string) => ({ text, applied: false, reason: null }),
|
||||||
|
applyGroundingGuard: (payload: { chatText: string }) => ({
|
||||||
|
text: payload.chatText,
|
||||||
|
applied: false,
|
||||||
|
reason: null
|
||||||
|
}),
|
||||||
|
buildAssistantSafetyRefusalReply: () => "safety",
|
||||||
|
buildAssistantDataScopeContractReply: () => "scope",
|
||||||
|
buildAssistantOrganizationFactBoundaryReply: () => "boundary",
|
||||||
|
buildAssistantDataScopeSelectionReply: () => "selection",
|
||||||
|
buildAssistantOperationalBoundaryReply: () => "operational",
|
||||||
|
buildAssistantCapabilityContractReply: () => "capability",
|
||||||
|
appendItem: () => {},
|
||||||
|
getSession: () => ({
|
||||||
|
session_id: "asst-1",
|
||||||
|
updated_at: "",
|
||||||
|
items: [],
|
||||||
|
investigation_state: null
|
||||||
|
}),
|
||||||
|
persistSession: () => {},
|
||||||
|
cloneConversation: (items: unknown[]) => items,
|
||||||
|
logEvent: () => {},
|
||||||
|
messageIdFactory: () => "msg-1",
|
||||||
|
nowIso: () => "2026-04-10T00:00:00.000Z",
|
||||||
|
payload: {
|
||||||
|
llmProvider: "openai",
|
||||||
|
apiKey: "key",
|
||||||
|
model: "gpt-5",
|
||||||
|
baseUrl: "http://localhost",
|
||||||
|
temperature: 0.2,
|
||||||
|
maxOutputTokens: 300
|
||||||
|
},
|
||||||
|
chatClient: {
|
||||||
|
chat: async () => ({ outputText: "chat-output" })
|
||||||
|
},
|
||||||
|
loadAssistantCanonExcerpt: () => "canon",
|
||||||
|
sanitizeOutgoingAssistantText: (value: unknown, fallback = "fallback") => {
|
||||||
|
const text = String(value ?? "").trim();
|
||||||
|
return text || fallback;
|
||||||
|
},
|
||||||
|
defaultModel: "gpt-5",
|
||||||
|
defaultBaseUrl: "http://localhost",
|
||||||
|
defaultApiKey: "key",
|
||||||
|
...overrides
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("assistant living chat attempt runtime adapter", () => {
|
||||||
|
it("wires llm runtime output into living handler execute callback", async () => {
|
||||||
|
const runLivingChatLlm = vi.fn(async () => "llm-answer");
|
||||||
|
const runLivingChatHandler = vi.fn(async (input) => {
|
||||||
|
const llm = await input.executeLlmChat();
|
||||||
|
return { ok: true, llm };
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await runAssistantLivingChatAttemptRuntime(
|
||||||
|
buildInput({
|
||||||
|
runLivingChatHandler,
|
||||||
|
runLivingChatLlm
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(runLivingChatHandler).toHaveBeenCalledTimes(1);
|
||||||
|
expect(runLivingChatLlm).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
userMessage: "контрагенты со сверкой"
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(response).toEqual({ ok: true, llm: "llm-answer" });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns null when delegated handler returns null", async () => {
|
||||||
|
const runLivingChatLlm = vi.fn(async () => "llm-answer");
|
||||||
|
const runLivingChatHandler = vi.fn(async () => null);
|
||||||
|
|
||||||
|
const response = await runAssistantLivingChatAttemptRuntime(
|
||||||
|
buildInput({
|
||||||
|
runLivingChatHandler,
|
||||||
|
runLivingChatLlm
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(response).toBeNull();
|
||||||
|
expect(runLivingChatLlm).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue