Tools(function-call)

在这里插入图片描述
想做企业级智能应用开发, 你肯定会有需求要让大模型和你的企业API能够互连 。

在之前我们可以通过链接多个模型的方式达到,但是很麻烦,那用tools,可以轻松完成。

tool calling也可以直接叫tool(也称function-call),主要用于提供大模型不具备的信息和能力:

  1. 信息检索:可用于从外部源(如数据库、Web服务、文件系统或Web 搜索引擎)检索信息。目是增强模型的知识,使其能够回答无法回答的问题。例如,工具可用于检索给定位置的当前天气索最新的新闻文章或查询数据库以获取特定记录。这也是一种检索增强方式。

  2. 采取行动:例如发送电子邮件、在数据库中创建新记录、提交表单或触发工作流。目标是自动执本需要人工干预或显式编程的任务。例如,可以使用工具为与聊天机器人交互的客户预订航班, 页上填写表单等。

需要使用tools必须要先保证大模型支持。 比如ollama列出了支持tool的模型

在这里插入图片描述

Spring AI Tools 快速参考指南

在这里插入图片描述

🎯 项目概述

05tools 是一个演示 Spring AI 工具调用(Function Calling)功能的项目,展示了如何让 AI 大模型调用 Java 方法作为工具,实现 AI 与业务系统的无缝集成。

核心价值:

  • 让 AI 不仅能理解和生成文本,还能执行实际的业务操作
  • 将 AI 对话能力与现有业务系统无缝连接
  • 支持权限控制和动态工具配置

📦 项目结构

在这里插入图片描述

05tools/
├── Application.java              # Spring Boot 启动类
├── ToolsController.java          # REST 控制器(/tool 接口)
├── ToolService.java              # 工具服务(定义工具方法)
├── TicketService.java            # 业务服务(票务逻辑)
└── config/
    └── SecurityConfig.java       # Spring Security 配置

🔧 核心概念

1. 工具定义方式

方式一:注解式(推荐)
@Service
public class ToolService {
    @Tool(description = "退票")
    @PreAuthorize("hasRole('ADMIN')")
    public String cancel(
        @ToolParam(description = "预定号,可以是纯数字") String ticketNumber,
        @ToolParam(description = "真实人名") String name
    ) {
        ticketService.cancel(ticketNumber, name);
        return "退票成功!";
    }
}
方式二:编程式(动态配置)
public List<ToolCallback> getToolCallList(ToolService toolService) {
    Method method = ReflectionUtils.findMethod(
        ToolService.class, "cancel", String.class, String.class
    );
    
    ToolDefinition toolDefinition = ToolDefinition.builder()
        .name("cancel")
        .description("退票")
        .inputSchema("""{
            "type": "object",
            "properties": {
                "ticketNumber": {"type": "string", "description": "预定号"},
                "name": {"type": "string", "description": "真实人名"}
            },
            "required": ["ticketNumber", "name"]
        }""")
        .build();
    
    ToolCallback toolCallback = MethodToolCallback.builder()
        .toolDefinition(toolDefinition)
        .toolMethod(method)
        .toolObject(toolService)
        .build();
    
    return List.of(toolCallback);
}

2. 工具注册

@RestController
public class ToolsController {
    ChatClient chatClient;

    public ToolsController(ChatClient.Builder builder, ToolService toolService) {
        this.chatClient = builder
            .defaultSystem("""
                # 角色
                你是智能航空客服助手
                ## 要求
                严禁随意补全或猜测工具调用参数。
                """)
            .defaultTools(toolService)  // 注册工具
            .build();
    }

    @RequestMapping("/tool")
    public String tool(@RequestParam String message) {
        return chatClient.prompt()
            .user(message)
            .call().content();
    }
}

🚀 快速开始

1. 配置

# application.properties
spring.ai.dashscope.api-key=your-api-key

2. 定义工具

@Tool(description = "工具描述")
public String myTool(@ToolParam(description = "参数描述") String param) {
    return "执行结果";
}

3. 注册工具

ChatClient chatClient = builder
    .defaultTools(toolService)
    .build();

4. 测试

curl "http://localhost:8080/tool?message=我要退票,订单号12345,姓名张三"

🔄 工作流程

用户输入
  ↓
ChatClient 构建 Prompt(包含工具定义)
  ↓
大模型分析 → 识别需要调用的工具 → 提取参数
  ↓
ToolCallback 执行器
  ↓
权限检查 (@PreAuthorize)
  ↓
执行 Java 方法
  ↓
返回结果给大模型
  ↓
大模型生成自然语言回复
  ↓
返回给用户

💡 使用示例

示例 1:退票工具

定义:

@Tool(description = "退票")
@PreAuthorize("hasRole('ADMIN')")
public String cancel(
    @ToolParam(description = "预定号") String ticketNumber,
    @ToolParam(description = "真实人名") String name
) {
    ticketService.cancel(ticketNumber, name);
    return name + "退票成功!";
}

使用:

用户:我要退票,订单号12345,姓名张三
AI:正在为您办理退票... [自动调用 cancel("12345", "张三")]
AI:张三退票成功!

示例 2:查询天气工具

定义:

@Tool(description = "获取指定位置天气")
public String getAirQuality(
    @ToolParam(description = "纬度") double latitude,
    @ToolParam(description = "经度") double longitude
) {
    return "天晴";
}

使用:

