Spring AI 实战系列 | 第 6 篇:Tool-Calling 工具调用

📄 约 7500 字 | ⏱️ 阅读约 18 分钟

系列说明:本文是《Spring AI 实战系列》第 6 篇,系统讲解 Tool Calling 的核心原理、声明式与编程式工具定义、多场景实战案例(天气/股票/数据库),以及生产环境的最佳实践。
前置知识:已完成第 2 篇,掌握 ChatClient 和 Prompt 工程化基础。


前言

笔者曾参与一个智能客服项目,用户询问"我的订单到哪了?"

若 AI 仅基于训练数据回答,其无法获知用户的订单状态。但若使 AI 能够调用订单查询接口,即可给出准确的实时信息。

此即 Tool Calling 的价值——赋予 AI “手”,使其能够操作业务系统。它是 AI 从"聊天机器人"进化为"智能助手"的关键一步。

本文将从基础概念到生产实践,将 Tool Calling 论述透彻。


一、什么是 Tool Calling

1.1 直观理解

无 Tool Calling 时:

用户:北京今天天气怎么样?
AI:抱歉,我无法获取实时天气信息。

有 Tool Calling 时:

用户:北京今天天气怎么样?
AI:我查一下...(调用天气 API)
AI:北京今天晴,25°C,空气质量优。

核心区别:无 Tool Calling 的 AI 仅能"说",有 Tool Calling 的 AI 能够"做"。

1.2 工作原理

Tool Calling 工作流程

用户提问 → AI 判断是否需要调用工具
          → 如果需要,生成工具调用参数
          → 系统执行工具,获取结果
          → 把工具结果返回给 AI
          → AI 基于结果生成最终回答

整个过程对用户透明,用户仅看到最终的回答。

1.3 与 RAG 的区别

Tool Calling 和 RAG 都是增强 AI 能力的手段,但侧重点不同:

维度 RAG Tool Calling
数据来源 静态文档 / 知识库 实时接口 / 数据库
核心能力 读取知识 执行操作
典型场景 企业问答、文档检索 查询订单、下单、预约
数据时效性 取决于索引更新频率 实时
是否修改数据 只读 可读写

简单记:RAG 使 AI “懂”,Tool Calling 使 AI “能”。 实际项目中两者经常配合使用。


二、声明式工具定义:@Tool 注解

Spring AI 1.0+ 提供了 @Tool@ToolParam 注解,使开发者能够像编写普通 Controller 一样将 Java 方法暴露给 AI。

2.1 定义工具方法

@Service
public class WeatherService {

    @Tool(name = "getWeather", description = "获取指定城市的当前天气信息,包括温度、天气状况、湿度和风速")
    public WeatherInfo getWeather(
            @ToolParam(description = "城市名称,如北京、上海、广州") String city) {

        // 调用天气 API
        return callWeatherApi(city);
    }

    @Tool(name = "getForecast", description = "获取指定城市未来几天的天气预报")
    public List<WeatherInfo> getForecast(
            @ToolParam(description = "城市名称") String city,
            @ToolParam(description = "预报天数,范围 1-7") int days) {

        return callForecastApi(city, days);
    }

    public record WeatherInfo(String city, String condition, 
                               int temperature, String airQuality, 
                               int humidity, int windSpeed) {}
}

关键点:

  • @Tooldescription 决定 AI 何时调用该工具
  • @ToolParamdescription 决定 AI 如何填写参数
  • 返回值可以是任意对象,Spring AI 会自动序列化为 JSON

2.2 注册工具到 ChatClient

@Service
public class AiAssistant {

    private final ChatClient chatClient;

    public AiAssistant(ChatClient.Builder builder, WeatherService weatherService) {
        this.chatClient = builder
            .defaultTools(weatherService)  // 注册工具
            .build();
    }

    public String chat(String message) {
        return chatClient.prompt()
            .user(message)
            .call()
            .content();
    }
}

defaultTools() 可以接收多个工具服务,Spring AI 会自动扫描所有带 @Tool 注解的方法。

2.3 运行效果

// 场景 1:单工具调用
String r1 = aiAssistant.chat("北京今天天气怎么样?");
// AI 自动调用 getWeather("北京")
// 输出:北京今天晴,25°C,空气质量优,湿度 45%,风速 3 级

// 场景 2:多参数工具
String r2 = aiAssistant.chat("上海未来三天天气如何?");
// AI 自动调用 getForecast("上海", 3)

