diff --git a/libs/agent_framework/src/agent_framework/__pycache__/__init__.cpython-313.pyc b/libs/agent_framework/src/agent_framework/__pycache__/__init__.cpython-313.pyc index dd2ef206..c8644504 100644 Binary files a/libs/agent_framework/src/agent_framework/__pycache__/__init__.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/__pycache__/__init__.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/__pycache__/extensions.cpython-313.pyc b/libs/agent_framework/src/agent_framework/__pycache__/extensions.cpython-313.pyc index 3c4a8cde..0b3673ef 100644 Binary files a/libs/agent_framework/src/agent_framework/__pycache__/extensions.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/__pycache__/extensions.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/__pycache__/idempotency.cpython-313.pyc b/libs/agent_framework/src/agent_framework/__pycache__/idempotency.cpython-313.pyc index 7c1f07aa..01a427cf 100644 Binary files a/libs/agent_framework/src/agent_framework/__pycache__/idempotency.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/__pycache__/idempotency.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/__pycache__/observer.cpython-313.pyc b/libs/agent_framework/src/agent_framework/__pycache__/observer.cpython-313.pyc index 16c60ca2..9f2cc95a 100644 Binary files a/libs/agent_framework/src/agent_framework/__pycache__/observer.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/__pycache__/observer.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/analytics/__pycache__/__init__.cpython-313.pyc b/libs/agent_framework/src/agent_framework/analytics/__pycache__/__init__.cpython-313.pyc index f90a6c2e..5d2df1d9 100644 Binary files a/libs/agent_framework/src/agent_framework/analytics/__pycache__/__init__.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/analytics/__pycache__/__init__.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/analytics/__pycache__/composite_publisher.cpython-313.pyc b/libs/agent_framework/src/agent_framework/analytics/__pycache__/composite_publisher.cpython-313.pyc index d80e9263..d0bf3578 100644 Binary files a/libs/agent_framework/src/agent_framework/analytics/__pycache__/composite_publisher.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/analytics/__pycache__/composite_publisher.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/analytics/__pycache__/event_builder.cpython-313.pyc b/libs/agent_framework/src/agent_framework/analytics/__pycache__/event_builder.cpython-313.pyc index f26065c2..86e20b7e 100644 Binary files a/libs/agent_framework/src/agent_framework/analytics/__pycache__/event_builder.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/analytics/__pycache__/event_builder.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/analytics/__pycache__/factory.cpython-313.pyc b/libs/agent_framework/src/agent_framework/analytics/__pycache__/factory.cpython-313.pyc index f2c6660d..6c157662 100644 Binary files a/libs/agent_framework/src/agent_framework/analytics/__pycache__/factory.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/analytics/__pycache__/factory.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/analytics/__pycache__/publisher.cpython-313.pyc b/libs/agent_framework/src/agent_framework/analytics/__pycache__/publisher.cpython-313.pyc index 08395717..4a48fe97 100644 Binary files a/libs/agent_framework/src/agent_framework/analytics/__pycache__/publisher.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/analytics/__pycache__/publisher.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/analytics/__pycache__/tim_payload_mapper.cpython-313.pyc b/libs/agent_framework/src/agent_framework/analytics/__pycache__/tim_payload_mapper.cpython-313.pyc index d60075e3..e54ad9bc 100644 Binary files a/libs/agent_framework/src/agent_framework/analytics/__pycache__/tim_payload_mapper.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/analytics/__pycache__/tim_payload_mapper.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/analytics/__pycache__/tim_sequence.cpython-313.pyc b/libs/agent_framework/src/agent_framework/analytics/__pycache__/tim_sequence.cpython-313.pyc index 526cd36a..fb3e4dec 100644 Binary files a/libs/agent_framework/src/agent_framework/analytics/__pycache__/tim_sequence.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/analytics/__pycache__/tim_sequence.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/analytics/providers/__pycache__/__init__.cpython-313.pyc b/libs/agent_framework/src/agent_framework/analytics/providers/__pycache__/__init__.cpython-313.pyc index b3d43155..db8287d1 100644 Binary files a/libs/agent_framework/src/agent_framework/analytics/providers/__pycache__/__init__.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/analytics/providers/__pycache__/__init__.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/analytics/providers/__pycache__/kafka.cpython-313.pyc b/libs/agent_framework/src/agent_framework/analytics/providers/__pycache__/kafka.cpython-313.pyc index b84fe312..3c07bb4d 100644 Binary files a/libs/agent_framework/src/agent_framework/analytics/providers/__pycache__/kafka.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/analytics/providers/__pycache__/kafka.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/analytics/providers/__pycache__/langfuse.cpython-313.pyc b/libs/agent_framework/src/agent_framework/analytics/providers/__pycache__/langfuse.cpython-313.pyc index af8601ce..4070c5c2 100644 Binary files a/libs/agent_framework/src/agent_framework/analytics/providers/__pycache__/langfuse.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/analytics/providers/__pycache__/langfuse.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/analytics/providers/__pycache__/oci_streaming.cpython-313.pyc b/libs/agent_framework/src/agent_framework/analytics/providers/__pycache__/oci_streaming.cpython-313.pyc index 9bd8ab35..df4431a0 100644 Binary files a/libs/agent_framework/src/agent_framework/analytics/providers/__pycache__/oci_streaming.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/analytics/providers/__pycache__/oci_streaming.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/analytics/providers/__pycache__/pubsub.cpython-313.pyc b/libs/agent_framework/src/agent_framework/analytics/providers/__pycache__/pubsub.cpython-313.pyc index b3e70975..60262b1d 100644 Binary files a/libs/agent_framework/src/agent_framework/analytics/providers/__pycache__/pubsub.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/analytics/providers/__pycache__/pubsub.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/billing/__pycache__/__init__.cpython-313.pyc b/libs/agent_framework/src/agent_framework/billing/__pycache__/__init__.cpython-313.pyc index 268522ef..437e994f 100644 Binary files a/libs/agent_framework/src/agent_framework/billing/__pycache__/__init__.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/billing/__pycache__/__init__.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/billing/__pycache__/usage_repository.cpython-313.pyc b/libs/agent_framework/src/agent_framework/billing/__pycache__/usage_repository.cpython-313.pyc index 8fe71156..c15ebda3 100644 Binary files a/libs/agent_framework/src/agent_framework/billing/__pycache__/usage_repository.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/billing/__pycache__/usage_repository.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/cache/__pycache__/__init__.cpython-313.pyc b/libs/agent_framework/src/agent_framework/cache/__pycache__/__init__.cpython-313.pyc index a0808ef1..7fd87c5b 100644 Binary files a/libs/agent_framework/src/agent_framework/cache/__pycache__/__init__.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/cache/__pycache__/__init__.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/cache/__pycache__/cache.cpython-313.pyc b/libs/agent_framework/src/agent_framework/cache/__pycache__/cache.cpython-313.pyc index f173ee89..3cd9912b 100644 Binary files a/libs/agent_framework/src/agent_framework/cache/__pycache__/cache.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/cache/__pycache__/cache.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/channels/__pycache__/__init__.cpython-313.pyc b/libs/agent_framework/src/agent_framework/channels/__pycache__/__init__.cpython-313.pyc index f3fdadf1..61140228 100644 Binary files a/libs/agent_framework/src/agent_framework/channels/__pycache__/__init__.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/channels/__pycache__/__init__.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/channels/__pycache__/adapters.cpython-313.pyc b/libs/agent_framework/src/agent_framework/channels/__pycache__/adapters.cpython-313.pyc index e0991987..b2e0598b 100644 Binary files a/libs/agent_framework/src/agent_framework/channels/__pycache__/adapters.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/channels/__pycache__/adapters.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/channels/__pycache__/base.cpython-313.pyc b/libs/agent_framework/src/agent_framework/channels/__pycache__/base.cpython-313.pyc index 2dfbd907..df2a44d7 100644 Binary files a/libs/agent_framework/src/agent_framework/channels/__pycache__/base.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/channels/__pycache__/base.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/channels/__pycache__/gateway.cpython-313.pyc b/libs/agent_framework/src/agent_framework/channels/__pycache__/gateway.cpython-313.pyc index 9b06a47f..d1b67b04 100644 Binary files a/libs/agent_framework/src/agent_framework/channels/__pycache__/gateway.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/channels/__pycache__/gateway.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/channels/__pycache__/interruption.cpython-313.pyc b/libs/agent_framework/src/agent_framework/channels/__pycache__/interruption.cpython-313.pyc index f51d600e..14f5aa7b 100644 Binary files a/libs/agent_framework/src/agent_framework/channels/__pycache__/interruption.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/channels/__pycache__/interruption.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/channels/__pycache__/transcription.cpython-313.pyc b/libs/agent_framework/src/agent_framework/channels/__pycache__/transcription.cpython-313.pyc index eb0fdc02..1f899dde 100644 Binary files a/libs/agent_framework/src/agent_framework/channels/__pycache__/transcription.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/channels/__pycache__/transcription.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/checkpoints/__pycache__/__init__.cpython-313.pyc b/libs/agent_framework/src/agent_framework/checkpoints/__pycache__/__init__.cpython-313.pyc index 38d35de0..c2fd4684 100644 Binary files a/libs/agent_framework/src/agent_framework/checkpoints/__pycache__/__init__.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/checkpoints/__pycache__/__init__.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/checkpoints/__pycache__/checkpoint_repository.cpython-313.pyc b/libs/agent_framework/src/agent_framework/checkpoints/__pycache__/checkpoint_repository.cpython-313.pyc index 7466402b..9e5786b2 100644 Binary files a/libs/agent_framework/src/agent_framework/checkpoints/__pycache__/checkpoint_repository.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/checkpoints/__pycache__/checkpoint_repository.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/checkpoints/__pycache__/langgraph_saver.cpython-313.pyc b/libs/agent_framework/src/agent_framework/checkpoints/__pycache__/langgraph_saver.cpython-313.pyc index 2c808c8d..33c0b82e 100644 Binary files a/libs/agent_framework/src/agent_framework/checkpoints/__pycache__/langgraph_saver.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/checkpoints/__pycache__/langgraph_saver.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/config/__pycache__/__init__.cpython-313.pyc b/libs/agent_framework/src/agent_framework/config/__pycache__/__init__.cpython-313.pyc index c2ae0f81..e10a46fd 100644 Binary files a/libs/agent_framework/src/agent_framework/config/__pycache__/__init__.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/config/__pycache__/__init__.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/config/__pycache__/agent_registry.cpython-313.pyc b/libs/agent_framework/src/agent_framework/config/__pycache__/agent_registry.cpython-313.pyc index ac1c17ec..0b1127f3 100644 Binary files a/libs/agent_framework/src/agent_framework/config/__pycache__/agent_registry.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/config/__pycache__/agent_registry.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/config/__pycache__/settings.cpython-313.pyc b/libs/agent_framework/src/agent_framework/config/__pycache__/settings.cpython-313.pyc index 1678ae97..d4da59b2 100644 Binary files a/libs/agent_framework/src/agent_framework/config/__pycache__/settings.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/config/__pycache__/settings.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/gateways/__pycache__/__init__.cpython-313.pyc b/libs/agent_framework/src/agent_framework/gateways/__pycache__/__init__.cpython-313.pyc index 7dcc5682..ef984047 100644 Binary files a/libs/agent_framework/src/agent_framework/gateways/__pycache__/__init__.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/gateways/__pycache__/__init__.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/gateways/__pycache__/mcp_gateway_client.cpython-313.pyc b/libs/agent_framework/src/agent_framework/gateways/__pycache__/mcp_gateway_client.cpython-313.pyc index a661b749..a12b9560 100644 Binary files a/libs/agent_framework/src/agent_framework/gateways/__pycache__/mcp_gateway_client.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/gateways/__pycache__/mcp_gateway_client.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/__pycache__/__init__.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/__pycache__/__init__.cpython-313.pyc index bc07267e..4c92ecd3 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/__pycache__/__init__.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/__pycache__/__init__.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/__pycache__/base.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/__pycache__/base.cpython-313.pyc index 50fd70a4..613c2b32 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/__pycache__/base.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/__pycache__/base.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/__pycache__/config_loader.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/__pycache__/config_loader.cpython-313.pyc index 868b18d1..0d971ac2 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/__pycache__/config_loader.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/__pycache__/config_loader.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/__pycache__/custom_rails.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/__pycache__/custom_rails.cpython-313.pyc index 5297e5ad..c7409ffb 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/__pycache__/custom_rails.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/__pycache__/custom_rails.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/__pycache__/framework_llm_client.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/__pycache__/framework_llm_client.cpython-313.pyc index 6a077d2e..e794c9ba 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/__pycache__/framework_llm_client.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/__pycache__/framework_llm_client.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/__pycache__/llm_rails.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/__pycache__/llm_rails.cpython-313.pyc index 70803d59..551e4fda 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/__pycache__/llm_rails.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/__pycache__/llm_rails.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/__pycache__/output_supervisor.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/__pycache__/output_supervisor.cpython-313.pyc index c62f7b42..8c8dbfde 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/__pycache__/output_supervisor.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/__pycache__/output_supervisor.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/__pycache__/parallel_executor.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/__pycache__/parallel_executor.cpython-313.pyc index dd9c3d8e..bf2bc30a 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/__pycache__/parallel_executor.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/__pycache__/parallel_executor.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/__pycache__/pipeline.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/__pycache__/pipeline.cpython-313.pyc index 49c70be6..1a2aa4df 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/__pycache__/pipeline.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/__pycache__/pipeline.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/__pycache__/rail_action.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/__pycache__/rail_action.cpython-313.pyc index 93052a6e..6408ba93 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/__pycache__/rail_action.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/__pycache__/rail_action.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/__pycache__/rail_decision.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/__pycache__/rail_decision.cpython-313.pyc index 863f5c04..5e4f5600 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/__pycache__/rail_decision.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/__pycache__/rail_decision.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/__pycache__/rail_result.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/__pycache__/rail_result.cpython-313.pyc index 67dcd0c3..e4861d3f 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/__pycache__/rail_result.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/__pycache__/rail_result.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/__pycache__/rails.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/__pycache__/rails.cpython-313.pyc index 0dbd89ea..f91e7d19 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/__pycache__/rails.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/__pycache__/rails.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/__init__.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/__init__.cpython-313.pyc index 715230e5..47e364fa 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/__init__.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/__init__.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/_compat.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/_compat.cpython-313.pyc index 4d7ec529..cd059405 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/_compat.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/_compat.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/contestation_validation.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/contestation_validation.cpython-313.pyc index ac2b9d08..01ee952f 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/contestation_validation.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/contestation_validation.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/contracts.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/contracts.cpython-313.pyc index c5bcaafc..1208cdc7 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/contracts.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/contracts.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/input_size.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/input_size.cpython-313.pyc index 89c78ab4..77f3ef5a 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/input_size.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/input_size.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/llm_adapter.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/llm_adapter.cpython-313.pyc index df23f355..a670b4ac 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/llm_adapter.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/llm_adapter.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/llm_client.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/llm_client.cpython-313.pyc index b6bfcaa0..d4f4b07e 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/llm_client.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/llm_client.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/llm_rails.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/llm_rails.cpython-313.pyc index 8b9f7599..2687ec92 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/llm_rails.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/llm_rails.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/output_sanitization.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/output_sanitization.cpython-313.pyc index fb48f8ca..a5d3af3e 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/output_sanitization.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/output_sanitization.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/pipeline.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/pipeline.cpython-313.pyc index 1b50e7c1..63511e79 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/pipeline.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/pipeline.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/__init__.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/__init__.cpython-313.pyc index a1c0ae30..6068ff8e 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/__init__.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/__init__.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/_context.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/_context.cpython-313.pyc index 85e68340..7f7d1e4a 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/_context.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/_context.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/ausencia_oferta_proativa.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/ausencia_oferta_proativa.cpython-313.pyc index 014d27e9..a301fe15 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/ausencia_oferta_proativa.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/ausencia_oferta_proativa.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/coerencia.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/coerencia.cpython-313.pyc index 1adbe28e..5c64d5e5 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/coerencia.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/coerencia.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/dlex_in.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/dlex_in.cpython-313.pyc index 4c253913..d64f6e62 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/dlex_in.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/dlex_in.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/dlex_out.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/dlex_out.cpython-313.pyc index 4c834efd..8113eaf6 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/dlex_out.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/dlex_out.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/dlex_out.cpython-313.pyc.128272546232176 b/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/dlex_out.cpython-313.pyc.128272546232176 new file mode 100644 index 00000000..e69de29b diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/fallback.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/fallback.cpython-313.pyc index ae92b20e..8dcd58de 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/fallback.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/fallback.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/fraseologia.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/fraseologia.cpython-313.pyc index e757574e..630db7e4 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/fraseologia.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/fraseologia.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/out_of_scope.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/out_of_scope.cpython-313.pyc index cdb2534f..eba298e3 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/out_of_scope.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/out_of_scope.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/pinj.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/pinj.cpython-313.pyc index d785a3a9..4faf4396 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/pinj.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/pinj.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/ragsec.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/ragsec.cpython-313.pyc index c586b640..9de8beb6 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/ragsec.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/ragsec.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/revprec.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/revprec.cpython-313.pyc index 6b2041d1..e6ed8632 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/revprec.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/revprec.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/tox.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/tox.cpython-313.pyc index 50fcb153..002f8cc3 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/tox.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/tox.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/toxicidade_output.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/toxicidade_output.cpython-313.pyc index ec8f20bf..58400365 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/toxicidade_output.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/toxicidade_output.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/__init__.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/__init__.cpython-313.pyc index 8f3fac0e..d3621383 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/__init__.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/__init__.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/alcada.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/alcada.cpython-313.pyc index e93d71ee..ab7d4782 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/alcada.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/alcada.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/anatel.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/anatel.cpython-313.pyc index 659cca02..83c2bda1 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/anatel.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/anatel.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/confirmation.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/confirmation.cpython-313.pyc index 6c7e3841..cd062385 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/confirmation.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/confirmation.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/dlex_in.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/dlex_in.cpython-313.pyc index f1573488..a0960356 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/dlex_in.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/dlex_in.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/dlex_out.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/dlex_out.cpython-313.pyc index e437bea9..fff88a07 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/dlex_out.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/dlex_out.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/ragsec.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/ragsec.cpython-313.pyc index b62a84fc..ffbc949c 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/ragsec.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/ragsec.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/revprec.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/revprec.cpython-313.pyc index aa2be86e..3f75fb4d 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/revprec.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/revprec.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/tox.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/tox.cpython-313.pyc index 15dcb19f..69e442e8 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/tox.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/tox.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/rules/__pycache__/__init__.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/rules/__pycache__/__init__.cpython-313.pyc index 313b48ab..b7192ce1 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/calibrated/rules/__pycache__/__init__.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/calibrated/rules/__pycache__/__init__.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/rules/__pycache__/alcada.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/rules/__pycache__/alcada.cpython-313.pyc index 86c6d7e1..c26edca1 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/calibrated/rules/__pycache__/alcada.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/calibrated/rules/__pycache__/alcada.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/rules/__pycache__/pinj_patterns.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/rules/__pycache__/pinj_patterns.cpython-313.pyc index 72d4bc1b..8bb4be0b 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/calibrated/rules/__pycache__/pinj_patterns.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/calibrated/rules/__pycache__/pinj_patterns.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/rules/__pycache__/tox_blocklist.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/rules/__pycache__/tox_blocklist.cpython-313.pyc index 85ecb207..5d61d1fb 100644 Binary files a/libs/agent_framework/src/agent_framework/guardrails/calibrated/rules/__pycache__/tox_blocklist.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/guardrails/calibrated/rules/__pycache__/tox_blocklist.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/identity/__pycache__/__init__.cpython-313.pyc b/libs/agent_framework/src/agent_framework/identity/__pycache__/__init__.cpython-313.pyc index 262a3b61..bb9ed415 100644 Binary files a/libs/agent_framework/src/agent_framework/identity/__pycache__/__init__.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/identity/__pycache__/__init__.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/identity/__pycache__/mcp_mapper.cpython-313.pyc b/libs/agent_framework/src/agent_framework/identity/__pycache__/mcp_mapper.cpython-313.pyc index 78250ff9..081033ba 100644 Binary files a/libs/agent_framework/src/agent_framework/identity/__pycache__/mcp_mapper.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/identity/__pycache__/mcp_mapper.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/identity/__pycache__/models.cpython-313.pyc b/libs/agent_framework/src/agent_framework/identity/__pycache__/models.cpython-313.pyc index 8444ca7d..08eb9c9c 100644 Binary files a/libs/agent_framework/src/agent_framework/identity/__pycache__/models.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/identity/__pycache__/models.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/identity/__pycache__/resolver.cpython-313.pyc b/libs/agent_framework/src/agent_framework/identity/__pycache__/resolver.cpython-313.pyc index ddd7be65..385d9b92 100644 Binary files a/libs/agent_framework/src/agent_framework/identity/__pycache__/resolver.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/identity/__pycache__/resolver.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/judges/__pycache__/__init__.cpython-313.pyc b/libs/agent_framework/src/agent_framework/judges/__pycache__/__init__.cpython-313.pyc index fd1f7bba..722d54e4 100644 Binary files a/libs/agent_framework/src/agent_framework/judges/__pycache__/__init__.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/judges/__pycache__/__init__.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/judges/__pycache__/judge.cpython-313.pyc b/libs/agent_framework/src/agent_framework/judges/__pycache__/judge.cpython-313.pyc index 1954bc6f..68ef4a2c 100644 Binary files a/libs/agent_framework/src/agent_framework/judges/__pycache__/judge.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/judges/__pycache__/judge.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/judges/calibrated/__pycache__/__init__.cpython-313.pyc b/libs/agent_framework/src/agent_framework/judges/calibrated/__pycache__/__init__.cpython-313.pyc index 4b91dab9..521a732c 100644 Binary files a/libs/agent_framework/src/agent_framework/judges/calibrated/__pycache__/__init__.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/judges/calibrated/__pycache__/__init__.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/judges/calibrated/__pycache__/llm_client.cpython-313.pyc b/libs/agent_framework/src/agent_framework/judges/calibrated/__pycache__/llm_client.cpython-313.pyc index 2e44150e..50724358 100644 Binary files a/libs/agent_framework/src/agent_framework/judges/calibrated/__pycache__/llm_client.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/judges/calibrated/__pycache__/llm_client.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/judges/calibrated/prompts/__pycache__/__init__.cpython-313.pyc b/libs/agent_framework/src/agent_framework/judges/calibrated/prompts/__pycache__/__init__.cpython-313.pyc index 0ab8d48c..eff1366d 100644 Binary files a/libs/agent_framework/src/agent_framework/judges/calibrated/prompts/__pycache__/__init__.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/judges/calibrated/prompts/__pycache__/__init__.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/judges/calibrated/prompts/__pycache__/aluc.cpython-313.pyc b/libs/agent_framework/src/agent_framework/judges/calibrated/prompts/__pycache__/aluc.cpython-313.pyc index 6c3e14d1..08fc36b6 100644 Binary files a/libs/agent_framework/src/agent_framework/judges/calibrated/prompts/__pycache__/aluc.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/judges/calibrated/prompts/__pycache__/aluc.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/judges/calibrated/prompts/__pycache__/csi.cpython-313.pyc b/libs/agent_framework/src/agent_framework/judges/calibrated/prompts/__pycache__/csi.cpython-313.pyc index 63a7acfa..7e429d19 100644 Binary files a/libs/agent_framework/src/agent_framework/judges/calibrated/prompts/__pycache__/csi.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/judges/calibrated/prompts/__pycache__/csi.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/judges/calibrated/prompts/__pycache__/fallback.cpython-313.pyc b/libs/agent_framework/src/agent_framework/judges/calibrated/prompts/__pycache__/fallback.cpython-313.pyc index c3493b7d..58913fa5 100644 Binary files a/libs/agent_framework/src/agent_framework/judges/calibrated/prompts/__pycache__/fallback.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/judges/calibrated/prompts/__pycache__/fallback.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/judges/calibrated/prompts/__pycache__/rqlt.cpython-313.pyc b/libs/agent_framework/src/agent_framework/judges/calibrated/prompts/__pycache__/rqlt.cpython-313.pyc index 032224bc..612b94cc 100644 Binary files a/libs/agent_framework/src/agent_framework/judges/calibrated/prompts/__pycache__/rqlt.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/judges/calibrated/prompts/__pycache__/rqlt.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/judges/calibrated/prompts/__pycache__/vctn.cpython-313.pyc b/libs/agent_framework/src/agent_framework/judges/calibrated/prompts/__pycache__/vctn.cpython-313.pyc index 6ba3e526..5e92c07e 100644 Binary files a/libs/agent_framework/src/agent_framework/judges/calibrated/prompts/__pycache__/vctn.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/judges/calibrated/prompts/__pycache__/vctn.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/llm/__pycache__/__init__.cpython-313.pyc b/libs/agent_framework/src/agent_framework/llm/__pycache__/__init__.cpython-313.pyc index da7e2194..acb7f1fc 100644 Binary files a/libs/agent_framework/src/agent_framework/llm/__pycache__/__init__.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/llm/__pycache__/__init__.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/llm/__pycache__/base.cpython-313.pyc b/libs/agent_framework/src/agent_framework/llm/__pycache__/base.cpython-313.pyc index 972b6626..524939ef 100644 Binary files a/libs/agent_framework/src/agent_framework/llm/__pycache__/base.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/llm/__pycache__/base.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/llm/__pycache__/profile_resolver.cpython-313.pyc b/libs/agent_framework/src/agent_framework/llm/__pycache__/profile_resolver.cpython-313.pyc index c2b4ecf0..6727084c 100644 Binary files a/libs/agent_framework/src/agent_framework/llm/__pycache__/profile_resolver.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/llm/__pycache__/profile_resolver.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/llm/__pycache__/providers.cpython-313.pyc b/libs/agent_framework/src/agent_framework/llm/__pycache__/providers.cpython-313.pyc index 4039e6cd..d473dc8d 100644 Binary files a/libs/agent_framework/src/agent_framework/llm/__pycache__/providers.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/llm/__pycache__/providers.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/llm/__pycache__/structured_output.cpython-313.pyc b/libs/agent_framework/src/agent_framework/llm/__pycache__/structured_output.cpython-313.pyc index f55398b8..3f35e1fd 100644 Binary files a/libs/agent_framework/src/agent_framework/llm/__pycache__/structured_output.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/llm/__pycache__/structured_output.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/llm/__pycache__/types.cpython-313.pyc b/libs/agent_framework/src/agent_framework/llm/__pycache__/types.cpython-313.pyc index 05014a07..2ab8fa3c 100644 Binary files a/libs/agent_framework/src/agent_framework/llm/__pycache__/types.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/llm/__pycache__/types.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/mcp/__pycache__/__init__.cpython-313.pyc b/libs/agent_framework/src/agent_framework/mcp/__pycache__/__init__.cpython-313.pyc index c872622e..9d7e7454 100644 Binary files a/libs/agent_framework/src/agent_framework/mcp/__pycache__/__init__.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/mcp/__pycache__/__init__.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/mcp/__pycache__/client.cpython-313.pyc b/libs/agent_framework/src/agent_framework/mcp/__pycache__/client.cpython-313.pyc index d8ae516a..f22ce715 100644 Binary files a/libs/agent_framework/src/agent_framework/mcp/__pycache__/client.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/mcp/__pycache__/client.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/mcp/__pycache__/models.cpython-313.pyc b/libs/agent_framework/src/agent_framework/mcp/__pycache__/models.cpython-313.pyc index a64e2705..423945af 100644 Binary files a/libs/agent_framework/src/agent_framework/mcp/__pycache__/models.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/mcp/__pycache__/models.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/mcp/__pycache__/registry.cpython-313.pyc b/libs/agent_framework/src/agent_framework/mcp/__pycache__/registry.cpython-313.pyc index 16e0b00f..549de3a2 100644 Binary files a/libs/agent_framework/src/agent_framework/mcp/__pycache__/registry.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/mcp/__pycache__/registry.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/mcp/__pycache__/tool_policy.cpython-313.pyc b/libs/agent_framework/src/agent_framework/mcp/__pycache__/tool_policy.cpython-313.pyc index ca72d392..d82cf4d9 100644 Binary files a/libs/agent_framework/src/agent_framework/mcp/__pycache__/tool_policy.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/mcp/__pycache__/tool_policy.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/mcp/__pycache__/tool_router.cpython-313.pyc b/libs/agent_framework/src/agent_framework/mcp/__pycache__/tool_router.cpython-313.pyc index bc53c02f..0b2a4c5e 100644 Binary files a/libs/agent_framework/src/agent_framework/mcp/__pycache__/tool_router.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/mcp/__pycache__/tool_router.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/memory/__pycache__/__init__.cpython-313.pyc b/libs/agent_framework/src/agent_framework/memory/__pycache__/__init__.cpython-313.pyc index fab016ee..c3f6be39 100644 Binary files a/libs/agent_framework/src/agent_framework/memory/__pycache__/__init__.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/memory/__pycache__/__init__.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/memory/__pycache__/long_term_extractor.cpython-313.pyc b/libs/agent_framework/src/agent_framework/memory/__pycache__/long_term_extractor.cpython-313.pyc index ab2e2fc2..3e11289b 100644 Binary files a/libs/agent_framework/src/agent_framework/memory/__pycache__/long_term_extractor.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/memory/__pycache__/long_term_extractor.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/memory/__pycache__/long_term_memory.cpython-313.pyc b/libs/agent_framework/src/agent_framework/memory/__pycache__/long_term_memory.cpython-313.pyc index 087fa9a9..2a331d42 100644 Binary files a/libs/agent_framework/src/agent_framework/memory/__pycache__/long_term_memory.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/memory/__pycache__/long_term_memory.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/memory/__pycache__/long_term_models.cpython-313.pyc b/libs/agent_framework/src/agent_framework/memory/__pycache__/long_term_models.cpython-313.pyc index 08efccd5..32da699d 100644 Binary files a/libs/agent_framework/src/agent_framework/memory/__pycache__/long_term_models.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/memory/__pycache__/long_term_models.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/memory/__pycache__/long_term_store.cpython-313.pyc b/libs/agent_framework/src/agent_framework/memory/__pycache__/long_term_store.cpython-313.pyc index d2853bee..58493a3e 100644 Binary files a/libs/agent_framework/src/agent_framework/memory/__pycache__/long_term_store.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/memory/__pycache__/long_term_store.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/memory/__pycache__/message_history.cpython-313.pyc b/libs/agent_framework/src/agent_framework/memory/__pycache__/message_history.cpython-313.pyc index aa77e381..fc59b682 100644 Binary files a/libs/agent_framework/src/agent_framework/memory/__pycache__/message_history.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/memory/__pycache__/message_history.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/memory/__pycache__/summary_memory.cpython-313.pyc b/libs/agent_framework/src/agent_framework/memory/__pycache__/summary_memory.cpython-313.pyc index 71ed3378..9da6a97c 100644 Binary files a/libs/agent_framework/src/agent_framework/memory/__pycache__/summary_memory.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/memory/__pycache__/summary_memory.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/memory/__pycache__/summary_store.cpython-313.pyc b/libs/agent_framework/src/agent_framework/memory/__pycache__/summary_store.cpython-313.pyc index 7274f40d..49c01984 100644 Binary files a/libs/agent_framework/src/agent_framework/memory/__pycache__/summary_store.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/memory/__pycache__/summary_store.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/models/__pycache__/__init__.cpython-313.pyc b/libs/agent_framework/src/agent_framework/models/__pycache__/__init__.cpython-313.pyc index 06466e2a..00f52bbc 100644 Binary files a/libs/agent_framework/src/agent_framework/models/__pycache__/__init__.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/models/__pycache__/__init__.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/models/__pycache__/identity.cpython-313.pyc b/libs/agent_framework/src/agent_framework/models/__pycache__/identity.cpython-313.pyc index ffe49033..9d334e0d 100644 Binary files a/libs/agent_framework/src/agent_framework/models/__pycache__/identity.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/models/__pycache__/identity.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/models/__pycache__/session.cpython-313.pyc b/libs/agent_framework/src/agent_framework/models/__pycache__/session.cpython-313.pyc index b1d21044..491605e0 100644 Binary files a/libs/agent_framework/src/agent_framework/models/__pycache__/session.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/models/__pycache__/session.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/observability/__pycache__/__init__.cpython-313.pyc b/libs/agent_framework/src/agent_framework/observability/__pycache__/__init__.cpython-313.pyc index d0cd341a..9f0978d9 100644 Binary files a/libs/agent_framework/src/agent_framework/observability/__pycache__/__init__.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/observability/__pycache__/__init__.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/observability/__pycache__/code_mapper.cpython-313.pyc b/libs/agent_framework/src/agent_framework/observability/__pycache__/code_mapper.cpython-313.pyc index 0ae593ee..5a074966 100644 Binary files a/libs/agent_framework/src/agent_framework/observability/__pycache__/code_mapper.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/observability/__pycache__/code_mapper.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/observability/__pycache__/context.cpython-313.pyc b/libs/agent_framework/src/agent_framework/observability/__pycache__/context.cpython-313.pyc index d783e517..89ff63c8 100644 Binary files a/libs/agent_framework/src/agent_framework/observability/__pycache__/context.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/observability/__pycache__/context.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/observability/__pycache__/event_bus.cpython-313.pyc b/libs/agent_framework/src/agent_framework/observability/__pycache__/event_bus.cpython-313.pyc index f8903e56..152b90fa 100644 Binary files a/libs/agent_framework/src/agent_framework/observability/__pycache__/event_bus.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/observability/__pycache__/event_bus.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/observability/__pycache__/guardrail_events.cpython-313.pyc b/libs/agent_framework/src/agent_framework/observability/__pycache__/guardrail_events.cpython-313.pyc index eccd2803..11581865 100644 Binary files a/libs/agent_framework/src/agent_framework/observability/__pycache__/guardrail_events.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/observability/__pycache__/guardrail_events.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/observability/__pycache__/ic_events.cpython-313.pyc b/libs/agent_framework/src/agent_framework/observability/__pycache__/ic_events.cpython-313.pyc index 96696e63..c58ea89d 100644 Binary files a/libs/agent_framework/src/agent_framework/observability/__pycache__/ic_events.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/observability/__pycache__/ic_events.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/observability/__pycache__/judge_events.cpython-313.pyc b/libs/agent_framework/src/agent_framework/observability/__pycache__/judge_events.cpython-313.pyc index 718770ed..12b0f882 100644 Binary files a/libs/agent_framework/src/agent_framework/observability/__pycache__/judge_events.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/observability/__pycache__/judge_events.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/observability/__pycache__/langgraph_telemetry.cpython-313.pyc b/libs/agent_framework/src/agent_framework/observability/__pycache__/langgraph_telemetry.cpython-313.pyc index 5634f6ca..20d68af9 100644 Binary files a/libs/agent_framework/src/agent_framework/observability/__pycache__/langgraph_telemetry.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/observability/__pycache__/langgraph_telemetry.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/observability/__pycache__/llm_advisors.cpython-313.pyc b/libs/agent_framework/src/agent_framework/observability/__pycache__/llm_advisors.cpython-313.pyc index 169138a5..8faa9094 100644 Binary files a/libs/agent_framework/src/agent_framework/observability/__pycache__/llm_advisors.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/observability/__pycache__/llm_advisors.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/observability/__pycache__/noc_contract.cpython-313.pyc b/libs/agent_framework/src/agent_framework/observability/__pycache__/noc_contract.cpython-313.pyc index 7d81579c..a499f1a0 100644 Binary files a/libs/agent_framework/src/agent_framework/observability/__pycache__/noc_contract.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/observability/__pycache__/noc_contract.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/observability/__pycache__/noc_otel.cpython-313.pyc b/libs/agent_framework/src/agent_framework/observability/__pycache__/noc_otel.cpython-313.pyc index e4fbb18c..499510eb 100644 Binary files a/libs/agent_framework/src/agent_framework/observability/__pycache__/noc_otel.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/observability/__pycache__/noc_otel.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/observability/__pycache__/observer.cpython-313.pyc b/libs/agent_framework/src/agent_framework/observability/__pycache__/observer.cpython-313.pyc index 14bb5c47..3f443d6d 100644 Binary files a/libs/agent_framework/src/agent_framework/observability/__pycache__/observer.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/observability/__pycache__/observer.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/observability/__pycache__/otel.cpython-313.pyc b/libs/agent_framework/src/agent_framework/observability/__pycache__/otel.cpython-313.pyc index e75b197d..19a4e22a 100644 Binary files a/libs/agent_framework/src/agent_framework/observability/__pycache__/otel.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/observability/__pycache__/otel.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/observability/__pycache__/streaming_events.cpython-313.pyc b/libs/agent_framework/src/agent_framework/observability/__pycache__/streaming_events.cpython-313.pyc index 69cab224..89c31f41 100644 Binary files a/libs/agent_framework/src/agent_framework/observability/__pycache__/streaming_events.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/observability/__pycache__/streaming_events.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/observability/__pycache__/telemetry.cpython-313.pyc b/libs/agent_framework/src/agent_framework/observability/__pycache__/telemetry.cpython-313.pyc index b6eae437..c4de4f6c 100644 Binary files a/libs/agent_framework/src/agent_framework/observability/__pycache__/telemetry.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/observability/__pycache__/telemetry.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/observability/__pycache__/token_cost.cpython-313.pyc b/libs/agent_framework/src/agent_framework/observability/__pycache__/token_cost.cpython-313.pyc index 584ec9fb..63bb6076 100644 Binary files a/libs/agent_framework/src/agent_framework/observability/__pycache__/token_cost.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/observability/__pycache__/token_cost.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/observability/__pycache__/workflow_events.cpython-313.pyc b/libs/agent_framework/src/agent_framework/observability/__pycache__/workflow_events.cpython-313.pyc index da593ed6..90691c5d 100644 Binary files a/libs/agent_framework/src/agent_framework/observability/__pycache__/workflow_events.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/observability/__pycache__/workflow_events.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/oci/__pycache__/__init__.cpython-313.pyc b/libs/agent_framework/src/agent_framework/oci/__pycache__/__init__.cpython-313.pyc index 48f64060..998eca43 100644 Binary files a/libs/agent_framework/src/agent_framework/oci/__pycache__/__init__.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/oci/__pycache__/__init__.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/oci/__pycache__/auth.cpython-313.pyc b/libs/agent_framework/src/agent_framework/oci/__pycache__/auth.cpython-313.pyc index 548cdccb..0ea7cb93 100644 Binary files a/libs/agent_framework/src/agent_framework/oci/__pycache__/auth.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/oci/__pycache__/auth.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/persistence/__pycache__/__init__.cpython-313.pyc b/libs/agent_framework/src/agent_framework/persistence/__pycache__/__init__.cpython-313.pyc index 41a9bdd9..e25b5b77 100644 Binary files a/libs/agent_framework/src/agent_framework/persistence/__pycache__/__init__.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/persistence/__pycache__/__init__.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/persistence/__pycache__/oracle_store.cpython-313.pyc b/libs/agent_framework/src/agent_framework/persistence/__pycache__/oracle_store.cpython-313.pyc index 162b6407..468a07cb 100644 Binary files a/libs/agent_framework/src/agent_framework/persistence/__pycache__/oracle_store.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/persistence/__pycache__/oracle_store.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/persistence/__pycache__/sqlite_store.cpython-313.pyc b/libs/agent_framework/src/agent_framework/persistence/__pycache__/sqlite_store.cpython-313.pyc index a7bfda16..f545b608 100644 Binary files a/libs/agent_framework/src/agent_framework/persistence/__pycache__/sqlite_store.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/persistence/__pycache__/sqlite_store.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/presentation/__pycache__/__init__.cpython-313.pyc b/libs/agent_framework/src/agent_framework/presentation/__pycache__/__init__.cpython-313.pyc index 220e3feb..1d12793c 100644 Binary files a/libs/agent_framework/src/agent_framework/presentation/__pycache__/__init__.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/presentation/__pycache__/__init__.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/presentation/__pycache__/renderers.cpython-313.pyc b/libs/agent_framework/src/agent_framework/presentation/__pycache__/renderers.cpython-313.pyc index 0e257d59..4e45ca54 100644 Binary files a/libs/agent_framework/src/agent_framework/presentation/__pycache__/renderers.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/presentation/__pycache__/renderers.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/rag/__pycache__/__init__.cpython-313.pyc b/libs/agent_framework/src/agent_framework/rag/__pycache__/__init__.cpython-313.pyc index eeabd1b8..a7bfaea1 100644 Binary files a/libs/agent_framework/src/agent_framework/rag/__pycache__/__init__.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/rag/__pycache__/__init__.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/rag/__pycache__/embedding_provider.cpython-313.pyc b/libs/agent_framework/src/agent_framework/rag/__pycache__/embedding_provider.cpython-313.pyc index bf4541b2..6c0fc9fa 100644 Binary files a/libs/agent_framework/src/agent_framework/rag/__pycache__/embedding_provider.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/rag/__pycache__/embedding_provider.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/rag/__pycache__/graph_store.cpython-313.pyc b/libs/agent_framework/src/agent_framework/rag/__pycache__/graph_store.cpython-313.pyc index 4b40d3cb..3467122c 100644 Binary files a/libs/agent_framework/src/agent_framework/rag/__pycache__/graph_store.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/rag/__pycache__/graph_store.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/rag/__pycache__/ingest.cpython-313.pyc b/libs/agent_framework/src/agent_framework/rag/__pycache__/ingest.cpython-313.pyc index f43963f6..1bcaf79f 100644 Binary files a/libs/agent_framework/src/agent_framework/rag/__pycache__/ingest.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/rag/__pycache__/ingest.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/rag/__pycache__/kbdb_service.cpython-313.pyc b/libs/agent_framework/src/agent_framework/rag/__pycache__/kbdb_service.cpython-313.pyc index 1c3214e2..6bee9b98 100644 Binary files a/libs/agent_framework/src/agent_framework/rag/__pycache__/kbdb_service.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/rag/__pycache__/kbdb_service.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/rag/__pycache__/rag_service.cpython-313.pyc b/libs/agent_framework/src/agent_framework/rag/__pycache__/rag_service.cpython-313.pyc index ac74e8a0..625161e4 100644 Binary files a/libs/agent_framework/src/agent_framework/rag/__pycache__/rag_service.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/rag/__pycache__/rag_service.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/rag/__pycache__/vector_store.cpython-313.pyc b/libs/agent_framework/src/agent_framework/rag/__pycache__/vector_store.cpython-313.pyc index 2775a7a0..760553de 100644 Binary files a/libs/agent_framework/src/agent_framework/rag/__pycache__/vector_store.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/rag/__pycache__/vector_store.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/repositories/__pycache__/__init__.cpython-313.pyc b/libs/agent_framework/src/agent_framework/repositories/__pycache__/__init__.cpython-313.pyc index 557f93ac..02ec67b7 100644 Binary files a/libs/agent_framework/src/agent_framework/repositories/__pycache__/__init__.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/repositories/__pycache__/__init__.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/repositories/__pycache__/session_repository.cpython-313.pyc b/libs/agent_framework/src/agent_framework/repositories/__pycache__/session_repository.cpython-313.pyc index 90f2737d..44d7a8e9 100644 Binary files a/libs/agent_framework/src/agent_framework/repositories/__pycache__/session_repository.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/repositories/__pycache__/session_repository.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/routing/__pycache__/__init__.cpython-313.pyc b/libs/agent_framework/src/agent_framework/routing/__pycache__/__init__.cpython-313.pyc index d1878255..76635cf0 100644 Binary files a/libs/agent_framework/src/agent_framework/routing/__pycache__/__init__.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/routing/__pycache__/__init__.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/routing/__pycache__/config_loader.cpython-313.pyc b/libs/agent_framework/src/agent_framework/routing/__pycache__/config_loader.cpython-313.pyc index 5dc21fb9..843d10e4 100644 Binary files a/libs/agent_framework/src/agent_framework/routing/__pycache__/config_loader.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/routing/__pycache__/config_loader.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/routing/__pycache__/continuity.cpython-313.pyc b/libs/agent_framework/src/agent_framework/routing/__pycache__/continuity.cpython-313.pyc index 170e4f41..8fcdd196 100644 Binary files a/libs/agent_framework/src/agent_framework/routing/__pycache__/continuity.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/routing/__pycache__/continuity.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/routing/__pycache__/enterprise_router.cpython-313.pyc b/libs/agent_framework/src/agent_framework/routing/__pycache__/enterprise_router.cpython-313.pyc index 9c23d1ae..b76f277e 100644 Binary files a/libs/agent_framework/src/agent_framework/routing/__pycache__/enterprise_router.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/routing/__pycache__/enterprise_router.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/routing/__pycache__/models.cpython-313.pyc b/libs/agent_framework/src/agent_framework/routing/__pycache__/models.cpython-313.pyc index 369446f1..d2da5c79 100644 Binary files a/libs/agent_framework/src/agent_framework/routing/__pycache__/models.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/routing/__pycache__/models.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/routing/enterprise_router.py b/libs/agent_framework/src/agent_framework/routing/enterprise_router.py index e622918f..ff6e5d5e 100644 --- a/libs/agent_framework/src/agent_framework/routing/enterprise_router.py +++ b/libs/agent_framework/src/agent_framework/routing/enterprise_router.py @@ -880,12 +880,13 @@ class EnterpriseRouter: text: str, state_decision: RouteDecision, ) -> RouteDecision | None: - """Detecta semanticamente mudança de intenção durante uma transação. + """Detecta abandono explícito da transação seguido (ou não) de nova intenção. - Não existe lista de palavras para desistência ou mudança de assunto. Uma - interrupção nasce de uma intent diferente resolvida por uma keyword - configurada no ``routing.yaml`` ou, na ausência dela, por uma decisão - semântica do LLM com o contexto da transação pendente. + Durante COLLECTING_PARAMETERS/AWAITING_CONFIRMATION, uma classificação + genérica de outra intent nunca basta para interromper a transação. A saída + só é permitida quando o turno expressa semanticamente abandono explícito + do objetivo atual (por exemplo, desistir/deixar para lá) ou handoff tratado + por sua regra própria. Não há blacklist/keyword hardcoded de abandono. """ active_tx = state.get("active_transaction") if isinstance(state.get("active_transaction"), dict) else {} started_intent = str(active_tx.get("started_from_intent") or "").strip() @@ -910,15 +911,10 @@ class EnterpriseRouter: # both failure modes: parameter extraction cannot hide a real new goal, # and a broad keyword cannot steal a legitimate parameter turn. if not (self.enable_llm_router and self.llm is not None): - configured_candidate.metadata = { - **(configured_candidate.metadata or {}), - "transaction_interruption": "intent_shift", - "interrupted_state": state_decision.next_state, - "interrupted_agent": state_decision.agent, - "interrupted_intent": started_intent or previous_intent, - "interruption_source": "configured_routing", - } - return configured_candidate + # Sem classificador semântico não há evidência suficiente para + # distinguir uma nova meta explícita de uma expressão pertencente + # à própria transação (ex.: "isso mesmo, pode cancelar"). + return None if not (self.enable_llm_router and self.llm is not None): return None @@ -951,11 +947,11 @@ class EnterpriseRouter: ), } system = ( - "Você decide apenas se o turno atual continua a transação ativa ou muda de intenção. " - "Use o significado da mensagem e o contexto transacional; não use palavras isoladas como regra. " - "A extração dos parâmetros pendentes já foi tentada antes desta etapa e não consumiu o turno. " - "Se ainda assim a mensagem for apenas uma resposta referencial/valor/nome ao dado pendente, retorne CONTINUE. " - "Se o usuário passou claramente a perseguir outro objetivo, retorne SHIFT e a nova intent permitida. " + "Você decide somente se o usuário ABANDONA EXPLICITAMENTE a transação ativa. " + "Uma nova intent semanticamente plausível NÃO basta. Use o significado completo da mensagem e o contexto transacional; nunca use uma palavra isolada como regra. " + "Retorne CONTINUE quando a fala puder ser confirmação, correção, parâmetro, referência ou instrução ligada à operação ativa, inclusive expressões como 'pode cancelar' quando cancelar pode ser a própria ação em andamento. " + "Retorne ABANDON somente quando houver evidência explícita de desistência/abandono da operação atual; se a mesma fala também trouxer um novo objetivo, informe a nova intent/agent permitidos. " + "Exemplo semântico: 'esquece isso, quero ver minha fatura' = ABANDON + nova intent. " "Retorne somente JSON válido com decision, intent, agent, confidence, reason." ) user = { @@ -981,7 +977,7 @@ class EnterpriseRouter: logger.warning("Falha ao avaliar mudança semântica de intent transacional via LLM: %s", exc) return None - if str(data.get("decision") or "").strip().upper() != "SHIFT": + if str(data.get("decision") or "").strip().upper() != "ABANDON": return None confidence = float(data.get("confidence") or 0.0) if confidence < self.intent_shift_threshold: @@ -1002,11 +998,11 @@ class EnterpriseRouter: reason=str(data.get("reason") or "Mudança semântica de intenção durante transação."), method="llm", metadata={ - "transaction_interruption": "intent_shift", + "transaction_interruption": "explicit_abandonment", "interrupted_state": state_decision.next_state, "interrupted_agent": state_decision.agent, "interrupted_intent": started_intent or previous_intent, - "interruption_source": "semantic_classifier", + "interruption_source": "semantic_abandonment_classifier", "configured_routing_hint": ( configured_candidate.intent if configured_candidate is not None else None ), diff --git a/libs/agent_framework/src/agent_framework/runtime/__pycache__/__init__.cpython-313.pyc b/libs/agent_framework/src/agent_framework/runtime/__pycache__/__init__.cpython-313.pyc index 53122589..9b34ab59 100644 Binary files a/libs/agent_framework/src/agent_framework/runtime/__pycache__/__init__.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/runtime/__pycache__/__init__.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/runtime/__pycache__/agent_runtime.cpython-313.pyc b/libs/agent_framework/src/agent_framework/runtime/__pycache__/agent_runtime.cpython-313.pyc index 9603442b..c27cb0da 100644 Binary files a/libs/agent_framework/src/agent_framework/runtime/__pycache__/agent_runtime.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/runtime/__pycache__/agent_runtime.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/runtime/__pycache__/transaction_parameters.cpython-313.pyc b/libs/agent_framework/src/agent_framework/runtime/__pycache__/transaction_parameters.cpython-313.pyc index b797d4f4..27ac2787 100644 Binary files a/libs/agent_framework/src/agent_framework/runtime/__pycache__/transaction_parameters.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/runtime/__pycache__/transaction_parameters.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/runtime/agent_runtime.py b/libs/agent_framework/src/agent_framework/runtime/agent_runtime.py index aeaabeca..4fa4428f 100644 --- a/libs/agent_framework/src/agent_framework/runtime/agent_runtime.py +++ b/libs/agent_framework/src/agent_framework/runtime/agent_runtime.py @@ -12,7 +12,7 @@ from typing import Any, Iterable, Mapping from agent_framework.memory.summary_memory import MemoryContext, render_recent_messages -from agent_framework.runtime.transaction_parameters import extract_transaction_parameters, reconcile_transaction_parameters, parse_transaction_confirmation +from agent_framework.runtime.transaction_parameters import extract_current_transaction_parameters, extract_transaction_parameters, reconcile_transaction_parameters, parse_transaction_confirmation from agent_framework.workflows.input_contract import match_expected_input @@ -859,6 +859,17 @@ class AgentRuntimeMixin: if parameter: recovered_arguments = dict(arguments or {}) recovered_arguments.pop(parameter, None) + # Domain validators may declare derived fields that became invalid + # together with the rejected parameter. The framework does not + # interpret field names; it simply honors the generic clear_fields + # contract. This prevents stale derived values from contaminating + # later collection turns. + validator_clear_fields = [ + str(name) for name in (payload.get("clear_fields") or []) + if str(name).strip() + ] + for field in validator_clear_fields: + recovered_arguments.pop(field, None) recovered_policy = self._resolve_tool_execution_policy(tool_name, recovered_arguments) missing = self._missing_required_arguments(recovered_policy, recovered_arguments) if parameter not in missing: @@ -877,6 +888,7 @@ class AgentRuntimeMixin: "status": status, "parameter": parameter, "terminal": False, + "validator_clear_fields": validator_clear_fields, "result": result, } # A recoverable validator may provide a user-facing explanation @@ -1497,6 +1509,100 @@ class AgentRuntimeMixin: newest = list(reversed(blocks)) return "\n".join(f"history:{i+1}: {block}" for i, block in enumerate(newest)) + @staticmethod + def _transaction_context_priority_view(context: Any) -> str: + """Group bounded history by reconciliation priority without losing recency. + + The temporal reconciler is a fallback, so current-turn values and validated + transaction state are supplied separately. Within historical conversation, + assistant explanations are intentionally considered before older user turns: + they frequently carry grounded relationships produced from tool/evidence + results (for example item -> amount). They remain interpretive candidates + only and must still pass the configured authoritative pre-validation. + """ + ordered = AgentRuntimeMixin._transaction_context_newest_first(context) + if not ordered: + return "" + assistant: list[str] = [] + user: list[str] = [] + other: list[str] = [] + for line in ordered.splitlines(): + payload = line.split(": ", 1)[1] if ": " in line else line + low = payload.lower().lstrip() + if low.startswith(("assistant:", "agente:")): + assistant.append(line) + elif low.startswith(("user:", "cliente:")): + user.append(line) + else: + other.append(line) + + sections: list[str] = [] + if assistant: + sections.append( + "priority_3_previous_assistant_tool_or_evidence_context:\n" + "\n".join(assistant) + ) + if user: + sections.append("priority_4_previous_user_utterances:\n" + "\n".join(user)) + if other: + sections.append("priority_5_other_conversational_context:\n" + "\n".join(other)) + return "\n".join(sections) + + @staticmethod + def _apply_transaction_reconciliation( + arguments: dict[str, Any], + reconciliation: dict[str, Any] | None, + *, + protected_current_fields: set[str] | None = None, + ) -> tuple[dict[str, Any], list[str]]: + """Apply temporal reconciliation without erasing current-turn candidates. + + Current-turn extraction is the first source of truth for *what the user + just said*. Reconciliation may canonicalize that candidate, and business + pre-validation may reject it, but temporal history must not make it + disappear before the validator sees it. + """ + out = dict(arguments or {}) + rec = dict(reconciliation or {}) + protected = {str(name) for name in (protected_current_fields or set())} + ignored_clears: list[str] = [] + for field in rec.get("clear_fields") or []: + name = str(field) + if name in protected: + ignored_clears.append(name) + continue + out.pop(name, None) + # Canonicalized values are still allowed; this is not a hard field lock. + out.update(dict(rec.get("values") or {})) + return out, ignored_clears + + @staticmethod + def _apply_prevalidated_structural_candidates( + arguments: dict[str, Any], + reconciliation: dict[str, Any] | None, + policy: dict[str, Any] | None, + ) -> tuple[dict[str, Any], dict[str, str]]: + """Apply structural *candidates* only behind authoritative pre-validation. + + Candidates are not resolved parameters. They may be raw local evidence + fragments and exist solely so a configured domain validator can + canonicalize/reject them. Without enabled pre-validation they are ignored. + """ + out = dict(arguments or {}) + cfg = (policy or {}).get("pre_validation") if isinstance(policy, dict) else None + if not isinstance(cfg, dict) or not cfg.get("enabled") or not str(cfg.get("tool") or "").strip(): + return out, {} + rec = dict(reconciliation or {}) + candidates = dict(rec.get("candidates") or {}) + provenance = dict(rec.get("candidate_provenance") or {}) + applied: dict[str, str] = {} + for field, value in candidates.items(): + name = str(field) + if out.get(name) not in _EMPTY_VALUES or value in _EMPTY_VALUES: + continue + out[name] = value + applied[name] = str(provenance.get(name) or "structural_candidate") + return out, applied + async def _reconcile_transaction_parameters( self, state: dict[str, Any], @@ -1543,7 +1649,7 @@ class AgentRuntimeMixin: suffix = "\n\nCONTINUAÇÃO ATUAL DO CLIENTE:\n" if prefix in effective and suffix in effective: conversational_context = effective.split(prefix, 1)[1].split(suffix, 1)[0].strip() - ordered_context = self._transaction_context_newest_first(conversational_context) + ordered_context = self._transaction_context_priority_view(conversational_context) cached = route_meta.get("transaction_parameter_values") effective_known = dict(known_arguments or {}) if isinstance(cached, dict): @@ -1602,7 +1708,7 @@ class AgentRuntimeMixin: route_meta = ((state.get("route_decision") or {}).get("metadata") or {}) if isinstance(state.get("route_decision"), dict) else {} text = route_meta.get("original_input") if route_meta.get("contextual_reentry") else None text = text or state.get("sanitized_input") or state.get("user_text") or "" - return await extract_transaction_parameters( + return await extract_current_transaction_parameters( getattr(self, "llm", None), text=str(text), tool_name=tool_name, @@ -1610,7 +1716,6 @@ class AgentRuntimeMixin: known_arguments=dict(known_arguments or {}), parameter_schema=schema, tool_description=description, - conversational_context="", ) async def _extract_transaction_parameters( @@ -2969,12 +3074,12 @@ class AgentRuntimeMixin: # intent, a transação anterior é encerrada e seus latches são limpos. active_before_interruption = self._active_transaction(state) interruption = str(route_meta.get("transaction_interruption") or "").strip().lower() - if active_before_interruption and interruption == "intent_shift": + if active_before_interruption and interruption == "explicit_abandonment": interrupted_tool = active_before_interruption.get("tool_name") self._finish_active_transaction(state, "CANCELLED") state["transaction_pre_validation"] = None state["tool_policy_result"] = { - "action": "cancelled_by_intent_shift", + "action": "cancelled_by_explicit_abandonment", "tool_name": interrupted_tool, } @@ -3008,21 +3113,59 @@ class AgentRuntimeMixin: policy = self._resolve_tool_execution_policy(tool_name, previous_args) missing_before = self._missing_required_arguments(policy, previous_args) - # Primeira tentativa: coleta tradicional usando SOMENTE a fala - # atual. O histórico não deve participar enquanto a mensagem corrente - # conseguir preencher o contrato normalmente. + # O EnterpriseRouter pode já ter consumido parâmetros pendentes + # neste MESMO turno para garantir precedência da transação sobre + # intent shift. Esses valores não são apenas telemetria: precisam + # ser materializados no estado transacional antes de qualquer nova + # extração. Eles continuam sendo candidatos e passam normalmente + # pela pre-validation autoritativa antes de confirmação/execução. arguments = dict(previous_args) + cached_route_values: dict[str, Any] = {} + cached = route_meta.get("transaction_parameter_values") + missing_before_set = {str(name) for name in missing_before} + if isinstance(cached, dict): + cached_route_values = { + str(key): value + for key, value in cached.items() + if str(key) in missing_before_set and value not in _EMPTY_VALUES + } + arguments.update(cached_route_values) + + # A fala atual pode tanto preencher um campo faltante quanto corrigir + # explicitamente um campo já coletado. Por isso o extrator current-only + # examina TODOS os parâmetros requeridos, nunca apenas missing_parameters. + # Ele continua estritamente local à mensagem atual e só devolve campos + # explicitamente presentes; portanto não compete com a reconciliação + # temporal e não apaga estado quando o usuário não mencionou o campo. + required_fields = [str(name) for name in (policy.get("requires") or [])] + # Legacy/domain overrides historically receive only pending fields. + # Keep that extension contract intact; the base framework extractor + # is the one allowed to inspect every required field so an explicit + # current-turn correction can replace an already collected value. + legacy_override = getattr(type(self), "_extract_transaction_parameters", None) + has_legacy_override = ( + legacy_override is not None + and legacy_override is not AgentRuntimeMixin._extract_transaction_parameters + ) + current_fields = ( + self._missing_required_arguments(policy, arguments) + if has_legacy_override else required_fields + ) extracted_current = await self._extract_transaction_parameters_current_only( state, tool_name=tool_name, - missing_parameters=missing_before, - known_arguments=previous_args, + missing_parameters=current_fields, + known_arguments=arguments, ) arguments.update(dict(extracted_current or {})) state["transaction_parameter_collection"] = { "tool_name": tool_name, "mode": "current_turn", - "resolved_fields": sorted(str(k) for k in (extracted_current or {}).keys()), + "router_resolved_fields": sorted(cached_route_values.keys()), + "current_turn_resolved_fields": sorted(str(k) for k in (extracted_current or {}).keys()), + "resolved_fields": sorted( + set(cached_route_values.keys()) | {str(k) for k in (extracted_current or {}).keys()} + ), } # Segunda opção: somente se a coleta tradicional ainda deixar @@ -3038,15 +3181,23 @@ class AgentRuntimeMixin: parameter_names=required_fields, known_arguments=arguments, ) - for field in reconciliation.get("clear_fields") or []: - arguments.pop(str(field), None) - arguments.update(dict(reconciliation.get("values") or {})) + protected_current_fields = { + str(k) + for k, v in dict(extracted_current or {}).items() + if v not in _EMPTY_VALUES + } + arguments, ignored_clears = self._apply_transaction_reconciliation( + arguments, reconciliation, protected_current_fields=protected_current_fields + ) state["transaction_parameter_reconciliation"] = { "tool_name": tool_name, "trigger": "traditional_collection_unresolved", "decisions": dict(reconciliation.get("decisions") or {}), "provenance": dict(reconciliation.get("provenance") or {}), "clear_fields": list(reconciliation.get("clear_fields") or []), + "values": dict(reconciliation.get("values") or {}), + "protected_current_fields": sorted(protected_current_fields), + "ignored_clear_fields": sorted(ignored_clears), } else: state.pop("transaction_parameter_reconciliation", None) @@ -3071,6 +3222,12 @@ class AgentRuntimeMixin: tool_name, arguments, state, exclude_fields=policy.get("requires") or [] ) policy = self._resolve_tool_execution_policy(tool_name, arguments) + arguments, applied_candidates = self._apply_prevalidated_structural_candidates( + arguments, locals().get("reconciliation"), policy + ) + if applied_candidates: + state.setdefault("transaction_parameter_reconciliation", {})["applied_structural_candidates"] = applied_candidates + policy = self._resolve_tool_execution_policy(tool_name, arguments) missing = self._missing_required_arguments(policy, arguments) if missing: self._set_collecting_parameters( @@ -3321,21 +3478,53 @@ class AgentRuntimeMixin: policy = self._resolve_tool_execution_policy(selected_action, action_args) required = [str(name) for name in (policy.get("requires") or [])] - # Valores já estruturados no contexto podem satisfazer requirements sem - # parsing textual. Para qualquer required field ainda ausente, a fala do - # usuário é interpretada exclusivamente pelo extrator LLM transacional. + # Primeiro turno transacional segue a mesma regra de COLLECTING_PARAMETERS: + # 1) coleta tradicional SOMENTE da fala atual; 2) reconciliação temporal + # apenas como fallback se ainda houver required fields faltando. + # Isso evita que histórico altere o happy path e, ao mesmo tempo, permite + # resolver referências contextuais quando a fala atual é incompleta. missing_initial = self._missing_required_arguments(policy, action_args) - # No primeiro turno, a fala atual pode fornecer/corrigir qualquer required - # field, inclusive um valor que exista no contexto estruturado mas pertença - # a uma transação anterior. O extrator continua restrito ao contrato - # ``requires`` e só sobrescreve quando a LLM realmente extrai um valor. - extracted_initial = await self._extract_transaction_parameters( + extracted_initial = await self._extract_transaction_parameters_current_only( state, tool_name=selected_action, missing_parameters=required, known_arguments={k: v for k, v in action_args.items() if k not in set(required)}, ) action_args.update(extracted_initial) + state["transaction_parameter_collection"] = { + "tool_name": selected_action, + "mode": "current_turn", + "resolved_fields": sorted(str(k) for k in (extracted_initial or {}).keys()), + } + + missing_after_current = self._missing_required_arguments(policy, action_args) + if missing_after_current: + reconciliation = await self._reconcile_transaction_parameters( + state, + tool_name=selected_action, + parameter_names=required, + known_arguments=action_args, + ) + protected_current_fields = { + str(k) + for k, v in dict(extracted_initial or {}).items() + if str(k) in set(missing_initial) and v not in _EMPTY_VALUES + } + action_args, ignored_clears = self._apply_transaction_reconciliation( + action_args, reconciliation, protected_current_fields=protected_current_fields + ) + state["transaction_parameter_reconciliation"] = { + "tool_name": selected_action, + "trigger": "initial_traditional_collection_unresolved", + "decisions": dict(reconciliation.get("decisions") or {}), + "provenance": dict(reconciliation.get("provenance") or {}), + "clear_fields": list(reconciliation.get("clear_fields") or []), + "values": dict(reconciliation.get("values") or {}), + "protected_current_fields": sorted(protected_current_fields), + "ignored_clear_fields": sorted(ignored_clears), + } + else: + state.pop("transaction_parameter_reconciliation", None) # O mapper MCP continua responsável somente por parâmetros auxiliares que # não pertencem ao contrato transacional. @@ -3354,6 +3543,18 @@ class AgentRuntimeMixin: ) state["tool_policy_result"] = {**policy, "tool_name": selected_action} + action_args, applied_candidates = self._apply_prevalidated_structural_candidates( + action_args, locals().get("reconciliation"), policy + ) + if applied_candidates: + state.setdefault("transaction_parameter_reconciliation", {})["applied_structural_candidates"] = applied_candidates + selected = {"tool_name": selected_action, "arguments": action_args} + state["selected_tool_call"] = selected + self._set_active_transaction( + state, tool_name=selected_action, arguments=action_args, status="COLLECTING_PARAMETERS" + ) + policy = self._resolve_tool_execution_policy(selected_action, action_args) + missing = self._missing_required_arguments(policy, action_args) if missing: self._set_collecting_parameters( diff --git a/libs/agent_framework/src/agent_framework/runtime/transaction_parameters.py b/libs/agent_framework/src/agent_framework/runtime/transaction_parameters.py index 88cfbeb6..d6c2687f 100644 --- a/libs/agent_framework/src/agent_framework/runtime/transaction_parameters.py +++ b/libs/agent_framework/src/agent_framework/runtime/transaction_parameters.py @@ -69,6 +69,81 @@ def parse_transaction_confirmation(text: str) -> str | None: return None +async def extract_current_transaction_parameters( + llm: Any, + *, + text: str, + tool_name: str, + missing_parameters: list[str], + known_arguments: Mapping[str, Any] | None = None, + parameter_schema: Mapping[str, Any] | None = None, + tool_description: str | None = None, +) -> dict[str, Any]: + """Extract only values explicitly present in the current user utterance. + + This is deliberately narrower than temporal reconciliation: no history, no + cross-turn inference and no routing. Spoken numeric expressions are normalized + according to the declared numeric type, while textual fields must satisfy the + schema description in the current message itself. + """ + names = [str(x) for x in (missing_parameters or []) if str(x).strip()] + message = str(text or "").strip() + if not names or not message or llm is None: + return {} + schema = dict(parameter_schema or {}) + field_spec = { + name: { + "type": schema.get(name, "string") if not isinstance(schema.get(name), dict) else schema.get(name, {}).get("type", "string"), + "description": None if not isinstance(schema.get(name), dict) else schema.get(name, {}).get("description"), + } + for name in names + } + output = {name: None for name in names} + prompt = ( + "Extraia SOMENTE parâmetros explicitamente expressos na mensagem atual do usuário. " + "Não use histórico, não complete referências elípticas com conhecimento anterior e não invente valores. " + "Use exclusivamente type/description do schema para decidir se um trecho preenche um campo. " + "Para campos numéricos, normalize também números falados por extenso para número (por exemplo uma expressão monetária falada deve virar number). " + "Para campos textuais que exigem uma entidade concreta, uma referência apenas por valor/posição não preenche o campo. " + "Retorne SOMENTE JSON válido com exatamente as chaves pedidas; use null quando ausente ou ambíguo.\n" + f"tool: {tool_name}\n" + f"tool_description: {tool_description or ''}\n" + f"known_arguments: {json.dumps(dict(known_arguments or {}), ensure_ascii=False, default=str)}\n" + f"parameter_schema: {json.dumps(field_spec, ensure_ascii=False, default=str)}\n" + f"current_user_message: {message}\n" + f"Formato: {json.dumps(output, ensure_ascii=False)}" + ) + try: + response = await llm.ainvoke( + [{"role": "user", "content": prompt}], + profile_name="transaction_parameter_extraction", + component_name="transaction_parameter_extraction", + generation_name="llm.transaction_parameter_current_only", + temperature=0.0, + ) + except TypeError: + response = await llm.ainvoke([{"role": "user", "content": prompt}]) + except Exception as exc: + logger.warning("transaction.parameter.current_extract_failed tool=%s fields=%s error=%s", tool_name, names, exc) + return {} + raw = _response_text(response).strip() + try: + payload = parse_json_object(raw) + except (TypeError, ValueError): + logger.warning("transaction.parameter.current_extract_invalid tool=%s raw=%r", tool_name, raw[:240]) + return {} + if not isinstance(payload, dict): + return {} + values: dict[str, Any] = {} + for name in names: + value = payload.get(name) + declared = field_spec.get(name, {}).get("type", "string") + coerced = _coerce(value, declared) + if coerced not in _EMPTY_VALUES: + values[name] = coerced + return values + + async def reconcile_transaction_parameters( llm: Any, *, @@ -113,33 +188,217 @@ async def reconcile_transaction_parameters( for name in names } } + + def _anchor_tokens(value: Any) -> list[str]: + """Return generic textual variants for a known scalar anchor.""" + if value in _EMPTY_VALUES: + return [] + raw = str(value).strip().lower() + tokens = {raw} + try: + number = float(str(value).replace(",", ".")) + tokens.add(f"{number:.2f}") + tokens.add(f"{number:.2f}".replace(".", ",")) + if number.is_integer(): + tokens.add(str(int(number))) + except (TypeError, ValueError): + pass + return [token for token in tokens if token] + + def _anchor_relation_candidates(context: str, arguments: Mapping[str, Any]) -> list[str]: + """Surface history fragments related to any already-known parameter. + + This is not a second reconciliation pass and does not resolve anything + deterministically. It only structures the SAME temporal scan so the LLM + can use known/current fields as cross-field anchors while reconciling all + pending fields coherently in one call. + """ + if not context or not arguments: + return [] + # Keep role/history prefixes but split long assistant explanations into + # smaller semantic clauses (bullets/semicolons) so one anchor is not lost + # among many unrelated entity/value pairs. + fragments: list[str] = [] + for raw_line in context.splitlines(): + line = raw_line.strip() + if not line: + continue + parts = [part.strip() for part in re.split(r"\s*\*\s*|\s*;\s*", line) if part.strip()] + fragments.extend(parts or [line]) + + candidates: list[str] = [] + for field_name, field_value in arguments.items(): + variants = _anchor_tokens(field_value) + if not variants: + continue + for fragment in fragments: + low = fragment.lower() + if any(token in low for token in variants): + candidates.append(f"anchor[{field_name}={field_value}]: {fragment}") + return list(dict.fromkeys(candidates)) + + def _structured_evidence_blocks(context: str, arguments: Mapping[str, Any]) -> list[dict[str, Any]]: + """Represent the same bounded history as explicit message evidence blocks. + + The reconciler remains a single LLM call. This helper only prevents role, + recency and anchor relationships from being hidden inside one prose blob. + It is fully domain-neutral: field meaning still comes from parameter_schema. + """ + blocks: list[dict[str, Any]] = [] + current_role = "other" + current_lines: list[str] = [] + rank = 0 + + def flush() -> None: + nonlocal rank, current_lines, current_role + if not current_lines: + return + text_block = " ".join(x.strip() for x in current_lines if x.strip()).strip() + current_lines = [] + if not text_block: + return + + # A single assistant message often contains a list of independent + # entity/attribute relations (for example several invoice items). + # Keeping the whole answer as one evidence block makes an otherwise + # exact anchor ambiguous because every entity in the list competes + # inside the same block. Segment LOCAL relations while preserving + # the original role, priority and message recency. This is still the + # same temporal reconciliation and the same single LLM call. + fragments = [ + frag.strip(" \t-•") + for frag in re.split( + r"\s+(?:\*|•)\s+|\s*;\s+|\s+(?=\d+[.)]\s+)", + text_block, + ) + if frag.strip(" \t-•") + ] + if not fragments: + fragments = [text_block] + + rank += 1 + role_priority = 3 if current_role == "assistant" else 4 if current_role == "user" else 5 + for fragment_index, fragment in enumerate(fragments, start=1): + anchor_matches: list[dict[str, Any]] = [] + low = fragment.lower() + for field_name, field_value in arguments.items(): + variants = _anchor_tokens(field_value) + matched = [token for token in variants if token in low] + if matched: + anchor_matches.append({ + "field": str(field_name), + "value": field_value, + "matched_tokens": matched, + }) + blocks.append({ + "priority": role_priority, + "recency": rank, + "fragment": fragment_index, + "role": current_role, + "text": fragment, + "anchor_matches": anchor_matches, + }) + + for raw_line in str(context or "").splitlines(): + line = raw_line.strip() + if not line: + continue + low = line.lower() + # Priority-view section headers are metadata, not conversational text. + if low.startswith("priority_3_previous_assistant"): + flush(); current_role = "assistant"; continue + if low.startswith("priority_4_previous_user"): + flush(); current_role = "user"; continue + if low.startswith("priority_5_other"): + flush(); current_role = "other"; continue + payload = line.split(": ", 1)[1] if line.startswith("history:") and ": " in line else line + plow = payload.lower().lstrip() + detected = None + if plow.startswith(("assistant:", "agente:")): + detected = "assistant" + elif plow.startswith(("user:", "cliente:")): + detected = "user" + elif plow.startswith("system:"): + detected = "other" + if detected is not None: + flush() + current_role = detected + payload = payload.split(":", 1)[1].strip() if ":" in payload else payload + current_lines.append(payload) + flush() + return blocks + + context_text = str(conversational_context or "").strip() + anchor_relations = _anchor_relation_candidates(context_text, known) + evidence_blocks = _structured_evidence_blocks(context_text, known) + missing_initial = [name for name in names if name not in known] + + # Field-targeted views do not resolve parameters and do not add another LLM + # pass. They expose the SAME local evidence blocks next to the declarative + # semantics of each still-missing field so the single temporal reconciliation + # call can correlate anchors with the correct schema-defined target instead of + # rediscovering that mapping inside a large generic JSON blob. No field names + # or domain concepts are hardcoded here. + anchored_blocks = [block for block in evidence_blocks if block.get("anchor_matches")] + field_evidence_views = { + name: { + "field_spec": field_spec.get(name, {}), + "anchored_evidence_blocks": anchored_blocks, + } + for name in missing_initial + } + + reconciliation_focus = { + "current_user_message": message, + "known_parameters": known, + "missing_parameters": missing_initial, + "missing_field_views": field_evidence_views, + } + prompt = ( + "FOCO PRINCIPAL DA RECONCILIAÇÃO (examine isto antes de qualquer contexto secundário):\n" + f"reconciliation_focus: {json.dumps(reconciliation_focus, ensure_ascii=False, default=str)}\n" + "Se um campo faltante tiver um único evidence_block ancorado semanticamente compatível com seu field_spec, resolva-o; use unresolved somente em ambiguidade real.\n\n" "Você é o conciliador temporal de parâmetros de uma transação ativa. " - "Reconstrua UM CONJUNTO COERENTE de parâmetros; não extraia cada campo de forma isolada. " + "Faça UMA ÚNICA VARREDURA TEMPORAL e reconstrua UM CONJUNTO COERENTE de parâmetros. " + "Todos os campos conhecidos e faltantes devem ser avaliados juntos; não execute uma busca isolada por campo e não faça uma segunda etapa de fallback. " "Não decida roteamento, confirmação nem execução.\n\n" "CONTRATO OBRIGATÓRIO:\n" - "1. O significado de cada campo vem EXCLUSIVAMENTE de parameter_schema e transaction_description, considerando principalmente a descrição semântica quando disponível. O framework não conhece conceitos de domínio por nome de campo.\n" - "2. A ausência de tipo ou descrição NÃO impede a extração quando o restante do contrato e o texto forem semanticamente suficientes.\n" - "3. A busca textual é temporal, em ordem decrescente: user_message é a fonte mais nova; depois conversational_context já vem do texto mais novo para o mais antigo.\n" - "3. Textos do usuário e textos explicativos do assistente podem ser usados para interpretar referências e relações entre campos. Não trate texto do contexto como uma nova afirmação do cliente; eles são contexto, não evidência autoritativa de negócio.\n" - "4. Para cada campo escolha: resolved = um texto determina novo valor; preserve = nenhum texto mais novo invalida o valor conhecido; clear = existe mudança textual mais nova que torna o valor conhecido incompatível/sem vínculo seguro com o novo contexto e ainda não há substituto; unresolved = não há valor conhecido nem candidato textual seguro.\n" - "5. Se uma fonte mais nova muda uma entidade, objeto, escopo ou outro contexto que dava sentido a campos extraídos anteriormente, REAVALIE os demais campos como conjunto. Não combine automaticamente um atributo antigo com um contexto novo só porque ambos existem.\n" - "6. Um valor de texto anterior pode continuar válido após uma mudança somente quando os textos, considerados em conjunto, sustentarem de forma inequívoca que ele ainda pertence ao contexto mais recente. Caso contrário use clear para o campo dependente.\n" - "7. Se a nova menção é inválida no mundo real, NÃO volte silenciosamente ao valor antigo. Ainda assim devolva o candidato textual mais recente como resolved; a pre-validation autoritativa é responsável por rejeitá-lo.\n" - "8. known_arguments é fallback de estado. Use preserve somente quando nenhum texto mais novo o contradiz ou rompe sua associação contextual.\n" - "9. Uma expressão só pode preencher um campo se satisfizer semanticamente type/description desse campo. Não transfira um trecho para outro campo apenas por proximidade lexical.\n" - "10. Se um texto recente corrige explicitamente informação anterior, a correção prevalece para os campos que o schema permite inferir; os demais devem ser reavaliados quanto à coerência com a correção.\n" - "11. Em ambiguidade razoável, prefira clear/unresolved a inventar uma associação. Em caso de dúvida razoável sobre a correspondência ou o valor, prefira null.\n" - "12. Responda SOMENTE JSON válido no formato pedido, sem markdown e sem chaves extras.\n\n" + "1. O significado de cada campo vem EXCLUSIVAMENTE de parameter_schema e transaction_description, principalmente a descrição semântica quando disponível. O framework não conhece conceitos de domínio por nome de campo.\n" + "2. A ausência de tipo ou descrição NÃO impede a extração nem a resolução quando o restante do contrato e o contexto forem semanticamente suficientes.\n" + "3. A resolução deve obedecer ESTA PRIORIDADE, sem inverter níveis: (1) informação explicitamente fornecida pelo usuário na mensagem atual e semanticamente compatível com o schema; (2) parâmetros já conhecidos/validados da transação; (3) respostas anteriores do assistente que apresentem relações locais entre parâmetros/atributos e que possam ter sido apoiadas por tool/evidence; (4) falas anteriores do usuário; (5) demais contexto conversacional.\n" + "4. Dentro de cada nível histórico, preserve a ordem temporal decrescente (mais novo -> mais antigo). Nunca escolha fonte de prioridade menor quando uma fonte de prioridade maior resolve o mesmo campo de forma coerente.\n" + "5. known_arguments contém o estado parcial já coletado/validado. Trate CADA parâmetro conhecido, independentemente de nome ou tipo, como possível ÂNCORA para resolver QUALQUER outro campo faltante no histórico. O significado e a compatibilidade de cada âncora vêm exclusivamente do parameter_schema. Use múltiplas âncoras simultaneamente quando disponíveis.\n" + "6. anchor_relation_candidates são apenas recortes do MESMO histórico que contêm representações de parâmetros conhecidos; eles servem para facilitar a correlação entre campos dentro desta única varredura. Não são uma segunda fonte, não têm prioridade própria e não autorizam inventar relações.\n" + "7. Quando uma âncora conhecida aparecer em um mesmo contexto local com uma ou mais informações semanticamente compatíveis com campos faltantes, use essa relação local para reconciliar TODOS os campos faltantes que puderem ser resolvidos de forma coerente na mesma resposta. Não pressuponha quais tipos de campo devem coexistir; siga apenas o schema e o contexto.\n" + "8. Texto anterior do assistente é CONTEXTO INTERPRETATIVO, não evidência autoritativa: pode fornecer relações locais entre quaisquer parâmetros/atributos definidos pelo schema, mas todo candidato continua sujeito à pre-validation/tool de negócio. Não descarte uma relação apenas por estar na fala do assistente. Não trate texto do contexto como uma nova afirmação do cliente.\n" + "9. Para cada campo escolha: resolved = texto/contexto determina novo valor; preserve = o valor conhecido continua coerente; clear = contexto mais novo rompe sua associação e não há substituto seguro; unresolved = não há candidato seguro.\n" + "10. Se uma fonte mais nova muda o contexto semântico que dava sentido a campos anteriores, REAVALIE os demais campos como conjunto. Não combine automaticamente informação antiga com contexto novo.\n" + "11. Se a nova menção é inválida no mundo real, NÃO volte silenciosamente ao valor antigo. Devolva o candidato textual mais recente como resolved e deixe a pre-validation autoritativa rejeitá-lo.\n" + "12. Uma expressão só pode preencher um campo se satisfizer semanticamente type/description desse campo. Não transfira um trecho para outro campo apenas por proximidade lexical.\n" + "13. Se o usuário corrige explicitamente informação anterior, a correção prevalece nos campos compatíveis e os demais devem ser reavaliados quanto à coerência.\n" + "14. unresolved é ÚLTIMO RECURSO. Antes de devolver unresolved para um campo faltante, você DEVE verificar todos os evidence_blocks na ordem de prioridade e recência e todas as anchor_relation_candidates. Se existir uma única interpretação semanticamente compatível com type/description do campo, resolva-a.\n" + "15. Se um parâmetro conhecido aparecer como anchor_match em um evidence_block e o MESMO bloco contiver uma única informação semanticamente compatível com um campo faltante segundo o schema, use a relação para resolved desse campo; não devolva unresolved apenas porque a informação foi apresentada pelo assistente.\n" + "16. Se não houver âncora útil, mas uma fonte histórica de prioridade 3 ou 4 contiver explicitamente uma informação que satisfaça semanticamente um campo faltante segundo o schema e não houver candidato concorrente de prioridade maior, resolva esse campo. Isso cobre referências posteriores em que o usuário omite um atributo porque ele já estava explícito no diálogo.\n" + "17. field_evidence_views reorganiza os MESMOS evidence_blocks ancorados por cada campo ainda faltante e coloca ao lado a semântica declarativa desse campo. Para cada campo faltante, examine obrigatoriamente essa visão antes de unresolved. Se um bloco ancorado contiver uma única informação compatível com field_spec e não houver concorrente de prioridade/recência maior, retorne resolved. Essa visão não é uma nova fonte nem uma segunda etapa.\n" + "18. Em ambiguidade REAL — dois ou mais candidatos semanticamente plausíveis sem desempate por prioridade, recência ou âncora — prefira clear/unresolved a inventar uma associação. Em caso de dúvida razoável sobre a correspondência ou o valor, prefira null/unresolved conforme o formato de saída.\n" + "19. Responda SOMENTE JSON válido no formato pedido, sem markdown e sem chaves extras.\n\n" f"transaction_tool: {tool_name}\n" f"transaction_description: {tool_description or ''}\n" f"parameter_names: {json.dumps(names, ensure_ascii=False)}\n" + f"known_parameters: {json.dumps(known, ensure_ascii=False, default=str)}\n" + f"missing_parameters: {json.dumps(missing_initial, ensure_ascii=False)}\n" f"pending_parameters: {json.dumps(names, ensure_ascii=False)}\n" f"parameter_schema: {json.dumps(field_spec, ensure_ascii=False, default=str)}\n" - f"known_arguments: {json.dumps(known, ensure_ascii=False, default=str)}\n" + f"anchor_relation_candidates: {json.dumps(anchor_relations, ensure_ascii=False, default=str)}\n" + f"evidence_blocks: {json.dumps(evidence_blocks, ensure_ascii=False, default=str)}\n" + f"field_evidence_views: {json.dumps(field_evidence_views, ensure_ascii=False, default=str)}\n" "conversation_sources_newest_to_oldest:\n" + f"priority_1_current_user_message: {message}\n" f"user_message: {message}\n" - f"conversational_context: {str(conversational_context or '').strip()}\n" + f"prioritized_conversational_context: {context_text}\n" + "RELEMBRE O FOCO PRINCIPAL: examine reconciliation_focus.missing_field_views antes de unresolved. " + "Um bloco ancorado único e semanticamente compatível deve produzir resolved, independentemente do papel assistant/user, respeitada a prioridade declarada.\n" f"Formato obrigatório: {json.dumps(output_shape, ensure_ascii=False)}" ) @@ -212,11 +471,49 @@ async def reconcile_transaction_parameters( else: decisions[name] = "unresolved" + # Structural candidates are intentionally NOT resolved values. They are only + # fallback material for a configured authoritative pre-validator. This lets + # the runtime carry a unique local evidence fragment (or, when no anchor + # exists, a single prior user utterance) to domain validation without letting + # the framework invent/canonicalize business entities. + structural_candidates: dict[str, Any] = {} + candidate_provenance: dict[str, str] = {} + # For a field that was already missing before reconciliation, ``clear`` has + # no distinct state effect: there is no prior value to invalidate. Treat it + # as unresolved *only for structural-candidate fallback* so a unique anchored + # history fragment can still be offered to the configured authoritative + # pre-validator. This does not promote the fragment to a resolved value and + # does not change ``clear_fields`` semantics for fields that were previously + # populated. + unresolved_missing = [ + name for name in missing_initial + if decisions.get(name) in {"unresolved", "clear"} and name not in values + ] + anchored = [b for b in evidence_blocks if b.get("anchor_matches") and str(b.get("text") or "").strip()] + prior_user = [ + b for b in evidence_blocks + if int(b.get("priority") or 99) == 4 and str(b.get("text") or "").strip() + ] + for name in unresolved_missing: + declared = str(field_spec.get(name, {}).get("type") or "string").strip().lower() + if declared not in {"string", "str", "text"}: + continue + if len(anchored) == 1: + structural_candidates[name] = str(anchored[0].get("text") or "").strip() + candidate_provenance[name] = "unique_anchored_evidence_block" + logger.info( - "transaction.parameter.reconciled tool=%s decisions=%s resolved=%s clear=%s provenance=%s", - tool_name, decisions, sorted(values), clear_fields, provenance, + "transaction.parameter.reconciled tool=%s decisions=%s resolved=%s clear=%s provenance=%s candidates=%s", + tool_name, decisions, sorted(values), clear_fields, provenance, sorted(structural_candidates), ) - return {"values": values, "decisions": decisions, "provenance": provenance, "clear_fields": clear_fields} + return { + "values": values, + "decisions": decisions, + "provenance": provenance, + "clear_fields": clear_fields, + "candidates": structural_candidates, + "candidate_provenance": candidate_provenance, + } async def extract_transaction_parameters( diff --git a/libs/agent_framework/src/agent_framework/sse/__pycache__/__init__.cpython-313.pyc b/libs/agent_framework/src/agent_framework/sse/__pycache__/__init__.cpython-313.pyc index c4a831ab..79f8d737 100644 Binary files a/libs/agent_framework/src/agent_framework/sse/__pycache__/__init__.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/sse/__pycache__/__init__.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/sse/__pycache__/events.cpython-313.pyc b/libs/agent_framework/src/agent_framework/sse/__pycache__/events.cpython-313.pyc index ff084315..02876344 100644 Binary files a/libs/agent_framework/src/agent_framework/sse/__pycache__/events.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/sse/__pycache__/events.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/supervisor/__pycache__/__init__.cpython-313.pyc b/libs/agent_framework/src/agent_framework/supervisor/__pycache__/__init__.cpython-313.pyc index a1defb9b..1259a446 100644 Binary files a/libs/agent_framework/src/agent_framework/supervisor/__pycache__/__init__.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/supervisor/__pycache__/__init__.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/supervisor/__pycache__/supervisor.cpython-313.pyc b/libs/agent_framework/src/agent_framework/supervisor/__pycache__/supervisor.cpython-313.pyc index 08a3aab2..3d6ec917 100644 Binary files a/libs/agent_framework/src/agent_framework/supervisor/__pycache__/supervisor.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/supervisor/__pycache__/supervisor.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/workflows/__pycache__/__init__.cpython-313.pyc b/libs/agent_framework/src/agent_framework/workflows/__pycache__/__init__.cpython-313.pyc index 0bd6d734..6a0c10f0 100644 Binary files a/libs/agent_framework/src/agent_framework/workflows/__pycache__/__init__.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/workflows/__pycache__/__init__.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/workflows/__pycache__/graph.cpython-313.pyc b/libs/agent_framework/src/agent_framework/workflows/__pycache__/graph.cpython-313.pyc index 3a3c2690..9131a481 100644 Binary files a/libs/agent_framework/src/agent_framework/workflows/__pycache__/graph.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/workflows/__pycache__/graph.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/workflows/__pycache__/input_contract.cpython-313.pyc b/libs/agent_framework/src/agent_framework/workflows/__pycache__/input_contract.cpython-313.pyc index 4ca4c0b7..7fdc6116 100644 Binary files a/libs/agent_framework/src/agent_framework/workflows/__pycache__/input_contract.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/workflows/__pycache__/input_contract.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/workflows/__pycache__/models.cpython-313.pyc b/libs/agent_framework/src/agent_framework/workflows/__pycache__/models.cpython-313.pyc index 730f0f64..106dbd5d 100644 Binary files a/libs/agent_framework/src/agent_framework/workflows/__pycache__/models.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/workflows/__pycache__/models.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/workflows/__pycache__/registry.cpython-313.pyc b/libs/agent_framework/src/agent_framework/workflows/__pycache__/registry.cpython-313.pyc index 93500be2..becb3666 100644 Binary files a/libs/agent_framework/src/agent_framework/workflows/__pycache__/registry.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/workflows/__pycache__/registry.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/workflows/__pycache__/repository.cpython-313.pyc b/libs/agent_framework/src/agent_framework/workflows/__pycache__/repository.cpython-313.pyc index eee267b7..6c1facfa 100644 Binary files a/libs/agent_framework/src/agent_framework/workflows/__pycache__/repository.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/workflows/__pycache__/repository.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/workflows/__pycache__/runtime.cpython-313.pyc b/libs/agent_framework/src/agent_framework/workflows/__pycache__/runtime.cpython-313.pyc index 011d24d9..62d17ee1 100644 Binary files a/libs/agent_framework/src/agent_framework/workflows/__pycache__/runtime.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/workflows/__pycache__/runtime.cpython-313.pyc differ diff --git a/libs/agent_framework/src/agent_framework/workflows/__pycache__/tool_executor.cpython-313.pyc b/libs/agent_framework/src/agent_framework/workflows/__pycache__/tool_executor.cpython-313.pyc index b80ecdc2..b3aa8244 100644 Binary files a/libs/agent_framework/src/agent_framework/workflows/__pycache__/tool_executor.cpython-313.pyc and b/libs/agent_framework/src/agent_framework/workflows/__pycache__/tool_executor.cpython-313.pyc differ diff --git a/tests/__pycache__/conftest.cpython-313-pytest-9.0.2.pyc b/tests/__pycache__/conftest.cpython-313-pytest-9.0.2.pyc index 16740551..9f26c74c 100644 Binary files a/tests/__pycache__/conftest.cpython-313-pytest-9.0.2.pyc and b/tests/__pycache__/conftest.cpython-313-pytest-9.0.2.pyc differ diff --git a/tests/__pycache__/test_contextual_reentry_transaction_parameters.cpython-313-pytest-9.0.2.pyc b/tests/__pycache__/test_contextual_reentry_transaction_parameters.cpython-313-pytest-9.0.2.pyc index 9ba6e6be..87d39ac0 100644 Binary files a/tests/__pycache__/test_contextual_reentry_transaction_parameters.cpython-313-pytest-9.0.2.pyc and b/tests/__pycache__/test_contextual_reentry_transaction_parameters.cpython-313-pytest-9.0.2.pyc differ diff --git a/tests/__pycache__/test_route_stickiness_transaction_shift.cpython-313-pytest-9.0.2.pyc b/tests/__pycache__/test_route_stickiness_transaction_shift.cpython-313-pytest-9.0.2.pyc index d6ff637f..f1635a55 100644 Binary files a/tests/__pycache__/test_route_stickiness_transaction_shift.cpython-313-pytest-9.0.2.pyc and b/tests/__pycache__/test_route_stickiness_transaction_shift.cpython-313-pytest-9.0.2.pyc differ diff --git a/tests/__pycache__/test_transaction_explicit_abandonment_and_structural_candidate.cpython-313-pytest-9.0.2.pyc b/tests/__pycache__/test_transaction_explicit_abandonment_and_structural_candidate.cpython-313-pytest-9.0.2.pyc new file mode 100644 index 00000000..ec3b3223 Binary files /dev/null and b/tests/__pycache__/test_transaction_explicit_abandonment_and_structural_candidate.cpython-313-pytest-9.0.2.pyc differ diff --git a/tests/__pycache__/test_transaction_explicit_abandonment_and_structural_candidate.cpython-313.pyc b/tests/__pycache__/test_transaction_explicit_abandonment_and_structural_candidate.cpython-313.pyc new file mode 100644 index 00000000..4636b040 Binary files /dev/null and b/tests/__pycache__/test_transaction_explicit_abandonment_and_structural_candidate.cpython-313.pyc differ diff --git a/tests/__pycache__/test_transaction_parameter_descriptions.cpython-313-pytest-9.0.2.pyc b/tests/__pycache__/test_transaction_parameter_descriptions.cpython-313-pytest-9.0.2.pyc index 95126f52..44c4613f 100644 Binary files a/tests/__pycache__/test_transaction_parameter_descriptions.cpython-313-pytest-9.0.2.pyc and b/tests/__pycache__/test_transaction_parameter_descriptions.cpython-313-pytest-9.0.2.pyc differ diff --git a/tests/__pycache__/test_transaction_parameter_llm_precedence.cpython-313-pytest-9.0.2.pyc b/tests/__pycache__/test_transaction_parameter_llm_precedence.cpython-313-pytest-9.0.2.pyc index 0d7b692e..d728688f 100644 Binary files a/tests/__pycache__/test_transaction_parameter_llm_precedence.cpython-313-pytest-9.0.2.pyc and b/tests/__pycache__/test_transaction_parameter_llm_precedence.cpython-313-pytest-9.0.2.pyc differ diff --git a/tests/__pycache__/test_transaction_state_regression_matrix.cpython-313-pytest-9.0.2.pyc b/tests/__pycache__/test_transaction_state_regression_matrix.cpython-313-pytest-9.0.2.pyc index d3d04a81..61bf31ba 100644 Binary files a/tests/__pycache__/test_transaction_state_regression_matrix.cpython-313-pytest-9.0.2.pyc and b/tests/__pycache__/test_transaction_state_regression_matrix.cpython-313-pytest-9.0.2.pyc differ diff --git a/tests/__pycache__/test_transaction_state_router_interruption.cpython-313-pytest-9.0.2.pyc b/tests/__pycache__/test_transaction_state_router_interruption.cpython-313-pytest-9.0.2.pyc index 66fcb9d3..5be25124 100644 Binary files a/tests/__pycache__/test_transaction_state_router_interruption.cpython-313-pytest-9.0.2.pyc and b/tests/__pycache__/test_transaction_state_router_interruption.cpython-313-pytest-9.0.2.pyc differ diff --git a/tests/__pycache__/test_transactional_tool_flow.cpython-313-pytest-9.0.2.pyc b/tests/__pycache__/test_transactional_tool_flow.cpython-313-pytest-9.0.2.pyc index 92ba4080..155772f3 100644 Binary files a/tests/__pycache__/test_transactional_tool_flow.cpython-313-pytest-9.0.2.pyc and b/tests/__pycache__/test_transactional_tool_flow.cpython-313-pytest-9.0.2.pyc differ diff --git a/tests/test_contextual_reentry_transaction_parameters.py b/tests/test_contextual_reentry_transaction_parameters.py index 46a7b3bd..bd2877f6 100644 --- a/tests/test_contextual_reentry_transaction_parameters.py +++ b/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: @@ -42,3 +42,370 @@ async def test_contextual_reentry_separates_current_claim_from_prior_context_for assert "Cobrança Tamboro Mensal" in llm.prompt assert "user_message: é a de quatorze e noventa e nove" in llm.prompt assert "Não trate texto do contexto como uma nova afirmação do cliente" in llm.prompt + +class _TwoPassContextLLM: + def __init__(self): + self.calls = [] + + async def ainvoke(self, messages, **kwargs): + prompt = messages[-1]["content"] + self.calls.append(prompt) + # First pass: current utterance provides only the amount. + if "user_message: é a de vinte e cinco e cinquenta" in prompt: + return json.dumps({"subject": None, "valor": 25.50}, ensure_ascii=False) + # Second bounded pass: previous USER utterance provides the missing entity. + if "user_message: não reconheço esse Tamboro Mensal na minha fatura" in prompt: + return json.dumps({"subject": "Tamboro Mensal"}, ensure_ascii=False) + return "{}" + + +@pytest.mark.asyncio +async def test_contextual_reentry_second_pass_recovers_only_missing_candidate_from_prior_user_turn(): + from agent_framework.runtime.agent_runtime import AgentRuntimeMixin + + class Runtime(AgentRuntimeMixin): + pass + + runtime = Runtime() + runtime.llm = _TwoPassContextLLM() + state = { + "route_decision": { + "metadata": { + "contextual_reentry": True, + "original_input": "é a de vinte e cinco e cinquenta", + "relevant_conversation_context": ( + "user: não reconheço esse Tamboro Mensal na minha fatura\n" + "assistant: identifiquei duas cobranças de Tamboro Mensal" + ), + } + }, + "sanitized_input": "é a de vinte e cinco e cinquenta", + } + out = await runtime._extract_transaction_parameters( + state=state, + tool_name="contestar_cobranca", + missing_parameters=["subject", "valor"], + known_arguments={}, + ) + assert float(out["valor"]) == 25.5 + assert out["subject"] == "Tamboro Mensal" + assert len(runtime.llm.calls) >= 2 + +class _CoherentTemporalLLM: + """Simulates the structured decisions expected from temporal reconciliation.""" + def __init__(self): + self.prompts = [] + + async def ainvoke(self, messages, **kwargs): + prompt = messages[-1]["content"] + self.prompts.append(prompt) + low = prompt.lower() + # Product A + value X established. + if "user_message: na verdade é tim fashion mensal" in low: + return json.dumps({"fields": { + "subject": {"decision": "resolved", "value": "TIM Fashion Mensal", "source": "current"}, + # Previous 14.99 was tied to the former product and must not be transplanted. + "valor": {"decision": "clear", "value": None, "source": "history:1"}, + }}, ensure_ascii=False) + if "user_message: na verdade é produto inexistente" in low: + return json.dumps({"fields": { + # Keep the newest candidate so authoritative validation can reject it. + "subject": {"decision": "resolved", "value": "Produto Inexistente", "source": "current"}, + "valor": {"decision": "clear", "value": None, "source": "history:1"}, + }}, ensure_ascii=False) + if "user_message: desculpa, era tamboro mensal mesmo" in low: + return json.dumps({"fields": { + "subject": {"decision": "resolved", "value": "Tamboro Mensal", "source": "current"}, + # Full temporal text makes the old amount coherent again with the restored product. + "valor": {"decision": "resolved", "value": 14.99, "source": "history:2"}, + }}, ensure_ascii=False) + return json.dumps({"fields": { + "subject": {"decision": "preserve", "value": None, "source": "state"}, + "valor": {"decision": "preserve", "value": None, "source": "state"}, + }}, ensure_ascii=False) + + +@pytest.mark.asyncio +async def test_temporal_reconciliation_product_change_rechecks_old_value_as_part_of_coherent_set(): + from agent_framework.runtime.transaction_parameters import reconcile_transaction_parameters + + llm = _CoherentTemporalLLM() + out = await reconcile_transaction_parameters( + llm, + text="na verdade é TIM Fashion Mensal", + conversational_context=( + "history:1: user: é R$ 14,99\n" + "history:2: user: quero contestar Tamboro Mensal" + ), + tool_name="contestar_cobranca", + parameter_names=["subject", "valor"], + known_arguments={"subject": "Tamboro Mensal", "valor": 14.99}, + parameter_schema={ + "subject": {"type": "string", "description": "Nome do serviço, produto, item ou cobrança objeto da contestação."}, + "valor": {"type": "number", "description": "Valor monetário associado ao item objeto da contestação."}, + }, + tool_description="Contesta uma cobrança após validação.", + ) + assert out["values"] == {"subject": "TIM Fashion Mensal"} + assert out["clear_fields"] == ["valor"] + assert out["provenance"]["subject"] == "current" + + +@pytest.mark.asyncio +async def test_temporal_reconciliation_invalid_new_product_does_not_erase_history_or_fall_back_silently(): + from agent_framework.runtime.transaction_parameters import reconcile_transaction_parameters + + llm = _CoherentTemporalLLM() + history = ( + "history:1: user: é R$ 14,99\n" + "history:2: user: quero contestar Tamboro Mensal" + ) + out = await reconcile_transaction_parameters( + llm, + text="na verdade é Produto Inexistente", + conversational_context=history, + tool_name="contestar_cobranca", + parameter_names=["subject", "valor"], + known_arguments={"subject": "Tamboro Mensal", "valor": 14.99}, + parameter_schema={ + "subject": {"type": "string", "description": "Nome do serviço, produto, item ou cobrança objeto da contestação."}, + "valor": {"type": "number", "description": "Valor monetário associado ao item objeto da contestação."}, + }, + tool_description="Contesta uma cobrança após validação.", + ) + # The newest candidate stays visible for pre-validation; the old product is not silently restored. + assert out["values"] == {"subject": "Produto Inexistente"} + assert "valor" in out["clear_fields"] + assert "Tamboro Mensal" in llm.prompts[-1] # history remains available to future reconciliation. + + +@pytest.mark.asyncio +async def test_temporal_reconciliation_can_restore_coherent_old_value_when_product_is_explicitly_restored(): + from agent_framework.runtime.transaction_parameters import reconcile_transaction_parameters + + llm = _CoherentTemporalLLM() + out = await reconcile_transaction_parameters( + llm, + text="desculpa, era Tamboro Mensal mesmo", + conversational_context=( + "history:1: user: na verdade é Produto Inexistente\n" + "history:2: user: é R$ 14,99\n" + "history:3: user: quero contestar Tamboro Mensal" + ), + tool_name="contestar_cobranca", + parameter_names=["subject", "valor"], + known_arguments={}, + parameter_schema={ + "subject": {"type": "string", "description": "Nome do serviço, produto, item ou cobrança objeto da contestação."}, + "valor": {"type": "number", "description": "Valor monetário associado ao item objeto da contestação."}, + }, + tool_description="Contesta uma cobrança após validação.", + ) + assert out["values"] == {"subject": "Tamboro Mensal", "valor": 14.99} + assert out["provenance"] == {"subject": "current", "valor": "history:2"} + + +def test_temporal_context_priority_places_assistant_before_older_user_history(): + from agent_framework.runtime.agent_runtime import AgentRuntimeMixin + + context = ( + "user: tem uma cobrança aqui que eu não reconheço\n" + "assistant: Cobrança Tamboro Mensal no valor de R$ 14,99; TIM Fashion Mensal no valor de R$ 10,00." + ) + prioritized = AgentRuntimeMixin._transaction_context_priority_view(context) + + assert "priority_3_previous_assistant_tool_or_evidence_context:" in prioritized + assert "priority_4_previous_user_utterances:" in prioritized + assert prioritized.index("assistant: Cobrança Tamboro Mensal") < prioritized.index("user: tem uma cobrança") + + +class _AssistantRelationshipReconcilerLLM: + def __init__(self): + self.prompt = "" + + async def ainvoke(self, messages, **kwargs): + self.prompt = messages[-1]["content"] + # Simula o comportamento desejado: a fala atual dá o valor, enquanto a + # relação item->valor está numa resposta anterior do assistant. + assert "user_message: é a de quatorze e noventa e nove" in self.prompt + assert "priority_3_previous_assistant_tool_or_evidence_context:" in self.prompt + assert "assistant: Cobrança Tamboro Mensal no valor de R$ 14,99" in self.prompt + assert "anchor_relation_candidates:" in self.prompt + assert "anchor[valor=14.99]" in self.prompt + return json.dumps({"fields": { + "subject": {"decision": "resolved", "value": "Tamboro Mensal", "source": "history:1"}, + "valor": {"decision": "resolved", "value": 14.99, "source": "current"}, + }}, ensure_ascii=False) + + +@pytest.mark.asyncio +async def test_temporal_reconciler_can_use_grounded_assistant_relationship_after_current_only_extraction_is_incomplete(): + from agent_framework.runtime.agent_runtime import AgentRuntimeMixin + + class Runtime(AgentRuntimeMixin): + pass + + runtime = Runtime() + runtime.llm = _AssistantRelationshipReconcilerLLM() + runtime.tool_router = type("TR", (), { + "registry": type("REG", (), { + "get_tool": staticmethod(lambda name: type("CFG", (), { + "requires": ["subject", "valor"], + "args_schema": { + "subject": {"type": "string", "description": "Nome do item ou cobrança a contestar."}, + "valor": {"type": "number", "description": "Valor monetário do item a contestar."}, + }, + "description": "Contesta uma cobrança após validação.", + })()) + })(), + "resolve_execution_policy": staticmethod(lambda name, arguments=None: { + "operation_type": "transactional", + "requires": ["subject", "valor"], + }), + })() + + state = { + "sanitized_input": "é a de quatorze e noventa e nove", + "user_text": "é a de quatorze e noventa e nove", + "route_decision": {"metadata": { + "contextual_reentry": True, + "original_input": "é a de quatorze e noventa e nove", + "relevant_conversation_context": ( + "user: tem uma cobrança aqui que eu não reconheço\n" + "assistant: Cobrança Tamboro Mensal no valor de R$ 14,99; TIM Fashion Mensal no valor de R$ 10,00." + ), + }}, + } + + # O happy path corrente só consegue o valor; o subject fica faltando. + class _CurrentOnlyLLM: + async def ainvoke(self, messages, **kwargs): + return json.dumps({"valor": 14.99}, ensure_ascii=False) + + original_llm = runtime.llm + runtime.llm = _CurrentOnlyLLM() + current = await runtime._extract_transaction_parameters_current_only( + state, + tool_name="contestar_cobranca", + missing_parameters=["subject", "valor"], + known_arguments={}, + ) + assert current == {"valor": 14.99} + + runtime.llm = original_llm + reconciled = await runtime._reconcile_transaction_parameters( + state, + tool_name="contestar_cobranca", + parameter_names=["subject", "valor"], + known_arguments=current, + ) + assert reconciled["values"]["subject"] == "Tamboro Mensal" + assert float(reconciled["values"]["valor"]) == 14.99 + assert "(3) respostas anteriores do assistente" in runtime.llm.prompt + +class _UnifiedAnchorScanLLM: + def __init__(self): + self.calls = [] + + async def ainvoke(self, messages, **kwargs): + prompt = messages[-1]["content"] + self.calls.append(prompt) + assert "UMA ÚNICA VARREDURA TEMPORAL" in prompt + assert "known_parameters: {\"valor\": 14.99}" in prompt + assert "missing_parameters: [\"subject\", \"data\"]" in prompt + assert "anchor_relation_candidates:" in prompt + assert "anchor[valor=14.99]" in prompt + assert "Tamboro Mensal no valor de R$ 14,99" in prompt + return json.dumps({"fields": { + "subject": {"decision": "resolved", "value": "Tamboro Mensal", "source": "history:1"}, + "valor": {"decision": "preserve", "value": None, "source": "state"}, + "data": {"decision": "resolved", "value": "01/11/2025", "source": "history:1"}, + }}, ensure_ascii=False) + + +@pytest.mark.asyncio +async def test_temporal_reconciler_single_scan_uses_all_known_fields_as_anchors_and_resolves_all_missing_fields(): + from agent_framework.runtime.transaction_parameters import reconcile_transaction_parameters + + llm = _UnifiedAnchorScanLLM() + result = await reconcile_transaction_parameters( + llm, + text="é a de quatorze e noventa e nove", + tool_name="contestar_cobranca", + parameter_names=["subject", "valor", "data"], + 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."}, + "data": {"type": "string", "description": "Data da cobrança selecionada."}, + }, + tool_description="Contesta uma cobrança após validação.", + conversational_context=( + "priority_3_previous_assistant_tool_or_evidence_context:\n" + "history:1: assistant: Cobrança Tamboro Mensal no valor de R$ 14,99 no dia 01/11/2025 * " + "TIM Fashion Mensal no valor de R$ 10,00 no dia 01/11/2025.\n" + "priority_4_previous_user_utterances:\n" + "history:2: user: tem uma cobrança aqui que eu não reconheço" + ), + ) + + assert len(llm.calls) == 1 + assert result["values"]["subject"] == "Tamboro Mensal" + assert float(result["values"]["valor"]) == 14.99 + 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/tests/test_reconciliation_state_rules_8_16.py b/tests/test_reconciliation_state_rules_8_16.py new file mode 100644 index 00000000..a1832f2d --- /dev/null +++ b/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/tests/test_transaction_explicit_abandonment_and_structural_candidate.py b/tests/test_transaction_explicit_abandonment_and_structural_candidate.py new file mode 100644 index 00000000..259cfad6 --- /dev/null +++ b/tests/test_transaction_explicit_abandonment_and_structural_candidate.py @@ -0,0 +1,293 @@ +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"}}, + }, + } + + +@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/tests/test_transaction_parameter_llm_precedence.py b/tests/test_transaction_parameter_llm_precedence.py index fe2ed05b..ef57780f 100644 --- a/tests/test_transaction_parameter_llm_precedence.py +++ b/tests/test_transaction_parameter_llm_precedence.py @@ -643,3 +643,129 @@ intents: [] decision = await router.route(state) assert decision.metadata["transaction_confirmation_decision"] == "confirm" assert decision.metadata["transaction_confirmation_source"] == "deterministic" + +@pytest.mark.asyncio +async def test_collecting_preserves_resolved_required_fields_and_only_extracts_currently_missing(): + class Runtime(_Runtime): + def __init__(self): + super().__init__() + self.pending_seen = None + + async def _extract_transaction_parameters( + self, state, *, tool_name, missing_parameters, known_arguments=None + ): + self.pending_seen = list(missing_parameters) + # Simulate the value extracted for the only missing field. A resolved + # required field must not be offered back to the semantic extractor. + return {"reason": "desisti da compra"} + + runtime = Runtime() + state = { + "user_text": "desisti da compra", + "sanitized_input": "desisti da compra", + "transaction_status": "COLLECTING_PARAMETERS", + "missing_parameters": ["reason"], + "mcp_tools": ["solicitar_devolucao"], + "active_transaction": { + "tool_name": "solicitar_devolucao", + "arguments": {"order_id": "PED-1001"}, + "status": "COLLECTING_PARAMETERS", + "parameter_schema": {"order_id": "string", "reason": "string"}, + "tool_description": "Abre uma solicitação de devolução de pedido.", + }, + "selected_tool_call": { + "tool_name": "solicitar_devolucao", + "arguments": {"order_id": "PED-1001"}, + }, + "route_decision": {"metadata": {}}, + "route": "support_agent", + "intent": "state:COLLECTING_SUPPORT_PARAMETERS", + } + + result = await runtime.execute_tools_for_intent(state) + assert runtime.pending_seen == ["reason"] + assert state["pending_tool_call"]["arguments"]["order_id"] == "PED-1001" + assert state["pending_tool_call"]["arguments"]["reason"] == "desisti da compra" + assert result[-1]["awaiting_confirmation"] is True + +@pytest.mark.asyncio +async def test_correction_of_missing_numeric_value_does_not_reopen_resolved_text_entity(): + class Runtime(_Runtime): + def __init__(self): + super().__init__() + self.pending_seen = None + + async def _extract_transaction_parameters( + self, state, *, tool_name, missing_parameters, known_arguments=None + ): + self.pending_seen = list(missing_parameters) + # If subject were incorrectly offered again, a semantic extractor + # could reinterpret the monetary phrase as an entity. The runtime + # contract must expose only the missing numeric field here. + out = {"valor": 19.99} + if "subject" in missing_parameters: + out["subject"] = "cobrança de R$ 19,99" + return out + + runtime = Runtime() + state = { + "user_text": "desculpa é a de dezenove e noventa e nove", + "sanitized_input": "desculpa é a de dezenove e noventa e nove", + "transaction_status": "COLLECTING_PARAMETERS", + "missing_parameters": ["valor"], + "mcp_tools": ["solicitar_devolucao"], + "active_transaction": { + "tool_name": "solicitar_devolucao", + "arguments": {"order_id": "PED-1001"}, + "status": "COLLECTING_PARAMETERS", + "parameter_schema": {"order_id": "string", "reason": "string", "valor": "number"}, + }, + "selected_tool_call": { + "tool_name": "solicitar_devolucao", + "arguments": {"order_id": "PED-1001"}, + }, + "route_decision": {"metadata": {}}, + "route": "support_agent", + "intent": "state:COLLECTING_SUPPORT_PARAMETERS", + } + # This synthetic tool doesn't require valor, so exercise the extraction + # contract directly with the same resolved-text/missing-number shape. + out = await runtime._extract_transaction_parameters( + state, + tool_name="solicitar_devolucao", + missing_parameters=["valor"], + known_arguments={"subject": "Tamboro Mensal"}, + ) + 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/tests/test_transactional_tool_flow.py b/tests/test_transactional_tool_flow.py index 48f6b0f7..49b57403 100644 --- a/tests/test_transactional_tool_flow.py +++ b/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)} @@ -1283,3 +1284,152 @@ async def test_prevalidation_parameter_message_is_used_once_without_changing_tra # One-shot: subsequent clarification falls back to the normal prompt. assert runtime.transaction_clarification_message(state) != expected assert "transaction_parameter_message_override" not in state + +class _TemporalFallbackRuntime(AgentRuntimeMixin): + def __init__(self): + from types import SimpleNamespace + self.calls = [] + self.llm = None + self.tool_router = SimpleNamespace( + registry=SimpleNamespace( + get_tool=lambda name: SimpleNamespace( + requires=["subject"], + args_schema={"subject": {"type": "string", "description": "Nome do serviço ou VAS alvo do cancelamento."}}, + description="Cancela o serviço VAS selecionado pelo cliente.", + confirmation_required=True, + tool_type="transactional", + ) + ), + resolve_execution_policy=lambda name, arguments=None: { + "operation_type": "transactional", + "require_confirmation": True, + "requires": ["subject"], + "pre_validation": {"enabled": True, "tool": "validar_vas_subject", "fail_open": False}, + }, + ) + + async def _extract_transaction_parameters_current_only(self, state, *, tool_name, missing_parameters, known_arguments=None): + # Simula o collector tradicional interpretando literalmente a fala atual. + return {"subject": "dezenove e noventa e nove"} + + async def _reconcile_transaction_parameters(self, state, *, tool_name, parameter_names, known_arguments=None): + # Simula o reconciliador temporal usando tools.yaml + contexto newest->oldest. + return { + "values": {"subject": "Tamboro Mensal"}, + "decisions": {"subject": "resolved"}, + "provenance": {"subject": "history:2"}, + "clear_fields": [], + } + + async def _call_mcp_tool(self, tool_name, arguments, state): + self.calls.append((tool_name, dict(arguments))) + if tool_name == "validar_vas_subject": + subject = arguments.get("subject") + if subject == "Tamboro Mensal": + return {"ok": True, "tool_name": tool_name, "result": {"eligible": True, "status": "ELIGIBLE"}} + return {"ok": True, "tool_name": tool_name, "result": { + "eligible": False, + "status": "NEEDS_PARAMETER", + "parameter": "subject", + "reason": "subject_not_resolved", + }} + return {"ok": True, "tool_name": tool_name, "result": {"status": "OK"}} + + +@pytest.mark.asyncio +async def test_temporal_reconciler_is_fallback_after_traditional_candidate_fails_prevalidation(): + runtime = _TemporalFallbackRuntime() + state = { + "user_text": "é a de dezenove e noventa e nove", + "sanitized_input": "é a de dezenove e noventa e nove", + "route": "contestacao_agent", + "intent": "state:COLLECTING_CONTESTACAO_PARAMETERS", + "transaction_status": "COLLECTING_PARAMETERS", + "selected_tool_call": {"tool_name": "cancelar_vas_avulso", "arguments": {}}, + "active_transaction": { + "transaction_id": "tx-1", + "tool_name": "cancelar_vas_avulso", + "arguments": {}, + "status": "COLLECTING_PARAMETERS", + "started_from_intent": "contas_vas_cancel", + "requires": ["subject"], + "parameter_schema": {"subject": {"type": "string", "description": "Nome do serviço ou VAS alvo do cancelamento."}}, + "tool_description": "Cancela o serviço VAS selecionado pelo cliente.", + "parameter_conversational_context": ( + "user: não reconheço esse Tamboro Mensal na minha fatura\n" + "assistant: identifiquei Tamboro Mensal por R$ 14,99 e R$ 19,99" + ), + }, + "context": {}, + } + + result = await runtime.execute_tools_for_intent(state, tools=[]) + + validations = [(name, args.get("subject")) for name, args in runtime.calls if name == "validar_vas_subject"] + assert validations == [ + ("validar_vas_subject", "dezenove e noventa e nove"), + ("validar_vas_subject", "Tamboro Mensal"), + ] + assert state["transaction_parameter_collection"]["mode"] == "current_turn" + assert state["transaction_parameter_reconciliation"]["trigger"] == "prevalidation_needs_parameter" + 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 == []