用户:北京今天天气怎么样?
AI:正在查询... [自动调用 getAirQuality(39.9042, 116.4074)]
AI:北京今天天晴。

🔐 权限控制

方法级权限

@Tool(description = "退票")
@PreAuthorize("hasRole('ADMIN')")  // 只有管理员可以调用
public String cancel(String orderId, String name) {
    // ...
}

获取当前用户

@Tool(description = "退票")
public String cancel(String orderId, String name) {
    String username = SecurityContextHolder.getContext()
        .getAuthentication().getName();
    // ...
}

⚠️ 注意事项

1. System Prompt 设计

关键要求:

  • 明确角色和职责
  • 强调不要随意猜测参数
  • 参数缺失时提示用户补充
.defaultSystem("""
    # 角色
    你是智能航空客服助手
    ## 要求
    严禁随意补全或猜测工具调用参数。
    参数如缺失或语义不准,请不要补充或随意传递,请直接放弃本次工具调用。
    """)

2. 参数描述要详细

// ❌ 不好的描述
@ToolParam(description = "订单号") String orderId

// ✅ 好的描述
@ToolParam(description = "订单号,格式为纯数字,如:12345,必填") String orderId

3. 工具方法不能自己 new

// ❌ 错误:自己 new 无法解析依赖注入
ToolService toolService = new ToolService();
ToolCallback callback = MethodToolCallback.builder()
    .toolObject(toolService)  // 依赖注入会失败
    .build();

// ✅ 正确:使用 Spring 注入的 Bean
@Autowired
private ToolService toolService;

ToolCallback callback = MethodToolCallback.builder()
    .toolObject(toolService)  // 依赖注入正常
    .build();

4. 错误处理

@Tool(description = "退票")
public String cancel(@ToolParam String orderId, @ToolParam String name) {
    try {
        ticketService.cancel(orderId, name);
        return "退票成功";
    } catch (Exception e) {
        log.error("退票失败", e);
        return "退票失败,请稍后重试";
    }
}

🎓 最佳实践

1. 单一职责

每个工具只做一件事:

// ✅ 好的设计
@Tool(description = "退票")
public String cancel(String orderId, String name) { ... }

@Tool(description = "查询订单")
public OrderInfo queryOrder(String orderId) { ... }

2. 参数验证

@Tool(description = "退票")
public String cancel(@ToolParam String orderId, @ToolParam String name) {
    if (orderId == null || orderId.trim().isEmpty()) {
        return "订单号不能为空";
    }
    // 执行业务逻辑
    return ticketService.cancel(orderId, name);
}

3. 日志记录

@Tool(description = "退票")
public String cancel(@ToolParam String orderId, @ToolParam String name) {
    log.info("开始退票 - 订单号: {}, 姓名: {}", orderId, name);
    String result = ticketService.cancel(orderId, name);
    log.info("退票成功 - 订单号: {}", orderId);
    return result;
}

🔍 常见问题

Q1: AI 不调用工具怎么办?

A: 检查以下几点:

  1. System Prompt 是否明确要求使用工具
  2. 工具描述是否清晰
  3. 用户输入是否包含足够信息
  4. 参数描述是否详细

Q2: 参数提取错误怎么办?

A:

  1. @ToolParam 中提供更详细的描述
  2. 在 System Prompt 中强调不要猜测参数
  3. 在工具方法中进行参数验证

Q3: 权限控制不生效?

A:

  1. 确保 @EnableMethodSecurity 已启用
  2. 确保用户已登录并具有相应角色
  3. 检查 @PreAuthorize 表达式是否正确

Q4: 动态工具配置如何使用?

A:

ChatClient chatClient = builder
    .defaultToolCallbacks(toolService.getToolCallList(toolService))
    .build();

Spring AI Tools 工具调用系统分析文档

📋 目录


📖 项目概述

项目定位

05tools 是一个基于 Spring AI 框架的**工具调用(Function Calling / Tool Calling)**演示项目,展示了如何让 AI 大模型调用 Java 方法作为工具,实现 AI 与业务系统的无缝集成。

核心价值

  • AI 能力扩展:让 AI 模型不仅能理解和生成文本,还能执行实际的业务操作
  • 业务系统集成:将 AI 对话能力与现有业务系统(如票务系统)无缝连接
  • 权限控制:集成 Spring Security,确保工具调用的安全性
  • 动态配置:支持基于注解的声明式工具注册和动态编程式工具配置

应用场景

  1. 智能客服系统:AI 可以帮用户退票、查询订单、获取天气等
  2. 业务自动化:AI 根据用户意图自动调用相应的业务接口
  3. 多工具协作:AI 可以组合调用多个工具完成复杂任务
  4. 权限控制:不同角色的用户只能调用授权的工具

🎯 核心功能

1. 工具声明与注册

注解式工具定义(推荐)

使用 @Tool@ToolParam 注解声明工具:

@Tool(description = "退票")
@PreAuthorize("hasRole('ADMIN')")  // 权限控制
public String cancel(
    @ToolParam(description = "预定号,可以是纯数字") String ticketNumber,
    @ToolParam(description = "真实人名(必填,必须为人的真实姓名,严禁用其他信息代替;如缺失请传null)") String name
) {
    // 业务逻辑
    ticketService.cancel(ticketNumber, name);
    return username + "退票成功!";
}

特点:

  • ✅ 声明式,代码简洁
  • ✅ 自动生成工具描述和参数 Schema
  • ✅ 支持 Spring Security 权限控制
  • ✅ 支持依赖注入