// 场景 3:不需要工具
String r3 = aiAssistant.chat("你好");
// AI 直接回答,不调用任何工具

AI 会根据问题自主判断是否需要调用工具,开发者无需手动控制。


三、编程式工具定义

除了注解方式,Spring AI 还支持用代码手动创建工具,适合动态生成或需要复杂逻辑的场景。

3.1 使用 FunctionToolCallback

@Service
public class OrderToolService {

    private final OrderRepository orderRepository;

    public ToolCallback getOrderTool() {
        return FunctionToolCallback.builder("getOrderStatus", (String orderId) -> {
            Order order = orderRepository.findById(orderId)
                .orElseThrow(() -> new RuntimeException("订单不存在:" + orderId));
            return new OrderStatus(order.getId(), order.getStatus(), order.getTrackingNumber());
        })
        .description("查询订单状态和物流信息,需要提供订单ID")
        .inputType(String.class)
        .build();
    }

    public ToolCallback cancelOrderTool() {
        return FunctionToolCallback.builder("cancelOrder", (String orderId) -> {
            Order order = orderRepository.findById(orderId)
                .orElseThrow(() -> new RuntimeException("订单不存在"));
            
            if (!order.canCancel()) {
                return "该订单当前状态为 " + order.getStatus() + ",无法取消";
            }
            
            order.cancel();
            orderRepository.save(order);
            return "订单 " + orderId + " 已成功取消";
        })
        .description("取消指定订单。注意:只有待发货状态的订单可以取消")
        .inputType(String.class)
        .build();
    }

    public record OrderStatus(String orderId, String status, String trackingNumber) {}
}

3.2 注册编程式工具

@Service
public class OrderAssistant {

    private final ChatClient chatClient;

    public OrderAssistant(ChatClient.Builder builder, OrderToolService orderToolService) {
        this.chatClient = builder
            .defaultTools(
                orderToolService.getOrderTool(),
                orderToolService.cancelOrderTool()
            )
            .build();
    }

    public String queryOrder(String question) {
        return chatClient.prompt()
            .system("你是一位订单查询助手。请基于查询结果友好地回答用户。")
            .user(question)
            .call()
            .content();
    }
}

3.3 声明式 vs 编程式对比

维度 @Tool 注解 FunctionToolCallback
适用场景 静态工具、方法明确的业务逻辑 动态工具、运行时生成
代码量 少,只需加注解 多,需手动构建
参数描述 通过 @ToolParam 描述 通过 inputType 和 description
灵活性 一般 高,可动态决定暴露哪些工具
推荐度 ⭐⭐⭐⭐⭐ 大多数场景 ⭐⭐⭐ 特殊场景

建议优先使用 @Tool 注解,代码更简洁、可读性更好。仅在需要动态控制工具时采用编程式方式。


四、实战案例

4.1 天气查询助手

完整实现一个天气查询工具,支持实时天气和预报查询:

@Service
public class WeatherToolService {

    private final RestTemplate restTemplate = new RestTemplate();

    @Tool(name = "getCurrentWeather", 
          description = "获取指定城市的当前实时天气,包括温度、天气状况、湿度、风速")
    public WeatherResult getCurrentWeather(
            @ToolParam(description = "城市名称,如北京、上海、广州、深圳") String city) {

        // 调用第三方天气 API(示例)
        String url = "https://api.weather.com/v1/current?city=" + URLEncoder.encode(city, StandardCharsets.UTF_8);
        
        try {
            WeatherApiResponse response = restTemplate.getForObject(url, WeatherApiResponse.class);
            return new WeatherResult(
                city, response.getTemperature(), response.getCondition(),
                response.getHumidity(), response.getWindSpeed()
            );
        } catch (Exception e) {
            return new WeatherResult(city, 0, "天气服务暂时不可用", 0, 0);
        }
    }

    @Tool(name = "getWeatherForecast", 
          description = "获取指定城市未来几天的天气预报,用于出行规划")
    public List<WeatherResult> getWeatherForecast(
            @ToolParam(description = "城市名称") String city,
            @ToolParam(description = "预报天数,1-7 天") int days) {

        String url = String.format("https://api.weather.com/v1/forecast?city=%s&days=%d",
            URLEncoder.encode(city, StandardCharsets.UTF_8), days);
        
        ForecastApiResponse response = restTemplate.getForObject(url, ForecastApiResponse.class);
        
        return response.getDaily().stream()
            .map(d -> new WeatherResult(city, d.getTemp(), d.getCondition(), 
                                         d.getHumidity(), d.getWind()))
            .collect(Collectors.toList());
    }

