☰
Java后端ai agent学习记录_03
2026/11/6 3:46:52 网站建设 项目流程

书接上回,昨天的demo已经实现了模拟ai多轮对话,但是每次对话仍然是依赖于模型自己的知识库去回答用户问题,不适于处理实际业务问题,还称不上是真正的agent。今天尝试给demo添加工具,让它在处理用户问题时可以为了完成用户目标自主循环调用工具解决。

1、添加工具方法,基于工具方法改造demo

在昨天的多轮对话demo基础上,复制新增一个AgentMain类,尝试添加两个工具:1>获取当前日期时间工具;2>获取订单信息工具,代码如下:

// 存储给模型的工具信息 List<Map<String, Object>> tools = getTools(); private static List<Map<String, Object>> getTools() { List<Map<String, Object>> tools = new ArrayList<>(); // 获取当前日期和时间工具 Map<String, Object> tool1 = Map.of( "type", "function", "function", Map.of( "name", "get_current_time", "description", "获取当前日期和时间,当用户询问日期和时间相关问题时使用。", "parameters", Map.of( "type", "object", "properties", Map.of() ) ) ); tools.add(tool1); // 查询订单信息 Map<String, Object> tool2 = Map.of( "type", "function", "function", Map.of( "name", "query_order", "description", "根据订单号查询订单的物流状态。当用户询问订单进度、物流、快递时调用。", "parameters", Map.of( "type", "object", "properties", Map.of( "order_id", Map.of( "type", "string", "description", "订单号,如A1001" ) ), "required", List.of("order_id") ) ) ); tools.add(tool2); return tools; }

上面的代码是构造给模型的请求体,告诉模型现在有哪些工具可用,具体的工具逻辑如下,此处的数据均为测试使用,因此简单定义:

// 模拟数据信息 private static final Map<String, String> ORDER_DB = Map.of( "A1001", "已发货", "A1002", "运输中", "A1003", "已签收" ); // tool1:获取日期和时间 private static String getCurrentTime() { return "{\"time\": \"" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + "\"}"; } // tool2:获取订单信息 private static String queryOrder(JsonNode node) { String orderId = node.path("order_id").asText(""); String info = ORDER_DB.get(orderId); if (info == null) { return "{\"error\": \"订单 " + orderId + " 不存在\"}"; } return "{\"order_id\": \"" + orderId + "\",\"status\":\"" + info + "\"}"; }

到此,我们要使用的工具方法已经定义完成,接下来改造demo,下面贴出来的是我根据自己的理解写出的第一版代码:

