把已有业务 API 接给 AI Agent 时,为什么不能直接暴露通用 CRUD?从“修改商品”说起

简介: 本文探讨如何将现有后台API(如商城、CRM)安全接入AI Agent。指出通用PATCH接口虽便捷,但对Agent而言语义过宽、风险难控。主张将“修改商品”等操作拆解为`product_set_show`、`product_update_stock`等原子业务动作,明确意图、参数、后果、权限与重试语义,并强调服务端仍需严格校验。核心:复用API不等于暴露宽接口,而应收敛为可验证、可约束、可审计的业务能力。

让 AI 进入商城、CRM、ERP 或工单后台时,很多团队的第一反应是:

我们已经有后台 API 了,把接口描述交给模型不就行了吗?

复用现有 API 当然是对的,但“复用”不等于把通用 updatePATCH
接口原样暴露给 Agent。

对普通后台表单来说,一个“编辑商品”接口同时接收名称、价格、库存、上下架状态与扩展字段,用起来很方便。但当调用方变成一个会自主选工具、组参数和重试的 Agent,这个大口子会把几种完全不同的业务后果混在一起。

这篇文章就拆一个问题:

怎样把已有后台 API,收敛成 Agent 可以理解、可以约束、也可以验证的业务动作?

一、同一个通用 PATCH,为什么给后台表单能用,给 Agent 却太宽

假设商城已经有这样一个接口:

PATCH /admin/products/42
Content-Type: application/json

{
  "name": "演示商品",
  "price": 99,
  "stock": 1000,
  "is_show": 1,
  "attributes": {}
}

在传统后台里,页面负责意图采集、字段组织和操作提示;权限、字段与值域校验仍必须由后端执行。Agent 接入后,原来由 UI 提供的操作引导不再天然存在。

而 Agent 看到的只是接口名、文字描述和参数 Schema。它要自己回答:

  • 用户说“把这个商品处理一下”,究竟是改价、改库存还是上架?
  • 不需要修改的字段应该省略、传空,还是把旧值再传一次?
  • 价格调整和上架是同一个业务决定吗?
  • 其中一个字段失败时,整个动作算什么?
  • 请求超时后,这几种副作用能一起重试吗?

问题不是模型“不够聪明”,而是这个接口同时表达了太多业务意图。

二、CRUD 描述数据形状,业务动作描述真实后果

createreadupdatedelete 很适合描述数据层的基本操作,但业务用户并不会说:

请对商品 42 执行一次 update。

他们会说:

  • 把这件商品下架;
  • 库存改成 20;
  • 售价调整为 99 元;
  • 创建一件新商品,先不上架。

这四句话可能最终都更新同一张商品表,但它们的业务含义不同:

动作 主要后果 典型前置条件 风险与重试语义
创建商品 产生新的业务对象 名称、价格、库存合法 可能重复创建,不能盲目重试
上架 / 下架 改变对外可见性 商品存在,状态迁移合法 设置明确终态时通常可幂等
设置库存 改变可售数量 数量非负,符合库存规则 “设为 20”与“增加 20”不同
修改价格 改变交易条件 价格范围和当前活动允许 往往需要更强的审核与记录

当然,团队也可以继续给宽接口做字段级授权和条件策略,但治理复杂度通常已接近重新构造一组业务动作。如果不做这些收敛,风险分级、权限映射、审批内容、幂等策略和审计结果就会向最宽的那组语义妥协。

三、把“修改商品”拆成原子业务动作

更合适的工具表面是:

product_create
product_set_show
product_update_stock
product_update_price

这不是为了把工具数量做大,而是让每一项能力只承诺一类可说清的后果。

例如上下架只需要两个参数:

{
   
  "id": 42,
  "is_show": 1
}

库存调整则使用另一个不相干的 Schema:

{
   
  "id": 42,
  "stock": 20
}

这样做带来的不只是“参数更少”:

  1. 模型更容易选对动作;
  2. 工具 Schema 与处理器可以把实际生效的字段限定在当前动作内;
  3. 每项工具可以单独绑定风险、权限和审批要求;
  4. 结果能明确返回发生了什么;
  5. 失败和重试可以按具体业务动作设计。

在当前公开的 CRMEB BailingHub 独立 Adapter 中,商品创建、上下架、调库存和改价格就是四个独立 operation。其中创建、详情核验和上下架已在维护者控制的 CRMEB-KY v6 演示环境跑通;这是动作拆分的一条代表性证据,不表示全部能力已在所有环境逐项验证。

