АРЧ - Усилить root-frame возврат, memory recap и colloquial provenance follow-up
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { runAddressDecomposeStage } from "../src/services/address_runtime/decomposeStage";
|
||||
|
||||
describe("inventory root frame regressions", () => {
|
||||
it("restores inventory root frame for restatement on the same period after foreign domain drift", () => {
|
||||
const result = runAddressDecomposeStage(
|
||||
"ладно ок. покажи мне еще раз позиции на складе на тот же период рассмотрения",
|
||||
{
|
||||
previous_intent: "customer_revenue_and_payments",
|
||||
previous_filters: {
|
||||
organization: "ООО \\Альтернатива Плюс\\"
|
||||
},
|
||||
previous_anchor_type: "organization",
|
||||
previous_anchor_value: "ООО \\Альтернатива Плюс\\",
|
||||
root_intent: "inventory_on_hand_as_of_date",
|
||||
root_filters: {
|
||||
organization: "ООО \\Альтернатива Плюс\\",
|
||||
period_from: "2022-02-01",
|
||||
period_to: "2022-02-28",
|
||||
as_of_date: "2022-02-28"
|
||||
},
|
||||
root_anchor_type: "organization",
|
||||
root_anchor_value: "ООО \\Альтернатива Плюс\\",
|
||||
current_frame_kind: "generic"
|
||||
}
|
||||
);
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.intent.intent).toBe("inventory_on_hand_as_of_date");
|
||||
expect(result?.intent.reasons).toContain("intent_restored_to_inventory_root_frame");
|
||||
expect(result?.filters.extracted_filters.organization).toBe("ООО \\Альтернатива Плюс\\");
|
||||
expect(result?.filters.extracted_filters.period_from).toBe("2022-02-01");
|
||||
expect(result?.filters.extracted_filters.period_to).toBe("2022-02-28");
|
||||
expect(result?.filters.extracted_filters.as_of_date).toBe("2022-02-28");
|
||||
expect(result?.filters.warnings).toContain("period_from_from_followup_context");
|
||||
expect(result?.filters.warnings).toContain("period_to_from_followup_context");
|
||||
});
|
||||
|
||||
it("promotes selected-object provenance slang with 'где мы взяли это' into inventory provenance", () => {
|
||||
const result = runAddressDecomposeStage(
|
||||
'По выбранному объекту "Зеркало для инвалидов поворотное травмобезопасное": где мы взяли это говнище?',
|
||||
{
|
||||
previous_intent: "inventory_on_hand_as_of_date",
|
||||
previous_filters: {
|
||||
organization: "ООО \\Альтернатива Плюс\\",
|
||||
warehouse: "Основной склад",
|
||||
period_from: "2022-02-01",
|
||||
period_to: "2022-02-28",
|
||||
as_of_date: "2022-02-28"
|
||||
},
|
||||
previous_anchor_type: "unknown",
|
||||
previous_anchor_value: null
|
||||
}
|
||||
);
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.intent.intent).toBe("inventory_purchase_provenance_for_item");
|
||||
expect(result?.filters.extracted_filters.item).toBe("Зеркало для инвалидов поворотное травмобезопасное");
|
||||
expect(result?.filters.extracted_filters.as_of_date).toBe("2022-02-28");
|
||||
expect(
|
||||
result?.baseReasons?.includes("intent_adjusted_to_inventory_followup_context") ||
|
||||
result?.intent.reasons.includes("inventory_selected_object_provenance_signal_detected")
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -23,6 +23,19 @@ describe("inventory warehouse anchor extraction", () => {
|
||||
expect(filters.as_of_date).toBe("2019-03-31");
|
||||
expect(filters.warehouse).toBeUndefined();
|
||||
});
|
||||
|
||||
it("does not materialize 'за май' as warehouse in inventory balance phrasing from the run", () => {
|
||||
const filters = extractAddressFilters(
|
||||
"проверить остатки по складу за май 2020 года",
|
||||
"inventory_on_hand_as_of_date"
|
||||
).extracted_filters;
|
||||
|
||||
expect(filters.period_from).toBe("2020-05-01");
|
||||
expect(filters.period_to).toBe("2020-05-31");
|
||||
expect(filters.as_of_date).toBe("2020-05-31");
|
||||
expect(filters.warehouse).toBeUndefined();
|
||||
});
|
||||
|
||||
it("treats 'у нас' as implicit self-scope instead of literal warehouse anchor", () => {
|
||||
const result = extractAddressFilters("что на складе у нас", "inventory_on_hand_as_of_date");
|
||||
|
||||
|
||||
@@ -2058,5 +2058,323 @@ describe("assistant address follow-up carryover", () => {
|
||||
expect(calls[1].options?.followupContext?.root_filters?.organization).toBe("ООО Альтернатива Плюс");
|
||||
expect(normalizerService.normalize).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("sanitizes selected-item carryover when inventory drilldown pivots into VAT follow-up", async () => {
|
||||
const calls: Array<{ message: string; options?: any }> = [];
|
||||
const followupMessage = "\u0430 \u043d\u0434\u0441?";
|
||||
const itemLabel =
|
||||
"\u041a\u0440\u043e\u043c\u043a\u0430 \u0441 \u043a\u043b\u0435\u0435\u043c 33 \u0434\u0443\u0431 \u043d\u0438\u0430\u0433\u0430\u0440\u0430 137 \u043c";
|
||||
const organization = "\u041e\u041e\u041e \\\u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441\\";
|
||||
const warehouse = "\u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0441\u043a\u043b\u0430\u0434";
|
||||
|
||||
const vatResult = buildAddressLaneResult({
|
||||
debug: {
|
||||
...buildAddressLaneResult().debug,
|
||||
detected_intent: "vat_payable_confirmed_as_of_date",
|
||||
extracted_filters: {
|
||||
sort: "period_desc",
|
||||
period_from: "2021-03-01",
|
||||
period_to: "2021-03-31",
|
||||
as_of_date: "2021-03-31",
|
||||
organization
|
||||
},
|
||||
selected_recipe: "address_vat_payable_confirmed_as_of_date_v1",
|
||||
response_type: "FACTUAL_SUMMARY",
|
||||
requested_result_mode: "confirmed_balance",
|
||||
result_mode: "confirmed_balance",
|
||||
balance_confirmed: true,
|
||||
reasons: [
|
||||
"address_action_detected",
|
||||
"address_entity_detected",
|
||||
"address_followup_context_applied"
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
const addressQueryService = {
|
||||
tryHandle: vi.fn(async (message: string, options?: any) => {
|
||||
calls.push({ message, options });
|
||||
return vatResult;
|
||||
})
|
||||
} as any;
|
||||
|
||||
const normalizerService = {
|
||||
normalize: vi.fn(async () => ({
|
||||
assistant_reply: "normalizer_fallback_should_not_be_used",
|
||||
reply_type: "partial_coverage",
|
||||
debug: {}
|
||||
}))
|
||||
} as any;
|
||||
|
||||
const sessions = new AssistantSessionStore();
|
||||
const service = new AssistantService(
|
||||
normalizerService,
|
||||
sessions as any,
|
||||
{} as any,
|
||||
{ persistSession: vi.fn() } as any,
|
||||
addressQueryService
|
||||
);
|
||||
|
||||
const sessionId = `asst-address-followup-inventory-vat-pivot-${Date.now()}`;
|
||||
sessions.appendItem(sessionId, {
|
||||
message_id: "msg-inventory-root-seed",
|
||||
session_id: sessionId,
|
||||
role: "assistant",
|
||||
text: "inventory root seed",
|
||||
reply_type: "factual",
|
||||
created_at: "2026-04-15T14:01:00.000Z",
|
||||
trace_id: "address-root-seed",
|
||||
debug: {
|
||||
detected_mode: "address_query",
|
||||
detected_intent: "inventory_on_hand_as_of_date",
|
||||
extracted_filters: {
|
||||
as_of_date: "2021-03-31",
|
||||
period_from: "2021-03-01",
|
||||
period_to: "2021-03-31",
|
||||
organization,
|
||||
warehouse
|
||||
},
|
||||
selected_recipe: "address_inventory_on_hand_as_of_date_v1"
|
||||
}
|
||||
} as any);
|
||||
sessions.appendItem(sessionId, {
|
||||
message_id: "msg-inventory-sale-seed",
|
||||
session_id: sessionId,
|
||||
role: "assistant",
|
||||
text: "inventory sale trace seed",
|
||||
reply_type: "factual",
|
||||
created_at: "2026-04-15T14:02:00.000Z",
|
||||
trace_id: "address-sale-seed",
|
||||
debug: {
|
||||
detected_mode: "address_query",
|
||||
detected_intent: "inventory_sale_trace_for_item",
|
||||
extracted_filters: {
|
||||
item: itemLabel,
|
||||
organization,
|
||||
as_of_date: "2021-03-31"
|
||||
},
|
||||
selected_recipe: "address_inventory_sale_trace_for_item_v1",
|
||||
anchor_type: "item",
|
||||
anchor_value_raw: itemLabel,
|
||||
anchor_value_resolved: itemLabel
|
||||
}
|
||||
} as any);
|
||||
|
||||
const second = await service.handleMessage({
|
||||
session_id: sessionId,
|
||||
user_message: followupMessage,
|
||||
useMock: true
|
||||
} as any);
|
||||
|
||||
expect(second.ok).toBe(true);
|
||||
expect(second.reply_type).toBe("factual");
|
||||
expect(second.debug?.detected_intent).toBe("vat_payable_confirmed_as_of_date");
|
||||
expect(calls).toHaveLength(1);
|
||||
expect(calls[0].message).toBe(followupMessage);
|
||||
expect(calls[0].options?.followupContext?.root_context_only).toBe(true);
|
||||
expect(calls[0].options?.followupContext?.previous_intent).toBeUndefined();
|
||||
expect(calls[0].options?.followupContext?.previous_anchor_type).toBeUndefined();
|
||||
expect(calls[0].options?.followupContext?.previous_anchor_value).toBeNull();
|
||||
expect(calls[0].options?.followupContext?.previous_filters?.item).toBeUndefined();
|
||||
expect(calls[0].options?.followupContext?.previous_filters?.organization).toBe(organization);
|
||||
expect(calls[0].options?.followupContext?.previous_filters?.warehouse).toBe(warehouse);
|
||||
expect(calls[0].options?.followupContext?.previous_filters?.as_of_date).toBe("2021-03-31");
|
||||
expect(calls[0].options?.followupContext?.previous_filters?.period_from).toBe("2021-03-01");
|
||||
expect(calls[0].options?.followupContext?.previous_filters?.period_to).toBe("2021-03-31");
|
||||
expect(calls[0].options?.followupContext?.root_intent).toBe("inventory_on_hand_as_of_date");
|
||||
expect(calls[0].options?.followupContext?.root_filters?.organization).toBe(organization);
|
||||
expect(calls[0].options?.followupContext?.root_filters?.as_of_date).toBe("2021-03-31");
|
||||
expect(calls[0].options?.followupContext?.root_filters?.period_from).toBe("2021-03-01");
|
||||
expect(calls[0].options?.followupContext?.root_filters?.period_to).toBe("2021-03-31");
|
||||
expect(calls[0].options?.followupContext?.current_frame_kind).toBe("inventory_root");
|
||||
expect(normalizerService.normalize).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("treats short supplier follow-up after sale trace as continuation of the active selected object", async () => {
|
||||
const calls: Array<{ message: string; options?: any }> = [];
|
||||
const followupMessage = "а купили у кого";
|
||||
const saleTraceResult = {
|
||||
handled: true,
|
||||
reply_text: "По позиции Столешница 600*3050*26 дуб ниагара подтвержден покупатель: ООО \\Ромашка\\.",
|
||||
reply_type: "factual",
|
||||
response_type: "FACTUAL_LIST",
|
||||
debug: {
|
||||
detected_mode: "address_query",
|
||||
detected_intent: "inventory_sale_trace_for_item",
|
||||
detected_intent_confidence: "medium",
|
||||
extracted_filters: {
|
||||
item: "Столешница 600*3050*26 дуб ниагара",
|
||||
organization: "ООО \\Альтернатива Плюс\\",
|
||||
as_of_date: "2020-05-31"
|
||||
},
|
||||
selected_recipe: "address_inventory_sale_trace_for_item_v1",
|
||||
anchor_type: "item",
|
||||
anchor_value_raw: "Столешница 600*3050*26 дуб ниагара",
|
||||
anchor_value_resolved: "Столешница 600*3050*26 дуб ниагара",
|
||||
reasons: ["address_action_detected", "address_entity_detected"]
|
||||
}
|
||||
} as any;
|
||||
const provenanceResult = {
|
||||
handled: true,
|
||||
reply_text: "По позиции Столешница 600*3050*26 дуб ниагара подтвержден поставщик: Торговый дом \\Союз\\.",
|
||||
reply_type: "factual",
|
||||
response_type: "FACTUAL_SUMMARY",
|
||||
debug: {
|
||||
detected_mode: "address_query",
|
||||
detected_intent: "inventory_purchase_provenance_for_item",
|
||||
detected_intent_confidence: "medium",
|
||||
extracted_filters: {
|
||||
item: "Столешница 600*3050*26 дуб ниагара",
|
||||
organization: "ООО \\Альтернатива Плюс\\",
|
||||
as_of_date: "2020-05-31"
|
||||
},
|
||||
selected_recipe: "address_inventory_purchase_provenance_for_item_v1",
|
||||
reasons: ["address_action_detected", "address_entity_detected", "address_followup_context_applied"]
|
||||
}
|
||||
} as any;
|
||||
|
||||
const addressQueryService = {
|
||||
tryHandle: vi.fn(async (message: string, options?: any) => {
|
||||
calls.push({ message, options });
|
||||
if (message === followupMessage && options?.followupContext) {
|
||||
return provenanceResult;
|
||||
}
|
||||
return saleTraceResult;
|
||||
})
|
||||
} as any;
|
||||
|
||||
const normalizerService = {
|
||||
normalize: vi.fn(async () => ({
|
||||
assistant_reply: "normalizer_fallback_should_not_be_used",
|
||||
reply_type: "partial_coverage",
|
||||
debug: {}
|
||||
}))
|
||||
} as any;
|
||||
|
||||
const sessions = new AssistantSessionStore();
|
||||
const service = new AssistantService(
|
||||
normalizerService,
|
||||
sessions as any,
|
||||
{} as any,
|
||||
{ persistSession: vi.fn() } as any,
|
||||
addressQueryService
|
||||
);
|
||||
|
||||
const sessionId = `asst-address-followup-sale-to-supplier-${Date.now()}`;
|
||||
sessions.appendItem(sessionId, {
|
||||
message_id: "msg-sale-trace-seed",
|
||||
session_id: sessionId,
|
||||
role: "assistant",
|
||||
text: saleTraceResult.reply_text,
|
||||
reply_type: saleTraceResult.reply_type,
|
||||
created_at: "2026-04-15T18:00:00.000Z",
|
||||
trace_id: "address-sale-seed",
|
||||
debug: saleTraceResult.debug
|
||||
} as any);
|
||||
|
||||
const second = await service.handleMessage({
|
||||
session_id: sessionId,
|
||||
user_message: followupMessage,
|
||||
useMock: true
|
||||
} as any);
|
||||
|
||||
expect(second.ok).toBe(true);
|
||||
expect(second.reply_type).toBe("factual");
|
||||
expect(calls).toHaveLength(1);
|
||||
expect(calls[0].message).toBe(followupMessage);
|
||||
expect(calls[0].options?.followupContext?.previous_intent).toBe("inventory_sale_trace_for_item");
|
||||
expect(calls[0].options?.followupContext?.previous_filters?.item).toBe("Столешница 600*3050*26 дуб ниагара");
|
||||
expect(calls[0].options?.followupContext?.previous_filters?.organization).toBe("ООО \\Альтернатива Плюс\\");
|
||||
expect(calls[0].options?.followupContext?.previous_filters?.as_of_date).toBe("2020-05-31");
|
||||
expect(normalizerService.normalize).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not carry VAT previous_intent into a fresh inventory root query", async () => {
|
||||
const calls: Array<{ message: string; options?: any }> = [];
|
||||
const firstMessage = "прогноз ндс на март 2020";
|
||||
const secondMessage = "остаток на складе за май 2020";
|
||||
|
||||
const addressQueryService = {
|
||||
tryHandle: vi.fn(async (message: string, options?: any) => {
|
||||
calls.push({ message, options });
|
||||
if (message === firstMessage) {
|
||||
return {
|
||||
handled: true,
|
||||
reply_text: "Прогноз НДС на март 2020 собран.",
|
||||
reply_type: "factual",
|
||||
response_type: "FACTUAL_SUMMARY",
|
||||
debug: {
|
||||
detected_mode: "address_query",
|
||||
detected_intent: "vat_payable_forecast",
|
||||
detected_intent_confidence: "high",
|
||||
extracted_filters: {
|
||||
period_from: "2020-03-01",
|
||||
period_to: "2020-03-31"
|
||||
},
|
||||
selected_recipe: "address_vat_payable_forecast_v1"
|
||||
}
|
||||
};
|
||||
}
|
||||
return {
|
||||
handled: true,
|
||||
reply_text: "Нужно уточнить организацию.",
|
||||
reply_type: "partial_coverage",
|
||||
response_type: "LIMITED_WITH_REASON",
|
||||
debug: {
|
||||
detected_mode: "address_query",
|
||||
detected_intent: "inventory_on_hand_as_of_date",
|
||||
detected_intent_confidence: "high",
|
||||
extracted_filters: {
|
||||
period_from: "2020-05-01",
|
||||
period_to: "2020-05-31",
|
||||
as_of_date: "2020-05-31"
|
||||
},
|
||||
selected_recipe: null,
|
||||
limited_reason_category: "missing_anchor",
|
||||
reasons: ["organization_clarification_required", "multiple_known_organizations_detected"]
|
||||
}
|
||||
};
|
||||
})
|
||||
} as any;
|
||||
|
||||
const normalizerService = {
|
||||
normalize: vi.fn(async () => ({
|
||||
assistant_reply: "normalizer_fallback_should_not_be_used",
|
||||
reply_type: "partial_coverage",
|
||||
debug: {}
|
||||
}))
|
||||
} as any;
|
||||
|
||||
const sessions = new AssistantSessionStore();
|
||||
const service = new AssistantService(
|
||||
normalizerService,
|
||||
sessions as any,
|
||||
{} as any,
|
||||
{ persistSession: vi.fn() } as any,
|
||||
addressQueryService
|
||||
);
|
||||
|
||||
const sessionId = `asst-address-followup-vat-inventory-${Date.now()}`;
|
||||
const first = await service.handleMessage({
|
||||
session_id: sessionId,
|
||||
user_message: firstMessage,
|
||||
useMock: true
|
||||
} as any);
|
||||
expect(first.ok).toBe(true);
|
||||
|
||||
const second = await service.handleMessage({
|
||||
session_id: sessionId,
|
||||
user_message: secondMessage,
|
||||
useMock: true
|
||||
} as any);
|
||||
|
||||
expect(second.ok).toBe(true);
|
||||
expect(calls.length).toBeGreaterThanOrEqual(2);
|
||||
const inventoryCalls = calls.slice(1);
|
||||
expect(inventoryCalls.every((call) => call.message === secondMessage)).toBe(true);
|
||||
expect(inventoryCalls.every((call) => call.options?.followupContext === undefined)).toBe(true);
|
||||
expect(normalizerService.normalize).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -133,4 +133,35 @@ describe("assistant living chat runtime adapter", () => {
|
||||
expect(output.debug?.living_chat_response_source).toBe("llm_chat_grounding_guard");
|
||||
expect(executeLlmChat).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("builds deterministic memory recap for prior selected-object address context", async () => {
|
||||
const executeLlmChat = vi.fn(async () => "raw-llm");
|
||||
const input = buildRuntimeInput({
|
||||
userMessage: "а ты помнишь мы зеркало обсуждали?",
|
||||
modeDecision: { mode: "chat", reason: "memory_recap_followup_detected" },
|
||||
sessionItems: [
|
||||
{
|
||||
role: "assistant",
|
||||
debug: {
|
||||
execution_lane: "address_query",
|
||||
detected_intent: "inventory_purchase_provenance_for_item",
|
||||
extracted_filters: {
|
||||
item: "Зеркало для инвалидов поворотное травмобезопасное",
|
||||
organization: "ООО Альтернатива Плюс",
|
||||
as_of_date: "2022-02-28"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
executeLlmChat
|
||||
});
|
||||
|
||||
const output = await runAssistantLivingChatRuntime(input);
|
||||
|
||||
expect(output.handled).toBe(true);
|
||||
expect(output.chatText).toContain("Зеркало для инвалидов поворотное травмобезопасное");
|
||||
expect(output.chatText).toContain("кто поставил");
|
||||
expect(output.debug?.living_chat_response_source).toBe("deterministic_memory_recap_contract");
|
||||
expect(executeLlmChat).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -730,6 +730,98 @@ describe("assistant orchestration contract", () => {
|
||||
expect(decision.livingReason).toBe("meta_followup_over_grounded_answer");
|
||||
});
|
||||
|
||||
it("routes evaluative VAT follow-up 'это много или мало' to contextual chat instead of replaying address lane", () => {
|
||||
const decision = resolveAssistantOrchestrationDecision({
|
||||
rawUserMessage: "это много или мало?",
|
||||
effectiveAddressUserMessage: "это много или мало?",
|
||||
followupContext: {
|
||||
previous_intent: "vat_payable_forecast",
|
||||
previous_filters: {
|
||||
period_from: "2020-03-01",
|
||||
period_to: "2020-03-31"
|
||||
},
|
||||
previous_anchor_type: "unknown",
|
||||
previous_anchor_value: null
|
||||
},
|
||||
llmPreDecomposeMeta: {
|
||||
applied: false,
|
||||
reason: "normalized_fragment_rejected_semantic_guard",
|
||||
llmCanonicalCandidateDetected: true,
|
||||
predecomposeContract: {
|
||||
mode: "unsupported",
|
||||
mode_confidence: "low",
|
||||
intent: "unknown",
|
||||
intent_confidence: "low"
|
||||
},
|
||||
semanticExtractionContract: {
|
||||
valid: false,
|
||||
apply_canonical_recommended: false
|
||||
}
|
||||
} as any,
|
||||
sessionItems: [
|
||||
{
|
||||
role: "assistant",
|
||||
debug: {
|
||||
execution_lane: "address_query",
|
||||
answer_grounding_check: {
|
||||
status: "grounded"
|
||||
},
|
||||
detected_intent: "vat_payable_forecast"
|
||||
}
|
||||
}
|
||||
],
|
||||
useMock: false
|
||||
} as any);
|
||||
|
||||
expect(decision.runAddressLane).toBe(false);
|
||||
expect(decision.toolGateDecision).toBe("skip_address_lane");
|
||||
expect(decision.toolGateReason).toBe("meta_followup_over_grounded_answer");
|
||||
expect(decision.livingMode).toBe("chat");
|
||||
expect(decision.livingReason).toBe("meta_followup_over_grounded_answer");
|
||||
});
|
||||
|
||||
it("routes memory recap follow-up over prior address context to deterministic chat instead of generic non-domain chat", () => {
|
||||
const decision = resolveAssistantOrchestrationDecision({
|
||||
rawUserMessage: "а ты помнишь мы зеркало обсуждали?",
|
||||
effectiveAddressUserMessage: "а ты помнишь мы зеркало обсуждали?",
|
||||
followupContext: null,
|
||||
llmPreDecomposeMeta: {
|
||||
applied: false,
|
||||
reason: "normalized_fragment_rejected_semantic_guard",
|
||||
llmCanonicalCandidateDetected: false,
|
||||
predecomposeContract: {
|
||||
mode: "unsupported",
|
||||
mode_confidence: "low",
|
||||
intent: "unknown",
|
||||
intent_confidence: "low"
|
||||
}
|
||||
} as any,
|
||||
sessionItems: [
|
||||
{
|
||||
role: "assistant",
|
||||
debug: {
|
||||
execution_lane: "address_query",
|
||||
answer_grounding_check: {
|
||||
status: "grounded"
|
||||
},
|
||||
detected_intent: "inventory_purchase_provenance_for_item",
|
||||
extracted_filters: {
|
||||
item: "Зеркало для инвалидов поворотное травмобезопасное",
|
||||
as_of_date: "2022-02-28"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
useMock: false
|
||||
} as any);
|
||||
|
||||
expect(decision.runAddressLane).toBe(false);
|
||||
expect(decision.toolGateDecision).toBe("skip_address_lane");
|
||||
expect(decision.toolGateReason).toBe("memory_recap_followup_detected");
|
||||
expect(decision.livingMode).toBe("chat");
|
||||
expect(decision.livingReason).toBe("memory_recap_followup_detected");
|
||||
});
|
||||
|
||||
it("keeps documentary inventory chain verification in address lane for supported exact intent", () => {
|
||||
const question =
|
||||
"Есть ли документально подтвержденная цепочка: поставщик Гамма-мебель, ООО -> товар Шкаф картотечный 1000*400*2100 -> покупатель Департамент капитального ремонта города Москвы";
|
||||
|
||||
Reference in New Issue
Block a user