咱们继续来聊 langchain4j。
最近松哥和大伙聊的基本上都是大模型应用开发的入门知识,松哥这边后面会开始做集团的知识库,到时候再和大家分享一些项目中的实践经验。
大语言模型(LLM)除了生成文本,还可以通过工具调用触发特定操作。该机制允许开发者为模型定义功能函数(如数学计算、API 调用等),当模型识别用户需求需要外部工具辅助时,会在响应中表达调用意图,开发者根据此意图执行工具并将结果返回给模型。
特别是在一些涉及到数学运算的场景,大家知道大模型并不擅长于此,如果能够直接调用我们自己的 API,就会方便很多。或者是一些涉及到内部 API 调用的场景,通过 Function Calling 就可以非常方便的实现。
squareRoot)x表示待计算数值)通过工具调用机制,大模型突破了自身在实时性、准确性和功能扩展性方面的限制。
开发者需要重点掌握工具定义规范、执行流程编排和错误处理策略,结合 ReAct 等框架实现更智能的 AI 应用。最新技术如 LLMCompiler 的并行调用优化,以及 OpenManus 的任务分解机制,正在推动该领域向更高阶的自主智能演进。
LangChain4j 中对于工具的调用提供了两种不同的抽象层次。
Low-Level 直接使用 ChatLanguageModel 和 ToolSpecification API 来实现。
Low-Level 具有如下一些特点:
ToolSpecification),包括工具名称、描述、参数结构(JSON Schema)等。适用场景:
High-Level 是通过 AI Services 和 @Tool 注解的 Java 方法
High-Level 具有如下一些特点:
@Tool 注解自动生成工具规格,无需手动定义。适用场景:
接下来我会逐一和大家介绍这两种方案。
下面我们通过一个完整的天气查询案例,展示如何在 LangChain4j 中手动构造ToolSpecification并实现工具调用流程。
// 步骤1:手动构建天气查询工具规格
ToolSpecification weatherTool = ToolSpecification.builder()
.name("getWeather")
.description("返回指定城市明日天气预报,当用户询问未来24小时天气时调用")
.parameters(JsonObjectSchema.builder()
.addStringProperty("city", "需要查询的城市名称,如北京、London")
.addEnumProperty("unit", List.of("CELSIUS", "FAHRENHEIT"), "温度单位,默认使用CELSIUS")
.required("city") // 显式声明必填参数
.build())
.build();
List<ToolSpecification> toolSpecifications = List.of(weatherTool);
关键设计原则:
getWeather采用动词+名词结构,明确功能定位required() 显式标记必填参数也可以利用注解工具自动定义这个规格,如下:
class WeatherTools {
@Tool("返回指定城市明日天气预报,当用户询问未来24小时天气时调用")
String getWeather(@P("需要查询的城市名称,如北京、London") String city, String temperatureUnit) {
return "2025-03-16深圳天气:多云转晴,气温18-25°C,湿度65%";
}
}
List<ToolSpecification> toolSpecifications = ToolSpecifications.toolSpecificationsFrom(WeatherTools.class);
// 步骤1:构造初始聊天请求
ChatRequest request = ChatRequest.builder()
.messages(UserMessage.from("2025年3月16日深圳的天气如何?"))
.toolSpecifications(toolSpecifications)
.build();
// 发送请求并获取响应
ChatResponse response = model.chat(request);
AiMessage aiMessage = response.aiMessage();
此时若模型判断需要调用工具,aiMessage.toolExecutionRequests()将包含:
{
"name": "getWeather",
"arguments": {"city": "深圳", "unit": "CELSIUS"}
}
// 步骤2:执行工具逻辑(模拟实现)
if (aiMessage.hasToolExecutionRequests()) {
ToolExecutionRequest request = aiMessage.toolExecutionRequests().get(0);
Map<String, Object> args = parseJson(request.arguments());
// 模拟工具执行(实际应调用外部API)
String result = "2025-03-16深圳天气:多云转晴,气温18-25°C,湿度65%";
// 构造结果消息
ToolExecutionResultMessage resultMsg = ToolExecutionResultMessage.from(request, result);
// 构建二次请求
ChatRequest followupRequest = ChatRequest.builder()
.messages(List.of(
UserMessage.from("2025年3月16日深圳的天气如何?"),
aiMessage,
resultMsg
))
.toolSpecifications(toolSpecifications)
.build();
// 获取最终响应
ChatResponse finalResponse = model.chat(followupRequest);
System.out.println(finalResponse.aiMessage().text());
// 输出:"2025年3月16日深圳将有多云转晴天气,温度范围18-25摄氏度"
}
JsonObjectSchema.builder()
.addStringProperty("city", "城市中文或英文名称,如'New York'需转换为'纽约'")
.addEnumProperty("unit", List.of("CELSIUS", "FAHRENHEIT"),
"单位转换需求,如用户特别说明华氏度时使用")
.build()
addStringProperty/addEnumProperty确保参数合法性try {
// 工具执行逻辑
} catch (InvalidCityException e) {
return ToolExecutionResultMessage.error(request, "城市名称无效: " + e.getMessage());
} catch (ApiTimeoutException e) {
return ToolExecutionResultMessage.error(request, "天气接口响应超时");
}
error()方法返回标准错误格式// 添加多个工具规格
ToolSpecification airQualityTool = ToolSpecification.builder()
.name("getAirQuality")
.description("获取城市空气质量指数,当用户询问 PM2.5 或空气质量时调用")
.parameters(/* 参数定义 */)
.build();
List<ToolSpecification> tools = List.of(weatherTool, airQualityTool);
@Tool 注解驱动开发,主要是通过 Java 注解自动生成工具规格,无需手动定义 JSON Schema。
@Tool("计算两个数之和")
public double add(@P("第一个数") int a,
@P(value = "第二个数", required = false) Integer b) {
return a + (b != null ? b : 0);
}
自动生成效果:
{
"name": "add",
"description": "计算两个数之和",
"parameters": {
"type": "object",
"properties": {
"a": {"type": "integer", "description": "第一个数"},
"b": {"type": "integer", "description": "第二个数"}
},
"required": ["a"]
}
}
来看个具体的代码案例吧,也是官方给的案例:
public class CalculatorDemo01 {
staticclass Calculator {
@Tool("计算字符串的长度")
int stringLength(String s) {
System.out.println("计算字符串的长度 s='" + s + "'");
return s.length();
}
@Tool("计算两个数的和")
int add(int a, int b) {
System.out.println("计算两个数的和 a=" + a + ", b=" + b);
return a + b;
}
@Tool("计算一个数的平方根")
double sqrt(int x) {
System.out.println("计算一个数的平方根 x=" + x);
return Math.sqrt(x);
}
}
public static void main(String[] args) {
ChatLanguageModel model = ZhipuAiChatModel.builder()
.apiKey(API_KEY)
.model("glm-4")
.temperature(0.6)
.maxToken(1024)
.maxRetries(1)
.callTimeout(Duration.ofSeconds(60))
.connectTimeout(Duration.ofSeconds(60))
.writeTimeout(Duration.ofSeconds(60))
.readTimeout(Duration.ofSeconds(60))
.build();
Assistant assistant = AiServices.builder(Assistant.class)
.chatLanguageModel(model)
.tools(new Calculator())
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
.build();
String question = "字符串 \"hello\" 和 \"world\" 长度总和的平方根是多少?";
String answer = assistant.chat(question);
System.out.println(answer);
}
}
来看下执行结果:

