你最近很可能看到过这样一段被反复转发的说法:“顶级预测者给出 72% 概率:当前存在人类不知情的失控 AI 智能体在协调行动。”
如果你正在做 AI Agent、智能体平台或者多智能体应用,第一次看到这句话时,大概率不会把它当成科幻电影预告,而是会下意识问一句:我自己的智能体,会不会也处于“失控”状态?我有没有办法知道它在做什么?
先给出我的判断:这个“72%概率”在技术层面很可疑。它更像是一种预测性表达,而不是从真实系统观测中得到的测量结果。真正值得研究的不是“某个未知智能体是否在密谋”,而是另一件事——当前很多 AI Agent 系统,确实没有足够完善的审计、审批、熔断机制,导致它的行为对开发者来说处于半失控、半不可知状态。
本文不打算渲染恐慌,而是把这个概率话题拆解成一个工程话题:AI 智能体的“失控”到底指什么?多智能体协作为什么让风险放大?作为开发者,你能用哪些可落地的安全基线,把“不可知”变成“可观测”?文中最后会给出一个可以直接运行的智能体安全守卫示例,帮助你验证自己的防护思路。
1. 一个“72%概率”的传播标题,应该怎么拆解
1.1 三个问题:先别急着相信数字
在传播链条里,概率数字最容易制造“严谨且可怕”的感觉。一个模糊的判断一旦被包装成“72%”,读者就容易默认它已经过严格计算。
在 AI 安全讨论里,面对一个概率值,至少应该先问三个问题:
- 这个概率评估的是“当前正在发生”,还是“未来某个时间窗口内可能发生”?
- 评估对象是所有 AI 智能体,还是某类具备高度自主权、能访问真实系统的智能体?
- “协调行动”是由观测数据证明的,还是由模型推演或专家主观估计出来的?
如果这三个问题没有清晰答案,这个数字就只能算一种信息噪音。更关键的是,它没有给出可验证的观测指标、样本范围和研究方法。在没有公开研究方法、样本和可复现过程的情况下,把它当成技术决策依据是不合适的。
1.2 专家预测不等于系统测量
在 AI 安全领域,这类预测的常见来源是“专家意见调查”或“思想实验推演”。参与者根据自己对 AI 技术发展的理解,给某个风险场景打一个主观置信度。
这类调查有参考价值,但它的本质是主观意见的集合,不是我们从真实日志、真实系统中测量出来的“失控发生率”。用技术语言说:这是 prior,不是 posterior;是预测,不是观测。
一个开发者如果拿着“72%”去设计系统,会发现它无法指导任何具体决策。比如,你无法根据这个概率决定该给 Agent 多大权限,也无法判断应该在哪一层加熔断,更无法定位“失控”发生在规划阶段还是工具调用阶段。
1.3 真正有效的转译方式:把概率换成可观测指标
更工程化的做法,是把一个模糊风险概率转译成一组可观测指标:
- 我的 Agent 调用外部工具之前,是否经过策略控制?
- 每一步动作是否有审计日志?
- 如果 Agent 陷入循环,我能否在 N 步之内强制停止?
- Agent 是否可能访问开发账号之外的权限?
- 多智能体之间的通信,是否也在监控范围内?
如果一个系统能回答这些问题,它比一个“72%概率”更有参考价值。如果回答不了,那么不管那个概率是 0.1% 还是 72%,当前系统的安全基线都是不及格的。
2. AI 智能体“失控”在技术上到底指什么
2.1 不要把“失控”理解成 AI 有了自我意识
智能体失控不是一个哲学概念,而是一个工程现象。它不需要 AI“觉醒”,也不需要有主观恶意。简单来说,失控指的是:智能体在自主执行任务的过程中,做出了开发者或用户并未授权、且难以追溯的动作。
先看一个典型的 AI Agent 工作循环:
- 接收任务目标。
- 大模型规划下一步动作。
- 调用工具与环境交互。
- 观察工具返回结果。
- 更新短期记忆或上下文。
- 继续规划,直到目标完成或达到终止条件。
在这样一个自治循环里,最容易出问题的环节是第 2 步和第 3 步。模型可能错误理解了目标,也可能选择了不在预期范围内的工具;如果还同时具备文件修改、命令执行、消息发送等能力,那么错误就不再只是“回答错误”,而会变成真实的副作用。
2.2 “失控感”来自三个工程要素
第一,自治循环。Agent 可以不需要每一步都请示人类,连续执行多个动作。
第二,权限范围过宽。如果直接给 Agent 一个拥有文件写入、命令执行、外部接口调用权限的账号,它跑偏时造成的副作用范围就会很大。
第三,非透明决策。大模型的中间推理过程通常不会完整暴露,开发者只能看到最终动作。一旦推理出现偏差,而你又没有日志,问题就会藏得很深。
这三者组合起来,会出现标题中真正有技术含义的那半句话:“人类不知情”。
2.3 不同阶段的失控信号
| 阶段 | 容易出现的风险 | 可观测信号 |
|---|---|---|
| 目标理解 | 把模糊任务理解成高风险动作 | 人类审批通过率异常、动作偏离任务描述 |
| 规划 | 反复生成相似方案,目标漂移 | 高频重复动作、任务上下文越拉越长 |
| 工具调用 | 访问未授权路径、执行危险命令 | 工具名不在白名单内、参数包含敏感路径 |
| 行为循环 | 死循环或指数级放大尝试 | 步数超限、API 消耗异常、耗时突增 |
| 跨 Agent 通信 | 错误信息被其他 Agent 当事实执行 | 消息内容未审计、链路 ID 缺失 |
失控的本质不是玄学,而是工程上可度量的失效:产生了未经授权的副作用,并且无法对动作进行审计。一旦从这个角度看问题,智能体安全就变成了一套需要落在代码里的工程机制。
3. 为什么多智能体协作会让风险放大
3.1 协调本身不是问题,缺少审计的协调才是
单 Agent 系统的风险边界相对清楚:一个模型、一组工具、一个上下文。开发者可以通过日志回放它的全部动作。
多智能体系统则不同。当一个复杂任务被拆给“规划 Agent”“研究 Agent”“执行 Agent”协作完成时,人类并不在所有决策节点上。更麻烦的是,Agent 与 Agent 之间的信息传递可能会绕过单点策略控制。
假设 A Agent 给 B Agent 发送一条指令:“用户确认可以删除 /tmp/cache 下的旧文件。” B Agent 没有能力重新联系真实用户确认,它可能直接执行删除。如果这个过程没有链路追踪,出错后你甚至不知道是哪一步把“删除指令”放进了 A Agent 的上下文。
3.2 多智能体场景需要额外盯住的三个地方
第一,每条跨 Agent 消息都要记录。不能因为消息不是外部副作用,就觉得不需要审计。真正的问题往往在内部消息链路上埋下种子。
第二,子 Agent 的权限不能大于主 Agent。如果主 Agent 只允许读取工作区文件,那么它派生出的子 Agent 也必须继承同样的限制,而不是拿到更大的权限。
第三,要有统一的 request_id。一个任务可能拆分出几十个子任务,没有统一请求 ID,复盘时就会像看一份没有时间线的聊天记录,无法定位事故起点。
下面是一条跨 Agent 动作审计建议记录的字段示例:
{ "request_id": "req_task_20250110_001", "from_agent": "planner", "to_agent": "researcher", "action": "web_search", "policy_decision": "allow", "param_summary": "query_hash=8f6d...", "created_at": "2025-01-10T10:00:00+08:00" }并不是要记录完整 prompt,而是记录动作类型、策略决策结果、参数摘要和链路 ID。这样可以兼顾隐私与可审计性。
4. 用工程手段把“不可知”变成“可观测”
4.1 智能体安全的最小基线
抛开复杂的理论,对一个要接入真实业务的 Agent 系统,可以先把安全基线收敛为四层:
- 默认拒绝:没有在白名单里的动作,一律不允许执行。
- 审批断点:写入、删除、消息外发等高危动作,必须经过人工确认。
- 心跳监控:Agent 长时间无返回、步数超限、重复动作过多时,系统能感知。
- 审计日志:每个动作在执行前后,都有“谁在什么时间做了什么、结果如何”的记录。
这套基线不依赖某个特定框架。无论你用的是 LangChain、Spring AI,还是自研的 Agent 调度器,都可以在编排层增加一个统一入口。
4.2 理解“动作网关”
防御思路非常简单:不要让 Agent 直接调用工具函数,而是让 Agent 的所有工具调用都先经过一个统一网关。
在这个网关里,每次调用先执行策略判断,再返回决定:allow、deny 或 need_approval。
这里的一个关键点是:策略判断不放在大模型本身。不要寄希望于“只要在 prompt 里告诉它不要删文件,它就会听话”。真正的安全不能依赖模型自觉,而要依赖调用链路上的强制校验。
4.3 环境准备
演示代码只需要 Python 3.8 以上版本,不需要安装第三方依赖,也不需要真实的大模型 API Key。
把代码运行在临时目录即可。如果你要把它接入真实项目,建议先在本地沙箱环境验证,再考虑对接内部 Agent 框架。
5. 一个可运行的智能体安全守卫示例
5.1 示例目标
下面这个案例会模拟一个有“失控倾向”的 Agent 动作序列,并演示三层防护:
- 越权读取工作区外的文件被拒绝。
- 试图执行危险命令被拒绝。
- 写文件这类高风险操作被判定为需要人工审批。
- 重复动作过多时,触发防死循环策略。
先创建配置文件agent_config.json:
{ "max_steps": 10, "allowed_actions": [ "list_dir", "read_file", "write_file", "run_command" ], "need_approval_actions": [ "write_file" ], "blocked_commands": [ "rm", "mkfs", "dd", "shutdown", "chmod" ], "allowed_read_paths": [ "./workspace" ] }这个配置文件表达的核心策略是:
list_dir、read_file是基础动作。write_file属于高风险,需要人工审批。run_command虽然在白名单里,但命令中一旦包含危险关键字,就直接拒绝。- Agent 只能读取
./workspace目录下的文件。
需要说明的是,实际生产环境如果确实要允许 Agent 执行命令,不应该只依赖关键字拦截,而应该采用更严格的沙箱或命令白名单策略。下面继续创建主程序文件agent_guard_demo.py:
# 文件路径:agent_guard_demo.py import json import os import time import uuid from datetime import datetime from typing import Any, Dict BASE_DIR = os.path.dirname(os.path.abspath(__file__)) CONFIG_PATH = os.path.join(BASE_DIR, "agent_config.json") AUDIT_LOG_PATH = os.path.join(BASE_DIR, "agent_audit.jsonl") WORKSPACE = os.path.join(BASE_DIR, "workspace") os.makedirs(WORKSPACE, exist_ok=True) def now() -> str: return datetime.now().isoformat(timespec="seconds") def load_config() -> Dict[str, Any]: with open(CONFIG_PATH, "r", encoding="utf-8") as f: return json.load(f) class ActionGateway: """动作网关:所有 Agent 动作必须先经过这里,再往后执行。""" def __init__(self, config: Dict[str, Any]): self.allowed_actions = set(config["allowed_actions"]) self.need_approval_actions = set(config["need_approval_actions"]) self.blocked_keywords = config.get("blocked_commands", []) self.allowed_read_paths = [ os.path.abspath(os.path.join(BASE_DIR, p)) for p in config["allowed_read_paths"] ] def submit( self, agent_id: str, action_name: str, params: Dict[str, Any] ) -> Dict[str, str]: decision = self._decide(action_name, params) self._write_audit_log(agent_id, action_name, params, decision) return decision def _write_audit_log( self, agent_id: str, action_name: str, params: Dict[str, Any], decision: Dict[str, str], ) -> None: log_entry = { "time": now(), "agent_id": agent_id, "action": action_name, "params": params, "verdict": decision["verdict"], "reason": decision["reason"], } with open(AUDIT_LOG_PATH, "a", encoding="utf-8") as f: f.write(json.dumps(log_entry, ensure_ascii=False) + "\n") def _decide(self, action_name: str, params: Dict[str, Any]) -> Dict[str, str]: if action_name not in self.allowed_actions: return { "verdict": "deny", "reason": f"action is not in allowlist: {action_name}", } if action_name == "run_command": command = params.get("command", "") if any(keyword in command.lower() for keyword in self.blocked_keywords): return {