
给 Agent 接一个发邮件工具很容易,真正难的是在它把邮件发出去之前,让人看一眼收件人、主题和正文。AI 决定“要做什么”,不等于它已经获得“可以执行”的授权。Spring AI 2.1 新增的工具确认机制,正好把这两件事拆开。
假设助手有两个工具:一个查询联系人邮箱,一个发送邮件。前者只读,后者不可撤销。正确做法不是让每一步都弹确认,而是只拦截有副作用的操作:
@Tool(name = "send-email",
description = "Sends an email",
requiresConfirmation = true)
public String sendEmail(@ToolParam(description = "Email to send") Email email) {
mailClient.send(email);
return "sent";
}
@Tool(name = "find-email", description = "Finds an email address by name")
public String findEmail(String name) {
return contacts.findEmail(name);
}加上 requiresConfirmation = true 后,Spring AI 不会直接执行 send-email,而是在 ChatResponse 中返回一个待确认的工具调用。查询邮箱仍然自动执行,不会把用户淹没在确认弹窗里。
常见聊天循环通常这样写:
String answer = chatClient.prompt(input).call().content();这只拿到了文本,把待确认工具调用丢掉了。接入确认机制后必须拿完整响应:
ChatResponse response = chatClient.prompt(input)
.advisors(SessionAdvisors.session(sessionId))
.call()
.chatResponse();然后检查其中是否有等待人工决策的调用。核心循环可以概括为:
while (!ToolCallDecisions.awaitingDecision(response).isEmpty()) {
ToolCallDecisions decisions = askUser(response);
var result = toolCallingManager.executeToolCalls(
new Prompt(List.of(), options), response, decisions);
response = chatClient.prompt()
.messages(result.conversationHistory().getLast())
.advisors(SessionAdvisors.session(sessionId))
.call()
.chatResponse();
}循环不能省略:用户批准发邮件后,模型可能基于工具结果又决定调用另一个高风险工具,仍然要再次确认。
控制台、Web 弹窗或企业微信交互消息都可以承载确认,但至少要展示工具名和完整参数:
Tool: send-email
Arguments:
{
"recipient": "bob@example.com",
"subject": "Project update",
"body": "..."
}
Allow? (y/N):默认必须是拒绝,只有显式输入 y 或点击“允许”才执行。一次响应可能包含多个工具调用,甚至同一个工具被调用多次,所以每个决策都要绑定 Spring AI 返回的 tool-call key,不能简单按工具名批量放行。
用户拒绝工具调用后,应用不应该直接报错退出。ToolCallingManager 会把“已拒绝”写回对话历史,模型可以继续说“好的,这封邮件不会发送”,或者建议修改正文。这样授权边界清晰,同时交互体验不会被打断。
恢复会话时要继续携带同一个 sessionId,让 Session 记忆覆盖确认前后的完整过程。否则 Agent 可能忘记刚刚被拒绝的动作,在下一轮又提出相同调用。
一个简单的判断标准是:操作是否可撤销、是否影响外部世界、是否可能造成资金或声誉损失。
工具确认不是为了让用户多点一次按钮,而是把“模型生成意图”和“系统执行权限”分离。Agent 可以起草邮件、准备 SQL、生成退款请求,但真正越过副作用边界的那一刻,应该由人决定。这个功能很小,却是 Agent 从演示走向生产环境的分水岭。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。