编程式工具定义(动态配置)

使用 ToolDefinitionMethodToolCallback 动态构建工具:

public List<ToolCallback> getToolCallList(ToolService toolService) {
    // 1. 获取方法
    Method method = ReflectionUtils.findMethod(
        ToolService.class, "cancel", String.class, String.class
    );
    
    // 2. 构建工具定义
    ToolDefinition toolDefinition = ToolDefinition.builder()
        .name("cancel")
        .description("退票")
        .inputSchema("""{
            "type": "object",
            "properties": {
                "ticketNumber": {
                    "type": "string",
                    "description": "预定号,可以是纯数字"
                },
                "name": {
                    "type": "string",
                    "description": "真实人名"
                }
            },
            "required": ["ticketNumber", "name"]
        }""")
        .build();
    
    // 3. 构建 ToolCallback
    ToolCallback toolCallback = MethodToolCallback.builder()
        .toolDefinition(toolDefinition)
        .toolMethod(method)
        .toolObject(toolService)  // 不能自己 new,需要依赖注入
        .build();
    
    return List.of(toolCallback);
}

特点:

  • ✅ 支持运行时动态配置
  • ✅ 可以从数据库读取工具配置
  • ✅ 可以根据用户角色动态加载工具
  • ⚠️ 需要手动构建 JSON Schema

2. 工具注册到 ChatClient

this.chatClient = ChatClientBuilder
    .defaultSystem("""
        # 角色
        你是智能航空客服助手
        ## 要求
        严禁随意补全或猜测工具调用参数。参数如缺失或语义不准,请不要补充或随意传递,请直接放弃本次工具调用。
        """)
    .defaultTools(toolService)  // 注册工具服务
    // 或使用动态工具列表
    // .defaultToolCallbacks(toolService.getToolCallList(toolService))
    .build();

3. AI 自动工具调用

用户发送消息后,AI 会自动:

  1. 理解用户意图:分析用户想要做什么
  2. 选择合适工具:从注册的工具中选择匹配的工具
  3. 提取参数:从用户消息中提取工具所需的参数
  4. 调用工具:执行 Java 方法
  5. 返回结果:将工具执行结果整合到回复中

示例对话:

用户:我要退票,订单号是12345,姓名是张三
AI:正在为您办理退票...
    [自动调用 cancel("12345", "张三")]
AI:张三退票成功!

🔧 技术原理

1. 工具调用工作流程

┌─────────────┐
│  用户输入    │
│ "我要退票"   │
└──────┬──────┘
       │
       ▼
┌─────────────────────────────────┐
│      ChatClient                  │
│  ┌───────────────────────────┐  │
│  │  1. 构建 Prompt            │  │
│  │  2. 添加工具定义到 Prompt  │  │
│  │  3. 调用 ChatModel         │  │
│  └──────┬────────────────────┘  │
└─────────┼───────────────────────┘
          │
          ▼
┌─────────────────────────────────┐
│      ChatModel (大模型)          │
│  ┌───────────────────────────┐  │
│  │  1. 理解用户意图           │  │
│  │  2. 识别需要调用的工具     │  │
│  │  3. 提取工具参数           │  │
│  │  4. 返回 ToolCall 请求     │  │
│  └──────┬────────────────────┘  │
└─────────┼───────────────────────┘
          │
          ▼
┌─────────────────────────────────┐
│      ToolCallback 执行器         │
│  ┌───────────────────────────┐  │
│  │  1. 解析 ToolCall          │  │
│  │  2. 参数验证               │  │
│  │  3. 权限检查               │  │
│  │  4. 调用 Java 方法         │  │
│  │  5. 返回执行结果           │  │
│  └──────┬────────────────────┘  │
└─────────┼───────────────────────┘
          │
          ▼
┌─────────────────────────────────┐
│      ChatModel (再次调用)        │
│  ┌───────────────────────────┐  │
│  │  1. 接收工具执行结果       │  │
│  │  2. 生成自然语言回复       │  │
│  └──────┬────────────────────┘  │
└─────────┼───────────────────────┘
          │
          ▼
┌─────────────┐
│  返回给用户  │
│ "退票成功!" │
└─────────────┘

2. 工具定义到 JSON Schema 的转换

Spring AI 会自动将 @Tool 注解的方法转换为 JSON Schema,供大模型理解:

Java 方法:

@Tool(description = "退票")
public String cancel(
    @ToolParam(description = "预定号") String ticketNumber,
    @ToolParam(description = "真实人名") String name
)

自动生成的 JSON Schema:

{
  "type": "object",
  "properties": {
    "ticketNumber": {
      "type": "string",
      "description": "预定号"
    },
    "name": {
      "type": "string",
      "description": "真实人名"
    }
  },
  "required": ["ticketNumber", "name"]
}

发送给大模型的工具定义:

{
  "name": "cancel",
  "description": "退票",
  "parameters": {
    "type": "object",
    "properties": {
      "ticketNumber": {
        "type": "string",
        "description": "预定号"
      },
      "name": {
        "type": "string",
        "description": "真实人名"
      }
    },
    "required": ["ticketNumber", "name"]
  }
}

3. 权限控制机制

Spring Security 集成
@Tool(description = "退票")
@PreAuthorize("hasRole('ADMIN')")  // 只有 ADMIN 角色可以调用
public String cancel(String ticketNumber, String name) {
    // 获取当前登录用户
    String username = SecurityContextHolder.getContext()
        .getAuthentication().getName();
    // 执行业务逻辑
    ticketService.cancel(ticketNumber, name);
    return username + "退票成功!";
}

