ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Java工程师转型AI Agent实战:从增删改查到智能体开发

Java工程师转型AI Agent实战:从增删改查到智能体开发 1. 从增删改查到智能体一个Java老兵为什么要折腾AI Agent我写Java写了八年前六年基本都在跟Spring Boot、MyBatis、MySQL打交道每天的工作节奏就是接需求、写Controller、调Service、改Mapper偶尔优化一下慢SQL日子过得不算轻松但也算安稳。真正让我开始焦虑的是2024年下半年开始团队里陆续出现了一些“不写业务代码”的岗位需求——不是运维不是测试而是要求“能搭建AI Agent、能调通大模型接口、能把现有业务系统改造成Agent可调用的工具”。我当时的第一反应是这玩意儿跟我有什么关系我一个写Java的难道要去学Python后来我花了大半年时间从零开始把AI Agent这套东西啃下来中间踩了无数坑也走了不少弯路。现在回头看Java转AI Agent这件事核心不是“换语言”而是“换思维”。你不需要把Java扔掉恰恰相反Java的工程化能力在Agent落地阶段反而是优势。但你必须补上几块关键拼图否则你写出来的东西永远停留在“调个API返回一段文本”的水平根本算不上Agent。这篇文章就是把我这大半年的转型过程完整复盘一遍。我会讲清楚Java开发者转AI Agent到底要补什么、为什么补这些、怎么补最省时间以及我在实际项目中踩过的坑和总结出来的经验。如果你也是一个写了几年Java、正在观望AI Agent方向的工程师或者你已经动手试过但觉得“好像能跑但不知道对不对”那这篇内容应该能帮你少走至少三个月的弯路。先说结论Java转AI Agent要补的不是Python语法而是四样东西——大模型交互的基本认知、Agent的核心架构理解、工具调用与函数编排能力、以及工程化落地的思维转换。下面我逐块拆开讲。2. 认知重构Java工程师理解AI Agent的正确姿势2.1 别把Agent当成一个“更聪明的接口”我刚接触AI Agent的时候最大的认知误区就是把它当成一个“输入问题、输出答案”的接口。因为Java开发者习惯了RESTful API的思维模式我发一个请求你返回一个JSON我解析完事。但Agent完全不是这个逻辑。Agent的本质是一个循环决策系统。它拿到一个目标后会自己决定下一步做什么、调用什么工具、拿到结果后判断是否继续、直到任务完成或达到终止条件。这跟Java里的状态机有点像但区别在于状态机的状态转移是你写死的而Agent的下一步动作是大模型动态决定的。举个例子。你让一个Agent“帮我查一下上个月销售额最高的三个产品然后给对应的负责人发一封汇总邮件”。传统Java写法是查数据库、排序、取Top3、查负责人信息、调邮件服务、发送。每一步都是你写死的。但Agent的做法是它先理解这个任务需要拆成几步然后自己决定先调数据库查询工具、再调邮件发送工具中间如果发现负责人信息缺失它还会自己去调通讯录接口补全。这个“自己决定”的能力就是Agent和普通接口的本质区别。理解这一点非常重要因为它决定了你后面学的东西的方向。如果你还用“接口思维”去写Agent你会把所有逻辑都写死在代码里那Agent就退化成了一个普通的业务接口大模型的存在感几乎为零。2.2 Java在Agent时代的真实定位很多Java工程师焦虑的点是“AI都是Python写的Java是不是没戏了”。我实际做下来发现这个担心是多余的但也不能盲目乐观。Python在AI Agent领域的优势确实明显LangChain、LlamaIndex、AutoGen这些主流框架都是Python优先模型训练和微调生态也基本围绕Python。但Agent落地到企业级应用时问题就来了你的业务系统是Java写的你的数据库连接池是Java管的你的权限体系是Java实现的你的消息队列是Java在消费的。你不可能为了跑一个Agent就把整个后端重写一遍。所以Java在Agent时代的真实定位是Agent的执行层和集成层。大模型负责决策Java负责执行。Agent决定“要查数据库”Java的Service去查Agent决定“要发消息”Java的消息服务去发Agent决定“要调第三方接口”Java的HTTP客户端去调。这个分工是合理的也是企业最容易接受的。我现在的做法是用Python做Agent的编排和实验验证跑通后把核心的工具调用层用Java实现通过HTTP或消息队列对接。这样既享受了Python生态的便利又保留了Java的工程稳定性。2.3 必须搞清楚的几个核心概念在动手之前有几个概念你必须搞清楚否则看文档都看不懂。Token这是大模型处理文本的基本单位。你可以粗略理解为“一个汉字约等于1到2个Token一个英文单词约等于1到1.5个Token”。为什么重要因为模型的上下文窗口是有限的比如8K、32K、128K Token。你传给模型的提示词、历史对话、工具返回结果全都占Token。超了就要截断或压缩这是Agent开发中最常见的坑之一。Function Calling这是大模型调用外部工具的标准机制。你告诉模型“我有这几个函数可用”模型根据用户问题决定调哪个、传什么参数然后你的代码去执行这个函数把结果再喂回模型。这是Agent能“做事”的关键。ReAct模式Reasoning Acting的缩写。核心思路是让模型先“想一步”Reasoning再“做一步”Acting循环往复。这是目前最主流的Agent架构模式之一。RAG检索增强生成。简单说就是先从知识库里检索相关内容再让模型基于检索结果回答。解决的是模型“不知道你公司内部文档”的问题。这几个概念你不需要一开始就理解得很深但至少要知道它们大概是什么、解决什么问题。我当初就是跳过了这一步直接看代码结果看了三天都没搞明白为什么要有“工具描述”这个东西。3. 技术补全Java工程师需要补齐的四块拼图3.1 第一块拼图大模型交互基础Java调大模型接口技术上没有任何难度。用HttpClient或者OkHttp发个POST请求body里带上model、messages、temperature这些参数就能拿到返回。但“能调通”和“调得好”是两回事。我一开始犯的错误是把大模型当成一个“问答机器人”用户问什么我就直接转发什么。结果就是模型经常答非所问或者输出格式乱七八糟。后来我才明白提示词的质量直接决定输出质量。你需要学会写System Prompt告诉模型它的角色是什么、要遵守什么规则、输出格式是什么。比如我在做一个客服Agent时System Prompt是这样写的String systemPrompt 你是一个电商客服助手。你的职责是回答用户关于订单、退换货、物流的问题。 规则 1. 如果用户问题涉及具体订单必须先调用queryOrder工具查询订单状态。 2. 如果用户要求退换货必须先确认订单是否在退换货期限内。 3. 如果用户问题超出你的知识范围回复“这个问题我需要转接人工客服”。 4. 所有回复必须简洁不超过三句话。 ;这个Prompt里包含了角色定义、工具调用规则、边界处理和输出约束。写和不写效果差距巨大。另外temperature参数也值得说一下。这个参数控制输出的随机性范围一般是0到2。做Agent的时候我一般设0.1到0.3因为Agent需要稳定、可预测的输出不需要“创意”。做文案生成时可以设0.7到1.0。这个参数的选择逻辑是任务越需要确定性temperature越低。3.2 第二块拼图Agent核心架构理解目前主流的Agent架构可以归纳为三种模式我用Java开发者熟悉的方式类比一下。第一种单Agent 工具调用。这就像一个有多个Service的Controller。Agent本身是一个大模型它手里有几个工具函数根据用户请求决定调哪个工具。适合场景明确的简单任务比如“查天气”“查订单”“发邮件”。第二种多Agent协作。这就像微服务架构。每个Agent负责一个领域有一个协调者Agent负责分发任务和汇总结果。比如一个“电商运营Agent”下面有“数据分析Agent”“文案生成Agent”“客服Agent”协调者根据任务类型分发给对应的子Agent。适合复杂业务流程。第三种Plan-and-Execute模式。这就像工作流引擎。Agent先制定一个完整的执行计划然后逐步执行每执行一步可以调整后续计划。适合步骤多、依赖关系复杂的任务。我实际项目中用得最多的是第一种和第二种的结合一个主Agent负责理解用户意图和分发任务子Agent各自负责具体执行。这种架构的好处是职责清晰每个Agent的Prompt可以写得很聚焦输出稳定性高。3.3 第三块拼图工具调用与函数编排这是Java工程师最有优势的地方也是最容易踩坑的地方。工具调用的本质是你把Java方法暴露给大模型大模型决定什么时候调、传什么参数。但大模型不认识你的Java代码它只认识你给它的“工具描述”。所以你需要为每个工具写一段描述告诉模型这个工具是干什么的、参数是什么、什么时候用。我踩过的坑是工具描述写得太简略导致模型不知道该用哪个工具。比如我有两个工具一个叫queryOrder一个叫queryLogistics描述分别写“查询订单”和“查询物流”。结果用户问“我的包裹到哪了”模型有时候调queryOrder有时候调queryLogistics因为它分不清这两个的区别。后来我把描述改成了queryOrder根据订单号查询订单的详细信息包括商品、金额、下单时间、支付状态。不包含物流信息。queryLogistics根据订单号查询物流轨迹包括当前所在城市、预计送达时间、配送员联系方式。仅在有物流单号时可用。改完之后模型的选择准确率明显提升。这个经验告诉我工具描述要写到“一个新人看了也知道什么时候用”的程度。另一个坑是参数校验。大模型生成的参数不一定符合你的预期可能类型不对、可能缺字段、可能传了不存在的值。所以每个工具方法内部必须做严格的参数校验不能直接信任模型传来的参数。我的做法是在工具方法入口加一层校验参数不合法就返回明确的错误信息让模型知道“你传的参数有问题请重新生成”。3.4 第四块拼图工程化落地思维Java工程师做Agent最大的优势就是工程化思维但这也可能变成劣势因为Agent系统的不确定性和传统Java系统的确定性是冲突的。传统Java系统里你调一个方法要么成功要么抛异常结果是确定的。但Agent系统里同样的输入模型可能给出不同的输出调用的工具可能不同执行路径可能不同。这种不确定性对测试、监控、日志都提出了新的要求。我的做法是在Agent的每个关键节点加日志和埋点。模型输入是什么、输出是什么、调了哪个工具、参数是什么、返回结果是什么、耗时多少全部记录下来。这样出问题的时候可以回溯也方便后续做效果分析。另外超时和重试机制必须做好。大模型接口的响应时间波动很大有时候2秒有时候20秒。工具调用也可能超时。我的做法是给每个环节设置独立的超时时间模型调用设30秒工具调用设10秒超时后走降级逻辑。还有一个容易被忽略的点是成本控制。大模型调用是按Token计费的Agent的循环调用很容易把Token消耗拉高。我一开始没注意一个测试用例跑了十几轮循环消耗了几十万Token。后来我加了两个限制最大循环次数设5次单次会话总Token数超过阈值就强制终止。这两个限制能有效防止“Agent陷入死循环烧钱”的问题。4. 实战落地从零搭建一个Java版Agent的完整过程4.1 环境准备与技术选型我的技术栈是这样的Java 17 Spring Boot 3.x OkHttp Jackson 一个大模型API。没有用任何Agent框架因为我想先理解底层原理而且Java生态里成熟的Agent框架确实不多。如果你刚开始我建议你也先不要上框架。框架帮你封装了很多东西但也屏蔽了很多细节。等你把底层跑通了再考虑用框架提效。依赖方面核心就几个dependency groupIdcom.squareup.okhttp3/groupId artifactIdokhttp/artifactId version4.12.0/version /dependency dependency groupIdcom.fasterxml.jackson.core/groupId artifactIdjackson-databind/artifactId version2.17.0/version /dependencyOkHttp负责发HTTP请求Jackson负责JSON序列化和反序列化。这两个就够了。4.2 核心代码结构设计我的Agent项目结构大概是这样的agent-core/ ├── model/ # 请求和响应的数据模型 │ ├── ChatMessage.java │ ├── ChatRequest.java │ └── ChatResponse.java ├── llm/ # 大模型客户端 │ └── LlmClient.java ├── tool/ # 工具定义和注册 │ ├── ToolDefinition.java │ ├── ToolRegistry.java │ └── impl/ │ ├── OrderQueryTool.java │ └── EmailSendTool.java ├── agent/ # Agent核心逻辑 │ └── ReActAgent.java └── controller/ # 对外接口 └── AgentController.java这个结构的好处是职责清晰model管数据、llm管通信、tool管能力、agent管编排、controller管入口。跟Java的MVC分层是一个思路。4.3 大模型调用层的实现先看最基础的模型调用。核心方法是发一个POST请求把消息列表传过去拿到返回。public class LlmClient { private final OkHttpClient client new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) .build(); private final ObjectMapper mapper new ObjectMapper(); private final String apiKey; private final String apiUrl; public LlmClient(String apiKey, String apiUrl) { this.apiKey apiKey; this.apiUrl apiUrl; } public ChatResponse chat(ListChatMessage messages, ListToolDefinition tools) { MapString, Object body new HashMap(); body.put(model, your-model-name); body.put(messages, messages); body.put(temperature, 0.2); if (tools ! null !tools.isEmpty()) { body.put(tools, tools); } String json mapper.writeValueAsString(body); Request request new Request.Builder() .url(apiUrl) .header(Authorization, Bearer apiKey) .header(Content-Type, application/json) .post(RequestBody.create(json, MediaType.parse(application/json))) .build(); try (Response response client.newCall(request).execute()) { String responseBody response.body().string(); return mapper.readValue(responseBody, ChatResponse.class); } } }这段代码里几个关键点超时时间要设合理readTimeout我设了30秒因为模型生成有时候确实慢temperature设0.2保证输出稳定tools参数是可选的不传就是普通对话传了就是Agent模式。4.4 工具注册与调用机制工具的定义需要包含名称、描述、参数Schema。参数Schema用JSON Schema格式描述告诉模型每个参数的类型和含义。public class ToolDefinition { private String name; private String description; private MapString, Object parameters; // JSON Schema格式 // getter/setter省略 }工具注册中心负责管理所有可用工具并提供执行入口public class ToolRegistry { private final MapString, ToolExecutor executors new HashMap(); public void register(String name, ToolExecutor executor) { executors.put(name, executor); } public String execute(String name, MapString, Object args) { ToolExecutor executor executors.get(name); if (executor null) { return 错误未找到工具 name; } try { return executor.execute(args); } catch (Exception e) { return 错误工具执行失败 - e.getMessage(); } } public ListToolDefinition getDefinitions() { // 返回所有工具的定义列表 } }这里有个重要设计工具执行失败时不要抛异常而是返回错误信息字符串。因为错误信息会喂回给模型模型看到错误后可以决定重试或者换一种方式。如果你直接抛异常整个Agent循环就断了。4.5 ReAct循环的完整实现ReAct循环是Agent的核心。逻辑是把用户问题发给模型模型返回要么是最终答案要么是工具调用请求。如果是工具调用执行工具把结果追加到消息列表再次发给模型如此循环。public class ReActAgent { private final LlmClient llmClient; private final ToolRegistry toolRegistry; private static final int MAX_ITERATIONS 5; public String run(String userInput) { ListChatMessage messages new ArrayList(); messages.add(ChatMessage.system(buildSystemPrompt())); messages.add(ChatMessage.user(userInput)); for (int i 0; i MAX_ITERATIONS; i) { ChatResponse response llmClient.chat(messages, toolRegistry.getDefinitions()); ChatMessage assistantMsg response.getFirstChoice().getMessage(); messages.add(assistantMsg); if (assistantMsg.hasToolCalls()) { for (ToolCall call : assistantMsg.getToolCalls()) { String result toolRegistry.execute( call.getName(), call.getArguments() ); messages.add(ChatMessage.tool(call.getId(), result)); } } else { return assistantMsg.getContent(); } } return 任务执行超过最大轮次已终止。; } }这段代码虽然短但包含了Agent的所有核心要素系统提示词、消息历史管理、工具调用判断、工具执行、结果回传、循环控制、终止条件。你把这几十行代码理解透了Agent的原理就通了。4.6 一个完整案例订单查询Agent我拿一个实际场景来串一遍。用户说“帮我查一下订单ORD-20240101的状态如果还没发货就取消掉”。第一轮模型收到用户消息和工具列表判断需要先查订单。返回工具调用queryOrder参数{orderId: ORD-20240101}。执行工具查数据库返回{status: 待发货, amount: 299, createTime: 2024-01-01}。第二轮模型看到订单状态是“待发货”判断需要调用取消订单工具。返回工具调用cancelOrder参数{orderId: ORD-20240101}。执行工具调用取消逻辑返回{success: true, message: 订单已取消}。第三轮模型看到取消成功生成最终回复“订单ORD-20240101当前状态为待发货已为您成功取消。”整个过程三轮循环两次工具调用。这就是一个完整的Agent执行链路。5. 踩坑实录那些文档里不会告诉你的问题5.1 模型不按格式返回怎么办这是最常见的问题。你明明在Prompt里写了“请以JSON格式返回”但模型有时候就是返回一段带解释的文字。我的解决方案是三层防护第一层Prompt里明确格式要求并给出示例。第二层代码里做解析容错尝试从返回文本中提取JSON部分。第三层如果解析失败把错误信息喂回模型让它重新生成。实测下来加了示例之后格式错误率从大概30%降到了5%以下。剩下5%靠代码容错和重试兜底。5.2 工具调用参数类型不匹配模型生成的参数类型经常和你的预期不一致。比如你期望一个整数模型传了字符串123你期望一个数组模型传了逗号分隔的字符串。我的做法是在工具执行前加一层类型转换和校验能转就转不能转就返回错误让模型重试。5.3 循环次数失控Agent有时候会陷入“调工具、失败、重试、再失败”的死循环。我遇到过最夸张的一次一个工具因为网络问题一直超时Agent重试了十几次烧了不少Token。后来我加了两个硬限制最大循环次数5次单个工具连续失败3次就强制终止并返回错误。5.4 上下文Token超限多轮对话加上工具返回结果消息列表会越来越长。超过模型上下文窗口后要么报错要么模型开始“遗忘”前面的内容。我的处理策略是保留System Prompt和最近N轮对话中间的工具调用结果做摘要压缩。具体保留多少轮取决于你的上下文窗口大小和单轮平均Token数。5.5 并发场景下的状态管理Agent是有状态的消息列表在循环中不断增长。如果你的Agent服务是多线程的每个请求必须有自己的消息列表实例不能共享。我一开始犯过这个错误把消息列表定义成了类的成员变量结果两个用户同时请求时消息串了。后来改成每次请求创建新的列表问题解决。6. 学习路线与资源推荐6.1 分阶段学习路径如果你现在完全零基础我建议按这个顺序来第一阶段1到2周理解大模型基本概念能调通API能写有效的Prompt。这个阶段不需要写复杂代码用Postman或者简单的Java main方法就行。第二阶段2到3周理解Function Calling机制能定义工具、注册工具、处理工具调用。这个阶段把ReAct循环跑通。第三阶段3到4周做一个完整的Agent项目包含至少3个工具、错误处理、日志记录、超时控制。这个阶段的目标是“能演示”。第四阶段持续优化Prompt、优化工具描述、加监控、加成本控制、处理边界情况。这个阶段的目标是“能上线”。6.2 Java工程师的差异化优势不要试图把自己变成Python工程师那是舍本逐末。你的优势在于工程化能力、系统设计能力、稳定性保障能力。Agent的Demo谁都能跑但能把Agent稳定运行在生产环境、能处理各种异常、能控制成本、能做监控告警的还是需要Java工程师的功底。我现在的定位很清晰我不做模型训练不做算法调优我做的是Agent的工程化落地。把大模型的能力通过Java服务稳定地输出给业务系统这就是我的价值。6.3 常见面试题准备如果你在准备转型面试这几个问题大概率会被问到Agent和普通大模型调用的区别是什么Function Calling的工作原理是什么如何防止Agent陷入死循环多轮对话中如何管理上下文如何评估一个Agent的效果这些问题的答案你在前面的内容里都能找到。关键是要结合自己的实际项目经验来回答不要背概念。7. 我个人的一些实操心得最后分享几个我在实际项目中总结出来的小技巧都是文档里不会写的。工具数量控制在7个以内。工具太多模型选择准确率会下降。如果确实需要很多工具先做一层分类让主Agent先选类别再选具体工具。System Prompt里加“思考步骤”引导。比如写“在调用工具前先用一句话说明你为什么选择这个工具”。这样模型的决策过程会体现在输出里方便你调试。工具返回结果要精简。不要把整个数据库查询结果都返回给模型只返回模型需要的关键字段。返回结果越长Token消耗越大模型理解成本也越高。做好降级方案。模型服务不可用时Agent应该能降级到“抱歉当前服务繁忙”的兜底回复而不是直接报错。定期回顾日志。我每周会抽时间看一遍Agent的执行日志分析哪些工具调用频繁、哪些Prompt效果不好、哪些场景模型容易出错。这个习惯帮我发现了很多优化点。转型这件事说难也难说简单也简单。难的是迈出第一步简单的是只要你动手做了后面的路会越来越清晰。我用了大半年时间从零走到能独立交付Agent项目你如果每天能投入两小时三个月应该能到差不多的水平。关键不是学多少理论而是动手把第一个Agent跑起来。跑起来之后一切都会变得具体。
返回列表