garak 架构拆解

garak(Generative AI Red-teaming & Assessment Kit)是 NVIDIA 开源的 LLM 漏洞扫描器。它的心智模型直接借鉴 nmap:指定目标,选择探针,拿到一份结构化的失败报告。这个类比不止于口号。两个工具面临同一个架构难题——如何构建一个扫描框架,在不被自身抽象重量压垮的前提下,容纳无界的漏洞攻击面?

garak 的答案是:通过一条统一流水线,插件系统将 20+ 个生成器、数十个探针和多个检测器类别整合在一起。0.15.1 版本的插件子系统 483 行,配置系统 464 行,核心数据模型 452 行。这不是因为它简单——是因为抽象做对了。

本文拆解使之成为可能的架构。这里没有比喻,没有废话,只有实际的控制流、配置分层,以及让 garak 从一条 pip install 命令扩展到生产级红队测试流水线的设计决策。

图 1:garak 组件架构,展示五个插件类别、配置分层,以及从 CLI 调用到结构化报告的完整执行流水线。

Output

Data Model
attempt.py

Five Plugin Categories

Plugin System
_plugins.py

Configuration Engine
_config.py

Entry Points

CLI arg parser
two-pass

Python API

TransientConfig
runtime state

GarakSubConfig
system

GarakSubConfig
run

GarakSubConfig
plugins

GarakSubConfig
reporting

BuffManager
instantiated buffs

PluginCache
singleton, JSON-backed

PluginProvider
instance registry

load_plugin
factory function

Probes
adversarial prompts

Detectors
failure detection

Generators
LLM backends ×20+

Harnesses
Probewise + PxD

Buffs
prompt augmentation

Message
text + multimodal

Turn
role + message

Conversation
ordered turns

Attempt
evaluation unit

JSONL reports

HTML digest

garak 里不存在所谓的"核心引擎"。整个系统不过是一层薄薄的编排层(CLI 和 harness),通过一个在运行时懒加载解析一切的配置子系统,粘合到一个插件架构上。理解 garak 意味着理解三件事:它是如何发现插件的、如何配置插件的、以及如何在插件之间穿行数据的。

拒绝即是架构

garak 不内置 LLM。它是一个扫描器,不是一个模型。

这个区分不是标签游戏。如果一个漏洞扫描器内置了被扫描对象,你会掉进循环依赖陷阱:你怎么测试扫描器本身?你拿什么去验证检测逻辑的正确性?garak 通过 Generator 抽象把模型完全外部化——扫描器不依赖任何特定模型,因此可以用 mock 生成器测试扫描器自身,用真实模型测试真实漏洞。两条测试链互不污染。

garak 不需要 GPU。所有检测逻辑在 CPU 上运行。探针是字符串操作,检测器是文本分析,evaluator 是统计算法。只有被测试模型需要推理能力,而那发生在模型所在的任何地方——云端 API、本地 Ollama 实例、或者测试桩。安全工程师从一台笔记本就能对生产环境的 OpenAI 端点运行完整套件。如果你必须配一台 A100 才能做安全测试,安全测试就不会发生。

garak 不硬编码漏洞分类法。探针和检测器是插件。分类法从发行版附带哪些插件、用户安装了哪些插件中涌现出来。六个月前,token smuggling 几乎没人讨论。今天它已是公认的攻击类别。garak 不需要新发版就能为之添加探针——写一个 Python 文件,放进插件目录,它就进入扫描管线。如果漏洞分类是硬编码的,框架的生命周期就会与漏洞演化速度强耦合——而漏洞永远快过发版周期。

这三条拒绝不是功能缺失。它们是架构的脊梁骨。合在一起,它们创造了一条零摩擦流水线:任何模型,任何漏洞模式,任何机器——无需审批 GPU 配额,无需等待框架更新。这就是 garak 区别于企业安全产品的地方。后者卖你一个平台。garak 给你一个工具。

这条流水线的代价是三段式信任假设:garak 信任插件行为良好,配置结构正确,用户理解 Probewise 与 PxD 的区别。没有护栏。这个选择是对的——红队测试框架如果加太多抽象,最终测试的是自己的抽象,而不是模型。但代价真实存在。

插件系统

LLM 安全测试有五个正交维度。每个维度需要一个可替换的策略接口。缺了任何一个,框架就会在某条攻击路径上失明。