public class AgentMain { // 智谱api key static final String API_KEY = System.getenv("GLM_API_KEY"); // 模拟数据信息 private static final Map<String, String> ORDER_DB = Map.of( "A1001", "已发货,杭州转运中心", "A1002", "运输中,预计明天到达", "A1003", "已签收" ); public static void main(String[] args) throws Exception { Scanner scanner = new Scanner(System.in); // 定义结束标记 boolean endFlag = false; // 信息体 List<Map<String, Object>> messages = new ArrayList<>(); messages.add(Map.of("role", "system", "content", "你是一个专业的 Java 技术顾问,回答简洁。")); // 工具体 List<Map<String, Object>> tools = getTools(); // 初始请求体 Map<String, Object> reqMap = Map.of( "model", "glm-5.3-flash", "messages", messages, "tools", tools, "thinking", Map.of("type", "disabled"), "temperature", 0.3 ); HttpClient httpClient = HttpClient.newHttpClient(); ObjectMapper objectMapper = new ObjectMapper(); // 追加用户输入信息到请求体中 while (!endFlag) { // 获取用户输入信息 System.out.print("\n请输入问题:"); String info = scanner.nextLine(); if ("exit".equals(info)) { endFlag = true; } else { messages.add(Map.of("role", "user", "content", info)); while (true) { // 发送请求 HttpRequest req = HttpRequest.newBuilder() .uri(URI.create("https://open.bigmodel.cn/api/paas/v4/chat/completions")) .header("Content-Type", "application/json") .header("Authorization", "Bearer " + API_KEY) .POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(reqMap))) .timeout(Duration.ofSeconds(60)) .build(); HttpResponse<String> res = httpClient.send(req, HttpResponse.BodyHandlers.ofString()); if (res.statusCode() != 200) { System.out.println("请求模型失败,接口响应:" + res.body()); break; } JsonNode node = objectMapper.readTree(res.body()); // 判断是否需要调用工具 if (!"tool_calls".equals(node.path("finish_reason").asText())) { System.out.println("\n===== 解析 ====="); System.out.println("finish_reason : " + node.at("/choices/0/finish_reason").asText()); System.out.println("回复内容 : " + node.at("/choices/0/message/content").asText()); System.out.println("输入 token : " + node.at("/usage/prompt_tokens").asInt()); System.out.println("输出 token : " + node.at("/usage/completion_tokens").asInt()); System.out.println("完整响应 : " + objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(node)); messages.add(Map.of("role", "assistant", "content", node.at("/choices/0/message/content").asText(""))); break; } for (JsonNode jsonNode : node.at("/choices/0/message/tool_calls")) { String id = jsonNode.at("/id").asText(); String name = jsonNode.at("/function/name").asText(); JsonNode param = objectMapper.readTree(jsonNode.at("/function/arguments").asText()); String result = switch (name) { case "get_current_time" -> getCurrentTime(); case "query_order" -> queryOrder(param); default -> "{\"error\": \"未知工具: " + name + "\"}"; }; messages.add(Map.of("role", "tool", "tool_call_id", id, "content", result)); } } } } } private static List<Map<String, Object>> getTools() { List<Map<String, Object>> tools = new ArrayList<>(); // 获取当前日期和时间工具 Map<String, Object> tool1 = Map.of( "type", "function", "function", Map.of( "name", "get_current_time", "description", "获取当前日期和时间,当用户询问日期和时间相关问题时使用。", "parameters", Map.of( "type", "object", "properties", Map.of() ) ) ); tools.add(tool1); // 查询订单信息 Map<String, Object> tool2 = Map.of( "type", "function", "function", Map.of( "name", "query_order", "description", "根据订单号查询订单的物流状态。当用户询问订单进度、物流、快递时调用。", "parameters", Map.of( "type", "object", "properties", Map.of( "order_id", Map.of( "type", "string", "description", "订单号,如A1001" ) ), "required", "order_id" ) ) ); tools.add(tool2); return tools; } // tool1:获取日期和时间 private static String getCurrentTime() { return "{\"time\": \"" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + "\"}"; } // tool2:获取订单信息 private static String queryOrder(JsonNode node) { String orderId = node.path("order_id").asText(""); String info = ORDER_DB.get(orderId); if (info == null) { return "{\"error\": \"订单 " + orderId + " 不存在\"}"; } return "{\"order_id\": \"" + orderId + "\",\"status\":\"" + info + "\"}"; } }

这个版本因为根据自己的理解编写,存在一些漏洞:

1.finish_reason的路径错了:低级bug

2.漏了 assistant 消息回填:此版代码中虽然将tool工具执行结果的消息回填了,但是模型在调用工具前发送的那条调用工具的信息漏了回填,这个也会导致报错

3.required这个字段必须是数组,此版中写成了字符串

4.此代码中的内层while循环并未设置最大轮数这种兜底处理,也缺少了日志打印

修复以上这些问题后最终得到代码如下:

package com.zsj.learning; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Scanner; public class AgentMain { // 智谱api key static final String API_KEY = System.getenv("GLM_API_KEY"); // 模拟数据信息 private static final Map<String, String> ORDER_DB = Map.of( "A1001", "已发货,杭州转运中心", "A1002", "运输中,预计明天到达", "A1003", "已签收" ); @SuppressWarnings("unchecked") public static void main(String[] args) throws Exception { Scanner scanner = new Scanner(System.in); // 定义结束标记 boolean endFlag = false; // 信息体 List<Map<String, Object>> messages = new ArrayList<>(); messages.add(Map.of("role", "system", "content", "你是一个专业的 Java 技术顾问,回答简洁。")); // 工具体 List<Map<String, Object>> tools = getTools(); // 初始请求体 Map<String, Object> reqMap = Map.of( "model", "glm-5.3-flash", "messages", messages, "tools", tools, "thinking", Map.of("type", "disabled"), "temperature", 0.3 ); HttpClient httpClient = HttpClient.newHttpClient(); ObjectMapper objectMapper = new ObjectMapper(); // 追加用户输入信息到请求体中 while (!endFlag) { // 获取用户输入信息 System.out.print("\n请输入问题:"); String info = scanner.nextLine(); if ("exit".equals(info)) { endFlag = true; } else { messages.add(Map.of("role", "user", "content", info)); // 计数器 int count = 0; while (true) { count++; // 控制轮次 if (count > 10) { System.out.println("达到最大轮数,强制停止!"); break; } // 发送请求 HttpRequest req = HttpRequest.newBuilder() .uri(URI.create("https://open.bigmodel.cn/api/paas/v4/chat/completions")) .header("Content-Type", "application/json") .header("Authorization", "Bearer " + API_KEY) .POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(reqMap))) .timeout(Duration.ofSeconds(60)) .build(); HttpResponse<String> res = httpClient.send(req, HttpResponse.BodyHandlers.ofString()); if (res.statusCode() != 200) { System.out.println("请求模型失败,接口响应:" + res.body()); break; } JsonNode node = objectMapper.readTree(res.body()); // 判断是否需要调用工具 if (!"tool_calls".equals(node.at("/choices/0/finish_reason").asText())) { System.out.println("\n===== 解析 ====="); System.out.println("finish_reason : " + node.at("/choices/0/finish_reason").asText()); System.out.println("回复内容 : " + node.at("/choices/0/message/content").asText()); System.out.println("输入 token : " + node.at("/usage/prompt_tokens").asInt()); System.out.println("输出 token : " + node.at("/usage/completion_tokens").asInt()); System.out.println("完整响应 : " + objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(node)); messages.add(Map.of("role", "assistant", "content", node.at("/choices/0/message/content").asText(""))); break; } // 回填模型调用工具响应,再组装调工具结果 messages.add(objectMapper.convertValue(node.at("/choices/0/message"), Map.class)); for (JsonNode jsonNode : node.at("/choices/0/message/tool_calls")) { String id = jsonNode.at("/id").asText(); String name = jsonNode.at("/function/name").asText(); String arguments = jsonNode.at("/function/arguments").asText(""); JsonNode param = arguments.isBlank() ? objectMapper.createObjectNode() : objectMapper.readTree(arguments); String result = switch (name) { case "get_current_time" -> getCurrentTime(); case "query_order" -> queryOrder(param); default -> "{\"error\": \"未知工具: " + name + "\"}"; }; System.out.println("第" + count + "轮 | 调用工具" + name + ",参数:" + arguments + ",获得结果:" + result); messages.add(Map.of("role", "tool", "tool_call_id", id, "content", result)); } } } } } private static List<Map<String, Object>> getTools() { List<Map<String, Object>> tools = new ArrayList<>(); // 获取当前日期和时间工具 Map<String, Object> tool1 = Map.of( "type", "function", "function", Map.of( "name", "get_current_time", "description", "获取当前日期和时间,当用户询问日期和时间相关问题时使用。", "parameters", Map.of( "type", "object", "properties", Map.of() ) ) ); tools.add(tool1); // 查询订单信息 Map<String, Object> tool2 = Map.of( "type", "function", "function", Map.of( "name", "query_order", "description", "根据订单号查询订单的物流状态。当用户询问订单进度、物流、快递时调用。", "parameters", Map.of( "type", "object", "properties", Map.of( "order_id", Map.of( "type", "string", "description", "订单号,如A1001" ) ), "required", List.of("order_id") ) ) ); tools.add(tool2); return tools; } // tool1:获取日期和时间 private static String getCurrentTime() { return "{\"time\": \"" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + "\"}"; } // tool2:获取订单信息 private static String queryOrder(JsonNode node) { String orderId = node.path("order_id").asText(""); String info = ORDER_DB.get(orderId); if (info == null) { return "{\"error\": \"订单 " + orderId + " 不存在\"}"; } return "{\"order_id\": \"" + orderId + "\",\"status\":\"" + info + "\"}"; } }

这次的demo完全符合要求了,也可以正常跑通!

测试的问题贴在下面:

1.现在几点了

请输入问题:现在几点了

第1轮 | 调用工具get_current_time,参数:{},获得结果:{"time": "2026-11-04 16:01:47"}

===== 解析 =====

finish_reason : stop

回复内容 : 现在是 **2026年11月4日 16:01:47**。

输入 token : 270

输出 token : 19

完整响应 :

{
"choices": [
{
"finish_reason": "stop",
"index": 0,
"message": {
"content": "现在是 **2026年11月4日 16:01:47**。",
"role": "assistant"
}
}
],
"created": 1793779310,
"id": "20261104160147a9cbb012aa184567",
"model": "glm-5.3-flash",
"object": "chat.completion",
"request_id": "20261104160147a9cbb012aa184567",
"usage": {
"completion_tokens": 19,
"completion_tokens_details": {
"reasoning_tokens": 0
},
"prompt_tokens": 270,
"prompt_tokens_details": {
"cached_tokens": 192
},
"total_tokens": 289
}
}

2.查一下订单A1002

请输入问题:查一下订单A1002

第1轮 | 调用工具query_order,参数:{"order_id":"A1002"},获得结果:{"order_id": "A1002","status":"运输中,预计明天到达"}

===== 解析 =====

finish_reason : stop

回复内容 : 订单 **A1002** 目前状态:**运输中,预计明天到达**。

输入 token : 334

输出 token : 20

完整响应 :

{
"choices": [
{
"finish_reason": "stop",
"index": 0,
"message": {
"content": "订单 **A1002** 目前状态:**运输中,预计明天到达**。",
"role": "assistant"
}
}
],
"created": 1793779378,
"id": "20261104160250837fc8798f2649d9",
"model": "glm-5.3-flash",
"object": "chat.completion",
"request_id": "20261104160250837fc8798f2649d9",
"usage": {
"completion_tokens": 20,
"completion_tokens_details": {
"reasoning_tokens": 0
},
"prompt_tokens": 334,
"prompt_tokens_details": {
"cached_tokens": 256
},
"total_tokens": 354
}
}

3.现在几点?顺便帮我看看 A1003 到哪了

请输入问题:现在几点?顺便帮我看看 A1003 到哪了

第1轮 | 调用工具query_order,参数:{"order_id":"A1003"},获得结果:{"order_id": "A1003","status":"已签收"}

第1轮 | 调用工具get_current_time,参数:{},获得结果:{"time": "2026-11-04 16:03:27"}

===== 解析 =====

finish_reason : stop

回复内容 : 现在是 **2026年11月4日 16:03:27**;订单 **A1003** 已**签收**,请留意查收。

输入 token : 426

输出 token : 36

完整响应 :

{
"choices": [
{
"finish_reason": "stop",
"index": 0,
"message": {
"content": "现在是 **2026年11月4日 16:03:27**;订单 **A1003** 已**签收**,请留意查收。",
"role": "assistant"
}
}
],
"created": 1793779411,
"id": "20261104160328c30acb492d8d4289",
"model": "glm-5.3-flash",
"object": "chat.completion",
"request_id": "20261104160328c30acb492d8d4289",
"usage": {
"completion_tokens": 36,
"completion_tokens_details": {
"reasoning_tokens": 0
},
"prompt_tokens": 426,
"prompt_tokens_details": {
"cached_tokens": 320
},
"total_tokens": 462
}
}

4.查一下订单 A9999

请输入问题:查一下订单 A9999

第1轮 | 调用工具query_order,参数:{"order_id":"A9999"},获得结果:{"error": "订单 A9999 不存在"}

===== 解析 =====

finish_reason : stop

回复内容 : 抱歉,未找到订单 **A9999**,请确认订单号是否正确。

输入 token : 500

输出 token : 20

完整响应 :

{
"choices": [
{
"finish_reason": "stop",
"index": 0,
"message": {
"content": "抱歉,未找到订单 **A9999**,请确认订单号是否正确。",
"role": "assistant"
}
}
],
"created": 1793779443,
"id": "202611041604000ad25cfdc23e415d",
"model": "glm-5.3-flash",
"object": "chat.completion",
"request_id": "202611041604000ad25cfdc23e415d",
"usage": {
"completion_tokens": 20,
"completion_tokens_details": {
"reasoning_tokens": 0
},
"prompt_tokens": 500,
"prompt_tokens_details": {
"cached_tokens": 448
},
"total_tokens": 520
}
}

以上四个用例全部跑通了,到此也是成功的搭建了一个完整的agent,通过用例3可以看出,模型自己去决定要调用哪个工具,要调几轮可以解决用户的问题。

2、新增工具类构造依赖链

到此步已经成功的搭建了一个简单的agent,但是调用方式比较简单,基本一轮就可以搞定了,此节尝试自己新增一个工具去搭建一个依赖链,即让agent自己去判断完成用户的需求,第一步应该干嘛,第二步应该干嘛,以此类推。

构造第三个工具,代码如下:

// 获取不同状态送达时间 Map<String, Object> tool3 = Map.of( "type", "function", "function", Map.of( "name", "query_arrive_time", "description", "根据订单的状态查询订单不同状态下需要的送达时间。当用户询问订单进度,还有多久送达,预估订单送达时间时调用", "parameters", Map.of( "type", "object", "properties", Map.of( "status", Map.of( "type", "string", "description", "状态,如已发货" ) ), "required", List.of("status") ) ) ); tools.add(tool3);

工具具体方法,这里为了方便使用,改造了ORDER_DB 的模拟数据:

// 模拟数据信息 private static final Map<String, String> ORDER_DB = Map.of( "A1001", "已发货", "A1002", "运输中", "A1003", "已签收" ); // 模拟状态对应耗时 private static final Map<String, Integer> TIME_DB = Map.of( "已发货", 2, "运输中", 1, "已签收", 0 ); // tool3:获取不同状态送达时间 private static String queryArriveTime(JsonNode node) { String status = node.path("status").asText(""); Integer info = TIME_DB.get(status); if (info == null) { return "{\"error\": \"状态 " + status + " 不存在\"}"; } return "{\"status\": \"" + status + "\",\"time\":\"" + info + "\"}"; }

demo改造成如下:

@SuppressWarnings("unchecked") public static void main(String[] args) throws Exception { Scanner scanner = new Scanner(System.in); // 定义结束标记 boolean endFlag = false; // 信息体 List<Map<String, Object>> messages = new ArrayList<>(); messages.add(Map.of("role", "system", "content", "你是一个专业的 Java 技术顾问,回答简洁。")); // 工具体 List<Map<String, Object>> tools = getTools(); // 初始请求体 Map<String, Object> reqMap = Map.of( "model", "glm-5.3-flash", "messages", messages, "tools", tools, "thinking", Map.of("type", "disabled"), "temperature", 0.3 ); HttpClient httpClient = HttpClient.newHttpClient(); ObjectMapper objectMapper = new ObjectMapper(); // 追加用户输入信息到请求体中 while (!endFlag) { // 获取用户输入信息 System.out.print("\n请输入问题:"); String info = scanner.nextLine(); if ("exit".equals(info)) { endFlag = true; } else { messages.add(Map.of("role", "user", "content", info)); // 计数器 int count = 0; while (true) { count++; // 控制轮次 if (count > 10) { System.out.println("达到最大轮数,强制停止!"); break; } // 发送请求 HttpRequest req = HttpRequest.newBuilder() .uri(URI.create("https://open.bigmodel.cn/api/paas/v4/chat/completions")) .header("Content-Type", "application/json") .header("Authorization", "Bearer " + API_KEY) .POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(reqMap))) .timeout(Duration.ofSeconds(60)) .build(); HttpResponse<String> res = httpClient.send(req, HttpResponse.BodyHandlers.ofString()); if (res.statusCode() != 200) { System.out.println("请求模型失败,接口响应:" + res.body()); break; } JsonNode node = objectMapper.readTree(res.body()); // 判断是否需要调用工具 if (!"tool_calls".equals(node.at("/choices/0/finish_reason").asText())) { System.out.println("\n===== 解析 ====="); System.out.println("finish_reason : " + node.at("/choices/0/finish_reason").asText()); System.out.println("回复内容 : " + node.at("/choices/0/message/content").asText()); System.out.println("输入 token : " + node.at("/usage/prompt_tokens").asInt()); System.out.println("输出 token : " + node.at("/usage/completion_tokens").asInt()); System.out.println("完整响应 : " + objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(node)); messages.add(Map.of("role", "assistant", "content", node.at("/choices/0/message/content").asText(""))); break; } // 回填模型调用工具响应,再组装调工具结果 messages.add(objectMapper.convertValue(node.at("/choices/0/message"), Map.class)); for (JsonNode jsonNode : node.at("/choices/0/message/tool_calls")) { String id = jsonNode.at("/id").asText(); String name = jsonNode.at("/function/name").asText(); String arguments = jsonNode.at("/function/arguments").asText(""); JsonNode param = arguments.isBlank() ? objectMapper.createObjectNode() : objectMapper.readTree(arguments); String result = switch (name) { case "get_current_time" -> getCurrentTime(); case "query_order" -> queryOrder(param); case "query_arrive_time" -> queryArriveTime(param); default -> "{\"error\": \"未知工具: " + name + "\"}"; }; System.out.println("第" + count + "轮 | 调用工具" + name + ",参数:" + arguments + ",获得结果:" + result); messages.add(Map.of("role", "tool", "tool_call_id", id, "content", result)); } } } } }

接下来跑用例测试验证:

请输入问题:可以帮我预估下A1001会在几号到达吗

第1轮 | 调用工具query_order,参数:{"order_id":"A1001"},获得结果:{"order_id": "A1001","status":"已发货"}

第2轮 | 调用工具query_arrive_time,参数:{"status":"已发货"},获得结果:{"status": "已发货","time":"2"}

第3轮 | 调用工具get_current_time,参数:{},获得结果:{"time": "2026-11-04 16:36:12"}

===== 解析 =====

finish_reason : stop

回复内容 : 您的订单 A1001 目前已发货,预计还需 2 天送达,即大约 **2026年11月6日** 到达。

输入 token : 424

输出 token : 32

完整响应 : {
"choices": [
{
"finish_reason": "stop",
"index": 0,
"message": {
"content": "您的订单 A1001 目前已发货,预计还需 2 天送达,即大约 **2026年11月6日** 到达。",
"role": "assistant"
}
}
],
"created": 1793781378,
"id": "20261104163612c8dbbf51e88c4c10",
"model": "glm-5.3-flash",
"object": "chat.completion",
"request_id": "20261104163612c8dbbf51e88c4c10",
"usage": {
"completion_tokens": 32,
"completion_tokens_details": {
"reasoning_tokens": 0
},
"prompt_tokens": 424,
"prompt_tokens_details": {
"cached_tokens": 384
},
"total_tokens": 456
}
}

可以看到,“预估 A1001 几号到”这个任务,模型自己规划出了一条三步链:查订单 → 拿状态查耗时 → 拿当前时间算日期,每一步都用上一轮的结果,最后还自己算出了“11月6日”,我们这里成功构造了一个完整的依赖链!

这里又让ai去帮我复核了下这个代码,发现一个小问题:tool3中告诉模型应该传什么入参的时候,其实最好改成这样:

"status", Map.of("type", "string", "description", "订单状态", "enum", List.of("已发货", "运输中", "已签收"))

用enum的形式告诉模型这个入参只能从这三种枚举中取,防止模型传来的参数不对,把“祈祷模型传对”变成“从结构上限制只能传对”。

到此,我们已经成功搭建了一个具有完整依赖链的agent!

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询