第12章:安全围栏 —— 人机协同与风险分级
本章摘要
在第 11 天,我们通过 MCP 协议和工具路由,赋予了智能体“三头六臂”。现在的 AI 不仅能读(Inbound),还能写(Outbound),甚至能联网抓取。它从一个被动的咨询顾问,变成了一个拥有执行力的数字员工。
但是,能力越大,风险越大。
如果 AI 因为幻觉,把“清理废纸篓”理解成了“删除整个项目文件夹”怎么办?如果它在联网搜索时,不小心把你的 API Key 发送给了第三方服务器怎么办?
今天,我们将构建智能体工程中最重要的一道防线:安全围栏 (Safety Rails)。我们将引入 人机协同(Human-in-the-Loop, HITL) 机制,学习如何对工具操作进行风险分级,并亲手编写代码,为那些高风险的“核按钮”操作(如删除文件)加上一道必须由人类亲自开启的保险锁。
基础理论:分级的风险控制模型
在工程实践中,我们不能对所有工具一视同仁。让 AI “读一篇笔记”和“删一篇笔记”,在风险权重上有着天壤之别。盲目的信任会导致灾难,而过度的限制则会扼杀效率。因此,我们需要建立一套分级的风险控制模型。
2.1 隐形的流量过滤器
想象一下,你的智能体内部有一个流量网关,所有的工具调用请求在抵达 Python 脚本之前,都必须经过这个网关的审查。

