new feature: External guardrails/judges
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
|
||||
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
|
||||
from agent_framework.observability.code_mapper import create_observability_code_mapper
|
||||
|
||||
|
||||
def _settings(**kwargs):
|
||||
defaults = dict(
|
||||
OBSERVABILITY_DEFAULT_MAPPING_ENABLED=True,
|
||||
OBSERVABILITY_DEFAULT_MAPPING_PATH=None,
|
||||
OBSERVABILITY_CODE_MAPPING_ENABLED=False,
|
||||
OBSERVABILITY_CODE_MAPPING_PATH=None,
|
||||
)
|
||||
defaults.update(kwargs)
|
||||
return SimpleNamespace(**defaults)
|
||||
|
||||
|
||||
def test_old_agent_without_mapping_gets_framework_default_registry():
|
||||
mapper = create_observability_code_mapper(_settings())
|
||||
assert mapper.map("guardrail.output_supervisor.started") == "GRL.001"
|
||||
assert mapper.map("guardrail.result.block") == "GRL.004"
|
||||
assert mapper.map("guardrail.output_supervisor.completed") == "GRL.009"
|
||||
assert mapper.action_for("REVPREC") == "retry"
|
||||
assert mapper.action_for("CMP") == "retry"
|
||||
assert mapper.action_for("SCO") == "retry"
|
||||
assert mapper.action_for("GND") == "retry"
|
||||
assert mapper.action_for("ATH") == "handover"
|
||||
assert mapper.map("GRL.004") == "GRL.004"
|
||||
|
||||
|
||||
def test_agent_overlay_overrides_only_declared_entries(tmp_path: Path):
|
||||
overlay = tmp_path / "observability_mapping.yaml"
|
||||
overlay.write_text(
|
||||
"""version: \"2\"\nmappings:\n guardrail.dlex_in:\n label: GRL.004\n aliases: [DLEX_IN]\n guardrail.tox:\n label: GRL.005\n aliases: [TOX]\n""",
|
||||
encoding="utf-8",
|
||||
)
|
||||
mapper = create_observability_code_mapper(_settings(
|
||||
OBSERVABILITY_CODE_MAPPING_ENABLED=True,
|
||||
OBSERVABILITY_CODE_MAPPING_PATH=str(overlay),
|
||||
))
|
||||
# Agent-specific contract overrides framework named GRL.DLEX_IN / GRL.TOX.
|
||||
assert mapper.map("guardrail.dlex_in") == "GRL.004"
|
||||
assert mapper.map("DLEX_IN") == "GRL.004"
|
||||
assert mapper.map("TOX") == "GRL.005"
|
||||
# Unrelated framework defaults remain intact.
|
||||
assert mapper.map("guardrail.result.retry") == "GRL.005"
|
||||
assert mapper.action_for("REVPREC") == "retry"
|
||||
assert mapper.map("guardrail.pinj") == "GRL.PINJ"
|
||||
|
||||
|
||||
def test_agent_overlay_can_override_default_action(tmp_path: Path):
|
||||
overlay = tmp_path / "observability_mapping.yaml"
|
||||
overlay.write_text(
|
||||
"""mappings:\n guardrail.revprec:\n action: handover\n aliases: [REVPREC]\n""",
|
||||
encoding="utf-8",
|
||||
)
|
||||
mapper = create_observability_code_mapper(_settings(
|
||||
OBSERVABILITY_CODE_MAPPING_ENABLED=True,
|
||||
OBSERVABILITY_CODE_MAPPING_PATH=str(overlay),
|
||||
))
|
||||
assert mapper.action_for("REVPREC") == "handover"
|
||||
# Other historical defaults are still inherited.
|
||||
assert mapper.action_for("CMP") == "retry"
|
||||
|
||||
|
||||
def test_framework_default_carries_legacy_phraseology_rewrite_policy():
|
||||
mapper = create_observability_code_mapper(_settings())
|
||||
remediation = mapper.remediation_for("FRASEOLOGIA")
|
||||
assert remediation is not None
|
||||
assert remediation["type"] == "rewrite"
|
||||
assert remediation["max_attempts"] == 1
|
||||
assert remediation["generation_name"] == "guardrail.fraseologia.rewrite"
|
||||
|
||||
|
||||
class _DeniedLegacyRail:
|
||||
code = "REVPREC"
|
||||
|
||||
async def evaluate(self, text, context):
|
||||
return RailDecision(code=self.code, allowed=False, reason="premature")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_old_legacy_rail_keeps_retry_with_new_framework_and_no_agent_mapping():
|
||||
mapper = create_observability_code_mapper(_settings())
|
||||
supervisor = OutputSupervisor(
|
||||
rails=[_DeniedLegacyRail()],
|
||||
enable_parallel=False,
|
||||
observability_mapper=mapper,
|
||||
)
|
||||
decision = await supervisor.evaluate("candidate", {})
|
||||
assert decision.action == RailAction.RETRY
|
||||
assert decision.results[0].metadata.get("action_source") == "observability_mapping"
|
||||
Reference in New Issue
Block a user