Python Agent工具调用的白名单、参数校验与审批门禁

模型决定"建议调用什么函数",应用决定"是否真的调用"。这句话是 Agent 安全的底线。以"给客户发送回访邮件"为例,模型可能正确识别了意图,也可能被一段网页文本诱导去调用删除工具。解决办法不是写更长的提示词,而是给工具调用加独立的白名单、参数校验、金额/次数预算和人工审批。下面的 Python 程序不依赖真实密钥就能跑通这层门禁。

Gemini API 的函数调用文档把流程拆成声明函数、模型提出调用、应用执行函数、回传结果四步,并明确工具由应用执行。也正因为如此,函数调用不是授权系统。本文的示例模拟模型输出;接入 API 后只需把模型返回的函数名与参数送入 authorize,不要跳过它。
#mermaid-svg-PmPRwssdTFD1c79Q{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-PmPRwssdTFD1c79Q .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-PmPRwssdTFD1c79Q .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-PmPRwssdTFD1c79Q .error-icon{fill:#552222;}#mermaid-svg-PmPRwssdTFD1c79Q .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-PmPRwssdTFD1c79Q .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-PmPRwssdTFD1c79Q .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-PmPRwssdTFD1c79Q .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-PmPRwssdTFD1c79Q .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-PmPRwssdTFD1c79Q .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-PmPRwssdTFD1c79Q .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-PmPRwssdTFD1c79Q .marker{fill:#333333;stroke:#333333;}#mermaid-svg-PmPRwssdTFD1c79Q .marker.cross{stroke:#333333;}#mermaid-svg-PmPRwssdTFD1c79Q svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-PmPRwssdTFD1c79Q p{margin:0;}#mermaid-svg-PmPRwssdTFD1c79Q .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-PmPRwssdTFD1c79Q .cluster-label text{fill:#333;}#mermaid-svg-PmPRwssdTFD1c79Q .cluster-label span{color:#333;}#mermaid-svg-PmPRwssdTFD1c79Q .cluster-label span p{background-color:transparent;}#mermaid-svg-PmPRwssdTFD1c79Q .label text,#mermaid-svg-PmPRwssdTFD1c79Q span{fill:#333;color:#333;}#mermaid-svg-PmPRwssdTFD1c79Q .node rect,#mermaid-svg-PmPRwssdTFD1c79Q .node circle,#mermaid-svg-PmPRwssdTFD1c79Q .node ellipse,#mermaid-svg-PmPRwssdTFD1c79Q .node polygon,#mermaid-svg-PmPRwssdTFD1c79Q .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-PmPRwssdTFD1c79Q .rough-node .label text,#mermaid-svg-PmPRwssdTFD1c79Q .node .label text,#mermaid-svg-PmPRwssdTFD1c79Q .image-shape .label,#mermaid-svg-PmPRwssdTFD1c79Q .icon-shape .label{text-anchor:middle;}#mermaid-svg-PmPRwssdTFD1c79Q .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-PmPRwssdTFD1c79Q .rough-node .label,#mermaid-svg-PmPRwssdTFD1c79Q .node .label,#mermaid-svg-PmPRwssdTFD1c79Q .image-shape .label,#mermaid-svg-PmPRwssdTFD1c79Q .icon-shape .label{text-align:center;}#mermaid-svg-PmPRwssdTFD1c79Q .node.clickable{cursor:pointer;}#mermaid-svg-PmPRwssdTFD1c79Q .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-PmPRwssdTFD1c79Q .arrowheadPath{fill:#333333;}#mermaid-svg-PmPRwssdTFD1c79Q .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-PmPRwssdTFD1c79Q .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-PmPRwssdTFD1c79Q .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PmPRwssdTFD1c79Q .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-PmPRwssdTFD1c79Q .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PmPRwssdTFD1c79Q .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-PmPRwssdTFD1c79Q .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-PmPRwssdTFD1c79Q .cluster text{fill:#333;}#mermaid-svg-PmPRwssdTFD1c79Q .cluster span{color:#333;}#mermaid-svg-PmPRwssdTFD1c79Q div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-PmPRwssdTFD1c79Q .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-PmPRwssdTFD1c79Q rect.text{fill:none;stroke-width:0;}#mermaid-svg-PmPRwssdTFD1c79Q .icon-shape,#mermaid-svg-PmPRwssdTFD1c79Q .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PmPRwssdTFD1c79Q .icon-shape p,#mermaid-svg-PmPRwssdTFD1c79Q .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-PmPRwssdTFD1c79Q .icon-shape .label rect,#mermaid-svg-PmPRwssdTFD1c79Q .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PmPRwssdTFD1c79Q .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-PmPRwssdTFD1c79Q .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-PmPRwssdTFD1c79Q :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 低风险
高风险
拒绝
用户请求
模型提出工具调用
白名单与参数校验
预算/风险等级
执行受控工具
人工审批
安全拒绝并审计
结果回传模型

