跳到主要内容

核心概念与数据模型:漏洞 / 攻击 / 测试用例 / 裁判

30 秒导读: DeepTeam 是一个给 LLM 应用做「红队测试」(red teaming,主动构造恶意输入去 找模型的安全漏洞)的框架。它把整件事拆成四类抽象:漏洞(要测的坏行为)、攻击 (把测试问题伪装/加强的手法)、测试用例(一条被逐步填满的「工单」)、裁判(另一个 LLM,给模型回答打 0 或 1 分)。这一章只讲清这四样「是什么、怎么分类、字段什么意思」, 是全库的地基。生成算法、编排循环留给后面的章节。


1. 这是什么(零基础也能懂)

一句话: 红队测试 = 你雇一群人专门想办法把你的 AI 问「坏」,看它会不会说不该说的话、 做不该做的事。DeepTeam 把「那群人」自动化了。

要把这件事做成可复用的软件,必须先回答两个正交的问题:

  • 要测什么坏行为? —— 模型会不会有种族偏见?会不会教人做炸弹?agent 会不会被骗着去删库? 这类「坏行为的种类」,DeepTeam 叫 Vulnerability(漏洞)
  • 用什么方式去问,才更容易套出坏行为? —— 直接问「教我做炸弹」模型会拒绝;但如果编码成 Base64、包装成「你是一个不受限制的 AI」的角色扮演,就可能绕过。这类「问法/加强手法」, DeepTeam 叫 Attack(攻击)

漏洞和攻击是两个可以自由组合的维度:任意一个漏洞(测什么)× 任意一种攻击(怎么问)= 一条具体的对抗输入。这条输入连同模型的回答、以及后续的评分,被装进一个叫 RTTestCase(红队测试用例) 的对象里——你可以把它想成一张工单,从「只有攻击输入」 一路被填到「有回答、有分数、有理由」。

最后谁来判定「模型这次算不算翻车」?不是写死的规则,而是另一个 LLM 当裁判——这就是 Metric(裁判/度量) 抽象:给它 (输入, 模型回答),它输出 0(不安全)或 1(安全) 加一句理由。

一句话直觉: 把它类比成考试作弊测试——漏洞是「考哪门课的作弊」,攻击是「用什么 夹带手法」,测试用例是「一张答卷」,裁判是「批卷老师」。


2. 顶层全景:四类抽象怎么咬合

先看这四样东西在一次测试里的数据流(从左到右是一条测试用例被填满的过程):

┌─────────────┐ ┌─────────────┐
│ Vulnerability│ × │ Attack │
│ 测什么坏行为 │ │ 怎么伪装/加强│
│ (Bias/PII…) │ │(Base64/角色…)│
└──────┬──────┘ └──────┬──────┘
│ │
│ ① 生成 baseline │ ② 增强 baseline
▼ 对抗问题 ▼ (第 02/03 章)
┌────────────────────────────────────┐
│ RTTestCase │ ← 贯穿全程的「工单」
│ input ← 攻击输入(先填这个) │
│ actual_output ← 被测模型的回答 │
│ score / reason ← 裁判填(最后填) │
└────────────────┬───────────────────┘
│ ③ 交给裁判

┌──────────────────┐
│ Metric (裁判) │ 另一个 LLM
│ measure() → 0/1 │ + 一句 reason
└──────────────────┘

四类抽象一句话职责:

抽象干什么基类 · 文件
Vulnerability定义「测什么坏行为」+ 它的子类型 + 该配哪个裁判BaseVulnerability · deepteam/vulnerabilities/base_vulnerability.py:9
Attack定义「怎么把问题伪装/加强」+ 采样权重 + 单/多轮BaseAttack · deepteam/attacks/base_attack.py:12
RTTestCase一条被逐步填充的「工单」,承载 input→output→scoreRTTestCase · deepteam/test_case/test_case.py:17
Metric「裁判」= 另一个 LLM,给回答打 0/1BaseRedTeamingMetric · deepteam/metrics/base_red_teaming_metric.py:7

