garak 恒定行为检测器模块解析:Fail / Pass / Passthru / Random 的原理与实战用法
2026/9/16 19:49:39 网站建设 项目流程

garak 恒定行为检测器模块解析:Fail / Pass / Passthru / Random 的原理与实战用法

【免费下载链接】garakthe LLM vulnerability scanner项目地址: https://gitcode.com/GitHub_Trending/ga/garak

导读

garak.detectors.always是 garak(the LLM vulnerability scanner)中一组特殊的检测器(detector)模块,它们不分析模型输出内容,而是恒定返回预设的检测结果,主要用于框架自测、插件原型开发与调试链路。本文基于 docs/source/detectors/always.rst 对应的 garak/detectors/always.py 源码,逐一拆解FailPassPassthruRandom四个检测器的实现、返回值语义、默认参数与调用约定,并结合仓库内的测试用例(tests/detectors/test_detectors_always.py)与命令行用法,说明如何在开发自有插件时用它们做链路自检。

一、模块定位:为什么需要"恒定结果"的检测器

garak 的检测器(detector)负责判断一次 attempt(探针请求 + 模型输出)是否命中漏洞,其基类定义在 garak/detectors/base.py 中,核心契约是:

def detect(self, attempt: garak.attempt.Attempt) -> Iterable[float | None]: """...returns a list of results in the range 0.0-1.0 where 0.0 represents no hit, 1.0 represents successful hit (i.e. vulnerability found)."""

返回值语义为:0.0 表示未命中(通过),1.0 表示命中(发现漏洞),None 表示无法评分。绝大多数真实检测器(如基于字符串匹配的StringDetector、基于 HF 模型的HFDetector)都要在这一契约下实现复杂的判定逻辑。

always模块(garak/detectors/always.py 第 1-4 行的模块文档字符串)明确声明了它的设计意图:

These always return a certain result, used mostly in testing or to indicate where a class' child should override something.

即:恒等返回某种结果,主要用于测试,或用于标记"子类应当覆盖此处"的位置。它解决的问题是:当你要调试探针、生成器或 garak 主流程本身时,需要一个"结果完全可预期"的检测器作为锚点,把变量隔离出来。这与 garak 的测试哲学一致——测试探针 garak/probes/test.py 中的Test探针(goal = "send some test strings, ignore the response")同样是为了测试基础设施而存在。

从插件注册表 garak/resources/plugin_cache.json(第 6125-6200 行)可以看到,四个检测器均以detectors.always.*命名注册,active均为true,即默认可用。

二、四个检测器的源码逐项解析

2.1 Fail:恒返回 1.0,标记"必定失败"

class Fail(Detector): """Always Fail (for testing & prototyping)""" lang_spec = "*" def detect(self, attempt: Attempt) -> List[float | None]: return [1.0] * len(attempt.outputs)
  • 行为:对 attempt 的每一个输出都返回1.0,即无论模型说什么,都判定"命中漏洞"。
  • 关键属性lang_spec = "*"表示适用于所有语言(BCP47 标签列表,*为通配),这是 garak/detectors/base.py 中定义的类属性。
  • 典型用途
    1. 作为探针的默认推荐检测器。在 garak/probes/base.py 第 50-51 行,探针基类声明了recommended_detector: Iterable[str] = ["always.Fail"],注释明确写道:"always.Fail is chosen as default to send a signal if this isn't overridden"——即如果一个新探针忘记指定自己的检测器,就会默认用always.Fail打出一个显眼的"必定失败"信号,提醒开发者补全配置。
    2. 构造"全失败"基线场景,验证报告聚合、CI 计算等下游逻辑。

2.2 Pass:恒返回 0.0,标记"必定通过"

class Pass(Detector): """Always Pass (for testing & prototyping)""" lang_spec = "*" def detect(self, attempt: Attempt) -> List[float | None]: return [0.0] * len(attempt.outputs)
  • 行为:对每个输出返回0.0,即恒判"未发现漏洞"。
  • 典型用途
    1. 作为测试探针Testprimary_detector(见 garak/probes/test.py 第 39 行),用于跑通"探针→生成器→检测器"全链路而不产生噪音结果。
    2. README 中给出的插件开发自检命令就大量使用always.Pass(详见下文第五节)。
    3. 构造"全通过"基线:在 tests/_assets/analyze/test.report.jsonl 等测试资产中,可以观察到"detector_results": {"always.Pass": [0.0]}的记录形态,以及对应的eval记录("passed": 8, "total_evaluated": 8, "fails": 0),说明这类检测器输出会被报告系统完整消费。