    public record WeatherResult(String city, int temperature, String condition,
                                 int humidity, int windSpeed) {}
}

使用方式:

@Service
public class WeatherAssistant {

    private final ChatClient chatClient;

    public WeatherAssistant(ChatClient.Builder builder, WeatherToolService weatherTool) {
        this.chatClient = builder.defaultTools(weatherTool).build();
    }

    public String ask(String question) {
        return chatClient.prompt()
            .system("你是一位天气助手。请根据天气数据给出友好的建议,比如是否适合出门、是否需要带伞等。")
            .user(question)
            .call()
            .content();
    }
}

// 测试
weatherAssistant.ask("北京今天天气怎么样?");
// → AI 调用 getCurrentWeather("北京"),输出:北京今天晴,25°C...

weatherAssistant.ask("上海未来三天天气如何,适合出门旅游吗?");
// → AI 调用 getWeatherForecast("上海", 3),结合数据给出建议

4.2 股票分析助手

@Service
public class StockToolService {

    private final RestTemplate restTemplate = new RestTemplate();

    @Tool(name = "getStockPrice", 
          description = "获取股票的实时价格信息,包括涨跌幅、成交量等")
    public StockInfo getStockPrice(
            @ToolParam(description = "股票代码,A 股如 600519,美股如 AAPL") String symbol) {

        String url = "https://api.stock.com/price?symbol=" + symbol;
        StockApiResponse response = restTemplate.getForObject(url, StockApiResponse.class);

        return new StockInfo(symbol, response.getName(), response.getCurrentPrice(),
                            response.getChangePercent(), response.getVolume());
    }

    @Tool(name = "getStockHistory", 
          description = "获取股票历史价格走势,用于技术分析")
    public List<StockHistory> getStockHistory(
            @ToolParam(description = "股票代码") String symbol,
            @ToolParam(description = "时间范围:1d(天), 1w(周), 1m(月), 3m(季), 1y(年)") String period) {

        String url = String.format("https://api.stock.com/history?symbol=%s&period=%s", symbol, period);
        return restTemplate.getForObject(url, new ParameterizedTypeReference<List<StockHistory>>() {});
    }

    public record StockInfo(String symbol, String name, double price,
                            double changePercent, long volume) {}
    public record StockHistory(String date, double open, double close,
                               double high, double low, long volume) {}
}

配合 System Prompt 做分析:

@Service
public class StockAssistant {

    private final ChatClient chatClient;

    public StockAssistant(ChatClient.Builder builder, StockToolService stockTool) {
        this.chatClient = builder.defaultTools(stockTool).build();
    }

    public String analyze(String question) {
        return chatClient.prompt()
            .system(""" 
                你是一位专业的股票分析师。当用户询问股票时:
                1. 先调用工具获取实时价格和历史数据
                2. 分析趋势和关键指标
                3. 给出客观分析(不构成投资建议)
                4. 用通俗易懂的语言解释专业术语
                """)
            .user(question)
            .call()
            .content();
    }
}

// 测试
stockAssistant.analyze("茅台最近走势怎么样?");
// → AI 调用 getStockPrice("600519") + getStockHistory("600519", "1m")
// → 输出综合分析报告

4.3 数据库查询助手(客服场景)

此场景最为实用——使 AI 直接查询数据库以回答用户问题:

@Service
public class DatabaseToolService {

    private final JdbcTemplate jdbcTemplate;

    public DatabaseToolService(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    @Tool(name = "queryUserInfo", description = "查询用户基本信息。当用户询问自己的账户信息时使用")
    public UserInfo queryUserInfo(
            @ToolParam(description = "用户ID,格式如 U12345") String userId) {

        return jdbcTemplate.queryForObject(
            "SELECT id, name, email, phone, create_time FROM users WHERE id = ?",
            new BeanPropertyRowMapper<>(UserInfo.class), userId);
    }

    @Tool(name = "queryUserOrders", 
          description = "查询用户的订单列表。当用户询问订单、购买记录、物流时使用")
    public List<OrderInfo> queryUserOrders(
            @ToolParam(description = "用户ID") String userId,
            @ToolParam(description = "订单状态筛选,可选值:PENDING, PAID, SHIPPED, COMPLETED。不传则查询全部") String status) {

        String sql = "SELECT id, user_id, total_amount, status, create_time FROM orders " +
                     "WHERE user_id = ?" + (status != null ? " AND status = ?" : "") +
                     " ORDER BY create_time DESC LIMIT 20";

        Object[] params = status != null ? new Object[]{userId, status} : new Object[]{userId};
        return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(OrderInfo.class), params);
    }