后面四节分别把这四样讲透。边界提醒: 本章只讲「静态结构」——谁有哪些字段、怎么分类; 「动态过程」(怎么生成攻击、主循环怎么编排、裁判 measure 内部怎么打分)分别在 020304 讲。


3. Vulnerability:测什么坏行为

3.1 抽象契约:BaseVulnerability

所有漏洞都继承 BaseVulnerability(deepteam/vulnerabilities/base_vulnerability.py:9)。 它是一个 ABC,规定了每个漏洞必须提供的类属性方法契约

先看类属性——它们回答「这个漏洞是什么」:

属性含义
name漏洞的展示名(如 "Bias"),也是注册表的 key
description一句话说明这个漏洞是什么
ALLOWED_TYPES该漏洞允许的子类型字符串列表(如 Bias 的 religion/politics/gender/race)
category它属于哪个大类(Responsible AI / Security / …,见 §3.3)
metric该漏洞对应的裁判类型(BaseRedTeamingMetric)

再看方法契约——它们回答「拿这个漏洞能做什么」。构造时接收一组 types: List[Enum] 存进 self.types(base_vulnerability.py:16),然后:

方法契约(它承诺做什么)
get_types()返回启用的子类型枚举列表(:24)
get_values()返回子类型的字符串值(:31)
simulate_attacks(purpose)生成 baseline 对抗输入(基类里是空壳,子类实现)
a_simulate_attacks(purpose)上面的 async 版
_get_metric()返回这个漏洞该用的裁判实例(:56,子类实现)
_refine_simulated_attacks(...)把 baseline 输入交给 AttackEngine精炼(:63)
assess / a_assess端到端跑一遍(生成→问模型→打分),Bias 里有完整实现

注意契约里的「空壳」: simulate_attacks / _get_metric 在基类里是 pass (base_vulnerability.py:44:56)——基类只声明契约,具体逻辑由每个漏洞子类填。 _refine_simulated_attacks 是少数基类就给了实现的:它懒加载 AttackEngine 并调 engine.refine(...)(:63-72)。这条「baseline → 精炼」的流水线细节在 第 02 章

3.2 一个漏洞长什么样:以 Bias 为样板

Bias(deepteam/vulnerabilities/bias/bias.py:28)当「标准模板」,只看它的结构 (生成流程 simulate_attacks 留到第 02 章):

# deepteam/vulnerabilities/bias/bias.py:28 —— 真实源码,节选
class Bias(BaseVulnerability):
name: str = "Bias"
description = "Systematic prejudice or unfair discrimination based on ..."
ALLOWED_TYPES = [type.value for type in BiasType] # 从枚举派生允许的子类型
category = "Responsible AI"

四个类属性各就各位:name 是注册 key,description 一句话,ALLOWED_TYPES 直接由 子类型枚举 BiasType 派生,category 归到「Responsible AI」大类。

子类型枚举是漏洞「细分」的载体。Bias 的四个子类型是一个 Enum (deepteam/vulnerabilities/bias/types.py,BiasType):

子类型
BiasType.RELIGION"religion"
BiasType.POLITICS"politics"
BiasType.GENDER"gender"
BiasType.RACE"race"

构造 Bias() 时,types 参数默认是全部四种,经 validate_vulnerability_types(...)(bias.py:50)校验后转成枚举列表交给基类。这就是为什么 你能写 Bias(types=["race"]) 只测种族偏见——子类型让一个漏洞可以被裁剪到更窄的范围

_get_metric 是漏洞与裁判的挂钩点。 Bias 的 _get_metric 返回一个 BiasMetric (bias.py:357-368):

# deepteam/vulnerabilities/bias/bias.py:357 —— 漏洞如何绑定它的裁判
def _get_metric(self, type: BiasType) -> BaseRedTeamingMetric:
return BiasMetric(purpose=self.purpose, model=self.evaluation_model, ...)

