AI Agent 数据访问:为什么 RESTful API 是比直连 SQL 更安全的边界

📅 发布时间:2026/10/9 10:36:55
AI Agent 数据访问:为什么 RESTful API 是比直连 SQL 更安全的边界
1. 这个问题是怎么冒出来的1.1 AI Agent 火了数据焦虑也跟着火了最近这段时间团队里聊得最多的话题已经从大模型能做什么变成了AI Agent 落地到业务里到底怎么接数据。很多人一上来就兴奋地说Agent 既然能理解自然语言那直接让它写 SQL、连数据库、把活干了不就行了听起来确实很丝滑——用户说一句查一下上个月华南区所有销售额超过 50 万的订单Agent 啪一下生成一条 SQL啪一下执行再啪一下把结果汇总成报表。整个链路仿佛没有任何多余的环节。但真把这个方案往生产环境里一放问题就全冒出来了。我自己在帮几个团队做 AI 项目架构评审时几乎每次都有人提出类似的疑问为什么不直接让 Agent 连数据库再多包一层 API 不是脱裤子放屁吗这个问题的背后其实是很多研发同学对 AI Agent 架构边界感不够清晰。大家默认了能干活就等于该干活忽略了在数据访问这件事上能力和权限是两回事。先说结论AI Agent 完全可以通过某种方式直接碰数据库这在技术上行得通但在架构上属于极其危险的偷懒。RESTful API 不是给 Agent 添麻烦恰恰是给 Agent 装了一道安全带。没有这道安全带Agent 的每一次数据操作都是一次裸奔。1.2 直接从数据库拿数据的诱惑到底在哪要理解为什么有人会倾向于直接执行 SQL得先理解这个方案的甜点在哪里。对开发者来说最大的诱惑是开发效率。假设我要做一个内部数据分析 Agent直接给 Agent 一个数据库连接串把表结构告诉它再让它根据用户的自然语言生成 SQL——这个链路用 Python 写可能三百行就搞定了。但如果你要包一层 RESTful API你得定义接口、写 DTO数据传输对象、做鉴权、处理分页和错误码、设计限流策略……那工作量一下翻了好几倍。还有一个更隐晦的原因是灵活性的幻觉。你永远不知道用户会问出什么样的问题如果是固定接口用户一问超出接口能力范围的问题Agent 就傻了。但 SQL 是无限灵活的任何问题理论上都能拼一条 SQL 出来。这两个理由都成立但它们只是短期视角上的成立。真正进入生产环境之后这种灵活性和开发速度会变成你最贵的账单。我们接下来把账一笔一笔算清楚。2. 直接执行 SQL 的致命伤2.1 安全维度SQL 注入只是最表层的问题谈到直接执行 SQL很多人的第一反应是SQL 注入。这个认知没错但远远不够。常规的 SQL 注入是攻击者恶意构造输入利用程序拼接 SQL 语句的漏洞去执行非预期的命令。在 AI Agent 的场景下这个问题被放大成了一个新的维度大模型本身就会不经意间构造恶意或非预期的 SQL。不需要有人恶意攻击仅仅是 Agent 对用户问题的理解偏差就可能生成一条删表语句。比如用户问帮我清一下测试数据Agent 可能生成DELETE FROM orders看起来问题不大但如果没有加 WHERE 条件它清掉的是整个线上订单表。我见过一个真实的复盘记录某个团队接了一个内部数据 Agent测试时一切正常正式环境第一次跑就被用户一句这个月的收入数据不对帮我调整一下给带沟里了。Agent 生成了一条带着UPDATE的 SQL在执行时因为权限给得太宽真的改了正式环境的账单数据。后来花了两个多星期才把数据恢复回来。这种事故不是安全工程师黑客攻击而是架构上没有把 Agent 的行为约束在安全边界内。更麻烦的是Agent 生成的 SQL 不一定能稳定复现同样的用户输入在不同会话里可能生成逻辑不同的 SQL。这意味着你没法用传统的SQL 审批流来预先拦截动态生成的语句永远在规则之外。2.2 资源维度一条慢查询能拖垮整个库如果你觉得安全问题还能靠仔细调教提示词来缓解那资源问题就是实打实的物理伤害。数据库是一个共享的、有瓶颈的资源系统。连接数是有限的、锁是互斥的、IO 是有上限的。Agent 生成 SQL 时并不会像 DBA 一样考虑这条 SQL 是否走索引、是否会锁表、是否会全表扫描。它唯一的目标是产出符合用户问题的结果。举个例子用户问对比一下最近一年每个月的订单总量变化趋势Agent 可能会生成一条对千万级订单表做GROUP BY month的聚合查询。这条 SQL 在测试库上一秒跑完因为测试库只有十万条数据。但在生产库上它可能扫掉几张表、持锁几分钟、把连接池打满直接把在线交易事务拖死。平时我们做慢查询优化是一天看一次日志发现问题然后改索引。AI Agent 则是每时每刻都在生成新的查询你根本没有时间事后去优化——你只能做事前防护。防护的核心手段就是Agent 不能直接接触数据库资源它只能接触到预先定义好、经过性能评估的接口。2.3 权限和审计的颗粒度问题直接连数据库还有一个非常难解决的问题权限怎么分传统数据库访问模型里权限是基于角色的运营角色只能查订单表财务角色只能查账单表管理员角色才能写数据。但 AI Agent 面对的是开放性极强的用户问题它顶多能区分管理员会话和普通用户会话再细粒度的行级权限、列级权限用数据库账号体系很难做到。更深一层的问题是Agent 操作数据库的行为和用户本人的身份绑定了吗你想要审计是谁、在什么时间、通过哪个 Agent 会话、执行了什么数据操作如果所有 Agent 共用一个数据库账号你根本没法做归属追溯。万一出了数据泄露你连是谁引发的都不知道。只有通过 API 层在请求中注入用户身份、会话 ID才能做到每次数据操作都可审计、可追踪。3. RESTful API 为什么是更合理的边界3.1 API 本质上是业务语义的封装说完了直接执行 SQL 的坑我们再来看 API 的价值。很多人把 API 理解成技术接口但在我看来API 的真正价值是业务语义层的封装。直接执行 SQL等于把数据库表和列的结构暴露给 Agent。表结构是物理模型它往往是历史包袱和工程妥协的结果字段命名混乱、多表关联复杂、同一业务含义分散在多张表里。让 Agent 直接理解这些就像让一个实习生去翻阅部门十年的纸质档案——不是不行但错误率一定高。而 API 把物理结构翻译成了业务语义GET /api/v1/orders/summary?periodmonthlyregion...语义清晰、参数明确、返回结构稳定。Agent 不需要知道orders表里status字段到底是1表示已支付还是2表示已支付它只需要知道调用这个接口是查订单汇总的。更重要的是稳定可变的接口语义可以大幅降低 Agent 的规划难度。AI Agent 的规划Planning过程很大程度上依赖于对工具描述Tool Description的理解。API 的路径、参数、返回结构天然适合写成工具描述而数据库表结构则极其冗长且晦涩。实际测试中给 Agent 提供 10 个精心设计的 API比给它看 30 张表结构图任务执行正确率能高出一大截。3.2 权限收敛从给你整个库到只给你需要的RESTful API 让你能做到接口即权限。在设计 Agent 的数据访问层时你可以按功能域拆接口查询类接口、写操作类接口、管理类接口。每个接口在后端对应一个已经写死、经过评审的 SQL 或存储过程权限控制落在能不能调这个接口这个层面而不是能不能执行任意 SQL这个层面。举个例子Agent 只需要做查询订单状态这件事你就只给它开GET /api/v1/orders/{id}不它给开DELETE /api/v1/orders/{id}。这样做完之后就算 Agent 疯了它也只能做查询连删数据的 SQL 语句都拼不出来因为根本没有执行 SQL 的通道。这种按能力开放的模式和人类团队的管理方式高度一致。你不会给每个员工都发一把能打开全公司所有门的钥匙你只发他负责区域的门禁卡。AI Agent 也一样而且它比人类员工更容易犯糊涂更需要这种强制性的边界。3.3 可观测、可回滚、可限流生产落地必备三件套API 层还是天然的治理抓手。我梳理一下生产环境里 API 层相对直连 SQL 的三个核心优势第一可观测性。在 API 层你可以记录每一次调用的参数、耗时、返回状态、调用方身份。这些日志不仅是排查问题的依据还是优化 Agent 提示词的素材——通过分析 Agent 调了哪些接口、没调哪些接口你能判断出工具编排是否合理。第二快速降级。假设某个查询接口因为数据量暴涨变慢了在 API 层你可以在网关直接对特定接口做降级或者熔断让 Agent 返回当前暂时无法查询的友好提示而不是让底层数据库扛不住直接宕机。这在直连 SQL 的模式下几乎无法实现——你没办法对某条动态生成的 SQL做熔断。第三限流与配额。API 可以按调用方维度限制 QPS每秒查询数这样即便 Agent 进入了死循环、疯狂重复调用查询接口也能被挡在网关层。直连 SQL 的话你能做的只有断掉整个数据库连接代价非常粗暴。这里放一个对比表格看得更直观一些对比维度直接执行 SQLRESTful API 中间层权限精细度按数据库账号粗粒度控制接口级、按业务语义控制SQL 注入风险高存在不可控动态语句低SQL 不在外部传入性能管控难以对动态查询做限流网关层可限流/熔断审计追溯无法关联用户身份与 Agent 会话可在请求链路中注入全链路标识接口演进表结构变了 Agent 立即失效接口不变则 Agent 不受影响排查难度动态 SQL 难复现、难定位日志清晰、失败可复现3.4 什么情况下直连 SQL 也可以接受我不是一个绝对不允许主义者毕竟架构上没有银弹。直连 SQL 在两类场景下是可以接受的一类是纯离线分析场景。比如让 Agent 写 SQL 去做数据探索、生成临时报表目标库是一个隔离开的只读副本有独立的账号、独立的连接池就算 SQL 写烂了也影响不到线上业务。这时直连确实效率更高Agent 的灵活性也能充分释放。另一类是强可控实验环境。比如你在本地 Docker 里跑一个玩具数据库Agent 怎么折腾都行完全不影响任何业务。但这两类场景都有一个共同前提数据库可以被任意折腾且不会产生不可恢复的后果。你的生产环境显然不满足这个前提。所以核心判断标准就一句话如果 Agent 的某次数据操作出错后果是否在可接受范围内如果答案是不可接受那就别直连 SQL老老实实包 API。4. 实操给 AI Agent 设计数据访问层4.1 设计原则友好接口 明确权限 语义稳定的三大法则如果现在你要给 Agent 设计一套 RESTful API 数据访问层我建议你先预设三个设计原则。第一个原则接口的工具描述必须足够清晰。AI Agent 是通过工具描述Tool Description来理解接口的用途的。你需要给每个接口写好一段人话描述包含这个接口是干什么的、参数是什么含义、返回什么格式、什么情况下会报错。这些描述直接影响 Agent 是否能正确选用接口。描述写得太技术化比如Query order by IDAgent 理解得就会很浅写成查询订单详情订单 ID 必传返回订单状态、金额、物流信息Agent 就知道什么时候该用它。第二个原则所有写操作必须走显式确认才执行。在 API 设计层面写接口和读接口要物理分离。读接口可以让 Agent 自由调用写接口必须设置门槛——比如强制要求 Agent 在调用时携带一个确认参数例如confirmtrue或者在网关层对写接口做额外的权限校验。有些团队还要求 Agent 对写操作先调用一个预检查接口也就是先查一下执行条件是否满足然后再真正写。这些都是很有效的保护措施。第三个原则接口语义要稳定不与数据库表结构绑定。接口的返回字段最好带有一个稳定版本号因为 Agent 一旦在你的提示词中记住了这个接口返回这个结构你改了字段类型Agent 就会开始编造数据。避免这个问题的方法就是接口语义层稳定表结构再怎么调整API 返回的总是一个稳定的业务视图。4.2 一个最小可落地的实现示例抽象地说原则没有用我直接写一个最小示例。假设我要做一个订单查询 Agent后端接口设计方案如下接口 1按订单 ID 查询订单详情GET /api/v1/orders/{order_id}返回示例{ order_id: SO20250101001, status: paid, total_amount: 1299.50, customer_id: 10086, items: [ {product_name: 无线耳机, quantity: 1, price: 1299.50} ], created_at: 2025-01-01T10:32:18Z }接口 2查询订单汇总统计GET /api/v1/orders/summary?start_date2025-01-01end_date2025-01-31granularitydaily返回示例{ period: {start: 2025-01-01, end: 2025-01-31}, granularity: daily, items: [ {date: 2025-01-01, order_count: 42, total_amount: 52300.00} ] }在 Agent 侧我们把这些接口注册成工具描述用 Python 代码大致是这样tools [ { type: function, function: { name: get_order_detail, description: 根据订单号查询订单的详细信息包括订单状态、金额、商品列表、客户ID。当用户询问某个具体订单的详情时使用。, parameters: { type: object, properties: { order_id: { type: string, description: 订单号格式如 SO20250101001 } }, required: [order_id] } } }, { type: function, function: { name: get_order_summary, description: 按时间段汇总订单数量和销售总金额可按天或按月统计。当用户询问销量、销售额趋势或汇总数据时使用。, parameters: { type: object, properties: { start_date: {type: string, description: 开始日期YYYY-MM-DD}, end_date: {type: string, description: 结束日期YYYY-MM-DD}, granularity: {type: string, enum: [daily, monthly], description: 统计粒度按天或按月} }, required: [start_date, end_date, granularity] } } } ]在 API 后端每个接口内部执行的是预先写好的固定 SQL比如订单汇总接口SELECT DATE_TRUNC(:granularity, created_at) AS period, COUNT(*) AS order_count, SUM(total_amount) AS total_amount FROM orders WHERE created_at :start_date AND created_at :end_date GROUP BY DATE_TRUNC(:granularity, created_at) ORDER BY period;看到这里你应该能理解真正访问数据库的 SQL 依然是存在的但它被锁在 API 内部Agent 根本接触不到。外部传入的只有业务参数日期、订单号、粒度SQL 的结构完全受控。这就是RESTful API 保护 SQL的本质——不是不用 SQL而是不让 SQL 暴露给不可控的调用方。4.3 工具选型与原生 SQL 的取舍聊到实现自然会碰到一个灵魂拷问用 ORM对象关系映射还是原生 SQL我在几种方案里都踩过坑说点真实感受。如果 API 后端的逻辑比较简单就是查一张表、返回固定字段用 ORM 完全没问题开发效率高。但一旦涉及复杂聚合、报表统计、多表关联ORM 生成的 SQL 往往不是最优的而且排查问题难度翻倍——你要先在 ORM 的链路上捋清它最终执行的 SQL 是什么。我更推荐在这类接口中直接用原生 SQL因为接口是固定的SQL 也是固定的你完全可以预先把 SQL 做性能调优、加好索引并让 DBA 评审。另外多说一句最近大家关心的话题AI 生成 SQL 不是不能用而是要用对位置。现在有很多中间件可以接受自然语言、帮你生成 SQL然后交给数据库执行。这类工具在只读副本或分析型数据仓库上体验确实不错能显著提升临时数据分析的效率。但你要注意这类工具的本质还是 SQL 直连只不过替你做了生成环节。在生产业务库上我仍然持保守态度——除非你能接受上文中提到的那些事故风险否则不要拿生产库去赌生成式 SQL 的稳定性。5. 常见问题与排查经验5.1 接口太宽Agent 乱调用怎么办一种典型情况是你确实提供了 API但接口的查询参数给得太宽了。比如GET /api/v1/orders?statuspaidcustomer_id...amount_min...amount_max...参数组合很多Agent 在规划时可能传一些奇怪的参数组合导致后端查询条件过于宽泛拉出海量数据。我的建议是接口数量宁可多单个接口的能力宁可窄。把宽接口拆成多个窄接口能显著提高 Agent 的工具选择准确率。比如拆成查单笔订单详情、查某个客户的订单列表、查销售额汇总每个接口参数少、语义清晰Agent 就不容易困惑。如果实在避免不了参数较多的接口可以给参数加上严格约束比如规定枚举值、规定最大值必要时在后端校验参数超范围直接返回 400 错误。让 Agent 在报错中学会正确传参比让它自由发挥要安全得多。5.2 API 响应慢、信息碎片化怎么优化很多时候Agent 为了回答一个问题需要连续调多个接口比如先查用户信息、再查其订单、再查订单里的商品详情。串行下来可能七八个 HTTP 请求虽然每个接口只有几十毫秒累计起来很可能超过用户的响应预期。解决方法有两个方向一是为 Agent 场景设计聚合接口。比如新增一个GET /api/v1/customers/{id}/dashboard接口一次性返回客户的基本信息、最近订单和售后概览。Agent 只需要一次调用就能完成任务规划耗时和稳定性都会大幅改善。另一个方向是让接口支持批量查询。例如订单详情接口支持GET /api/v1/orders?idsSO001,SO002,SO003这样避免 Agent 对每笔订单各发一次请求。批量接口要注意设置最大数量上限防止一次拉取太多数据。5.3 要不要给 Agent 单独建一套专用 Schema这是一个很值得讨论的问题。如果你的 Agent 只是对接内部系统且后端 API 结构相对稳定直接用现有接口即可。但如果你的系统历史包袱重、接口混乱、字段含义不统一我建议做一个面向 Agent 的专用聚合层而不是让 Agent 去逐个对接二三十个历史接口。假设一下你的系统里查订单状态的接口有两个版本一个返回status另一个返回order_stateAgent 每次都要靠提示词去消歧。这种状态下你应该抽一个新的仅面向 Agent 的接口版本统一字段命名统一错误码只暴露 Agent 需要的那些能力。这也是目前不少团队做 Agent 落地时比较推荐的Agent 网关模式。5.4 避坑清单最后给一份踩过坑之后的速查清单拒绝把数据库连接串或 JDBC 配置写进 Agent 的提示词或工具描述里一旦泄露等同于把生产库交给外部。所有 Agent 调用的 API 都必须做鉴权至少用 API Key 或服务间认证认证凭证不要用明文用环境变量或密钥管理服务。接口日志必须包含请求方会话 ID方便还原 Agent 的完整思考链路。写操作的接口在网关层做二次确认模式第一次调用返回预执行结果第二次调用携带确认标记才真正写入。定期给 Agent 的 API 调用做回归测试因为后端参数变了、接口逻辑变了Agent 的规划能力会立刻退化。别忽略返回数据量的问题接口要默认分页否则 Agent 可能一次拉回几十万行数据直接把自身上下文窗口撑爆。我个人在实际操作中最受益的一个习惯是为每个 Agent 接口都维护一份人话版使用说明并且和 Agent 系统提示词里的工具描述保持同步。每次更新接口都先改使用说明再改代码最后验证 Agent 行为。这样折腾几轮以后Agent 的工具调用准确率肉眼可见地稳定下来。最后再分享一点个人的体会很多人觉得 RESTful API 多了一层、慢了半拍是给 AI Agent 拖后腿。但真正在线上跑过一阵子你就会发现这层 API 保护的恰恰是你最不能出问题的东西——生产数据和团队的安全底线。它让 Agent 变成一个有边界、可治理、出问题能兜住的员工而不是一个拿到数据库钥匙就乱跑的小孩。架构无优劣只有代价和边界清楚的边界才是 AI 能力落地的底气。