-
Notifications
You must be signed in to change notification settings - Fork 260
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
harness: Detector only #833
base: main
Are you sure you want to change the base?
Changes from 7 commits
d9e7f28
c8b7e77
13c89fe
b87068f
df107c2
d9d43a6
662fbf0
15c1097
3f8e263
4714757
9f63ab1
1b5aa46
239cfc8
65c0c36
e9eb742
a35cb5a
eab5c67
c3d33d4
153ff2e
5086e80
6526194
a7b3e1f
dbe916a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -105,6 +105,24 @@ def as_dict(self) -> dict: | |
"messages": self.messages, | ||
} | ||
|
||
@classmethod | ||
def from_dict(cls, dicti): | ||
"""Initializes an attempt object from dictionary""" | ||
attempt_obj = cls() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this skip the attempt constructor? Can we add an explicit type signature to signal what There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Due to the current overrides in the class For the purposes of this PR I suspect this is acceptable, however it is worth noting. |
||
attempt_obj.uuid = dicti['uuid'] | ||
attempt_obj.seq = dicti['seq'] | ||
attempt_obj.status = dicti['status'] | ||
attempt_obj.probe_classname = dicti['probe_classname'] | ||
attempt_obj.probe_params = dicti['probe_params'] | ||
attempt_obj.targets = dicti['targets'] | ||
attempt_obj.prompt = dicti['prompt'] | ||
attempt_obj.outputs = dicti['outputs'] | ||
attempt_obj.detector_results = dicti['detector_results'] | ||
attempt_obj.notes = dicti['notes'] | ||
attempt_obj.goal = dicti['goal'] | ||
attempt_obj.messages = dicti['messages'] | ||
return attempt_obj | ||
|
||
def __getattribute__(self, name: str) -> Any: | ||
"""override prompt and outputs access to take from history""" | ||
if name == "prompt": | ||
|
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -3,7 +3,7 @@ | |||
|
||||
"""Flow for invoking garak from the command line""" | ||||
|
||||
command_options = "list_detectors list_probes list_generators list_buffs list_config plugin_info interactive report version".split() | ||||
command_options = "list_detectors list_probes list_generators list_buffs list_config plugin_info interactive report version detector_only".split() | ||||
|
||||
|
||||
def main(arguments=None) -> None: | ||||
|
@@ -107,6 +107,12 @@ def main(arguments=None) -> None: | |||
parser.add_argument( | ||||
"--config", type=str, default=None, help="YAML config file for this run" | ||||
) | ||||
parser.add_argument( | ||||
"--probed_report_path", | ||||
type=str, | ||||
default=None, | ||||
help="Path to jsonl report that stores the generators responses" | ||||
) | ||||
|
||||
## PLUGINS | ||||
# generator | ||||
|
@@ -247,6 +253,11 @@ def main(arguments=None) -> None: | |||
action="store_true", | ||||
help="Launch garak in interactive.py mode", | ||||
) | ||||
parser.add_argument( | ||||
"--detector_only", | ||||
action="store_true", | ||||
help="run detector on jsonl report" | ||||
) | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if this might shift to a
Some validation may be need on the object received to ensure options provided are for a valid harness type and meet the requirements for launching the harness. This would then remove the need to also add {
"DetectorOnly":
{
"report_path": "file.report.jsonl"
}
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not exactly sure if |
||||
|
||||
logging.debug("args - raw argument string received: %s", arguments) | ||||
|
||||
|
@@ -512,6 +523,30 @@ def main(arguments=None) -> None: | |||
) | ||||
|
||||
command.end_run() | ||||
|
||||
elif args.detector_only: | ||||
# Run detector only detection | ||||
if not _config.plugins.detector_spec: | ||||
logging.error("Detector(s) not specified. Use --detectors") | ||||
raise ValueError("use --detectors to specify some detectors") | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. By default the detectors to use should probably be extracted from the |
||||
|
||||
if not _config.run.probed_report_path: | ||||
logging.error("report path not specified") | ||||
raise ValueError("Specify jsonl report path using --probed_report_path") | ||||
|
||||
evaluator = garak.evaluators.ThresholdEvaluator(_config.run.eval_threshold) | ||||
print(_config.plugins.detector_spec.split(",")) | ||||
|
||||
detector_names, detector_rejected = _config.parse_plugin_spec( | ||||
getattr(_config.plugins, "detector_spec", ""), | ||||
"detectors", | ||||
getattr(_config.run, "detector_tags", "") | ||||
) | ||||
|
||||
command.start_run() | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the refactor for harness selection offered is not used, this needs to be removed as
Suggested change
|
||||
command.detector_only_run(_config.run.probed_report_path, detector_names, evaluator) | ||||
command.end_run() | ||||
|
||||
else: | ||||
print("nothing to do 🤷 try --help") | ||||
if _config.plugins.model_name and not _config.plugins.model_type: | ||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,36 @@ | ||||||
import logging | ||||||
|
||||||
from garak import _config, _plugins | ||||||
from garak.harnesses import Harness | ||||||
from garak.detectors import Detector | ||||||
|
||||||
class DetectorOnly(Harness): | ||||||
def __init__(self, config_root=_config): | ||||||
super().__init__(config_root) | ||||||
|
||||||
def _load_detector(self, detector_name: str) -> Detector: | ||||||
detector = _plugins.load_plugin( | ||||||
detector_name, break_on_fail=False | ||||||
) | ||||||
if detector: | ||||||
return detector | ||||||
else: | ||||||
print(f" detector load failed: {detector_name}, skipping >>") | ||||||
logging.error(f" detector load failed: {detector_name}, skipping >>") | ||||||
return False | ||||||
|
||||||
def run(self, attempts, detector_names, evaluator): | ||||||
detectors = [] | ||||||
for detector in sorted(detector_names): | ||||||
d = self._load_detector(detector) | ||||||
if d: | ||||||
detectors.append(d) | ||||||
|
||||||
if len(detectors) == 0: | ||||||
msg = "No detectors, nothing to do" | ||||||
logging.warning(msg) | ||||||
if hasattr(_config.system, "verbose") and _config.system.verbose >= 2: | ||||||
print(msg) | ||||||
raise ValueError(msg) | ||||||
|
||||||
super().run_detectors(detectors, attempts, evaluator) # The probe is None, but hopefully no errors occur with probe. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can this work?
Suggested change
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,7 @@ run: | |
eval_threshold: 0.5 | ||
generations: 10 | ||
probe_tags: | ||
probed_report_path: | ||
|
||
plugins: | ||
model_type: | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-- out of scope for here, but we should implement serialization/deserialization for
Attempt
s