Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion garak/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@
filename=_log_filename,
level=logging.DEBUG,
format="%(asctime)s %(levelname)s %(message)s",
)
)
133 changes: 73 additions & 60 deletions garak/analyze/aggregate_reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
import datetime
import json
import uuid
import sys

import garak


def _process_file_body(in_file, out_file, aggregate_uuid):
Expand All @@ -37,80 +40,90 @@ def _process_file_body(in_file, out_file, aggregate_uuid):
out_file.write(json.dumps(entry, ensure_ascii=False) + "\n")


p = argparse.ArgumentParser(
description="aggregate multiple similar garak reports into one jsonl"
)
p.add_argument("-o", help="output filename", required=True)
p.add_argument("infiles", nargs="+", help="garak jsonl reports to be aggregated")
a = p.parse_args()
def main(argv=None) -> None:
if argv is None:
argv = sys.argv[1:]

garak._config.load_config()
print(
f"garak {garak.__description__} v{garak._config.version} ( https://github.com/NVIDIA/garak )"
)

p = argparse.ArgumentParser(
prog="python -m garak.analyze.aggregate_reports",
description="Aggregate multiple similar garak reports into one JSONL",
epilog="See https://github.com/NVIDIA/garak",
allow_abbrev=False,
)
p.add_argument("-o", "--output_path", help="Output filename", required=True)
p.add_argument("infiles", nargs="+", help="garak jsonl reports to be aggregated")
a = p.parse_args(argv)

# get the list of files
in_filenames = a.infiles

# get the list of files
in_filenames = a.infiles
# get the header from the first file
aggregate_uuid = str(uuid.uuid4())
aggregate_starttime_iso = datetime.datetime.now().isoformat()

# get the header from the first file
# start_run setup
# init
# attempt status 1
# attempt status 2
# eval
print("writing aggregated data to", a.output_path)
with open(a.output_path, "w", encoding="utf-8") as out_file:
print("lead file", in_filenames[0])
with open(in_filenames[0], "r", encoding="utf8") as lead_file:
# extract model type, model name, garak version

aggregate_uuid = str(uuid.uuid4())
aggregate_starttime_iso = datetime.datetime.now().isoformat()
setup_line = lead_file.readline()
setup = json.loads(setup_line)
assert setup["entry_type"] == "start_run setup"
model_type = setup["plugins.model_type"]
model_name = setup["plugins.model_name"]
version = setup["_config.version"]
setup["aggregation"] = in_filenames

print("writing aggregated data to", a.o)
with open(a.o, "w", encoding="utf-8") as out_file:
print("lead file", in_filenames[0])
with open(in_filenames[0], "r", encoding="utf8") as lead_file:
# extract model type, model name, garak version
# write the header, completed attempts, and eval rows

setup_line = lead_file.readline()
setup = json.loads(setup_line)
assert setup["entry_type"] == "start_run setup"
model_type = setup["plugins.model_type"]
model_name = setup["plugins.model_name"]
version = setup["_config.version"]
setup["aggregation"] = in_filenames
out_file.write(json.dumps(setup) + "\n")

# write the header, completed attempts, and eval rows
init_line = lead_file.readline()
init = json.loads(init_line)
assert init["entry_type"] == "init"
assert init["garak_version"] == version

out_file.write(json.dumps(setup) + "\n")
orig_uuid = init["run"]
init["orig_uuid"] = init["run"]
init["run"] = aggregate_uuid

init_line = lead_file.readline()
init = json.loads(init_line)
assert init["entry_type"] == "init"
assert init["garak_version"] == version
init["orig_start_time"] = init["start_time"]
init["start_time"] = aggregate_starttime_iso

orig_uuid = init["run"]
init["orig_uuid"] = init["run"]
init["run"] = aggregate_uuid
out_file.write(json.dumps(init) + "\n")

init["orig_start_time"] = init["start_time"]
init["start_time"] = aggregate_starttime_iso
_process_file_body(lead_file, out_file, aggregate_uuid)

out_file.write(json.dumps(init) + "\n")
if len(in_filenames) > 1:
# for each other file
for subsequent_filename in in_filenames[1:]:
print("processing", subsequent_filename)
with open(subsequent_filename, "r", encoding="utf8") as subsequent_file:
# check the header, quit if not good

_process_file_body(lead_file, out_file, aggregate_uuid)
setup_line = subsequent_file.readline()
setup = json.loads(setup_line)
assert setup["entry_type"] == "start_run setup"
assert model_type == setup["plugins.model_type"]
assert model_name == setup["plugins.model_name"]
assert version == setup["_config.version"]

if len(in_filenames) > 1:
# for each other file
for subsequent_filename in in_filenames[1:]:
print("processing", subsequent_filename)
with open(subsequent_filename, "r", encoding="utf8") as subsequent_file:
# check the header, quit if not good
init_line = subsequent_file.readline()
init = json.loads(init_line)
assert init["entry_type"] == "init"
assert init["garak_version"] == version

setup_line = subsequent_file.readline()
setup = json.loads(setup_line)
assert setup["entry_type"] == "start_run setup"
assert model_type == setup["plugins.model_type"]
assert model_name == setup["plugins.model_name"]
assert version == setup["_config.version"]
# write the completed attempts and eval rows
_process_file_body(subsequent_file, out_file, aggregate_uuid)

