From 875f3bfbcdc7f5ba33546266201607063f73f5f2 Mon Sep 17 00:00:00 2001 From: dctouch Date: Fri, 10 Apr 2026 23:27:37 +0300 Subject: [PATCH] =?UTF-8?q?=D0=93=D0=9B=D0=9E=D0=91=D0=90=D0=9B=D0=AC?= =?UTF-8?q?=D0=9D=D0=AB=D0=99=20=D0=A0=D0=95=D0=A4=D0=90=D0=9A=D0=A2=D0=9E?= =?UTF-8?q?=D0=A0=D0=98=D0=9D=D0=93=20=D0=90=D0=A0=D0=A5=D0=98=D0=A2=D0=95?= =?UTF-8?q?=D0=9A=D0=A2=D0=A3=D0=A0=D0=AB=20-=20=D0=A0=D0=B5=D1=84=D0=B0?= =?UTF-8?q?=D0=BA=D1=82=D0=BE=D1=80=D0=B8=D0=BD=D0=B3=20=D1=8D=D1=82=D0=B0?= =?UTF-8?q?=D0=BF=D0=BE=D0=B2=202.42:=20=D0=B2=D1=8B=D0=BD=D0=BE=D1=81=20f?= =?UTF-8?q?inalizeAddressLaneResponse=20=D0=B8=D0=B7=20assistantService=20?= =?UTF-8?q?=D0=B2=20=D0=BE=D1=82=D0=B4=D0=B5=D0=BB=D1=8C=D0=BD=D1=8B=D0=B9?= =?UTF-8?q?=20attempt-bridge=20(=D0=BA=D0=B0=D0=BA=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?living=20chat),=20=20=D0=B4=D0=BB=D1=8F=20=D1=83=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D1=8C=D1=88=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BC=D0=BE=D0=BD?= =?UTF-8?q?=D0=BE=D0=BB=D0=B8=D1=82=D0=B0=20=D0=B1=D0=B5=D0=B7=20=D0=B8?= =?UTF-8?q?=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=B2=D0=B5=D0=B4=D0=B5=D0=BD=D0=B8=D1=8F.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/TECH/1CLLMARCH-FACT.md | 30 ++++++- ...ddressLaneResponseAttemptRuntimeAdapter.js | 29 +++++++ .../backend/dist/services/assistantService.js | 47 +++++------ ...ddressLaneResponseAttemptRuntimeAdapter.ts | 45 ++++++++++ .../backend/src/services/assistantService.ts | 7 +- ...sLaneResponseAttemptRuntimeAdapter.test.ts | 84 +++++++++++++++++++ 6 files changed, 211 insertions(+), 31 deletions(-) create mode 100644 llm_normalizer/backend/dist/services/assistantAddressLaneResponseAttemptRuntimeAdapter.js create mode 100644 llm_normalizer/backend/src/services/assistantAddressLaneResponseAttemptRuntimeAdapter.ts create mode 100644 llm_normalizer/backend/tests/assistantAddressLaneResponseAttemptRuntimeAdapter.test.ts diff --git a/docs/TECH/1CLLMARCH-FACT.md b/docs/TECH/1CLLMARCH-FACT.md index c591d55..fed2be7 100644 --- a/docs/TECH/1CLLMARCH-FACT.md +++ b/docs/TECH/1CLLMARCH-FACT.md @@ -1316,7 +1316,35 @@ 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 + 2.39 + 2.40 + 2.41 completed)** +Implemented in current pass (Phase 2.42): +1. Extracted address-lane response attempt bridge (`finalizeAddressLaneResponse`) from `assistantService` into dedicated runtime adapter: + - `assistantAddressLaneResponseAttemptRuntimeAdapter.ts` + - introduced: + - `runAssistantAddressLaneResponseAttemptRuntime(...)` +2. Centralized address-lane response handoff logic (behavior-preserving): + - delegated response runtime invocation (`runAssistantAddressLaneResponseRuntime(...)`); + - preserved followup-offer/debug payload and session finalization contract wiring. +3. Rewired `assistantService` to consume address-lane response attempt runtime adapter. +4. Added focused unit tests: + - `assistantAddressLaneResponseAttemptRuntimeAdapter.test.ts` + +Validation: +1. `npm run build` passed. +2. Targeted living/address/deep followup pack passed: + - `assistantAddressLaneResponseAttemptRuntimeAdapter.test.ts` + - `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 + 2.42 completed)** ## Stage 3 (P2): Hybrid Semantic Layer (LLM + Deterministic Guards) diff --git a/llm_normalizer/backend/dist/services/assistantAddressLaneResponseAttemptRuntimeAdapter.js b/llm_normalizer/backend/dist/services/assistantAddressLaneResponseAttemptRuntimeAdapter.js new file mode 100644 index 0000000..0aa803c --- /dev/null +++ b/llm_normalizer/backend/dist/services/assistantAddressLaneResponseAttemptRuntimeAdapter.js @@ -0,0 +1,29 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.runAssistantAddressLaneResponseAttemptRuntime = runAssistantAddressLaneResponseAttemptRuntime; +const assistantAddressLaneResponseRuntimeAdapter_1 = require("./assistantAddressLaneResponseRuntimeAdapter"); +function runAssistantAddressLaneResponseAttemptRuntime(input) { + const runAddressLaneResponseRuntimeSafe = input.runAddressLaneResponseRuntime ?? assistantAddressLaneResponseRuntimeAdapter_1.runAssistantAddressLaneResponseRuntime; + const runtime = runAddressLaneResponseRuntimeSafe({ + sessionId: input.sessionId, + userMessage: input.userMessage, + effectiveAddressUserMessage: input.effectiveAddressUserMessage, + addressLane: input.addressLane, + carryoverMeta: input.carryoverMeta, + llmPreDecomposeMeta: input.llmPreDecomposeMeta, + knownOrganizations: input.knownOrganizations, + activeOrganization: input.activeOrganization, + sanitizeOutgoingAssistantText: input.sanitizeOutgoingAssistantText, + buildAddressDebugPayload: input.buildAddressDebugPayload, + buildAddressFollowupOffer: input.buildAddressFollowupOffer, + mergeKnownOrganizations: input.mergeKnownOrganizations, + toNonEmptyString: input.toNonEmptyString, + appendItem: input.appendItem, + getSession: input.getSession, + persistSession: input.persistSession, + cloneConversation: input.cloneConversation, + logEvent: input.logEvent, + messageIdFactory: input.messageIdFactory + }); + return runtime.response; +} diff --git a/llm_normalizer/backend/dist/services/assistantService.js b/llm_normalizer/backend/dist/services/assistantService.js index b6bdd29..6fb8b97 100644 --- a/llm_normalizer/backend/dist/services/assistantService.js +++ b/llm_normalizer/backend/dist/services/assistantService.js @@ -65,7 +65,7 @@ const openaiResponsesClient_1 = __importStar(require("./openaiResponsesClient")) const addressMcpClient_1 = __importStar(require("./addressMcpClient")); const capabilitiesRegistry_1 = __importStar(require("./capabilitiesRegistry")); const assistantCanon_1 = __importStar(require("./assistantCanon")); -const assistantAddressLaneResponseRuntimeAdapter_1 = __importStar(require("./assistantAddressLaneResponseRuntimeAdapter")); +const assistantAddressLaneResponseAttemptRuntimeAdapter_1 = __importStar(require("./assistantAddressLaneResponseAttemptRuntimeAdapter")); const assistantCoverageGrounding_1 = __importStar(require("./assistantCoverageGrounding")); const assistantDeepTurnAnalysisRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnAnalysisRuntimeAdapter")); const assistantDeepTurnCompositionRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnCompositionRuntimeAdapter")); @@ -4398,30 +4398,27 @@ class AssistantService { nowIso: () => new Date().toISOString() }); const sessionOrganizationScope = resolveSessionOrganizationScopeContext(userMessage, session.items); - const finalizeAddressLaneResponse = (addressLane, effectiveAddressUserMessage, carryoverMeta = null, llmPreDecomposeMeta = null) => { - const runtime = (0, assistantAddressLaneResponseRuntimeAdapter_1.runAssistantAddressLaneResponseRuntime)({ - sessionId, - userMessage, - effectiveAddressUserMessage, - addressLane, - carryoverMeta, - llmPreDecomposeMeta, - knownOrganizations: sessionOrganizationScope.knownOrganizations, - activeOrganization: sessionOrganizationScope.activeOrganization, - sanitizeOutgoingAssistantText, - buildAddressDebugPayload, - buildAddressFollowupOffer, - mergeKnownOrganizations, - toNonEmptyString, - appendItem: (targetSessionId, item) => this.sessions.appendItem(targetSessionId, item), - getSession: (targetSessionId) => this.sessions.getSession(targetSessionId), - persistSession: (sessionState) => this.sessionLogger.persistSession(sessionState), - cloneConversation: (items) => cloneItems(items), - logEvent: (payload) => (0, log_1.logJson)(payload), - messageIdFactory: () => `msg-${(0, nanoid_1.nanoid)(10)}` - }); - return runtime.response; - }; + const finalizeAddressLaneResponse = (addressLane, effectiveAddressUserMessage, carryoverMeta = null, llmPreDecomposeMeta = null) => (0, assistantAddressLaneResponseAttemptRuntimeAdapter_1.runAssistantAddressLaneResponseAttemptRuntime)({ + sessionId, + userMessage, + effectiveAddressUserMessage, + addressLane, + carryoverMeta, + llmPreDecomposeMeta, + knownOrganizations: sessionOrganizationScope.knownOrganizations, + activeOrganization: sessionOrganizationScope.activeOrganization, + sanitizeOutgoingAssistantText, + buildAddressDebugPayload, + buildAddressFollowupOffer, + mergeKnownOrganizations, + toNonEmptyString, + appendItem: (targetSessionId, item) => this.sessions.appendItem(targetSessionId, item), + getSession: (targetSessionId) => this.sessions.getSession(targetSessionId), + persistSession: (sessionState) => this.sessionLogger.persistSession(sessionState), + cloneConversation: (items) => cloneItems(items), + logEvent: (payload) => (0, log_1.logJson)(payload), + messageIdFactory: () => `msg-${(0, nanoid_1.nanoid)(10)}` + }); const tryHandleLivingChat = async (modeDecision, addressRuntimeMeta = null) => (0, assistantLivingChatAttemptRuntimeAdapter_1.runAssistantLivingChatAttemptRuntime)({ sessionId, userMessage, diff --git a/llm_normalizer/backend/src/services/assistantAddressLaneResponseAttemptRuntimeAdapter.ts b/llm_normalizer/backend/src/services/assistantAddressLaneResponseAttemptRuntimeAdapter.ts new file mode 100644 index 0000000..f701ca9 --- /dev/null +++ b/llm_normalizer/backend/src/services/assistantAddressLaneResponseAttemptRuntimeAdapter.ts @@ -0,0 +1,45 @@ +import type { AssistantMessageResponsePayload } from "../types/assistant"; +import { + runAssistantAddressLaneResponseRuntime, + type RunAssistantAddressLaneResponseRuntimeInput, + type RunAssistantAddressLaneResponseRuntimeOutput +} from "./assistantAddressLaneResponseRuntimeAdapter"; + +export interface RunAssistantAddressLaneResponseAttemptRuntimeInput< + ResponseType = AssistantMessageResponsePayload +> extends RunAssistantAddressLaneResponseRuntimeInput { + runAddressLaneResponseRuntime?: ( + input: RunAssistantAddressLaneResponseRuntimeInput + ) => RunAssistantAddressLaneResponseRuntimeOutput; +} + +export function runAssistantAddressLaneResponseAttemptRuntime< + ResponseType = AssistantMessageResponsePayload +>( + input: RunAssistantAddressLaneResponseAttemptRuntimeInput +): ResponseType { + const runAddressLaneResponseRuntimeSafe = + input.runAddressLaneResponseRuntime ?? runAssistantAddressLaneResponseRuntime; + const runtime = runAddressLaneResponseRuntimeSafe({ + sessionId: input.sessionId, + userMessage: input.userMessage, + effectiveAddressUserMessage: input.effectiveAddressUserMessage, + addressLane: input.addressLane, + carryoverMeta: input.carryoverMeta, + llmPreDecomposeMeta: input.llmPreDecomposeMeta, + knownOrganizations: input.knownOrganizations, + activeOrganization: input.activeOrganization, + sanitizeOutgoingAssistantText: input.sanitizeOutgoingAssistantText, + buildAddressDebugPayload: input.buildAddressDebugPayload, + buildAddressFollowupOffer: input.buildAddressFollowupOffer, + mergeKnownOrganizations: input.mergeKnownOrganizations, + toNonEmptyString: input.toNonEmptyString, + appendItem: input.appendItem, + getSession: input.getSession, + persistSession: input.persistSession, + cloneConversation: input.cloneConversation, + logEvent: input.logEvent, + messageIdFactory: input.messageIdFactory + }); + return runtime.response; +} diff --git a/llm_normalizer/backend/src/services/assistantService.ts b/llm_normalizer/backend/src/services/assistantService.ts index a7390d7..2917c58 100644 --- a/llm_normalizer/backend/src/services/assistantService.ts +++ b/llm_normalizer/backend/src/services/assistantService.ts @@ -19,7 +19,7 @@ import * as openaiResponsesClient_1 from "./openaiResponsesClient"; import * as addressMcpClient_1 from "./addressMcpClient"; import * as capabilitiesRegistry_1 from "./capabilitiesRegistry"; import * as assistantCanon_1 from "./assistantCanon"; -import * as assistantAddressLaneResponseRuntimeAdapter_1 from "./assistantAddressLaneResponseRuntimeAdapter"; +import * as assistantAddressLaneResponseAttemptRuntimeAdapter_1 from "./assistantAddressLaneResponseAttemptRuntimeAdapter"; import * as assistantCoverageGrounding_1 from "./assistantCoverageGrounding"; import * as assistantDeepTurnAnalysisRuntimeAdapter_1 from "./assistantDeepTurnAnalysisRuntimeAdapter"; import * as assistantDeepTurnCompositionRuntimeAdapter_1 from "./assistantDeepTurnCompositionRuntimeAdapter"; @@ -4353,8 +4353,7 @@ export class AssistantService { nowIso: () => new Date().toISOString() }); const sessionOrganizationScope = resolveSessionOrganizationScopeContext(userMessage, session.items); - const finalizeAddressLaneResponse = (addressLane, effectiveAddressUserMessage, carryoverMeta = null, llmPreDecomposeMeta = null) => { - const runtime = (0, assistantAddressLaneResponseRuntimeAdapter_1.runAssistantAddressLaneResponseRuntime)({ + const finalizeAddressLaneResponse = (addressLane, effectiveAddressUserMessage, carryoverMeta = null, llmPreDecomposeMeta = null) => (0, assistantAddressLaneResponseAttemptRuntimeAdapter_1.runAssistantAddressLaneResponseAttemptRuntime)({ sessionId, userMessage, effectiveAddressUserMessage, @@ -4375,8 +4374,6 @@ export class AssistantService { logEvent: (payload) => (0, log_1.logJson)(payload), messageIdFactory: () => `msg-${(0, nanoid_1.nanoid)(10)}` }); - return runtime.response; - }; const tryHandleLivingChat = async (modeDecision, addressRuntimeMeta = null) => (0, assistantLivingChatAttemptRuntimeAdapter_1.runAssistantLivingChatAttemptRuntime)({ sessionId, userMessage, diff --git a/llm_normalizer/backend/tests/assistantAddressLaneResponseAttemptRuntimeAdapter.test.ts b/llm_normalizer/backend/tests/assistantAddressLaneResponseAttemptRuntimeAdapter.test.ts new file mode 100644 index 0000000..c549ea5 --- /dev/null +++ b/llm_normalizer/backend/tests/assistantAddressLaneResponseAttemptRuntimeAdapter.test.ts @@ -0,0 +1,84 @@ +import { describe, expect, it, vi } from "vitest"; +import { runAssistantAddressLaneResponseAttemptRuntime } from "../src/services/assistantAddressLaneResponseAttemptRuntimeAdapter"; + +function buildInput(overrides: Record = {}) { + return { + sessionId: "asst-1", + userMessage: "где хвост по оплате", + effectiveAddressUserMessage: "где хвост по оплате", + addressLane: { + reply_text: "address reply", + reply_type: "factual_with_explanation", + debug: { extracted_filters: {} } + }, + carryoverMeta: null, + llmPreDecomposeMeta: null, + knownOrganizations: [], + activeOrganization: null, + sanitizeOutgoingAssistantText: (value: unknown, fallback = "") => { + const text = String(value ?? "").trim(); + return text || fallback; + }, + buildAddressDebugPayload: () => ({}), + buildAddressFollowupOffer: () => null, + mergeKnownOrganizations: (value: string[]) => value, + toNonEmptyString: (value: unknown) => (typeof value === "string" && value.trim() ? value.trim() : null), + appendItem: () => {}, + getSession: () => ({ + session_id: "asst-1", + updated_at: "", + items: [], + investigation_state: null + }), + persistSession: () => {}, + cloneConversation: (items: unknown[]) => items, + logEvent: () => {}, + messageIdFactory: () => "msg-1", + ...overrides + } as any; +} + +describe("assistant address lane response attempt runtime adapter", () => { + it("returns delegated runtime response", () => { + const runAddressLaneResponseRuntime = vi.fn(() => ({ + response: { ok: true, lane: "address" }, + debug: { marker: "v1" } + })); + + const response = runAssistantAddressLaneResponseAttemptRuntime( + buildInput({ runAddressLaneResponseRuntime }) + ); + + expect(response).toEqual({ ok: true, lane: "address" }); + expect(runAddressLaneResponseRuntime).toHaveBeenCalledWith( + expect.objectContaining({ + sessionId: "asst-1", + userMessage: "где хвост по оплате" + }) + ); + }); + + it("forwards carryover and llm predecompose metadata", () => { + const carryoverMeta = { previousReplyType: "partial_coverage" }; + const llmPreDecomposeMeta = { mode: "supported", confidence: "high" }; + const runAddressLaneResponseRuntime = vi.fn(() => ({ + response: { ok: true }, + debug: {} + })); + + runAssistantAddressLaneResponseAttemptRuntime( + buildInput({ + carryoverMeta, + llmPreDecomposeMeta, + runAddressLaneResponseRuntime + }) + ); + + expect(runAddressLaneResponseRuntime).toHaveBeenCalledWith( + expect.objectContaining({ + carryoverMeta, + llmPreDecomposeMeta + }) + ); + }); +});