Tool (Function Calling)
low level api
逻辑如下:
properties
客户端提问
↓
【你编写的代码】
├─ 发送请求给大模型(附带工具定义)
↓
大模型思考后返回
├─ 情况1: 直接回答 → 你直接返回给客户端 ✅
├─ 情况2: 要调用工具 → 返回工具名称+参数JSON
↓
【你手动处理工具调用】
├─ 解析JSON参数
├─ 编写调用代码(调用API/查数据库/算数)
├─ 拿到工具执行结果
├─ 将结果再次发送给大模型
↓
大模型根据工具结果生成最终回答
↓
【你返回最终答案给客户端】
工具的定义和使用
java
public class WeatherTools {
@Tool("给定一个城市,返回城市的天气预报信息")
public String getWeather(@P("需要返回天气预报信息的城市") String city, String temperatureUnit) {
System.out.println("......................");
return city + ": 83" + temperatureUnit;
}
}
java
@Test
void test03() throws JsonProcessingException {
List<ToolSpecification> toolSpecifications = ToolSpecifications.toolSpecificationsFrom(WeatherTools.class);
// 可以把 ToolSpecification 转换为json字符串,然后保存到数据库,或者发送给其他的服务。
//String json = toolSpecifications.get(0).toJson();
//ToolSpecification deserialized = ToolSpecification.fromJson(json);
UserMessage userMessage = UserMessage.from("明天伦敦的天气怎么样?");
ChatRequest request = ChatRequest.builder()
.messages(userMessage)
.toolSpecifications(toolSpecifications)
.build();
ChatResponse response = BASE_MODEL.chat(request);
AiMessage aiMessage = response.aiMessage();
//System.out.println(aiMessage.text());
// todo: 这里因为ds和openai不兼容,那么需要获取 reasoning_content 来封装到消息里面
OpenAiChatResponseMetadata metadata = (OpenAiChatResponseMetadata) response.metadata();
SuccessfulHttpResponse successfulHttpResponse = metadata.rawHttpResponse();
String jsonResponse = successfulHttpResponse.body();
ObjectMapper objectMapper = new ObjectMapper();
JsonNode root = objectMapper.readTree(jsonResponse);
JsonNode choices = root.get("choices");
StringBuilder reasoningBuilder = new StringBuilder();
if (choices != null && choices.isArray()) {
for (JsonNode choice : choices) {
JsonNode message = choice.get("message");
if (message != null) {
// 提取 reasoning_content
JsonNode reasoningNode = message.get("reasoning_content");
if (reasoningNode != null && !reasoningNode.isNull()) {
String reasoning = reasoningNode.asText();
if (!reasoningBuilder.isEmpty()) {
reasoningBuilder.append("\n"); // 多个 choice 之间用换行分隔
}
reasoningBuilder.append(reasoning);
}
}
}
}
String reasonContent = reasoningBuilder.toString();
aiMessage = aiMessage.toBuilder().thinking(reasonContent).build();
// 4. 检查 LLM 是否想要调用工具
if (response.aiMessage().hasToolExecutionRequests()) {
// LLM 请求调用工具!
List<ToolExecutionRequest> toolExecutionRequests = response.aiMessage().toolExecutionRequests();
for (ToolExecutionRequest toolExecutionRequest : toolExecutionRequests) {
System.out.println("要调用的工具: " + toolExecutionRequest.name());
System.out.println("提取的参数: " + toolExecutionRequest.arguments());
// 执行调用工具的方法
//String toolResult = executeTool(toolExecutionRequest);
String toolResult = "伦敦的温度为30摄氏度"; // 这里假定调用了tool的方法
ToolExecutionResultMessage toolExecutionResultMessage = ToolExecutionResultMessage.from(toolExecutionRequest, toolResult);
ChatRequest request2 = ChatRequest.builder()
.messages(List.of(userMessage, aiMessage, toolExecutionResultMessage))
.toolSpecifications(toolSpecifications)
.build();
ChatResponse response2 = BASE_MODEL.chat(request2);
AiMessage aiMessage2 = response2.aiMessage();
System.out.println(aiMessage2.text());
}
} else {
// LLM 直接回答了问题,没有调用工具的意图
System.out.println(response.aiMessage().text());
}
}
带工具的请求参数
json
{
"model" : "deepseek-v4-flash",
"messages" : [ {
"role" : "user",
"content" : "明天伦敦的天气怎么样?"
} ],
"stream" : false,
"tools" : [ {
"type" : "function",
"function" : {
"name" : "getWeather",
"description" : "给定一个城市,返回城市的天气预报信息",
"parameters" : {
"type" : "object",
"properties" : {
"city" : {
"type" : "string",
"description" : "需要返回天气预报信息的城市"
},
"temperatureUnit" : {
"type" : "string"
}
},
"required" : [ "city", "temperatureUnit" ]
}
}
} ]
}
使用流式输出
java
@Test
void test01() {
CompletableFuture<ChatResponse> futureResponse = new CompletableFuture<>();
List<ToolSpecification> toolSpecifications = ToolSpecifications.toolSpecificationsFrom(WeatherTools.class);
UserMessage userMessage = UserMessage.from("明天伦敦的天气怎么样?");
ChatRequest request = ChatRequest.builder()
.messages(userMessage)
.toolSpecifications(toolSpecifications)
.build();
STREAMING_BASE_MODEL.chat(request, new StreamingChatResponseHandler() {
@Override
public void onPartialResponse(String partialResponse) {
System.out.println("onPartialResponse: " + partialResponse);
}
@Override
public void onPartialToolCall(PartialToolCall partialToolCall) {
System.out.println("onPartialToolCall: " + partialToolCall);
}
@Override
public void onCompleteToolCall(CompleteToolCall completeToolCall) {
System.out.println("onCompleteToolCall: " + completeToolCall);
}
@Override
public void onCompleteResponse(ChatResponse completeResponse) {
System.out.println("onCompleteResponse: " + completeResponse);
}
@Override
public void onError(Throwable error) {
error.printStackTrace();
}
});
futureResponse.join();
}
高级API
模型的定义需要增加 sendThinking 和 returnThinking,不然会报错
java
public static final ChatModel BASE_MODEL = OpenAiChatModel.builder()
.baseUrl("https://api.deepseek.com")
.apiKey(System.getenv("DS_API_KEY"))
.modelName("deepseek-v4-flash")
.strictJsonSchema(true)
.sendThinking(true)
.returnThinking(true)
.logRequests(true)
.logResponses(true)
.build();
java
interface MathGenius {
String ask(String question);
}
java
public class Calculator {
@Tool
public double add(int a, int b) {
return a + b;
}
@Tool
public double squareRoot(double x) {
return Math.sqrt(x);
}
}
java
@Test
void test03() {
MathGenius mathGenius = AiServices.builder(MathGenius.class).chatModel(BASE_MODEL).tools(new Calculator()).build();
String answer = mathGenius.ask("475695037565的平方根是多少?");
System.out.println(answer);
}
调用的日志如下:
第一次请求的body:
log
{
"model" : "deepseek-v4-flash",
"messages" : [ {
"role" : "user",
"content" : "475695037565的平方根是多少?"
} ],
"stream" : false,
"tools" : [ {
"type" : "function",
"function" : {
"name" : "add",
"parameters" : {
"type" : "object",
"properties" : {
"a" : {
"type" : "integer"
},
"b" : {
"type" : "integer"
}
},
"required" : [ "a", "b" ]
}
}
}, {
"type" : "function",
"function" : {
"name" : "squareRoot",
"parameters" : {
"type" : "object",
"properties" : {
"x" : {
"type" : "number"
}
},
"required" : [ "x" ]
}
}
} ]
}
第二次请求的body
json
{
"model" : "deepseek-v4-flash",
"messages" : [ {
"role" : "user",
"content" : "475695037565的平方根是多少?"
}, {
"role" : "assistant",
"tool_calls" : [ {
"id" : "call_00_Dw5Lyy8qVKsJIu2R5lmH2573",
"type" : "function",
"function" : {
"name" : "squareRoot",
"arguments" : "{\"x\": \"475695037565\"}"
}
} ],
"reasoning_content" : "我们被问到:\"475695037565的平方根是多少?\" 这是一个计算问题。我们需要计算475695037565的平方根。我们可以使用squareRoot工具来计算。\n\n注意:475695037565是一个很大的数,但平方根函数应该能处理。我们将它作为x传入。"
}, {
"role" : "tool",
"tool_call_id" : "call_00_Dw5Lyy8qVKsJIu2R5lmH2573",
"content" : "689706.4865324959"
} ],
"stream" : false,
"tools" : [ {
"type" : "function",
"function" : {
"name" : "add",
"parameters" : {
"type" : "object",
"properties" : {
"a" : {
"type" : "integer"
},
"b" : {
"type" : "integer"
}
},
"required" : [ "a", "b" ]
}
}
}, {
"type" : "function",
"function" : {
"name" : "squareRoot",
"parameters" : {
"type" : "object",
"properties" : {
"x" : {
"type" : "number"
}
},
"required" : [ "x" ]
}
}
} ]
}
tool 的定义要求和参数要求
java
import dev.langchain4j.agent.tool.P;
import dev.langchain4j.agent.tool.Tool;
import dev.langchain4j.model.output.structured.Description;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Tool需要清晰的描述信息
* 1. tool的名字
* 2. 描述tool是用来干什么的和什么时候应该使用
* 3. 描述每一个tool的参数
* <p>
* Tool的参数类型:
* 1. 所有的基本类型,如 int, double等
* 2. 对象类型: String, Integer, Double 等
* 3. 自定义的POJOs(POJO里面可以内嵌POJO)
* 4. 枚举
* 5. 多态类型
* 6. List<T>/Set<T> 类型,泛型可以是以上的任意类型
* 7. Map<K,V>:你需要在参数描述中使用@P注解来手动指定 K 和 V 的类型
*/
public class Calculator {
@Tool("计算两个整数的和")
public double add(@P("第一个加数(整数)") int a, @P("第二个加数(整数)") int b) {
return (double) a + b; // 结果以 double 返回,保持统一
}
@Tool("计算一个数的平方根。如果输入为 null 或负数,返回错误提示信息")
public String squareRoot(@P("需要求平方根的数(非负实数)") Double x) {
if (x == null) {
return "错误:输入不能为 null";
}
if (x < 0) {
return "错误:不能对负数 " + x + " 求平方根";
}
double result = Math.sqrt(x);
return "√" + x + " = " + result;
}
// 3. 自定义 POJO(含内嵌 POJO)
@Tool("打印人员完整信息,包括嵌套地址")
public String printPersonInfo(@P("人员对象") Person person) {
return String.format("姓名: %s, 年龄: %d, 地址: %s %s %s",
person.name, person.age,
person.address.street, person.address.city, person.address.zipCode);
}
// 4. 枚举类型
@Tool("根据优先级返回处理建议")
public String handlePriority(@P("任务优先级") Priority priority) {
switch (priority) {
case LOW:
return "可以稍后处理";
case MEDIUM:
return "建议今天内完成";
case HIGH:
return "立即处理!";
default:
return "未知优先级";
}
}
// 5. 多态类型(接口 Shape)
@Tool("计算任意形状的面积")
public double computeArea(@P("形状对象,可以是圆形或矩形") Shape shape) {
return shape.area();
}
// 6. List / Set 类型(泛型)
@Tool("计算整数列表的总和")
public int sumList(@P("整数列表", defaultValue = "[1,2]") List<Integer> numbers) {
return numbers.stream().mapToInt(Integer::intValue).sum();
}
@Tool("判断集合中是否包含重复元素")
public boolean hasDuplicate(@P("任意类型的集合") Set<String> items) {
return items.size() < items.size(); // 仅示意,实际Set无重复
// 这里仅为演示,真正的去重判断需与List对比
}
// 7. Map 类型(必须用 @P 手动指定 K 和 V 的类型)
@Tool("获取映射中指定键的值")
public String getValueFromMap(
@P("键值对映射,键为字符串类型,值为整数类型") Map<String, Integer> map,
@P("要查找的键") String key) {
Integer value = map.get(key);
return value != null ? "找到值: " + value : "键不存在";
}
@Tool("合并两个映射(字符串键到字符串值)")
public Map<String, String> mergeMaps(
@P("第一个映射,键类型 String,值类型 String") Map<String, String> map1,
@P("第二个映射,键类型 String,值类型 String") Map<String, String> map2) {
map1.putAll(map2);
return map1;
}
}
// ========== 3. 自定义 POJO(可内嵌 POJO) ==========
class Address {
@Description("街道地址")
public String street;
@Description("城市")
public String city;
@Description("邮政编码")
public String zipCode;
}
class Person {
@Description("姓名")
public String name;
@Description("年龄")
public int age;
@Description("住址")
public Address address; // 内嵌 POJO
}
// ========== 4. 枚举类型 ==========
enum Priority {
LOW, MEDIUM, HIGH
}
// ========== 5. 多态类型(接口或父类) ==========
interface Shape {
double area();
}
class Circle implements Shape {
public double radius;
public double area() {
return Math.PI * radius * radius;
}
}
class Rectangle implements Shape {
public double width;
public double height;
public double area() {
return width * height;
}
}
InvocationParameters 大模型不感知这些参数,只在langchain4j里面看到
InvocationParameters:用途:参数可以在多个tool之间传递,或者tool到RAG传递
java
interface Assistant {
String chat(@UserMessage String userMessage, InvocationParameters parameters);
}
java
@Tool
public String getWeather(String city, InvocationParameters parameters) {
String userId = parameters.get("userId");
System.out.println("........................." + userId);
//UserPreferences preferences = getUserPreferences(userId);
return "London: 30" + "摄氏度";
}
java
@Test
void test4() {
Assistant assistant = AiServices.builder(Assistant.class).chatModel(BASE_MODEL).tools(new Tools()).build();
InvocationParameters parameters = InvocationParameters.from(Map.of("userId", "12345"));
String response = assistant.chat("What is the weather in London?", parameters);
System.out.println(response);
}
InvocationContext 同 InvocationParameters
@ToolMemoryId tool的调用也需要保证在同一个会话里面
tool 如何并发的执行
java
@Test
void test4() {
Assistant assistant = AiServices.builder(Assistant.class)
.chatModel(BASE_MODEL)
.tools(new Tools())
.executeToolsConcurrently()
//.executeToolsConcurrently(Executor)
.build();
}
如何访问执行的tool
非流式调用
java
public interface Assistant {
Result<String> chat(String userMessage);
}
java
@Test
void test03() {
Assistant assistant = AiServices.builder(Assistant.class)
.chatModel(BASE_MODEL)
.tools(new Tools())
.build();
Result<String> result = assistant.chat("Cancel my booking 123-456");
String answer = result.content();
List<ToolExecution> toolExecutions = result.toolExecutions();
ToolExecution toolExecution = toolExecutions.get(0);
ToolExecutionRequest request = toolExecution.request();
String result2 = toolExecution.result(); // tool execution result as text
List<Content> resultContents = toolExecution.resultContents(); // tool execution result as content list (may include images)
Object resultObject = toolExecution.resultObject(); // actual value returned by the tool
}
流式调用
java
public interface Assistant {
TokenStream chat(String message);
}
java
@Test
void test03() {
Assistant assistant = AiServices.builder(Assistant.class)
.streamingChatModel(STREAMING_BASE_MODEL)
.tools(new Tools())
.build();
TokenStream tokenStream = assistant.chat("Cancel my booking");
tokenStream
.onToolExecuted((ToolExecution toolExecution) -> System.out.println(toolExecution)) // 这里访问执行的tool
.onPartialResponse(partialResponse -> {
})
.onCompleteResponse(chatResponse -> {
})
.onError(throwable -> {
})
.start();
}
编程来创建Tool
java
/**
* 编程式创建Tool
*/
@Test
void test03() throws NoSuchMethodException {
ToolSpecification toolSpecification = ToolSpecification.builder()
.name("get_booking_details")
.description("Returns booking details")
.parameters(JsonObjectSchema.builder()
.addProperties(
Map.of("bookingNumber",
JsonStringSchema.builder()
.description("Booking number in B-12345 format")
.build()
))
.build())
.build();
// 针对每一个ToolSpecification, 都需要提供一个ToolExecutor的实现该实现处理大模型所产生的tool的执行请求
ToolExecutor toolExecutor = new ToolExecutor() {
@Override
public String execute(ToolExecutionRequest toolExecutionRequest, Object memoryId) {
String arguments = toolExecutionRequest.arguments();
JsonObject argumentsJson = arguments == null ? null : new Gson().fromJson(arguments, JsonObject.class);
String bookingNumber = argumentsJson.get("bookingNumber").toString();
return getBookingDetails(bookingNumber);
}
};
UserMessage userMessage = UserMessage.from("根据图书的编号 A-1 查询图书的详细信息");
ChatRequest request = ChatRequest.builder()
.messages(userMessage)
.toolSpecifications(toolSpecification)
.build();
ChatResponse response = BASE_MODEL.chat(request);
AiMessage aiMessage = response.aiMessage();
if (response.aiMessage().hasToolExecutionRequests()) {
List<ToolExecutionRequest> toolExecutionRequests = response.aiMessage().toolExecutionRequests();
for (ToolExecutionRequest toolExecutionRequest : toolExecutionRequests) {
String toolExecutionResult = toolExecutor.execute(toolExecutionRequest, "12354");
ToolExecutionResultMessage toolExecutionResultMessage = ToolExecutionResultMessage.from(toolExecutionRequest, toolExecutionResult);
ChatRequest request2 = ChatRequest.builder()
.messages(List.of(userMessage, aiMessage, toolExecutionResultMessage))
.toolSpecifications(toolSpecification)
.build();
ChatResponse response2 = BASE_MODEL.chat(request2);
AiMessage aiMessage2 = response2.aiMessage();
System.out.println(aiMessage2.text());
}
}
}
private String getBookingDetails(String bookingNumber) {
return "图书编号为:" + bookingNumber+":的详细信息是:xxxx";
}
使用DefaultToolExecutor
java
BookingTools tools = new BookingTools();
Method method = BookingTools.class.getMethod("getBookingDetails", String.class);
ToolExecutor toolExecutor = new DefaultToolExecutor(tools, method);
ToolSpecification 和 ToolExecutor 成对设置, 通过 AiServiceTool 来实现
java
@Test
void test5() throws NoSuchMethodException {
ToolSpecification toolSpecification = null;
ToolExecutor toolExecutor = null;
AiServiceTool tool = AiServiceTool.builder()
.toolSpecification(toolSpecification)
.toolExecutor(toolExecutor)
.build();
List<AiServiceTool> tools = new ArrayList<>();
tools.add(tool);
Assistant assistant = AiServices.builder(Assistant.class)
.chatModel(BASE_MODEL)
.tools(tools)
.build();
}
给程序式的tools配置返回的行为
java
@Test
void test6() {
AiServiceTool bookingTool = AiServiceTool.builder()
.toolSpecification(null /*bookingToolSpec*/)
.toolExecutor(null /*bookingExecutor*/)
.returnBehavior(ReturnBehavior.IMMEDIATE)
.build();
AiServiceTool closeTool = AiServiceTool.builder()
.toolSpecification(null/*closeToolSpec*/)
.toolExecutor(null/*closeExecutor*/)
.returnBehavior(ReturnBehavior.IMMEDIATE_IF_LAST)
.build();
AiServiceTool weatherTool = AiServiceTool.builder()
.toolSpecification(null/*weatherToolSpec*/)
.toolExecutor(null/*weatherExecutor*/)
// ReturnBehavior.TO_LLM by default
.build();
List<AiServiceTool> tools = List.of(bookingTool, closeTool, weatherTool);
Assistant assistant = AiServices.builder(Assistant.class)
.chatModel(BASE_MODEL)
.tools(tools)
.build();
}
通过ToolProvider动态创建Tools
java
@Test
void test7() throws NoSuchMethodException {
BookingTools tools = new BookingTools();
Method method = BookingTools.class.getMethod("getBookingDetails", String.class);
ToolExecutor toolExecutor = new DefaultToolExecutor(tools, method);
// toolProvider 定义了 ToolSpecification 和 toolExecutor
ToolProvider toolProvider = (toolProviderRequest) -> {
if (toolProviderRequest.userMessage().singleText().contains("booking")) {
ToolSpecification toolSpecification = ToolSpecification.builder()
.name("get_booking_details")
.description("Returns booking details")
.parameters(JsonObjectSchema.builder()
.addStringProperty("bookingNumber")
.build())
.build();
return ToolProviderResult.builder()
.add(toolSpecification, toolExecutor)
.build();
} else {
return null;
}
};
Assistant assistant = AiServices.builder(Assistant.class)
.chatModel(BASE_MODEL)
.toolProvider(toolProvider)
.build();
// 这里可以多此add。并指定ReturnBehavior
/*ToolProvider toolProvider = (toolProviderRequest) -> {
return ToolProviderResult.builder()
.add(bookingToolSpec, bookingExecutor, ReturnBehavior.IMMEDIATE)
.add(closeToolSpec, closeExecutor, ReturnBehavior.IMMEDIATE_IF_LAST)
.add(weatherToolSpec, weatherExecutor) // ReturnBehavior.TO_LLM by default
.build();
};*/
}
tool的错误处理
java
Assistant assistant = AiServices.builder(Assistant.class)
.chatModel(BASE_MODEL)
.tools(new Tools())
// tool 的名字产生幻觉,这里怎么处理
.hallucinatedToolNameStrategy(new Function<ToolExecutionRequest, ToolExecutionResultMessage>() {
@Override
public ToolExecutionResultMessage apply(ToolExecutionRequest toolExecutionRequest) {
return null;
}
})
// tool执行失败,
.toolExecutionErrorHandler(new ToolExecutionErrorHandler() {
@Override
public ToolErrorHandlerResult handle(Throwable error, ToolErrorContext context) {
return null;
}
})
// tool的参数错误
.toolArgumentsErrorHandler(new ToolArgumentsErrorHandler() {
@Override
public ToolErrorHandlerResult handle(Throwable error, ToolErrorContext context) {
return null;
}
})
.build();