news 2026/9/16 17:35:51

garak run.spec 选择解析机制全解:从 `_selection.py` 看探针与 Buff 的统一选择流水线

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
garak run.spec 选择解析机制全解:从 `_selection.py` 看探针与 Buff 的统一选择流水线

garak run.spec 选择解析机制全解:从_selection.py看探针与 Buff 的统一选择流水线

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

garak(LLM vulnerability scanner)通过统一的run.spec选择语法来决定一次扫描运行哪些探针(probes)与 buff,而garak/_selection.py正是把run.spec语法树解析为具体插件名的核心模块。本文以 docs/source/_selection.rst 为骨架,结合 garak/_selection.py、garak/_spec.py 源码与 CLI 集成,系统讲解run.spec从命令行/配置文件语法、逐层解析、tier/tag/intent 过滤,到空选择诊断与旧配置迁移的完整链路。读完你将掌握--spec的全部选择器语义、排除优先规则,以及 garak 内部如何区分"未知选择器"与"已知但未激活的插件"。

一、语法与解析:两个模块各司其职

garak 把"选择语言"拆成两个层次,这一分工在 docs/source/_selection.rst 开篇即已点明:

  • 语法层garak/_spec.py):负责run.spec解析与序列化,把 CLI 字符串或配置文件中的include/exclude列表统一转换为内部Spec对象;
  • 解析层garak/_selection.py):把Spec中的选择器(Selector)对照插件注册表(garak/_plugins.py 中的 active/tier/tag 状态)解析为具体的插件名,如probes.dan.DanInTheWild

resolve_spec是 CLI 与 harness 使用的唯一入口;而同一套插件路径解析核心_resolve_plugin_paths也被探测器的parse_plugin_spec适配器复用(见 garak/_config.py),只是探测器仍保留旧的无前缀 spec 字符串形式,尚未并入run.spec(源码注释明确说明_CATEGORIES = ("probes", "buffs"),见 garak/_spec.py)。

两种输入形态最终都落到同一个内部数据结构:

输入形态解析函数说明
CLI 字符串(逗号分隔)parse_spec_string(garak/_spec.py)-前缀表示排除,+或无前缀表示包含
配置文件(YAML/JSON 的include/exclude列表)parse_spec_file(garak/_spec.py)列表项为插件路径字符串,或单键映射如{"tag": "owasp:llm01"}

Spec内部是一个include列表加一个exclude列表,每个元素都是SelectorSelectorkind取值有五种:plugin_pathnonetagtierintent;其中plugin_pathnone携带类别前缀(如probes.danprobes.none)并设置category,而tag/tier/intent属于非插件轴,categoryNone(见 garak/_spec.py)。

语法约束要点

parse_spec_string的源码可以看到两条硬性约束:

  1. 选择器之间不允许空白run.spec必须是单个逗号分隔的 token,选择器间出现空白会直接抛ValueError(garak/_spec.py)。这样设计的好处是:纯逗号列表在 shell 中无需引号;但*通配符仍是 shell 通配符,含*的 spec 必须加引号,或改用all别名。
  2. intent:每个代码一个选择器intent:S004,S005这种逗号分隔写法是语法错误,必须写成intent:S004, intent:S005(garak/_spec.py)。