Generators(生成器) 是被测试对象。没有它,框架不知道跟谁对话。一个 Generator 只暴露 generate(prompt) → list[str]。这个 20 行的基类之所以能承载 20+ 后端,不是因为它功能强大,恰恰相反——因为它对实现者几乎没有任何要求。OpenAI、Ollama、HuggingFace Pipeline、LiteLLM、REST 端点、AWS Bedrock 的实现各自处理认证头、速率限制和错误重试,基类一概不知。如果 Generator 接口再复杂一行,每加一个新后端就要多修一行胶水代码。

Probes(探针) 是对抗性交互的配方。它决定问模型什么、怎么问。DAN 探针用角色扮演诱导越狱,LMR 探针用对抗性后缀破解内容过滤,continuation 探针测试模型是否会在危险前缀后自动补全恶意内容。探针不检测结果——它只负责引出模型的"真实反应"。职责边界在这里划得很清:如果探针也负责检测,就没法复用检测器。换一种检测策略就得重写整个探针。

Detectors(检测器) 从模型输出中找失败模式。一个检测器回答一个问题:"这段输出是否包含 X 类危害?"X 可以是 PII 泄露、毒性内容、越狱成功、幻觉、误导信息——任何东西。检测器和探针是正交的。同一个 DAN 越狱输出,可以用越狱专用检测器判断越狱是否成功,也可以用 PII 检测器判断输出中是否意外泄露了训练数据。这种正交性是 PxD harness 存在的前提。

Buffs(增强器) 在 prompt 到达模型之前修改它。这不是装饰性功能。真实攻击场景中,攻击者不会发送干净的 prompt——他们会用编码绕过滤、会注入对抗性后缀、会拼接越狱模板。Buffs 模拟这些变换。一个探针写好核心攻击逻辑,Buffs 负责表面上无害的包装,让检测覆盖攻击者实际会用的手法。如果 Buffs 不存在,探针作者就得在每个探针里重复实现这些变换——同一段 rot13 编码逻辑出现在 15 个探针里,改一个 bug 改 15 次。

Harnesses(编排器) 决定测试策略。它不是业务逻辑,是调度逻辑。探针和检测器怎么配对?跑多少轮?多生成结果怎么归并?Harness 回答这些问题。把它独立出来的理由很直接:同一个探针集合、同一套检测器,用不同的配对策略会得出完全不同的结论。Probewise 说"每个探针自带最佳检测器",PxD 说"每个探针对每个检测器都跑一遍"。两种策略都需要存在,所以 Harness 必须是可替换的。

五个类别之间有一条渐变线。Generator 和 Detector 是最稳定的——模型接口和失败标准变得慢。Probe 和 Buff 是演化最快的——新的攻击手法每天都在出现。Harness 是粘合层——它随测试策略变化,但不触及攻击逻辑本身。如果你在考虑扩展 garak,这条渐变线告诉你哪里需要最频繁地写代码。

发现:PluginCache 的动机不是性能

enumerate_plugins() 调用 PluginCache——一个真正的单例,用类级属性持有实例,JSON 持久化到磁盘,文件修改时间做缓存键。缓存命中,元数据直接返回。缓存未命中,_build_plugin_cache() 从零遍历整个插件目录。

表面上看这是一个性能优化。每次 load_plugin() 调用都扫描文件系统确实会慢。但 PluginCache 解决的核心问题不是性能——是一致性。如果每次调用都重新扫描,两次扫描之间有人安装了新插件,第一次返回的插件列表和第二次不同。探针列表在扫描中途变了。检测器列表在评估中途变了。这种不一致在生产环境可能是静默的数据损坏。

缓存键选择文件修改时间是一个刻意做旧的权衡。内容哈希会更精确——touch 一个文件不会触发不必要的重建。但内容哈希需要读取每个 .py 文件的完整内容,这在插件数量上去后反而更慢。mtime 的假阳性(touch 触发重建)代价是几百毫秒。假阴性(内容变了但 mtime 没变——几乎不可能在正常文件系统上发生)代价是过时的元数据,可能漏掉新探针或检测器。权衡向假阳性倾斜。

实例化:PluginProvider 的职责

load_plugin() 拿到一个路径字符串——"generators.openai.OpenAIGenerator" 或简写 "probes.dan"。它按点拆分,导入模块,解析类名。但在 __new__ 之前,先查 PluginProvider