图12-1:智能体Prompt分级风险控制模型
表12-1:分级风险控制模型 (Risk Grading Matrix)
#fJXTmHzONWwJ text { white-space: pre; }#fJXTmHzONWwJ .fb0 { stroke: var(--N900-FG, 0.15); }#fJXTmHzONWwJ .fb0 {stroke: rgba(31,35,41, 0.15)}#fJXTmHzONWwJ .fx0 { stroke-miterlimit: 10; }#fJXTmHzONWwJ .fx0 {stroke-miterlimit: 10}#fJXTmHzONWwJ .fa1 { fill: rgb(187, 191, 196); }#fJXTmHzONWwJ .fa1 {fill: rgb(187, 191, 196)}#fJXTmHzONWwJ .fx1 { font: 400 12px MonospacedNumber, LarkHackSafariFont, LarkEmojiFont, LarkChineseQuote, -apple-system, BlinkMacSystemFont, "Helvetica Neue", Tahoma, "PingFang SC", "Microsoft Yahei", Arial, "Hiragino Sans GB", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; }#fJXTmHzONWwJ .fx1 {font-style: normal}#fJXTmHzONWwJ .fx2 { text-anchor: middle; }#fJXTmHzONWwJ .fx2 {text-anchor: middle}#fJXTmHzONWwJ .fx3 { dominant-baseline: central; }#fJXTmHzONWwJ .fx3 {dominant-baseline: central}#fJXTmHzONWwJ .fx4 { stroke: none; }#fJXTmHzONWwJ .fx4 {stroke: none}#fJXTmHzONWwJ .fc0 { fill: var(--text-caption); }#fJXTmHzONWwJ .fc0 {fill: #646a73}#fJXTmHzONWwJ .fa2 { fill: var(--text-link-disabled); }#fJXTmHzONWwJ .fa2 {fill: rgb(187, 191, 196)}#fJXTmHzONWwJ .fx5 { font: 700 14px LarkHackSafariFont, LarkEmojiFont, "Microsoft YaHei", -apple-system, BlinkMacSystemFont, "Helvetica Neue", Helvetica, "Noto Sans", Tahoma, "Nimbus Sans L", Arial, "Liberation Sans", "PingFang SC", "Hiragino Sans GB", "Noto Sans CJK SC", "Source Han Sans SC", "Source Han Sans CN", "Microsoft YaHei", "Wenquanyi Micro Hei", "WenQuanYi Zen Hei", STHeiti, SimHei, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji", system-ui; }#fJXTmHzONWwJ .fx5 {font-style: normal}#fJXTmHzONWwJ .fx6 { text-anchor: start; }#fJXTmHzONWwJ .fx6 {text-anchor: start}#fJXTmHzONWwJ .fx7 { dominant-baseline: alphabetic; }#fJXTmHzONWwJ .fx7 {dominant-baseline: alphabetic}#fJXTmHzONWwJ .fc1 { fill: rgb(5, 16, 26); }#fJXTmHzONWwJ .fc1 {fill: rgb(5, 16, 26)}#fJXTmHzONWwJ .fx8 { font: 700 14px 微软雅黑; }#fJXTmHzONWwJ .fx8 {font-style: normal}#fJXTmHzONWwJ .fx9 { font: 400 14px 微软雅黑; }#fJXTmHzONWwJ .fx9 {font-style: normal}#fJXTmHzONWwJ .fx10 { font: 400 14px LarkHackSafariFont, LarkEmojiFont, "Microsoft YaHei", -apple-system, BlinkMacSystemFont, "Helvetica Neue", Helvetica, "Noto Sans", Tahoma, "Nimbus Sans L", Arial, "Liberation Sans", "PingFang SC", "Hiragino Sans GB", "Noto Sans CJK SC", "Source Han Sans SC", "Source Han Sans CN", "Microsoft YaHei", "Wenquanyi Micro Hei", "WenQuanYi Zen Hei", STHeiti, SimHei, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji", system-ui; }#fJXTmHzONWwJ .fx10 {font-style: normal}#fJXTmHzONWwJ .fx11 { font: 400 12px LarkHackSafariFont, LarkEmojiFont, Consolas, -apple-system, BlinkMacSystemFont, "Helvetica Neue", Helvetica, "Noto Sans", Tahoma, "Nimbus Sans L", Arial, "Liberation Sans", "PingFang SC", "Hiragino Sans GB", "Noto Sans CJK SC", "Source Han Sans SC", "Source Han Sans CN", "Microsoft YaHei", "Wenquanyi Micro Hei", "WenQuanYi Zen Hei", STHeiti, SimHei, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji", system-ui; }#fJXTmHzONWwJ .fx11 {font-style: normal}#fJXTmHzONWwJ .fx12 { font: 400 11px LarkHackSafariFont, LarkEmojiFont, Consolas, -apple-system, BlinkMacSystemFont, "Helvetica Neue", Helvetica, "Noto Sans", Tahoma, "Nimbus Sans L", Arial, "Liberation Sans", "PingFang SC", "Hiragino Sans GB", "Noto Sans CJK SC", "Source Han Sans SC", "Source Han Sans CN", "Microsoft YaHei", "Wenquanyi Micro Hei", "WenQuanYi Zen Hei", STHeiti, SimHei, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji", system-ui; }#fJXTmHzONWwJ .fx12 {font-style: normal}#fJXTmHzONWwJ .fb1 { fill: var(--N900-FG, 0.15); }#fJXTmHzONWwJ .fb1 {fill: rgba(31,35,41, 0.15)}#fJXTmHzONWwJ .fb2 { fill: rgb(221, 221, 221); }#fJXTmHzONWwJ .fb2 {fill: rgb(221, 221, 221)}#fJXTmHzONWwJ .fa3 { fill: var(--bg-body); }#fJXTmHzONWwJ .fa3 {fill: #fff}ABCD1234风险等级定义工具示例防御策略L1 低风险区L2 中风险区L3 高风险区自动放行沙箱限制人机协同用户授权(Read-Only)(Sandboxed Write)(Destructive) (追加日记) (新建草稿) (删除) (覆盖) (发送邮件) (Auto-Approve)为了保证流畅的交互体验,此类操作无需人工干预,系统默认允许执行。 (Sandboxing)如第 10 天所学,通过 白名单或目录限制,允许 AI 自由创作,但将其圈定在 或“草稿箱”内,防止污染核心数据。 (HITL)这是最高级别的防御。系统必须挂起 (Suspend),向用户发起 (User Approval)请求,等待人类明确确认后,才能放行指令。只读操作,不改变物理世界状态。增量写入或受限修改,风险可控。不可逆的破坏性操作,或涉及敏感隐私。read_note``list_files``fetch_urlappend_to_notecreate_filedelete_noteoverwrite_filesend_emailALLOWED_FILES/Inbox
🎨 架构可视化描述:
用户指令 -> LLM 大脑 (生成 JSON) -> MCP 路由器 (识别工具名) -> 风险过滤器 (判定 Risk Level): -> 若 L1/L2: -> 放行 -> 执行工具 -> 若 L3: -> 拦截 -> 触发 HITL 弹窗 -> 人类确认 (Y/N) -> (Y)执行 / (N)驳回
2.2 理解人机协同机制
HITL (Human-in-the-Loop) 不仅仅是一个弹窗,它是一种协议层面的协作。