_classify(garak/_spec.py)负责把裸 token 归类:识别tag:tier:intent:前缀;把裸none规范化为probes.none;把all/*规范化为probes.*;校验类别前缀必须是probesbuffs,否则报错。

二、解析流水线:resolve_spec的四层处理

resolve_spec(spec, skip_unknown=False)(garak/_selection.py)把整个选择过程组织为四个层次,顺序与优先级是理解run.spec的关键。

第 1 层:探针候选集(plugin-path include)

  • 如果include中存在类别为probesplugin_path选择器,则调用_resolve_plugin_paths得到候选集;
  • 否则,如果存在显式的probes.nonenone选择器),候选集为空——这是故意为之的空选择,不是错误;
  • 否则(未指定任何探针路径),默认取所有激活探针(等价于probes.*)。

关键代码(garak/_selection.py):

probe_includes = [ s for s in spec.include if s.kind == "plugin_path" and s.category == "probes" ] probe_none = any(s.kind == "none" and s.category == "probes" for s in spec.include) if probe_includes: candidate, rej, inact = _resolve_plugin_paths(probe_includes, "probes") ... elif probe_none: candidate = set() else: candidate = { p for p, active in _plugins.enumerate_plugins(category="probes") if active is True }

第 2 层:正向过滤(tier + tag,AND 关系)

  • tier:选择器是**包含式(inclusive)**的"日志级别"语义:tier:N放行 tier 1..N。多个 tier 选择器取max作为天花板(garak/_selection.py);
  • tag:选择器按前缀过滤,多个 tag 前缀之间是AND关系(需同时命中至少一个匹配前缀,见_has_any_tag,garak/_selection.py)。
tier_ceilings = [int(s.value) for s in spec.include if s.kind == "tier"] if tier_ceilings: ceiling = max(tier_ceilings) candidate = {p for p in candidate if _tier_of(p) <= ceiling} tag_prefixes = [s.value for s in spec.include if s.kind == "tag"] if tag_prefixes: candidate = {p for p in candidate if _has_any_tag(p, tag_prefixes)}

这里有个重要细节:tiertag过滤作用于整个候选集,包括显式点名的类。文档给出的反例是:probes.foo.Bar,tier:1foo.Bar为 tier 3 时解析结果为空(docs/source/configurable.rst)。

第 3 层:Buff 收集

buffs的选择是buffs.*include 的并集,且没有隐式默认——不写buffs:选择器就不会运行任何 buff(garak/_selection.py)。

第 4 层:排除(exclude)最后应用,排除优先

遍历spec.exclude,按选择器类型逐一从候选集中移除(garak/_selection.py):

  • plugin_path:移除对应探针/ buff;
  • tier:N精确移除 tier 恰好等于 N 的探针(注意与tier:Ninclude 的包含式语义不对称);
  • tag::移除带该标签前缀的探针。

tier:3include 放行 1..3,再-tier:2精确移除 tier 2,最终得到 tiers {1,3}——这正是官方文档示例garak --spec "+probes.*,+tier:3,-tier:2"的含义。

三、插件路径解析核心:_resolve_plugin_paths

_resolve_plugin_paths(selectors, category)(garak/_selection.py)是类别无关的单一解析核心,镜像了旧parse_plugin_spec的三档粒度:

选择器形态语义
<category>.*全部激活的插件
<category>.<module>该模块下所有激活的插件(family)
<category>.<module>.<Class>精确匹配单个类,忽略 active 状态

返回三元组(names, unknown, inactive),其中三者的区分是 garak 诊断质量的关键:

  • names:解析出的插件名集合;
  • unknown(代码中rejected):指向根本不存在的选择器
  • inactive:模块存在但其下所有插件都标记为 inactive 的裸模块选择器("known-but-empty",区别于 unknown,对应 issue #830 的语义)。

精确点名(第三档)会忽略active状态,这意味着你可以用probes.fitd.FITD这种形式强制运行一个默认未激活的探针——官方示例garak --spec probes.all,probes.fitd.FITD正是"全部激活探针 + 一个特定未激活类"的组合。

四、tier 语义:重要性分级与包含式过滤

tier 由 garak/probes/_tier.py 的Tier枚举定义:

Tier名称含义
1OF_CONCERN需要关注:低通过率或低 z-score 可能存在问题,应上报安全/对齐团队并考虑写入模型卡
2COMPETE_WITH_SOTA与 SOTA 竞争:低 z-score 可能存在问题,建议检查结果
3INFORMATIONAL信息性:结果与具体使用场景相关,若你已知探针契合某场景可视为 Tier 2
9UNLISTED未列入:重复、废弃、波动或非对抗性探针

源码中未声明 tier 的探针默认取_DEFAULT_TIER = 9(garak/_selection.py)。_normalize_tier(garak/_spec.py)支持整数与枚举名两种写法:tier:of_concern等价于tier:1,非法值会给出"use an int (1..3, 9) or a Tier name"的明确报错。

五、intent 轴:独立于插件选择的选择维度

intent:run.spec独立的一条轴,它不增删任何探针,而是为意图型探针(IntentProbe子类)与 IntentService 收集 typology 代码。处理逻辑在 garak/_selection.py:

  • intent:*/intent:all表示选择所有意图(由 IntentService 展开空泛的哨兵值);
  • 其他代码必须匹配 typology 说明符格式,校验正则(garak/_spec.py)为:
re.fullmatch("CTMS?)?", intent_specifier)

即首字母C/T/M/S,后跟可选的 3 位数字和可选小写叶子后缀,如S(整条 Safety 分支)、S001(类别)、S001mis(叶子)。

  • 未指定intent:时,默认注入S(Safety 分支):DEFAULT_INTENT_SCOPE = "S"(garak/_spec.py),保证在run.spec覆盖后意图范围依然存活;
  • typology 成员的展开与无探测器过滤发生在 IntentService 阶段,受run.serve_detectorless_intentsrun.*意图修饰符控制;
  • 选择intent:但没有选中任何IntentProbe时会给出警告并继续运行;
  • 解析结果中的intentsblocked_intentsintents_explicit会被 CLI 存到_config.transient.*,供 IntentService 在 harness 加载时消费(garak/cli.py)。

