Java后端ai agent学习记录_02
今天尝试用java的方式去调通一次api使用智谱模型的api key1、单次调通使用jdk17maven引入jackson依赖用于解析和构建json数据代码编写如下package com.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.util.List; import java.util.Map; public class Main { static final String API_URL https://open.bigmodel.cn/api/paas/v4/chat/completions; static final String MODEL glm-5.3-flash; public static void main(String[] args) throws Exception { String apiKey System.getenv(ZHIPU_API_KEY); if (apiKey null || apiKey.isBlank()) { System.err.println(缺少环境变量 ZHIPU_API_KEY见 Run Configuration 的配置方法); return; } ObjectMapper mapper new ObjectMapper(); // 1. 构造请求体 MapString, Object body Map.of( model, MODEL, messages, List.of( Map.of(role, system, content, 你是一个简洁的 Java 技术顾问。), Map.of(role, user, content, 用两句话解释什么是 Function Calling。) ), temperature, 0.3 ); // 2. 发请求 HttpClient client HttpClient.newBuilder() .connectTimeout(Duration.ofSeconds(10)) .build(); HttpRequest request HttpRequest.newBuilder() .uri(URI.create(API_URL)) .header(Authorization, Bearer apiKey) .header(Content-Type, application/json) .timeout(Duration.ofSeconds(60)) .POST(HttpRequest.BodyPublishers.ofString(mapper.writeValueAsString(body))) .build(); HttpResponseString response client.send(request, HttpResponse.BodyHandlers.ofString()); // 3. 打印完整响应 System.out.println(HTTP response.statusCode()); JsonNode root mapper.readTree(response.body()); System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(root)); if (response.statusCode() ! 200) { System.err.println(\n调用失败看上面的错误信息常见原因模型名不对 / Key 无效 / 余额不足); return; } // 4. 拆出需要的字段 System.out.println(\n 解析 ); System.out.println(finish_reason : root.at(/choices/0/finish_reason).asText()); System.out.println(回复内容 : root.at(/choices/0/message/content).asText()); System.out.println(输入 token : root.at(/usage/prompt_tokens).asInt()); System.out.println(输出 token : root.at(/usage/completion_tokens).asInt()); } }通过此方式把模型当做一个普通的http通信方式的接口去调用得到的响应结果如下 解析 finish_reason : stop回复内容 : ## Transactional 失效的常见原因1. **同类自调用最常见**this.method() 不经过代理事务不生效。解决注入自身 Bean、用 AopContext.currentProxy()、或拆分到另一个类。2. **方法不是 public**基于代理的 AOP 只拦截 public 方法。3. **异常被 catch 吞掉**切面捕获不到异常自然不会回滚。4. **异常类型不匹配**默认只回滚 RuntimeException 和 Error受检异常需指定 Transactional(rollbackFor Exception.class)。5. **类未被 Spring 管理**没有 Service/Component 等注解不会生成代理对象。6. **方法被 final 或 static 修饰**CGLIB 无法重写 final 方法static 方法不经过实例代理。7. **数据库引擎不支持事务**如 MySQL 的 MyISAM需使用 InnoDB。8. **传播行为配置错误**如 NOT_SUPPORTED、NEVER 会以非事务方式执行。9. **多线程调用**事务上下文绑定 ThreadLocal子线程中的操作不在同一事务。10. **事务管理器未配置或配置错误**非 Spring Boot 项目需 EnableTransactionManagement多数据源时事务管理器需与数据源对应。**排查口诀**先确认走的是代理对象再确认异常能否抛到切面最后确认异常类型和传播行为。输入 token : 36输出 token : 1272完整响应信息 {choices: [{finish_reason: stop,index: 0,message: {content: ## Transactional 失效的常见原因\n\n1. **同类自调用最常见**this.method() 不经过代理事务不生效。解决注入自身 Bean、用 AopContext.currentProxy()、或拆分到另一个类。\n\n2. **方法不是 public**基于代理的 AOP 只拦截 public 方法。\n\n3. **异常被 catch 吞掉**切面捕获不到异常自然不会回滚。\n\n4. **异常类型不匹配**默认只回滚 RuntimeException 和 Error受检异常需指定 Transactional(rollbackFor Exception.class)。\n\n5. **类未被 Spring 管理**没有 Service/Component 等注解不会生成代理对象。\n\n6. **方法被 final 或 static 修饰**CGLIB 无法重写 final 方法static 方法不经过实例代理。\n\n7. **数据库引擎不支持事务**如 MySQL 的 MyISAM需使用 InnoDB。\n\n8. **传播行为配置错误**如 NOT_SUPPORTED、NEVER 会以非事务方式执行。\n\n9. **多线程调用**事务上下文绑定 ThreadLocal子线程中的操作不在同一事务。\n\n10. **事务管理器未配置或配置错误**非 Spring Boot 项目需 EnableTransactionManagement多数据源时事务管理器需与数据源对应。\n\n**排查口诀**先确认走的是代理对象再确认异常能否抛到切面最后确认异常类型和传播行为。,reasoning_content: The user is asking in Chinese about common reasons why Springs Transactional annotation fails to work. This is a classic Java interview question and a practical development concern. The system prompt says Im a professional Java technical consultant and should answer concisely (回答简洁).\n\nLet me think about all the common reasons Transactional fails:\n\n1. **方法不是 public** - Transactional only works on public methods (in proxy-based AOP). Non-public methods wont be intercepted (though this changed somewhat with AspectJ mode).\n\n2. **自调用** - Calling a Transactional method from another method within the same class bypasses the proxy, so the transaction annotation doesnt take effect. This is the most common cause.\n\n3. **异常被捕获吞掉** - If you catch the exception inside the method and dont rethrow it, Spring never sees the exception and wont roll back.\n\n4. **异常类型不匹配** - By default, Spring only rolls back on RuntimeException and Error. Checked exceptions (like Exception) dont trigger rollback unless you specify rollbackFor Exception.class.\n\n5. **类没有被 Spring 管理** - The class isnt a Spring bean (no Service, Component, etc.), so no proxy is created.\n\n6. **数据库引擎不支持事务** - e.g., MySQL MyISAM engine doesnt support transactions; need InnoDB.\n\n7. **传播行为配置错误** - e.g., using NOT_SUPPORTED, NEVER, or nested transaction issues where inner transaction with REQUIRES_NEW fails but expectation is outer rollback.\n\n8. **多线程调用** - Transaction context is bound to ThreadLocal; if you spawn new threads inside a transactional method, those threads dont participate in the transaction.\n\n9. **final/static 方法** - Cant be overridden by CGLIB proxy (final methods), static methods cant be proxied at all.\n\n10. **事务管理器未配置或配置错误** - Missing EnableTransactionManagement (though Spring Boot auto-configures), wrong PlatformTransactionManager for the datasource.\n\n11. **catch 后又手动 return 或没有抛出** - similar to #3.\n\n12. **propagation 设置问题** - like using SUPPORTS when no transaction exists.\n\n13. **编程式事务混用** issues.\n\n14. **Spring 版本/代理方式问题** - e.g., using JDK dynamic proxy with interface injection issues causing wrong instance.\n\nLet me organize the most common ones concisely with brief explanations and possibly fixes.\n\nFormat considerations: The user asked a technical question in Chinese. The system prompt says to be concise (回答简洁). So I should use a structured list format with the reason and brief explanation/solution. Markdown list is appropriate here since this is likely rendered in a chat interface. I should keep each item brief - one line or two. Maybe group them or just list ~8-10 most common ones.\n\nIll answer in Chinese since the question is in Chinese.\n\nLet me draft:\n\n## Transactional 失效常见原因\n\n1. **方法非 public**基于代理的实现只拦截 public 方法\n2. **同类自调用最常见**this.method() 不走代理事务不生效。解决方案注入自身 / AopContext.currentProxy() / 拆分类\n3. **异常被 catch 吞掉**事务切面捕获不到异常不会回滚\n4. **异常类型不匹配**默认只回滚 RuntimeException 和 Error受检异常需指定 rollbackFor Exception.class\n5. **类未被 Spring 管理**没有 Service 等注解无代理对象\n6. **方法被 final/static 修饰**CGLIB 无法重写 final 方法static 方法不通过实例调用\n7. **数据库引擎不支持事务**如 MySQL 的 MyISAM\n8. **传播行为配置错误**如 PROPAGATION_NOT_SUPPORTED、NEVER\n9. **多线程调用**事务基于 ThreadLocal子线程不在同一事务中\n10. **未启用事务管理 / 事务管理器配置错误**非 Boot 项目需 EnableTransactionManagement\n\nMost common is self-invocation. I could add a quick note about the fix for self-invocation.\n\nShould I include code? Maybe a tiny example for self-invocation would be helpful but the prompt says concise. Ill keep it list-based, maybe with minimal inline code for the fix. Keep it tight.\n\nIll aim for ~10 items, each one line. Thats a good balance of completeness and conciseness.,role: assistant}}],created: 1793690293,id: 202611031517483e76838b5a7942d2,model: glm-5.3-flash,object: chat.completion,request_id: 202611031517483e76838b5a7942d2,usage: {completion_tokens: 1272,completion_tokens_details: {reasoning_tokens: 935},prompt_tokens: 36,prompt_tokens_details: {cached_tokens: 0},total_tokens: 1308}}可以看到到这里我们已经成功调用了一次模型模型也给出了响应结果接下来尝试将问题作为控制台输入循环调用模型模拟我们日常使用ai时的多轮对话。2、多轮对话编写此程序时要注意的点在于每次模型的回复都要作为“assistant”角色的content构造在请求体中进行下一轮对话请求可以看到上面的模型中会额外返回一个字段叫做“reasoning_content”这是调用的glm-5.3是思考模型这个字段相当于它思考的草稿纸也会消耗token但在回填请求的时候这段思考内容不应添加进请求体中否则会放大token的消耗经我改进后的多轮对话demo代码如下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.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Scanner; public class Main { // 智谱api key static final String API_KEY 自己的api_key; SuppressWarnings(unchecked) public static void main(String[] args) throws Exception { Scanner scanner new Scanner(System.in); // 定义结束标记 boolean endFlag false; // 初始请求体 ListMapString, String messages new ArrayList(); messages.add(Map.of(role, system, content, 你是一个专业的 Java 技术顾问回答简洁。)); MapString, Object reqMap Map.of( model, glm-5.3-flash, messages, messages, temperature, 0.3 ); // 追加用户输入信息到请求体中 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)); // 发送请求 HttpClient httpClient HttpClient.newHttpClient(); ObjectMapper objectMapper new ObjectMapper(); 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()); // 解析结果并将结果信息追加到请求中 JsonNode node objectMapper.readTree(res.body()); 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(null))); } } } }此段为自己编写的测试demo校验逻辑并不完善正常应该优先去校验http状态码是否是200再进行后续操作经验证此demo可以正常运行且得到的结果示例如下请输入问题你好 解析 finish_reason : stop回复内容 : 你好我是 Java 技术顾问。有什么 Java 相关的问题随时问我。输入 token : 26输出 token : 84完整响应 :{choices: [{finish_reason: stop,index: 0,message: {content: 你好我是 Java 技术顾问。有什么 Java 相关的问题随时问我。,reasoning_content: The user just said \你好\ (Hello) in Chinese. The system prompt says Im a professional Java technical consultant and should answer concisely (回答简洁).\n\nI should greet them back briefly in Chinese since theyre speaking Chinese, and perhaps invite them to ask their Java-related question. Keep it concise as instructed.,role: assistant}}],created: 1793697925,id: 202611031725197c847776b9e64ed0,model: glm-5.3-flash,object: chat.completion,request_id: 202611031725197c847776b9e64ed0,usage: {completion_tokens: 84,completion_tokens_details: {reasoning_tokens: 65},prompt_tokens: 26,prompt_tokens_details: {cached_tokens: 0},total_tokens: 110}}请输入问题介绍一下你自己 解析 finish_reason : stop回复内容 : 你好我是一个专业的 Java 技术顾问。**我能帮你**- 解答 Java 核心知识JVM、集合、并发、IO 等- 分析代码问题、排查 Bug- 讲解主流框架 - 提供架构设计、性能优化建议- 解答面试相关技术问题我的回答风格是**简洁、专业、直击要点**。有什么问题尽管问输入 token : 49输出 token : 156完整响应 :{choices: [{finish_reason: stop,index: 0,message: {content: 你好我是一个专业的 Java 技术顾问。\n\n**我能帮你**\n- 解答 Java 核心知识JVM、集合、并发、IO 等\n- 分析代码问题、排查 Bug\n- 讲解主流框架\n- 提供架构设计、性能优化建议\n- 解答面试相关技术问题\n\n我的回答风格是**简洁、专业、直击要点**。\n\n有什么问题尽管问,reasoning_content: The user is asking me to introduce myself. Im set up as a professional Java technical consultant who answers concisely. The conversation is in Chinese, so I should respond in Chinese.\n\nI should keep the introduction brief since the system prompt says answers should be concise (回答简洁). Let me introduce myself as a Java technical consultant.,role: assistant}}],created: 1793697935,id: 20261103172530ea7beffc926244a2,model: glm-5.3-flash,object: chat.completion,request_id: 20261103172530ea7beffc926244a2,usage: {completion_tokens: 156,completion_tokens_details: {reasoning_tokens: 67},prompt_tokens: 49,prompt_tokens_details: {cached_tokens: 0},total_tokens: 205}}请输入问题exit进程已结束退出代码为 0这里我设定用户输入exit就认为退出结束对话此demo可以正常运行到这里也算是初步学习完了怎么去简单调用模型。