Java工程师如何转型AI Agent开发?从原理到Spring Boot实战全解析

Java工程师如何转型AI Agent开发?从原理到Spring Boot实战全解析 各位 Java 开发朋友尤其是正在关注 AI 方向、准备转型的同学大家好。这段时间AI Agent智能体的火热程度大家有目共睹。从各家大模型厂商推出的 Agent 平台到开源社区里层出不穷的 Agent 框架再到招聘网站上“Agent 开发工程师”的岗位薪资都在传递一个信号AI 时代Agent 开发已经成为了新的技术风口。很多 Java 工程师想转型但普遍有一个疑虑我是搞后端、搞 Spring Boot、搞微服务的Agent 开发是不是必须用 Python是不是得先转行学几个月 Python 才行今天结合我近期的学习和项目落地经验围绕“Java 转 Agent 开发”这条主线拆解一套适合 Java 工程师的 Agent 学习与实战路线。这篇文章不仅会讲 Agent 的核心概念还会给出可落地的 Java 技术栈选型、环境配置、代码实战——包含一个基于 Spring Boot 的三方 API 调用实战——以及完整的排错思路希望给想入门 Agent 开发的 Java 同行一条更平滑的上手路径。如果你正好想了解 Agent 项目如何从 0 到 1 搭建或者想看看 Java 工程师能不能吃上 AI 这碗饭这篇文章建议先收藏跟着走一遍。1. 为什么 Java 工程师可以切入 Agent 开发很多同学被市面上的 Agent 教程劝退原因很简单大多数教程默认使用 Python。但如果你是一名 Java 工程师过早跳到 Python 并不一定是最优选除非你未来想去纯算法岗。大部分 Agent 工程项目本质上是构建在 LLM大语言模型之上的复杂业务系统它涉及大量的工程化问题高并发、分布式事务、数据一致性、权限控制、可观测性、系统集成。这些恰恰是 Java 技术栈的优势领域。Agent 这个概念本身是一个“大而全”的抽象它不是某个特定的框架而是一种应用架构模式。业界通常把它定义为“具备感知Perception、决策Decision、行动Action能力的智能系统”。在技术落地时它可能是一个自动写 SQL 的分析助手也可能是一个能操作内部系统的客服机器人。如果我们用 Java 构建这样一个系统我们的任务不是从零训练神经网络而是负责设计调度、编排、状态机、插件通信和业务日志的流转也就是将大模型能力嵌入到企业级应用中。有一句比较流行的说法是“Agent 的上限取决于模型能力Agent 的下限取决于工程能力。”Java 工程师多年的工程化沉淀在这里反而成了优势。很多 Agent 框架本身就是用 Java 写的或提供了 Java SDK例如 Spring AI、LangChain4j、Semantic KernelJava 版本、通义百炼 SDK、百度千帆 SDK 等所以在技术路线上不存在“Java 不能做 Agent”的问题。因此Java 转向 Agent 开发的核心思路是“复用工程经验补齐模型认知”。我们需要掌握的不再是 Spring Data JPA、Redis、MQ 怎么写而是如何把大模型封装成一个可编程的“函数”如何让模型自主选择调用我们的业务 API以及如何管理多轮对话中的上下文。2. Agent 基础与核心概念扫盲2.1 从 ChatGPT 到 Agent模型与应用的区别我们平时使用的网页版大模型本质上是一个“被动的问答工具”你输入一句 Prompt提示词模型返回一段文本。这种方式解决的是单轮、静态的知识问答问题。Agent 则不一样。Agent 的核心能力是“自主规划 工具调用”。比如你让它“帮我查一下北京未来三天的天气如果下雨记得提醒我带伞”如果只是单纯的对话模型回答会很不准确或者说无法实时获取数据。但是 Agent 可以通过大模型的理解能力拆分出两个步骤调用天气查询 API 获取北京天气数据根据返回数据判断是否下雨并生成提醒文案。在技术上这种“让模型学会使用外部工具”的能力通常通过Function Calling函数调用或Tool Use来实现。大模型本身不负责计算天气它只负责输出一段结构化的 JSON 指令。比如模型输出{ function: queryWeather, params: {city: 北京} }。我们的业务系统拿到这段 JSON 后再去执行真实的天气 API。2.2 Agent 的四大核心要素不管是用什么语言开发 Agent业界普遍认可 Agent 由四部分组成核心要素作用Java 中的实现思路规划Planning模型将复杂任务拆解成子步骤Prompt 工程、思维链、任务分解策略记忆Memory保存对话历史和关键信息Redis / 数据库 / 向量数据库存储工具Tools模型调用外部 API 或执行业务方法定义 Function、HTTP 接口、Spring Bean 方法行动Action执行规划好的步骤并反馈结果Java 服务中的业务逻辑编排很多教程会忽略记忆这一环但对于生产级 Agent多轮对话中的上下文管理非常重要。例如用户上一轮说了“上海”下一轮说“那后天呢”如果没有记忆设计Agent 根本不知道“后天”指的是上海的哪天。2.3 主流 Agent 技术形态目前主流的 Agent 技术实现大致分为以下几类单一 Agent由一个模型实例完成规划、记忆、工具调用适合任务链路不长的场景。例如“SQL 生成助手”。多 Agent 协作多个角色不同的 Agent 配合一个负责拆解任务一个负责代码生成一个负责测试。这种方式更像一个虚拟团队适合复杂项目但工程复杂度也更高。工作流 Agent预先编排好执行步骤Agent 按流程循环“思考-行动-观察”直到任务结束。这种模式更可控适合生产环境。对 Java 工程师来说刚开始不需要纠结太复杂的多 Agent 架构。我们先把单一 Agent 和工具调用跑通理解“模型 - 工具 - 业务逻辑”的三角关系再逐步叠加工作流与记忆。3. Java Agent 工程环境准备与基础选型3.1 JDK 与构建工具Agent 开发属于后端工程开发JDK 版本建议使用 17 及以上。JDK 17 是长期支持版本Spring Boot 3.x 和主流 Java Agent 框架对 JDK 17 的支持都比较完善。如果你公司内部项目还在 JDK 8熟悉语法迁移也是必要功课但不要用 JDK 8 作为新项目起点。构建工具推荐 Maven 或 Gradle两者都可以。Maven 在大多数企业中普及度更高本文示例以 Maven 为例。3.2 网络代理与 API Key 准备Agent 的核心是调用大模型 API。在开发测试阶段你需要准备一个可以访问的 LLM API。如果你选择 OpenAI 系列因为网络环境原因可能访问不稳定备选方案是使用国内大模型服务商的兼容接口。国内主流大模型服务商都提供兼容 OpenAI 风格的接口例如智谱 AI、百川智能、通义千问、DeepSeek 等注册后即可获得 API Key。需要注意这里说的“OpenAI 兼容接口”指的是 HTTP 接口风格相同不是让你去绕过网络限制。生产项目请优先选择你能稳定访问、且合法合规的模型服务商。如果你是公司内部搭建的模型网关也可以直接配置内网地址。无论选择哪种请把 API Key 放在环境变量或配置中心不要硬编码在代码仓库里。3.3 Spring AI 与 LangChain4j 简介Java 生态里做 Agent目前主流的两套框架Spring AISpring 官方推出的 AI 抽象层如果你已经熟悉 Spring Boot 的自动配置思想上手这个很快。它对聊天模型、向量数据库、结构化输出都有封装。LangChain4j社区驱动的 Java 版 LangChain设计初衷就是把 LangChain 的能力带给 Java 开发者。它内置了很多 Tool工具的封装也支持与 Spring Boot 集成。框架本身不是银弹。对于教学案例我们可以不用框架直接用最基础的 HttpClient 调用大模型 API先把原理讲透生产级项目再引入框架提升开发效率。3.4 推荐的项目基础结构一个常规的 Java Agent 工程建议按如下结构组织agent-demo ├── src/main/java/com/example/agent │ ├── controller # HTTP 接口层 │ ├── service # Agent 核心服务层 │ ├── tool # 工具定义与实现 │ ├── common # 通用实体、常量 │ └── config # 配置类 ├── src/main/resources │ └── application.yml # 配置文件 └── pom.xmlJava 工程强调分层Agent 项目也不例外。把“模型调用逻辑”和“业务工具逻辑”分离是保持项目可维护的关键。4. 核心原理拆解从 Prompt 到 Function Calling4.1 Prompt 结构设计Agent 的能力很大程度上取决于你如何写 Prompt。Prompt 不是简单的“你是一个助手”而是系统指令。在 Java 项目中Prompt 可以放在配置中心或数据库中方便随时调整不要硬编码在代码里。一个生产级的 Agent Prompt 通常包含以下部分角色定位你是一个智能助手负责帮助用户查询信息并执行业务操作。能力边界你可以调用以下工具查天气、查快递、生成报表当用户需求超出能力范围时明确告知无法处理。输出格式要求必须调用工具时返回结构化 JSON不需要调用工具时直接回复文本。上下文引用结合历史对话信息不要重复询问用户已经给出的信息。下面是 Java 项目中一段典型的 Prompt 模板你是一个智能运维助手可以查询服务器状态。 请根据用户的意图选择合适的工具并返回参数。 工具列表 1. queryServerStatus(serverName: string)查询服务器CPU、内存使用率。 规则 - 如果用户询问服务器状态调用 queryServerStatus 工具。 - 参数必须提取用户语句中的主机名。 - 如果你无法处理用户请求直接回复“无法处理该请求”。大模型对结构化指令的理解比自由文本更准确因此我们在设计 Prompt 时建议使用“列表 规则 示例”的组合方式。4.2 Function Calling 机制Function Calling 的工作原理可以理解为三步我们在 API 请求中附带一个tools参数描述有哪些函数可以调用、参数类型是什么。模型根据用户输入决定是否需要调用函数。如果需要返回值中会包含函数名和实参。我们的代码执行函数并把执行结果回传给模型模型再生成最终的自然语言答复。在 OpenAI 兼容接口中一个工具的 JSON Schema 示例{ type: function, function: { name: queryWeather, description: 查询一个城市的实时天气信息, parameters: { type: object, properties: { city: { type: string, description: 城市名称例如北京 } }, required: [city] } } }Java 后端收到模型返回的调用意图后核心任务是根据function.name找到对应的 Java Bean 方法并完成参数映射。这里最常见的做法是维护一个 Mapkey 为函数名value 为处理该函数调用的 Spring Bean。4.3 上下文与记忆为了让 Agent 记住用户说过的话我们需要维护一个消息列表。主流大模型 API 的 chat completion 接口支持messages参数常见角色有system系统指令、user用户、assistant助手、tool工具返回结果。一个包含工具调用结果的对话消息序列大致如下[ {role: system, content: 你是运维助手}, {role: user, content: 帮我查一下 webserver-01 的状态}, {role: assistant, content: null, tool_calls: [{function: {name: queryServerStatus, arguments: {\serverName\: \webserver-01\}}}]}, {role: tool, content: {\cpu\: 12.5, \memory\: 67.8}}, {role: assistant, content: webserver-01 当前 CPU 使用率为 12.5%内存使用率为 67.8%状态正常。} ]注意tool_calls中arguments是一个 JSON 字符串Java 端需要先反序列化成 Map 对象再反射或路由调用业务方法。5. 实战案例基于 Spring Boot 的天气查询 Agent下面我们通过一个完整案例把 Java 工程师从零构建 Agent 的流程跑通。案例目标构建一个“天气查询助手”。用户输入“北京明天天气怎么样”Agent 调用三方天气 API 返回结构化天气信息再用自然语言回复用户。为了便于理解这里不引入 Spring AI 框架而是直接通过 Java 原生 HttpClient 调用大模型 API。这样的好处是你能清晰地看到底层请求和响应的完整逻辑不会被框架的高级抽象干扰。5.1 初始化 Spring Boot 项目使用 Spring Initializr 创建一个 Spring Boot 项目Java 版本 17依赖选择spring-boot-starter-web、lombok。如果你的网络环境访问 spring.io 困难可以在 IDEA 中直接创建。pom.xml关键依赖parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.2.4/version relativePath/ /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency dependency groupIdcom.fasterxml.jackson.core/groupId artifactIdjackson-databind/artifactId /dependency /dependencies这里的重点是我们通过 Jackson 处理 JSON 序列化与解析通过HttpClient发起 HTTP 调用。5.2 配置 application.ymlapplication.yml主要保存模型 API 地址、API Key 和模型名。server: port: 8080 ai: model: api-key: ${LLM_API_KEY} base-url: https://your-model-endpoint.example.com model-name: your-model-name注意${LLM_API_KEY}是环境变量占位符。不要将真实密钥直接写入 YAML 文件。生产环境可以接入 Nacos 等配置中心这里为了简化直接使用环境变量。5.3 定义模型请求与响应实体我们先定义与大模型交互的数据结构。一个标准的 chat completion 请求结构package com.example.agent.common; import lombok.Data; import java.util.List; Data public class ChatRequest { private String model; private ListMessage messages; private ListTool tools; private Double temperature; }消息体结构package com.example.agent.common; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.util.List; Data NoArgsConstructor AllArgsConstructor public class Message { private String role; private String content; private ListToolCall toolCalls; public Message(String role, String content) { this.role role; this.content content; } }工具调用参数package com.example.agent.common; import lombok.Data; import java.util.List; import java.util.Map; Data public class ToolCall { private String id; private String type; private FunctionCall function; Data public static class FunctionCall { private String name; private String arguments; } }工具定义package com.example.agent.common; import lombok.Data; import java.util.Map; Data public class Tool { private String type; private FunctionInfo function; Data public static class FunctionInfo { private String name; private String description; private MapString, Object parameters; } }这些实体类和普通后端项目的 DTO 没有本质区别熟悉 Java 的同学一看就懂。5.4 定义天气查询工具接下来定义一个工具接口和实现。工具的作用是让 Agent 能调用真实的三方天气 API。这里我们模拟一个真实可用的思路经纬度查询天气。package com.example.agent.tool; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.stereotype.Component; 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.Map; Component public class WeatherTool { private final HttpClient httpClient HttpClient.newBuilder() .connectTimeout(Duration.ofSeconds(10)) .build(); private final ObjectMapper objectMapper new ObjectMapper(); public String execute(MapString, Object arguments) { String city String.valueOf(arguments.get(city)); // 这里以 open-meteo 等开放天气接口为例生产环境替换为你自己的天气服务商 // 先通过地理编码 API 获取经纬度再查询天气简化演示时也可以直接根据城市映射固定坐标 try { // 示例 URL实际使用时替换为可用的合法天气服务地址 String geoUrl https://your-geocoding-api.example.com/search?city city; HttpRequest request HttpRequest.newBuilder() .uri(URI.create(geoUrl)) .timeout(Duration.ofSeconds(10)) .GET() .build(); HttpResponseString response httpClient.send(request, HttpResponse.BodyHandlers.ofString()); JsonNode root objectMapper.readTree(response.body()); double lat root.path(latitude).asDouble(); double lon root.path(longitude).asDouble(); // 返回一个 JSON 字符串方便模型理解 return {\city\: \ city \, \latitude\: lat , \longitude\: lon }; } catch (Exception e) { return {\error\: \查询天气失败请稍后重试\}; } } }这里我们刻意简化了天气工具的完整实现只演示了 Java 端如何用一个类去封装第三方 API。实际项目中工具内部可以做更复杂的缓存、重试、权限校验、埋点统计等逻辑。5.5 实现 Agent 核心服务层这一段是整个案例的核心。我们需要实现这样的逻辑接收用户输入。组装 System Prompt 和用户消息。定义可用的工具列表这里只有天气查询。调用模型 API。判断返回是否包含tool_calls。如果有执行工具把结果作为tool角色的消息返回给模型再次调用模型获取最终回答。Service 层代码package com.example.agent.service; import com.example.agent.common.*; import com.example.agent.tool.WeatherTool; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; 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; Service public class AgentService { private final ObjectMapper objectMapper new ObjectMapper(); private final WeatherTool weatherTool; private final HttpClient httpClient HttpClient.newBuilder() .connectTimeout(Duration.ofSeconds(15)) .build(); Value(${ai.model.api-key}) private String apiKey; Value(${ai.model.base-url}) private String baseUrl; Value(${ai.model.model-name}) private String modelName; public AgentService(WeatherTool weatherTool) { this.weatherTool weatherTool; } public String chat(String userMessage) throws Exception { // 1. 构建消息列表 ListMessage messages new ArrayList(); messages.add(new Message(system, 你是一个天气助手可以查询城市天气。请根据用户问题调用工具。)); messages.add(new Message(user, userMessage)); // 2. 调用模型 JsonNode firstResponse callModel(messages); // 3. 如果有 tool_calls执行工具并回传 JsonNode toolCalls firstResponse.path(choices).path(0).path(message).path(tool_calls); if (toolCalls.isArray() toolCalls.size() 0) { // 把带 tool_calls 的 assistant 消息加入上下文 Message assistantMsg new Message(); assistantMsg.setRole(assistant); assistantMsg.setContent(null); assistantMsg.setToolCalls( objectMapper.convertValue(toolCalls, new TypeReferenceListToolCall() {}) ); messages.add(assistantMsg); for (JsonNode toolCall : toolCalls) { String functionName toolCall.path(function).path(name).asText(); String arguments toolCall.path(function).path(arguments).asText(); String toolCallId toolCall.path(id).asText(); MapString, Object argsMap objectMapper.readValue(arguments, new TypeReference() {}); String result; if (queryWeather.equals(functionName)) { result weatherTool.execute(argsMap); } else { result {\error\: \未知工具\}; } Message toolMessage new Message(); toolMessage.setRole(tool); toolMessage.setContent(result); toolMessage.setToolCallId(toolCallId); messages.add(toolMessage); } // 4. 重新调用模型生成最终文本回复 JsonNode finalResponse callModel(messages); return finalResponse.path(choices).path(0).path(message).path(content).asText(); } return firstResponse.path(choices).path(0).path(message).path(content).asText(); } private JsonNode callModel(ListMessage messages) throws Exception { ChatRequest request new ChatRequest(); request.setModel(modelName); request.setMessages(messages); request.setTemperature(0.2); // 构建工具列表 Tool weatherToolDef buildWeatherToolDefinition(); request.setTools(List.of(weatherToolDef)); String body objectMapper.writeValueAsString(request); HttpRequest httpRequest HttpRequest.newBuilder() .uri(URI.create(baseUrl /v1/chat/completions)) .header(Content-Type, application/json) .header(Authorization, Bearer apiKey) .POST(HttpRequest.BodyPublishers.ofString(body)) .timeout(Duration.ofSeconds(30)) .build(); HttpResponseString response httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() ! 200) { throw new RuntimeException(模型调用失败: response.statusCode() response.body()); } return objectMapper.readTree(response.body()); } private Tool buildWeatherToolDefinition() { Tool tool new Tool(); tool.setType(function); Tool.FunctionInfo functionInfo new Tool.FunctionInfo(); functionInfo.setName(queryWeather); functionInfo.setDescription(查询指定城市的天气情况); functionInfo.setParameters(Map.of( type, object, properties, Map.of( city, Map.of( type, string, description, 城市名称如北京 ) ), required, List.of(city) )); tool.setFunction(functionInfo); return tool; } }这段代码包含了很多 Java 17 和 Spring 的常用写法比如构造注入、var类型推断这里没用、Map.of不可变集合等。在callModel方法中组装了标准的 HTTP 请求并设置了鉴权 Header。需要注意代码里的Message类中我用到了toolCallId字段但前面实体类中并没有定义我建议你在实体类中补充private String toolCallId;这是工具调用匹配的重要字段模型会根据它区分不同工具的回传结果。5.6 编写 Controller 测试入口暴露一个 REST 接口方便我们用浏览器或 Postman 验证package com.example.agent.controller; import com.example.agent.service.AgentService; import org.springframework.web.bind.annotation.*; import java.util.Map; RestController RequestMapping(/agent) public class AgentController { private final AgentService agentService; public AgentController(AgentService agentService) { this.agentService agentService; } PostMapping(/chat) public MapString, String chat(RequestBody MapString, String body) throws Exception { String message body.get(message); String reply agentService.chat(message); return Map.of(reply, reply); } }到这里一个最小可运行的 Java Agent 服务就完成了。5.7 运行与验证在命令行启动项目mvn spring-boot:run然后使用 curl 发起会话curl -X POST http://localhost:8080/agent/chat \ -H Content-Type: application/json \ -d {message: 帮我查一下北京的天气}如果一切正常模型最终会返回类似下面的响应{ reply: 北京当前天气晴气温 26℃北风 2 级适合户外活动。 }这个案例完整演示了 Java Agent 的核心流程接收用户输入 - 模型规划 - 工具执行 - 模型总结回复。这也是目前绝大多数“大模型 业务系统”落地的基本模式。6. 从“能跑”到“生产级”Java Agent 的工程化关键点日常开发中只要接口通了很多人就觉得完事了。但真实的 Agent 项目往往在以下工程化问题上翻车需要大家格外注意。6.1 工具调用的参数校验大模型有概率“凭空捏造”参数。例如用户问“北京天气”模型可能给出{city: 北京市}也可能给出{city: 北京}。如果业务参数需要精确匹配工具内部必须做兜底映射。我的建议是工具方法入口先对参数做非空校验不满足条件时返回友好错误提示而不是直接抛异常。对枚举值使用标准化映射比如北京/北京市/beijing统一归一化。不允许模型传入的任意参数直接拼接进 SQL 或命令避免注入风险。6.2 上下文窗口与 Token 管理大模型有上下文长度限制Java 项目必须自己做 Token 和消息条数控制。常用手段是滑动窗口保留 system 消息只保留最近的 N 轮对话更早的历史可以存到 Redis 或数据库中按需召回。对 Java 工程来说这其实就是一个内存淘汰策略的变体。可以使用LinkedHashMap实现最简单的 LRU 缓存也可以引入 Caffeine 等缓存框架。6.3 可观测性日志、链路追踪、审计Agent 系统比普通接口更难以排查问题因为一次用户请求可能涉及多次模型调用和多次工具调用。生产环境建议为每次会话生成sessionId为每次请求生成requestId。将完整的 Prompt 消息列表、模型响应、工具执行结果都打到日志中方便回放。如果你引入了 Spring Cloud 或 SkyWalking可以把模型调用和工具调用都埋入链路追踪。6.4 API Key 安全与权限控制模型 API 的调用成本不可忽视。生产环境必须做API Key 存放在环境变量、Vault 或配置中心禁止提交到 Git。接口层面做鉴权防止别人通过你的接口肆无忌惮调用模型。对每个用户的请求量做配额限制防止程序异常导致费用飙升。对工具能力做白名单特别是涉及删除、修改、支付等高风险操作时必须经过二次确认。7. Java Agent 开发学习中常见的坑与排查思路结合很多新手同学的实践反馈这里把 Java Agent 开发过程中最常见的几个问题和排查思路整理出来。问题现象常见原因解决思路接口返回 401 或 403API Key 错误、鉴权 Header 格式不正确检查Authorization: Bearer key是否拼写正确确认环境变量是否加载成功模型一直不调用工具直接返回文本Prompt 中没有明确工具列表、模型参数遗漏tools检查请求体中是否包含tools字段在 Prompt 中明确“当用户询问天气时必须调用工具”工具被调用后模型回复“无法获取结果”工具返回的数据结构模型无法理解工具返回值尽量使用纯 JSON 文本在 Prompt 中说明工具返回内容的含义agent execution terminated due to errorAgent 执行链路中某一步抛出了未捕获异常查看 Spring Boot 控制台完整堆栈尤其关注工具方法内的 HTTP 调用是否超时多轮对话丢失上下文消息列表没有保留历史消息检查是否每次请求都新建了一个空的 messages 列表需要把历史消息从存储中取出并拼接Token 消耗过高费用暴涨没有对消息历史做窗口截断增加滑动窗口策略超长历史通过摘要代替原文并发高时模型调用超时HttpClient 连接池配置不合理为 HttpClient 配置合理的连接池大小、超时时间和异步调用方式工具方法中发生NullPointerException模型未按 schema 传入 required 参数工具入口统一对 Map 参数取值做防御式编程避免直接使用arguments.get(xxx)强转排查时建议遵循以下顺序先看日志确认模型 API 的完整请求与响应体是否正常。确认工具方法是否被正确路由到可以临时打印函数名。确认工具返回内容是否已经被编码或截断。确认最终回答是否由模型的第二次调用生成。8. Java 工程师 Agent 学习路线图给自己定一个 12 周计划如果你已经决定转型这里给你一条可以落地的路径可以结合 B 站那套“Java 转 Agent 开发全套教程”的章节顺序来学。第一阶段AI 基础与大模型原理解读第 1 - 2 周不必研究神经网络数学原理但要理解大模型的生成逻辑、Token 概念、上下文窗口限制。熟练使用 Prompt理解 system / user / assistant / tool 不同角色的含义。了解主流大模型 API 的请求与响应结构。用 Java 写一个最简单的 HTTP Client 调用大模型 API 并输出回复。第二阶段Function Calling 与工具调用第 3 - 4 周学习 OpenAI 兼容接口的 Function Calling 完整流程。手动实现一次“模型决定调用哪个函数”的完整链路。定义 3 个以上不同的工具熟练处理参数解析和结果回传。学习如何用 JSON Schema 描述复杂参数结构嵌套对象、数组、枚举。第三阶段记忆与多轮对话第 5 - 6 周在 Java 项目中使用 Redis 或数据库保存会话历史。实现基于 Token 数的会话窗口截断逻辑。学习向量化存储的基本概念为后续引入 RAG检索增强生成做铺垫。完成一个带多轮上下文记忆的“运维问答助手”。第四阶段Agent 框架与工程化第 7 - 9 周学习 Spring AI 或 LangChain4j理解框架是如何封装底层调用的。学习 Agent 的状态机设计掌握 ReAct 模式思考-行动-观察循环。在项目中接入配置中心、日志链路追踪和监控面板。研究多 Agent 协作模式理解“规划 Agent 执行 Agent”的拆解思路。第五阶段项目实战与面试准备第 10 - 12 周独立完成一个完整的 Agent 应用例如“智能周报助手”或“企业知识库问答 Agent”。整理技术方案文档包括系统架构图、工具调用流程、异常处理方案。针对面试中常见的“为什么用 Java 做 Agent”“Agent 与 RAG 的区别”“如何设计工具调用上下文”准备体系化回答。9. 最后想说的话很多 Java 工程师对转型 AI 有一个误解觉得必须转 Python。这种想法会让自己错过很多机会。Agent 开发是目前 AI 领域离工程师最近、工程化程度最高、也是 Java 工程师最有机会切入的方向之一。模型层的算法创新或许离我们远但应用层的 Agent 编排、工具调用、业务集成正是后端工程师的强项。文章里这个天气查询案例虽然简单但它映射了所有 Agent 工程的核心骨架。哪怕以后你接触 LangChain4j 或 Spring AI底层依然是这套逻辑模型负责理解与推理业务代码负责执行与反馈。希望这篇文章能帮你打破“Java 做不了 Agent”的刻板印象。接下来建议你不要只看教程而是打开 IDEA自己动手写一个“调用工具”的 Demo把文中的代码敲一遍或者改造一遍。遇到问题不要怕排错本身才是涨经验最快的过程。如果你觉得这篇文章对你有帮助欢迎收藏备用。后续我也会继续更新 Agent 开发中的实战项目、多 Agent 编排和生产落地方案大家可以持续关注。