这里的“原子”指单一、可辨识的业务意图边界,不等于数据库 ACID 事务,也不代表 Exactly-once。当前 product_create 仍允许指定初始 is_show,且默认创建为上架;因此它是面向实际接入的实用拆分,不是“每个工具只能修改一个字段”的绝对范本。

四、一项工具应该说清五件事

一个可以安全接给 Agent 的业务动作,至少应该说清:

1. 什么时候使用

product_set_show 的描述应该是“上架或下架某个商品”,而不是模糊的“修改商品”。

2. 必需参数是什么

对上下架来说,理想的工具 Schema 只声明 idis_show,业务处理器再把 is_show 值域限定为 0 | 1。它不应让额外的价格、库存或分类字段产生业务效果。

当前 Adapter 2.4.1ToolSpec 尚未输出 additionalProperties: falseis_show0 | 1 也是由处理器再次检查;直接请求携带的其他 JSON 字段会被具体 handler 忽略,不是在 Schema 层一定被拒绝。如果项目要求“未知字段也必须 fail closed”,就需要显式关闭额外属性,并在服务端执行允许列表校验。

3. 会造成什么后果

工具应明确返回商品 ID 与最终 is_show,而不只是一句“处理成功”。

4. 它的风险与重试语义是什么

“把状态设为上架”与“创建一件商品”不能因为都是 POST 就共用同一套重试规则。前者可以被设计为幂等状态设置;后者如果没有业务幂等键,超时后直接重试可能创建重复商品。

5. 谁有权执行

工具层可以声明需要可信主体和某类 scope,但最终仍要让业务系统根据当前账号、角色、对象和实时状态做判断。

五、原子工具不是把校验逻辑从业务系统搬走

工具拆细之后,仍然不能只相信 Agent 发来的参数。

例如 product_update_stock 收到:

{
   
  "id": 42,
  "stock": -5
}

理想情况下,Schema 层应该把库存下限声明为 0,业务接口仍应再拒绝负数。当前 Adapter 2.4.1 只在工具声明中标记 stock 为整数,非负边界由业务 handler 实际执行。这也说明为什么服务端校验不能被 Schema 取代:请求可能不来自当前这个 Agent,也可能绕过工具发现层直接到达 Adapter。

执行前的业务系统至少还要确认:

当前操作主体存在且未停用
AND 账号具有对应后台权限
AND 商品存在且未删除
AND 参数符合当前业务规则
AND 对象当前状态允许这次迁移

在公开的 CRMEB Adapter 中,工具调用会先验证 BailingHub 请求签名,再使用 CRMEB 当前管理员、角色和原生权限点做实时裁决;通过后才解析业务参数并执行具体动作。

这个次序很重要:

请求是否来自可信中枢
-> 这个业务操作主体是谁
-> 他当前是否有权
-> 参数和对象是否满足业务条件
-> 执行并返回真实结果

就算工具名叫 product_publish,也不代表它可以跳过原系统的权限与业务校验。

六、当对象在“查询”与“执行”之间变了

Agent 通常会先查询商品,再根据返回结果决定下一步。但查询结果只代表那个时刻。

两步之间,另一位运营人员可能已经改过价格、清空库存或删除商品。所以成熟的动作还应根据后果强度,考虑条件更新:

{
   
  "id": 42,
  "stock": 20,
  "expected_stock": 12
}

或者:

{
   
  "id": 42,
  "is_show": 1,
  "expected_state": 0
}

当实际状态不再等于预期值时,接口返回冲突,让 Agent 重新查询和计算,而不是悠悠地覆盖他人的新结果。

expected_stockexpected_state 是推荐的条件更新设计示例,不是对当前 CRMEB Adapter 2.4.1 已实现字段的声称。把推荐设计和现存实现分开,比为了文章漂亮而偷换完成度更重要。

七、不同动作不应共用同一套失败和重试策略

原子动作还让系统可以精确标注:

  • 查询商品详情是只读且通常可重试;
  • 把上下架状态设为明确值,可以被设计为幂等更新;
  • 新建商品会产生新对象,如果没有业务幂等键,就不应在结果不确定时自动重放;
  • “库存增加 5”与“库存设为 5”的重试语义也完全不同。

当 HTTP 已经发出、但调用方没收到明确结果时,“是否能重试”不可以只看状态码,更不能因为工具属于同一个 update 大类就使用同一策略。