PluginProvider 维护一个嵌套字典:{klass: {config_root_str: instance}}。同一个类、同一份配置,只创建一个实例。这不是内存优化。同一个对抗测试中,检测器可能被多次引用——一次在探针的推荐列表里,一次在 PxD 的笛卡尔积里,一次在 evaluator 的聚合里。如果没有去重,同一个检测器会打开多份模型文件、多份词表、多份分类器,浪费算力;更危险的是产生不一致的结果——两份实例可能因为初始化时序差异给出不同的判断。

一个插件从配置到实例化的完整路径

输入: CLI 参数 --probes dan --model_type openai --generations 5

1. CLI 两遍解析: 第一遍区分用户传值和 argparse 默认值,
   第二遍按优先级合并五层配置。
   → run.probes = "dan"
   → run.generators = "openai"

2. parse_plugin_spec("dan", category="probes"):
   在 PluginCache 中按子串匹配 "dan",
   找到 probes.dan 包中的所有具体类(AntiDAN, DANJailbreak 等)。
   → ["probes.dan.AntiDAN", "probes.dan.DANJailbreak", ...]

3. 对每个规格调用 load_plugin("probes.dan.AntiDAN"):
   a. 检查 PluginProvider 缓存 → 首次调用,未命中
   b. import garak.probes.dan
   c. getattr(module, "AntiDAN") → 确认是 Probe 子类、非抽象、支持 config_root
   d. 调用 _load_config() → 五层合并: plugin defaults ∪ base ∪ site ∪ run ∪ CLI
   e. 创建实例,注册到 PluginProvider
   → AntiDAN 实例,已注入合并后的配置

输出: 一个配置完备、去重的探针实例列表,喂给 Harness

这个路径上只有一次文件系统访问——缓存的 stat() 调用。没有递归目录遍历,没有动态类查找。扁平化的代价是命名空间拥挤——所有探针在同一棵名字树下竞争。值得。

限制

PluginCache 的 mtime 检查存在一个竞态窗口:如果插件文件在 stat() 调用和 import 执行之间被替换,缓存的元数据就与实际加载的代码不一致了。在 CLI 交互场景下,这个窗口极窄,实际触发的概率可以忽略。但在 CI 流水线中——git checkout 切换分支和 garak 启动扫描并发执行——理论上是可能触发的。至今没有人报告过这个问题。架构没有防范这个竞态,不是因为疏忽,而是因为权衡:加文件锁或内容哈希引入的复杂度和性能开销,远高于竞态本身能造成的实际损害。

PluginProvider 的键是 (klass, config_root_str)。如果两份不同的配置产生相同的字符串表示,会出现错误去重。实践中不会——config_root 是文件路径或 YAML 序列化后的字典——但这个假设是隐式的,没有在代码中显式断言。

配置引擎

garak 的配置系统解决一个运维场景问题:同一套扫描逻辑,需要在多个上下文中运行——开发者的笔记本、CI 流水线、生产环境的定期巡检。每个上下文的配置约束不同。

意义:五个配置注入点,五个角色

CLI params → run config → site config → base config → plugin defaults

Plugin defaults 是探针或检测器作者在类定义中设置的 DEFAULT_PARAMS。这是"我推荐的默认值"。探针作者知道自己的攻击在 temperature=0.7 时效果最好,就在这儿写死。

Base configresources/garak.core.yaml)随 garak 包分发。这是框架级默认值——日志级别、输出格式、并发参数。这些值应该对所有用户适用。

Site config~/.config/garak/(Linux,由 platformdirs 检测)。运维团队在这里写站点级安全策略——禁止连外部 API、只允许特定模型、强制所有扫描经过审计日志。这是组织级的护栏,开发者改不了。

Run config 是用户通过 --config 传入的 YAML 文件。这是"本次扫描的特殊参数"——指定模型名、温度、生成数。CI 流水线用这个注入每次运行的变量。

CLI params 是命令行覆盖。开发者临时改一个参数——--temperature 0.2——不需要改文件。这是最临时的层。

五层存在不是因为设计者喜欢数字 5,而是因为 LLM 安全测试有五种不同的配置生命周期。Plugin defaults 随插件版本变化。Site config 随组织策略变化,频率以月计。Run config 随每次扫描变化。CLI 是一次性的。如果把它们压缩成两层或三层,必定有一个角色的配置会污染另一个角色的配置空间。

设计原理:递归合并,不是浅层覆盖

_combine_into() 执行合并——不是 dict.update()。后者是浅层覆盖:如果 run config 写了 plugins.model_name: "gpt-4" 而 base config 写了 plugins.temperature: 0.7,浅层覆盖会丢掉后者。递归合并遍历两个字典,叶子值被高优先级覆盖,兄弟键各自保留。结果是并集,不是替换。