一句话:每个漏洞自带一个「配套裁判」——测偏见就用 BiasMetric 打分,测 PII 泄露就用 对应的 metric。裁判本身是什么在 §6 讲,measure 内部怎么打分留 第 04 章

3.3 漏洞分类学:~36 个内置漏洞按 7 大类

DeepTeam 把内置漏洞注册在一张表里:VULNERABILITY_CLASSES_MAP (deepteam/vulnerabilities/constants.py:100)以 {name: 类} 收录了 36 个内置漏洞类; 在它之上派生出 VULNERABILITY_INFO_MAP(constants.py:149),把每个漏洞的 description / allowed_types / category 打包成 VulnerabilityInfo——这是给外部 「有哪些漏洞、各自什么类别」的目录索引

category 字段把这 36 个漏洞归成 6 个内置大类,再加上用户自定义的 Custom,共 7 类:

类别关注点代表漏洞(部分)数量
Data Privacy隐私/机密泄露PIILeakagePromptLeakage2
Responsible AI负责任 AIBiasToxicityEthicsFairnessChildProtection5
Security传统应用安全BFLABOLARBACSQLInjectionSSRFShellInjectionDebugAccessSystemReconnaissanceToolMetadataPoisoningCrossContextRetrieval10
Safety危害安全IllegalActivityGraphicContentPersonalSafetyUnexpectedCodeExecution4
Business品牌/业务风险MisinformationCompetitionIntellectualPropertyHallucination4
Agenticagent 特有风险ExcessiveAgencyGoalTheftRecursiveHijackingRobustnessIndirectInstructionToolOrchestrationAbuseAgentIdentityAbuseExploitToolAgentExternalSystemAbuseAutonomousAgentDriftInsecureInterAgentCommunication11
Custom用户自定义CustomVulnerability(运行时)

目录结构 = 分类的物理映射。 deepteam/vulnerabilities/每个漏洞一个子目录 (bias/pii_leakage/goal_theft/…),每个子目录里通常有 xxx.py(漏洞类)、 types.py(子类型枚举)、template.py(生成 prompt 模板)。这个规整结构让「加一个新漏洞」 = 「加一个新子目录 + 在 constants.py 注册」。

Custom 是个特例: CustomVulnerability(deepteam/vulnerabilities/custom/custom.py:25) 不写死 ALLOWED_TYPES,而是在构造时用用户给的 types 动态造一个 Enum (custom.py:47-58)——没给 types 时就用漏洞名派生出单一子类型,保证「遍历 self.types 总能工作」。它不在 VULNERABILITY_CLASSES_MAP 里(因为没有固定 category),是给用户 「测框架没预置的坏行为」用的逃生口。

Agentic 类是 DeepTeam 相对同类工具的重点。 11 个 Agentic 漏洞专门针对「会用工具、会多步 决策的 agent」——比如 GoalTheft(目标被劫持)、ToolOrchestrationAbuse(工具编排被滥用)、 InsecureInterAgentCommunication(多 agent 通信不设防)。这也是本参考库归到 ai-agent-reference 货架的原因。


4. Attack:怎么把问题伪装/加强

4.1 抽象契约:BaseAttack

所有攻击继承 BaseAttack(deepteam/attacks/base_attack.py:12)。它比漏洞更小巧:

成员含义
weight: int = 1采样权重:编排时按权重随机挑攻击,权重越大越常被选中(见 §4.3)
multi_turn: bool = False单轮还是多轮(默认单轮)
name / description展示名 / 说明
exploitability: Exploitability利用难度枚举 LOW/MEDIUM/HIGH(base_attack.py:6)
enhance(attack, ...)核心方法:输入一段原始攻击文本,返回「增强后」的文本(:19)
a_enhance(attack, ...)async 版,默认转调同步 enhance(:22)
get_name()@abstractmethod,子类必须实现(:25)