图12-2:Human-in-the-loop决策与执行工作流
在 MCP 架构中,当 Server(服务端)收到高危指令时,它不会立即执行 Python 代码中的逻辑,而是向 Client(客户端)发送一个“采样请求”。此时,你的终端或界面会进入 Pending(等待)状态。 这就像核潜艇发射导弹的流程:大副(AI)可以计算弹道并建议发射,但最终扭动发射钥匙的,必须是艇长(你)。
💡 提示工程技巧:系统提示词中的“交通规则”
在 System Prompt 中,你可以显式定义这些规则,帮助模型自我审查: “对于读取操作,你可以自主执行;但如果涉及删除或修改现有文件,你必须先向用户请求许可,并解释修改的具体内容。”
2.3 安全围栏的两种基础模式:Soft Rails 与 Hard Rails
针对 L3 级高风险区的人机协同 (HITL) 拦截,根据系统防御层级和实现深度的不同,在基础原理上分为 Soft Rails(软围栏) 和 Hard Rails(硬围栏) 两种核心模式:
图12-3:软围栏 vs 硬围栏
2.3.1 Soft Rails (认知层的软围栏)
Soft Rails 是一种基大模型提示词工程和纯对话拦截 (Chat-based HITL)构建的逻辑防线。它本质上是给智能体设定一套严格的多阶段“交通规则”,完全依靠大模型自身的认知理解、指令遵循能力以及上下文记忆 (Context Window) 来实现安全控制。
运作机制:
规则约束:通过在系统提示词中增加“描述示例”(Few-Shot Prompting)或设定 S.C.O.R.E. 执行契约,要求 AI 必须自我审查。
两阶段路由:当涉及破坏性修改时,AI 必须先调用只读工具(如 read_file)将内容加载到内存中处理,并在聊天框内输出修改预览。
状态挂起:展示预览后,AI 会在聊天框主动挂起,等待用户输入纯文本(如“Y”或“N”)进行授权。只有在识别到人类的文本许可后,AI 才会动态路由去调用写入工具。
Soft Rails部署轻量,无需编写底层拦截代码。但由于它依赖于大模型的“自觉性”,一旦遭遇恶意的提示词注入(Jailbreak)或发生严重的 AI 幻觉,这道软围栏仍有被绕过的潜在风险。
2.3.2 Hard Rails (代码级的强制物理围栏)
Hard Rails 是一种操作系统级别的物理拦截网关。它不再盲目信任大模型在聊天框里的“文字承诺”,而是将拦截逻辑下沉到 MCP 协议后端的底层代码中(如 Python 拦截脚本)。
运作机制:
1. 流量劫持:当 AI 试图调用 L3 级高危工具时,指令载荷被路由给底层网关程序。此时,协议层会强制挂起大模型的执行流和底层进程。
2. 系统级物理弹窗:底层脚本利用操作系统的原生 UI 库(如 Tkinter),在屏幕最前端强行召唤出一个图形确认界面。这完美解决了 Cherry Studio 等客户端在 Headless(无头)模式下的交互盲区。
3. 真实物理落盘控制:由于脚本同时接管了文件路径(file_path)和待写入的新内容(new_content),真正的文件 I/O 被死死卡在操作系统的弹窗层。
Hard Rails 是最高级别的“核按钮”防御机制。无论大模型产生怎样的幻觉或尝试跳步,只要用户没有用鼠标物理点击系统弹窗上的“Yes”,任何危险代码都绝对无法在本地磁盘上落盘执行。
实战演练:代码化的安全拦截
本次实验,我们将分别部署软围栏与硬围栏。我们将摒弃将目录路径“写死”的静态做法,采用“动态输入”的真实业务场景:全凭大模型通过多轮对话提取用户输入的目录与文件,并在上下文中保持状态。
图:12-4:实验流程
3.1 纯对话式人机协同 (Chat-based HITL)
3.1.1 实验目标与前置条件
实验目标:
🥇
学会如何使用Cherry Studio、 Cursor 或 Claude Desktop 配合 MCP 协议调用 filesystem 工具进行跨风险层级的文件操作。
在执行 L3 级高风险修改操作前,强制 AI 展示修改预览并获取用户的纯文本授权(基于 Chat-based HITL 模型)。
测试 AI 在客户端环境中保持对话上下文和多轮对话的能力。
前置条件:
Cherry Studio、Cursor或者Claude Desktop 已安装并能够与 MCP 协议配合使用。
目标文件(如会议记录)已存在,并且 filesystem 工具已正确配置,允许读写文件。
3.1.2 设计“四步走”安全修改提示词 (S.C.O.R.E. 软围栏)
为了防止 AI 产生幻觉直接覆盖文件,我们需要在系统提示词中显式定义“交通规则”,构建认知层的软围栏(Soft Rails)。
请在聊天框中输入以下设计的提示词协议:
代码块
Markdown
**S (Role)**: 你是一个运行在本地系统的知识库智能体。你具备基于 MCP 协议读取和修改本地文件的能力,但必须严格遵守系统的安全围栏规范。
**C (Context)**: 我需要你协助我查找特定目录下的文件,并更新某份会议记录的内容。
3.1.3 实验操作流与执行阶段
发送上述提示词后,请按照以下流程与 AI 进行多轮对话交互:
步骤 1:触发目录扫描 (L1 低风险区)
•
用户操作:输入“请扫描我的 /home/wguo/Down;oads.MyVault/Inbox 测试目录。”
•
AI 行为:AI 识别为只读操作(L1 级),自动放行(Auto-Approve)调用 list_directory 工具,输出该目录下的会议记录列表,并主动询问你需要修改哪个文件及具体内容。
步骤 2 & 3:执行阶段一(指定信息、读取与预览)
•
用户操作:输入“修改 会议记录A.md,将日期改为2026年2月16日。”
•
AI 行为:AI 动态路由至 read_file 工具读取原文件内容。随后,AI 在内存中将日期更新为 2026年2月16日,并在窗口中输出修改后的内容预览。
•
挂起等待:AI 停止执行,主动向用户发起用户授权(User Approval)请求,等待你输入 Y 或 N 确认。
步骤 4:执行阶段二(用户纯文本决策与保存)
根据你的风险判断,在聊天框中直接输入纯文本指令:
分支 A:确认写入
•
用户输入:Y(是)
•
AI 行为:AI 确认用户授权,调用 write_file 工具将修改后的内容写入原文件。随后回复:“已成功更新文件。”
•
验证:你可以打开 Obsidian 或本地文件,确认日期已变更为 2026年2月16日。
分支 B:拒绝写入
•
用户输入:N(否)
•
AI 行为:AI 确认用户拒绝修改,拦截指令并取消操作。回复:“操作已取消,文件未做任何修改。”
•
验证:检查文件的“最后修改时间”,确认未发生变化。
3.2 基于 FastMCP 的动态硬围栏与物理拦截 (Dynamic Hard Rails HITL)
3.2.1 实验目标与前置条件
实验目标:
🎨
实现对 L3 级高风险区(破坏性操作)的物理拦截,在执行前强制操作系统弹出 Tkinter 确认窗口。
支持“动态目录与文件指定”,全凭大模型通过多轮对话提取用户输入,保持上下文状态不丢失。
解决工具调用的“伪成功”问题,使拦截网关真正具备物理文件 I/O 的写入能力。
前置条件:
•
客户端环境:Cherry Studio 、Cursor 或者Claude Desktop已正确安装并支持 MCP 协议。
•
Python 环境:已安装 Python 3,并通过 pip install mcp 安装了官方 SDK。
3.2.2 环境与底层代码配置
3.2.2.1 MCP 客户端 JSON 配置
在您的 MCP 配置文件(如 claude_desktop_config.json 或客户端 UI 设置)中,需要挂载两个服务器,分别处理 L1/L2 低风险只读操作和 L3 高风险操作:
代码块
JSON
注1:autoApprove: [] 确保无自动批准特权,强制人机协同拦截。
3.2.2.2 部署物理网关拦截脚本 (note-guard.py)
使用 FastMCP 框架替换传统的进程调用,完美解决启动生命周期问题,并接收 AI 传入的 new_content 实现真实物理落盘。
代码块
Python
from mcp.server.fastmcp import FastMCP
import tkinter as tk
from tkinter import messagebox
import os
# 创建 FastMCP 实例,持续监听客户端 stdio 通信流
mcp = FastMCP("Note-Guard")
# 注册 L3 级高风险操作拦截工具
@mcp.tool()
def safe_modify_or_delete(file_path: str, new_content: str = "") -> str:
"""当执行 L3 级高风险修改或删除操作前,触发底层的安全拦截弹窗。"""
3.2.3 S.C.O.R.E. 安全提示词契约
在对话框中向智能体下发以下指令,构建“认知层+物理层”的双重围栏:
代码块
Markdown
3.2.4 实验交互演示流
•
[第一轮] 扫描目录 (L1 自动放行)
◦
您:“请按照契约开始工作,我要扫描 /home/wguo/Downloads/MyVault/Inbox 目录。”
•
[第二轮] 动态提取与读取预览 (状态保持)
◦
您:“修改 会议记录.md,把日期改为 2026年2月16日。”
◦
AI:(调用 read_file,在内存中完成修改并输出文本预览)“预览如下… 是否确认保存?”此时大模型主动挂起。
•
[第三轮] 触发高风险物理网关 (L3 人机协同)
◦
您:“Y” 或 “确认”。
◦
底层触发:AI 组装 file_path 和 new_content 调用 safe_modify_or_delete。此时 Python 进程挂起,操作系统最前端弹出 Tkinter 警告框。
◦
您的物理裁决:
▪
点击 “Yes” -> Python 代码中的 open(..).write() 执行,文件在硬盘上被真实篡改。AI 在客户端回复“已成功更新文件”。
本章总结
本章建立了智能体工程的安全分级模型。你将操作划分为 L1(自动放行)、L2(沙箱写入)、L3(人机协同)三个风险等级,并通过 Tkinter 弹窗实现了最高风险级别的物理拦截。安全围栏不是限制智能体,而是保障它能被放心使用。软围栏防的是 AI 的无心之失,硬围栏防的是恶意攻击——两者缺一不可。
代码块
Plain Text
💡 能力越大,风险越大——安全围栏不是限制智能体,而是保障它能被放心使用。
下一章预告:安全底座已筑牢,是时候将所有组件组装为完整系统。第13章将进入 MVA 实战——构建一个能自动扫描收件箱、识别核心概念、并在获得人类批准后写入知识库的“笔记数字助手”。
课后思考
流量过滤器的自动化审查
在企业级分级的风险控制模型中,纯粹依赖人类点击弹窗会导致效率瓶颈。如何在系统架构中引入另一个更小、更快的“合规审查模型”(如 Qwen-2.5-1.5B),使其作为独立的判别器去自动化审查主模型的高危请求?
幂等性与竞态条件控制
在复杂的网络环境中,如果用户在系统弹出 Tkinter 窗口时犹豫了 5 分钟,MCP 客户端可能会发生超时重试 (Retry)。你的 safe_modify_or_delete 工具是否具备幂等性 (Idempotency),以防止因为重试导致文件内容被重复覆盖或损坏?
硬围栏的云端适配
我们利用 Tkinter 解决了本地客户端的硬围栏问题。如果未来你的智能体部署在没有操作系统的纯云端 Docker 容器中(无法渲染 GUI),你该如何设计类似于“硬围栏”的物理阻断机制(例如通过钉钉/企业微信的异步审批回调)?
附录1: 核心术语表 (Glossary)