权限检查流程:

  1. 用户请求 → Spring Security 过滤器链
  2. 身份认证 → 验证用户名密码
  3. 工具调用 → @PreAuthorize 检查权限
  4. 权限通过 → 执行工具方法
  5. 权限拒绝 → 抛出 AccessDeniedException

4. 参数提取与验证

大模型会根据用户输入自动提取参数:

用户输入: “我要退票,订单号是12345,姓名是张三”

大模型提取的参数:

{
  "ticketNumber": "12345",
  "name": "张三"
}

参数验证:

  • 类型检查:确保参数类型匹配(String、int、double 等)
  • 必填检查:检查 required 字段中的参数是否存在
  • 业务验证:在工具方法内部进行业务逻辑验证

5. 多工具协作

AI 可以组合调用多个工具完成复杂任务:

示例:

用户:帮我查一下北京的天气,然后退掉订单12345的票

AI 执行流程:
1. 调用 getAirQuality(39.9042, 116.4074) → "天晴"
2. 调用 cancel("12345", "张三") → "退票成功!"
3. 生成回复:"北京今天天晴。您的订单12345已成功退票。"

💼 业务价值

1. 提升用户体验

传统方式:

用户:我要退票
客服:请提供订单号和姓名
用户:订单号12345,姓名张三
客服:正在处理...
客服:退票成功

AI 工具调用方式:

用户:我要退票,订单号12345,姓名张三
AI:正在为您办理退票... [自动调用工具]
AI:张三退票成功!

优势:

  • ✅ 自然语言交互,无需学习系统操作
  • ✅ 一次性提供信息,无需多轮对话
  • ✅ 24/7 自动服务,无需人工等待

2. 降低开发成本

传统方式:

  • 需要开发前端界面
  • 需要设计 API 接口
  • 需要处理表单验证
  • 需要编写业务逻辑

AI 工具调用方式:

  • ✅ 只需定义工具方法
  • ✅ AI 自动处理参数提取
  • ✅ 自然语言即接口
  • ✅ 复用现有业务代码

3. 提高系统灵活性

  • 动态工具注册:可以根据配置动态加载工具
  • 权限控制:不同用户看到不同的工具
  • 版本管理:可以轻松添加、修改、删除工具
  • A/B 测试:可以测试不同工具组合的效果

4. 业务场景应用

场景 1:智能客服
@Tool(description = "查询订单")
public OrderInfo queryOrder(@ToolParam String orderId) { ... }

@Tool(description = "退票")
public String cancel(@ToolParam String orderId, @ToolParam String name) { ... }

@Tool(description = "改签")
public String change(@ToolParam String orderId, @ToolParam Date newDate) { ... }
场景 2:数据查询
@Tool(description = "查询用户信息")
public UserInfo getUserInfo(@ToolParam String userId) { ... }

@Tool(description = "查询交易记录")
public List<Transaction> getTransactions(@ToolParam String userId, @ToolParam Date startDate) { ... }
场景 3:系统操作
@Tool(description = "发送邮件")
@PreAuthorize("hasRole('ADMIN')")
public String sendEmail(@ToolParam String to, @ToolParam String subject, @ToolParam String content) { ... }

@Tool(description = "生成报表")
@PreAuthorize("hasRole('MANAGER')")
public String generateReport(@ToolParam String type, @ToolParam Date date) { ... }

🏗️ 代码架构分析

1. 项目结构

05tools/
├── src/main/java/com/xushu/tools/
│   ├── Application.java              # Spring Boot 启动类
│   ├── ToolsController.java          # REST 控制器
│   ├── ToolService.java              # 工具服务(定义工具方法)
│   ├── TicketService.java            # 业务服务(票务逻辑)
│   └── config/
│       └── SecurityConfig.java       # Spring Security 配置
└── src/main/resources/
    └── application.properties         # 应用配置

2. 核心类分析

ToolsController

职责:

  • 接收用户 HTTP 请求
  • 构建配置了工具的 ChatClient
  • 调用 AI 并返回结果

关键代码:

@RestController
public class ToolsController {
    ChatClient chatClient;

    public ToolsController(ChatClient.Builder ChatClientBuilder,
                           ToolService toolService) {
        this.chatClient = ChatClientBuilder
            .defaultSystem("""
                # 角色
                你是智能航空客服助手
                ## 要求
                严禁随意补全或猜测工具调用参数。
                """)
            .defaultTools(toolService)  // 注册工具
            .build();
    }

    @RequestMapping("/tool")
    public String tool(@RequestParam String message) {
        return chatClient.prompt()
            .user(message)
            .call().content();
    }
}

设计要点:

  • 使用构造函数注入,确保工具在 ChatClient 构建时注册
  • System Prompt 中明确要求 AI 不要随意猜测参数
  • 简单的 REST 接口,易于测试和使用
ToolService

职责:

  • 定义可被 AI 调用的工具方法
  • 提供动态工具配置方法
  • 集成权限控制

关键代码:

@Service
public class ToolService {
    @Autowired
    private TicketService ticketService;

