first commit

This commit is contained in:
dongdongunique
2025-12-10 00:54:02 +08:00
parent 2cf4168ecf
commit f3af94df0b
254 changed files with 9821 additions and 25 deletions
+4
View File
@@ -0,0 +1,4 @@
# FILE: /jailbreak-toolbox/jailbreak-toolbox/jailbreak_toolbox/evaluation/__init__.py
"""
This file is intentionally left blank.
"""
@@ -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
@@ -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")