auditkit 1.0.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.
- auditkit/README.md +99 -0
- auditkit/__init__.py +177 -0
- auditkit/__main__.py +3 -0
- auditkit/_bootstrap.py +77 -0
- auditkit/_identity_guard.py +99 -0
- auditkit/adapter.py +264 -0
- auditkit/annotator.py +339 -0
- auditkit/api.py +502 -0
- auditkit/assets/auditkit_logo.png +0 -0
- auditkit/cache.py +47 -0
- auditkit/cli.py +417 -0
- auditkit/comparison.py +563 -0
- auditkit/diff.py +265 -0
- auditkit/errors.py +54 -0
- auditkit/evaluator.py +20 -0
- auditkit/experiment.py +145 -0
- auditkit/hf_publish.py +262 -0
- auditkit/lmeval_engine.py +550 -0
- auditkit/loaders.py +121 -0
- auditkit/logs.py +18 -0
- auditkit/metric.py +199 -0
- auditkit/metrics/README.md +15 -0
- auditkit/metrics/__init__.py +0 -0
- auditkit/metrics/code.py +222 -0
- auditkit/metrics/embedding.py +131 -0
- auditkit/metrics/encoder_judge.py +423 -0
- auditkit/metrics/generation.py +331 -0
- auditkit/metrics/guard.py +412 -0
- auditkit/metrics/hallucination.py +45 -0
- auditkit/metrics/judge.py +547 -0
- auditkit/metrics/pairwise.py +153 -0
- auditkit/metrics/perf.py +53 -0
- auditkit/metrics/rag.py +149 -0
- auditkit/metrics/security.py +64 -0
- auditkit/metrics/toxicity.py +238 -0
- auditkit/model/README.md +16 -0
- auditkit/model/__init__.py +485 -0
- auditkit/model/anthropic.py +94 -0
- auditkit/model/api_gen.py +133 -0
- auditkit/model/groq_gen.py +121 -0
- auditkit/model/hf_gen.py +385 -0
- auditkit/model/lexsi.py +155 -0
- auditkit/model/litellm_gen.py +65 -0
- auditkit/model/openai.py +90 -0
- auditkit/model/openrouter_gen.py +152 -0
- auditkit/model/vllm_gen.py +316 -0
- auditkit/model_compare.py +655 -0
- auditkit/redteam/README.md +9 -0
- auditkit/redteam/__init__.py +26 -0
- auditkit/redteam/detector.py +37 -0
- auditkit/redteam/detectors/README.md +5 -0
- auditkit/redteam/detectors/builtin.py +126 -0
- auditkit/redteam/probe.py +39 -0
- auditkit/redteam/probes/README.md +5 -0
- auditkit/redteam/probes/builtin.py +85 -0
- auditkit/redteam/runner.py +206 -0
- auditkit/registry.py +65 -0
- auditkit/report.py +278 -0
- auditkit/report_format.py +52 -0
- auditkit/router.py +54 -0
- auditkit/runner.py +575 -0
- auditkit/runspec.py +159 -0
- auditkit/sample.py +40 -0
- auditkit/scenario.py +88 -0
- auditkit/scenarios/README.md +10 -0
- auditkit/scenarios/__init__.py +4 -0
- auditkit/scenarios/arc.py +33 -0
- auditkit/scenarios/gsm8k.py +32 -0
- auditkit/scenarios/hellaswag.py +33 -0
- auditkit/scenarios/humaneval.py +32 -0
- auditkit/scenarios/mmlu.py +34 -0
- auditkit/scenarios/truthfulqa.py +33 -0
- auditkit/score.py +165 -0
- auditkit/scorers.py +117 -0
- auditkit/scoring.py +79 -0
- auditkit/types.py +69 -0
- auditkit-1.0.0.dist-info/METADATA +396 -0
- auditkit-1.0.0.dist-info/RECORD +81 -0
- auditkit-1.0.0.dist-info/WHEEL +4 -0
- auditkit-1.0.0.dist-info/entry_points.txt +2 -0
- auditkit-1.0.0.dist-info/licenses/LICENSE.md +92 -0
auditkit/cli.py
ADDED
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
"""auditkit CLI — run evaluations from the command line."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
import argparse
|
|
4
|
+
import sys
|
|
5
|
+
import os
|
|
6
|
+
from .api import evaluate
|
|
7
|
+
from .loaders import load_csv
|
|
8
|
+
from .sample import Sample
|
|
9
|
+
from .report_format import Report
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _load_yaml_config(path: str) -> dict:
|
|
13
|
+
"""Load a YAML config file (pyyaml optional)."""
|
|
14
|
+
with open(path) as f:
|
|
15
|
+
raw = f.read()
|
|
16
|
+
try:
|
|
17
|
+
import yaml as _yaml
|
|
18
|
+
return _yaml.safe_load(raw) or {}
|
|
19
|
+
except ImportError:
|
|
20
|
+
import json as _json
|
|
21
|
+
try:
|
|
22
|
+
return _json.loads(raw)
|
|
23
|
+
except _json.JSONDecodeError:
|
|
24
|
+
raise SystemExit(
|
|
25
|
+
"PyYAML is not installed. Install it with 'pip install pyyaml' "
|
|
26
|
+
"or provide a JSON config file instead."
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _merge_yaml(args: argparse.Namespace, cfg: dict) -> argparse.Namespace:
|
|
31
|
+
"""Overlay YAML keys onto CLI args (CLI flags take precedence when not None)."""
|
|
32
|
+
mapping = {
|
|
33
|
+
"model": "model", "system_prompt": "system_prompt",
|
|
34
|
+
"instruction": "instruction", "template": "template",
|
|
35
|
+
"temperature": "temperature", "top_p": "top_p",
|
|
36
|
+
"max_tokens": "max_tokens", "seed": "seed",
|
|
37
|
+
"limit": "limit", "trials": "trials",
|
|
38
|
+
"num_fewshot": "num_fewshot", "concurrency": "concurrency",
|
|
39
|
+
"experiment": "experiment", "output": "output",
|
|
40
|
+
"format": "format", "mlflow_uri": "mlflow_uri",
|
|
41
|
+
"dataset": "dataset", "subject": "subject",
|
|
42
|
+
"adapter": "adapter",
|
|
43
|
+
"gpu_memory_utilization": "gpu_memory_utilization",
|
|
44
|
+
}
|
|
45
|
+
for yaml_key, ns_key in mapping.items():
|
|
46
|
+
yv = cfg.get(yaml_key)
|
|
47
|
+
if yv is not None and getattr(args, ns_key, None) is None:
|
|
48
|
+
setattr(args, ns_key, yv)
|
|
49
|
+
|
|
50
|
+
if "tags" in cfg and not args.tag:
|
|
51
|
+
args.tag = cfg["tags"]
|
|
52
|
+
if "stop" in cfg and not args.stop:
|
|
53
|
+
args.stop = cfg["stop"]
|
|
54
|
+
if "split" in cfg:
|
|
55
|
+
s = cfg["split"]
|
|
56
|
+
if args.split_strategy is None:
|
|
57
|
+
args.split_strategy = s.get("strategy")
|
|
58
|
+
args.train_ratio = s.get("train_ratio", args.train_ratio)
|
|
59
|
+
args.val_ratio = s.get("val_ratio", args.val_ratio)
|
|
60
|
+
args.test_ratio = s.get("test_ratio", args.test_ratio)
|
|
61
|
+
args.split_seed = s.get("seed", args.split_seed)
|
|
62
|
+
return args
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _build_eval_parser(prog: str = "auditkit eval") -> argparse.ArgumentParser:
|
|
66
|
+
p = argparse.ArgumentParser(prog=prog, description="Evaluate any model on any task")
|
|
67
|
+
p.add_argument("--config", help="Path to YAML/JSON config file")
|
|
68
|
+
p.add_argument("--model", required=True, help="Model spec (e.g. hf:gpt2, groq:llama-3.3-70b-versatile)")
|
|
69
|
+
p.add_argument("--csv", help="Path to CSV dataset")
|
|
70
|
+
p.add_argument("--input-col", default="input", help="CSV input column")
|
|
71
|
+
p.add_argument("--target-col", default="target", help="CSV target column")
|
|
72
|
+
p.add_argument("--output", "-o", help="Save results to path")
|
|
73
|
+
p.add_argument("--format", choices=["json", "csv", "md"], default="json", help="Output format")
|
|
74
|
+
p.add_argument("--temperature", type=float, help="Generation temperature")
|
|
75
|
+
p.add_argument("--top-p", type=float, help="Nucleus sampling top-p")
|
|
76
|
+
p.add_argument("--max-tokens", type=int, help="Max tokens to generate")
|
|
77
|
+
p.add_argument("--stop", nargs="*", help="Stop sequences")
|
|
78
|
+
p.add_argument("--seed", type=int, help="Random seed")
|
|
79
|
+
p.add_argument("--limit", type=int, help="Max samples")
|
|
80
|
+
p.add_argument("--trials", type=int, help="Number of trials")
|
|
81
|
+
p.add_argument("--num-fewshot", type=int, help="Few-shot examples count")
|
|
82
|
+
p.add_argument("--concurrency", type=int, help="Max concurrency")
|
|
83
|
+
p.add_argument("--verbose", action="store_true", help="Verbose output")
|
|
84
|
+
p.add_argument("--split-strategy", choices=["sequential", "random"], help="Dataset split strategy")
|
|
85
|
+
p.add_argument("--train-ratio", type=float, default=0.0, help="Train split ratio")
|
|
86
|
+
p.add_argument("--val-ratio", type=float, default=0.0, help="Validation split ratio")
|
|
87
|
+
p.add_argument("--test-ratio", type=float, default=1.0, help="Test split ratio")
|
|
88
|
+
p.add_argument("--split-seed", type=int, help="Split random seed")
|
|
89
|
+
p.add_argument("--experiment", help="Experiment name for tracking")
|
|
90
|
+
p.add_argument("--mlflow-uri", help="MLflow tracking URI")
|
|
91
|
+
p.add_argument("--tag", action="append", help="Tags (can repeat)")
|
|
92
|
+
p.add_argument("--dataset", help="Built-in dataset name (mmlu, gsm8k, arc)")
|
|
93
|
+
p.add_argument("--subject", help="MMLU subject (when --dataset=mmlu)")
|
|
94
|
+
p.add_argument("--engine", choices=["native", "lmeval"], default="native",
|
|
95
|
+
help="Eval engine: 'native' spine (default) or 'lmeval' (lm-eval harness)")
|
|
96
|
+
p.add_argument("--tasks", help="lm-eval task name(s), comma-separated (with --engine lmeval)")
|
|
97
|
+
p.add_argument("--adapter", default="generation", choices=["generation", "chat", "instruction", "fewshot", "rag", "template"], help="Adapter type")
|
|
98
|
+
p.add_argument("--system-prompt", default="You are a helpful assistant.", help="System prompt (for chat adapter)")
|
|
99
|
+
p.add_argument("--instruction", default="Answer the following question:", help="Instruction prefix (for instruction adapter)")
|
|
100
|
+
p.add_argument("--template", default="{input}", help="Template string (for template adapter)")
|
|
101
|
+
p.add_argument(
|
|
102
|
+
"--gpu-memory-utilization",
|
|
103
|
+
type=float,
|
|
104
|
+
help="vLLM GPU memory fraction (forwarded to AutoModel.resolve)",
|
|
105
|
+
)
|
|
106
|
+
return p
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _run_eval(args: argparse.Namespace, yaml_prompts: list[str] | None = None) -> None:
|
|
110
|
+
"""Execute an evaluation from parsed args."""
|
|
111
|
+
cfg = {}
|
|
112
|
+
if args.config:
|
|
113
|
+
cfg = _load_yaml_config(args.config)
|
|
114
|
+
args = _merge_yaml(args, cfg)
|
|
115
|
+
|
|
116
|
+
from .runspec import RunConfig, SplitConfig
|
|
117
|
+
|
|
118
|
+
split = None
|
|
119
|
+
if args.split_strategy:
|
|
120
|
+
split_kwargs = dict(
|
|
121
|
+
strategy=args.split_strategy,
|
|
122
|
+
train_ratio=args.train_ratio,
|
|
123
|
+
val_ratio=args.val_ratio,
|
|
124
|
+
test_ratio=args.test_ratio,
|
|
125
|
+
)
|
|
126
|
+
# Only override SplitConfig's own seed=0 default when --split-seed
|
|
127
|
+
# was actually passed -- args.split_seed is None whenever it wasn't
|
|
128
|
+
# (argparse has no default= on that flag), and passing seed=None
|
|
129
|
+
# explicitly would silently defeat SplitConfig's own reproducibility
|
|
130
|
+
# guarantee (None draws fresh OS entropy every call, reshuffling
|
|
131
|
+
# differently each run instead of the same way every time).
|
|
132
|
+
if args.split_seed is not None:
|
|
133
|
+
split_kwargs["seed"] = args.split_seed
|
|
134
|
+
split = SplitConfig(**split_kwargs)
|
|
135
|
+
|
|
136
|
+
config = RunConfig(
|
|
137
|
+
temperature=args.temperature,
|
|
138
|
+
top_p=args.top_p,
|
|
139
|
+
max_tokens=args.max_tokens,
|
|
140
|
+
stop_sequences=args.stop,
|
|
141
|
+
seed=args.seed,
|
|
142
|
+
limit=args.limit,
|
|
143
|
+
trials=args.trials,
|
|
144
|
+
num_fewshot=args.num_fewshot,
|
|
145
|
+
concurrency=args.concurrency if args.concurrency is not None else 1,
|
|
146
|
+
split=split,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
engine = getattr(args, "engine", "native")
|
|
150
|
+
tasks = getattr(args, "tasks", None) or cfg.get("tasks")
|
|
151
|
+
if engine == "lmeval" or tasks:
|
|
152
|
+
from .api import run_lmeval
|
|
153
|
+
if not tasks:
|
|
154
|
+
print("--engine lmeval needs --tasks (e.g. --tasks mmlu,gsm8k).")
|
|
155
|
+
return
|
|
156
|
+
result = run_lmeval(tasks, model=args.model, config=config,
|
|
157
|
+
experiment_name=args.experiment, tags=args.tag)
|
|
158
|
+
if args.output:
|
|
159
|
+
if args.format == "md":
|
|
160
|
+
with open(args.output, "w") as fh:
|
|
161
|
+
fh.write(str(Report(result)))
|
|
162
|
+
else:
|
|
163
|
+
result.save(args.output, fmt=args.format)
|
|
164
|
+
else:
|
|
165
|
+
print(result.summary())
|
|
166
|
+
return
|
|
167
|
+
|
|
168
|
+
dataset_kwargs = {}
|
|
169
|
+
yaml_prompts = yaml_prompts or cfg.get("prompts")
|
|
170
|
+
if args.dataset:
|
|
171
|
+
dataset = args.dataset
|
|
172
|
+
if args.subject:
|
|
173
|
+
dataset_kwargs["subject"] = args.subject
|
|
174
|
+
elif args.csv:
|
|
175
|
+
samples = load_csv(args.csv, input_col=args.input_col, target_col=args.target_col)
|
|
176
|
+
dataset = samples
|
|
177
|
+
elif yaml_prompts:
|
|
178
|
+
dataset = [Sample(input=p) for p in yaml_prompts]
|
|
179
|
+
else:
|
|
180
|
+
dataset = [Sample(input=line.rstrip()) for line in sys.stdin if line.strip()]
|
|
181
|
+
|
|
182
|
+
if not dataset:
|
|
183
|
+
print("No dataset provided. Use --csv, --dataset, --config with prompts:, or pipe input via stdin.")
|
|
184
|
+
return
|
|
185
|
+
|
|
186
|
+
if args.adapter == "chat":
|
|
187
|
+
from .adapter import ChatAdapter
|
|
188
|
+
adapter = ChatAdapter(system_prompt=args.system_prompt)
|
|
189
|
+
elif args.adapter == "instruction":
|
|
190
|
+
from .adapter import InstructionAdapter
|
|
191
|
+
adapter = InstructionAdapter(instruction=args.instruction)
|
|
192
|
+
elif args.adapter == "fewshot":
|
|
193
|
+
from .adapter import FewShotAdapter
|
|
194
|
+
adapter = FewShotAdapter(num_shots=args.num_fewshot or 3)
|
|
195
|
+
elif args.adapter == "rag":
|
|
196
|
+
from .adapter import RAGAdapter
|
|
197
|
+
adapter = RAGAdapter()
|
|
198
|
+
elif args.adapter == "template":
|
|
199
|
+
from .adapter import TemplateAdapter
|
|
200
|
+
adapter = TemplateAdapter(template=args.template)
|
|
201
|
+
else:
|
|
202
|
+
adapter = None
|
|
203
|
+
|
|
204
|
+
eval_opts = dict(dataset_kwargs)
|
|
205
|
+
gpu_util = getattr(args, "gpu_memory_utilization", None)
|
|
206
|
+
if gpu_util is not None:
|
|
207
|
+
eval_opts["gpu_memory_utilization"] = gpu_util
|
|
208
|
+
result = evaluate(dataset, model=args.model, adapter=adapter, config=config,
|
|
209
|
+
experiment_name=args.experiment, tags=args.tag, **eval_opts)
|
|
210
|
+
|
|
211
|
+
if args.mlflow_uri:
|
|
212
|
+
from .experiment import Experiment
|
|
213
|
+
exp = Experiment(name=args.experiment or "cli_run")
|
|
214
|
+
exp.add(result)
|
|
215
|
+
exp.log_mlflow(experiment_name=args.experiment, tracking_uri=args.mlflow_uri)
|
|
216
|
+
|
|
217
|
+
if args.output:
|
|
218
|
+
if args.format == "md":
|
|
219
|
+
with open(args.output, "w") as fh:
|
|
220
|
+
fh.write(str(Report(result)))
|
|
221
|
+
else:
|
|
222
|
+
result.save(args.output, fmt=args.format)
|
|
223
|
+
else:
|
|
224
|
+
print(result.summary())
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def cmd_init(args: argparse.Namespace) -> None:
|
|
228
|
+
"""Scaffold a new auditkit project."""
|
|
229
|
+
path = args.path or "."
|
|
230
|
+
config_path = os.path.join(path, "auditkit.yaml")
|
|
231
|
+
if os.path.exists(config_path):
|
|
232
|
+
print(f"File already exists: {config_path}")
|
|
233
|
+
return
|
|
234
|
+
os.makedirs(path, exist_ok=True)
|
|
235
|
+
with open(config_path, "w") as f:
|
|
236
|
+
f.write("""# auditkit evaluation config
|
|
237
|
+
model: hf:gpt2
|
|
238
|
+
prompts:
|
|
239
|
+
- "What is the capital of France?"
|
|
240
|
+
- "Explain quantum computing in one sentence."
|
|
241
|
+
temperature: 0.0
|
|
242
|
+
max_tokens: 128
|
|
243
|
+
concurrency: 4
|
|
244
|
+
output: results.json
|
|
245
|
+
""")
|
|
246
|
+
print(f"Created {config_path}")
|
|
247
|
+
print("Edit the file then run: auditkit eval --config auditkit.yaml")
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def cmd_list(args: argparse.Namespace) -> None:
|
|
251
|
+
"""List available resources."""
|
|
252
|
+
from .registry import ADAPTERS, ANNOTATORS, METRICS, SCENARIOS
|
|
253
|
+
|
|
254
|
+
resource = getattr(args, "resource", "all")
|
|
255
|
+
if resource in ("metrics", "all"):
|
|
256
|
+
print("Built-in metrics:")
|
|
257
|
+
print(" " + ", ".join(METRICS.names()))
|
|
258
|
+
print("")
|
|
259
|
+
if resource in ("datasets", "all"):
|
|
260
|
+
print("Built-in datasets:")
|
|
261
|
+
print(" " + ", ".join(SCENARIOS.names()))
|
|
262
|
+
print("")
|
|
263
|
+
if resource in ("adapters", "all"):
|
|
264
|
+
print("Built-in adapters:")
|
|
265
|
+
print(" " + ", ".join(ADAPTERS.names()))
|
|
266
|
+
print("")
|
|
267
|
+
if resource in ("annotators", "all"):
|
|
268
|
+
print("Built-in annotators:")
|
|
269
|
+
print(" " + ", ".join(ANNOTATORS.names()))
|
|
270
|
+
print("")
|
|
271
|
+
if resource in ("models", "all"):
|
|
272
|
+
print("Model backends:")
|
|
273
|
+
print(" precomputed - Scores samples with actual_output already set")
|
|
274
|
+
print(" openai:<model> - OpenAI (e.g. openai:gpt-4o)")
|
|
275
|
+
print(" anthropic:<model> - Anthropic (e.g. anthropic:claude-3-opus)")
|
|
276
|
+
print(" hf:<model> - HuggingFace Transformers")
|
|
277
|
+
print(" lexsi:<model> - Lexsi gateway (OpenAI-compatible)")
|
|
278
|
+
print(" vllm:<model> - vLLM")
|
|
279
|
+
print(" litellm:<model> - LiteLLM proxy (e.g. litellm:ollama/llama3.1)")
|
|
280
|
+
print(" api:<model> - Generic OpenAI-compatible endpoint (needs base_url=)")
|
|
281
|
+
print(" groq:<model> - Groq (e.g. groq:llama-3.3-70b-versatile)")
|
|
282
|
+
print("")
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def cmd_compare(args: argparse.Namespace) -> None:
|
|
286
|
+
"""Compare multiple models on the same dataset."""
|
|
287
|
+
from .model_compare import compare_models
|
|
288
|
+
from .sample import Sample
|
|
289
|
+
|
|
290
|
+
models = args.models.split(",")
|
|
291
|
+
dataset = [Sample(input=line.rstrip()) for line in sys.stdin if line.strip()] if not args.csv else None
|
|
292
|
+
|
|
293
|
+
if args.csv:
|
|
294
|
+
from .loaders import load_csv
|
|
295
|
+
dataset = load_csv(args.csv, input_col=args.input_col, target_col=args.target_col)
|
|
296
|
+
elif args.dataset:
|
|
297
|
+
dataset = args.dataset
|
|
298
|
+
|
|
299
|
+
if not dataset:
|
|
300
|
+
print("Provide a dataset via --csv, --dataset, or stdin")
|
|
301
|
+
return
|
|
302
|
+
|
|
303
|
+
scorers = args.scorers.split(",") if getattr(args, "scorers", None) else None
|
|
304
|
+
result = compare_models(models, dataset, scorers=scorers, model_names=models)
|
|
305
|
+
|
|
306
|
+
baseline = getattr(args, "baseline", None)
|
|
307
|
+
if baseline:
|
|
308
|
+
if baseline not in result.runs:
|
|
309
|
+
print(f"--baseline {baseline!r} is not one of --models ({', '.join(models)})")
|
|
310
|
+
return
|
|
311
|
+
for name in models:
|
|
312
|
+
if name != baseline:
|
|
313
|
+
print(result.pairwise(baseline, name).summary())
|
|
314
|
+
print()
|
|
315
|
+
else:
|
|
316
|
+
print(result.summary())
|
|
317
|
+
|
|
318
|
+
if getattr(args, "output", None):
|
|
319
|
+
import json
|
|
320
|
+
from .api import compare as _leaderboard
|
|
321
|
+
payload = {
|
|
322
|
+
"models": models,
|
|
323
|
+
"baseline": baseline,
|
|
324
|
+
"leaderboard": _leaderboard(list(result.runs.values())),
|
|
325
|
+
"per_metric": [
|
|
326
|
+
{"metric": m.metric, "scores": m.scores, "winner": m.winner}
|
|
327
|
+
for m in result.per_metric()
|
|
328
|
+
],
|
|
329
|
+
}
|
|
330
|
+
with open(args.output, "w", encoding="utf-8") as fh:
|
|
331
|
+
json.dump(payload, fh, indent=2, default=str)
|
|
332
|
+
print(f"wrote {args.output}")
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def cmd_redteam(args: argparse.Namespace) -> None:
|
|
336
|
+
"""Run red team evaluation."""
|
|
337
|
+
from .redteam import RedTeamRunner
|
|
338
|
+
|
|
339
|
+
probes = args.probes.split(",") if args.probes else None
|
|
340
|
+
detectors = args.detectors.split(",") if args.detectors else None
|
|
341
|
+
|
|
342
|
+
runner = RedTeamRunner(model=args.model)
|
|
343
|
+
result = runner.run(probes=probes, detectors=detectors)
|
|
344
|
+
print(result.summary())
|
|
345
|
+
if args.output:
|
|
346
|
+
import json
|
|
347
|
+
data = [
|
|
348
|
+
{
|
|
349
|
+
"probe": r.probe_name,
|
|
350
|
+
"prompt": r.prompt,
|
|
351
|
+
"output": r.output,
|
|
352
|
+
"passed": r.passed,
|
|
353
|
+
"detector": r.detector_name,
|
|
354
|
+
}
|
|
355
|
+
for r in result.results
|
|
356
|
+
]
|
|
357
|
+
with open(args.output, "w") as f:
|
|
358
|
+
json.dump(data, f, indent=2)
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def main(argv: list[str] | None = None) -> None:
|
|
362
|
+
# Detect subcommand manually so --flags work in flat mode
|
|
363
|
+
args_list = argv if argv is not None else sys.argv[1:]
|
|
364
|
+
first_arg = args_list[0] if args_list else None
|
|
365
|
+
if first_arg and not first_arg.startswith("-") and first_arg in ("init", "list", "eval", "redteam", "compare"):
|
|
366
|
+
command = first_arg
|
|
367
|
+
rest = args_list[1:] if len(args_list) > 1 else []
|
|
368
|
+
else:
|
|
369
|
+
command = None
|
|
370
|
+
rest = args_list
|
|
371
|
+
|
|
372
|
+
if command == "init":
|
|
373
|
+
p = argparse.ArgumentParser(prog="auditkit init", description="Scaffold a new project")
|
|
374
|
+
p.add_argument("path", nargs="?", default=".", help="Project directory")
|
|
375
|
+
args = p.parse_args(rest)
|
|
376
|
+
cmd_init(args)
|
|
377
|
+
return
|
|
378
|
+
|
|
379
|
+
if command == "list":
|
|
380
|
+
p = argparse.ArgumentParser(prog="auditkit list", description="List available resources")
|
|
381
|
+
p.add_argument("resource", nargs="?", default="all", choices=["all", "metrics", "datasets", "adapters", "annotators", "models"], help="Resource type to list")
|
|
382
|
+
args = p.parse_args(rest)
|
|
383
|
+
cmd_list(args)
|
|
384
|
+
return
|
|
385
|
+
|
|
386
|
+
if command == "redteam":
|
|
387
|
+
p = argparse.ArgumentParser(prog="auditkit redteam", description="Run red team evaluation")
|
|
388
|
+
p.add_argument("--model", required=True, help="Model spec (e.g. hf:gpt2, groq:llama-3.3-70b-versatile)")
|
|
389
|
+
p.add_argument("--probes", help="Comma-separated probe names")
|
|
390
|
+
p.add_argument("--detectors", help="Comma-separated detector names")
|
|
391
|
+
p.add_argument("--output", "-o", help="Save results to JSON file")
|
|
392
|
+
args = p.parse_args(rest)
|
|
393
|
+
cmd_redteam(args)
|
|
394
|
+
return
|
|
395
|
+
|
|
396
|
+
if command == "compare":
|
|
397
|
+
p = argparse.ArgumentParser(prog="auditkit compare", description="Compare multiple models on the same dataset")
|
|
398
|
+
p.add_argument("--models", required=True, help="Comma-separated model specs")
|
|
399
|
+
p.add_argument("--csv", help="Path to CSV dataset")
|
|
400
|
+
p.add_argument("--input-col", default="input", help="CSV input column")
|
|
401
|
+
p.add_argument("--target-col", default="target", help="CSV target column")
|
|
402
|
+
p.add_argument("--dataset", help="Built-in dataset name")
|
|
403
|
+
p.add_argument("--scorers", help="Comma-separated scorer/metric names (default: auto)")
|
|
404
|
+
p.add_argument("--baseline", help="Model spec (from --models) to anchor a base-vs-candidate comparison against")
|
|
405
|
+
p.add_argument("--output", "-o", help="Save results to JSON file")
|
|
406
|
+
args = p.parse_args(rest)
|
|
407
|
+
cmd_compare(args)
|
|
408
|
+
return
|
|
409
|
+
|
|
410
|
+
# Default: eval (with backwards compat for flat argument style)
|
|
411
|
+
parser = _build_eval_parser("auditkit" if command is None else "auditkit eval")
|
|
412
|
+
args = parser.parse_args(rest)
|
|
413
|
+
_run_eval(args)
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
if __name__ == "__main__":
|
|
417
|
+
main()
|