    // 注解式工具定义
    @Tool(description = "退票")
    @PreAuthorize("hasRole('ADMIN')")
    public String cancel(
        @ToolParam(description = "预定号,可以是纯数字") String ticketNumber,
        @ToolParam(description = "真实人名(必填,必须为人的真实姓名,严禁用其他信息代替;如缺失请传null)") String name
    ) {
        String username = SecurityContextHolder.getContext()
            .getAuthentication().getName();
        ticketService.cancel(ticketNumber, name);
        return username + "退票成功!";
    }

    // 编程式工具定义(动态配置)
    public List<ToolCallback> getToolCallList(ToolService toolService) {
        Method method = ReflectionUtils.findMethod(
            ToolService.class, "cancel", String.class, String.class
        );
        ToolDefinition toolDefinition = ToolDefinition.builder()
            .name("cancel")
            .description("退票")
            .inputSchema("""{...}""")
            .build();
        ToolCallback toolCallback = MethodToolCallback.builder()
            .toolDefinition(toolDefinition)
            .toolMethod(method)
            .toolObject(toolService)
            .build();
        return List.of(toolCallback);
    }
}

设计要点:

  • 使用 @Tool 注解简化工具定义
  • 使用 @ToolParam 提供详细的参数描述,帮助 AI 正确提取参数
  • 集成 Spring Security,支持方法级权限控制
  • 提供动态配置方法,支持运行时工具管理
SecurityConfig

职责:

  • 配置 Spring Security
  • 定义用户和角色
  • 配置 URL 访问规则

关键代码:

@Configuration
@EnableMethodSecurity  // 启用方法级安全
public class SecurityConfig {
    @Bean
    public UserDetailsService userDetailsService() {
        UserDetails user = User.withUsername("user1")
            .password("pass1").roles("USER").build();
        UserDetails admin = User.withUsername("admin")
            .password("pass2").roles("ADMIN").build();
        return new InMemoryUserDetailsManager(user, admin);
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(authz -> authz
            .requestMatchers("/tool").permitAll()  // /tool 接口无需认证
            .anyRequest().authenticated()
        );
        return http.build();
    }
}

设计要点:

  • 使用内存用户存储(生产环境应使用数据库)
  • /tool 接口允许匿名访问(实际项目中可能需要认证)
  • 启用方法级安全,支持 @PreAuthorize 注解

3. 数据流分析

HTTP 请求
  ↓
ToolsController.tool()
  ↓
ChatClient.prompt().user(message).call()
  ↓
ChatModel.call(Prompt)  [包含工具定义]
  ↓
大模型分析 → 返回 ToolCall 请求
  ↓
ToolCallback 执行器
  ↓
权限检查 (@PreAuthorize)
  ↓
ToolService.cancel() 执行
  ↓
TicketService.cancel() 业务逻辑
  ↓
返回结果
  ↓
ChatModel 再次调用,生成自然语言回复
  ↓
返回给用户

📚 使用指南

1. 快速开始

步骤 1:配置 AI 模型

application.properties 中配置:

spring.ai.dashscope.api-key=your-api-key
步骤 2:定义工具
@Service
public class MyToolService {
    @Tool(description = "工具描述")
    public String myTool(@ToolParam(description = "参数描述") String param) {
        // 业务逻辑
        return "执行结果";
    }
}
步骤 3:注册工具
@RestController
public class MyController {
    ChatClient chatClient;

    public MyController(ChatClient.Builder builder, MyToolService toolService) {
        this.chatClient = builder
            .defaultTools(toolService)
            .build();
    }

    @RequestMapping("/chat")
    public String chat(@RequestParam String message) {
        return chatClient.prompt().user(message).call().content();
    }
}
步骤 4:测试
curl "http://localhost:8080/tool?message=我要退票,订单号12345,姓名张三"

2. 工具定义最佳实践

2.1 工具描述要清晰
// ❌ 不好的描述
@Tool(description = "退票")

// ✅ 好的描述
@Tool(description = "为用户办理退票业务,需要提供订单号和真实姓名")
2.2 参数描述要详细
// ❌ 不好的参数描述
@ToolParam(description = "订单号") String orderId

// ✅ 好的参数描述
@ToolParam(description = "订单号,格式为纯数字,如:12345,必填") String orderId
2.3 明确必填和可选参数
@Tool(description = "查询订单")
public OrderInfo queryOrder(
    @ToolParam(description = "订单号,必填") String orderId,
    @ToolParam(description = "是否需要详细信息,可选,默认false") Boolean includeDetails
) {
    // ...
}
2.4 处理参数缺失情况
@Tool(description = "退票")
public String cancel(
    @ToolParam(description = "订单号,必填") String orderId,
    @ToolParam(description = "姓名,如缺失请传null") String name
) {
    if (name == null || name.trim().isEmpty()) {
        return "请提供姓名信息";
    }
    // 执行业务逻辑
    return "退票成功";
}

3. System Prompt 设计

3.1 明确角色和职责
.defaultSystem("""
    # 角色
    你是智能航空客服助手,负责帮助用户处理票务相关业务。
    
    ## 职责
    1. 理解用户意图
    2. 提取必要信息
    3. 调用相应工具
    4. 以友好、专业的方式回复用户
    """)
3.2 明确工具调用规则
.defaultSystem("""
    ## 工具调用规则
    1. 严禁随意补全或猜测工具调用参数
    2. 参数如缺失或语义不准,请不要补充或随意传递,请直接放弃本次工具调用
    3. 如果用户提供的信息不完整,请友好地提示用户补充信息
    4. 工具调用成功后,请用自然语言向用户说明结果
    """)

