乐于分享
好东西不私藏

5.Spring AI 工具进阶与生态:构建企业级工具框架

5.Spring AI 工具进阶与生态:构建企业级工具框架

本文是 Spring AI Agent 开发学习系列的第 5 篇


开篇

上一篇文章我们学会了用 @Tool 定义工具。但在生产环境中,你还需要面对这些问题:

  • • ❓ 怎么确保工具输入是有效的? —— 邮箱格式、年龄范围、必填字段
  • • ❓ 怎么动态创建工具? —— 不依赖 @Tool 注解,运行时注册
  • • ❓ 怎么控制谁可以调用什么工具? —— 基于角色的权限控制
  • • ❓ 怎么集成第三方 API? —— 把外部服务包装成 AI 工具

这些就是我们今天要解决的进阶问题。

读完本文,你将收获:

  • • ✅ 工具参数验证与错误处理模式
  • • ✅ 使用 FunctionToolCallback 动态注册工具
  • • ✅ 通过 ToolContext 实现权限控制
  • • ✅ 将第三方 API 包装为 AI 工具
  • • ✅ 配套代码验证通过的所有接口

一、参数验证:让工具更健壮

工具调用流程

1.1 验证模式

在 @Tool 方法中直接进行参数验证:

@Tool(name = "send_email", description = "发送电子邮件")public String sendEmail(    @ToolParam(description = "收件人邮箱") String to,    @ToolParam(description = "邮件主题") String subject,    @ToolParam(description = "邮件正文") String body) {    // 格式验证    if (!to.matches("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$")) {        throw new IllegalArgumentException("邮箱格式无效:" + to);    }    // 必填验证    if (subject == null || subject.isBlank()) {        throw new IllegalArgumentException("邮件主题不能为空");    }    // 长度验证    if (body.length() > 1000) {        throw new IllegalArgumentException("正文超过1000字限制");    }    return "邮件发送成功 → " + to;}

当验证失败时:

  1. 1. 工具抛出 IllegalArgumentException
  2. 2. Spring AI 捕获异常,将错误信息返回给模型
  3. 3. 模型根据错误信息调整参数后重试
  4. 4. 或告知用户输入不合法

1.2 生产级验证工具

验证类型
示例工具
验证逻辑
格式验证
send_email
邮箱正则、内容长度
范围验证
create_user
年龄 18-120、用户名 3-20 字符
业务验证
book_flight
出发地≠目的地、支持城市列表

二、动态工具注册:摆脱 @Tool 束缚

@Tool 注解虽然方便,但有些场景需要运行时动态创建工具:

  • • 工具列表来自配置文件
  • • 为不同用户生成不同的工具
  • • 集合类操作需要统一注册

2.1 使用 FunctionToolCallback

Spring AI 提供了 FunctionToolCallback.builder() 来编程式创建工具:

// 无参数工具(Supplier)ToolCallback randomTool = FunctionToolCallback.builder("generate_random",    () -> "随机数:" + ThreadLocalRandom.current().nextInt(100, 1000))    .description("生成一个随机的三位数")    .build();

支持的四种函数签名:

函数类型
输入
输出
适用场景
Supplier<O>
生成随机数、UUID
Consumer<I>
日志记录、事件通知
Function<I, O>
数据处理、查询
BiFunction<I, ToolContext, O>
有+上下文
需要 ToolContext

2.2 实现 ToolCallbackProvider

@Componentpublic class DynamicToolProvider implements ToolCallbackProvider {    @Override    public ToolCallback[] getToolCallbacks() {        return new ToolCallback[]{            createRandomNumberTool(),            createSystemInfoTool(),            createUuidTool()        };    }    private ToolCallback createSystemInfoTool() {        return FunctionToolCallback.builder("get_system_info",            () -> {                Runtime rt = Runtime.getRuntime();                return "操作系统:" + System.getProperty("os.name")                    + ",内存:" + usedMem + "MB / " + totalMem + "MB";            })            .description("获取服务器系统信息")            .build();    }}

2.3 注册到 ChatClient

chatClient.prompt()    .user("生成一个随机数")    .toolCallbacks(dynamicToolProvider) // ← ToolCallbackProvider    .call()    .content();

toolCallbacks() 接受三种参数:

  • • ToolCallback... — 单个或多个回调
  • • List<ToolCallback> — 回调列表
  • • ToolCallbackProvider... — 提供者(自动调用 getToolCallbacks()

三、工具上下文与权限控制

3.1 ToolContext 是什么?

ToolContext 是 Spring AI 用来给工具传递上下文的机制,比如当前用户、角色、请求 ID 等:

权限控制流程
// 在 Controller 中设置上下文chatClient.prompt()    .user("查看用户 zhangsan 的信息")    .tools(contextAwareToolService)    .toolContext(Map.of(        "current_user", "admin",        "user_role", "admin"    ))    .call()    .content();

在工具方法中通过参数接收:

@Tool(name = "get_user_profile", description = "获取用户信息")public String getUserProfile(    @ToolParam(description = "用户名") String username,    ToolContext ctx) { // ← 自动注入 ToolContext    String role = (String) ctx.getContext().get("user_role");    if (!"admin".equals(role)) {        return "权限不足:需要 admin 角色";    }    // ... 执行查询}

⚠️ 注意:ToolContext 参数不需要 @ToolParam 注解。

3.2 基于角色的访问控制

用户请求 → toolContext({"role": "user"})    ↓工具检查 role → 不是 admin → 返回"权限不足"    ↓模型告知用户:你需要管理员权限

对比效果:

角色
请求
结果
user
查看薪资数据
❌ 权限不足
admin
查看薪资数据
✅ 返回薪资统计

四、第三方 API 集成

API集成架构

4.1 在工具中调用外部 API

工具方法内部可以直接调用 HTTP API:

@Componentpublic class ExternalApiToolService {    private final RestTemplate restTemplate = new RestTemplate();    @Tool(name = "get_exchange_rate", description = "查询实时汇率")    public String getExchangeRate(        @ToolParam(description = "源货币") String from,        @ToolParam(description = "目标货币") String to) {        // 调用第三方汇率 API        String url = "https://api.exchangerate-api.com/v4/latest/" + from;        Map<String, Object> response = restTemplate.getForObject(url, Map.class);        Map<String, Double> rates = (Map<String, Double>) response.get("rates");        return from + " → " + to + " = " + rates.get(to);    }}

4.2 本项目的模拟 API 调用

由于教程的限制,我们用内置数据模拟了三个常用场景:

工具
模拟数据源
真实替代方案
get_ip_info
内置 IP 数据库
ip-api.com[1]
get_exchange_rate
内置汇率表
exchangerate-api.com[2]
get_stock_price
内置股票数据
yahoo-finance, alphavantage

五、完整接口一览

本项目部署在 8084 端口,覆盖 4 个主题共 10 个端点:

参数验证

端点
示例
验证内容
GET /api/advanced/email
发送邮件
邮箱格式、必填字段、长度限制
GET /api/advanced/create-user
创建用户
用户名长度、年龄范围、角色枚举
GET /api/advanced/book-flight
预订航班
目的地列表、人数范围

权限控制

端点
角色参数
效果
GET /api/advanced/profile?role=admin
admin/user
查看他人信息
GET /api/advanced/sensitive?role=admin
admin/user
admin 可查看,user 被拒

第三方 API(模拟)

端点
说明
GET /api/advanced/exchange-rate
汇率查询(USD→CNY→EUR)
GET /api/advanced/stock
股票价格查询
GET /api/advanced/ip-info
IP 地址归属查询

动态注册

端点
说明
GET /api/advanced/dynamic
随机数生成(Supplier 风格)
GET /api/advanced/system-info
服务器系统信息


📌 核心知识点总结

✅ 掌握内容

  • • @Tool 方法中的参数验证与异常处理
  • • FunctionToolCallback.builder() 动态创建工具
  • • ToolCallbackProvider 接口的实现与注册
  • • ToolContext 传递用户上下文实现权限控制
  • • 第三方 API 包装为 AI 工具的模式

🔑 关键要点

  1. 1. 验证要前置:在工具方法入口验证所有参数,抛出 IllegalArgumentException
  2. 2. 动态 vs 注解:注解适合固定工具,FunctionToolCallback 适合运行时创建
  3. 3. 上下文就是安全ToolContext 不只是传参,更是实现权限控制的核心机制
  4. 4. API 集成模式:工具方法 = 普通 Java 方法 = 可以调用任何 API

🎯 实战能力

读完本文,你应该能够:

  • • ✅ 在工具方法中实现参数校验和错误提示
  • • ✅ 使用 FunctionToolCallback 动态注册工具
  • • ✅ 通过 ToolContext 实现角色权限控制
  • • ✅ 将任意外部 API 封装为 AI 可调用的工具

下期预告

第6篇:聊天记忆与会话管理——让 AI 记住每一次对话

当前的 AI 每一次对话都是独立的,它不记得你刚才说了什么。下期我们将解决这个问题,让 AI 拥有"记忆"。

敬请期待!


关注本系列,系统掌握 Spring AI Agent 开发!🔔

4.Spring AI 工具调用:让 AI 模型具备行动能力

引用链接

[1] ip-api.com: https://ip-api.com[2] exchangerate-api.com: https://exchangerate-api.com