2.3 Passthru:透传已有检测结果,用于"二次处理"

class Passthru(Detector): """Send existing attempt detection results; use first detector""" lang_spec = "*" DEFAULT_PARAMS = Detector.DEFAULT_PARAMS | { "skip": True, } def detect(self, attempt: Attempt) -> List[float | None]: assert ( len(attempt.detector_results) > 0 ), "Only use passthru on attempts that already have detector scores" detector_chosen = sorted(list(attempt.detector_results.keys()))[0] return attempt.detector_results[detector_chosen]
  • 行为:不再自行判定,而是从attempt.detector_results(一个以检测器名为键、分数列表为值的字典)中取出字典序排在最前的一个检测器的已有分数原样返回。
  • 前置条件assert len(attempt.detector_results) > 0,即 attempt 必须已经带有一组检测器分数才能使用,否则直接触发断言失败。这一点在实现层面保证了"只用于已有评分的 attempt"。
  • 参数差异:它覆盖了DEFAULT_PARAMS,新增"skip": Trueskip是 garak/detectors/base.py 第 42-44 行定义的基类默认参数(默认False),用于控制插件枚举/加载时是否跳过;Passthru 默认跳过,可以推断是为了避免它在常规扫描中被动加载(因为它依赖"上游已产生检测结果"这一前置状态)。
  • 典型用途:在 garak/probes/base.py 第 678 行可以看到self.primary_detector = "always.Passthru"的使用——当一个探针期望复用 attempt 上已存在的检测结果(例如经过其他检测器打分后再做转发或转换)时,用它作为主检测器。

2.4 Random:恒返回随机分数,用于随机性测试

class Random(Detector): """Always return a random result (for testing & prototyping)""" lang_spec = "*" def detect(self, attempt: Attempt) -> List[float | None]: return [random.random() for _ in range(len(attempt.outputs))]
  • 行为:对每个输出调用random.random()生成[0.0, 1.0)区间内的随机浮点数,每个输出独立取值。
  • 典型用途:构造"结果不可复现/噪声分布"的场景,例如测试报告统计、置信区间(CI)计算等对分数分布敏感的下游逻辑是否能正确处理随机输入;也可用于给原型检测器提供一个占位输出,验证管道不会因数据形态而崩溃。

三、共同约定:与 Detector 基类的对齐

四个检测器均直接继承自 garak/detectors/base.py 的Detector基类,遵守以下约定:

  • lang_spec = "*":声明适用于全部语言,因此detect无需按语言过滤输出。
  • hit_desc/pass_desc:未覆盖基类默认值,即命中时描述为"Detector identified matching indicators of risk",通过时描述为"Detector found no issue",与插件缓存 garak/resources/plugin_cache.json 中记录的元数据一致。
  • modality:沿用基类{"out": {"text"}},即只处理文本输出(garak 检测器聚焦 LLM 文本输出,见 base.py 第 40 行的注释)。
  • 返回长度契约:返回值列表长度必须等于len(attempt.outputs),四个实现均严格遵循,保证与 attempt 的每个输出一一对应。
  • detect参数:统一接收Attempt对象。Attempt 封装了一次提示词及其元数据与结果(见 garak/attempt.py),其中outputs是模型输出列表,detector_results是已有检测结果字典。

四、测试用例验证:行为可预期

仓库在 tests/detectors/test_detectors_always.py 中对这些恒定行为做了直接断言:

def test_fail(): d = garak._plugins.load_plugin("detectors.always.Fail") a = Attempt(prompt=Message()) a.outputs = [""] assert d.detect(a) == [1.0] def test_pass(): d = garak._plugins.load_plugin("detectors.always.Pass") a = Attempt(prompt=Message()) a.outputs = [""] assert d.detect(a) == [0.0] def test_passthru(): d = garak._plugins.load_plugin("detectors.always.Passthru") a = Attempt(prompt=Message()) a.outputs = [""] a.detector_results = {"always.Fail": [0.5]} assert d.detect(a) == [0.5]

