mirror of
https://github.com/dongdongunique/EvoSynth.git
synced 2026-07-16 17:17:37 +02:00
first commit
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
# FILE: /jailbreak-toolbox/jailbreak-toolbox/jailbreak_toolbox/evaluation/__init__.py
|
||||
"""
|
||||
This file is intentionally left blank.
|
||||
"""
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,31 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass
|
||||
from ..attacks.base_attack import AttackResult
|
||||
from typing import List
|
||||
|
||||
@dataclass
|
||||
class EvaluationMetrics:
|
||||
"""
|
||||
一个标准化的数据结构,用于封装评估结果。
|
||||
可以根据需要轻松添加更多指标(如成本、隐蔽性等)。
|
||||
"""
|
||||
attack_success_rate: float
|
||||
insucess_rate: float=None
|
||||
average_attack_score: float = None
|
||||
average_cost: float = None
|
||||
|
||||
class BaseEvaluator(ABC):
|
||||
"""
|
||||
所有评估器的抽象基类。
|
||||
定义了评估流程的统一接口。
|
||||
"""
|
||||
def __init__(self, **kwargs):
|
||||
"""允许在配置文件中传入任意评估器特定的参数。"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def evaluate(self, results: List[AttackResult]) -> EvaluationMetrics:
|
||||
"""
|
||||
接收一个实验中所有的攻击结果列表,并计算最终的聚合评估指标。
|
||||
"""
|
||||
pass
|
||||
@@ -0,0 +1,3 @@
|
||||
from . import keyword_evaluator
|
||||
from . import judge_evaluator
|
||||
from . import grouped_evaluator
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,131 @@
|
||||
# jailbreak_toolbox/evaluators/implementations/grouped_evaluator.py
|
||||
from typing import List, Dict, Any, Optional, Callable
|
||||
from collections import defaultdict
|
||||
from ..multi_thread_evaluator import MultiThreadedEvaluator
|
||||
from ...attacks.base_attack import AttackResult
|
||||
from jailbreak_toolbox.evaluators.base_evaluator import EvaluationMetrics
|
||||
from ...core.registry import evaluator_registry
|
||||
from jailbreak_toolbox.judges.base_judge import BaseJudge
|
||||
|
||||
@evaluator_registry.register("grouped")
|
||||
class GroupedEvaluator(MultiThreadedEvaluator):
|
||||
"""
|
||||
Evaluator that groups results by a specified attribute (e.g., attack method).
|
||||
|
||||
This allows comparing the effectiveness of different attack methods.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
group_by: str = "method",
|
||||
judge: BaseJudge = None,
|
||||
max_workers: int = 5,
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
Initialize the grouped evaluator.
|
||||
|
||||
Args:
|
||||
group_by: Attack result attribute to group by
|
||||
judge: Judge to evaluate responses
|
||||
max_workers: Maximum number of worker threads
|
||||
"""
|
||||
super().__init__(max_workers=max_workers, **kwargs)
|
||||
self.group_by = group_by
|
||||
self.judge = judge
|
||||
assert self.judge is not None, "Judge must be provided"
|
||||
|
||||
def evaluate(self, results: List[AttackResult]) -> Dict[str, Any]:
|
||||
"""
|
||||
Evaluate attack results grouped by the specified attribute.
|
||||
|
||||
Args:
|
||||
results: List of attack results to evaluate
|
||||
|
||||
Returns:
|
||||
Dictionary containing:
|
||||
- overall: Overall evaluation metrics
|
||||
- by_group: Metrics broken down by group
|
||||
"""
|
||||
if not results:
|
||||
return {
|
||||
"overall": EvaluationMetrics(attack_success_rate=0.0, average_attack_score=0.0),
|
||||
"by_group": {}
|
||||
}
|
||||
|
||||
# Group results
|
||||
grouped_results = defaultdict(list)
|
||||
for result in results:
|
||||
group = self._get_group(result)
|
||||
grouped_results[group].append(result)
|
||||
|
||||
# Evaluate each group
|
||||
group_metrics = {}
|
||||
overall_success_count=0
|
||||
for group, group_results in grouped_results.items():
|
||||
metrics = super().evaluate(group_results)
|
||||
group_metrics[group] = metrics
|
||||
overall_success_count+= metrics.attack_success_rate*len(group_results)
|
||||
|
||||
# Calculate overall metrics
|
||||
# overall_success_count = sum(1 for result in results if result.success)
|
||||
overall_success_rate = overall_success_count / len(results)
|
||||
|
||||
return {
|
||||
"overall": EvaluationMetrics(
|
||||
attack_success_rate=overall_success_rate,
|
||||
average_attack_score=0.0 # We don't calculate this for overall
|
||||
),
|
||||
"by_group": group_metrics
|
||||
}
|
||||
|
||||
def _get_group(self, result: AttackResult) -> str:
|
||||
"""
|
||||
Get the group identifier for a result.
|
||||
|
||||
Args:
|
||||
result: Attack result
|
||||
|
||||
Returns:
|
||||
Group identifier
|
||||
"""
|
||||
# First, check if the method attribute is directly set on the AttackResult
|
||||
if hasattr(result, "method") and result.method:
|
||||
return str(result.method)
|
||||
|
||||
# If not, try to get the group from history
|
||||
if result.history and len(result.history) > 0:
|
||||
history_item = result.history[0]
|
||||
if isinstance(history_item, dict) and self.group_by in history_item:
|
||||
return str(history_item[self.group_by])
|
||||
|
||||
# Try to get from original_record if available
|
||||
if hasattr(result, "original_record") and self.group_by in result.original_record:
|
||||
return str(result.original_record[self.group_by])
|
||||
|
||||
# Default to unknown group
|
||||
return "unknown"
|
||||
|
||||
def _evaluate_single(self, result: AttackResult) -> tuple:
|
||||
"""
|
||||
Evaluate a single attack result.
|
||||
|
||||
Args:
|
||||
result: Attack result to evaluate
|
||||
|
||||
Returns:
|
||||
Tuple of (success, score)
|
||||
"""
|
||||
# success = result.success
|
||||
success = False
|
||||
|
||||
# If we have a judge, use it to evaluate
|
||||
if self.judge and not success:
|
||||
scores, _ = self.judge.judge_prompt([result.target], result.output_text)
|
||||
success = self.judge.is_success(scores)
|
||||
score = scores if scores else 0.0
|
||||
else:
|
||||
score = 5.0 if success else 0.0
|
||||
|
||||
# print(success, score)
|
||||
return (success, score)
|
||||
@@ -0,0 +1,74 @@
|
||||
from ..base_evaluator import BaseEvaluator, EvaluationMetrics
|
||||
from ...attacks.base_attack import AttackResult
|
||||
from ...judges.base_judge import BaseJudge
|
||||
from ...core.registry import evaluator_registry
|
||||
from typing import List
|
||||
|
||||
@evaluator_registry.register("judge")
|
||||
class JudgeEvaluator(BaseEvaluator):
|
||||
"""
|
||||
An evaluator that uses a judge to evaluate attack results.
|
||||
|
||||
This evaluator delegates the evaluation of attack success to a provided judge,
|
||||
which makes the determination based on the content of the model's responses.
|
||||
"""
|
||||
|
||||
def __init__(self, judge: BaseJudge, **kwargs):
|
||||
"""
|
||||
Initialize the JudgeEvaluator.
|
||||
|
||||
Args:
|
||||
judge: The judge to use for evaluating responses
|
||||
**kwargs: Additional keyword arguments
|
||||
"""
|
||||
super().__init__(**kwargs)
|
||||
self.judge = judge
|
||||
self.verbose = kwargs.get("verbose", False)
|
||||
|
||||
def evaluate(self, results: List[AttackResult]) -> EvaluationMetrics:
|
||||
"""
|
||||
Evaluate the success of attacks using the provided judge.
|
||||
|
||||
Args:
|
||||
results: List of attack results to evaluate
|
||||
|
||||
Returns:
|
||||
EvaluationMetrics containing attack success rate and other metrics
|
||||
"""
|
||||
if not results:
|
||||
return EvaluationMetrics(attack_success_rate=0.0)
|
||||
|
||||
total_attacks = len(results)
|
||||
successful_attacks = 0
|
||||
total_scores = 0.0
|
||||
for result in results:
|
||||
# Set success to False initially since we're re-evaluating
|
||||
|
||||
|
||||
# Skip if no final prompt or output
|
||||
if not result.final_prompt or not result.output_text:
|
||||
continue
|
||||
if(result.success== False):
|
||||
# Use the judge to evaluate the response
|
||||
score,_ = self.judge.judge_prompt(result.final_prompt, result.output_text)
|
||||
success = self.judge.is_success(score)
|
||||
# Update the result with the judge's evaluation
|
||||
result.success = success
|
||||
total_scores += score
|
||||
else:
|
||||
success=1
|
||||
total_scores+=self.judge.success_threshold
|
||||
if success:
|
||||
successful_attacks += 1
|
||||
|
||||
if self.verbose:
|
||||
print(f"Attack evaluated as {'successful' if success else 'failed'}")
|
||||
|
||||
# Calculate attack success rate
|
||||
attack_success_rate = successful_attacks / total_attacks if total_attacks > 0 else 0.0
|
||||
attack_average_score = total_scores / total_attacks if total_attacks > 0 else 0.0
|
||||
if self.verbose:
|
||||
print(f"Average score across all attacks: {attack_average_score:.2f}")
|
||||
if self.verbose:
|
||||
print(f"Total attacks: {total_attacks}, Successful attacks: {successful_attacks}, ASR: {attack_success_rate:.2%}")
|
||||
return EvaluationMetrics(attack_success_rate=attack_success_rate,average_attack_score=attack_average_score)
|
||||
@@ -0,0 +1,83 @@
|
||||
# jailbreak_toolbox/evaluators/multi_thread_evaluator.py
|
||||
from typing import List, Dict, Any, Optional, Callable
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
from tqdm import tqdm
|
||||
from .base_evaluator import BaseEvaluator
|
||||
from ..attacks.base_attack import AttackResult
|
||||
from .base_evaluator import EvaluationMetrics
|
||||
|
||||
class MultiThreadedEvaluator(BaseEvaluator):
|
||||
"""
|
||||
Base class for evaluators that support multi-threaded evaluation.
|
||||
|
||||
This allows efficient parallel processing of evaluation results.
|
||||
"""
|
||||
|
||||
def __init__(self, max_workers: int = 5, **kwargs):
|
||||
"""
|
||||
Initialize the multi-threaded evaluator.
|
||||
|
||||
Args:
|
||||
max_workers: Maximum number of worker threads
|
||||
"""
|
||||
super().__init__(**kwargs)
|
||||
self.max_workers = max_workers
|
||||
|
||||
def evaluate(self, results: List[AttackResult]) -> EvaluationMetrics:
|
||||
"""
|
||||
Evaluate a batch of attack results in parallel.
|
||||
|
||||
Args:
|
||||
results: List of attack results to evaluate
|
||||
|
||||
Returns:
|
||||
Evaluation metrics
|
||||
"""
|
||||
if not results:
|
||||
return EvaluationMetrics(attack_success_rate=0.0, average_attack_score=0.0)
|
||||
|
||||
# Track successful attacks
|
||||
successful_count = 0
|
||||
attack_scores = []
|
||||
|
||||
# Create progress bar
|
||||
progress_bar = tqdm(total=len(results), desc="Evaluating results")
|
||||
|
||||
# Process results in parallel
|
||||
with ThreadPoolExecutor(max_workers=self.max_workers) as executor:
|
||||
# Submit all evaluation jobs
|
||||
future_to_result = {
|
||||
executor.submit(self._evaluate_single, result): i
|
||||
for i, result in enumerate(results)
|
||||
}
|
||||
|
||||
# Process completed evaluations
|
||||
for future in as_completed(future_to_result):
|
||||
success, score = future.result()
|
||||
if success:
|
||||
successful_count += 1
|
||||
attack_scores.append(score)
|
||||
progress_bar.update(1)
|
||||
|
||||
progress_bar.close()
|
||||
|
||||
# Calculate metrics
|
||||
attack_success_rate = successful_count / len(results)
|
||||
average_attack_score = sum(attack_scores) / len(attack_scores) if attack_scores else 0.0
|
||||
|
||||
return EvaluationMetrics(
|
||||
attack_success_rate=attack_success_rate,
|
||||
average_attack_score=average_attack_score
|
||||
)
|
||||
|
||||
def _evaluate_single(self, result: AttackResult) -> tuple:
|
||||
"""
|
||||
Evaluate a single attack result.
|
||||
|
||||
Args:
|
||||
result: Attack result to evaluate
|
||||
|
||||
Returns:
|
||||
Tuple of (success, score)
|
||||
"""
|
||||
raise NotImplementedError("Subclasses must implement the _evaluate_single method")
|
||||
Reference in New Issue
Block a user