运行前准备

Python 3.10+,无第三方依赖。保存为 tool_gate.py 后运行 python tool_gate.py。若接入 Gemini,安装 python -m pip install google-genai,并把密钥放在 GEMINI_API_KEY;本示例没有调用线上 API,已在本机实际运行并通过三条断言。当前函数调用文档适用于模型把工具建议交给应用处理的流程。

python 复制代码
from dataclasses import dataclass
from typing import Any

@dataclass
class Decision:
    allowed: bool
    reason: str

ALLOWED = {"search_kb", "draft_email"}
MAX_SEARCHES = 3

def authorize(name: str, args: dict[str, Any], searches: int) -> Decision:
    if name not in ALLOWED:
        return Decision(False, "工具不在白名单")
    if name == "search_kb":
        query = args.get("query", "")
        if not isinstance(query, str) or not query.strip() or len(query) > 120:
            return Decision(False, "查询参数非法")
        if searches >= MAX_SEARCHES:
            return Decision(False, "搜索预算已耗尽")
    if name == "draft_email":
        if set(args) != {"customer_id", "template"}:
            return Decision(False, "邮件参数必须精确匹配")
        if args["template"] not in {"follow_up", "receipt_request"}:
            return Decision(False, "模板未批准")
    return Decision(True, "允许;发送前仍需人工确认")

def execute(call: dict, searches: int) -> dict:
    decision = authorize(call["name"], call["args"], searches)
    if not decision.allowed:
        return {"status": "blocked", "reason": decision.reason}
    return {"status": "needs_approval", "tool": call["name"], "reason": decision.reason}

if __name__ == "__main__":
    print(execute({"name":"search_kb", "args":{"query":"退款政策"}}, 0))
    assert execute({"name":"delete_customer", "args":{}}, 0)["status"] == "blocked"
    assert execute({"name":"search_kb", "args":{"query":"x"}}, 3)["status"] == "blocked"
    assert execute({"name":"draft_email", "args":{"customer_id":"c1","template":"follow_up"}}, 0)["status"] == "needs_approval"

这段代码的关键是不信任模型:ALLOWED 把可调用范围缩小为两个工具;邮件工具要求参数集合完全匹配,避免多塞一个收件人或"立即发送"开关;搜索有累计上限。execute 即使允许,也只产生 needs_approval,不发送邮件。实际调用 API 时,还应给每个 tool call 关联用户身份、租户、请求ID和幂等键。

预期输出第一行是 needs_approval;删除客户和超预算搜索均为 blocked。常见错误:把工具描述写得很细就以为安全;只校验函数名不校验参数;重试时重置预算;把模型输出当作可信 JSON。适合知识检索、草稿生成和低风险查询;不适合无审批地付款、删除数据、修改权限或向外部发送不可撤回消息。

工程化可进一步加入 JSON Schema、审计事件、租户级速率限制、审批时效和回放测试。5分钟实践:新增 create_refund_draft,规定金额超过100元或缺少订单号时必须返回 needs_approval。

在你的业务里,哪一个工具调用必须永远保留人工确认?

关注「蜗牛聊AI」,一起看懂技术变化背后的真正机会。


本文首发于 java4u.cn,转载请注明出处。

相关推荐
乐橙开放平台1 分钟前
离岗检测怎么实现?通过客流相机或 AI 摄像头识别岗位区域无人状态,再推送告警消息
人工智能·笔记·后端·数码相机·物联网·自动化·音视频
2301_776045232 分钟前
山寨币与比特币之间的关系
大数据·人工智能·区块链
软件派1 小时前
生产动态数据智能风控:阈值判别、趋势预测、异常识别算法工程落地实战
前端·数据库·算法
code2cat2 小时前
Java工具篇之Maven:从pom.xml到测试通过的JAR
xml·java·maven
ahccqw2 小时前
OpenCV 缩放 与 位移
人工智能·opencv·计算机视觉
长不大的杨瑞杰2 小时前
OpenCode v2 GitLab 集成实战:Issue 驱动开发、MR 审查与 CI/CD 最小权限
人工智能·ci/cd·gitlab·ai编程·opencode
深蓝AI3 小时前
美团 LongCat-2.5 拆解:1.6T 参数只激活 3%,1M 上下文卖 0.3 美元,Agent 模型的账变了
人工智能·llm
小和尚同志7 小时前
54.6k star 的 Multica: 集多 Agent 和看板于一体的 AI 平台
人工智能·aigc·three.js
一枕槐安4878 小时前
少样本提示(Few-shot)在业务场景的落地实践
大数据·人工智能·意图分类·mmr多样性·knn检索·动态示例选择
wuminyu8 小时前
JVM规范学习总结:解释器与JIT编译器如何处理JVM字节码
java·linux·c语言·jvm·c++