guidedbench 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,27 @@
1
+ from ._version import BENCHMARK_VERSION, __version__
2
+ from .dataset import (
3
+ DATASET_REPO_ID,
4
+ DATASET_REVISION,
5
+ export_jsonl,
6
+ get_case,
7
+ load_cases,
8
+ )
9
+ from .evaluator import GuidedBenchEvaluator
10
+ from .models import EvaluationResult, GuidedBenchCase, Guideline, ScoringPointResult
11
+ from .parsing import EvaluationParseError
12
+
13
+ __all__ = [
14
+ "BENCHMARK_VERSION",
15
+ "DATASET_REPO_ID",
16
+ "DATASET_REVISION",
17
+ "EvaluationParseError",
18
+ "EvaluationResult",
19
+ "GuidedBenchCase",
20
+ "GuidedBenchEvaluator",
21
+ "Guideline",
22
+ "ScoringPointResult",
23
+ "__version__",
24
+ "export_jsonl",
25
+ "get_case",
26
+ "load_cases",
27
+ ]
@@ -0,0 +1,2 @@
1
+ __version__ = "0.1.0"
2
+ BENCHMARK_VERSION = "1.0"
guidedbench/cli.py ADDED
@@ -0,0 +1,423 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ from concurrent.futures import ThreadPoolExecutor, as_completed
5
+ from dataclasses import dataclass
6
+ import hashlib
7
+ import json
8
+ import os
9
+ import sys
10
+ import threading
11
+ import uuid
12
+ from pathlib import Path
13
+ from typing import Any, Iterable
14
+
15
+ from ._version import BENCHMARK_VERSION, __version__
16
+ from .dataset import export_jsonl, get_case, load_cases
17
+ from .evaluator import GuidedBenchEvaluator
18
+ from .judges import (
19
+ AnthropicJudge,
20
+ MissingDependencyError,
21
+ OpenAICompatibleJudge,
22
+ OpenAIJudge,
23
+ TransformersJudge,
24
+ )
25
+
26
+
27
+ def _parser() -> argparse.ArgumentParser:
28
+ parser = argparse.ArgumentParser(
29
+ prog="guidedbench",
30
+ description="Run the GuidedBench guideline-grounded jailbreak evaluation.",
31
+ )
32
+ parser.add_argument("--version", action="version", version=f"guidedbench {__version__}")
33
+ subparsers = parser.add_subparsers(dest="command", required=True)
34
+
35
+ subparsers.add_parser("info", help="Show benchmark version and split counts.")
36
+
37
+ export = subparsers.add_parser(
38
+ "export", help="Export the benchmark from Hugging Face as JSON Lines."
39
+ )
40
+ export.add_argument("output", type=Path)
41
+ export.add_argument("--subset", choices=("all", "core", "additional"), default="all")
42
+ export.add_argument("--topic")
43
+
44
+ evaluate = subparsers.add_parser("evaluate", help="Score victim-model responses from JSON Lines.")
45
+ evaluate.add_argument("input", type=Path, help="JSONL with id/index/question and response fields.")
46
+ evaluate.add_argument("--output", type=Path, required=True)
47
+ evaluate.add_argument(
48
+ "--provider",
49
+ required=True,
50
+ choices=("openai", "openai-compatible", "anthropic", "transformers"),
51
+ )
52
+ evaluate.add_argument("--model", required=True)
53
+ evaluate.add_argument("--base-url", help="Base URL for an OpenAI-compatible endpoint.")
54
+ evaluate.add_argument(
55
+ "--api-key-env",
56
+ help="Environment variable containing the provider API key.",
57
+ )
58
+ evaluate.add_argument(
59
+ "--api-style",
60
+ choices=("chat_completions", "responses"),
61
+ default="chat_completions",
62
+ help="Protocol for an OpenAI-compatible endpoint.",
63
+ )
64
+ evaluate.add_argument("--max-tokens", type=int, default=800)
65
+ evaluate.add_argument(
66
+ "--workers",
67
+ type=int,
68
+ default=1,
69
+ help=(
70
+ "Concurrent remote-judge requests. Every completed evaluation is "
71
+ "persisted as an independent atomic checkpoint before final gather."
72
+ ),
73
+ )
74
+ evaluate.add_argument(
75
+ "--checkpoint-dir",
76
+ type=Path,
77
+ help="Checkpoint directory (default: <output>.parts).",
78
+ )
79
+ evaluate.add_argument(
80
+ "--keep-checkpoints",
81
+ action="store_true",
82
+ help="Keep per-evaluation checkpoint files after a successful gather.",
83
+ )
84
+ evaluate.add_argument("--device-map", default="auto", help="Transformers device_map value.")
85
+ return parser
86
+
87
+
88
+ def _read_jsonl(path: Path) -> Iterable[tuple[int, dict[str, Any]]]:
89
+ with path.open("r", encoding="utf-8") as handle:
90
+ for line_number, line in enumerate(handle, start=1):
91
+ if line.strip():
92
+ value = json.loads(line)
93
+ if not isinstance(value, dict):
94
+ raise ValueError(f"Line {line_number} must contain a JSON object")
95
+ yield line_number, value
96
+
97
+
98
+ def _api_key(args: argparse.Namespace) -> str | None:
99
+ if not args.api_key_env:
100
+ return None
101
+ try:
102
+ return os.environ[args.api_key_env]
103
+ except KeyError as exc:
104
+ raise ValueError(f"Environment variable {args.api_key_env!r} is not set") from exc
105
+
106
+
107
+ def _make_judge(args: argparse.Namespace):
108
+ key = _api_key(args)
109
+ if args.provider == "openai":
110
+ return OpenAIJudge(args.model, api_key=key)
111
+ if args.provider == "openai-compatible":
112
+ if not args.base_url:
113
+ raise ValueError("--base-url is required for --provider openai-compatible")
114
+ return OpenAICompatibleJudge(
115
+ args.model,
116
+ api_key=key,
117
+ base_url=args.base_url,
118
+ api_style=args.api_style,
119
+ )
120
+ if args.provider == "anthropic":
121
+ return AnthropicJudge(args.model, api_key=key)
122
+ return TransformersJudge(args.model, device_map=args.device_map)
123
+
124
+
125
+ def _case_identifier(record: dict[str, Any], line_number: int) -> int | str:
126
+ for key in ("id", "index", "question"):
127
+ if key in record:
128
+ return record[key]
129
+ raise ValueError(f"Line {line_number} must contain one of: id, index, question")
130
+
131
+
132
+ def _evaluate_record(
133
+ evaluator: GuidedBenchEvaluator,
134
+ line_number: int,
135
+ record: dict[str, Any],
136
+ ) -> tuple[dict[str, Any], float]:
137
+ if not isinstance(record.get("response"), str):
138
+ raise ValueError(f"Line {line_number} must contain a string response")
139
+ case = get_case(_case_identifier(record, line_number))
140
+ result = evaluator.evaluate(case, record["response"])
141
+ value = result.to_dict()
142
+ value["status"] = "completed"
143
+ value["input_line"] = line_number
144
+ for optional_key in ("method", "victim_model", "metadata"):
145
+ if optional_key in record:
146
+ value[optional_key] = record[optional_key]
147
+ return value, result.score
148
+
149
+
150
+ @dataclass(frozen=True, slots=True)
151
+ class _EvaluationTask:
152
+ task_id: str
153
+ line_number: int
154
+ record: dict[str, Any]
155
+ result_path: Path
156
+ error_path: Path
157
+
158
+
159
+ def _checkpoint_directory(args: argparse.Namespace) -> Path:
160
+ if args.checkpoint_dir is not None:
161
+ return args.checkpoint_dir
162
+ return args.output.with_name(args.output.name + ".parts")
163
+
164
+
165
+ def _task_id(
166
+ args: argparse.Namespace,
167
+ line_number: int,
168
+ record: dict[str, Any],
169
+ ) -> str:
170
+ payload = {
171
+ "package_version": __version__,
172
+ "benchmark_version": BENCHMARK_VERSION,
173
+ "provider": args.provider,
174
+ "model": args.model,
175
+ "base_url": args.base_url,
176
+ "api_style": args.api_style,
177
+ "max_tokens": args.max_tokens,
178
+ "input_line": line_number,
179
+ "record": record,
180
+ }
181
+ encoded = json.dumps(
182
+ payload,
183
+ ensure_ascii=False,
184
+ sort_keys=True,
185
+ separators=(",", ":"),
186
+ ).encode("utf-8")
187
+ return hashlib.sha256(encoded).hexdigest()
188
+
189
+
190
+ def _atomic_write_json(path: Path, value: dict[str, Any]) -> None:
191
+ path.parent.mkdir(parents=True, exist_ok=True)
192
+ temporary = path.with_name(f".{path.name}.{uuid.uuid4().hex}.tmp")
193
+ try:
194
+ with temporary.open("x", encoding="utf-8") as handle:
195
+ handle.write(json.dumps(value, ensure_ascii=False) + "\n")
196
+ handle.flush()
197
+ os.fsync(handle.fileno())
198
+ os.replace(temporary, path)
199
+ finally:
200
+ if temporary.exists():
201
+ temporary.unlink()
202
+
203
+
204
+ def _atomic_write_jsonl(path: Path, values: Iterable[dict[str, Any]]) -> None:
205
+ path.parent.mkdir(parents=True, exist_ok=True)
206
+ temporary = path.with_name(f".{path.name}.{uuid.uuid4().hex}.tmp")
207
+ try:
208
+ with temporary.open("x", encoding="utf-8") as handle:
209
+ for value in values:
210
+ handle.write(json.dumps(value, ensure_ascii=False) + "\n")
211
+ handle.flush()
212
+ os.fsync(handle.fileno())
213
+ os.replace(temporary, path)
214
+ finally:
215
+ if temporary.exists():
216
+ temporary.unlink()
217
+
218
+
219
+ def _read_completed_result(path: Path, task_id: str) -> dict[str, Any] | None:
220
+ if not path.exists():
221
+ return None
222
+ try:
223
+ value = json.loads(path.read_text(encoding="utf-8"))
224
+ except (json.JSONDecodeError, OSError):
225
+ return None
226
+ if not isinstance(value, dict):
227
+ return None
228
+ if value.get("task_id") != task_id or value.get("status") != "completed":
229
+ return None
230
+ return value
231
+
232
+
233
+ def _read_gathered_results(path: Path) -> dict[str, dict[str, Any]]:
234
+ if not path.exists():
235
+ return {}
236
+ results: dict[str, dict[str, Any]] = {}
237
+ for _, value in _read_jsonl(path):
238
+ task_id = value.get("task_id")
239
+ if isinstance(task_id, str) and value.get("status") == "completed":
240
+ results[task_id] = value
241
+ return results
242
+
243
+
244
+ def _evaluate_and_checkpoint(
245
+ evaluator: GuidedBenchEvaluator,
246
+ task: _EvaluationTask,
247
+ ) -> tuple[bool, dict[str, Any]]:
248
+ try:
249
+ value, _ = _evaluate_record(evaluator, task.line_number, task.record)
250
+ value["task_id"] = task.task_id
251
+ _atomic_write_json(task.result_path, value)
252
+ if task.error_path.exists():
253
+ task.error_path.unlink()
254
+ return True, value
255
+ except Exception as exc:
256
+ failure = {
257
+ "task_id": task.task_id,
258
+ "status": "failed",
259
+ "input_line": task.line_number,
260
+ "identifier": _case_identifier(task.record, task.line_number),
261
+ "error_type": exc.__class__.__name__,
262
+ "error": str(exc),
263
+ }
264
+ _atomic_write_json(task.error_path, failure)
265
+ return False, failure
266
+
267
+
268
+ def _clean_checkpoints(tasks: Iterable[_EvaluationTask], directory: Path) -> None:
269
+ for task in tasks:
270
+ for path in (task.result_path, task.error_path):
271
+ if path.exists():
272
+ path.unlink()
273
+ try:
274
+ directory.rmdir()
275
+ except OSError:
276
+ # Leave unrelated or crash-leftover files untouched.
277
+ pass
278
+
279
+
280
+ def _run_evaluate(args: argparse.Namespace) -> int:
281
+ if args.workers < 1:
282
+ raise ValueError("--workers must be at least 1")
283
+ if args.provider == "transformers" and args.workers != 1:
284
+ raise ValueError(
285
+ "The local Transformers backend currently requires --workers 1; "
286
+ "serve the model behind an OpenAI-compatible endpoint for concurrent requests"
287
+ )
288
+
289
+ records = list(_read_jsonl(args.input))
290
+ for line_number, record in records:
291
+ if not isinstance(record.get("response"), str):
292
+ raise ValueError(f"Line {line_number} must contain a string response")
293
+ get_case(_case_identifier(record, line_number))
294
+ _api_key(args) # Fail before scheduling work if an explicitly named key is missing.
295
+ args.output.parent.mkdir(parents=True, exist_ok=True)
296
+ checkpoint_dir = _checkpoint_directory(args)
297
+ tasks = tuple(
298
+ _EvaluationTask(
299
+ task_id=(task_id := _task_id(args, line_number, record)),
300
+ line_number=line_number,
301
+ record=record,
302
+ result_path=checkpoint_dir / f"{line_number:06d}-{task_id}.json",
303
+ error_path=checkpoint_dir / f"{line_number:06d}-{task_id}.error.json",
304
+ )
305
+ for line_number, record in records
306
+ )
307
+
308
+ gathered_cache = _read_gathered_results(args.output)
309
+ completed: dict[str, dict[str, Any]] = {}
310
+ pending: list[_EvaluationTask] = []
311
+ for task in tasks:
312
+ value = _read_completed_result(task.result_path, task.task_id)
313
+ if value is None:
314
+ value = gathered_cache.get(task.task_id)
315
+ if value is None:
316
+ pending.append(task)
317
+ else:
318
+ completed[task.task_id] = value
319
+
320
+ failures: list[dict[str, Any]] = []
321
+ if pending and args.workers == 1:
322
+ evaluator = GuidedBenchEvaluator(_make_judge(args), max_tokens=args.max_tokens)
323
+ for task in pending:
324
+ succeeded, value = _evaluate_and_checkpoint(evaluator, task)
325
+ if succeeded:
326
+ completed[task.task_id] = value
327
+ else:
328
+ failures.append(value)
329
+ elif pending:
330
+ worker_state = threading.local()
331
+
332
+ def evaluate_in_worker(
333
+ task: _EvaluationTask,
334
+ ) -> tuple[_EvaluationTask, bool, dict[str, Any]]:
335
+ evaluator = getattr(worker_state, "evaluator", None)
336
+ if evaluator is None:
337
+ evaluator = GuidedBenchEvaluator(
338
+ _make_judge(args),
339
+ max_tokens=args.max_tokens,
340
+ )
341
+ worker_state.evaluator = evaluator
342
+ succeeded, value = _evaluate_and_checkpoint(evaluator, task)
343
+ return task, succeeded, value
344
+
345
+ with ThreadPoolExecutor(max_workers=args.workers) as executor:
346
+ futures = [executor.submit(evaluate_in_worker, task) for task in pending]
347
+ for future in as_completed(futures):
348
+ task, succeeded, value = future.result()
349
+ if succeeded:
350
+ completed[task.task_id] = value
351
+ else:
352
+ failures.append(value)
353
+
354
+ if failures:
355
+ print(
356
+ json.dumps(
357
+ {
358
+ "status": "incomplete",
359
+ "completed": len(completed),
360
+ "failed": len(failures),
361
+ "checkpoint_dir": str(checkpoint_dir),
362
+ }
363
+ ),
364
+ file=sys.stderr,
365
+ )
366
+ return 1
367
+
368
+ missing = [task.task_id for task in tasks if task.task_id not in completed]
369
+ if missing:
370
+ raise RuntimeError(f"Missing {len(missing)} checkpoint results before gather")
371
+
372
+ ordered_results = [completed[task.task_id] for task in tasks]
373
+ _atomic_write_jsonl(args.output, ordered_results)
374
+ if not args.keep_checkpoints:
375
+ _clean_checkpoints(tasks, checkpoint_dir)
376
+
377
+ scores = [float(value["score"]) for value in ordered_results]
378
+ mean = sum(scores) / len(scores) if scores else 0.0
379
+ print(
380
+ json.dumps(
381
+ {
382
+ "status": "completed",
383
+ "evaluated": len(scores),
384
+ "recovered": len(tasks) - len(pending),
385
+ "mean_score": mean,
386
+ "workers": args.workers,
387
+ "output": str(args.output),
388
+ }
389
+ ),
390
+ file=sys.stderr,
391
+ )
392
+ return 0
393
+
394
+
395
+ def main(argv: list[str] | None = None) -> int:
396
+ args = _parser().parse_args(argv)
397
+ try:
398
+ if args.command == "info":
399
+ all_cases = load_cases()
400
+ value = {
401
+ "package_version": __version__,
402
+ "benchmark_version": BENCHMARK_VERSION,
403
+ "cases": len(all_cases),
404
+ "core": len(load_cases(subset="core")),
405
+ "additional": len(load_cases(subset="additional")),
406
+ "topics": len({case.topic for case in all_cases}),
407
+ }
408
+ print(json.dumps(value, indent=2))
409
+ return 0
410
+ if args.command == "export":
411
+ path = export_jsonl(args.output, subset=args.subset, topic=args.topic)
412
+ print(path)
413
+ return 0
414
+ if args.command == "evaluate":
415
+ return _run_evaluate(args)
416
+ except (KeyError, MissingDependencyError, OSError, RuntimeError, ValueError) as exc:
417
+ print(f"guidedbench: error: {exc}", file=sys.stderr)
418
+ return 2
419
+ return 1
420
+
421
+
422
+ if __name__ == "__main__":
423
+ raise SystemExit(main())
guidedbench/dataset.py ADDED
@@ -0,0 +1,172 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from functools import lru_cache
5
+ from pathlib import Path
6
+ from typing import Literal
7
+
8
+ from huggingface_hub import hf_hub_download
9
+ from huggingface_hub.errors import GatedRepoError
10
+
11
+ from ._version import BENCHMARK_VERSION
12
+ from .models import GuidedBenchCase
13
+
14
+
15
+ Subset = Literal["all", "core", "additional"]
16
+
17
+ DATASET_REPO_ID = "HRXUST/GuidedBench"
18
+ DATASET_REVISION = "10cc683c8df8c07139d3f900e381fb52738123c3"
19
+ _DATA_FILES = (
20
+ ("core", "core.jsonl", 180),
21
+ ("additional", "additional.jsonl", 20),
22
+ )
23
+
24
+
25
+ @lru_cache(maxsize=8)
26
+ def _all_cases(
27
+ repo_id: str,
28
+ revision: str,
29
+ cache_dir: str | None,
30
+ local_files_only: bool,
31
+ ) -> tuple[GuidedBenchCase, ...]:
32
+ cases: list[GuidedBenchCase] = []
33
+ try:
34
+ for expected_subset, filename, expected_count in _DATA_FILES:
35
+ data_file = hf_hub_download(
36
+ repo_id=repo_id,
37
+ filename=filename,
38
+ repo_type="dataset",
39
+ revision=revision,
40
+ cache_dir=cache_dir,
41
+ local_files_only=local_files_only,
42
+ )
43
+ subset_cases: list[GuidedBenchCase] = []
44
+ with Path(data_file).open("r", encoding="utf-8") as handle:
45
+ for line_number, line in enumerate(handle, start=1):
46
+ if not line.strip():
47
+ continue
48
+ try:
49
+ case = GuidedBenchCase.from_dict(json.loads(line))
50
+ except (KeyError, TypeError, ValueError, json.JSONDecodeError) as exc:
51
+ raise RuntimeError(
52
+ f"Invalid GuidedBench record in {filename}:{line_number}"
53
+ ) from exc
54
+ if case.subset != expected_subset:
55
+ raise RuntimeError(
56
+ f"Expected subset {expected_subset!r} in {filename}, "
57
+ f"found {case.subset!r}"
58
+ )
59
+ subset_cases.append(case)
60
+ if len(subset_cases) != expected_count:
61
+ raise RuntimeError(
62
+ f"Expected {expected_count} cases in {filename}, "
63
+ f"found {len(subset_cases)}"
64
+ )
65
+ cases.extend(subset_cases)
66
+ except GatedRepoError as exc:
67
+ raise RuntimeError(
68
+ "GuidedBench dataset access is gated. Request access at "
69
+ f"https://huggingface.co/datasets/{repo_id}, then authenticate with "
70
+ "`hf auth login` or set HF_TOKEN."
71
+ ) from exc
72
+
73
+ if len(cases) != 200:
74
+ raise RuntimeError(f"Expected 200 GuidedBench cases, found {len(cases)}")
75
+ if len({case.id for case in cases}) != len(cases):
76
+ raise RuntimeError("GuidedBench contains duplicate case ids")
77
+ if any(case.benchmark_version != BENCHMARK_VERSION for case in cases):
78
+ raise RuntimeError(
79
+ f"Dataset revision {revision} does not match benchmark version "
80
+ f"{BENCHMARK_VERSION}"
81
+ )
82
+ return tuple(cases)
83
+
84
+
85
+ def _load_source(
86
+ *,
87
+ repo_id: str,
88
+ revision: str,
89
+ cache_dir: str | Path | None,
90
+ local_files_only: bool,
91
+ ) -> tuple[GuidedBenchCase, ...]:
92
+ normalized_cache = (
93
+ str(Path(cache_dir).expanduser()) if cache_dir is not None else None
94
+ )
95
+ return _all_cases(repo_id, revision, normalized_cache, local_files_only)
96
+
97
+
98
+ def load_cases(
99
+ *,
100
+ subset: Subset = "all",
101
+ topic: str | None = None,
102
+ repo_id: str = DATASET_REPO_ID,
103
+ revision: str = DATASET_REVISION,
104
+ cache_dir: str | Path | None = None,
105
+ local_files_only: bool = False,
106
+ ) -> tuple[GuidedBenchCase, ...]:
107
+ """Load a pinned GuidedBench release from the Hugging Face Hub cache."""
108
+
109
+ if subset not in {"all", "core", "additional"}:
110
+ raise ValueError("subset must be one of: all, core, additional")
111
+ cases = _load_source(
112
+ repo_id=repo_id,
113
+ revision=revision,
114
+ cache_dir=cache_dir,
115
+ local_files_only=local_files_only,
116
+ )
117
+ if subset != "all":
118
+ cases = tuple(case for case in cases if case.subset == subset)
119
+ if topic is not None:
120
+ cases = tuple(case for case in cases if case.topic == topic)
121
+ return cases
122
+
123
+
124
+ def get_case(
125
+ identifier: int | str,
126
+ *,
127
+ repo_id: str = DATASET_REPO_ID,
128
+ revision: str = DATASET_REVISION,
129
+ cache_dir: str | Path | None = None,
130
+ local_files_only: bool = False,
131
+ ) -> GuidedBenchCase:
132
+ """Find a case by integer index, stable id, or exact question text."""
133
+
134
+ cases = _load_source(
135
+ repo_id=repo_id,
136
+ revision=revision,
137
+ cache_dir=cache_dir,
138
+ local_files_only=local_files_only,
139
+ )
140
+ for case in cases:
141
+ if isinstance(identifier, int) and case.index == identifier:
142
+ return case
143
+ if isinstance(identifier, str) and identifier in {case.id, case.question}:
144
+ return case
145
+ raise KeyError(f"No GuidedBench case matches {identifier!r}")
146
+
147
+
148
+ def export_jsonl(
149
+ path: str | Path,
150
+ *,
151
+ subset: Subset = "all",
152
+ topic: str | None = None,
153
+ repo_id: str = DATASET_REPO_ID,
154
+ revision: str = DATASET_REVISION,
155
+ cache_dir: str | Path | None = None,
156
+ local_files_only: bool = False,
157
+ ) -> Path:
158
+ """Export records downloaded from Hugging Face as JSON Lines."""
159
+
160
+ destination = Path(path)
161
+ destination.parent.mkdir(parents=True, exist_ok=True)
162
+ with destination.open("w", encoding="utf-8") as handle:
163
+ for case in load_cases(
164
+ subset=subset,
165
+ topic=topic,
166
+ repo_id=repo_id,
167
+ revision=revision,
168
+ cache_dir=cache_dir,
169
+ local_files_only=local_files_only,
170
+ ):
171
+ handle.write(json.dumps(case.to_dict(), ensure_ascii=False) + "\n")
172
+ return destination
@@ -0,0 +1,29 @@
1
+ from __future__ import annotations
2
+
3
+ from .dataset import get_case
4
+ from .judges.base import Judge
5
+ from .models import EvaluationResult, GuidedBenchCase
6
+ from .parsing import parse_guided_response
7
+ from .prompt import JUDGE_SYSTEM_PROMPT, build_guided_prompt
8
+
9
+
10
+ class GuidedBenchEvaluator:
11
+ """Evaluate responses using GuidedBench's verified case-specific guidelines."""
12
+
13
+ def __init__(self, judge: Judge, *, max_tokens: int = 800) -> None:
14
+ self.judge = judge
15
+ self.max_tokens = max_tokens
16
+
17
+ def evaluate(
18
+ self,
19
+ case: GuidedBenchCase | int | str,
20
+ response: str,
21
+ ) -> EvaluationResult:
22
+ resolved_case = case if isinstance(case, GuidedBenchCase) else get_case(case)
23
+ prompt = build_guided_prompt(resolved_case, response)
24
+ raw = self.judge.generate(
25
+ prompt,
26
+ system=JUDGE_SYSTEM_PROMPT,
27
+ max_tokens=self.max_tokens,
28
+ )
29
+ return parse_guided_response(raw, resolved_case, judge=self.judge.name)
@@ -0,0 +1,13 @@
1
+ from .anthropic import AnthropicJudge
2
+ from .base import Judge, MissingDependencyError
3
+ from .openai import OpenAICompatibleJudge, OpenAIJudge
4
+ from .transformers import TransformersJudge
5
+
6
+ __all__ = [
7
+ "AnthropicJudge",
8
+ "Judge",
9
+ "MissingDependencyError",
10
+ "OpenAICompatibleJudge",
11
+ "OpenAIJudge",
12
+ "TransformersJudge",
13
+ ]