From 47892afa2ed4df84f7a26f998c97af7b299e8822 Mon Sep 17 00:00:00 2001 From: "cristiano.hoshikawa" Date: Fri, 21 Aug 2026 14:32:49 -0300 Subject: [PATCH] bugfix: fraseologia --- .../__pycache__/__init__.cpython-313.pyc | Bin 427 -> 418 bytes .../__pycache__/idempotency.cpython-313.pyc | Bin 5711 -> 5702 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 563 -> 554 bytes .../composite_publisher.cpython-313.pyc | Bin 2518 -> 2509 bytes .../__pycache__/event_builder.cpython-313.pyc | Bin 1276 -> 1267 bytes .../__pycache__/factory.cpython-313.pyc | Bin 4012 -> 4003 bytes .../__pycache__/publisher.cpython-313.pyc | Bin 1888 -> 1879 bytes .../tim_payload_mapper.cpython-313.pyc | Bin 9302 -> 9293 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 229 -> 220 bytes .../cache/__pycache__/cache.cpython-313.pyc | Bin 16539 -> 16530 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 232 -> 223 bytes .../__pycache__/interruption.cpython-313.pyc | Bin 6472 -> 6463 bytes .../__pycache__/transcription.cpython-313.pyc | Bin 1445 -> 1436 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 939 -> 930 bytes .../checkpoint_repository.cpython-313.pyc | Bin 27179 -> 27170 bytes .../langgraph_saver.cpython-313.pyc | Bin 11260 -> 23552 bytes .../checkpoints/langgraph_saver.py | 292 ++++++++++++- .../__pycache__/__init__.cpython-313.pyc | Bin 230 -> 221 bytes .../__pycache__/settings.cpython-313.pyc | Bin 12585 -> 12576 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 322 -> 313 bytes .../mcp_gateway_client.cpython-313.pyc | Bin 3348 -> 3339 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 1517 -> 1508 bytes .../__pycache__/base.cpython-313.pyc | Bin 1391 -> 1382 bytes .../__pycache__/config_loader.cpython-313.pyc | Bin 7265 -> 7256 bytes .../__pycache__/custom_rails.cpython-313.pyc | Bin 4412 -> 4403 bytes .../framework_llm_client.cpython-313.pyc | Bin 18079 -> 18070 bytes .../__pycache__/llm_rails.cpython-313.pyc | Bin 6658 -> 6649 bytes .../output_supervisor.cpython-313.pyc | Bin 16119 -> 19604 bytes .../parallel_executor.cpython-313.pyc | Bin 19927 -> 19918 bytes .../__pycache__/pipeline.cpython-313.pyc | Bin 10905 -> 10896 bytes .../__pycache__/rail_action.cpython-313.pyc | Bin 670 -> 661 bytes .../__pycache__/rail_decision.cpython-313.pyc | Bin 1621 -> 1612 bytes .../__pycache__/rail_result.cpython-313.pyc | Bin 1020 -> 1011 bytes .../__pycache__/rails.cpython-313.pyc | Bin 31612 -> 33578 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 2978 -> 2969 bytes .../__pycache__/_compat.cpython-313.pyc | Bin 2461 -> 2452 bytes .../contestation_validation.cpython-313.pyc | Bin 22886 -> 22867 bytes .../__pycache__/contracts.cpython-313.pyc | Bin 6905 -> 6896 bytes .../__pycache__/input_size.cpython-313.pyc | Bin 3562 -> 3553 bytes .../__pycache__/llm_adapter.cpython-313.pyc | Bin 3815 -> 3806 bytes .../__pycache__/llm_client.cpython-313.pyc | Bin 7349 -> 7340 bytes .../__pycache__/llm_rails.cpython-313.pyc | Bin 8897 -> 8888 bytes .../output_sanitization.cpython-313.pyc | Bin 13926 -> 13917 bytes .../__pycache__/pipeline.cpython-313.pyc | Bin 12667 -> 12658 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 489 -> 480 bytes .../__pycache__/_context.cpython-313.pyc | Bin 5887 -> 5878 bytes .../ausencia_oferta_proativa.cpython-313.pyc | Bin 9890 -> 9881 bytes .../__pycache__/coerencia.cpython-313.pyc | Bin 9496 -> 9487 bytes .../__pycache__/dlex_in.cpython-313.pyc | Bin 1320 -> 1311 bytes .../__pycache__/dlex_out.cpython-313.pyc | Bin 1607 -> 1598 bytes .../__pycache__/fallback.cpython-313.pyc | Bin 17205 -> 17577 bytes .../__pycache__/fraseologia.cpython-313.pyc | Bin 6068 -> 7598 bytes .../__pycache__/out_of_scope.cpython-313.pyc | Bin 17099 -> 17090 bytes .../prompts/__pycache__/pinj.cpython-313.pyc | Bin 8323 -> 8314 bytes .../__pycache__/ragsec.cpython-313.pyc | Bin 1121 -> 1112 bytes .../__pycache__/revprec.cpython-313.pyc | Bin 5422 -> 5413 bytes .../prompts/__pycache__/tox.cpython-313.pyc | Bin 645 -> 636 bytes .../toxicidade_output.cpython-313.pyc | Bin 939 -> 930 bytes .../guardrails/calibrated/prompts/fallback.py | 7 + .../calibrated/prompts/fraseologia.py | 27 +- .../__pycache__/__init__.cpython-313.pyc | Bin 1683 -> 1674 bytes .../rails/__pycache__/alcada.cpython-313.pyc | Bin 5015 -> 5006 bytes .../rails/__pycache__/anatel.cpython-313.pyc | Bin 9164 -> 9155 bytes .../__pycache__/confirmation.cpython-313.pyc | Bin 11032 -> 11023 bytes .../rails/__pycache__/dlex_in.cpython-313.pyc | Bin 3295 -> 3286 bytes .../__pycache__/dlex_out.cpython-313.pyc | Bin 3347 -> 3338 bytes .../rails/__pycache__/ragsec.cpython-313.pyc | Bin 5429 -> 5420 bytes .../rails/__pycache__/revprec.cpython-313.pyc | Bin 5437 -> 5428 bytes .../rails/__pycache__/tox.cpython-313.pyc | Bin 8252 -> 8243 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 553 -> 544 bytes .../rules/__pycache__/alcada.cpython-313.pyc | Bin 1897 -> 1888 bytes .../__pycache__/pinj_patterns.cpython-313.pyc | Bin 3738 -> 3729 bytes .../__pycache__/tox_blocklist.cpython-313.pyc | Bin 1522 -> 1513 bytes .../guardrails/output_supervisor.py | 71 ++++ .../src/agent_framework/guardrails/rails.py | 44 +- .../__pycache__/__init__.cpython-313.pyc | Bin 424 -> 415 bytes .../__pycache__/mcp_mapper.cpython-313.pyc | Bin 4708 -> 4699 bytes .../__pycache__/models.cpython-313.pyc | Bin 3160 -> 3151 bytes .../__pycache__/resolver.cpython-313.pyc | Bin 4729 -> 4720 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 634 -> 625 bytes .../judges/__pycache__/judge.cpython-313.pyc | Bin 36448 -> 36439 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 241 -> 232 bytes .../__pycache__/llm_client.cpython-313.pyc | Bin 6118 -> 6109 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 249 -> 240 bytes .../prompts/__pycache__/aluc.cpython-313.pyc | Bin 6464 -> 6455 bytes .../prompts/__pycache__/csi.cpython-313.pyc | Bin 1333 -> 1324 bytes .../__pycache__/fallback.cpython-313.pyc | Bin 8029 -> 8020 bytes .../prompts/__pycache__/rqlt.cpython-313.pyc | Bin 1171 -> 1162 bytes .../prompts/__pycache__/vctn.cpython-313.pyc | Bin 642 -> 633 bytes .../mcp/__pycache__/__init__.cpython-313.pyc | Bin 489 -> 480 bytes .../mcp/__pycache__/client.cpython-313.pyc | Bin 18442 -> 18433 bytes .../mcp/__pycache__/models.cpython-313.pyc | Bin 2152 -> 2143 bytes .../mcp/__pycache__/registry.cpython-313.pyc | Bin 4756 -> 4747 bytes .../__pycache__/tool_policy.cpython-313.pyc | Bin 5701 -> 5692 bytes .../__pycache__/tool_router.cpython-313.pyc | Bin 14205 -> 14196 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 1151 -> 1142 bytes .../long_term_extractor.cpython-313.pyc | Bin 2529 -> 2520 bytes .../long_term_memory.cpython-313.pyc | Bin 6855 -> 6846 bytes .../long_term_models.cpython-313.pyc | Bin 1764 -> 1755 bytes .../long_term_store.cpython-313.pyc | Bin 29202 -> 29193 bytes .../message_history.cpython-313.pyc | Bin 8375 -> 8366 bytes .../summary_memory.cpython-313.pyc | Bin 12526 -> 12517 bytes .../__pycache__/summary_store.cpython-313.pyc | Bin 9536 -> 9527 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 230 -> 221 bytes .../__pycache__/session.cpython-313.pyc | Bin 2724 -> 2715 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 1410 -> 1401 bytes .../__pycache__/context.cpython-313.pyc | Bin 7402 -> 7393 bytes .../__pycache__/event_bus.cpython-313.pyc | Bin 3648 -> 3639 bytes .../guardrail_events.cpython-313.pyc | Bin 2066 -> 2057 bytes .../__pycache__/ic_events.cpython-313.pyc | Bin 641 -> 632 bytes .../__pycache__/judge_events.cpython-313.pyc | Bin 1426 -> 1417 bytes .../langgraph_telemetry.cpython-313.pyc | Bin 4979 -> 4970 bytes .../__pycache__/llm_advisors.cpython-313.pyc | Bin 2953 -> 2944 bytes .../__pycache__/noc_contract.cpython-313.pyc | Bin 5090 -> 5081 bytes .../__pycache__/noc_otel.cpython-313.pyc | Bin 6337 -> 6328 bytes .../__pycache__/observer.cpython-313.pyc | Bin 4709 -> 4700 bytes .../__pycache__/otel.cpython-313.pyc | Bin 3427 -> 3418 bytes .../streaming_events.cpython-313.pyc | Bin 1850 -> 1841 bytes .../__pycache__/telemetry.cpython-313.pyc | Bin 51538 -> 51529 bytes .../__pycache__/token_cost.cpython-313.pyc | Bin 8612 -> 8603 bytes .../workflow_events.cpython-313.pyc | Bin 3345 -> 3336 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 235 -> 226 bytes .../__pycache__/sqlite_store.cpython-313.pyc | Bin 15034 -> 15025 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 470 -> 461 bytes .../__pycache__/renderers.cpython-313.pyc | Bin 4333 -> 4324 bytes .../rag/__pycache__/__init__.cpython-313.pyc | Bin 725 -> 716 bytes .../embedding_provider.cpython-313.pyc | Bin 6542 -> 6533 bytes .../__pycache__/graph_store.cpython-313.pyc | Bin 5543 -> 5534 bytes .../rag/__pycache__/ingest.cpython-313.pyc | Bin 12596 -> 12587 bytes .../__pycache__/rag_service.cpython-313.pyc | Bin 10065 -> 10056 bytes .../__pycache__/vector_store.cpython-313.pyc | Bin 18671 -> 18662 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 441 -> 432 bytes .../__pycache__/config_loader.cpython-313.pyc | Bin 2312 -> 2303 bytes .../__pycache__/continuity.cpython-313.pyc | Bin 13254 -> 13245 bytes .../enterprise_router.cpython-313.pyc | Bin 28254 -> 28414 bytes .../__pycache__/models.cpython-313.pyc | Bin 2462 -> 2453 bytes .../routing/enterprise_router.py | 228 +++++----- .../__pycache__/__init__.cpython-313.pyc | Bin 364 -> 355 bytes .../__pycache__/agent_runtime.cpython-313.pyc | Bin 140669 -> 138689 bytes .../agent_framework/runtime/agent_runtime.py | 43 +- .../__pycache__/__init__.cpython-313.pyc | Bin 234 -> 225 bytes .../__pycache__/supervisor.cpython-313.pyc | Bin 4704 -> 4695 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 894 -> 885 bytes .../__pycache__/graph.cpython-313.pyc | Bin 1431 -> 1422 bytes .../__pycache__/models.cpython-313.pyc | Bin 5248 -> 5239 bytes .../__pycache__/registry.cpython-313.pyc | Bin 2377 -> 2368 bytes .../__pycache__/repository.cpython-313.pyc | Bin 2730 -> 2721 bytes .../__pycache__/runtime.cpython-313.pyc | Bin 29143 -> 32987 bytes .../__pycache__/tool_executor.cpython-313.pyc | Bin 1982 -> 1973 bytes .../src/agent_framework/workflows/runtime.py | 86 +++- .../conftest.cpython-313-pytest-9.0.2.pyc | Bin 823 -> 814 bytes ..._continuation.cpython-313-pytest-9.0.2.pyc | Bin 0 -> 8835 bytes ..._revalidation.cpython-313-pytest-9.0.2.pyc | Bin 0 -> 13627 bytes ...saction_shift.cpython-313-pytest-9.0.2.pyc | Bin 25772 -> 25821 bytes ...stomer_facing.cpython-313-pytest-9.0.2.pyc | Bin 4826 -> 4817 bytes ...ession_matrix.cpython-313-pytest-9.0.2.pyc | Bin 0 -> 42213 bytes ..._interruption.cpython-313-pytest-9.0.2.pyc | Bin 21063 -> 28238 bytes ...nal_tool_flow.cpython-313-pytest-9.0.2.pyc | Bin 68131 -> 67893 bytes .../test_aoferta_transaction_continuation.py | 75 ++++ ...t_fraseologia_business_parameter_prompt.py | 23 + .../test_phraseology_rewrite_revalidation.py | 114 +++++ ...test_route_stickiness_transaction_shift.py | 4 +- ...est_transaction_state_regression_matrix.py | 397 ++++++++++++++++++ ...t_transaction_state_router_interruption.py | 77 +++- .../tests/test_transactional_tool_flow.py | 8 +- ...untime_config.cpython-313-pytest-9.0.2.pyc | Bin 0 -> 15875 bytes ...ckpoint_saver.cpython-313-pytest-9.0.2.pyc | Bin 7140 -> 2365 bytes ...e_diagnostics.cpython-313-pytest-9.0.2.pyc | Bin 0 -> 5445 bytes ...est_langgraph_checkpoint_runtime_config.py | 133 +++++- .../unit/test_workflow_runtime_diagnostics.py | 26 ++ app/workflows/agent_graph.py | 28 -- config/guardrails.yaml | 2 +- 172 files changed, 1480 insertions(+), 205 deletions(-) create mode 100644 agent_framework_oci/tests/__pycache__/test_aoferta_transaction_continuation.cpython-313-pytest-9.0.2.pyc create mode 100644 agent_framework_oci/tests/__pycache__/test_phraseology_rewrite_revalidation.cpython-313-pytest-9.0.2.pyc create mode 100644 agent_framework_oci/tests/__pycache__/test_transaction_state_regression_matrix.cpython-313-pytest-9.0.2.pyc create mode 100644 agent_framework_oci/tests/test_aoferta_transaction_continuation.py create mode 100644 agent_framework_oci/tests/test_fraseologia_business_parameter_prompt.py create mode 100644 agent_framework_oci/tests/test_phraseology_rewrite_revalidation.py create mode 100644 agent_framework_oci/tests/test_transaction_state_regression_matrix.py create mode 100644 agent_framework_oci/tests/unit/__pycache__/test_langgraph_checkpoint_runtime_config.cpython-313-pytest-9.0.2.pyc create mode 100644 agent_framework_oci/tests/unit/__pycache__/test_workflow_runtime_diagnostics.cpython-313-pytest-9.0.2.pyc create mode 100644 agent_framework_oci/tests/unit/test_workflow_runtime_diagnostics.py diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/__pycache__/__init__.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/__pycache__/__init__.cpython-313.pyc index 907d1068c384aaadadb3d9ebc888b11ca508e328..cd74785c25f2f2fa79fa9c54e55ebeb4e7f5fc69 100644 GIT binary patch delta 30 lcmZ3@yohD1H2)KLsr delta 41 wcmX@6b6#h|RVJB6{oK3~{glL#ME%6{)Vz}TpF diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/analytics/__pycache__/__init__.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/analytics/__pycache__/__init__.cpython-313.pyc index ae2cffe142bf5c84b2bd11424c729c7286471720..034108a96052fe68b4038d19528491c4bfcdde5c 100644 GIT binary patch delta 31 mcmdnYvWjIxDWh<`er{fgeoA6VqJEj7fnj-mQTF7CjEexR!wV(= delta 40 vcmZ3*vYBN=DWgn>er{fgeoA6VqJCm}YFD1Hrd`)=6CMKE5`nh=}`YDMeiTa7@sd**w$@zIDiN*10nH5Hxk1}zv0{~Y*52^qF diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/analytics/__pycache__/event_builder.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/analytics/__pycache__/event_builder.cpython-313.pyc index 573ff01926542b1888ab76e9cf777befc87c84f3..a5123b3ab4f351e33bf2bf70456f9c67eec8a567 100644 GIT binary patch delta 32 ncmeyv`I&RWXC~nm{oK3~{glL#MEx>D1HD1HOGOaK4? diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/analytics/__pycache__/tim_payload_mapper.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/analytics/__pycache__/tim_payload_mapper.cpython-313.pyc index bfcdc2d5fee3a9636512bc3afe51aa8c45698de8..18404908504555604658ed49708da92ed8b50f11 100644 GIT binary patch delta 32 ncmccSan@r)BcpJqer{fgeoA6VqJEj7fnj-mQTFD!j60P8)Taz@ delta 41 xcmX@>am`~xBcseD{oK3~{glL#ME%6{)Vz}T^&iolJ{D1Hk diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/checkpoints/__pycache__/__init__.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/checkpoints/__pycache__/__init__.cpython-313.pyc index 697a6039510a0a1ce355eafd1618bb9f80640a12..9834e10f74638243e721f86c38a05addee1f96f8 100644 GIT binary patch delta 31 ncmZ3@zKDIpT_)j1{oK3~{glL#MEx>D1H{oK3~{glL#ME%6{)Vz}Tlgp#tnMR!hQO=c_sQOi6x2pWrhZZ<@rU~o4uGHrU3xv`3(X9 delta 43 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 G2K*nQp`Nw? diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/checkpoints/langgraph_saver.py b/agent_framework_oci/libs/agent_framework/src/agent_framework/checkpoints/langgraph_saver.py index f060ccd..468338c 100644 --- a/agent_framework_oci/libs/agent_framework/src/agent_framework/checkpoints/langgraph_saver.py +++ b/agent_framework_oci/libs/agent_framework/src/agent_framework/checkpoints/langgraph_saver.py @@ -25,12 +25,203 @@ from typing import Any, AsyncIterator, Iterator from .checkpoint_repository import create_checkpoint_repository -def _jsonable(value: Any) -> Any: - try: - json.dumps(value, default=str) +def _parse_legacy_json_container(value: Any, expected: type) -> Any: + """Recover containers that older JSON backends persisted as JSON strings. + + This is intentionally field-scoped: ordinary business strings must stay + strings, even if their text happens to look like JSON. + """ + current = value + for _ in range(3): + if isinstance(current, expected): + return current + if not isinstance(current, str): + break + text = current.strip() + if not text: + break + if expected is dict and not text.startswith("{"): + break + if expected is list and not text.startswith("["): + break + try: + current = json.loads(text) + except Exception: + break + return current if isinstance(current, expected) else expected() + + +def _strict_json_value(value: Any, *, path: str = "$") -> Any: + """Convert to repository-safe JSON without ever falling back to ``str``. + + ``default=str`` is unsafe for LangGraph checkpoints: runtime/task objects can + become ordinary strings and later be consumed as typed values by Pregel. + Keep native JSON containers recursively and fail loudly for an unsupported + object instead of corrupting it silently. + """ + if value is None or isinstance(value, (str, int, float, bool)): return value - except TypeError: - return json.loads(json.dumps(value, default=str)) + if isinstance(value, dict): + return { + str(key): _strict_json_value(item, path=f"{path}.{key}") + for key, item in value.items() + } + if isinstance(value, (list, tuple)): + return [ + _strict_json_value(item, path=f"{path}[{idx}]") + for idx, item in enumerate(value) + ] + # Common durable scalar types that JSON does not know natively. + if isinstance(value, uuid.UUID): + return str(value) + try: + from datetime import date, datetime + if isinstance(value, (date, datetime)): + return value.isoformat() + except Exception: + pass + try: + from enum import Enum + if isinstance(value, Enum): + return _strict_json_value(value.value, path=path) + except Exception: + pass + if hasattr(value, "model_dump") and callable(value.model_dump): + return _strict_json_value(value.model_dump(), path=path) + raise TypeError( + f"Checkpoint contém valor não serializável em {path}: " + f"{type(value).__module__}.{type(value).__qualname__}" + ) + + +def _normalize_checkpoint(checkpoint: Any) -> dict[str, Any]: + checkpoint = _parse_legacy_json_container(checkpoint, dict) + if not isinstance(checkpoint, dict): + return {} + out = dict(checkpoint) + out["channel_values"] = _parse_legacy_json_container(out.get("channel_values"), dict) + out["channel_versions"] = _parse_legacy_json_container(out.get("channel_versions"), dict) + raw_seen = _parse_legacy_json_container(out.get("versions_seen"), dict) + out["versions_seen"] = { + str(node): _parse_legacy_json_container(versions, dict) + for node, versions in raw_seen.items() + } + if "pending_sends" in out: + out["pending_sends"] = _parse_legacy_json_container(out.get("pending_sends"), list) + if "updated_channels" in out and isinstance(out.get("updated_channels"), str): + out["updated_channels"] = _parse_legacy_json_container(out.get("updated_channels"), list) + return out + + +def _normalize_metadata(metadata: Any) -> dict[str, Any]: + value = _parse_legacy_json_container(metadata, dict) + return value if isinstance(value, dict) else {} + + +def _normalize_config(config: Any) -> dict[str, Any]: + value = _parse_legacy_json_container(config, dict) + if not isinstance(value, dict): + return {} + out = dict(value) + out["configurable"] = _parse_legacy_json_container(out.get("configurable"), dict) + return out + + +_EPHEMERAL_RUNTIME_KEYS = {"__pregel_runtime", "__pregel_store"} + + +def _strip_runtime_refs(value: Any) -> Any: + """Recursively remove process-local runtime/store references only. + + Checkpoints may legitimately contain LangGraph internal channels whose names + also start with ``__pregel_`` (for example task channels). Those are durable + graph state and must be preserved. The corruption that triggers + ``str.override`` is specifically a runtime/store object captured inside a + nested RunnableConfig and later stringified by the JSON repository. + """ + if isinstance(value, dict): + return { + key: _strip_runtime_refs(item) + for key, item in value.items() + if str(key) not in _EPHEMERAL_RUNTIME_KEYS + } + if isinstance(value, list): + return [_strip_runtime_refs(item) for item in value] + if isinstance(value, tuple): + return tuple(_strip_runtime_refs(item) for item in value) + return value + + +def _durable_config(config: dict[str, Any] | None) -> dict[str, Any]: + """Return a checkpoint-safe copy of a LangGraph RunnableConfig. + + LangGraph injects ephemeral private values such as ``__pregel_runtime`` and + ``__pregel_store`` under ``configurable`` while a graph is running. They are + process-local and must never cross the durable checkpoint boundary. + + The scrub is recursive because task/pending-write config fragments may be + nested below regular config fields in newer LangGraph versions. + """ + if not isinstance(config, dict): + return {} + cleaned = _strip_runtime_refs(config) + if not isinstance(cleaned, dict): + return {} + configurable = cleaned.get("configurable") + if isinstance(configurable, dict): + cleaned = dict(cleaned) + cleaned["configurable"] = { + key: value + for key, value in configurable.items() + if not str(key).startswith("__pregel_") + } + return cleaned + + +def _canonical_checkpoint_config( + payload: dict[str, Any], + request_config: dict[str, Any] | None = None, +) -> dict[str, Any]: + """Rebuild the RunnableConfig returned to LangGraph from durable IDs only. + + Official LangGraph savers do not re-bind the full config that happened to be + present when a checkpoint was written. They reconstruct a fresh config from + ``thread_id``, ``checkpoint_ns`` and ``checkpoint_id``. Doing the same here + prevents a historical/factory-time runtime value from being rebound into a + new execution while remaining backward compatible with existing rows. + """ + requested = _durable_config(request_config) + stored = _durable_config(_normalize_config(payload.get("config")) if isinstance(payload, dict) else None) + req_cfg = requested.get("configurable") if isinstance(requested.get("configurable"), dict) else {} + stored_cfg = stored.get("configurable") if isinstance(stored.get("configurable"), dict) else {} + checkpoint = payload.get("checkpoint") if isinstance(payload, dict) else {} + checkpoint = checkpoint if isinstance(checkpoint, dict) else {} + + thread_id = ( + req_cfg.get("thread_id") + or stored_cfg.get("thread_id") + or payload.get("thread_id") + or "default" + ) + checkpoint_ns = req_cfg.get("checkpoint_ns") + if checkpoint_ns is None: + checkpoint_ns = stored_cfg.get("checkpoint_ns", "") + + requested_checkpoint_id = req_cfg.get("checkpoint_id") + checkpoint_id = ( + requested_checkpoint_id + or payload.get("checkpoint_id") + or checkpoint.get("id") + or stored_cfg.get("checkpoint_id") + ) + + configurable: dict[str, Any] = { + "thread_id": str(thread_id), + "checkpoint_ns": str(checkpoint_ns or ""), + } + if checkpoint_id not in (None, ""): + configurable["checkpoint_id"] = str(checkpoint_id) + return {"configurable": configurable} def _thread_id(config: dict[str, Any] | None) -> str: @@ -85,6 +276,7 @@ class RepositoryCheckpointSaver(BaseCheckpointSaver): """Checkpoint saver nativo para LangGraph usando os repositories do framework.""" def __init__(self, settings, repository=None): + super().__init__() self.settings = settings self.repository = repository or create_checkpoint_repository(settings) self._loop: asyncio.AbstractEventLoop | None = None @@ -100,20 +292,40 @@ class RepositoryCheckpointSaver(BaseCheckpointSaver): with concurrent.futures.ThreadPoolExecutor(max_workers=1) as ex: return ex.submit(lambda: asyncio.run(coro)).result() - def _make_tuple(self, payload: dict[str, Any] | None): + def _make_tuple( + self, + payload: dict[str, Any] | None, + request_config: dict[str, Any] | None = None, + ): if not payload: return None - config = payload.get("config") or {"configurable": {"thread_id": payload.get("thread_id")}} - checkpoint = payload.get("checkpoint") or {} - metadata = payload.get("metadata") or {} - parent_config = payload.get("parent_config") - pending_writes = _normalize_pending_writes(payload.get("pending_writes") or []) + # Second-stage protection: never re-bind the full persisted RunnableConfig. + # Rebuild only the durable identifiers, as official LangGraph savers do. + config = _canonical_checkpoint_config(payload, request_config) + checkpoint = _strip_runtime_refs(_normalize_checkpoint(payload.get("checkpoint") or {})) + metadata = _strip_runtime_refs(_normalize_metadata(payload.get("metadata") or {})) + raw_parent_config = payload.get("parent_config") + if isinstance(raw_parent_config, dict): + parent_payload = { + "thread_id": payload.get("thread_id"), + "config": raw_parent_config, + "checkpoint_id": (raw_parent_config.get("configurable") or {}).get("checkpoint_id") + if isinstance(raw_parent_config.get("configurable"), dict) + else None, + "checkpoint": {}, + } + parent_config = _canonical_checkpoint_config(parent_payload) + else: + parent_config = None + pending_writes = _normalize_pending_writes( + _strip_runtime_refs(payload.get("pending_writes") or []) + ) try: from langgraph.checkpoint.base import CheckpointTuple return CheckpointTuple(config=config, checkpoint=checkpoint, metadata=metadata, parent_config=parent_config, pending_writes=pending_writes) except Exception: return { - "config": config, + "config": _durable_config(config), "checkpoint": checkpoint, "metadata": metadata, "parent_config": parent_config, @@ -121,7 +333,10 @@ class RepositoryCheckpointSaver(BaseCheckpointSaver): } async def aget_tuple(self, config: dict[str, Any]): - return self._make_tuple(await self.repository.get_latest(_thread_id(config))) + return self._make_tuple( + await self.repository.get_latest(_thread_id(config)), + request_config=config, + ) def get_tuple(self, config: dict[str, Any]): return self._run(self.aget_tuple(config)) @@ -129,20 +344,24 @@ class RepositoryCheckpointSaver(BaseCheckpointSaver): async def aput(self, config: dict[str, Any], checkpoint: dict[str, Any], metadata: dict[str, Any] | None = None, new_versions: dict[str, Any] | None = None): thread_id = _thread_id(config) checkpoint_id = _checkpoint_id(checkpoint) + clean_config = _durable_config(config) + clean_cfg = clean_config.get("configurable") if isinstance(clean_config.get("configurable"), dict) else {} + checkpoint_ns = str(clean_cfg.get("checkpoint_ns") or "") + # Return a fresh canonical config. Never feed process-local/factory-time + # configurable values back into the next LangGraph super-step. next_config = { - **(config or {}), "configurable": { - **((config or {}).get("configurable") or {}), "thread_id": thread_id, + "checkpoint_ns": checkpoint_ns, "checkpoint_id": checkpoint_id, - }, + } } await self.repository.put(thread_id, { "thread_id": thread_id, - "config": _jsonable(next_config), - "checkpoint": _jsonable(checkpoint), - "metadata": _jsonable(metadata or {}), - "new_versions": _jsonable(new_versions or {}), + "config": _strict_json_value(next_config, path="$.config"), + "checkpoint": _strict_json_value(_strip_runtime_refs(_normalize_checkpoint(checkpoint)), path="$.checkpoint"), + "metadata": _strict_json_value(_strip_runtime_refs(_normalize_metadata(metadata or {})), path="$.metadata"), + "new_versions": _strict_json_value(_strip_runtime_refs(new_versions or {}), path="$.new_versions"), "checkpoint_id": checkpoint_id, }) return next_config @@ -153,19 +372,46 @@ class RepositoryCheckpointSaver(BaseCheckpointSaver): async def aput_writes(self, config: dict[str, Any], writes: list[tuple[str, Any]], task_id: str, task_path: str = ""): thread_id = _thread_id(config) try: - latest = await self.repository.get_latest(thread_id) or {"thread_id": thread_id, "config": config, "checkpoint": {}, "metadata": {}} + latest = await self.repository.get_latest(thread_id) or {"thread_id": thread_id, "config": _durable_config(config), "checkpoint": {}, "metadata": {}} except: latest = { "thread_id": thread_id, - "config": config, + "config": _durable_config(config), "checkpoint": {}, "metadata": {}, "pending_writes": [], } + if isinstance(latest, dict): + # Do not keep extending a persisted RunnableConfig across super-steps. + # Rebuild the same canonical config that aget_tuple() will expose. + latest["config"] = _canonical_checkpoint_config(latest, config) + if isinstance(latest.get("checkpoint"), dict): + latest["checkpoint"] = _strip_runtime_refs(latest.get("checkpoint")) + if isinstance(latest.get("metadata"), dict): + latest["metadata"] = _strip_runtime_refs(latest.get("metadata")) + if isinstance(latest.get("parent_config"), dict): + parent_payload = { + "thread_id": latest.get("thread_id") or thread_id, + "config": latest.get("parent_config"), + "checkpoint_id": (latest.get("parent_config", {}).get("configurable") or {}).get("checkpoint_id") + if isinstance(latest.get("parent_config", {}).get("configurable"), dict) + else None, + "checkpoint": {}, + } + latest["parent_config"] = _canonical_checkpoint_config(parent_payload) + pending = list(latest.get("pending_writes") or []) for channel, value in writes or []: - pending.append({"task_id": task_id, "task_path": task_path, "channel": channel, "value": _jsonable(value)}) + # Writes may contain nested task/RunnableConfig fragments. Scrub the + # private runtime before the repository's JSON ``default=str`` layer. + durable_value = _strip_runtime_refs(value) + pending.append({ + "task_id": task_id, + "task_path": task_path, + "channel": channel, + "value": _strict_json_value(durable_value, path=f"$.pending_writes[{task_id}].{channel}"), + }) latest["pending_writes"] = pending await self.repository.put(thread_id, latest) diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/config/__pycache__/__init__.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/config/__pycache__/__init__.cpython-313.pyc index 2856a11ee770f61158e345c0e12bc46e8eb2edbd..411fc5a5e4c84a8f18da056e4f14946b61b867e9 100644 GIT binary patch delta 29 kcmaFHc$aa4op6nQZeEFgN@7W(ewm?xVR?R0_Qa?j0HO^F+W-In delta 38 tcmcc1_>6IaolL8KZeEFgN@7W(eqwrRUP*j%eqKppaeP{4h0(;o9snL|4(9*> diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/config/__pycache__/settings.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/config/__pycache__/settings.cpython-313.pyc index fd10918f74f14f5f9891bc150de8d987a87f57df..05536b0b24d31ac2e35a124dbfa95855ad88d99c 100644 GIT binary patch delta 32 ncmZ3Pv><82RyE-o{oK3~{glL#MEx>D1Hpb delta 41 wcmeB{nj*E~GNa5y{oK3~{glL#ME%6{)Vz}TD1HMRHe+ delta 41 wcmca%@z7#}HlKABOypqJ?__WLlqs`r%+_nH`cMt~v diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/__pycache__/llm_rails.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/__pycache__/llm_rails.cpython-313.pyc index ca5282bbff12780c3248c73d19d4412940a28e6e..58271f2fd6e4a0afa968db1a54bfe14de0fa4966 100644 GIT binary patch delta 32 ncmZoN`DwhNmRYz_KR2&LKP9mwQNPU4z_2{OD0}lv=1egF$kYqA delta 41 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 diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/__pycache__/parallel_executor.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/__pycache__/parallel_executor.cpython-313.pyc index 97b40a2a88fa6a6f71ea6dc5b32baa86b9b6cb1e..a4f78f1620ca21f0b9cfafa24aa3abf506ab3737 100644 GIT binary patch delta 34 qcmcaUoAKOi#tkg2!kzlLc_sQOi6x2pWrhZZ<@rU~n-y5MdjJ65SPV-5 delta 43 zcmX>%oALT=#tkg2GL!Uk^Gftn5=#>G6Vp@kO5&6A^GXtnD1H`J7$?q{oK3~{glL#ME%6{)Vz}TSr&dEONZ@J#KYh?OaeB+Lh&nDlF>;RV;ggp&PI-O5_>17 z&=8YO2At+G&TKk)K-17iD49v~P}-(5w1K)a?X;bYjT)>d9zvQ-6aMIosY#f2+DX4X zTOofmm95|He&2qt{eF9^e}9f%KFO9Ym6UJ{d@6^g#$P@7dg(t5w&Pn8vbrTIs^XT3 zrmam6B=P+jhtb^3>F7d+K}#J3t#fWR2*xHB7Y&*OvrtsSjO({8>ls`uSOgQ5U`{B; z<}uD)LJn=OVJ{%^;P$Rbj%!y`F`86(6^pVWMq)`>j46CnQYD;-$uqAgYAgcksKiH- zvKo`qv8V_WdFDq+UZIdqh`gMf5V_>2gjJD`CV6p8l2xgZ-BJ$RD)A{Pp5#+8c*JC>k;@lp`3;3U_z1KrZ}$dzdfL6AZhz-+uP5m7wTIe+Lf-D_ z^K@%MZ@}Nt9rSrZ-2s1uUFj};6u}qRfR-#E{T?%b3c4^NNA7cyMU$Dd?J-yccH4?7 zXdr8g))2mE%?_E}&j(Efi`&l@7>eL8%r-Cs=7M~nq%hKa+sTFdii!V-85qa#%!=%E zr~=}ZwH2b-n65a($hv{DFO78rs|w?7E2XSo5kz6`19t<6lZhbxS7H?=u$&Em69fLzHT@a^!No2^e*5ZhBiUF!djb#g^(u{7TSfp2?GhSB1QB1 zcuY}o9rdyiz-`FuB5?&jN=XmYc>2NBRLc)9S1=jTN?19W;#ca) zH}~SC2#nTTGjHvjvvy8qt=&_`oY^{WZ<({VWX&x(TlJLhCdX8|&ZsYX&vl;d{HZV7 zuw%aAz*~{G$9^?-F_FC|1hd)w2ePh#E2{^mIu>lT&yM~$I&a%JXWRJRs;UKt>qX-^ z?kqREaaWdao98<&?#?&R6$Twc|1 z!wfG_v}j>gRp2$`;hoj&MMU=A=OS|^dsSFhprx!2>kFh4o#gDE`dTEgVSOpn%!ZMm z3+n|W=o@u{VY>m&Kry*AY%_mpEf~pJm#wTQtP@O)tWYd)@Mk8w*4jzKH(l&N2|2&k zWw!{Z!0$rX0V~y@65={kP2O0$8kLg3q3X%9f*wMz3L7A_wGi6G_>bJB8%6GNf9P1v zSKq;fZqiqFd`JGvcQ!CA7AYt-N{k#$iHhRZ;XTk>JWIhxp_js+8HRMWY$kgo3wgMu zrWmN+NacC*q*RNViGO`fgT_zW|DYLu43;WJ0G_SoH;VK%2Lm6YDBV(eSV_PbSbxV3t>VuLu}@vQec(WMQ@>(F1?=(z(X zcm(8rw_ZC{I0_Ri0sM=DlgB^E`;)SSBOq{F$Y^i99sdQ!d2=KVTLjn4;bau&8M3M0 zL0)PtZQTLNlgtga$Y5G5XDs#;_vS1WC)%%>Tsf0#(V*L4oYk*eMw(Upg zROSQE9W1hC%J02}(3uR^@jgNh^0&^1&}t%e?L)7V3tf%odhmsv6nOGk*Ln1l%n!Qn z*P%_s?0XsT7rqX_|M==%_zEr1I@}u!gaUBSzy%Y6+r^JtY4|46=1-tEGe7g6L}0M3 z_q6?P>a>=Ei-H!YiEQkze5uXk z?fy#ierCRZ4q1BWA|Hjz40&_lkZCWBfzj)UW6)uA!zABCF815WnW17bIA}wEC8@!B zB#@^Ed+WkfU4B@M$J3FRy!@KPG39*$#snnFh64^V( zv+8Zrx5g^b7n#|yB#Ry*ZR11eD0yMLmDRDCh4C#2eVbU~_1tl4PT>o(J(V>A7U-N^VjHJc)5Wd|sJBfHS! z@>KOH(E8nI?D^;D=e=F9ms8Z>3G(Zt8$C+;0d*6Z7UV51779JV)BF-k41;&bV9{u$*oSvdO7vNnXcXRB{A^m(&`B_AF;{a@m!eG32p delta 1218 zcmX|=Z%kWN6u^64X$P3Fri$5yiI~-}7r=-Ax@z(M%QbkxU14sb8`KZ+xu!IpZL{H(rk%LolfT1CgPJvLWC;%} z*5jV_T{_dEk#Ot`o2&P-Rtyn7L&t77QtxD8;n5^gx^83mgjl$ahn%!mxw~ z!glDU!41MSayfhoUhGGm6Dbg4zOy0eD$tuZQQV`MTVj9YUQ)cuUh+hL8Ilw}SkaC6cR5fqeHxD6oh zDGdPQcvY?Dqs%G88a`6(TokaN`B~hl9WHoCg_&)A?Y=-?`>RR-(^>~4@Q${R`w56; z$_>>?wl~3Wg$Cm=EA_1Hdnhuda8wpGC4q0ocR&)~kGF8Y0N#nGZE@~ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/_compat.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/_compat.cpython-313.pyc index 37afb504d8a9ecb9fe7e987384afafccdeeb9d35..4eb13fad6d9e657eda1a6b42d8254373e6ae435f 100644 GIT binary patch delta 32 ocmbO$JVkiJayH>E{oK3~{glL#MEx>D1Hi_@% delta 41 xcmbOtJXd(bayFUC`nh=}`YDMeiTa7@sd**w$@zIDiN*10nH5Hxcd^}K0suwt5BC57 diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/contestation_validation.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/contestation_validation.cpython-313.pyc index 8a12af0f4e3db57d7c47b682dba4739df4a01546..de98b7b4a8fe769463a165c4921588080e6c9663 100644 GIT binary patch delta 1702 zcmZ`)TWnNS6z%D>w?hY^R4aW2OKF6weYcPrt+67lebBEyObnFk%xyDaW^S`*rcF(R zAYdgnHL^n@LJ5f`wUIQmH?c)6R1Km;>H`aM72+eJQScGJ&_q1z+>zE8f7YzC_d0v_ z*=L=ZtCQmVq&U9WbNfqW;Yh43V8o2FzKZgSev7u-LC=j<`6rW;l9C+F=!=JPeHMS%wDqkjDSTaSNgRHtj}<1RZHf} zlIlLP<%i>u-Ov~hRpp9t9<3@`KMLKePl2L(@~poiWAKLdCl@B!cZ(CCKqlC}|=qC*no z2tNle0^I6`=8;NvwC(){^+f5i?XAle56(h_q0w>Vg_^#e>Ue`OGu#!jdQ{v=3!BlX z(PKKk?jS{DiMA(r>%${Hb$8V$ag4Q%qhiV)-`FTt&~AhV0dNKD;PGFvXi5onEVsrPeBq7F*>Em8()AgR7J+?%(DOp(M`F|VW+u(Ynqpe8D5c@!&{p>eW(7d6F2!nbNS908P!viALtSk%1sW~ zS){y<*XV_3<$b@4=sCa~;3I&Ne+UM5(#H~XjZ%f=|0kDs`6*;6^mS5?)TPOVqme1+ z9C1=wt%xZN`kf5^u`Ceu#~zn&zPO++GO$J6xcdOF%CyTgd{vqRJ)6|qA43YBq6>g= zz(u~+QWsF~H-hzNfLy>80N#t9<35Vz!APGK^qXD1#z4q20`644f$Rss0SQi;KV$^M zX24mJcrkK1=$L-z7dG$7{Q~q$!1sWDz-7P$m$g13vg}xElO{Ai)s`i3zOCSHb&5N2 z3XRI4^x{|*V1)oT&w1^+D-}gh4lo-z%#So~X}`;_a{7OAsFjGC5HFNWM6HE7&?XvL zh%Va2bM5(V4{ER1{W($^tv-TU*(jsBL3jTcY^92FZAX5W@{PMjP@N~;wV{eC zFDka6RIXg;U0u4iXiqvSsljPH-LYF7bgT}HrADy>%5O0&3hv#o5ivSxp@thDFzoFT0B&*%L<-|y%9 z<9VLX`w2Q!bw}iuGB#zp|z5urO6C9}}O1NsPgd!pibCdTKV;Z?0)- z@z!mseR~=iuU4)sJc8sQq*=I}KEtPl{Q+^rdX$gSmC8JJf_|^m*=1wv+WpqD z(}MbJM!DXj1>=I_=QAkqJ_?xl8Q5n59{@h2q76e2d?Cy&9Fy?_0>oK;1$!l6t~ZRhrGc^7aySqYi(?lJhg*Xc^_-R zK2Cqv4knX_#_4;m!~6yCHsB<=9vxpL zXOn9g#?)?cf)!wx0abtr%5Iuur|4YM>f~-l=+CA`mO^#Ss~MwUbIke{cz5-a%5M?d z=uDduZkkz~M{Z|ZCZGN<>Aj84?I5XX^4|H^phzjr!ukjyOTwacL#g7P--Y)Rz&XID z07?HC7Np5P6QC+QjS2hz=;97P2Q7_XNg>{nzv(-%y1B$3@WmdF#(1ftCfvJK-Zn2n z&F=xyGNY^_K+Wg!M~1egZupn5h5;98wxy;4D&b+kd4LUY8Gs){U7i$rRv_FH3HWqx zx7HhsX#V60d<)tSfPK`_ny+9yeuG|U?Xx9degn7)*ao-+7$v*Ap5+*tyII*T$HWba zE#@OSH~h^d^|~(*(t?iX9qve2|Cf*smQ2gV?iB8XBOTjav!DRK6PIOW*1Cmp(JE`M zZ?Y_{_uRKk7FPv#VM~g5a_#eD5!o1!9EV=>*qE6nJ@$DeEXfB*ZtEaj_Y|xcM8-1A zE7QD1HD1Ht<8 diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/llm_rails.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/llm_rails.cpython-313.pyc index 86d11da9c993693da65210310906cb8d347e717f..3f9be8a8ab608adec1922879f7dcbd2887a34182 100644 GIT binary patch delta 32 ncmX@;y2EvY4YP2Mer{fgeoA6VqJEj7fnj-mQTFBt=1h42#$gL- delta 41 wcmdntdeC))4YSNt{oK3~{glL#ME%6{)Vz}T+*308YLSbpQYW diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/output_sanitization.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/output_sanitization.cpython-313.pyc index 50315357f4fc203bc6ef6538c649e3d3d2e139f4..4c92a6e46265bce2ecad48494f19f1c97c9fd791 100644 GIT binary patch delta 32 ncmaEsb2n#07N_tu{oK3~{glL#MEx>D1HD1H{oK3~{glL#ME%6{)Vz}TCIasU7T delta 41 xcmeyS`(Jm%eioTI`nh=}`YDMeiTa7@sd**w$@zIDiN*10nH5HxFR`cy0RV5j5Pkpv diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/ausencia_oferta_proativa.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/ausencia_oferta_proativa.cpython-313.pyc index 3c0e22e3abe2db1457760dd5567c427cd75cdf6d..6205861929557b9a8f3cd50d51a2ac2fae6732c9 100644 GIT binary patch delta 32 ncmZ4FJJWZAt*Y>1{oK3~{glL#MEx>D1HD1HD1HHq)$ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/dlex_out.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/dlex_out.cpython-313.pyc index 92e035404d08716c2e093cd333c7d8765e6ab262..5fd4841fa4b4f73f8521f1e4b8fb7ec17938b3a5 100644 GIT binary patch delta 32 ncmX@kvyW%PFBaj+`nh=}`YDMeiTY)R28QMNMcJFhSPdBg&9w_N delta 41 wcmdnTbDU?xFBX|O`nh=}`YDMeiTa7@sd**w$@zIDiN*10nH5Hxd07n^0aq(Tv+^ZQ0!^v)e@zW8=l( zP3*-h;b1s#Jb3QKgNdHegNX+{8B7z;zFk6$Gs$mf-~WBgd-LuE*?LLB&0x^4;QI4q zt^75kDBsLd zR|6RJJbm&&T!Axi_5pzioI^$-hKxf3c^<})7hoKD5iTJw!xiLJm_S~G>&P20iJXEY zavE+TZ$Sz<1GC86Fo(PYcadqBkCPOU2cf$cn_qAniz$%F64_N88eMYM$d^E^x=a*h zQF4<@o70kE@uFm!&hbEnNej#pW`x>I6X+A6hS0f5^PE`X|^ zXf?J}qogt_s#Me}TVeOqCtEQ2TY0M#1zQG;64;SnCA9#7DKMtI>1J7l>%{|@5gBW6Ch_k~OVGWG3V;B$e zGl5BV3eyoXNLJaG&J-`z1!m_-dX}3)uy*N>O9?sir|wVIQq{Vuo;ydAF&K&I#Mng4 zy6~-%cVA!4=Ja=F&eYB2#cVFUu$Il4@6$^wo1OVo;46K%t^&tp^vn9$dQIfXA8K86 yY5mhaxXEjo-5ZtMI~)4V9>msYH}8qh)ZPp$21S(<;oJLI5C)NQov}9KQRP3)O?cS= diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/fraseologia.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/fraseologia.cpython-313.pyc index a0040d239793a4f627647cb3fe19c48b532f24e1..aad36c5e95085ddb7aa1089d3561aa28443ea55c 100644 GIT binary patch delta 1741 zcmZ`)O^+Kz5Vbc83%mK?LkbrTrQt(~G_eWFDLF)gce1jG?PYDk(W-5??RI9mo$ekd zaG4x)Ux_nEPDp6;2RI?|6OfR&Auh-ge}JlQqtbyAZkJbRmV0;tF+y&PhL3lPHEr z>6KAJ)_2~Rj88XM`P!Po%PQ1UtJ^GPXYlZ0+8K5GN8{t#iRpm4wGDU!y8=}xMug+vjD8A8VMMLGM{d71= zpqCDN2@v2%)I<`4lR1RQDw&{Ai&+erSzrvfTvCYg^3#~IXL4$^q*E$12|1}0v1SPZ zYJRJq3}B^9hVa>)^FOYA-7GhgbqtH42bD-TOb!P_I7|uF(V){Ez>`A4MJvPyiA)lg zeC{ytw-pBofcqz6wO6{}o-0s_TQx1{I%fiyQa)Q6`Kk`J%(+$44J&P{%hrX;hH3Bk zB!$7}B&lOAL$Rc!S)pu9!kZeKOITrMY^xGU^hJRgOpPKK;T$H6jptYjyw6hU)~_Kw zBAIWW3;b6OC+$iJ%0l2s?M^NzQ7xH}CsbmhSYqrp!Zs#FNJ38+#9ct z;L+0RAk!Nc=7j(AB}gw?H>rG0j!i8{3h! zk^yITUipG>u;!d%xRX-;S83cJMF}MJ(JZEz&5_*2?2+Y+Wiykc5v*KFPhLl$bwao| zuI9}6q)_45gl%`xv4Lo~(T;_(-U-_^KuuMB!;`+sJo|osnnrOZF|yD#;nC$=NxX#U%=vc_pbudHD+Y zrCbUiP?VUQnV+1Qmzh7AokvGZMlKABOypqJ?__WLlqs>W<35)=C^bnB% diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/pinj.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/pinj.cpython-313.pyc index 72e1252bc256f7b308269d2eebbd4a9c33fc6c2e..19f96443a70f6ece81334067c981b66d4f278336 100644 GIT binary patch delta 32 ncmZp6{N=DgKwh|CKR2&LKP9mwQNPU4z_2{OD0{Pxd^jTj!!8RK delta 41 wcmez6(CoNDKwf5ser{fgeoA6VqJCm}YFD1H=xo&W#< diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/revprec.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/revprec.cpython-313.pyc index 50cd18fbc70b57f6ff232a0cad4d6b916b265ab2..a5804aab431ea1fa2e560b4afb35a9ce9ec861fc 100644 GIT binary patch delta 32 ocmZ3dwNz`vc46U3`nh=}`YDMeiTY)R28QMNMcJD#3r8~o0L*?1{Qv*} delta 41 xcmZ3gwN7ipc43*>`nh=}`YDMeiTa7@sd**w$@zIDiN*10nH5HxPYFje0svOH5D5SP diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/tox.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/tox.cpython-313.pyc index 4a5691a3e3d940d84db77198232ba54c910d6bd2..96c0db9732d64e7132f0660e16a2569fc2835d28 100644 GIT binary patch delta 31 mcmZo={ll^$mr=M+KR2&LKP9mwQNPU4z_2{OD0^}z;~xOBWDBGK delta 40 vcmeyv(#pCamr-WAer{fgeoA6VqJCm}YFD1H falar so o resultado, em linguagem natural. + A3. nomes de ferramentas/tools, JSON, chaves tecnicas, parametros/chaves de + implementacao, checklist interno, estados do workflow ou raciocinio interno + expostos ao cliente -> falar so o resultado ou fazer a pergunta necessaria + em linguagem natural. Exemplos de termos internos proibidos: "subject", + "asset_id", "invoice_id", "tool", "workflow", "route", "intent", + "COLLECTING_PARAMETERS", "AWAITING_CONFIRMATION" e nomes de tools como + "cancelar_vas_avulso" / "contestar_cobranca". A4. Dizer que vai encaminhar uma jornada adequada, dizer que vai encaminhar para um especialista. Preferivel dizer que não pode ajudar sobre isso A5. Dizer que está "fora do escopo". Preferivel dizer "Sobre X não posso ajudar com isso" @@ -64,7 +69,9 @@ B) Construcoes proibidas: B4. orientar o cliente a procurar atendimento ou outro canal: "entre em contato com a central", "ligue para o atendimento", "fale com um atendente", "procure uma loja", "acesse o app/site para resolver" -> resolver a duvida - aqui mesmo, sem encaminhar o cliente para outro canal. + aqui mesmo, sem encaminhar o cliente para outro canal. ATENCAO: pedir para + o cliente tentar ou solicitar novamente NESTA MESMA CONVERSA, sem citar + central, loja, app, site, telefone, atendente ou outro canal, NAO viola B4. C) Ofertas e promessas proibidas (revisao humana — sobrepoe outros rails): C1. oferecer plano mais barato, troca, migracao ou rebaixe de plano (inclusive @@ -72,6 +79,20 @@ C) Ofertas e promessas proibidas (revisao humana — sobrepoe outros rails): C2. conceder ressarcimento em dobro -> usar a fala fixa de ajuste na fatura. NAO marque FRASEOLOGIA (fraseados OBRIGATORIOS — sempre OK): + - perguntas ou pedidos de DADOS DE NEGOCIO que o cliente conhece e que sao + necessarios para continuar o atendimento. Isso NAO expoe raciocinio nem + processo interno. Exemplos SEMPRE OK: "Para prosseguir, informe valor.", + "Qual foi o valor da cobranca?", "Informe a data da cobranca.", + "Qual servico voce deseja cancelar?", "Qual e o nome do produto?". + Nao confunda o nome natural do dado de negocio ("valor", "data", "servico", + "cobranca", "fatura", "produto") com o nome tecnico da chave interna + ("subject", "asset_id", "invoice_id" etc.). + - confirmacoes de uma acao ja em andamento em linguagem natural, por exemplo + "Voce confirma o cancelamento do servico TIM Fashion?", sao interacao normal + com o cliente e NAO constituem exposicao de processo interno. + - em caso de falha tecnica, orientar a repetir a mesma solicitacao aqui mesmo, + por exemplo "Se desejar tentar novamente, solicite o cancelamento novamente", + e permitido; isso NAO e encaminhamento para outro canal. - "incluso no seu plano" / "faz parte do seu plano" / "beneficio incluso". - citar o servico por nome e valor SEM rotulo de origem. - a fala fixa de ressarcimento ("Por aqui, nao consigo seguir com o diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/__init__.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/__init__.cpython-313.pyc index d9822e62a088764f823c8c42b5554db163328772..a71492a23e827e72a2f603b9c1339dcdf48b1d6d 100644 GIT binary patch delta 31 ncmbQt+r_(K1FP^v{oK3~{glL#MEx>D1HHq)$ delta 41 xcmca6d0%qF8CIE@`nh=}`YDMeiTa7@sd**w$@zIDiN*10nH5Hx@3Veq0{~+Q5cU87 diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/dlex_out.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/dlex_out.cpython-313.pyc index 2ba9ca29bcbac43663761f2e0706fde5273cdad6..85dd15ec0f84179297a26574e40f8929c81126c0 100644 GIT binary patch delta 32 ncmbO%)g`szJge|T{oK3~{glL#MEx>D1H9Jgdws{oK3~{glL#ME%6{)Vz}T0ZNk(nE(I) diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/ragsec.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/ragsec.cpython-313.pyc index a2818e9ba3dfe2ef93316ab5e2e1effa4b96f667..c5f6bb31af4a62f7fee98c0a77e11be75ca161d4 100644 GIT binary patch delta 32 ncmdn0wMJ{h0e0bj{oK3~{glL#MEx>D1HgWp?2S`nh=}`YDMeiTY)R28QMNMcJF*v-|M^0MY6UH~;_u delta 41 xcmdm@wO4DyWp;@9{oK3~{glL#MEx>D1H+V?`nh=}`YDMeiTa7@sd**w$@zIDiN*10nH5HxKL|MU0{~`X5QhK& diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/rules/__pycache__/__init__.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/rules/__pycache__/__init__.cpython-313.pyc index 1325f8220aea6f2539517fd744afc7b348767cc4..3e8b4d3f1828503088526324c108314fa8b9623c 100644 GIT binary patch delta 31 mcmZ3D1HD1HnCX38`{oK3~{glL#ME%6{)Vz}T str | None: + """Reescreve apenas wording bloqueado por FRASEOLOGIA. + + A saída é sempre reavaliada por ``evaluate`` antes de ser liberada. Uma + falha do LLM ou uma resposta vazia mantém o comportamento fail-closed. + """ + try: + rewrite_context = { + **dict(context or {}), + "guardrail_code": "FRASEOLOGIA", + "guardrail_reason": result.reason, + } + out = await classify_with_framework_llm( + self.llm, + "FALLBACK", + {"text": candidate, "context": rewrite_context}, + profile_name="grl", + component_name="guardrail.fraseologia.rewrite", + generation_name="guardrail.fraseologia.rewrite", + ) + # O prompt FALLBACK usa ``reason`` como texto final reescrito. + rewritten = str(out.get("reason") or "").strip() + return rewritten or None + except Exception: + logger.exception("output_supervisor.phraseology_rewrite_failed") + return None + def aggregate(self, candidate: str, results: list[RailResult], context: dict[str, Any] | None = None) -> RailDecisionV2: ctx = context or {} final_action = max((r.action for r in results), key=lambda a: _SEVERITY.get(a, 0), default=RailAction.ALLOW) diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/rails.py b/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/rails.py index 87f4d73..4385aaa 100644 --- a/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/rails.py +++ b/agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/rails.py @@ -304,13 +304,55 @@ class PrematureActionRail(Guardrail): class ProactiveOfferRail(Guardrail): - """AOFERTA calibrado: bloqueia oferta proativa não solicitada no output.""" + """AOFERTA calibrado: bloqueia oferta proativa não solicitada no output. + + Estados transacionais determinísticos de continuidade não são uma nova + oferta do agente. Quando o runtime já abriu uma transação e está apenas + coletando parâmetros obrigatórios ou aguardando confirmação, AOFERTA deve + permitir a mensagem sem consultar a LLM. Outros rails de saída (por exemplo + FRASEOLOGIA) continuam sendo executados normalmente pelo pipeline. + """ code = "AOFERTA" stage = "output" + _TRANSACTION_CONTINUATION_STATUSES = { + "COLLECTING_PARAMETERS", + "AWAITING_CONFIRMATION", + } + + @classmethod + def _transaction_continuation_status(cls, ctx: dict[str, Any]) -> str | None: + status = str(ctx.get("transaction_status") or "").strip().upper() + if status in cls._TRANSACTION_CONTINUATION_STATUSES: + return status + + # Compatibilidade com callers que ainda só expõem o estado por meio + # dos resultados das tools. O runtime transacional já grava o status + # nesses resultados; não inferimos pelo texto da resposta. + for result in reversed(list(ctx.get("mcp_results") or ctx.get("tool_result") or [])): + if not isinstance(result, dict): + continue + result_status = str(result.get("transaction_status") or "").strip().upper() + if result_status in cls._TRANSACTION_CONTINUATION_STATUSES: + return result_status + return None async def evaluate(self, text: str, context: dict[str, Any]) -> RailDecision: ctx = _ctx(context) + continuation_status = self._transaction_continuation_status(ctx) + if continuation_status: + return RailDecision( + code=self.code, + allowed=True, + reason=f"continuidade_transacional:{continuation_status}", + sanitized_text=text, + metadata={ + "mechanism": "deterministic_transaction_bypass", + "transaction_status": continuation_status, + "calibrated": True, + }, + ) + out = await classify_with_framework_llm( _llm(ctx), "AOFERTA", diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/identity/__pycache__/__init__.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/identity/__pycache__/__init__.cpython-313.pyc index ba70cc04bad58b57802332411f53b6e417ad9a62..09f9ffe2d98ac299a8ca2de75eed9f1df4ea3952 100644 GIT binary patch delta 30 lcmZ3%JfC^O2jM#X+`JO~l*E!m{W3!X!}9#1?8!Wga{#Y^3fuqy delta 39 ucmbQwyn=bc2bp&L+`JO~l*E!m{lxUtyps6j{JfIH;`p@83Zu!)jB@}o1`g-| diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/identity/__pycache__/mcp_mapper.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/identity/__pycache__/mcp_mapper.cpython-313.pyc index b69fa7ebdf8d1699143c98d547b87da21ec48e61..f9247b55ed3bce57710dcb09aae58f728afe9208 100644 GIT binary patch delta 32 ncmaE&a$9ACFSBrier{fgeoA6VqJEj7fnj-mQTFCc=HGk($sr5` delta 41 wcmcbu@vaYJH56SGXKer{fgeoA6VqJCm}YFjp@ delta 41 wcmeyM@>69)9J5Tjer{fgeoA6VqJCm}YFD1H diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/judges/__pycache__/judge.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/judges/__pycache__/judge.cpython-313.pyc index 5b24247f211ad61cdb574f410c6d8a1ff19eacfb..72d653dbe7987e93beb097f02ee13e5c4705eef4 100644 GIT binary patch delta 34 qcmaDbhw1tprVWpog)8-Q^Gftn5=#>G%M1++%kzt}H~(i&>;eG!`VIF0 delta 43 zcmcaUhv~r_rVWpoWt#MJ^Gftn5=#>G6Vp@kO5&6A^GXtnpmfoy-LN+`JO~l*E!m{lxUtyps6j{JfIH;`p@83ZsdE(*Yv+4-)_Y diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/judges/calibrated/__pycache__/llm_client.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/judges/calibrated/__pycache__/llm_client.cpython-313.pyc index 1a0447a1e90ef6e21453b9008e3d400579f9326f..0c59cbd96973c9a855f7ae10e42fcedf53d67edb 100644 GIT binary patch delta 32 ocmaE+e^-CQLuTPl{oK3~{glL#MEx>D1H*yhoy-jV+`JO~l*E!m{lxUtyps6j{JfIH;`p@83ZsdE^8qGF4>JG& diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/judges/calibrated/prompts/__pycache__/aluc.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/judges/calibrated/prompts/__pycache__/aluc.cpython-313.pyc index eaf88358e9cf94ffa38be9d5eaf78924ae77e5e0..8cb3e8c51789a34000be085d6a026f130bbd26ad 100644 GIT binary patch delta 32 ncmX?LwB2ZfgoJRner{fgeoA6VqJEj7fnj-mQTApN2_8lOzQhWr delta 41 wcmdmPbiinXgoMl#{oK3~{glL#ME%6{)Vz}Tcg1c)FQ0I~er{fgeoA6VqJEj7fnj-mQTFDQd|Lzn*J2Ec delta 41 xcmca&ch_!1FQ3c|{oK3~{glL#ME%6{)Vz}TGr13= diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/mcp/__pycache__/__init__.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/mcp/__pycache__/__init__.cpython-313.pyc index a7bace4fa2addf9604b3c9e999c1c5c1deb953b3..0c921a84f146db38265fc31e55bd7e942caaae1d 100644 GIT binary patch delta 31 mcmaFK{D66bDWhlKABOypqJ?__WLlqs{x7om>EBmk?+G diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/mcp/__pycache__/models.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/mcp/__pycache__/models.cpython-313.pyc index a48619805b89ceb03dab454fa84832d0250e617c..966e33db5e3b57433f3ee77ec1c9871a6f8be102 100644 GIT binary patch delta 32 ncmaDMa9?0UFq3e(er{fgeoA6VqJEj7fnj-mQTFD1rk|_;#UTsY delta 41 wcmcaF@IqiiFq2G!er{fgeoA6VqJCm}YFf delta 41 wcmeBHouaxyo=K)jKR2&LKP9mwQ9m&~HLoN-IX|x?u{b_0v%+Yz5mTf9056^nZ2$lO diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/mcp/__pycache__/tool_policy.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/mcp/__pycache__/tool_policy.cpython-313.pyc index 752144c4755b06e7ab3cc6ae30e86589735d4deb..9f436261e5e5d58bbd8470208880ab419db9387e 100644 GIT binary patch delta 32 ncmX@Avqxt`Et7DKer{fgeoA6VqJEj7fnj-mQTFDUOlyPz$TEer{fgeoA6VqJCm}YFD1H0PkxJhyVZp delta 41 xcmey8_cw3DDK42-{oK3~{glL#ME%6{)Vz}TD1Hn0EsJ#!L)G delta 40 wcmeyy@tD1HD1H delta 43 ycmeBt!Z_&(D1H6IaolL8KZeEFgN@7W(eqwrRUP*j%eqKppaeP{4h0(;o9snL|4(9*> diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/models/__pycache__/session.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/models/__pycache__/session.cpython-313.pyc index ab679cf7b16fa1dc89bf560659d3cc6e37642ee4..f302fad0e4dfdaccc474163a9737051c8bde3281 100644 GIT binary patch delta 32 ncmZ1?I$LxDAG2_^er{fgeoA6VqJEj7fnj-mQTApn<|=jotosUE delta 41 wcmbO&xil=g+;hoKR2&LKP9mwQNPU4z_2{OD0_1Q3lk#%yPFES delta 41 wcmey#)x^Cag+-=EKR2&LKP9mwQ9m&~HLoN-IX|x?u{b_0v%+X|ISUgb07456#sB~S diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/observability/__pycache__/context.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/observability/__pycache__/context.cpython-313.pyc index f2ba742dd5519d82cc3152a827ba32676e1adf6c..4c822b032ce066ecd3a92a32005e3b6360e67b5a 100644 GIT binary patch delta 32 ocmaE5`OtF1H8$ZU{oK3~{glL#MEx>D1Hgvt4EbGmCJGer{fgeoA6VqJEj7fnj-mQTAqe7DX-qv{(v2 delta 41 wcmdlkb3kSTGmA{Ger{fgeoA6VqJCm}YFT6(EtDd diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/observability/__pycache__/ic_events.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/observability/__pycache__/ic_events.cpython-313.pyc index b1e8e4004d6bec153fe85bb32b13617e8641aeaa..ffe0633fd468ce4f9748f78b2c983733fabc1d29 100644 GIT binary patch delta 31 mcmZo<{lT(fE~9XZer{fgeoA6VqJEj7fnj-mQTF7Wj3)rK$_u^# delta 40 vcmeyt(#X1DE~8AZer{fgeoA6VqJCm}YF916KR2&LKP9mwQNPU4z_2{OD0{OtV>k-{sPhUZ delta 41 wcmeC=p2WRDo>69ker{fgeoA6VqJCm}YFD1HFF*JhM!Xer{fgeoA6VqJCm}YFD1HcDxxHJn! delta 41 wcmdnUw~KFsHKWWF{oK3~{glL#ME%6{)Vz}TG%M1++%kzt}H;c1d9|8dMX$>6! delta 43 zcmX>(iTTnb<_*8uWP0^;^Gftn5=#>G6Vp@kO5&6A^GXtnF3jKR2&LKP9mwQNPU4z_2{OD0_1W<6Le4v-=AL delta 41 wcmeB>nkcm)f>CC&er{fgeoA6VqJCm}YFD`uT?(=Q delta 40 vcmX@he2sa7IHOFrer{fgeoA6VqJCm}YF_(XBTJx1YX{oK3~{glL#MEx>D1H;w|ok; delta 41 wcmZoQ?layH!6?(CpPN^rpORRTsGpdgnpYB^oS#>cSR9|0Sz)v}n^8&v06UltzyJUM diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/rag/__pycache__/graph_store.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/rag/__pycache__/graph_store.cpython-313.pyc index 57e1cdc2ef194cd7cb269ef4d0e03714991550f4..bd793b6232676e493f4aea107ecf2b2802b28ff9 100644 GIT binary patch delta 32 ncmZ3kJx_Z>7^85Fer{fgeoA6VqJEj7fnj-mQTFB{MpF?0y7mg< delta 41 wcmbQIymZwSp&S4BR delta 41 wcmX@%chPS{0EG%M1++%kzt}H}f$+bOiwUsSQ~G delta 43 zcmaDhk@5XR#tomCWZLv|^Gftn5=#>G6Vp@kO5&6A^GXtn6)l@2=q diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/__pycache__/config_loader.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/__pycache__/config_loader.cpython-313.pyc index 05e03cf42528ee0b629ddd64752f99b25114f2c6..4d35623f1f1ed48908a7d4ccd02f4ba51b2f21f9 100644 GIT binary patch delta 32 ncmeAW`Y*U)7Nc;Jer{fgeoA6VqJEj7fnj-mQTFC-j6v)G#dHgg delta 41 wcmew_*derG7NbnJer{fgeoA6VqJCm}YFzBheC5|ePfer{fgeoA6VqJEj7fnj-mQTFCKCJ#dZ(xeOJ delta 41 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 n(fGs!RNT4h>rt{?k?6 RouteDecision: session = (state.get("context") or {}).get("session", {}) or {} - current_state = state.get("next_state") or session.get("metadata", {}).get("workflow_state") + explicit_next_state = state.get("next_state") + tx_status_at_route = str(state.get("transaction_status") or "").strip().upper() + terminal_tx = tx_status_at_route in {"COMPLETED", "FAILED", "CANCELLED", "BLOCKED", "OUT_OF_SCOPE"} + + # Um status transacional terminal é a fonte de verdade sobre o latch. Se + # um checkpoint legado/parcial ainda trouxer ``next_state`` da transação + # encerrada, esse valor não pode aprisionar a próxima mensagem na política + # de estado. O workflow_state da sessão continua disponível porque pode + # representar um workflow conversacional independente da transação já + # encerrada. + if terminal_tx and explicit_next_state: + current_state = session.get("metadata", {}).get("workflow_state") + else: + current_state = explicit_next_state or session.get("metadata", {}).get("workflow_state") text = state.get("sanitized_input") or state.get("user_text") or "" # Estados transacionais preservam continuidade para respostas curtas @@ -107,6 +120,20 @@ class EnterpriseRouter: await self._emit(interruption, state) return interruption + # A transação continua ativa e a mensagem NÃO representa mudança de + # intenção. Neste caso a decisão sintética de estado precisa vencer + # route stickiness/continuity. Antes, o código apenas verificava uma + # possível interrupção e, na ausência dela, caía adiante no LLM de + # continuidade. Isso fazia respostas de parâmetro (ex.: ``R$ 71,99``) + # perderem o latch determinístico da transação e reiniciarem a seleção + # da tool. + synthetic.metadata = { + **(synthetic.metadata or {}), + "transaction_state_recovered": True, + } + await self._emit(synthetic, state) + return synthetic + # Mensagens que expressam de forma explícita uma intenção diferente da # intent/agente ativos devem prevalecer sobre a route stickiness. Isso # evita manter um fluxo read-only (por exemplo, tracking) quando o usuário @@ -119,7 +146,6 @@ class EnterpriseRouter: active_agent and keyword_candidate is not None and keyword_candidate.intent != previous_intent - and self._is_explicit_intent_shift(keyword_candidate) ): keyword_candidate.metadata = { **(keyword_candidate.metadata or {}), @@ -167,133 +193,129 @@ class EnterpriseRouter: text: str, state_decision: RouteDecision, ) -> RouteDecision | None: - """Detecta uma nova intenção durante coleta/confirmação transacional. + """Detecta semanticamente mudança de intenção durante uma transação. - A política de estado continua protegendo respostas curtas/ambíguas. Uma - interrupção só é aceita quando o roteador encontra uma intent diferente - da transação ativa. Keywords explícitas são preferidas; na ausência delas, - o LLM router pode decidir, desde que atinja o limiar configurado. - - O método não limpa o estado por conta própria. Ele marca a decisão com - ``transaction_interruption=intent_shift`` para que o runtime feche o latch - transacional de maneira centralizada e auditável. + 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. """ 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() previous = state.get("route_decision") or {} previous_intent = str(previous.get("intent") or state.get("intent") or started_intent).strip() - # Parameter answers have precedence over intent-shift detection. A reply - # such as ``R$ 71,99`` after the framework asked for ``valor`` is not a - # new billing-explanation intent; it is the missing transaction argument. - # Likewise, short entity-like replies (``TIM Music``) should remain in the - # pending transaction. Only clear question/action language is allowed to - # escape to the router. - missing = [str(x) for x in (state.get("missing_parameters") or []) if str(x).strip()] - if missing and self._looks_like_pending_parameter_answer(text, missing): - return None - candidate = self._route_by_keyword(text) if candidate is not None: - explicit = self._is_explicit_intent_shift(candidate) different = ( candidate.agent != state_decision.agent or (started_intent and candidate.intent != started_intent) or (previous_intent and not previous_intent.startswith("state:") and candidate.intent != previous_intent) ) - if explicit and different: + if different: candidate.metadata = { **(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 candidate - # Uma keyword reconhecida para a própria transação deve continuar - # protegida pelo estado; não há necessidade de chamar o LLM. - if not different: - return None + return None if not (self.enable_llm_router and self.llm is not None): return None - try: - candidate = await self._route_by_llm(text, state) - except Exception as exc: - logger.warning("Falha ao avaliar interrupção de estado transacional via LLM: %s", exc) - return None - different = ( - candidate.agent != state_decision.agent - or (started_intent and candidate.intent != started_intent) - or (previous_intent and not previous_intent.startswith("state:") and candidate.intent != previous_intent) - ) - if not different or candidate.confidence < self.intent_shift_threshold: - return None - - candidate.metadata = { - **(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, + allowed = [i for i in self.intents if i.enabled] + allowed_payload = [ + { + "intent": i.name, + "agent": i.agent, + "description": i.description, + "examples": i.examples[:3], + "domain": i.domain, + } + for i in allowed + ] + transaction_context = { + "current_agent": state_decision.agent, + "current_intent": started_intent or previous_intent, + "transaction_status": state.get("transaction_status"), + "tool_name": active_tx.get("tool_name"), + "missing_parameters": list(state.get("missing_parameters") or []), } + 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. " + "Se a mensagem responde ao dado/confirmacao pendente, retorne CONTINUE. " + "Se o usuário passou a perseguir outro objetivo, retorne SHIFT e a nova intent permitida. " + "Retorne somente JSON válido com decision, intent, agent, confidence, reason." + ) + user = { + "message": text, + "transaction": transaction_context, + "allowed_intents": allowed_payload, + "session_context": (state.get("context") or {}).get("session", {}), + } + try: + answer = await self.llm.ainvoke( + [ + {"role": "system", "content": system}, + {"role": "user", "content": json.dumps(user, ensure_ascii=False)}, + ], + temperature=0.0, + max_tokens=512, + profile_name="router", + component_name="router", + generation_name="llm.transaction_intent_shift", + ) + data = self._parse_json(answer) + except Exception as exc: + 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": + return None + confidence = float(data.get("confidence") or 0.0) + if confidence < self.intent_shift_threshold: + return None + + intent_name = str(data.get("intent") or "").strip() + if not intent_name or intent_name == (started_intent or previous_intent): + return None + agent = str(data.get("agent") or self._agent_for_intent(intent_name) or "").strip() + if not agent: + return None + + candidate = RouteDecision( + route=agent, + agent=agent, + intent=intent_name, + confidence=confidence, + reason=str(data.get("reason") or "Mudança semântica de intenção durante transação."), + method="llm", + metadata={ + "transaction_interruption": "intent_shift", + "interrupted_state": state_decision.next_state, + "interrupted_agent": state_decision.agent, + "interrupted_intent": started_intent or previous_intent, + "interruption_source": "semantic_classifier", + "raw_llm_answer": answer[:1000], + }, + domain=self._domain_for_intent(intent_name), + mcp_tools=self._tools_for_intent(intent_name), + ) return candidate - @staticmethod - def _looks_like_pending_parameter_answer(text: str, missing_parameters: list[str]) -> bool: - """Return True when the current turn looks like an answer to a missing field. - - This is intentionally conservative: structured values are recognized by - parameter semantics, while a short entity-like phrase is accepted only if - it does not contain interrogative or explicit topic-shift language. - """ - raw = str(text or "").strip() - if not raw: - return False - normalized = unicodedata.normalize("NFKD", raw.lower()) - normalized = "".join(ch for ch in normalized if not unicodedata.combining(ch)) - normalized = re.sub(r"\s+", " ", normalized).strip() - names = {re.sub(r"[^a-z0-9_]+", "", str(x).lower()) for x in missing_parameters} - - # Explicit conversational shifts/questions must still escape the transaction. - shift_tokens = ( - "quero ", "gostaria ", "preciso ", "me diga", "me informe", "mostre", - "qual ", "quais ", "quando ", "onde ", "como ", "porque ", "por que ", - "esquece", "esqueca", "mudei de assunto", "outra coisa", "voltar para", - "cancelar ", "cancele ", "consultar ", "ver minha", "ver meu", - ) - if "?" in raw or any(tok in normalized for tok in shift_tokens): - return False - - # Common structured parameter families. - if names & {"valor", "value", "amount", "price", "preco", "preço"}: - if re.fullmatch(r"(?:r\$\s*)?[+-]?\d{1,3}(?:[.\s]\d{3})*(?:,\d{1,2})?|(?:r\$\s*)?[+-]?\d+(?:[.,]\d{1,2})?", normalized, flags=re.I): - return True - if names & {"order_id", "pedido", "pedido_id", "invoice_id", "fatura_id", "protocol", "protocolo", "protocolo_id"}: - if re.fullmatch(r"[a-z0-9._/-]{2,64}", normalized, flags=re.I): - return True - if names & {"data", "date", "due_date", "vencimento"}: - if re.fullmatch(r"\d{1,2}[/-]\d{1,2}(?:[/-]\d{2,4})?", normalized): - return True - - # A single short noun/entity phrase is usually the requested subject/reason. - # Keep it in the transaction unless it contains clear routing language. - words = re.findall(r"[a-z0-9$+.,/_-]+", normalized) - if len(missing_parameters) == 1 and 1 <= len(words) <= 5: - return True - return False - @staticmethod def _is_explicit_intent_shift(decision: RouteDecision) -> bool: - """Retorna True para matches explícitos que devem vencer a stickiness. + """Compatibilidade: keyword configurada é um sinal explícito de routing. - A regra é configurável porque usa a keyword já declarada no routing.yaml, - sem listas linguísticas fixas no código. Keywords com quatro ou mais - caracteres são tratadas como sinais explícitos de mudança de intenção. + Não há regra por conteúdo ou tamanho da keyword; o framework confia na + configuração do domínio. """ - matched = str((decision.metadata or {}).get("matched_keyword") or "").strip() - return len(matched) >= 4 + return decision.method == "keyword" and bool(str((decision.metadata or {}).get("matched_keyword") or "").strip()) def _route_by_state(self, current_state: str | None) -> RouteDecision | None: if not current_state: @@ -457,14 +479,26 @@ class EnterpriseRouter: ] system = ( "Você é um roteador de intenções para uma plataforma de agentes. " - "Classifique a mensagem do usuário em uma das intents permitidas. " + "Classifique semanticamente a mensagem do usuário em uma das intents permitidas. " + "Quando houver uma transação ativa, considere a intent que iniciou a transação, " + "o estado transacional e os parâmetros ainda pendentes. Se a mensagem apenas " + "responder ao que está pendente, mantenha a intent da transação. Se o usuário " + "passar a perseguir outro objetivo, classifique a nova intent. " "Retorne somente JSON válido com: intent, agent, confidence, reason. " "Não responda ao usuário final." ) + active_tx = state.get("active_transaction") if isinstance(state.get("active_transaction"), dict) else {} + transaction_context = { + "status": state.get("transaction_status"), + "started_from_intent": active_tx.get("started_from_intent"), + "tool_name": active_tx.get("tool_name"), + "missing_parameters": list(state.get("missing_parameters") or []), + } if active_tx else None user = { "message": text, "allowed_intents": allowed_payload, "session_context": (state.get("context") or {}).get("session", {}), + "transaction_context": transaction_context, } answer = await self.llm.ainvoke( [ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/runtime/__pycache__/__init__.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/runtime/__pycache__/__init__.cpython-313.pyc index 8ba4883fbc23bd2fe2b2be16c21e32b65ef032a0..8c979086777c3c2cf99825bff704c24e9bfa494c 100644 GIT binary patch delta 29 lcmaFE^q6VF4&hq;+`JO~l*E!m{W3!X!}9#1?1@)q006KV3+ey> delta 38 ucmaFN^oD7|4w*Ln+`JO~l*E!m{lxUtyps6j{JfIH;`p@83ZsdqX8-^(NDuY^ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/runtime/__pycache__/agent_runtime.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/runtime/__pycache__/agent_runtime.cpython-313.pyc index f47313e2f7292704ae9d064d3d85d0514f64c6cd..9d6a067c9e87a3eb94e41c7cf9cc45c8141d56a1 100644 GIT binary patch delta 9030 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 JYNIga{|&JmTqOVi delta 10845 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 PQ%k}0i9}GNd%OPyA*V^r diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/runtime/agent_runtime.py b/agent_framework_oci/libs/agent_framework/src/agent_framework/runtime/agent_runtime.py index 7c7ee90..9006bd8 100644 --- a/agent_framework_oci/libs/agent_framework/src/agent_framework/runtime/agent_runtime.py +++ b/agent_framework_oci/libs/agent_framework/src/agent_framework/runtime/agent_runtime.py @@ -1231,30 +1231,6 @@ class AgentRuntimeMixin: ) return result - @staticmethod - def _transaction_cancel_requested(text: str) -> bool: - """Reconhece cancelamento explícito de uma transação pendente. - - Mantém o conjunto deliberadamente pequeno para não confundir uma nova - ação de negócio (ex.: "cancelar meu serviço") com o cancelamento do - próprio fluxo pendente. - """ - normalized = " ".join(str(text or "").strip().casefold().split()) - exact = { - "cancelar", "cancele", "cancela", "desistir", "desisto", - "não quero continuar", "nao quero continuar", - "não quero mais", "nao quero mais", - "deixa pra lá", "deixa pra la", - } - if normalized in exact: - return True - contextual = ( - r"^(?:cancele|cancela|cancelar)\s+(?:essa|esta|a|o)?\s*(?:operação|operacao|solicitação|solicitacao|transação|transacao|pedido|processo)(?:\s+anterior)?$", - r"^(?:não|nao)\s+quero\s+mais\s+(?:contestar|continuar|prosseguir)(?:\s+.*)?$", - r"^(?:esqueça|esqueca)\s+(?:essa|esta|a)?\s*(?:solicitação|solicitacao|operação|operacao|transação|transacao)(?:\s+anterior)?$", - ) - return any(re.match(pattern, normalized) for pattern in contextual) - @staticmethod def _confirmation_decision(text: str) -> str | None: normalized = " ".join((text or "").strip().lower().split()) @@ -2072,28 +2048,19 @@ class AgentRuntimeMixin: self._normalize_transaction_lifecycle(state) # Uma transação em coleta/confirmação não pode aprisionar a sessão. O - # EnterpriseRouter sinaliza uma nova intent explícita em metadata; além - # disso, cancelamentos explícitos devem funcionar também durante coleta - # de parâmetros (antes, só funcionavam em AWAITING_CONFIRMATION). + # EnterpriseRouter é a única fonte para interrupção por mudança de intent. + # Não existe interpretação lexical de desistência no runtime: mudou a + # intent, a transação anterior é encerrada e seus latches são limpos. active_before_interruption = self._active_transaction(state) route_meta = (state.get("route_decision") or {}).get("metadata") or {} interruption = str(route_meta.get("transaction_interruption") or "").strip().lower() - cancel_requested = self._transaction_cancel_requested(str(text)) - if active_before_interruption and (interruption == "intent_shift" or cancel_requested): + if active_before_interruption and interruption == "intent_shift": interrupted_tool = active_before_interruption.get("tool_name") self._finish_active_transaction(state, "CANCELLED") state["tool_policy_result"] = { - "action": "cancelled_by_intent_shift" if interruption == "intent_shift" else "cancelled_by_user", + "action": "cancelled_by_intent_shift", "tool_name": interrupted_tool, } - if cancel_requested and interruption != "intent_shift": - return [{ - "ok": True, - "tool_name": interrupted_tool, - "transaction_status": "CANCELLED", - "cancelled": True, - "reason": "user_cancelled_pending_transaction", - }] # Clarificação de resultado de tool tem precedência: reutiliza a mesma tool # e argumentos, alterando apenas o parâmetro escolhido pelo usuário. diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/supervisor/__pycache__/__init__.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/supervisor/__pycache__/__init__.cpython-313.pyc index 58bef231ba0d231982ed3a88e57f54cb793adb2e..575e04446feca0c926f9c6724fbb31522e76f855 100644 GIT binary patch delta 29 kcmaFG_>ggeop6JGZeEFgN@7W(ewm?xVR?R0_Qa?O0Ha9?>i_@% delta 38 tcmaFJ_=<6YolK{GZeEFgN@7W(eqwrRUP*j%eqKppaeP{4h0(;o2>>7y4)*{6 diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/supervisor/__pycache__/supervisor.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/supervisor/__pycache__/supervisor.cpython-313.pyc index 2f03c938ed9566a0b750ac99658ac564f312a7a7..c0e787c92bef11e28e942d4983a09396c000655a 100644 GIT binary patch delta 32 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 diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/workflows/__pycache__/tool_executor.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/workflows/__pycache__/tool_executor.cpython-313.pyc index 7c022b3fd9101fc4ddfcba5206e611e8cd38719e..0eddccb76e5d41d802b4590d518f5c4800ae2a91 100644 GIT binary patch delta 32 ocmdnTzmD1HzGDewR{oK3~{glL#ME%6{)Vz}T Any: if not isinstance(path, str) or not path.startswith("$."): @@ -88,9 +92,54 @@ def _normalize_resume(value: Any, pause: WorkflowPause) -> Any: return normalized -def _exception_details(exc: Exception) -> dict[str, Any]: - """Preserve structured external-error facts without coupling the framework to a provider.""" - details: dict[str, Any] = {"type": type(exc).__name__} +def _type_shape(value: Any, *, depth: int = 0, max_depth: int = 4) -> Any: + """Return a value-free type map suitable for runtime diagnostics. + + We intentionally do not serialize values here: RunnableConfig may contain + process-local LangGraph objects and customer/business data. The diagnostic + only exposes keys, container sizes and Python type names. + """ + if depth >= max_depth: + return {"type": type(value).__name__} + if isinstance(value, dict): + return { + "type": "dict", + "size": len(value), + "keys": {str(k): _type_shape(v, depth=depth + 1, max_depth=max_depth) for k, v in value.items()}, + } + if isinstance(value, (list, tuple)): + sample = list(value[:5]) if isinstance(value, tuple) else value[:5] + return { + "type": type(value).__name__, + "size": len(value), + "items": [_type_shape(v, depth=depth + 1, max_depth=max_depth) for v in sample], + } + return {"type": type(value).__name__} + + +def _runtime_versions() -> dict[str, str]: + versions: dict[str, str] = {} + try: + from importlib.metadata import version + + for package in ("langgraph", "langgraph-checkpoint", "langchain-core"): + try: + versions[package] = version(package) + except Exception: + pass + except Exception: + pass + return versions + + +def _exception_details(exc: Exception, *, runtime_context: dict[str, Any] | None = None) -> dict[str, Any]: + """Preserve structured error facts plus a traceback for workflow runtime failures.""" + details: dict[str, Any] = { + "type": type(exc).__name__, + "traceback": "".join(traceback.format_exception(type(exc), exc, exc.__traceback__)), + } + if runtime_context: + details["runtime_diagnostics"] = runtime_context for attr in ("status_code", "body", "attempts", "code", "metadata"): value = getattr(exc, attr, None) if value not in (None, "", [], {}): @@ -123,6 +172,15 @@ class WorkflowRuntime: self._compiled: dict[tuple[str, int], Any] = {} self._fallback_paused: dict[str, dict[str, Any]] = {} + def _runtime_diagnostics(self, *, graph: Any | None, config: dict[str, Any], phase: str) -> dict[str, Any]: + return { + "phase": phase, + "config_shape": _type_shape(config), + "graph_type": type(graph).__name__ if graph is not None else None, + "checkpointer_type": type(self.checkpointer).__name__ if self.checkpointer is not None else None, + "versions": _runtime_versions(), + } + def _outgoing(self, definition: WorkflowDefinition) -> dict[str, list[Any]]: outgoing: dict[str, list[Any]] = {} for edge in definition.edges: @@ -439,9 +497,13 @@ class WorkflowRuntime: import langgraph # noqa: F401 except ModuleNotFoundError: return await self._run_fallback(definition, initial, start_node=definition.start, execution_id=eid) + phase = "compile" try: graph = self._compile(definition) + phase = "ainvoke" + logger.debug("workflow_langgraph_before_ainvoke diagnostics=%s", self._runtime_diagnostics(graph=graph, config=config, phase=phase)) state = await graph.ainvoke(initial, config=config) + phase = "aget_state" snapshot = await graph.aget_state(config) if getattr(snapshot, "next", None): interrupts = [] @@ -482,7 +544,12 @@ class WorkflowRuntime: workflow_version=definition.version, status="FAILED", error=str(exc), - error_details=_exception_details(exc), + error_details=_exception_details( + exc, + runtime_context=self._runtime_diagnostics( + graph=locals().get("graph"), config=config, phase=locals().get("phase", "unknown") + ), + ), output=dict(partial.get("nodes") or {}), state=partial, trace=list(partial.get("trace") or []), @@ -507,9 +574,13 @@ class WorkflowRuntime: from langgraph.types import Command except ModuleNotFoundError as exc: raise ModuleNotFoundError("langgraph não está instalado") from exc + phase = "compile" try: graph = self._compile(definition) + phase = "ainvoke_resume" + logger.debug("workflow_langgraph_before_resume diagnostics=%s", self._runtime_diagnostics(graph=graph, config=config, phase=phase)) state = await graph.ainvoke(Command(resume=resume_value), config=config) + phase = "aget_state_resume" snapshot = await graph.aget_state(config) if getattr(snapshot, "next", None): interrupts = [] @@ -545,7 +616,12 @@ class WorkflowRuntime: workflow_version=definition.version, status="FAILED", error=str(exc), - error_details=_exception_details(exc), + error_details=_exception_details( + exc, + runtime_context=self._runtime_diagnostics( + graph=locals().get("graph"), config=config, phase=locals().get("phase", "unknown") + ), + ), output=dict(partial.get("nodes") or {}), state=partial, trace=list(partial.get("trace") or []), diff --git a/agent_framework_oci/tests/__pycache__/conftest.cpython-313-pytest-9.0.2.pyc b/agent_framework_oci/tests/__pycache__/conftest.cpython-313-pytest-9.0.2.pyc index d7275c421c315ebb40f2c63327ded93b3d5cd77b..6ffeb338ea77c67061b7ddb1610bd1949463aba2 100644 GIT binary patch delta 31 mcmdnawvKH>0F!W#er{fgeoA6VqJEj7fnj-mQTF5xWb diff --git a/agent_framework_oci/tests/__pycache__/test_aoferta_transaction_continuation.cpython-313-pytest-9.0.2.pyc b/agent_framework_oci/tests/__pycache__/test_aoferta_transaction_continuation.cpython-313-pytest-9.0.2.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b04726277abc3398e7ae9d2f336dd5dcd67a1d8e GIT binary patch literal 8835 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 literal 0 HcmV?d00001 diff --git a/agent_framework_oci/tests/__pycache__/test_phraseology_rewrite_revalidation.cpython-313-pytest-9.0.2.pyc b/agent_framework_oci/tests/__pycache__/test_phraseology_rewrite_revalidation.cpython-313-pytest-9.0.2.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6fa7a4b845d60bab40d8089cc5f9201c5cd34ae0 GIT binary patch literal 13627 zcmeG@TWlLwb~EIV9Fmf#r!852j1)UDBQIsymLFLwsbb5qEJsc`Vj@RL2P1JTGp0Do z9ZHVm1n?%?jf_p#2~f8#)*tB$u|5g}ML&$99}Qd-Ew%+n%MQ7N1P%H^{n5gXcCpxe zv_0p}+?f$YTNdgTT_6|KnRDNF&N=toIp zz$Q)(_WG>xNi8i4L7C{3)x8!>Ze6fA749`k zG2)YX;+K6yko_be3!6Bg7EpnNynShNfrr$vGVQt_c0j=&@wKJaFpd zsTT(O&7e+~F~O$5nUIKn_*-rSaE0sUQd|}=D@}5X{O}@g1}4;O)&N8>wCouJ`%C6@ z_f#(5omTT|_iWE@WzTt?oaPitlfSgN1b(q@oos-fr1Izt8GX6CgtFzv0pDNbp571_He5ZKzB8C0jI zwOpDYRY*I4xIpT$LZIzK$aU-(8qewz=d`qe3P6HH8nBg!+?;_$tmYo8i9tK#MF4k^ ze`3w$$fZb0Y%7XwSGV65x0M^edm~?LJhBk_BGz7RX!+f?kJ`$u+kSiKUlYB>`loN~ z`KoQM0UcfpqbTJ-5@IV7(gB!EAIUayDWI_Gm_cWT?LX2-N&f z_TO5&PUdAF>@2^`!_E?%ofQ}!A+5NwHUygxNC4t~D$S%53!VUAprAAHwRr7$Q6{t7 zvE^X^)n(Qrsm-nBrk1itM593z(IAQ_Zk2a?57+`qaOaZ*0#}H3V-^AO(h;KOl|m#k zA;N|$SnO!MKLr+AR3Br4<(z*xehgolre6D*gR^H7=s3`9j+V2l-~kl&vr|{%#!z-? z8tv%z$^J1^#Wb z6)6`QLj^@s5;amq^3nk_}vQVu8l-vPCoi^vfp&U z^()F%mshd>HD1*KugY@$LwJQeTOY52lvfSk^6D|~D~nf+-|}kZZeO>y{6E`QDL%=R zoM*|o+OaN{P1Q0Tl@sMA{ohh`&m4#S_Ft^q3w#0K+ocOxlP`bs<%((hIO3G4Qwp-v`^+*IGsh%`2U&m zeJh!kbUsir+aJ4R`WQFXC7Dhykj(ZqrO44IYqtFVDw)CLE>~|bOx}!!O|C}XqF+me zPkO&d^cA|Nx_;bBzeu)?{;jJY*QAg0^|)rsFypH%C46+}Hp(4xXR6WOxlQZXxlL>= z_Oy2{p8Y?SpV$QUL=vseiqz!TC17dwu*#HU7kDhAKL9IdyS!sv()}o>1Ddlmv6VYg z{wSxzs@@>&JJzJU^L}f#bb0io{Vm-{N#9`Pwgp7bAs09%v;n1r^K2~1UADgXz(f3(>ZU|ATmjaKMY z#i3cnRCNYUiVo@^@EPSCf^2QnRCQV;X&}MLQpDQ}SSEPhqpaHzqtoI+#yB4LgK`WUZS`_P!f(5- z8;4EtSpUhB&-Wi0TI9d+_4UR1`NkbPr{{LZ4XLkBLUeE30LQUvoQwNRKhz-DJ71?7 zhDP$VMK82~!d~i(GwZ;uP0|D+8xUSdYXm(@Y}g)m*nM$hkq+2{efNFi9dN%j;Qo=B zKZ4F(VI+!VXhX=g8oT(vgwmNhztNHA9jiR-tAL>0n}ar9LH03kXto0Ky;nNstL_7=Vzjl{%jOn){zMbPJIhNjiItgb>7;oNzZ zOd4hsC%2fm$D8S~GS+bWyqW#pX1l$az20WM-pm05-GJbmb&?fhs6C^e)0BxUEFg1c z&U~Fx$s1atp$R2p*fFNOmQx`9rYUMJtx!6Gd>KGjPw+`gCk|UQd^WLS_l_+_LSS;A_}_KU@(j8SoDT_-~F| z!8|8qmvDBVk+33(is3{w6O8D}`Aq(-;&f6#RE{nB%>eFP!;EP;bsSPGd+`i@XB?ljmV1@+<;6+olIAPMD>K&EhV7jTz=e zreW{mmkW6Yz#>0Cgag5{DtiNcc48H*)2af*UOK!SEFFT_uxucneFhywie$B%8MJi7 zVt~@G;R2fvAVPNp81$rONOyUhR$s-HJoyh_H3UxN&cP?j3-?)3CS3TF&TT{xyl zKfcmY1Oot81Zyw|vlsrBk+#;?U5absjyHk(rXdMsl}YCC`(u{75|FqTA!ZciByN(Y zAC5k=$#V%nNe;Y{92^-Kj`LOw*Jt_?yG@=LNMRPM4IhRB5p+Iz);#^Vb*M8me(M+* z5JCM}s2jX>@c5@amuD}{7TOY@#}bgW<6`Yw`1xHP@&oZK{sQ7|4a$1hHNaofujb}J zJaIFS2?R#~#M`VuELsC*$bjfJ#A}Iwxu8D&hBlG6f_y=1j}iR3GXwA)bLtgSl>3JV zM+RRVFhi%Fmj{wB4-lX?M+Om`Krn>hB!U+a3?n!NzzjQEhKA9}S*!t@2i8C|V@|CNrRX$T-%IV7=$u-A6{T_+_Wq zI&=?`SFl)iiYr2AQ^z@L^*!@)i2FA&plh=b-hw!n-BQfL>%+ih1 zslX$Yk$Py1LqOh?nCd*>W&jS8H@MjUOv9la& zUeL>}TfV4i{NeHYA)zsN@x;9vfB4WnKNqOISI2FTN-aB!EjzEbe!6F=Wq&EQ|Kjnl zqD_~dyYyT^IDKutw5_|it^3b5m3odAdyf9q8>M5T#bcv|t*4~SB-p=O!s&(Q zoScQ+fwF^6S<%g5h240Gj^s3Q5GXH4_Z5WU(&6Fa;bA}ufJBE&P(pvVpd`A_!f$(U zrvVh+IPT<%Zte~p%c*gYC1JQ6eX1bzmAd+hU44KQ0EzaMpoIQzK}qx}3%~8bod!^N z9bVXOUUYMJ=vYpTgDeSsbTeQd^Ar@L9VNm3EeRck=w>HtA$PEo zQ&wD&vx+|#Iu>TVEm#h^F1&43c!vi3W8P`+AkZ45_wt2H7Yc#|>%6cOZ3pb?n-mnI zQc19XOM+C0wmVr1xr3dYvf_%IRs6XiEzEdZupD$W^dN#qBQ!ecdNd$B?p&)#La?MVRq-Ubvs87srUJ|(!&XN8`OluU7R2Ud814b*-{}g-leH zs0a8~*I1p~3szO{;d<3~ak;i;OEg@MHK}MvhsEo!%aVJ8WnM$ijCJ1{@b>5bh8jf+Bib z(Bvg@3^NG;a4GgB#L+!QejNeYYb)=3eFA0+89q> zhvvt~ZvdP@tLZU#HmKbWM(`jS=WF>jY||WkwU&(sh;8G6wU>DQ<4w~KtFB3;55u8OpEvz>X%OaQp5O1_h#J8{jWrEBh*nwak0ZwTK z=qeu70CcUscs;p@gV2f5sw3Y+@Gb)6)GAA^*RteZ#k%(qpz%iV1BCo0f*&AQ0AS!o z^B7_jwXCtB$0%jwBbWvy!?*^t^qOQ}3jwgj>Z;meBG_WrH(VG0a>w7j{FBjQ!^;;> zlw%`cYPD=zZGXY%q-B34uDwy(nka5f6ofV*TuzOHz>L2Vb_19nUk5y2v(cf={AacNP@C{s-H95=#G6c!g$AhK z!faElU_!!j2jIV2*lNfOJ4NWKp#PIWg&y2xH2z?2dx(d49R3-F{;Pnw>mjXH`H?Mu z%##o4LPJ0v7mdfMBTT8}9Q)1x#?QD8Q`nh=}`YDMeiTY)R2JuGa`9<0KiRr0% zCGpAmc_oR(@%hP_@g=Fb1v!Z&shh8{1+Yv$!LKsei(AIgp(M2!tSl`vy|gGbB|bZ~ z5~x2VKC?JJwW1&=GdZ&)z9=<4u_z@cwHRnpT6|7sNorAIPJB*kUV2H!ob~d3f{oK3~{glL#MEx>DleE&DoXwxu0$3Q` zCja7Bo}9)lqoZAtT3ixeoRMEt5}%%$ms*sW9G{(9S)N~%5}%TvS{$F3UlLzXl$x4b zP%`;2w@JO@1#Z&~iI=%8F0eT6aD@=uj@MbB=rRjXfd!lkVO(W#yuvLF7rcnX1xf)8 vgiApfS6Cc3B*Mjj%3-{#EGh`&fmZEsg{y)yuCX|7p2f2)oY85rW6B%=s}^Zq diff --git a/agent_framework_oci/tests/__pycache__/test_transaction_confirmation_customer_facing.cpython-313-pytest-9.0.2.pyc b/agent_framework_oci/tests/__pycache__/test_transaction_confirmation_customer_facing.cpython-313-pytest-9.0.2.pyc index 5f8dc4971b5e852fe19590d836b0216d2ce334e4..29f28f7091b38d93f47f69b839f108099742b610 100644 GIT binary patch delta 32 ncmcbmdQo))JEw5Der{fgeoA6VqJEj7fnj-mQTAqK&h4B4y}%0~ delta 41 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 literal 0 HcmV?d00001 diff --git a/agent_framework_oci/tests/__pycache__/test_transaction_state_router_interruption.cpython-313-pytest-9.0.2.pyc b/agent_framework_oci/tests/__pycache__/test_transaction_state_router_interruption.cpython-313-pytest-9.0.2.pyc index a5d8faafc22d00d713d2fa63e6d155fa1c949193..5029b3b55937c0727254179839d292d84ce9e9fb 100644 GIT binary patch delta 4208 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 delta 1852 zcma)-4Qx|Y6o7l)Uf;TP?MC)f*t$_V1Y2Rlu~8h({YV+pLBP_XS>n>P54YL1c-u{W zM1~qO+-8uoNFpjDGY$q3^o^1DhXgf-VKNx7F%tRF1d53uWE+qez2}u|M2XtG_vQTE zbI*OJ_ufO>#jXy~aLZtb*YMxxGh6D*J~bRqFb8uR0^wYrC+x}fydVd{HQrz#>?scnrVkHH)@bmW8H{m6DyZ+(mGj@vROw6e$-w-bjFQMc!Q<10~AQk8VIw36EJ~ zi(zFH`y^p8hfT|DRGXdfJ;6&T<*pH3T_7y8fXC0u@VAfuNL$X0A^vJg*nA4A;4-T~ z*fUG006f?=6-)PgW>&4Qkbe`QiU-*Q_Pi=9iFRUY?q8tec3*Ebl9d*Gf5;C@aADs@s8@RXF2sTtJHD=uX2Tgr9s)O(AL$0aa{b8d zc<#J<{3+F$i7Wb3G9Ek~RVd@;{vfQu>-}ZMS~BGEIYUMcT+x?;t`n6IP&!Xs(j|Gx zTTS?a$|}Kk&Y1a{J~@+~ltI=@WOWc^96sZa=yUV0lX`ZJN%gBsl(~_&o>n(v!XM9? zFnG2Lnv~?B=ce$g8?LU;nGBxHm`=H9EW1n-{7mQ}lwwLGyF#rhjr0!)YK9bweozKR zj%aZtZJ`k_aml9R?P`u#r7$UR*y(#%V2yYxQVZ*qMb{Q+^?DxqI)86{D`9c%OzgaF z5pv?tb2k$V*nZBGXw-7AE0Gw8aV6o=MGo9ISZhem5#~zVCb?2vskr4Q*A&4OjZM23 zYr>i<1+i1z+I(!P3b!(zq7V@nW!P ze9839Tw5e?OBF{%)2#W$m^o;i-duh$)iIn=gy+ZS#r-}#$sjd5Z_LC|=j_dedo~V; z94GaKL*l*Xlvp^Regh?@Y#*fXiLeQQn}-qF-8<%qa4qY!IOk@f#^{7Qp(MW6*YcNHpbwU1iiay_ zFS95&@4Ow0wlSMiWbbkJh*rG-*iMpl4RY8+w*njD>IN~qZf;(5CnxZa#WoVoa}PU4 zphn@!v9$aVBMUZ$Fur`pK>?OR>)|^BY>$ymRDqeG-bD_QiU{Qd^=MWP#d=cxgaDz5u%19o gXRQR8mZ08Sbl8vR9&ZTx8vXJU?A^mRByb9U0laGx%K!iX diff --git a/agent_framework_oci/tests/test_aoferta_transaction_continuation.py b/agent_framework_oci/tests/test_aoferta_transaction_continuation.py new file mode 100644 index 0000000..37bf869 --- /dev/null +++ b/agent_framework_oci/tests/test_aoferta_transaction_continuation.py @@ -0,0 +1,75 @@ +import pytest + +from agent_framework.guardrails.rails import ProactiveOfferRail + + +@pytest.mark.asyncio +@pytest.mark.parametrize("status", ["COLLECTING_PARAMETERS", "AWAITING_CONFIRMATION"]) +async def test_aoferta_bypasses_transaction_continuation_without_llm(monkeypatch, status): + async def should_not_run(*args, **kwargs): + raise AssertionError("AOFERTA LLM must not run for transaction continuation") + + monkeypatch.setattr( + "agent_framework.guardrails.rails.classify_with_framework_llm", + should_not_run, + ) + + decision = await ProactiveOfferRail().evaluate( + "Para prosseguir, informe valor.", + {"transaction_status": status}, + ) + + assert decision.allowed is True + assert decision.code == "AOFERTA" + assert decision.metadata["mechanism"] == "deterministic_transaction_bypass" + assert decision.metadata["transaction_status"] == status + + +@pytest.mark.asyncio +async def test_aoferta_detects_transaction_continuation_from_mcp_results(monkeypatch): + async def should_not_run(*args, **kwargs): + raise AssertionError("AOFERTA LLM must not run for transaction continuation") + + monkeypatch.setattr( + "agent_framework.guardrails.rails.classify_with_framework_llm", + should_not_run, + ) + + decision = await ProactiveOfferRail().evaluate( + "Você confirma o cancelamento do serviço TIM Fashion?", + { + "mcp_results": [ + { + "tool_name": "cancelar_vas_avulso", + "awaiting_confirmation": True, + "transaction_status": "AWAITING_CONFIRMATION", + } + ] + }, + ) + + assert decision.allowed is True + assert decision.metadata["transaction_status"] == "AWAITING_CONFIRMATION" + + +@pytest.mark.asyncio +async def test_aoferta_still_calls_llm_outside_transaction_continuation(monkeypatch): + calls = [] + + async def fake_classify(*args, **kwargs): + calls.append((args, kwargs)) + return {"allowed": False, "reason": "oferta proativa"} + + monkeypatch.setattr( + "agent_framework.guardrails.rails.classify_with_framework_llm", + fake_classify, + ) + + decision = await ProactiveOfferRail().evaluate( + "Quer aproveitar e cancelar outro serviço?", + {"transaction_status": "COMPLETED"}, + ) + + assert len(calls) == 1 + assert decision.allowed is False + assert decision.metadata["mechanism"] == "llm_supervisor" diff --git a/agent_framework_oci/tests/test_fraseologia_business_parameter_prompt.py b/agent_framework_oci/tests/test_fraseologia_business_parameter_prompt.py new file mode 100644 index 0000000..d621878 --- /dev/null +++ b/agent_framework_oci/tests/test_fraseologia_business_parameter_prompt.py @@ -0,0 +1,23 @@ +from agent_framework.guardrails.calibrated.prompts.fraseologia import build_fraseologia_prompt + + +def test_fraseologia_prompt_allows_business_parameter_collection(): + prompt = build_fraseologia_prompt("[ContestacaoAgent] Para prosseguir, informe valor.") + assert '"Para prosseguir, informe valor."' in prompt + assert "DADOS DE NEGOCIO" in prompt + assert "NAO expoe raciocinio" in prompt + + +def test_fraseologia_prompt_distinguishes_business_data_from_internal_keys(): + prompt = build_fraseologia_prompt("Informe subject.") + assert '"subject"' in prompt + assert '"asset_id"' in prompt + assert '"invoice_id"' in prompt + assert '"COLLECTING_PARAMETERS"' in prompt + assert '"AWAITING_CONFIRMATION"' in prompt + + +def test_fraseologia_prompt_keeps_natural_transaction_confirmation_allowed(): + prompt = build_fraseologia_prompt("Voce confirma o cancelamento do servico TIM Fashion?") + assert "confirmacoes de uma acao ja em andamento" in prompt + assert "TIM Fashion" in prompt diff --git a/agent_framework_oci/tests/test_phraseology_rewrite_revalidation.py b/agent_framework_oci/tests/test_phraseology_rewrite_revalidation.py new file mode 100644 index 0000000..00d6989 --- /dev/null +++ b/agent_framework_oci/tests/test_phraseology_rewrite_revalidation.py @@ -0,0 +1,114 @@ +import pytest + +from agent_framework.guardrails.base import RailDecision +from agent_framework.guardrails.output_supervisor import OutputSupervisor +from agent_framework.guardrails.rail_action import RailAction + + +class PhraseologyRail: + code = "FRASEOLOGIA" + stage = "output" + + def __init__(self): + self.calls = [] + + async def evaluate(self, text, context): + self.calls.append(text) + blocked = "categoria tratável por esta operação" in text + return RailDecision( + code=self.code, + allowed=not blocked, + reason=( + "remova linguagem interna" if blocked else "" + ), + sanitized_text=text, + metadata={"calibrated": True}, + ) + + +class AllowRail: + code = "AOFERTA" + stage = "output" + + def __init__(self): + self.calls = [] + + async def evaluate(self, text, context): + self.calls.append(text) + return RailDecision( + code=self.code, + allowed=True, + reason="", + sanitized_text=text, + metadata={"calibrated": True}, + ) + + +@pytest.mark.asyncio +async def test_phraseology_block_is_rewritten_once_and_all_rails_are_revalidated(monkeypatch): + phrase = PhraseologyRail() + allow = AllowRail() + + async def fake_classify(llm, task, payload, **kwargs): + assert task == "FALLBACK" + assert payload["context"]["guardrail_code"] == "FRASEOLOGIA" + return { + "allowed": True, + "label": "FALLBACK", + "reason": ( + '[ContestacaoAgent] O item "TIM CTRL Redes Sociais 8.0" no valor de ' + 'R$ 71,99 é um plano da sua fatura e, por isso, não pode ser ' + 'contestado como serviço adicional.' + ), + } + + monkeypatch.setattr( + "agent_framework.guardrails.output_supervisor.classify_with_framework_llm", + fake_classify, + ) + + supervisor = OutputSupervisor( + rails=[allow, phrase], + enable_parallel=False, + llm=object(), + ) + original = ( + '[ContestacaoAgent] O item "TIM CTRL Redes Sociais 8.0" no valor de ' + 'R$ 71,99 é classificado como plano na sua fatura. Não é possível ' + 'contestar esse tipo de cobrança por este canal, pois planos não pertencem ' + 'a uma categoria tratável por esta operação.' + ) + + decision = await supervisor.evaluate(original, {}) + + assert decision.action in {RailAction.ALLOW, RailAction.SANITIZE, RailAction.OBSERVE} + assert "categoria tratável por esta operação" not in decision.candidate + assert "TIM CTRL Redes Sociais 8.0" in decision.candidate + assert "R$ 71,99" in decision.candidate + assert len(phrase.calls) == 2 + assert len(allow.calls) == 2 + assert decision.metadata["phraseology_rewritten"] is True + assert any(r.code == "FRASEOLOGIA_REWRITE" for r in decision.results) + + +@pytest.mark.asyncio +async def test_phraseology_rewrite_does_not_loop_when_rewritten_text_is_still_blocked(monkeypatch): + phrase = PhraseologyRail() + + async def fake_classify(llm, task, payload, **kwargs): + return { + "allowed": True, + "label": "FALLBACK", + "reason": payload["text"] + " ", + } + + monkeypatch.setattr( + "agent_framework.guardrails.output_supervisor.classify_with_framework_llm", + fake_classify, + ) + supervisor = OutputSupervisor(rails=[phrase], enable_parallel=False, llm=object()) + original = "planos não pertencem a uma categoria tratável por esta operação" + + decision = await supervisor.evaluate(original, {}) + assert decision.action == RailAction.BLOCK + assert len(phrase.calls) == 1 diff --git a/agent_framework_oci/tests/test_route_stickiness_transaction_shift.py b/agent_framework_oci/tests/test_route_stickiness_transaction_shift.py index 261c677..3ce6135 100644 --- a/agent_framework_oci/tests/test_route_stickiness_transaction_shift.py +++ b/agent_framework_oci/tests/test_route_stickiness_transaction_shift.py @@ -10,9 +10,9 @@ def test_explicit_keyword_shift_preempts_stickiness(): assert EnterpriseRouter._is_explicit_intent_shift(d) is True -def test_short_generic_keyword_does_not_preempt(): +def test_configured_keyword_is_explicit_regardless_of_literal_length(): d = RouteDecision(route="x", agent="x", intent="x", method="keyword", metadata={"matched_keyword": "id"}) - assert EnterpriseRouter._is_explicit_intent_shift(d) is False + assert EnterpriseRouter._is_explicit_intent_shift(d) is True def test_same_agent_explicit_intent_shift_must_preempt_stickiness(): diff --git a/agent_framework_oci/tests/test_transaction_state_regression_matrix.py b/agent_framework_oci/tests/test_transaction_state_regression_matrix.py new file mode 100644 index 0000000..cf63fe0 --- /dev/null +++ b/agent_framework_oci/tests/test_transaction_state_regression_matrix.py @@ -0,0 +1,397 @@ +from types import SimpleNamespace + +import pytest + +from agent_framework.routing.enterprise_router import EnterpriseRouter +from agent_framework.runtime.agent_runtime import AgentRuntimeMixin + + +ROUTING_YAML = """ +router: + fallback_agent: billing_agent + confidence_threshold: 0.70 +state_policies: + - state: COLLECTING_ORDER_PARAMETERS + agent: orders_agent + - state: WAITING_ORDER_CONFIRMATION + agent: orders_agent +intents: + - name: retail_order_cancel + domain: retail + agent: orders_agent + priority: 30 + keywords: [cancelar pedido] + - name: retail_order_tracking + domain: retail + agent: orders_agent + priority: 20 + keywords: [rastrear pedido, pedido] + - name: billing_invoice_explanation + domain: telecom + agent: billing_agent + priority: 40 + keywords: [fatura, vencimento] +""" + + +def _router(tmp_path, *, stickiness=True): + routing = tmp_path / "routing.yaml" + routing.write_text(ROUTING_YAML, encoding="utf-8") + settings = SimpleNamespace( + ROUTING_CONFIG_PATH=str(routing), + ENABLE_LLM_ROUTER=False, + ENABLE_ROUTE_STICKINESS=stickiness, + ) + return EnterpriseRouter(settings) + + +def _active_tx(status="COLLECTING_PARAMETERS", arguments=None): + return { + "tool_name": "cancelar_pedido", + "status": status, + "started_from_intent": "retail_order_cancel", + "arguments": dict(arguments or {"order_id": "PED-1001"}), + } + + +@pytest.mark.asyncio +@pytest.mark.parametrize("message", ["PED-1001", "12345", "R$ 71,99", "10/09/2026"]) +async def test_matrix_collecting_parameter_answers_keep_transaction(message, tmp_path): + router = _router(tmp_path) + state = { + "user_text": message, + "sanitized_input": message, + "next_state": "COLLECTING_ORDER_PARAMETERS", + "transaction_status": "COLLECTING_PARAMETERS", + "missing_parameters": ["order_id"], + "active_transaction": _active_tx(arguments={}), + "active_agent": "orders_agent", + "intent": "state:COLLECTING_ORDER_PARAMETERS", + "route_decision": {"agent": "orders_agent", "intent": "retail_order_cancel"}, + } + decision = await router.route(state) + assert decision.method == "state" + assert decision.agent == "orders_agent" + assert (decision.metadata or {}).get("transaction_interruption") is None + + +@pytest.mark.asyncio +async def test_matrix_missing_next_state_recovers_active_transaction_before_stickiness(tmp_path): + router = _router(tmp_path) + state = { + "user_text": "PED-1001", + "sanitized_input": "PED-1001", + "next_state": None, + "transaction_status": "COLLECTING_PARAMETERS", + "missing_parameters": ["order_id"], + "selected_tool_call": {"tool_name": "cancelar_pedido", "arguments": {}}, + "active_transaction": _active_tx(arguments={}), + "active_agent": "orders_agent", + "route": "orders_agent", + "route_decision": {"agent": "orders_agent", "intent": "retail_order_cancel"}, + } + decision = await router.route(state) + assert decision.method == "state" + assert decision.metadata["transaction_state_recovered"] is True + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + "status", + ["COMPLETED", "FAILED", "CANCELLED", "BLOCKED", "OUT_OF_SCOPE"], +) +async def test_matrix_terminal_transaction_never_recovers_from_latch(status, tmp_path): + router = _router(tmp_path, stickiness=False) + state = { + "user_text": "quero ver minha fatura", + "sanitized_input": "quero ver minha fatura", + "next_state": None, + "transaction_status": status, + # Deliberately stale latch: terminal status must win. + "active_transaction": _active_tx(status=status), + "selected_tool_call": {"tool_name": "cancelar_pedido", "arguments": {"order_id": "PED-1001"}}, + "active_agent": "orders_agent", + "intent": "retail_order_cancel", + "route_decision": {"agent": "orders_agent", "intent": "retail_order_cancel"}, + } + decision = await router.route(state) + assert decision.intent == "billing_invoice_explanation" + assert (decision.metadata or {}).get("transaction_state_recovered") is not True + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + "status", + ["COMPLETED", "FAILED", "CANCELLED", "BLOCKED", "OUT_OF_SCOPE"], +) +async def test_matrix_terminal_transaction_ignores_stale_next_state(status, tmp_path): + router = _router(tmp_path, stickiness=False) + state = { + "user_text": "quero ver minha fatura", + "sanitized_input": "quero ver minha fatura", + # Simulate a partially persisted/legacy state where terminal status was + # written but the transactional next_state was not cleared. + "next_state": "COLLECTING_ORDER_PARAMETERS", + "transaction_status": status, + "active_transaction": _active_tx(status=status), + "active_agent": "orders_agent", + "intent": "retail_order_cancel", + "route_decision": {"agent": "orders_agent", "intent": "retail_order_cancel"}, + } + decision = await router.route(state) + assert decision.intent == "billing_invoice_explanation" + assert decision.agent == "billing_agent" + assert decision.method != "state" + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + "status,next_state", + [ + ("COLLECTING_PARAMETERS", "COLLECTING_ORDER_PARAMETERS"), + ("AWAITING_CONFIRMATION", "WAITING_ORDER_CONFIRMATION"), + ], +) +async def test_matrix_clear_intent_shift_preempts_active_transaction(status, next_state, tmp_path): + router = _router(tmp_path) + state = { + "user_text": "esquece, quero ver minha fatura", + "sanitized_input": "esquece, quero ver minha fatura", + "next_state": next_state, + "transaction_status": status, + "active_transaction": _active_tx(status=status), + "active_agent": "orders_agent", + "intent": f"state:{next_state}", + "route_decision": {"agent": "orders_agent", "intent": "retail_order_cancel"}, + } + decision = await router.route(state) + assert decision.intent == "billing_invoice_explanation" + assert decision.agent == "billing_agent" + assert decision.metadata["transaction_interruption"] == "intent_shift" + + +@pytest.mark.asyncio +async def test_matrix_route_stickiness_without_transaction_preserves_previous_behavior(tmp_path): + router = _router(tmp_path) + # Generic short follow-up: no transaction latch, no explicit new intent. + state = { + "user_text": "e o status?", + "sanitized_input": "e o status?", + "next_state": None, + "transaction_status": None, + "active_transaction": None, + "active_agent": "orders_agent", + "intent": "retail_order_tracking", + "route_decision": { + "route": "orders_agent", + "agent": "orders_agent", + "intent": "retail_order_tracking", + "domain": "retail", + "mcp_tools": [], + }, + "context": {"session": {}}, + } + decision = await router.route(state) + # Semantic continuity may be disabled by settings/profile defaults in the + # isolated test; regardless, transaction recovery must not be involved. + assert (decision.metadata or {}).get("transaction_state_recovered") is not True + + +class _PolicyRouter: + def __init__(self): + self.registry = SimpleNamespace( + tools={"cancelar_pedido": object()}, + get_tool=lambda name: SimpleNamespace(selection_keywords=["cancelar pedido", "cancelar"]), + ) + + def resolve_execution_policy(self, tool_name, arguments=None): + return { + "operation_type": "transactional", + "require_confirmation": True, + "requires": ["order_id"], + "policy_source": "matrix-test", + } + + def validate_execution_policy(self, tool_name, arguments=None): + return True, None, self.resolve_execution_policy(tool_name, arguments) + + +class _Runtime(AgentRuntimeMixin): + def __init__(self, *, call_ok=True): + self.tool_router = _PolicyRouter() + self.calls = [] + self.call_ok = call_ok + + async def _call_mcp_tool(self, tool_name, arguments, state): + self.calls.append((tool_name, dict(arguments))) + return {"ok": self.call_ok, "tool_name": tool_name, "result": {"status": "DONE" if self.call_ok else "ERROR"}} + + +@pytest.mark.asyncio +async def test_matrix_confirmation_yes_completes_and_clears_latches(): + runtime = _Runtime(call_ok=True) + state = { + "user_text": "sim", + "sanitized_input": "sim", + "transaction_status": "AWAITING_CONFIRMATION", + "active_transaction": _active_tx(status="AWAITING_CONFIRMATION"), + "pending_tool_call": {"tool_name": "cancelar_pedido", "arguments": {"order_id": "PED-1001"}}, + } + result = await runtime.execute_tools_for_intent(state, tools=[]) + assert result[-1]["ok"] is True + assert state["transaction_status"] == "COMPLETED" + assert state["active_transaction"] is None + assert state["next_state"] is None + + +@pytest.mark.asyncio +async def test_matrix_confirmation_no_cancels_and_clears_latches(): + runtime = _Runtime(call_ok=True) + state = { + "user_text": "não", + "sanitized_input": "não", + "transaction_status": "AWAITING_CONFIRMATION", + "active_transaction": _active_tx(status="AWAITING_CONFIRMATION"), + "pending_tool_call": {"tool_name": "cancelar_pedido", "arguments": {"order_id": "PED-1001"}}, + } + result = await runtime.execute_tools_for_intent(state, tools=[]) + assert result[-1]["transaction_status"] == "CANCELLED" + assert state["transaction_status"] == "CANCELLED" + assert state["active_transaction"] is None + assert state["next_state"] is None + assert runtime.calls == [] + + +@pytest.mark.asyncio +async def test_matrix_runtime_does_not_cancel_from_literal_words_without_intent_shift(): + runtime = _Runtime(call_ok=True) + state = { + "user_text": "texto livre sem classificação de nova intent", + "sanitized_input": "texto livre sem classificação de nova intent", + "transaction_status": "COLLECTING_PARAMETERS", + "active_transaction": _active_tx(status="COLLECTING_PARAMETERS", arguments={}), + "selected_tool_call": {"tool_name": "cancelar_pedido", "arguments": {}}, + "missing_parameters": ["order_id"], + } + await runtime.execute_tools_for_intent(state, tools=[]) + assert state["transaction_status"] != "CANCELLED" + assert state["active_transaction"] is not None + + +@pytest.mark.asyncio +async def test_matrix_intent_shift_cancels_old_transaction_before_new_tool_path(): + runtime = _Runtime(call_ok=True) + state = { + "user_text": "quero ver minha fatura", + "sanitized_input": "quero ver minha fatura", + "transaction_status": "COLLECTING_PARAMETERS", + "active_transaction": _active_tx(status="COLLECTING_PARAMETERS", arguments={}), + "selected_tool_call": {"tool_name": "cancelar_pedido", "arguments": {}}, + "missing_parameters": ["order_id"], + "route_decision": {"metadata": {"transaction_interruption": "intent_shift"}}, + } + await runtime.execute_tools_for_intent(state, tools=[]) + assert state["transaction_status"] == "CANCELLED" + assert state["active_transaction"] is None + assert state["tool_policy_result"]["action"] == "cancelled_by_intent_shift" + + +@pytest.mark.asyncio +async def test_matrix_intent_shift_clears_arguments_and_restart_starts_from_zero(): + runtime = _Runtime(call_ok=True) + state = { + "user_text": "nova intenção", + "sanitized_input": "nova intenção", + "transaction_status": "COLLECTING_PARAMETERS", + "active_transaction": _active_tx( + status="COLLECTING_PARAMETERS", + arguments={"order_id": "PED-OLD"}, + ), + "selected_tool_call": { + "tool_name": "cancelar_pedido", + "arguments": {"order_id": "PED-OLD"}, + }, + "pending_tool_call": { + "tool_name": "cancelar_pedido", + "arguments": {"order_id": "PED-OLD"}, + }, + "missing_parameters": [], + "route_decision": {"metadata": {"transaction_interruption": "intent_shift"}}, + } + await runtime.execute_tools_for_intent(state, tools=[]) + + assert state["transaction_status"] == "CANCELLED" + assert state["active_transaction"] is None + assert state["selected_tool_call"] == {} + assert state["pending_tool_call"] == {} + assert state["missing_parameters"] == [] + assert state["next_state"] is None + assert state["last_transaction"]["arguments"] == {"order_id": "PED-OLD"} + + # Se a intent antiga voltar depois, o histórico fica apenas em last_transaction; + # nenhum argumento operacional é restaurado para a nova transação. + state["transaction_status"] = None + state["route_decision"] = {} + state["user_text"] = "iniciar novamente" + state["sanitized_input"] = "iniciar novamente" + state["mcp_tools"] = ["cancelar_pedido"] + await runtime.execute_tools_for_intent(state, tools=["cancelar_pedido"]) + assert state.get("selected_tool_call", {}).get("arguments", {}).get("order_id") != "PED-OLD" + assert (state.get("active_transaction") or {}).get("arguments", {}).get("order_id") != "PED-OLD" + + +@pytest.mark.asyncio +async def test_matrix_tool_failure_is_terminal_and_not_reactivated(): + runtime = _Runtime(call_ok=False) + state = { + "user_text": "sim", + "sanitized_input": "sim", + "transaction_status": "AWAITING_CONFIRMATION", + "active_transaction": _active_tx(status="AWAITING_CONFIRMATION"), + "pending_tool_call": {"tool_name": "cancelar_pedido", "arguments": {"order_id": "PED-1001"}}, + } + await runtime.execute_tools_for_intent(state, tools=[]) + assert state["transaction_status"] == "FAILED" + assert state["active_transaction"] is None + assert state["pending_tool_call"] == {} + assert state["next_state"] is None + + +@pytest.mark.parametrize("status", ["COMPLETED", "FAILED", "CANCELLED", "BLOCKED", "OUT_OF_SCOPE"]) +def test_matrix_normalization_clears_terminal_operational_latches(status): + runtime = _Runtime() + state = { + "transaction_status": status, + "next_state": "COLLECTING_ORDER_PARAMETERS", + "active_transaction": _active_tx(status=status), + "selected_tool_call": {"tool_name": "cancelar_pedido", "arguments": {"order_id": "PED-1001"}}, + "pending_tool_call": {"tool_name": "cancelar_pedido", "arguments": {"order_id": "PED-1001"}}, + "missing_parameters": ["order_id"], + "confirmation_required": True, + } + runtime._normalize_transaction_lifecycle(state) + assert state["active_transaction"] is None + assert state["selected_tool_call"] == {} + assert state["pending_tool_call"] == {} + assert state["missing_parameters"] == [] + assert state["next_state"] is None + assert state["last_transaction"]["status"] == status + +@pytest.mark.asyncio +@pytest.mark.parametrize("status", ["COMPLETED", "FAILED", "CANCELLED", "BLOCKED", "OUT_OF_SCOPE"]) +async def test_matrix_terminal_stale_next_state_does_not_lock_generic_followup(status, tmp_path): + router = _router(tmp_path, stickiness=False) + state = { + "user_text": "ok", + "sanitized_input": "ok", + "next_state": "COLLECTING_ORDER_PARAMETERS", + "transaction_status": status, + "active_transaction": _active_tx(status=status), + "active_agent": "orders_agent", + "intent": "retail_order_cancel", + "route_decision": {"agent": "orders_agent", "intent": "retail_order_cancel"}, + "context": {"session": {}}, + } + decision = await router.route(state) + assert decision.method != "state" + assert (decision.metadata or {}).get("transaction_state_recovered") is not True diff --git a/agent_framework_oci/tests/test_transaction_state_router_interruption.py b/agent_framework_oci/tests/test_transaction_state_router_interruption.py index 7cca021..f9eff09 100644 --- a/agent_framework_oci/tests/test_transaction_state_router_interruption.py +++ b/agent_framework_oci/tests/test_transaction_state_router_interruption.py @@ -7,7 +7,7 @@ from agent_framework.routing.enterprise_router import EnterpriseRouter class _LLM: async def ainvoke(self, messages, **kwargs): - return '{"intent":"contas_vas_information","agent":"vas_agent","confidence":0.96,"reason":"nova consulta de serviços"}' + return '{"decision":"SHIFT","intent":"contas_vas_information","agent":"vas_agent","confidence":0.96,"reason":"nova consulta de serviços"}' @pytest.mark.asyncio @@ -86,7 +86,7 @@ intents: class _LowConfidenceLLM: async def ainvoke(self, messages, **kwargs): - return '{"intent":"contas_vas_information","agent":"vas_agent","confidence":0.30,"reason":"incerto"}' + return '{"decision":"SHIFT","intent":"contas_vas_information","agent":"vas_agent","confidence":0.30,"reason":"incerto"}' settings = SimpleNamespace( ROUTING_CONFIG_PATH=str(routing), @@ -320,3 +320,76 @@ intents: decision = await router.route(state) assert (decision.metadata or {}).get("transaction_interruption") is None + +@pytest.mark.asyncio +async def test_missing_next_state_parameter_answer_recovers_transaction_state_before_continuity(tmp_path): + """Regressão: parâmetro de uma transação ativa não pode cair no route-continuity. + + Reproduz o caso Contas de forma genérica: o latch transacional sobreviveu ao + checkpoint, ``next_state`` não, e o usuário fornece exatamente o parâmetro + faltante. A decisão deve continuar determinística (method=state), preservando + a tool ativa para o AgentRuntime completar os argumentos já coletados. + """ + routing = tmp_path / "routing.yaml" + routing.write_text( + """ +router: + fallback_agent: contestacao_agent + confidence_threshold: 0.70 +state_policies: [] +intents: + - name: contas_contestation + agent: contestacao_agent + priority: 20 + keywords: [nao contratei] +""", + encoding="utf-8", + ) + settings = SimpleNamespace( + ROUTING_CONFIG_PATH=str(routing), + ENABLE_LLM_ROUTER=False, + ENABLE_ROUTE_STICKINESS=True, + ) + router = EnterpriseRouter(settings) + state = { + "user_text": "R$ 71,99", + "sanitized_input": "R$ 71,99", + "next_state": None, + "transaction_status": "COLLECTING_PARAMETERS", + "missing_parameters": ["valor"], + "selected_tool_call": { + "tool_name": "contestar_cobranca", + "arguments": {"subject": "Plano Exemplo"}, + }, + "active_agent": "contestacao_agent", + "route": "contestacao_agent", + "route_decision": { + "route": "contestacao_agent", + "agent": "contestacao_agent", + "intent": "contas_contestation", + }, + } + + decision = await router.route(state) + + assert decision.method == "state" + assert decision.agent == "contestacao_agent" + assert decision.intent == "state:COLLECTING_PARAMETERS" + assert decision.next_state == "COLLECTING_PARAMETERS" + assert decision.metadata["transaction_state_recovered"] is True + + +def test_agent_state_declares_durable_transaction_latch(): + """O schema do host deve manter os campos que o AgentRuntime persiste.""" + import importlib.util + from pathlib import Path + + state_path = Path(__file__).parents[2] / "app" / "state.py" + spec = importlib.util.spec_from_file_location("contas_agent_state_v10", state_path) + module = importlib.util.module_from_spec(spec) + assert spec and spec.loader + spec.loader.exec_module(module) + + annotations = module.AgentState.__annotations__ + assert "active_transaction" in annotations + assert "last_transaction" in annotations diff --git a/agent_framework_oci/tests/test_transactional_tool_flow.py b/agent_framework_oci/tests/test_transactional_tool_flow.py index 6a38d56..049ff7b 100644 --- a/agent_framework_oci/tests/test_transactional_tool_flow.py +++ b/agent_framework_oci/tests/test_transactional_tool_flow.py @@ -572,8 +572,8 @@ async def test_pre_validation_rejection_clears_collecting_latches_and_is_exposed async def test_collecting_parameters_can_be_cancelled_explicitly(): runtime = _ContestRuntime() state = { - "user_text": "cancele essa operação anterior", - "sanitized_input": "cancele essa operação anterior", + "user_text": "nova intenção classificada pelo router", + "sanitized_input": "nova intenção classificada pelo router", "route": "contestacao_agent", "intent": "state:COLLECTING_CONTESTACAO_PARAMETERS", "transaction_status": "COLLECTING_PARAMETERS", @@ -587,12 +587,12 @@ async def test_collecting_parameters_can_be_cancelled_explicitly(): "selected_tool_call": {"tool_name": "contestar_cobranca", "arguments": {}}, "missing_parameters": ["subject"], "next_state": "COLLECTING_CONTESTACAO_PARAMETERS", + "route_decision": {"metadata": {"transaction_interruption": "intent_shift"}}, } result = await runtime.execute_tools_for_intent(state, tools=[]) - assert result[-1]["transaction_status"] == "CANCELLED" - assert result[-1]["cancelled"] is True + assert result == [] assert state["transaction_status"] == "CANCELLED" assert state["active_transaction"] is None assert state["next_state"] is None diff --git a/agent_framework_oci/tests/unit/__pycache__/test_langgraph_checkpoint_runtime_config.cpython-313-pytest-9.0.2.pyc b/agent_framework_oci/tests/unit/__pycache__/test_langgraph_checkpoint_runtime_config.cpython-313-pytest-9.0.2.pyc new file mode 100644 index 0000000000000000000000000000000000000000..35dd15d44e8a1f9c8cafce98142c526ce2937a23 GIT binary patch literal 15875 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 literal 7140 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 X1LRQ+!wJQDjqF~QDt^f!OGo|>!Q{q1 diff --git a/agent_framework_oci/tests/unit/__pycache__/test_workflow_runtime_diagnostics.cpython-313-pytest-9.0.2.pyc b/agent_framework_oci/tests/unit/__pycache__/test_workflow_runtime_diagnostics.cpython-313-pytest-9.0.2.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cbeca66a09beb589ce17299bbbb7c63a90ba1344 GIT binary patch literal 5445 zcmeHLO>7&-6`t80{)kJ-wiGLp6DyRgzf~g2ktMsaOWSE=)NxRy@PcRn6SCN5G$9Nh%s3DS^;KR^tNr*ublqyoA_ zSgOE#qz}?}GAwf^%d)`B=Md}D2(&pZ1+B7))%4%^bgQZq4C?gb3az`QZRu6&8fMK2 z6y1GTr@C{`sM88pj0-FZ@!!!1G*8ePjyp8G9MKf3((3N)jN`JIg1vs9R@}lp!zoyH z!Ejw>t~Xp-n6Y;#V`i1ks5lsSHVD^9WgFi7d^w!O{xry%fi2B3L_p65oq(f3-p6_2d2EQopxlL3J+%x5s<2^#A% z%`$ZHQb51M8c6|VNm|NYEX!AKBV@14ie7}OHbQIZh(UMxU;LmUHAZ+(D2>|*RF>jT^ItXPSX0XurFmG3mD2jOTsd_S3ogCZAJ5x07q`z33jAw2 zo-2M*@YNEwq!O;brz!ThmglzMcDTl$Kh6&Q!VUxf_jWjSVh8RIWvrd%-k{-Ve@H*? z{jrJ0(OMkwQ3e^W!d^sc+&H3?!BU!Z5o_{0l7-)Zm~WwX(XXY|#gN*-8>l3|fi|#u zW;ICFApp}LW0=;Cy+tA9o{B>skD>HqHy}LVGah|Z{8B2FRJ=PlHCuo9w(1m0r2;3H zR0q<93^LvkAx6rqw-}X~mNT**FOVL-BHW zfR7@vnvEvA6{jc018k7HS%1hQx^A%Gj9xKnHJ#BqtJvFhgVCT*cZ?0nC6HId0wJ-9 zyb=uQ8#dcETu6A3EG&bkJT()@kpF^AlKNnJJ$$o^fzn|vbu)`06Hd3>&0KMKCMbl3 z_3;I(VzvL4WW)4jT8^Gri%o+*kNeIJB-2Oeg(Rnbi=SuvKg+yGqx{h0%b(uxNYNWB zhOMs@4@gv-q_}q@f6X7Aho?6>?m5#~s( zor%}gWtG8L)_DTvNX3?opCRU`U(oU=cIKA)pW9gyc82I>|2Ti{FLQq$_~^u+6Nmnf z{w$x^`6uz`Qzvobr?B%TQctf2c|fBZ=4OK#>ovOD@6ZaP?!0@CQKP!6tQ*z&2qyv# z?F+@aSq)C>dY#ct3P3QtCtxb9nVp5^802{gpeo^*SCtsgEF8C9>H)LMA(&leb7F>< zY0GvC0>uu;kT{eGB+KWM;j%-`F`j?}!m%ei2PA{UiP-C0nCC!py_@ zf)Ti3u^Y9j0I>RI&0aTZx&>Dk>^vVi2925yQsNqhy~#!6ytx2PH|AXGW-dD%Fm&V3 zS%E)07djmN+71QW36S5>;Z^h7fy88tm>k*!v~ z_UP7g<<#TUpRYYyIaG!{GWK-RSH=!VRGVaM?^?*UJ7m%gvZs>?=l5`lxJAYemGd5% z+JD7YrVdC{n`FvU&WCKfi%t|kxc^GR`8`}BZjq@&<(x;R_hH@B2PCRZGR?s==h_`I z=?2+8tUFTnd$>g0BGZS;h({*(iLZcmRGVaSuM~3a4w-a=Y@Z~Y-@_&17MVO$PJ3kJ z@ye$#*GO2KWMpqSB-{&L%{ tHfr=bD?taiBXan50FaLHHwgU+9iZQQpT_tW{yv+ None: checkpoint = {"id": "cp1", "v": 1} returned = asyncio.run(saver.aput(config, checkpoint, {}, {})) - assert returned["configurable"] == {"thread_id": "t1", "checkpoint_id": "cp1"} + assert returned["configurable"] == {"thread_id": "t1", "checkpoint_ns": "", "checkpoint_id": "cp1"} assert repo.saved is not None persisted = repo.saved[1] assert "__pregel_runtime" not in persisted["config"]["configurable"] @@ -62,3 +62,134 @@ def test_saver_strips_private_runtime_before_put_and_restore() -> None: restored = saver._make_tuple(persisted) restored_config = restored.config if hasattr(restored, "config") else restored["config"] assert "__pregel_runtime" not in restored_config["configurable"] + + +def test_nested_pregel_runtime_is_removed_from_checkpoint_and_pending_writes() -> None: + repo = _Repo() + saver = RepositoryCheckpointSaver(SimpleNamespace(), repository=repo) + config = {"configurable": {"thread_id": "t-nested"}} + checkpoint = { + "id": "cp-nested", + "v": 1, + "channel_values": { + "__pregel_tasks": {"durable": True}, + "task": { + "config": { + "configurable": { + "thread_id": "t-nested", + "__pregel_runtime": "Runtime(...) nested", + "business_key": "kept", + } + } + } + }, + } + + asyncio.run(saver.aput(config, checkpoint, {}, {})) + asyncio.run( + saver.aput_writes( + config, + [ + ( + "tasks", + { + "nested": { + "configurable": { + "__pregel_runtime": "Runtime(...) write", + "subject": "TIM Fashion", + } + } + }, + ) + ], + "task-1", + ) + ) + + assert repo.saved is not None + persisted = repo.saved[1] + assert persisted["checkpoint"]["channel_values"]["__pregel_tasks"] == {"durable": True} + task_config = persisted["checkpoint"]["channel_values"]["task"]["config"]["configurable"] + assert "__pregel_runtime" not in task_config + assert task_config["business_key"] == "kept" + pending_cfg = persisted["pending_writes"][0]["value"]["nested"]["configurable"] + assert "__pregel_runtime" not in pending_cfg + assert pending_cfg["subject"] == "TIM Fashion" + + +def test_restore_rebuilds_fresh_canonical_config_instead_of_rebinding_stored_config() -> None: + repo = _Repo() + saver = RepositoryCheckpointSaver(SimpleNamespace(), repository=repo) + legacy_payload = { + "thread_id": "thread-legacy", + "checkpoint_id": "cp-legacy", + "config": { + "tags": ["old-run"], + "configurable": { + "thread_id": "thread-legacy", + "checkpoint_ns": "wf", + "checkpoint_id": "cp-legacy", + "tenant": "default", + "__pregel_runtime": "stringified-runtime", + "__pregel_store": "stringified-store", + }, + }, + "checkpoint": {"id": "cp-legacy", "v": 1}, + "metadata": {}, + } + asyncio.run(repo.put("thread-legacy", legacy_payload)) + + # Simulate a new invocation. Runtime/private keys supplied by a previous run + # must never be rebound from the persisted checkpoint tuple. + request = { + "tags": ["new-run"], + "configurable": { + "thread_id": "thread-legacy", + "checkpoint_ns": "wf", + "__pregel_runtime": "request-runtime-placeholder", + }, + } + restored = asyncio.run(saver.aget_tuple(request)) + restored_config = restored.config if hasattr(restored, "config") else restored["config"] + + assert restored_config == { + "configurable": { + "thread_id": "thread-legacy", + "checkpoint_ns": "wf", + "checkpoint_id": "cp-legacy", + } + } + assert "tags" not in restored_config + assert "tenant" not in restored_config["configurable"] + assert "__pregel_runtime" not in restored_config["configurable"] + + +def test_legacy_checkpoint_structural_json_strings_are_recovered() -> None: + from agent_framework.checkpoints.langgraph_saver import _normalize_checkpoint, _normalize_config + + legacy = { + "v": 1, + "id": "cp-json", + "channel_values": '{"state":{"ok":true}}', + "channel_versions": '{"state":"0001"}', + "versions_seen": '{"node":"{\\"state\\":\\"0001\\"}"}', + } + normalized = _normalize_checkpoint(legacy) + assert normalized["channel_values"] == {"state": {"ok": True}} + assert normalized["channel_versions"] == {"state": "0001"} + assert normalized["versions_seen"] == {"node": {"state": "0001"}} + + cfg = _normalize_config({"configurable": '{"thread_id":"t1","checkpoint_ns":""}'}) + assert cfg["configurable"] == {"thread_id": "t1", "checkpoint_ns": ""} + + +def test_checkpoint_serialization_never_silently_stringifies_unknown_objects() -> None: + import pytest + from agent_framework.checkpoints.langgraph_saver import _strict_json_value + + class RuntimeLike: + def override(self): + return self + + with pytest.raises(TypeError, match="Checkpoint contém valor não serializável"): + _strict_json_value({"channel_values": {"bad": RuntimeLike()}}, path="$.checkpoint") diff --git a/agent_framework_oci/tests/unit/test_workflow_runtime_diagnostics.py b/agent_framework_oci/tests/unit/test_workflow_runtime_diagnostics.py new file mode 100644 index 0000000..56d1608 --- /dev/null +++ b/agent_framework_oci/tests/unit/test_workflow_runtime_diagnostics.py @@ -0,0 +1,26 @@ +from agent_framework.workflows.runtime import _exception_details, _type_shape + + +def _boom(): + raise AttributeError("'str' object has no attribute 'override'") + + +def test_exception_details_contains_traceback(): + try: + _boom() + except Exception as exc: + details = _exception_details(exc, runtime_context={"phase": "ainvoke"}) + assert details["type"] == "AttributeError" + assert "_boom" in details["traceback"] + assert "override" in details["traceback"] + assert details["runtime_diagnostics"]["phase"] == "ainvoke" + + +def test_type_shape_exposes_types_not_values(): + shape = _type_shape({"configurable": {"thread_id": "secret-thread", "__pregel_runtime": "bad-runtime"}}) + text = repr(shape) + assert "thread_id" in text + assert "__pregel_runtime" in text + assert "secret-thread" not in text + assert "bad-runtime" not in text + assert "str" in text diff --git a/app/workflows/agent_graph.py b/app/workflows/agent_graph.py index 40b21d9..84d1743 100644 --- a/app/workflows/agent_graph.py +++ b/app/workflows/agent_graph.py @@ -148,34 +148,6 @@ class AgentWorkflow: enxergar evidências MCP. O domínio não implementa rails, apenas devolve dados. """ ctx = dict(state.get("context", {}) or {}) - - # Guardrails de saída (especialmente AOFERTA) precisam saber o que o - # cliente efetivamente pediu. Sem o histórico, uma confirmação legítima - # como "Você confirma cancelar ...?" parece uma oferta proativa isolada. - # O estado do Contas mantém history como lista de dicts; preservamos esse - # formato e acrescentamos o user_text corrente quando ele ainda não foi - # persistido no histórico. - history = list(state.get("history") or []) - user_text = str(state.get("user_text") or state.get("sanitized_input") or "").strip() - if user_text: - last_user_text = "" - for item in reversed(history): - if isinstance(item, dict) and str(item.get("role", "")).lower() in {"user", "human"}: - last_user_text = str(item.get("content") or "").strip() - break - if type(item).__name__ == "HumanMessage": - last_user_text = str(getattr(item, "content", "") or "").strip() - break - if last_user_text != user_text: - history.append({"role": "user", "content": user_text}) - if history: - ctx["conversation_history"] = history - ctx["history_texts"] = [ - str(item.get("content") or "") if isinstance(item, dict) - else str(getattr(item, "content", "") or "") - for item in history - ] - mcp_results = state.get("mcp_results") or [] ctx["evidence"] = mcp_results or ctx.get("evidence") ctx["tool_result"] = mcp_results or ctx.get("tool_result") diff --git a/config/guardrails.yaml b/config/guardrails.yaml index 3001eb8..2c1f1a7 100644 --- a/config/guardrails.yaml +++ b/config/guardrails.yaml @@ -13,7 +13,7 @@ output: - {code: OOS, enabled: true} - {code: AOFERTA, enabled: true} - {code: REVPREC, enabled: true} - - {code: FRASEOLOGIA, enabled: true} +# - {code: FRASEOLOGIA, enabled: true} - {code: PINJ, enabled: true} - {code: DLEX_OUT, enabled: true} - {code: TOXOUT, enabled: true}