工具调用:让大模型从“会说”走向“会做”
工具调用:让大模型从“会说”走向“会做”
从一个平方根计算示例出发,理解 LangChain4j 中 Tool / Function Calling 的核心机制与 Spring Boot 落地方式。
01 为什么需要工具调用?
很多人第一次接触大模型时,会天然觉得:
既然它能回答问题,那它应该也能完成计算、查资料、发邮件、调用系统接口吧?
但实际上,LLM 本质上更擅长的是语言理解与文本生成。
它并不会真正“执行操作”。
比如数学计算。
对于一些复杂数字,大模型可能能给出一个“看起来很像”的答案,但这个答案未必准确。
这就是工具调用(Tool Calling / Function Calling)存在的意义。
它让大模型不再只是“凭感觉回答”,而是在必要时把任务交给外部函数、API 或业务系统去执行,然后再基于真实结果生成最终回复。
02 什么是工具调用?
工具调用,也常被称为函数调用。
它的核心逻辑并不复杂:
- 开发者提前定义好一组工具;
- 请求大模型时,把这些工具的名称、描述、参数告诉模型;
- 模型判断当前问题是否需要调用工具;
- 如果需要,它会在响应中表达“我要调用哪个工具,以及传什么参数”;
- 开发者执行对应工具;
- 再把工具执行结果交还给模型;
- 模型基于结果生成最终回答。
注意这里有一个关键点:
大模型本身并不会真正执行工具。
它只是表达调用意图,真正执行动作的是我们的程序。
工具可以是任何东西:
- 网络搜索
- 调用外部 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 方法进行计算。
这虽然只是一个很小的案例,但它背后的模式非常重要。
未来无论是接入搜索、查询数据库、调用订单系统,还是执行企业内部流程,本质上都是同一套机制:
- 定义工具;
- 描述工具能力;
- 让模型判断是否调用;
- 程序执行工具;
- 模型整合结果并回复用户。
掌握这套机制之后,LLM 应用就不再只是一个聊天窗口,而可以逐步演化成真正能处理业务的智能助手。
一文讲透向量 Embedding:从数学概念到 LangChain4j + Qdrant 实战
Qdrant 向量数据库安装与入门指南
langchain4j 工具调用:让大模型从“会说”走向“会做”
LangChain4j 提示词完全指南:SystemMessage、UserMessage 与 PromptTemplate
LangChain4j 实战:ChatMemory 聊天记忆完全指南
手把手教你用 LangChain4j 集成图片理解与图片生成
LangChain4j 流式模式实战指南
LangChain4j 与 Spring Boot集成指南
更多推荐





所有评论(0)