Java后端ai agent学习记录_03

发布时间:2026/11/6 3:46:54
Java后端ai agent学习记录_03 书接上回昨天的demo已经实现了模拟ai多轮对话但是每次对话仍然是依赖于模型自己的知识库去回答用户问题不适于处理实际业务问题还称不上是真正的agent。今天尝试给demo添加工具让它在处理用户问题时可以为了完成用户目标自主循环调用工具解决。1、添加工具方法基于工具方法改造demo在昨天的多轮对话demo基础上复制新增一个AgentMain类尝试添加两个工具1获取当前日期时间工具2获取订单信息工具代码如下// 存储给模型的工具信息 ListMapString, Object tools getTools(); private static ListMapString, Object getTools() { ListMapString, Object tools new ArrayList(); // 获取当前日期和时间工具 MapString, 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); // 查询订单信息 MapString, 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 MapString, 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 MapString, 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; // 信息体 ListMapString, Object messages new ArrayList(); messages.add(Map.of(role, system, content, 你是一个专业的 Java 技术顾问回答简洁。)); // 工具体 ListMapString, Object tools getTools(); // 初始请求体 MapString, 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(); HttpResponseString 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 ListMapString, Object getTools() { ListMapString, Object tools new ArrayList(); // 获取当前日期和时间工具 MapString, 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); // 查询订单信息 MapString, 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的路径错了低级bug2.漏了 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 MapString, 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; // 信息体 ListMapString, Object messages new ArrayList(); messages.add(Map.of(role, system, content, 你是一个专业的 Java 技术顾问回答简洁。)); // 工具体 ListMapString, Object tools getTools(); // 初始请求体 MapString, 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(); HttpResponseString 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 ListMapString, Object getTools() { ListMapString, Object tools new ArrayList(); // 获取当前日期和时间工具 MapString, 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); // 查询订单信息 MapString, 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自己去判断完成用户的需求第一步应该干嘛第二步应该干嘛以此类推。构造第三个工具代码如下// 获取不同状态送达时间 MapString, 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 MapString, String ORDER_DB Map.of( A1001, 已发货, A1002, 运输中, A1003, 已签收 ); // 模拟状态对应耗时 private static final MapString, 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; // 信息体 ListMapString, Object messages new ArrayList(); messages.add(Map.of(role, system, content, 你是一个专业的 Java 技术顾问回答简洁。)); // 工具体 ListMapString, Object tools getTools(); // 初始请求体 MapString, 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(); HttpResponseString 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

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询