别让 AI 自己决定"要不要发出去":我给 AI 系统设计的三层风险兜底
「AI 应用工程实录」系列 · 02 当 AI 判断错了,什么东西能拦住它?
一个让客户睡不着的问题
做 AI 客服、AI 审核、AI 排查这类系统时,客户最怕的从来不是"AI 不够聪明",而是------
"万一 AI 判断错了,把一个不该自动发的东西发出去了,怎么办?"
这个担心是对的。LLM 会幻觉,会把错的说得理直气壮。如果你让 AI 既做判断、又做执行,一旦它判断错,没有任何东西拦得住它。
所以我在设计这类系统时守一条线:风险判断和不可逆操作之间,必须有兜底。 在我做的一个客服工单系统里,这条线落地成了三层。下面用真实代码讲每一层在拦什么、为什么这么设计。
背景:这个系统在做什么
工单进来后,流程是:分类 → 路由到专职 Agent 处理 → 生成回复草稿 → 风险审查 → 高危走人工确认、低危自动发送。
关键的分叉点在"风险审查":这条回复到底能不能直接发给客户? 这个判断错了,后果最严重------把一条涉及生产事故的错误回复自动发出去,是不可逆的。三层兜底就是围绕这个判断展开的。
第一层:硬规则兜底,不把高危判断交给 LLM
最关键的认知是:越不可逆的判断,越不敢只信 LLM。
"这条回复要不要人工确认",我没有一上来就问 LLM。而是先用代码硬规则扫一遍关键词:
python
def detect_rule_risk(state):
text = f"{state['ticket']}\n{state.get('reply_draft') or ''}"
high_risk_keywords = ["500", "生产", "故障", "数据丢失", "权限", "支付", "订单", "合同", "赔偿"]
matched = [kw for kw in high_risk_keywords if kw in text]
return {
"requires_human": bool(matched),
"risk_level": "high" if matched else "low",
"matched_keywords": matched,
}
逻辑很笨:只要工单或回复里出现"生产""500""赔偿""数据丢失"这类词,不管 LLM 怎么想,直接标记高危。
为什么要这么笨?因为如果风险判断纯靠 LLM,它完全可能幻觉说"这条没风险",然后系统就把一条涉及生产事故的回复自动发出去了。硬规则不聪明,但它确定、可复现------同样的输入永远给同样的结果,这是 LLM 给不了的保证。它是最后一道不依赖模型的防线。
对比一下同系统里的另一个判断------工单"优先级"(P0~P3)我就纯用 LLM,没加硬规则:
python
# 优先级:纯 LLM 判断,代码只校验合法性
priority = parsed.get("priority")
state["priority"] = priority if priority in valid_priorities else "P2"
为什么区别对待?因为出错后果不对等 。优先级判错,顶多排期慢一点,可挽回;高危判错,会把事故回复自动发出去,不可逆。兜底策略应该和"出错代价"挂钩,而不是一刀切。 风险越高、越不可逆的判断,才越值得用代码兜底。
第二层:LLM 判断,但结构化 + 保守失败
第二层才轮到 LLM,让它综合语义做更细的风险评估。但有两个约束。
约束一:强制输出 JSON,代码来决策,不信自由文本。
python
# prompt 要求模型只输出 JSON:
# {"requires_human": true, "risk_level": "high", "reasons": [...], "suggestion": "..."}
review_json = safe_json(llm_output)
约束二:解析失败时,保守地走人工。
python
if not review_json:
# LLM 返回的 JSON 解析不了 → 不赌,直接转人工
review_json = {"requires_human": True, "risk_level": "high",
"reasons": ["审查 JSON 解析失败"], "suggestion": "请人工复核"}
最终决策是"硬规则 OR LLM 判断,任一为真就走人工":
python
state["requires_human"] = bool(rule_risk["requires_human"] or review_json.get("requires_human"))
保守失败 是这一层的核心:不确定的时候,永远选风险更低的那条路。JSON 解析失败本可以直接报错或默认放行,但我选择默认转人工------宁可多让人看一眼,绝不漏放一个高危。这个"默认往安全方向倒"的原则,比"追求自动化率"重要得多。
三层:Human-in-the-loop,人保留最终控制权
前两层筛出"高危"之后,第三层是真正的人工卡点。高危工单不自动发,而是挂起流程、等人处理:
python
def human_review_node(state):
# 展示草稿和风险原因,等人输入
choice = input("你的选择 (a 批准 / e 修改后批准 / r 拒绝): ")
if choice == "e":
state["final_reply"] = state["reply_draft"] + "\n\n" + input("补充内容:")
elif choice == "r":
state["final_reply"] = None # 拒绝,不发送
else:
state["final_reply"] = state["reply_draft"]
这里的关键是流程真的挂起了 ------input() 会阻塞,直到人做出决定。在真实系统里,这一步等价于"把状态存进数据库、前端展示待办、人在后台点击、流程从断点恢复"。所以这类系统的状态必须是可序列化的,能存下来、之后接着跑。
同样的思路我还用在权限设计 上:AI 可以自动执行只读查询,但任何写操作 (改配置、建工单)必须人工确认。道理一样------读是可逆的,写是不可逆的,不可逆的交给人。
值得强调的是:HITL 不是"AI 做不了就找人",而是把人工判断设计成流程的正式一环。它还会产生新数据------审核人、动作、修改内容------这些能反哺 prompt 优化和评估。
三层怎么配合
AI 生成回复
↓
① 硬规则扫关键词 ──命中──┐
↓ 未命中 │
② LLM 风险评估 ──高危──┤(解析失败也算高危)
↓ 低危 ↓
自动发送 ③ HITL 人工确认 → 批准 / 修改 / 拒绝
要让一条高危回复被错误地自动发出去,得三层同时失效:硬规则没命中关键词、LLM 判断为低危、且解析没失败。概率极低。这就是分层防御的意义------单层都可能漏,但叠起来就很难穿透。
面对"你能保证不出错吗"
客户问这句,我从不说"能"。任何 AI 系统都有出错概率,说"保证不出错"要么外行、要么不诚实。
我会说:出错概率极低,而且即使出错,我有日志能定位是哪一层失守、能把这个 case 变成新的硬规则关键词,保证不犯第二次。 客户要的从来不是"零险"的空话,而是"你有没有能力管理风险"。
三层兜底,加上"出了错能定位、能补、能防复发",就是这个问题的完整答案。
本文是「AI 应用工程实录」系列第 2 篇。文中的系统设计基于真实项目经验,已做脱敏处理,讲的是可复用的设计思路而非具体实现。
系列其他文章:
- 01 · 从手写 RAG 到理解 LangChain:为什么我先绕了"远路"
- 03 · 48h 做一个 RAG 知识库:一个 FDE 的取舍笔记
- 04 · 微调 embedding 实录:正例拉近、负例推远,以及没学好的那一对