工具调用:让大模型从“会说”走向“会做”

从一个平方根计算示例出发,理解 LangChain4j 中 Tool / Function Calling 的核心机制与 Spring Boot 落地方式。


01 为什么需要工具调用?

很多人第一次接触大模型时,会天然觉得:

既然它能回答问题,那它应该也能完成计算、查资料、发邮件、调用系统接口吧?

但实际上,LLM 本质上更擅长的是语言理解与文本生成
它并不会真正“执行操作”。

比如数学计算。
对于一些复杂数字,大模型可能能给出一个“看起来很像”的答案,但这个答案未必准确。

这就是工具调用(Tool Calling / Function Calling)存在的意义。

它让大模型不再只是“凭感觉回答”,而是在必要时把任务交给外部函数、API 或业务系统去执行,然后再基于真实结果生成最终回复。


02 什么是工具调用?

工具调用,也常被称为函数调用。

它的核心逻辑并不复杂:

  1. 开发者提前定义好一组工具;
  2. 请求大模型时,把这些工具的名称、描述、参数告诉模型;
  3. 模型判断当前问题是否需要调用工具;
  4. 如果需要,它会在响应中表达“我要调用哪个工具,以及传什么参数”;
  5. 开发者执行对应工具;
  6. 再把工具执行结果交还给模型;
  7. 模型基于结果生成最终回答。

注意这里有一个关键点:

大模型本身并不会真正执行工具。
它只是表达调用意图,真正执行动作的是我们的程序。

工具可以是任何东西:

  • 网络搜索
  • 调用外部 API
  • 查询数据库
  • 执行一段业务代码
  • 发送邮件
  • 数学计算
  • 查询订单状态
  • 调用内部系统

这也是为什么工具调用非常适合用来构建 Agent、智能客服、企业知识助手和自动化办公系统。


03 一个简单例子:计算平方根

假设用户问:

475695037565 的平方根是多少?

如果没有工具,大模型可能会直接回答:

475695037565 的平方根约为 689710。

这个答案接近,但并不准确。

而如果我们给模型提供一个平方根工具:

@Tool("对给定的 2 个数字求和")
double sum(double a, double b) {
    return a + b;
}

@Tool("返回给定数字的平方根")
double squareRoot(double x) {
    return Math.sqrt(x);
}

那么模型在看到问题后,就可以判断:

这个问题需要精确计算,不应该靠我自己猜,应该调用 squareRoot 工具。

于是一次完整的交互过程大致如下。

第一次请求

用户消息:

475695037565 的平方根是多少?

可用工具:

  • sum(double a, double b):对给定的 2 个数字求和
  • squareRoot(double x):返回给定数字的平方根

模型响应:

toolExecutionRequests: squareRoot(475695037565)

这时程序执行:

Math.sqrt(475695037565)

得到结果:

689706.486532

第二次请求

程序把工具执行结果再交给模型:

ToolExecutionResultMessage: 689706.486532

最终模型回复:

475695037565 的平方根是 689706.486532。

这就是工具调用的完整闭环。

它把大模型擅长的“理解和表达”,与程序擅长的“精确执行”结合了起来。


04 项目代码实现

下面用 LangChain4j + Spring Boot 实现一个最小可运行示例。

4.1 新建模块

新建 tool-function 模块,并引入 pom.xml 配置。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>com.whc</groupId>
    <artifactId>langChain4j-whc</artifactId>
    <version>1.0-SNAPSHOT</version>
  </parent>

  <artifactId>langchain4j-whc-tool-function</artifactId>

  <properties>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- langChain4j -->
    <dependency>
      <groupId>dev.langchain4j</groupId>
      <artifactId>langchain4j-open-ai</artifactId>
    </dependency>
    <dependency>
      <groupId>dev.langchain4j</groupId>
      <artifactId>langchain4j</artifactId>
    </dependency>

    <dependency>
      <groupId>dev.langchain4j</groupId>
      <artifactId>langchain4j-reactor</artifactId>
    </dependency>

    <!-- 阿里百炼平台 -->
    <dependency>
      <groupId>dev.langchain4j</groupId>
      <artifactId>langchain4j-community-dashscope-spring-boot-starter</artifactId>
    </dependency>

    <!-- lombok -->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
    </dependency>

    <!-- hutool -->
    <dependency>
      <groupId>cn.hutool</groupId>
      <artifactId>hutool-all</artifactId>
      <version>5.8.22</version>
    </dependency>

    <dependency>
      <groupId>dev.langchain4j</groupId>
      <artifactId>langchain4j-spring-boot-starter</artifactId>
    </dependency>
    <dependency>
      <groupId>org.jsoup</groupId>
      <artifactId>jsoup</artifactId>
      <version>1.18.3</version>
      <scope>compile</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
</project>

这一部分主要完成两件事:

  • 引入 Spring Boot Web 能力;
  • 引入 LangChain4j 与阿里百炼相关依赖。

05 YAML 配置

接着配置服务端口和大模型参数。

server:
  port: 9007
  servlet:
    encoding:
      enabled: true
      force: true
      charset: UTF-8

spring:
  application:
    name: langchain_whc_tool_function

ai:
  dashScope:
    # 配置 dashScope
    apiKey: ${AI_DASHSCOPE_API_KEY}
    modelName: ${AI_DASHSCOPE_MODEL_NAME}
    baseUrl: ${AI_DASHSCOPE_BASE_URL}

