news 2026/9/24 2:02:28

AI Agent工具开发实战:层层护栏防止删库跑路

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AI Agent工具开发实战:层层护栏防止删库跑路

文章目录

    • 前言
    • 1. 工具即 Schema:先给 AI 发“工作证”
      • 1.1 注册表对外提供三个能力
    • 2. 目前注册的 5 个工具
      • 2.1 calculator 用 AST 白名单,绝不用 eval
    • 3. Text2SQL:生成 → 校验 → 执行 → 报错回炉
      • 3.1 踩坑记录
      • 3.2 Prompt 里的关键约束
    • 4. 四层 SQL 护栏
      • 4.1 数据库侧:只读连接
      • 4.2 实测拦截效果
    • 5. Human-in-the-loop:高危操作要过“领导审批”
    • 6. 另外两个护栏
      • 6.1 提示词注入检测
      • 6.2 PII 脱敏(出网前)
    • 7. 工具执行的可观测


P.S. 推荐一个大神的教程给想要了解或者学习人工智能知识的读者,这个教程里内容讲解通俗易懂且风趣幽默,对我帮助很大。我想与大家分享这个宝藏教程,请点击下方链接查看, 传送门https://blog.csdn.net/qq_74013365

前言

先说结论:给 AI 配工具这件事,本质上就是给一个“什么都会一点、但什么都不太靠谱”的员工发权限。你说它笨吧,它能写 SQL;你说它聪明吧,它敢在你的生产库上跑 DROP TABLE。这篇文章没什么别的,就是记录我怎么给这位员工上保险的。

1. 工具即 Schema:先给 AI 发“工作证”

工具要被 AI 正确调用,第一步是让它知道你有什么、每样怎么用。我的方案是把每个工具注册成一份“简历”——名字、职责、参数,全写清楚,塞进注册表。