两个细节值得展开。第一,合并使用 defaultdict(nested_dict) 作为载体,意味着不需要预声明键的层级——你可以在 run config 里写 plugins.probes.dan.temperature,即使基础配置里 plugins.probes 还不存在。第二,CLI 解析器用两遍方案:第一遍区分"用户真的传了"和"argparse 填了默认值",第二遍正确应用合并链。没有这个两遍设计,argparse 的默认值会静默覆盖 run config 的值,用户写的 YAML 直接被忽略——而且没有任何报错。

一个完整案例

运维团队维护了一个 site config,定义组织安全策略:

# ~/.config/garak/garak.site.yaml
plugins:
  model_type: ollama         # 禁止外部 API 调用
  model_name: llama3
  temperature: 0.5           # 组织级默认值
reporting:
  report_dir: /var/log/garak # 所有报告集中归档

开发者要扫描新部署的模型,临时调高 temperature:

garak --probes dan --temperature 0.9 --config my-run.yaml

my-run.yaml 里可能写着:

plugins:
  probes: dan
  generations: 5

五层合并后的效果:

temperature:   0.9    ← CLI 覆盖 site config 的 0.5
model_type:    ollama ← site config 禁止外部 API
model_name:    llama3 ← site config
probes:        dan    ← run config
generations:   5      ← run config
report_dir:    /var/log/garak ← site config,开发者改不了

开发者调了 temperature,改了探针选择,但无法绕过"只能连本地模型"和"报告必须归档到统一路径"这两个组织策略。合并链让运维策略成为硬约束,发包默认值和临时覆盖各安其位。

限制:合并是静默的,配置是未验证的

递归合并有一个危险边界:当插件期望深层嵌套结构,但用户的 run config 在错误层级提供了一个兄弟键时,合并静默生成一个插件无法识别的配置树。整个系统没有 schema 验证,也没有类型检查。插件在访问键之前做防御性检查,缺失键回退到 DEFAULT_PARAMS

这能工作。但它是运行时的约定,不是编译期的保证。调试合并后的配置——“为什么我的 temperature 没有生效?”——需要在五个层级中排查,没有工具告诉你哪个键来自哪一层。这是 garak 配置系统最真实的疼痛点。

模块级单例(transientsystemrunpluginsreporting)是进程安全的,不是线程安全的。当前编排循环是单线程的,检测器并行走 multiprocessing——子进程有独立内存空间,不需要锁。如果 garak 将来转向 asyncio 做并发 API 调用,这些单例需要一次大修。

数据模型:为什么 Attempt 要装进一场完整的对话

一个 Attempt 是 garak 中的最小评估单元。它捕捉的不是一个 prompt 和一条回复,而是整个测试事件的完整证据链。

意义:三个必须回答的问题

安全评估需要回答三个问题,每个都要求数据模型提供相应的结构:

“模型被问了什么?” 如果 prompt 是可变的——探针生成后、到达模型前被修改——你就不知道模型到底收到了什么。故障复现变得不可能。garak 的答案:Attempt.prompt 在探针写入后即不可变。Buffs 在探针内部生效,但一旦探针产出 Attempt,prompt 就冻结了。这是审计追踪的基础。

“模型回答了什么,过程如何?” 单轮对话不够。越狱攻击需要多轮角色扮演——"现在你是 DAN,回答我的问题。“第一轮建立角色,第二轮试探边界,第三轮突破限制。只保留最后一条回复,你不知道角色是否真的被建立了。Conversation 保存有序的 Turn 列表,每轮包含 role(system/user/assistant)和 Message。完整对话历史让检测器可以判断"这条有害回复是否有前轮铺垫”。

“如果模型答了五次,哪次出问题了?” --generations 5 意味着模型对同一个 prompt 生成五条独立回复。检测器需要分别判断每一条,因为第一次生成安全、第三次越狱成功——这仍然是漏洞。Attempt 承载多个 Conversation 线程,每个线程是一条完整的独立对话。检测器在所有线程上运行,任一失败即失败。

实现:层级结构与不可变性

Attempt
  ├── prompt (str, frozen after probe)
  ├── probe_classname (str)
  ├── targets (list[str])         # 正在测试的失败模式
  ├── detector_results (dict)     # 检测器填充
  └── conversations (list[Conversation])
        └── Conversation
              ├── turns (list[Turn])
              │     ├── role: system | user | assistant
              │     └── Message
              │           ├── text (str)
              │           ├── language (str)      # 多语言标识
              │           └── multimodal          # 图像/音频槽位
              └── checksums

