Skip to content

Tool Calling 工具调用

Tool Calling(也称 Function Calling)是让 LLM 具备"行动能力"的核心机制,是构建 AI Agent 的基础。

什么是 Tool Calling?

LLM 本身只能处理文本,无法直接访问外部系统。Tool Calling 让模型能够:

  • 调用 REST API 获取实时数据
  • 执行数据库查询
  • 操作文件系统
  • 调用业务系统接口
  • 执行代码
用户:"北京今天天气怎么样,适合出门吗?"


LLM 分析:需要调用天气工具


Tool Call: getWeather(city="北京")


工具执行:调用天气 API → "晴,25°C,空气质量优"


LLM 综合:基于天气数据生成回答


"北京今天天气晴朗,25°C,非常适合出门!"

@Tool 注解驱动开发

Spring AI Alibaba 提供了最简洁的工具定义方式:

基础工具定义

java
@Component
public class WeatherTools {

    @Tool(description = "获取指定城市的实时天气信息")
    public WeatherInfo getWeather(
        @ToolParam(description = "城市名称,如:北京、上海、广州") String city
    ) {
        // 调用真实天气 API
        return weatherApiClient.getWeather(city);
    }

    @Tool(description = "获取未来 N 天的天气预报")
    public List<WeatherForecast> getWeatherForecast(
        @ToolParam(description = "城市名称") String city,
        @ToolParam(description = "预报天数,1-7 之间") int days
    ) {
        return weatherApiClient.getForecast(city, days);
    }
}

// 返回值类型(自动序列化为 JSON)
record WeatherInfo(
    String city,
    String condition,
    int temperature,
    int humidity,
    String airQuality
) {}

数据库查询工具

java
@Component
public class DatabaseTools {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Tool(description = "根据用户 ID 查询用户信息")
    public UserInfo getUserById(
        @ToolParam(description = "用户 ID") Long userId
    ) {
        return jdbcTemplate.queryForObject(
            "SELECT * FROM users WHERE id = ?",
            new BeanPropertyRowMapper<>(UserInfo.class),
            userId
        );
    }

    @Tool(description = "查询订单列表,支持按状态和时间范围筛选")
    public List<Order> queryOrders(
        @ToolParam(description = "订单状态:PENDING/PAID/SHIPPED/COMPLETED") String status,
        @ToolParam(description = "开始日期,格式 yyyy-MM-dd") String startDate,
        @ToolParam(description = "结束日期,格式 yyyy-MM-dd") String endDate
    ) {
        // 查询逻辑
        return orderRepository.findByStatusAndDateRange(status, startDate, endDate);
    }
}

代码执行工具

java
@Component
public class CodeExecutionTools {

    @Tool(description = "执行 Java 代码片段并返回结果(沙箱环境)")
    public CodeResult executeJava(
        @ToolParam(description = "要执行的 Java 代码") String code
    ) {
        // 在沙箱中执行代码
        return sandboxExecutor.execute(code, "java");
    }

    @Tool(description = "执行 SQL 查询(只读)")
    public QueryResult executeSQL(
        @ToolParam(description = "SELECT 查询语句") String sql
    ) {
        // 安全检查:只允许 SELECT
        if (!sql.trim().toUpperCase().startsWith("SELECT")) {
            throw new IllegalArgumentException("只允许 SELECT 查询");
        }
        return jdbcTemplate.query(sql, new ColumnMapRowMapper());
    }
}

在 ChatClient 中使用工具

java
// 方式一:传入工具实例
String answer = chatClient.prompt()
    .user("北京今天天气怎么样?")
    .tools(new WeatherTools())
    .call()
    .content();

// 方式二:传入 Spring Bean(推荐)
@Autowired
private WeatherTools weatherTools;

@Autowired
private DatabaseTools databaseTools;

String answer = chatClient.prompt()
    .user(question)
    .tools(weatherTools, databaseTools)  // 多个工具
    .call()
    .content();

// 方式三:默认工具(全局注册)
@Bean
public ChatClient chatClient(ChatClient.Builder builder) {
    return builder
        .defaultTools(weatherTools, databaseTools)
        .build();
}

工具调用流程详解

1. 用户发送消息
   "查询用户 123 的最近 3 笔订单"

2. ChatClient 将工具定义(JSON Schema)注入 Prompt
   {
     "tools": [
       {
         "type": "function",
         "function": {
           "name": "queryOrders",
           "description": "查询订单列表...",
           "parameters": {
             "type": "object",
             "properties": {
               "userId": {"type": "integer"},
               "limit": {"type": "integer"}
             }
           }
         }
       }
     ]
   }

