Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
_fluency,
_xpia,
_coherence,
_code_vulnerability,
_ungrounded_attributes,
)
from azure.ai.evaluation._evaluators._eci._eci import ECIEvaluator
from azure.ai.evaluation._evaluate import _evaluate
Expand All @@ -32,7 +34,7 @@
AdversarialScenario,
AdversarialScenarioJailbreak,
IndirectAttackSimulator,
DirectAttackSimulator ,
DirectAttackSimulator,
)
from azure.ai.evaluation.simulator._adversarial_scenario import _UnstableAdversarialScenario
from azure.ai.evaluation.simulator._utils import JsonLineList
Expand Down Expand Up @@ -72,6 +74,7 @@ class _SafetyEvaluator(Enum):
"""

CONTENT_SAFETY = "content_safety"
CODE_VULNERABILITY = "code_vulnerability"
GROUNDEDNESS = "groundedness"
PROTECTED_MATERIAL = "protected_material"
RELEVANCE = "relevance"
Expand All @@ -81,6 +84,7 @@ class _SafetyEvaluator(Enum):
INDIRECT_ATTACK = "indirect_attack"
DIRECT_ATTACK = "direct_attack"
ECI = "eci"
UNGROUNDED_ATTRIBUTES = "ungrounded_attributes"


@experimental
Expand Down Expand Up @@ -380,6 +384,10 @@ def _get_scenario(
)
if evaluator == _SafetyEvaluator.ECI:
return _UnstableAdversarialScenario.ECI
if evaluator == _SafetyEvaluator.CODE_VULNERABILITY:
return AdversarialScenario.ADVERSARIAL_CODE_VULNERABILITY
if evaluator == _SafetyEvaluator.UNGROUNDED_ATTRIBUTES:
return AdversarialScenario.ADVERSARIAL_UNGROUNDED_ATTRIBUTES
if evaluator in [
_SafetyEvaluator.GROUNDEDNESS,
_SafetyEvaluator.RELEVANCE,
Expand Down Expand Up @@ -461,6 +469,14 @@ def _get_evaluators(
evaluators_dict["eci"] = ECIEvaluator(
azure_ai_project=self.azure_ai_project, credential=self.credential
)
elif evaluator == _SafetyEvaluator.CODE_VULNERABILITY:
evaluators_dict["code_vulnerability"] = _code_vulnerability.CodeVulnerabilityEvaluator(
azure_ai_project=self.azure_ai_project, credential=self.credential
)
elif evaluator == _SafetyEvaluator.UNGROUNDED_ATTRIBUTES:
evaluators_dict["ungrounded_attributes"] = _ungrounded_attributes.UngroundedAttributesEvaluator(
azure_ai_project=self.azure_ai_project, credential=self.credential
)
else:
msg = (
f"Invalid evaluator: {evaluator}. Supported evaluators are: {_SafetyEvaluator.__members__.values()}"
Expand Down Expand Up @@ -597,7 +613,28 @@ def _validate_inputs(
category=ErrorCategory.INVALID_VALUE,
blame=ErrorBlame.USER_ERROR,
)


if _SafetyEvaluator.CODE_VULNERABILITY in evaluators and num_turns > 1:
self.logger.error("Code vulnerability evaluation only supports single-turn conversations.")
msg = "Code vulnerability evaluation only supports single-turn conversations."
raise EvaluationException(
message=msg,
internal_message=msg,
target=ErrorTarget.UNKNOWN,
category=ErrorCategory.INVALID_VALUE,
blame=ErrorBlame.USER_ERROR,
)
if _SafetyEvaluator.UNGROUNDED_ATTRIBUTES in evaluators and num_turns > 1:
self.logger.error("Ungrounded attributes evaluation only supports single-turn conversations.")
msg = "Ungrounded attributes evaluation only supports single-turn conversations."
raise EvaluationException(
message=msg,
internal_message=msg,
target=ErrorTarget.UNKNOWN,
category=ErrorCategory.INVALID_VALUE,
blame=ErrorBlame.USER_ERROR,
)

if _SafetyEvaluator.CONTENT_SAFETY in evaluators and scenario and num_turns > 1 and scenario != AdversarialScenario.ADVERSARIAL_CONVERSATION:
self.logger.error(f"Adversarial scenario {scenario} is not supported for content safety evaluation with more than 1 turn.")
msg = f"Adversarial scenario {scenario} is not supported for content safety evaluation with more than 1 turn."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -298,3 +298,39 @@ async def test_call_with_async_target(self, mock_evaluate, mock_simulate, safety
# Verify that _simulate was called with the async target
mock_simulate.assert_called_once()
assert mock_simulate.call_args[1]["target"] == mock_async_target

def test_get_scenario_code_vulnerability(self, safety_eval):
scenario = safety_eval._get_scenario([_SafetyEvaluator.CODE_VULNERABILITY])
assert scenario == AdversarialScenario.ADVERSARIAL_CODE_VULNERABILITY

def test_get_scenario_ungrounded_attributes(self, safety_eval):
scenario = safety_eval._get_scenario([_SafetyEvaluator.UNGROUNDED_ATTRIBUTES])
assert scenario == AdversarialScenario.ADVERSARIAL_UNGROUNDED_ATTRIBUTES

def test_get_evaluators_code_vulnerability(self, safety_eval):
evaluators = safety_eval._get_evaluators([_SafetyEvaluator.CODE_VULNERABILITY])
assert "code_vulnerability" in evaluators
assert evaluators["code_vulnerability"].__class__.__name__ == "CodeVulnerabilityEvaluator"

def test_get_evaluators_ungrounded_attributes(self, safety_eval):
evaluators = safety_eval._get_evaluators([_SafetyEvaluator.UNGROUNDED_ATTRIBUTES])
assert "ungrounded_attributes" in evaluators
assert evaluators["ungrounded_attributes"].__class__.__name__ == "UngroundedAttributesEvaluator"

def test_validate_inputs_code_vulnerability_multi_turn(self, safety_eval, mock_target):
with pytest.raises(EvaluationException) as exc_info:
safety_eval._validate_inputs(
target=mock_target,
evaluators=[_SafetyEvaluator.CODE_VULNERABILITY],
num_turns=3,
)
assert "Code vulnerability evaluation only supports single-turn conversations" in str(exc_info.value)

def test_validate_inputs_ungrounded_attributes_multi_turn(self, safety_eval, mock_target):
with pytest.raises(EvaluationException) as exc_info:
safety_eval._validate_inputs(
target=mock_target,
evaluators=[_SafetyEvaluator.UNGROUNDED_ATTRIBUTES],
num_turns=3,
)
assert "Ungrounded attributes evaluation only supports single-turn conversations" in str(exc_info.value)