步骤级护栏:从结果过滤到过程控制的LLM安全新范式
很多团队做 LLM 应用安全,都有同一个感觉:过滤器越加越多,但该出的问题还是会出。
原因不复杂。绝大多数护栏是“结果级”的——等模型把完整回答生成出来后,再去做内容审核,判断要不要整段拦截。这种模式在短问答场景够用,但在推理过程比较长、Agent 会调用工具、思维链会逐步演化的场景里,就明显不够了。风险不是在最后一句才产生的,而是在某一中间步骤悄悄出现,再被后续推理不断放大。你拦住了最终答案,但模型已经沿着危险路径走了一段,这个路径本身也会污染后续更长的生成任务。
所以,把护栏从“整个输出”推进到“生成过程中的每一步”,就成了一条非常自然的技术主线。StepGuard 这个方向的标题里有两个关键词特别值得注意:Step-Level(步骤级)和 Scalable Supervision(可扩展监督),再加上 Safety-Utility Balancing(安全与效用平衡)。这三个词基本划出了下一代 LLM 护栏要解决的三个核心问题:在哪个粒度拦截、训练数据从哪里来、以及如何不因为过度安全把模型变成复读机。
这篇文章会围绕这三条主线展开,最后给出可以落到实际业务里的思路和代码骨架。
1. 为什么需要步骤级护栏
1.1 结果级护栏的四个典型问题
先看现在普遍在用的结果级护栏:
- 输入侧做提示词注入检测;
- 输出侧对整段生成结果做关键词匹配、分类器判断或大模型审核;
- 命中高风险就替换整段、拒绝回答,或者交给额外的人工会话流程处理。
这种方式在短问答场景里问题不大,但一旦生成内容变长,四个问题会依次出现。
第一是“发现太晚”。模型已经生成了包含敏感步骤的长文本,即使最后被过滤掉,推理过程中的计算已经消耗了,且很容易出现“前面安全、后面危险”的片段误判。如果是一次长报告生成、代码生成或多轮 Agent 调用,用户可能已经在流式输出里看到了前面一半,后补的过滤只能做到“不让它完整落地”。
第二是“解释性差”。结果级护栏判断的是整段文本,触发后很难定位到底是哪句话、哪个推理步骤导致了拦截。运营人员看到一条“拒绝回答”日志,往往要重新读一遍上下文明白发生了什么,排障效率非常低。
第三是“误伤率高”。长度越长,片段级风险信号越容易被整体概率稀释。模型写了一长段推理,只有中间某一步引用了不可信前提,结果级分类器很可能判断为正常;反过来,如果一步判断有误,又可能因为上下文长导致误杀整个回答。
第四是“无法形成交互闭环”。在 Agent 和工具调用场景里,风险往往不是最后输出,而是中间某个操作:读了一个不该读的文件、调了一个不该调的接口。结果级护栏就算发现了,也已经“晚了一步”,无法回退已经发生的工具调用。
这四点叠加起来,就是很多团队对护栏的真实评价:看起来有,但业务真正出事时指望不上。
1.2 生成过程的“踩雷路径”
更底层的原因是:LLM 的生成是逐步推进的。尤其是在思维链、长文档生成和工具调用流程里,前一步输出的内容会成为后一步的上下文。模型第一步可能只是提出了一个看似合理的假设,第二步开始引用特定数据,第三步已经推导出了明显有问题的结论。
如果只有结果级护栏,相当于你允许一条推导链完整走完,最后一刻才喊停。虽然最终答案被拦住了,但模型这次调用的“状态”里已经产生了有风险的中间产物。在流式输出场景下,用户可能已经看到了这些中间内容;在 Agent 场景下,后续工具调用已经被触发;在训练数据生成场景下,这些危险路径还会成为后续数据的一部分。
步骤级护栏的逻辑,是在这条“踩雷路径”的每一跳上都装一个传感器。风险分数一旦超过阈值,就立即阻止、改写,或者切换到安全的替代路径。
1.3 步骤级护栏改变了什么
从工程视角看,步骤级护栏把四个能力下放到了单步粒度:
- 定位:能精确知道是第几步、哪句话、哪个工具调用出问题;
- 干预:可以对当前步骤做阻止、修正或重试,而不是整段覆盖;
- 追溯:每一步的安全分数都能写成结构化日志;
- 控制:不同业务可以设置不同的步骤阈值,而不是一个全局黑名单。
这四点能力才是“步骤级”三个字真正的价值,也是后续的判别器和监督数据设计要围绕的核心。
2. StepGuard 核心概念:Step 指的是什么
2.1 步骤的几种粒度
要讨论步骤级,先要回答“步骤”到底是什么。从现有大模型应用实践看,至少存在三种常见粒度:
- 语义句子级。把回答拆成若干个语义完整的句子,以句子为单位做安全判断。适合问答、文章生成等文本型任务。
- 推理步骤级。在思维链场景里,把一段推理拆成若干“结论 + 依据”的逻辑单元。适合数学推理、逻辑判断、数据分析。
- 操作动作级。在 Agent 场景里,把一次工具调用、一次检索、一次代码执行看作一个步骤。适合有外部动作的复杂任务。
三者不是互斥的。一个完整系统完全可以同时使用多种粒度:先按动作监控工具调用,再按推理步骤监控思维链,最后按句子监控输出正文。
2.2 StepGuard 把关的位置
从名称和关键词来看,StepGuard 关注的核心是“在生成过程中学习安全判断”,而不是只靠关键词规则。它会有一个安全判别器或风险评分模型,对当前步骤结合历史上下文输出一个安全分数;然后由策略层决定是放行、修正还是阻断。
这里有个容易误解的地方:步骤级护栏不是简单把整段文本切碎后逐段用内容审核 API 过一遍,也不是更细粒度的结果过滤。真正关键的是三点:
- 上下文性。每一步的判断必须基于它前面的所有步骤,孤立的句子可能看起来完全无害,放在推理链条里才是风险。
- 前瞻性。某一步本身安全,但大概率会引导后续步骤走偏,这一步也需要纳入判断。
- 策略分离。模型负责打分,策略层负责决策,二者分开才能灵活调节安全与效用的平衡。
2.3 与其他方式的对比
| 维度 | 结果级过滤 | 简单分段过滤 | 步骤级护栏 |
|---|---|---|---|
| 判断时机 | 生成结束后 | 生成结束后分段 | 每一步生成后立即判断 |
| 是否考虑上下文 | 通常只考虑整段 | 较少考虑 | 考虑完整生成历史 |
| 干预方式 | 整段替换或拒绝 | 删除问题片段 | 阻止/改写/重试/重定向 |
| 可解释性 | 弱 | 一般 | 强,可定位到具体步骤 |
| 实现成本 | 低 | 低 | 较高,需要单独设计判别器 |
| 典型适用场景 | 短问答 | 内容审核 | 长推理、Agent、工具调用 |
从这个表格可以看得很清楚:步骤级护栏不是为了替代所有过滤,而是在结果级和规则级显然不够用的场景里补上关键一环。
3. 可扩展监督:护栏的学习信号从哪里来
3.1 人工标注的瓶颈在哪
要让步骤级护栏做到“学习”,首先需要训练数据。这里最直接的方案是让人工标注每个步骤是否安全。但从实际工程经验看,这个方案很快会遇到三个瓶颈。
一是成本。安全判断不能脱离上下文,标注者必须阅读完整的生成历史,才能判断当前步骤是不是风险。一个人一天能高质量标注的量非常有限。
二是一致性。不同标注者对“什么叫风险步骤”的尺度不一样。同一个步骤,在 A 标注者眼里是潜在攻击,在 B 眼里只是正常的反问。没有统一标准时,标注数据噪声会很大。
三是时效性。LLM 的能力和风险形态在快速变化。今天标注的策略,三个月后可能就不适应新的提示词攻击方式或新的推理模式了。
3.2 可扩展监督的解决思路
可扩展监督(Scalable Supervision)想解决的问题,就是让监督信号不纯粹依赖人类全量打标。从方法思路上看,它通常会组合多种信号来源:
- 人类标注核心样本,比如高风险边界、典型误报样本;
- 用自动或半自动信号给大量步骤打分,比如规则、知识库、外部工具结果、模型自评;
- 用相对偏好而不是绝对评分来训练判别器,比如让模型学会判断“哪一步更危险”;
- 在部署后持续收集真实拦截数据,回流到下一轮训练。
在这种设定下,人类监督的角色从“每天标注一万条”变成“每天校准一百条关键样本”,监督能力才能真正随模型能力一起延伸。
3.3 落地时怎么用这个思路
如果你不打算训练自己的判别模型,可扩展监督思路依然有借鉴意义。最典型的落地是“三级筛选”:
- 先用规则或现成分类器对所有步骤做初筛;
- 把靠近决策边界、分类器不确定性高的步骤送人工复核;
- 人工复核结果再回流到规则阈值调整和模型优化。
这个做法的核心是:不让人工淹没在大量明显安全或明显危险的样本里,而是把精力聚焦在最有信息量的边界样本上。边界样本才是决定护栏质量的关键。
4. 安全与效用的平衡:不是越严越好
4.1 过度安全的代价
很多团队在给护栏调参时,第一反应是把阈值调严。看起来误报可以接受,但实际上,过度安全会带来四类代价:
- 用户体验。正常用户得不到该有的答案,产品会被评价为“不好用”;
- 模型能力退化。护栏如果过度干预正确推理步骤,会让模型在复杂任务上失去流畅推理能力;
- 运营成本。误报样本回到人工处理,误杀率越高,人工成本越高;
- 信任成本。用户发现系统经常给出“无法回答”或内容被改写,慢慢会对系统失去信任。
在步骤级场景里,过度安全的代价更明显。步骤级护栏会在生成中途打断模型,这种“过程性打断”比结果级替换更干扰用户体验,所以格外需要权衡。
4.2 安全与效用怎么平衡
从工程角度,平衡的核心不是追求“安全事故为零”,而是把风险压到可接受范围,同时尽量保留模型的原生能力。常见的做法有四类:
- 阈值管理。在安全分数超过硬性拦截线时才阻断,低于拦截线但高于提示线时只记录或降低置信度;
- 分级干预。高风险步骤直接阻止;中风险步骤重写或换一种表达;低风险步骤放行;
- 分场景配置。开放社区可以更严格,工作台内部工具可以更宽松,同一套判别器跑出不同决策曲线;
- 动态策略。对有明确证据的题目放宽安全限制,对无法验证的高风险请求收紧。
这样做的好处是:安全能力和真实业务是同一套策略层在控制,调节阈值就是调节业务目标,而不必重新训练模型。
4.3 一个值得注意的边界
安全与效用平衡并不总是线性的。有时某类任务本身用途正当,但推理过程容易被误判为风险步骤。这种情况下,需要从产品规则层面引入正反馈机制,而不是在安全分类器上强行开洞。强行降低这类样本的分数,很可能顺带把真正有害的相似步骤也放过去。
5. StepGuard 概念工作流与代码骨架
5.1 整体管线
步骤级护栏的完整管线通常包含五个环节:
- 步骤拆分:把模型输出拆成当前要判断的最小信息单元;
- 上下文组装:把当前步骤和生成历史拼成判断模型需要的输入;
- 风险评分:用安全判别器对当前步骤输出安全分数;
- 策略决策:把安全分数和阈值、业务规则结合起来,决定放行/阻止/改写/重试;
- 干预执行:执行决策,并把结果写入日志。
5.2 概念演示代码
下面用一个最小演示来体现第 3、4 两个环节。这里的代码是接口层面的演示,帮助理解判断逻辑,不是 StepGuard 论文的实际实现。
# step_guard_demo.py from typing import List, Literal Decision = Literal["pass", "rewrite", "block"] class StepGuard: def __init__(self, risk_scorer, threshold: float = 0.72): self.risk_scorer = risk_scorer # 输入:(step, history) -> float self.threshold = threshold def decide(self, step: str, history: List[str]) -> tuple[Decision, float]: score = self.risk_scorer(step, history) if score >= self.threshold: return "block", score if score >= self.threshold * 0.8: return "rewrite", score return "pass", score关键逻辑解释:
risk_scorer可以是任意可调用的风险评分函数:基于规则的、基于小模型的、基于大模型提示词的都可以;- 在“软阈值”区间内,策略选择改写而不是直接阻断,这就是一种安全与效用平衡的体现;
history的传入保证了护栏不是孤立判断单句,而是结合上下文。
5.3 与结果级拦截的对比
再看一个对比例子,理解步骤级和结果级在流程上的关键差异。
# generation_with_guard.py def generate_with_result_level_guard(model, prompt, guard): full_text = model.generate(prompt) if guard.check(full_text): return full_text return "抱歉,无法回答" def generate_with_step_level_guard(model, prompt, guard, step_splitter): history = [] for step in step_splitter(model.stream_generate(prompt)): decision, score = guard.decide(step, history) if decision == "block": return "触发步骤级护栏,生成已终止" if decision == "rewrite": step = rewrite(step) history.append(step) return history这段代码的价值在于把“什么时候检查”这件事变得非常显式。结果级是在完整调用之后做一次检查;步骤级是在每次流式产出之后立刻检查,然后决定下一步怎么走。
6. 落地到业务系统:接入架构与配置建议
6.1 三个常见的接入位置
根据实际业务形态,步骤级护栏可以接入三个不同层次。
第一是模型网关层。所有调用统一经过网关,网关对返回的流式内容按步骤拆分并实时判断。这一层适合平台型产品和 To B 服务,部署一次即可覆盖所有下游应用。
第二是应用层。在 Agent 编排、工具调用、报告生成等具体流程中,按自己的业务逻辑决定步骤边界。这一层灵活性最高,适合业务多样的团队。
第三是数据生产层。在用 LLM 批量生成训练数据或合成数据时,对每一步生成的中间内容做步骤级过滤。这一层能避免把危险推理路径灌进下一轮训练。
6.2 接入配置示例
配置上,建议把“判别器模型”“阈值”“干预策略”全部外置到配置文件,这样后续调参不需要改代码。下面是一份 YAML 配置示例。
# guard_config.yaml step_guard: mode: streaming # 流式拦截 splitter: sentence # 按句子拆分,也可以是 tool_call / reasoning_step risk_scorer: type: classifier model: safety_classifier_v2 timeout_ms: 150 decision: block_threshold: 0.75 rewrite_threshold: 0.60 rewrite_prompt: "请用安全且保留原意的方式改写这一句" audit: log_to: "/data/logs/step_guard" sample_ratio: 0.2说明几个配置项的意义:
splitter决定步骤边界,不同业务可以不同;rewrite_threshold比block_threshold低,这是给“软干预”留的空间;sample_ratio表示拦截日志的抽样率,全量日志在业务量大的时候会非常占存储,但要保证高风险样本全量保留。
6.3 实时系统里的几个关键点
接入实时系统时,有几件事是容易遗漏的。
一是超时必须处理。风险打分再快也是额外网络调用,判别器超时不能让主流程等待。建议设置超时时间,超时后按“放行但要降级记录”处理。
二是流式场景下,拆分和判断都要有缓冲。不能等一个完整句子结束再向用户输出,否则流式体验会被卡住;比较稳妥的做法是维护一个小的句子缓冲池,边输出边判断。
三是审计日志必须结构化。每条日志至少包含:请求ID、步骤序号、步骤内容、上下文摘要、安全分数、决策、模型返回码。没有结构化日志,后面做指标分析和误报复盘都非常痛苦。
7. 效果验证与评估体系
7.1 核心指标
评估步骤级护栏,不能只看“拦截了多少危险内容”,那只是安全侧的一个角度。更完整的指标体系至少包含:
| 指标 | 含义 | 计算公式 |
|---|---|---|
| 步骤安全率 | 安全判断中真正放行的比例 | 安全放行且人工复核无风险 / 总放行数 |
| 危险步骤召回率 | 真正的危险步骤被识别的比例 | 识别出的危险步骤 / 实际危险步骤 |
| 误杀率 | 正常步骤被误判为危险的占比 | 误判步骤 / 正常步骤 |
| 效用保持率 | 有护栏与无护栏时的有效回答质量比 | 有护栏任务成功率 / 无护栏任务成功率 |
| 步骤级延迟 | 单次步骤判断的额外耗时 | 平均判断耗时(毫秒) |
这里的第 4 个指标,效用保持率,是步骤级护栏最容易翻车的地方,评估时必须单独统计,不能只看安全指标。
7.2 最小评估脚本
可以写一个简单的评估脚本,用一批标注好的历史记录算误杀率和危险步骤召回率。
# evaluate_guard.py def evaluate(records, guard): true_positive = 0 total_positive = 0 false_block = 0 total_normal = 0 for item in records: step = item["step"] history = item["history"] label = item["label"] # 0 安全,1 高风险 decision, _ = guard.decide(step, history) if label == 1: total_positive += 1 if decision == "block": true_positive += 1 else: total_normal += 1 if decision in ("rewrite", "block"): false_block += 1 return { "recall": true_positive / total_positive if total_positive else 0, "false_block_rate": false_block / total_normal if total_normal else 0, }这个脚本只做最基本的统计,生产环境建议改用统计工具计算,并用 AB 实验做在线对比。
7.3 怎么判断步骤级护栏是否值得上
判断要不要上步骤级护栏,最终要看三条:
- 你的业务是不是有长链路生成或工具调用?没有的话,结果级过滤可能已经够用。
- 风险是否集中在中间步骤而不是最终输出?如果集中在最终输出,步骤级收益不大。
- 团队是否有能力维护监督数据和阈值策略?步骤级护栏比结果级多了一层策略迭代工作,小团队需要有心理准备。
这三条判断清晰了,再决定投入多少资源,会比盲目跟风稳妥得多。
8. 常见问题与排查思路
步骤级护栏的落地,很多坑是共通的。下面按高频问题整理成一张表。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 生成速度明显变慢 | 每步都调用风险判别器 | 查看判别器平均耗时和调用次数 | 改用更轻量判别器,或对低风险步骤做抽检 |
| 经常在奇怪的地方断句 | 步骤拆分器和任务不匹配 | 查看拆分流和原始输出对比 | 换用针对任务的拆分规则或提示词 |
| 大量正常回答被改写 | 改写阈值设得太高 | 统计误杀样本的分数分布 | 下调 rewrite_threshold 或按场景分档 |
| 危险请求有漏过 | 判别器对新型攻击不敏感 | 分析漏网样本特征 | 补充对抗样本并重新训练或调规则 |
| 拦截日志太多无法排查 | 日志粒度过细或没抽样 | 检查日志量和存储曲线 | 配置全量保留高风险,低风险按比例抽样 |
| 回调函数重复触发 | 对同一步骤多次生成 | 检查流式消息去重逻辑 | 给每步加唯一序号并做幂等处理 |
排查时有一个通用顺序:先查日志,再看拆分,再调阈值,最后看模型。不要一开始就改配置,很多问题其实是日志里能直接看到的。
9. 工程建议、边界与未来方向
9.1 工程建议
整合成九条落地建议。
第一,判别器要和生成模型分离部署。不能因为安全判断卡住生成主流程,建议独立服务并做好降级。
第二,阈值配置做成平台能力。每个业务线都有自己的安全容忍度,平台化配置可以避免改一次需求就发一次版本。
第三,建立误报复盘机制。每一条误报都是一次调整监督数据的机会,建议每周做一次误报样本评审。
第四,初始阈值从松到严。上线首周不妨先观察再收紧,避免一上来就误杀大量正常请求。
第五,步骤拆分要做成可插拔组件。不同任务需要不同拆分方式,写死一种拆分会在新任务上反复返工。
第六,历史上下文要控制长度。步骤级判断依赖上下文,但历史过长既增加判别器负担,也容易引入噪声,建议使用滑动窗口。
第七,对工具调用步骤单独设计规则。工具调用与文本生成的判断逻辑差别很大,不建议共用同一套阈值。
第八,保存步骤历史用于离线回放。这一步对后续调整策略和训练新版本判别器都很有价值。
第九,不要完全依赖自动判断。高风险决策最好保留人工复核位,尤其是可能产生真实损失的场景。
9.2 这个方向的边界
步骤级护栏不是银弹。它仍然依赖判别器的泛化能力,对从未见过的攻击模式,判别器照样可能漏过。它也不能替代产品层面的权限控制、数据脱敏和操作审计。安全是一个体系,护栏只是其中一环。
从材料来看,StepGuard 这个名字强调的是步骤级监督信号学习和安全效用平衡的方法思路。它带来的最大启发,不是某一个具体的拦截规则,而是把安全从“事后审查”变成“过程控制”的思维方式。
9.3 未来值得继续深入的方向
下一步有几个方向值得持续关注:
- 步骤粒度的自适应选择:什么时候按句子,什么时候按操作,需要系统自己判断;
- 判别器的持续学习:如何让安全判别器跟上模型能力更新和新型攻击手法;
- 跨语言与跨模态的步骤护栏:文本之外,图片、语音和视频生成同样需要过程级控制;
- 与推理能力结合的护栏:既保证自由推理,又避免危险结论。
对已经在上手护栏系统的团队来说,现在就是补课的最好时间:先把结果级日志做好,再把步骤拆分器跑通,然后逐步引入过程级判断。这条路不一定短,但方向已经很清楚。