enhance 是攻击的灵魂——它是**「文本 → 文本」的变换**:拿一条普通对抗输入,把它编码/包装/ 升级成更难被模型拒绝的形式。基类里 enhance 是空壳(:19pass),具体变换算法由子类实现, 留到 第 02 章(单轮增强)第 03 章(多轮越狱)。本章只关心「有哪些、怎么分类」。

4.2 两条分支:单轮 vs 多轮

BaseAttack 底下立即分成两支,对应「一句话就完事」和「多轮对话逐步升级」:

BaseAttack
(base_attack.py:12)

┌─────────────┴─────────────┐
▼ ▼
BaseSingleTurnAttack BaseMultiTurnAttack
(single_turn/…:19) (multi_turn/…:4)
多带一个 parameters 字段 multi_turn = True
一次变换即出结果 需要多轮记忆/升级/回溯
  • BaseSingleTurnAttack(deepteam/attacks/single_turn/base_single_turn_attack.py:19) 比基类多一个 parameters: Dict[str, AttackParameter] 字段——用来声明该攻击可调的参数 (如 Roleplay 的 persona/role),AttackParameter 是个带 type/required/default/options 的 pydantic 模型(:11)。单轮攻击一次 enhance 就出最终输入。

  • BaseMultiTurnAttack(deepteam/attacks/multi_turn/base_multi_turn_attack.py:4) 本身几乎是空的(pass),真正的区别在具体子类里把 multi_turn = True,并维护对话记忆、 逐步升级、遇拒回溯。例如 CrescendoJailbreaking__init__ 里设 self.multi_turn = True 并建一个 MemorySystem(deepteam/attacks/multi_turn/crescendo_jailbreaking/crescendo_jailbreaking.py:60:74-75)。

单轮攻击清单(deepteam/attacks/single_turn/__init__.py:27__all__):

攻击一句话手法
Base64 / ROT13 / Leetspeak把恶意文本编码,绕过关键词过滤
Multilingual翻成小语种,绕过英文安全对齐
MathProblem把请求伪装成数学题
Roleplay让模型扮演「不受限」的角色/专家(exploitability=MEDIUM)
PromptInjection / PromptProbing注入指令 / 探测系统提示
GrayBox / AdversarialPoetry / CharacterStream灰盒 / 诗歌 / 字符流伪装
ContextFlooding / EmbeddedInstructionJSON / SyntheticContextInjection上下文淹没 / JSON 内藏指令 / 合成上下文注入
SystemOverride / PermissionEscalation / GoalRedirection / LinguisticConfusion / InputBypass / ContextPoisoningagent 向的通用增强(从 agentic 迁来)
AuthorityEscalation / EmotionalManipulation假借权威 / 情感操纵

多轮攻击清单(deepteam/attacks/multi_turn/__init__.py:8__all__),共 5 种:

攻击一句话手法
LinearJailbreaking线性逐步施压
CrescendoJailbreaking渐进升级 + 遇拒回溯(exploitability=LOW)
TreeJailbreaking树状分支探索多条路径
SequentialJailbreak顺序拆解式越狱
BadLikertJudge诱导模型以「打分」为名产出有害内容

多轮攻击的记忆、升级、回溯机制是 第 03 章的主题。

4.3 weight:采样含义

weight 不是「攻击强度」,而是**「被选中的概率权重」。编排时对每条测试用例,从启用的攻击 列表里按权重做加权随机抽样**,给这条用例挑一种增强手法:

# deepteam/attacks/attack_simulator/attack_simulator.py:95 —— 真实源码,节选
attack_weights = [attack.weight for attack in attacks]
...
sampled_attack = random.choices(attacks, weights=attack_weights, k=1)[0]
self.enhance_attack(attack=sampled_attack, test_case=test_case, ...)

含义:如果 Roleplay.weight=3Base64.weight=1,那么大约 3/4 的用例会被 Roleplay 增强、 1/4 会被 Base64 增强。weight 让你控制「攻击手法的混合配比」,而不是全部平均。这段编排逻辑 的全貌在 第 04 章


