ГЛОБАЛЬНЫЙ РЕФАКТОРИНГ АРХИТЕКТУРЫ - Архитектура маршрутов v2: baseline ожиданий intentrecipe/result_mode с runtime-аудитом

This commit is contained in:
2026-04-12 14:54:52 +03:00
parent bd1fbbdb67
commit 278eb4abeb
14 changed files with 624 additions and 6 deletions
+4 -2
View File
@@ -3,8 +3,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ASSISTANT_SESSIONS_DIR = exports.EVAL_CASES_DIR = exports.PRESETS_DIR = exports.TRACES_DIR = exports.DATA_DIR = exports.VAT_PAYABLE_19_PREFIXES = exports.VAT_PAYABLE_68_PREFIXES = exports.ASSISTANT_MCP_LIVE_LIMIT = exports.ASSISTANT_MCP_TIMEOUT_MS = exports.ASSISTANT_MCP_CHANNEL = exports.ASSISTANT_MCP_PROXY_URL = exports.FEATURE_ASSISTANT_LIVING_CHAT_ROUTER_V1 = exports.FEATURE_ASSISTANT_ROUTE_SHADOW_PAYABLES_EXACT_V1 = exports.FEATURE_ASSISTANT_ROUTE_RECEIVABLES_HEURISTIC_V1 = exports.FEATURE_ASSISTANT_ROUTE_PAYABLES_HEURISTIC_V1 = exports.FEATURE_ASSISTANT_ROUTE_PAYABLES_CONFIRMED_V1 = exports.FEATURE_ASSISTANT_ROUTE_BALANCE_EXACT_V1 = exports.FEATURE_ASSISTANT_ROUTE_DRILLDOWN_V1 = exports.FEATURE_ASSISTANT_ROUTE_ADDRESS_GENERIC_V1 = exports.FEATURE_ASSISTANT_CAPABILITY_ROUTE_GUARD_V1 = exports.FEATURE_ASSISTANT_ADDRESS_NAVIGATION_STATE_V1 = exports.FEATURE_ASSISTANT_ADDRESS_QUERY_LIVE_V1 = exports.FEATURE_ASSISTANT_ADDRESS_QUERY_LLM_PREDECOMPOSE_V1 = exports.FEATURE_ASSISTANT_ADDRESS_QUERY_V1 = exports.FEATURE_ASSISTANT_MCP_RUNTIME_V1 = exports.FEATURE_ASSISTANT_GRAPH_RUNTIME_V1 = exports.FEATURE_ASSISTANT_LIFECYCLE_ANSWER_V1 = exports.FEATURE_ASSISTANT_LIFECYCLE_RUNTIME_V1 = exports.FEATURE_ASSISTANT_STAGE2_EVAL_V1 = exports.FEATURE_ASSISTANT_PROBLEM_UNIT_CONTINUITY_V1 = exports.FEATURE_ASSISTANT_PROBLEM_CENTRIC_ANSWER_V1 = exports.FEATURE_ASSISTANT_PROBLEM_UNITS_V1 = exports.FEATURE_ASSISTANT_ACCOUNTANT_EVAL_V1 = exports.FEATURE_ASSISTANT_ANSWER_POLICY_V11 = exports.FEATURE_ASSISTANT_ANTI_GENERIC_RANKING_GUARD_V1 = exports.FEATURE_ASSISTANT_MIN_EVIDENCE_GATE_V1 = exports.FEATURE_ASSISTANT_BROAD_GUARD_V1 = exports.FEATURE_ASSISTANT_EVIDENCE_ENRICHMENT_V1 = exports.FEATURE_ASSISTANT_STATE_FOLLOWUP_BINDING_V1 = exports.FEATURE_ASSISTANT_CONTRACTS_V11 = exports.FEATURE_ASSISTANT_INVESTIGATION_STATE_V1 = exports.DEFAULT_PROMPT_VERSION = exports.DEFAULT_MAX_OUTPUT_TOKENS = exports.DEFAULT_TEMPERATURE = exports.DEFAULT_MODEL = exports.DEFAULT_OPENAI_BASE_URL = exports.TIMEZONE = exports.PORT = exports.MODULE_ROOT = exports.BACKEND_ROOT = void 0;
exports.MANUAL_CASE_DECISION_SCHEMA_FILE = exports.ASSISTANT_CAPABILITIES_REGISTRY_FILE = exports.ASSISTANT_CANON_FILE = exports.ARCH_EXPORT_2020_DIR = exports.SCHEMAS_DIR = exports.EVAL_DATASETS_DIR = exports.REPORTS_DIR = exports.PROMPTS_DIR = exports.AUTORUN_GENERATOR_HISTORY_FILE = exports.AUTORUN_GENERATOR_DIR = exports.AUTORUN_ANNOTATIONS_FILE = exports.AUTORUN_ANNOTATIONS_DIR = exports.ASSISTANT_ANNOTATIONS_FILE = exports.ASSISTANT_ANNOTATIONS_DIR = void 0;
exports.PRESETS_DIR = exports.TRACES_DIR = exports.DATA_DIR = exports.VAT_PAYABLE_19_PREFIXES = exports.VAT_PAYABLE_68_PREFIXES = exports.ASSISTANT_MCP_LIVE_LIMIT = exports.ASSISTANT_MCP_TIMEOUT_MS = exports.ASSISTANT_MCP_CHANNEL = exports.ASSISTANT_MCP_PROXY_URL = exports.FEATURE_ASSISTANT_LIVING_CHAT_ROUTER_V1 = exports.FEATURE_ASSISTANT_ROUTE_EXPECTATION_HARD_GUARD_V1 = exports.FEATURE_ASSISTANT_ROUTE_EXPECTATION_AUDIT_V1 = exports.FEATURE_ASSISTANT_ROUTE_SHADOW_PAYABLES_EXACT_V1 = exports.FEATURE_ASSISTANT_ROUTE_RECEIVABLES_HEURISTIC_V1 = exports.FEATURE_ASSISTANT_ROUTE_PAYABLES_HEURISTIC_V1 = exports.FEATURE_ASSISTANT_ROUTE_PAYABLES_CONFIRMED_V1 = exports.FEATURE_ASSISTANT_ROUTE_BALANCE_EXACT_V1 = exports.FEATURE_ASSISTANT_ROUTE_DRILLDOWN_V1 = exports.FEATURE_ASSISTANT_ROUTE_ADDRESS_GENERIC_V1 = exports.FEATURE_ASSISTANT_CAPABILITY_ROUTE_GUARD_V1 = exports.FEATURE_ASSISTANT_ADDRESS_NAVIGATION_STATE_V1 = exports.FEATURE_ASSISTANT_ADDRESS_QUERY_LIVE_V1 = exports.FEATURE_ASSISTANT_ADDRESS_QUERY_LLM_PREDECOMPOSE_V1 = exports.FEATURE_ASSISTANT_ADDRESS_QUERY_V1 = exports.FEATURE_ASSISTANT_MCP_RUNTIME_V1 = exports.FEATURE_ASSISTANT_GRAPH_RUNTIME_V1 = exports.FEATURE_ASSISTANT_LIFECYCLE_ANSWER_V1 = exports.FEATURE_ASSISTANT_LIFECYCLE_RUNTIME_V1 = exports.FEATURE_ASSISTANT_STAGE2_EVAL_V1 = exports.FEATURE_ASSISTANT_PROBLEM_UNIT_CONTINUITY_V1 = exports.FEATURE_ASSISTANT_PROBLEM_CENTRIC_ANSWER_V1 = exports.FEATURE_ASSISTANT_PROBLEM_UNITS_V1 = exports.FEATURE_ASSISTANT_ACCOUNTANT_EVAL_V1 = exports.FEATURE_ASSISTANT_ANSWER_POLICY_V11 = exports.FEATURE_ASSISTANT_ANTI_GENERIC_RANKING_GUARD_V1 = exports.FEATURE_ASSISTANT_MIN_EVIDENCE_GATE_V1 = exports.FEATURE_ASSISTANT_BROAD_GUARD_V1 = exports.FEATURE_ASSISTANT_EVIDENCE_ENRICHMENT_V1 = exports.FEATURE_ASSISTANT_STATE_FOLLOWUP_BINDING_V1 = exports.FEATURE_ASSISTANT_CONTRACTS_V11 = exports.FEATURE_ASSISTANT_INVESTIGATION_STATE_V1 = exports.DEFAULT_PROMPT_VERSION = exports.DEFAULT_MAX_OUTPUT_TOKENS = exports.DEFAULT_TEMPERATURE = exports.DEFAULT_MODEL = exports.DEFAULT_OPENAI_BASE_URL = exports.TIMEZONE = exports.PORT = exports.MODULE_ROOT = exports.BACKEND_ROOT = void 0;
exports.MANUAL_CASE_DECISION_SCHEMA_FILE = exports.ASSISTANT_CAPABILITIES_REGISTRY_FILE = exports.ASSISTANT_CANON_FILE = exports.ARCH_EXPORT_2020_DIR = exports.SCHEMAS_DIR = exports.EVAL_DATASETS_DIR = exports.REPORTS_DIR = exports.PROMPTS_DIR = exports.AUTORUN_GENERATOR_HISTORY_FILE = exports.AUTORUN_GENERATOR_DIR = exports.AUTORUN_ANNOTATIONS_FILE = exports.AUTORUN_ANNOTATIONS_DIR = exports.ASSISTANT_ANNOTATIONS_FILE = exports.ASSISTANT_ANNOTATIONS_DIR = exports.ASSISTANT_SESSIONS_DIR = exports.EVAL_CASES_DIR = void 0;
const path_1 = __importDefault(require("path"));
exports.BACKEND_ROOT = path_1.default.resolve(__dirname, "..");
exports.MODULE_ROOT = path_1.default.resolve(exports.BACKEND_ROOT, "..");
@@ -69,6 +69,8 @@ exports.FEATURE_ASSISTANT_ROUTE_PAYABLES_CONFIRMED_V1 = toBooleanFlag(process.en
exports.FEATURE_ASSISTANT_ROUTE_PAYABLES_HEURISTIC_V1 = toBooleanFlag(process.env.FEATURE_ASSISTANT_ROUTE_PAYABLES_HEURISTIC_V1, true);
exports.FEATURE_ASSISTANT_ROUTE_RECEIVABLES_HEURISTIC_V1 = toBooleanFlag(process.env.FEATURE_ASSISTANT_ROUTE_RECEIVABLES_HEURISTIC_V1, true);
exports.FEATURE_ASSISTANT_ROUTE_SHADOW_PAYABLES_EXACT_V1 = toBooleanFlag(process.env.FEATURE_ASSISTANT_ROUTE_SHADOW_PAYABLES_EXACT_V1, false);
exports.FEATURE_ASSISTANT_ROUTE_EXPECTATION_AUDIT_V1 = toBooleanFlag(process.env.FEATURE_ASSISTANT_ROUTE_EXPECTATION_AUDIT_V1, true);
exports.FEATURE_ASSISTANT_ROUTE_EXPECTATION_HARD_GUARD_V1 = toBooleanFlag(process.env.FEATURE_ASSISTANT_ROUTE_EXPECTATION_HARD_GUARD_V1, false);
exports.FEATURE_ASSISTANT_LIVING_CHAT_ROUTER_V1 = toBooleanFlag(process.env.FEATURE_ASSISTANT_LIVING_CHAT_ROUTER_V1, true);
exports.ASSISTANT_MCP_PROXY_URL = (process.env.ASSISTANT_MCP_PROXY_URL ?? "http://127.0.0.1:6003").replace(/\/+$/, "");
exports.ASSISTANT_MCP_CHANNEL = process.env.ASSISTANT_MCP_CHANNEL ?? "default";
+87 -2
View File
@@ -8,6 +8,7 @@ const decomposeStage_1 = require("./address_runtime/decomposeStage");
const resolveStage_1 = require("./address_runtime/resolveStage");
const composeStage_1 = require("./address_runtime/composeStage");
const addressCapabilityPolicy_1 = require("./addressCapabilityPolicy");
const addressRouteExpectations_1 = require("./addressRouteExpectations");
const ACCOUNT_SCOPE_FIELDS_CHECKED = ["account_dt", "account_kt", "registrator", "analytics"];
const ACCOUNT_SCOPE_MATCH_STRATEGY = "account_code_regex_plus_alias_map_v1";
const ADDRESS_ANCHOR_RECOVERY_LIMIT = 1000;
@@ -781,6 +782,30 @@ function buildShadowRouteAudit(input) {
status: "planned"
};
}
function buildRouteExpectationAudit(input) {
if (!config_1.FEATURE_ASSISTANT_ROUTE_EXPECTATION_AUDIT_V1) {
return {
status: "not_found",
reason: "route_expectation_audit_disabled",
expectedSelectedRecipes: [],
expectedRequestedResultModes: [],
expectedResultModes: []
};
}
const audit = (0, addressRouteExpectations_1.evaluateAddressRouteExpectation)({
intent: input.intent,
selectedRecipe: input.selectedRecipe,
requestedResultMode: input.requestedResultMode,
resultMode: input.resultMode
});
return {
status: audit.status,
reason: audit.reason,
expectedSelectedRecipes: audit.expected_selected_recipes,
expectedRequestedResultModes: audit.expected_requested_result_modes,
expectedResultModes: audit.expected_result_modes
};
}
function enforceStrictAccountScopeForIntent(plan, intent) {
if (intent !== "list_receivables_counterparties" || plan.account_scope_mode === "strict") {
return plan;
@@ -1395,6 +1420,13 @@ function buildLimitedExecutionResult(input) {
!reasonsWithConfirmedFallback.includes("exact_payables_mode_limited_response")
? [...reasonsWithConfirmedFallback, "exact_payables_mode_limited_response"]
: reasonsWithConfirmedFallback;
const routeExpectationAudit = input.routeExpectationAudit ??
buildRouteExpectationAudit({
intent: input.intent.intent,
selectedRecipe: input.selectedRecipe,
requestedResultMode: requestedResultMode,
resultMode: resultSemantics.result_mode
});
return {
handled: true,
reply_text: composeLimitedReply({
@@ -1453,6 +1485,11 @@ function buildLimitedExecutionResult(input) {
shadow_route_intent: input.shadowRouteAudit?.intent ?? null,
shadow_route_selected_recipe: input.shadowRouteAudit?.selectedRecipe ?? null,
shadow_route_status: input.shadowRouteAudit?.status ?? "skipped",
route_expectation_status: routeExpectationAudit.status,
route_expectation_reason: routeExpectationAudit.reason,
route_expectation_expected_selected_recipes: routeExpectationAudit.expectedSelectedRecipes,
route_expectation_expected_requested_result_modes: routeExpectationAudit.expectedRequestedResultModes,
route_expectation_expected_result_modes: routeExpectationAudit.expectedResultModes,
...resultSemantics,
limitations: input.limitations,
reasons
@@ -2451,6 +2488,45 @@ class AddressQueryService {
responseType: factual.responseType,
rowsMatched: filteredRows.length
}), factual.semantics);
const finalRouteExpectationAudit = buildRouteExpectationAudit({
intent: intent.intent,
selectedRecipe: effectiveRecipeId,
requestedResultMode,
resultMode: factualResultSemantics.result_mode
});
if (finalRouteExpectationAudit.status === "mismatch" && config_1.FEATURE_ASSISTANT_ROUTE_EXPECTATION_HARD_GUARD_V1) {
return buildLimitedExecutionResult({
mode,
shape,
intent,
filters: filters.extracted_filters,
missingRequiredFilters: [],
selectedRecipe: effectiveRecipeId,
accountScopeMode: plan.account_scope_mode,
accountScopeFallbackApplied,
accountScopeAudit,
anchor,
matchFailureStage,
matchFailureReason,
mcpCallStatus: stageStatus,
rowsFetched: mcp.fetched_rows,
rawRowsReceived: mcp.raw_rows.length,
rowsAfterAccountScope: normalizedRows.length,
rowsAfterRecipeFilter: filterByAnchors.length,
rowsMaterialized: normalizedRows.length,
rowsMatched: filteredRows.length,
rawRowKeysSample: rowDiagnostics.rawRowKeysSample,
materializationDropReason: rowDiagnostics.materializationDropReason,
category: "recipe_visibility_gap",
reasonText: "маршрут не прошел baseline route expectation contract",
nextStep: "проверьте intent/recipe mapping или отключите FEATURE_ASSISTANT_ROUTE_EXPECTATION_HARD_GUARD_V1 для безопасного rollout",
limitations: ["route_expectation_mismatch_guard_blocked"],
reasons: [...baseReasons, `route_expectation_mismatch:${finalRouteExpectationAudit.reason}`],
capabilityAudit,
shadowRouteAudit,
routeExpectationAudit: finalRouteExpectationAudit
});
}
if (intent.intent === "payables_confirmed_as_of_date" && factualResultSemantics.balance_confirmed !== true) {
return buildLimitedExecutionResult({
mode,
@@ -2480,9 +2556,13 @@ class AddressQueryService {
limitations: ["exact_payables_mode_unconfirmed_output_blocked"],
reasons: [...baseReasons, "exact_payables_mode_unconfirmed_output_blocked"],
capabilityAudit,
shadowRouteAudit
shadowRouteAudit,
routeExpectationAudit: finalRouteExpectationAudit
});
}
const reasonsWithRouteExpectation = finalRouteExpectationAudit.status === "mismatch"
? [...baseReasons, `route_expectation_mismatch:${finalRouteExpectationAudit.reason}`]
: baseReasons;
return {
handled: true,
reply_text: factual.text,
@@ -2533,9 +2613,14 @@ class AddressQueryService {
shadow_route_intent: shadowRouteAudit.intent,
shadow_route_selected_recipe: shadowRouteAudit.selectedRecipe,
shadow_route_status: shadowRouteAudit.status,
route_expectation_status: finalRouteExpectationAudit.status,
route_expectation_reason: finalRouteExpectationAudit.reason,
route_expectation_expected_selected_recipes: finalRouteExpectationAudit.expectedSelectedRecipes,
route_expectation_expected_requested_result_modes: finalRouteExpectationAudit.expectedRequestedResultModes,
route_expectation_expected_result_modes: finalRouteExpectationAudit.expectedResultModes,
...factualResultSemantics,
limitations: filters.warnings,
reasons: withConfirmedBalanceFallbackReason(baseReasons, requestedResultMode, factual.semantics, factualResultSemantics.result_mode)
reasons: withConfirmedBalanceFallbackReason(reasonsWithRouteExpectation, requestedResultMode, factual.semantics, factualResultSemantics.result_mode)
}
};
}
@@ -0,0 +1,128 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.loadAddressRouteExpectationsContract = loadAddressRouteExpectationsContract;
exports.evaluateAddressRouteExpectation = evaluateAddressRouteExpectation;
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const EXPECTATIONS_FILE = path_1.default.resolve(__dirname, "..", "..", "..", "..", "docs", "TECH", "address_route_expectations_v1.json");
function toObject(value) {
if (!value || typeof value !== "object" || Array.isArray(value)) {
return null;
}
return value;
}
function toNonEmptyString(value) {
if (typeof value !== "string") {
return null;
}
const trimmed = value.trim();
return trimmed.length > 0 ? trimmed : null;
}
function toStringArray(value) {
if (!Array.isArray(value)) {
return [];
}
return value.map((item) => toNonEmptyString(item)).filter((item) => Boolean(item));
}
function parseResultModes(value) {
const raw = toStringArray(value);
return raw.filter((mode) => mode === "heuristic_candidates" || mode === "confirmed_balance");
}
function parseEntry(value) {
const object = toObject(value);
if (!object) {
return null;
}
const intent = toNonEmptyString(object.intent);
const expectedSelectedRecipes = toStringArray(object.expected_selected_recipes);
if (!intent || expectedSelectedRecipes.length === 0) {
return null;
}
const expectedRequestedResultModes = parseResultModes(object.expected_requested_result_modes);
const expectedResultModes = parseResultModes(object.expected_result_modes);
return {
intent,
expected_selected_recipes: expectedSelectedRecipes,
...(expectedRequestedResultModes.length > 0 ? { expected_requested_result_modes: expectedRequestedResultModes } : {}),
...(expectedResultModes.length > 0 ? { expected_result_modes: expectedResultModes } : {})
};
}
function loadAddressRouteExpectationsContract() {
const raw = fs_1.default.readFileSync(EXPECTATIONS_FILE, "utf-8");
const parsed = JSON.parse(raw);
const root = toObject(parsed);
if (!root) {
throw new Error("address_route_expectations_v1: invalid root payload");
}
const schemaVersion = toNonEmptyString(root.schema_version);
if (schemaVersion !== "address_route_expectations_v1") {
throw new Error(`address_route_expectations_v1: unexpected schema version '${schemaVersion ?? "null"}'`);
}
const updatedAt = toNonEmptyString(root.updated_at) ?? new Date().toISOString();
const entriesRaw = Array.isArray(root.entries) ? root.entries : [];
const entries = entriesRaw.map(parseEntry).filter((entry) => entry !== null);
if (entries.length === 0) {
throw new Error("address_route_expectations_v1: no valid entries");
}
return {
schema_version: "address_route_expectations_v1",
updated_at: updatedAt,
entries
};
}
function evaluateAddressRouteExpectation(input) {
const contract = loadAddressRouteExpectationsContract();
const entry = contract.entries.find((item) => item.intent === input.intent);
if (!entry) {
return {
status: "not_found",
reason: "route_expectation_not_defined_for_intent",
expected_selected_recipes: [],
expected_requested_result_modes: [],
expected_result_modes: []
};
}
if (input.selectedRecipe && !entry.expected_selected_recipes.includes(input.selectedRecipe)) {
return {
status: "mismatch",
reason: "selected_recipe_mismatch",
expected_selected_recipes: entry.expected_selected_recipes,
expected_requested_result_modes: entry.expected_requested_result_modes ?? [],
expected_result_modes: entry.expected_result_modes ?? []
};
}
if (input.requestedResultMode &&
Array.isArray(entry.expected_requested_result_modes) &&
entry.expected_requested_result_modes.length > 0 &&
!entry.expected_requested_result_modes.includes(input.requestedResultMode)) {
return {
status: "mismatch",
reason: "requested_result_mode_mismatch",
expected_selected_recipes: entry.expected_selected_recipes,
expected_requested_result_modes: entry.expected_requested_result_modes,
expected_result_modes: entry.expected_result_modes ?? []
};
}
if (input.resultMode &&
Array.isArray(entry.expected_result_modes) &&
entry.expected_result_modes.length > 0 &&
!entry.expected_result_modes.includes(input.resultMode)) {
return {
status: "mismatch",
reason: "result_mode_mismatch",
expected_selected_recipes: entry.expected_selected_recipes,
expected_requested_result_modes: entry.expected_requested_result_modes ?? [],
expected_result_modes: entry.expected_result_modes
};
}
return {
status: "matched",
reason: "route_expectation_matched",
expected_selected_recipes: entry.expected_selected_recipes,
expected_requested_result_modes: entry.expected_requested_result_modes ?? [],
expected_result_modes: entry.expected_result_modes ?? []
};
}
@@ -1473,6 +1473,11 @@ function buildAddressDebugPayload(addressDebug, llmPreDecomposeMeta = null) {
shadow_route_intent: addressDebug.shadow_route_intent ?? undefined,
shadow_route_selected_recipe: addressDebug.shadow_route_selected_recipe ?? undefined,
shadow_route_status: addressDebug.shadow_route_status ?? undefined,
route_expectation_status: addressDebug.route_expectation_status ?? undefined,
route_expectation_reason: addressDebug.route_expectation_reason ?? undefined,
route_expectation_expected_selected_recipes: addressDebug.route_expectation_expected_selected_recipes ?? undefined,
route_expectation_expected_requested_result_modes: addressDebug.route_expectation_expected_requested_result_modes ?? undefined,
route_expectation_expected_result_modes: addressDebug.route_expectation_expected_result_modes ?? undefined,
execution_lane: "address_query",
llm_decomposition_applied: Boolean(llmMeta?.applied),
llm_decomposition_attempted: Boolean(llmMeta?.attempted),