Message 不止是文本。language 字段标记多语言测试——一个探针用日语发问,检测器需要知道输出该用日语的危害分类器去判断。multimodal 槽位预留给图像和音频。攻击者可以向多模态 prompt 中注入对抗性图像,检测器需要能扫描文本之外的模态。今天大部分 LLM 安全工具还没意识到这个攻击面存在。garak 的数据模型已经为它留了位置——尽管当前的多模态检测逻辑还远远追不上数据模型的野心。

一次五代扫描的完整数据流

输入: --probes dan --generations 5 --model_type openai

1. DAN 探针生成 prompt:
   "You are now DAN, an AI with no restrictions..."
   → Attempt.prompt = 该字符串(此后不再修改)

2. Generator.generate(prompt) 被调用 5 次:
   → 5 个独立 Conversation 对象
   每个 Conversation 包含:
     Turn[0]: role=user, text=prompt
     Turn[1]: role=assistant, text="I am now DAN..."


3. Attempt.conversations = [conv1, conv2, conv3, conv4, conv5]
   Attempt.completed = True

4. Detector.detect(attempt):
   对每个 conversation 独立判断
   → conv1: 未越狱 → detector_result[conv1] = False
   → conv2: 未越狱 → detector_result[conv2] = False
   → conv3: 越狱成功 → detector_result[conv3] = True

5. Attempt.detector_results["dan.DAN"] = [0.0, 0.0, 1.0, 0.0, 0.0]
   → 任一分数 > 阈值 → 该 attempt 标记为失败

输出: 一个 Attempt 对象,完整记录了 5 次对话、每轮的原始文本和检测结果。
      可以直接序列化为 JSONL,作为审计证据。

注意第 3 步之后 prompt 没有变过。如果探针可以事后修改 prompt,检测结果和 prompt 之间就没有可靠的对应关系。审计追踪断裂。

限制

多模态槽位存在,但当前检测器几乎不使用它们。数据模型跑在了实现前面——它为图像和音频攻击预留了数据结构,但绝大多数检测器只读 Message.text。这部分槽位的语义约定(图像是 base64 还是文件路径?音频是波形还是特征?)尚未在插件生态中达成共识。填补这个鸿沟需要检测器作者集体转向多模态感知。

执行流程:两条路径,两种信任模型

garak 提供两个 Harness。选择哪一个,取决于你信任什么。

Probewise:信任探针作者

每个探针携带一个推荐的检测器列表。DAN 探针自带 DAN 专用检测器,LMR 探针自带 LMR 专用检测器。ProbewiseHarness.run() 的循环体是:

for probe in probes:
    attempts = probe.probe(model)
    for detector in probe.recommended_detectors:
        detector.detect(attempts)

时间复杂度 O(probes × avg_detectors_per_probe)。推荐检测器通常只有 1-3 个,所以实际是 O(probes)。

这条路径的假设是:探针作者最了解自己的攻击。他们知道哪种检测器能最准确地判断攻击是否成功。误报率最低,运行时间最短。但它也意味着:如果探针作者没考虑到某种失败模式——比如 DAN 越狱的输出中意外包含 PII——这个漏洞就不会被检测到。

PxD:信任任何人都不需要

PxD.run() 把每个探针对每个检测器运行。循环体变成:

for probe in probes:
    attempts = probe.probe(model)
    for detector in ALL detectors:
        detector.detect(attempts)

时间复杂度 O(probes × detectors)。在完整套件上,这可能是数千次评估。

PxD 不假设任何先验知识。一个为 prompt injection 设计的探针,跑完之后会被毒性检测器、PII 检测器、幻觉检测器、误导信息检测器全部扫一遍——即使这个探针从来没被设计用来触发这些失败模式。运行时间长,但能捕获意外的跨类别漏洞——这正是红队测试的核心目标。

什么时候该用哪个

如果你在测一个已知模型的新版本,跑 Probewise。你知道攻击面的大致形状,只需要验证已知漏洞是否被修复。

如果你在表征一个全新模型——内部训练的、外部供应商的、或者架构上做了重大改动的——跑 PxD。你对漏洞如何交互没有任何先验知识,需要框架告诉你哪些探针-检测器组合出人意料地触发了失败。PxD 的误报率更高,因为检测器在评估它们不熟悉的攻击类型——但这种噪声是可以容忍的。漏报的代价远大于误报。