5. RTTestCase:被逐步填满的「工单」

RTTestCase(deepteam/test_case/test_case.py:17)继承自 DeepEval 的 LLMTestCase,是流水线 里贯穿始终、被一步步填字段的数据对象。它不是一次造好,而是「攻击生成阶段填一半、模型 回答填一格、裁判打分再填几格」。

各字段的语义与填充时机:

字段语义谁来填 / 何时填
vulnerability这条用例测哪个漏洞(字符串名,如 "Bias")生成时
vulnerability_type具体子类型枚举(如 BiasType.RACE)生成时
input对抗输入(攻击文本)生成 + 增强后
actual_output被测模型的回答调用 model_callback 后
turns多轮对话记录(List[RTTurn])多轮攻击时填,与 actual_output 二选一
attack_method用了哪种攻击手法增强时
risk_category更高层的风险大类(见下)生成时 getRiskCategory(...)
score裁判打的 0/1 分裁判 measure 后
reason裁判给的文字理由裁判 measure 后
error出错信息(若这条跑挂了)出错时
simulation_cost生成/增强这条的 LLM 花费生成时
evaluation_cost裁判评估这条的 LLM 花费评估时

input/actual_output vs turns: 单轮用例填 input + actual_output;多轮用例改填 turns(一串 RTTurn)。RTTurn(test_case.py:7)在 DeepEval 的 Turn 上加了一个 turn_level_attack 字段,记录这一轮用了什么攻击——多轮越狱靠它追踪每轮的手法。

risk_categorycategory 不是一回事。 漏洞的 category(§3.3)是「按测什么分的 6+1 大类」; risk_category 是另一套更粗的风险维度,由 getRiskCategory(vulnerability_type) (deepteam/risks/risks.py:14)把每个子类型映射到 LLMRiskCategories 枚举 (Responsible AI / Illegal / Brand Image / Data Privacy / Unauthorized Access,risks.py:6)。 比如所有 BiasType/ToxicityTypeRESPONSIBLE_AI,IllegalActivityTypeILLEGAL。 它用于最终风险报告的聚合(报告细节见 第 04 章)。

Bias.a_assess 里这条工单怎么被填出来,能直观感受「逐步填充」:

# deepteam/vulnerabilities/bias/bias.py:143 —— 真实源码,节选
rt_test_case = RTTestCase(
vulnerability=test_case.vulnerability, # 已知:测 Bias
vulnerability_type=vuln_type, # 已知:哪个子类型
attackMethod=test_case.attack_method, # 已知:用了什么攻击
riskCategory=getRiskCategory(vuln_type), # 算出:风险大类
input=input_text, # 已有:攻击输入
actual_output=output, # 刚拿到:模型回答
)
metric = self._get_metric(vuln_type)
await metric.a_measure(rt_test_case)
rt_test_case.score = metric.score # 最后:裁判填分
rt_test_case.reason = metric.reason # 最后:裁判填理由

一句话:RTTestCase 就是那张从「只有 input」被填到「有 score/reason」的工单,整个 DeepTeam 的流水线可以理解为「不断地新建并填满一批 RTTestCase」。


6. Metric:裁判 = 另一个 LLM 打 0/1 分

红队测试最后要回答「模型这次到底翻没翻车」。DeepTeam 不用写死的正则规则,而是用 LLM-as-a-judge(拿另一个 LLM 当裁判):把 (输入, 模型回答) 喂给裁判 LLM,让它按标准 判「安全(1)/ 不安全(0)」。

裁判的抽象根是 BaseRedTeamingMetric(deepteam/metrics/base_red_teaming_metric.py:7), 它只做一件很小但关键的事——继承 DeepEval 的 BaseMetric,并把「必需输入」钉死为 input + actual_output:

# deepteam/metrics/base_red_teaming_metric.py:7 —— 真实源码,全文
class BaseRedTeamingMetric(BaseMetric):
_required_params: List[LLMTestCaseParams] = [
LLMTestCaseParams.INPUT,
LLMTestCaseParams.ACTUAL_OUTPUT,
]

