PII 信号
概述
pii 定位请求中的个人数据,并给出每个 span 的确切字符。它提出 pii 预设问题,即基于 11 个个人数据标签的 span 问题,并读取完整输入。当模型找到规则计入的标签的 span 时,规则匹配;masking 插件用占位符替换同样的 span。
主要优势
- 让隐私敏感的路由变得明确:把这类请求留在私有模型上,或拒绝它们。
- 返回位置,因此遮蔽只替换真正的个人数据。
- 可以忽略路由接受的标签,例如电子邮件地址。
- 按要求读取工具结果和之前的轮次。
解决什么问题?
没有个人数据信号时,敏 感流量可能在被发现之前就到达错误的模型或插件链。临时过滤器让策略难以审计。pii 把个人数据检测变成可复用的路由输入。
何时使用
在以下情况使用 pii:
- 请求可能携带受监管或敏感的个人数据;
- 有些标签可以接受,另一些必须走更安全的路由;
- 决策在提供方看到请求之前遮蔽个人数据;
- 工具结果可能把个人数据带回对话。
配置
recipes:
- name: default
signals:
pii:
- name: personal_data
predicate: {gte: 0.85}
ignore_labels: [EMAIL_ADDRESS]
input:
history: true
- name: tool_result_pii
input:
tool_results: true
request: false
decisions:
- name: keep-private
priority: 200
rules:
operator: OR
conditions:
- {type: pii, name: personal_data}
- {type: pii, name: tool_result_pii}
modelRefs:
- model: private-model
| 字段 | 含义 |
|---|---|
name | 规则名称,供 {type: pii, name: ...} 条件和 masking 插件的 signal 使用 |
predicate | span 的最低概率;默认为模型的校准阈值(vela3-fast 上为 0.18),否则为 0.5 |
ignore_labels | 规则永不计入的标签 |
input.history | 同时读取之前的用户轮次 |
input.tool_results | 同时读取请求中的工具结果 |
input.request | 设为 false 时不读取当前用户轮次,规则只读取它指定的之前用户轮次或工具结果 |
on_unscanned | 长输入只读了部分窗口时规则报告什么:best_effort(默认)、match、no_match 或 unknown |
min_coverage | 使用 best_effort 时,至少要读完的窗口比例(0 到 1),达到后才根据已读窗口判断;低于该比例时规则为未知。默认关闭 |
on_unknown | 模型没有给出答案时规则报告什么:match(默认)或 no_match |
deployment、timeout_ms | 回答问题的部署,以及答案的截止时间 |
规则的 predicate 是策略:请为你使用的模型校准这个值,或者省略它以使用模型的校准阈值。vela3-fast 的 span 适配器在返回 span 之前会应用它自己按长度设定的阈值,所以它的 0.18 会接受它返回的每个 span。
读取什么
问题读取当前用户轮次;设置 input.history 时读取之前的用户轮次,设置 input.tool_results 时读取文本工具结果; 工具调用和非文本内容不会被读取。它还会读取 global.router.routing_input 从路由文本中移除的内容,所以个人数据无法藏在伪造的记忆块里。
编码智能体的用户轮次带有其运行框架(harness)自身的指令:例如 Claude Code 2.1.317 会在第一条用户消息的提醒中放入一行带邮箱地址的提交尾注(commit trailer)。针对智能体工具返回内容的规则应同时设置 input.request: false 和 input.tool_results: true,如上面的 tool_result_pii,这样它只读取工具结果,不读取当前轮次及 routing_input 从中移除的内容。在请求带有工具结果之前,这样的规则不会被询问,也不会匹配。
它读取完整输入。超过部署一个 scan_window 的输入被切成相互重叠的窗口,先读结尾,再读开头,然后按二分读中间;每个窗口的 span 都按其在原输入中的位置计入,因此脱敏会替换每一处。参见长输入。如果截止时间在所有窗口读完之前到达,且已读窗口中没有发现个人数据,由 on_unscanned 决定:best_effort(默认)根据已读窗口作出判断(规则设置了 min_coverage 时,需读完该比例的窗口,低于该比例时规则为未知),match 把请求按个人数据计入(严格模式),no_match 不计入,unknown 交给 on_unknown。已读窗口中发现的个人数据无论 on_unscanned 如何设置都会计入。masking 插件始终需要读完所有窗口:无法完整读取的文本不会被发送。
模型可能只找到某个值的一次出现,而漏掉后面的副本。所找到的、长度至少两个字符的值,其其他所有完全相同、整词匹配的副本也会以同一标签报告,因此遮蔽会替换每一个副本。
模型没有给出答案时
没有任何窗口得到答案(模型失败、过载或错过截止时间)的规则会匹配,匹配类型为 unavailable:失败即拦截,决策的 rules.on_unknown 仍然决定处理方式。若要放行这类请求,写 on_unknown: no_match:
recipes:
- name: default
signals:
pii:
- name: personal_data
on_unknown: no_match
decisions:
- name: keep-private
priority: 200
rules:
operator: AND
conditions:
- {type: pii, name: personal_data}
modelRefs:
- model: private-model
回答问题的模型
除非 pii 绑定或规则的 deployment 指定了其他部署,否则由决策模型回答所有 PII 规则;见选择部署。该问题需要 span:解析到不支持 span 的部署(例如 d3 模型)的规则会以 span_required 加载失败,除非该部署在 capabilities 中声明了 pii 预设。vela3-fast(默认)、Vela 3.0 的 d3 规格和 Vela 2.0 用它们自己的 PII 问题回答,并与该请求针对同一文本的其他问题放在同一次调用中。
global:
model_catalog:
deployments:
d3-remote:
provider: systemone
endpoint: https://systemone.example.com/v1/systemone
model: vllm-sr/d3-ultra
schema: vllm-sr/5.0
auth: {api_key_env: SYSTEMONE_API_KEY}
system:
decision_model: {deployment: d3-remote}
bindings:
pii: primary
这个信号是路由控制,不能替代脱敏、加密、访问控制或数据防泄漏。完整示例见 config/fragments/signal/pii/strict.yaml。