校准语义筛查安全护栏
语义筛查阈值决定了流量要与你的示例相似到什么程度,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 服务器,因此只有环境级、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 对应的设置。