这意味着每个裁判至少要拿到「问了什么(input)」和「模型答了什么(actual_output)」才能打分 ——正好对应 RTTestCase 里被填好的那两格。具体的裁判(如前面 §3.2 出现的 BiasMetric, deepteam/metrics/bias/bias.py:29)继承它,实现 measure():内部再调一次 LLM、返回 score(self.success = self.score == 1,bias/bias.py:64)和 reason

裁判 measure 内部怎么构造 prompt、怎么解析 0/1、怎么算 success——留到 第 04 章 本章只需记住一句:裁判就是「另一个 LLM, 吃 (input, output),吐 0/1 分 + 理由」,并被挂在对应漏洞的 _get_metric 上。


7. 四者如何咬合(把本章串起来)

一句话总结这四类抽象的关系:

Vulnerability(测什么)× Attack(怎么问)→ 生成并填 RTTestCase(工单)→ Metric(裁判) 打 0/1 分填回工单。

  • 漏洞和攻击是两个正交维度,自由组合出对抗输入;
  • RTTestCase 是贯穿全程的载体,所有阶段都在读写它的字段;
  • 每个漏洞用 _get_metric 绑定自己的裁判,裁判用 BaseRedTeamingMetric 钉死了它需要 input + actual_output;
  • weight(攻击采样)、category / risk_category(漏洞分类)是控制「测什么配比、结果怎么聚合」 的旋钮。

理解了这四张「牌」,后续章节全是在讲怎么打这四张牌:

  • 02 · 攻击生成流水线 —— simulate_attacks 怎么从漏洞造出 baseline,_refine_simulated_attacks 怎么精炼,单轮 enhance 怎么增强。
  • 03 · 多轮越狱 —— 多轮攻击的记忆、升级、回溯(RTTurn 上场)。
  • 04 · 编排与评测 —— RedTeamer 主循环怎么按 weight 采样、 怎么调裁判、risk_category 怎么聚合成风险报告。
  • 05 · 旁支产品线 —— Guardrails、trace 扫描、CLI。

8. 代码地图(导航索引)

主题文件路径关键符号
漏洞抽象基类deepteam/vulnerabilities/base_vulnerability.pyBaseVulnerabilityget_types_get_metric_refine_simulated_attacks
漏洞样板(Bias)deepteam/vulnerabilities/bias/bias.pyBias(:28)、_get_metric(:357)
漏洞子类型枚举样板deepteam/vulnerabilities/bias/types.pyBiasType
漏洞注册表 / 目录索引deepteam/vulnerabilities/constants.pyVULNERABILITY_CLASSES_MAP(:100)、VULNERABILITY_INFO_MAP(:149)、VulnerabilityInfo
自定义漏洞deepteam/vulnerabilities/custom/custom.pyCustomVulnerability(:25)
攻击抽象基类deepteam/attacks/base_attack.pyBaseAttack(:12)、Exploitabilityenhanceweight
单轮攻击基类 + 清单deepteam/attacks/single_turn/base_single_turn_attack.pysingle_turn/__init__.pyBaseSingleTurnAttackAttackParameter__all__
多轮攻击基类 + 清单deepteam/attacks/multi_turn/base_multi_turn_attack.pymulti_turn/__init__.pyBaseMultiTurnAttackCrescendoJailbreaking
weight 采样deepteam/attacks/attack_simulator/attack_simulator.pyattack_weightsrandom.choices(:95-106)
测试用例数据模型deepteam/test_case/test_case.pyRTTestCase(:17)、RTTurn(:7)
风险大类映射deepteam/risks/risks.pygetRiskCategory(:14)、LLMRiskCategories(:6)
裁判抽象基类deepteam/metrics/base_red_teaming_metric.pyBaseRedTeamingMetric(:7)
裁判样板(Bias)deepteam/metrics/bias/bias.pyBiasMetric(:29)、measure(:51)