АРЧ АП11 - Добавить builder смешанных AGENT-прогонов и починить meta-memory recall в turnaround 11
This commit is contained in:
parent
f7edf6aacb
commit
9f0f7f3e79
|
|
@ -0,0 +1,342 @@
|
|||
{
|
||||
"schema_version": "domain_truth_harness_spec_v1",
|
||||
"scenario_id": "address_truth_harness_phase7_meta_domain_mix",
|
||||
"domain": "address_phase7_meta_domain_mix",
|
||||
"title": "Phase 7 mixed replay for documents, selected-object continuity, meta context, and cross-domain pivots",
|
||||
"description": "Mixed AGENT replay for turnaround 11. The pack interleaves counterparty documents, inventory root and selected-object continuity, meta-space interruptions, memory recap, receivables to inventory same-date pivot, and an account 60 tail check.",
|
||||
"bindings": {},
|
||||
"steps": [
|
||||
{
|
||||
"step_id": "step_01_smalltalk",
|
||||
"title": "Casual chat stays human and non-technical",
|
||||
"question": "привет, как дела?",
|
||||
"required_answer_patterns_any": [
|
||||
"(?i)привет|дела|помочь|норм"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"(?i)tool_gate_reason",
|
||||
"(?i)address_mode",
|
||||
"(?i)hard_meta_mode",
|
||||
"(?i)living_reason"
|
||||
],
|
||||
"criticality": "info",
|
||||
"semantic_tags": [
|
||||
"meta_smalltalk"
|
||||
],
|
||||
"notes": "[mixed_pack_slot=slot_01_smalltalk source=address_truth_harness_phase6_provider_axis_mix:step_01_smalltalk]"
|
||||
},
|
||||
{
|
||||
"step_id": "step_01_counterparty_documents",
|
||||
"title": "Counterparty documents use the normalized legal name",
|
||||
"question": "покажи все документы по чепурнову",
|
||||
"allowed_reply_types": [
|
||||
"factual"
|
||||
],
|
||||
"expected_intents": [
|
||||
"list_documents_by_counterparty"
|
||||
],
|
||||
"required_direct_answer_patterns_any": [
|
||||
"(?i)контрагент:",
|
||||
"(?i)чепурнов"
|
||||
],
|
||||
"criticality": "critical",
|
||||
"semantic_tags": [
|
||||
"counterparty_documents"
|
||||
],
|
||||
"notes": "[mixed_pack_slot=slot_03_counterparty_documents source=address_truth_harness_phase4_coverage_evidence_mix:step_01_counterparty_documents]"
|
||||
},
|
||||
{
|
||||
"step_id": "step_02_counterparty_shipments_or_fallback",
|
||||
"title": "Supplier shipment question stays human even when exact supply rows are absent",
|
||||
"question": "что нам отгружал чепурнов, какой товар или услугу?",
|
||||
"allowed_reply_types": [
|
||||
"factual",
|
||||
"partial_coverage"
|
||||
],
|
||||
"expected_intents": [
|
||||
"list_documents_by_counterparty"
|
||||
],
|
||||
"required_direct_answer_patterns_any": [
|
||||
"(?i)чепурнов",
|
||||
"(?i)постав",
|
||||
"(?i)оплат|возврат|товар|услуг"
|
||||
],
|
||||
"forbidden_direct_answer_patterns": [
|
||||
"(?i)^сейчас не дам прямой адресный ответ",
|
||||
"(?i)^в текущем адресном контуре этот запрос лучше не закрывать в лоб"
|
||||
],
|
||||
"criticality": "critical",
|
||||
"semantic_tags": [
|
||||
"counterparty_shipment_fallback"
|
||||
],
|
||||
"notes": "[mixed_pack_slot=slot_04_counterparty_shipment_fallback source=address_truth_harness_phase4_coverage_evidence_mix:step_02_counterparty_shipments_or_fallback]"
|
||||
},
|
||||
{
|
||||
"step_id": "step_01_inventory_march_2021",
|
||||
"title": "Inventory root snapshot at March 2021",
|
||||
"criticality": "critical",
|
||||
"question": "какие остатки на складе на март 2021",
|
||||
"allowed_reply_types": [
|
||||
"factual"
|
||||
],
|
||||
"expected_intents": [
|
||||
"inventory_on_hand_as_of_date"
|
||||
],
|
||||
"required_filters": {
|
||||
"as_of_date": "2021-03-31",
|
||||
"period_from": "2021-03-01",
|
||||
"period_to": "2021-03-31"
|
||||
},
|
||||
"required_direct_answer_patterns_any": [
|
||||
"31\\.03\\.2021",
|
||||
"(?i)на складе",
|
||||
"(?i)столешница 600\\*3050\\*26 альмандин"
|
||||
],
|
||||
"semantic_tags": [
|
||||
"inventory_root"
|
||||
],
|
||||
"notes": "[mixed_pack_slot=slot_05_inventory_root source=address_truth_harness_phase7_acceptance_gate_mix:step_01_inventory_march_2021]"
|
||||
},
|
||||
{
|
||||
"step_id": "step_02_selected_item_supplier",
|
||||
"title": "Selected-object supplier provenance",
|
||||
"criticality": "critical",
|
||||
"question": "По выбранному объекту \"Столешница 600*3050*26 альмандин\": кто нам это поставил?",
|
||||
"allowed_reply_types": [
|
||||
"factual"
|
||||
],
|
||||
"expected_intents": [
|
||||
"inventory_purchase_provenance_for_item"
|
||||
],
|
||||
"required_direct_answer_patterns_any": [
|
||||
"(?i)столешница 600\\*3050\\*26 альмандин",
|
||||
"(?i)поставщик|поставил|куплен",
|
||||
"(?i)союз|торговый дом"
|
||||
],
|
||||
"forbidden_direct_answer_patterns": [
|
||||
"(?i)^на 31\\.03\\.2021 на складе",
|
||||
"(?i)^сейчас не дам прямой адресный ответ"
|
||||
],
|
||||
"semantic_tags": [
|
||||
"selected_object",
|
||||
"selected_object_supplier"
|
||||
],
|
||||
"notes": "[mixed_pack_slot=slot_06_selected_object_supplier source=address_truth_harness_phase7_acceptance_gate_mix:step_02_selected_item_supplier]"
|
||||
},
|
||||
{
|
||||
"step_id": "step_05_capability_meta_interrupt",
|
||||
"title": "Capability meta question does not break the address context",
|
||||
"question": "что ты умеешь?",
|
||||
"required_answer_patterns_any": [
|
||||
"(?i)могу|умею",
|
||||
"(?i)остатк|документ|контрагент|ндс"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"(?i)tool_gate_reason",
|
||||
"(?i)address_mode"
|
||||
],
|
||||
"criticality": "warning",
|
||||
"semantic_tags": [
|
||||
"meta_capability"
|
||||
],
|
||||
"notes": "[mixed_pack_slot=slot_07_meta_capability source=address_truth_harness_phase5_meta_memory_mix:step_05_capability_meta_interrupt]"
|
||||
},
|
||||
{
|
||||
"step_id": "step_03_selected_item_documents",
|
||||
"title": "Selected-object documents stay in the same contour",
|
||||
"criticality": "critical",
|
||||
"question": "По выбранному объекту \"Столешница 600*3050*26 альмандин\": покажи документы по этой позиции",
|
||||
"allowed_reply_types": [
|
||||
"factual"
|
||||
],
|
||||
"expected_intents": [
|
||||
"inventory_purchase_documents_for_item"
|
||||
],
|
||||
"required_direct_answer_patterns_any": [
|
||||
"(?i)столешница 600\\*3050\\*26 альмандин",
|
||||
"(?i)документ",
|
||||
"(?i)союз|торговый дом"
|
||||
],
|
||||
"semantic_tags": [
|
||||
"selected_object",
|
||||
"selected_object_documents"
|
||||
],
|
||||
"notes": "[mixed_pack_slot=slot_08_selected_object_documents source=address_truth_harness_phase7_acceptance_gate_mix:step_03_selected_item_documents]"
|
||||
},
|
||||
{
|
||||
"step_id": "step_06_memory_recap_after_interrupts",
|
||||
"title": "Memory recap still remembers the selected object after meta interruptions",
|
||||
"question": "а ты помнишь, что мы по этой позиции уже выяснили?",
|
||||
"required_answer_patterns_any": [
|
||||
"(?i)помню",
|
||||
"(?i)столешница 600\\*3050\\*26 альмандин",
|
||||
"(?i)позици"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"(?i)^сейчас не дам прямой адресный ответ",
|
||||
"(?i)^в текущем адресном контуре этот запрос лучше не закрывать в лоб"
|
||||
],
|
||||
"criticality": "warning",
|
||||
"semantic_tags": [
|
||||
"meta_memory"
|
||||
],
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)помню",
|
||||
"(?i)столешница 600\\*3050\\*26 альмандин"
|
||||
],
|
||||
"notes": "[mixed_pack_slot=slot_09_meta_memory source=address_truth_harness_phase5_meta_memory_mix:step_06_memory_recap_after_interrupts]"
|
||||
},
|
||||
{
|
||||
"step_id": "step_04_inventory_same_date_restore",
|
||||
"title": "Same-date restore returns to the March root snapshot",
|
||||
"criticality": "critical",
|
||||
"question": "покажи еще раз остатки на эту же дату",
|
||||
"allowed_reply_types": [
|
||||
"factual"
|
||||
],
|
||||
"expected_intents": [
|
||||
"inventory_on_hand_as_of_date"
|
||||
],
|
||||
"required_filters": {
|
||||
"as_of_date": "2021-03-31",
|
||||
"period_from": "2021-03-01",
|
||||
"period_to": "2021-03-31"
|
||||
},
|
||||
"required_direct_answer_patterns_any": [
|
||||
"31\\.03\\.2021",
|
||||
"(?i)на складе"
|
||||
],
|
||||
"forbidden_direct_answer_patterns": [
|
||||
"(?i)^сейчас не дам прямой адресный ответ",
|
||||
"(?i)transition_not_supported_by_capability"
|
||||
],
|
||||
"semantic_tags": [
|
||||
"inventory_root",
|
||||
"same_date_restore"
|
||||
],
|
||||
"notes": "[mixed_pack_slot=slot_10_same_date_restore source=address_truth_harness_phase7_acceptance_gate_mix:step_04_inventory_same_date_restore]"
|
||||
},
|
||||
{
|
||||
"step_id": "step_02_receivables_march_2020",
|
||||
"title": "Receivables at March 2020",
|
||||
"question": "кто нам должен на март 2020",
|
||||
"allowed_reply_types": [
|
||||
"factual"
|
||||
],
|
||||
"expected_intents": [
|
||||
"receivables_confirmed_as_of_date"
|
||||
],
|
||||
"expected_capability": "confirmed_receivables_as_of_date",
|
||||
"expected_result_mode": "confirmed_balance",
|
||||
"required_filters": {
|
||||
"as_of_date": "2020-03-31",
|
||||
"period_from": "2020-03-01",
|
||||
"period_to": "2020-03-31"
|
||||
},
|
||||
"required_direct_answer_patterns_any": [
|
||||
"31\\.03\\.2020",
|
||||
"(?i)дебиторск"
|
||||
],
|
||||
"notes": "Базовый корневой финансовый вопрос должен отработать точно и задать март 2020 как carryover-якорь. [mixed_pack_slot=slot_11_receivables_root source=address_truth_harness_test2:step_02_receivables_march_2020]",
|
||||
"criticality": "critical",
|
||||
"semantic_tags": [
|
||||
"settlements_receivables"
|
||||
]
|
||||
},
|
||||
{
|
||||
"step_id": "step_03_inventory_same_date",
|
||||
"title": "Inventory on the same date",
|
||||
"question": "остатки по складу на эту же дату",
|
||||
"allowed_reply_types": [
|
||||
"factual"
|
||||
],
|
||||
"expected_intents": [
|
||||
"inventory_on_hand_as_of_date"
|
||||
],
|
||||
"expected_capability": "confirmed_inventory_on_hand_as_of_date",
|
||||
"expected_result_mode": "confirmed_balance",
|
||||
"required_filters": {
|
||||
"as_of_date": "{{step_02_receivables_march_2020.filters.as_of_date}}",
|
||||
"period_from": "{{step_02_receivables_march_2020.filters.period_from}}",
|
||||
"period_to": "{{step_02_receivables_march_2020.filters.period_to}}"
|
||||
},
|
||||
"required_direct_answer_patterns_any": [
|
||||
"31\\.03\\.2020",
|
||||
"(?i)на складе"
|
||||
],
|
||||
"notes": "Смена контура receivables -> inventory должна сохранить ту же дату, без дополнительного ручного уточнения. [mixed_pack_slot=slot_12_same_date_pivot source=address_truth_harness_test2:step_03_inventory_same_date]",
|
||||
"criticality": "critical",
|
||||
"semantic_tags": [
|
||||
"inventory_root",
|
||||
"same_date_pivot"
|
||||
]
|
||||
},
|
||||
{
|
||||
"step_id": "step_06_historical_capability_followup",
|
||||
"title": "Historical capability follow-up stays human",
|
||||
"criticality": "warning",
|
||||
"question": "а исторические остатки тоже можешь?",
|
||||
"required_answer_patterns_any": [
|
||||
"(?i)историческ|история",
|
||||
"(?i)могу|умею"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"(?i)^сейчас не дам прямой адресный ответ",
|
||||
"(?i)^в текущем адресном контуре этот запрос лучше не закрывать в лоб",
|
||||
"(?i)tool_gate_reason",
|
||||
"(?i)hard_meta_mode"
|
||||
],
|
||||
"semantic_tags": [
|
||||
"meta_historical_capability",
|
||||
"inventory_root"
|
||||
],
|
||||
"notes": "[mixed_pack_slot=slot_13_meta_historical source=address_truth_harness_phase7_acceptance_gate_mix:step_06_historical_capability_followup]"
|
||||
},
|
||||
{
|
||||
"step_id": "step_04_open_items_account_60",
|
||||
"title": "Account 60 tails for August 2022",
|
||||
"question": "хвосты покажи по счету 60 на август 2022",
|
||||
"allowed_reply_types": [
|
||||
"factual",
|
||||
"factual_with_explanation"
|
||||
],
|
||||
"expected_intents": [
|
||||
"open_items_by_counterparty_or_contract"
|
||||
],
|
||||
"required_filters": {
|
||||
"account": "60",
|
||||
"period_from": "2022-08-01",
|
||||
"period_to": "2022-08-31",
|
||||
"as_of_date": "2022-08-31"
|
||||
},
|
||||
"required_direct_answer_patterns_any": [
|
||||
"(?i)счету 60",
|
||||
"(?i)хвост"
|
||||
],
|
||||
"criticality": "critical",
|
||||
"semantic_tags": [
|
||||
"settlements_account_60"
|
||||
],
|
||||
"notes": "[mixed_pack_slot=slot_14_account_60 source=address_truth_harness_targeted_counterparty_tails:step_04_open_items_account_60]"
|
||||
},
|
||||
{
|
||||
"step_id": "step_02_data_scope_meta",
|
||||
"title": "Data-scope meta question stays deterministic and non-technical",
|
||||
"question": "по какой компании мы сейчас работаем?",
|
||||
"required_answer_patterns_any": [
|
||||
"(?i)компан|организац|контур",
|
||||
"(?i)работ"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"(?i)tool_gate_reason",
|
||||
"(?i)hard_meta_mode",
|
||||
"(?i)living_reason"
|
||||
],
|
||||
"criticality": "warning",
|
||||
"semantic_tags": [
|
||||
"meta_scope"
|
||||
],
|
||||
"notes": "[mixed_pack_slot=slot_15_meta_scope source=address_truth_harness_phase6_provider_axis_mix:step_02_data_scope_meta]"
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,166 @@
|
|||
# Agent semantic source catalog
|
||||
|
||||
- truth_harness_steps_total: `58`
|
||||
- saved_session_questions_total: `81`
|
||||
|
||||
## Reusable truth-harness tags
|
||||
- `counterparty_documents`: `3`
|
||||
- `counterparty_shipment_fallback`: `3`
|
||||
- `inventory_root`: `21`
|
||||
- `meta_capability`: `3`
|
||||
- `meta_historical_capability`: `4`
|
||||
- `meta_memory`: `2`
|
||||
- `meta_scope`: `4`
|
||||
- `meta_smalltalk`: `3`
|
||||
- `same_date_pivot`: `3`
|
||||
- `same_date_restore`: `4`
|
||||
- `selected_object`: `11`
|
||||
- `selected_object_documents`: `3`
|
||||
- `selected_object_sale`: `1`
|
||||
- `selected_object_supplier`: `7`
|
||||
- `settlements_account_60`: `2`
|
||||
- `settlements_receivables`: `4`
|
||||
- `vat`: `3`
|
||||
|
||||
## Reusable truth-harness steps
|
||||
- `address_truth_harness_inventory_provenance_restore:step_01_inventory_march_2021` | tags: inventory_root | question: какие остатки на складе на март 2021
|
||||
- `address_truth_harness_inventory_provenance_restore:step_02_selected_item_supplier` | tags: selected_object, selected_object_supplier | question: По выбранному объекту "Столешница 600*3050*26 альмандин": кто нам это поставил?
|
||||
- `address_truth_harness_inventory_provenance_restore:step_03_selected_item_documents` | tags: selected_object, selected_object_documents | question: По выбранному объекту "Столешница 600*3050*26 альмандин": покажи документы по этой позиции
|
||||
- `address_truth_harness_inventory_provenance_restore:step_04_inventory_same_date_restore` | tags: inventory_root, same_date_restore | question: покажи еще раз остатки на эту же дату
|
||||
- `address_truth_harness_phase4_coverage_evidence_mix:step_01_counterparty_documents` | tags: counterparty_documents | question: покажи все документы по чепурнову
|
||||
- `address_truth_harness_phase4_coverage_evidence_mix:step_02_counterparty_shipments_or_fallback` | tags: counterparty_shipment_fallback | question: что нам отгружал чепурнов, какой товар или услугу?
|
||||
- `address_truth_harness_phase4_coverage_evidence_mix:step_03_inventory_reset_march_2021` | tags: inventory_root | question: какие остатки на складе на март 2021
|
||||
- `address_truth_harness_phase4_coverage_evidence_mix:step_04_selected_item_supplier_temporal_limit` | tags: selected_object, selected_object_supplier | question: По выбранному объекту "Столешница 600*3050*26 альмандин": кто нам это поставил?
|
||||
- `address_truth_harness_phase4_coverage_evidence_mix:step_05_inventory_same_date_restore` | tags: inventory_root, same_date_restore | question: покажи еще раз остатки на эту же дату
|
||||
- `address_truth_harness_phase5_meta_memory_mix:step_01_inventory_root_march_2021` | tags: inventory_root | question: какие остатки на складе на март 2021
|
||||
- `address_truth_harness_phase5_meta_memory_mix:step_02_inventory_history_capability_followup` | tags: meta_historical_capability, inventory_root | question: а исторические остатки тоже можешь?
|
||||
- `address_truth_harness_phase5_meta_memory_mix:step_03_data_scope_meta_interrupt` | tags: meta_scope | question: по какой компании мы сейчас работаем?
|
||||
- `address_truth_harness_phase5_meta_memory_mix:step_04_selected_item_supplier` | tags: selected_object, selected_object_supplier | question: По выбранному объекту "Столешница 600*3050*26 альмандин": кто нам это поставил?
|
||||
- `address_truth_harness_phase5_meta_memory_mix:step_05_capability_meta_interrupt` | tags: meta_capability | question: что ты умеешь?
|
||||
- `address_truth_harness_phase5_meta_memory_mix:step_06_memory_recap_after_interrupts` | tags: meta_memory | question: а ты помнишь, что мы по этой позиции уже выяснили?
|
||||
- `address_truth_harness_phase6_provider_axis_mix:step_01_smalltalk` | tags: meta_smalltalk | question: привет, как дела?
|
||||
- `address_truth_harness_phase6_provider_axis_mix:step_02_data_scope_meta` | tags: meta_scope | question: по какой компании мы сейчас работаем?
|
||||
- `address_truth_harness_phase6_provider_axis_mix:step_03_capability_meta` | tags: meta_capability | question: что ты можешь по 1С?
|
||||
- `address_truth_harness_phase6_provider_axis_mix:step_04_inventory_root_march_2021` | tags: inventory_root | question: какие остатки на складе на март 2021
|
||||
- `address_truth_harness_phase6_provider_axis_mix:step_05_historical_capability_followup` | tags: meta_historical_capability, inventory_root | question: а исторические остатки тоже можешь?
|
||||
- `address_truth_harness_phase7_acceptance_gate_mix:step_01_inventory_march_2021` | tags: inventory_root | question: какие остатки на складе на март 2021
|
||||
- `address_truth_harness_phase7_acceptance_gate_mix:step_02_selected_item_supplier` | tags: selected_object, selected_object_supplier | question: По выбранному объекту "Столешница 600*3050*26 альмандин": кто нам это поставил?
|
||||
- `address_truth_harness_phase7_acceptance_gate_mix:step_03_selected_item_documents` | tags: selected_object, selected_object_documents | question: По выбранному объекту "Столешница 600*3050*26 альмандин": покажи документы по этой позиции
|
||||
- `address_truth_harness_phase7_acceptance_gate_mix:step_04_inventory_same_date_restore` | tags: inventory_root, same_date_restore | question: покажи еще раз остатки на эту же дату
|
||||
- `address_truth_harness_phase7_acceptance_gate_mix:step_05_data_scope_meta_interrupt` | tags: meta_scope | question: по какой компании мы сейчас работаем?
|
||||
- `address_truth_harness_phase7_acceptance_gate_mix:step_06_historical_capability_followup` | tags: meta_historical_capability, inventory_root | question: а исторические остатки тоже можешь?
|
||||
- `address_truth_harness_phase7_meta_domain_mix:step_01_smalltalk` | tags: meta_smalltalk | question: привет, как дела?
|
||||
- `address_truth_harness_phase7_meta_domain_mix:step_02_data_scope_meta` | tags: meta_scope | question: по какой компании мы сейчас работаем?
|
||||
- `address_truth_harness_phase7_meta_domain_mix:step_01_counterparty_documents` | tags: counterparty_documents | question: покажи все документы по чепурнову
|
||||
- `address_truth_harness_phase7_meta_domain_mix:step_02_counterparty_shipments_or_fallback` | tags: counterparty_shipment_fallback | question: что нам отгружал чепурнов, какой товар или услугу?
|
||||
- `address_truth_harness_phase7_meta_domain_mix:step_01_inventory_march_2021` | tags: inventory_root | question: какие остатки на складе на март 2021
|
||||
- `address_truth_harness_phase7_meta_domain_mix:step_02_selected_item_supplier` | tags: selected_object, selected_object_supplier | question: По выбранному объекту "Столешница 600*3050*26 альмандин": кто нам это поставил?
|
||||
- `address_truth_harness_phase7_meta_domain_mix:step_05_capability_meta_interrupt` | tags: meta_capability | question: что ты умеешь?
|
||||
- `address_truth_harness_phase7_meta_domain_mix:step_03_selected_item_documents` | tags: selected_object, selected_object_documents | question: По выбранному объекту "Столешница 600*3050*26 альмандин": покажи документы по этой позиции
|
||||
- `address_truth_harness_phase7_meta_domain_mix:step_06_memory_recap_after_interrupts` | tags: meta_memory | question: а ты помнишь, что мы по этой позиции уже выяснили?
|
||||
- `address_truth_harness_phase7_meta_domain_mix:step_04_inventory_same_date_restore` | tags: inventory_root, same_date_restore | question: покажи еще раз остатки на эту же дату
|
||||
- `address_truth_harness_phase7_meta_domain_mix:step_02_receivables_march_2020` | tags: settlements_receivables | question: кто нам должен на март 2020
|
||||
- `address_truth_harness_phase7_meta_domain_mix:step_03_inventory_same_date` | tags: inventory_root, same_date_pivot | question: остатки по складу на эту же дату
|
||||
- `address_truth_harness_phase7_meta_domain_mix:step_06_historical_capability_followup` | tags: meta_historical_capability, inventory_root | question: а исторические остатки тоже можешь?
|
||||
- `address_truth_harness_phase7_meta_domain_mix:step_04_open_items_account_60` | tags: settlements_account_60 | question: хвосты покажи по счету 60 на август 2022
|
||||
- `address_truth_harness_targeted_counterparty_tails:step_01_documents_by_counterparty` | tags: counterparty_documents | question: покажи все документы по чапурнову
|
||||
- `address_truth_harness_targeted_counterparty_tails:step_02_counterparty_item_flow` | tags: counterparty_shipment_fallback | question: что нам отгружал чапурнов? какой товар или услугу?
|
||||
- `address_truth_harness_targeted_counterparty_tails:step_03_inventory_reset` | tags: inventory_root | question: какие остатки на складе на сегодня?
|
||||
- `address_truth_harness_targeted_counterparty_tails:step_04_open_items_account_60` | tags: settlements_account_60 | question: хвосты покажи по счету 60 на август 2022
|
||||
- `address_truth_harness_test2:step_01_chat_opening` | tags: meta_smalltalk | question: йо чо как
|
||||
- `address_truth_harness_test2:step_02_receivables_march_2020` | tags: settlements_receivables | question: кто нам должен на март 2020
|
||||
- `address_truth_harness_test2:step_03_inventory_same_date` | tags: inventory_root, same_date_pivot | question: остатки по складу на эту же дату
|
||||
- `address_truth_harness_test2:step_04_selected_item_seller` | tags: selected_object, selected_object_supplier | question: По выбранному объекту "Четки Пост (84*117)": кто продавец
|
||||
- `address_truth_harness_test2:step_05_selected_item_sale_trace` | tags: selected_object, selected_object_sale | question: По выбранному объекту "Четки Пост (84*117)": кому мы продали эту хуйню
|
||||
- `address_truth_harness_test2:step_06_selected_item_purchase_followup` | tags: selected_object, selected_object_supplier | question: а купили у кого известно?
|
||||
- `address_truth_harness_test2:step_07_inventory_july_2019` | tags: inventory_root | question: остатки на июль 2019
|
||||
- `address_truth_harness_test2:step_08_inventory_september_2019` | tags: inventory_root | question: сентябрь 2019
|
||||
- `address_truth_harness_test2:step_09_inventory_march_2020` | tags: inventory_root | question: март 2020
|
||||
- `address_truth_harness_test2:step_10_inventory_same_date_negative_wording` | tags: inventory_root, same_date_pivot | question: остатков на складе нет на эту дату?
|
||||
- `address_truth_harness_test2:step_11_vat_same_date` | tags: vat | question: ндс какой надо заплатить на эту же дату
|
||||
- `address_truth_harness_test2:step_12_vat_may_2016` | tags: vat | question: а на май 2016
|
||||
- `address_truth_harness_test2:step_13_receivables_same_date_after_vat_2016` | tags: settlements_receivables, vat | question: кто нам должен денег на эту дату
|
||||
- `address_truth_harness_test2:step_14_receivables_today` | tags: settlements_receivables | question: а на сегодня
|
||||
|
||||
## Saved session questions
|
||||
- `assistant_saved_session_20260416175150_gen-mo1s0m9z-ndf56a3:q01` | tags: meta_smalltalk | question: привет как дела
|
||||
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q01` | tags: meta_smalltalk | question: приветик - че как там дела
|
||||
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q02` | tags: meta_capability | question: расскажи что можешь интересного
|
||||
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q03` | tags: inventory_root | question: кайф - что там на складе по остаткам?
|
||||
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q04` | tags: meta_capability, meta_historical_capability, inventory_root | question: а исторические остатки на другие даты умеешь?
|
||||
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q05` | tags: none | question: давай на июль 2017
|
||||
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q06` | tags: none | question: март 2016
|
||||
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q07` | tags: selected_object | question: По выбранному объекту "Рабочая станция универсального специалиста (индивидуальное изготовление)": где взяли это?
|
||||
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q08` | tags: none | question: а кому продали?
|
||||
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q09` | tags: none | question: у тебя написано кто контрагент: рабочая станция - это ошибка?
|
||||
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q10` | tags: vat | question: ндс можешь прикинуть на дату покупки рабочей станции?
|
||||
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q11` | tags: vat | question: а какой ндс мы должны сгрузить на март 2020?
|
||||
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q12` | tags: vat | question: прикинь какой ндс нам надо заплатить на февраль 2017
|
||||
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q13` | tags: none | question: кто у нас самый доходный клиент за все время
|
||||
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q14` | tags: settlements_receivables | question: кто нам должен денег на май 2017
|
||||
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q15` | tags: vat | question: а какой ндс мы должны примерно заплатить за этот период?
|
||||
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q16` | tags: none | question: мы должны комуто денег на сегодня?
|
||||
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q17` | tags: none | question: а нам?
|
||||
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q18` | tags: none | question: какой у нас самый доходный год
|
||||
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q19` | tags: none | question: а за 2017 мы скок заработали?
|
||||
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q20` | tags: none | question: сколько вообще денег мы заработали за все время?
|
||||
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q21` | tags: meta_capability | question: ты умеешь считать дельту по договорам?
|
||||
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q22` | tags: counterparty_documents | question: по чепурнову покажи все доки
|
||||
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q23` | tags: none | question: а по свк
|
||||
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q24` | tags: inventory_root | question: а сейчас у нас есть что на складе?
|
||||
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q25` | tags: counterparty_shipment_fallback | question: что нам отгружать чепурнов? какой товар или услугу?
|
||||
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q26` | tags: inventory_root | question: какие остатки на складе на сегодня
|
||||
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q27` | tags: inventory_root | question: остатки на март 2016
|
||||
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q28` | tags: meta_scope | question: это по общей базе уже нужен вывод не по чепурнову
|
||||
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q29` | tags: none | question: хвосты покажи по счету 60 на август 2022
|
||||
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q30` | tags: inventory_root | question: Есть ли остатки товара, которые закупались очень давно
|
||||
- `assistant_saved_session_20260416182626_gen-mo1t93wq-jy0453e:q31` | tags: inventory_root | question: Какие конкретно номенклатуры формируют остаток по складу на май 2020
|
||||
- `assistant_saved_session_20260417070448_gen-mo2kcds2-tlqmvng:q01` | tags: counterparty_documents | question: покажи все документы по чепурнову
|
||||
- `assistant_saved_session_20260417070448_gen-mo2kcds2-tlqmvng:q02` | tags: counterparty_shipment_fallback | question: что нам отгружал чепурнов, какой товар или услугу?
|
||||
- `assistant_saved_session_20260417070448_gen-mo2kcds2-tlqmvng:q03` | tags: inventory_root | question: какие остатки на складе на сегодня?
|
||||
- `assistant_saved_session_20260417070448_gen-mo2kcds2-tlqmvng:q04` | tags: settlements_account_60 | question: хвосты по счету 60 на август 2022
|
||||
- `assistant_saved_session_20260417070448_gen-mo2kcds2-tlqmvng:q05` | tags: inventory_root | question: какие остатки на складе на март 2021
|
||||
- `assistant_saved_session_20260417070448_gen-mo2kcds2-tlqmvng:q06` | tags: selected_object | question: По выбранному объекту "Столешница 600*3050*26 альмандин": кто нам это поставил?
|
||||
- `assistant_saved_session_20260417070448_gen-mo2kcds2-tlqmvng:q07` | tags: selected_object | question: По выбранному объекту "Столешница 600*3050*26 альмандин": покажи документы по этой позиции
|
||||
- `assistant_saved_session_20260417070448_gen-mo2kcds2-tlqmvng:q08` | tags: inventory_root | question: покажи еще раз остатки на эту же дату
|
||||
- `assistant_saved_session_20260417070448_gen-mo2kcds2-tlqmvng:q09` | tags: inventory_root | question: какие остатки на складе на март 2016
|
||||
- `assistant_saved_session_20260417070448_gen-mo2kcds2-tlqmvng:q10` | tags: none | question: на июль 2019
|
||||
- `assistant_saved_session_20260417070448_gen-mo2kcds2-tlqmvng:q11` | tags: none | question: на сентябрь
|
||||
- `assistant_saved_session_20260417070448_gen-mo2kcds2-tlqmvng:q12` | tags: none | question: а на март
|
||||
- `assistant_saved_session_20260417070448_gen-mo2kcds2-tlqmvng:q13` | tags: meta_scope | question: это по общей базе
|
||||
- `assistant_saved_session_20260417080808_gen-ag04170808-1907fa:q01` | tags: counterparty_documents | question: покажи все документы по чепурнову
|
||||
- `assistant_saved_session_20260417080808_gen-ag04170808-1907fa:q02` | tags: counterparty_shipment_fallback | question: что нам отгружал чепурнов, какой товар или услугу?
|
||||
- `assistant_saved_session_20260417080808_gen-ag04170808-1907fa:q03` | tags: inventory_root | question: какие остатки на складе на март 2021
|
||||
- `assistant_saved_session_20260417080808_gen-ag04170808-1907fa:q04` | tags: selected_object | question: По выбранному объекту "Столешница 600*3050*26 альмандин": кто нам это поставил?
|
||||
- `assistant_saved_session_20260417080808_gen-ag04170808-1907fa:q05` | tags: inventory_root | question: покажи еще раз остатки на эту же дату
|
||||
- `assistant_saved_session_20260417083044_gen-ag04170830-5f771d:q01` | tags: inventory_root | question: какие остатки на складе на март 2021
|
||||
- `assistant_saved_session_20260417083044_gen-ag04170830-5f771d:q02` | tags: meta_historical_capability, inventory_root | question: а исторические остатки тоже можешь?
|
||||
- `assistant_saved_session_20260417083044_gen-ag04170830-5f771d:q03` | tags: meta_scope | question: по какой компании мы сейчас работаем?
|
||||
- `assistant_saved_session_20260417083044_gen-ag04170830-5f771d:q04` | tags: selected_object | question: По выбранному объекту "Столешница 600*3050*26 альмандин": кто нам это поставил?
|
||||
- `assistant_saved_session_20260417083044_gen-ag04170830-5f771d:q05` | tags: meta_capability | question: что ты умеешь?
|
||||
- `assistant_saved_session_20260417083044_gen-ag04170830-5f771d:q06` | tags: meta_memory | question: а ты помнишь, что мы по этой позиции уже выяснили?
|
||||
- `assistant_saved_session_20260417085550_gen-ag04170855-d13dd3:q01` | tags: meta_smalltalk | question: привет, как дела?
|
||||
- `assistant_saved_session_20260417085550_gen-ag04170855-d13dd3:q02` | tags: meta_scope | question: по какой компании мы сейчас работаем?
|
||||
- `assistant_saved_session_20260417085550_gen-ag04170855-d13dd3:q03` | tags: none | question: что ты можешь по 1С?
|
||||
- `assistant_saved_session_20260417085550_gen-ag04170855-d13dd3:q04` | tags: inventory_root | question: какие остатки на складе на март 2021
|
||||
- `assistant_saved_session_20260417085550_gen-ag04170855-d13dd3:q05` | tags: meta_historical_capability, inventory_root | question: а исторические остатки тоже можешь?
|
||||
- `assistant_saved_session_20260417091127_gen-ag04170911-ff51e1:q01` | tags: inventory_root | question: какие остатки на складе на март 2021
|
||||
- `assistant_saved_session_20260417091127_gen-ag04170911-ff51e1:q02` | tags: selected_object | question: По выбранному объекту "Столешница 600*3050*26 альмандин": кто нам это поставил?
|
||||
- `assistant_saved_session_20260417091127_gen-ag04170911-ff51e1:q03` | tags: selected_object | question: По выбранному объекту "Столешница 600*3050*26 альмандин": покажи документы по этой позиции
|
||||
- `assistant_saved_session_20260417091127_gen-ag04170911-ff51e1:q04` | tags: inventory_root | question: покажи еще раз остатки на эту же дату
|
||||
- `assistant_saved_session_20260417091127_gen-ag04170911-ff51e1:q05` | tags: meta_scope | question: по какой компании мы сейчас работаем?
|
||||
- `assistant_saved_session_20260417091127_gen-ag04170911-ff51e1:q06` | tags: meta_historical_capability, inventory_root | question: а исторические остатки тоже можешь?
|
||||
- `assistant_saved_session_20260417093144_gen-ag04170931-6bb7e5:q01` | tags: meta_smalltalk | question: привет, как дела?
|
||||
- `assistant_saved_session_20260417093144_gen-ag04170931-6bb7e5:q02` | tags: meta_scope | question: по какой компании мы сейчас работаем?
|
||||
- `assistant_saved_session_20260417093144_gen-ag04170931-6bb7e5:q03` | tags: counterparty_documents | question: покажи все документы по чепурнову
|
||||
- `assistant_saved_session_20260417093144_gen-ag04170931-6bb7e5:q04` | tags: counterparty_shipment_fallback | question: что нам отгружал чепурнов, какой товар или услугу?
|
||||
- `assistant_saved_session_20260417093144_gen-ag04170931-6bb7e5:q05` | tags: inventory_root | question: какие остатки на складе на март 2021
|
||||
- `assistant_saved_session_20260417093144_gen-ag04170931-6bb7e5:q06` | tags: selected_object | question: По выбранному объекту "Столешница 600*3050*26 альмандин": кто нам это поставил?
|
||||
- `assistant_saved_session_20260417093144_gen-ag04170931-6bb7e5:q07` | tags: meta_capability | question: что ты умеешь?
|
||||
- `assistant_saved_session_20260417093144_gen-ag04170931-6bb7e5:q08` | tags: selected_object | question: По выбранному объекту "Столешница 600*3050*26 альмандин": покажи документы по этой позиции
|
||||
- `assistant_saved_session_20260417093144_gen-ag04170931-6bb7e5:q09` | tags: meta_memory | question: а ты помнишь, что мы по этой позиции уже выяснили?
|
||||
- `assistant_saved_session_20260417093144_gen-ag04170931-6bb7e5:q10` | tags: inventory_root | question: покажи еще раз остатки на эту же дату
|
||||
- `assistant_saved_session_20260417093144_gen-ag04170931-6bb7e5:q11` | tags: settlements_receivables | question: кто нам должен на март 2020
|
||||
- `assistant_saved_session_20260417093144_gen-ag04170931-6bb7e5:q12` | tags: inventory_root | question: остатки по складу на эту же дату
|
||||
- `assistant_saved_session_20260417093144_gen-ag04170931-6bb7e5:q13` | tags: meta_historical_capability, inventory_root | question: а исторические остатки тоже можешь?
|
||||
- `assistant_saved_session_20260417093144_gen-ag04170931-6bb7e5:q14` | tags: none | question: хвосты покажи по счету 60 на август 2022
|
||||
|
|
@ -107,9 +107,14 @@ function createAssistantLivingModePolicy(deps) {
|
|||
}
|
||||
const hasMemoryCue = samples.some((sample) => /(?:помни(?:шь|те|м)?|remember|recall)/iu.test(sample));
|
||||
const hasDiscussionCue = samples.some((sample) => /(?:обсуждал[аи]?|говорил[аи]?|смотрел[аи]?|разбирал[аи]?|спрашивал[аи]?)/iu.test(sample));
|
||||
if (!hasMemoryCue || !hasDiscussionCue) {
|
||||
const hasExplicitRecapPrompt = samples.some((sample) => /(?:что\s+мы\s+.*(?:обсуждали|выяснили)|что\s+уже\s+выяснили|напомни\s+что\s+мы|what\s+we\s+already\s+(?:discussed|figured\s+out))/iu.test(sample));
|
||||
if (!hasMemoryCue || !(hasDiscussionCue || hasExplicitRecapPrompt)) {
|
||||
return false;
|
||||
}
|
||||
if (hasExplicitRecapPrompt) {
|
||||
return !samples.some((sample) => hasAssistantDataScopeMetaQuestionSignal(sample) ||
|
||||
shouldHandleAsAssistantCapabilityMetaQuery(sample));
|
||||
}
|
||||
return !samples.some((sample) => hasAssistantDataScopeMetaQuestionSignal(sample) ||
|
||||
shouldHandleAsAssistantCapabilityMetaQuery(sample) ||
|
||||
hasDataRetrievalRequestSignal(sample) ||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,9 @@ function collectMessageSamples(input) {
|
|||
function hasSignalAcrossSamples(samples, detector) {
|
||||
return samples.some((sample) => detector(sample));
|
||||
}
|
||||
function hasExplicitRecapPromptSignal(samples) {
|
||||
return samples.some((sample) => /(?:что\s+мы\s+.*(?:обсуждали|выяснили)|что\s+уже\s+выяснили|что\s+уже\s+поняли|напомни\s+что\s+мы)/iu.test(sample));
|
||||
}
|
||||
function findLastGroundedInventoryAddressDebug(items) {
|
||||
if (!Array.isArray(items)) {
|
||||
return null;
|
||||
|
|
@ -182,6 +185,7 @@ function createAssistantMemoryRecapPolicy(deps) {
|
|||
const samples = collectMessageSamples(input);
|
||||
const historicalCapabilitySignal = hasSignalAcrossSamples(samples, deps.hasHistoricalCapabilityFollowupSignal);
|
||||
const memoryRecapSignal = hasSignalAcrossSamples(samples, deps.hasConversationMemoryRecallFollowupSignal);
|
||||
const explicitRecapPromptSignal = hasExplicitRecapPromptSignal(samples);
|
||||
return {
|
||||
contextualHistoricalCapabilityFollowupDetected: Boolean(input.capabilityMetaQuery &&
|
||||
!input.dataScopeMetaQuery &&
|
||||
|
|
@ -190,10 +194,9 @@ function createAssistantMemoryRecapPolicy(deps) {
|
|||
deps.isGroundedInventoryContextDebug(input.lastGroundedAddressDebug)),
|
||||
contextualMemoryRecapFollowupDetected: Boolean(!input.dataScopeMetaQuery &&
|
||||
!input.capabilityMetaQuery &&
|
||||
!input.dataRetrievalSignal &&
|
||||
!input.strongDataSignal &&
|
||||
!input.aggregateBusinessAnalyticsSignal &&
|
||||
memoryRecapSignal &&
|
||||
(explicitRecapPromptSignal || (!input.dataRetrievalSignal && !input.strongDataSignal)) &&
|
||||
input.hasPriorAddressDebug)
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -304,7 +304,6 @@ function createAssistantRoutePolicy(deps) {
|
|||
}
|
||||
};
|
||||
}
|
||||
if (nonDomainQueryIndexed) {
|
||||
if (contextualMemoryRecapFollowupDetected) {
|
||||
return {
|
||||
runAddressLane: false,
|
||||
|
|
@ -314,7 +313,7 @@ function createAssistantRoutePolicy(deps) {
|
|||
livingReason: "memory_recap_followup_detected",
|
||||
orchestrationContract: {
|
||||
schema_version: "assistant_orchestration_contract_v1",
|
||||
hard_meta_mode: "non_domain",
|
||||
hard_meta_mode: nonDomainQueryIndexed ? "non_domain" : null,
|
||||
provider_execution: providerExecution,
|
||||
address_mode: resolvedModeDetection.mode,
|
||||
address_mode_confidence: resolvedModeDetection.confidence,
|
||||
|
|
@ -322,7 +321,7 @@ function createAssistantRoutePolicy(deps) {
|
|||
address_intent_confidence: resolvedIntentResolution.confidence,
|
||||
strong_data_signal_detected: strongDataSignal,
|
||||
data_retrieval_signal_detected: dataRetrievalSignal,
|
||||
followup_context_detected: Boolean(followupContext || lastGroundedAddressDebug),
|
||||
followup_context_detected: Boolean(followupContext || lastGroundedAddressDebug || lastAddressAssistantDebug),
|
||||
unsupported_address_intent_fallback_to_deep: false,
|
||||
final_decision: {
|
||||
run_address_lane: false,
|
||||
|
|
@ -334,6 +333,7 @@ function createAssistantRoutePolicy(deps) {
|
|||
}
|
||||
};
|
||||
}
|
||||
if (nonDomainQueryIndexed) {
|
||||
return {
|
||||
runAddressLane: false,
|
||||
toolGateDecision: "skip_address_lane",
|
||||
|
|
|
|||
|
|
@ -171,9 +171,14 @@ export function createAssistantLivingModePolicy(deps: AssistantLivingModePolicyD
|
|||
}
|
||||
const hasMemoryCue = samples.some((sample) => /(?:помни(?:шь|те|м)?|remember|recall)/iu.test(sample));
|
||||
const hasDiscussionCue = samples.some((sample) => /(?:обсуждал[аи]?|говорил[аи]?|смотрел[аи]?|разбирал[аи]?|спрашивал[аи]?)/iu.test(sample));
|
||||
if (!hasMemoryCue || !hasDiscussionCue) {
|
||||
const hasExplicitRecapPrompt = samples.some((sample) => /(?:что\s+мы\s+.*(?:обсуждали|выяснили)|что\s+уже\s+выяснили|напомни\s+что\s+мы|what\s+we\s+already\s+(?:discussed|figured\s+out))/iu.test(sample));
|
||||
if (!hasMemoryCue || !(hasDiscussionCue || hasExplicitRecapPrompt)) {
|
||||
return false;
|
||||
}
|
||||
if (hasExplicitRecapPrompt) {
|
||||
return !samples.some((sample) => hasAssistantDataScopeMetaQuestionSignal(sample) ||
|
||||
shouldHandleAsAssistantCapabilityMetaQuery(sample));
|
||||
}
|
||||
return !samples.some((sample) => hasAssistantDataScopeMetaQuestionSignal(sample) ||
|
||||
shouldHandleAsAssistantCapabilityMetaQuery(sample) ||
|
||||
hasDataRetrievalRequestSignal(sample) ||
|
||||
|
|
|
|||
|
|
@ -69,6 +69,14 @@ function hasSignalAcrossSamples(
|
|||
return samples.some((sample) => detector(sample));
|
||||
}
|
||||
|
||||
function hasExplicitRecapPromptSignal(samples: string[]): boolean {
|
||||
return samples.some((sample) =>
|
||||
/(?:что\s+мы\s+.*(?:обсуждали|выяснили)|что\s+уже\s+выяснили|что\s+уже\s+поняли|напомни\s+что\s+мы)/iu.test(
|
||||
sample
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function findLastGroundedInventoryAddressDebug(items: unknown[]): Record<string, unknown> | null {
|
||||
if (!Array.isArray(items)) {
|
||||
return null;
|
||||
|
|
@ -269,6 +277,7 @@ export function createAssistantMemoryRecapPolicy(
|
|||
samples,
|
||||
deps.hasConversationMemoryRecallFollowupSignal
|
||||
);
|
||||
const explicitRecapPromptSignal = hasExplicitRecapPromptSignal(samples);
|
||||
return {
|
||||
contextualHistoricalCapabilityFollowupDetected: Boolean(
|
||||
input.capabilityMetaQuery &&
|
||||
|
|
@ -280,10 +289,9 @@ export function createAssistantMemoryRecapPolicy(
|
|||
contextualMemoryRecapFollowupDetected: Boolean(
|
||||
!input.dataScopeMetaQuery &&
|
||||
!input.capabilityMetaQuery &&
|
||||
!input.dataRetrievalSignal &&
|
||||
!input.strongDataSignal &&
|
||||
!input.aggregateBusinessAnalyticsSignal &&
|
||||
memoryRecapSignal &&
|
||||
(explicitRecapPromptSignal || (!input.dataRetrievalSignal && !input.strongDataSignal)) &&
|
||||
input.hasPriorAddressDebug
|
||||
)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -338,7 +338,6 @@ export function createAssistantRoutePolicy(deps) {
|
|||
}
|
||||
};
|
||||
}
|
||||
if (nonDomainQueryIndexed) {
|
||||
if (contextualMemoryRecapFollowupDetected) {
|
||||
return {
|
||||
runAddressLane: false,
|
||||
|
|
@ -348,7 +347,7 @@ export function createAssistantRoutePolicy(deps) {
|
|||
livingReason: "memory_recap_followup_detected",
|
||||
orchestrationContract: {
|
||||
schema_version: "assistant_orchestration_contract_v1",
|
||||
hard_meta_mode: "non_domain",
|
||||
hard_meta_mode: nonDomainQueryIndexed ? "non_domain" : null,
|
||||
provider_execution: providerExecution,
|
||||
address_mode: resolvedModeDetection.mode,
|
||||
address_mode_confidence: resolvedModeDetection.confidence,
|
||||
|
|
@ -356,7 +355,7 @@ export function createAssistantRoutePolicy(deps) {
|
|||
address_intent_confidence: resolvedIntentResolution.confidence,
|
||||
strong_data_signal_detected: strongDataSignal,
|
||||
data_retrieval_signal_detected: dataRetrievalSignal,
|
||||
followup_context_detected: Boolean(followupContext || lastGroundedAddressDebug),
|
||||
followup_context_detected: Boolean(followupContext || lastGroundedAddressDebug || lastAddressAssistantDebug),
|
||||
unsupported_address_intent_fallback_to_deep: false,
|
||||
final_decision: {
|
||||
run_address_lane: false,
|
||||
|
|
@ -368,6 +367,7 @@ export function createAssistantRoutePolicy(deps) {
|
|||
}
|
||||
};
|
||||
}
|
||||
if (nonDomainQueryIndexed) {
|
||||
return {
|
||||
runAddressLane: false,
|
||||
toolGateDecision: "skip_address_lane",
|
||||
|
|
|
|||
|
|
@ -34,6 +34,14 @@ function buildPolicy() {
|
|||
}
|
||||
|
||||
describe("assistantLivingModePolicy", () => {
|
||||
it("detects explicit recap wording as memory signal even when selected-object words are present", () => {
|
||||
const policy = buildPolicy();
|
||||
|
||||
expect(
|
||||
policy.hasConversationMemoryRecallFollowupSignal("а ты помнишь, что мы по этой позиции уже выяснили?")
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("routes casual small-talk to chat mode", () => {
|
||||
const policy = buildPolicy();
|
||||
|
||||
|
|
|
|||
|
|
@ -55,6 +55,25 @@ describe("assistantMemoryRecapPolicy", () => {
|
|||
expect(signals.contextualMemoryRecapFollowupDetected).toBe(true);
|
||||
});
|
||||
|
||||
it("treats explicit recap wording over selected-object phrasing as memory follow-up even when data cues are present", () => {
|
||||
const signals = policy.resolveRouteMemorySignals({
|
||||
rawUserMessage: "а ты помнишь, что мы по этой позиции уже выяснили?",
|
||||
repairedRawUserMessage: "",
|
||||
effectiveAddressUserMessage: "",
|
||||
repairedEffectiveAddressUserMessage: "",
|
||||
dataScopeMetaQuery: false,
|
||||
capabilityMetaQuery: false,
|
||||
dataRetrievalSignal: true,
|
||||
strongDataSignal: true,
|
||||
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",
|
||||
|
|
|
|||
|
|
@ -187,6 +187,43 @@ describe("assistantRoutePolicy", () => {
|
|||
expect(decision.livingReason).toBe("memory_recap_followup_detected");
|
||||
});
|
||||
|
||||
it("routes explicit recap wording with selected-object phrasing to chat even when address-like cues exist", () => {
|
||||
const policy = buildPolicy({
|
||||
hasStrongDataIntentSignal: () => true,
|
||||
hasDataRetrievalRequestSignal: () => true,
|
||||
resolveRouteMemorySignals: () => ({
|
||||
contextualHistoricalCapabilityFollowupDetected: false,
|
||||
contextualMemoryRecapFollowupDetected: true
|
||||
}),
|
||||
findLastGroundedAddressAnswerDebug: () => ({
|
||||
execution_lane: "address_query",
|
||||
detected_intent: "inventory_purchase_provenance_for_item"
|
||||
})
|
||||
});
|
||||
|
||||
const decision = policy.resolveAssistantOrchestrationDecision({
|
||||
rawUserMessage: "а ты помнишь, что мы по этой позиции уже выяснили?",
|
||||
effectiveAddressUserMessage: "а ты помнишь, что мы по этой позиции уже выяснили?",
|
||||
followupContext: null,
|
||||
llmPreDecomposeMeta: {
|
||||
applied: false,
|
||||
reason: "normalized_fragment_rejected_semantic_guard",
|
||||
predecomposeContract: {
|
||||
mode: "unsupported",
|
||||
mode_confidence: "low",
|
||||
intent: "unknown",
|
||||
intent_confidence: "low"
|
||||
}
|
||||
},
|
||||
useMock: false
|
||||
});
|
||||
|
||||
expect(decision.runAddressLane).toBe(false);
|
||||
expect(decision.toolGateReason).toBe("memory_recap_followup_detected");
|
||||
expect(decision.livingMode).toBe("chat");
|
||||
expect(decision.livingReason).toBe("memory_recap_followup_detected");
|
||||
});
|
||||
|
||||
it("does not force unsupported-intent fallback when predecompose runtime is unavailable", () => {
|
||||
const policy = buildPolicy({
|
||||
hasStrongDataIntentSignal: () => true,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,88 @@
|
|||
[
|
||||
{
|
||||
"generation_id": "gen-ag04170941-87680e",
|
||||
"created_at": "2026-04-17T09:41:32+00:00",
|
||||
"mode": "saved_user_sessions",
|
||||
"title": "AGENT | Phase 7 mixed replay for documents, selected-object continuity, meta context, and cross-domain pivots",
|
||||
"count": 14,
|
||||
"domain": "address_phase7_meta_domain_mix",
|
||||
"questions": [
|
||||
"привет, как дела?",
|
||||
"по какой компании мы сейчас работаем?",
|
||||
"покажи все документы по чепурнову",
|
||||
"что нам отгружал чепурнов, какой товар или услугу?",
|
||||
"какие остатки на складе на март 2021",
|
||||
"По выбранному объекту \"Столешница 600*3050*26 альмандин\": кто нам это поставил?",
|
||||
"что ты умеешь?",
|
||||
"По выбранному объекту \"Столешница 600*3050*26 альмандин\": покажи документы по этой позиции",
|
||||
"а ты помнишь, что мы по этой позиции уже выяснили?",
|
||||
"покажи еще раз остатки на эту же дату",
|
||||
"кто нам должен на март 2020",
|
||||
"остатки по складу на эту же дату",
|
||||
"а исторические остатки тоже можешь?",
|
||||
"хвосты покажи по счету 60 на август 2022"
|
||||
],
|
||||
"generated_by": "codex_agent",
|
||||
"saved_case_set_file": "assistant_autogen_saved_user_sessions_20260417094132_gen-ag04170941-87680e.json",
|
||||
"context": {
|
||||
"llm_provider": null,
|
||||
"model": null,
|
||||
"assistant_prompt_version": null,
|
||||
"decomposition_prompt_version": null,
|
||||
"prompt_fingerprint": null,
|
||||
"autogen_personality_id": null,
|
||||
"autogen_personality_prompt": null,
|
||||
"source_session_id": null,
|
||||
"saved_session_file": "assistant_saved_session_20260417094132_gen-ag04170941-87680e.json",
|
||||
"saved_case_set_kind": "agent_semantic_scenario",
|
||||
"agent_run": true,
|
||||
"agent_focus": "mixed documents meta and cross-domain replay for turnaround 11",
|
||||
"architecture_phase": "turnaround_11_phase7",
|
||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase7_meta_domain_mix.json"
|
||||
}
|
||||
},
|
||||
{
|
||||
"generation_id": "gen-ag04170931-6bb7e5",
|
||||
"created_at": "2026-04-17T09:31:44+00:00",
|
||||
"mode": "saved_user_sessions",
|
||||
"title": "AGENT | Phase 7 mixed replay for documents, selected-object continuity, meta context, and cross-domain pivots",
|
||||
"count": 14,
|
||||
"domain": "address_phase7_meta_domain_mix",
|
||||
"questions": [
|
||||
"привет, как дела?",
|
||||
"по какой компании мы сейчас работаем?",
|
||||
"покажи все документы по чепурнову",
|
||||
"что нам отгружал чепурнов, какой товар или услугу?",
|
||||
"какие остатки на складе на март 2021",
|
||||
"По выбранному объекту \"Столешница 600*3050*26 альмандин\": кто нам это поставил?",
|
||||
"что ты умеешь?",
|
||||
"По выбранному объекту \"Столешница 600*3050*26 альмандин\": покажи документы по этой позиции",
|
||||
"а ты помнишь, что мы по этой позиции уже выяснили?",
|
||||
"покажи еще раз остатки на эту же дату",
|
||||
"кто нам должен на март 2020",
|
||||
"остатки по складу на эту же дату",
|
||||
"а исторические остатки тоже можешь?",
|
||||
"хвосты покажи по счету 60 на август 2022"
|
||||
],
|
||||
"generated_by": "codex_agent",
|
||||
"saved_case_set_file": "assistant_autogen_saved_user_sessions_20260417093144_gen-ag04170931-6bb7e5.json",
|
||||
"context": {
|
||||
"llm_provider": null,
|
||||
"model": null,
|
||||
"assistant_prompt_version": null,
|
||||
"decomposition_prompt_version": null,
|
||||
"prompt_fingerprint": null,
|
||||
"autogen_personality_id": null,
|
||||
"autogen_personality_prompt": null,
|
||||
"source_session_id": null,
|
||||
"saved_session_file": "assistant_saved_session_20260417093144_gen-ag04170931-6bb7e5.json",
|
||||
"saved_case_set_kind": "agent_semantic_scenario",
|
||||
"agent_run": true,
|
||||
"agent_focus": "mixed documents meta and cross-domain replay for turnaround 11",
|
||||
"architecture_phase": "turnaround_11_phase7",
|
||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase7_meta_domain_mix.json"
|
||||
}
|
||||
},
|
||||
{
|
||||
"generation_id": "gen-ag04170911-ff51e1",
|
||||
"created_at": "2026-04-17T09:11:27+00:00",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,173 @@
|
|||
{
|
||||
"saved_at": "2026-04-17T09:31:44+00:00",
|
||||
"generation_id": "gen-ag04170931-6bb7e5",
|
||||
"mode": "saved_user_sessions",
|
||||
"title": "AGENT | Phase 7 mixed replay for documents, selected-object continuity, meta context, and cross-domain pivots",
|
||||
"agent_run": true,
|
||||
"questions": [
|
||||
"привет, как дела?",
|
||||
"по какой компании мы сейчас работаем?",
|
||||
"покажи все документы по чепурнову",
|
||||
"что нам отгружал чепурнов, какой товар или услугу?",
|
||||
"какие остатки на складе на март 2021",
|
||||
"По выбранному объекту \"Столешница 600*3050*26 альмандин\": кто нам это поставил?",
|
||||
"что ты умеешь?",
|
||||
"По выбранному объекту \"Столешница 600*3050*26 альмандин\": покажи документы по этой позиции",
|
||||
"а ты помнишь, что мы по этой позиции уже выяснили?",
|
||||
"покажи еще раз остатки на эту же дату",
|
||||
"кто нам должен на март 2020",
|
||||
"остатки по складу на эту же дату",
|
||||
"а исторические остатки тоже можешь?",
|
||||
"хвосты покажи по счету 60 на август 2022"
|
||||
],
|
||||
"metadata": {
|
||||
"assistant_prompt_version": null,
|
||||
"decomposition_prompt_version": null,
|
||||
"prompt_fingerprint": null,
|
||||
"agent_focus": "mixed documents meta and cross-domain replay for turnaround 11",
|
||||
"architecture_phase": "turnaround_11_phase7",
|
||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase7_meta_domain_mix.json"
|
||||
},
|
||||
"source_session_id": null,
|
||||
"session": {
|
||||
"session_id": null,
|
||||
"mode": "agent_semantic_run",
|
||||
"items": [
|
||||
{
|
||||
"message_id": "agent-user-001",
|
||||
"role": "user",
|
||||
"text": "привет, как дела?",
|
||||
"created_at": "2026-04-17T09:31:44+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-002",
|
||||
"role": "user",
|
||||
"text": "по какой компании мы сейчас работаем?",
|
||||
"created_at": "2026-04-17T09:31:44+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-003",
|
||||
"role": "user",
|
||||
"text": "покажи все документы по чепурнову",
|
||||
"created_at": "2026-04-17T09:31:44+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-004",
|
||||
"role": "user",
|
||||
"text": "что нам отгружал чепурнов, какой товар или услугу?",
|
||||
"created_at": "2026-04-17T09:31:44+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-005",
|
||||
"role": "user",
|
||||
"text": "какие остатки на складе на март 2021",
|
||||
"created_at": "2026-04-17T09:31:44+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-006",
|
||||
"role": "user",
|
||||
"text": "По выбранному объекту \"Столешница 600*3050*26 альмандин\": кто нам это поставил?",
|
||||
"created_at": "2026-04-17T09:31:44+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-007",
|
||||
"role": "user",
|
||||
"text": "что ты умеешь?",
|
||||
"created_at": "2026-04-17T09:31:44+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-008",
|
||||
"role": "user",
|
||||
"text": "По выбранному объекту \"Столешница 600*3050*26 альмандин\": покажи документы по этой позиции",
|
||||
"created_at": "2026-04-17T09:31:44+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-009",
|
||||
"role": "user",
|
||||
"text": "а ты помнишь, что мы по этой позиции уже выяснили?",
|
||||
"created_at": "2026-04-17T09:31:44+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-010",
|
||||
"role": "user",
|
||||
"text": "покажи еще раз остатки на эту же дату",
|
||||
"created_at": "2026-04-17T09:31:44+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-011",
|
||||
"role": "user",
|
||||
"text": "кто нам должен на март 2020",
|
||||
"created_at": "2026-04-17T09:31:44+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-012",
|
||||
"role": "user",
|
||||
"text": "остатки по складу на эту же дату",
|
||||
"created_at": "2026-04-17T09:31:44+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-013",
|
||||
"role": "user",
|
||||
"text": "а исторические остатки тоже можешь?",
|
||||
"created_at": "2026-04-17T09:31:44+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-014",
|
||||
"role": "user",
|
||||
"text": "хвосты покажи по счету 60 на август 2022",
|
||||
"created_at": "2026-04-17T09:31:44+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
}
|
||||
],
|
||||
"agent_run": true,
|
||||
"metadata": {
|
||||
"assistant_prompt_version": null,
|
||||
"decomposition_prompt_version": null,
|
||||
"prompt_fingerprint": null,
|
||||
"agent_focus": "mixed documents meta and cross-domain replay for turnaround 11",
|
||||
"architecture_phase": "turnaround_11_phase7",
|
||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase7_meta_domain_mix.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,173 @@
|
|||
{
|
||||
"saved_at": "2026-04-17T09:41:32+00:00",
|
||||
"generation_id": "gen-ag04170941-87680e",
|
||||
"mode": "saved_user_sessions",
|
||||
"title": "AGENT | Phase 7 mixed replay for documents, selected-object continuity, meta context, and cross-domain pivots",
|
||||
"agent_run": true,
|
||||
"questions": [
|
||||
"привет, как дела?",
|
||||
"по какой компании мы сейчас работаем?",
|
||||
"покажи все документы по чепурнову",
|
||||
"что нам отгружал чепурнов, какой товар или услугу?",
|
||||
"какие остатки на складе на март 2021",
|
||||
"По выбранному объекту \"Столешница 600*3050*26 альмандин\": кто нам это поставил?",
|
||||
"что ты умеешь?",
|
||||
"По выбранному объекту \"Столешница 600*3050*26 альмандин\": покажи документы по этой позиции",
|
||||
"а ты помнишь, что мы по этой позиции уже выяснили?",
|
||||
"покажи еще раз остатки на эту же дату",
|
||||
"кто нам должен на март 2020",
|
||||
"остатки по складу на эту же дату",
|
||||
"а исторические остатки тоже можешь?",
|
||||
"хвосты покажи по счету 60 на август 2022"
|
||||
],
|
||||
"metadata": {
|
||||
"assistant_prompt_version": null,
|
||||
"decomposition_prompt_version": null,
|
||||
"prompt_fingerprint": null,
|
||||
"agent_focus": "mixed documents meta and cross-domain replay for turnaround 11",
|
||||
"architecture_phase": "turnaround_11_phase7",
|
||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase7_meta_domain_mix.json"
|
||||
},
|
||||
"source_session_id": null,
|
||||
"session": {
|
||||
"session_id": null,
|
||||
"mode": "agent_semantic_run",
|
||||
"items": [
|
||||
{
|
||||
"message_id": "agent-user-001",
|
||||
"role": "user",
|
||||
"text": "привет, как дела?",
|
||||
"created_at": "2026-04-17T09:41:32+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-002",
|
||||
"role": "user",
|
||||
"text": "по какой компании мы сейчас работаем?",
|
||||
"created_at": "2026-04-17T09:41:32+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-003",
|
||||
"role": "user",
|
||||
"text": "покажи все документы по чепурнову",
|
||||
"created_at": "2026-04-17T09:41:32+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-004",
|
||||
"role": "user",
|
||||
"text": "что нам отгружал чепурнов, какой товар или услугу?",
|
||||
"created_at": "2026-04-17T09:41:32+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-005",
|
||||
"role": "user",
|
||||
"text": "какие остатки на складе на март 2021",
|
||||
"created_at": "2026-04-17T09:41:32+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-006",
|
||||
"role": "user",
|
||||
"text": "По выбранному объекту \"Столешница 600*3050*26 альмандин\": кто нам это поставил?",
|
||||
"created_at": "2026-04-17T09:41:32+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-007",
|
||||
"role": "user",
|
||||
"text": "что ты умеешь?",
|
||||
"created_at": "2026-04-17T09:41:32+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-008",
|
||||
"role": "user",
|
||||
"text": "По выбранному объекту \"Столешница 600*3050*26 альмандин\": покажи документы по этой позиции",
|
||||
"created_at": "2026-04-17T09:41:32+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-009",
|
||||
"role": "user",
|
||||
"text": "а ты помнишь, что мы по этой позиции уже выяснили?",
|
||||
"created_at": "2026-04-17T09:41:32+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-010",
|
||||
"role": "user",
|
||||
"text": "покажи еще раз остатки на эту же дату",
|
||||
"created_at": "2026-04-17T09:41:32+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-011",
|
||||
"role": "user",
|
||||
"text": "кто нам должен на март 2020",
|
||||
"created_at": "2026-04-17T09:41:32+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-012",
|
||||
"role": "user",
|
||||
"text": "остатки по складу на эту же дату",
|
||||
"created_at": "2026-04-17T09:41:32+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-013",
|
||||
"role": "user",
|
||||
"text": "а исторические остатки тоже можешь?",
|
||||
"created_at": "2026-04-17T09:41:32+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-014",
|
||||
"role": "user",
|
||||
"text": "хвосты покажи по счету 60 на август 2022",
|
||||
"created_at": "2026-04-17T09:41:32+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
}
|
||||
],
|
||||
"agent_run": true,
|
||||
"metadata": {
|
||||
"assistant_prompt_version": null,
|
||||
"decomposition_prompt_version": null,
|
||||
"prompt_fingerprint": null,
|
||||
"agent_focus": "mixed documents meta and cross-domain replay for turnaround 11",
|
||||
"architecture_phase": "turnaround_11_phase7",
|
||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase7_meta_domain_mix.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
{
|
||||
"suite_id": "assistant_saved_session_gen-ag04170931-6bb7e5",
|
||||
"suite_version": "0.1.0",
|
||||
"schema_version": "assistant_saved_session_suite_v0_1",
|
||||
"generated_at": "2026-04-17T09:31:44+00:00",
|
||||
"generation_id": "gen-ag04170931-6bb7e5",
|
||||
"mode": "saved_user_sessions",
|
||||
"title": "AGENT | Phase 7 mixed replay for documents, selected-object continuity, meta context, and cross-domain pivots",
|
||||
"domain": "address_phase7_meta_domain_mix",
|
||||
"scenario_count": 1,
|
||||
"case_ids": [
|
||||
"SAVED-001"
|
||||
],
|
||||
"cases": [
|
||||
{
|
||||
"case_id": "SAVED-001",
|
||||
"scenario_tag": "agent_saved_user_sessions",
|
||||
"title": "AGENT | Phase 7 mixed replay for documents, selected-object continuity, meta context, and cross-domain pivots",
|
||||
"question_type": "followup",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "привет, как дела?"
|
||||
},
|
||||
{
|
||||
"user_message": "по какой компании мы сейчас работаем?"
|
||||
},
|
||||
{
|
||||
"user_message": "покажи все документы по чепурнову"
|
||||
},
|
||||
{
|
||||
"user_message": "что нам отгружал чепурнов, какой товар или услугу?"
|
||||
},
|
||||
{
|
||||
"user_message": "какие остатки на складе на март 2021"
|
||||
},
|
||||
{
|
||||
"user_message": "По выбранному объекту \"Столешница 600*3050*26 альмандин\": кто нам это поставил?"
|
||||
},
|
||||
{
|
||||
"user_message": "что ты умеешь?"
|
||||
},
|
||||
{
|
||||
"user_message": "По выбранному объекту \"Столешница 600*3050*26 альмандин\": покажи документы по этой позиции"
|
||||
},
|
||||
{
|
||||
"user_message": "а ты помнишь, что мы по этой позиции уже выяснили?"
|
||||
},
|
||||
{
|
||||
"user_message": "покажи еще раз остатки на эту же дату"
|
||||
},
|
||||
{
|
||||
"user_message": "кто нам должен на март 2020"
|
||||
},
|
||||
{
|
||||
"user_message": "остатки по складу на эту же дату"
|
||||
},
|
||||
{
|
||||
"user_message": "а исторические остатки тоже можешь?"
|
||||
},
|
||||
{
|
||||
"user_message": "хвосты покажи по счету 60 на август 2022"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
{
|
||||
"suite_id": "assistant_saved_session_gen-ag04170941-87680e",
|
||||
"suite_version": "0.1.0",
|
||||
"schema_version": "assistant_saved_session_suite_v0_1",
|
||||
"generated_at": "2026-04-17T09:41:32+00:00",
|
||||
"generation_id": "gen-ag04170941-87680e",
|
||||
"mode": "saved_user_sessions",
|
||||
"title": "AGENT | Phase 7 mixed replay for documents, selected-object continuity, meta context, and cross-domain pivots",
|
||||
"domain": "address_phase7_meta_domain_mix",
|
||||
"scenario_count": 1,
|
||||
"case_ids": [
|
||||
"SAVED-001"
|
||||
],
|
||||
"cases": [
|
||||
{
|
||||
"case_id": "SAVED-001",
|
||||
"scenario_tag": "agent_saved_user_sessions",
|
||||
"title": "AGENT | Phase 7 mixed replay for documents, selected-object continuity, meta context, and cross-domain pivots",
|
||||
"question_type": "followup",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "привет, как дела?"
|
||||
},
|
||||
{
|
||||
"user_message": "по какой компании мы сейчас работаем?"
|
||||
},
|
||||
{
|
||||
"user_message": "покажи все документы по чепурнову"
|
||||
},
|
||||
{
|
||||
"user_message": "что нам отгружал чепурнов, какой товар или услугу?"
|
||||
},
|
||||
{
|
||||
"user_message": "какие остатки на складе на март 2021"
|
||||
},
|
||||
{
|
||||
"user_message": "По выбранному объекту \"Столешница 600*3050*26 альмандин\": кто нам это поставил?"
|
||||
},
|
||||
{
|
||||
"user_message": "что ты умеешь?"
|
||||
},
|
||||
{
|
||||
"user_message": "По выбранному объекту \"Столешница 600*3050*26 альмандин\": покажи документы по этой позиции"
|
||||
},
|
||||
{
|
||||
"user_message": "а ты помнишь, что мы по этой позиции уже выяснили?"
|
||||
},
|
||||
{
|
||||
"user_message": "покажи еще раз остатки на эту же дату"
|
||||
},
|
||||
{
|
||||
"user_message": "кто нам должен на март 2020"
|
||||
},
|
||||
{
|
||||
"user_message": "остатки по складу на эту же дату"
|
||||
},
|
||||
{
|
||||
"user_message": "а исторические остатки тоже можешь?"
|
||||
},
|
||||
{
|
||||
"user_message": "хвосты покажи по счету 60 на август 2022"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,505 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from collections import Counter
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
ORCHESTRATION_DIR = REPO_ROOT / "docs" / "orchestration"
|
||||
SAVED_SESSIONS_DIR = REPO_ROOT / "llm_normalizer" / "data" / "autorun_generators" / "saved_sessions"
|
||||
TRUTH_HARNESS_GLOB = "address_truth_harness*.json"
|
||||
CATALOG_SCHEMA_VERSION = "agent_semantic_source_catalog_v1"
|
||||
|
||||
INVENTORY_KEYWORDS = ("остатк", "склад", "41 счет")
|
||||
META_SCOPE_KEYWORDS = ("по какой компании", "по какой организации", "по общей базе", "по какой базе")
|
||||
META_CAPABILITY_KEYWORDS = ("что ты умеешь", "что можешь", "умеешь")
|
||||
META_MEMORY_KEYWORDS = ("ты помнишь", "что мы уже выяснили", "по этой позиции уже")
|
||||
META_HISTORICAL_KEYWORDS = ("историческ",)
|
||||
SMALLTALK_KEYWORDS = ("привет", "как дела", "йо", "че как", "приветик")
|
||||
COUNTERPARTY_DOCS_KEYWORDS = ("покажи все документы", "покажи все доки")
|
||||
COUNTERPARTY_SHIPMENT_KEYWORDS = ("что нам отгружал", "какой товар или услугу")
|
||||
ACCOUNT_60_KEYWORDS = ("хвосты по счету 60",)
|
||||
RECEIVABLES_KEYWORDS = ("кто нам должен",)
|
||||
VAT_KEYWORDS = ("ндс",)
|
||||
|
||||
RECIPE_LIBRARY: dict[str, dict[str, Any]] = {
|
||||
"turnaround_11_phase7_meta_domain_mix": {
|
||||
"scenario_id": "address_truth_harness_phase7_meta_domain_mix",
|
||||
"domain": "address_phase7_meta_domain_mix",
|
||||
"title": "Phase 7 mixed replay for documents, selected-object continuity, meta context, and cross-domain pivots",
|
||||
"description": (
|
||||
"Mixed AGENT replay for turnaround 11. The pack interleaves counterparty documents, "
|
||||
"inventory root and selected-object continuity, meta-space interruptions, memory recap, "
|
||||
"receivables to inventory same-date pivot, and an account 60 tail check."
|
||||
),
|
||||
"bindings": {},
|
||||
"step_plan": [
|
||||
{
|
||||
"slot_id": "slot_01_smalltalk",
|
||||
"criticality": "info",
|
||||
"preferred_candidate_ids": [
|
||||
"address_truth_harness_phase6_provider_axis_mix:step_01_smalltalk",
|
||||
"address_truth_harness_test2:step_01_chat_opening",
|
||||
],
|
||||
"required_tags": ["meta_smalltalk"],
|
||||
},
|
||||
{
|
||||
"slot_id": "slot_03_counterparty_documents",
|
||||
"criticality": "critical",
|
||||
"preferred_candidate_ids": [
|
||||
"address_truth_harness_phase4_coverage_evidence_mix:step_01_counterparty_documents",
|
||||
"address_truth_harness_targeted_counterparty_tails:step_01_documents_by_counterparty",
|
||||
],
|
||||
"required_tags": ["counterparty_documents"],
|
||||
},
|
||||
{
|
||||
"slot_id": "slot_04_counterparty_shipment_fallback",
|
||||
"criticality": "critical",
|
||||
"preferred_candidate_ids": [
|
||||
"address_truth_harness_phase4_coverage_evidence_mix:step_02_counterparty_shipments_or_fallback",
|
||||
"address_truth_harness_targeted_counterparty_tails:step_02_counterparty_item_flow",
|
||||
],
|
||||
"required_tags": ["counterparty_shipment_fallback"],
|
||||
},
|
||||
{
|
||||
"slot_id": "slot_05_inventory_root",
|
||||
"criticality": "critical",
|
||||
"preferred_candidate_ids": [
|
||||
"address_truth_harness_phase7_acceptance_gate_mix:step_01_inventory_march_2021",
|
||||
"address_truth_harness_phase5_meta_memory_mix:step_01_inventory_root_march_2021",
|
||||
],
|
||||
"required_tags": ["inventory_root"],
|
||||
},
|
||||
{
|
||||
"slot_id": "slot_06_selected_object_supplier",
|
||||
"criticality": "critical",
|
||||
"preferred_candidate_ids": [
|
||||
"address_truth_harness_phase7_acceptance_gate_mix:step_02_selected_item_supplier",
|
||||
"address_truth_harness_inventory_provenance_restore:step_02_selected_item_supplier",
|
||||
],
|
||||
"required_tags": ["selected_object_supplier"],
|
||||
},
|
||||
{
|
||||
"slot_id": "slot_07_meta_capability",
|
||||
"criticality": "warning",
|
||||
"preferred_candidate_ids": [
|
||||
"address_truth_harness_phase5_meta_memory_mix:step_05_capability_meta_interrupt",
|
||||
"address_truth_harness_phase6_provider_axis_mix:step_03_capability_meta",
|
||||
],
|
||||
"required_tags": ["meta_capability"],
|
||||
},
|
||||
{
|
||||
"slot_id": "slot_08_selected_object_documents",
|
||||
"criticality": "critical",
|
||||
"preferred_candidate_ids": [
|
||||
"address_truth_harness_phase7_acceptance_gate_mix:step_03_selected_item_documents",
|
||||
"address_truth_harness_inventory_provenance_restore:step_03_selected_item_documents",
|
||||
],
|
||||
"required_tags": ["selected_object_documents"],
|
||||
},
|
||||
{
|
||||
"slot_id": "slot_09_meta_memory",
|
||||
"criticality": "warning",
|
||||
"preferred_candidate_ids": [
|
||||
"address_truth_harness_phase5_meta_memory_mix:step_06_memory_recap_after_interrupts",
|
||||
],
|
||||
"required_tags": ["meta_memory"],
|
||||
"override_fields": {
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)помню",
|
||||
"(?i)столешница 600\\*3050\\*26 альмандин"
|
||||
]
|
||||
},
|
||||
},
|
||||
{
|
||||
"slot_id": "slot_10_same_date_restore",
|
||||
"criticality": "critical",
|
||||
"preferred_candidate_ids": [
|
||||
"address_truth_harness_phase7_acceptance_gate_mix:step_04_inventory_same_date_restore",
|
||||
"address_truth_harness_phase4_coverage_evidence_mix:step_05_inventory_same_date_restore",
|
||||
],
|
||||
"required_tags": ["same_date_restore"],
|
||||
},
|
||||
{
|
||||
"slot_id": "slot_11_receivables_root",
|
||||
"criticality": "critical",
|
||||
"preferred_candidate_ids": [
|
||||
"address_truth_harness_test2:step_02_receivables_march_2020",
|
||||
],
|
||||
"required_tags": ["settlements_receivables"],
|
||||
},
|
||||
{
|
||||
"slot_id": "slot_12_same_date_pivot",
|
||||
"criticality": "critical",
|
||||
"preferred_candidate_ids": [
|
||||
"address_truth_harness_test2:step_03_inventory_same_date",
|
||||
],
|
||||
"required_tags": ["same_date_pivot"],
|
||||
},
|
||||
{
|
||||
"slot_id": "slot_13_meta_historical",
|
||||
"criticality": "warning",
|
||||
"preferred_candidate_ids": [
|
||||
"address_truth_harness_phase7_acceptance_gate_mix:step_06_historical_capability_followup",
|
||||
"address_truth_harness_phase5_meta_memory_mix:step_02_inventory_history_capability_followup",
|
||||
],
|
||||
"required_tags": ["meta_historical_capability"],
|
||||
},
|
||||
{
|
||||
"slot_id": "slot_14_account_60",
|
||||
"criticality": "critical",
|
||||
"preferred_candidate_ids": [
|
||||
"address_truth_harness_targeted_counterparty_tails:step_04_open_items_account_60",
|
||||
],
|
||||
"required_tags": ["settlements_account_60"],
|
||||
},
|
||||
{
|
||||
"slot_id": "slot_15_meta_scope",
|
||||
"criticality": "warning",
|
||||
"preferred_candidate_ids": [
|
||||
"address_truth_harness_phase6_provider_axis_mix:step_02_data_scope_meta",
|
||||
"address_truth_harness_phase5_meta_memory_mix:step_03_data_scope_meta_interrupt",
|
||||
],
|
||||
"required_tags": ["meta_scope"],
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def read_json(path: Path) -> Any:
|
||||
return json.loads(path.read_text(encoding="utf-8-sig"))
|
||||
|
||||
|
||||
def write_json(path: Path, payload: Any) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8", newline="\n")
|
||||
|
||||
|
||||
def write_text(path: Path, content: str) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(content, encoding="utf-8", newline="\n")
|
||||
|
||||
|
||||
def _now_iso() -> str:
|
||||
return datetime.now(timezone.utc).replace(microsecond=0).isoformat()
|
||||
|
||||
|
||||
def _normalize_tags(raw_tags: list[str]) -> list[str]:
|
||||
ordered: list[str] = []
|
||||
seen: set[str] = set()
|
||||
for raw_tag in raw_tags:
|
||||
tag = str(raw_tag or "").strip().lower()
|
||||
if not tag or tag in seen:
|
||||
continue
|
||||
seen.add(tag)
|
||||
ordered.append(tag)
|
||||
return ordered
|
||||
|
||||
|
||||
def _contains_any(text: str, needles: tuple[str, ...]) -> bool:
|
||||
normalized = text.casefold()
|
||||
return any(needle.casefold() in normalized for needle in needles)
|
||||
|
||||
|
||||
def _base_step_tags(question: str, step_id: str, title: str, expected_intents: list[str]) -> list[str]:
|
||||
haystack = " | ".join([question, step_id, title, " ".join(expected_intents)])
|
||||
tags: list[str] = []
|
||||
if _contains_any(haystack, SMALLTALK_KEYWORDS):
|
||||
tags.append("meta_smalltalk")
|
||||
if _contains_any(haystack, META_SCOPE_KEYWORDS) or "data_scope" in step_id:
|
||||
tags.append("meta_scope")
|
||||
if _contains_any(haystack, META_CAPABILITY_KEYWORDS) or "capability_meta" in step_id:
|
||||
tags.append("meta_capability")
|
||||
if _contains_any(haystack, META_MEMORY_KEYWORDS) or "memory" in step_id:
|
||||
tags.append("meta_memory")
|
||||
if _contains_any(haystack, META_HISTORICAL_KEYWORDS) or "historical" in step_id:
|
||||
tags.append("meta_historical_capability")
|
||||
if _contains_any(haystack, COUNTERPARTY_DOCS_KEYWORDS) or "counterparty_documents" in step_id or "documents_by_counterparty" in step_id:
|
||||
tags.append("counterparty_documents")
|
||||
if _contains_any(haystack, COUNTERPARTY_SHIPMENT_KEYWORDS) or "counterparty_shipments" in step_id or "counterparty_item_flow" in step_id:
|
||||
tags.append("counterparty_shipment_fallback")
|
||||
if any(intent == "inventory_on_hand_as_of_date" for intent in expected_intents) or _contains_any(haystack, INVENTORY_KEYWORDS):
|
||||
tags.append("inventory_root")
|
||||
if "same_date_restore" in step_id or "same_date_restore" in title.casefold():
|
||||
tags.append("same_date_restore")
|
||||
if "inventory_same_date" in step_id and "restore" not in step_id:
|
||||
tags.append("same_date_pivot")
|
||||
if any(intent == "receivables_confirmed_as_of_date" for intent in expected_intents) or _contains_any(haystack, RECEIVABLES_KEYWORDS):
|
||||
tags.append("settlements_receivables")
|
||||
if "open_items_account_60" in step_id or _contains_any(haystack, ACCOUNT_60_KEYWORDS):
|
||||
tags.append("settlements_account_60")
|
||||
if "vat" in step_id or _contains_any(haystack, VAT_KEYWORDS):
|
||||
tags.append("vat")
|
||||
if any(intent == "inventory_purchase_provenance_for_item" for intent in expected_intents):
|
||||
tags.extend(["selected_object", "selected_object_supplier"])
|
||||
if any(intent == "inventory_purchase_documents_for_item" for intent in expected_intents):
|
||||
tags.extend(["selected_object", "selected_object_documents"])
|
||||
if any(intent == "inventory_sale_trace_for_item" for intent in expected_intents):
|
||||
tags.extend(["selected_object", "selected_object_sale"])
|
||||
if "selected_item" in step_id or "по выбранному объекту" in question.casefold():
|
||||
tags.append("selected_object")
|
||||
return _normalize_tags(tags)
|
||||
|
||||
|
||||
def classify_truth_harness_step(spec_path: Path, spec: dict[str, Any], step: dict[str, Any]) -> dict[str, Any]:
|
||||
question = str(step.get("question") or "").strip()
|
||||
step_id = str(step.get("step_id") or "").strip()
|
||||
title = str(step.get("title") or step_id).strip() or step_id
|
||||
expected_intents = [str(item).strip() for item in (step.get("expected_intents") or []) if str(item).strip()]
|
||||
semantic_tags = _normalize_tags(
|
||||
[*step.get("semantic_tags", []), *_base_step_tags(question, step_id, title, expected_intents)]
|
||||
)
|
||||
return {
|
||||
"entry_id": f"{spec_path.stem}:{step_id}",
|
||||
"source_type": "truth_harness_step",
|
||||
"source_file": str(spec_path.relative_to(REPO_ROOT)).replace("\\", "/"),
|
||||
"source_title": spec.get("title"),
|
||||
"scenario_id": spec.get("scenario_id"),
|
||||
"domain": spec.get("domain"),
|
||||
"reusable_in_agent_pack": True,
|
||||
"step_id": step_id,
|
||||
"title": title,
|
||||
"question": question,
|
||||
"criticality": str(step.get("criticality") or "critical"),
|
||||
"expected_intents": expected_intents,
|
||||
"semantic_tags": semantic_tags,
|
||||
"step_payload": step,
|
||||
}
|
||||
|
||||
|
||||
def classify_saved_session_question(session_path: Path, session: dict[str, Any], question: str, index: int) -> dict[str, Any]:
|
||||
tags = _base_step_tags(
|
||||
question=question,
|
||||
step_id=f"saved_session_q{index:02d}",
|
||||
title=str(session.get("title") or ""),
|
||||
expected_intents=[],
|
||||
)
|
||||
return {
|
||||
"entry_id": f"{session_path.stem}:q{index:02d}",
|
||||
"source_type": "saved_session_question",
|
||||
"source_file": str(session_path.relative_to(REPO_ROOT)).replace("\\", "/"),
|
||||
"source_title": session.get("title"),
|
||||
"scenario_id": session.get("generation_id"),
|
||||
"domain": session.get("domain"),
|
||||
"reusable_in_agent_pack": False,
|
||||
"step_id": f"saved_session_q{index:02d}",
|
||||
"title": f"Saved session question {index}",
|
||||
"question": question,
|
||||
"criticality": "info",
|
||||
"expected_intents": [],
|
||||
"semantic_tags": tags,
|
||||
"session_mode": session.get("mode"),
|
||||
"agent_run": bool(session.get("agent_run") or (session.get("context") or {}).get("agent_run")),
|
||||
}
|
||||
|
||||
|
||||
def build_source_catalog() -> dict[str, Any]:
|
||||
truth_harness_entries: list[dict[str, Any]] = []
|
||||
saved_session_entries: list[dict[str, Any]] = []
|
||||
for spec_path in sorted(ORCHESTRATION_DIR.glob(TRUTH_HARNESS_GLOB)):
|
||||
spec = read_json(spec_path)
|
||||
for step in spec.get("steps") or []:
|
||||
if isinstance(step, dict) and step.get("question"):
|
||||
truth_harness_entries.append(classify_truth_harness_step(spec_path, spec, step))
|
||||
for session_path in sorted(SAVED_SESSIONS_DIR.glob("*.json")):
|
||||
session = read_json(session_path)
|
||||
for index, question in enumerate(session.get("questions") or [], start=1):
|
||||
text = str(question or "").strip()
|
||||
if text:
|
||||
saved_session_entries.append(classify_saved_session_question(session_path, session, text, index))
|
||||
|
||||
tag_counter: Counter[str] = Counter()
|
||||
reusable_tag_counter: Counter[str] = Counter()
|
||||
for entry in [*truth_harness_entries, *saved_session_entries]:
|
||||
for tag in entry.get("semantic_tags") or []:
|
||||
tag_counter[tag] += 1
|
||||
if entry.get("reusable_in_agent_pack"):
|
||||
reusable_tag_counter[tag] += 1
|
||||
|
||||
return {
|
||||
"schema_version": CATALOG_SCHEMA_VERSION,
|
||||
"generated_at": _now_iso(),
|
||||
"summary": {
|
||||
"truth_harness_steps_total": len(truth_harness_entries),
|
||||
"saved_session_questions_total": len(saved_session_entries),
|
||||
"reusable_truth_harness_tags": dict(sorted(reusable_tag_counter.items())),
|
||||
"all_tags": dict(sorted(tag_counter.items())),
|
||||
},
|
||||
"truth_harness_entries": truth_harness_entries,
|
||||
"saved_session_entries": saved_session_entries,
|
||||
}
|
||||
|
||||
|
||||
def _catalog_markdown(catalog: dict[str, Any]) -> str:
|
||||
summary = catalog.get("summary") if isinstance(catalog.get("summary"), dict) else {}
|
||||
lines = [
|
||||
"# Agent semantic source catalog",
|
||||
"",
|
||||
f"- truth_harness_steps_total: `{summary.get('truth_harness_steps_total', 0)}`",
|
||||
f"- saved_session_questions_total: `{summary.get('saved_session_questions_total', 0)}`",
|
||||
"",
|
||||
"## Reusable truth-harness tags",
|
||||
]
|
||||
reusable_tags = summary.get("reusable_truth_harness_tags") or {}
|
||||
for tag, count in reusable_tags.items():
|
||||
lines.append(f"- `{tag}`: `{count}`")
|
||||
lines.extend(["", "## Reusable truth-harness steps"])
|
||||
for entry in catalog.get("truth_harness_entries") or []:
|
||||
tags = ", ".join(entry.get("semantic_tags") or []) or "none"
|
||||
lines.append(
|
||||
f"- `{entry.get('entry_id')}` | tags: {tags} | question: {entry.get('question')}"
|
||||
)
|
||||
lines.extend(["", "## Saved session questions"])
|
||||
for entry in catalog.get("saved_session_entries") or []:
|
||||
tags = ", ".join(entry.get("semantic_tags") or []) or "none"
|
||||
lines.append(
|
||||
f"- `{entry.get('entry_id')}` | tags: {tags} | question: {entry.get('question')}"
|
||||
)
|
||||
return "\n".join(lines).strip() + "\n"
|
||||
|
||||
|
||||
def _find_catalog_entry(catalog: dict[str, Any], entry_id: str) -> dict[str, Any] | None:
|
||||
for entry in catalog.get("truth_harness_entries") or []:
|
||||
if entry.get("entry_id") == entry_id:
|
||||
return entry
|
||||
return None
|
||||
|
||||
|
||||
def _entry_matches_tags(entry: dict[str, Any], required_tags: list[str]) -> bool:
|
||||
entry_tags = set(entry.get("semantic_tags") or [])
|
||||
return all(tag in entry_tags for tag in required_tags)
|
||||
|
||||
|
||||
def _select_recipe_entry(
|
||||
catalog: dict[str, Any],
|
||||
slot: dict[str, Any],
|
||||
used_entry_ids: set[str],
|
||||
) -> dict[str, Any]:
|
||||
preferred_ids = [str(item).strip() for item in (slot.get("preferred_candidate_ids") or []) if str(item).strip()]
|
||||
required_tags = [str(item).strip().lower() for item in (slot.get("required_tags") or []) if str(item).strip()]
|
||||
for entry_id in preferred_ids:
|
||||
entry = _find_catalog_entry(catalog, entry_id)
|
||||
if entry and entry.get("reusable_in_agent_pack") and entry_id not in used_entry_ids:
|
||||
if not required_tags or _entry_matches_tags(entry, required_tags):
|
||||
return entry
|
||||
candidates = [
|
||||
entry
|
||||
for entry in catalog.get("truth_harness_entries") or []
|
||||
if entry.get("reusable_in_agent_pack")
|
||||
and entry.get("entry_id") not in used_entry_ids
|
||||
and _entry_matches_tags(entry, required_tags)
|
||||
]
|
||||
if not candidates:
|
||||
raise RuntimeError(
|
||||
f"Could not resolve slot `{slot.get('slot_id')}` with tags {required_tags or ['<none>']}"
|
||||
)
|
||||
return candidates[0]
|
||||
|
||||
|
||||
def build_recipe_spec(catalog: dict[str, Any], recipe_name: str) -> dict[str, Any]:
|
||||
recipe = RECIPE_LIBRARY.get(recipe_name)
|
||||
if recipe is None:
|
||||
supported = ", ".join(sorted(RECIPE_LIBRARY))
|
||||
raise RuntimeError(f"Unknown recipe `{recipe_name}`. Supported recipes: {supported}")
|
||||
used_entry_ids: set[str] = set()
|
||||
steps: list[dict[str, Any]] = []
|
||||
for slot_index, slot in enumerate(recipe.get("step_plan") or [], start=1):
|
||||
entry = _select_recipe_entry(catalog, slot, used_entry_ids)
|
||||
used_entry_ids.add(str(entry["entry_id"]))
|
||||
payload = dict(entry["step_payload"])
|
||||
payload["criticality"] = str(slot.get("criticality") or payload.get("criticality") or "critical")
|
||||
payload["semantic_tags"] = _normalize_tags(
|
||||
[*payload.get("semantic_tags", []), *(entry.get("semantic_tags") or [])]
|
||||
)
|
||||
override_fields = slot.get("override_fields")
|
||||
if isinstance(override_fields, dict):
|
||||
payload.update(override_fields)
|
||||
payload["notes"] = (
|
||||
f"{str(payload.get('notes') or '').strip()} "
|
||||
f"[mixed_pack_slot={slot.get('slot_id')} source={entry.get('entry_id')}]"
|
||||
).strip()
|
||||
steps.append(payload)
|
||||
return {
|
||||
"schema_version": "domain_truth_harness_spec_v1",
|
||||
"scenario_id": recipe["scenario_id"],
|
||||
"domain": recipe["domain"],
|
||||
"title": recipe["title"],
|
||||
"description": recipe["description"],
|
||||
"bindings": recipe.get("bindings") or {},
|
||||
"steps": steps,
|
||||
}
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description="Inventory reusable semantic sources and build mixed AGENT replay packs.")
|
||||
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||
|
||||
inventory_parser = subparsers.add_parser("inventory", help="Build a source catalog from harness specs and saved sessions.")
|
||||
inventory_parser.add_argument(
|
||||
"--output-json",
|
||||
default="docs/orchestration/agent_semantic_source_catalog.json",
|
||||
help="Where to write the catalog JSON.",
|
||||
)
|
||||
inventory_parser.add_argument(
|
||||
"--output-md",
|
||||
default="docs/orchestration/agent_semantic_source_catalog.md",
|
||||
help="Where to write the catalog markdown summary.",
|
||||
)
|
||||
|
||||
build_parser = subparsers.add_parser("build-pack", help="Build a mixed truth-harness pack from catalogued sources.")
|
||||
build_parser.add_argument("--recipe", required=True, help="Recipe name to build.")
|
||||
build_parser.add_argument(
|
||||
"--output-spec",
|
||||
default="docs/orchestration/address_truth_harness_phase7_meta_domain_mix.json",
|
||||
help="Where to write the generated truth-harness spec.",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def _resolve_repo_path(raw_path: str) -> Path:
|
||||
path = Path(raw_path)
|
||||
return path if path.is_absolute() else (REPO_ROOT / path).resolve()
|
||||
|
||||
|
||||
def run_inventory(args: argparse.Namespace) -> int:
|
||||
catalog = build_source_catalog()
|
||||
output_json = _resolve_repo_path(args.output_json)
|
||||
output_md = _resolve_repo_path(args.output_md)
|
||||
write_json(output_json, catalog)
|
||||
write_text(output_md, _catalog_markdown(catalog))
|
||||
print(f"Catalog written to {output_json}")
|
||||
print(f"Summary written to {output_md}")
|
||||
print(
|
||||
"Reusable truth-harness tags:",
|
||||
", ".join(sorted((catalog.get("summary") or {}).get("reusable_truth_harness_tags", {}).keys())),
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
def run_build_pack(args: argparse.Namespace) -> int:
|
||||
catalog = build_source_catalog()
|
||||
spec = build_recipe_spec(catalog, args.recipe)
|
||||
output_spec = _resolve_repo_path(args.output_spec)
|
||||
write_json(output_spec, spec)
|
||||
print(f"Mixed pack written to {output_spec}")
|
||||
print(f"scenario_id={spec['scenario_id']}")
|
||||
print(f"steps={len(spec['steps'])}")
|
||||
return 0
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
if args.command == "inventory":
|
||||
return run_inventory(args)
|
||||
if args.command == "build-pack":
|
||||
return run_build_pack(args)
|
||||
raise RuntimeError(f"Unsupported command: {args.command}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
|
@ -84,6 +84,7 @@ def normalize_step_spec(index: int, raw_step: Any) -> dict[str, Any]:
|
|||
normalized_step = dcl.normalize_step_definition(index, raw_step)
|
||||
step = raw_step if isinstance(raw_step, dict) else {}
|
||||
normalized_step["criticality"] = normalize_criticality(step.get("criticality"))
|
||||
normalized_step["semantic_tags"] = dcl.normalize_string_list(step.get("semantic_tags"))
|
||||
normalized_step["allowed_reply_types"] = normalize_pattern_list(step.get("allowed_reply_types"))
|
||||
normalized_step["allowed_limited_reason_categories"] = normalize_pattern_list(
|
||||
step.get("allowed_limited_reason_categories")
|
||||
|
|
@ -219,6 +220,7 @@ def build_generated_manifest(spec: dict[str, Any]) -> dict[str, Any]:
|
|||
}
|
||||
for field_name in TECHNICAL_QUESTION_FIELDS:
|
||||
manifest_step[field_name] = step.get(field_name)
|
||||
manifest_step["semantic_tags"] = step.get("semantic_tags") or []
|
||||
manifest_steps.append(manifest_step)
|
||||
previous_step_id = step["step_id"]
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,13 @@ SELECTED_OBJECT_INTENTS = {
|
|||
"inventory_profitability_for_item",
|
||||
"inventory_purchase_to_sale_chain",
|
||||
}
|
||||
META_CONTEXT_TAGS = {
|
||||
"meta_smalltalk",
|
||||
"meta_scope",
|
||||
"meta_capability",
|
||||
"meta_memory",
|
||||
"meta_historical_capability",
|
||||
}
|
||||
|
||||
|
||||
def _now_iso() -> str:
|
||||
|
|
@ -49,13 +56,23 @@ def _highest_priority(findings: list[dict[str, Any]]) -> str:
|
|||
return sorted(priorities, key=lambda item: PRIORITY_RANK.get(item, 99))[0]
|
||||
|
||||
|
||||
def _normalize_semantic_tags(step: dict[str, Any]) -> set[str]:
|
||||
raw = step.get("semantic_tags")
|
||||
if not isinstance(raw, list):
|
||||
return set()
|
||||
return {str(item).strip().lower() for item in raw if str(item).strip()}
|
||||
|
||||
|
||||
def _has_selected_object_signal(step: dict[str, Any]) -> bool:
|
||||
question = str(step.get("question_template") or "").lower()
|
||||
semantic_tags = _normalize_semantic_tags(step)
|
||||
expected_intents = {
|
||||
str(item).strip()
|
||||
for item in (step.get("expected_intents") or [])
|
||||
if str(item).strip()
|
||||
}
|
||||
if "selected_object" in semantic_tags:
|
||||
return True
|
||||
if expected_intents & SELECTED_OBJECT_INTENTS:
|
||||
return True
|
||||
return any(
|
||||
|
|
@ -70,6 +87,19 @@ def _has_selected_object_signal(step: dict[str, Any]) -> bool:
|
|||
)
|
||||
|
||||
|
||||
def _has_meta_context_signal(step: dict[str, Any]) -> bool:
|
||||
semantic_tags = _normalize_semantic_tags(step)
|
||||
if semantic_tags & META_CONTEXT_TAGS:
|
||||
return True
|
||||
title = str(step.get("title") or "").lower()
|
||||
step_id = str(step.get("step_id") or "").lower()
|
||||
question = str(step.get("question_template") or "").lower()
|
||||
return any(
|
||||
marker in f"{step_id} {title} {question}"
|
||||
for marker in ("meta", "memory", "smalltalk", "историческ", "что ты умеешь", "что можешь", "по какой компании", "по какой базе")
|
||||
)
|
||||
|
||||
|
||||
def _is_direct_answer_code(code: str) -> bool:
|
||||
return code.startswith("required_direct_answer_") or code.startswith("forbidden_direct_answer_")
|
||||
|
||||
|
|
@ -105,15 +135,26 @@ def _is_human_answer_quality_code(code: str) -> bool:
|
|||
}
|
||||
|
||||
|
||||
def _is_meta_context_code(code: str) -> bool:
|
||||
return (
|
||||
_is_route_code(code)
|
||||
or _is_truth_gate_code(code)
|
||||
or _is_human_answer_quality_code(code)
|
||||
or _is_direct_answer_code(code)
|
||||
)
|
||||
|
||||
|
||||
def _derive_step_invariant_failures(step: dict[str, Any], findings: list[dict[str, Any]]) -> dict[str, bool]:
|
||||
codes = [str(item.get("code") or "").strip() for item in findings]
|
||||
selected_object_step = _has_selected_object_signal(step)
|
||||
meta_context_step = _has_meta_context_signal(step)
|
||||
return {
|
||||
"direct_answer": any(_is_direct_answer_code(code) for code in codes),
|
||||
"temporal_honesty": any(_is_temporal_code(code) for code in codes),
|
||||
"selected_object_continuity": selected_object_step and any(_is_route_code(code) for code in codes),
|
||||
"truth_gate": any(_is_truth_gate_code(code) for code in codes),
|
||||
"human_answer_quality": any(_is_human_answer_quality_code(code) for code in codes),
|
||||
"meta_context_integrity": meta_context_step and any(_is_meta_context_code(code) for code in codes),
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -129,6 +170,7 @@ def build_scenario_acceptance_matrix(
|
|||
"selected_object_continuity": 0,
|
||||
"truth_gate": 0,
|
||||
"human_answer_quality": 0,
|
||||
"meta_context_integrity": 0,
|
||||
}
|
||||
|
||||
for index, step in enumerate(spec.get("steps") or [], start=1):
|
||||
|
|
@ -151,11 +193,13 @@ def build_scenario_acceptance_matrix(
|
|||
"title": step.get("title"),
|
||||
"question": step.get("question_template"),
|
||||
"criticality": str(step.get("criticality") or "critical"),
|
||||
"semantic_tags": sorted(_normalize_semantic_tags(step)),
|
||||
"review_status": str(step_state.get("review_status") or "unknown"),
|
||||
"reply_type": step_state.get("reply_type"),
|
||||
"detected_intent": step_state.get("detected_intent"),
|
||||
"capability_id": step_state.get("capability_id"),
|
||||
"selected_object_step": _has_selected_object_signal(step),
|
||||
"meta_context_step": _has_meta_context_signal(step),
|
||||
"highest_unresolved_priority": highest_priority,
|
||||
"unresolved_findings_count": len(findings),
|
||||
"invariant_failures": [name for name, failed in invariant_failures.items() if failed],
|
||||
|
|
@ -169,6 +213,7 @@ def build_scenario_acceptance_matrix(
|
|||
"selected_object_continuity_ok": invariant_failure_counts["selected_object_continuity"] == 0,
|
||||
"truth_gate_ok": invariant_failure_counts["truth_gate"] == 0,
|
||||
"human_answer_quality_ok": invariant_failure_counts["human_answer_quality"] == 0,
|
||||
"meta_context_integrity_ok": invariant_failure_counts["meta_context_integrity"] == 0,
|
||||
}
|
||||
critical_rows = [row for row in rows if row["criticality"] == "critical"]
|
||||
critical_path_green = bool(critical_rows) and all(row["review_status"] == "pass" for row in critical_rows)
|
||||
|
|
@ -274,6 +319,7 @@ def build_scenario_acceptance_matrix_markdown(acceptance_matrix: dict[str, Any])
|
|||
f"- selected_object_continuity_ok: `{invariants.get('selected_object_continuity_ok')}`",
|
||||
f"- truth_gate_ok: `{invariants.get('truth_gate_ok')}`",
|
||||
f"- human_answer_quality_ok: `{invariants.get('human_answer_quality_ok')}`",
|
||||
f"- meta_context_integrity_ok: `{invariants.get('meta_context_integrity_ok')}`",
|
||||
"",
|
||||
"## Steps",
|
||||
]
|
||||
|
|
@ -283,8 +329,10 @@ def build_scenario_acceptance_matrix_markdown(acceptance_matrix: dict[str, Any])
|
|||
f"- `{row.get('step_id')}`",
|
||||
f" review_status: `{row.get('review_status')}`",
|
||||
f" criticality: `{row.get('criticality')}`",
|
||||
f" semantic_tags: {', '.join(row.get('semantic_tags') or []) or 'none'}",
|
||||
f" highest_unresolved_priority: `{row.get('highest_unresolved_priority')}`",
|
||||
f" selected_object_step: `{row.get('selected_object_step')}`",
|
||||
f" meta_context_step: `{row.get('meta_context_step')}`",
|
||||
f" invariant_failures: {', '.join(row.get('invariant_failures') or []) or 'none'}",
|
||||
]
|
||||
)
|
||||
|
|
@ -303,4 +351,6 @@ def build_truth_harness_final_status_markdown(pack_state: dict[str, Any]) -> str
|
|||
f"- temporal_honesty_ok: `{invariants.get('temporal_honesty_ok')}`\n"
|
||||
f"- selected_object_continuity_ok: `{invariants.get('selected_object_continuity_ok')}`\n"
|
||||
f"- truth_gate_ok: `{invariants.get('truth_gate_ok')}`\n"
|
||||
f"- human_answer_quality_ok: `{invariants.get('human_answer_quality_ok')}`\n"
|
||||
f"- meta_context_integrity_ok: `{invariants.get('meta_context_integrity_ok')}`\n"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
|
||||
import agent_semantic_pack_builder as builder
|
||||
|
||||
|
||||
class AgentSemanticPackBuilderTests(unittest.TestCase):
|
||||
def test_build_source_catalog_finds_reusable_meta_and_selected_object_tags(self) -> None:
|
||||
catalog = builder.build_source_catalog()
|
||||
summary = catalog["summary"]
|
||||
|
||||
self.assertGreater(summary["truth_harness_steps_total"], 0)
|
||||
self.assertIn("meta_scope", summary["reusable_truth_harness_tags"])
|
||||
self.assertIn("selected_object_supplier", summary["reusable_truth_harness_tags"])
|
||||
self.assertIn("counterparty_documents", summary["reusable_truth_harness_tags"])
|
||||
|
||||
def test_build_recipe_spec_creates_mixed_phase7_pack(self) -> None:
|
||||
catalog = builder.build_source_catalog()
|
||||
spec = builder.build_recipe_spec(catalog, "turnaround_11_phase7_meta_domain_mix")
|
||||
|
||||
self.assertEqual(spec["scenario_id"], "address_truth_harness_phase7_meta_domain_mix")
|
||||
self.assertGreaterEqual(len(spec["steps"]), 10)
|
||||
all_tags = {tag for step in spec["steps"] for tag in step.get("semantic_tags", [])}
|
||||
self.assertIn("meta_scope", all_tags)
|
||||
self.assertIn("meta_capability", all_tags)
|
||||
self.assertIn("selected_object_supplier", all_tags)
|
||||
self.assertIn("same_date_restore", all_tags)
|
||||
self.assertIn("settlements_receivables", all_tags)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
@ -1,6 +1,11 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
|
||||
import scenario_acceptance_policy as sap
|
||||
|
||||
|
|
@ -18,6 +23,7 @@ class ScenarioAcceptancePolicyTests(unittest.TestCase):
|
|||
"question_template": 'По выбранному объекту "Стол": кто поставил?',
|
||||
"criticality": "critical",
|
||||
"expected_intents": ["inventory_purchase_provenance_for_item"],
|
||||
"semantic_tags": ["selected_object"],
|
||||
}
|
||||
],
|
||||
}
|
||||
|
|
@ -52,6 +58,7 @@ class ScenarioAcceptancePolicyTests(unittest.TestCase):
|
|||
self.assertFalse(pack_state["invariants"]["selected_object_continuity_ok"])
|
||||
self.assertFalse(pack_state["invariants"]["temporal_honesty_ok"])
|
||||
self.assertEqual(pack_state["unresolved_p0_count"], 2)
|
||||
self.assertTrue(pack_state["invariants"]["meta_context_integrity_ok"])
|
||||
|
||||
def test_accepts_when_all_review_and_acceptance_invariants_are_green(self) -> None:
|
||||
spec = {
|
||||
|
|
@ -65,6 +72,7 @@ class ScenarioAcceptancePolicyTests(unittest.TestCase):
|
|||
"question_template": "какие остатки на складе на март 2021",
|
||||
"criticality": "critical",
|
||||
"expected_intents": ["inventory_on_hand_as_of_date"],
|
||||
"semantic_tags": ["inventory_root"],
|
||||
}
|
||||
],
|
||||
}
|
||||
|
|
@ -97,6 +105,52 @@ class ScenarioAcceptancePolicyTests(unittest.TestCase):
|
|||
self.assertTrue(pack_state["critical_path_green"])
|
||||
self.assertTrue(all(pack_state["invariants"].values()))
|
||||
|
||||
def test_flags_meta_context_integrity_when_meta_step_leaks_technical_answer_shape(self) -> None:
|
||||
spec = {
|
||||
"scenario_id": "demo_phase7_meta",
|
||||
"domain": "inventory_demo",
|
||||
"title": "Demo meta",
|
||||
"steps": [
|
||||
{
|
||||
"step_id": "step_meta",
|
||||
"title": "Capability meta",
|
||||
"question_template": "что ты умеешь?",
|
||||
"criticality": "warning",
|
||||
"semantic_tags": ["meta_capability"],
|
||||
}
|
||||
],
|
||||
}
|
||||
scenario_state = {
|
||||
"session_id": "asst-meta",
|
||||
"step_outputs": {
|
||||
"step_meta": {
|
||||
"review_status": "warning",
|
||||
"reply_type": "factual_with_explanation",
|
||||
"detected_intent": None,
|
||||
"capability_id": None,
|
||||
"review_findings": [
|
||||
{"code": "forbidden_answer_pattern_hit", "severity": "warning"},
|
||||
],
|
||||
}
|
||||
},
|
||||
}
|
||||
review_summary = {
|
||||
"review_source": "live_strict_replay",
|
||||
"overall_status": "warning",
|
||||
"steps_total": 1,
|
||||
"steps_passed": 0,
|
||||
"steps_with_warning": 1,
|
||||
"steps_failed": 0,
|
||||
}
|
||||
|
||||
acceptance_matrix = sap.build_scenario_acceptance_matrix(spec, scenario_state, review_summary)
|
||||
pack_state = sap.derive_truth_harness_pack_state(spec, scenario_state, review_summary, acceptance_matrix)
|
||||
|
||||
self.assertFalse(pack_state["invariants"]["meta_context_integrity_ok"])
|
||||
row = acceptance_matrix["rows"][0]
|
||||
self.assertTrue(row["meta_context_step"])
|
||||
self.assertIn("meta_context_integrity", row["invariant_failures"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
|||
Loading…
Reference in New Issue