ARCH: усилить semantic integrity и добавить human AGENT сценарии
This commit is contained in:
@@ -112,6 +112,89 @@ describe("address navigation state", () => {
|
||||
expect(evolved.session_context.date_scope.period_to).toBe("2020-12-31");
|
||||
});
|
||||
|
||||
it("prefers grounded discovery counterparty over stale referential anchor when updating focus", () => {
|
||||
const initial = normalizeAddressNavigationState(
|
||||
{
|
||||
schema_version: "address_navigation_state_v1",
|
||||
session_id: "asst-3b",
|
||||
updated_at: "2026-04-12T10:00:00.000Z",
|
||||
session_context: {
|
||||
active_result_set_id: "rs-prev",
|
||||
active_focus_object: {
|
||||
object_type: "counterparty",
|
||||
object_id: "counterparty:нортон",
|
||||
label: "НОРТОН",
|
||||
provenance_result_set_id: "rs-prev",
|
||||
selected_at: "2026-04-12T09:59:00.000Z"
|
||||
},
|
||||
last_confirmed_route: "address_customer_revenue_and_payments_v1",
|
||||
date_scope: {
|
||||
as_of_date: null,
|
||||
period_from: "2020-01-01",
|
||||
period_to: "2020-12-31"
|
||||
},
|
||||
organization_scope: "ООО Альтернатива Плюс"
|
||||
},
|
||||
result_sets: [],
|
||||
navigation_history: []
|
||||
} as any,
|
||||
"asst-3b"
|
||||
);
|
||||
const assistantItem = {
|
||||
message_id: "msg-a2b",
|
||||
session_id: "asst-3b",
|
||||
role: "assistant",
|
||||
text: "По Группа СВК подтверждены исходящие платежи за 2020 год.",
|
||||
reply_type: "factual",
|
||||
created_at: "2026-04-12T10:02:30.000Z",
|
||||
trace_id: "address-456b",
|
||||
debug: {
|
||||
detected_mode: "address_query",
|
||||
detected_intent: "supplier_payouts_profile",
|
||||
selected_recipe: "address_supplier_payouts_profile_v1",
|
||||
extracted_filters: {
|
||||
counterparty: "НОРТОН",
|
||||
period_from: "2020-01-01",
|
||||
period_to: "2020-12-31"
|
||||
},
|
||||
anchor_type: "counterparty",
|
||||
anchor_value_raw: "он",
|
||||
anchor_value_resolved: "он",
|
||||
mcp_discovery_response_applied: true,
|
||||
assistant_mcp_discovery_entry_point_v1: {
|
||||
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
|
||||
entry_status: "bridge_executed",
|
||||
turn_input: {
|
||||
turn_meaning_ref: {
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "payout",
|
||||
explicit_entity_candidates: ["Группа СВК"]
|
||||
}
|
||||
},
|
||||
bridge: {
|
||||
bridge_status: "answer_draft_ready",
|
||||
business_fact_answer_allowed: true,
|
||||
pilot: {
|
||||
pilot_scope: "counterparty_supplier_payout_query_movements_v1"
|
||||
},
|
||||
answer_draft: {
|
||||
answer_mode: "confirmed_factual"
|
||||
}
|
||||
}
|
||||
},
|
||||
dialog_continuation_contract_v2: {
|
||||
decision: "continue_previous"
|
||||
}
|
||||
}
|
||||
} as any;
|
||||
|
||||
const evolved = evolveAddressNavigationStateWithAssistantItem(initial, assistantItem, 4);
|
||||
expect(evolved.session_context.active_focus_object?.object_type).toBe("counterparty");
|
||||
expect(evolved.session_context.active_focus_object?.label).toBe("Группа СВК");
|
||||
expect(evolved.session_context.active_focus_object?.object_id).toBe("counterparty:группа свк");
|
||||
expect(evolved.navigation_history[0]?.target_object_id).toBe("counterparty:группа свк");
|
||||
});
|
||||
|
||||
it("captures item focus from inventory answers when no anchor is materialized", () => {
|
||||
const base = createEmptyAddressNavigationState("asst-4", "2026-04-12T10:00:00.000Z");
|
||||
const assistantItem = {
|
||||
|
||||
@@ -731,13 +731,14 @@ describe("assistant address follow-up carryover", () => {
|
||||
expect(second.reply_type).toBe("factual");
|
||||
|
||||
expect(calls).toHaveLength(2);
|
||||
expect(calls[1].message).toBe(followupMessage);
|
||||
expect(calls[1].options?.followupContext?.previous_anchor_type).toBe("counterparty");
|
||||
expect(String(calls[1].options?.followupContext?.previous_anchor_value ?? "")).toContain("Жуковка 51");
|
||||
expect(String(calls[1].options?.followupContext?.previous_filters?.counterparty ?? "")).toContain("жуковке 51");
|
||||
expect(normalizerService.normalize).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("retries with raw user message after rewrite degraded anchor and returns factual follow-up result", async () => {
|
||||
it("prefers the raw referential document follow-up over a degraded rewrite and returns factual follow-up result", async () => {
|
||||
const calls: Array<{ message: string; options?: any }> = [];
|
||||
const firstMessage = "покажи документы по жуковке 51";
|
||||
const followupMessage = "кроме этого документа есть еще чтото?";
|
||||
@@ -826,12 +827,10 @@ describe("assistant address follow-up carryover", () => {
|
||||
} as any);
|
||||
expect(second.ok).toBe(true);
|
||||
expect(second.reply_type).toBe("factual");
|
||||
expect(second.debug?.address_retry_audit?.attempted).toBe(true);
|
||||
expect(second.debug?.address_retry_audit?.initial_limited_category).toBe("missing_anchor");
|
||||
expect(second.debug?.address_retry_audit?.retry_message).toBe(followupMessage);
|
||||
expect(second.debug?.address_retry_audit?.attempted).toBe(false);
|
||||
|
||||
expect(calls.some((entry) => String(entry.message).toLowerCase().startsWith("документы по контрагенту"))).toBe(true);
|
||||
expect(calls.some((entry) => String(entry.message).toLowerCase() === followupMessage)).toBe(true);
|
||||
expect(calls).toHaveLength(2);
|
||||
expect(calls[1].message).toBe(followupMessage);
|
||||
expect(normalizerService.normalize).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
@@ -230,6 +230,102 @@ describe("assistantContinuityPolicy organization authority", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("replaces referential counterparty placeholders with grounded discovery entity during carryover", () => {
|
||||
const debug = {
|
||||
execution_lane: "address_query",
|
||||
extracted_filters: {
|
||||
counterparty: "РѕРЅ",
|
||||
period_from: "2020-01-01",
|
||||
period_to: "2020-12-31"
|
||||
},
|
||||
anchor_type: "counterparty",
|
||||
anchor_value_raw: "РѕРЅ",
|
||||
anchor_value_resolved: "РѕРЅ",
|
||||
mcp_discovery_response_applied: true,
|
||||
assistant_mcp_discovery_entry_point_v1: {
|
||||
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
|
||||
entry_status: "bridge_executed",
|
||||
turn_input: {
|
||||
turn_meaning_ref: {
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "turnover",
|
||||
explicit_entity_candidates: ["Группа СВК"],
|
||||
explicit_date_scope: "2020"
|
||||
}
|
||||
},
|
||||
bridge: {
|
||||
bridge_status: "answer_draft_ready",
|
||||
business_fact_answer_allowed: true,
|
||||
pilot: {
|
||||
pilot_scope: "counterparty_value_flow_query_movements_v1"
|
||||
},
|
||||
answer_draft: {
|
||||
answer_mode: "confirmed_with_bounded_inference"
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
expect(readAddressDebugCounterparty(debug)).toBe("Группа СВК");
|
||||
expect(resolveAddressDebugCarryoverFilters(debug)).toEqual({
|
||||
counterparty: "Группа СВК",
|
||||
period_from: "2020-01-01",
|
||||
period_to: "2020-12-31"
|
||||
});
|
||||
expect(resolveAddressDebugAnchorContext(debug)).toEqual({
|
||||
anchorType: "counterparty",
|
||||
anchorValue: "Группа СВК"
|
||||
});
|
||||
});
|
||||
|
||||
it("prefers grounded discovery metadata scope over conflicting stale exact-route counterparty", () => {
|
||||
const debug = {
|
||||
execution_lane: "address_query",
|
||||
extracted_filters: {
|
||||
counterparty: "РќРћР РўРћРќ",
|
||||
period_from: "2020-01-01",
|
||||
period_to: "2020-12-31"
|
||||
},
|
||||
anchor_type: "counterparty",
|
||||
anchor_value_resolved: "СервисКонсалт, ООО",
|
||||
mcp_discovery_response_applied: true,
|
||||
assistant_mcp_discovery_entry_point_v1: {
|
||||
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
|
||||
entry_status: "bridge_executed",
|
||||
turn_input: {
|
||||
turn_meaning_ref: {
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "payout",
|
||||
explicit_entity_candidates: ["РѕРЅ"],
|
||||
metadata_scope_hint: "Группа СВК",
|
||||
explicit_date_scope: "2020"
|
||||
}
|
||||
},
|
||||
bridge: {
|
||||
bridge_status: "answer_draft_ready",
|
||||
business_fact_answer_allowed: true,
|
||||
pilot: {
|
||||
pilot_scope: "counterparty_supplier_payout_query_movements_v1"
|
||||
},
|
||||
answer_draft: {
|
||||
answer_mode: "confirmed_with_bounded_inference"
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
expect(readAddressDebugCounterparty(debug)).toBe("Группа СВК");
|
||||
expect(resolveAddressDebugCarryoverFilters(debug)).toEqual({
|
||||
counterparty: "Группа СВК",
|
||||
period_from: "2020-01-01",
|
||||
period_to: "2020-12-31"
|
||||
});
|
||||
expect(resolveAddressDebugAnchorContext(debug)).toEqual({
|
||||
anchorType: "counterparty",
|
||||
anchorValue: "Группа СВК"
|
||||
});
|
||||
});
|
||||
|
||||
it("prefers the resolved entity from grounded entity-resolution discovery for counterparty carryover", () => {
|
||||
const debug = {
|
||||
execution_lane: "living_chat",
|
||||
|
||||
@@ -235,6 +235,40 @@ describe("assistant orchestration contract", () => {
|
||||
expect(decision.orchestrationContract?.hard_meta_mode).toBe("non_domain");
|
||||
});
|
||||
|
||||
it("keeps address lane for a short net follow-up over grounded value-flow context", () => {
|
||||
const decision = resolveAssistantOrchestrationDecision({
|
||||
rawUserMessage: "А какое получилось нетто?",
|
||||
effectiveAddressUserMessage: "А какое получилось нетто?",
|
||||
followupContext: {
|
||||
previous_intent: "customer_revenue_and_payments",
|
||||
previous_filters: {
|
||||
counterparty: "Группа СВК",
|
||||
period_from: "2020-01-01",
|
||||
period_to: "2020-12-31"
|
||||
},
|
||||
previous_anchor_type: "counterparty",
|
||||
previous_anchor_value: "Группа СВК",
|
||||
previous_discovery_pilot_scope: "counterparty_supplier_payout_query_movements_v1"
|
||||
},
|
||||
llmPreDecomposeMeta: {
|
||||
applied: false,
|
||||
reason: "no_usable_fragment",
|
||||
predecomposeContract: {
|
||||
mode: "unsupported",
|
||||
mode_confidence: "low",
|
||||
intent: "unknown",
|
||||
intent_confidence: "low"
|
||||
}
|
||||
} as any,
|
||||
useMock: false
|
||||
});
|
||||
|
||||
expect(decision.runAddressLane).toBe(true);
|
||||
expect(decision.toolGateDecision).toBe("run_address_lane");
|
||||
expect(decision.toolGateReason).not.toBe("non_domain_query_indexed");
|
||||
expect(decision.livingMode).toBe("address_data");
|
||||
});
|
||||
|
||||
it("routes historical capability follow-up over grounded inventory answer to contextual chat", () => {
|
||||
const decision = resolveAssistantOrchestrationDecision({
|
||||
rawUserMessage: "а исторические данные ты можешь же показать?",
|
||||
|
||||
@@ -585,6 +585,41 @@ describe("assistant MCP discovery turn input adapter", () => {
|
||||
expect(result.reason_codes).not.toContain("mcp_discovery_not_applicable_for_supported_exact_turn");
|
||||
});
|
||||
|
||||
it("prefers grounded discovery metadata scope over stale conflicting counterparty in a short net follow-up", () => {
|
||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||
userMessage: "а какое нетто?",
|
||||
assistantTurnMeaning: {
|
||||
asked_domain_family: "counterparty",
|
||||
asked_action_family: "turnover",
|
||||
explicit_intent_candidate: "customer_revenue_and_payments"
|
||||
},
|
||||
followupContext: {
|
||||
previous_discovery_pilot_scope: "counterparty_supplier_payout_query_movements_v1",
|
||||
previous_filters: {
|
||||
counterparty: "РќРћР РўРћРќ",
|
||||
period_from: "2020-01-01",
|
||||
period_to: "2020-12-31"
|
||||
},
|
||||
previous_anchor_type: "counterparty",
|
||||
previous_anchor_value: "СервисКонсалт, ООО",
|
||||
previous_discovery_loop_metadata_scope_hint: "Группа СВК"
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.adapter_status).toBe("ready");
|
||||
expect(result.should_run_discovery).toBe(true);
|
||||
expect(result.turn_meaning_ref).toMatchObject({
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "net_value_flow",
|
||||
explicit_entity_candidates: ["Группа СВК"],
|
||||
explicit_date_scope: "2020",
|
||||
unsupported_but_understood_family: "counterparty_bidirectional_value_flow_or_netting",
|
||||
stale_replay_forbidden: true
|
||||
});
|
||||
expect(result.turn_meaning_ref?.explicit_entity_candidates).not.toContain("РќРћР РўРћРќ");
|
||||
expect(result.reason_codes).toContain("mcp_discovery_grounded_value_flow_followup");
|
||||
});
|
||||
|
||||
it.skip("switches from a grounded exact value-flow answer into document evidence without restating the counterparty", () => {
|
||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||
userMessage: "а по документам?",
|
||||
@@ -1268,6 +1303,35 @@ describe("assistant MCP discovery turn input adapter", () => {
|
||||
expect(result.reason_codes).toContain("mcp_discovery_not_applicable_for_supported_exact_turn");
|
||||
});
|
||||
|
||||
it("does not bootstrap metadata discovery from a referential document exclusion follow-up over exact document context", () => {
|
||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||
userMessage: "кроме этого документа есть еще что-то?",
|
||||
effectiveMessage: "документы по контрагенту документа",
|
||||
assistantTurnMeaning: {
|
||||
asked_domain_family: "counterparty",
|
||||
asked_action_family: "list_documents",
|
||||
explicit_intent_candidate: "list_documents_by_counterparty",
|
||||
explicit_entity_candidates: [{ value: "ТСЖ \"Жуковка 51\"" }]
|
||||
},
|
||||
followupContext: {
|
||||
previous_intent: "list_documents_by_counterparty",
|
||||
target_intent: "list_documents_by_counterparty",
|
||||
previous_anchor_type: "counterparty",
|
||||
previous_anchor_value: "ТСЖ \"Жуковка 51\"",
|
||||
previous_filters: {
|
||||
counterparty: "жуковке 51"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.adapter_status).toBe("not_applicable");
|
||||
expect(result.should_run_discovery).toBe(false);
|
||||
expect(result.turn_meaning_ref).toBeNull();
|
||||
expect(result.reason_codes).toContain("mcp_discovery_not_applicable_for_supported_exact_turn");
|
||||
expect(result.reason_codes).not.toContain("mcp_discovery_metadata_signal_detected");
|
||||
expect(result.reason_codes).not.toContain("mcp_discovery_metadata_scope_hint_from_raw_text");
|
||||
});
|
||||
|
||||
it("never serializes object candidates as [object Object]", () => {
|
||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||
assistantTurnMeaning: {
|
||||
|
||||
Reference in New Issue
Block a user