校准语义筛查安全护栏
语义筛查阈值决定了流量要与你的示例相似到什么程度,AISIX 才会放行或拦截它。合适的分界点取决于所用的 embedding 模型、示例列表、语言和流量,因此在一条策略上有效的取值,不能安全地照搬到另一条策略上。
校准分两个阶段。首先,给有代表性的文本打分,找出候选阈值;然后,在把护栏切换到 block 之前,先观察真实流量上的分数。这个过程还能暴露出那些并没有运行在你以为的位置上、或者 embedding 调用一直在失败的策略。
开始之前,请先用任一种配置方式创建语义筛查安全护栏。在 AISIX Cloud 中,给样本打分期间保 持它处于停用状态,然后用 monitor 模式观察真实流量。开源 AISIX 网关应当从 monitor 模式开始。只要相应的示例列表非空,deny_threshold 和 allow_threshold 就都是必填的,AISIX 不会为新配置的策略提供任何可通用的默认值。
在 AISIX Cloud 中给文本打分
在安全护栏中打开该护栏,展开测试此护栏,粘贴要判定的文本,然后选择运行测试。AISIX Cloud 会对该文本和护栏中已保存的示例做 embedding,然后给出:
- 网关对这段文本会做出的判定(放行或拦截),以及是哪一道关卡拒绝了它;
- 每个示例列表一行,包含实测相似度、配置的阈值和得分最高的示例;如果列表为空,则说明该方向无法拦截;
- 产生这些分数的 embedding 模型。
运行测试需要安全护栏的写权限。只读角色会收到 403,应改用逐请求分数来校准。这道权限保护的是测试结果中包含的示例文本。由于该路由使用 POST,授权会把它归为写操作。
分别给你希望被拦截的文本和你希望被放行的文本打分,然后选择一个能体现两组之间取舍的阈值。这个面板位于护栏的编辑表单上,因此请先保存护栏。它对已保存的配置打分,而不是未保存的修改。
对于输入侧护栏,每次测试一条消息。网关会从最新的消息开始逐条筛查,而把整段对话粘贴进面板会被当作一整段文本做 embedding,分数可能不同。对于输出侧护栏,请粘贴完整 的模型响应,因为输出 hook 会把整段响应当作一整段文本筛查。hook_point 为 both 的护栏,请分别测试请求样本和响应样本。
这个面板测试的是配置,而不是它的部署状态。控制面自己完成 embedding 和比较,不涉及任何网关。只有当已保存的策略处于启用状态、且被测文本会被拦截时,它才会提示该护栏没有任何挂载。因此,没有出现这条提示并不能证明策略已经挂载。
这个面板无法确认某条挂载是否覆盖了特定模型或调用方 API Key,也无法确认网关能否解析到 embedding 模型。此外,embedding 端点必须能从控制面访问到。如果某个模型只能在你的内网中访问,请改用真实流量分数。
面板报告的是护栏会做出的判定。拦截判定并不意味着一条已停用或处于 monitor 模式的策略真的会拦截请求。面板也无法区分被打分的文本来自请求侧还是响应侧。
测试面板仅在 AISIX Cloud 中提供。开源 AISIX 网关没有对应的面板,因此请先设置一个占位阈值并配合 enforcement_mode: monitor,然后完全依据下文的真实流量分数来校准。
观察真实流量上的分数
语义护栏会在它筛查的每个请求上记录实测结果,包括被放行的请求,以及处于 monitor 或 block 模式的策略。仅看 monitor 命中是不够的:只有在护栏本会拦截时才会产生 monitor 命中。如果阈值设得刚好高于它实际收到的分数,就不会有任何 monitor 命中,看上去就像这条护栏从未运行过。
有几类端点只支持输 入 hook。/a2a 会记录分数、强制命中、monitor 命中和跳过原因,但它不解析模型,也不解析 MCP Server,因此只有环境级、API Key 级和团队级作用域的护栏能覆盖到它。
rerank、/v1/embeddings、/v1/images/*、/v1/videos、/v1/audio/speech 和 /v1/messages/count_tokens 同样只支持输入 hook。对于被筛查过的 rerank 请求,即使上游没有提供用量数据,语义分数也足以作为归因依据来产生一条用量事件;而没有被筛查过的、发往这类上游的请求可能不产生任何事件。上面列出的其他端点,每个被分发的请求都会产生用量事件。
排查分数缺失
一个请求没有语义分数,可能有以下几种原因:
- 没有任何语义护栏筛查过它。
- 策略没有覆盖该端点。输出侧护栏不会在只支持输入的端点上运行;模型级作用域的护栏不会在
/a2a、MCP 工具调用或透传路由上运行,因为这些请求不解析模型。 - 该请求是由早于语义分数功能的网关版本记录的。
- 更前面的某条护栏已经拦截了该请求。护栏链会在挂载优先级顺序中的第一次拦截处停止。
- 流式输出超过了
max_buffer_bytes,在语义护栏运行之前就被拒绝了。 - 该行属于一次被取代的重试、故障转移尝试或 ensemble 成员。只有终态行携带分数,而它不一定是显示在最后的那一行。
- 该请求没有可筛查的文本。空文本会被跳过,因此在默认的
text_source: user_messages下,只包含图片的请求不会产生 embedding 调用。 - embedding 调用失败了。当 embedding 模型超时或返回错误时,筛查会在记录分数之前停止。
embedding 失败记录在分数数组之外。请检查与策略模式和失败设置相对应的字段:
| 策略 | 结果 | 记录的证据 |
|---|---|---|
block,失败即拒绝 | 请求或响应被拒绝 | guardrail_enforced_hits 中 action 为 blocked_unavailable,error_type 中带失败标记;控制台在强制命中下显示 check unavailable |
monitor,失败即拒绝 | 流量照常放行 | guardrail_monitor_hits 中 action 为 would_block,reason 为 semantic guardrail evaluation unavailable (…);控制台在 Monitor 命中下显示 would block |
| 任一模式,失败即放行 | 流量在缺少本条护栏判定的情况下放行,后续护栏仍可拦截 | guardrail_bypassed_reason;控制台显示跳过原因 |
请求 hook 使用 fail_open,响应 hook 使用 output_fail_open。两者默认都是失败即拒绝,因此请检查你正在排查的那个 hook 对应的设置。
读取分数字段
在 Logs 中展开一个请求,可以看到语义护栏分数。每一条都标明护栏、hook、示例列表方向、分数、阈值、embedding 模型,以及最接近的那条示例的行号。
对于每个护栏、 每个 hook、每个示例列表,数组中最多只有一条记录。它是对整个请求的汇总,而不是逐条上报被筛查的每条消息。拒绝列表的记录保留最高相似度,也就是最接近被拒绝的那个点;放行列表的记录保留被评估消息中的最低分。评估会在第一个拦截结果处停止,因此更早的某条消息可能分数更低,而拒绝列表的一次拒绝也可能导致放行列表根本没有被评估。
同样的取值也可以在控制台之外获取:
| 位置 | 字段 |
|---|---|
| 请求日志导出(CSV) | guardrail_scores 列 |
| 请求日志导出(JSON),以及 AISIX Cloud Admin API 的用量事件 | guardrail_scores 数组 |
| 网关日志导出器 | 用量记录上的 guardrail_scores;在 Datadog 中为 aisix.guardrail_scores,因为没有 OpenTelemetry 语义约定名称的字段会加上 aisix. 前缀。OTLP trace 不包含该字段。 |
每条记录包含 guardrail_name、hook、direction、score、threshold、matched、top_example_index 和 embedding_model。在两个方向上,matched 都表示 score >= threshold。它描述的是相似度,而不是最终判定:匹配上的拒绝示例会被拒绝,而放行列表则是在没有任何示例匹配时才拒绝。
被筛查的文本和示例文本都不会被记录。top_example_index 是该方向列表中从 0 开始的下标,控制台把它显示为从 1 开始的行号,这样你可以在自己的配置中找到对应示例,而不必把策略文本暴露在日志里。
请求日志的其余部分及其导出方式,参见日志与审计。
解读分数分布
把你打算拦截的文本的分数,与你打算放行的文本的分数放在一起比较。指南中的示例展示了拒绝阈值和放行阈值有何不同;API7 更大范围的实测则说明了为什么得到的取值不能迁移到另一个模型或另一批流量上。
理解指南中的示例阈值
主指南在 text-embedding-3-small 上使用 0.47 的拒绝阈值。API7 是针对该指南的示例列表和探针集测出这个工作点的,它让指南中被放行和被拦截的验证请求分别落在阈值两侧。
同一个取值低于实测的无关流量噪声地板:bge-m3 为 0.480,gemini-embedding-001 为 0.542。把它照搬到这两个模型上,可能会拦截普通流量,并让本应被放行的验证请求失败。
话题放行列表的示例在 text-embedding-3-small 上使用 0.3。针对该列表,实测的客服类请求分数从订单状态问题的 0.309 到配送问题的 0.574 不等;退货和取消类请求分别为 0.427 和 0.452。跑题请求都在 0.180 及以下。示例阈值能放行这些客服探针,但对得分最低的那类请求余量很小。
拒绝阈值和放行阈值承受的压力方向相反。正常流量会把拒绝阈值往上推,以减少误拒;范围内的流量会把放行阈值往下压,以放行更多目标请求。两类流量仍然可能重叠,因此这些并不是硬边界,而且即使使用同一个 embedding 模型,两个阈值也都不能迁移。
关于 API7 的实测
下面的结论来自 API7 于 2026 年 9 月 1 日进行的一次扫描,涉及四家厂商的五个 embedding 模型:OpenAI 的 text-embedding-3-small 和 text-embedding-3-large、Google 的 gemini-embedding-001、阿里的 qwen3-embedding-8b,以及 BAAI 的 bge-m3。API7 用每个模型评估了同一套 250 条探针。这套探针在中英文之间均分,覆盖武器、违禁药物、越狱、PII 泄露和提及竞品五类策略,每一类都使用自己的一份中英双语四条拒绝示例列表。
每条探针属于一个类别。其中五类是策略应当拦截的攻击尝试:近义改写、同意图改写、角色扮演包装、迂回提问,以及发散尝试。发散尝试用完全无关的措辞达到同一目的。另有两类代表普通流量:相关话题上的正常请求,以及无关的正常请求。
每个模型和语言条件下包含 75 条攻击探针(每个攻击类别 15 条)和 40 条正常探针(相关 25 条、无关 15 条)。其余 10 条是对照探针,不计入下面的比率。每份拒绝列表中都有两条示例与自身做了打分,用于确认完全相同的文本能达到约 1.0。
这是一次有明确日期的实测,针对的是特定模型和特定探针集,不是产品保证。模型会变化,你的流量也会有不同的分数分布。其中的规律有参考价值,具体数值则不是可以照搬的阈值。
阈值不能在 embedding 模型之间迁移
第一个对比是噪声地板,也就是一条无关的正常请求在拒绝列表上能达到的最高分。以下结果使用英文拒绝列表和英文探针。
| Embedding 模型 | 无关请求最高分 | 相关但正常的最高分 | 攻击分数中位数 |
|---|---|---|---|
text-embedding-3-large | 0.198 | 0.462 | 0.487 |
text-embedding-3-small | 0.228 | 0.498 | 0.448 |
qwen3-embedding-8b | 0.459 | 0.657 | 0.664 |
bge-m3 | 0.480 | 0.629 | 0.600 |
gemini-embedding-001 | 0.542 | 0.676 | 0.731 |
噪声地板跨越 0.344,从 0.198 到 0.542。0.55 这个阈值在 text-embedding-3-large 上高于所有正常探针;同一个取值在 gemini-embedding-001 上会拒绝 55% 的正常探针,因为那里相关的正常流量最高能到 0.676。正因如此,AISIX 会在每个分数旁边记录 embedding 模型。
调整阈值同样无法把攻击尝试和同话题的正常流量完全分开。在其中四个模型上,44% 到 57% 的攻击探针得分不高于那条得分最高的相关正常请求:
| Embedding 模型 | 得分不高于最高相关正常探针的攻击探针占比 |
|---|---|
bge-m3 | 57% |
text-embedding-3-small | 56% |
qwen3-embedding-8b | 49% |
text-embedding-3-large | 44% |
gemini-embedding-001 | 12% |
在这四个模型上,低到足以拦住大部分攻击尝试的阈值,也会拒绝一部分正常的相关流量。请有意识地做出这个取舍,而不是去寻找一个能消除重叠的分界点。
用流量所使用的语言编写示例
同一份拒绝列表面对另一种语言的流量时分数可能更低,差异幅度取决于模型。下表是三种条件下的攻击分数中位数:英文列表对英文探针、同一份英文列表对中文探针,以及中文列表对同一批中文探针。
| Embedding 模型 | 英文列表,英文流量 | 英文列表,中文流量 | 中文列表,中文流量 |
|---|---|---|---|
text-embedding-3-small | 0.448 | 0.362 | 0.464 |
text-embedding-3-large | 0.487 | 0.378 | 0.463 |
qwen3-embedding-8b | 0.664 | 0.557 | 0.660 |
bge-m3 | 0.600 | 0.609 | 0.648 |
gemini-embedding-001 | 0.731 | 0.705 | 0.746 |
这种下降在部分模型上会改变判定结果。text-embedding-3-large 的中位数从 0.487 降到 0.378,而它的工作点是 0.43;qwen3-embedding-8b 从 0.664 降到 0.557,工作点是 0.63;text-embedding-3-small 起点就只有 0.448, 对应阈值 0.47,之后进一步下降。在这套探针上,bge-m3 和 gemini-embedding-001 接近与语言无关,bge-m3 的中位数还略有上升。
用流量自身的语言编写示例可以把损失补回来:第三列在三个模型上超过了第一列,另外两个模型的差距也在 0.024 以内。请为调用方使用的每一种语言都写上拒绝示例。AISIX 不会检测请求语言,也不会在拒绝列表不再匹配该语言时发出警告。
按尝试类型衡量覆盖率
下表按攻击类别给出每个模型在各自工作点上的召回率。这个工作点是在 0.01 的网格上,拒绝该模型 40 条正常探针中不超过 5% 的最低阈值。每个单元格代表 15 条攻击探针,使用英文列表和英文流量。
| Embedding 模型 | 阈值 | 近义改写 | 同意图 | 角色扮演 | 迂回 | 发散 |
|---|---|---|---|---|---|---|
text-embedding-3-small | 0.47 | 100% | 53% | 27% | 33% | 20% |
text-embedding-3-large | 0.43 | 100% | 67% | 40% | 67% | 40% |
gemini-embedding-001 | 0.67 | 100% | 93% | 87% | 93% | 73% |
qwen3-embedding-8b | 0.63 | 100% | 73% | 40% | 60% | 20% |
bge-m3 | 0.61 | 100% | 67% | 40% | 27% | 13% |
在这套探针中,每个模型都拦下了全部近义改写。五个模型中有四个,对于用无关措 辞达到同一目的的尝试只拦下了 13% 到 40%。embedding 相似度衡量的是文本之间有多接近,因此重要策略不应只依赖语义护栏。请为已知的字面表达搭配关键词护栏,并为相似度示例无法表示的类别搭配专门的内容审核或注入检测服务商。
升级到 1.0.0 时复核已有安全护栏
AISIX Cloud 1.0.0 的迁移会保留显式设置的阈值。此前没有设置阈值的策略,会为每个非空示例列表得到 0.75,也就是它此前实际执行的取值。
阈值或 timeout_ms 等其他具体字段上写着 JSON null 的策略,行为则不同。网关无法加载它,因此它并没有筛查任何流量,尽管控制台把它显示为生效中。只有当改写后的配置确实能在网关上加载时,迁移才会移除这类可修复的 null 值。被成功修复的策略会恢复工作,并可能开始拒绝此前它并未筛查过的流量。
迁移不会猜测缺失的策略内容。当 embedding_model、deny_examples 或 allow_examples 为 null、示例列表格式错误,或者改写后的配置仍然无法加载时,它会保持该行不变。启动日志会指出哪些行需要由运维人员补充取值。
把 On-Premises 控制面从 1.0 之前的版本升级到 1.0.0 之前,请先找出那些已启用、且存储配置中含有 null 值或格式错误字段的语义护栏。在修正它们之前或开始升级之前,先停用每一条受影响的策略。在策略仍处于启用状态时保存修正后的配置,可能会让它立即重新投入使用;恢复生效并不会等待迁移完成。不要用 0.75 作为判别标志,因为未受影响的历史策略本来就可能带着这个值。示例列表为空时,被修复的阈值会被移除,而不是被设成某个数字。
控制面会在启动时尝试这次一次性迁移。如果整次迁移失败,失败会被记录并在下次启动时重试,同时控制面继续提供服务。在一次成功的迁移中保持不变的行,不会被自动重试。滚动升级期间,较旧的控制面副本仍可能在迁移完成之后接受一个显式写入新 null 的请求,那一行就落在已完成的这轮清扫之外。
升级到 1.0.0 之后,请检查启动日志,看迁移重新激活了哪些策略、又保持哪些策略不变。如果某条已启用的策略被重新激活,请先停用它再测试。在策略处于停用状态时,用测试面板给有代表性的文本打分;然后把它设为 monitor 并启用,观察真实流量,之后再恢复 block 模式。
对于保持不变的策略,测试面板会返回 409,并指出它能够加载到足以诊断的那部分无效配置。如果是 embedding 模型引用无法读取或无法解析,则返回 404,提示未找到该语义护栏或其 embedding 模型。请在补充缺失取值并保存修正后的配置期间,保持该策略停用;然后按同样的测试与 monitor 流程操作,再恢复强制拦截。
开源资源文件在升级时不会被改写。在 1.0.0 及以后的版本中,有示例但没有对应阈值的语义护栏无法通过校验,整个文件都会被拒绝,直到每个受影响的条目都补上必填阈值为止。网关不会以这样的文件启动,正在运行的网关也会拒绝一次无效的重载。
后续步骤
选定候选阈值之后,请用调用方可见的请求验证语义筛查安全护栏,并在流量变化时持续观察分数分布。挂载优先级、执行模式和失败策略参见安全护栏行为。