要点:

  • 测试通过garak._plugins.load_plugin("detectors.always.*")按完整插件名加载,验证了插件注册与动态加载路径(这也是为什么命令行中要用always.Failalways.Pass这种带模块前缀的名字)。
  • test_passthru验证了"取字典序第一个检测器"的规则:detector_results中只有"always.Fail"一个键,故返回其分数[0.5]
  • 同文件的test_loadgarak._plugins.enumerate_plugins("detectors")枚举所有含.always.的插件并逐一加载,断言它们都是garak.detectors.base.Detector的实例,保证了四个类都能被框架正常实例化。

五、实战用法:用恒定检测器自检插件链路

根据 README.md 第 287-300 行给出的插件开发建议,always.*检测器是"分而治之"调试法中的关键组件:

  • 调试新探针(probe):用空白生成器 +always.Pass检测器隔离探针问题:
    python3 -m garak -m test.Blank -p mymodule -d always.Pass
  • 调试新检测器(detector):用空白生成器 + 空白探针,让新检测器直接作用于空输出:
    python3 -m garak -m test.Blank -p test.Blank -d mymodule
  • 调试新生成器(generator):用空白探针 +always.Pass检测器隔离生成器问题:
    python3 -m garak -m mymodule -p test.Blank -d always.Pass

上述命令中:test.Blanktest.Test是 garak/probes/test.py 中定义的测试探针(active = False,仅用于测试);-m指定生成器,-p指定探针,-d指定检测器。docs/source/extending.rst第 54-56 行也给出了等价的-t用法说明。

其背后的调试逻辑是:用"结果可预期"的组件替换掉被测变量两侧。例如当你怀疑自己的探针生成不了有效 prompt 时,always.Pass保证检测环节永远返回 0.0,那么任何异常都必然来自探针或生成器;反过来,检测器开发时用test.Blank探针,则 prompt 固定为空白字符串,便于观察检测器对空输入的处理。

命令行指定检测器时的命名规则

  • 命令行中必须使用带模块前缀的完整插件名,即always.Failalways.Passalways.Passthrualways.Random,而不是裸类名。
  • 这四个检测器active均为true,可直接在-d参数中使用;若使用--list_detectors查看插件清单,也会看到它们以detectors.always.*形式列出(插件缓存 garak/resources/plugin_cache.json 中即为该注册名)。

六、在报告数据中的形态

由于always.*检测器常被用于测试运行,可以在仓库的测试资产中看到其输出被完整记录与消费的样例,例如:

  • tests/_assets/analyze/test.report.jsonl:每个 attempt 记录形如"detector_results": {"always.Pass": [0.0]},对应的 eval 记录为{"probe": "test.Test", "detector": "always.Pass", "passed": 8, "total_evaluated": 8, "fails": 0, ...}
  • tests/_assets/analyze/qual_review_mixed.report.jsonl:展示always.Fail产生"detector_results": {"always.Fail": [1.0]}"fails": 1的记录。

这些文件说明:恒定检测器的结果会像真实检测器一样进入 attempt 记录、eval 统计与报告流水线,不会因为"没有实际判定逻辑"而被特殊对待——这正是它们适合做端到端自检的原因:能验证从插件加载、扫描执行、结果落盘到报告聚合的整条链路

七、小结与使用建议

检测器返回值默认 skip主要用途
always.Fail1.0False探针默认推荐检测器(未覆盖时发信号)、构造全失败基线
always.Pass0.0False插件链路自检、测试探针主检测器、构造全通过基线
always.Passthru透传首个已有检测结果True复用 attempt 上已有的检测分数(需先有detector_results
always.Random[0,1)随机值False随机性/统计下游逻辑测试

在实际开发中,建议把always.*作为"可控变量"融入你的插件调试流程:先用always.Pass/always.Fail确认管道通畅,再替换为真实检测器;当你的探针或检测器需要覆盖recommended_detectorprimary_detector时,参考 garak/probes/base.py 与 garak/probes/test.py 的写法即可。

延伸阅读

  • 检测器基类与子类体系:garak/detectors/base.py
  • 恒定检测器实现:garak/detectors/always.py
  • 单元测试:tests/detectors/test_detectors_always.py
  • 插件注册元数据:garak/resources/plugin_cache.json
  • 测试探针定义:garak/probes/test.py
  • 插件开发调试命令:README.md 与 docs/source/extending.rst

【免费下载链接】garakthe LLM vulnerability scanner项目地址: https://gitcode.com/GitHub_Trending/ga/garak

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询