4. 权限控制

4.1 方法级权限
@Tool(description = "退票")
@PreAuthorize("hasRole('ADMIN')")  // 只有管理员可以调用
public String cancel(String orderId, String name) {
    // ...
}
4.2 动态权限检查
@Tool(description = "查询订单")
public OrderInfo queryOrder(@ToolParam String orderId) {
    String username = SecurityContextHolder.getContext()
        .getAuthentication().getName();
    
    // 检查用户是否有权限查询该订单
    if (!hasPermission(username, orderId)) {
        throw new AccessDeniedException("无权查询该订单");
    }
    
    return orderService.getOrder(orderId);
}

5. 错误处理

5.1 工具方法中的错误处理
@Tool(description = "退票")
public String cancel(@ToolParam String orderId, @ToolParam String name) {
    try {
        ticketService.cancel(orderId, name);
        return "退票成功";
    } catch (OrderNotFoundException e) {
        return "订单不存在,请检查订单号";
    } catch (InvalidNameException e) {
        return "姓名不匹配,请提供正确的姓名";
    } catch (Exception e) {
        log.error("退票失败", e);
        return "退票失败,请稍后重试";
    }
}
5.2 全局异常处理
@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(AccessDeniedException.class)
    public ResponseEntity<String> handleAccessDenied(AccessDeniedException e) {
        return ResponseEntity.status(403).body("权限不足");
    }
}

🎓 最佳实践

1. 工具设计原则

单一职责原则

每个工具应该只做一件事:

// ❌ 不好的设计
@Tool(description = "处理订单")
public String processOrder(String action, String orderId) {
    if ("cancel".equals(action)) { ... }
    else if ("query".equals(action)) { ... }
    else if ("change".equals(action)) { ... }
}

// ✅ 好的设计
@Tool(description = "退票")
public String cancel(String orderId, String name) { ... }

@Tool(description = "查询订单")
public OrderInfo queryOrder(String orderId) { ... }

@Tool(description = "改签")
public String change(String orderId, Date newDate) { ... }
参数验证

在工具方法内部进行参数验证:

@Tool(description = "退票")
public String cancel(@ToolParam String orderId, @ToolParam String name) {
    // 参数验证
    if (orderId == null || orderId.trim().isEmpty()) {
        return "订单号不能为空";
    }
    if (name == null || name.trim().isEmpty()) {
        return "姓名不能为空";
    }
    
    // 业务验证
    if (!isValidOrderId(orderId)) {
        return "订单号格式不正确";
    }
    
    // 执行业务逻辑
    return ticketService.cancel(orderId, name);
}

2. 性能优化

异步工具调用

对于耗时操作,考虑异步处理:

@Tool(description = "生成报表")
public String generateReport(@ToolParam String type) {
    // 异步生成报表
    CompletableFuture.supplyAsync(() -> {
        return reportService.generate(type);
    });
    return "报表生成中,请稍后查询";
}
工具缓存

对于查询类工具,考虑添加缓存:

@Tool(description = "查询天气")
@Cacheable("weather")
public String getWeather(@ToolParam String city) {
    return weatherService.getWeather(city);
}

3. 安全性

输入验证
@Tool(description = "查询订单")
public OrderInfo queryOrder(@ToolParam String orderId) {
    // SQL 注入防护
    if (!orderId.matches("^[0-9]+$")) {
        throw new IllegalArgumentException("订单号格式不正确");
    }
    
    // XSS 防护
    String sanitizedOrderId = HtmlUtils.htmlEscape(orderId);
    
    return orderService.getOrder(sanitizedOrderId);
}
敏感信息保护
@Tool(description = "查询用户信息")
public UserInfo getUserInfo(@ToolParam String userId) {
    UserInfo user = userService.getUser(userId);
    
    // 脱敏处理
    user.setPassword(null);
    user.setIdCard(maskIdCard(user.getIdCard()));
    
    return user;
}

4. 监控与日志

工具调用日志
@Tool(description = "退票")
public String cancel(@ToolParam String orderId, @ToolParam String name) {
    log.info("开始退票 - 订单号: {}, 姓名: {}", orderId, name);
    
    try {
        String result = ticketService.cancel(orderId, name);
        log.info("退票成功 - 订单号: {}", orderId);
        return result;
    } catch (Exception e) {
        log.error("退票失败 - 订单号: {}, 错误: {}", orderId, e.getMessage(), e);
        throw e;
    }
}
性能监控
@Tool(description = "查询订单")
public OrderInfo queryOrder(@ToolParam String orderId) {
    long startTime = System.currentTimeMillis();
    try {
        return orderService.getOrder(orderId);
    } finally {
        long duration = System.currentTimeMillis() - startTime;
        metrics.recordToolCall("queryOrder", duration);
    }
}

🚀 扩展与优化

1. 动态工具管理

从数据库加载工具
@Service
public class DynamicToolService {
    @Autowired
    private ToolConfigRepository toolConfigRepository;
    
    public List<ToolCallback> loadToolsByUserRole(String role) {
        List<ToolConfig> configs = toolConfigRepository.findByRole(role);
        
        return configs.stream()
            .map(this::buildToolCallback)
            .collect(Collectors.toList());
    }
    
    private ToolCallback buildToolCallback(ToolConfig config) {
        // 根据配置构建 ToolCallback
        // ...
    }
}
工具热更新
@Service
public class ToolRegistry {
    private final Map<String, ToolCallback> tools = new ConcurrentHashMap<>();
    