注意intent代码的格式校验发生在解析时(错误计入rejected),而 typology 成员校验在 IntentService 加载时——两者时机不同。

六、空选择诊断:empty_reason的贴心报错

当 spec 解析不出任何探针时,garak 不会抛出晦涩的错误,而是尽力给出可操作的诊断(_empty_reason,garak/_selection.py):

  1. 若同时存在 tier 天花板与显式探针点名:报"probe 'X' is tier N but the spec restricts to tiers 1..M; widen the tier filter or drop the explicit probe"——直接点名冲突双方;
  2. 若存在 tag/tier 过滤:报"no active probe matches the given tier/tag filters; widen the filters";
  3. 否则:报"every included probe was removed by an exclusion; adjust includes/excludes"。

CLI 侧的兜底逻辑在 garak/cli.py:当未设置--skip_unknown且解析结果为空时,打印❌ No probes selected: <empty_reason>并抛出ValueError中止运行。唯一的例外是显式none选择——那是故意的空运行(no-op),不算错误。另外,若选择器全部指向 inactive 模块,报错信息会建议按名字点名,例如all plugins in 'probes.xxx' are marked inactive; select one or more by name (e.g. probes.xxx.<ClassName>) to continue

resolve_spec返回的Resolution数据类(garak/_spec.py)包含selected(按类别映射到规范名category.module.Class)、rejected(未知选择器)、inactive(已知但全 inactive 的模块)、empty_reasonintents/blocked_intents/intents_explicit,并提供了probesbuffs便捷属性。若rejected非空且未设置skip_unknown,会直接抛ValueError(f"unknown run.spec selectors: {rejected}")

七、CLI 集成与旧配置迁移

--spec命令行入口

统一选择参数是--spec(短选项-S),定义在 garak/cli.py,默认值取自_config.run.spec。CLI 解析流程(garak/cli.py)为:--spec优先,若有--spec则通过parse_spec_string解析并写入_config.run.spec;旧的--probes/--probe_tags/--buffs参数会被映射到run.spec并打出弃用提示,两者同时给出时--spec获胜。

后续在_check_selection附近(garak/cli.py),CLI 调用resolve_spec(parse_spec_file(_config.run.spec), skip_unknown=True),把解析出的rejected/inactive交给人性化检查函数,并把意图轴与激活探针列表存入 transient 状态。--list_probes还可以与--spec组合使用(如--list_probes --spec probes.dan)来预览某个 spec 会选中哪些探针(garak/cli.py)。

配置文件形态

在 YAML/JSON 配置中,run.spec使用include/exclude列表(示例见 docs/source/configurable.rst):

run: spec: include: - probes.dan - tag: owasp:llm01 exclude: - probes.dan.DanInTheWild

parse_spec_file要求列表项要么是插件路径字符串,要么是单键映射({"tag": ...}{"tier": 1}),多键映射会报错。

旧键迁移映射