    @Tool(name = "queryOrderDetail", description = "查询订单详情,包括商品列表")
    public OrderDetail queryOrderDetail(
            @ToolParam(description = "订单ID,格式如 O98765") String orderId) {

        OrderInfo order = jdbcTemplate.queryForObject(
            "SELECT * FROM orders WHERE id = ?",
            new BeanPropertyRowMapper<>(OrderInfo.class), orderId);

        List<OrderItem> items = jdbcTemplate.query(
            "SELECT * FROM order_items WHERE order_id = ?",
            new BeanPropertyRowMapper<>(OrderItem.class), orderId);

        return new OrderDetail(order, items);
    }

    // Record 定义
    public record UserInfo(String id, String name, String email, 
                           String phone, LocalDateTime createTime) {}
    public record OrderInfo(String id, String userId, BigDecimal totalAmount,
                            String status, LocalDateTime createTime) {}
    public record OrderItem(String id, String orderId, String productName,
                            int quantity, BigDecimal price) {}
    public record OrderDetail(OrderInfo order, List<OrderItem> items) {}
}

客服助手整合:

@Service
public class CustomerServiceAssistant {

    private final ChatClient chatClient;

    public CustomerServiceAssistant(ChatClient.Builder builder, DatabaseToolService dbTool) {
        this.chatClient = builder
            .defaultTools(dbTool)
            .build();
    }

    public String handleQuery(String question) {
        return chatClient.prompt()
            .system("""
                你是某电商平台的智能客服助手。规则:
                1. 先通过工具查询数据库获取准确信息
                2. 基于真实数据回答,不要编造
                3. 语气友好、专业
                4. 如果查不到数据,引导用户提供正确的 ID
                5. 涉及退款、投诉等问题,建议转人工客服
                """)
            .user(question)
            .call()
            .content();
    }
}

// 测试效果
customerServiceAssistant.handleQuery("用户 U12345 最近有什么订单?");
// → AI 调用 queryUserOrders("U12345", null),返回订单列表

customerServiceAssistant.handleQuery("订单 O98765 买了什么?什么时候能到?");
// → AI 调用 queryOrderDetail("O98765"), 返回订单详情和商品信息

4.4 多工具组合

实际项目中往往需要同时挂载多种工具:

@Service
public class ComprehensiveAssistant {

    private final ChatClient chatClient;

    public ComprehensiveAssistant(
            ChatClient.Builder builder,
            WeatherToolService weatherTool,
            StockToolService stockTool,
            DatabaseToolService dbTool) {

        this.chatClient = builder
            .defaultTools(weatherTool, stockTool, dbTool)  // 同时注册多个工具
            .build();
    }

    public String ask(String question) {
        return chatClient.prompt()
            .system("你是一位全能助手,可以查询天气、股票和订单信息。根据用户问题智能选择合适的工具。")
            .user(question)
            .call()
            .content();
    }
}

// AI 会根据问题自动选择合适的工具
comprehensiveAssistant.ask("北京今天天气怎么样?顺便帮我查下茅台股价");
// → AI 同时调用 getCurrentWeather("北京") + getStockPrice("600519")

五、生产环境最佳实践

Demo 能跑通只是第一步,上生产环境还需要处理诸多细节。

5.1 工具描述优化

工具描述是 AI 判断"调不调"、"调哪个"的唯一依据,描述的质量直接决定调用的准确率

❌ 反面教材:

@Tool(name = "getData", description = "获取数据")
@Tool(name = "tool1", description = "查询工具")

✅ 正确做法:

// 描述要具体,说明何时使用
@Tool(name = "getUserOrders", 
      description = "查询指定用户的订单列表。当用户询问自己的订单、购买记录、物流状态时使用此工具")

// 参数描述要给示例
@ToolParam(description = "用户ID,格式为 U 开头加 5 位数字,如 U12345")

// 命名要有语义
@Tool(name = "queryUserOrders", description = "...")
@Tool(name = "createRefundRequest", description = "...")
@Tool(name = "checkRefundStatus", description = "...")

