第三章:1、Tool Calling(工具调用)

内容概览

  • Tool Calling 原理:大模型如何决定调用哪个工具、传递哪些参数
  • Spring AI 注解:@Tool 和 @ToolParam 的用法
  • ToolCallingAdvisor 配置:最大调用次数限制、回退策略

一、@Tool 注解与工具调用

1. 核心概念理解

什么是 Tool Calling?
  • 大模型本身只能生成文本,无法直接查数据库、调 API 或执行计算。
  • Tool Calling 机制让模型在对话中"决定"调用你定义的 Java 方法,拿到执行结果后继续生成回答。
  • 类比:模型是"大脑",Tool 是"手脚"。大脑决定要做什么,手脚去执行,执行结果再反馈给大脑继续思考。
Tool Calling 的完整循环

用户提问:"北京今天天气怎么样?"

↓

模型推理:需要调用天气查询工具

↓

生成 Tool Call 请求:getWeather(city="北京")

↓

ToolCallingAdvisor 拦截并执行 Java 方法 getWeather("北京")

↓

Java 方法返回结果:"北京,28°C,晴"

↓

结果回传给模型

↓

模型生成最终回答:"北京今天气温28°C,天气晴朗,适合外出。"

Spring AI 2.0 的关键变化
  • 1.x 版本用 @Bean Function<...> 注册工具,代码冗长。
  • 2.0 版本只需用 @Tool 注解标注普通方法,一个注解即可完成注册。
  • 工具调用循环由 ChatModel 内部黑盒提升为 ToolCallingAdvisor,可组合、可观测。

2. 定义第一个工具类

新增一个计算器工具类:

java 复制代码
@Component
public class CalculatorTools {

    @Tool(description = "计算两个数的加法。当用户需要进行数学加法运算时使用此工具。")
    public double add(
            @ToolParam(description = "第一个加数") double a,
            @ToolParam(description = "第二个加数") double b) {
        return a + b;
    }

    @Tool(description = "计算两个数的乘法。当用户需要进行数学乘法运算时使用此工具。")
    public double multiply(
            @ToolParam(description = "第一个因数") double a,
            @ToolParam(description = "第二个因数") double b) {
        return a * b;
    }
}

关键点:

  • @Tool 的 description 是给模型看的,要清晰描述"什么场景下该用"。
  • @ToolParam 的 description 也是给模型看的,说明每个参数的含义。
  • Spring AI 会自动根据方法签名生成 JSON Schema,无需手写。

3. 注册工具到 ChatClient

java 复制代码
@RestController
@RequestMapping("/agent")
public class AgentController {

    private final ChatClient agentChatClient;

    public AgentController(ChatClient.Builder builder, CalculatorTools calculatorTools) {
        this.agentChatClient = builder
                .defaultSystem("你是一个智能助手,可以帮用户做数学计算。")
                .defaultTools(calculatorTools)  // 注册工具
                .build();
    }

    @GetMapping("/chat")
    public String chat(@RequestParam String message) {
        return agentChatClient.prompt()
                .user(message)
                .call()
                .content();
    }
}

4. 测试工具调用

bash 复制代码
# 触发工具调用 
curl "http://localhost:8080/agent/chat?message=帮我算一下123乘以456等于多少" 

# 不触发工具调用(模型直接回答) 
curl "http://localhost:8080/agent/chat?message=什么是微服务架构"
  • 第一个请求会调用 multiply 工具,返回计算结果。
  • 第二个请求,模型直接回答,不调用工具。

5. 观察工具调用过程

在 application.yml 中开启日志,便于追踪工具调用的完整流程:

logging: level: org.springframework.ai: DEBUG

二、ToolCallingAdvisor 深入与多步工具调用

1. ToolCallingAdvisor 执行流程

用户消息 → ChatClient

↓

Advisor 链执行(按 order 排序)

↓

ToolCallingAdvisor(DEFAULT_ORDER=300)

↓

① 将工具定义(JSON Schema)注入请求

↓

② 发送给模型

↓

③ 模型返回 tool_call → 执行工具 → 结果回传模型 → 循环

↓

④ 模型返回纯文本(不再调工具)→ 退出循环 → 返回最终回答

2. 添加更多工具,支持多步调用

java 复制代码
@Component
public class UnitConvertTools {