幂等与结果不确定已在前文专门拆解,本文不再重复;这里只需记住:动作越原子,失败后究竟可以做什么就越容易说清。

八、目标设计上线前至少做六组负向验证

正常路径跑通一次,只能证明“理想输入可以工作”。真正的边界在负向验证里。下面是把存量 API 产品化为 Agent 能力时应达到的目标矩阵,不是对 Adapter 2.4.1 已经通过全部六组测试的声称。

1. 多余字段

product_set_show 故意增加 pricestock,先验证它们绝不会改变商品价格或库存。如果契约要求未知字段失败关闭,还应验证 Schema 和服务端会明确拒绝,而不是静默忽略。当前 2.4.1 属于 handler 忽略无关字段的实现。

2. 值域越界

传入 is_show=7stock=-1 或非法价格,业务端应明确拒绝。

3. 对象失效

对不存在或已删除商品发起操作,不能把“影响 0 行”伪装成成功。

4. 权限失效

在查询后撤销当前管理员的商品操作权限,再执行写动作,业务系统必须按当前权限拒绝。

5. 状态漂移

先让 Agent 查到商品为下架,再由另一操作者改变它。对要求防止覆盖的高后果动作,应先实现条件更新,再验证本次会以状态冲突失败。当前 2.4.1 尚无 expected_* 条件字段,不能把这项写成现成保证。

6. 重复请求与不确定结果

用同一业务请求重复调用、人为丢失响应,观察它是返回已有结果、拒绝冲突,还是创建第二个对象。当前 2.4.1product_create 没有业务幂等去重,重复调用会再次插入;所以在自动重试之前,必须先补上幂等契约与结果查询能力。

九、ACC、BailingHub、Adapter 与业务系统各自负责什么

一个通用 CRUD 接口变成 Agent 能力,不是改一个接口名就结束了。

负责的事 不应替代的事
ACC / 能力契约 描述动作、参数、风险、主体要求和幂等语义 不保存商品真实状态
BailingHub / Agent 控制面 发现工具、绑定主体、编排执行并记录任务过程 不自称为 CRMEB 的商品与权限真值
独立 Adapter 把原子能力映射到目标系统、验证请求并转换参数 不建立第二套业务角色与数据主权
CRMEB 或其他业务系统 按当前账号、权限、对象和规则最终执行 不信任模型自称的身份或成功结果

这个分工让模型可以发挥擅长的语言理解和工具选择能力,同时不把业务权限、实时状态和最终后果搬进提示词。

十、把存量 API 接给 Agent 前的检查清单

  • [ ] 每项工具只表达一个明确业务动词;
  • [ ] 没有通用 fields、任意属性 Map 或整行更新入口;
  • [ ] 参数是完成该动作所需的最小集合;
  • [ ] 工具结果包含业务对象 ID 与最终状态;
  • [ ] 读、幂等设置、非幂等创建拥有不同的重试语义;
  • [ ] 风险、权限、审批和审计能绑定到具体动作;
  • [ ] 业务系统在执行时重新校验主体、对象和参数;
  • [ ] 高后果更新考虑预期状态或版本冲突;
  • [ ] 负向测试覆盖越权、越界、漂移、重复和结果不确定;
  • [ ] 文档把“能力声明”与“真人实测范围”分开。

结语

把已有业务系统接给 AI,最好的起点不是再写一套业务逻辑,而是复用已经稳定的 API 和权限体系。

但复用的单位不应是一个任意写字段的通用 CRUD 入口,而应是一个个业务人员能够说清、系统能够验证、失败后也知道怎样处理的原子动作。

通用数据修改
-> 拆成具体业务动词
-> 收紧参数与结果
-> 分别绑定权限、风险与重试语义
-> 由业务系统最终执行

工具更原子,不是让 Agent 变得更保守,而是让它真正可以在清晰边界内把事情办完。

延伸阅读

说明:本文中的 CRMEB 接入由独立 Adapter 实现,不代表 CRMEB 官方开发、合作、背书或全版本兼容。

相关文章
人工智能 缓存 前端开发
9146 40
人工智能 JavaScript 开发工具
3765 9
开发工具 Swift git
1429 2
缓存 JavaScript Shell
1732 2
人工智能 JavaScript 测试技术
1307 0
Shell API 调度
949 3
人工智能 JavaScript 测试技术
523 4
人工智能 Java BI
610 0

热门文章

最新文章