上面代码比较简单,就不啰嗦解释了。
注意,在 @Tool 注解中,包含触发条件和数据来源。
参数类型 | 支持情况 | 示例 |
|---|---|---|
基本类型 | 完全支持(int, double) | void log(String message) |
集合类型 | List/Set/Map | List<User> findUsers(Query) |
嵌套POJO | 支持递归结构 | Order create(OrderRequest) |
枚举 | 自动识别取值范围 | Status update(Status status) |
复杂参数处理:
@Description("用户查询条件")
class Query {
@Description("筛选状态:ACTIVE/INACTIVE")
Status status;
@Description("返回结果数量限制")
@P(required = false)
Integer limit;
}
@Tool("查找用户")
List<User> findUsers(Query query) { ... }
根据上下文动态加载工具。举个例子。
下面是一个使用 ToolProvider 实现动态工具加载的完整示例,该示例模拟酒店预订场景,根据用户消息内容动态选择工具:
// 预定查询工具(静态定义)
publicclass BookingTools {
@Tool("根据预定号查询预定详情,预定号格式为B-后接5位数字")
public String getBookingDetails(
@P("预定号,例如B-12345") String bookingNumber) {
// 模拟数据库查询
return"预定号: " + bookingNumber + ", 状态: 已确认, 房型: 豪华套房";
}
}
publicclass WeatherTools {
@Tool("返回指定城市明日天气预报,当用户询问未来24小时天气时调用")
String getWeather(@P("需要查询的城市名称,如北京、London") String city, String temperatureUnit) {
return"2025-03-16深圳天气:多云转晴,气温18-25°C,湿度65%";
}
public static ToolExecutor getWeatherExecutor() {
return (request, memoryId) -> {
return"2023-10-15 天气: 晴, 温度20-25°C ";
};
}
}
public class DynamicToolProvider implements ToolProvider {
@Override
public ToolProviderResult provideTools(ToolProviderRequest request) {
String userMessage = request.userMessage().singleText().toLowerCase();
Map<ToolSpecification, ToolExecutor> tools = new HashMap<>();
// 预定相关工具
if (userMessage.contains("预定")) {
// 通过反射获取@Tool注解的方法规格
ToolSpecification bookingSpec = null;
try {
bookingSpec = ToolSpecifications.toolSpecificationFrom(
BookingTools.class.getDeclaredMethod("getBookingDetails", String.class)
);
} catch (NoSuchMethodException e) {
thrownew RuntimeException(e);
}
// 方法引用执行器
ToolExecutor bookingExecutor = (req, memId) -> {
return"bookingExecutor";
};
tools.put(bookingSpec, bookingExecutor);
}
// 天气相关工具
if (userMessage.contains("天气")) {
tools.put(
ToolSpecifications.toolSpecificationsFrom(WeatherTools.class).get(0),
WeatherTools.getWeatherExecutor()
);
}
return ToolProviderResult.builder()
.addAll(tools)
.build();
}
}
public class Demo01 {
// 1. 定义AI服务接口
interface TravelAssistant {
Result<String> handleRequest(String userMessage);
}
// 2. 配置AI服务
TravelAssistant assistant = AiServices.builder(TravelAssistant.class)
.chatLanguageModel(createOpenAiModel()) // 创建模型实例
.toolProvider(new DynamicToolProvider())
.build();
private ChatLanguageModel createOpenAiModel() {
ChatLanguageModel chatModel = ZhipuAiChatModel.builder()
.apiKey(API_KEY)
.model("glm-4")
.temperature(0.6)
.maxToken(1024)
.maxRetries(1)
.callTimeout(Duration.ofSeconds(60))
.connectTimeout(Duration.ofSeconds(60))
.writeTimeout(Duration.ofSeconds(60))
.readTimeout(Duration.ofSeconds(60))
.build();
return chatModel;
}
// 3. 测试不同场景
public static void main(String[] args) {
Demo01 demo01 = new Demo01();
// 案例1: 预定查询
demo01.testRequest("我的预定号B-12345的状态是什么?");
// 案例2: 天气查询
demo01.testRequest("今天巴黎的天气如何?");
// 案例3: 普通咨询
demo01.testRequest("酒店的早餐时间是几点?");
}
private void testRequest(String message) {
Result<String> result = assistant.handleRequest(message);
System.out.println("用户问题: " + message);
System.out.println("最终回答: " + result.content());
result.toolExecutions().forEach(exec ->
System.out.println("调用工具: " + exec.request().name() + ", 参数: " + exec.request().arguments())
);
System.out.println("-------------------");
}
}
用户问题: 我的预定号B-12345的状态是什么?
最终回答: 根据查询结果,您的预定号B-12345的状态是bookingExecutor。如果您需要更详细的预定详情,请提供更多信息,我将尽力帮助您。
调用工具: getBookingDetails, 参数: {"arg0":"B-12345"}
-------------------
用户问题: 今天巴黎的天气如何?
最终回答: 根据我获取的信息,巴黎今天的天气是晴天,温度在20-25°C之间。
调用工具: getWeather, 参数: {"arg0":"Paris","arg1":"today"}
-------------------
用户问题: 酒店的早餐时间是几点?
最终回答: 酒店的早餐时间通常根据酒店的规定而有所不同,但一般而言,大多数酒店的早餐时间可能在以下范围内:
- 早上6:00至上午10:00
- 早上7:00至上午11:00
有些酒店可能提供更早或更晚的早餐服务,以适应不同客人的需求。如果您正在计划前往某家酒店并想了解具体的早餐时间,建议您直接联系酒店的前台或查看官方网站上的信息,以获取最准确的时间安排。
-------------------
获取工具执行记录:
Result<String> result = assistant.chat("查看订单123状态");
List<ToolExecution> executions = result.toolExecutions();
executions.forEach(exec -> {
System.out.println("调用工具: " + exec.request().name());
System.out.println("参数: " + exec.request().arguments());
System.out.println("结果: " + exec.result());
});
结构化错误反馈:
@Tool
String getStockPrice(String symbol) {
try {
return apiClient.fetchPrice(symbol);
} catch (InvalidSymbolException e) {
throw new ToolExecutionException("STOCK_SYMBOL_INVALID", e.getMessage());
}
}
错误类型定义:
class ToolExecutionException extends RuntimeException {
private String errorCode;
public ToolExecutionException(String code, String message) {
super(message);
this.errorCode = code;
}
// Getters
}
缓存机制:
@Tool("查询城市信息")
@Cacheable(value = "cityCache", key = "#cityName")
public CityInfo getCityInfo(String cityName) { ... }
批量处理:
@Tool("批量查询温度")
public Map<String, Double> batchGetTemperatures(List<String> cities) { ... }
维度 | 传统REST API | LangChain4j工具API |
|---|---|---|
接口定义 | Swagger/OpenAPI | Java注解自动生成 |
参数校验 | 手动实现 | 自动类型转换+JSON Schema |
调用方式 | 显式HTTP调用 | LLM动态决策调用 |
错误处理 | HTTP状态码 | 结构化异常消息反馈 |
协议耦合 | 强依赖HTTP | 与协议解耦 |
通过高阶API,开发者可以快速将现有业务能力转化为大语言模型可用的工具,显著提升开发效率。结合动态工具配置和结果追踪功能,能够构建出高度智能化的对话系统。最新实践显示,采用该模式可使工具集成开发时间缩短约 60%。
维度 | 低阶API | 高阶API |
|---|---|---|
灵活性 | 高(完全自定义) | 中(依赖框架自动生成逻辑) |
开发效率 | 低(需手动处理所有细节) | 高(注解驱动,减少重复代码) |
适用阶段 | 探索性开发、复杂集成 | 成熟业务场景、标准化工具 |
学习成本 | 高(需掌握 JSON Schema 等细节) | 低(通过注解简化) |