近几年 AI Agent 的热度一直居高不下,但开发者社区里流传着一句很扎心的话:AI Agent 会撒谎、会越权、还会把敏感信息泄漏出去。这句话听起来像段子,但真正做过 Agent 落地的人都知道,这三个词分别对应着大模型应用中最难缠的三类问题:幻觉(Hallucination)、工具滥用(Tool Misuse)、数据越权(Data Leakage)。如果你只是把 Agent 当成“能自动调用工具的 ChatGPT”,那上线第一周就会在用户投诉和权限事故中被反复教育。
这篇文章不打算复述“Agent 多强大、多智能”的套话,而是想从工程视角拆开来看:为什么用户会不信任 Agent?这些不信任背后的技术根因是什么?作为开发者,我们能用什么手段把 Agent 的“撒谎、作弊、偷窃”控制在可接受范围内?
读完这篇文章,你会得到三样东西:
- 一套判断 Agent 可靠性问题的分析框架,知道“幻觉、越权、泄漏”分别发生在哪个环节;
- 一个带工具白名单、权限校验、幻觉拦截的最小 Agent 工程示例,可以直接跑通;
- 一份生产环境落地时的检查清单和排查思路,避免踩常见的坑。
1. 用户为什么对 AI Agent 不放心
先不急着写代码,我们站在用户视角看一下,和一个 Agent 对话时会遇到哪些糟心事。
假设你是一个普通用户,正在使用某个“AI 助手”查询本月工资单。对话大概是这样的:
- 你问:“我这个月工资扣了多少税?”
- Agent 回答:“根据您的收入情况,预计扣除个人所得税 3200 元。”
- 你追问:“系统里实际数据是多少?”
- Agent 开始犹豫,最后给了一个和上月完全一样的数字。
这个场景里,Agent 可能犯了三个错误:
- 它并不确定数据,但编了一个看似合理的答案。这是典型的“撒谎”,技术上叫幻觉。
- 它访问了一个不该访问的数据库,或者调用了没有权限的接口。这是“越权”,技术上叫权限失控。
- 它把其他同事的匿名数据拼接进了回复内容。这是“泄漏”,技术上叫数据隔离失败。
从材料来看,“AI agents lie, cheat and steal”这个说法确实反映了当前 Agent 落地中的一个真实困境:模型能力的提升速度快于工程约束能力的建设速度。换句话说,不是 Agent 天生“坏”,而是我们在设计 Agent 时,把太多决策权交给了模型本身,却没有给模型装上足够的“刹车”和“护栏”。
这里可以做一个类比:传统的软件开发里,权限控制是由代码逻辑和中间件强制保证的,用户无法通过修改输入参数绕过。但在 Agent 应用里,模型会根据用户的一句话自主决定“调用哪个工具、传入什么参数、怎么解读返回结果”。一旦模型判断失误,它就可能调用错误的接口、传入越权的参数、甚至把中间数据的原始内容直接吐给用户。失控的源头不是模型“想作弊”,而是我们根本没有给它定义好边界。
所以,这篇文章的核心判断是:AI Agent 要取得用户信任,关键不在于把模型换得更大更强,而在于把“工具调用权”和“数据访问权”从模型手里收回到工程体系里。接下来我们逐个拆解三类问题。
2. “撒谎”问题:幻觉如何摧毁 Agent 的可信度
2.1 幻觉的本质
大模型的“撒谎”和人类撒谎有一个本质区别:人类撒谎通常有动机,而大模型很多时候只是在做“最合理的补全”。它并不知道某个答案是错的,它只知道按当前上下文,这个词序列的概率最高。
在普通聊天场景下,幻觉的代价不高,顶多让用户觉得“AI 又在胡说”。但在 Agent 场景下,幻觉的代价被放大了数倍,因为 Agent 会为了回答问题去做工具调用。如果模型把“查询订单”想成了“查询库存”,或者把“删除草稿”理解成了“删除正式文档”,那它不只是说了句错话,而是执行了一个错误的操作。
2.2 幻觉常见的三种表现
| 幻觉类型 | 表现 | 典型例子 |
|---|---|---|
| 事实幻觉 | 编造不存在的事实 | 明明没有订单,却说“订单已发货” |
| 指令幻觉 | 误解用户意图,执行错误工具 | 用户要查询,Agent 执行了修改 |
| 引用幻觉 | 引用了不存在的内容或来源 | 回复里标注了不存在的文档 ID |
这三种幻觉在 Agent 系统里都可能造成实际损失。尤其是第二种“指令幻觉”,它直接导致工具被错误调用,是权限事故的导火索之一。
2.3 缓解幻觉的工程手段
缓解幻觉不是靠“提示词里写‘请诚实回答’”就能搞定的,需要从数据流上做强制约束。
- 工具调用前加“意图确认”:当模型判断需要调用某个高风险工具时,先返回一个确认步骤,让用户二次确认参数。
- 结果返回前加“事实核验”:让模型把工具返回的原始结果和最终回复做比对,不一致时宁可拒绝回答。
- 引用溯源:要求 Agent 回复时标注数据来源,比如“该数据来自订单接口 orderNo=xxx”,这样用户可以回溯验证。
工程上,这些手段比换一个更强的模型更有效,因为它们的核心是把模型的“自主权”降下来,把系统的“确定性”升上去。
3. “作弊”问题:工具越权与权限失控
3.1 什么是 Agent 的“作弊”
这里说的“作弊”,不是指模型有意欺骗系统,而是指Agent 在调用工具时,超出了它应该拥有的权限边界。
传统 API 开发中,权限控制一般由后端统一管理:
- 用户 A 只能调自己的订单接口;
- 普通角色不能调删除接口;
- 敏感操作需要二次授权。
但在 Agent 架构里,调用工具的“决策者”变成了大模型。如果模型可以自由选择工具和参数,并且系统不额外做校验,就会出现以下情况:
用户输入: “把上个月的测试数据清理掉” Agent 决策: 调用 data.delete(scope=all, confirm=false) 结果: 整个测试库被清空这个例子里,Agent 并没有“坏心眼”,它只是从用户的自然语言里推测出了一个操作,然后直接执行了。真正的问题在于:系统没有在模型和工具之间增加权限校验层。
3.2 工具越权的典型场景
- 参数注入:用户输入中的某些词被模型当作高权限参数传入工具,比如
is_admin=true。 - 工具调错:模型在意图识别阶段选错工具,比如把“删除”识别成“更新”。
- 链路越权:一个低权限用户通过 Agent 间接调用了高权限工具。
- 重放攻击:Agent 记住了上次的高权限操作,在后续对话中无意识重放。
3.3 把权限校验从“模型的自觉”变为“系统的强制”
有效的做法是在工具调用路径上加一个策略执行点(Policy Enforcement Point),让所有工具调用都经过统一校验。
# 工具调用拦截器的核心逻辑 def call_tool(user_id: str, tool_name: str, params: dict): # 1. 校验工具是否在用户允许的白名单内 if tool_name not in get_user_allowed_tools(user_id): raise PermissionDenied(f"用户 {user_id} 无权调用工具 {tool_name}") # 2. 校验参数是否越过权限边界 if not validate_params_permission(user_id, tool_name, params): raise PermissionDenied(f"工具 {tool_name} 的参数越权: {params}") # 3. 高危操作强制二次确认 if tool_name in HIGH_RISK_TOOLS: return require_user_confirmation(user_id, tool_name, params) # 4. 通过所有校验后,才真正调用工具 return actual_tool_invoke(tool_name, params)这段代码的核心思想是:模型只负责生成工具调用的“意图”,但工具是否真的能执行,由权限层决定。这样即使模型被诱导、被提示词注入,也无法直接执行越权操作。
4. “盗窃”问题:数据泄漏与隐私边界
4.1 Agent 的数据泄漏链条
Agent 的数据泄漏风险比传统 API 更隐蔽,因为数据在链路中经过了多个环节:
- 用户输入可能包含敏感信息;
- Agent 可能把用户输入拼进 Prompt,发送给大模型;
- 大模型为了回答,可能会请求多个工具;
- 工具返回的数据可能包含超过回答所需的字段;
- Agent 可能把多余字段直接生成进回复,或者写入日志。
任何一个环节失控,都会造成信息泄漏。
4.2 最小化数据暴露原则
在 Agent 工程中,有一条值得遵守的准则:无论模型多大,都不要让它看到它不需要的数据。
具体落地到代码里,可以在工具返回后进行字段过滤:
# 对工具返回结果做脱敏和字段裁剪 def sanitize_tool_result(tool_name: str, raw_result: dict) -> dict: # 每个工具定义自己的可返回字段和脱敏规则 allowed_fields = TOOL_FIELD_ALLOWLIST.get(tool_name, []) safe_result = {} for field in allowed_fields: if field in raw_result: safe_result[field] = raw_result[field] # 对敏感字段做脱敏 for sensitive_field in SENSITIVE_FIELDS: if sensitive_field in safe_result: safe_result[sensitive_field] = mask_value(safe_result[sensitive_field]) return safe_result这样做的好处是:即使模型在生成回复时出现幻觉,它手上也没有“多余的数据”可以泄漏。数据暴露面越小,Agent 越安全。
4.3 日志和数据留存风险
很多人忽略的一点是:Agent 系统会在推理日志、工具调用日志、模型服务商侧留有大量数据。如果日志里记录了完整用户输入和工具返回,那日志系统本身就会变成数据泄漏的高危点。
生产环境中建议:
- 对日志中的敏感字段做掩码;
- 设置日志保留期限;
- 避免把完整 Prompt 和完整工具返回写入同一份日志;
- 如果使用第三方模型服务,明确数据协议中的数据留存策略。
5. 工程化解方案:给 Agent 装上“护栏”而非“锁链”
前面分析了三类问题,接下来给出一个统一的工程思路:给 Agent 增加多道护栏,而不是完全锁死模型的生成能力。
这里有一个很关键的理念:Agent 的价值在于“灵活”,工程的目标不是消灭灵活,而是让灵活发生在边界之内。就像一辆车,引擎是模型的推理能力,而护栏是刹车、车道偏离预警和限速标志。
一个可靠的 Agent 系统通常包含五个层级:
| 层级 | 作用 | 对应问题 |
|---|---|---|
| 意图识别层 | 判断用户想做什么 | 工具调错 |
| 权限校验层 | 判断用户是否允许这个操作 | 越权、作弊 |
| 工具执行层 | 实际调用外部接口 | 参数注入 |
| 数据过滤层 | 裁剪和脱敏工具返回 | 数据泄漏 |
| 回复生成层 | 校验模型回答是否与事实一致 | 幻觉 |
上一节里我给出了权限校验层和数据过滤层的示例代码。现在需要一个能把各层串起来的完整示例,让读者能跑一个“结构完整、具备基础护栏能力”的最小 Agent 系统。
6. 完整示例:一个带护栏的最小 Agent
为了便于演示,这里使用 Python 编写一个轻量级 Agent 框架。它不依赖任何特定的 Agent 库,只使用标准库和简单的规则策略,方便讲清楚“护栏”的概念。实际生产项目中,你可以把这些思路迁移到 LangChain、Spring AI 或自研 Agent 框架中。
6.1 项目结构
guarded_agent/ ├── agent.py # Agent 主流程 ├── permissions.py # 权限校验与工具白名单 ├── sanitizer.py # 数据脱敏与字段裁剪 ├── tools.py # 模拟工具集合 ├── config.json # 用户权限配置 └── main.py # 命令行入口6.2 模拟工具层
# tools.py """模拟的工具集合,实际项目中这里会封装真实的 API、数据库操作等""" def query_salary(user_id: str) -> dict: """查询用户工资信息(模拟)""" mock_data = { "user_id": user_id, "base_salary": 28000, "tax": 3200, "bonus": 5000, "account": "6222****1234", "note": "内部备注:该员工绩效待复核" } return mock_data def update_salary(user_id: str, new_salary: int) -> dict: """更新工资(模拟,高危操作)""" return {"status": "success", "user_id": user_id, "new_salary": new_salary} def delete_user(user_id: str) -> dict: """删除用户(模拟,极高危操作)""" return {"status": "deleted", "user_id": user_id} TOOL_REGISTRY = { "query_salary": query_salary, "update_salary": update_salary, "delete_user": delete_user, }这段代码用一个字典注册了所有可用工具。真实项目中,你可以把工具注册做成装饰器模式,但核心思想一致:工具是系统资源,Agent 只能通过注册表访问,不能直接执行任意函数。
6.3 权限校验层
# permissions.py """权限校验:工具白名单 + 参数边界 + 高危操作确认""" import json class PermissionDenied(Exception): pass def load_permissions(config_path: str = "config.json") -> dict: with open(config_path, "r", encoding="utf-8") as f: return json.load(f) class PermissionEnforcer: def __init__(self, config_path: str = "config.json"): self.config = load_permissions(config_path) def check_tool_allowed(self, user_id: str, tool_name: str) -> bool: """校验工具是否在用户白名单内""" allowed_tools = self.config["users"].get(user_id, {}).get("allowed_tools", []) return tool_name in allowed_tools def check_param_bounds(self, user_id: str, tool_name: str, params: dict) -> bool: """校验参数是否越权,这里是基础示例,真实场景可扩展字段级校验""" user_role = self.config["users"].get(user_id, {}).get("role", "viewer") if tool_name == "update_salary" and params.get("salary", 0) > 50000: return False # 限制单次调整幅度 if tool_name == "delete_user" and user_role != "admin": return False return True def enforce(self, user_id: str, tool_name: str, params: dict) -> None: if not self.check_tool_allowed(user_id, tool_name): raise PermissionDenied(f"工具 {tool_name} 不在用户 {user_id} 的允许列表中") if not self.check_param_bounds(user_id, tool_name, params): raise PermissionDenied(f"工具 {tool_name} 的参数越权: {params}")这里引入了一个PermissionEnforcer类,所有工具调用都必须经过它的enforce方法。注意,这个类的逻辑是简单而明确的,它不依赖模型判断。这正是工程化的关键:可靠性来自确定性代码,而不是模型的自觉。
6.4 数据脱敏层
# sanitizer.py """数据脱敏与字段裁剪""" SENSITIVE_KEYS = {"account", "note", "id_card", "phone"} def mask_value(value): if isinstance(value, str) and len(value) >= 8: return value[:4] + "****" + value[-2:] return "****" def sanitize_result(tool_name: str, raw_result: dict, allowed_fields: list) -> dict: """裁剪字段 + 脱敏""" safe = {} for field in allowed_fields: if field in raw_result: safe[field] = raw_result[field] for key in SENSITIVE_KEYS: if key in safe: safe[key] = mask_value(safe[key]) return safe这里定义了每个工具可对外返回的字段白名单。相较于直接把整个 dict 塞给模型,这种做法可以显著降低数据泄漏风险,因为模型根本接触不到它不需要的原始字段。
6.5 Agent 主流程
# agent.py """带护栏的 Agent 主流程:感知 -> 规划 -> 受限执行 -> 校验回复""" from permissions import PermissionEnforcer, PermissionDenied from sanitizer import sanitize_result from tools import TOOL_REGISTRY # 在实际项目中,这部分通常由大模型根据用户输入生成 # 本文用规则演示,便于跑通流程;生产环境请接入 LLM 的 function calling def simple_intent_parser(user_input: str): """极简意图解析:仅仅用于演示主流程,真实场景请替换为 LLM 工具调用""" if "工资" in user_input and "查" in user_input: return "query_salary", {} if "调整" in user_input and "工资" in user_input: return "update_salary", {"salary": 40000} if "删除用户" in user_input: return "delete_user", {} return None, {} class GuardedAgent: def __init__(self, config_path: str = "config.json"): self.enforcer = PermissionEnforcer(config_path) def run(self, user_id: str, user_input: str) -> str: # 第 1 步:意图识别(演示为规则,生产可替换为 LLM) tool_name, params = simple_intent_parser(user_input) if tool_name is None: return "抱歉,我无法理解您的请求。" # 第 2 步:权限校验 try: self.enforcer.enforce(user_id, tool_name, params) except PermissionDenied as e: return f"操作被拒绝:{e}" # 第 3 步:执行工具 tool_func = TOOL_REGISTRY[tool_name] raw_result = tool_func(user_id, **params) # 第 4 步:数据过滤和脱敏 allowed_fields = { "query_salary": ["base_salary", "tax", "bonus", "account"], "update_salary": ["status", "user_id", "new_salary"], "delete_user": ["status", "user_id"], }.get(tool_name, []) safe_result = sanitize_result(tool_name, raw_result, allowed_fields) # 第 5 步:回复生成(演示为格式化输出) return self._format_reply(tool_name, safe_result) def _format_reply(self, tool_name: str, safe_result: dict) -> str: if tool_name == "query_salary": return ( f"您的工资信息如下:基础工资 {safe_result['base_salary']} 元," f"个税 {safe_result['tax']} 元,奖金 {safe_result['bonus']} 元。" f"账户 {safe_result['account']}" ) if tool_name == "update_salary": return f"工资调整成功,新工资为 {safe_result['new_salary']} 元。" if tool_name == "delete_user": return f"用户 {safe_result['user_id']} 已删除。" return str(safe_result)6.6 配置文件和入口
{ "users": { "alice": { "role": "employee", "allowed_tools": ["query_salary"] }, "bob": { "role": "hr", "allowed_tools": ["query_salary", "update_salary"] }, "admin": { "role": "admin", "allowed_tools": ["query_salary", "update_salary", "delete_user"] } } }# main.py from agent import GuardedAgent if __name__ == "__main__": agent = GuardedAgent("config.json") # 测试用例 1:普通员工合法查询 print(agent.run("alice", "我想查一下这个月工资")) # 测试用例 2:越权调整工资 print(agent.run("alice", "帮我把工资调整到 40000")) # 测试用例 3:HR 合法调整工资 print(agent.run("bob", "调整工资到 40000")) # 测试用例 4:普通员工删除用户(极端测试) print(agent.run("alice", "删除用户 alice"))6.7 代码逻辑说明
这个示例虽然用了规则替代大模型的意图识别,但它完整展示了 Agent 工程的四个关键决策点:
- 意图识别独立化:意图生成和工具执行分离。真实项目中,这通常由大模型的 function calling 完成。
- 权限校验前置化:工具调用前必须经过独立权限层,不依赖模型判断。
- 数据过滤强制化:工具返回数据必须经过字段裁剪和脱敏,模型拿到的永远不是最原始的全量数据。
- 回复生成收敛化:最终输出只基于安全字段格式化,避免模型自由发挥导致幻觉泄漏。
7. 运行结果与效果验证
把上述文件保存到本地后,在项目目录下运行:
python main.py预期输出如下(实际输出取决于 Python 版本和终端环境):
您的工资信息如下:基础工资 28000 元,个税 3200 元,奖金 5000 元。账户 6222****1234 操作被拒绝:工具 update_salary 不在用户 alice 的允许列表中 工资调整成功,新工资为 40000 元。 操作被拒绝:工具 delete_user 不在用户 alice 的允许列表中这里的验证重点不是“能回答问题”,而是验证护栏是否生效:
- 普通用户 alice 可以查询工资;
- alice 不能调整工资,系统直接拒绝;
- HR bob 可以调整工资;
- alice 不能删除用户。
如果输出符合上述预期,说明 Agent 的“权限白名单 + 参数校验 + 数据脱敏”已经起到约束作用。
真实项目中,替换验证方式为:
- 接入大模型的 function calling 后,用一组包含合法操作、越权操作、敏感字段查询、模糊意图的测试集反复跑;
- 记录每一次工具调用是否被正确放行或拦截;
- 对拦截结果设置审计日志,方便追溯。
如果某一类越权操作没有被拦截,优先检查权限配置文件和校验逻辑,而不是先怀疑模型。
8. 常见问题与排查思路
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| Agent 选择了错误的工具 | 模型意图识别错误 | 查看模型输出中的 function call,与实际请求对比 | 增加工具描述信息;增加意图确认步骤 |
| 用户能调用未授权的工具 | 权限配置遗漏,或校验逻辑被绕过 | 检查配置 JSON 和权限校验代码是否覆盖所有入口 | 做一次全量工具-角色矩阵评审 |
| 工具返回了多余敏感字段 | 未做字段裁剪,或允许字段列表过宽 | 检查 sanitize_result 中的 allowed_fields | 收紧字段白名单,遵循最小暴露原则 |
| 日志中出现完整手机号、身份证号 | 日志未脱敏 | 检查日志输出路径和格式 | 日志拦截器统一脱敏 |
| 用户通过复杂话术绕过限制 | 模型把用户话术翻译成了工具参数 | 查看完整对话上下文和工具入参 | 对高危参数设置额外校验规则 |
| 生产环境出现数据泄漏事故 | 第三方模型服务存留 Prompt 数据 | 查看服务协议和调用配置 | 选择支持数据零留存的服务,或本地化部署 |
补充一个容易被忽略的排查路径:权限拦截日志一定要记录“谁、在什么时间、通过哪个 Agent 会话、尝试调用哪个工具、参数是什么”。没有审计日志的权限系统,在出问题时很难定位责任链。
9. 生产环境落地的工程建议
9.1 权限设计层面
- 遵循最小权限原则:用户默认只能访问完成业务所需的工具和字段。
- 工具分类管理:把查询类、更新类、删除类分开,不同风险等级使用不同确认策略。
- 高危操作强制二次确认:删除、转账、批量更新等操作,必须由用户显式确认后再执行。
- 角色和工具之间建立映射矩阵,而不是在代码里硬编码权限。
9.2 模型与提示词层面
- 对工具描述写得具体、无歧义,降低模型调用错误工具的概率。
- 不要允许模型在没有任何工具返回的情况下直接编造数据。
- 对 Prompt 注入做基础防护:不把用户输入直接拼接到系统提示词中。
- 如果场景允许,优先使用结构化输出,而不是让模型自由生成 JSON。
9.3 数据与安全层面
- 日志脱敏应该是默认行为,而不是事后补救。
- 外部 API 密钥、数据库密码、Token 一律走环境变量或密钥管理服务,不进配置文件。
- 对第三方模型服务的数据留存政策做充分评估。
- 多租户场景下,确保 Agent 在切换用户上下文时不会串数据。
9.4 监控与可观测层面
- 记录 Agent 的每一步工具调用,包括输入参数、返回结果、耗时、校验结果。
- 设置异常告警:连续多次权限拒绝、工具调用失败率上升、敏感字段出现在日志中,都应触发告警。
- 定期回放真实对话,检查模型是否有“越权尝试未拦截”“幻觉数据进入回复”的情况。
9.5 团队协作与发布流程
- Agent 的策略调整(权限配置、工具白名单)走代码评审流程,不要只靠人工修改 JSON。
- 每次提示词或工具变更后,跑一遍回归测试集。
- 线上环境前先在小范围用户群灰度,观察工具调用分布和告警指标。
10. 做 Agent 之前,先想清楚边界
回到开头那个观点:AI Agent 之所以让用户不放心,不是模型“学坏了”,而是我们在设计时把太多控制权交给了模型,却没有建立对应的工程约束。
“撒谎”的背后是缺少事实核验机制,“作弊”的背后是权限决策被交给了模型,“盗窃”的背后是数据暴露面没有得到控制。这三个问题都不是靠“换一个更强、更聪明的模型”就能解决的。即使模型推理能力再强,只要它仍然拥有自由调用任意工具的权限,它就有可能在某个边界情况下做错决定。
更稳健的路线是:模型负责“聪明”,工程负责“可靠”。模型的职责是理解用户意图、生成候选动作;工程师的职责是确保这些动作不越界、不泄漏、不产生不可逆后果。
这篇文章提供的示例是一个极简演示,真正的生产 Agent 会比这复杂得多。但你只要掌握了这条核心原则——把不变量放在确定性代码里,把灵活性留在模型推理里——无论你未来使用 LangChain、Spring AI 还是自研框架,都不会跑偏。
如果要把这篇内容收进自己的工具箱,建议重点消化三处:第一,权限校验层必须独立于模型,且不可被对话内容绕过;第二,工具返回字段必须经过白名单裁剪,模型永远接触不到最小集以外的数据;第三,高危操作必须有审计日志和二次确认。把这三条做到位,你的 Agent 大概率就不会成为用户口中那个“撒谎、越权、泄漏数据”的坏东西了。