核心原则:

  • 描述要说明"什么时候用",而非"做什么"
  • 参数描述要给格式示例和取值范围
  • 命名采用动词开头,见名知意

5.2 错误处理

工具执行可能出错,关键是让 AI 能理解错误并妥善回应。

工具内捕获异常:

@Tool(name = "getWeather", description = "获取指定城市的当前天气")
public WeatherResult getWeather(String city) {
    try {
        return callWeatherApi(city);
    } catch (HttpClientErrorException e) {
        if (e.getStatusCode() == HttpStatus.NOT_FOUND) {
            // 返回特殊结果而非抛异常,让 AI 能理解并友好回复
            return new WeatherResult(city, 0, "未找到该城市的天气信息,请确认城市名称是否正确", 0, 0);
        }
        throw new RuntimeException("天气服务暂时不可用,请稍后重试");
    } catch (Exception e) {
        throw new RuntimeException("查询天气失败:" + e.getMessage());
    }
}

错误信息要让人能看懂:

// ❌ 差的错误信息 —— AI 无法据此生成有用的回复
throw new RuntimeException("NullPointerException at line 45");

// ✅ 好的错误信息 —— AI 据此向用户说明情况
throw new RuntimeException("查询失败:用户不存在。请确认用户ID格式是否正确(应为 U 开头加数字,如 U12345)");

外部 API 加重试:

@Tool(name = "getStockPrice", description = "获取股票实时价格")
public StockInfo getStockPrice(String symbol) {
    int maxRetries = 3;
    for (int i = 0; i < maxRetries; i++) {
        try {
            return callStockApi(symbol);
        } catch (Exception e) {
            if (i == maxRetries - 1) {
                throw new RuntimeException("股票服务暂时不可用,请稍后重试");
            }
            try { Thread.sleep(1000 * (i + 1)); } catch (InterruptedException ignored) {}
        }
    }
    throw new RuntimeException("查询失败");
}

5.3 权限控制

生产环境必须限制 AI 能操作什么、不能操作什么。

身份校验——只能查自己的数据:

@Tool(name = "queryUserOrders", description = "查询用户订单列表")
public List<OrderInfo> queryUserOrders(
        @ToolParam(description = "用户ID") String userId) {

    // 获取当前登录用户
    String currentUserId = SecurityContextHolder.getContext()
        .getAuthentication().getName();

    // 强制校验:只能查自己的订单
    if (!currentUserId.equals(userId)) {
        throw new SecurityException("无权查询其他用户的订单");
    }

    return orderRepository.findByUserId(userId);
}

按角色分配工具:

@Configuration
public class ToolConfig {

    @Bean
    public ChatClient adminChatClient(ChatClient.Builder builder,
                                      AdminToolService adminTool,
                                      UserToolService userTool) {
        // 管理员拥有全部工具
        return builder.defaultTools(adminTool, userTool).build();
    }

    @Bean
    public ChatClient userChatClient(ChatClient.Builder builder,
                                     UserToolService userTool) {
        // 普通用户只有基础工具
        return builder.defaultTools(userTool).build();
    }
}

运行时动态控制:

@Service
public class DynamicToolService {

    public ChatClient createClientForUser(String userId, ChatClient.Builder builder) {
        List<Object> tools = new ArrayList<>();

        // 所有用户都有基础工具
        tools.add(new WeatherToolService());
        tools.add(new StockToolService());

        // 管理员额外有管理工具
        if (isAdmin(userId)) {
            tools.add(new AdminToolService());
        }

        // VIP 用户有高级工具
        if (isVip(userId)) {
            tools.add(new PremiumToolService());
        }

        return builder.defaultTools(tools.toArray()).build();
    }
}

5.4 操作审计

所有工具调用都应该记录审计日志,便于排查问题和合规检查:

@Aspect
@Component
public class ToolAuditAspect {

    @Autowired
    private AuditLogRepository auditLogRepository;

    @Around("@annotation(org.springframework.ai.tool.annotation.Tool)")
    public Object audit(ProceedingJoinPoint joinPoint) throws Throwable {
        String toolName = joinPoint.getSignature().getName();
        String userId = SecurityContextHolder.getContext()
            .getAuthentication().getName();
        String params = Arrays.toString(joinPoint.getArgs());
        long startTime = System.currentTimeMillis();

        try {
            Object result = joinPoint.proceed();

            auditLogRepository.save(new AuditLog(
                userId, toolName, params, "SUCCESS",
                System.currentTimeMillis() - startTime,
                LocalDateTime.now()
            ));

            return result;
        } catch (Exception e) {
            auditLogRepository.save(new AuditLog(
                userId, toolName, params, "FAILED: " + e.getMessage(),
                System.currentTimeMillis() - startTime,
                LocalDateTime.now()
            ));
            throw e;
        }
    }
}