这里建议把 API Key、模型名、Base URL 放到环境变量中,不要直接写死在配置文件里。

这样更安全,也方便在不同环境之间切换。


06 大模型配置类

然后创建大模型配置类。

@Slf4j
@Configuration
public class LlmConfig {

    @Value("${ai.dashScope.apiKey}")
    private String dashScopeApiKey;

    @Value("${ai.dashScope.modelName}")
    private String dashScopeModelName;

    @Value("${ai.dashScope.baseUrl}")
    private String dashScopeBaseUrl;

    @Bean
    public StreamingChatModel streamingChatModel() {
        return OpenAiStreamingChatModel.builder()
                .apiKey(dashScopeApiKey)
                .modelName(dashScopeModelName)
                .baseUrl(dashScopeBaseUrl)
                .logRequests(true)
                .logResponses(true)
                .build();
    }
}

这里使用的是 OpenAiStreamingChatModel

它虽然名字里带着 OpenAI,但只要服务端兼容 OpenAI API 协议,也可以接入其他模型服务,例如阿里百炼。

配置中的几个关键参数:

  • apiKey:平台密钥;
  • modelName:使用的模型名称;
  • baseUrl:模型接口地址;
  • logRequests(true):打印请求日志,方便调试;
  • logResponses(true):打印响应日志,方便观察工具调用过程。

07 定义工具处理类

接下来写一个 ToolHandler,用于真正执行工具逻辑。

@Slf4j
@Component
public class ToolHandler {

    /**
     * 计算数字的平方根
     *
     * @param number 输入的数字
     * @return 数字的平方根
     */
    @Tool("计算数值的平方根")
    public double calculateSquareRoot(@P("数值") double number) {
        if (number < 0) {
            throw new IllegalArgumentException("Cannot calculate square root of a negative number");
        }
        return Math.sqrt(number);
    }
}

这里有两个重点注解:

@Tool

用于告诉大模型:

这是一个可调用工具。

注解里的描述非常重要。模型会根据这个描述判断什么时候该调用它。

所以描述要尽量清晰、准确,不要写得太模糊。

@P

用于描述参数含义。

例如这里的 @P("数值"),就是告诉模型:这个参数代表要参与平方根计算的数字。

参数描述越明确,模型生成参数时越不容易出错。


08 定义 Assistant 接口

然后通过 @AiService 定义一个 Assistant。

@AiService(
        wiringMode = AiServiceWiringMode.EXPLICIT,
        streamingChatModel = "streamingChatModel",
        tools = {"toolHandler"})
public interface FunctionAssistant {

    /**
     * 聊天
     *
     * @param message 消息
     * @return 应答信息
     */
    Flux<String> chat(String message);
}

这里的关键配置是:

tools = {"toolHandler"}

它表示把 Spring 容器中的 toolHandler 注册为当前 Assistant 可使用的工具。

当用户提问时,模型不仅会看到用户问题,也会知道自己有哪些工具可以用。


09 Controller 调用

最后提供一个 HTTP 接口,用来测试对话能力。

@Slf4j
@RestController
@RequiredArgsConstructor
public class FunctionController {

    private final FunctionAssistant assistant;

    @GetMapping("/function/chat")
    public Flux<String> chat(String message) {
        return assistant.chat(message);
    }
}

访问接口时传入 message 参数即可。

例如:

/function/chat?message=计算475695037565的平方根

10 测试工具调用效果

启动项目后,在 calculateSquareRoot 方法上打断点。

然后请求接口:

/function/chat?message=计算475695037565的平方根

如果断点进入,说明模型确实触发了工具调用,而不是自己直接生成答案。

从调试结果可以看到,请求进来了,参数也被正确解析成了数值。

再看请求参数,可以发现工具调用链路已经形成:

  • 用户先发起计算请求;
  • 模型生成 tool_calls
  • 程序执行 calculateSquareRoot
  • 工具结果再返回给模型;
  • 模型基于真实结果输出最终回答。

测试通过。


11 小结

工具调用的价值,可以用一句话概括:

让大模型负责理解意图,让程序负责精确执行。

在这个示例中,大模型不再自己“猜”平方根,而是主动调用我们提供的 Java 方法进行计算。

这虽然只是一个很小的案例,但它背后的模式非常重要。

未来无论是接入搜索、查询数据库、调用订单系统,还是执行企业内部流程,本质上都是同一套机制:

  1. 定义工具;
  2. 描述工具能力;
  3. 让模型判断是否调用;
  4. 程序执行工具;
  5. 模型整合结果并回复用户。

掌握这套机制之后,LLM 应用就不再只是一个聊天窗口,而可以逐步演化成真正能处理业务的智能助手。


一文讲透向量 Embedding:从数学概念到 LangChain4j + Qdrant 实战
Qdrant 向量数据库安装与入门指南
langchain4j 工具调用:让大模型从“会说”走向“会做”
LangChain4j 提示词完全指南:SystemMessage、UserMessage 与 PromptTemplate
LangChain4j 实战:ChatMemory 聊天记忆完全指南
手把手教你用 LangChain4j 集成图片理解与图片生成
LangChain4j 流式模式实战指南
LangChain4j 与 Spring Boot集成指南

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