АРЧ АП11 - Вынести meta и memory recap policy из route runtime и закрыть Phase 5 агентным прогоном

This commit is contained in:
2026-04-17 11:39:45 +03:00
parent b3572d9d11
commit 15fa643fc8
17 changed files with 1342 additions and 142 deletions
@@ -0,0 +1,90 @@
import { describe, expect, it } from "vitest";
import {
buildAddressMemoryRecapReply,
createAssistantMemoryRecapPolicy,
resolveAssistantLivingChatMemoryContext
} from "../src/services/assistantMemoryRecapPolicy";
const policy = createAssistantMemoryRecapPolicy({
hasHistoricalCapabilityFollowupSignal: (text: unknown) =>
/историческ|архив|раньше/i.test(String(text ?? "")),
hasConversationMemoryRecallFollowupSignal: (text: unknown) =>
/помнишь|remember/i.test(String(text ?? "")),
isGroundedInventoryContextDebug: (debug: unknown) =>
String((debug as Record<string, unknown> | null)?.detected_intent ?? "") === "inventory_on_hand_as_of_date"
});
describe("assistantMemoryRecapPolicy", () => {
it("detects contextual historical capability follow-up", () => {
const signals = policy.resolveRouteMemorySignals({
rawUserMessage: "а исторические остатки тоже можешь?",
repairedRawUserMessage: "",
effectiveAddressUserMessage: "",
repairedEffectiveAddressUserMessage: "",
dataScopeMetaQuery: false,
capabilityMetaQuery: true,
dataRetrievalSignal: false,
strongDataSignal: false,
aggregateBusinessAnalyticsSignal: false,
lastGroundedAddressDebug: {
detected_intent: "inventory_on_hand_as_of_date"
},
hasPriorAddressDebug: true
});
expect(signals.contextualHistoricalCapabilityFollowupDetected).toBe(true);
expect(signals.contextualMemoryRecapFollowupDetected).toBe(false);
});
it("detects contextual memory recap over prior address debug", () => {
const signals = policy.resolveRouteMemorySignals({
rawUserMessage: "а ты помнишь что мы обсуждали?",
repairedRawUserMessage: "",
effectiveAddressUserMessage: "",
repairedEffectiveAddressUserMessage: "",
dataScopeMetaQuery: false,
capabilityMetaQuery: false,
dataRetrievalSignal: false,
strongDataSignal: false,
aggregateBusinessAnalyticsSignal: false,
lastGroundedAddressDebug: null,
hasPriorAddressDebug: true
});
expect(signals.contextualHistoricalCapabilityFollowupDetected).toBe(false);
expect(signals.contextualMemoryRecapFollowupDetected).toBe(true);
});
it("builds deterministic recap from prior selected object context", () => {
const context = resolveAssistantLivingChatMemoryContext({
modeDecisionReason: "memory_recap_followup_detected",
sessionItems: [
{
role: "assistant",
debug: {
execution_lane: "address_query",
anchor_type: "item",
anchor_value_resolved: "Рабочая станция",
extracted_filters: {
item: "Рабочая станция",
as_of_date: "2022-02-28"
}
}
}
]
});
const reply = buildAddressMemoryRecapReply({
organization: null,
addressDebug: context.lastMemoryAddressDebug,
toNonEmptyString: (value: unknown) => {
const text = String(value ?? "").trim();
return text.length > 0 ? text : null;
}
});
expect(context.contextualMemoryRecapFollowup).toBe(true);
expect(reply).toContain("Рабочая станция");
expect(reply).toContain("28.02.2022");
});
});
@@ -0,0 +1,75 @@
import { describe, expect, it } from "vitest";
import { createAssistantMetaFollowupPolicy } from "../src/services/assistantMetaFollowupPolicy";
const policy = createAssistantMetaFollowupPolicy({
hasAssistantDataScopeMetaQuestionSignal: (text: unknown) =>
/по какой компании|какая база/i.test(String(text ?? "")),
shouldHandleAsAssistantCapabilityMetaQuery: (text: unknown) =>
/что ты можешь|что ты умеешь/i.test(String(text ?? "")),
hasMetaAnswerFollowupSignal: (text: unknown) =>
/это норм|что думаешь/i.test(String(text ?? ""))
});
describe("assistantMetaFollowupPolicy", () => {
it("collects meta signals across message variants", () => {
const signals = policy.resolveMetaSignalSet({
rawUserMessage: "",
repairedRawUserMessage: "",
effectiveAddressUserMessage: "по какой компании мы можем работать?",
repairedEffectiveAddressUserMessage: ""
});
expect(signals.dataScopeMetaQuery).toBe(true);
expect(signals.capabilityMetaQuery).toBe(false);
expect(signals.metaAnswerFollowupSignal).toBe(false);
});
it("treats historical capability phrasing as capability meta follow-up", () => {
const signals = policy.resolveMetaSignalSet({
rawUserMessage: "а исторические остатки тоже можешь?",
repairedRawUserMessage: "",
effectiveAddressUserMessage: "",
repairedEffectiveAddressUserMessage: ""
});
expect(signals.capabilityMetaQuery).toBe(true);
});
it("resolves hard meta mode with data retrieval guard", () => {
expect(
policy.resolveHardMetaMode({
dataScopeMetaQuery: true,
capabilityMetaQuery: false,
dataRetrievalSignal: false
})
).toBe("data_scope");
expect(
policy.resolveHardMetaMode({
dataScopeMetaQuery: false,
capabilityMetaQuery: true,
dataRetrievalSignal: true
})
).toBeNull();
});
it("detects evaluative meta follow-up over grounded answer", () => {
const detected = policy.isMetaFollowupOverGroundedAnswer({
followupContext: { previous_intent: "vat_payable_forecast" },
hasPriorAddressAnswerContext: true,
metaAnswerFollowupSignal: true,
vatEvaluativeFollowupSignal: false,
dataScopeMetaQuery: false,
capabilityMetaQuery: false,
aggregateBusinessAnalyticsSignal: false,
dataRetrievalSignal: false,
strongDataSignal: false,
resolvedMode: "unsupported",
resolvedIntent: "unknown",
llmContractIntent: "unknown",
llmContractMode: "unsupported"
});
expect(detected).toBe(true);
});
});
@@ -32,8 +32,35 @@ function buildPolicy(overrides: Record<string, unknown> = {}) {
),
normalizeOrganizationScopeValue,
resolveOrganizationSelectionFromMessage: () => null,
hasAssistantDataScopeMetaQuestionSignal: (text: string) => /по какой компании|какая база|по каким конторам/i.test(text),
shouldHandleAsAssistantCapabilityMetaQuery: (text: string) => /что ты можешь|что ты умеешь/i.test(text),
resolveMetaSignalSet: (input: {
rawUserMessage?: string;
repairedRawUserMessage?: string;
effectiveAddressUserMessage?: string;
repairedEffectiveAddressUserMessage?: string;
}) => {
const samples = [
input.rawUserMessage,
input.repairedRawUserMessage,
input.effectiveAddressUserMessage,
input.repairedEffectiveAddressUserMessage
].join(" ");
return {
dataScopeMetaQuery: /по какой компании|какая база|по каким конторам/i.test(samples),
capabilityMetaQuery: /что ты можешь|что ты умеешь/i.test(samples),
metaAnswerFollowupSignal: /это норм|что думаешь/i.test(samples)
};
},
resolveHardMetaMode: (input: {
dataScopeMetaQuery?: boolean;
capabilityMetaQuery?: boolean;
dataRetrievalSignal?: boolean;
}) =>
input.dataScopeMetaQuery
? "data_scope"
: input.capabilityMetaQuery && !input.dataRetrievalSignal
? "capability"
: null,
isMetaFollowupOverGroundedAnswer: () => false,
hasDataRetrievalRequestSignal: () => false,
hasAggregateBusinessAnalyticsSignal: () => false,
hasStandaloneAddressTopicSignal: () => false,
@@ -49,11 +76,11 @@ function buildPolicy(overrides: Record<string, unknown> = {}) {
hasShortDebtMirrorFollowupSignal: () => false,
isInventorySelectedObjectIntent: (intent: unknown) => /inventory/i.test(String(intent ?? "")),
hasShortInventoryObjectFollowupSignal: () => false,
hasHistoricalCapabilityFollowupSignal: () => false,
isGroundedInventoryContextDebug: (debug: unknown) => Boolean(debug),
hasConversationMemoryRecallFollowupSignal: () => false,
resolveRouteMemorySignals: () => ({
contextualHistoricalCapabilityFollowupDetected: false,
contextualMemoryRecapFollowupDetected: false
}),
findLastAddressAssistantItem: () => null,
hasMetaAnswerFollowupSignal: () => false,
resolveAddressToolGateDecision: () => ({
runAddressLane: false,
decision: "skip_address_lane",
@@ -118,7 +145,10 @@ describe("assistantRoutePolicy", () => {
it("routes memory recap follow-up over grounded answer to chat", () => {
const policy = buildPolicy({
hasConversationMemoryRecallFollowupSignal: () => true,
resolveRouteMemorySignals: () => ({
contextualHistoricalCapabilityFollowupDetected: false,
contextualMemoryRecapFollowupDetected: true
}),
findLastGroundedAddressAnswerDebug: () => ({ execution_lane: "address_query" })
});