    public void registerTool(String name, ToolCallback tool) {
        tools.put(name, tool);
    }
    
    public void unregisterTool(String name) {
        tools.remove(name);
    }
    
    public List<ToolCallback> getAllTools() {
        return new ArrayList<>(tools.values());
    }
}

2. 工具组合与编排

工具链
@Tool(description = "完整退票流程")
public String cancelTicketFlow(@ToolParam String orderId, @ToolParam String name) {
    // 1. 查询订单
    OrderInfo order = queryOrder(orderId);
    
    // 2. 验证信息
    if (!validateCancel(order, name)) {
        return "退票验证失败";
    }
    
    // 3. 执行退票
    String result = cancel(orderId, name);
    
    // 4. 发送通知
    sendNotification(order.getEmail(), "退票成功");
    
    return result;
}
条件工具调用
@Tool(description = "智能处理")
public String smartProcess(@ToolParam String action, @ToolParam String orderId) {
    switch (action) {
        case "cancel":
            return cancel(orderId, getUserName());
        case "query":
            return queryOrder(orderId).toString();
        case "change":
            return change(orderId, getNewDate());
        default:
            return "不支持的操作";
    }
}

3. 工具版本管理

@Tool(description = "退票", version = "2.0")
public String cancelV2(@ToolParam String orderId, @ToolParam String name) {
    // 新版本的退票逻辑
}

@Tool(description = "退票", version = "1.0")
public String cancelV1(@ToolParam String orderId, @ToolParam String name) {
    // 旧版本的退票逻辑(保持兼容)
}

4. 工具测试

单元测试
@SpringBootTest
class ToolServiceTest {
    @Autowired
    private ToolService toolService;
    
    @Test
    void testCancel() {
        String result = toolService.cancel("12345", "张三");
        assertEquals("admin退票成功!", result);
    }
}
集成测试
@SpringBootTest
@AutoConfigureMockMvc
class ToolsControllerTest {
    @Autowired
    private MockMvc mockMvc;
    
    @Test
    void testToolCall() throws Exception {
        mockMvc.perform(get("/tool")
            .param("message", "我要退票,订单号12345,姓名张三"))
            .andExpect(status().isOk())
            .andExpect(content().string(containsString("退票成功")));
    }
}

测试

在这里插入图片描述

📊 总结

核心优势

  1. 简化集成:通过注解即可将 Java 方法暴露给 AI
  2. 自动参数提取:AI 自动从自然语言中提取参数
  3. 权限控制:集成 Spring Security,确保安全性
  4. 灵活配置:支持注解式和编程式两种工具定义方式
  5. 业务价值:让 AI 能够执行实际业务操作,提升用户体验

适用场景

  • ✅ 智能客服系统
  • ✅ 业务自动化
  • ✅ 数据查询接口
  • ✅ 系统操作自动化
  • ✅ 多工具协作场景

技术栈

  • Spring AI:AI 集成框架
  • Spring Security:安全框架
  • Spring Boot:应用框架
  • DashScope:阿里云大模型服务

学习路径

  1. 基础:理解 @Tool@ToolParam 注解
  2. 进阶:掌握动态工具配置和权限控制
  3. 高级:实现工具编排、版本管理和性能优化

📚 参考资料


测试 :

在这里插入图片描述
填写姓名 和 预定号
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

tools注意事项:

1.参数或者返回值不支持:

以下类型目前不支持作为工具使用的方法的参数或返回类型:

  • Optional

    • 异步类型(CompletableFuture 例如 Future)
    • 反应类型(例如F1Ow、Mono、Flux)
    • 功能类型(例如 Function,,supplier)Consumer。

推荐:pojo record java基础类型 list map

  1. 温度过低导致参数无法自动推算问题 • 溫度(即模型随机性)太低,AI可能缺失自由度,

  2. 大模型“强行适配”Tool参数的幻觉问题

    • 加严参数描述与校验
@Parameter(description = “真实人名(必填,必须为人名,严禁用其他信息代替;如缺失请传null)“) String name
- 后端代码加强校验和兜底保护 
- 系统Prompt设定限制
“严禁随意补全或猜测工具调用参数。

参数如缺失或语义不准,请不要补充或随意传递,请直接放弃本次工具调用。”
  • 高风险接口(如资金、风控等)tools方法加强人工确认,多走一步校验。
  1. 工具暴露的接口名、方法名、参数名要可读、业务化

    • Al是“看”你的签名和注释来决定用不用工具的;
    • 尽量避免乱码、缩写等。
  2. 工具方法不适合做超耗时操作,更长的耗时意味着用户延迟响应时间变长

    • 性能优化
  3. 关于Tools的鉴权

方式1 ,Spring Security

在这里插入图片描述

在这里插入图片描述

测试 :

在这里插入图片描述

访问退票接口
在这里插入图片描述

用代码中的配置,访问登录接口

在这里插入图片描述

在这里插入图片描述

再次访问退票接口

在这里插入图片描述
提示退票成功了 。

方式二

