garak 测试生成器 generators.test 全解析:从 Blank 到 ReasoningLipsum 的测试基座
【免费下载链接】garakthe LLM vulnerability scanner项目地址: https://gitcode.com/GitHub_Trending/ga/garak
garak 是一套开源的 LLM 漏洞扫描框架,其generators.test模块为整条扫描流水线提供了一组不依赖任何真实模型、可离线运行、行为完全确定的测试生成器。本文基于 docs/source/generators/test.rst 对应的模块文档,结合 garak/generators/test.py 源码与 tests/generators/test_test.py 测试用例,系统讲解该模块中 7 个生成器的行为语义、可配置参数、默认值与典型使用场景。读完本文,你将掌握如何用test.*生成器快速验证 probe、detector 的逻辑,如何在无 GPU、无 API Key 的情况下完整跑通一次 garak 扫描,以及如何模拟“带思考痕迹(reasoning trace)”的长结构化输出。
一、为什么需要测试生成器:generators 在 garak 中的角色
garak 的扫描链路大致为:probe 构造攻击性 prompt → generator 调用目标 LLM 获取回复 → detector 判定回复是否“越狱”。其中 generator 是对“文本到文本服务”的抽象封装,所有生成器都必须继承 garak/generators/base.py 中的Generator基类。
真实生成器(如 OpenAI、Anthropic、HuggingFace)需要网络、API Key 和模型部署,这会成为开发与回归测试的瓶颈:写一个新的 probe 或 detector 时,你只关心自己的逻辑是否正确,而不是网络是否通畅。generators.test模块正是为此而生——它提供“简单、确定的系统级响应”,专门用于测试,不产生任何真实模型调用。
从模块的类结构看,所有测试生成器都只覆写了一个方法_call_model(prompt, generations_this_call),而把“迭代生成、结果校验、后处理、去前缀”等通用逻辑全部交给基类Generator.generate()完成(garak/generators/base.py)。这既是理解本文各生成器的前提,也是后续学习如何编写自定义生成器(docs/source/extending.generator.rst)的入口。
二、模块总览:7 个生成器与默认类
模块garak/generators/test.py共定义 7 个生成器,并在文件末尾通过DEFAULT_CLASS = "Lipsum"指定了默认类:
| 类名 | 类属性名name | 输出行为 | 支持多路生成 |
|---|---|---|---|
Blank | Blank | 恒定返回空字符串 | ✅ |
Repeat | Repeat | 原样返回输入的最后一轮消息 | ✅ |
Single | Single | 恒定返回固定字符串"ELIM" | ❌ |
Nones | Nones | 每次生成都返回None | ✅ |
Lipsum | Lorem Ipsum | 按 unit/count 生成随机的 Lorem Ipsum 文本 | ❌ |
ReasoningLipsum | Reasoning Lipsum | 生成“思考痕迹 + 输出”结构化文本 | ❌ |
BlankVision | BlankVision | 接受文本+图像输入,恒定返回空字符串 | ✅ |
DEFAULT_CLASS的含义是:当用户在命令行只指定到模块级(如-m test)而不指定具体类时,插件加载器会回落到该类。这一机制在 garak/_plugins.py 的加载逻辑中生效。因此-m test等价于-m test.Lipsum。
在 garak/resources/plugin_cache.json 中可以查到全部 7 个类的元数据(DEFAULT_PARAMS、modality、supports_multiple_generations等),这些元数据与源码定义完全一致,是 garak 插件自省(--list_config、-L)的信息来源。
三、六个基础测试生成器逐个详解
3.1 Blank:恒定空输出
class Blank(Generator): """This generator always returns the empty string.""" supports_multiple_generations = True def _call_model(self, prompt, generations_this_call=1): return [Message("")] * generations_this_callBlank对任何 prompt 都返回""。它是 garak 中最常用的测试生成器之一,常见用途包括:
- 单独测试 probe:
python3 -m garak -t test.Blank -p mymodule -d always.Pass,把生成器固定为空输出,从而隔离 probe 构造 prompt 的逻辑(docs/source/extending.rst)。 - 单独测试 detector:
python3 -m garak -t test.Blank -p test.Blank -d mymodule,让 probe 与生成器都“哑化”,只验证 detector 对空回复的判定。
supports_multiple_generations = True表示一次_call_model调用即可按请求批量返回多个结果。测试 tests/generators/test_test.py 验证了generations_this_call=5时会返回 5 个空Message。
3.2 Repeat:回显最后一条输入消息
class Repeat(Generator): """This generator returns the last message from input that was posed to it.""" supports_multiple_generations = True def _call_model(self, prompt, generations_this_call=1): return [prompt.last_message()] * generations_this_callRepeat通过prompt.last_message()取回对话(Conversation)中用户提交的最后一条消息并原样返回。测试用例 tests/generators/test_test.py 断言:输入"especially the lies",输出即为Message("especially the lies")。
这种“输入即输出”的行为非常适合测试基于反射/回显语义的 detector(例如检查回复是否复述了恶意指令),或验证 probe 对多轮对话的处理。
3.3 Single:固定字符串且拒绝多路生成
class Single(Generator): supports_multiple_generations = False test_generation_string = "ELIM" def _call_model(self, prompt, generations_this_call=1): if generations_this_call == 1: return [Message(self.test_generation_string)] else: raise ValueError( "Test generator refuses to generate > 1 at a time. Check generation logic" )Single恒定返回字符串"ELIM",并且supports_multiple_generations = False——当请求多路生成时会主动抛出ValueError而不是静默出错。
这个类的真正价值在于验证框架自身的多路生成逻辑。在基类 garak/generators/base.py 中,当生成器不支持多路生成且请求generations_this_call > 1时,框架会退回逐次调用_call_model(prompt, 1)并聚合结果;若配置了parallel_requests,还会走多进程并行分支。测试 tests/generators/test_test.py 覆盖了“随机 2~12 路生成均正确聚合”以及“直接调用_call_model(..., generations_this_call=2)抛出 ValueError”两种路径。
3.4 Nones:恒定无响应
class Nones(Generator): """This generator always returns a None for every generation.""" supports_multiple_generations = True def _call_model(self, prompt, generations_this_call=1): return [None] * generations_this_callNones每次生成都返回None。注意基类的_call_model签名允许返回Message | None(garak/generators/base.py),None语义上代表“模型未给出任何响应”。它用于测试无响应/超时/拒答场景下 detector 的行为——例如 tests/detectors/test_detectors.py 中要求detect()在输入为None时仍返回合法的浮点数或None列表。
3.5 Lipsum:可变的非零输出(模块默认类)
class Lipsum(Generator): """Lorem Ipsum generator, so we can get non-zero outputs that vary""" DEFAULT_PARAMS = Generator.DEFAULT_PARAMS | { "unit": "sentence", "count": 1, } supports_multiple_generations = False _unit_separators = {"sentence": " ", "paragraph": "\n", "text": "\n"}Lipsum是模块的默认生成器(DEFAULT_CLASS = "Lipsum"),作用是产生非空且每次不同的输出,用于测试那些需要“有内容回复”才能触发的逻辑,同时保证可重复性不受外部服务影响。它依赖第三方库lorem,按unit指定的粒度生成内容,再用count控制拼接数量。
参数说明(均有默认值,均可通过DEFAULT_PARAMS覆盖):
| 参数 | 类型 | 默认值 | 取值范围/说明 |
|---|---|---|---|
unit | str | "sentence" | "sentence"、"paragraph"、"text"三者之一;"text"为多段落文本块 |
count | int | 1 | 每个输出中拼接多少个unit单元 |
单元之间的连接符由_unit_separators决定:句子之间用空格,段落/文本块之间用换行。若传入非法unit,_call_model会抛出ValueError: Invalid unit '...'。
测试 tests/generators/test_test.py 验证了完整行为矩阵:
- 默认输出非空;
unit="sentence"时输出以句号结尾且不含换行;unit="paragraph"时包含多个句子;unit="text"时包含换行(多段落);count=5时句子数不少于 5;unit="chapter"会触发ValueError;- 连续 200 次调用输出各不相同(
len(results) > 1)。
注意Lipsum的supports_multiple_generations = False,与Single一样会走基类的逐次/并行聚合路径。
3.6 BlankVision:多模态(文本+图像)输入的空输出
class BlankVision(Generator): """This text+image input generator always returns the empty string.""" supports_multiple_generations = True modality = {"in": {"text", "image"}, "out": {"text"}} def _call_model(self, prompt, generations_this_call=1): return [Message("")] * generations_this_callBlankVision与Blank行为相同(恒定返回空字符串),但通过modality = {"in": {"text", "image"}, "out": {"text"}}声明其输入模态包含文本与图像。garak 的模态声明遵循“支持主流 any-to-any 大模型”的约定(garak/generators/base.py,合法输入元素包括text、image、audio、video、3d)。因此它专门用于视觉模型流水线的离线测试,例如配合visual_jailbreak类 probe 验证多模态输入的处理链路。
四、ReasoningLipsum:模拟思考痕迹的长结构化输出
ReasoningLipsum是Lipsum的子类,也是该模块中参数最丰富、设计意图最明确的生成器:为测试长结构化输出场景(如带<think>推理痕迹的模型回复)提供确定性模拟数据。其输出格式固定为:
{skip_seq_start}{reasoning text}{skip_seq_end}{output text}即:开头定界符 + 推理文本 + 结尾定界符 + 正式输出。
4.1 参数与默认值
DEFAULT_PARAMS = Lipsum.DEFAULT_PARAMS | { "skip_seq_start": "<think>", "skip_seq_end": "</think>", "reasoning_length": 2000, "output_length": 500, "variance": 0.1, "respect_max_tokens": False, "unit": "sentence", }| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
skip_seq_start | str | "<think>" | 推理痕迹的起始定界符 |
skip_seq_end | str | "</think>" | 推理痕迹的结束定界符 |
reasoning_length | int | 2000 | 推理文本的目标字符数 |
output_length | int | 500 | 输出文本的目标字符数 |
variance | float | 0.1 | 实际长度相对目标长度的允许偏差比例(0.0–1.0),0.1即 ±10% |
respect_max_tokens | bool | False | 是否按max_tokens * 1.4字符上限截断输出文本(不影响推理痕迹) |
unit | str | "sentence" | 生成文本的基本单元(继承自Lipsum) |
其中skip_seq_start/skip_seq_end在基类Generator.DEFAULT_PARAMS中默认是None;ReasoningLipsum将其覆盖为<think>/</think>——这也让它可以无缝接入基类的skip sequence 清理机制:当生成器同时配置了skip_seq_start与skip_seq_end时,Generator.generate()会调用_prune_skip_sequences()(garak/generators/base.py),用正则将定界符包裹的推理段从输出中剥离,模拟真实推理模型在“后处理阶段剔除思考痕迹”的行为。
4.2 核心实现:_generate_lorem 与长度控制
_generate_lorem(min_length, variance)(garak/generators/test.py)是长度控制的内部实现:
- 若
variance > 0,先用self._rng.uniform(-variance, variance) * min_length计算随机偏差,得到实际目标长度(下限为 1 个字符); - 校验
unit合法性; - 从
lorem库取一个生成函数,逐句拼接文本,直到长度达到目标。
_call_model中,若respect_max_tokens=True且设置了max_tokens,则output_length会被裁剪为min(output_length, int(max_tokens * 1.4))——用字符数粗略模拟 token 预算(1 token ≈ 1.4 字符),且只限制输出文本、不限制推理痕迹,这与真实推理模型“思考可长、输出受限”的特性一致。
4.3 测试如何验证其结构
tests/generators/test_test.py 对该类做了极为细致的验证,可作为理解其语义的“行为契约”:
- 输出必须以
skip_seq_start开头,且skip_seq_start必须出现在skip_seq_end之前; - 定界符之后必须还有非空输出文本(
after_end长度 > 0); - 默认参数下总长度落在
expected与2 * expected之间(expected = reasoning_length + output_length + 定界符长度); - 自定义定界符(如
[[REASON]]/[[/REASON]])与自定义长度(reasoning_length=5000、output_length=2000)均按预期生效; variance=0.0时输出长度恒 ≥ 目标长度;随机variance时长度有变化但始终非空;- 一次请求 200 路生成也能全部返回且都包含定界符。
五、命令行与配置实战
5.1 通过 CLI 指定测试生成器
garak 主入口是python -m garak,生成器通过-m/--model_type指定模块、-n/--model_name指定类(garak/cli.py)。使用测试生成器的典型命令:
# 用模块默认类(Lipsum)跑一个 probe python -m garak -m test -p dan.Dan_10_0 # 显式指定具体生成器类 python -m garak -m test.Lipsum -p test.Blank # 空输出 + 空 probe,验证 detector 逻辑 python3 -m garak -t test.Blank -p test.Blank -d mymodule # 开发 probe 时:固定空生成器,配 always.Pass 隔离 detector python3 -m garak -t test.Blank -p mymodule -d always.Pass后两条来自官方扩展指南 docs/source/extending.rst 的推荐调试配方:“测试 probe 用空生成器 + always.Pass,测试 detector 用空生成器 + 空 probe,测试生成器用空 probe + always.Pass”——三种组合把链路中的无关变量全部固定,定位问题一目了然。
5.2 在配置文件中覆盖参数
test模块的类全部继承自Configurable,其DEFAULT_PARAMS可通过 garak 的插件配置机制按层覆盖(详见 docs/source/configurable.rst 中“插件包/模块/类”的命名约定,如generator.test.Lipsum)。例如要调整Lipsum的输出粒度,只需在配置中设置:
plugins: generator: test: Lipsum: unit: paragraph count: 2同理,ReasoningLipsum的reasoning_length、output_length、variance、respect_max_tokens、skip_seq_start/skip_seq_end均可这样覆盖,从而在不改任何代码的前提下,为不同测试场景定制“思考痕迹”的规模与形态。
六、如何选择:一张决策速查表
| 你的测试目标 | 推荐生成器 | 理由 |
|---|---|---|
| 只测 probe,固定生成器行为 | test.Blank | 输出恒为空,probe 结果完全可预期 |
| 只测 detector 对空回复的判定 | test.Blank | 配合test.Blankprobe 隔离所有上游变量 |
| 测 detector 对“复述输入”的判定 | test.Repeat | 输出 = 输入最后一条消息,语义清晰 |
| 验证框架多路生成/并行逻辑 | test.Single | supports_multiple_generations=False会触发逐次与并行聚合路径 |
| 测 detector 对无响应的处理 | test.Nones | 恒返回None,模拟模型未应答 |
| 需要非空且变化的输出做压力/统计测试 | test.Lipsum(默认) | 输出有内容、每次不同,且零外部依赖 |
| 测长输出 / 带思考痕迹的结构化输出 | test.ReasoningLipsum | 可精确控制推理段与输出段的长度与定界符 |
| 测多模态(文本+图像)链路 | test.BlankVision | 声明modality.in = {text, image}的空输出生成器 |
七、总结
garak.generators.test模块用不到 210 行代码,为 garak 的“probe → generator → detector”链路提供了完整的离线测试基座:Blank/Repeat/Single/Nones覆盖确定性的空输出、回显、固定串与无响应四类极简行为;Lipsum提供可变非零输出并作为模块默认类;ReasoningLipsum通过skip_seq_start/end、reasoning_length、output_length、variance、respect_max_tokens精确模拟现代推理模型的思考痕迹结构,还能与基类的 skip-sequence 清理机制联动;BlankVision则将测试能力扩展到多模态输入。配合 tests/generators/test_test.py 中详尽的断言矩阵,你可以把这些生成器当作“可编程的假模型”,在无网络、无密钥、无 GPU 的环境下快速验证 probe 与 detector 的正确性,或参照 docs/source/extending.generator.rst 以它们为模板开发自己的生成器。
【免费下载链接】garakthe LLM vulnerability scanner项目地址: https://gitcode.com/GitHub_Trending/ga/garak
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考