Guide · 使用教程
garak 入门教程:限定测试范围、控制调用预算与解读扫描报告
固定 0.17.1,用内置无模型目标验证八条输入的通过与失败分支,解析 JSONL 报告,并明确真实扫描的范围、预算和证据边界。
这篇教程会完成什么
你会运行一个限定范围的 garak 示例,得到 JSONL 与 HTML 报告,并验证报告的通过与失败分支。这里使用内置 test.Blank 目标,不运行真实语言模型,不需要 API 密钥,也不对第三方接口发起扫描。
这是一篇流程入门:学会固定版本、明确探针、记录生成数量、检查报告和定义下一阶段预算。示例的 PASS 与 FAIL 都是人为设定的对照,不能证明某个模型安全或不安全。只有接入明确授权的目标、合适探针与人工复核后,才进入真实风险评估。
本次核对日期为 2026-10-22,测试使用 Python 3.11 和 garak 0.17.1。代码、CLI 和报告字段均以这个固定版本为准,不把 GitHub 开发分支命令直接混进来。
开始前需要准备
先创建独立环境,标准安装方式如下。完整包包含多种目标和探针的依赖,安装时间、磁盘及平台兼容性需确认。本文本地验证只安装固定官方 wheel 与内置测试路径所需依赖,没有完整安装或验证全部模型供应商,不能据此宣称所有插件可用。
python3.11 -m venv .venv
source .venv/bin/activate
python -m pip install 'garak==0.17.1'
python -m garak --version
mkdir -p reports
若完整安装遇到大型推理依赖不兼容,先按官方环境说明处理。不要把 --no-deps 当成真实目标扫描的通用修复,它会留下未安装依赖。对本次测试路径,能加载内置生成器不代表能加载其他模型或探针。
报告可能包含完整输入、响应和配置,先用合成数据验证存储目录和读写权限。真实测试前明确目标、授权、数据范围、测试窗口、预算与停止条件;若 Agent 连接写入、发信等操作,先用隔离实现避免真实副作用。
第一步:固定目标和探针
本例使用 --target_type test.Blank,它始终返回空文本;--spec probes.test.Test 选择稳定版中的八条基础测试输入。--generations 1 只指定每条输入的一次生成。本例因为没有真实模型,生成动作不产生模型 API 费用。
这条测试探针默认对应 always.Pass。所以即使目标完全不回答,也会得到通过结果。下面显式写出检测器,避免把默认行为误读成有意义的安全判断。
python -m garak \
--target_type test.Blank \
--spec probes.test.Test \
--generations 1 \
--detectors always.Pass \
--report_prefix "$PWD/reports/smoke-pass"
预期是八条输入经测试目标处理,并由始终通过的检测器判为通过。日志里可能还显示其他服务初始化或意图信息,不代表这些类别都已经运行;检查实际选中的探针、报告里的 eval 及样本数量。
第二步:运行必然失败的对照
保持目标、探针和生成数量不变,只将检测器换成 always.Fail,并使用新的报告前缀。这样可以检查失败结果是否会被记录,而不必连接任何真实模型。
python -m garak \
--target_type test.Blank \
--spec probes.test.Test \
--generations 1 \
--detectors always.Fail \
--report_prefix "$PWD/reports/smoke-fail"
本次实际运行中,通过对照为 8/8 通过,失败对照为 0/8 通过。两条 CLI 命令都完成并返回进程退出码 0。这说明“进程正常退出”和“检测结果通过”是两件事,CI 不能只检查进程退出码。
验证时还在 Python 进程中阻断了 socket 外连,确认这条内置测试路径无需外部连接。它不证明全部插件离线;安装、权重准备与真实目标调用属于另外的数据路径。
第三步:读取报告,而不只读终端颜色
每个前缀会对应本例的 .report.jsonl 和 .report.html 文件。JSONL 里同一次 attempt 可以在不同状态出现多次,不能把所有行都当成独立问题。要结合运行完成记录、最终 eval、实际数量以及 attempt 标识理解。
将下面代码保存为 check_report.py。它是专门针对本例 0.17.1、八条测试输入和两个对照检测器的校验器,不是任意扫描报告的通用安全判定程序。它只打印摘要,不回显原始提示、响应或配置。
"""Validate this fixed garak 0.17.1 test-only report, not arbitrary scan safety."""
import json
from pathlib import Path
import sys
def summarize(path, detector):
rows=[json.loads(line) for line in Path(path).read_text().splitlines() if line.strip()]
init=[r for r in rows if r.get('entry_type')=='init']
if len(init)!=1 or init[0].get('garak_version')!='0.17.1':
raise ValueError('expected garak 0.17.1 report')
if not any(r.get('entry_type')=='completion' for r in rows):
raise ValueError('run did not complete')
evaluations=[r for r in rows if r.get('entry_type')=='eval']
if len(evaluations)!=1:
raise ValueError('expected exactly one evaluation')
result=evaluations[0]
if result.get('probe')!='test.Test' or result.get('detector')!=detector:
raise ValueError('unexpected probe or detector')
if result.get('total_evaluated')!=8 or result.get('total_processed')!=8 or result.get('nones')!=0:
raise ValueError('missing or unscored outputs')
expected_passed=8 if detector=='always.Pass' else 0
if result.get('passed')!=expected_passed or result.get('fails')!=8-expected_passed:
raise ValueError('unexpected negative-control outcome')
attempt_ids={r['uuid'] for r in rows if r.get('entry_type')=='attempt'}
if len(attempt_ids)!=8:
raise ValueError('unexpected attempt count')
# Do not print raw prompts, responses or setup configuration.
return dict(version='0.17.1',probe='test.Test',detector=detector,
unique_attempts=8,passed=result['passed'],fails=result['fails'],
none_results=0,completed=True,real_model_test=False,
security_guarantee=False)
if __name__=='__main__':
if len(sys.argv)!=3 or sys.argv[2] not in ('always.Pass','always.Fail'):
raise SystemExit('usage: python check_report.py report.jsonl always.Pass|always.Fail')
print(json.dumps(summarize(sys.argv[1],sys.argv[2]),ensure_ascii=False,indent=2))
运行摘要检查:
python check_report.py reports/smoke-pass.report.jsonl always.Pass
python check_report.py reports/smoke-fail.report.jsonl always.Fail
本次检查还构造了五个坏报告:缺少运行完成记录、缺少 eval、版本不符、检测器不符、出现未评分结果。校验器均拒绝它们。两条真实 CLI 对照加五项坏报告检查,共七项验证通过;这些检查仅证明示例的流程与报告约束。
本例里的 real_model_test 和 security_guarantee 明确为 false。将来为真实目标建立 CI 时,应根据业务定义严重问题、错误、超时和不适用项的处理,不能直接复用本例“八条结果符合预设”的判定。
第四步:规划真实扫描的预算
接入真实目标前,先列出要测的风险、探针和检测器,了解每个组件是否会额外调用模型或下载资源。不要因为示例没有费用,就把真实目标替换进命令后直接运行。
可以从“输入数量 × 每条生成次数 × 平均输入输出 token”估算基础量,再加重试、动态测试、判定模型及其他调用。--generations 不是全部请求数或美元的硬上限;供应商预算、请求超时、并发与停止机制应独立设置,并在小范围执行后核对实际用量。
被测目标也要写清:是基础模型、带检索的聊天接口,还是包含工具调用的完整 Agent。若只连接基础模型,报告不代表数据库权限、跨用户隔离和业务审批已经验证。先限定目标再解释结果,避免给出超出证据的结论。
第五步:把发现的问题变成回归案例
真实检测命中后,保存目标版本、输入、响应、检测器与人工结论。确认它是可复现的业务风险,还是规则误报、接口异常或不适用样本。修复后重跑同一案例,并保留正常请求作为对照,观察是否过度拒绝。
中文场景需要检查输入的自然程度、业务相关性、实体格式与检测器覆盖。英文关键词没有出现,不代表中文结果没有风险;空输出或接口错误也不应自动记为安全通过。把未执行、失败、超时和跳过单列,保留清楚的分母。
报告和复现包可能包含敏感响应。对外分享前检查数据、配置和权限,不直接公开原始 JSONL。涉及真实客户记录的修复,应通过正常代码审查与业务审批,不让扫描结果自动触发退款、删除或外发消息。
常见错误
test.Test 显示全通过,是不是安全了
不是。默认的 always.Pass 本来就始终返回通过信号;本例还使用始终空输出的测试生成器。它只能检查流程。真实测试需要适用的探针、目标、检测器和人工复核,仍不能保证没有遗漏。
FAIL 但进程退出码为 0
在本例实测中确实如此。进程完成与检测结果不同。CI 应解析所需报告字段,同时确认运行完整、样本数量和未评分结果;不能只看 shell 是否成功。
报告行数比八条多很多
JSONL 还包含配置、插件信息、不同 attempt 状态、评估、汇总和完成记录。用稳定标识区分重复状态,并按固定版本的最终汇总理解结果。不能简单用总行数计算测试通过率。
能跑测试目标,换供应商后就报错
不同连接器依赖不同。本文的轻量开发验证环境不是完整供应商安装;真实目标需要按官方说明补齐依赖、配置和账户。也要确认旧教程里的参数名称是否适用于固定版本。
本地运行是否意味着所有数据留在本机
不能这样推断。实际目标、动态探针、评分与日志组件可能联网。逐项确认数据流,密钥放在受控环境中;原本要求本地处理的数据,不因测试失败就静默切换到外部服务。
什么时候需要其他工具
需要观察多步 Agent 与工具调用过程,可结合 Inspect AI 的任务评估;需要识别和处理敏感信息,可结合 Presidio。整体选择见 LLM 安全测试工具推荐。
中国大陆网络、模型下载、供应商账户和执行平台兼容性,均需在目标环境检查。本次没有扫描第三方系统,没有调用付费模型,没有测试真实模型的漏洞覆盖,也没有提供安全认证或生产服务等级保证。
官方资料
以上 GitHub 链接可能随开发分支变化;本例行为以实际安装的 0.17.1 wheel 为准。
常见问题
- 示例调用真实模型或收费 API 吗?
- 没有。使用内置 test.Blank 与测试探针,最终对照运行阻断 Python socket 外连。
- 全通过为什么不能证明安全?
- 本例 always.Pass 始终返回通过,目标还只输出空文本,它只是流程对照。
- FAIL 的命令一定返回非零退出码吗?
- 本次固定版本实测失败对照仍返回进程退出码 0,CI 还需读取报告中的真实判定。
- 报告行数就是测试数量吗?
- 不是。存在配置、插件信息、不同 attempt 状态与汇总,应结合稳定标识及 eval 数量判断。
- 七项验证包含哪些内容?
- 两条真实内置目标 CLI 对照,加缺少完成、缺少 eval、版本不符、检测器不符及未评分五种坏报告拒绝检查。
- 本地验证环境能支持全部供应商吗?
- 不能。此次只安装固定 wheel 与测试路径依赖,完整供应商和真实模型环境需另行安装、配置与验证。