From 82d96f2dbf1300730454862eef2c5d3b6d62bf87 Mon Sep 17 00:00:00 2001 From: "cristiano.hoshikawa" Date: Thu, 10 Sep 2026 16:12:08 -0300 Subject: [PATCH] new feature: Multi-Intent --- IMPLEMENTACAO_MULTI_INTENT.md | 75 +++++ agent_framework_oci/README.md | 29 +- agent_framework_oci/README_en.md | 30 +- .../app/state.py | 2 + .../app/workflows/agent_graph.py | 23 +- .../agent_template_backend/app/state.py | 2 + .../app/workflows/agent_graph.py | 23 +- .../agent_template_backend/app/state.py | 2 + .../app/workflows/agent_graph.py | 23 +- .../agent_template_backend/app/state.py | 2 + .../app/workflows/agent_graph.py | 23 +- .../agent_template_backend/app/state.py | 2 + .../app/workflows/agent_graph.py | 23 +- .../docs/TRANSACTION_SEMANTIC_CONFIRMATION.md | 2 +- .../app/state.py | 2 + .../app/workflows/agent_graph.py | 23 +- .../docs/TRANSACTION_SEMANTIC_CONFIRMATION.md | 2 +- .../agent_template_backend/app/state.py | 2 + .../app/workflows/agent_graph.py | 23 +- .../app/state.py | 2 + .../app/workflows/agent_graph.py | 23 +- .../agent_template_backend/app/state.py | 2 + .../app/workflows/agent_graph.py | 23 +- .../agent_template_backend/app/state.py | 2 + .../app/workflows/agent_graph.py | 23 +- .../features/en/16_multi_intent_planning.md | 27 ++ .../docs/features/en/README.md | 1 + .../pt-BR/16_multi_intent_planning.md | 46 +++ .../docs/features/pt-BR/README.md | 1 + .../src/agent_framework.egg-info/SOURCES.txt | 2 + .../__pycache__/__init__.cpython-313.pyc | Bin 411 -> 413 bytes .../__pycache__/extensions.cpython-313.pyc | Bin 2139 -> 2141 bytes .../__pycache__/idempotency.cpython-313.pyc | Bin 8912 -> 8914 bytes .../__pycache__/observer.cpython-313.pyc | Bin 17555 -> 17557 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 547 -> 549 bytes .../composite_publisher.cpython-313.pyc | Bin 2502 -> 2504 bytes .../__pycache__/event_builder.cpython-313.pyc | Bin 1260 -> 1262 bytes .../__pycache__/factory.cpython-313.pyc | Bin 3996 -> 3998 bytes .../__pycache__/publisher.cpython-313.pyc | Bin 1872 -> 1874 bytes .../tim_payload_mapper.cpython-313.pyc | Bin 9286 -> 9288 bytes .../__pycache__/tim_sequence.cpython-313.pyc | Bin 17561 -> 17563 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 514 -> 516 bytes .../__pycache__/kafka.cpython-313.pyc | Bin 1812 -> 1814 bytes .../__pycache__/langfuse.cpython-313.pyc | Bin 23172 -> 23174 bytes .../__pycache__/oci_streaming.cpython-313.pyc | Bin 1821 -> 1823 bytes .../__pycache__/pubsub.cpython-313.pyc | Bin 7440 -> 7442 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 403 -> 405 bytes .../usage_repository.cpython-313.pyc | Bin 15686 -> 15688 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 213 -> 215 bytes .../cache/__pycache__/cache.cpython-313.pyc | Bin 16523 -> 16525 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 216 -> 218 bytes .../__pycache__/adapters.cpython-313.pyc | Bin 4172 -> 4174 bytes .../channels/__pycache__/base.cpython-313.pyc | Bin 1796 -> 1798 bytes .../__pycache__/gateway.cpython-313.pyc | Bin 5771 -> 5773 bytes .../__pycache__/interruption.cpython-313.pyc | Bin 6456 -> 6458 bytes .../__pycache__/transcription.cpython-313.pyc | Bin 1429 -> 1431 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 923 -> 925 bytes .../checkpoint_repository.cpython-313.pyc | Bin 27163 -> 27165 bytes .../langgraph_saver.cpython-313.pyc | Bin 33265 -> 33267 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 214 -> 216 bytes .../agent_registry.cpython-313.pyc | Bin 6947 -> 6949 bytes .../__pycache__/settings.cpython-313.pyc | Bin 13789 -> 13791 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 306 -> 308 bytes .../mcp_gateway_client.cpython-313.pyc | Bin 3332 -> 3334 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 1501 -> 1503 bytes .../__pycache__/base.cpython-313.pyc | Bin 1375 -> 1377 bytes .../__pycache__/config_loader.cpython-313.pyc | Bin 9160 -> 9162 bytes .../__pycache__/custom_rails.cpython-313.pyc | Bin 4404 -> 4406 bytes .../framework_llm_client.cpython-313.pyc | Bin 19673 -> 19675 bytes .../__pycache__/llm_rails.cpython-313.pyc | Bin 5923 -> 5925 bytes .../output_supervisor.cpython-313.pyc | Bin 24306 -> 24308 bytes .../parallel_executor.cpython-313.pyc | Bin 21862 -> 21864 bytes .../__pycache__/pipeline.cpython-313.pyc | Bin 10889 -> 10891 bytes .../__pycache__/rail_action.cpython-313.pyc | Bin 654 -> 656 bytes .../__pycache__/rail_decision.cpython-313.pyc | Bin 1605 -> 1607 bytes .../__pycache__/rail_result.cpython-313.pyc | Bin 1004 -> 1006 bytes .../__pycache__/rails.cpython-313.pyc | Bin 43324 -> 43326 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 2994 -> 2996 bytes .../__pycache__/_compat.cpython-313.pyc | Bin 2445 -> 2447 bytes .../contestation_validation.cpython-313.pyc | Bin 2177 -> 2179 bytes .../__pycache__/contracts.cpython-313.pyc | Bin 6901 -> 6903 bytes .../__pycache__/input_size.cpython-313.pyc | Bin 3649 -> 3651 bytes .../__pycache__/llm_adapter.cpython-313.pyc | Bin 3799 -> 3801 bytes .../__pycache__/llm_client.cpython-313.pyc | Bin 7454 -> 7456 bytes .../__pycache__/llm_rails.cpython-313.pyc | Bin 8894 -> 8896 bytes .../output_sanitization.cpython-313.pyc | Bin 13910 -> 13912 bytes .../__pycache__/pipeline.cpython-313.pyc | Bin 12661 -> 12663 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 473 -> 475 bytes .../__pycache__/_context.cpython-313.pyc | Bin 5871 -> 5873 bytes .../ausencia_oferta_proativa.cpython-313.pyc | Bin 12205 -> 12207 bytes .../__pycache__/coerencia.cpython-313.pyc | Bin 4157 -> 4159 bytes .../__pycache__/dlex_in.cpython-313.pyc | Bin 1304 -> 1306 bytes .../__pycache__/dlex_out.cpython-313.pyc | Bin 1815 -> 1817 bytes .../__pycache__/fallback.cpython-313.pyc | Bin 17652 -> 17654 bytes .../__pycache__/fraseologia.cpython-313.pyc | Bin 7633 -> 7635 bytes .../__pycache__/out_of_scope.cpython-313.pyc | Bin 17493 -> 17495 bytes .../prompts/__pycache__/pinj.cpython-313.pyc | Bin 8424 -> 8426 bytes .../__pycache__/ragsec.cpython-313.pyc | Bin 1120 -> 1122 bytes .../__pycache__/revprec.cpython-313.pyc | Bin 3150 -> 3152 bytes .../prompts/__pycache__/tox.cpython-313.pyc | Bin 644 -> 646 bytes .../toxicidade_output.cpython-313.pyc | Bin 928 -> 930 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 1663 -> 1665 bytes .../rails/__pycache__/alcada.cpython-313.pyc | Bin 4999 -> 5001 bytes .../rails/__pycache__/anatel.cpython-313.pyc | Bin 9148 -> 9150 bytes .../__pycache__/confirmation.cpython-313.pyc | Bin 11042 -> 11044 bytes .../rails/__pycache__/dlex_in.cpython-313.pyc | Bin 3279 -> 3281 bytes .../__pycache__/dlex_out.cpython-313.pyc | Bin 3331 -> 3333 bytes .../rails/__pycache__/ragsec.cpython-313.pyc | Bin 5462 -> 5464 bytes .../rails/__pycache__/revprec.cpython-313.pyc | Bin 5470 -> 5472 bytes .../rails/__pycache__/tox.cpython-313.pyc | Bin 8293 -> 8295 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 533 -> 535 bytes .../rules/__pycache__/alcada.cpython-313.pyc | Bin 1881 -> 1883 bytes .../__pycache__/pinj_patterns.cpython-313.pyc | Bin 3730 -> 3732 bytes .../__pycache__/tox_blocklist.cpython-313.pyc | Bin 1506 -> 1508 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 408 -> 410 bytes .../__pycache__/mcp_mapper.cpython-313.pyc | Bin 4692 -> 4694 bytes .../__pycache__/models.cpython-313.pyc | Bin 3144 -> 3146 bytes .../__pycache__/resolver.cpython-313.pyc | Bin 4713 -> 4715 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 618 -> 620 bytes .../judges/__pycache__/judge.cpython-313.pyc | Bin 41125 -> 41127 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 225 -> 227 bytes .../__pycache__/llm_client.cpython-313.pyc | Bin 5404 -> 5406 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 233 -> 235 bytes .../prompts/__pycache__/aluc.cpython-313.pyc | Bin 6529 -> 6531 bytes .../prompts/__pycache__/csi.cpython-313.pyc | Bin 1332 -> 1334 bytes .../__pycache__/fallback.cpython-313.pyc | Bin 8035 -> 8037 bytes .../prompts/__pycache__/rqlt.cpython-313.pyc | Bin 1165 -> 1167 bytes .../prompts/__pycache__/vctn.cpython-313.pyc | Bin 626 -> 628 bytes .../llm/__pycache__/__init__.cpython-313.pyc | Bin 476 -> 478 bytes .../llm/__pycache__/base.cpython-313.pyc | Bin 1623 -> 1625 bytes .../profile_resolver.cpython-313.pyc | Bin 9322 -> 9324 bytes .../llm/__pycache__/providers.cpython-313.pyc | Bin 46978 -> 46980 bytes .../structured_output.cpython-313.pyc | Bin 7459 -> 7461 bytes .../llm/__pycache__/types.cpython-313.pyc | Bin 1274 -> 1276 bytes .../mcp/__pycache__/__init__.cpython-313.pyc | Bin 473 -> 475 bytes .../mcp/__pycache__/client.cpython-313.pyc | Bin 18426 -> 18428 bytes .../mcp/__pycache__/models.cpython-313.pyc | Bin 2136 -> 2138 bytes .../mcp/__pycache__/registry.cpython-313.pyc | Bin 4740 -> 4742 bytes .../__pycache__/tool_policy.cpython-313.pyc | Bin 5685 -> 5687 bytes .../__pycache__/tool_router.cpython-313.pyc | Bin 14189 -> 14191 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 1135 -> 1137 bytes .../long_term_extractor.cpython-313.pyc | Bin 2513 -> 2515 bytes .../long_term_memory.cpython-313.pyc | Bin 6839 -> 6841 bytes .../long_term_models.cpython-313.pyc | Bin 1748 -> 1750 bytes .../long_term_store.cpython-313.pyc | Bin 29186 -> 29188 bytes .../message_history.cpython-313.pyc | Bin 8359 -> 8361 bytes .../summary_memory.cpython-313.pyc | Bin 12510 -> 12512 bytes .../__pycache__/summary_store.cpython-313.pyc | Bin 9520 -> 9522 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 214 -> 216 bytes .../__pycache__/identity.cpython-313.pyc | Bin 3465 -> 3467 bytes .../__pycache__/session.cpython-313.pyc | Bin 2708 -> 2710 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 1394 -> 1396 bytes .../__pycache__/code_mapper.cpython-313.pyc | Bin 21447 -> 21449 bytes .../__pycache__/context.cpython-313.pyc | Bin 7386 -> 7388 bytes .../__pycache__/event_bus.cpython-313.pyc | Bin 3632 -> 3634 bytes .../guardrail_events.cpython-313.pyc | Bin 2050 -> 2052 bytes .../__pycache__/ic_events.cpython-313.pyc | Bin 625 -> 627 bytes .../__pycache__/judge_events.cpython-313.pyc | Bin 1410 -> 1412 bytes .../langgraph_telemetry.cpython-313.pyc | Bin 4963 -> 4965 bytes .../__pycache__/llm_advisors.cpython-313.pyc | Bin 2937 -> 2939 bytes .../__pycache__/noc_contract.cpython-313.pyc | Bin 5074 -> 5076 bytes .../__pycache__/noc_otel.cpython-313.pyc | Bin 6321 -> 6323 bytes .../__pycache__/observer.cpython-313.pyc | Bin 5104 -> 5106 bytes .../__pycache__/otel.cpython-313.pyc | Bin 3411 -> 3413 bytes .../streaming_events.cpython-313.pyc | Bin 1834 -> 1836 bytes .../__pycache__/telemetry.cpython-313.pyc | Bin 52427 -> 52429 bytes .../__pycache__/token_cost.cpython-313.pyc | Bin 8690 -> 8692 bytes .../workflow_events.cpython-313.pyc | Bin 3329 -> 3331 bytes .../oci/__pycache__/__init__.cpython-313.pyc | Bin 211 -> 213 bytes .../oci/__pycache__/auth.cpython-313.pyc | Bin 3243 -> 3245 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 219 -> 221 bytes .../__pycache__/oracle_store.cpython-313.pyc | Bin 45931 -> 45933 bytes .../__pycache__/sqlite_store.cpython-313.pyc | Bin 15018 -> 15020 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 454 -> 456 bytes .../__pycache__/renderers.cpython-313.pyc | Bin 4317 -> 4319 bytes .../rag/__pycache__/__init__.cpython-313.pyc | Bin 709 -> 711 bytes .../embedding_provider.cpython-313.pyc | Bin 6516 -> 6518 bytes .../__pycache__/graph_store.cpython-313.pyc | Bin 5527 -> 5529 bytes .../rag/__pycache__/ingest.cpython-313.pyc | Bin 12580 -> 12582 bytes .../__pycache__/kbdb_service.cpython-313.pyc | Bin 10011 -> 10013 bytes .../__pycache__/rag_service.cpython-313.pyc | Bin 10897 -> 10899 bytes .../__pycache__/vector_store.cpython-313.pyc | Bin 18655 -> 18657 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 220 -> 222 bytes .../session_repository.cpython-313.pyc | Bin 8589 -> 8591 bytes .../src/agent_framework/routing/__init__.py | 7 + .../__pycache__/__init__.cpython-313.pyc | Bin 425 -> 649 bytes .../__pycache__/config_loader.cpython-313.pyc | Bin 2296 -> 2691 bytes .../__pycache__/continuity.cpython-313.pyc | Bin 15933 -> 15935 bytes .../enterprise_router.cpython-313.pyc | Bin 61971 -> 64784 bytes .../__pycache__/models.cpython-313.pyc | Bin 2446 -> 2448 bytes .../__pycache__/multi_intent.cpython-313.pyc | Bin 0 -> 13086 bytes .../pending_topics.cpython-313.pyc | Bin 0 -> 5570 bytes .../agent_framework/routing/config_loader.py | 7 + .../routing/enterprise_router.py | 56 +++- .../agent_framework/routing/multi_intent.py | 203 ++++++++++++ .../agent_framework/routing/pending_topics.py | 101 ++++++ .../__pycache__/__init__.cpython-313.pyc | Bin 348 -> 350 bytes .../__pycache__/agent_runtime.cpython-313.pyc | Bin 198228 -> 198240 bytes .../transaction_parameters.cpython-313.pyc | Bin 19573 -> 19585 bytes .../sse/__pycache__/__init__.cpython-313.pyc | Bin 211 -> 213 bytes .../sse/__pycache__/events.cpython-313.pyc | Bin 9587 -> 9589 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 218 -> 220 bytes .../__pycache__/supervisor.cpython-313.pyc | Bin 4943 -> 4945 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 878 -> 880 bytes .../__pycache__/graph.cpython-313.pyc | Bin 1415 -> 1417 bytes .../input_contract.cpython-313.pyc | Bin 8790 -> 8792 bytes .../__pycache__/models.cpython-313.pyc | Bin 7669 -> 7671 bytes .../__pycache__/registry.cpython-313.pyc | Bin 2361 -> 2363 bytes .../__pycache__/repository.cpython-313.pyc | Bin 2714 -> 2716 bytes .../__pycache__/runtime.cpython-313.pyc | Bin 36152 -> 36154 bytes .../__pycache__/tool_executor.cpython-313.pyc | Bin 1966 -> 1968 bytes .../scripts/sync_pending_topics_to_tuning.py | 109 +++++++ .../agent_template_backend/app/state.py | 2 + .../app/workflows/agent_graph.py | 23 +- .../docs/TRANSACTION_SEMANTIC_CONFIRMATION.md | 2 +- .../app/state.py | 2 + .../app/workflows/agent_graph.py | 23 +- .../docs/TRANSACTION_SEMANTIC_CONFIRMATION.md | 2 +- .../conftest.cpython-313-pytest-9.0.2.pyc | Bin 791 -> 806 bytes ...y_consistency.cpython-313-pytest-9.0.2.pyc | Bin 0 -> 9785 bytes ...on_parameters.cpython-313-pytest-9.0.2.pyc | Bin 5451 -> 43623 bytes ...saction_shift.cpython-313-pytest-9.0.2.pyc | Bin 25806 -> 25812 bytes ...ral_candidate.cpython-313-pytest-9.0.2.pyc | Bin 0 -> 17575 bytes ...t_and_structural_candidate.cpython-313.pyc | Bin 0 -> 9600 bytes ..._descriptions.cpython-313-pytest-9.0.2.pyc | Bin 10435 -> 10430 bytes ...lm_precedence.cpython-313-pytest-9.0.2.pyc | Bin 47193 -> 57846 bytes ...ession_matrix.cpython-313-pytest-9.0.2.pyc | Bin 52049 -> 52053 bytes ..._interruption.cpython-313-pytest-9.0.2.pyc | Bin 28283 -> 28289 bytes ...nal_tool_flow.cpython-313-pytest-9.0.2.pyc | Bin 127006 -> 133120 bytes ...ent_template_backend_safety_consistency.py | 70 ++++ ...ntextual_reentry_transaction_parameters.py | 56 +++- .../test_reconciliation_state_rules_8_16.py | 191 +++++++++++ ...test_route_stickiness_transaction_shift.py | 14 + ...it_abandonment_and_structural_candidate.py | 298 ++++++++++++++++++ ...st_transaction_parameter_llm_precedence.py | 31 ++ .../tests/test_transactional_tool_flow.py | 65 +++- .../tests/unit/test_multi_intent_planner.py | 66 ++++ .../unit/test_pending_topics_isolation.py | 53 ++++ .../tests/unit/test_readme_feature_catalog.py | 39 +++ .../unit/test_tuning_pending_topics_sync.py | 38 +++ app/__pycache__/__init__.cpython-313.pyc | Bin 150 -> 152 bytes app/__pycache__/main.cpython-313.pyc | Bin 37622 -> 37624 bytes app/__pycache__/state.cpython-313.pyc | Bin 3396 -> 3520 bytes .../contas_prompting.cpython-313.pyc | Bin 1060 -> 1062 bytes .../contestacao_agent.cpython-313.pyc | Bin 5301 -> 5303 bytes .../__pycache__/faturas_agent.cpython-313.pyc | Bin 10801 -> 10803 bytes .../__pycache__/prompting.cpython-313.pyc | Bin 1147 -> 1149 bytes .../__pycache__/runtime.cpython-313.pyc | Bin 434 -> 436 bytes .../suporte_contas_agent.cpython-313.pyc | Bin 6247 -> 6249 bytes .../__pycache__/vas_agent.cpython-313.pyc | Bin 5712 -> 5714 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 232 -> 234 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 328 -> 330 bytes .../contas/__pycache__/client.cpython-313.pyc | Bin 51885 -> 51887 bytes .../contestation_rules.cpython-313.pyc | Bin 13576 -> 13578 bytes .../contestation_validation.cpython-313.pyc | Bin 25401 -> 25403 bytes .../conversation_policy.cpython-313.pyc | Bin 17025 -> 17027 bytes .../__pycache__/ic_tags.cpython-313.pyc | Bin 17226 -> 17228 bytes .../informational_context.cpython-313.pyc | Bin 3495 -> 3497 bytes .../invoice_context.cpython-313.pyc | Bin 20357 -> 20359 bytes .../invoice_models.cpython-313.pyc | Bin 1803 -> 1805 bytes .../invoice_resolver.cpython-313.pyc | Bin 56048 -> 56050 bytes .../__pycache__/item_matcher.cpython-313.pyc | Bin 9916 -> 9918 bytes .../line_reference.cpython-313.pyc | Bin 3429 -> 3431 bytes .../pro_rata_rules.cpython-313.pyc | Bin 17673 -> 17675 bytes .../protocol_triplets.cpython-313.pyc | Bin 7736 -> 7738 bytes .../__pycache__/rct_policy.cpython-313.pyc | Bin 6329 -> 6331 bytes .../__pycache__/service.cpython-313.pyc | Bin 21632 -> 21634 bytes .../string_metrics.cpython-313.pyc | Bin 6302 -> 6304 bytes .../vas_cancellation_message.cpython-313.pyc | Bin 16323 -> 16325 bytes .../__pycache__/vas_variation.cpython-313.pyc | Bin 42128 -> 42130 bytes .../workflow_actions.cpython-313.pyc | Bin 150211 -> 150213 bytes .../secure_pdf_crypto.cpython-313.pyc | Bin 2474 -> 2476 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 935 -> 937 bytes .../__pycache__/bill_parser.cpython-313.pyc | Bin 23739 -> 23741 bytes .../bill_processor.cpython-313.pyc | Bin 36254 -> 36256 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 302 -> 304 bytes .../tim_guardrails.cpython-313.pyc | Bin 37474 -> 37476 bytes .../__pycache__/tim_judges.cpython-313.pyc | Bin 5861 -> 5863 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 173 -> 175 bytes .../__pycache__/aluc.cpython-313.pyc | Bin 6388 -> 6390 bytes .../ausencia_oferta_proativa.cpython-313.pyc | Bin 10750 -> 10752 bytes .../__pycache__/fraseologia.cpython-313.pyc | Bin 7527 -> 7529 bytes .../__pycache__/out_of_scope.cpython-313.pyc | Bin 17019 -> 17021 bytes .../__pycache__/revprec.cpython-313.pyc | Bin 3086 -> 3088 bytes .../__pycache__/rqlt.cpython-313.pyc | Bin 1095 -> 1097 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 164 -> 166 bytes .../telemetry_observer.cpython-313.pyc | Bin 4987 -> 4989 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 256 -> 258 bytes .../tool_renderers.cpython-313.pyc | Bin 5429 -> 5431 bytes app/state.py | 3 +- .../__pycache__/agent_graph.cpython-313.pyc | Bin 73359 -> 74394 bytes app/workflows/agent_graph.py | 24 +- config/routing.yaml | 4 + .../__pycache__/__init__.cpython-313.pyc | Bin 157 -> 159 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 165 -> 167 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 183 -> 185 bytes .../authorized_lines_service.cpython-313.pyc | Bin 4935 -> 4937 bytes .../discount_history_service.cpython-313.pyc | Bin 4879 -> 4881 bytes .../__pycache__/line_policy.cpython-313.pyc | Bin 3072 -> 3074 bytes .../__pycache__/main.cpython-313.pyc | Bin 96148 -> 102289 bytes contas_mcp/servers/contas_mcp_server/main.py | 89 ++++++ tests/migration/test_multi_intent_contas.py | 55 ++++ 302 files changed, 2070 insertions(+), 28 deletions(-) create mode 100644 IMPLEMENTACAO_MULTI_INTENT.md create mode 100644 agent_framework_oci/docs/features/en/16_multi_intent_planning.md create mode 100644 agent_framework_oci/docs/features/pt-BR/16_multi_intent_planning.md create mode 100644 agent_framework_oci/libs/agent_framework/src/agent_framework/routing/__pycache__/multi_intent.cpython-313.pyc create mode 100644 agent_framework_oci/libs/agent_framework/src/agent_framework/routing/__pycache__/pending_topics.cpython-313.pyc create mode 100644 agent_framework_oci/libs/agent_framework/src/agent_framework/routing/multi_intent.py create mode 100644 agent_framework_oci/libs/agent_framework/src/agent_framework/routing/pending_topics.py create mode 100644 agent_framework_oci/scripts/sync_pending_topics_to_tuning.py create mode 100644 agent_framework_oci/tests/__pycache__/test_agent_template_backend_safety_consistency.cpython-313-pytest-9.0.2.pyc create mode 100644 agent_framework_oci/tests/__pycache__/test_transaction_explicit_abandonment_and_structural_candidate.cpython-313-pytest-9.0.2.pyc create mode 100644 agent_framework_oci/tests/__pycache__/test_transaction_explicit_abandonment_and_structural_candidate.cpython-313.pyc create mode 100644 agent_framework_oci/tests/test_agent_template_backend_safety_consistency.py create mode 100644 agent_framework_oci/tests/test_reconciliation_state_rules_8_16.py create mode 100644 agent_framework_oci/tests/test_transaction_explicit_abandonment_and_structural_candidate.py create mode 100644 agent_framework_oci/tests/unit/test_multi_intent_planner.py create mode 100644 agent_framework_oci/tests/unit/test_pending_topics_isolation.py create mode 100644 agent_framework_oci/tests/unit/test_readme_feature_catalog.py create mode 100644 agent_framework_oci/tests/unit/test_tuning_pending_topics_sync.py create mode 100644 tests/migration/test_multi_intent_contas.py diff --git a/IMPLEMENTACAO_MULTI_INTENT.md b/IMPLEMENTACAO_MULTI_INTENT.md new file mode 100644 index 00000000..7f1ad2b7 --- /dev/null +++ b/IMPLEMENTACAO_MULTI_INTENT.md @@ -0,0 +1,75 @@ +# Implementação Multi-Intent + +## Entregas + +- Planejador determinístico no framework, limitado às intents, agents e tools + cadastrados no `routing.yaml`. +- Priorização automática pela natureza das tools em `tool_policies.yaml`, sem + política por combinação. +- Persistência padrão em `pending_topics` e histórico em `handled_topics`. +- Tratamento de confirmação composta (`sim e ...`) sem provocar troca de intent + durante uma transação aberta. +- Composição das mensagens controladas das operações secundárias antes dos + guardrails de saída. +- Tratamento do cancelamento de Tamboro combinado com segunda via ou assunto + realmente fora do escopo. +- Desambiguação de cobranças repetidas por seleção relativa, incluindo + `as duas mais caras`, alimentando o batch já suportado pelo workflow de + cancelamento. +- Guias em português e inglês em `agent_framework_oci/docs/features`. +- Integração propagada às dez variantes de backend existentes em + `agent_framework_oci/Tuning-Performance`, sem criar um exemplo artificial. +- Script idempotente `agent_framework_oci/scripts/sync_pending_topics_to_tuning.py` + para manter as cópias sincronizadas. + +## Testes adicionados + +- `agent_framework_oci/tests/unit/test_multi_intent_planner.py` +- `tests/migration/test_multi_intent_contas.py` +- `agent_framework_oci/tests/unit/test_tuning_pending_topics_sync.py` + +## Evidências recuperadas do Contas antigo + +O documento antigo `docs/PENDING_TOPICS_CAMADA.md` descrevia explicitamente: + +- cancelamento simples; +- cancelamento + pergunta conceitual sobre outro serviço; +- cancelamento + solicitação fora do contexto, como alteração de CNPJ; +- drenagem de tópicos pendentes antes da confirmação ou finalização; +- manutenção do tópico para retry quando não fosse produzida resposta útil. + +Esses comportamentos foram usados como referência. A segunda via não é tratada +como fora de escopo quando a versão atual do Contas possui uma intent/tool para +ela; nesse caso ela é executada como consulta secundária. Somente uma solicitação +sem capability registrada vira `off_context`. + +## Resultado da validação + +O recorte de 23 testes do planejador e das integrações transacionais afetadas +passa integralmente. A execução ampla de `agent_framework_oci/tests/unit` e +`tests/migration` resultou em 946 testes aprovados e 33 falhas já presentes em +áreas fora desta alteração (principalmente checkpoints, runtime legado de +workflows, guardrails e paridade histórica). + +## Limite de execução + +Consultas secundárias conhecidas são executadas e consolidadas. Assuntos sem +capability são tratados como `off_context`. Uma segunda ação mutável permanece +pendente e requer confirmação própria ou um workflow transacional coordenador. + +## Correção de isolamento da operação secundária + +O teste real `cancelar Tamboro + segunda via` revelou que o agent secundário +herdava o snapshot operacional da transação primária. Isso fazia o +`FaturasAgent` reutilizar `selected_tool_call=cancelar_vas_avulso` e repetir a +confirmação do cancelamento. O estado filho agora limpa integralmente transação, +tool selecionada/pendente, workflow, parâmetros ausentes, resultados MCP, +`next_state`, `workflow_id` e usa a cláusula secundária também como +`sanitized_input`. O teste `test_pending_topics_isolation.py` protege essa regra. + +Uma segunda execução real mostrou outro campo de roteamento que precisava ser +isolado: `mcp_tools` permanecia com a allowlist da operação primária. O runtime +usa esse campo de primeiro nível como fonte de verdade, portanto o +`FaturasAgent` ainda selecionava o cancelamento. O estado filho agora sobrescreve +`domain`, `mcp_tools` e `available_mcp_tools` exclusivamente com a capability do +tópico secundário. diff --git a/agent_framework_oci/README.md b/agent_framework_oci/README.md index 37427cf0..9fe147f4 100644 --- a/agent_framework_oci/README.md +++ b/agent_framework_oci/README.md @@ -185,6 +185,7 @@ O framework deve concentrar os motores reutilizáveis: - Telemetria Langfuse/OpenTelemetry. - Analytics IC/NOC/GRL. - MCP Tool Router. +- Planejamento multi-intent e fila persistente de `pending_topics`. - Cache. - RAG genérico. @@ -230,6 +231,8 @@ Input Guardrails ↓ Enterprise Router ou Supervisor ↓ +Planejamento multi-intent / pending_topics + ↓ Agente especializado ↓ MCP Tool Router / RAG / Cache / LLM @@ -11486,6 +11489,8 @@ O conteúdo desta pasta deve ser tratado como uma extensão adicional do framewo | Um exemplo antigo não compila | documentação histórica versus API atual | [Validação README x Código](docs/developer/pt/VALIDATION_README_ALIGNMENT.md) | | Preciso criar um agente novo do zero | fluxo completo | [`README.md`](README.md) | | Preciso saber onde colocar uma nova feature | arquitetura e boundaries | [Arquitetura e Conceitos](docs/developer/pt/01_architecture_and_concepts.md) | +| Duas solicitações na mesma mensagem acionam apenas uma intent | decomposição multi-intent, `pending_topics`, allowlist de tools | [Planejamento Multi-Intent](docs/features/pt-BR/16_multi_intent_planning.md) | +| O agent secundário repete a transação ou confirmação primária | isolamento de `active_transaction`, `mcp_tools` e estado operacional | [Planejamento Multi-Intent](docs/features/pt-BR/16_multi_intent_planning.md) | ### 34. Funcionalidades Avançadas @@ -11561,9 +11566,31 @@ O conteúdo desta pasta deve ser tratado como uma extensão adicional do framewo **Use quando:** um `COER`/guardrail de entrada gera mensagem genérica, `route=blocked` carrega resultados antigos ou há dúvida sobre a precedência entre input guardrails, routing e tools. +### Guias de features implementadas + +Os manuais `01` a `12` acima organizam os assuntos por área de desenvolvimento. Os guias abaixo documentam cada feature executável individualmente: + +1. [Autenticação](docs/features/pt-BR/01_authentication.md) +2. [Workflow Transacional Determinístico](docs/features/pt-BR/02_deterministic_transactional_workflow.md) +3. [Composição por LLM Solicitada pelo Domínio](docs/features/pt-BR/03_domain_requested_llm_composition.md) +4. [RAG Solicitado pelo Domínio](docs/features/pt-BR/04_domain_requested_rag.md) +5. [Memória de Longo Prazo](docs/features/pt-BR/05_long_term_memory.md) +6. [Regressão Offline de Workflow](docs/features/pt-BR/06_offline_workflow_regression.md) +7. [Pause e Resume de Workflow](docs/features/pt-BR/07_pause_resume_workflow.md) +8. [Route Stickiness](docs/features/pt-BR/08_route_stickiness.md) +9. [Replay em Interrupções de Voz](docs/features/pt-BR/09_voice_interruption_replay.md) +10. [Recuperação de Erro em Workflow](docs/features/pt-BR/10_workflow_error_recovery.md) +11. [Clarificação](docs/features/pt-BR/11_clarification.md) +12. [Idempotência Durável](docs/features/pt-BR/12_durable_idempotency.md) +13. [Estados Transacionais Dinâmicos](docs/features/pt-BR/13_dynamic_transaction_states.md) +14. [Replay Após Finalização](docs/features/pt-BR/14_post_finalization_replay.md) +15. [Guardrails de Retrieval e Tools](docs/features/pt-BR/15_retrieval_tool_guardrails.md) +16. [Planejamento Multi-Intent e Pending Topics](docs/features/pt-BR/16_multi_intent_planning.md) + +O [índice de features em português](docs/features/pt-BR/README.md) deve ser atualizado sempre que um novo guia for adicionado. + ### Tutorial principal [`README.md`](README.md) continua sendo a referência para o passo a passo completo: `arquitetura → configuração → criação do agente → registro → estado → routing → tools → MCP → identidade → execução → testes → gateways → memória → RAG`. - diff --git a/agent_framework_oci/README_en.md b/agent_framework_oci/README_en.md index 38df2bf4..b877ea44 100644 --- a/agent_framework_oci/README_en.md +++ b/agent_framework_oci/README_en.md @@ -126,7 +126,7 @@ Details the evaluation and certification framework, including evaluation archite Defines the platform operational readiness and SRE model, including managed components, health checks, readiness, SLOs, metrics, dashboards, alerts, runbooks, incident management, capacity planning, and production checklists. -### [IMPORTANT: Disclaimer Auth and Security](specs/Disclaimer%20Auth%20and%20Security_EN.md) +### [IMPORTANT: Disclaimer Auth and Security](specs/Disclaimer_Auth_and_Security_EN.md) Oracle Cloud Infrastructure Security Best Practice Recommendations. @@ -186,6 +186,7 @@ The framework should concentrate the reusable engines: - Langfuse/OpenTelemetry Telemetry. - Analytics IC/NOC/GRL. - MCP Tool Router. +- Multi-intent planning and the persistent `pending_topics` queue. - Cache. - Generic RAG. @@ -231,6 +232,8 @@ Input Guardrails ↓ Enterprise Router or Supervisor ↓ +Multi-intent planning / pending_topics +↓ Specialized agent ↓ MCP Tool Router / RAG / Cache / LLM @@ -11388,6 +11391,8 @@ The content of this folder should be treated as an additional framework extensio | Logs/traces do not correlate the same agent | labels, IDs, observability mapping | [Observability](docs/developer/en/11_observability_persistence_and_operational_readiness.md) | | Historical example no longer compiles | stale docs vs current API | [README Alignment Validation](docs/developer/en/VALIDATION_README_ALIGNMENT.md) | | Need to create a new agent from scratch | complete flow | [`README_en.md`](README_en.md) | +| Two requests in one message trigger only one intent | multi-intent decomposition, `pending_topics`, tool allowlist | [Multi-Intent Planning](docs/features/en/16_multi_intent_planning.md) | +| A secondary agent repeats the primary transaction or confirmation | isolation of `active_transaction`, `mcp_tools`, and operational state | [Multi-Intent Planning](docs/features/en/16_multi_intent_planning.md) | ### 34. Advanced Features @@ -11463,6 +11468,29 @@ The content of this folder should be treated as an additional framework extensio **Use it when:** `COER`/input guardrails generate generic messages, `route=blocked` carries stale results, or you need to reason about precedence between input guardrails, routing, and tools. +### Implemented feature guides + +The `01` through `12` manuals above organize documentation by development area. The guides below document each executable feature individually: + +1. [Authentication](docs/features/en/01_authentication.md) +2. [Deterministic Transactional Workflow](docs/features/en/02_deterministic_transactional_workflow.md) +3. [Domain-Requested LLM Composition](docs/features/en/03_domain_requested_llm_composition.md) +4. [Domain-Requested RAG](docs/features/en/04_domain_requested_rag.md) +5. [Long-Term Memory](docs/features/en/05_long_term_memory.md) +6. [Offline Workflow Regression](docs/features/en/06_offline_workflow_regression.md) +7. [Pause and Resume Workflow](docs/features/en/07_pause_resume_workflow.md) +8. [Route Stickiness](docs/features/en/08_route_stickiness.md) +9. [Voice Interruption Replay](docs/features/en/09_voice_interruption_replay.md) +10. [Workflow Error Recovery](docs/features/en/10_workflow_error_recovery.md) +11. [Clarification](docs/features/en/11_clarification.md) +12. [Durable Idempotency](docs/features/en/12_durable_idempotency.md) +13. [Dynamic Transaction States](docs/features/en/13_dynamic_transaction_states.md) +14. [Post-Finalization Replay](docs/features/en/14_post_finalization_replay.md) +15. [Retrieval and Tool Guardrails](docs/features/en/15_retrieval_tool_guardrails.md) +16. [Multi-Intent Planning and Pending Topics](docs/features/en/16_multi_intent_planning.md) + +The [English feature index](docs/features/en/README.md) must be updated whenever a new feature guide is added. + ### Main tutorial [`README_en.md`](README_en.md) remains the complete step-by-step guide. diff --git a/agent_framework_oci/Tuning-Performance/Authentication/agent_template_backend_authentication/app/state.py b/agent_framework_oci/Tuning-Performance/Authentication/agent_template_backend_authentication/app/state.py index 84d9f49e..6ec11159 100644 --- a/agent_framework_oci/Tuning-Performance/Authentication/agent_template_backend_authentication/app/state.py +++ b/agent_framework_oci/Tuning-Performance/Authentication/agent_template_backend_authentication/app/state.py @@ -35,6 +35,8 @@ class AgentState(TypedDict, total=False): missing_parameters: list[str] supervisor_plan: dict[str, Any] supervisor_results: list[dict[str, Any]] + pending_topics: list[dict[str, Any]] + handled_topics: list[dict[str, Any]] active_agent: str route_bypassed: bool continuity_signal: dict[str, Any] diff --git a/agent_framework_oci/Tuning-Performance/Authentication/agent_template_backend_authentication/app/workflows/agent_graph.py b/agent_framework_oci/Tuning-Performance/Authentication/agent_template_backend_authentication/app/workflows/agent_graph.py index ed17e04b..bb2d550a 100644 --- a/agent_framework_oci/Tuning-Performance/Authentication/agent_template_backend_authentication/app/workflows/agent_graph.py +++ b/agent_framework_oci/Tuning-Performance/Authentication/agent_template_backend_authentication/app/workflows/agent_graph.py @@ -7,6 +7,7 @@ from agent_framework.guardrails.rail_action import RailAction from agent_framework.guardrails.rail_result import RailResult from agent_framework.judges.judge import JudgePipeline from agent_framework.routing.enterprise_router import EnterpriseRouter +from agent_framework.routing.pending_topics import drain_pending_topics from agent_framework.supervisor.supervisor import Supervisor from agent_framework.observability.workflow_events import WorkflowTelemetry from agent_framework.observability.guardrail_events import GuardrailTelemetry @@ -409,6 +410,10 @@ class AgentWorkflow: "reason": decision.reason if decision.method == "continuity" else None, "profile": (decision.metadata or {}).get("continuity_profile"), } if decision.method == "continuity" else {}, + "pending_topics": ( + (decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:] + if (decision.metadata or {}).get("multi_intent_plan") else [] + ), } async def billing_agent(self, state): @@ -547,14 +552,28 @@ class AgentWorkflow: agente gerar `answer` e antes dos judges/persistência, produzindo campos supervisor_* no state e eventos GRL.001..GRL.009 via AgentObserver. """ + drained = await drain_pending_topics( + state, + str(state.get("answer") or ""), + { + "billing_agent": self.billing.run, + "product_agent": self.product.run, + "orders_agent": self.orders.run, + "support_agent": self.support.run, + }, + ) + candidate = drained.answer if not bool(getattr(self.settings, "ENABLE_OUTPUT_SUPERVISOR", True)): return { + "answer": candidate, + "final_answer": candidate, + "pending_topics": drained.pending_topics, + "handled_topics": drained.handled_topics, "output_guardrails_already_applied": False, "supervisor_action": "disabled", "supervisor_attempt": int(state.get("supervisor_attempt", 0)), } - candidate = state.get("answer") or "" context = { **(state.get("context") or {}), "tenant_id": state.get("tenant_id"), @@ -623,6 +642,8 @@ class AgentWorkflow: for r in decision.results ], "output_guardrails_already_applied": True, + "pending_topics": drained.pending_topics, + "handled_topics": drained.handled_topics, "guardrail_decisions": state.get("guardrail_decisions", []) + [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])], } diff --git a/agent_framework_oci/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/state.py b/agent_framework_oci/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/state.py index 84d9f49e..6ec11159 100644 --- a/agent_framework_oci/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/state.py +++ b/agent_framework_oci/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/state.py @@ -35,6 +35,8 @@ class AgentState(TypedDict, total=False): missing_parameters: list[str] supervisor_plan: dict[str, Any] supervisor_results: list[dict[str, Any]] + pending_topics: list[dict[str, Any]] + handled_topics: list[dict[str, Any]] active_agent: str route_bypassed: bool continuity_signal: dict[str, Any] diff --git a/agent_framework_oci/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/workflows/agent_graph.py b/agent_framework_oci/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/workflows/agent_graph.py index ed17e04b..bb2d550a 100644 --- a/agent_framework_oci/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/workflows/agent_graph.py +++ b/agent_framework_oci/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/workflows/agent_graph.py @@ -7,6 +7,7 @@ from agent_framework.guardrails.rail_action import RailAction from agent_framework.guardrails.rail_result import RailResult from agent_framework.judges.judge import JudgePipeline from agent_framework.routing.enterprise_router import EnterpriseRouter +from agent_framework.routing.pending_topics import drain_pending_topics from agent_framework.supervisor.supervisor import Supervisor from agent_framework.observability.workflow_events import WorkflowTelemetry from agent_framework.observability.guardrail_events import GuardrailTelemetry @@ -409,6 +410,10 @@ class AgentWorkflow: "reason": decision.reason if decision.method == "continuity" else None, "profile": (decision.metadata or {}).get("continuity_profile"), } if decision.method == "continuity" else {}, + "pending_topics": ( + (decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:] + if (decision.metadata or {}).get("multi_intent_plan") else [] + ), } async def billing_agent(self, state): @@ -547,14 +552,28 @@ class AgentWorkflow: agente gerar `answer` e antes dos judges/persistência, produzindo campos supervisor_* no state e eventos GRL.001..GRL.009 via AgentObserver. """ + drained = await drain_pending_topics( + state, + str(state.get("answer") or ""), + { + "billing_agent": self.billing.run, + "product_agent": self.product.run, + "orders_agent": self.orders.run, + "support_agent": self.support.run, + }, + ) + candidate = drained.answer if not bool(getattr(self.settings, "ENABLE_OUTPUT_SUPERVISOR", True)): return { + "answer": candidate, + "final_answer": candidate, + "pending_topics": drained.pending_topics, + "handled_topics": drained.handled_topics, "output_guardrails_already_applied": False, "supervisor_action": "disabled", "supervisor_attempt": int(state.get("supervisor_attempt", 0)), } - candidate = state.get("answer") or "" context = { **(state.get("context") or {}), "tenant_id": state.get("tenant_id"), @@ -623,6 +642,8 @@ class AgentWorkflow: for r in decision.results ], "output_guardrails_already_applied": True, + "pending_topics": drained.pending_topics, + "handled_topics": drained.handled_topics, "guardrail_decisions": state.get("guardrail_decisions", []) + [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])], } diff --git a/agent_framework_oci/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/state.py b/agent_framework_oci/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/state.py index fc680927..ef4f2c70 100644 --- a/agent_framework_oci/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/state.py +++ b/agent_framework_oci/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/state.py @@ -35,6 +35,8 @@ class AgentState(TypedDict, total=False): missing_parameters: list[str] supervisor_plan: dict[str, Any] supervisor_results: list[dict[str, Any]] + pending_topics: list[dict[str, Any]] + handled_topics: list[dict[str, Any]] active_agent: str route_bypassed: bool continuity_signal: dict[str, Any] diff --git a/agent_framework_oci/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/workflows/agent_graph.py b/agent_framework_oci/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/workflows/agent_graph.py index fc292459..5c5f9a28 100644 --- a/agent_framework_oci/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/workflows/agent_graph.py +++ b/agent_framework_oci/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/workflows/agent_graph.py @@ -7,6 +7,7 @@ from agent_framework.guardrails.rail_action import RailAction from agent_framework.guardrails.rail_result import RailResult from agent_framework.judges.judge import JudgePipeline from agent_framework.routing.enterprise_router import EnterpriseRouter +from agent_framework.routing.pending_topics import drain_pending_topics from agent_framework.supervisor.supervisor import Supervisor from agent_framework.observability.workflow_events import WorkflowTelemetry from agent_framework.observability.guardrail_events import GuardrailTelemetry @@ -407,6 +408,10 @@ class AgentWorkflow: "reason": decision.reason if decision.method == "continuity" else None, "profile": (decision.metadata or {}).get("continuity_profile"), } if decision.method == "continuity" else {}, + "pending_topics": ( + (decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:] + if (decision.metadata or {}).get("multi_intent_plan") else [] + ), } async def billing_agent(self, state): @@ -545,14 +550,28 @@ class AgentWorkflow: agente gerar `answer` e antes dos judges/persistência, produzindo campos supervisor_* no state e eventos GRL.001..GRL.009 via AgentObserver. """ + drained = await drain_pending_topics( + state, + str(state.get("answer") or ""), + { + "billing_agent": self.billing.run, + "product_agent": self.product.run, + "orders_agent": self.orders.run, + "support_agent": self.support.run, + }, + ) + candidate = drained.answer if not bool(getattr(self.settings, "ENABLE_OUTPUT_SUPERVISOR", True)): return { + "answer": candidate, + "final_answer": candidate, + "pending_topics": drained.pending_topics, + "handled_topics": drained.handled_topics, "output_guardrails_already_applied": False, "supervisor_action": "disabled", "supervisor_attempt": int(state.get("supervisor_attempt", 0)), } - candidate = state.get("answer") or "" context = { **(state.get("context") or {}), "tenant_id": state.get("tenant_id"), @@ -621,6 +640,8 @@ class AgentWorkflow: for r in decision.results ], "output_guardrails_already_applied": True, + "pending_topics": drained.pending_topics, + "handled_topics": drained.handled_topics, "guardrail_decisions": state.get("guardrail_decisions", []) + [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])], } diff --git a/agent_framework_oci/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/state.py b/agent_framework_oci/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/state.py index 84d9f49e..6ec11159 100644 --- a/agent_framework_oci/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/state.py +++ b/agent_framework_oci/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/state.py @@ -35,6 +35,8 @@ class AgentState(TypedDict, total=False): missing_parameters: list[str] supervisor_plan: dict[str, Any] supervisor_results: list[dict[str, Any]] + pending_topics: list[dict[str, Any]] + handled_topics: list[dict[str, Any]] active_agent: str route_bypassed: bool continuity_signal: dict[str, Any] diff --git a/agent_framework_oci/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/workflows/agent_graph.py b/agent_framework_oci/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/workflows/agent_graph.py index ed17e04b..bb2d550a 100644 --- a/agent_framework_oci/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/workflows/agent_graph.py +++ b/agent_framework_oci/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/workflows/agent_graph.py @@ -7,6 +7,7 @@ from agent_framework.guardrails.rail_action import RailAction from agent_framework.guardrails.rail_result import RailResult from agent_framework.judges.judge import JudgePipeline from agent_framework.routing.enterprise_router import EnterpriseRouter +from agent_framework.routing.pending_topics import drain_pending_topics from agent_framework.supervisor.supervisor import Supervisor from agent_framework.observability.workflow_events import WorkflowTelemetry from agent_framework.observability.guardrail_events import GuardrailTelemetry @@ -409,6 +410,10 @@ class AgentWorkflow: "reason": decision.reason if decision.method == "continuity" else None, "profile": (decision.metadata or {}).get("continuity_profile"), } if decision.method == "continuity" else {}, + "pending_topics": ( + (decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:] + if (decision.metadata or {}).get("multi_intent_plan") else [] + ), } async def billing_agent(self, state): @@ -547,14 +552,28 @@ class AgentWorkflow: agente gerar `answer` e antes dos judges/persistência, produzindo campos supervisor_* no state e eventos GRL.001..GRL.009 via AgentObserver. """ + drained = await drain_pending_topics( + state, + str(state.get("answer") or ""), + { + "billing_agent": self.billing.run, + "product_agent": self.product.run, + "orders_agent": self.orders.run, + "support_agent": self.support.run, + }, + ) + candidate = drained.answer if not bool(getattr(self.settings, "ENABLE_OUTPUT_SUPERVISOR", True)): return { + "answer": candidate, + "final_answer": candidate, + "pending_topics": drained.pending_topics, + "handled_topics": drained.handled_topics, "output_guardrails_already_applied": False, "supervisor_action": "disabled", "supervisor_attempt": int(state.get("supervisor_attempt", 0)), } - candidate = state.get("answer") or "" context = { **(state.get("context") or {}), "tenant_id": state.get("tenant_id"), @@ -623,6 +642,8 @@ class AgentWorkflow: for r in decision.results ], "output_guardrails_already_applied": True, + "pending_topics": drained.pending_topics, + "handled_topics": drained.handled_topics, "guardrail_decisions": state.get("guardrail_decisions", []) + [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])], } diff --git a/agent_framework_oci/Tuning-Performance/Normal/templates/agent_template_backend/app/state.py b/agent_framework_oci/Tuning-Performance/Normal/templates/agent_template_backend/app/state.py index fc680927..ef4f2c70 100644 --- a/agent_framework_oci/Tuning-Performance/Normal/templates/agent_template_backend/app/state.py +++ b/agent_framework_oci/Tuning-Performance/Normal/templates/agent_template_backend/app/state.py @@ -35,6 +35,8 @@ class AgentState(TypedDict, total=False): missing_parameters: list[str] supervisor_plan: dict[str, Any] supervisor_results: list[dict[str, Any]] + pending_topics: list[dict[str, Any]] + handled_topics: list[dict[str, Any]] active_agent: str route_bypassed: bool continuity_signal: dict[str, Any] diff --git a/agent_framework_oci/Tuning-Performance/Normal/templates/agent_template_backend/app/workflows/agent_graph.py b/agent_framework_oci/Tuning-Performance/Normal/templates/agent_template_backend/app/workflows/agent_graph.py index fc292459..5c5f9a28 100644 --- a/agent_framework_oci/Tuning-Performance/Normal/templates/agent_template_backend/app/workflows/agent_graph.py +++ b/agent_framework_oci/Tuning-Performance/Normal/templates/agent_template_backend/app/workflows/agent_graph.py @@ -7,6 +7,7 @@ from agent_framework.guardrails.rail_action import RailAction from agent_framework.guardrails.rail_result import RailResult from agent_framework.judges.judge import JudgePipeline from agent_framework.routing.enterprise_router import EnterpriseRouter +from agent_framework.routing.pending_topics import drain_pending_topics from agent_framework.supervisor.supervisor import Supervisor from agent_framework.observability.workflow_events import WorkflowTelemetry from agent_framework.observability.guardrail_events import GuardrailTelemetry @@ -407,6 +408,10 @@ class AgentWorkflow: "reason": decision.reason if decision.method == "continuity" else None, "profile": (decision.metadata or {}).get("continuity_profile"), } if decision.method == "continuity" else {}, + "pending_topics": ( + (decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:] + if (decision.metadata or {}).get("multi_intent_plan") else [] + ), } async def billing_agent(self, state): @@ -545,14 +550,28 @@ class AgentWorkflow: agente gerar `answer` e antes dos judges/persistência, produzindo campos supervisor_* no state e eventos GRL.001..GRL.009 via AgentObserver. """ + drained = await drain_pending_topics( + state, + str(state.get("answer") or ""), + { + "billing_agent": self.billing.run, + "product_agent": self.product.run, + "orders_agent": self.orders.run, + "support_agent": self.support.run, + }, + ) + candidate = drained.answer if not bool(getattr(self.settings, "ENABLE_OUTPUT_SUPERVISOR", True)): return { + "answer": candidate, + "final_answer": candidate, + "pending_topics": drained.pending_topics, + "handled_topics": drained.handled_topics, "output_guardrails_already_applied": False, "supervisor_action": "disabled", "supervisor_attempt": int(state.get("supervisor_attempt", 0)), } - candidate = state.get("answer") or "" context = { **(state.get("context") or {}), "tenant_id": state.get("tenant_id"), @@ -621,6 +640,8 @@ class AgentWorkflow: for r in decision.results ], "output_guardrails_already_applied": True, + "pending_topics": drained.pending_topics, + "handled_topics": drained.handled_topics, "guardrail_decisions": state.get("guardrail_decisions", []) + [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])], } diff --git a/agent_framework_oci/Tuning-Performance/Normal/templates/agent_template_backend/docs/TRANSACTION_SEMANTIC_CONFIRMATION.md b/agent_framework_oci/Tuning-Performance/Normal/templates/agent_template_backend/docs/TRANSACTION_SEMANTIC_CONFIRMATION.md index a5508710..8cbc0077 100644 --- a/agent_framework_oci/Tuning-Performance/Normal/templates/agent_template_backend/docs/TRANSACTION_SEMANTIC_CONFIRMATION.md +++ b/agent_framework_oci/Tuning-Performance/Normal/templates/agent_template_backend/docs/TRANSACTION_SEMANTIC_CONFIRMATION.md @@ -4,7 +4,7 @@ Este template suporta confirmação transacional em duas camadas: primeiro um pa A configuração `router.transaction_confirmation.semantic_fallback` usa três classes: `SIM`, `NAO` e `CONTINUAR`. O prompt pode usar `{{ pending_prompt }}`, `{{ relevant_conversation_context }}`, `{{ user_input }}` e `{{ allowed_values }}`. O histórico injetado é apenas contexto de interpretação; não substitui validação de negócio ou evidência MCP. -Exemplo: após `Você confirma o cancelamento do serviço Tamboro Mensal?`, a frase `isso mesmo, pode confirmar` pode ser classificada como `SIM`. Já `mas qual é o valor?` deve ser `CONTINUAR`, portanto não executa a ação por confirmação. +Exemplo: após `Você confirma o cancelamento do serviço adicional selecionado?`, a frase `isso mesmo, pode confirmar` pode ser classificada como `SIM`. Já `mas qual é o valor?` deve ser `CONTINUAR`, portanto não executa a ação por confirmação. Entradas explícitas já suportadas continuam no caminho determinístico e não geram custo adicional de LLM. Em observabilidade, o fallback usa `transaction.confirmation.semantic_classifier` e o `route_decision.metadata` informa `transaction_confirmation_source: semantic`. diff --git a/agent_framework_oci/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/state.py b/agent_framework_oci/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/state.py index fc680927..ef4f2c70 100644 --- a/agent_framework_oci/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/state.py +++ b/agent_framework_oci/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/state.py @@ -35,6 +35,8 @@ class AgentState(TypedDict, total=False): missing_parameters: list[str] supervisor_plan: dict[str, Any] supervisor_results: list[dict[str, Any]] + pending_topics: list[dict[str, Any]] + handled_topics: list[dict[str, Any]] active_agent: str route_bypassed: bool continuity_signal: dict[str, Any] diff --git a/agent_framework_oci/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/workflows/agent_graph.py b/agent_framework_oci/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/workflows/agent_graph.py index fc292459..5c5f9a28 100644 --- a/agent_framework_oci/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/workflows/agent_graph.py +++ b/agent_framework_oci/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/workflows/agent_graph.py @@ -7,6 +7,7 @@ from agent_framework.guardrails.rail_action import RailAction from agent_framework.guardrails.rail_result import RailResult from agent_framework.judges.judge import JudgePipeline from agent_framework.routing.enterprise_router import EnterpriseRouter +from agent_framework.routing.pending_topics import drain_pending_topics from agent_framework.supervisor.supervisor import Supervisor from agent_framework.observability.workflow_events import WorkflowTelemetry from agent_framework.observability.guardrail_events import GuardrailTelemetry @@ -407,6 +408,10 @@ class AgentWorkflow: "reason": decision.reason if decision.method == "continuity" else None, "profile": (decision.metadata or {}).get("continuity_profile"), } if decision.method == "continuity" else {}, + "pending_topics": ( + (decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:] + if (decision.metadata or {}).get("multi_intent_plan") else [] + ), } async def billing_agent(self, state): @@ -545,14 +550,28 @@ class AgentWorkflow: agente gerar `answer` e antes dos judges/persistência, produzindo campos supervisor_* no state e eventos GRL.001..GRL.009 via AgentObserver. """ + drained = await drain_pending_topics( + state, + str(state.get("answer") or ""), + { + "billing_agent": self.billing.run, + "product_agent": self.product.run, + "orders_agent": self.orders.run, + "support_agent": self.support.run, + }, + ) + candidate = drained.answer if not bool(getattr(self.settings, "ENABLE_OUTPUT_SUPERVISOR", True)): return { + "answer": candidate, + "final_answer": candidate, + "pending_topics": drained.pending_topics, + "handled_topics": drained.handled_topics, "output_guardrails_already_applied": False, "supervisor_action": "disabled", "supervisor_attempt": int(state.get("supervisor_attempt", 0)), } - candidate = state.get("answer") or "" context = { **(state.get("context") or {}), "tenant_id": state.get("tenant_id"), @@ -621,6 +640,8 @@ class AgentWorkflow: for r in decision.results ], "output_guardrails_already_applied": True, + "pending_topics": drained.pending_topics, + "handled_topics": drained.handled_topics, "guardrail_decisions": state.get("guardrail_decisions", []) + [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])], } diff --git a/agent_framework_oci/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/docs/TRANSACTION_SEMANTIC_CONFIRMATION.md b/agent_framework_oci/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/docs/TRANSACTION_SEMANTIC_CONFIRMATION.md index a5508710..8cbc0077 100644 --- a/agent_framework_oci/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/docs/TRANSACTION_SEMANTIC_CONFIRMATION.md +++ b/agent_framework_oci/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/docs/TRANSACTION_SEMANTIC_CONFIRMATION.md @@ -4,7 +4,7 @@ Este template suporta confirmação transacional em duas camadas: primeiro um pa A configuração `router.transaction_confirmation.semantic_fallback` usa três classes: `SIM`, `NAO` e `CONTINUAR`. O prompt pode usar `{{ pending_prompt }}`, `{{ relevant_conversation_context }}`, `{{ user_input }}` e `{{ allowed_values }}`. O histórico injetado é apenas contexto de interpretação; não substitui validação de negócio ou evidência MCP. -Exemplo: após `Você confirma o cancelamento do serviço Tamboro Mensal?`, a frase `isso mesmo, pode confirmar` pode ser classificada como `SIM`. Já `mas qual é o valor?` deve ser `CONTINUAR`, portanto não executa a ação por confirmação. +Exemplo: após `Você confirma o cancelamento do serviço adicional selecionado?`, a frase `isso mesmo, pode confirmar` pode ser classificada como `SIM`. Já `mas qual é o valor?` deve ser `CONTINUAR`, portanto não executa a ação por confirmação. Entradas explícitas já suportadas continuam no caminho determinístico e não geram custo adicional de LLM. Em observabilidade, o fallback usa `transaction.confirmation.semantic_classifier` e o `route_decision.metadata` informa `transaction_confirmation_source: semantic`. diff --git a/agent_framework_oci/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/state.py b/agent_framework_oci/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/state.py index fc680927..ef4f2c70 100644 --- a/agent_framework_oci/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/state.py +++ b/agent_framework_oci/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/state.py @@ -35,6 +35,8 @@ class AgentState(TypedDict, total=False): missing_parameters: list[str] supervisor_plan: dict[str, Any] supervisor_results: list[dict[str, Any]] + pending_topics: list[dict[str, Any]] + handled_topics: list[dict[str, Any]] active_agent: str route_bypassed: bool continuity_signal: dict[str, Any] diff --git a/agent_framework_oci/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/workflows/agent_graph.py b/agent_framework_oci/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/workflows/agent_graph.py index fc292459..5c5f9a28 100644 --- a/agent_framework_oci/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/workflows/agent_graph.py +++ b/agent_framework_oci/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/workflows/agent_graph.py @@ -7,6 +7,7 @@ from agent_framework.guardrails.rail_action import RailAction from agent_framework.guardrails.rail_result import RailResult from agent_framework.judges.judge import JudgePipeline from agent_framework.routing.enterprise_router import EnterpriseRouter +from agent_framework.routing.pending_topics import drain_pending_topics from agent_framework.supervisor.supervisor import Supervisor from agent_framework.observability.workflow_events import WorkflowTelemetry from agent_framework.observability.guardrail_events import GuardrailTelemetry @@ -407,6 +408,10 @@ class AgentWorkflow: "reason": decision.reason if decision.method == "continuity" else None, "profile": (decision.metadata or {}).get("continuity_profile"), } if decision.method == "continuity" else {}, + "pending_topics": ( + (decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:] + if (decision.metadata or {}).get("multi_intent_plan") else [] + ), } async def billing_agent(self, state): @@ -545,14 +550,28 @@ class AgentWorkflow: agente gerar `answer` e antes dos judges/persistência, produzindo campos supervisor_* no state e eventos GRL.001..GRL.009 via AgentObserver. """ + drained = await drain_pending_topics( + state, + str(state.get("answer") or ""), + { + "billing_agent": self.billing.run, + "product_agent": self.product.run, + "orders_agent": self.orders.run, + "support_agent": self.support.run, + }, + ) + candidate = drained.answer if not bool(getattr(self.settings, "ENABLE_OUTPUT_SUPERVISOR", True)): return { + "answer": candidate, + "final_answer": candidate, + "pending_topics": drained.pending_topics, + "handled_topics": drained.handled_topics, "output_guardrails_already_applied": False, "supervisor_action": "disabled", "supervisor_attempt": int(state.get("supervisor_attempt", 0)), } - candidate = state.get("answer") or "" context = { **(state.get("context") or {}), "tenant_id": state.get("tenant_id"), @@ -621,6 +640,8 @@ class AgentWorkflow: for r in decision.results ], "output_guardrails_already_applied": True, + "pending_topics": drained.pending_topics, + "handled_topics": drained.handled_topics, "guardrail_decisions": state.get("guardrail_decisions", []) + [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])], } diff --git a/agent_framework_oci/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/state.py b/agent_framework_oci/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/state.py index fc680927..ef4f2c70 100644 --- a/agent_framework_oci/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/state.py +++ b/agent_framework_oci/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/state.py @@ -35,6 +35,8 @@ class AgentState(TypedDict, total=False): missing_parameters: list[str] supervisor_plan: dict[str, Any] supervisor_results: list[dict[str, Any]] + pending_topics: list[dict[str, Any]] + handled_topics: list[dict[str, Any]] active_agent: str route_bypassed: bool continuity_signal: dict[str, Any] diff --git a/agent_framework_oci/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/workflows/agent_graph.py b/agent_framework_oci/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/workflows/agent_graph.py index fc292459..5c5f9a28 100644 --- a/agent_framework_oci/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/workflows/agent_graph.py +++ b/agent_framework_oci/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/workflows/agent_graph.py @@ -7,6 +7,7 @@ from agent_framework.guardrails.rail_action import RailAction from agent_framework.guardrails.rail_result import RailResult from agent_framework.judges.judge import JudgePipeline from agent_framework.routing.enterprise_router import EnterpriseRouter +from agent_framework.routing.pending_topics import drain_pending_topics from agent_framework.supervisor.supervisor import Supervisor from agent_framework.observability.workflow_events import WorkflowTelemetry from agent_framework.observability.guardrail_events import GuardrailTelemetry @@ -407,6 +408,10 @@ class AgentWorkflow: "reason": decision.reason if decision.method == "continuity" else None, "profile": (decision.metadata or {}).get("continuity_profile"), } if decision.method == "continuity" else {}, + "pending_topics": ( + (decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:] + if (decision.metadata or {}).get("multi_intent_plan") else [] + ), } async def billing_agent(self, state): @@ -545,14 +550,28 @@ class AgentWorkflow: agente gerar `answer` e antes dos judges/persistência, produzindo campos supervisor_* no state e eventos GRL.001..GRL.009 via AgentObserver. """ + drained = await drain_pending_topics( + state, + str(state.get("answer") or ""), + { + "billing_agent": self.billing.run, + "product_agent": self.product.run, + "orders_agent": self.orders.run, + "support_agent": self.support.run, + }, + ) + candidate = drained.answer if not bool(getattr(self.settings, "ENABLE_OUTPUT_SUPERVISOR", True)): return { + "answer": candidate, + "final_answer": candidate, + "pending_topics": drained.pending_topics, + "handled_topics": drained.handled_topics, "output_guardrails_already_applied": False, "supervisor_action": "disabled", "supervisor_attempt": int(state.get("supervisor_attempt", 0)), } - candidate = state.get("answer") or "" context = { **(state.get("context") or {}), "tenant_id": state.get("tenant_id"), @@ -621,6 +640,8 @@ class AgentWorkflow: for r in decision.results ], "output_guardrails_already_applied": True, + "pending_topics": drained.pending_topics, + "handled_topics": drained.handled_topics, "guardrail_decisions": state.get("guardrail_decisions", []) + [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])], } diff --git a/agent_framework_oci/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/state.py b/agent_framework_oci/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/state.py index 1d82e35c..de25c1e8 100644 --- a/agent_framework_oci/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/state.py +++ b/agent_framework_oci/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/state.py @@ -38,6 +38,8 @@ class AgentState(TypedDict, total=False): missing_parameters: list[str] supervisor_plan: dict[str, Any] supervisor_results: list[dict[str, Any]] + pending_topics: list[dict[str, Any]] + handled_topics: list[dict[str, Any]] active_agent: str route_bypassed: bool continuity_signal: dict[str, Any] diff --git a/agent_framework_oci/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/workflows/agent_graph.py b/agent_framework_oci/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/workflows/agent_graph.py index e22cf447..b950fa4e 100644 --- a/agent_framework_oci/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/workflows/agent_graph.py +++ b/agent_framework_oci/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/workflows/agent_graph.py @@ -7,6 +7,7 @@ from agent_framework.guardrails.rail_action import RailAction from agent_framework.guardrails.rail_result import RailResult from agent_framework.judges.judge import JudgePipeline from agent_framework.routing.enterprise_router import EnterpriseRouter +from agent_framework.routing.pending_topics import drain_pending_topics from agent_framework.supervisor.supervisor import Supervisor from agent_framework.observability.workflow_events import WorkflowTelemetry from agent_framework.observability.guardrail_events import GuardrailTelemetry @@ -409,6 +410,10 @@ class AgentWorkflow: "reason": decision.reason if decision.method == "continuity" else None, "profile": (decision.metadata or {}).get("continuity_profile"), } if decision.method == "continuity" else {}, + "pending_topics": ( + (decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:] + if (decision.metadata or {}).get("multi_intent_plan") else [] + ), } async def billing_agent(self, state): @@ -547,14 +552,28 @@ class AgentWorkflow: agente gerar `answer` e antes dos judges/persistência, produzindo campos supervisor_* no state e eventos GRL.001..GRL.009 via AgentObserver. """ + drained = await drain_pending_topics( + state, + str(state.get("answer") or ""), + { + "billing_agent": self.billing.run, + "product_agent": self.product.run, + "orders_agent": self.orders.run, + "support_agent": self.support.run, + }, + ) + candidate = drained.answer if not bool(getattr(self.settings, "ENABLE_OUTPUT_SUPERVISOR", True)): return { + "answer": candidate, + "final_answer": candidate, + "pending_topics": drained.pending_topics, + "handled_topics": drained.handled_topics, "output_guardrails_already_applied": False, "supervisor_action": "disabled", "supervisor_attempt": int(state.get("supervisor_attempt", 0)), } - candidate = state.get("answer") or "" context = { **(state.get("context") or {}), "tenant_id": state.get("tenant_id"), @@ -623,6 +642,8 @@ class AgentWorkflow: for r in decision.results ], "output_guardrails_already_applied": True, + "pending_topics": drained.pending_topics, + "handled_topics": drained.handled_topics, "guardrail_decisions": state.get("guardrail_decisions", []) + [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])], } diff --git a/agent_framework_oci/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/state.py b/agent_framework_oci/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/state.py index 69555085..0b4da3e9 100644 --- a/agent_framework_oci/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/state.py +++ b/agent_framework_oci/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/state.py @@ -39,6 +39,8 @@ class AgentState(TypedDict, total=False): missing_parameters: list[str] supervisor_plan: dict[str, Any] supervisor_results: list[dict[str, Any]] + pending_topics: list[dict[str, Any]] + handled_topics: list[dict[str, Any]] active_agent: str route_bypassed: bool continuity_signal: dict[str, Any] diff --git a/agent_framework_oci/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/workflows/agent_graph.py b/agent_framework_oci/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/workflows/agent_graph.py index e22cf447..b950fa4e 100644 --- a/agent_framework_oci/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/workflows/agent_graph.py +++ b/agent_framework_oci/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/workflows/agent_graph.py @@ -7,6 +7,7 @@ from agent_framework.guardrails.rail_action import RailAction from agent_framework.guardrails.rail_result import RailResult from agent_framework.judges.judge import JudgePipeline from agent_framework.routing.enterprise_router import EnterpriseRouter +from agent_framework.routing.pending_topics import drain_pending_topics from agent_framework.supervisor.supervisor import Supervisor from agent_framework.observability.workflow_events import WorkflowTelemetry from agent_framework.observability.guardrail_events import GuardrailTelemetry @@ -409,6 +410,10 @@ class AgentWorkflow: "reason": decision.reason if decision.method == "continuity" else None, "profile": (decision.metadata or {}).get("continuity_profile"), } if decision.method == "continuity" else {}, + "pending_topics": ( + (decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:] + if (decision.metadata or {}).get("multi_intent_plan") else [] + ), } async def billing_agent(self, state): @@ -547,14 +552,28 @@ class AgentWorkflow: agente gerar `answer` e antes dos judges/persistência, produzindo campos supervisor_* no state e eventos GRL.001..GRL.009 via AgentObserver. """ + drained = await drain_pending_topics( + state, + str(state.get("answer") or ""), + { + "billing_agent": self.billing.run, + "product_agent": self.product.run, + "orders_agent": self.orders.run, + "support_agent": self.support.run, + }, + ) + candidate = drained.answer if not bool(getattr(self.settings, "ENABLE_OUTPUT_SUPERVISOR", True)): return { + "answer": candidate, + "final_answer": candidate, + "pending_topics": drained.pending_topics, + "handled_topics": drained.handled_topics, "output_guardrails_already_applied": False, "supervisor_action": "disabled", "supervisor_attempt": int(state.get("supervisor_attempt", 0)), } - candidate = state.get("answer") or "" context = { **(state.get("context") or {}), "tenant_id": state.get("tenant_id"), @@ -623,6 +642,8 @@ class AgentWorkflow: for r in decision.results ], "output_guardrails_already_applied": True, + "pending_topics": drained.pending_topics, + "handled_topics": drained.handled_topics, "guardrail_decisions": state.get("guardrail_decisions", []) + [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])], } diff --git a/agent_framework_oci/docs/features/en/16_multi_intent_planning.md b/agent_framework_oci/docs/features/en/16_multi_intent_planning.md new file mode 100644 index 00000000..fbc3c122 --- /dev/null +++ b/agent_framework_oci/docs/features/en/16_multi_intent_planning.md @@ -0,0 +1,27 @@ +# Multi-Intent Planning + +`MultiIntentPlanner` recognizes independent requests in one utterance and +builds a plan constrained to the intents, agents, and tools declared in +`config/routing.yaml`. It does not execute operations or let the LLM invent a +sequence: the router selects the primary operation and the domain workflow +continues to own the transaction. + +## Configuration and execution + +No additional configuration is required. The planner uses the intents already +declared in `routing.yaml`, selects the single transactional operation from +`tool_policies.yaml`, and persists the remaining requests as `pending_topics`. + +Known read-only topics execute through their own agent. A second mutation stays +pending for its own confirmation, while an unknown clause becomes off-context. + +The router resolves clauses from registered intent keywords and routes the +transactional operation first. The graph drains read-only pending topics before +output supervision. During an open transaction, +`yes, and send me the invoice` confirms the transaction and records the +secondary request instead of causing an intent shift. + +For multiple side-effecting actions, implement a transactional coordinator +that persists the plan, confirms it, executes idempotently, and records partial +failure or compensation. See `tests/unit/test_multi_intent_planner.py`. No +special Tuning-Performance template is needed because the behavior is native. diff --git a/agent_framework_oci/docs/features/en/README.md b/agent_framework_oci/docs/features/en/README.md index 4cf22497..e74d579c 100644 --- a/agent_framework_oci/docs/features/en/README.md +++ b/agent_framework_oci/docs/features/en/README.md @@ -17,3 +17,4 @@ Documentation for the main `agent_framework_oci` features. - [Dynamic Transaction States](13_dynamic_transaction_states.md) - [Post Finalization Replay](14_post_finalization_replay.md) - [Retrieval / Tool Guardrails](15_retrieval_tool_guardrails.md) +- [Multi-Intent Planning](16_multi_intent_planning.md) diff --git a/agent_framework_oci/docs/features/pt-BR/16_multi_intent_planning.md b/agent_framework_oci/docs/features/pt-BR/16_multi_intent_planning.md new file mode 100644 index 00000000..92700152 --- /dev/null +++ b/agent_framework_oci/docs/features/pt-BR/16_multi_intent_planning.md @@ -0,0 +1,46 @@ +# Planejamento Multi-Intent + +O `MultiIntentPlanner` reconhece solicitações independentes na mesma mensagem e +cria um plano limitado às intents, agents e tools declarados em +`config/routing.yaml`. O planejador não executa operações nem permite que o LLM +invente uma sequência: o roteador escolhe a operação primária e o workflow do +domínio continua responsável pela transação. + +## Configuração + +Não existe configuração adicional. O planejador usa as intents do `routing.yaml` +e lê `tool_policies.yaml` para priorizar automaticamente a única operação +transacional. Pedidos secundários são persistidos em `pending_topics`. + +- intent conhecida e read-only: executada pelo agent correspondente; +- intent conhecida e transacional: mantida pendente para confirmação própria; +- trecho sem capability correspondente: respondido como `off_context`. + +Consultas secundárias com `execute` podem ser executadas pelo agent correspondente +e consolidadas. Uma segunda mutação é marcada como `defer`, pois exige um +workflow transacional coordenador. + +## Fluxo de execução + +1. O `EnterpriseRouter` separa a entrada em cláusulas e resolve cada uma pelas + keywords das intents cadastradas. +2. `tool_policies.yaml` identifica a operação transacional e a torna primária. +3. A primeira operação é roteada normalmente para seu agent/workflow. +4. O `agent_graph` compõe as mensagens públicas antes do supervisor de saída. +5. Durante uma confirmação, uma frase como `sim e me manda a segunda via` + confirma a transação aberta e registra a solicitação secundária, sem causar + troca indevida de intent. + +## Operações mutáveis + +Para executar mais de uma ação com efeito colateral, implemente um workflow +transacional coordenador. Ele deve persistir o plano, pedir confirmação quando +necessário, executar cada passo com idempotência e registrar sucesso parcial, +falha e compensação. O planejador fornece o contrato; a política de negócio e a +orquestração permanecem no domínio. + +## Referência executável + +Veja os testes genéricos em `tests/unit/test_multi_intent_planner.py`. Como a +feature é padrão do runtime, não existe uma cópia especial do +`agent_template_backend` em `Tuning-Performance`. diff --git a/agent_framework_oci/docs/features/pt-BR/README.md b/agent_framework_oci/docs/features/pt-BR/README.md index 3654d7d7..41b7813d 100644 --- a/agent_framework_oci/docs/features/pt-BR/README.md +++ b/agent_framework_oci/docs/features/pt-BR/README.md @@ -17,3 +17,4 @@ Documentação das principais features do `agent_framework_oci`. - [Estados Transacionais Dinâmicos](13_dynamic_transaction_states.md) - [Replay Após Finalização](14_post_finalization_replay.md) - [Guardrails de Retrieval e Tools](15_retrieval_tool_guardrails.md) +- [Planejamento Multi-Intent](16_multi_intent_planning.md) diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework.egg-info/SOURCES.txt b/agent_framework_oci/libs/agent_framework/src/agent_framework.egg-info/SOURCES.txt index ddcd57f4..a54b9a88 100644 --- a/agent_framework_oci/libs/agent_framework/src/agent_framework.egg-info/SOURCES.txt +++ b/agent_framework_oci/libs/agent_framework/src/agent_framework.egg-info/SOURCES.txt @@ -201,6 +201,8 @@ src/agent_framework/routing/config_loader.py src/agent_framework/routing/continuity.py src/agent_framework/routing/enterprise_router.py src/agent_framework/routing/models.py +src/agent_framework/routing/multi_intent.py +src/agent_framework/routing/pending_topics.py src/agent_framework/runtime/__init__.py src/agent_framework/runtime/agent_runtime.py src/agent_framework/runtime/transaction_input.py diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/__pycache__/__init__.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/__pycache__/__init__.cpython-313.pyc index ce1e9f932ef9681c3ac6d82b0b3abcf39917f394..df6d85885c4db3dc4b4db7900e4e3bef1f668a63 100644 GIT binary patch delta 31 lcmbQuJeQgKGcPX}0}!ZuS~QXSJ!8Sd->OXUMw1m7s{xO935Wmy delta 28 icmbQsJe!&OGcPX}0}$NKoIjEKJ!9U)->Q?97^?wy@d+RR diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/__pycache__/extensions.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/__pycache__/extensions.cpython-313.pyc index 807caa308a05696a980d0132b0f4a131280a78af..cb5548522a75b83c8b0af853f355fbe06649b711 100644 GIT binary patch delta 34 ocmcaDa94o)GcPX}0}!ZuTC|Z{g_*HvvH`OyQ@qjUP-Zq(0H^H;od5s; delta 31 lcmcaBa9e=;GcPX}0}$NKoWGGR delta 31 lcmccQdcl?ZGcPX}0}$NKoWGIVjhV4thO30(jH diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/__pycache__/observer.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/__pycache__/observer.cpython-313.pyc index 91e70cbde5248387d855fa5a5b8f9450e8b82efd..b1c9b6240599a23f8c411c1d1fdae8b20191d8b2 100644 GIT binary patch delta 36 qcmbQ-$vCx>k^3_*FBbz4sC-(qkvpA@v0!pBn<`Vh(dHR!zik1*uL}ME delta 33 ncmbQ*$vC-_k^3_*FBbz4+|Hc8kvpA@F>i7)o9gCSY`<**sly7E diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/analytics/__pycache__/__init__.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/analytics/__pycache__/__init__.cpython-313.pyc index fb49d21249dc76d215aae01ae8558e99a0dfe131..e898c3f011cbde695b04dafb3a7c7e7b29e6ff32 100644 GIT binary patch delta 33 ncmZ3?vXq7UGcPX}0}!ZwTC|Zni;=Nvav7s4Q@qjS*^C_kmQ)Ex delta 30 kcmZ3=vY3VYGcPX}0}$NKoWGGfi;=Nnav7uQ-T(jq diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/analytics/__pycache__/composite_publisher.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/analytics/__pycache__/composite_publisher.cpython-313.pyc index e981f8015b7461b23cb310b3750aa1817929347d..e712d2f94ce14c3df605ed1231b504cfb4ca64ee 100644 GIT binary patch delta 34 pcmX>md_tJ}GcPX}0}!ZwTC|aSITK^+7 delta 31 mcmX>hd`y`8GcPX}0}$NKoWGHKITK^^!4&kB)2x0&L diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/analytics/__pycache__/publisher.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/analytics/__pycache__/publisher.cpython-313.pyc index c229aea4baf9f9a73d6e22a6ce2670e9f4207c3e..ae1fbc0b0fd370fb6d0ce008b147ff6dc8add9c9 100644 GIT binary patch delta 34 pcmcb>cZrYtGcPX}0}!ZwTC|b-5EEnd}APP|c diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/analytics/__pycache__/tim_payload_mapper.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/analytics/__pycache__/tim_payload_mapper.cpython-313.pyc index 1283c1078a048d6fc08587964b5efe9576b4165c..d410915717efbdbab5af2c73d209618a51b5bc6f 100644 GIT binary patch delta 34 ocmX@+al(W9GcPX}0}!ZwTC|b7jFGWraucH}Q@qjU<&5){0KA6^oB#j- delta 31 lcmX@%am<7JGcPX}0}$NKoWGH~jFGWvaucKK=9P@|l>nq>3Nruz diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/analytics/__pycache__/tim_sequence.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/analytics/__pycache__/tim_sequence.cpython-313.pyc index 696a9cad26ad465dc634876eed1211932254ec94..cd2fd5384b3d0fc6bba921d72a57088a676ef8bd 100644 GIT binary patch delta 36 qcmbQ)$vC@{k^3_*FBbz4sD4_sk(-f)v2HRiiz-vR(Pkr-bO!*oVF^$G delta 33 ncmbQ;$vCrH4iQ)*D delta 30 kcmZo+X=362%*)Hg00g%)=Wpb;XJl-e?9He;xrlKR0CuDZF8}}l diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/analytics/providers/__pycache__/kafka.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/analytics/providers/__pycache__/kafka.cpython-313.pyc index 1de30648167f05abed817b4765865c9ce59c33a3..96ac00f6f13dd55bb718f8516c065ea33b6a5fc4 100644 GIT binary patch delta 34 ocmbQjH;s?`GcPX}0}!ZwTC|bdfr+tkvJaCgQ@qjULZ-zm0GfCS2mk;8 delta 31 lcmbQnH-(S;GcPX}0}$NKoWGIVfr+txvJaE$=3=JBEC7Zk2%i7| diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/analytics/providers/__pycache__/langfuse.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/analytics/providers/__pycache__/langfuse.cpython-313.pyc index 97d50e4236dee604530cce4ce7fa2211fa43e604..ca72ccc1cd5492474dc7441f0cfe195ad06a9812 100644 GIT binary patch delta 36 rcmZqK%GkD*k^3_*FBbz4sD4_sk$Vy|W6R`u%&JWBMw<^a9}EKk&c_R+ delta 33 ocmZqM%Gk1%k^3_*FBbz4+|Hc8k$Vy|W7Fh$%&MD@G9L^B0JQ82I{*Lx diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/analytics/providers/__pycache__/oci_streaming.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/analytics/providers/__pycache__/oci_streaming.cpython-313.pyc index d79f3db27efb638aca926455b195f2889595e41d..51cf17eb41f00ad7b97aa40fad86b310ed64c5fe 100644 GIT binary patch delta 34 pcmbQsH=mFDGcPX}0}!ZwTC|aSBNJoij8|}31I*L delta 28 icmbQrJeisMGcPX}0}$NKoIjEKF=N@p_o|b{80!Ibp$PZ@ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/billing/__pycache__/usage_repository.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/billing/__pycache__/usage_repository.cpython-313.pyc index dcb6ac636e5ded731cd3f54b9857dac3c217e476..7203cc94d9632e32a87df7cbdd3113d7a1359dcf 100644 GIT binary patch delta 34 ocmX?Bb)t&?szyJUM delta 31 lcmZqUYvJSm%*)Hg00g%)=Wpb;Vqz?w?8cn+a delta 31 lcmeCx?bhY~%*)Hg00g%)=Wpbm&(2sjc{RK0=1c6m`2duq3J?GQ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/channels/__pycache__/interruption.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/channels/__pycache__/interruption.cpython-313.pyc index bbcd9dec39f20898c3cf9973fd5f7d13cc57b385..120a77d969e24519857b93394c065c1dd02cd309 100644 GIT binary patch delta 34 pcmdmCw9AP5GcPX}0}!ZwTC|aS8WUsft<8 delta 31 lcmbQvJ(ZjLGcPX}0}$NKoWGG2lM~{ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/checkpoints/__pycache__/__init__.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/checkpoints/__pycache__/__init__.cpython-313.pyc index f0d1c0a3d27435f2caf378446bccd912f15a1bc8..156fbc620ead88022047c72125c743736f322e66 100644 GIT binary patch delta 33 ocmbQuK9`;QGcPX}0}!ZwTC|b-5))(1mi`M()qNyj%=Gp!#XiMs8(h#?Hz5%&JWBMw>&JkEa0u&;ttI delta 33 ocmbPxg>m*3M()qNyj%=Ga65DUMs8(h#`ekj%&MEin2)Cc0Jm=nbpQYW diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/checkpoints/__pycache__/langgraph_saver.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/checkpoints/__pycache__/langgraph_saver.cpython-313.pyc index 5b1b72e52b354a656b251737e60f951f5b1f4999..8a9eeb090c2f945892de4fc4a5e9cde5221d82fe 100644 GIT binary patch delta 36 rcmey^%=EdLiTg7zFBbz4sD4_sk$W2xWAo%gOsY)rMw_2Aan%C=;z0}c delta 33 ocmey|%=EFDiTg7zFBbz4+|Hc8k$W2xW8>sQOsbn-GI7-d0LCv1jQ{`u diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/config/__pycache__/__init__.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/config/__pycache__/__init__.cpython-313.pyc index d425540198f6eade2b8d2ebf7be4c147ccd9cc5e..30c732eca1ac3114de36c7312a8e1af2f9abec3b 100644 GIT binary patch delta 30 kcmcb{c!QDqGcPX}0}!ZuS~QW{n6Z4Ky(&|@(ZrM@0EK1qk2w4CC diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/config/__pycache__/agent_registry.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/config/__pycache__/agent_registry.cpython-313.pyc index 4cb1188fa51191c52b35d170e580f72eebe78810..8bb7929ced31af1ba6d1d9ce2a0b4e3b1223d309 100644 GIT binary patch delta 34 ocmZ2%w$zOKGcPX}0}!ZuTC|bdk(se}vM;kLQ@qjUBIb?a0H}8fm;e9( delta 31 lcmZ2#w%CmOGcPX}0}$NKoWGIVk(sf2vM;mh<`U+O;sBEH2`~Tv diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/config/__pycache__/settings.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/config/__pycache__/settings.cpython-313.pyc index 2b8e87945d35c337ab7babf068b3f2cf7df812f3..a3a2721b27af4685fcba81a1b81ecb425e399900 100644 GIT binary patch delta 34 ocmcbceLtJ~GcPX}0}!ZuTC|ZnN{g|4a+;PZQ@qjUZml400LtD9#Q*>R delta 31 lcmcbgeK(u?GcPX}0}$NKoWGGfN{g{{a+;Rv=3cEJZUC`Q3S9sI diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/gateways/__pycache__/__init__.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/gateways/__pycache__/__init__.cpython-313.pyc index d3caec6dbfb738b0929b76c5a8609d64cfda0701..39c367612555acede75fa09b14e65901d65350fe 100644 GIT binary patch delta 30 kcmdnQw1tWLGcPX}0}!ZuS~QWniLr8GpDI(l(ZtR50EA%)cK`qY delta 27 hcmdnOw26uPGcPX}0}$NKoIjDfiLrcQpX$V|^#E@&2@e1O diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/gateways/__pycache__/mcp_gateway_client.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/gateways/__pycache__/mcp_gateway_client.cpython-313.pyc index 35c314ee5b3c43c701992554fdf2b30ea8c9c7fa..ea291b6bfe6fa10d5092a217732cb30db7e1dafa 100644 GIT binary patch delta 34 ocmZpXYLnvr%*)Hg00b(Z7H#A{!N}M=`3j>dQ@qjUpN!nx0H}}(1poj5 delta 31 lcmZpZYLVjp%*)Hg00g%)=Wpab!N}M+`3j@z=HHCm+yId~37r4{ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/__pycache__/__init__.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/__pycache__/__init__.cpython-313.pyc index 9d7ec1f172bbf2e2aad34879f80a2271fd0da34f..25eb6a003bbf0ea27723bedd32dfd60d309e9c0b 100644 GIT binary patch delta 33 ocmcc1eV?2AGcPX}0}!ZwTC|b-BMW2otC834Z_p diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/__pycache__/framework_llm_client.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/__pycache__/framework_llm_client.cpython-313.pyc index 37a3be28a91e1f83bedc1b86c176591245db0216..e250fed35e05e50c58006ba0db1c1f779b5187a3 100644 GIT binary patch delta 36 rcmcaPlkxUUM()qNyj%=Gp!#XiM(#dN#`ejxI8~YAjW+M+lyU|D-A)T- delta 33 ocmcaTlkw(EM()qNyj%=Ga65DUM(#dN#@5NRI8`?vo delta 31 mcmZ3gw^)z+GcPX}0}$NKoWGHKGBab<|a{vGU diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/__pycache__/parallel_executor.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/__pycache__/parallel_executor.cpython-313.pyc index 7e6aac259fe338f6d76810aa3eafceb6211cbe8e..60934f023578ee4c6ef3399a8db4ded2992a7add 100644 GIT binary patch delta 36 rcmaF1it)uNM()qNyj%=Gp!#XiMs7D&#+J!Jtg1}$Mw`o7-}?gq-Kh(k delta 33 ocmaE{it*VhM()qNyj%=Ga65DUMs7D&#-_O8u0K*a$l5`1} delta 30 kcmbQh+Q-WMnU|M~0SInq&fmzL&&XIaxtdXR@*>7}0DamCF8}}l diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/__pycache__/rail_decision.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/__pycache__/rail_decision.cpython-313.pyc index 15d43753ba9b2a84fac78b18ea10f89de07b65fa..9c9966ad3ac704c91f6be2a78ac65db81aae0b3f 100644 GIT binary patch delta 34 ocmX@gbDW3!GcPX}0}!ZwTC|b7oSCs_Y^qH0Mw{E%wk-kx-a-r4 delta 33 ocmdmYiD}OzChpI?yj%=Ga65DUM($8H#GcPX}0}$NKoWGGffRnLhatx>H=4MVqCIFDG2*UsX diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/_compat.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/_compat.cpython-313.pyc index 6d79bde398d68a35c24d2dbb1c7aa9026a1d3f55..a580e3921007d9f25d609a8dffbc4d1566b3543f 100644 GIT binary patch delta 34 ocmeAb?ic3%%*)Hg00gR^7H#C7!^YS;c?Fv)Q@qjUvup>L0Hx{*LjV8( delta 31 lcmeAd?iJ?#%*)Hg00g%)=Wpbm!^YS=c?Fy5=JRX^m;jGh3EBVv diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/contestation_validation.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/contestation_validation.cpython-313.pyc index 4dfda6f51ec3c74a81caeb6a090689a7a76ade6e..a101e62981864cc6f79678debc4bf381e8f63d3e 100644 GIT binary patch delta 34 ocmZn^Y!>AH%*)Hg00gR^7H#BKV`ZE=*@#t@Dc)#vII9*j0F#jjJpcdz delta 31 lcmZn`Y!u}F%*)Hg00g%)=WpazV`ZE?*@#tjb0n)4GXQ_>2h;!n diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/contracts.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/contracts.cpython-313.pyc index 3865d2ccb10837dc2e49b8e7d8222ec4cf2e0d39..49a250a36a0c67e94703133a0c9aace9a549a344 100644 GIT binary patch delta 34 pcmexr`rVZKGcPX}0}!ZwTC|b-6dPmvob6AG^GcPX}0}!ZwTC|b-FAHPGWKLF9rg)>xdaS~n0I>QAIRF3v delta 31 lcmX>sb5Mr+GcPX}0}$NKoWGI#FAHPaWKLGq%?7N(oB)}$2+#lk diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/llm_adapter.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/llm_adapter.cpython-313.pyc index 0366ecf2ff1b033d61a5370c37a5af783c9465e4..bbf2cb75c6361041fe4a06cf15d2936c56c83a3e 100644 GIT binary patch delta 34 ocmcaEdsCMCGcPX}0}!ZwTC|b7fP=Afat((nQ@qjUg&byV0JVk*>i_@% delta 31 lcmca9dtH|MGcPX}0}$NKoWGH~fP=Apat(*-=EWRlYyh3?34#Cs diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/llm_client.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/llm_client.cpython-313.pyc index 8435c79964d1aef42f5e0a215f4da581bfb3f895..54882cd316d3f48566cc1b3a7c222653ae0337ae 100644 GIT binary patch delta 34 ocmbPdwZMw|GcPX}0}!ZwTC|Znii5FZavFy!Q@qjUZjQ+!0IVem+W-In delta 31 lcmZ2rHP4FsGcPX}0}$NKoWGGfii5FjavF!~=3b7;A^?@T3331c diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/llm_rails.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/llm_rails.cpython-313.pyc index 9f11ca3c5ffb34802a11f668a2e4595be112873a..738d967fceea86e6ee16a2e231cda257e75efbf2 100644 GIT binary patch delta 34 ocmdnzdcc+YGcPX}0}!ZwTC|bdkeRW4vMsYJQ@qjUB<2`-0I@O&dH?_b delta 31 lcmX@$y3dvSGcPX}0}$NKoWGIVkeRV{vMsaf<`m`_c>tQ+2@wDQ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/output_sanitization.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/output_sanitization.cpython-313.pyc index 401fe78747fa6765ff6d5e4aa7f1adf40cf4abf0..c9af6e39fb44117422a773e189ff237acc0f9cd7 100644 GIT binary patch delta 34 pcmcbXb0dfQGcPX}0}!ZwTC|Zno|AFnx6F86Q0079p3l#tW delta 31 lcmcbSb1jGaGcPX}0}$NKoWGGfo|CbEayF;x=1H8#bO5iJ3akJC diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/pipeline.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/pipeline.cpython-313.pyc index 69494e732a706979de39af6aa3fb89d8c196b8f9..367c823ff27e4bf9340dd13bd6d19c5b15ed948f 100644 GIT binary patch delta 34 ocmeyG^gW6DGcPX}0}!ZwTC|Z{Re-T=vY~(~Q@qjUFadc*0LBvu=>Px# delta 31 lcmeyK^fih5GcPX}0}$NKoWGGt<8 diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/_context.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/_context.cpython-313.pyc index 154eeca3942098426f9ca4fe30ef342edc03b541..efbe6b8bb5e5cec23f67b8cecb46c1beee6ee78b 100644 GIT binary patch delta 34 pcmaE_`%#zsGcPX}0}!ZwTC|aS3kzf4}4Da0++; diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/ausencia_oferta_proativa.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/ausencia_oferta_proativa.cpython-313.pyc index 8030b6c4ea3971ee3e84eb9d42e108adcc0a82a9..84b19f8fc632b12e12e5880d556d473f166a327f 100644 GIT binary patch delta 34 ocmZ1*zdoM(GcPX}0}!ZwTC|bdSC4Vtxb$a@Y0K6LtEdT%j delta 31 lcmZ1>a diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/coerencia.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/coerencia.cpython-313.pyc index 77fcc99b89f5e444dd02db55de42f74f1ea2a5a3..d378484121290287d5894995442c142ee54ee1d7 100644 GIT binary patch delta 34 pcmdn1uwQ}uGcPX}0}!ZwTC|b-G#_LCtdMvL1nV$)B delta 30 kcmbQmHG_-$GcPX}0}$NFoWGI#FEeBJWKI^<$p$R10E8?F3jhEB diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/dlex_out.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/dlex_out.cpython-313.pyc index ec16fea2b8a44c072efec444d2b5f4f5834eab22..59f0fb27f7e0b11f21a34daa8e6110c9bf04317d 100644 GIT binary patch delta 33 ncmbQvHxEi%500J^ve8UO$Q delta 31 lcmca?ebJixGcPX}0}$NFoWGGfScb8Ga=eV{<~A8$MgXF|39pF diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/pinj.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/pinj.cpython-313.pyc index 6414806b2764b7848fa2bfbf5996ca95571402cf..c947a70ed1e9f72e1b02ceb2a1be01b36030aeac 100644 GIT binary patch delta 34 ocmaFi_{x#{GcPX}0}!ZwTC|b7N}jQ6a+|y=Q@qjU)$+EC0LHiqy8r+H delta 31 lcmaFm_`;FnaY2InGcPX}0}!ZwTC|b-HWy>>}xlbHAz0iVDK^8f$< delta 31 lcmZ3)zJQ(kGcPX}0}$NFoWGGfk%@8Y*6^rg)>tmsr;V0H;R^cmMzZ delta 30 lcmZqV{m;YwnU|M~0SNA7&fmzrkd?7(@>*8a$yZp{0sxNx3J?GQ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/alcada.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/alcada.cpython-313.pyc index 8e6db06e0b92999e4f7823b53b09e1d19f3fdaec..a6892a37332d2dfb9538660fe8e0ed57017978af 100644 GIT binary patch delta 34 ocmZoy?^Ngh%*)Hg00gR^7H#By&BoX@`8%5`Q@qh;8TM=*0Irh?2LJ#7 delta 31 lcmeBFZ&&C3%*)Hg00eh3=WpbG&BoX<`8%8HW;yn39srfr37-G} diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/anatel.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/anatel.cpython-313.pyc index c11fe045c7868d0df77f418b26a915d5260541d6..04fcca85114c1126c0f04e11a8114e79cd9c30c0 100644 GIT binary patch delta 34 ocmdnvzR#WeGcPX}0}!ZwTC|b-D>q};WJVrUrg)>xYCO}#0lR7n6#xJL delta 31 lcmdnzzQ>*WGcPX}0}$NFoWGI#D>q}uWJVs<%^Ez@!~vsH39SGC diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/confirmation.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/confirmation.cpython-313.pyc index cc0ecfdaaa98bd87996476b9892c47cd699b882a..ef27b0365afec6e912b037368a4d5c6657630508 100644 GIT binary patch delta 34 ocmZ1!wj_-EGcPX}0}!ZwTC|bdSe$XfWP5Q{rg)>xDdPKt0kPEz1poj5 delta 31 lcmZ1ywkVAIGcPX}0}$NFoWGIVSe&tMvc0(K<}~qr!T_5z37r4{ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/dlex_in.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/dlex_in.cpython-313.pyc index c0af6cba1317f5dbf4a35bca10d653980aee474c..031c6db0c976270a2d1c7c0abf11700c2ffa1735 100644 GIT binary patch delta 34 pcmX>vc~O%4GcPX}0}!ZwTC|b-AS+|{H$Wdi`DKMMW; diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/dlex_out.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/dlex_out.cpython-313.pyc index 5ba6e9b7eff2d1c3b99375ed704cb2d3ea26ad9e..05fe8ace342a6159daf82e4e00498f71338845b3 100644 GIT binary patch delta 34 ocmZpcYL(*t%*)Hg00gR^7H#A{!phh)`2wpdQ@qjUudICR0I1Fi3;+NC delta 31 lcmZpbYL?>u%*)Hg00eh3=Wpab!phh+`2wrz=I^Y0>;REr38Vl3 diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/ragsec.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/ragsec.cpython-313.pyc index 5131d4d0a6674019fae99a2f6c1953abe000ae87..ec9e2e53aed7f0f425bd6959f2649cc41e440abf 100644 GIT binary patch delta 34 ocmcbnbwi8$GcPX}0}!ZwTC|b-8ardxjiBc>$){3G4s> diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/revprec.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/revprec.cpython-313.pyc index 65cc96c33437694d3dc1ff285cdc87083bf2df0f..c944a36ec41d93741bb9c5763d5990e3a08551c6 100644 GIT binary patch delta 34 ocmcbo^+1dJGcPX}0}!ZwTC|b-B|BsHGcPX}0}!ZwTC|b-w*X`NWOhMSrg)>xI)aw`0K}OJ5&!@I delta 31 lcmaFv@YI3(GcPX}0}$NFoWGI#w*X`7WOhN-&3b~C`~a(b390}9 diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/rules/__pycache__/__init__.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/rules/__pycache__/__init__.cpython-313.pyc index 65e10062714d88d6309bee94fdfcb8be8c039e29..a280463231c8753740771be4e95e7adc4bf24d19 100644 GIT binary patch delta 33 ncmbQrGM$C{GcPX}0}!ZwTC|Znf|0Rjatfm=Q@qjSPR1Spk+=y9 delta 30 kcmbQvGL?nNonc|H$-{jiK3;?Vr3YP!? delta 31 mcmbOtJ4u%NGcPX}0}$NFoWGHK1s7xgx{w$Lj0jiq`I{*Lx delta 31 lcmaFD{fL|UGcPX}0}$NFoWGG2-5%n diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/identity/__pycache__/__init__.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/identity/__pycache__/__init__.cpython-313.pyc index 02cfd33cdafdcfe487e67a09a4586fcf63201aff..8e60226c1d282f93653ef861ac61487f5594b5ae 100644 GIT binary patch delta 31 lcmbQiJd2t8GcPX}0}!ZwS~QXSDP!ftkE%@ZMw7)Dn*feQ33UJf delta 28 icmbQmJcF70GcPX}0}$NFoIjEKDP#G>kE)X;7@Gig?+Fb6 diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/identity/__pycache__/mcp_mapper.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/identity/__pycache__/mcp_mapper.cpython-313.pyc index ebfe626848cb13201d3f47d16724e2a5a3f9f34d..15e00436bcfa4dbb93b6cc1a06d6331467cef250 100644 GIT binary patch delta 34 ocmcbja!rN%GcPX}0}!ZwTC|bdiJ7r_vLCZ5Q@qjUV&;c@0I>=Q{{R30 delta 31 lcmcbnaz%yvGcPX}0}$NFoWGIViJ7r-vLCbR=2GT|d;prp36}r> diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/identity/__pycache__/models.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/identity/__pycache__/models.cpython-313.pyc index c7643ed88d9fcb0c5f955f74a732bc4e3e9146fc..a9e0d412c2d723ecb824ad3d9331487a6823b590 100644 GIT binary patch delta 34 ocmX>haY};wGcPX}0}!ZwTC|b7oSCtFax=3kQ@qjU70e4b0I;D7BLDyZ delta 31 lcmX>laYBOoGcPX}0}$NFoWGH~oSCt7ax=5)=2grKH~^Wu3A+FQ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/identity/__pycache__/resolver.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/identity/__pycache__/resolver.cpython-313.pyc index a6cfed472a36d72cc8cedaa012ff71d6150f488c..667d8c2e1341cb33f8bde81f9549ba4d860976d3 100644 GIT binary patch delta 34 ocmaE<@>+%aGcPX}0}!ZwTC|Znn3=J1ay+vtQ@qjU7UrjX0Jw1qUH||9 delta 31 lcmaE@@=}HSGcPX}0}$NFoWGGfn3=JBay+x@<~HW1d;p+aMs9l+#*)e2EUHZLMw<&*Oy>Xq&uI#? delta 33 ocmZ2}kZI{bChpI?yj%=Ga3^#AMs9l+#-hpIEUKG}SWM>t0JgmfOaK4? diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/judges/calibrated/__pycache__/__init__.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/judges/calibrated/__pycache__/__init__.cpython-313.pyc index 2f95a868653bc463b2becd4da52c383697130dbf..f29e56b8a016920672c23e851f91ee98a640bba8 100644 GIT binary patch delta 30 kcmaFJ_?VIVGcPX}0}!ZuS~QW{n6YW1y(&|@(ZrNm0Ek@(?EnA( delta 27 hcmaFN_>htNGcPX}0}$NFoIjDx8Z2J~0IQk_*#H0l delta 31 lcmbQIHAjp4GcPX}0}$NFoWGI#J2PX`WM&rC%~~v91OSyg3331c diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/judges/calibrated/prompts/__pycache__/__init__.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/judges/calibrated/prompts/__pycache__/__init__.cpython-313.pyc index cf731d8f5ed168b5936e3199898001ee582f79c7..4d82da4c3025135e6dbe5be50c7be83e1986b712 100644 GIT binary patch delta 30 kcmaFK_?nUXGcPX}0}!ZuS~QW{n6Yc3y(&|@(ZrNi0E&eP4FCWD delta 27 hcmaFO_>z(PGcPX}0}$NFoIjDk6mK-SU*Z!0n+pl| delta 30 kcmZoRZZzip%*)Hg00eh3=WpbWlVEI_oF$<;d7{K80ELtZkN^Mx diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/judges/calibrated/prompts/__pycache__/csi.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/judges/calibrated/prompts/__pycache__/csi.cpython-313.pyc index 615c00918c4526c7ee9de6095f341ed089b7250d..f1dc7156ba6270a4527bc3246cc49ffd2362f9ea 100644 GIT binary patch delta 33 ncmdnOwT+AWGcPX}0}!ZuTC|Z{m4&f&vLTBqQ@qjSFqV4&m5m7x delta 30 kcmdnSwS|lOGcPX}0}$NFoWGGxJNQ-$006~13mX6c delta 31 mcmaEA_t=j6GcPX}0}$NFoWGH~osY3&@?<{M&Aa$k3jhGD2@13T diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/judges/calibrated/prompts/__pycache__/rqlt.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/judges/calibrated/prompts/__pycache__/rqlt.cpython-313.pyc index 3de4edefcd0aaa883d0a02dad6c1c0c9e7e158ee..bc32f1015af446afa6bea6f5e58b84a437e8e601 100644 GIT binary patch delta 33 ncmeC>?C0eE%*)Hg00b(Z7H#A%U}kKaT*Iu&6mK+nA@dsmlf((E delta 30 kcmeC@?B(SC%*)Hg00eh3=WpaLU}kKYT*It7c`@@F0Dlt+MF0Q* diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/judges/calibrated/prompts/__pycache__/vctn.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/judges/calibrated/prompts/__pycache__/vctn.cpython-313.pyc index 3250734d187c42e76dd191b399bf8bc92674dd2d..16fc593ab54c46d725b1d9245f5b54a430157b91 100644 GIT binary patch delta 33 ncmeyw@`Z)_GcPX}0}!ZuTC|Znk&&@&axSAPQ@qjSNsJEwsq6|Z delta 30 kcmeyu@`;7}GcPX}0}$NFoWGGfk&&@waxSCl#0EkQoe*gdg diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/llm/__pycache__/base.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/llm/__pycache__/base.cpython-313.pyc index 1cc721696070506232a4c3bdbc2104aa3c2474c3..467a89078ec5fe8d66bc74dbfa294754fba67ec7 100644 GIT binary patch delta 34 pcmcc4bCZYrGcPX}0}!ZwTC|b-3?pN~b&}zzS#p diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/llm/__pycache__/profile_resolver.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/llm/__pycache__/profile_resolver.cpython-313.pyc index a8df724167e12f1a46531c7d27cb8158ca4c1537..37d1d0c09bb8e2ffac4b2d088dbc788fd8392bd5 100644 GIT binary patch delta 34 ocmaFm@y3JuGcPX}0}!ZwTC|b7nwha?ayzpsQ@qjUHO%`I0LFF;4gdfE delta 31 lcmaFk@ydhyGcPX}0}$NKoWGH~nwha`ayzr?=5@^b6acMj3Z(!5 diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/llm/__pycache__/providers.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/llm/__pycache__/providers.cpython-313.pyc index 55667e21d4a1c237a62147b39d68e3f00203f89b..31f23ae75f731381df0b18cfde43e091c32d9a72 100644 GIT binary patch delta 36 qcmZpA&(!jsiTg7zFBbz4sD4_skz19Ov2?N_t145x(dIB#olOAGAPS8D delta 33 ncmZp9&(!puiTg7zFBbz4+|Hc8kz19Ov3Rl}tLo+mR-H`%ww?+k diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/llm/__pycache__/structured_output.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/llm/__pycache__/structured_output.cpython-313.pyc index 51f486a6969f6a23d417ecc607343c89d4a949fc..97ff627d28cd7c7e6c3480c87f26731f390a31da 100644 GIT binary patch delta 34 pcmZ2%wbY9HGcPX}0}!ZwTC|aS4>M!!M!+!atpI6Q@qjUmCS*R0J!lA5C8xG delta 31 lcmeyv`HPeLGcPX}0}$NKoWGH~f|)UYatpKS=GDxBi~ylC38w%6 diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/mcp/__pycache__/__init__.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/mcp/__pycache__/__init__.cpython-313.pyc index d4bfbb4b0e7b682562881676864f1043fbb418dc..72a2ad3ec4e124d2fc443ccdcf20b70f2868f289 100644 GIT binary patch delta 33 ncmcb~e4Cm3GcPX}0}!ZwTC|Z{i;=NpvKgZ)Q@qjSXvR7KoJ0uW delta 30 kcmcc3e3O~`GcPX}0}$NKoWGG&-kaGk^3_*FBbz4sD4_sk$WLCW6|Wb%&JWBMw>4&Yq$Ua diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/mcp/__pycache__/models.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/mcp/__pycache__/models.cpython-313.pyc index 2c1f9120711765e55ee48eb043d255067cf4df44..c29938b2d1363f7faf16de53025c0d4c4990a031 100644 GIT binary patch delta 34 ocmca1a7%#uGcPX}0}!ZwTC|bdgNd^=4z(5X$avhT@Q@qjU#Y~fg0k8845C8xG delta 31 lcmdn4vsH)tGcPX}0}$NKoWGH~h>5XuavhWE=A}%NgaMfu38w%6 diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/mcp/__pycache__/tool_router.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/mcp/__pycache__/tool_router.cpython-313.pyc index 78b78233a1eb585942f249c82c2e99522ba014d5..fbc730745e615e9f7b9ab0ed7067034b121d573a 100644 GIT binary patch delta 34 pcmaEx_dbvNGcPX}0}!ZwTC|aSKNn;9Hq+}Q45{` diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/memory/__pycache__/__init__.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/memory/__pycache__/__init__.cpython-313.pyc index eb070c7e05299ca1835aeb4e4b30ab498154fae0..aaf6786f45868c3e0327dbfef2c05a58c615d099 100644 GIT binary patch delta 33 ocmaFQ@sWf3GcPX}0}!ZwTC|aSE;D2K^VGcPX}0}!ZwTC|bdiixp)vKx~sQ@qjUY$hKG0ISdmMF0Q* delta 31 lcmdmKy4{rfGcPX}0}$NKoWGIViixpyvKy1?=3FKp2>_Ms2;2Yw diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/memory/__pycache__/long_term_models.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/memory/__pycache__/long_term_models.cpython-313.pyc index 2193260b049aaeae9d42d8a436cb8a5696a7bc07..8af63eb24dc8d3bc298366374588642cf33db458 100644 GIT binary patch delta 34 ocmcb@dySX-GcPX}0}!ZwTC|b7i;=N@@-#+Org)>xyBQ@}0kUNY)&Kwi delta 31 lcmcb{dxe+#GcPX}0}$NKoWGH~i;=N*@-#-(&3hRoSpk~q32guX diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/memory/__pycache__/long_term_store.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/memory/__pycache__/long_term_store.cpython-313.pyc index 5aa19260dc11bc3b250ca1c1af8b687daf1d2d88..43b94734c3f92bbb670a23022b4d979e860d0440 100644 GIT binary patch delta 36 rcmZpA!r1bJk^3_*FBbz4sD4_sk^3;L-{k=S;A9Lg delta 33 ocmZp9!r1hLk^3_*FBbz4+|Hc8k^3R diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/memory/__pycache__/message_history.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/memory/__pycache__/message_history.cpython-313.pyc index 8d6318d37ea9c56faa41e7709032b734574327ad..ff0d9324af503f91cee26ebf73bdb30fe37d7332 100644 GIT binary patch delta 34 ocmZ4PxYCjPGcPX}0}!ZwTC|b-A0uPkWG*IErg)>x`b-sa0JE+Mxc~qF delta 31 lcmZ4KxZIKZGcPX}0}$NKoWGI#A0uPUWG*Jv&4x@BasZrA2~Yq4 diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/memory/__pycache__/summary_memory.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/memory/__pycache__/summary_memory.cpython-313.pyc index eb0f8b4106542bee35f4a3e88d9182285bd33b8b..ad97060a12c9cab32379472a2ccff71c9ce99b04 100644 GIT binary patch delta 34 pcmcbY_#lz{GcPX}0}!ZwTC|b-C^KX2qk2w4CC diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/models/__pycache__/identity.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/models/__pycache__/identity.cpython-313.pyc index 0916ec78ad8068ac37f86ce23a6bcdb7998472ab..470d06179324a7185399ee7f5c91dc571b9822ae 100644 GIT binary patch delta 34 ocmeB_?w02M%*)Hg00b(Z7H#A{%E(wg`68n#Q@qjUZ;X?<0jeK%*)Hg00g%)=Wpab%E(wc`68q0<{ylcxdD>y3LF3c diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/models/__pycache__/session.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/models/__pycache__/session.cpython-313.pyc index 68e68130b9cb3a2f7b6a7cb165866deb156b3453..b8dec2db152fdd1f734b6f60ff1ccfc2e5639536 100644 GIT binary patch delta 34 ocmbOtI!%=OGcPX}0}!ZuTC|aynVGR{GC#8_Q@qh;6XpbV0F%lH761SM delta 31 lcmbOxIz^QGGcPX}0}$NKoWGHqnVGR}|Q@qjSE|wPns)q_I delta 30 kcmeyu^@)r7GcPX}0}$NKoWGGfl7+Emaw?1J9bGcPX}0}!ZwTC|b-G#g{>+WEK`xrg)>xnk?*G0IO99@c;k- delta 31 lcmdlavq6UYGcPX}0}$NKoWGI#2Qy>sWEK|H&Dt#NTmY1`2#5dx diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/observability/__pycache__/guardrail_events.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/observability/__pycache__/guardrail_events.cpython-313.pyc index aa4a5022d33fe949dbc8b87baf13fdd6db4726fc..b1947d21dea0b1b79913ae35dbc1621732ce08d5 100644 GIT binary patch delta 34 ocmZn?Xc6H4%*)Hg00gR^7H#BKVq|QatjDOz6mPUSm~lND0FvXcFN5%*)Hg00g%)=WpazVq|QYtjDOjIh1id8vuW*2sHoz diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/observability/__pycache__/ic_events.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/observability/__pycache__/ic_events.cpython-313.pyc index 10a61af95e5a4a4452abfdde49a3103a1116b0f5..27ca857fb71f23be39ed24ed2b36dc29f0192f03 100644 GIT binary patch delta 33 ocmey!@|lJEGcPX}0}!ZwTC|aS5+h^%PU<0Irq_h5!Hn delta 30 lcmey&@{xu6GcPX}0}$NKoWGHK5+h^n(T=2^|0c diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/observability/__pycache__/noc_otel.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/observability/__pycache__/noc_otel.cpython-313.pyc index 6ac05fd6c7a57e4a2962b0957b3eed3c7255d5ce..f953f75d4d843669eee55782d730300a91205871 100644 GIT binary patch delta 34 pcmdmJxY>~VGcPX}0}!ZwTC|aS9W!Iy<}8(+V>H diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/observability/__pycache__/otel.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/observability/__pycache__/otel.cpython-313.pyc index 4961d2f537277d2974c0f3fcff9b14ee92ceb959..54969b6ed70478c9543f1631e8f123185ff6be51 100644 GIT binary patch delta 34 pcmcaCbybS{GcPX}0}!ZwTC|aS6&qvK0G_4^EdT%j delta 31 lcmZ3(w~CMZGcPX}0}$NKoWGIVfRV9fvJIo^=48f`tN@B92*dyY diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/observability/__pycache__/telemetry.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/observability/__pycache__/telemetry.cpython-313.pyc index 6e14600a6939321ee95ea4ecc1ee7b2f5d0f8f70..1403568adbaa51b2d8d3dfb882f1f3932b7eba33 100644 GIT binary patch delta 36 rcmX>-llkmSX710tyj%=Gp!#XiMs5Xm#`?)R?5a%hMw*llk;aX710tyj%=Ga65DUMs5Xm#@fj`?5dlC*{hBM0JRhfZU6uP diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/observability/__pycache__/token_cost.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/observability/__pycache__/token_cost.cpython-313.pyc index 676cc7dedb94dc6df122e5f6509cbfe3a8ba0116..65093788144fde6d31cecf249f51fb767bb8f999 100644 GIT binary patch delta 34 ocmez5{Kc93GcPX}0}!ZwTC|bdjFYipvJ_?63AX?M diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/observability/__pycache__/workflow_events.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/observability/__pycache__/workflow_events.cpython-313.pyc index 8cfe8a502b587de0b19ae61825a16d21cb1cd2e3..0895aa48d59a818db7febed3b1f36988da16b933 100644 GIT binary patch delta 34 ocmZpaYL?>u%*)Hg00gR^7H#DAWn^rf9LcE46mPV-j<{9 delta 31 lcmZpcYLw#s%*)Hg00g%)=WpcpWn^rg9LcDGcPX}0}$NFoWGGfg^{stasi|2=IM;0mH?$Q38?@8 diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/presentation/__pycache__/__init__.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/presentation/__pycache__/__init__.cpython-313.pyc index c23bb7da8e2bd7b33448f87330d44e367274a8b1..f3d2b878835cde264dc191337dbb2fa05929b243 100644 GIT binary patch delta 33 ncmX@ce1e(#GcPX}0}!ZwTC|ayhmo;%vIL_lQ@qh+TgFZRl(PtR delta 30 kcmX@Xe2kgGcPX}0}$NKoWGHqlZmltvM7`4WE-Y#0D$5M0RR91 diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/rag/__pycache__/embedding_provider.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/rag/__pycache__/embedding_provider.cpython-313.pyc index fdf9cd31ec7deb781ec22a5aea1191f4319b017b..a7defeb1aec60ab8897d4f05d69f2814f9faf40f 100644 GIT binary patch delta 34 ocmexj^v#I-GcPX}0}!ZwTC|bdmyxkv9}0KP#AmjD0& delta 31 lcmexn^u>t#GcPX}0}$NKoWGIVmyxk%awMbb<_5-Z;sBc diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/rag/__pycache__/rag_service.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/rag/__pycache__/rag_service.cpython-313.pyc index 46bd04b43ef8158bb634d05edad9fbd369c33f94..853b2c2e7017d7851f931ddef5a1e26fa7c3c550 100644 GIT binary patch delta 34 ocmbOjIysd4GcPX}0}!ZwTC|bdm4&f{GcPX}0}$NKoWGIVm4&f%av+Q9<_Z>RH2{>~2+RNg diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/rag/__pycache__/vector_store.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/rag/__pycache__/vector_store.cpython-313.pyc index 5cc1572641a040630b657dca0af7d38936184766..bcb6f09dca2d29042e2d7222885ff80544fa68d3 100644 GIT binary patch delta 36 rcmcaVk@4X~M()qNyj%=Gpz>+aM(*cKj1`kVGpRDg8*P?gKJE$t<6aB* delta 33 ocmaDjk@5aSM()qNyj%=Ga65DUM(*cKjAfHQGpTNtVm|H)0LFR?j{pDw diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/repositories/__pycache__/__init__.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/repositories/__pycache__/__init__.cpython-313.pyc index 2ba82514c00d0326d5771de7ac4ffe21b2f24e48..f8fa52ddc84229b509cc290c330ecc4fb25ae2e2 100644 GIT binary patch delta 30 kcmcb^c#o0$GcPX}0}!ZuS~QW{n6Y-Ey(&|@(ZrMr0EYw!*#H0l delta 27 hcmcb|c!!buGcPX}0}$NKoIjDS6ov2P|6iX%S$%7ew>zfrDHjNbY_u_s%VOugf3x7*b*kd9-t+!@;m4XTt-3j z5ubSvvJ@@9Tgl*0PR{Y*?-Ren+@v(CDoPiy5kcV3b3#Ie9?uWSZQKsMDvv%xFmoVd5nDQoChNqS011{NW}u=X z77)P-BG`b0pCon4(!>(xFVzY%sPd6NIG!6-Wf~Lzz?<3>Z`xG?}Vwof4C?%M*)Ibd&RQ z3ld8*lX6lON>YE*?TaGX9i|spj5i1YiOFl(4cSb9nu|;) zKVesfF?l&;83iY6bL41%3@+jU5kepW>>duA-29Z%oK(9aYoHh?{EMw8@8U4w|H8$@ NsPvVKh0z488UW1@e+>Wt delta 147 zcmZn`{UONrnU|M~0SNA7&d)l)Jdsa=v2LPzgf?$5rxZgLOA${nUoaP##T(2Y%nfGo z1@i{;fLZ+MESdrnw|ro%p1hP%b@LgGcPX}0}!ZwTC|b7f{C$eato6xQ@qjUl}uBt0ml#u{{R30 delta 31 lcmdmAv$ux(GcPX}0}$NFoWGH~f{C$Wato8{=G9D7tO2eI3YP!? diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/__pycache__/enterprise_router.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/__pycache__/enterprise_router.cpython-313.pyc index aa65e5cbfd4533e0400df0d9ed67e4e9b3648647..1c415ff5a6f59e8391dda3168f1e2aaf4cc06448 100644 GIT binary patch delta 11979 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%0AtKySFehhIs{Du&BaZ!pw@m$I_X z>t52dym=9BA0ybFtR=ZXPlN(b(gFVGg;`=MSYBMColE*GiiKa?t(vt2Y2}i9#bXk? z5nE+5skj>VBUyu&oP7bjWLr%odMT5S{x;}5T$)9Wgtz29n=f;fcF*Bv(Fv~|ei(bW z_2r*QD{n^O7Jf}1Z;jo8DK7KZs-{Yf!+2kbLyG-k-@*j;ZG!Ctw-Ve&!2S4k*!F6w za(9^v&%c@r2VR{a*2Ye~Y8Ewmv_IY7rNg$>2FQLbSKI@&uQiFc!N_YlLva2vv@@VMmRE65B!@yGr z>AmVs3*G985=PmnYJFD{d?gnf@C;93-LhZ8H;!h)nYU74^-*(Bijdb|=m)0Lu9Ua;e68XP+6ND_HSinkXYi&tY6JXW{~b1KJlsbyZBM>UaazbMxM z-#qF-DJ^u3nV@mZA$cXdXQYpC-yPG#kH)5FlZz_3^nH zVRul*-2`Y*%)kqTFbhVvC{8Fv!Ys-XGmWo-iFMYf%sK9oRagh^(R?k4C^s>x4Z~nQ zF{RvspFr>mqi>53-Fbs7`o8vA2Ak$9vs{4lvp~B}!d^ZO_n&CgI-IXKDvm(O>zhoa zLVC#=oseGmX$3s``d`tp?|dVy$zOyG<8qVO&k}1iZ3gT_Ea&etRU;1*%{RF^>h0&m zQY3}n*7$lb6k!)%{AOJCYwD!7m!vH$UX zir0AYKE+m1-Ss5Any4(I_|~+N4>}zS*cycKFB^QQ`SRcB)XkFIIJGfx2c8CD8Hteml{%$Ks1RFHSbQctu|gKY71Y^{NDa zdA~}W3%O@9A^U>?*Vn4zZtyrZaB=Z9@XHTMRmUVa`oZjY@$%CfzFwUB``3$i!850) zs@|g33&4CPiB~bJkXmPS+{Y6>`unLCH3L{9?6ThEwh`LC0EoD)v@}YQu zsDz|l*+Y3g?hNrt?<9(+@k1J?csR=`Hjx`4O3M@XL*SF*8a^B1dW~0+Xe|DPfamkc z6EFGOPo|9|Nl{s&4y2GIW|6*{Rfj%vFki=Kedqnui$(Nzc+3UbO$4Nc8j-#HF7tZ5aS^orc$8Ao+RMy96$B36@7GEup9QtIV8DKWlG@CJA(QV#37B=spJ z6I+qd~{VX8P@oF_0 zrw7feL{0ANww%)%B2McCTk>fGCyhd8$!QZO&4MxaV+$uYso?7KcX_Kd#*h2OI>Z}A zNt5!iS<)mEASG%_&uXn2tD+RC;>wqeK40$;&hh|ijPajxaFTWX;l4ilX~cEZiH*?s zd1Ve?VCY&<)((Vw0{9KjNb=kp&G5kIWug^6_`F)n?d*@-H^O#V1LTEWG Wv>X$f;KYRj+cgf+`k_F``}j{VR!3I= diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/__pycache__/models.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/__pycache__/models.cpython-313.pyc index dffeae85abe43d0b7ebeb9704a5bdaaf5677b285..3829534f76ef217219a80694f091d7cc2a1d2595 100644 GIT binary patch delta 34 ocmeAZo*>NqnU|M~0SHt-E!xPf#>`kY*@#({Dc)#vII}q$0F}K6MgRZ+ delta 31 lcmbOr+$YTanU|M~0SNA7&fmzb#>`kU*@#(nb0o7l8vuej2i^bx diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/__pycache__/multi_intent.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/__pycache__/multi_intent.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f701a67c4b43b45c850b640d3611aa74e51c6e19 GIT binary patch literal 13086 zcmb_idvH_NnZH-Mddrp{vi!tcunh?O3g%_P8%(gl26QcwMh4|tx;6s&$~{*O!F5x* z*|w(pz;u&^Ji0h(cWScPjXTXw*llN)G?^x|j~OYVNWEp-&Ne$8{xdO~H2r7y`_9#s zWg*gbW{*bap7--R-}n2za~4ZV>1vQGcosBh=rEp4qN+d#MWmgc8Hq<^I=DylQ?;%hFQ%B zmO4hT4%s#vwZ2FRO|?O)JukJCraB4hmSb@-9*N1yfY+cJ z_sZv0%aKT2B4QNcwtb=^^~b_eR5cxpNYU_JR;yUnE5{`{en1+D$Pug_(wf(NPW!Nn zGZ1)z8Da=4FfePD7+YWkhB(2v7IJ8a%Y{rdWF{uT0wt^%8#1qEhAe`unxN=u z<54Nw(1;j`YE3Ft-+uyj!D6wY7Yj-V&RD>hMmFS7bnDM zHpdkVjzowOk49ukjzKmglm2ck~Or!DA$LMheB1j_A(OGw!VG*JaNDfsE1^t_`&-Ilc}gm zaa9?o>$$@jN9l8Ync5W>4o>%_xD`LEZkQWPR&Tt}Gu@ryHfE}8FC3n!OmVfDL&q;1 zPL%hgxZ|0+#tV;4KbhhhGy9KRIGAwmxy>CR&3Zq}j#Bo3vx^5I`*V&G2Si@v00}l? zUUqOSqb0Dzu6~@PM*esf?$X_$=fE-!aiIRmN=nAdPRdEuo;x6l>KP}IF_D}Po{Pj! z2NemnAq>&UjvQ4NFM#x@NMD?QGZhh|KzpSc_2a1nYC}@ij_2gzrIxf5tV$+i8TRP` zsvZ{!wii7Q^zb0DpQN-#iE6-WB`a{T8_`>d9*-XIafY;_hh#~b&_g2BcBTbG$VI4! z>!n}`poNmM=Z<8`no?X-=HRId0}1#36n84~WC(Zm@!MQTtB+Ne?QD)oZULUUd}jp% z;f72;CsZkL!1|4lQ(BM=(Hw)fKC~vb`FS;Mjf_x^qNbf~Wb~gT!Kdc!IM|64C+=3&Sh!kLXA*DYgFj{>0hB% zD5W`-P_IsKEvx4i%1G6y$y={h6i~7(k-Iz$ZSpqD}um$A*TzP;7GWlvY@Yj7=$1<8a=nH>7O9l!&6lAP7QbEH(v0BeAL2*(o|7 zG8V%I^82Cj_6^?Vf$OYV3Ck)gAOm3vVl+sXDU@Yj48jH*{2|URev1;kpRHt0aEhUW zQ4E#Lp=ySS9q>VsLbooP5C#O(U&Szf?wFw;&Yy$fp{C6FnJBhST_B6Spp;=Jn_rV> zI+@Mca!^Ab7ZboyQRioTW<9+0^&$H&Z~`kB^y8M-X0B8K87XywG2qg}e?^v0rr-|p=m>IL#3G0=0btZWc+%VT%yuZMd=tD2%)L@({UfRB zy{}i#y5BzXk4LU?*Q=A&d#AZSci!>TUEOkR$E+{qSqE{)eKS+jaP`2o!!I7bcJ#%g zbG`HNRKt!`^-fy2ans`D`9tGkH4=r9n3^=sT;^hz#`U!#)a}V60>C)D=>MFLwr(D(GdJ880`W zasvksA0Tk}m}T=odg#Iv{S*Y6U@R{%eg@8RGsDR2@0*pM)yJ)$QxQ)%4`-Ksnt8#* zGyQ9{Q$EUUHIA^?S=3M*6LKU3Oe-wLMb!q9ei+C@9)%o3d#7p$omOxOb?UM00OT$` zGfsA?oyEvU2Ba-UfrGd2h{i%CWCO%^$j&XHUE#%vZf#e`9s3ZD-20Ynscr z$}gR}crNMbd-Xud)0cAfrCmbOC8S*bgw;>MNhqJ}a5|JYpMg}|P+vajLMFPU&65p* zC1%QLxlB=3iiGSp1eVHRPX$gebQ^WDkc}M-vUgvOT`d&`BKtQWGLye3N`l-QkZXR( z*v+{fEP10T!u;uo^UFMB#IiMy&0Cias0cd*tQa*R>ikx{XTi7~=p?Tsw9GSptH2@u z^BZwjmvX}(?jG9CpXQeoVRd=t34-Fmq0uxnRNh1Jkh% zOqv2i=T2?_4cjyl6-N~UO9Vy)@Rso)BtDo5yNIeKlB>YdB?P{R-%SQ+%s4>nX)Pe6 z_p)R!d=wZPqYIiHQGiNeahJNa*rC&;T@Y0BpoZA;I7YUjA6vw|zQD}J=k~sOZl0Sz z{(Vbw%{F?-03sRBO6Li3RwBqh_L=#*_cp%2{hjT}hJjSg(dm-RqdTW98Bg7n==0I( zgPHnO*LJ_Md&ZKfX}IQk(KTmD)pX97?yPG1UfpYTbBE@oZ0%J8Go^Rx8Z++IpE6vP z=cC$&tEW=6t@CWM_K|e$6Z7)T3G}X{Czbe_#m2NnE z!<5{1c+qCAub8oXTFSWVXSQbC^;ZTi56m9?-qBZ&z7@LB@`E$;ftza*O})3>eT&sh z!-jiCrn>2qI;N~TQ(2v{x-K~{I+L~ArkzP^cgE$J`QpXNtE(=4Ibm&hkO#vl)_5?k z(kUS808%P-er6LZu%HcD{cJI~@Su2y17H|<;LK3Dy$71y#pX$FI7zYcK|?Vdjw~fB zmjMVM`qtz!c?>ehBj9)qbQ%JCMT~!I~TSk9E&{U zsqC>Y4@L_5R-22`Z$YUia5!kl`t1egj;rEU#j%^V_gz1Br7Di4T*ngDV-IdB3K%L_ z!w~rY7pyw^(^)j#_R0Sm8eRvc7SQ9yL0yd~v8gc;%{1DF`qGpnKOKQ2`k}rwH41i6 zLVfh4DKUyRVUqo7A8BfAf;I&{1|?J!6|gKpIX$EYAWxJ-5@?iqAp3>hpBaxvLXeRS zXkQrg)L0C4br>uCY9DE8M5L{Nkq7d;pw@C4GerU=L*RiiQxZw&wAZK_Ls5m`dJ_q} zEIDSkzA_k%#m*|hXymLEBxsOQ;_8}WoBv?B0Jer&%LV3FRW(;mT|PB?FjchyxRu}Nq1Bf)E063@= zt^vA?f?2S1Td0Io9kG!SYW)NZNzpmN7?H)W*iP_xc`XEOL{v7AG7J^5*@FB;y%^3GU0bZM48uBuc_UL-7F*N2 z0wY)*RXD`44Vl`OOeLSGaA#^7Gc_$4Ps7y{R}Uqc4<+2anaYNHE^C>6fw9``4=yAc z%;-WM0nvZTNzWK06}kdw!W3hpJ_HX1E|Aly{A?aL19k#hSWcVK&mUMf3_0t@2H>l! zi|JWQ+FNc^&|W|6gx~P_<-rQlrtx9fD1#M6=HPD%NPtv34p9PE>=erw1JDU2@%YKxWA=ORKW0F@wB3f)qh03P>1DQhw4 z={Mt{rg|ZWB)_>Bh%8=H-v$05P&WXOT5|#ysMo`OPOm4JU~%Edbs37c52*+w3ml}P zQMtecR_O_lzgiD3ef2*5768LJ+Wj z_+P8HzV!7w0mz^!*^2jjxKChu)@Tt7OtJ%Ltk~!rdQZzn2kYW@`1GWuPT)Qi$3tfT z3nDoDj%ZHB=?A+M&czS?2H=EhPQX)8Ol`F=FTTFw+D2D*TwjpGh<=#2@^K&Kw|p;#(#t8+FV9~$op9HnwjC=p7#jm^NwdHBnQ z5j|uB+n{TJ%9lw4-%b^nNn>ohjp|d^&4uy;IYZuUAEK>g=o1rwBe09O$h%nN(u3>~ zwwck8BpxdeEPLUjmg_f2%fXutC}C$A)mucmptU~_LuM7Uzl7on^2gs^VCDuB-F?Zn zJ}N}WlaNSIK~+ukmi2esSOn&WG;!TXfZ;J@J$kL+dFwR;s5vMcJJLI-niPO9am~zQ z0=yC#r)nKFdXgyCI**t+c(lg!?3UvvvO320gFx8@IehMAF>Y9+x%5bZYRbdsh z!%DIy!;$Z6i(aw}iW3Dho&!%Q0gvi_S#OV~19G_9YV||5fnd!(*mGF)jH~j})TJrZ zhdMXC?f5=e)AlAi_rBM3GyMLU4~F0ExYg-PRg8Yv>6_!yC!R{4cq$=E=@TQV%BGo3 zSGHf?el>i%vMJf=o3Mlw7T$=An@?G;{lHZ#0 zYy(BHsy1EKnyhM_0}XQ%#9dVvpM^oBD>o!7H_RPOS9T>UyE5*kw0nKhy?$=XE%(Ns zmDND8OJBP9rK{nTYc)olWtR?JJT!CSO5k$fmUC6c=}9|RB%Lc>8Oy9({oStF$?xo* zKb~ybl32Ypv0~dz&-?Z7)B}3C61^Oq+w$hl>pS1$Zk|Z@_>(>U)SeTWhSm2h+{)5V z7_P*1uac?m{-l8^t9wP5t$R86hBCi9)v-UhV*gDswc;@t>pe5GVRNd+|N8pb=Jd9K zC9IG>Y+Z@ckGvQnTqr7zV;?Y}t-}KR> z^KhoNX?E}IzLy)Pdto!uu9l>$Wj2y_b*5aM^Iu4B?n`d&`yleu&5vi+bRJ=-wj1j+^pHqfE2oSQs{;_U>`=bHjl3AYBngUxUy#^R%K|mBSxeGaaTCq!~ z9Go-3`0ykaIDby?%jP_5qydC1N38{n4;f8?Te21afX&Vt1ekdkft%R};GxhB8mNGW zIl(;4Az+#?9xw6N6}Y3I2j!K6-tvU8 zr5F&?h2>GU11yY!MX+|`tiaxu*T1d!3?a1$h=bH3Bg-3y`SZpB5X0VWK3WcIwW?rp z5QSWuzWN;o0NISMsYofSU_`A%pIdI-;4uSOj%l6`b-e;7IOvYSSrD9hxoB=@1373W zlmzr986S$f0+$COP!1M24Jr$zf(t=}w@fU7?e^(6#y z!HXx_?YYyjDCpU7G?9Rj126qS#g?;||GaWKG7@jMYY>Wpre$~tSd(^()(3%Q);J)z zSUF1bLZ=-DIdI(58Y&PP5ik`(JOEb^lB4Kh@rplih>YxEHzC8?>9V=PH}nmuyY!BnOD{ z5!IRB#X}4d1ppJZH(Glm`6>h{jw4^g(AUvJ5J$cNp0`>9Zq>p4M+Xk}`uc;9_YNKk z`g;2J_6{8A@u?LnF&JjN^)Gpw5Az9CRapfl&(QC`zO7 z4Vi}aZV2HgxrE*fcwXBQSR*e%f?A^Q3w`QA{sv2;c}2D52pFD5X>YD5o>NeW@cdI6 z9Miy9J%X`^?33F5$L;Hj(7f_hIK}|o7AlxBw+`OQ-08B`WLayvY-6%)<6B+x@wX>$ zkRR+$mF=H4WgKM}cOt|q_gtyJTz~cWUp1!7Tax81sq(ec=8U^0?cR`d1MEtCFx|2<*|PJ- zNV=sbNi#lj)@D{T%?;da`-Qi2=G>KMzxC{V_!p0Cdp>-1Yr4KQS>Jlg({^Jx)3AQN z`<~5IQVNr)s85!!xo=@g>#=I@n*-MeZhGJEc&8&%&8Mr^Cac%J5ufi(Z+a}b>9JJD zp~Q)yk4?tXQb0m&kEC07CRqNkA2#{P{TOO(vDS0$Ew-&bH`t8OFKG~ zj?VcfZ?fM%nch5*+&qx*1yY*>0Ia6_!1&*P%h_`055+&4sS9=@=W@p6K1fAegSe*y zZqD*=3IaJCqe~Z^!HYnpAZ1XT6gVAc90eRCgY=XQaL-^VC4|-h!kW<70hpzsC|b&?@SsjVo;3sDRUtQ^ zX?kcLqAIV~qS_-0GJa(3n&xH4XNu8acmhn&JFp<)F^yM<5Q0Yle+?ga8H0}$tl1a3 zil#|n!wRlDJ+<1Qb&XF9gM102p`N{#}Rsa=UEZJquH} z9ZcM1HPbFip)m37Fe1ukHC7207VUKp)$P4znmXyBPfL3Xo{*oUl_#t4JrO>iZsp-M z`pN8rYx+cJ(gkmb=*#MCDwzI{V-L9gs+mYAlxYMqkacPs^Y;*%tS;nZliRkZ|z;aCXXR}VoMun)jB3cLna*$fgGxLcD)}yYe}^7|Sb``(v$C{c2yIr>;~}8@4|rc^?ioxb z>q040T6XElZ$6o*>A6YXkG~U7mB8!hOe25o%u8o7)?mhJzrPX(d^*B)@I>ZGo{2!GL2EAYj0FY`{VdsZFlgmn@*=4!PEWqudf$e2>p|E^v6;LsLkI1a2G{TgqlF-C`<(jY?v^d zGh(9#8z)TX%-DR+f-UE)*a|pP#5_Ttvte5h&01&evyNHwL4z6OgsEA});__NDVUix zcA$tQVjZUH5kPb!ir9t>zyQxs`Gv5v6+QopFcEtrx^BX*tB#QK3E>M-s)G~7q|C`e zQk0-;i*qs;OK_4DGOETC;-YFhag!5dZZ5&A^hqv}AkY)4JlH##PRPQzDD$FxApw$j zdALv0=e;4Yd)n2I25h)~$r4NjUhMU1^biI@N~S0RfAS+V&l z9kQvO3%nQ?#H*K*DIs zDi394GFDPu*Elhr;NzA4@6c4pqS7oYatl1ms&lBiUF|lsSn#Q0dNN(CKCpzr!YDLO3DJN!tlw3CCW*;bd9{UxlAHd?2-m z8BjuNi1VacXg)-b8TSVhMPHv{>Vx((Qy*^7xnh;2gFZrJ$Oi~fn?I)!>O?_U${7+2 z+7m_7TX`$k2sKpUm@!pQNRQUO3WD<>^s3S4sX-(fqsATQFbq~@?npQ4I*6tjtFFZ^ zGn#bXqm-^+ydQzBTP!ub#1=o;V%kv)bGWJ{`F>64JO!AFcSN9ymRD*=>-<}^gKs~G zwn(yqBsyxYMQ03WFCrw`qP89NvFWlzd(Iy3>e36t#^}kawJe)L1nNVDu!6Fzlq!42du8*+pZQuJLy3BW|6dzePvX z5&Le|+(qb>GuP;Hcb|FMscZl2t>wA^8!0-PKJb_(! z0=n)Xr*0RXx-C5QyYMvVJllN`Mj)BmWmI*|z9+q^{VwBQqE)0Z(lkg#ng`6FUrWRa zZL8L{X>Bml4ww$D?HnXFbZKojp+e-scSQc*V_s{-8flO84A5hg2s?4q(Sf2)-FxF8 z^%`W#8`NFnl$k-IYfJRGw)=K)=d9YROWV%>>`QoQJi4RqYIgH|mv8=zVKM}K^iE%T zwEFc0|Ib_iQZJEh1;)|SunTN|X2`y&J=37&_6NLL&DOd%Cx~J>5OL|s0MX8rjM<83riJUb<88oiR>|2+gCT#x^uh9X8HWq0p@{ ztso3yo{R5GiiyQwxzYs%L=|yR4v9yKD-|U0egYtM+7y$K?VV(}0-5B#c!@T#!;p zNg$QAYL4^sJXWpzdwdMUs}@orrX|%DOD?1myv)Z{vsV16Fi=XSag1kW{ykZ>r6nGh zL1uxMBw#A{+!7~(>|0R63SufPt7c7cmEmHtaD!*d(kx|dRZBd%03J|n3$YZGyvc;5 zn#-@ib5=<_6rXVpFJ20n@m??kcavrxX~Lu-F`;@ZbI2HKZUTda-Yp%i{srqAx8hQm zXK!!`0engL{V)*}(hFLT#r(Tz0rTvU?_8!RcR=M%}Bs&_$7!af#0zzIQmV=jNjf^6>~uS`erv z678y`a(1#|o&*#4OSM*xQL3jJIdB>x=P)h6;HZUzM6(JX#^gMwnq$`l(8*Hvq~;3M zk`{$`)1X9EkVGrZ@MaA1{p+9W}RySB6)AnDr`-u7aa4@94{g zMis}=rBjcbp4IMqp*x{mbHC!<_boEiS|05=xPIx62Mc?S=l2|6Hr=LI=+)*7R_KoG z{_KG~-Scp6(`ay6mrj=~s5i7U{>bHB9lAGsXE@igPpKU!)E->#P+UVxXNsNOIg|g9 zr*7F?3^d&n?g&|@5*S!^6di#~ui|JgGX4V7oM)Od(G8~abH+yqA;r;IWNL3utxV;b zMlw7!*~@G1=bJ`0m{G#xZM@fir~j8ycIdamzZzb9OX(acbdEgmDs7{R_vo^<=|v#0Z=qrT8ZOlmiVrzkiVdw*QhDtk zT9rd*i;ZoXU1ycXeGkU|H2#P2hgX%+slw=`-1Mbl%h0-c zr8WWw%Wyk^JCHN9)>Y~xa2IlRWuv8T0{4{RUIOf%)?!mzv9pT{$3($DENEx{@yiO@$WDA53f%vo+Aa%8+p$gis$&U=`{~)) zBICV1xiXpQ`HX3QYDTqP|F$67RrEF9o47NPwJ5&cWgCQ2jsN!I%3`izu;AXGckeG5 zQBA}0I~iX_D6|gbTL+ZZ!CcG!9CM(+9LY0BHkj8R`&tUVuDq{H@%7|td$aP|_}WO$ zbGYC+n)e)4JjY5_Wb=MwM@&oR?e)WoVv@ z0W!+Z&Ur5ysZacl3qivtLFz(_<&$<3fxF*)4G4c}4nq4^WFjs7rXOCp77FOOVADlp z&W#X4?idAd9vK0i3eosH$i3xy{-z3($+TpT;oy#L)ome@%b~N{wH3ZXlBb-;VmXDo z$%WN?8W3<{g-MkK<%fQe$4i*pyFmJ0OhL}Z(8xC_~ z%cR;|T8`B&v&uI_S~jLf%eQ53;5ZD03K>ZsLQ^tQ6!mx1@fB+S679{SyOY>!?>U*PKbd!*T4J7BkioIqm8a^yH5jQ-2|=@I qM27m3)@33THCl8ul#B#;?Dm$-1c1nK_mr#}NFz(#zib556#XAo+T(Em literal 0 HcmV?d00001 diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/config_loader.py b/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/config_loader.py index 6ab4bd04..95a9740e 100644 --- a/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/config_loader.py +++ b/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/config_loader.py @@ -33,3 +33,10 @@ def load_state_policies(path: str) -> list[RouterStatePolicy]: def load_router_defaults(path: str) -> dict[str, Any]: data = load_routing_config(path) return data.get("router", {}) + + +def load_multi_intent_config(path: str) -> dict[str, Any]: + """Backward-compatible technical limits; no section is required.""" + data = load_routing_config(path) + value = data.get("multi_intent", {}) + return value if isinstance(value, dict) else {} diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/enterprise_router.py b/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/enterprise_router.py index e622918f..ac060a6e 100644 --- a/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/enterprise_router.py +++ b/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/enterprise_router.py @@ -6,10 +6,12 @@ import re import unicodedata from typing import Any -from .config_loader import load_intents, load_router_defaults, load_state_policies +from .config_loader import load_intents, load_multi_intent_config, load_router_defaults, load_state_policies +from .multi_intent import MultiIntentPlanner from .continuity import SemanticRouteContinuity from .models import IntentDefinition, RouteDecision, RouterStatePolicy from agent_framework.llm.structured_output import parse_json_object +from agent_framework.mcp.tool_policy import ToolPolicyRegistry from agent_framework.runtime.transaction_parameters import extract_transaction_parameters, parse_transaction_confirmation from agent_framework.workflows.input_contract import ( expected_input_reprompt, @@ -44,6 +46,16 @@ class EnterpriseRouter: self.intents: list[IntentDefinition] = load_intents(self.config_path) self.state_policies: list[RouterStatePolicy] = load_state_policies(self.config_path) self.defaults = load_router_defaults(self.config_path) + tool_policies = ToolPolicyRegistry(getattr(settings, "TOOL_POLICIES_PATH", None)) + transactional_tools = { + name for name, policy in tool_policies.policies.items() + if policy.operation_type == "transactional" or policy.require_confirmation + } + self.multi_intent_planner = MultiIntentPlanner( + self.intents, + load_multi_intent_config(self.config_path), + transactional_tools=transactional_tools, + ) self.fallback_agent = self.defaults.get("fallback_agent", "billing_agent") self.intent_shift_threshold = float(self.defaults.get("confidence_threshold", 0.7)) self.transaction_confirmation = dict(self.defaults.get("transaction_confirmation") or {}) @@ -727,6 +739,27 @@ class EnterpriseRouter: # intent/agente ativos devem prevalecer sobre a route stickiness. Isso # evita manter um fluxo read-only (por exemplo, tracking) quando o usuário # muda para uma ação transacional (por exemplo, devolução). + planner = getattr(self, "multi_intent_planner", None) + multi_intent_plan = planner.plan(str(text)) if planner is not None else None + if multi_intent_plan is not None: + primary = multi_intent_plan.operations[0] + decision = RouteDecision( + route=primary.agent, + agent=primary.agent, + intent=primary.intent, + confidence=1.0, + reason="Plano multi-intent validado a partir de intents configuradas.", + method="keyword", + metadata={ + "multi_intent": True, + "multi_intent_plan": multi_intent_plan.model_dump(mode="json"), + }, + domain=primary.domain, + mcp_tools=primary.tools, + ) + await self._emit(decision, state) + return decision + keyword_candidate = self._route_by_keyword(text) active_agent = str(state.get("active_agent") or "").strip() previous = state.get("route_decision") or {} @@ -799,7 +832,13 @@ class EnterpriseRouter: """ tx_status = str(state.get("transaction_status") or "").strip().upper() if tx_status == "AWAITING_CONFIRMATION": - confirmation = parse_transaction_confirmation(text) + active = state.get("active_transaction") if isinstance(state.get("active_transaction"), dict) else {} + previous = state.get("route_decision") if isinstance(state.get("route_decision"), dict) else {} + owner_intent = str(active.get("started_from_intent") or previous.get("intent") or state.get("intent") or "") + compound_plan = self.multi_intent_planner.plan_confirmation_secondary( + text, primary_intent=owner_intent, primary_agent=state_decision.agent + ) + confirmation = "confirm" if compound_plan is not None else parse_transaction_confirmation(text) source = "deterministic" classifier_output = None raw_classifier = None @@ -828,6 +867,12 @@ class EnterpriseRouter: "transaction_confirmation_decision": confirmation, "transaction_confirmation_source": source, } + if compound_plan is not None: + state_decision.metadata.update({ + "multi_intent": True, + "multi_intent_confirmation": True, + "multi_intent_plan": compound_plan.model_dump(mode="json"), + }) if source == "semantic": state_decision.metadata.update({ "transaction_confirmation_classifier_output": classifier_output, @@ -1129,7 +1174,12 @@ class EnterpriseRouter: kw_normalized = kw.casefold() strategy = None # Exato primeiro para preservar o comportamento existente. - if kw_normalized in normalized: + keyword_tokens = self._keyword_tokens(kw) + text_tokens = self._keyword_tokens(text) + if ( + (len(keyword_tokens) == 1 and keyword_tokens[0] in text_tokens) + or (len(keyword_tokens) > 1 and kw_normalized in normalized) + ): strategy = "exact" elif self._ordered_keyword_match(kw, text): strategy = "ordered_tokens" diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/multi_intent.py b/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/multi_intent.py new file mode 100644 index 00000000..27354e17 --- /dev/null +++ b/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/multi_intent.py @@ -0,0 +1,203 @@ +from __future__ import annotations + +import re +import unicodedata +import uuid +from typing import Any, Literal + +from pydantic import BaseModel, Field + +from .models import IntentDefinition + + +class PlannedIntent(BaseModel): + operation_id: str + intent: str + agent: str + domain: str | None = None + tools: list[str] = Field(default_factory=list) + source_text: str = "" + disposition: Literal["execute", "inform", "unsupported", "defer"] = "execute" + message: str | None = None + status: Literal["pending", "completed", "failed", "skipped"] = "pending" + + +class MultiIntentPlan(BaseModel): + plan_id: str + operations: list[PlannedIntent] + execution_mode: Literal["primary_with_secondary", "sequential"] = "primary_with_secondary" + status: Literal["planned", "running", "completed", "partially_completed", "failed"] = "planned" + + +class MultiIntentPlanner: + """Deterministic planner constrained by routing.yaml. + + The planner never invents agents or tools. It recognizes independently + configured intents in conjunction-separated clauses. Execution remains + owned by the agent/workflow runtime. + """ + + _SPLIT = re.compile(r"\s*(?:,|;|\be\b|\bmas\b|\btamb[eé]m\b)\s*", re.I) + _CONFIRM_WITH_REMAINDER = re.compile( + r"^\s*(?:sim|s|pode|pode\s+sim|isso\s+mesmo|claro|ok|confirmo)\s*(?:,|;|\be\b)\s*(.+)$", + re.I, + ) + + def __init__( + self, + intents: list[IntentDefinition], + config: dict[str, Any] | None = None, + *, + transactional_tools: set[str] | None = None, + ): + self.intents = [item for item in intents if item.enabled] + self.config = dict(config or {}) + # Multi-intent é capacidade padrão. O bloco opcional só permite limites + # técnicos/overrides; sua ausência nunca desabilita a detecção. + self.enabled = bool(self.config.get("enabled", True)) + self.max_operations = max(2, int(self.config.get("max_operations", 4))) + self.transactional_tools = set(transactional_tools or set()) + + @staticmethod + def _normalize(value: str) -> str: + text = unicodedata.normalize("NFKD", str(value or "")) + return "".join(ch for ch in text if not unicodedata.combining(ch)).casefold() + + def _intent_for_clause(self, clause: str) -> IntentDefinition | None: + normalized = self._normalize(clause) + clause_tokens = set(re.findall(r"[\w]+", normalized, flags=re.UNICODE)) + matches: list[tuple[int, int, IntentDefinition]] = [] + for intent in self.intents: + for keyword in intent.keywords: + token = self._normalize(keyword) + keyword_tokens = re.findall(r"[\w]+", token, flags=re.UNICODE) + matched = bool( + keyword_tokens + and ( + (len(keyword_tokens) == 1 and keyword_tokens[0] in clause_tokens) + or (len(keyword_tokens) > 1 and token in normalized) + ) + ) + if matched: + matches.append((intent.priority, -len(token), intent)) + if not matches: + return None + matches.sort(key=lambda item: (item[0], item[1])) + return matches[0][2] + + def _is_transactional(self, operation: PlannedIntent) -> bool: + return any(tool in self.transactional_tools for tool in operation.tools) + + @classmethod + def _looks_like_request(cls, clause: str) -> bool: + normalized = cls._normalize(clause) + return bool(re.search( + r"\b(quero|preciso|mandar|manda|mande|enviar|envia|envie|gerar|gera|gere|" + r"alterar|altera|altere|mudar|muda|mude|consultar|consulta|consulte|" + r"cancelar|cancela|cancele|explicar|explica|explique|mostrar|mostra|mostre|" + r"falar|comprar)\b", + normalized, + )) + + @staticmethod + def _off_context(clause: str, operation_id: str) -> PlannedIntent: + return PlannedIntent( + operation_id=operation_id, + intent="off_context", + agent="", + source_text=clause, + disposition="unsupported", + message=f"Sobre {clause}, não consigo ajudar nesta jornada.", + status="pending", + ) + + def plan(self, text: str) -> MultiIntentPlan | None: + if not self.enabled: + return None + clauses = [part.strip(" .") for part in self._SPLIT.split(str(text or "")) if part.strip(" .")] + operations: list[PlannedIntent] = [] + unknown_clauses: list[str] = [] + seen: set[str] = set() + for clause in clauses[: self.max_operations * 2]: + intent = self._intent_for_clause(clause) + if intent is None: + if self._looks_like_request(clause): + unknown_clauses.append(clause) + continue + if intent.name in seen: + continue + seen.add(intent.name) + operations.append(PlannedIntent( + operation_id=f"op-{len(operations) + 1}", intent=intent.name, + agent=intent.agent, domain=intent.domain, tools=list(intent.mcp_tools), + source_text=clause, + )) + if not operations or len(operations) + len(unknown_clauses) < 2: + return None + # Regra técnica geral: uma mutação que exige o contrato transacional é a + # operação primária. Não é necessário cadastrar cada par de intents. + transactional = [item for item in operations if self._is_transactional(item)] + if len(transactional) == 1: + primary_operation = transactional[0] + operations.sort(key=lambda item: item is not primary_operation) + for operation in operations[1:]: + if self._is_transactional(operation) and operation.disposition == "execute": + operation.disposition = "defer" + for clause in unknown_clauses[: max(0, self.max_operations - len(operations))]: + operations.append(self._off_context(clause, f"op-{len(operations) + 1}")) + return MultiIntentPlan( + plan_id=f"mip-{uuid.uuid4().hex}", operations=operations, + execution_mode="primary_with_secondary", + ) + + def plan_confirmation_secondary( + self, text: str, *, primary_intent: str, primary_agent: str + ) -> MultiIntentPlan | None: + """Recognize an explicit confirmation followed by another request. + + The confirmation remains authoritative for the open transaction. The + secondary request is recorded and governed by the same combination + policy instead of turning the whole utterance into an intent shift. + """ + if not self.enabled: + return None + match = self._CONFIRM_WITH_REMAINDER.match(str(text or "")) + if match is None: + return None + clause = match.group(1).strip() + secondary = self._intent_for_clause(clause) + primary = next((item for item in self.intents if item.name == primary_intent), None) + if secondary is not None and secondary.name == primary_intent: + return None + if secondary is None and not self._looks_like_request(clause): + return None + operations = [ + PlannedIntent( + operation_id="op-1", intent=primary_intent, agent=primary_agent, + domain=primary.domain if primary else None, + tools=list(primary.mcp_tools) if primary else [], + source_text="confirmation", status="pending", + ), + (PlannedIntent( + operation_id="op-2", intent=secondary.name, agent=secondary.agent, + domain=secondary.domain, tools=list(secondary.mcp_tools), source_text=clause, + ) if secondary is not None else self._off_context(clause, "op-2")), + ] + if self._is_transactional(operations[1]) and operations[1].disposition == "execute": + operations[1].disposition = "defer" + return MultiIntentPlan( + plan_id=f"mip-{uuid.uuid4().hex}", operations=operations, + execution_mode="primary_with_secondary", + ) + + @staticmethod + def public_messages(plan: dict[str, Any] | MultiIntentPlan | None) -> list[str]: + if plan is None: + return [] + data = plan.model_dump() if isinstance(plan, MultiIntentPlan) else plan + messages = [] + for operation in data.get("operations", []) or []: + message = str(operation.get("message") or "").strip() + if message and message not in messages: + messages.append(message) + return messages diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/pending_topics.py b/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/pending_topics.py new file mode 100644 index 00000000..5de65c78 --- /dev/null +++ b/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/pending_topics.py @@ -0,0 +1,101 @@ +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, Awaitable, Callable + +from .multi_intent import MultiIntentPlanner + +AgentHandler = Callable[[dict[str, Any]], Awaitable[dict[str, Any]]] + + +@dataclass(frozen=True) +class PendingTopicDrain: + answer: str + pending_topics: list[dict[str, Any]] + handled_topics: list[dict[str, Any]] + + +async def drain_pending_topics( + state: dict[str, Any], + candidate: str, + handlers: dict[str, AgentHandler], +) -> PendingTopicDrain: + """Drain read-only topics and retain anything that cannot finish safely.""" + route = state.get("route_decision") if isinstance(state.get("route_decision"), dict) else {} + plan = route.get("metadata", {}).get("multi_intent_plan") + topics = list(state.get("pending_topics") or ( + plan.get("operations", [])[1:] if isinstance(plan, dict) else [] + )) + remaining: list[dict[str, Any]] = [] + handled = list(state.get("handled_topics") or []) + secondary_answers: list[str] = [] + for topic in topics: + disposition = topic.get("disposition") + if disposition == "defer": + remaining.append(topic) + continue + if disposition != "execute": + handled.append({**topic, "status": "completed"}) + continue + handler = handlers.get(str(topic.get("agent") or "")) + if handler is None: + remaining.append(topic) + continue + source_text = str(topic.get("source_text") or "").strip() + child = { + **state, + "user_text": source_text, + "message_text": source_text, + "sanitized_input": source_text, + "route": topic.get("agent"), + "active_agent": topic.get("agent"), + "intent": topic.get("intent"), + "domain": topic.get("domain"), + "mcp_tools": topic.get("tools") or [], + "route_decision": { + "route": topic.get("agent"), "agent": topic.get("agent"), + "intent": topic.get("intent"), "mcp_tools": topic.get("tools") or [], + "metadata": {"multi_intent_secondary": True}, + }, + "active_transaction": None, + "transaction_status": None, + "transaction_pre_validation": None, + "confirmation_required": False, + "confirmation_received": False, + "selected_tool_call": {}, + "pending_tool_call": {}, + "pending_tool_clarification": None, + "pending_domain_workflow": None, + "missing_parameters": [], + "tool_policy_result": None, + "mcp_results": [], + "available_mcp_tools": topic.get("tools") or [], + "next_state": None, + "workflow_id": None, + "route_bypassed": False, + } + try: + result = await handler(child) + except Exception: + remaining.append(topic) + continue + answer = str((result or {}).get("answer") or "").strip() + if not answer: + remaining.append(topic) + continue + secondary_answers.append(answer) + handled.append({**topic, "status": "completed"}) + + secondary_answers.extend(MultiIntentPlanner.public_messages(plan)) + unique = [] + for answer in secondary_answers: + if answer and answer.casefold() not in candidate.casefold() and answer not in unique: + unique.append(answer) + if unique: + secondary = "\n\n".join(unique) + candidate = ( + f"{secondary}\n\n{candidate.lstrip()}".strip() + if state.get("confirmation_required") + else f"{candidate.rstrip()}\n\n{secondary}".strip() + ) + return PendingTopicDrain(candidate, remaining, handled) diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/runtime/__pycache__/__init__.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/runtime/__pycache__/__init__.cpython-313.pyc index a35920c55239c319b7f9a26329efc678d601cd51..a0fbec33b36e42d8ff815f6d7679383ec17f8df4 100644 GIT binary patch delta 30 lcmcb^bdQPqGcPX}0}!ZuS~QV+En~&RovKXnMicMV0RWMV3Q7O~ delta 27 icmcb|bcc!iGcPX}0}$NKoIjC!Eo0flovITb)Byl`VhQ2^ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/runtime/__pycache__/agent_runtime.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/runtime/__pycache__/agent_runtime.cpython-313.pyc index 23e52040f9fbf575b364daff0b9504c416890c46..5bb68f33354e72ce5b20de38e8c99aa60b82cd77 100644 GIT binary patch delta 76 zcmcc8!}Fkrhx;=xFBbz4sC-(qk$Vbb8Q2DfIBex5)Mw@8dfZx-_a delta 56 zcmZpi$@p~!Bll-sUM>b8FkxP>k=un?rb<6IuS7p3u_RGHIX|x?u{gdsGq)foGc7YU KWpg<*mnQ(}SQ0+~ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/sse/__pycache__/__init__.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/sse/__pycache__/__init__.cpython-313.pyc index abd217765813067c53de17863ce66d03f5a3e817..cd1f4cc42226925bc65a8022cf8c4b1761ab0ee0 100644 GIT binary patch delta 30 kcmcc2c$Ja+GcPX}0}!ZwS~QW{n6YG{y(&|@(ZrN|0EDOrwg3PC delta 27 hcmcc0c$ty=GcPX}0}$NKoIjDx8Z6$50LStQ9smFU delta 31 lcmezB_1TO2GcPX}0}$NKoWGI#J2PX!WM&rC%~~wpiU6+I3AF$K diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/supervisor/__pycache__/__init__.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/supervisor/__pycache__/__init__.cpython-313.pyc index 0db928e02476996ce1035f9937179d0c20fd7a42..65b6f01aaa43fc0ea3a54204f1d1bdd163b00477 100644 GIT binary patch delta 30 kcmcb`c!!buGcPX}0}!ZwS~QW{n6Y}Iy(&|@(ZrN80EUYQ(f|Me delta 27 hcmcb^c#DzyGcPX}0}$NKoIjDx`t<8 delta 31 lcmeC=Zs+Fy%*)Hg00eh3=Wpb8V`eO!9K@`;xsq9c2>^kf2lfB} diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/workflows/__pycache__/input_contract.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/workflows/__pycache__/input_contract.cpython-313.pyc index 1eb76230833fc9cc6342beef525db965132754fb..078f3f4807341d8252a432417369e3c9b35b047f 100644 GIT binary patch delta 34 pcmccSa>IrDGcPX}0}!ZuTC|aS9W!IY_2*#0|2yT3X1>$ delta 31 mcmdljv{Q)tGcPX}0}$NFoWGI#6eDBB$~{BMKw{ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/workflows/__pycache__/repository.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/workflows/__pycache__/repository.cpython-313.pyc index 5ab0dd87182b933417206b9d5786abf5bf47de3d..f5f2a86af3e7c7e26b0ded66a2222c64e54cde34 100644 GIT binary patch delta 34 pcmbOwI!BcIGcPX}0}!ZuTC|b-CL?3b+aM($W<#>&Z=%&JWBMw|PXS9Jma&hZOL delta 33 ocmdlri)qI!ChpI?yj%=Ga3^#AM($W<#`4LT%&MCwFt6$a0JZ=M;s5{u diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/workflows/__pycache__/tool_executor.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/workflows/__pycache__/tool_executor.cpython-313.pyc index 15f1453ad726f48b72edeacf2e752bddecd388ad..c99df20e8eaeabc70c1572c97f238d87a4e45070 100644 GIT binary patch delta 34 pcmZ3-zk#3oGcPX}0}!ZuTC|aSHX~#G1put23W)#! delta 31 mcmdnMzmA{#GcPX}0}$NFoWGHKHX~#0 str: + if old not in text: + raise RuntimeError(f"Marcador não encontrado em {path}: {old[:60]!r}") + return text.replace(old, new, 1) + + +def update_state(path: Path) -> None: + text = path.read_text(encoding="utf-8") + if " pending_topics: list[dict[str, Any]]" in text: + return + text = replace_once( + text, + " supervisor_results: list[dict[str, Any]]\n", + " supervisor_results: list[dict[str, Any]]\n" + " pending_topics: list[dict[str, Any]]\n" + " handled_topics: list[dict[str, Any]]\n", + path, + ) + path.write_text(text, encoding="utf-8") + + +def update_graph(path: Path) -> None: + text = path.read_text(encoding="utf-8") + if "from agent_framework.routing.pending_topics import drain_pending_topics" in text: + return + text = replace_once( + text, + "from agent_framework.routing.enterprise_router import EnterpriseRouter\n", + "from agent_framework.routing.enterprise_router import EnterpriseRouter\n" + "from agent_framework.routing.pending_topics import drain_pending_topics\n", + path, + ) + text = replace_once( + text, + " } if decision.method == \"continuity\" else {},\n }\n\n async def billing_agent", + " } if decision.method == \"continuity\" else {},\n" + " \"pending_topics\": (\n" + " (decision.metadata or {}).get(\"multi_intent_plan\", {}).get(\"operations\", [])[1:]\n" + " if (decision.metadata or {}).get(\"multi_intent_plan\") else []\n" + " ),\n" + " }\n\n async def billing_agent", + path, + ) + marker = " async def output_supervisor(self, state):" + before, output = text.split(marker, 1) + old = """ if not bool(getattr(self.settings, \"ENABLE_OUTPUT_SUPERVISOR\", True)): + return { + \"output_guardrails_already_applied\": False, + \"supervisor_action\": \"disabled\", + \"supervisor_attempt\": int(state.get(\"supervisor_attempt\", 0)), + } + + candidate = state.get(\"answer\") or \"\" +""" + new = """ drained = await drain_pending_topics( + state, + str(state.get(\"answer\") or \"\"), + { + \"billing_agent\": self.billing.run, + \"product_agent\": self.product.run, + \"orders_agent\": self.orders.run, + \"support_agent\": self.support.run, + }, + ) + candidate = drained.answer + if not bool(getattr(self.settings, \"ENABLE_OUTPUT_SUPERVISOR\", True)): + return { + \"answer\": candidate, + \"final_answer\": candidate, + \"pending_topics\": drained.pending_topics, + \"handled_topics\": drained.handled_topics, + \"output_guardrails_already_applied\": False, + \"supervisor_action\": \"disabled\", + \"supervisor_attempt\": int(state.get(\"supervisor_attempt\", 0)), + } + +""" + output = replace_once(output, old, new, path) + output = replace_once( + output, + " \"output_guardrails_already_applied\": True,\n", + " \"output_guardrails_already_applied\": True,\n" + " \"pending_topics\": drained.pending_topics,\n" + " \"handled_topics\": drained.handled_topics,\n", + path, + ) + path.write_text(before + marker + output, encoding="utf-8") + + +def main() -> None: + tuning = ROOT / "Tuning-Performance" + states = sorted(tuning.glob("**/app/state.py")) + graphs = sorted(tuning.glob("**/app/workflows/agent_graph.py")) + for path in states: + update_state(path) + for path in graphs: + update_graph(path) + print(f"pending_topics sincronizado: {len(states)} states, {len(graphs)} graphs") + + +if __name__ == "__main__": + main() diff --git a/agent_framework_oci/templates/agent_template_backend/app/state.py b/agent_framework_oci/templates/agent_template_backend/app/state.py index 69555085..0b4da3e9 100644 --- a/agent_framework_oci/templates/agent_template_backend/app/state.py +++ b/agent_framework_oci/templates/agent_template_backend/app/state.py @@ -39,6 +39,8 @@ class AgentState(TypedDict, total=False): missing_parameters: list[str] supervisor_plan: dict[str, Any] supervisor_results: list[dict[str, Any]] + pending_topics: list[dict[str, Any]] + handled_topics: list[dict[str, Any]] active_agent: str route_bypassed: bool continuity_signal: dict[str, Any] diff --git a/agent_framework_oci/templates/agent_template_backend/app/workflows/agent_graph.py b/agent_framework_oci/templates/agent_template_backend/app/workflows/agent_graph.py index 99bf4c5f..7101db41 100644 --- a/agent_framework_oci/templates/agent_template_backend/app/workflows/agent_graph.py +++ b/agent_framework_oci/templates/agent_template_backend/app/workflows/agent_graph.py @@ -7,6 +7,7 @@ from agent_framework.guardrails.rail_action import RailAction from agent_framework.guardrails.rail_result import RailResult from agent_framework.judges.judge import JudgePipeline from agent_framework.routing.enterprise_router import EnterpriseRouter +from agent_framework.routing.pending_topics import drain_pending_topics from agent_framework.supervisor.supervisor import Supervisor from agent_framework.observability.workflow_events import WorkflowTelemetry from agent_framework.observability.guardrail_events import GuardrailTelemetry @@ -409,6 +410,10 @@ class AgentWorkflow: "reason": decision.reason if decision.method == "continuity" else None, "profile": (decision.metadata or {}).get("continuity_profile"), } if decision.method == "continuity" else {}, + "pending_topics": ( + (decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:] + if (decision.metadata or {}).get("multi_intent_plan") else [] + ), } async def billing_agent(self, state): @@ -547,14 +552,28 @@ class AgentWorkflow: agente gerar `answer` e antes dos judges/persistência, produzindo campos supervisor_* no state e eventos GRL.001..GRL.009 via AgentObserver. """ + drained = await drain_pending_topics( + state, + str(state.get("answer") or ""), + { + "billing_agent": self.billing.run, + "product_agent": self.product.run, + "orders_agent": self.orders.run, + "support_agent": self.support.run, + }, + ) + candidate = drained.answer if not bool(getattr(self.settings, "ENABLE_OUTPUT_SUPERVISOR", True)): return { + "answer": candidate, + "final_answer": candidate, + "pending_topics": drained.pending_topics, + "handled_topics": drained.handled_topics, "output_guardrails_already_applied": False, "supervisor_action": "disabled", "supervisor_attempt": int(state.get("supervisor_attempt", 0)), } - candidate = state.get("answer") or "" context = { **(state.get("context") or {}), "tenant_id": state.get("tenant_id"), @@ -623,6 +642,8 @@ class AgentWorkflow: for r in decision.results ], "output_guardrails_already_applied": True, + "pending_topics": drained.pending_topics, + "handled_topics": drained.handled_topics, "guardrail_decisions": state.get("guardrail_decisions", []) + [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])], } diff --git a/agent_framework_oci/templates/agent_template_backend/docs/TRANSACTION_SEMANTIC_CONFIRMATION.md b/agent_framework_oci/templates/agent_template_backend/docs/TRANSACTION_SEMANTIC_CONFIRMATION.md index a5508710..8cbc0077 100644 --- a/agent_framework_oci/templates/agent_template_backend/docs/TRANSACTION_SEMANTIC_CONFIRMATION.md +++ b/agent_framework_oci/templates/agent_template_backend/docs/TRANSACTION_SEMANTIC_CONFIRMATION.md @@ -4,7 +4,7 @@ Este template suporta confirmação transacional em duas camadas: primeiro um pa A configuração `router.transaction_confirmation.semantic_fallback` usa três classes: `SIM`, `NAO` e `CONTINUAR`. O prompt pode usar `{{ pending_prompt }}`, `{{ relevant_conversation_context }}`, `{{ user_input }}` e `{{ allowed_values }}`. O histórico injetado é apenas contexto de interpretação; não substitui validação de negócio ou evidência MCP. -Exemplo: após `Você confirma o cancelamento do serviço Tamboro Mensal?`, a frase `isso mesmo, pode confirmar` pode ser classificada como `SIM`. Já `mas qual é o valor?` deve ser `CONTINUAR`, portanto não executa a ação por confirmação. +Exemplo: após `Você confirma o cancelamento do serviço adicional selecionado?`, a frase `isso mesmo, pode confirmar` pode ser classificada como `SIM`. Já `mas qual é o valor?` deve ser `CONTINUAR`, portanto não executa a ação por confirmação. Entradas explícitas já suportadas continuam no caminho determinístico e não geram custo adicional de LLM. Em observabilidade, o fallback usa `transaction.confirmation.semantic_classifier` e o `route_decision.metadata` informa `transaction_confirmation_source: semantic`. diff --git a/agent_framework_oci/templates/agent_template_backend_day_zero/app/state.py b/agent_framework_oci/templates/agent_template_backend_day_zero/app/state.py index 7e3f280e..fb4a93bd 100644 --- a/agent_framework_oci/templates/agent_template_backend_day_zero/app/state.py +++ b/agent_framework_oci/templates/agent_template_backend_day_zero/app/state.py @@ -36,6 +36,8 @@ class AgentState(TypedDict, total=False): missing_parameters: list[str] supervisor_plan: dict[str, Any] supervisor_results: list[dict[str, Any]] + pending_topics: list[dict[str, Any]] + handled_topics: list[dict[str, Any]] active_agent: str route_bypassed: bool continuity_signal: dict[str, Any] diff --git a/agent_framework_oci/templates/agent_template_backend_day_zero/app/workflows/agent_graph.py b/agent_framework_oci/templates/agent_template_backend_day_zero/app/workflows/agent_graph.py index 2770df01..3df0833f 100644 --- a/agent_framework_oci/templates/agent_template_backend_day_zero/app/workflows/agent_graph.py +++ b/agent_framework_oci/templates/agent_template_backend_day_zero/app/workflows/agent_graph.py @@ -7,6 +7,7 @@ from agent_framework.guardrails.rail_action import RailAction from agent_framework.guardrails.rail_result import RailResult from agent_framework.judges.judge import JudgePipeline from agent_framework.routing.enterprise_router import EnterpriseRouter +from agent_framework.routing.pending_topics import drain_pending_topics from agent_framework.supervisor.supervisor import Supervisor from agent_framework.observability.workflow_events import WorkflowTelemetry from agent_framework.observability.guardrail_events import GuardrailTelemetry @@ -407,6 +408,10 @@ class AgentWorkflow: "reason": decision.reason if decision.method == "continuity" else None, "profile": (decision.metadata or {}).get("continuity_profile"), } if decision.method == "continuity" else {}, + "pending_topics": ( + (decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:] + if (decision.metadata or {}).get("multi_intent_plan") else [] + ), } async def billing_agent(self, state): @@ -545,14 +550,28 @@ class AgentWorkflow: agente gerar `answer` e antes dos judges/persistência, produzindo campos supervisor_* no state e eventos GRL.001..GRL.009 via AgentObserver. """ + drained = await drain_pending_topics( + state, + str(state.get("answer") or ""), + { + "billing_agent": self.billing.run, + "product_agent": self.product.run, + "orders_agent": self.orders.run, + "support_agent": self.support.run, + }, + ) + candidate = drained.answer if not bool(getattr(self.settings, "ENABLE_OUTPUT_SUPERVISOR", True)): return { + "answer": candidate, + "final_answer": candidate, + "pending_topics": drained.pending_topics, + "handled_topics": drained.handled_topics, "output_guardrails_already_applied": False, "supervisor_action": "disabled", "supervisor_attempt": int(state.get("supervisor_attempt", 0)), } - candidate = state.get("answer") or "" context = { **(state.get("context") or {}), "tenant_id": state.get("tenant_id"), @@ -621,6 +640,8 @@ class AgentWorkflow: for r in decision.results ], "output_guardrails_already_applied": True, + "pending_topics": drained.pending_topics, + "handled_topics": drained.handled_topics, "guardrail_decisions": state.get("guardrail_decisions", []) + [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])], } diff --git a/agent_framework_oci/templates/agent_template_backend_day_zero/docs/TRANSACTION_SEMANTIC_CONFIRMATION.md b/agent_framework_oci/templates/agent_template_backend_day_zero/docs/TRANSACTION_SEMANTIC_CONFIRMATION.md index a5508710..8cbc0077 100644 --- a/agent_framework_oci/templates/agent_template_backend_day_zero/docs/TRANSACTION_SEMANTIC_CONFIRMATION.md +++ b/agent_framework_oci/templates/agent_template_backend_day_zero/docs/TRANSACTION_SEMANTIC_CONFIRMATION.md @@ -4,7 +4,7 @@ Este template suporta confirmação transacional em duas camadas: primeiro um pa A configuração `router.transaction_confirmation.semantic_fallback` usa três classes: `SIM`, `NAO` e `CONTINUAR`. O prompt pode usar `{{ pending_prompt }}`, `{{ relevant_conversation_context }}`, `{{ user_input }}` e `{{ allowed_values }}`. O histórico injetado é apenas contexto de interpretação; não substitui validação de negócio ou evidência MCP. -Exemplo: após `Você confirma o cancelamento do serviço Tamboro Mensal?`, a frase `isso mesmo, pode confirmar` pode ser classificada como `SIM`. Já `mas qual é o valor?` deve ser `CONTINUAR`, portanto não executa a ação por confirmação. +Exemplo: após `Você confirma o cancelamento do serviço adicional selecionado?`, a frase `isso mesmo, pode confirmar` pode ser classificada como `SIM`. Já `mas qual é o valor?` deve ser `CONTINUAR`, portanto não executa a ação por confirmação. Entradas explícitas já suportadas continuam no caminho determinístico e não geram custo adicional de LLM. Em observabilidade, o fallback usa `transaction.confirmation.semantic_classifier` e o `route_decision.metadata` informa `transaction_confirmation_source: semantic`. diff --git a/agent_framework_oci/tests/__pycache__/conftest.cpython-313-pytest-9.0.2.pyc b/agent_framework_oci/tests/__pycache__/conftest.cpython-313-pytest-9.0.2.pyc index 1674055131f4d291f7139130c1a0968ea9dd0c60..9f26c74c3b6136e657932816f2b7d50d75858903 100644 GIT binary patch delta 62 zcmbQvwv3JYGcPX}0}uq9owkwNl}Xi0KR2&LKP9mwQNJj)JijPAJ}0pxwYWq-F+DY} QBtAJmuOzW}avIY{09vONUH||9 delta 47 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 literal 0 HcmV?d00001 diff --git a/agent_framework_oci/tests/__pycache__/test_contextual_reentry_transaction_parameters.cpython-313-pytest-9.0.2.pyc b/agent_framework_oci/tests/__pycache__/test_contextual_reentry_transaction_parameters.cpython-313-pytest-9.0.2.pyc index 9ba6e6be018a610bc053e354253cb274afe6e939..87d39ac029f8eedbc97d81e72445450c3827bef5 100644 GIT binary patch literal 43623 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 OY|zVp+X;--`TqsKM7gy9 delta 610 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