    @Tool(description = "将美元转换为人民币。当用户需要进行美元到人民币的汇率换算时使用此工具。")
    public String convertUsdToCny(
            @ToolParam(description = "美元金额") double usd) {
        // 模拟实时汇率(生产环境应调API)
        double rate = 7.24;
        double cny = usd * rate;
        return String.format("%.2f USD = %.2f CNY(汇率:%.4f)", usd, cny, rate);
    }

    @Tool(description = "将摄氏度转换为华氏度。当用户需要进行温度单位换算时使用此工具。")
    public String celsiusToFahrenheit(
            @ToolParam(description = "摄氏度温度值") double celsius) {
        double fahrenheit = celsius * 9.0 / 5.0 + 32;
        return String.format("%.1f°C = %.1f°F", celsius, fahrenheit);
    }
}

注册多个工具到 ChatClient:

java 复制代码
public AgentController(ChatClient.Builder builder,
                       CalculatorTools calculatorTools,
                       UnitConvertTools unitConvertTools) {
    this.agentChatClient = builder
            .defaultSystem("你是一个智能助手,可以帮用户做数学计算和单位换算。")
            .defaultTools(calculatorTools, unitConvertTools)  // 注册多个工具
            .build();
}

3. 测试多步工具调用

bash 复制代码
# 单步调用:只需要一个工具 
curl "http://localhost:8080/agent/chat?message=100美元等于多少人民币" 

# 多步调用:需要先用一个工具,再用另一个 
curl "http://localhost:8080/agent/chat?message=我有500美元,换成人民币后能买多少件单价为199元的商品"

多步调用流程示例:

  • 第一步:调用 convertUsdToCny(500) 得到 3620 元
  • 第二步:用 multiply 或模型自行除法 → 3620/199 ≈ 18 件
  • 最终回答:"500美元约合3620元人民币,可以购买18件单价199元的商品。"

4. 配置 maxToolExecuteCount

  • 工具调用循环默认最多执行 5 次(防止死循环)。
  • 如需更多轮次,可自定义最大调用次数:
java 复制代码
this.agentChatClient = builder
        .defaultSystem("你是一个智能助手,可以帮用户做数学计算和单位换算。")
        .defaultTools(calculatorTools, unitConvertTools)
        .defaultAdvisors(
            ToolCallingAdvisor.builder()
                .toolCallingManager(ToolCallingManager.builder()
                    .maxTotalToolCalls(10) // 最多调用10次工具
                    .build())
                .build()
        )
        .build();

一般设置 5-10 次即可,过小模型来不及完成多步推理,过大会有死循环风险。

三、工具与 RAG 组合(Advisor 链编排)

1. Advisor 链的组合能力

  • RAG(检索增强生成)用的是 RetrievalAugmentationAdvisor
  • 现在可与 ToolCallingAdvisor 组合,形成多阶段处理链:

用户提问

↓

Memory Advisor(加载历史对话)

↓

ToolCallingAdvisor(如需工具调用)

↓

DocumentRetrievalAdvisor(如需查知识库)

↓

模型生成最终回答

2. 编写"智能运维助手"工具集

java 复制代码
/**
 * 智能运维助手工具
 */
@Component
public class OpsTools {

    @Tool(description = "查询指定服务的当前健康状态。当用户询问某个服务是否正常、是否宕机时使用此工具。")
    public String checkServiceHealth(@ToolParam(description = "服务名称,如order-service、user-service") String serviceName) {
        // 模拟服务健康检查(生产环境调Actuator或监控系统API)
        Map<String, String> statusMap = Map.of(
                "order-service", "UP - CPU: 45%, Memory: 62%, QPS: 850",
                "user-service", "UP - CPU: 23%, Memory: 41%, QPS: 320",
                "payment-service", "DOWN - CPU: 98%, Memory: 89%, 连接池耗尽"
        );
        return statusMap.getOrDefault(serviceName, "未知服务: " + serviceName + ",请确认服务名称是否正确");
    }

    @Tool(description = "查询指定服务最近1小时的错误日志摘要。当用户询问某个服务有什么报错、异常时使用此工具。")
    public String getRecentErrors(@ToolParam(description = "服务名称") String serviceName,
                                  @ToolParam(description = "查询最近多少分钟的日志,默认60") Integer minutes) {
        // 模拟日志查询(生产环境调ELK或Loki)
        if ("payment-service".equals(serviceName)) {
            return String.format("""
                    最近%d分钟错误日志摘要(共127条):
                    1. [ERROR] HikariPool-1 - Connection is not available, request timed out after 30000ms (出现89次)
                    2. [ERROR] OrderController - java.lang.NullPointerException at line 42 (出现23次)
                    3. [WARN] Redis connection lost, retrying... (出现15次)
                    """, minutes != null ? minutes : 60);
        }
        return "最近" + (minutes != null ? minutes : 60) + "分钟内无错误日志";
    }