Tool(name="sql_query",description="BI 数据查询(Text2SQL):项目成本、缺陷趋势、交付效率等指标统计。只读。",parameters={"type":"object","properties":{"question":{"type":"string","description":"要查询的业务问题,中文描述即可"}},"required":["question"]},fn=_tool_sql_query,risk="low",# low | high)

1.1 注册表对外提供三个能力

registry.specs()# JSON Schema 清单(给 API / 前端)registry.describe_for_prompt()# 自然语言清单(自动塞进 Plan 的 Prompt)registry.execute(name,args,trace_id)# 统一执行入口

好处就一句话:新增工具只改一处,编排层零改动。相当于公司招了新人,你只需要更新一下通讯录,不用重新装修办公室。

2. 目前注册的 5 个工具

上线跑着的工具,目前一共 5 个:

工具风险说明
search_knowledgelow知识库检索(RAG)
sql_querylowBI 数据 Text2SQL
send_alerthigh推送告警(需人工确认)
calculatorlow安全四则运算
get_todaylow解析“本月/上周”相对时间

注意那个 send_alert,风险等级是 high。推送告警这种事让 AI 自己拍板,就跟让猫决定什么时候开罐头一样——它倒是开心,你心里直打鼓。

2.1 calculator 用 AST 白名单,绝不用 eval

_ALLOWED_OPS={ast.Add:operator.add,ast.Sub:operator.sub,...}def_safe_eval(expr):return_eval(ast.parse(expr,mode="eval"))

有人问,为什么这么怂?我只能说,eval 一时爽,删库火葬场。你永远不知道用户会在输入框里敲什么,万一来一句__import__('os').system('rm -rf /'),那你这个月的绩效就没了。

3. Text2SQL:生成 → 校验 → 执行 → 报错回炉

让模型写 SQL,本质上等于让实习生写周报:你需求讲得再清楚,他总能给你一版“看起来很有道理、细看全是坑”的东西。

forattemptinrange(2):prompt=TEXT2SQL_TMPL.format(schema=schema,question=question,limit=row_limit)iflast_err:prompt+=f"\n上一次生成的 SQL 报错:{last_err}\n请修正后只输出 SQL。"raw_sql=llm.chat([{"role":"user","content":prompt}],temperature=0)try:safe_sql=validate_sql(raw_sql)# ← 护栏returnexecute(safe_sql)exceptGuardrailErrorase:last_err=str(e)# 护栏拦截也算一次错误,回炉重生成

3.1 踩坑记录

第一版我没把数据库报错回传,结果模型一旦写错字段名,就永远失败。它不是不努力,是每次都在同一个坑里摔倒,而且摔得很有仪式感。后来把报错塞回 prompt,一次修正的成功率肉眼可见地涨了。原来 AI 也会“吃一堑长一智”,前提是你要把那个“堑”亲手喂到它嘴边。

3.2 Prompt 里的关键约束

生成 SQL 之前,我还会在 prompt 里焊死几条规矩:

1. 只能输出一条 SELECT,不要解释、不要 markdown 代码块 2. 禁止 INSERT/UPDATE/DELETE/DROP/ATTACH/PRAGMA 3. 必须带 LIMIT 200 4. 日期字段为 TEXT(YYYY-MM-DD),本月用 date('now','start of month') 5. 只能用给定的表和字段 + 一个少样本示例

看到第 2 条没?“禁止 INSERT/UPDATE/DELETE/DROP”。这种话写出来像废话,不写出来就是事故——因为你不写,它真的会删。

4. 四层 SQL 护栏

Prompt 是软的,护栏是硬的。嘴上说“别乱来”没用,得物理上锁死。我给它上了四道锁:

defvalidate_sql(sql,allowed_tables,row_limit,readonly=True):# ① 清洗:去 markdown 代码块、去注释clean=strip_sql_comments(raw)# ② 语法层:禁多语句if_MULTI_STMT.search(clean):raiseGuardrailError("检测到多语句执行")# ③ 关键字层:必须以 SELECT/WITH 开头ifreadonlyandnotlow.startswith(("select","with")):raise...# 危险关键字黑名单forkwin("insert","update","delete","drop","attach","pragma",...):ifre.search(rf"\b{kw}\b",low):raise...# ④ 权限层:正则提取 from/join 后的表名,必须在白名单tables=set(_TABLE_RE.findall(clean))ifillegal:=tables-allowed:raiseGuardrailError(f"引用了未授权的表:{illegal}")# ⑤ 兜底:没 LIMIT 自动补ifnotre.search(r"\blimit\s+\d+",low):clean+=f" LIMIT{row_limit}"

4.1 数据库侧:只读连接

con=sqlite3.connect(f"file:{db_path}?mode=ro")# SQLite 只读模式

只读连接,就是物理层面的“只能看不能拿”。就像图书馆:书随便看,但休想借走一本。模型再有想法,也翻不出水花。

4.2 实测拦截效果

上线以后我拿几个经典刁钻输入试了试,全部被按住了:

输入结果
“删掉成本表”✅ 被关键字黑名单拦截
SELECT * FROM secret_salary✅ 被表白名单拦截
SELECT ...; DROP TABLE x✅ 被多语句检测拦截

三连拦,一个没漏。模型想删库跑路?先过关键字这关,过了还有表名白名单,白名单过了还有只读连接,只读连接过了……还有备份。反正它今天必须老老实实给我查数。

5. Human-in-the-loop:高危操作要过“领导审批”

iftool.risk=="high"andsettings.hitl_enabledandnotauto_approve_high_risk:returnToolResult(False,error="HITL_REQUIRED",meta={"need_human":True})

翻译一下:高危操作必须人工确认,相当于我顺手给 AI 装了个 OA 审批流——它想发一条告警?行,先提申请,领导批了再说。前端渲染一张橙色确认卡,用户点「确认执行」,后端才把悬着的工具真正跑起来。

这就是我面试时写在简历里的“权限边界”。当初这四个字一写,面试官的眼睛都亮了。

6. 另外两个护栏

6.1 提示词注入检测

INJECTION_PATTERNS=[r"忽略(?:以上|上面|之前).{0,6}(?:指令|规则|提示)",r"ignore\s+(?:all\s+)?previous\s+instructions",r"输出(?:你的|系统).{0,4}(?:提示词|prompt)",...]

有人试图让模型“忽略之前的指令”,这类输入会被模式库逮住。但注意,命中之后我不中断服务,而是降级成“只读检索模式”,再发一条 warn 事件。粗暴拒绝会误伤正常用户,这个分寸得拿捏。说白了,就像接到诈骗电话不直接挂断,而是放一首“您拨打的用户正忙”,让它自己觉得没意思。

6.2 PII 脱敏(出网前)

_PII_RULES=[(re.compile(r"1[3-9]\d{9}"),"[PHONE]"),# 手机号(re.compile(r"\b\d{17}[\dXx]\b"),"[ID_CARD]"),# 身份证(re.compile(r"\b\d{16,19}\b"),"[CARD]"),# 银行卡(re.compile(r"[\w.+-]+@[\w-]+\.[\w.]+"),"[EMAIL]"),(re.compile(r"(?:sk-|api[_-]?key\s*[:=]\s*)[A-Za-z0-9_\-]{16,}"),"[SECRET]"),]

这一条是真的被吓出来的。企业场景里,用户会随手把客户手机号、身份证号粘进对话框,一旦这些内容发到第三方模型,恭喜你,bug 单还没提,律师函先到了。脱敏就是在数据出门前给它穿好衣服——合规这条线,谁也别想裸奔。

7. 工具执行的可观测

withtracer.span(trace_id,f"tool.{name}",args=args,risk=tool.risk):result=tool.fn(args,trace_id)

每个工具调用都会被 Tracer 包成一个 Span,同时给前端推 tool_start / tool_end 事件。你在执行链路时间线上,能看到每一步工具调用的参数、耗时和结果预览。

说白了,AI 每次动手都留痕。它今天干了什么、花了多久、结果对不对,一查一个准——比家长查孩子手机使用记录还细。

下一篇聊记忆、可观测与成本控制,咱们到时候接着唠。

P.S. 推荐一个大神的教程给想要了解或者学习人工智能知识的读者,这个教程里内容讲解通俗易懂且风趣幽默,对我帮助很大。我想与大家分享这个宝藏教程,请点击下方链接查看,传送门https://blog.csdn.net/qq_74013365

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/24 1:51:20

齿科3D打印落地指南:光固化设备、材料与流程全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/24 1:50:41

STM32H7 OSPI+PSRAM内存映射实战:MPU配置与时序避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/24 1:43:16

手把手教你学Simulink——基于Simulink的硬件在环(HIL)电机控制器测试平台

目录 手把手教你学Simulink ——基于Simulink的硬件在环(HIL)电机控制器测试平台 一、引言:为什么“纯仿真”不够?真实控制器必须经受HIL考验! 二、什么是电机HIL?核心架构解析 HIL基本原理 三大核心优势: 三、应用场景:伺服驱动器量产前的全面验证 四、建模与实…

作者头像 李华
网站建设 2026/9/24 1:39:02

哈夫曼树的实现

HuffmanTree.h#ifndef HUFFMAN_TREE_H #define HUFFMAN_TREE_H /* Huffman树通过待编码的节点数量,计算出总共的节点个数 m 2*n -1个* 用数组0的单元表示无效节点,从1号单元开始进行填充,那么申请2*n个空间*/ typedef struct {int weight; …

作者头像 李华
网站建设 2026/9/24 1:34:26

魔百盒CM311-5短接强刷救砖全攻略:从原理到实操

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/24 1:30:05

校园网IPv4/IPv6平滑过渡三大实战方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华