АРЧ АП11 - Вынести meta и memory recap policy из route runtime и закрыть Phase 5 агентным прогоном
This commit is contained in:
@@ -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" })
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user