    @Tool(description = "查询数据库连接池的当前状态。当用户询问数据库连接、连接池、慢查询等问题时使用此工具。")
    public String getDbPoolStatus(@ToolParam(description = "数据源名称,如master、slave") String datasourceName) {
        // 模拟数据库连接池状态
        return String.format("""
                数据源 [%s] 连接池状态:
                - 最大连接数:50
                - 活跃连接数:48
                - 空闲连接数:2
                - 等待队列:37个请求
                - 最近慢查询:SELECT * FROM orders WHERE status='PENDING'(耗时12.3s)
                """, datasourceName);
    }
}

3. 组合 Advisor 链

java 复制代码
/**
 * Agent 配置类
 */
@Configuration
public class AgentConfig {

    @Bean
    public ChatClient opsAgentChatClient(ChatClient.Builder builder,
                                     OpsTools opsTools,
                                     VectorStore vectorStore,
                                     ChatMemory chatMemory) {
        return builder
                .defaultSystem("""
                        你是一个智能运维助手,负责帮助开发和运维团队排查线上问题。
                        你可以通过工具查询服务状态、错误日志和数据库连接池状态。
                        也可以通过知识库查询历史故障处理方案。
                        排查问题时,请先收集信息(调工具),再给出分析结论和处理建议。
                        """)
                .defaultAdvisors(
                        // 对话记忆(保留最近10轮)
                        MessageChatMemoryAdvisor.builder(chatMemory)
                                .order(0)
                                .build(),
                        // RAG 知识库检索(允许空上下文,让 LLM 能触发工具调用)
                        RetrievalAugmentationAdvisor.builder()
                                .documentRetriever(VectorStoreDocumentRetriever.builder()
                                        .vectorStore(vectorStore)
                                        .similarityThreshold(0.7)
                                        .topK(6)
                                        .build())
                                .queryAugmenter(ContextualQueryAugmenter.builder()
                                        .allowEmptyContext(true)
                                        .build())
                                .build()
                )
                .defaultTools(opsTools)  // 注册运维工具
                .build();
    }
}

4. 测试组合效果

bash 复制代码
# 只触发工具调用 
curl "http://localhost:8080/agent/ops/chat?message=payment-service好像挂了,帮我看看" 

# 同时触发工具+知识库(如果知识库有历史处理方案) 
curl "http://localhost:8080/agent/ops/chat?message=payment-service连接池耗尽了,之前有没有类似的处理方案"

第二个请求流程:

  • 调用 checkServiceHealth("payment-service") 确认服务状态
  • 调用 getRecentErrors("payment-service", 60) 查看错误日志
  • 调用 getDbPoolStatus("master") 查看连接池
  • 检索知识库历史处理方案
  • 综合所有信息,给出分析和建议

以上为 Spring AI 工具调用与 RAG 组合的核心实践流程,欢迎参考与采纳。

相关推荐
会编程的吕洞宾1 小时前
Spring AI 2.0 自校正结构化输出实战:让大模型稳定吐出合格 JSON
java·人工智能·后端·spring
深蓝AI2 小时前
Future.result 超时了,Python 线程却没停:线程池取消任务的三个陷阱
后端
suhan2 小时前
【数据结构】三、栈和队列:6.链队列、双端队列、队列的应用(树的层次遍历、广度优先BFS、先来先服务FCFS)
数据结构·后端·算法
鸿蒙开发2 小时前
我把自己的应用上线了:从买服务器、域名和 DNS,到备案与 HTTPS
后端·github
neoooo2 小时前
🚀Claude Code 小技巧(二):找回会话,撤销修改
前端·人工智能·后端
数据库小学妹2 小时前
向量检索最小可运行实现:HNSW索引参数与召回率/延迟权衡
数据库·后端
姜鱼问生2 小时前
Docker Volume 备份与恢复实战:不停机迁移数据
后端·算法·架构
suhan2 小时前
【数据结构】三、栈和队列:5.顺序队列(循环队列)(初始化,判空判满,入队,出队,实例)
数据结构·后端·算法
看浪的路人2 小时前
第7讲:大模型后端服务化——Go 实现高并发 LLM Proxy / Router / Fallback
开发语言·后端·golang