langchain4j笔记-07-tool

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();
相关推荐
東隅已逝,桑榆非晚1 小时前
深⼊理解指针(4)
c语言·笔记
大大杰哥1 小时前
2025ccpc南昌补题笔记(前六题)
c++·笔记·算法
sheeta19981 小时前
LeetCode 每日一题笔记 日期:2026.05.14 题目:2784. 检查数组是否是好的
笔记·算法·leetcode
AOwhisky1 小时前
Docker 学习笔记:Docker Compose 多容器编排
linux·运维·笔记·学习·docker·容器
许长安2 小时前
gRPC 数据包传输格式解析:从 Protobuf 到 HTTP/2
c++·经验分享·笔记·http·rpc
问心无愧05132 小时前
ctf show web入门47
前端·笔记
网络工程小王2 小时前
【LangGraph 状态持久化(Checkpoint)详解】学习笔记
jvm·人工智能·笔记·langchain
问心无愧05132 小时前
ctf show web入门81
前端·笔记
sheeta19982 小时前
TypeScript 学习笔记
笔记·学习·typescript