~~NccJni= z2f4Gv^hEI&AI4MNO(qzP{Tk9$9DoxJz2l+b+boNyhQ$Cj@BZbZH(d98ZErR0DK+)n zaQ&>W+_DJ_z#t(Bdqk+?!~F%^tK0BS=1CV~C|G}OEAKyLpED>S=KR2%3QraV z#weeJ8biE>UkIil3bXE#?RrN?A7b%~gywZiS%$&7of5{Efm+ zw0tIaLJvhpM~}-D_caK|pHbhu8NcmJ3wzj~Q1NF8Y$YHO=mOB)GZ|IMVH*;2tFTW- zkr({EG!jO+Mq#khI>RhDmzd{*i!N=j4Y}da!uSQ$*KPxF8sw|_THvL?lCQn!Yri@4 z?c}RN-%<-52k!X}A`YX_)>{hpzSY`(GyIL-ulK&{FLmr)>Ns?Jy3{&!W2_w7dh1{* zwC8*I+t2*orPnUKey}uns?>M7aBiy9H+3Ho7+rhrgEMdIxxb~q@MN^`%%_Wc76J{8%dNtu&9An9f3WcE+%o8kc*3zF2uH^V){i?-AuuE)+cae(N#*}@6Rh(0s;>0{aTz7%zZ<=z$fYEkg zf0Y5FQio{Nx(g;Gn|nwJFgxoXj0nCwyaS@!{!;EnPgaO?xzt z#Iy~h@=nv;8vF><$#&L0Or5fBP^e#mYk3!+&E4j%3)7A}EvO%jUhm=b`tcuidd&^a z&S-*5c0JK|Nj$ooRLl_a4V`=GotIEM*4gD`yXm# z(NiTLl8njiG3+!_x@1e6WU+x=UgO3Yiuffn-U~U{0>`Ne8D|!Q>MbMZM7Ds( z8jcIZL?_spqSfPSHiI|=#InPBQ|Soa6E%zQ{S1N(M)NL)*bzL93 z-*TwD`N?wYo1W&Yho zd2pwZy;qekaeN!iPN2L@ZFU!fJ8oSm2KU|`Dh7`fTu&E9o;F}H`1F#?{JV?t;1MHx zuPR;Q_%@oIK>51X;p{=U!tvkFb5IN(Uvim$cU{K|6v05*dsXQYVcS@C0>!*(?mZg1 zPz*k^EI8brvabQt1=aD>A_6i|0D$C*;HdGuNBLC(|KxfgyyHfq5PXK0;<-_}TwTf= zAY5K*w8YUXz^?cnRpjN%9-;NIU%d45mu?LdTXx)1iY>dZN6SH|#>HTF!F6gW*jEhp zAyMc*Nf7D&lk}~F>(raUQ%gWVA(eL<=@Q4Or29`+(iXl^0{8>hDSSoo^0t?T3$4Ay zV5HzWa2uTx2p(8+nSXa(2MWQ6k-b-yE)k-QWha30c8gFq3a?rdPRZv-{LumNji4Bf zxZbEavmKd@)!u)Fbn?;FUasgm`VA8@DZTs)D5$mg28tp8x`~utU^O7u_y~o! zi7UL#YvN^Xg;#FHFiGw8^?f`A_hv-xtxU5|vo1bk7cWF+&eQb{9;JsDv|oXZcR zy=|bl*JupuVj1pI`y~Qz68HfD9U%HaU&bcXj2Y0NhiqSg;aB`X zoyAaz{)*auivTHCh7aNoD8(I`e~Z*4`+t=GdjR;Nxf%K&sNgbzG=a6Uc-Y7OkxIE2 z;D?Ic8re-Ox!RDaKgnfkb2;J&%gU0s9%@hSaw;2NBWojMSiy%r{QEiE%Wr%VgmueZ ztNy%{{eSfI7r3?a(38lS@Qz0aNgr0gesx!&b1%u*?VCSPygpxSzp$}*{e4#KjD0w{ zdS5ZPzc6^B7(^`V!Lu^>;$;KL{uOS~&Ker%4R12mo*Qbcvp~ z$HjW+R67NS@5`HiYV(rVTojvE98PgUTy+YLmSvtq9KrhMa^<-!zE{&C237=s_xfwZ ut`z~`oz7Nq+)-!?zavm$dGER=c8Leg9*|nb=gn(-=r$j(IFRC=xPJ%uhRS*X literal 0 HcmV?d00001 diff --git a/agent_framework_oci/tests/__pycache__/test_transaction_explicit_abandonment_and_structural_candidate.cpython-313.pyc b/agent_framework_oci/tests/__pycache__/test_transaction_explicit_abandonment_and_structural_candidate.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4636b04092749a915fa40ea03daa40d7cb7638ab GIT binary patch literal 9600 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 literal 0 HcmV?d00001 diff --git a/agent_framework_oci/tests/__pycache__/test_transaction_parameter_descriptions.cpython-313-pytest-9.0.2.pyc b/agent_framework_oci/tests/__pycache__/test_transaction_parameter_descriptions.cpython-313-pytest-9.0.2.pyc index 95126f52d6f7cf6a564dc8bcc8fb3185af7b21cc..44c4613f2ec6008454a29600f0077fc4a43dd370 100644 GIT binary patch delta 52 zcmX>cxG#|VGcPX}0}uq9owkv?h)F6=KR2&LKP9mwQNJiPIX^EsGbc4ZEi*4MXY(8; Gbu|FU;}OyT delta 57 zcmdlNcsP*zGcPX}0}u#bnY59+h)F(KKR2&LKP9mwQ9m&~HLoN-IX|x?u{b_0v!Xn| LD0}mCCUrFcBUlsP diff --git a/agent_framework_oci/tests/__pycache__/test_transaction_parameter_llm_precedence.cpython-313-pytest-9.0.2.pyc b/agent_framework_oci/tests/__pycache__/test_transaction_parameter_llm_precedence.cpython-313-pytest-9.0.2.pyc index 0d7b692ed8f25b2a5d2aabe7ab2cd95ea3872861..d728688f7a303d95282720daf298ec673db9b0fa 100644 GIT binary patch delta 7921 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 delta 239 zcmex%nEB=hCce+Syj%=GAbe#~7FWVVJ_)8-OdB<3F|njGX^L&mV;19(Pu0)OE74C$ zEJ@T)Oi#@#iBHbYD@iPlPs^+*&o9c}ypnrX7o+B8muVjv8O=AF&DhG%WX3po-ZaI{ zd@KAJ8ErSmt<>U@abRR%C}(0|DCPj#*2eIaiIFprvB(?9DRQ2?WOEXu=oYM5_P* diff --git a/agent_framework_oci/tests/__pycache__/test_transaction_state_regression_matrix.cpython-313-pytest-9.0.2.pyc b/agent_framework_oci/tests/__pycache__/test_transaction_state_regression_matrix.cpython-313-pytest-9.0.2.pyc index d3d04a813ea4b3166b7a2e7b3483533d6375c731..61bf31ba1285688b761b09bbfd24c63693493f1b 100644 GIT binary patch delta 54 zcmcaOjrr;{X710tyj%=G5O8+dMs7DAsd)X|yb}GC#F9k)qSWO4yyVQB)cCZ_yu_T% J={&FY0RZig6P5q~ delta 50 zcmcaQjrrm diff --git a/agent_framework_oci/tests/__pycache__/test_transaction_state_router_interruption.cpython-313-pytest-9.0.2.pyc b/agent_framework_oci/tests/__pycache__/test_transaction_state_router_interruption.cpython-313-pytest-9.0.2.pyc index 66fcb9d3044eecd640d06327c80b75d83c71b6b9..5be251241c59e0ec9c84962dbd9583842db43d78 100644 GIT binary patch delta 65 zcmex;hq3W4Bll-sUM>b82sk@!Bex5)YL0$xUWtB6Vo9QYQEGX9QFeSzVo7RoiGE^w TYFb85WX^LBex5)e42i4UWtB6Vo9QYVtQ&`NqlmCUP)qcd|GBj Nd45s$<`QP!Yyex?6w&|y diff --git a/agent_framework_oci/tests/__pycache__/test_transactional_tool_flow.cpython-313-pytest-9.0.2.pyc b/agent_framework_oci/tests/__pycache__/test_transactional_tool_flow.cpython-313-pytest-9.0.2.pyc index 92ba4080fdda1cfd9460905817c1c9c12a9a751e..155772f3c2d067659972f729f1b7c99397026b9f 100644 GIT binary patch delta 6972 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 diff --git a/agent_framework_oci/tests/test_agent_template_backend_safety_consistency.py b/agent_framework_oci/tests/test_agent_template_backend_safety_consistency.py new file mode 100644 index 00000000..9c2788a3 --- /dev/null +++ b/agent_framework_oci/tests/test_agent_template_backend_safety_consistency.py @@ -0,0 +1,70 @@ +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] + + +def _template_dirs(): + return sorted( + p + for p in ROOT.rglob("*") + if p.is_dir() and p.name in {"agent_template_backend", "agent_template_backend_day_zero"} + ) + + +def test_all_agent_template_backend_copies_have_safe_agent_prompts(): + targets = _template_dirs() + assert targets, "Nenhum template backend encontrado" + for base in targets: + checks = { + "billing_agent.py": ( + "Nunca exponha identificadores técnicos", + "Não acrescente canais", + "Se uma tool retornar BLOCKED", + ), + "product_agent.py": ( + "Nunca exponha identificadores técnicos", + "can.cancel", + "Se uma tool retornar BLOCKED", + ), + "orders_agent.py": ( + "Nunca exponha identificadores técnicos", + "Não declare sucesso", + "Se uma tool retornar BLOCKED", + ), + "support_agent.py": ( + "Nunca exponha identificadores técnicos", + "Não declare sucesso", + "Se uma tool retornar BLOCKED", + ), + } + for filename, required in checks.items(): + text = (base / "app" / "agents" / filename).read_text(encoding="utf-8") + for marker in required: + assert marker in text, f"{base.relative_to(ROOT)}/{filename} sem {marker!r}" + + +def test_all_agent_template_backend_copies_isolate_interrupted_transaction_context(): + for base in _template_dirs(): + graph = (base / "app" / "workflows" / "agent_graph.py").read_text(encoding="utf-8") + assert 'route_metadata.get("transaction_interruption")' in graph + assert '== "intent_shift"' in graph + assert 'ctx["historical_transaction_ignored"] = True' in graph + assert 'ctx["expected_protocols"] = protocols' in graph + assert 'self._output_guardrail_context(state)' in graph + + +def test_full_templates_do_not_dump_raw_invoice_payload(): + checked = 0 + for base in _template_dirs(): + renderer = base / "app" / "presentation" / "tool_renderers.py" + if not renderer.exists(): + continue + checked += 1 + text = renderer.read_text(encoding="utf-8") + assert 'Fatura consultada: {result}' not in text + assert 'result.get("valor_total")' in text + assert 'result.get("vencimento")' in text + assert 'result.get("status")' in text + assert 'result.get("itens")' in text + assert checked > 0 diff --git a/agent_framework_oci/tests/test_contextual_reentry_transaction_parameters.py b/agent_framework_oci/tests/test_contextual_reentry_transaction_parameters.py index af32141f..bd2877f6 100644 --- a/agent_framework_oci/tests/test_contextual_reentry_transaction_parameters.py +++ b/agent_framework_oci/tests/test_contextual_reentry_transaction_parameters.py @@ -3,7 +3,7 @@ from __future__ import annotations import json import pytest -from agent_framework.runtime.transaction_parameters import extract_transaction_parameters +from agent_framework.runtime.transaction_parameters import extract_transaction_parameters, reconcile_transaction_parameters class _ContextAwareLLM: @@ -355,3 +355,57 @@ async def test_temporal_reconciler_single_scan_uses_all_known_fields_as_anchors_ assert result["values"]["data"] == "01/11/2025" assert result["provenance"]["subject"] == "history:1" + +class _AssistantEvidenceSegmentationLLM: + def __init__(self): + self.calls = 0 + + async def ainvoke(self, messages, **kwargs): + import json + self.calls += 1 + prompt = messages[-1]["content"] + evidence = json.loads(prompt.split("evidence_blocks: ", 1)[1].split("\n", 1)[0]) + anchored = [ + block for block in evidence + if block.get("role") == "assistant" + and any(match.get("field") == "valor" and float(match.get("value")) == 14.99 for match in block.get("anchor_matches", [])) + ] + assert len(anchored) == 1 + assert "Tamboro Mensal" in anchored[0]["text"] + assert "TIM Fashion" not in anchored[0]["text"] + assert "Neymar" not in anchored[0]["text"] + return { + "content": json.dumps({ + "fields": { + "subject": {"decision": "resolved", "value": "Tamboro Mensal", "source": "history:1"}, + "valor": {"decision": "preserve", "value": 14.99, "source": "state"}, + } + }, ensure_ascii=False) + } + + +@pytest.mark.asyncio +async def test_reconciler_segments_assistant_list_so_known_anchor_resolves_missing_field(): + llm = _AssistantEvidenceSegmentationLLM() + result = await reconcile_transaction_parameters( + llm, + text="é a de quatorze e noventa e nove", + tool_name="contestar_cobranca", + parameter_names=["subject", "valor"], + known_arguments={"valor": 14.99}, + parameter_schema={ + "subject": {"type": "string", "description": "Nome do item ou cobrança a contestar."}, + "valor": {"type": "number", "description": "Valor monetário do item a contestar."}, + }, + conversational_context=( + "priority_3_previous_assistant_tool_or_evidence_context:\n" + "history:1: assistant: Houve as cobranças: * Cobrança Tamboro Mensal no valor de R$ 14,99 no dia 01/11/25. " + "* Cobrança TIM Fashion Mensal no valor de R$ 10,00 no dia 01/11/25. " + "* Cobrança Neymar Jr no valor de R$ 12,00 no dia 01/11/25.\n" + "priority_4_previous_user_utterances:\n" + "history:2: user: tem uma cobrança aqui que eu não reconheço" + ), + ) + assert llm.calls == 1 + assert result["values"]["subject"] == "Tamboro Mensal" + assert float(result["values"]["valor"]) == 14.99 diff --git a/agent_framework_oci/tests/test_reconciliation_state_rules_8_16.py b/agent_framework_oci/tests/test_reconciliation_state_rules_8_16.py new file mode 100644 index 00000000..a1832f2d --- /dev/null +++ b/agent_framework_oci/tests/test_reconciliation_state_rules_8_16.py @@ -0,0 +1,191 @@ +import json +import pytest +from types import SimpleNamespace + +from agent_framework.runtime.agent_runtime import AgentRuntimeMixin + + +class _Router: + def __init__(self): + self.registry = SimpleNamespace( + tools={"contestar_cobranca": object(), "validar_contestacao": object()}, + get_tool=lambda name: SimpleNamespace( + description="Contesta uma cobrança após validação", + args_schema={ + "subject": { + "type": "string", + "description": "Referência a um item concreto e identificável da fatura", + "user_prompt": "Qual cobrança ou item você deseja contestar?", + }, + "valor": { + "type": "number", + "description": "Valor monetário explicitamente associado pelo cliente ao item", + "user_prompt": "Qual é o valor da cobrança que você deseja contestar?", + }, + }, + requires=["subject", "valor"], + ) if name == "contestar_cobranca" else None, + ) + + def resolve_execution_policy(self, tool_name, arguments=None): + if tool_name == "contestar_cobranca": + return { + "operation_type": "transactional", + "require_confirmation": True, + "requires": ["subject", "valor"], + "policy_source": "test", + "pre_validation": {"enabled": True, "tool": "validar_contestacao", "fail_open": False}, + } + return {"operation_type": "read_only", "require_confirmation": False, "policy_source": "test"} + + +class _LLM: + async def ainvoke(self, messages, **kwargs): + prompt = messages[-1]["content"] + generation = kwargs.get("generation_name") + if generation == "llm.transaction_parameter_current_only": + low = prompt.lower() + fields = json.loads(prompt.split("Formato: ", 1)[1]) + out = dict(fields) + if "quatorze e noventa e nove" in low: + if "valor" in out: + out["valor"] = 14.99 + elif "vinte e cinco e cinquenta" in low: + if "valor" in out: + out["valor"] = 25.50 + elif "dezenove e noventa e nove" in low: + if "valor" in out: + out["valor"] = 19.99 + return {"content": json.dumps(out, ensure_ascii=False)} + if generation == "llm.transaction_parameter_extraction": + # Deliberately leave subject unresolved so scenario 8 exercises the + # unique anchored structural candidate + authoritative validator path. + return {"content": json.dumps({"fields": { + "subject": {"decision": "unresolved", "value": None, "source": ""}, + "valor": {"decision": "preserve", "value": None, "source": "state"}, + }}, ensure_ascii=False)} + return {"content": "{}"} + + +class _Runtime(AgentRuntimeMixin): + def __init__(self): + self.tool_router = _Router() + self.llm = _LLM() + self.calls = [] + + async def _call_mcp_tool(self, tool_name, arguments, state): + self.calls.append((tool_name, dict(arguments))) + if tool_name != "validar_contestacao": + return {"ok": True, "tool_name": tool_name, "result": {"status": "OK"}} + + subject = str(arguments.get("subject") or "") + valor = arguments.get("valor") + # Canonicalize the raw anchored fragment from scenario 8. + if "Tamboro Mensal" in subject: + canonical = "Tamboro Mensal" + else: + return {"ok": True, "eligible": False, "status": "NEEDS_PARAMETER", "parameter": "subject"} + + if float(valor) not in {14.99, 19.99}: + return { + "ok": True, + "eligible": False, + "status": "NEEDS_PARAMETER", + "parameter": "valor", + "clear_fields": ["resolved_value"], + } + return { + "ok": True, + "eligible": True, + "transaction_decision": { + "resolved_arguments": {"subject": canonical, "valor": float(valor)} + }, + } + + +def _collecting_state(arguments, text, *, metadata=None, context=None): + return { + "user_text": text, + "sanitized_input": text, + "route": "contestacao_agent", + "intent": "state:COLLECTING_CONTESTACAO_PARAMETERS", + "transaction_status": "COLLECTING_PARAMETERS", + "active_transaction": { + "transaction_id": "tx", + "tool_name": "contestar_cobranca", + "arguments": dict(arguments), + "status": "COLLECTING_PARAMETERS", + "parameter_schema": { + "subject": {"type": "string", "description": "Referência a um item concreto e identificável da fatura"}, + "valor": {"type": "number", "description": "Valor monetário explicitamente associado pelo cliente ao item"}, + }, + "parameter_conversational_context": context or "", + }, + "selected_tool_call": {"tool_name": "contestar_cobranca", "arguments": dict(arguments)}, + "missing_parameters": [x for x in ["subject", "valor"] if arguments.get(x) in (None, "")], + "route_decision": {"metadata": metadata or {}}, + } + + +@pytest.mark.asyncio +async def test_scenario8_value_anchor_reconciles_subject_then_awaits_confirmation(): + runtime = _Runtime() + context = ( + "user: tem uma cobrança aqui que eu não reconheço\n" + "assistant: Analisando a fatura. * Cobrança Tamboro Mensal no valor de R$ 14.99 no dia 01/11/25. " + "* Cobrança TIM Fashion Mensal no valor de R$ 10.00 no dia 01/11/25." + ) + state = _collecting_state( + {}, + "é a de quatorze e noventa e nove", + metadata={ + "contextual_reentry": True, + "original_input": "é a de quatorze e noventa e nove", + "relevant_conversation_context": context, + }, + context=context, + ) + + result = await runtime.execute_tools_for_intent(state, tools=[]) + + assert state["transaction_status"] == "AWAITING_CONFIRMATION" + assert state["missing_parameters"] == [] + assert state["pending_tool_call"]["arguments"]["subject"] == "Tamboro Mensal" + assert state["pending_tool_call"]["arguments"]["valor"] == 14.99 + assert state["transaction_pre_validation"]["eligible"] is True + assert result[-1]["awaiting_confirmation"] is True + + +@pytest.mark.asyncio +async def test_scenario16_invalid_value_clears_only_value_and_preserves_subject(): + runtime = _Runtime() + state = _collecting_state({"subject": "Tamboro Mensal"}, "é a de vinte e cinco e cinquenta") + + result = await runtime.execute_tools_for_intent(state, tools=[]) + + assert state["transaction_status"] == "COLLECTING_PARAMETERS" + assert state["active_transaction"]["arguments"]["subject"] == "Tamboro Mensal" + assert "valor" not in state["active_transaction"]["arguments"] + assert state["missing_parameters"] == ["valor"] + assert result[-1]["collecting_parameters"] is True + + +@pytest.mark.asyncio +async def test_scenario16_current_turn_correction_updates_existing_value_then_awaits_confirmation(): + runtime = _Runtime() + state = _collecting_state( + {"subject": "Tamboro Mensal", "valor": 25.50}, + "desculpa é a de dezenove e noventa e nove", + ) + # The old value exists, so missing_parameters alone must NOT restrict which + # current-turn fields may be corrected. + state["missing_parameters"] = ["subject"] + + result = await runtime.execute_tools_for_intent(state, tools=[]) + + assert state["transaction_status"] == "AWAITING_CONFIRMATION" + assert state["missing_parameters"] == [] + assert state["pending_tool_call"]["arguments"]["subject"] == "Tamboro Mensal" + assert state["pending_tool_call"]["arguments"]["valor"] == 19.99 + assert state["transaction_pre_validation"]["eligible"] is True + assert result[-1]["awaiting_confirmation"] is True diff --git a/agent_framework_oci/tests/test_route_stickiness_transaction_shift.py b/agent_framework_oci/tests/test_route_stickiness_transaction_shift.py index 3ce6135e..099b1bc9 100644 --- a/agent_framework_oci/tests/test_route_stickiness_transaction_shift.py +++ b/agent_framework_oci/tests/test_route_stickiness_transaction_shift.py @@ -2,6 +2,20 @@ import pytest from types import SimpleNamespace from agent_framework.routing.enterprise_router import EnterpriseRouter + + +def test_single_word_keyword_requires_a_complete_token(): + from agent_framework.routing.models import IntentDefinition + + router = object.__new__(EnterpriseRouter) + router.intents = [ + IntentDefinition( + name="cancel", agent="orders_agent", priority=10, + keywords=["cancela"], examples=[], enabled=True, + ) + ] + assert router._route_by_keyword("cancela o pedido") is not None + assert router._route_by_keyword("sim, pode cancelar") is None from agent_framework.routing.models import RouteDecision diff --git a/agent_framework_oci/tests/test_transaction_explicit_abandonment_and_structural_candidate.py b/agent_framework_oci/tests/test_transaction_explicit_abandonment_and_structural_candidate.py new file mode 100644 index 00000000..9a2ea3cb --- /dev/null +++ b/agent_framework_oci/tests/test_transaction_explicit_abandonment_and_structural_candidate.py @@ -0,0 +1,298 @@ +import json +from types import SimpleNamespace + +import pytest + +from agent_framework.routing.enterprise_router import EnterpriseRouter +from agent_framework.runtime.agent_runtime import AgentRuntimeMixin +from agent_framework.runtime.transaction_parameters import reconcile_transaction_parameters + + +class _RouterLLM: + async def ainvoke(self, messages, **kwargs): + prompt = messages[-1]["content"] if isinstance(messages[-1], dict) else str(messages[-1]) + if kwargs.get("profile_name") == "transaction_parameter_extraction" or "pending_parameters:" in prompt: + return json.dumps({"subject": None, "valor": None}) + if "esquece" in prompt.lower(): + return json.dumps({ + "decision": "ABANDON", + "intent": "billing_invoice_explanation", + "agent": "billing_agent", + "confidence": 0.99, + "reason": "abandono explícito da transação atual", + }) + return json.dumps({ + "decision": "SHIFT", + "intent": "contas_vas_cancel", + "agent": "vas_agent", + "confidence": 0.99, + "reason": "classificação genérica motivada por cancelar", + }) + + +def _router(tmp_path): + routing = tmp_path / "routing.yaml" + routing.write_text( + """ +router: + fallback_agent: contestacao_agent + confidence_threshold: 0.70 +state_policies: + - state: COLLECTING_PARAMETERS + agent: contestacao_agent +intents: + - name: contas_contestation + agent: contestacao_agent + priority: 10 + keywords: [contestar] + - name: contas_vas_cancel + agent: vas_agent + priority: 20 + keywords: [cancelar] + - name: billing_invoice_explanation + agent: billing_agent + priority: 30 + keywords: [fatura] +""", + encoding="utf-8", + ) + return EnterpriseRouter( + SimpleNamespace( + ROUTING_CONFIG_PATH=str(routing), + ENABLE_LLM_ROUTER=True, + ENABLE_ROUTE_STICKINESS=False, + ), + llm=_RouterLLM(), + ) + + +def _active_state(message): + return { + "user_text": message, + "sanitized_input": message, + "next_state": "COLLECTING_PARAMETERS", + "transaction_status": "COLLECTING_PARAMETERS", + "missing_parameters": ["valor"], + "intent": "state:COLLECTING_PARAMETERS", + "active_agent": "contestacao_agent", + "active_transaction": { + "tool_name": "contestar_cobranca", + "arguments": {"subject": "Tamboro Mensal"}, + "status": "COLLECTING_PARAMETERS", + "started_from_intent": "contas_contestation", + "parameter_schema": {"valor": {"type": "number"}}, + }, + "history": [ + {"role": "user", "content": "não reconheço o Tamboro Mensal, quero tirar da fatura"}, + {"role": "assistant", "content": "Qual é o valor da cobrança que você deseja contestar?"}, + {"role": "user", "content": message}, + ], + } + + +@pytest.mark.asyncio +async def test_generic_shift_does_not_interrupt_active_transaction(tmp_path): + decision = await _router(tmp_path).route(_active_state("isso mesmo, pode cancelar")) + assert decision.intent == "state:COLLECTING_PARAMETERS" + assert not (decision.metadata or {}).get("transaction_interruption") + + +@pytest.mark.asyncio +async def test_explicit_abandonment_can_leave_active_transaction(tmp_path): + decision = await _router(tmp_path).route(_active_state("esquece isso, quero ver minha fatura")) + assert decision.intent == "billing_invoice_explanation" + assert decision.metadata["transaction_interruption"] == "explicit_abandonment" + + +class _PolicyRouter: + def __init__(self): + self.registry = SimpleNamespace( + tools={"contestar_cobranca": object()}, + get_tool=lambda name: SimpleNamespace( + selection_keywords=["contestar"], + args_schema={ + "subject": {"type": "string", "description": "item concreto da fatura"}, + "valor": {"type": "number", "description": "valor associado"}, + }, + requires=["subject", "valor"], + description="Contesta cobrança validada", + ), + ) + + def resolve_execution_policy(self, tool_name, arguments=None): + return { + "operation_type": "transactional", + "require_confirmation": True, + "requires": ["subject", "valor"], + "pre_validation": {"enabled": True, "tool": "validar_contestacao", "fail_open": False}, + } + + +class _UnresolvedReconcileLLM: + async def ainvoke(self, messages, **kwargs): + prompt = messages[-1]["content"] + if kwargs.get("profile_name") == "transaction_parameter_extraction": + if "FOCO PRINCIPAL DA RECONCILIAÇÃO" in prompt: + return {"content": json.dumps({ + "fields": { + "subject": {"decision": "unresolved", "value": None, "source": ""}, + "valor": {"decision": "preserve", "value": 14.99, "source": "state"}, + } + })} + return {"content": json.dumps({"subject": None, "valor": 14.99})} + return {"content": "{}"} + + +class _CandidateRuntime(AgentRuntimeMixin): + def __init__(self): + self.tool_router = _PolicyRouter() + self.llm = _UnresolvedReconcileLLM() + self.calls = [] + + async def _call_mcp_tool(self, tool_name, arguments, state): + self.calls.append((tool_name, dict(arguments))) + if tool_name == "validar_contestacao": + subject = str(arguments.get("subject") or "") + if "Tamboro Mensal" in subject and float(arguments.get("valor")) == 14.99: + return { + "ok": True, + "tool_name": tool_name, + "result": { + "eligible": True, + "status": "ELIGIBLE", + "transaction_decision": { + "resolved_arguments": {"subject": "Tamboro Mensal", "valor": 14.99} + }, + }, + } + return {"ok": True, "tool_name": tool_name, "result": {"eligible": False, "status": "NEEDS_PARAMETER", "parameter": "subject"}} + + +@pytest.mark.asyncio +async def test_unique_anchored_block_is_candidate_only_and_prevalidator_canonicalizes(): + runtime = _CandidateRuntime() + state = { + "user_text": "é a de quatorze e noventa e nove", + "sanitized_input": "é a de quatorze e noventa e nove", + "mcp_tools": ["contestar_cobranca"], + "route": "contestacao_agent", + "active_agent": "contestacao_agent", + "intent": "contas_contestation", + "route_decision": {"metadata": {}}, + "transaction_status": "COLLECTING_PARAMETERS", + "missing_parameters": ["subject"], + "active_transaction": { + "tool_name": "contestar_cobranca", + "arguments": {"valor": 14.99}, + "status": "COLLECTING_PARAMETERS", + "requires": ["subject", "valor"], + "parameter_schema": { + "subject": {"type": "string", "description": "item concreto da fatura"}, + "valor": {"type": "number", "description": "valor associado"}, + }, + "parameter_conversational_context": ( + "history:1: assistant: Houve: * Cobrança Tamboro Mensal no valor de R$ 14,99 no dia 01/11/25. " + "* Cobrança TIM Fashion Mensal no valor de R$ 10,00 no dia 01/11/25." + ), + }, + "selected_tool_call": {"tool_name": "contestar_cobranca", "arguments": {"valor": 14.99}}, + } + + result = await runtime.execute_tools_for_intent(state, tools=[]) + + assert state["transaction_status"] == "AWAITING_CONFIRMATION" + assert state["pending_tool_call"]["arguments"]["subject"] == "Tamboro Mensal" + assert state["pending_tool_call"]["arguments"]["valor"] == 14.99 + assert state["transaction_parameter_reconciliation"]["applied_structural_candidates"] == { + "subject": "unique_anchored_evidence_block" + } + validator_args = runtime.calls[0][1] + assert validator_args["subject"].startswith("Cobrança Tamboro Mensal") + assert result[-1]["awaiting_confirmation"] is True + +@pytest.mark.asyncio +async def test_current_turn_value_then_unique_anchor_reaches_confirmation_from_empty_transaction(): + """Regression for Contas scenario 08: current amount first, temporal subject second.""" + runtime = _CandidateRuntime() + state = { + "user_text": "é a de quatorze e noventa e nove", + "sanitized_input": "é a de quatorze e noventa e nove", + "mcp_tools": ["contestar_cobranca"], + "route": "contestacao_agent", + "active_agent": "contestacao_agent", + "intent": "contas_contestation", + "route_decision": {"metadata": {}}, + "transaction_status": "COLLECTING_PARAMETERS", + "missing_parameters": ["subject", "valor"], + "active_transaction": { + "tool_name": "contestar_cobranca", + "arguments": {}, + "status": "COLLECTING_PARAMETERS", + "requires": ["subject", "valor"], + "parameter_schema": { + "subject": {"type": "string", "description": "item concreto da fatura"}, + "valor": {"type": "number", "description": "valor monetário associado"}, + }, + "parameter_conversational_context": ( + "history:1: assistant: Houve: * Cobrança Tamboro Mensal no valor de R$ 14,99 no dia 01/11/25. " + "* Cobrança TIM Fashion Mensal no valor de R$ 10,00 no dia 01/11/25." + ), + }, + "selected_tool_call": {"tool_name": "contestar_cobranca", "arguments": {}}, + } + + result = await runtime.execute_tools_for_intent(state, tools=[]) + + assert state["transaction_parameter_collection"]["resolved_fields"] == ["valor"] + assert state["transaction_status"] == "AWAITING_CONFIRMATION" + assert state["pending_tool_call"]["arguments"]["subject"] == "Tamboro Mensal" + assert state["pending_tool_call"]["arguments"]["valor"] == 14.99 + assert result[-1]["awaiting_confirmation"] is True + + +class _ClearMissingSubjectReconcileLLM(_UnresolvedReconcileLLM): + async def ainvoke(self, messages, **kwargs): + prompt = messages[-1]["content"] + if kwargs.get("profile_name") == "transaction_parameter_extraction": + if "FOCO PRINCIPAL DA RECONCILIAÇÃO" in prompt: + return {"content": json.dumps({ + "fields": { + "subject": {"decision": "clear", "value": None, "source": "history:1"}, + "valor": {"decision": "preserve", "value": 14.99, "source": "state"}, + } + })} + return {"content": json.dumps({"subject": None, "valor": 14.99})} + return {"content": "{}"} + + +@pytest.mark.asyncio +async def test_initially_missing_subject_clear_still_allows_unique_anchored_candidate(): + """Scenario 08: clear on an already-missing field must not suppress candidate fallback.""" + rec = await reconcile_transaction_parameters( + _ClearMissingSubjectReconcileLLM(), + text="é a de quatorze e noventa e nove", + tool_name="contestar_cobranca", + parameter_names=["subject", "valor"], + known_arguments={"valor": 14.99}, + parameter_schema={ + "subject": {"type": "string", "description": "item concreto da fatura"}, + "valor": {"type": "number", "description": "valor monetário associado"}, + }, + tool_description="Contesta uma cobrança identificada por item e valor", + conversational_context=( + "priority_3_previous_assistant_tool_or_evidence_context:\n" + "history:1: assistant: Analisando a sua fatura atual. * Cobrança Tamboro Mensal no valor de R$ 14,99 no dia 01/11/25. " + "* Cobrança TIM Fashion Mensal no valor de R$ 10,00 no dia 01/11/25.\n" + "priority_4_previous_user_utterances:\n" + "history:2: user: tem uma cobrança aqui que eu não reconheço" + ), + ) + + assert rec["decisions"]["subject"] == "clear" + assert rec["clear_fields"] == ["subject"] + assert rec["candidates"] == { + "subject": "Cobrança Tamboro Mensal no valor de R$ 14,99 no dia 01/11/25." + } + assert rec["candidate_provenance"] == { + "subject": "unique_anchored_evidence_block" + } diff --git a/agent_framework_oci/tests/test_transaction_parameter_llm_precedence.py b/agent_framework_oci/tests/test_transaction_parameter_llm_precedence.py index c15adcb8..ef57780f 100644 --- a/agent_framework_oci/tests/test_transaction_parameter_llm_precedence.py +++ b/agent_framework_oci/tests/test_transaction_parameter_llm_precedence.py @@ -738,3 +738,34 @@ async def test_correction_of_missing_numeric_value_does_not_reopen_resolved_text ) assert runtime.pending_seen == ["valor"] assert out == {"valor": 19.99} + +@pytest.mark.asyncio +async def test_reconciler_exposes_schema_driven_field_evidence_views_for_missing_fields(): + class _LLM: + def __init__(self): + self.prompt = "" + async def ainvoke(self, messages, **kwargs): + self.prompt = messages[0]["content"] + return {"content": '{"fields":{"subject":{"decision":"resolved","value":"Produto A","source":"history:1"},"valor":{"decision":"preserve","value":14.99,"source":"state"}}}'} + + from agent_framework.runtime.transaction_parameters import reconcile_transaction_parameters + llm = _LLM() + result = await reconcile_transaction_parameters( + llm, + text="é a de quatorze e noventa e nove", + tool_name="acao_generica", + parameter_names=["subject", "valor"], + known_arguments={"valor": 14.99}, + parameter_schema={ + "subject": {"type": "string", "description": "referência concreta identificável"}, + "valor": {"type": "number", "description": "quantia associada"}, + }, + conversational_context=( + "priority_3_previous_assistant_tool_or_evidence_context:\n" + "history:1: assistant: * Produto A no atributo 14,99. * Produto B no atributo 10,00." + ), + ) + assert result["values"]["subject"] == "Produto A" + assert "field_evidence_views:" in llm.prompt + assert '"subject"' in llm.prompt + assert '"anchor_matches"' in llm.prompt diff --git a/agent_framework_oci/tests/test_transactional_tool_flow.py b/agent_framework_oci/tests/test_transactional_tool_flow.py index 71c03b0d..49b57403 100644 --- a/agent_framework_oci/tests/test_transactional_tool_flow.py +++ b/agent_framework_oci/tests/test_transactional_tool_flow.py @@ -1058,11 +1058,12 @@ class _CorrectionDuringCollectingLLM: prompt = messages[-1]["content"] self.prompts.append(prompt) if kwargs.get("profile_name") == "transaction_parameter_extraction": + # Current-only extraction now exposes the requested keys through the + # declarative schema/format rather than the legacy pending_parameters label. + if "current_user_message: desculpa, é a de quatorze e noventa e nove" in prompt: + return {"content": json.dumps({"subject": None, "valor": 14.99}, ensure_ascii=False)} pending = json.loads(prompt.split("pending_parameters: ", 1)[1].split("\n", 1)[0]) out = {name: None for name in pending} - # O router já resolveu subject a partir do contexto. O runtime ainda - # precisa permitir que a mensagem atual corrija um valor previamente - # coletado, mesmo que valor não esteja em missing_parameters. if "valor" in out: out["valor"] = 14.99 return {"content": json.dumps(out, ensure_ascii=False)} @@ -1374,3 +1375,61 @@ async def test_temporal_reconciler_is_fallback_after_traditional_candidate_fails assert state["pending_tool_call"]["arguments"]["subject"] == "Tamboro Mensal" assert state["transaction_status"] == "AWAITING_CONFIRMATION" assert result[-1]["awaiting_confirmation"] is True + +@pytest.mark.asyncio +async def test_collecting_parameters_materializes_router_cache_before_current_only_t6_regression(): + """T6 regression: router-resolved subject must enter active arguments. + + The router already consumed the pending subject in the same turn. A subsequent + current-only extraction over the literal message "sim" must not be required to + rediscover it, and temporal reconciliation must not leave it only as metadata. + """ + runtime = _ContestRuntime() + state = { + "user_text": "sim", + "sanitized_input": "sim", + "route": "contestacao_agent", + "active_agent": "contestacao_agent", + "intent": "state:COLLECTING_CONTESTACAO_PARAMETERS", + "route_decision": { + "route": "contestacao_agent", + "agent": "contestacao_agent", + "intent": "state:COLLECTING_CONTESTACAO_PARAMETERS", + "metadata": { + "transaction_turn_consumed": True, + "transaction_parameter_values": {"subject": "Tamboro Mensal"}, + "transaction_parameter_source": "llm", + "transaction_parameter_missing_before": ["subject"], + }, + }, + "transaction_status": "COLLECTING_PARAMETERS", + "missing_parameters": ["subject"], + "active_transaction": { + "transaction_id": "tx-t6-router-cache", + "tool_name": "contestar_cobranca", + "arguments": {"valor": 19.99}, + "status": "COLLECTING_PARAMETERS", + "started_from_intent": "contas_contestation", + "requires": ["subject", "valor"], + "parameter_schema": { + "subject": {"type": "string", "description": "item concreto da fatura"}, + "valor": {"type": "number", "description": "valor da cobrança"}, + }, + "tool_description": "Contesta cobrança validada", + }, + "selected_tool_call": { + "tool_name": "contestar_cobranca", + "arguments": {"valor": 19.99}, + }, + } + + results = await runtime.execute_tools_for_intent(state, tools=[]) + + assert results[-1]["transaction_status"] == "AWAITING_CONFIRMATION" + assert state["pending_tool_call"]["arguments"]["subject"] == "Tamboro Mensal" + assert state["pending_tool_call"]["arguments"]["valor"] == 19.99 + assert state["active_transaction"]["arguments"]["subject"] == "Tamboro Mensal" + assert state["missing_parameters"] == [] + collection = state["transaction_parameter_collection"] + assert collection["router_resolved_fields"] == ["subject"] + assert runtime.calls == [] diff --git a/agent_framework_oci/tests/unit/test_multi_intent_planner.py b/agent_framework_oci/tests/unit/test_multi_intent_planner.py new file mode 100644 index 00000000..0c720620 --- /dev/null +++ b/agent_framework_oci/tests/unit/test_multi_intent_planner.py @@ -0,0 +1,66 @@ +from agent_framework.routing.models import IntentDefinition +from agent_framework.routing.multi_intent import MultiIntentPlanner + + +def _planner(): + return MultiIntentPlanner([ + IntentDefinition(name="cancel", agent="orders", priority=10, keywords=["cancelar pedido"], mcp_tools=["cancelar_pedido"]), + IntentDefinition(name="invoice", agent="billing", priority=20, keywords=["segunda via"], mcp_tools=["consultar_fatura"]), + ], { + "enabled": True, + }, transactional_tools={"cancelar_pedido"}) + + +def test_builds_validated_plan_and_keeps_primary_first(): + plan = _planner().plan("quero cancelar pedido e preciso da segunda via") + assert plan is not None + assert [item.intent for item in plan.operations] == ["cancel", "invoice"] + assert plan.operations[1].disposition == "execute" + assert plan.operations[1].tools == ["consultar_fatura"] + + +def test_confirmation_with_secondary_does_not_lose_confirmation(): + plan = _planner().plan_confirmation_secondary( + "sim e preciso da segunda via", primary_intent="cancel", primary_agent="orders" + ) + assert plan is not None + assert [item.intent for item in plan.operations] == ["cancel", "invoice"] + assert MultiIntentPlanner.public_messages(plan) == [] + + +def test_transactional_intent_is_primary_even_when_mentioned_second(): + plan = _planner().plan("preciso da segunda via e quero cancelar pedido") + assert plan is not None + assert [item.intent for item in plan.operations] == ["cancel", "invoice"] + + +def test_does_not_plan_single_intent_or_invent_unknown_intent(): + assert _planner().plan("quero cancelar pedido") is None + assert _planner().plan("quero cancelar pedido e saber o clima") is None + + +def test_known_primary_with_actionable_unknown_creates_off_context_topic(): + plan = _planner().plan("quero cancelar pedido e alterar meu CNPJ") + assert plan is not None + assert plan.operations[1].intent == "off_context" + assert plan.operations[1].disposition == "unsupported" + + +def test_confirmation_with_unknown_secondary_keeps_confirmation(): + plan = _planner().plan_confirmation_secondary( + "sim e altere meu CNPJ", primary_intent="cancel", primary_agent="orders" + ) + assert plan is not None + assert plan.operations[0].intent == "cancel" + assert plan.operations[1].intent == "off_context" + + +def test_single_word_keyword_does_not_match_a_longer_word_prefix(): + planner = MultiIntentPlanner([ + IntentDefinition(name="cancel", agent="orders", priority=10, keywords=["cancela"]), + IntentDefinition(name="invoice", agent="billing", priority=20, keywords=["segunda via"]), + ]) + plan = planner.plan("sim, pode cancelar e preciso da segunda via") + assert plan is not None + assert all(item.intent != "cancel" for item in plan.operations) + assert [item.intent for item in plan.operations] == ["invoice", "off_context"] diff --git a/agent_framework_oci/tests/unit/test_pending_topics_isolation.py b/agent_framework_oci/tests/unit/test_pending_topics_isolation.py new file mode 100644 index 00000000..e614cd7a --- /dev/null +++ b/agent_framework_oci/tests/unit/test_pending_topics_isolation.py @@ -0,0 +1,53 @@ +import pytest + +from agent_framework.routing.pending_topics import drain_pending_topics + + +@pytest.mark.asyncio +async def test_secondary_topic_does_not_inherit_primary_transaction_state(): + captured = {} + + async def billing_agent(state): + captured.update(state) + return {"answer": "Aqui está a sua segunda via."} + + state = { + "answer": "Você confirma o cancelamento do Tamboro?", + "sanitized_input": "quero cancelar Tamboro e quero minha segunda via", + "confirmation_required": True, + "active_transaction": {"tool_name": "cancelar_vas_avulso"}, + "transaction_status": "AWAITING_CONFIRMATION", + "selected_tool_call": {"tool_name": "cancelar_vas_avulso"}, + "pending_tool_call": {"tool_name": "cancelar_vas_avulso"}, + "pending_domain_workflow": {"workflow_name": "cancelamento_vas_avulso"}, + "missing_parameters": ["subject"], + "next_state": "WAITING_CONTESTACAO_CONFIRMATION", + "mcp_results": [{"tool_name": "consultar_vas"}], + "pending_topics": [{ + "operation_id": "op-2", + "intent": "contas_invoice_query", + "agent": "faturas_agent", + "domain": "telecom_contas", + "tools": ["consultar_faturas"], + "source_text": "quero minha segunda via", + "disposition": "execute", + "status": "pending", + }], + } + + result = await drain_pending_topics( + state, state["answer"], {"faturas_agent": billing_agent} + ) + + assert captured["active_transaction"] is None + assert captured["selected_tool_call"] == {} + assert captured["pending_tool_call"] == {} + assert captured["pending_domain_workflow"] is None + assert captured["next_state"] is None + assert captured["sanitized_input"] == "quero minha segunda via" + assert captured["mcp_results"] == [] + assert captured["mcp_tools"] == ["consultar_faturas"] + assert captured["available_mcp_tools"] == ["consultar_faturas"] + assert captured["domain"] == "telecom_contas" + assert result.answer.startswith("Aqui está a sua segunda via.") + assert result.pending_topics == [] diff --git a/agent_framework_oci/tests/unit/test_readme_feature_catalog.py b/agent_framework_oci/tests/unit/test_readme_feature_catalog.py new file mode 100644 index 00000000..6a56b5d6 --- /dev/null +++ b/agent_framework_oci/tests/unit/test_readme_feature_catalog.py @@ -0,0 +1,39 @@ +from pathlib import Path +import re +from urllib.parse import unquote + + +ROOT = Path(__file__).resolve().parents[2] + + +def _local_links(path: Path) -> list[str]: + text = path.read_text(encoding="utf-8") + return [ + target for target in re.findall(r"\[[^]]*\]\(([^)]+)\)", text) + if not target.startswith(("http://", "https://", "#", "mailto:")) + ] + + +def test_main_readmes_have_no_broken_local_links(): + for name in ("README.md", "README_en.md"): + readme = ROOT / name + for target in _local_links(readme): + relative = unquote(target.split("#", 1)[0]) + assert not relative or (readme.parent / relative).exists(), (name, target) + + +def test_every_feature_guide_is_listed_in_corresponding_main_readme(): + pairs = (("pt-BR", "README.md"), ("en", "README_en.md")) + for language, readme_name in pairs: + readme = (ROOT / readme_name).read_text(encoding="utf-8") + guides = sorted((ROOT / "docs" / "features" / language).glob("[0-9][0-9]_*.md")) + assert guides + for guide in guides: + expected = f"docs/features/{language}/{guide.name}" + assert expected in readme, (readme_name, expected) + + +def test_portuguese_and_english_feature_catalogs_are_symmetric(): + pt = {path.name for path in (ROOT / "docs" / "features" / "pt-BR").glob("*.md")} + en = {path.name for path in (ROOT / "docs" / "features" / "en").glob("*.md")} + assert pt == en diff --git a/agent_framework_oci/tests/unit/test_tuning_pending_topics_sync.py b/agent_framework_oci/tests/unit/test_tuning_pending_topics_sync.py new file mode 100644 index 00000000..23e29d46 --- /dev/null +++ b/agent_framework_oci/tests/unit/test_tuning_pending_topics_sync.py @@ -0,0 +1,38 @@ +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[2] + + +def test_all_tuning_backend_graphs_support_pending_topics(): + graphs = sorted((ROOT / "Tuning-Performance").glob("**/app/workflows/agent_graph.py")) + assert graphs + for path in graphs: + source = path.read_text(encoding="utf-8") + assert "from agent_framework.routing.pending_topics import drain_pending_topics" in source, path + assert '"pending_topics": drained.pending_topics' in source, path + assert '"handled_topics": drained.handled_topics' in source, path + + +def test_all_tuning_backend_states_expose_pending_topics(): + states = sorted((ROOT / "Tuning-Performance").glob("**/app/state.py")) + assert states + for path in states: + source = path.read_text(encoding="utf-8") + assert "pending_topics: list[dict[str, Any]]" in source, path + assert "handled_topics: list[dict[str, Any]]" in source, path + + +def test_canonical_templates_delegate_drain_to_framework_helper(): + for template in ("agent_template_backend", "agent_template_backend_day_zero"): + graph = ROOT / "templates" / template / "app" / "workflows" / "agent_graph.py" + state = ROOT / "templates" / template / "app" / "state.py" + graph_source = graph.read_text(encoding="utf-8") + state_source = state.read_text(encoding="utf-8") + assert "from agent_framework.routing.pending_topics import drain_pending_topics" in graph_source + assert "drained = await drain_pending_topics(" in graph_source + assert '"pending_topics": drained.pending_topics' in graph_source + assert '"handled_topics": drained.handled_topics' in graph_source + assert "MultiIntentPlanner.public_messages" not in graph_source + assert "pending_topics: list[dict[str, Any]]" in state_source + assert "handled_topics: list[dict[str, Any]]" in state_source diff --git a/app/__pycache__/__init__.cpython-313.pyc b/app/__pycache__/__init__.cpython-313.pyc index ea9389d42f8f08d2184f0c19b23339b7e1f0bc59..98020b3d7212a8c846ec0d4aa877526b0a67274d 100644 GIT binary patch delta 30 kcmbQnID?VD27&+p diff --git a/app/__pycache__/main.cpython-313.pyc b/app/__pycache__/main.cpython-313.pyc index e291349c9548eb2918361ffbfba0a2d3f7940159..dcae19eecedddd5bd1911022880aabc460a6e036 100644 GIT binary patch delta 36 rcmeyilibwHZ$GcPX}0}$}tTa?AdIgziJNsVP=^IE3KV$33pT9XBtB}KK>f$}jdF|5Hl zU=}Bkr90V?Q8-W!E~5{ZF@UoS;VdJ#I%7D?1kB+XGXvQ-`2jN4!7Pv-J2=Z8%mSJ30Mf_k2qG1o;Ch_lEEf)V!3;y!7~z{DREn;#+(fiFqkGsVNZtW-Io+Ox)f;U7vxN;g-o{CN5R^cq9G9 zf&%^GlEji!y@JXjHlP%kU;#3UH*R*}I>4wZA~8c`M)CsV85uL>E{bSeX3@BTDKwdx z=K^E+geME}yGcPX}0}zxj%*fixF_EvB=@awD=Cw@xEHSLXTIxWV7)~Hdd$J>=aG(yD z50cS^%jm&b`f!#3T%94DWdvq%0reWg#Y`p(GD~usg83j@%s~9fADG!#L3UVx?3^sZ zBA^W7S;JM?z*)9n7RXFHkXlB25UJ<@76s{XgtMGLtYGIN?sOMT*Uf8L4l`|@z`mP_ z+Y>1K8HgEf8BGr3Qk`7OCBxXgc^20J#>ovl=NUsM2k`1TgA^5of`~AXN)DUc{FKt1 pRJ)=`AeRw{izR@>2WCb_#`_G4U)UTO8Pz99E>Qi<0Ahi40sxHTN$UUr diff --git a/app/agents/__pycache__/contas_prompting.cpython-313.pyc b/app/agents/__pycache__/contas_prompting.cpython-313.pyc index 16fc0c5a454a6a299849b9fa36f3c9a1290080ab..9d465b5f74372d54b7a6d2bb2a1f6da3de149a12 100644 GIT binary patch delta 34 pcmZ3&v5bTJGcPX}0}$}vTeOjTG83cQt32FcU delta 31 lcmZ3+v4n&BGcPX}0}zxj%-G01nTgS5@_Z)M&BvG$7y*AL2vh(7 diff --git a/app/agents/__pycache__/contestacao_agent.cpython-313.pyc b/app/agents/__pycache__/contestacao_agent.cpython-313.pyc index 2bf53faa513f5994d23d1b8bd0d0161c024b80c2..4029ba6af772b3e7e86fa028507ae0a735391969 100644 GIT binary patch delta 34 pcmdn0xm}a{GcPX}0}$}vTeOk8n~BkV@^mIurg)>xdzgL-0syTz3Sa;L delta 31 lcmdn4xmA<&Em{5>HwD42-yGt diff --git a/app/agents/__pycache__/prompting.cpython-313.pyc b/app/agents/__pycache__/prompting.cpython-313.pyc index 9906660ef1f543c751485a8ecaaa5db63395fd94..72d4f1b832aba1e78c8d04a70506ea22dfb418bd 100644 GIT binary patch delta 34 ocmey(@t1@9GcPX}0}$}vTeOi|fSJ*LvJA5-Q@qh;M`kNV0H#FCe{{R30 diff --git a/app/agents/__pycache__/suporte_contas_agent.cpython-313.pyc b/app/agents/__pycache__/suporte_contas_agent.cpython-313.pyc index 08b6d61ba1502a2081e9516075e17ba9a639b777..fd7af9968d09a6a501e16331db7fdff2e301ac91 100644 GIT binary patch delta 34 pcmaEE@X~<$GcPX}0}$}vTeOjTHxr}R delta 31 lcmaE9@Z5m=GcPX}0}zxj%-G1in~Bk5@^L2B&F`41MFE^e3Dp1q diff --git a/app/agents/__pycache__/vas_agent.cpython-313.pyc b/app/agents/__pycache__/vas_agent.cpython-313.pyc index b2cf3cfc8b502497cb911cb7c118bcebf8c5e3ff..57aa742b627a74716925b7fbbca95084bbf60d5d 100644 GIT binary patch delta 34 pcmcbhb4iE$GcPX}0}$}vTeOjT855)Z(Zn@20E&bOBme*a delta 27 hcmaFG_=1u9GcPX}0}zxj%$Ues&1f^RU3KC*8vtxw2oC@N diff --git a/app/domain/contas/__pycache__/__init__.cpython-313.pyc b/app/domain/contas/__pycache__/__init__.cpython-313.pyc index 9ebe6d4b77a7c09489495336a7f57e51af6fbc76..4f5aedadfc753783c5365aa34b543f672358c7d4 100644 GIT binary patch delta 30 lcmX@Xbc%`lGcPX}0}$}vTQrgTB%|xZtEx=#MiYPe0RW1b3F-g< delta 27 hcmX@bbb^WdGcPX}0}zxj%$Uf1lF@nMRn>`q`~Yh*2-E-o diff --git a/app/domain/contas/__pycache__/client.cpython-313.pyc b/app/domain/contas/__pycache__/client.cpython-313.pyc index 3549f927c41f26a1331701a5de233f7ca3c986f0..df29afe0e5d4da20139013e9e96d4f85a1a318d8 100644 GIT binary patch delta 36 rcmZ2Gm3jSCX710tyj%=Gz<+PiM((3bjLwrUGO04f8*To^wEZ{$(YXvv delta 33 ocmZ2Km3i${X710tyj%=GP{J@{Bll4zM#sq)nN&CbVA_5h0ITW?HUIzs diff --git a/app/domain/contas/__pycache__/contestation_rules.cpython-313.pyc b/app/domain/contas/__pycache__/contestation_rules.cpython-313.pyc index 3234a4e35610ef52bd16e443c895dee82f02e32b..3d573dc7d05c9cb8bb474d038c8efb88daa4b4d1 100644 GIT binary patch delta 34 ocmeCk>dNB&%*)Hg00jK^7H#BaWnuK6EXbnD6mPWIjOCI60HPHL`~Uy| delta 31 lcmeCm>d4~$%*)Hg00borGd6OwvM~Bi7GzP~Y{7EL004r92j&0( diff --git a/app/domain/contas/__pycache__/contestation_validation.cpython-313.pyc b/app/domain/contas/__pycache__/contestation_validation.cpython-313.pyc index 8e6f974dc99816f7307d71570a4eb9ed4a5ce551..5ee1be038998605ac8c990265d85fe45ffadf0b8 100644 GIT binary patch delta 36 rcmdmajB)odM()qNyj%=Gz<+PiMs6o&#*oQ=%&JWBMw^S7kHrH3(ijT# delta 33 ncmdmejB)2NM()qNyj%=GP{J@{BexSXW6)$jX4TE5%*WyZt-uN1 diff --git a/app/domain/contas/__pycache__/conversation_policy.cpython-313.pyc b/app/domain/contas/__pycache__/conversation_policy.cpython-313.pyc index 3f0630560617afc8f725a81bdf7eaafbb7d746e9..ac2f45076fbf20d4a853a2f68b04b8b3c8697579 100644 GIT binary patch delta 36 qcmZo{Wo&L`GBo%0Fc diff --git a/app/domain/contas/__pycache__/invoice_models.cpython-313.pyc b/app/domain/contas/__pycache__/invoice_models.cpython-313.pyc index ba453a02b15c209a2e80f125e40be8ae5a0bdb46..156e46df5b91cc730c593ee3e47839ebf6bbeeb8 100644 GIT binary patch delta 34 ocmeC?>*eGA%*)Hg00jK^7H#CNVrKN7+{UcR6mPV7HFE+p0GOo-0{{R3 delta 31 lcmeC>>*nMB%*)Hg00borGd6NpF*ABjZev#6yp}nE831^I2kig= diff --git a/app/domain/contas/__pycache__/invoice_resolver.cpython-313.pyc b/app/domain/contas/__pycache__/invoice_resolver.cpython-313.pyc index 3b17448a4944d72bf329cfe9e0bf17a5bac1b69a..b6498bd4e8aa0469ddf855247937a58662e2476d 100644 GIT binary patch delta 36 rcmeycmHE?FX710tyj%=Gz<+PiM(!JejJ}hf2&yv08*SzkdVT@`?Eeh3 delta 33 ocmeygmHER~X710tyj%=GP{J@{BlitKM(@c_1XVY43q3yp0K@bPo&W#< diff --git a/app/domain/contas/__pycache__/item_matcher.cpython-313.pyc b/app/domain/contas/__pycache__/item_matcher.cpython-313.pyc index 9c842b8f13563cc6bfc627ee009dd31caef75f35..2d7e3a6515b2015adbd2e33df586588ebd66ce53 100644 GIT binary patch delta 34 pcmdnvyU&;VGcPX}0}$}vTeOk8hlSB|@(dPLrg)>xds+S~0sy#^3fBMt delta 31 lcmdnzyT_ONGcPX}0}zxj%-G1?!@}r3c?OH>=KU=H6#37r4{ diff --git a/app/domain/contas/__pycache__/pro_rata_rules.cpython-313.pyc b/app/domain/contas/__pycache__/pro_rata_rules.cpython-313.pyc index 1dc212c6c6929b1a2c99c257c2eb315a9c252701..fe13c7e140e61429893e32c46d0a4bcd34bcdb47 100644 GIT binary patch delta 36 qcmeC|V(ji>w&DU9yB>|D*2|xe< diff --git a/app/domain/contas/__pycache__/rct_policy.cpython-313.pyc b/app/domain/contas/__pycache__/rct_policy.cpython-313.pyc index d34ef17886fbeacdf3a96af533659b908613020c..8e2f833cb1056f033f01da9169e3d8a51a15cf87 100644 GIT binary patch delta 34 ocmdmKxZ9BXGcPX}0}$}vTeOiopOevjay6$aQ@qjU1)P%n0IS{!R{#J2 delta 31 lcmdmOxYLmPGcPX}0}zxj%-G1C&&lXIxtddT^CC`3egKL62tfb< diff --git a/app/domain/contas/__pycache__/service.cpython-313.pyc b/app/domain/contas/__pycache__/service.cpython-313.pyc index 513ddd33d11dc91ccf894cb253f9f055203e55d6..e1348d61fa23fb22589494aae9018dc93cd40242 100644 GIT binary patch delta 36 qcmZoz$=I}#k^3_*FBbz4@ZDRqk^3_If2i^bx diff --git a/app/domain/contas/__pycache__/vas_cancellation_message.cpython-313.pyc b/app/domain/contas/__pycache__/vas_cancellation_message.cpython-313.pyc index 16dbefe98807133238138cffcb92f6f9ea01fccd..251973b1a63ceafab4f104440e4905b255ff9773 100644 GIT binary patch delta 34 ocmX?Hf3%+aGcPX}0}$}vTeOk8fs-+Gau25}Q@qjUjhsSe0L0J=XaE2J delta 31 lcmX?Ff4H9eGcPX}0}zxj%-G1?z{waqxrbAA^JY#VGXS7Z2~q$6 diff --git a/app/domain/contas/__pycache__/vas_variation.cpython-313.pyc b/app/domain/contas/__pycache__/vas_variation.cpython-313.pyc index 589b865356cbc8117d93fcb6bae40fccd5a39ba4..610d4fdf1e1a2af5d5448db4bfed00c1a66a4e44 100644 GIT binary patch delta 36 qcmbPml4;UOChpI?yj%=Gz<+PiMs8h6Mz6`1lB!JcMw{a#jk*EGbP78F delta 33 ncmbPql4-(8ChpI?yj%=GP{J@{Be$+3qsL@RN!85>l1AMCqY?=v diff --git a/app/domain/contas/__pycache__/workflow_actions.cpython-313.pyc b/app/domain/contas/__pycache__/workflow_actions.cpython-313.pyc index f2c3ff7b7c814bb2c87ff11aec38419e03cd2dbd..be3c29b0d2abd5ac76d2e69654859b200c3adfaa 100644 GIT binary patch delta 44 zcmX>+mGkISPVUdVyj%=Gz<+PiM(#d#M&HS^*j1V0jhgqfx9?|X+`gZkiNzTJH+l_T delta 41 wcmX>)mGkgaPVUdVyj%=GP{J@{BX=J=qxa-l?5fQN+1n4YGj2b~&cxyj0115zOaK4? diff --git a/app/domain/contas/integrations/__pycache__/secure_pdf_crypto.cpython-313.pyc b/app/domain/contas/integrations/__pycache__/secure_pdf_crypto.cpython-313.pyc index 17a8d7006096ded52d7ec9a074b336613b4842df..e0a2a432932fab1c11b1e6c259b92dc3519f04ac 100644 GIT binary patch delta 34 pcmZ1_yhfP&GcPX}0}$}vTeOk;5;J4;x`b-s!0G~Dp%>V!Z delta 31 lcmZ3S9jW+9Z^n?Ha*2xOj delta 33 ncmdnHlX3S>M()qNyj%=GP{J@{BlkacM!(5i9IBfQIeJ0>u{;UE diff --git a/app/domain/contas/parsers/__pycache__/bill_processor.cpython-313.pyc b/app/domain/contas/parsers/__pycache__/bill_processor.cpython-313.pyc index 3434ae1d1edc0daf38eba25d1825a1d12cc5a4a8..918f6d887a9f5ee9666a61704a11b23a94996fe8 100644 GIT binary patch delta 36 rcmbO?n`yypChpI?yj%=Gz<+PiM(z(BjKP!taHulH8*NtPtZe}R(D(~q delta 33 ocmZ25n`z!`ChpI?yj%=GP{J@{Bliam#=yycI8-+)bJn&10IClPNdN!< diff --git a/app/extensions/__pycache__/__init__.cpython-313.pyc b/app/extensions/__pycache__/__init__.cpython-313.pyc index 31a04df9653436e9d32697103217e49d84857ec6..d66f837c18f0dbcb8c943080b0d260cf027406d3 100644 GIT binary patch delta 30 lcmZ3-w1J8HGcPX}0}$}vTQre-H>1FsU-d8*RSH)Y%UJ-`ETq delta 33 ocmaE|gz3=|ChpI?yj%=GaKvxMM(!0%jBb;+FsW|7&D7Zs0K?=9q5uE@ diff --git a/app/extensions/__pycache__/tim_judges.cpython-313.pyc b/app/extensions/__pycache__/tim_judges.cpython-313.pyc index c13caca339ea6d7e4f147364e0815b913a432d93..5d14efc90aba92f898468e906d8ad30eb46917bf 100644 GIT binary patch delta 34 ocmaE=`&^g%GcPX}0}$}vTeOjzkD1YBvJ|r_Q@qh;d*)@r0IJ3b00000 delta 31 lcmaE^`&5_vGcPX}0}zxj%-G1y$IR$7S&CV8vm^5|VE~F~2kHO- diff --git a/app/extensions/tim_prompts/__pycache__/__init__.cpython-313.pyc b/app/extensions/tim_prompts/__pycache__/__init__.cpython-313.pyc index d60c9769e1bd560f8f8fa349378ce91819ba3cbe..60f410c6397cb45580b146ddc9bca44dc47138ad 100644 GIT binary patch delta 30 kcmZ3>xSo;wGcPX}0}$}vTQrf|n9*;dy(&|@(Zm!_0C%(p^Z)<= delta 27 hcmZ3_xR#OoGcPX}0}zxj%$Ue+%;+=GUUg!cCjeYt2HpSw diff --git a/app/extensions/tim_prompts/__pycache__/aluc.cpython-313.pyc b/app/extensions/tim_prompts/__pycache__/aluc.cpython-313.pyc index 51b29b943ff3a82db455f4de93559f0b3cc31814..ab9e42ab62c91367d4ab57952a2f0be2c9e814fc 100644 GIT binary patch delta 33 ncmexj_|1^}GcPX}0}$}vTeOjzSAx-NvZRD6Q@qh+JBe)os7DD! delta 30 kcmexn_{EU>GcPX}0}zxj%-G1yE5Yb7SyDoEvV+7n0Ehz!F8}}l diff --git a/app/extensions/tim_prompts/__pycache__/ausencia_oferta_proativa.cpython-313.pyc b/app/extensions/tim_prompts/__pycache__/ausencia_oferta_proativa.cpython-313.pyc index b40ec02f91300db88503b1be1ddf5f6e027855c3..d9ea46302ae07f050384f08eb99ffdc53b414698 100644 GIT binary patch delta 33 ncmewt+z`V3nU|M~0SNf-E!xO^PlGXT@+%Ehrg)>t{F?6px;P6D delta 30 kcmZn&`4`OnnU|M~0SHPMW^ClXr@dTx0JHE38~^|S delta 31 lcmaE9_1ud4GcPX}0}zxj%-G1SDZ}VL*;Gb#bBqixBLI=v2nGNE diff --git a/app/extensions/tim_prompts/__pycache__/out_of_scope.cpython-313.pyc b/app/extensions/tim_prompts/__pycache__/out_of_scope.cpython-313.pyc index b8c4c566f492f6a0be5d02de392566575359b70a..d33670432820a84a66d170d300f7c76671dacf0d 100644 GIT binary patch delta 36 qcmey}!uYp^k^3_*FBbz4@ZVdsk=w|LZnU|M~0SNf-E!xO^n~TwR@^da#rg)>xJlsr-0He(a_y7O^ delta 31 lcmbOr(I>(EnU|M~0SHPMW^ClX&Bf?F`8k*BWC77dn!*WR delta 30 kcmX@fah!wuGcPX}0}zxj%-G1C#?0t3xrkYH@=WFp0D9^ONdN!< diff --git a/app/observability/__pycache__/__init__.cpython-313.pyc b/app/observability/__pycache__/__init__.cpython-313.pyc index 4195e82a5217dd5228b7c8dd9f71e5e46310eea5..123ac28ac41180f9206edbad3950b9a40427cba3 100644 GIT binary patch delta 30 kcmZ3&xQvneGcPX}0}$}vTQrf|n9+5jy(&|@(Zm!70Ch(P&;S4c delta 27 hcmZ3+xP+1WGcPX}0}zxj%$Ue+%;-GPUUg!c0{~h=2D$(M diff --git a/app/observability/__pycache__/telemetry_observer.cpython-313.pyc b/app/observability/__pycache__/telemetry_observer.cpython-313.pyc index af53bdbdfb8bb14e6b2f4201992f58f7ab9e5488..9208893b019069dee94092811e2dec573a3ef570 100644 GIT binary patch delta 34 pcmeyZ_E(MjGcPX}0}$}vTeOjTB_pH%WLG&370V3IYI~Qwi4q diff --git a/app/presentation/__pycache__/__init__.cpython-313.pyc b/app/presentation/__pycache__/__init__.cpython-313.pyc index f9fe47780828d96cbffa4dd7e9636c991800250c..7ab86a5a9a8cfaf889f7a84e712274a1d8240b08 100644 GIT binary patch delta 30 kcmZo*YGUI4%*)Hg00jK^7ER=CWptT1QI#p)XyP_60CZyri2wiq delta 27 hcmZo-YGC62%*)Hg00borGbVDkGCED1s5)_n7XVmC2Xg=b diff --git a/app/presentation/__pycache__/tool_renderers.cpython-313.pyc b/app/presentation/__pycache__/tool_renderers.cpython-313.pyc index 8e103a45c4e739043c93cefdca422c26d850b57b..047c1d1aad49505ab1fda93cf9f9682eea8cd051 100644 GIT binary patch delta 34 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 F{{u$Ku*U!Z delta 9737 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 diff --git a/app/workflows/agent_graph.py b/app/workflows/agent_graph.py index 63a9fc31..580060bf 100644 --- a/app/workflows/agent_graph.py +++ b/app/workflows/agent_graph.py @@ -7,6 +7,7 @@ from agent_framework.guardrails.rail_action import RailAction from agent_framework.guardrails.rail_result import RailResult from agent_framework.judges.judge import JudgePipeline from agent_framework.routing.enterprise_router import EnterpriseRouter +from agent_framework.routing.pending_topics import drain_pending_topics from app.domain.contas.conversation_policy import evaluate as evaluate_contas_conversation_policy from agent_framework.supervisor.supervisor import Supervisor from agent_framework.observability.workflow_events import WorkflowTelemetry @@ -724,6 +725,10 @@ class AgentWorkflow: "reason": decision.reason if decision.method == "continuity" else None, "profile": (decision.metadata or {}).get("continuity_profile"), } if decision.method == "continuity" else {}, + "pending_topics": ( + (decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:] + if (decision.metadata or {}).get("multi_intent_plan") else [] + ), } async def conversation_policy(self, state): @@ -922,14 +927,27 @@ class AgentWorkflow: agente gerar `answer` e antes dos judges/persistência, produzindo campos supervisor_* no state e eventos GRL.001..GRL.009 via AgentObserver. """ + drained = await drain_pending_topics( + state, + str(state.get("answer") or ""), + { + "faturas_agent": self.faturas.run, + "vas_agent": self.vas.run, + "contestacao_agent": self.contestacao.run, + "suporte_contas_agent": self.suporte_contas.run, + }, + ) + candidate = drained.answer if not bool(getattr(self.settings, "ENABLE_OUTPUT_SUPERVISOR", True)): return { + "answer": candidate, + "final_answer": candidate, + "pending_topics": drained.pending_topics, + "handled_topics": drained.handled_topics, "output_guardrails_already_applied": False, "supervisor_action": "disabled", "supervisor_attempt": int(state.get("supervisor_attempt", 0)), } - - candidate = state.get("answer") or "" context = { **self._output_guardrail_context(state), "tenant_id": state.get("tenant_id"), @@ -1000,6 +1018,8 @@ class AgentWorkflow: "output_guardrails_already_applied": True, "guardrail_decisions": state.get("guardrail_decisions", []) + [item for r in decision.results for item in (r.metadata or {}).get("framework_decisions", [])], + "pending_topics": drained.pending_topics, + "handled_topics": drained.handled_topics, } async def output_guardrails(self, state): diff --git a/config/routing.yaml b/config/routing.yaml index 878d867a..937237a5 100644 --- a/config/routing.yaml +++ b/config/routing.yaml @@ -213,6 +213,10 @@ intents: - consultar_vas - cancelar_vas_avulso keywords: + - quero cancelar + - cancelar meu + - cancela o + - cancele o - cancelar serviço - cancelar servico - cancelar assinatura diff --git a/contas_mcp/__pycache__/__init__.cpython-313.pyc b/contas_mcp/__pycache__/__init__.cpython-313.pyc index abd191a6291862c8044e63a6a9d6d826eab34309..af4a88cbf15e32dc9104666fcc6f5a1823a60d44 100644 GIT binary patch delta 30 kcmbQsIG>UGGcPX}0}$j~Su~N`n9**cy(&|@(Zm!>0C@2T2LJ#7 delta 27 hcmbQwIG2(8GcPX}0}!--m^YEzn9*jUz3Rj?O8{md2d4l4 diff --git a/contas_mcp/servers/__pycache__/__init__.cpython-313.pyc b/contas_mcp/servers/__pycache__/__init__.cpython-313.pyc index fb77a9c25a35adb443cf8b6756cb8e8d8eb1aa3d..c1791ec0fbe3d5f4e07879d58d410c0a9b156b5a 100644 GIT binary patch delta 30 kcmZ3=xSWyuGcPX}0}$j~Su~N`n9*&by(&|@(Zm!-0DBn-CjbBd delta 27 hcmZ3^xRjCmGcPX}0}!--m^YEzn9*gTz3Rj?M*wI52gd*a diff --git a/contas_mcp/servers/contas_mcp_server/__pycache__/__init__.cpython-313.pyc b/contas_mcp/servers/contas_mcp_server/__pycache__/__init__.cpython-313.pyc index 94ad7ad8791a467540d7da8b7ba1d7b46e7bcd50..1d16207b351fe2b5b41bb4c56c66fb50065c5d0d 100644 GIT binary patch delta 30 kcmdnaxRa6lGcPX}0}$j~Su~N`m@#6ay(&|@(ZrNs0DtocZvX%Q delta 27 hcmdnVxSf&vGcPX}0}!--m^YEzm@#akz3RlYU;u152oC@N diff --git a/contas_mcp/servers/contas_mcp_server/__pycache__/authorized_lines_service.cpython-313.pyc b/contas_mcp/servers/contas_mcp_server/__pycache__/authorized_lines_service.cpython-313.pyc index f5504cd01912e00a2109e96ad5c2e3c094e2e5bf..bcf8b27190faa7552ff143dd813e39b76d2b2425 100644 GIT binary patch delta 34 pcmX@Ec2bS|GcPX}0}$j~S+tRRFB4xhnRc?0IHJ-=Kufz delta 31 lcmbQJ*009>nU|M~0SMYZ%-hI4k%=*F@?0j>%}1Dg1pt${35Ngx diff --git a/contas_mcp/servers/contas_mcp_server/__pycache__/line_policy.cpython-313.pyc b/contas_mcp/servers/contas_mcp_server/__pycache__/line_policy.cpython-313.pyc index 9f5e07cbc0707b1dd6ed1ae41a2cb3e018ef9d0d..af5f8937c6907a0d8bdec25c37289fa3bc36b45c 100644 GIT binary patch delta 34 ocmZpWXp-Rm%*)Hg00cQ#7H#BKW@3zY0G06wt^fc4 delta 31 lcmZpYXprFk%*)Hg00iwH=56FwW@3z-tk0ynIgDuyCjf$f2u=V1 diff --git a/contas_mcp/servers/contas_mcp_server/__pycache__/main.cpython-313.pyc b/contas_mcp/servers/contas_mcp_server/__pycache__/main.cpython-313.pyc index 10352f8e3f44d40bdb94100f89d6ca06a61dd497..3394013c20119823c936a0a5b2884e501ffecfaa 100644 GIT binary patch delta 7188 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 delta 2582 zcmaJ>eQZiA+VLm2TcQ}AL_?wuJcYv&b>h=F zB2gTl!95Zop2qJ=)P-m8VTm+6i+csi7#s0f9F=4@p2K|-_25r&zeEq>dHkM4o2aet zOOzmVK%$56XPA+w7cbx=67^BjKal9#)bv4#Hj{Tuq9nzROSFY_hbo%IFt*}zcvzDC z_;Y+zq7*)lMji-V-lqwS{eVXS(Wxx{;&bW#2Le1;E#l_Vdg6{ zzLVEHR-k$OHq9VuK9YH!$ZKB2$=fvgHYM`X>*s7>fw+0TsAg=cC_7aI%a;J~XCDT? zZK;#7KEHYDZ};#WZ4fj+{@iIkvbEIQ@OS4^>tLQ`r$6B|uWa}6Gp*nTKYy_mCeYvG zZBS=gjX^!rt%;IbjWf-f@5SEqOUC-E7;#H(xs%r!OO)od{<{Gk)UN}b=9(K7w&b)B z^M8H|7W2z)aGEQcJp5D#RL^{i4CVXcgAs5w*mWDa(B0)PsBh2L_cZk|mb6kzI~LQy zVqPqX`^+mhe0~<*U&rp zw%xD4W#0mG;iVqm2w;O(!{MDroc$9XRQ?H^Na0J6LS6!ccpaSRTO41#l@&m z3)b@{vwet!%WX2zSgF_ycfUPiyWPb=2!_ipSZ1POe>?ePjk+CGBYluM6+a$ zD_rfbOII-7{JP^}TJ0rKdo`mcCW5M_7B+#kF~B>!{F>@x8*Rf%Jx_H&`2RfHWgSY} zD#pZIhZTCdtI=h491CZ@w)E1dign_taqyq$@uifQPMO4bVP z{sFd9tz-i*t8Y{XAQIizg6aNLU20$;wRNO!z(}RSDSae8G8#7cGo7#wzBH3xg!rB= z*v%h{LoIakKXpNKxp9JObs6{3(2X|;8U&bei1B(2LY|0XY!=^>WLo$-4fgU=2;2B& z4QlyJYX0{mt-)Rb9w z?in0O8(jF03Uih>^gxUEgorvR%w}ST1AN(9N_f5p=DRKs8>9ci;c*LpuLnkymaeRC z6MUjNdR1m!_0?aBxup*lWpoN0^Tz#PLg(F<fLWN z+dJ=NQ!9gHr_0w=((zv7+BxKU-xG?L6u)1)nC!LEYGST6T7|vW9e1lAI8?$PxIkdH zgYZ`BEdR=0>Vub6+ndBqS+hH~z`JgEHG9`C2twdBaj{>M)Yu?QqZrJaymttezy*GI z2yWV@N%5IAyMH$*4%;h2evyxj!kw;b0?YVI{H;-_w|+!KclO38lmL9l|FsWRTmL}R z_UzjIFrmU#K79bT&{3a(z?`chdW9$lWcDdd;fQKn<(o240n>a>2DVr~CCSe0?=z55 zl+ToG??D&@<+{SB#^3>Q2FF1!zb^7jh~Arp`He7w list[dict[str, Any]]: + """Resolve bounded quantity/superlative references over invoice evidence. + + This is deliberately deterministic: the LLM may preserve the customer's + phrase, but ordering and cardinality are calculated from authoritative rows. + When no focused name is supplied, selection is allowed only if exactly one + repeated service exists, preventing a global "most expensive" guess. + """ + raw = str(subject or "").strip() + normalized = _norm_entity_reference(raw) + quantity_match = re.search(r"\b(?:as|os)?\s*(duas|dois|2)\s+mais\s+car[oa]s?\b", normalized) + single_match = re.search(r"\b(?:a|o)?\s*mais\s+car[oa]\b", normalized) + if not quantity_match and not single_match: + return [] + limit = 2 if quantity_match else 1 + detail = args.get("invoice_detail") + if not isinstance(detail, dict): + return [] + parsed = detail.get("parsed_content") if isinstance(detail.get("parsed_content"), dict) else detail + if not isinstance(parsed, dict): + return [] + focus = str(args.get("focused_subject") or args.get("context_subject") or "").strip() + focus_norm = _norm_entity_reference(focus) + rows: list[dict[str, Any]] = [] + roots = list(parsed.items()) if parsed and all(isinstance(v, dict) for v in parsed.values()) else [(str(args.get("msisdn") or ""), parsed)] + for line, sections in roots: + if not isinstance(sections, dict): + continue + for section, values in sections.items(): + if not isinstance(values, list): + continue + for index, row in enumerate(values): + if not isinstance(row, dict): + continue + name = str(row.get("desc") or row.get("name") or row.get("description") or "").strip() + if not name: + continue + value = row.get("value") if row.get("value") is not None else row.get("valor") + try: + amount = float(str(value).replace(".", "").replace(",", ".")) if "," in str(value) else float(value) + except (TypeError, ValueError): + continue + rows.append({ + "name": name, "msisdn": str(row.get("msisdn") or line or ""), + "value": amount, "charge_date": row.get("date") or row.get("charge_date"), + "section": str(section), "_order": len(rows), + }) + if focus_norm: + rows = [row for row in rows if focus_norm in _norm_entity_reference(row["name"]) or _norm_entity_reference(row["name"]) in focus_norm] + else: + groups: dict[str, list[dict[str, Any]]] = {} + for row in rows: + groups.setdefault(_norm_entity_reference(row["name"]), []).append(row) + repeated = [group for group in groups.values() if len(group) >= limit] + if len(repeated) != 1: + return [] + rows = repeated[0] + if len(rows) < limit: + return [] + selected = sorted(rows, key=lambda row: (-row["value"], row["_order"]))[:limit] + for row in selected: + row.pop("_order", None) + resolved_class, resolved_type = _vas_domain_policy_from_invoice_detail(row["name"], args) + row["tool_category"] = resolved_class or "cancelar_vas_avulso" + row["item_type"] = resolved_type or "avulso" + return selected + + async def _validate_vas_subject(args: dict[str, Any]) -> dict[str, Any]: """Side-effect-free entity resolution used before confirmation/execution.""" await _enrich_invoice_context("validar_vas_subject", args) @@ -1363,6 +1431,27 @@ async def _validate_vas_subject(args: dict[str, Any]) -> dict[str, Any]: catalog = _vas_entity_catalog(args) canonical, matches = _resolve_catalog_entity(subject, catalog) requested_tool = str(args.get("target_tool") or "").strip() + relative = _select_relative_invoice_charges(subject, args) if requested_tool == "cancelar_vas_avulso" else [] + if relative: + names = [str(item["name"]) for item in relative] + summary = ", ".join(f"{item['name']} de R$ {_money_ptbr(item['value'])}" for item in relative) + return { + "eligible": True, + "status": "ELIGIBLE", + "subject": subject, + "resolved_subject": names[0] if len(set(names)) == 1 else ", ".join(names), + "resolved_subjects": names, + "entity_resolution": "relative_invoice_charge_selection", + "transaction_decision": { + "resolved_arguments": {"subject": names[0], "items": relative}, + "target_tool": requested_tool, + "action_changed": False, + "requires_reconfirmation": True, + "confirmation_message": f"Você confirma o tratamento destas cobranças: {summary}? Responda 'sim' ou 'não'.", + "domain_policy": {"class": "cancelar_vas_avulso", "item_type": "multiple_charges"}, + }, + "metadata": {"side_effect_free": True, "selection": "highest", "limit": len(relative)}, + } multi = _resolve_multiple_vas_subjects(subject, catalog, args) if requested_tool == "cancelar_vas_avulso" else [] if len(multi) >= 2 or (multi and any(token in _norm_entity_reference(subject) for token in ("todos os vas avulsos", "todos os servicos avulsos"))): names = [str(item.get("name") or "").strip() for item in multi if item.get("name")] diff --git a/tests/migration/test_multi_intent_contas.py b/tests/migration/test_multi_intent_contas.py new file mode 100644 index 00000000..a857809a --- /dev/null +++ b/tests/migration/test_multi_intent_contas.py @@ -0,0 +1,55 @@ +from agent_framework.routing.config_loader import load_intents, load_multi_intent_config +from agent_framework.routing.multi_intent import MultiIntentPlanner +from agent_framework.mcp.tool_policy import ToolPolicyRegistry + + +def _planner(): + path = "config/routing.yaml" + registry = ToolPolicyRegistry("config/tool_policies.yaml") + transactional = {name for name, policy in registry.policies.items() if policy.operation_type == "transactional"} + return MultiIntentPlanner(load_intents(path), load_multi_intent_config(path), transactional_tools=transactional) + + +def test_cancelamento_e_segunda_via_preserva_cancelamento_como_primario(): + plan = _planner().plan("Cancela o tamboro e manda a segunda via para mim") + assert plan is not None + assert plan.operations[0].intent == "contas_vas_cancel" + assert plan.operations[0].agent == "contestacao_agent" + assert plan.operations[1].intent == "contas_invoice_query" + assert plan.operations[1].disposition == "execute" + + +def test_sim_composto_preserva_confirmacao_e_topico_secundario(): + plan = _planner().plan_confirmation_secondary( + "Sim e me manda a segunda via", + primary_intent="contas_vas_cancel", + primary_agent="contestacao_agent", + ) + assert plan is not None + assert plan.operations[0].intent == "contas_vas_cancel" + assert plan.operations[1].intent == "contas_invoice_query" + + +def test_pedido_secundario_realmente_fora_do_escopo_vira_pending_topic(): + plan = _planner().plan("Cancela o tamboro e altera o CNPJ da conta") + assert plan is not None + assert plan.operations[0].intent == "contas_vas_cancel" + assert plan.operations[1].intent == "off_context" + assert plan.operations[1].disposition == "unsupported" + + +def test_duas_mais_caras_sao_calculadas_sobre_evidencia_autorizada(): + from contas_mcp.servers.contas_mcp_server.main import _select_relative_invoice_charges + + args = { + "focused_subject": "Tamboro Mensal", + "invoice_detail": {"119": {"SVA Detalhe Total": [ + {"desc": "Tamboro Mensal", "value": 9.90, "date": "01/09/26", "classe": "avulso"}, + {"desc": "Tamboro Mensal", "value": 29.90, "date": "02/09/26", "classe": "avulso"}, + {"desc": "Tamboro Mensal", "value": 14.90, "date": "03/09/26", "classe": "avulso"}, + {"desc": "Tamboro Mensal", "value": 24.90, "date": "04/09/26", "classe": "avulso"}, + ]}}, + } + selected = _select_relative_invoice_charges("as duas mais caras", args) + assert [row["value"] for row in selected] == [29.90, 24.90] + assert [row["charge_date"] for row in selected] == ["02/09/26", "04/09/26"]