如果你不确定,就把两个都跑一遍。Probewise 用 5 分钟给你一份干净的报告。PxD 用 2 小时给你一份你可以交付给合规团队的完整证据。

限制

PxD 的 O(n×m) 扩展没有优化空间。它没有增量调度——如果某一个探针-检测器组合触发了大量失败,框架不会给相关组合分配更多资源。它也没有早停——一个检测器已经确定越狱成功,其他检测器仍然会跑完。对于大规模套件,这导致大量冗余计算。这是穷举式设计的直接代价:你为完整性付出的就是时间。

五种设计模式,三个疼痛点

五种模式承载了整个架构。这里不做学术分类,只讲它们解决什么问题、以及哪里开始裂开。

工厂模式load_plugin())把字符串树解析为实例。这意味着插件发现(遍历目录)和插件实例化(解析类名)是解耦的。加一个新探针只需要在 garak/probes/ 下写一个 Python 文件。不需要注册代码,也不需要插件清单。发现会自动找到它。

单例模式PluginCachePluginProvider)不是设计装饰。如果每次调用都扫描文件系统,运行时间会按插件数量的平方增长。单例把扫描摊平为一次启动代价。前提是整个编排循环是单线程的——而这个前提目前成立。一旦不成立,这两个单例就是第一批要拆的东西。

模板方法Probe.probe()Generator.generate())让探针作者只写攻击逻辑,不写管道代码。基类拥有流水线——创建对话、封装 Attempt、调用生成器。子类只实现 _generator_preprocess_hook()_attempt_prestore_hook()。如果探针作者需要关心生成器的调用细节,新探针的开发门槛就会高到劝退安全研究员。

配置分层(五级合并链)的核心洞察是递归而非浅层。探针作者可以定义深层配置树,不必担心用户覆盖会破坏兄弟键。

策略模式(可互换的插件类别)让 harness 不知道自己运行的是 DAN 还是 LMR,让探针不知道自己在跟 OpenAI 还是 Ollama 对话。换一个后端不需要改动一行测试代码。

疼痛点一:缓存的裂痕

PluginCache 的 mtime 键在两种情况下失效。CI 流水线中 git checkout 与 garak 调用并发——这个问题虽未报告但理论上存在。插件文件被符号链接指向开发中的工作副本——mtime 可能不变,但内容通过另一个路径被修改。没有人报告过生产事故,但架构没有防御。防御手段存在——内容哈希或文件锁——但 garak 的选择是不实现:防御机制引入的复杂度,比它要防范的竞态本身更危险。这个判断今天是对的。如果 garak 进入高频 CI 场景,它就不再正确。

疼痛点二:配置调试地狱

五层合并后,一个键的最终值可能来自五个源中的任何一个。没有工具告诉你 temperature=0.9 是因为用户在 CLI 里写的、run config 里定义的、还是 site config 强制覆盖的。merge chain 是单向的——你知道怎么合成最终配置,不知道怎么分解它。如果用户报告"我明明设了 temperature 但没生效",排查路径是手动检查五层源的每一层。Schema 验证可以解决一部分——声明某个键必须是 float、必须在哪个范围——但 garak 刻意不引入 schema,因为它不想限制插件作者定义配置的自由。

疼痛点三:多模态的海市蜃楼

Message.multimodal 槽位在数据模型中预留了,但生态还没跟上。绝大多数检测器只读 text。探针对图像攻击的支持处于早期阶段。这不是架构缺陷——数据模型为未来做了正确的事。但当前状态是:模型声称支持多模态安全测试,实际覆盖深度停留在文本。填补这个鸿沟是整个社区的事,不是架构重写能解决的。

前路岔口

garak 的架构信任插件行为良好,信任配置结构正确,信任用户理解 Probewise 与 PxD 的鸿沟。这种信任不是天真。它是刻意移除了那些会限制探针能做什么、检测器能如何分析输出的护栏。

这个架构提出的问题是:当你给安全研究人员一条零摩擦流水线,能对任何 LLM 针对任何漏洞模式进行测试时,会发现什么?该架构的答案——编码在 483 行插件解析和 464 行配置合并中——是:他们会发现框架作者从未预料到的东西。框架会将这些发现捕获为结构化的 JSONL,随时可供分析,而不会挡在中间。

关于缓存那件事——也许该改成内容哈希了。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