3. LLM 决定调用工具,返回 ToolCall
   {
     "tool_calls": [{
       "id": "call_abc123",
       "function": {
         "name": "queryOrders",
         "arguments": "{\"userId\": 123, \"limit\": 3}"
       }
     }]
   }

4. Spring AI 自动执行工具方法
   queryOrders(123, 3) → [Order1, Order2, Order3]

5. 将工具结果注入对话,再次调用 LLM
   ToolResponseMessage: [订单数据 JSON]

6. LLM 基于工具结果生成最终回答
   "用户 123 的最近 3 笔订单为:..."

并行工具调用

当一个问题需要多个工具时,LLM 可以并行调用:

java
// 问题:"比较北京和上海的天气,并查询两地的机票价格"
// LLM 会同时调用:
// - getWeather("北京")
// - getWeather("上海")
// - searchFlights("北京", "上海")
// - searchFlights("上海", "北京")

// Spring AI 自动处理并行调用,无需额外配置
String result = chatClient.prompt()
    .user("比较北京和上海的天气,并查询两地的机票价格")
    .tools(weatherTools, flightTools)
    .call()
    .content();

工具返回值处理

java
// 返回简单类型
@Tool(description = "获取当前时间戳")
public long getCurrentTimestamp() {
    return System.currentTimeMillis();
}

// 返回复杂对象(自动 JSON 序列化)
@Tool(description = "搜索商品")
public List<Product> searchProducts(String keyword) {
    return productService.search(keyword);
}

// 返回字符串(直接传递给 LLM)
@Tool(description = "执行 Shell 命令")
public String executeShell(String command) {
    // 安全检查...
    return shellExecutor.execute(command);
}

// 工具执行失败处理
@Tool(description = "调用外部 API")
public ApiResult callExternalApi(String endpoint) {
    try {
        return httpClient.get(endpoint);
    } catch (Exception e) {
        // 返回错误信息,让 LLM 决定如何处理
        return new ApiResult(false, "API 调用失败:" + e.getMessage(), null);
    }
}

工具权限控制

java
@Component
public class SecureTools {

    @Autowired
    private SecurityContext securityContext;

    @Tool(description = "删除指定记录(需要管理员权限)")
    public DeleteResult deleteRecord(
        @ToolParam(description = "记录 ID") Long id
    ) {
        // 权限检查
        if (!securityContext.hasRole("ADMIN")) {
            throw new AccessDeniedException("需要管理员权限");
        }
        return recordService.delete(id);
    }

    @Tool(description = "查询当前用户的数据")
    public List<UserData> getMyData() {
        // 自动获取当前用户,无需参数
        Long userId = securityContext.getCurrentUserId();
        return dataService.findByUserId(userId);
    }
}

ToolCallback 底层接口

如果需要更精细的控制,可以直接实现 ToolCallback

java
public class DynamicTool implements ToolCallback {

    @Override
    public ToolDefinition getToolDefinition() {
        return ToolDefinition.builder()
            .name("dynamicSearch")
            .description("动态搜索工具")
            .inputSchema("""
                {
                  "type": "object",
                  "properties": {
                    "query": {"type": "string"},
                    "maxResults": {"type": "integer", "default": 10}
                  },
                  "required": ["query"]
                }
                """)
            .build();
    }

    @Override
    public String call(String toolInput) {
        // toolInput 是 JSON 字符串
        Map<String, Object> params = JsonUtils.parse(toolInput);
        String query = (String) params.get("query");
        int maxResults = (int) params.getOrDefault("maxResults", 10);

        List<SearchResult> results = searchEngine.search(query, maxResults);
        return JsonUtils.toJson(results);
    }
}

最佳实践

工具设计原则

  1. 描述要精准description 是 LLM 选择工具的依据,要清晰描述功能和适用场景
  2. 参数要明确:每个参数的 description 要说明格式、范围、示例
  3. 单一职责:每个工具只做一件事,避免过于复杂的工具
  4. 幂等设计:工具应尽量设计为幂等的,避免重复调用产生副作用
  5. 错误处理:工具内部捕获异常,返回结构化错误信息而非抛出异常

安全注意事项

  • 对写操作工具(删除、修改)添加权限检查
  • SQL 工具只允许 SELECT,防止 SQL 注入
  • Shell 工具需要严格的命令白名单
  • 敏感数据工具要做脱敏处理

相关组件

本站内容由 褚成志 整理编写,仅供学习参考