init_line = subsequent_file.readline()
init = json.loads(init_line)
assert init["entry_type"] == "init"
assert init["garak_version"] == version
print("done")

# write the completed attempts and eval rows
_process_file_body(subsequent_file, out_file, aggregate_uuid)

print("done")
if __name__ == "__main__":
main()
167 changes: 104 additions & 63 deletions garak/analyze/analyze_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,81 +12,122 @@
"""
import sys
import json
import argparse

started_attempt_uuids = set([])
completed_attempt_uuids = set([])
import garak

completed_attempts = {}

current_probe_classname = None
def analyze_log(report_path: str) -> None:
started_attempt_uuids = set([])
completed_attempt_uuids = set([])

with open(sys.argv[1], "r", encoding="utf-8") as reportfile:
for i, line in enumerate(reportfile):
line = line.strip()
if not line:
continue
current_probe_classname = None

record = json.loads(line)
with open(report_path, "r", encoding="utf-8") as reportfile:
for _, line in enumerate(reportfile):
line = line.strip()
if not line:
continue

if not isinstance(record, dict):
continue
record = json.loads(line)

if "entry_type" not in record.keys():
continue
if not isinstance(record, dict):
continue

if record["entry_type"] in ("config", "init"):
print("##", line)
continue
if "entry_type" not in record.keys():
continue

elif record["entry_type"] == "attempt":
if record["status"] == 1: # started
started_attempt_uuids.add(record["uuid"])
if record["entry_type"] in ("config", "init"):
print("##", line)
continue

if record["status"] == 2:
completed_attempt_uuids.add(record["uuid"])

if record["probe_classname"] != current_probe_classname:
print("## PROBE:", record["probe_classname"])
current_probe_classname = record["probe_classname"]

# for each detector:
for detector_name, detector_scores in record["detector_results"].items():
normalised_scores = [1 if i >= 0.5 else 0 for i in detector_scores]
# are there any hits?
if sum(normalised_scores):
# if so print prompt & hit rate
# i guess make this sortable so give probe, detector, hit rate, prompt
hit_rate = sum(normalised_scores) / len(normalised_scores)
print(
"\t".join(
elif record["entry_type"] == "attempt":
if record["status"] == 1: # started
started_attempt_uuids.add(record["uuid"])
continue

if record["status"] == 2:
completed_attempt_uuids.add(record["uuid"])

if record["probe_classname"] != current_probe_classname:
print("## PROBE:", record["probe_classname"])
current_probe_classname = record["probe_classname"]

# for each detector:
for detector_name, detector_scores in record[
"detector_results"
].items():
normalised_scores = [1 if i >= 0.5 else 0 for i in detector_scores]
# are there any hits?
if sum(normalised_scores):
# if so print prompt & hit rate
# i guess make this sortable so give probe, detector, hit rate, prompt
hit_rate = sum(normalised_scores) / len(normalised_scores)
print(
"\t".join(
[
current_probe_classname,
detector_name,
f"{hit_rate:0.2%}",
repr(record["prompt"]),
]
)
)
elif record["entry_type"] == "eval":
print(
"\t".join(
map(
str,
[
current_probe_classname,
detector_name,
f"{hit_rate:0.2%}",
repr(record["prompt"]),
]
record["probe"],
record["detector"],
"%0.4f" % (record["passed"] / record["total"]),
record["total"],
],
)
)
elif record["entry_type"] == "eval":
print(
"\t".join(
map(
str,
[
record["probe"],
record["detector"],
"%0.4f" % (record["passed"] / record["total"]),
record["total"],
],
)
)
)

if not started_attempt_uuids:
print("## no attempts in log")
else:
completion_rate = len(completed_attempt_uuids) / len(started_attempt_uuids)
print("##", len(started_attempt_uuids), "attempts started")
print("##", len(completed_attempt_uuids), "attempts completed")
print(f"## attempt completion rate {completion_rate:.0%}")

if not started_attempt_uuids:
print("## no attempts in log")
else:
completion_rate = len(completed_attempt_uuids) / len(started_attempt_uuids)
print("##", len(started_attempt_uuids), "attempts started")
print("##", len(completed_attempt_uuids), "attempts completed")
print(f"## attempt completion rate {completion_rate:.0%}")


def main(argv=None) -> None:
if argv is None:
argv = sys.argv[1:]

garak._config.load_config()
print(
f"garak {garak.__description__} v{garak._config.version} ( https://github.com/NVIDIA/garak )"
)

parser = argparse.ArgumentParser(
prog="python -m garak.analyze.analyze_log",
description="Analyze a garak JSONL report and emit summary lines",
epilog="See https://github.com/NVIDIA/garak",
allow_abbrev=False,
)
# Support both positional and -r/--report_path for backward compatibility
parser.add_argument("report_path", nargs="?", help="Path to the garak JSONL report")
parser.add_argument(
"-r",
"--report_path",
dest="report_path_opt",
help="Path to the garak JSONL report",
)
args = parser.parse_args(argv)
report_path = args.report_path_opt or args.report_path
if not report_path:
parser.error("a report path is required (positional or -r/--report_path)")

sys.stdout.reconfigure(encoding="utf-8")
analyze_log(report_path)


if __name__ == "__main__":
main()
Loading
Loading