5.5 性能优化

工具结果缓存:

@Service
public class CachedToolService {

    private final Cache<String, Object> cache = Caffeine.newBuilder()
        .expireAfterWrite(5, TimeUnit.MINUTES)   // 5 分钟过期
        .maximumSize(1000)                        // 最多缓存 1000 条
        .build();

    @Tool(name = "getWeather", description = "获取指定城市的当前天气")
    public WeatherResult getWeather(String city) {
        String cacheKey = "weather:" + city;

        // 先查缓存
        WeatherResult cached = (WeatherResult) cache.getIfPresent(cacheKey);
        if (cached != null) {
            return cached;
        }

        // 缓存未命中,调用 API
        WeatherResult result = callWeatherApi(city);
        cache.put(cacheKey, result);
        return result;
    }
}

异步执行非关键操作:

@Tool(name = "sendNotification", description = "发送通知消息")
public String sendNotification(
        @ToolParam(description = "用户ID") String userId,
        @ToolParam(description = "通知内容") String message) {

    // 异步发送,不阻塞 AI 响应
    CompletableFuture.runAsync(() -> {
        notificationService.send(userId, message);
    });

    return "通知已发送";
}

控制返回数据量:

@Tool(name = "getStockPrice", description = "获取股票实时价格")
public StockInfo getStockPrice(String symbol) {
    StockFullData fullData = callApi(symbol);  // API 返回大量字段
    
    // 只返回关键字段,减少 Token 消耗
    return new StockInfo(
        fullData.getSymbol(),
        fullData.getName(),
        fullData.getCurrentPrice(),
        fullData.getChangePercent(),
        fullData.getVolume()
        // 不要把 K线数据、买卖盘等全部返回
    );
}

六、常见问题汇总

Q1:AI 何时会调用工具?

AI 根据用户问题和工具描述自动判断。判断依据包括:

  • 用户问题是否匹配工具的功能描述
  • 用户是否提供了必要的参数信息
  • 问题是否需要实时数据或执行操作

若用户问"你好",AI 不会调用任何工具;若问"北京天气",AI 则会调用天气工具。

Q2:AI 总是调错工具怎么办?

  • 优化工具描述:让每个工具的边界更清晰
  • 减少工具数量:工具越多,AI 越容易混淆
  • 合并相似工具:比如"按日期查订单"和"按状态查订单"可以合并为一个
  • 在 System Prompt 中明确指令:告诉 AI 什么情况用什么工具

Q3:工具执行太慢怎么办?

  • 加缓存:天气、股价等变化不频繁的数据适合缓存
  • 异步执行:通知、日志等非关键操作异步处理
  • 设置超时:避免某个工具拖慢整个响应
  • 优化内部逻辑:检查 SQL 是否有索引、API 调用是否必要

Q4:工具返回数据太多怎么办?

  • 在工具方法里做数据裁剪,只返回关键字段
  • 分页返回:列表类数据限制条数
  • 多层查询:先返回摘要,AI 判断后再查详情

Q5:如何调试工具调用?

Spring AI 提供了详细的日志,开启 DEBUG 级别可以看到:

logging:
  level:
    org.springframework.ai.chat.client: DEBUG

日志会显示 AI 选择了哪个工具、传了什么参数、返回了什么结果。


写在总结

Tool Calling 是 Spring AI 中最实用的特性之一。它使 AI 从"能聊天"进化到"能干活",是构建企业级 AI 应用的核心技术栈。

回顾核心要点:

要点 关键做法
工具定义 优先用 @Tool 注解,动态场景用 FunctionToolCallback
描述质量 决定 AI 调用准确率,要具体、给示例
错误处理 异常要转化为 AI 能理解的友好信息
权限控制 身份校验 + 角色分级 + 动态分配
性能优化 缓存 + 异步 + 数据裁剪
可观测性 审计日志 + DEBUG 日志

下一篇进入 Advisor 机制与对话管理,讲解如何让 AI 记住对话历史、管理上下文窗口、过滤敏感内容。


系列目录:


若本文对您有所帮助,欢迎点赞、收藏与关注,系列持续更新中!

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