АРЧ АП11 - Выделить route policy adapter для архитектуры assistant runtime

This commit is contained in:
dctouch 2026-04-15 23:10:46 +03:00
parent 1d95ed9b60
commit 9d5928125d
6 changed files with 207 additions and 16 deletions

View File

@ -1,6 +1,7 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildAssistantAddressOrchestrationRuntime = buildAssistantAddressOrchestrationRuntime;
const assistantRoutePolicyRuntimeAdapter_1 = require("./assistantRoutePolicyRuntimeAdapter");
function hasSelectedObjectInventorySignal(text) {
return /(?:по\s+выбранному\s+объекту|по\s+этой\s+позиции|по\s+этому\s+товару|selected\s+object)/iu.test(String(text ?? ""));
}
@ -78,26 +79,25 @@ async function buildAssistantAddressOrchestrationRuntime(input) {
carryover = input.resolveAddressFollowupCarryoverContext(input.userMessage, input.sessionItems, addressInputMessage, addressPreDecompose, input.sessionAddressNavigationState);
}
const followupContext = carryover?.followupContext ?? null;
const orchestrationDecision = input.resolveAssistantOrchestrationDecision({
const routePolicyRuntime = (0, assistantRoutePolicyRuntimeAdapter_1.runAssistantRoutePolicyRuntime)({
rawUserMessage: input.userMessage,
effectiveAddressUserMessage: addressInputMessage,
followupContext,
llmPreDecomposeMeta: addressPreDecompose,
sessionItems: input.sessionItems,
sessionOrganizationScope: input.sessionOrganizationScope ?? null,
useMock: input.useMock
useMock: input.useMock,
resolveAssistantOrchestrationDecision: input.resolveAssistantOrchestrationDecision
});
const orchestrationDecision = routePolicyRuntime.orchestrationDecision;
const dialogContinuationContract = input.buildAddressDialogContinuationContractV2(input.userMessage, addressInputMessage, carryover, addressPreDecompose);
const addressRuntimeMeta = {
...addressPreDecompose,
toolGateDecision: orchestrationDecision.toolGateDecision ?? null,
toolGateReason: orchestrationDecision.toolGateReason ?? null,
dialogContinuationContract,
orchestrationContract: orchestrationDecision.orchestrationContract ?? null
};
const livingModeDecision = {
mode: orchestrationDecision.livingMode,
reason: orchestrationDecision.livingReason
orchestrationContract: orchestrationDecision.orchestrationContract ?? null,
routePolicyContract: routePolicyRuntime.routePolicyContract
};
return {
addressPreDecompose,
@ -105,6 +105,6 @@ async function buildAssistantAddressOrchestrationRuntime(input) {
carryover,
orchestrationDecision,
addressRuntimeMeta,
livingModeDecision
livingModeDecision: routePolicyRuntime.livingModeDecision
};
}

View File

@ -0,0 +1,38 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ASSISTANT_ROUTE_POLICY_RUNTIME_SCHEMA_VERSION = void 0;
exports.runAssistantRoutePolicyRuntime = runAssistantRoutePolicyRuntime;
exports.ASSISTANT_ROUTE_POLICY_RUNTIME_SCHEMA_VERSION = "assistant_route_policy_runtime_v1";
function hasObject(value) {
return Boolean(value && typeof value === "object");
}
function runAssistantRoutePolicyRuntime(input) {
const orchestrationDecision = input.resolveAssistantOrchestrationDecision({
rawUserMessage: input.rawUserMessage,
effectiveAddressUserMessage: input.effectiveAddressUserMessage,
followupContext: input.followupContext,
llmPreDecomposeMeta: input.llmPreDecomposeMeta,
sessionItems: input.sessionItems,
sessionOrganizationScope: input.sessionOrganizationScope,
useMock: input.useMock
});
const livingModeDecision = {
mode: orchestrationDecision.livingMode,
reason: orchestrationDecision.livingReason
};
return {
orchestrationDecision,
livingModeDecision,
routePolicyContract: {
schema_version: exports.ASSISTANT_ROUTE_POLICY_RUNTIME_SCHEMA_VERSION,
policy_owner: "assistantRoutePolicyRuntimeAdapter",
decision_source: "resolveAssistantOrchestrationDecision",
living_mode: livingModeDecision.mode,
living_reason: livingModeDecision.reason,
tool_gate_decision: orchestrationDecision.toolGateDecision ?? null,
tool_gate_reason: orchestrationDecision.toolGateReason ?? null,
has_followup_context: hasObject(input.followupContext),
has_orchestration_contract: hasObject(orchestrationDecision.orchestrationContract)
}
};
}

View File

@ -1,3 +1,5 @@
import { runAssistantRoutePolicyRuntime } from "./assistantRoutePolicyRuntimeAdapter";
export interface BuildAssistantAddressOrchestrationRuntimeInput {
userMessage: string;
sessionItems: unknown[];
@ -195,15 +197,17 @@ export async function buildAssistantAddressOrchestrationRuntime(
}
const followupContext = carryover?.followupContext ?? null;
const orchestrationDecision = input.resolveAssistantOrchestrationDecision({
const routePolicyRuntime = runAssistantRoutePolicyRuntime({
rawUserMessage: input.userMessage,
effectiveAddressUserMessage: addressInputMessage,
followupContext,
llmPreDecomposeMeta: addressPreDecompose,
sessionItems: input.sessionItems,
sessionOrganizationScope: input.sessionOrganizationScope ?? null,
useMock: input.useMock
useMock: input.useMock,
resolveAssistantOrchestrationDecision: input.resolveAssistantOrchestrationDecision
});
const orchestrationDecision = routePolicyRuntime.orchestrationDecision;
const dialogContinuationContract = input.buildAddressDialogContinuationContractV2(
input.userMessage,
addressInputMessage,
@ -215,11 +219,8 @@ export async function buildAssistantAddressOrchestrationRuntime(
toolGateDecision: orchestrationDecision.toolGateDecision ?? null,
toolGateReason: orchestrationDecision.toolGateReason ?? null,
dialogContinuationContract,
orchestrationContract: orchestrationDecision.orchestrationContract ?? null
};
const livingModeDecision = {
mode: orchestrationDecision.livingMode,
reason: orchestrationDecision.livingReason
orchestrationContract: orchestrationDecision.orchestrationContract ?? null,
routePolicyContract: routePolicyRuntime.routePolicyContract
};
return {
@ -228,6 +229,6 @@ export async function buildAssistantAddressOrchestrationRuntime(
carryover,
orchestrationDecision,
addressRuntimeMeta,
livingModeDecision
livingModeDecision: routePolicyRuntime.livingModeDecision
};
}

View File

@ -0,0 +1,79 @@
export const ASSISTANT_ROUTE_POLICY_RUNTIME_SCHEMA_VERSION = "assistant_route_policy_runtime_v1" as const;
export interface RunAssistantRoutePolicyRuntimeInput {
rawUserMessage: string;
effectiveAddressUserMessage: string;
followupContext: unknown;
llmPreDecomposeMeta: Record<string, unknown>;
sessionItems?: unknown[];
sessionOrganizationScope?: unknown;
useMock: boolean;
resolveAssistantOrchestrationDecision: (input: {
rawUserMessage: string;
effectiveAddressUserMessage: string;
followupContext: unknown;
llmPreDecomposeMeta: Record<string, unknown>;
sessionItems?: unknown[];
sessionOrganizationScope?: unknown;
useMock: boolean;
}) => Record<string, unknown>;
}
export interface AssistantRoutePolicyRuntimeContract {
schema_version: typeof ASSISTANT_ROUTE_POLICY_RUNTIME_SCHEMA_VERSION;
policy_owner: "assistantRoutePolicyRuntimeAdapter";
decision_source: "resolveAssistantOrchestrationDecision";
living_mode: unknown;
living_reason: unknown;
tool_gate_decision: unknown;
tool_gate_reason: unknown;
has_followup_context: boolean;
has_orchestration_contract: boolean;
}
export interface RunAssistantRoutePolicyRuntimeOutput {
orchestrationDecision: Record<string, unknown>;
livingModeDecision: {
mode: unknown;
reason: unknown;
};
routePolicyContract: AssistantRoutePolicyRuntimeContract;
}
function hasObject(value: unknown): boolean {
return Boolean(value && typeof value === "object");
}
export function runAssistantRoutePolicyRuntime(
input: RunAssistantRoutePolicyRuntimeInput
): RunAssistantRoutePolicyRuntimeOutput {
const orchestrationDecision = input.resolveAssistantOrchestrationDecision({
rawUserMessage: input.rawUserMessage,
effectiveAddressUserMessage: input.effectiveAddressUserMessage,
followupContext: input.followupContext,
llmPreDecomposeMeta: input.llmPreDecomposeMeta,
sessionItems: input.sessionItems,
sessionOrganizationScope: input.sessionOrganizationScope,
useMock: input.useMock
});
const livingModeDecision = {
mode: orchestrationDecision.livingMode,
reason: orchestrationDecision.livingReason
};
return {
orchestrationDecision,
livingModeDecision,
routePolicyContract: {
schema_version: ASSISTANT_ROUTE_POLICY_RUNTIME_SCHEMA_VERSION,
policy_owner: "assistantRoutePolicyRuntimeAdapter",
decision_source: "resolveAssistantOrchestrationDecision",
living_mode: livingModeDecision.mode,
living_reason: livingModeDecision.reason,
tool_gate_decision: orchestrationDecision.toolGateDecision ?? null,
tool_gate_reason: orchestrationDecision.toolGateReason ?? null,
has_followup_context: hasObject(input.followupContext),
has_orchestration_contract: hasObject(orchestrationDecision.orchestrationContract)
}
};
}

View File

@ -63,6 +63,16 @@ describe("assistant address orchestration runtime adapter", () => {
expect(output.orchestrationDecision.runAddressLane).toBe(true);
expect(output.livingModeDecision.mode).toBe("deep_analysis");
expect(output.addressRuntimeMeta.toolGateDecision).toBe("run_address_lane");
expect(output.addressRuntimeMeta.routePolicyContract).toEqual(
expect.objectContaining({
schema_version: "assistant_route_policy_runtime_v1",
policy_owner: "assistantRoutePolicyRuntimeAdapter",
living_mode: "deep_analysis",
tool_gate_decision: "run_address_lane",
has_followup_context: true,
has_orchestration_contract: true
})
);
expect(output.addressRuntimeMeta.dialogContinuationContract).toEqual({
schema_version: "address_dialog_continuation_contract_v2"
});

View File

@ -0,0 +1,63 @@
import { describe, expect, it, vi } from "vitest";
import { runAssistantRoutePolicyRuntime } from "../src/services/assistantRoutePolicyRuntimeAdapter";
describe("assistant route policy runtime adapter", () => {
it("delegates to the current orchestration decision function and emits a route policy contract", () => {
const resolveAssistantOrchestrationDecision = vi.fn(() => ({
runAddressLane: true,
livingMode: "address_data",
livingReason: "address_lane_triggered",
toolGateDecision: "run_address_lane",
toolGateReason: "followup_context_detected",
orchestrationContract: {
schema_version: "assistant_orchestration_contract_v1"
}
}));
const followupContext = { previous_intent: "inventory_on_hand_as_of_date" };
const output = runAssistantRoutePolicyRuntime({
rawUserMessage: "кто это поставил нам",
effectiveAddressUserMessage: "кто это поставил нам",
followupContext,
llmPreDecomposeMeta: {
attempted: true
},
sessionItems: [{ role: "assistant" }],
sessionOrganizationScope: {
selectedOrganization: "ООО Ромашка"
},
useMock: false,
resolveAssistantOrchestrationDecision
});
expect(resolveAssistantOrchestrationDecision).toHaveBeenCalledWith({
rawUserMessage: "кто это поставил нам",
effectiveAddressUserMessage: "кто это поставил нам",
followupContext,
llmPreDecomposeMeta: {
attempted: true
},
sessionItems: [{ role: "assistant" }],
sessionOrganizationScope: {
selectedOrganization: "ООО Ромашка"
},
useMock: false
});
expect(output.orchestrationDecision.runAddressLane).toBe(true);
expect(output.livingModeDecision).toEqual({
mode: "address_data",
reason: "address_lane_triggered"
});
expect(output.routePolicyContract).toEqual({
schema_version: "assistant_route_policy_runtime_v1",
policy_owner: "assistantRoutePolicyRuntimeAdapter",
decision_source: "resolveAssistantOrchestrationDecision",
living_mode: "address_data",
living_reason: "address_lane_triggered",
tool_gate_decision: "run_address_lane",
tool_gate_reason: "followup_context_detected",
has_followup_context: true,
has_orchestration_contract: true
});
});
});