Skip to main content

哪些工具该弹确认,声明机制设计

比特严选的用户刚买了一台iPhone,又发来一句话:

刚买的手机我不想要了,还没发货的话帮我取消。

要办这件事,Agent得先找到那笔订单,查清当前状态;如果还允许取消,再发起取消调用。两个动作处理的是同一句请求,对业务数据的影响却不同:查订单只是读取信息;取消订单会改变订单状态,还要归还占用的库存和优惠券。按比特严选当前的配置,前面的查询不弹确认卡,后面的取消要让用户核对订单号、点同意后才执行。

上一篇讲了工具执行前怎样停下来等用户。本篇接着回答:系统怎么知道哪个工具该停,哪个工具可以继续?

哪些工具要确认,能让模型自己判断吗​

在提示词里写一句“涉及写操作时,先向用户确认”,模型通常能理解,也能据此先问一句。但问不问取决于这一次推理。例如用户已经说了“帮我取消”,模型可能把这句话视为充分授权,直接生成取消调用。

用户表达了意愿,不代表模型选中的订单号一定正确。所以Ragent的确认不看模型有没有问过,而是看这次调用的工具属不属于需要确认的范围:属于,就由程序停下来,让用户核对即将提交的参数。接下来要解决的,就是怎样确定这个范围。

按工具名里的动词判断,看起来很直接:名字里有取消、退款,就要求确认。但更新购物车同样会改业务数据,只列取消和退款就会漏掉它;把关键词扩大到订单、售后,又会把查订单、查售后进度一起算进去。工具少、命名约定严格时,关键词规则可以工作;工具增多以后,这份规则要不断追着工具名称和描述修改,描述里还可能写着“查询取消进度”,出现了取消两个字,实际并不执行取消。

Ragent把这件事交给工具提供方声明。写工具的人知道它会不会修改数据,就在工具定义里明确告诉调用方,后端按这个声明判断,不需要模型每次猜,也不用从自然语言描述里提取关键词。

工具怎样声明自己是只读还是写操作​

MCP Server把工具清单交给客户端时,每个工具除了名字、描述、参数格式,还可以带一组行为说明。这组信息放在annotations里,其中readOnlyHint表示工具是否只读。对订单工具来说,可以直接理解为:这次操作是不是只读取数据,不改变业务状态。查订单声明为只读,取消订单声明为非只读。

这个字段跟着工具定义走,不在模型填写的参数中。

先看查订单。下面是BitOrderQueryMcpExecutor.buildTool()构造工具定义的部分,行内注释为本文添加:

// 省略 inputSchema 的构造
return Tool.builder()
.name(TOOL_ID)
.description("查询当前登录用户的订单。传订单号返回单笔详情,含状态、下单支付发货签收四个时间、"
+ "商品明细、金额与优惠、运单号、收货信息,已签收的还会给出七天无理由窗口的剩余天数;"
+ "不传订单号返回最近几笔订单摘要。用户说不出订单号时先不传参数拿最近订单,"
+ "再按用户确认的那一单继续。运单号只能从这里取,不要自行构造")
.inputSchema(inputSchema)
// 工具提供方声明:这是只读操作
.annotations(McpToolAnnotations.READ_ONLY)
.build();

描述告诉模型什么时候查、参数从哪里来;READ_ONLY告诉客户端这项操作的性质。

取消订单由BitOrderCancelMcpExecutor提供,buildTool()结构相同,声明换成McpToolAnnotations.WRITE:

// 省略 inputSchema 的构造
return Tool.builder()
.name(TOOL_ID)
.description("取消当前登录用户的一笔订单,同时归还它占用的库存与优惠券。"
+ "只有待支付和已支付待发货的订单能取消,已发货之后只能拒收或走售后。"
+ "取消前先用订单查询确认订单号与当前状态")
.inputSchema(inputSchema)
// 工具提供方声明:这项操作会修改业务数据
.annotations(McpToolAnnotations.WRITE)
.build();

这两个常量都定义在McpToolAnnotations中,使用MCP SDK的ToolAnnotations类型。构造参数的第二项就是readOnlyHint,其他几项是另外几种行为提示,本篇不展开:

// 第二项为 true:明确声明只读
public static final ToolAnnotations READ_ONLY =
new ToolAnnotations(null, true, false, true, false, null);

// 第二项为 false:明确声明非只读
public static final ToolAnnotations WRITE =
new ToolAnnotations(null, false, false, false, false, null);

到这里,工具已经说明了自己会做什么,但协议并没有替Ragent定好怎样弹卡。读写声明怎样影响执行,仍由客户端的确认规则决定。

声明也不等于验证。MCP官方规范 明确说ToolAnnotations里的字段都是提示,不保证如实描述工具行为,客户端不应仅凭不可信服务端的声明决定怎样使用工具。工具标成只读,并不影响它写数据库。同一版规范的工具章节还要求客户端在敏感操作前向用户确认、调用前把工具入参展示给用户;Ragent的确认卡做的就是这件事,声明只是用来决定哪些调用要走这一步。

解锁付费内容,👉戳​