自己动态指定,比如从数据库中获取配置。
在这里插入图片描述

  /**
     * 动态获取工具回调列表
     *
     * <p>该方法演示了如何通过编程方式动态构建工具定义,而不是使用 {@code @Tool} 注解。
     * 这种方式适用于需要从数据库或配置中心动态加载工具的场景。
     *
     * <p><b>使用场景:</b>
     * <ul>
     *   <li>根据用户角色动态显示不同的工具</li>
     *   <li>从数据库读取工具配置</li>
     *   <li>支持工具的热更新(无需重启应用)</li>
     *   <li>工具配置需要外部化管理</li>
     * </ul>
     *
     * <p><b>实现步骤:</b>
     * <ol>
     *   <li>获取工具方法:使用 {@code ReflectionUtils.findMethod()} 查找方法</li>
     *   <li>构建工具定义:使用 {@code ToolDefinition.builder()} 构建 JSON Schema</li>
     *   <li>创建工具回调:使用 {@code MethodToolCallback.builder()} 创建回调</li>
     *   <li>返回工具列表:返回 {@code List<ToolCallback>}</li>
     * </ol>
     *
     * <p><b>与注解式的对比:</b>
     * <table border="1">
     *   <tr>
     *     <th>特性</th>
     *     <th>注解式(@Tool)</th>
     *     <th>编程式(本方法)</th>
     *   </tr>
     *   <tr>
     *     <td>代码简洁性</td>
     *     <td>✅ 简洁</td>
     *     <td>❌ 复杂</td>
     *   </tr>
     *   <tr>
     *     <td>自动生成 Schema</td>
     *     <td>✅ 自动</td>
     *     <td>❌ 手动</td>
     *   </tr>
     *   <tr>
     *     <td>动态配置</td>
     *     <td>❌ 不支持</td>
     *     <td>✅ 支持</td>
     *   </tr>
     *   <tr>
     *     <td>从数据库加载</td>
     *     <td>❌ 不支持</td>
     *     <td>✅ 支持</td>
     *   </tr>
     * </table>
     *
     * <p><b>注意事项:</b>
     * <ul>
     *   <li><b>重要:</b>{@code toolObject} 参数必须是 Spring Bean,不能自己 new。
     *       如果自己 new,Spring 的依赖注入(如 {@code @Autowired})将无法工作。</li>
     *   <li>需要手动构建 JSON Schema,确保格式正确</li>
     *   <li>方法签名必须与工具定义中的参数匹配</li>
     *   <li>注解式工具定义中的 {@code @Tool} 和 {@code @ToolParam} 在编程式中无效</li>
     * </ul>
     *
     * <p><b>使用方式:</b>
     * <pre>
     * // 在 ToolsController 中使用
     * ChatClient chatClient = builder
     *     .defaultToolCallbacks(toolService.getToolCallList(toolService))
     *     .build();
     * </pre>
     *
     * <p><b>扩展建议:</b>
     * <ul>
     *   <li>从数据库读取工具配置,而不是硬编码</li>
     *   <li>根据当前用户角色过滤工具</li>
     *   <li>支持工具的热更新机制</li>
     *   <li>添加工具配置的缓存机制</li>
     * </ul>
     *
     * @param toolService 工具服务实例,必须是 Spring Bean(不能自己 new)。
     *                    用于执行工具方法,Spring 的依赖注入会正常工作。
     * @return 工具回调列表,每个 {@code ToolCallback} 对应一个可被 AI 调用的工具
     *
     * @see org.springframework.ai.tool.definition.ToolDefinition ToolDefinition,工具定义
     * @see org.springframework.ai.tool.method.MethodToolCallback MethodToolCallback,方法工具回调
     * @see org.springframework.util.ReflectionUtils ReflectionUtils,反射工具类
     * @see com.xushu.tools.ToolsController ToolsController,使用此方法注册动态工具
     *
     * @implNote 当前实现为示例代码,只返回一个工具(cancel)。
     *           实际项目中应该:
     * <ul>
     *   <li>从数据库读取工具配置</li>
     *   <li>根据用户角色过滤工具</li>
     *   <li>支持多个工具的动态加载</li>
     * </ul>
     */
    public List<ToolCallback> getToolCallList(ToolService toolService) {

        // todo.. 从数据库中读取的代码省略

        // 拿1个tool为例

        // 1.获取tools处理的方法
        Method method = ReflectionUtils.findMethod(ToolService.class, "cancel",String.class,String.class);
        // 2.构建Tool定义信息  动态配置的方式 @Tool @ToolParam都无效
        ToolDefinition toolDefinition = ToolDefinition.builder()
                .name("cancel")
                .description("退票")  // 对应@Tool的description
                // 对应@ToolParam
                .inputSchema("""
                        {
                          "type": "object",
                          "properties": {
                            "ticketNumber": {
                              "type": "string",
                              "description": "预定号,可以是纯数字"
                            },
                            "name": {
                              "type": "string",
                              "description": "真实人名"
                            }
                          },
                          "required": ["ticketNumber", "name"]
                        }
                        """)
                .build();
        // 一个ToolCallback对应一个tool
        ToolCallback toolCallback = MethodToolCallback.builder()
                .toolDefinition(toolDefinition)
                .toolMethod(method)
                .toolObject(toolService)        // 不能自己new,自己new不能解析依赖注入
                .build();

        return List.of(toolCallback);
    }

测试 :

在这里插入图片描述
8. tools过多导致AI出现选择困难证
问题:

  • token上限
  • 选择困难证

向量数据库。一个数据库,做相似性检索。

实现方式:

  1. 把所有的tools描述信息存入到向量数据库
  2. 每次对话的时候根据当前对话信息检索到相似的tools(RAG)
  3. 然后动态设置tools
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