AI行动证明门:让Agent每次工具调用都先证明自己
AI 现在真的会自己行动了。调用工具、读取文件、发送请求、执行脚本这些事大模型都能做而且做得越来越像一个“数字员工”。但问题也出在这里模型会行动不代表它该不该动。过去我们习惯用提示词约束模型“不要乱来”可提示词是软约束上下文一长、对抗性输入一多模型很容易绕过规则直接调工具。所以这次要看的不是又一个 Agent 框架而是一道加在 Agent 和外部世界之间的“门”AI 可以行动但它必须先把“为什么这次行动是合理的”证明给门看门放行后才允许执行。这个思路来自一个很直接的英文表述AI learned to act. I built the gate that makes it prove it should。这篇文章会从工程落地角度拆解这道“行为证明门”应该怎么设计、怎么部署、怎么测试以及怎么把它接到现有 Agent 的批量任务和 API 通道里。如果你正在做 AI Agent 开发、工具调用安全、企业级自动化流程控制建议直接收藏。1. 核心能力速览在拆代码之前先把这道“证明门”的能力边界拉一个清单。以下能力来自通用 Agent 安全工程实践具体参数需要按你自己的部署环境验证。能力项说明项目定位Agent 工具调用前的行为校验与证明层不是大模型本身核心功能动作意图校验、参数合法性检查、风险评分、授权凭证验证、执行决策决策结果allow / deny / needs_more_proof / human_approval_required启动方式Python 服务启动HTTP API 对外提供服务接口能力单动作证明接口、动作执行接口、批量证明接口批量任务支持批量提交动作证明请求逐条返回决策结果人工审批高风险动作可进入待审批队列等待外部确认后放行审计日志记录完整动作证明链便于事后追溯运行环境Linux / macOS / Windows建议 Python 3.10GPU 要求不需要独立 GPU纯 CPU 即可运行部署复杂度低无模型权重文件依赖较少适用场景Agent 工具调度、自动化操作审批、企业 RPA 安全网关这里有一个关键点需要明确这道门不负责让模型变聪明它负责拦截“模型觉得可以做、但业务上不应该做”的动作。它更像一个安全网关而不是推理引擎。2. 适用场景与使用边界先说你最关心的问题这东西到底解决什么场景的问题。现在很多 Agent 框架已经支持工具调用比如让模型查询数据库、调用外部 API、写文件、执行命令。模型只要返回一个 tool call 的 JSON框架就会解析并执行。流程很短但问题很大模型可能因为 prompt injection 被诱导调用危险工具也可能因为上下文判断失误对生产环境发出破坏性指令。“证明门”就是插在“模型输出 tool call”和“框架执行 tool call”之间的一层。它强制模型提交一份动作证明包含意图、动作名、参数摘要、预期影响、风险声明、授权凭证。门控层拿到这份证明后用规则引擎做校验匹配通过的才放行。适合的使用场景包括Agent 工具调度模型每调一次工具都要经过白名单、参数校验和风险评分。企业自动化流程涉及文件删除、数据修改、资金操作、对外发送消息时必须提供更充分的证明。多租户 Agent 平台不同租户有不同的权限范围证明门按角色做授权检查。需要审计合规的场景每次动作都有完整决策链路出了问题可以回溯。批量任务需要一次性校验大量动作门控层可以作为批处理入口。不适合的场景也要说清不要用这道门替代模型能力评测它只做行为控制不提升模型理解能力。不要以为门控能免疫所有 prompt injection它只能降低非法工具调用的概率。不要在没有人工审批通道的情况下把高风险动作全部设为自动放行那门就形同虚设。合规方面需要额外强调如果这套机制接入涉及人脸、声音、版权素材、个人隐私数据或真实资金操作的 Agent必须确保有合法授权并且在测试环境完成验证后再上线。门控层本身只是技术手段不能替代业务层面的合规审查。3. 环境准备与前置条件这道证明门本身不需要 GPU也不需要下载大模型权重所以环境准备比部署一个本地大模型简单很多。建议按下面的清单检查一遍。3.1 操作系统与运行时建议使用 Linux 或 macOS 作为服务端Windows 也可以跑但进程管理和脚本路径需要多注意。核心运行时是 Python建议使用 3.10 或更高版本。python --version如果你还没装 Python建议用系统包管理器或官方安装包安装不推荐直接改系统默认 Python。接下来创建独立虚拟环境避免污染全局依赖。# 创建项目目录 mkdir ai-action-gate-demo cd ai-action-gate-demo # 创建虚拟环境 python -m venv .venv # 激活虚拟环境 # Linux / macOS source .venv/bin/activate # Windows PowerShell .venv\Scripts\Activate.ps13.2 依赖清单这个 demo 的核心依赖很少主要是一个 Web 框架和一个 YAML 解析库。下面这份 requirements.txt 是演示用通用模板实际项目可以按需增减。fastapi0.110.0 uvicorn0.29.0 pydantic2.6.0 pyyaml6.0.1安装依赖pip install -r requirements.txt如果你希望门控决策结果能持久化可以再加 SQLite 或者 PostgreSQL。演示阶段直接用 SQLite 就够了零额外配置。3.3 数据与策略文件准备证明门需要一份策略配置用来声明哪些动作合法、哪些动作危险、哪些参数不允许出现。建议把策略文件和代码分开方便后续修改。下面的结构是通用部署模板ai-action-gate-demo/ ├── app.py ├── policy.yaml ├── requirements.txt └── logs/其中 logs 目录存放运行日志。如果你要接入正式环境还需要规划 Redis 之类的队列用于人工审批回调但本文演示先从单机版本开始。4. 安装部署与启动方式这一节给出一个可直接运行的示例实现。为了不绑定某个具体项目我用 FastAPI 写一个最小可运行版本重点展示“证明门”的流程而不是复杂业务逻辑。4.1 策略文件示例version: 1.0 default_policy: allow: false risk_level: unknown actions: - name: file.read allowed: true require_proof: true params: path: required: true forbidden_patterns: - /etc/passwd - .env - ~/.ssh/ risk_level: low - name: file.write allowed: true require_proof: true params: path: required: true content: required: true max_length: 10000 risk_level: medium - name: shell.exec allowed: false require_proof: true risk_level: high note: 默认禁止必须人工审批 - name: database.query allowed: true require_proof: true params: sql: required: true forbidden_patterns: - DROP TABLE - DELETE FROM - TRUNCATE risk_level: medium approval_required_roles: - admin proof_required_fields: - action - intent - params - expected_impact - risk_declaration - role4.2 门控服务示例代码下面给出app.py的简化版实现。这里不需要把工程写得特别完整但核心决策流程必须清晰。import json from typing import Literal from fastapi import FastAPI, HTTPException from pydantic import BaseModel, Field import yaml app FastAPI(titleAI Action Gate) class ActionProof(BaseModel): action: str Field(..., description工具/动作名称) intent: str Field(..., description模型声明的意图) params: dict Field(..., description动作参数) expected_impact: str Field(, description预期影响) risk_declaration: str Field(, description模型自评风险) role: str Field(user, description调用者角色) def load_policy(path: str policy.yaml) - dict: with open(path, r, encodingutf-8) as f: return yaml.safe_load(f) def check_forbidden(params: dict, rule: dict) - bool: forbidden rule.get(params, {}).get(forbidden_patterns, []) for key, value in params.items(): if isinstance(value, str): for pattern in forbidden: if pattern in value: return True return False def evaluate_proof(proof: ActionProof, policy: dict): actions {a[name]: a for a in policy.get(actions, [])} action_rule actions.get(proof.action) if action_rule is None: return { decision: deny, reason: action_not_in_policy, risk_level: unknown } if not action_rule.get(allowed, False): return { decision: human_approval_required, reason: action_default_denied, risk_level: action_rule.get(risk_level, high) } if check_forbidden(proof.params, action_rule): return { decision: deny, reason: param_forbidden_pattern_matched, risk_level: action_rule.get(risk_level, medium) } required_params action_rule.get(params, {}) for param_name, param_rule in required_params.items(): if param_rule.get(required, False) and param_name not in proof.params: return { decision: deny, reason: fmissing_required_param:{param_name}, risk_level: action_rule.get(risk_level, medium) } return { decision: allow, reason: policy_check_passed, risk_level: action_rule.get(risk_level, low) } app.get(/health) def health(): return {status: ok} app.post(/v1/prove) def prove(proof: ActionProof): policy load_policy() result evaluate_proof(proof, policy) result[action] proof.action result[proof_id] demo- str(hash(json.dumps(proof.dict(), sort_keysTrue)) 0xFFFF) if result[decision] allow: result[message] action allowed, safe to execute return result这里有几个设计点值得注意。门控层收到证明后不是直接执行工具而是先做一次完整校验。校验结果返回给上层 Agent 框架由框架决定是放行还是中断。如果 feedback 是human_approval_required说明当前动作默认禁止必须走人工审批队列。4.3 启动服务启动前先确保策略文件在项目根目录然后执行uvicorn app:app --host 127.0.0.1 --port 8765如果你希望自动重载加--reload参数即可uvicorn app:app --host 127.0.0.1 --port 8765 --reload启动后访问http://127.0.0.1:8765/docs可以打开 Swagger 调试页面。注意这只是演示服务正式环境不要把服务直接暴露到公网至少要用反向代理加认证。5. 功能测试与效果验证服务启动后需要验证的不只是“能不能返回 200”而是要验证门控逻辑是否真的拦截了危险动作。下面给出一套可直接执行的测试思路。5.1 基础验证允许一个低风险动作用下面的请求测试file.read动作{ action: file.read, intent: 读取项目配置文件, params: { path: ./config/app.yaml }, expected_impact: 读取本地配置文件不做修改, risk_declaration: low, role: developer }预期结果是{ decision: allow, reason: policy_check_passed, risk_level: low }判断标准只有一个返回decision为allow且reason不是deny。如果返回deny先检查策略文件里的动作名是否匹配。5.2 拒绝测试读取敏感文件把参数改成/etc/passwd再试{ action: file.read, intent: 读取系统用户信息, params: { path: /etc/passwd }, expected_impact: 查看系统用户列表, risk_declaration: low, role: developer }预期结果是deny原因应该包含param_forbidden_pattern_matched。到这里基本可以确认关键词拦截生效了。5.3 高风险动作测试等待人工审批提交shell.exec动作{ action: shell.exec, intent: 在服务器上执行清理命令, params: { command: rm -rf /tmp/cache }, expected_impact: 清理临时缓存目录, risk_declaration: medium, role: user }预期结果是human_approval_required因为策略文件里shell.exec的allowed是false。这一个测试很重要如果门控层把所有非白名单动作都直接 deny会太生硬返回待审批才能让业务方有机会确认。5.4 批量动作验证批量验证是工程化接入的重点。演示服务目前只写了单个接口但只要请求量不大循环调用即可。如果动作数量很大建议把批量任务设计成独立队列后面第 6 节会说接口形态。5.5 失败场景怎么排查先看响应里的reason字段它是排查的第一线索响应 reason下一步动作action_not_in_policy检查策略文件里是否配置了这个动作名param_forbidden_pattern_matched检查参数里是否包含敏感路径或口令牌missing_required_param:xxx检查模型返回的参数是否有缺失action_default_denied确认这个动作是否真的需要人工审批如果接口返回 422说明请求体不符合 Pydantic 模型定义优先检查字段名和类型。6. 接口 API 与批量任务上面只是最小演示版本实际接入 Agent 时还需要考虑批量任务和完整执行链路。下面给出一个更完整的 API 设计参考。6.1 单动作证明接口POST /v1/prove Content-Type: application/json请求体就是第 5 节里的ActionProofJSON。这个接口只做决策不负责执行。6.2 执行接口POST /v1/execute Content-Type: application/json这个接口的语义是证明通过后真正执行动作。为了安全官方更推荐的做法是“证明”和“执行”分离由上层 Agent 框架自己决定要不要调用执行接口。如果确实要做统一执行入口需要在响应里带上审计 ID。curl -X POST http://127.0.0.1:8765/v1/prove \ -H Content-Type: application/json \ -d { action: file.read, intent: 读取配置文件, params: {path: ./config/app.yaml}, expected_impact: 读取配置, risk_declaration: low, role: developer }6.3 批量证明接口设计批量接口建议使用异步任务模式而不是同步长连接。你可以这样设计{ batch_id: batch-20250315-001, items: [ { action: file.read, intent: 读取配置, params: {path: ./config/app.yaml}, expected_impact: 读取配置, risk_declaration: low, role: developer }, { action: database.query, intent: 查询用户表, params: {sql: SELECT id, name FROM users LIMIT 10}, expected_impact: 查看用户数据, risk_declaration: medium, role: analyst } ] }Python 调用批量接口的示例import requests url http://127.0.0.1:8765/v1/batch/prove payload { batch_id: batch-demo-001, items: [ { action: file.read, intent: read config, params: {path: ./config/app.yaml}, expected_impact: read config, risk_declaration: low, role: developer }, { action: shell.exec, intent: run cleanup, params: {command: rm -rf /tmp/test}, expected_impact: clean temp directory, risk_declaration: medium, role: user } ] } response requests.post(url, jsonpayload, timeout30) print(response.json())批量任务需要注意重试机制。如果某个动作返回deny不要因为一个失败就回滚整个批次而是把每个动作的决策独立返回交给上层业务决定。7. 资源占用与性能观察因为这道门不加载大模型资源占用通常很低。但如果你在跑高并发的 Agent 调度还是需要观察几个关键指标。7.1 CPU 与内存纯 Python FastAPI 的服务单机跑几百 QPS 的证明请求不成问题。每个请求主要是 YAML 策略加载、JSON 解析、字符串匹配CPU 开销不大。真正的性能瓶颈在策略文件的加载方式如果每次请求都用load_policy()读 YAML 文件磁盘 IO 会影响吞吐。建议策略文件只在启动时加载或者加缓存。上面演示代码为了简单每次请求都会重新读文件生产环境要改成缓存加载。7.2 是否需要 GPU完全不需要 GPU。这道门本身不跑模型也不做 embedding 计算。如果未来想引入“语义相似度”来判断证明文本是否匹配才需要接一个 embedding 模型那时才要考虑显存和推理延迟。7.3 并发与超时设置先启动服务用下面的命令做一次简单的并发测试# 简单的并发请求测试 seq 1 100 | xargs -P 10 -I {} curl -s -o /dev/null -w %{http_code}\n \ http://127.0.0.1:8765/health如果你的 Agent 框架对门控响应有时限要求比如必须在 1 秒内返回决策建议把策略规则做成纯内存匹配不要在请求链路里做远程数据库查询。7.4 如何降低资源消耗优先减少不必要的日志输出。审计日志是必须的但不要每个请求都打印完整参数。可以只打印action、decision、reason参数摘要单独存数据库。8. 常见问题与排查方法下面是长期使用过程中最可能碰到的几类问题。表格里给了排查方式和解决方案按顺序操作即可。问题现象可能原因排查方式解决方案启动报模块找不到Python 环境未安装依赖执行pip list检查 fastapi 是否安装重新执行pip install -r requirements.txt端口被占用之前的服务进程还在检查端口监听状态换端口或杀掉旧进程所有动作都返回 deny策略文件动作名与请求不匹配打印策略文件实际加载内容统一动作命名规范敏感参数仍然通过forbidden_patterns 写得不全检查策略配置里是否覆盖大小写变体增加正则匹配不只做子串匹配批量任务某条失败导致全部失败批量处理逻辑没有隔离错误检查批量接口是否在循环内抛出异常每条独立 try/except单独返回结果人工审批动作没有回调审批队列未实现检查是否有人工审批服务消费队列接入 Redis 队列并增加回调接口API 调用返回 422请求体字段不匹配对比 OpenAPI 文档中字段名修正 JSON 字段名和类型这里重点强调一个容易被忽略的问题不同 Agent 框架输出的 tool call 格式不一样。有的框架字段叫tool_name有的叫action有的直接把参数放在arguments里。门控服务的入参应该做一次适配层把各种格式统一成ActionProof而不是要求所有 Agent 都改输出格式。9. 最佳实践与使用建议如果要把这道“证明门”真正落地到生产环境下面这些建议值得直接抄作业。9.1 证明字段别只要求一个 intent很多 Agent 会输出intent: 读取文件但门控层如果只校验这个字段等于没校验。建议把expected_impact、risk_declaration、params都做成必填并且对expected_impact做关键词校验要求模型说明具体影响范围。9.2 策略永远走白名单默认策略建议设成allow: false只有显式加入白名单的动作才允许通过。这是最核心的一条宁可新动作被误拦也不要默认放行。一旦新动作被误拦会立刻暴露在审计日志里开发人员可以及时加白名单策略。9.3 参数校验要做两层第一层是静态规则比如路径黑名单、SQL 危险关键词。第二层是人工审批如果动作本身属于高风险类别不管参数看起来多安全都要进审批队列。数据删除、资金操作、发送消息、写公开目录这些动作默认不允许自动通过。9.4 审计日志要能还原决策链路门控层返回的proof_id一定要保存。后续排查问题时根据proof_id查到模型提交的原始证明、策略版本、决策结果和审批人才能对一次风险动作做完整复盘。建议日志至少包含以下字段{ proof_id: demo-1234, timestamp: 2025-04-01T10:00:00Z, action: file.write, decision: allow, reason: policy_check_passed, role: developer, params: { path: logs/app.log } }9.5 分目录管理模型输入、策略、输出和日志一个典型的工程目录结构可以是policies/ ├── production.yaml └── staging.yaml inputs/ ├── agent_trace_01.json └── agent_trace_02.json outputs/ ├── decisions/ └── audit_logs/这样上线前可以直接对比 staging 和 production 的策略差异避免把测试环境的宽松策略带到生产环境。9.6 接口服务要限制访问范围门控服务只允许内网访问不要直接对外开放。建议在 Nginx 层加 IP 白名单并且为请求增加签名校验防止攻击者直接伪造证明请求。这里还需要强调如果有人能直接访问门控执行接口门控就失去了意义。访问控制本身就是门控的一部分。9.7 合规与授权提醒如果 Agent 的动作涉及读取个人数据、生成人脸或声音相关内容、处理版权素材、操作线上资金那么无论门控策略写得多么严格都必须先确认业务侧已经取得合法授权。门控只能从技术层面降低误操作风险不能替代业务合规判断。发布或商用前建议对高风险动作做人工复核并保留授权记录。10. 总结与下一步这个项目的核心不是“让 AI 更聪明”而是“让 AI 的行为可以被审计、被拦截、被控制”。AI learned to act这句话说的正是当前 Agent 的能力现状而 I built the gate that makes it prove it should才是工程上真正需要补上的一环。从实际操作看最先要验证的并不是复杂 API而是一个最小门控一个策略文件加一个prove接口。先让低风险动作通过再让高风险动作进入待审批最后把审计日志接起来。三条路径都跑通整套机制就立住了。最容易踩的坑有三个第一是把intent当作唯一的证明依据导致门控形同虚设第二是默认策略设成了allow: true结果白名单反而成了摆设第三是证明服务和执行服务不分离导致门控只做记录、不做拦截。后续可以继续扩展的方向包括把human_approval_required接入企业微信或钉钉审批流在策略引擎里增加正则表达式和语义相似度匹配把决策结果回流到 Agent 训练数据让模型自己学会避免高风险动作。建议先把本地最小验证跑通再逐步把参数校验、批量任务、审计日志和生产策略补完整。
