Архитектура: вернуть company activity assessment в address lane и убрать ложный organization anchor из root-scoped carryover
This commit is contained in:
@@ -65,8 +65,10 @@ function hasUnicodeCounterpartyActivityLifecycleSignal(text) {
|
||||
if (!normalized) {
|
||||
return false;
|
||||
}
|
||||
const hasActivityAssessmentCue = /(?:\u043a\u0430\u043a\s+[\p{L}\d_-]+\s+\u043e\u0446\u0435\u043d(?:\u0438\u0448\u044c|\u0438\u0442\u044c|\u0438\u0432\u0430\u0435\u0448\u044c)|\u043e\u0446\u0435\u043d(?:\u0438\u0442\u044c|\u043a\u0430)|\u043e\u0445\u0430\u0440\u0430\u043a\u0442\u0435\u0440\u0438\u0437(?:\u0443\u0435\u0448\u044c|\u043e\u0432\u0430\u0442\u044c)|\u0447\u0442\u043e\s+\u043c\u043e\u0436\u043d\u043e\s+\u0441\u043a\u0430\u0437\u0430\u0442\u044c\s+\u043e)/iu.test(normalized) &&
|
||||
/(?:\u0434\u0435\u044f\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442|\u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442|\u0440\u0430\u0431\u043e\u0442)/iu.test(normalized);
|
||||
const hasActivityAgeCue = /(?:\u0441\u043a\u043e\u043b\u044c\u043a\u043e\s+\u043b\u0435\u0442\s+\u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u0438|\u0441\u043a\u043e\u043b\u044c\u043a\u043e\s+\u043b\u0435\u0442\s+\u0432\s+\u0431\u0430\u0437\u0435|\u0432\u043e\u0437\u0440\u0430\u0441\u0442\s+\u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u0438|\u043f\u0435\u0440\u0432(?:\u0430\u044f|\u044b\u0439|\u043e\u0435)\s+(?:\u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u044c|\u043f\u043b\u0430\u0442\u0435\u0436|\u043f\u043e\u0441\u0442\u0443\u043f\u043b\u0435\u043d\u0438\u0435|\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442)|\u043f\u043e\u0441\u043b\u0435\u0434\u043d(?:\u044f\u044f|\u0438\u0439|\u0435\u0435)\s+\u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u044c|\u0441\s+\u043a\u0430\u043a\u043e\u0433\u043e\s+\u0433\u043e\u0434\u0430\s+\u0430\u043a\u0442\u0438\u0432)/iu.test(normalized);
|
||||
if (!hasActivityAgeCue) {
|
||||
if (!hasActivityAgeCue && !hasActivityAssessmentCue) {
|
||||
return false;
|
||||
}
|
||||
const hasOneCLexeme = /(?:\u0432\s+\u0431\u0430\u0437\u0435\s+1[\u0441c]|\u0432\s+1[\u0441c]\s+\u0431\u0430\u0437\u0435|\u0438\u0437\s+1[\u0441c])/iu.test(normalized);
|
||||
|
||||
@@ -721,6 +721,20 @@ function createAssistantRoutePolicy(deps) {
|
||||
let runAddressLane = Boolean(baseToolGate?.runAddressLane);
|
||||
let toolGateDecision = String(baseToolGate?.decision ?? "skip_address_lane");
|
||||
let toolGateReason = String(baseToolGate?.reason ?? "no_address_signal_after_l0");
|
||||
const semanticAddressLaneRecovery = Boolean(!runAddressLane &&
|
||||
supportedAddressRouteCandidateDetected &&
|
||||
!deepAnalysisPreferenceDetected &&
|
||||
!unsupportedAddressIntentFallbackToDeep &&
|
||||
!deepAnalysisSignalFallbackToDeep &&
|
||||
!aggregateAnalyticsFallbackToDeep &&
|
||||
!deepSessionContinuationFallbackToDeep);
|
||||
if (semanticAddressLaneRecovery) {
|
||||
runAddressLane = true;
|
||||
toolGateDecision = "run_address_lane";
|
||||
toolGateReason = resolvedIntentResolution.intent !== "unknown" || llmContractIntent
|
||||
? "address_intent_resolver_detected"
|
||||
: "address_signal_detected";
|
||||
}
|
||||
if (unsupportedAddressIntentFallbackToDeep) {
|
||||
runAddressLane = false;
|
||||
toolGateDecision = "skip_address_lane";
|
||||
|
||||
@@ -73,6 +73,7 @@ const assistantBoundaryPolicy_1 = __importStar(require("./assistantBoundaryPolic
|
||||
const assistantLivingModePolicy_1 = __importStar(require("./assistantLivingModePolicy"));
|
||||
const assistantMetaFollowupPolicy_1 = __importStar(require("./assistantMetaFollowupPolicy"));
|
||||
const assistantMemoryRecapPolicy_1 = __importStar(require("./assistantMemoryRecapPolicy"));
|
||||
const assistantContinuityPolicy_1 = __importStar(require("./assistantContinuityPolicy"));
|
||||
const assistantProviderExecutionPolicy_1 = __importStar(require("./assistantProviderExecutionPolicy"));
|
||||
const assistantRoutePolicy_1 = __importStar(require("./assistantRoutePolicy"));
|
||||
const assistantTransitionPolicy_1 = __importStar(require("./assistantTransitionPolicy"));
|
||||
@@ -2512,18 +2513,12 @@ function buildAddressFollowupOffer(addressDebug) {
|
||||
if (!Array.isArray(suggestedIntents) || suggestedIntents.length === 0) {
|
||||
return null;
|
||||
}
|
||||
const anchorType = toNonEmptyString(addressDebug.anchor_type);
|
||||
const anchorValue = toNonEmptyString(addressDebug.anchor_value_resolved) ??
|
||||
toNonEmptyString(addressDebug.anchor_value_raw) ??
|
||||
readAddressInventoryItemFilter(addressDebug) ??
|
||||
readAddressFilterString(addressDebug, "counterparty") ??
|
||||
readAddressFilterString(addressDebug, "contract") ??
|
||||
readAddressFilterString(addressDebug, "account");
|
||||
const anchorContext = (0, assistantContinuityPolicy_1.resolveAddressDebugAnchorContext)(addressDebug, toNonEmptyString);
|
||||
return {
|
||||
enabled: true,
|
||||
source_intent: intent,
|
||||
anchor_type: anchorType ?? "unknown",
|
||||
anchor_value: anchorValue,
|
||||
anchor_type: anchorContext.anchorType ?? "unknown",
|
||||
anchor_value: anchorContext.anchorValue,
|
||||
suggested_intents: suggestedIntents
|
||||
};
|
||||
}
|
||||
|
||||
@@ -344,10 +344,11 @@ function createAssistantTransitionPolicy(deps) {
|
||||
const organizationClarificationCandidates = Array.isArray(organizationAuthority.organizationClarificationCandidates)
|
||||
? organizationAuthority.organizationClarificationCandidates
|
||||
: [];
|
||||
const organizationClarificationSelection = deps.resolveOrganizationSelectionFromMessage(userMessage, organizationClarificationCandidates) ??
|
||||
const explicitOrganizationClarificationSelection = deps.resolveOrganizationSelectionFromMessage(userMessage, organizationClarificationCandidates) ??
|
||||
(deps.toNonEmptyString(alternateMessage)
|
||||
? deps.resolveOrganizationSelectionFromMessage(String(alternateMessage ?? ""), organizationClarificationCandidates)
|
||||
: null) ??
|
||||
: null);
|
||||
const organizationClarificationSelection = explicitOrganizationClarificationSelection ??
|
||||
deps.normalizeOrganizationScopeValue(organizationAuthority.organizationClarificationSelectionFromScope);
|
||||
const hasOrganizationClarificationContinuation = Boolean(lastOrganizationClarificationDebug && organizationClarificationSelection);
|
||||
const followupOffer = previousAddressDebug ? deps.buildAddressFollowupOffer(previousAddressDebug) : null;
|
||||
@@ -798,9 +799,9 @@ function createAssistantTransitionPolicy(deps) {
|
||||
previousAnchor = selectedObjectLabel;
|
||||
}
|
||||
}
|
||||
if (organizationClarificationSelection && !previousAnchor) {
|
||||
if (explicitOrganizationClarificationSelection && !previousAnchor) {
|
||||
previousAnchorType = "organization";
|
||||
previousAnchor = organizationClarificationSelection;
|
||||
previousAnchor = explicitOrganizationClarificationSelection;
|
||||
}
|
||||
if (inventoryRootFrame &&
|
||||
organizationClarificationSelection &&
|
||||
|
||||
@@ -114,11 +114,19 @@ function hasUnicodeCounterpartyActivityLifecycleSignal(text: string): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
const hasActivityAssessmentCue =
|
||||
/(?:\u043a\u0430\u043a\s+[\p{L}\d_-]+\s+\u043e\u0446\u0435\u043d(?:\u0438\u0448\u044c|\u0438\u0442\u044c|\u0438\u0432\u0430\u0435\u0448\u044c)|\u043e\u0446\u0435\u043d(?:\u0438\u0442\u044c|\u043a\u0430)|\u043e\u0445\u0430\u0440\u0430\u043a\u0442\u0435\u0440\u0438\u0437(?:\u0443\u0435\u0448\u044c|\u043e\u0432\u0430\u0442\u044c)|\u0447\u0442\u043e\s+\u043c\u043e\u0436\u043d\u043e\s+\u0441\u043a\u0430\u0437\u0430\u0442\u044c\s+\u043e)/iu.test(
|
||||
normalized
|
||||
) &&
|
||||
/(?:\u0434\u0435\u044f\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442|\u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442|\u0440\u0430\u0431\u043e\u0442)/iu.test(
|
||||
normalized
|
||||
);
|
||||
|
||||
const hasActivityAgeCue =
|
||||
/(?:\u0441\u043a\u043e\u043b\u044c\u043a\u043e\s+\u043b\u0435\u0442\s+\u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u0438|\u0441\u043a\u043e\u043b\u044c\u043a\u043e\s+\u043b\u0435\u0442\s+\u0432\s+\u0431\u0430\u0437\u0435|\u0432\u043e\u0437\u0440\u0430\u0441\u0442\s+\u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u0438|\u043f\u0435\u0440\u0432(?:\u0430\u044f|\u044b\u0439|\u043e\u0435)\s+(?:\u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u044c|\u043f\u043b\u0430\u0442\u0435\u0436|\u043f\u043e\u0441\u0442\u0443\u043f\u043b\u0435\u043d\u0438\u0435|\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442)|\u043f\u043e\u0441\u043b\u0435\u0434\u043d(?:\u044f\u044f|\u0438\u0439|\u0435\u0435)\s+\u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u044c|\u0441\s+\u043a\u0430\u043a\u043e\u0433\u043e\s+\u0433\u043e\u0434\u0430\s+\u0430\u043a\u0442\u0438\u0432)/iu.test(
|
||||
normalized
|
||||
);
|
||||
if (!hasActivityAgeCue) {
|
||||
if (!hasActivityAgeCue && !hasActivityAssessmentCue) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -760,6 +760,20 @@ export function createAssistantRoutePolicy(deps) {
|
||||
let runAddressLane = Boolean(baseToolGate?.runAddressLane);
|
||||
let toolGateDecision = String(baseToolGate?.decision ?? "skip_address_lane");
|
||||
let toolGateReason = String(baseToolGate?.reason ?? "no_address_signal_after_l0");
|
||||
const semanticAddressLaneRecovery = Boolean(!runAddressLane &&
|
||||
supportedAddressRouteCandidateDetected &&
|
||||
!deepAnalysisPreferenceDetected &&
|
||||
!unsupportedAddressIntentFallbackToDeep &&
|
||||
!deepAnalysisSignalFallbackToDeep &&
|
||||
!aggregateAnalyticsFallbackToDeep &&
|
||||
!deepSessionContinuationFallbackToDeep);
|
||||
if (semanticAddressLaneRecovery) {
|
||||
runAddressLane = true;
|
||||
toolGateDecision = "run_address_lane";
|
||||
toolGateReason = resolvedIntentResolution.intent !== "unknown" || llmContractIntent
|
||||
? "address_intent_resolver_detected"
|
||||
: "address_signal_detected";
|
||||
}
|
||||
if (unsupportedAddressIntentFallbackToDeep) {
|
||||
runAddressLane = false;
|
||||
toolGateDecision = "skip_address_lane";
|
||||
|
||||
@@ -26,6 +26,7 @@ import * as assistantBoundaryPolicy_1 from "./assistantBoundaryPolicy";
|
||||
import * as assistantLivingModePolicy_1 from "./assistantLivingModePolicy";
|
||||
import * as assistantMetaFollowupPolicy_1 from "./assistantMetaFollowupPolicy";
|
||||
import * as assistantMemoryRecapPolicy_1 from "./assistantMemoryRecapPolicy";
|
||||
import * as assistantContinuityPolicy_1 from "./assistantContinuityPolicy";
|
||||
import * as assistantProviderExecutionPolicy_1 from "./assistantProviderExecutionPolicy";
|
||||
import * as assistantRoutePolicy_1 from "./assistantRoutePolicy";
|
||||
import * as assistantTransitionPolicy_1 from "./assistantTransitionPolicy";
|
||||
@@ -2467,18 +2468,12 @@ function buildAddressFollowupOffer(addressDebug) {
|
||||
if (!Array.isArray(suggestedIntents) || suggestedIntents.length === 0) {
|
||||
return null;
|
||||
}
|
||||
const anchorType = toNonEmptyString(addressDebug.anchor_type);
|
||||
const anchorValue = toNonEmptyString(addressDebug.anchor_value_resolved) ??
|
||||
toNonEmptyString(addressDebug.anchor_value_raw) ??
|
||||
readAddressInventoryItemFilter(addressDebug) ??
|
||||
readAddressFilterString(addressDebug, "counterparty") ??
|
||||
readAddressFilterString(addressDebug, "contract") ??
|
||||
readAddressFilterString(addressDebug, "account");
|
||||
const anchorContext = (0, assistantContinuityPolicy_1.resolveAddressDebugAnchorContext)(addressDebug, toNonEmptyString);
|
||||
return {
|
||||
enabled: true,
|
||||
source_intent: intent,
|
||||
anchor_type: anchorType ?? "unknown",
|
||||
anchor_value: anchorValue,
|
||||
anchor_type: anchorContext.anchorType ?? "unknown",
|
||||
anchor_value: anchorContext.anchorValue,
|
||||
suggested_intents: suggestedIntents
|
||||
};
|
||||
}
|
||||
|
||||
@@ -431,11 +431,13 @@ export function createAssistantTransitionPolicy(deps) {
|
||||
const organizationClarificationCandidates = Array.isArray(organizationAuthority.organizationClarificationCandidates)
|
||||
? organizationAuthority.organizationClarificationCandidates
|
||||
: [];
|
||||
const organizationClarificationSelection =
|
||||
const explicitOrganizationClarificationSelection =
|
||||
deps.resolveOrganizationSelectionFromMessage(userMessage, organizationClarificationCandidates) ??
|
||||
(deps.toNonEmptyString(alternateMessage)
|
||||
? deps.resolveOrganizationSelectionFromMessage(String(alternateMessage ?? ""), organizationClarificationCandidates)
|
||||
: null) ??
|
||||
: null);
|
||||
const organizationClarificationSelection =
|
||||
explicitOrganizationClarificationSelection ??
|
||||
deps.normalizeOrganizationScopeValue(organizationAuthority.organizationClarificationSelectionFromScope);
|
||||
const hasOrganizationClarificationContinuation = Boolean(
|
||||
lastOrganizationClarificationDebug && organizationClarificationSelection
|
||||
@@ -979,9 +981,9 @@ export function createAssistantTransitionPolicy(deps) {
|
||||
previousAnchor = selectedObjectLabel;
|
||||
}
|
||||
}
|
||||
if (organizationClarificationSelection && !previousAnchor) {
|
||||
if (explicitOrganizationClarificationSelection && !previousAnchor) {
|
||||
previousAnchorType = "organization";
|
||||
previousAnchor = organizationClarificationSelection;
|
||||
previousAnchor = explicitOrganizationClarificationSelection;
|
||||
}
|
||||
if (
|
||||
inventoryRootFrame &&
|
||||
|
||||
@@ -66,6 +66,18 @@ describe("address counterparty utf8 regression", () => {
|
||||
it("keeps the main resolver in the supported contour for direct company activity-age wording", () => {
|
||||
const result = resolveAddressIntent("а по Альтернативе Плюс сколько лет активности в базе 1С?");
|
||||
|
||||
expect(result.intent).toBe("counterparty_activity_lifecycle");
|
||||
});
|
||||
it("classifies company activity assessment wording into lifecycle intent", () => {
|
||||
const result = resolveCounterpartyAddressIntent("Как ты оценишь деятельность компании?", utf8Deps);
|
||||
|
||||
expect(result?.intent).toBe("counterparty_activity_lifecycle");
|
||||
expect(result?.reasons).toContain("counterparty_activity_lifecycle_signal_detected");
|
||||
});
|
||||
|
||||
it("keeps the main resolver in the supported contour for company activity assessment wording", () => {
|
||||
const result = resolveAddressIntent("Как ты оценишь деятельность компании?");
|
||||
|
||||
expect(result.intent).toBe("counterparty_activity_lifecycle");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -406,4 +406,69 @@ describe("assistantRoutePolicy", () => {
|
||||
expect(decision.orchestrationContract?.hard_meta_mode).toBeNull();
|
||||
expect(decision.orchestrationContract?.followup_context_detected).toBe(false);
|
||||
});
|
||||
|
||||
it("keeps company activity assessment follow-up in address lane when lifecycle intent is resolved from grounded continuity", () => {
|
||||
const policy = buildPolicy({
|
||||
resolveAddressIntent: () => ({ intent: "counterparty_activity_lifecycle", confidence: "high" }),
|
||||
findLastGroundedAddressAnswerDebug: () => ({
|
||||
execution_lane: "address_query",
|
||||
answer_grounding_check: { status: "grounded" },
|
||||
detected_intent: "counterparty_activity_lifecycle",
|
||||
extracted_filters: {
|
||||
organization: 'ООО "Альтернатива Плюс"',
|
||||
period_to: "2026-04-18"
|
||||
}
|
||||
}),
|
||||
resolveAddressToolGateDecision: () => ({
|
||||
runAddressLane: false,
|
||||
decision: "skip_address_lane",
|
||||
reason: "no_address_signal_after_l0"
|
||||
})
|
||||
});
|
||||
|
||||
const decision = policy.resolveAssistantOrchestrationDecision({
|
||||
rawUserMessage: "Как ты оценишь деятельность компании?",
|
||||
effectiveAddressUserMessage: "Как ты оценишь деятельность компании?",
|
||||
followupContext: null,
|
||||
llmPreDecomposeMeta: {
|
||||
applied: true,
|
||||
predecomposeContract: {
|
||||
mode: "unsupported",
|
||||
mode_confidence: "low",
|
||||
intent: "unknown",
|
||||
intent_confidence: "low"
|
||||
},
|
||||
semanticExtractionContract: {
|
||||
valid: true,
|
||||
apply_canonical_recommended: true,
|
||||
extraction: {
|
||||
query_shape: "UNKNOWN",
|
||||
aggregation_profile: "unknown"
|
||||
},
|
||||
guard_hints: {
|
||||
deep_investigation_signal_detected: false
|
||||
}
|
||||
}
|
||||
},
|
||||
sessionItems: [
|
||||
{
|
||||
role: "assistant",
|
||||
debug: {
|
||||
execution_lane: "address_query",
|
||||
answer_grounding_check: { status: "grounded" },
|
||||
detected_intent: "counterparty_activity_lifecycle",
|
||||
extracted_filters: {
|
||||
organization: 'ООО "Альтернатива Плюс"',
|
||||
period_to: "2026-04-18"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
useMock: false
|
||||
});
|
||||
|
||||
expect(decision.runAddressLane).toBe(true);
|
||||
expect(decision.toolGateDecision).toBe("run_address_lane");
|
||||
expect(decision.livingMode).toBe("address_data");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user