legacy_selection_spec(garak/_spec.py)是plugins.probe_spec/plugins.buff_spec/run.probe_tags三个废弃键到run.spec字典的单一映射点,被配置加载 shim(_map_legacy_selection)、CLI 标志处理和run.specfixer 迁移共同复用。其语义:

  • 无意义值(缺失、空串、auto)视为未指定,返回None
  • none映射为显式空选择probes.none
  • all/*映射为probes.*
  • 旧键的值不允许带类别前缀(如probes.dan这种写法在 legacy 键下会报错,提示 legacy 键取无前缀值如encoding.CharCode)。

仓库中还提供了自动迁移工具garak/resources/fixer/20260612_run_spec.py,内部同样通过parse_spec_file+resolve_spec(..., skip_unknown=True)校验并重写旧配置。

探测器适配器

parse_plugin_spec(spec, category, probe_tag_filter="")(garak/_config.py)是探测器解析的薄适配层:它把 legacy 无前缀 spec 经_legacy_path_selectors转成Selector,再调用_resolve_plugin_paths复用同一核心,并额外支持probe_tag_filter标签过滤;返回(sorted names, unknown clauses),其中 unknown 保留裸形式以兼容旧行为。detectors 目前仍走这条 legacy 路径(对应plugins.detector_spec,即 CLI 的-d),尚未折叠进run.spec——从源码结构看,Resolution.selected被设计成可按类别扩展的字典,正是为未来把 detectors 纳入统一 spec 预留的空间。

八、实战示例汇总

以下示例均可在命令行直接运行(源自 docs/source/configurable.rst,并可与上文源码语义互相印证):

# 整个 dan 家族,去掉其中 DanInTheWild 类(exclude 优先) garak --spec probes.dan,-probes.dan.DanInTheWild # 家族 + tag 过滤 garak --spec probes.grandma,tag:owasp:llm06 # 全部激活探针 + 全部激活 buff,排除 paraphrase buff(* 需引号) garak --spec "probes.*,buffs.*,-buffs.paraphrase" # 全部激活探针 + 一个特定的未激活类(all 是免引号的 *) garak --spec probes.all,probes.fitd.FITD # tiers {1,3}:tier:3 放行 1..3,再 -tier:2 精确移除 tier 2 garak --spec "+probes.*,+tier:3,-tier:2" # 意图探针 + 单个意图类别(intent 是独立轴) garak --spec probes.grandma.GrandmaIntent,intent:S004

配置文件形态(YAML):

run: spec: include: - probes.latentinjection exclude: []
{ "run": { "spec": { "include": ["probes.latentinjection"], "exclude": [] } } }

九、延伸阅读

  • 语法层详解:docs/source/_spec.rst(run.spec选择语法、Spec/Selector数据结构)
  • 用户视角配置指南:docs/source/configurable.rst(配置层级、run.spec全部选择器说明与示例)
  • 意图选择轴:docs/source/cas.rst(intent:的 typology 展开与 IntentService)
  • 核心实现:garak/_selection.py、garak/_spec.py
  • 插件注册表状态来源:garak/_plugins.py(enumerate_pluginsplugin_info
  • tier 枚举定义:garak/probes/_tier.py
  • CLI 集成点:garak/cli.py 与 garak/cli.py
  • 迁移工具:garak/resources/fixer/20260612_run_spec.py
  • 探测器适配器:garak/_config.py

理解run.spec的解析机制,意味着你不仅能精确控制"跑什么、不跑什么",还能在解析结果为空的场景下,从empty_reason的报错中一眼定位是 tier/tag 过滤过严、排除误伤,还是插件模块全部 inactive——这让 garak 的大规模批量扫描配置变得可预测、可调试。

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

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 17:35:51

BWAPI 4.4.0 环境配置与 ualbertabot 编译实战:星际 AI Bot 跑通指南

先说一个可能很多人都有过的经历&#xff1a;折腾半天把 BWAPI 官方例程编出来了&#xff0c;一加载进星际争霸就黑屏或者 Bot 完全不动&#xff0c;最后才发现根本不是代码问题&#xff0c;而是环境配错了。星际争霸的 AI 开发入门的门槛其实不低&#xff0c;BWAPI 的版本、游…

作者头像 李华
网站建设 2026/9/16 17:34:37

WeChatMsg 免费开源微信聊天记录导出工具:3步完成本地备份

WeChatMsg 免费开源微信聊天记录导出工具&#xff1a;3步完成本地备份 【免费下载链接】WeChatMsg 提取微信聊天记录&#xff0c;将其导出成HTML、Word、CSV文档永久保存&#xff0c;对聊天记录进行分析生成年度聊天报告 项目地址: https://gitcode.com/GitHub_Trending/we/W…

作者头像 李华
网站建设 2026/9/16 17:34:35

Pinocchio零知识证明库实战:可验证计算工程落地指南

1. 项目概述&#xff1a;这不是童话&#xff0c;是密码学工程现场“Show HN: Pinocchio: Harness for Verifiable Work”——这个标题一出现&#xff0c;我就立刻停下手头三个正在跑的零知识证明&#xff08;ZKP&#xff09;验证任务&#xff0c;把终端窗口最小化&#xff0c;点…

作者头像 李华
网站建设 2026/9/16 17:34:09

GroundingDINO 配置选型指南:SwinT 与 SwinB 选型对比

GroundingDINO 配置选型指南&#xff1a;SwinT 与 SwinB 选型对比 【免费下载链接】GroundingDINO [ECCV 2024] Official implementation of the paper "Grounding DINO: Marrying DINO with Grounded Pre-Training for Open-Set Object Detection" 项目地址: http…

作者头像 李华
网站建设 2026/9/16 17:34:02

10MB 的 Postman 替代品 Bruno:本地优先的开源 API 客户端实战

这些年我前后换过三四个接口调试工具&#xff0c;说实话&#xff0c;最开始看到有人聊一个 10MB 的 Postman 替代品时&#xff0c;我是不太信的。毕竟 Postman 的安装包动辄几百 MB&#xff0c;运行起来还要吃掉大量内存&#xff0c;一个只有它零头大小的工具能干什么&#xff…

作者头像 李华
网站建设 2026/9/16 17:32:24

Arthas在霸王餐高并发接口性能优化实战

1. 项目概述&#xff1a;霸王餐接口的性能挑战与Arthas的价值霸王餐业务接口作为高并发场景下的典型代表&#xff0c;对系统稳定性和响应速度有着严苛要求。去年我们团队接手的一个餐饮平台项目中&#xff0c;就曾遇到过一个查询接口在晚高峰时段出现响应时间从50ms飙升到2秒的…

作者头像 李华