Java后端转型AI Agent实战:LLM认知、Prompt工程与工具调用

发布时间:2026/10/8 4:53:24
Java后端转型AI Agent实战:LLM认知、Prompt工程与工具调用
1. 从增删改查到提示词工程一个Java老兵的真实转型起点八年Java后端日常打交道的东西很固定Spring Boot的Controller、Service、Mapper三层结构MyBatis的XML映射文件MySQL的索引优化Redis的缓存穿透处理偶尔还要跟消息队列和分布式事务较劲。这套东西我闭着眼睛都能写面试的时候也能把JVM内存模型、GC算法、并发编程讲得头头是道。但去年开始身边越来越多的项目开始提AI Agent这个词客户的需求也从帮我做个后台管理系统变成了能不能让系统自动处理这些工单。说实话一开始我是有点抗拒的。总觉得AI这东西是算法工程师的活跟我一个写业务代码的后端有什么关系直到我真正动手去搭第一个Agent才发现事情没那么简单也没那么难。简单的地方在于Agent的核心逻辑其实就是一个循环调用工具编排的流程这玩意儿跟写一个状态机或者工作流引擎没有本质区别。难的地方在于你需要重新理解模型能做什么、不能做什么以及怎么用工程手段去弥补模型的不确定性。这篇复盘不打算给你画什么宏大的学习路线图那种东西网上太多了。我想做的是把我在转型过程中真正踩过的坑、真正补上的知识缺口、真正觉得早知道就好了的东西一条一条拆开来讲。如果你也是一个写了几年Java、想往AI Agent方向靠的后端这篇东西应该能帮你省下不少瞎折腾的时间。先给一个最直观的结论Java转AI Agent你不需要从头学一遍机器学习也不需要去啃Transformer的论文。你需要补的是三块东西——对LLM能力边界的认知、Prompt工程化的思维、以及用Java生态去编排Agent的能力。这三块里面第一块靠动手试第二块靠刻意练习第三块反而是你最擅长的。2. 认知重构LLM不是数据库Agent不是微服务2.1 为什么用写业务代码的思维去理解LLM会翻车我刚接触OpenAI API的时候脑子里第一反应是这不就是一个远程接口吗我传个参数进去它返回一个结果跟调用一个RESTful接口有什么区别于是我按照写Service层的习惯把Prompt当成SQL语句来写把temperature当成一个普通的配置参数把返回结果直接当成确定性的数据来处理。结果就是各种翻车。同样的输入模型有时候返回JSON有时候返回一段解释文字有时候干脆给你加一句好的我来帮你处理。我一开始还想着用正则去解析后来发现根本解析不过来。这就是典型的用确定性系统的思维去理解概率性系统。LLM的本质是一个概率性的文本生成器。你给它一段输入Prompt它根据训练时学到的概率分布一个token一个token地往外吐结果。它不是在查询什么而是在续写什么。这个认知转变非常关键因为它决定了你后面所有的工程决策。举个例子你让模型返回一个JSON格式的用户信息它大概率会返回JSON但不是100%。它可能会在JSON外面包一层markdown代码块可能会在字段名上加引号可能会多返回一个你不想要的字段。你不能假设它一定按你说的做你只能通过Prompt设计、输出格式约束、后处理校验等手段把它的输出收敛到一个可接受的范围内。2.2 Agent和微服务的本质区别在哪里微服务的核心是确定性编排A服务调用B服务B服务调用C服务每个环节的输入输出都是明确的、可预期的。你可以画一张精确的调用链路图可以给每个接口写单元测试可以保证同样的请求返回同样的结果。Agent的核心是不确定性决策。你给Agent一个目标比如帮我查一下这个订单的物流状态并回复用户它需要自己决定先调用哪个工具查不到怎么办要不要追问用户回复的语气应该怎样这些决策不是你在代码里写死的而是模型根据上下文动态生成的。这意味着你不能像写微服务那样去写Agent。你不能假设Agent一定会按你预设的路径走你只能给它提供足够的工具、清晰的指令、以及合理的约束然后接受它有时候会自由发挥。但这不意味着Agent就是不可控的。恰恰相反Agent的工程化程度决定了它的可靠性。一个设计良好的Agent系统应该有明确的工具边界、严格的输入输出校验、完善的错误处理机制、以及可观测的执行日志。这些东西恰恰是Java后端最擅长的。2.3 一个让我彻底转变思路的失败案例我做的第一个Agent是一个自动分类工单的小工具。逻辑很简单用户提交工单Agent读取工单内容判断它属于技术问题、账单问题还是其他然后打上对应的标签。我当时的实现方式是写一个Prompt把工单内容拼进去调用OpenAI API拿到返回结果直接存库。测试的时候一切正常准确率也挺高。但上线之后问题就来了有些工单内容特别长超过了模型的上下文窗口API直接报错有些工单内容包含特殊字符模型返回的结果里带了乱七八糟的东西还有些工单是英文的模型有时候返回中文标签有时候返回英文标签导致我的分类统计全乱了。这个案例让我明白了一件事Agent的可靠性不取决于模型有多强而取决于你的工程兜底有多完善。上下文超限要截断或分段输出格式要强制约束异常情况要有降级方案。这些东西跟写一个健壮的后端服务没有本质区别。3. Java工程师的独特优势别把自己当新手3.1 你已有的工程能力比你想的值钱很多Java工程师转AI Agent的时候会陷入一种我是新手的心态觉得自己什么都不懂要从头学起。但实际上你已有的工程能力在这个领域非常值钱。第一你对接口设计的理解。Agent的核心是工具调用而工具调用的本质就是接口设计。你需要定义每个工具的名称、描述、参数、返回值这跟设计一个RESTful API没有本质区别。一个好的工具描述应该像一个好的API文档一样让调用者在这里是模型能够准确理解这个工具是干什么的、什么时候该用、参数怎么传。第二你对异常处理的理解。Agent执行过程中会遇到各种异常模型返回格式错误、工具调用失败、超时、限流。这些异常的处理方式跟你在微服务里处理异常的思路是一样的重试、降级、熔断、日志记录。第三你对状态管理的理解。一个多轮对话的Agent需要维护对话状态这跟你在Web应用里维护Session是一个道理。你需要考虑状态存哪里、怎么过期、怎么并发安全。第四你对可观测性的理解。Agent的执行过程是一个黑盒你需要通过日志、指标、链路追踪来观察它的行为。这跟你在微服务里做监控是一模一样的思路。3.2 Spring Boot生态在Agent编排中的实际价值我现在的Agent项目主体框架还是Spring Boot。为什么不用Python因为我的业务系统是Java写的Agent需要跟业务系统深度集成用Java可以少一层跨语言调用的开销和复杂度。Spring Boot在Agent编排中的价值主要体现在几个方面依赖注入和Bean管理。每个工具Tool都可以是一个Spring Bean通过依赖注入获取需要的Service。这样工具的实现可以复用现有的业务逻辑不需要重新写一遍。配置管理。API Key、模型名称、超时时间、重试次数这些配置直接用Spring的ConfigurationProperties管理跟管理数据库连接池的配置没有区别。异步和并发。Agent的很多操作是IO密集型的调用模型API、调用外部工具用Spring的Async或者WebClient可以很方便地做异步处理。监控和健康检查。Spring Boot Actuator可以直接用来暴露Agent的运行指标比如调用次数、成功率、平均耗时。下面是一个简化的工具定义示例用Java注解的方式描述一个工具Component public class OrderQueryTool implements AgentTool { Autowired private OrderService orderService; Override public String getName() { return query_order_status; } Override public String getDescription() { return 根据订单号查询订单的当前状态和物流信息。 当用户询问订单进度、物流状态时使用此工具。 参数orderId必须是用户提供的订单号不要自己编造。; } Override public String execute(MapString, Object params) { String orderId (String) params.get(orderId); if (orderId null || orderId.isBlank()) { return 错误缺少订单号参数; } try { Order order orderService.getByOrderId(orderId); if (order null) { return 未找到订单号为 orderId 的订单; } return String.format(订单状态%s物流信息%s, order.getStatus(), order.getLogisticsInfo()); } catch (Exception e) { return 查询订单时发生错误 e.getMessage(); } } }这个例子里有几个细节值得注意工具的描述写得非常具体明确告诉模型什么时候用、参数从哪来、不要编造参数。执行方法里做了参数校验和异常捕获保证工具本身不会抛异常给Agent。返回结果用自然语言描述方便模型理解。3.3 哪些Java技能需要暂时放一放转型过程中有些你引以为傲的Java技能在Agent开发里可能暂时用不上或者说优先级没那么高。复杂的SQL优化。Agent的数据存储通常比较简单对话历史、工具调用记录这些用个MySQL或者Redis就够了不需要你去做分库分表。高并发架构设计。大部分Agent应用的并发量远没有达到需要你设计复杂架构的程度。当然如果你要做企业级的Agent平台那另说。JVM调优。Agent的性能瓶颈通常在模型API的响应时间上不在JVM上。你花在GC调优上的时间不如花在Prompt优化上。设计模式的过度使用。Agent的代码结构应该尽量简单直接过度抽象反而会让调试变得困难。我见过有人用策略模式工厂模式责任链模式来写一个简单的Agent结果自己都理不清调用链路。4. 必须补上的三块硬骨头Prompt、工具调用、上下文管理4.1 Prompt工程不是玄学是有章可循的工程实践Prompt工程这个词听起来很虚但实际做起来它跟写代码一样是有章可循的。我总结下来一个好的Prompt应该包含以下几个部分角色定义。告诉模型它是谁比如你是一个专业的客服助手、你是一个代码审查专家。角色定义会影响模型的语气和回答风格。任务描述。清晰地说明要做什么越具体越好。不要写帮我处理这个工单要写阅读以下工单内容判断它属于技术问题、账单问题还是其他类别只返回类别名称。约束条件。明确告诉模型什么能做、什么不能做。比如不要编造订单号、如果信息不足返回需要更多信息、只返回JSON格式不要添加任何解释。输出格式。如果需要对输出做程序化处理一定要明确指定格式。最好给一个示例这叫Few-shot prompting。边界情况处理。提前告诉模型遇到异常情况怎么办。比如如果用户的问题与订单无关返回无法处理。下面是一个我实际在用的Prompt模板你是一个电商客服助手负责处理用户的订单相关咨询。 你的任务是根据用户的问题判断是否需要调用工具查询订单信息。 规则 1. 如果用户提供了订单号并询问订单状态调用 query_order_status 工具 2. 如果用户没有提供订单号回复请提供您的订单号 3. 如果用户的问题与订单无关回复抱歉我只能处理订单相关的问题 4. 不要编造任何订单信息所有信息必须来自工具返回结果 用户问题{user_input}这个Prompt里角色、任务、规则、变量占位符都很清晰。实测下来这种结构化的Prompt比那种一大段自然语言的Prompt输出稳定性要高很多。4.2 工具调用的设计原则让模型看得懂、用得对工具调用是Agent的核心能力。模型本身不能查数据库、不能发邮件、不能调API它只能通过你提供的工具来与外部世界交互。所以工具设计的好坏直接决定了Agent的能力上限。我踩过的坑包括工具描述太模糊模型不知道该什么时候用参数定义不清晰模型传错参数工具太多模型选择困难工具返回结果太长撑爆上下文。总结下来工具设计要遵循几个原则单一职责。一个工具只做一件事。不要设计一个万能工具参数一大堆模型根本不知道怎么传。描述具体。工具描述要写清楚这个工具是干什么的、什么时候用、参数是什么含义、返回什么结果。描述要像写给一个新同事看的文档。参数精简。参数越少越好必填参数和可选参数要区分清楚。参数类型要明确是字符串还是数字是枚举还是自由文本。返回可控。工具返回的结果要控制长度太长的结果要截断或摘要。返回格式要统一方便模型理解。错误友好。工具执行失败时返回的错误信息要清晰告诉模型发生了什么、可以怎么处理。下面是一个工具定义的JSON Schema示例这是OpenAI API要求的格式{ type: function, function: { name: query_order_status, description: 根据订单号查询订单状态和物流信息。当用户询问订单进度时使用。, parameters: { type: object, properties: { orderId: { type: string, description: 订单号通常是10-20位的数字字符串 } }, required: [orderId] } } }这个Schema里description字段非常关键。模型就是靠这个描述来决定要不要调用这个工具的。描述写得好模型的工具选择准确率会高很多。4.3 上下文窗口管理Agent的记忆该怎么设计上下文窗口是LLM的一个硬限制。每个模型都有一个最大token数你的Prompt加上对话历史加上工具返回结果不能超过这个限制。超过就会报错或者被截断。我一开始没把这事当回事觉得对话历史能有多长结果实际跑起来一个稍微复杂点的任务几轮工具调用下来上下文就满了。特别是工具返回的结果有时候一个API返回的JSON就有好几千token。上下文管理有几个常用策略滑动窗口。只保留最近N轮对话更早的丢弃。简单粗暴但会丢失早期的重要信息。摘要压缩。把早期的对话用模型总结成一段简短的摘要保留关键信息丢弃细节。关键信息提取。从对话历史中提取出结构化的关键信息比如用户ID、订单号、已确认的需求只保留这些信息丢弃原始对话。分层存储。把对话历史存在外部数据库或Redis需要的时候再检索出来。这其实就是RAG的思路。我现在的做法是组合使用最近的几轮对话保留原文更早的对话做摘要关键实体信息单独提取存储。这样既能控制token数量又不会丢失重要信息。5. 从零搭建一个Java Agent的完整实操路径5.1 环境准备依赖选型和项目结构先说依赖选型。Java生态里做Agent开发主要有几个选择官方OpenAI Java SDK。OpenAI官方提供了Java SDK但更新频率一般功能也不是最全的。适合简单场景。Spring AI。Spring官方推出的AI框架跟Spring Boot集成度最高支持多种模型提供商工具调用、RAG这些都有封装。如果你本来就是Spring Boot项目这是最自然的选择。LangChain4j。受Python版LangChain启发的一个Java库功能比较全社区也活跃。适合需要复杂编排的场景。自己封装HTTP调用。如果你只需要调用OpenAI API自己用WebClient封装一层也不复杂灵活性最高。我个人的建议是如果是新项目直接用Spring AI省事。如果是老项目集成自己封装HTTP调用可能更可控。LangChain4j适合需要复杂链式调用的场景。项目结构上我建议按职责分层src/main/java/com/example/agent/ ├── config/ # 配置类API Key、模型参数等 ├── controller/ # 对外接口 ├── service/ # Agent核心逻辑 │ ├── AgentService.java │ └── PromptBuilder.java ├── tool/ # 工具定义 │ ├── AgentTool.java │ ├── OrderQueryTool.java │ └── ToolRegistry.java ├── memory/ # 上下文管理 │ └── ConversationMemory.java └── model/ # 数据模型 ├── AgentRequest.java └── AgentResponse.java这个结构跟普通的Spring Boot项目没有本质区别只是把Agent相关的逻辑单独抽出来了。5.2 核心循环Agent的思考-行动-观察是怎么跑起来的Agent的核心是一个循环模型思考→决定调用工具→执行工具→把结果返回给模型→模型继续思考。这个循环一直持续到模型认为任务完成或者达到最大轮次限制。用Java实现这个循环大概长这样public AgentResponse run(String userInput, String conversationId) { // 1. 构建初始消息列表 ListMessage messages new ArrayList(); messages.add(systemMessage()); messages.addAll(memory.getHistory(conversationId)); messages.add(userMessage(userInput)); int maxRounds 10; for (int round 0; round maxRounds; round) { // 2. 调用模型 ChatResponse response chatClient.call(messages, toolRegistry.getToolDefinitions()); // 3. 检查是否有工具调用 if (response.hasToolCalls()) { messages.add(assistantMessage(response)); for (ToolCall toolCall : response.getToolCalls()) { // 4. 执行工具 String result toolRegistry.execute(toolCall.getName(), toolCall.getArguments()); messages.add(toolMessage(toolCall.getId(), result)); } // 继续循环把工具结果返回给模型 } else { // 5. 没有工具调用说明模型给出了最终回答 String finalAnswer response.getContent(); memory.add(conversationId, userMessage(userInput)); memory.add(conversationId, assistantMessage(finalAnswer)); return new AgentResponse(finalAnswer); } } return new AgentResponse(抱歉处理超时请稍后重试); }这个循环里有几个关键点最大轮次限制。一定要设一个上限防止模型陷入死循环。我一般设10轮大部分任务3-5轮就能完成。工具执行异常处理。工具执行可能失败失败信息也要返回给模型让模型决定怎么处理。记忆更新。对话结束后把用户输入和最终回答存入记忆。注意只存最终回答不存中间的思考过程否则上下文会膨胀得很快。5.3 工具注册与执行用反射还是用Map工具注册有两种常见方式一种是基于反射扫描所有实现了AgentTool接口的Bean自动注册另一种是手动维护一个Map把工具名和工具实例对应起来。反射的方式更优雅新增工具不需要改注册代码。但调试的时候稍微麻烦一点因为工具是动态发现的。手动Map的方式更直观但新增工具要记得注册。我现在的做法是折中用Spring的ApplicationContext获取所有AgentTool类型的Bean然后构建一个Map。这样既有自动发现的便利又有Map的直观。Component public class ToolRegistry { private final MapString, AgentTool tools new HashMap(); Autowired public ToolRegistry(ListAgentTool toolList) { for (AgentTool tool : toolList) { tools.put(tool.getName(), tool); } } public ListToolDefinition getToolDefinitions() { return tools.values().stream() .map(this::toDefinition) .collect(Collectors.toList()); } public String execute(String name, MapString, Object args) { AgentTool tool tools.get(name); if (tool null) { return 错误未找到工具 name; } try { return tool.execute(args); } catch (Exception e) { return 工具执行失败 e.getMessage(); } } }这个Registry在构造时注入所有AgentTool BeanSpring会自动把实现了AgentTool接口的Bean都传进来。新增工具只需要加一个Component注解不需要改Registry的代码。5.4 记忆模块对话历史存哪里、怎么存对话历史的存储取决于你的应用场景。如果是单机应用存内存里就行。如果是分布式部署需要存Redis或者数据库。我现在的做法是短期记忆存Redis设置一个合理的过期时间比如30分钟。长期记忆存MySQL用于分析和审计。存储的内容也有讲究。不是把所有消息都原样存下来而是存结构化的数据public class ConversationMemory { Autowired private RedisTemplateString, String redisTemplate; private static final int MAX_HISTORY 20; private static final Duration TTL Duration.ofMinutes(30); public void add(String conversationId, Message message) { String key agent:conv: conversationId; redisTemplate.opsForList().rightPush(key, toJson(message)); redisTemplate.opsForList().trim(key, -MAX_HISTORY, -1); redisTemplate.expire(key, TTL); } public ListMessage getHistory(String conversationId) { String key agent:conv: conversationId; ListString jsonList redisTemplate.opsForList().range(key, 0, -1); if (jsonList null) { return Collections.emptyList(); } return jsonList.stream() .map(this::fromJson) .collect(Collectors.toList()); } }这里用Redis的List结构存储消息每次添加新消息后做一次trim只保留最近20条。同时设置过期时间避免无用数据堆积。6. 那些只有踩过才知道的坑6.1 模型返回的JSON为什么总是解析失败这个问题我遇到过无数次。你明明在Prompt里写了只返回JSON但模型就是会给你加一些乱七八糟的东西。常见的情况包括在JSON外面包了markdown代码块、在JSON前后加了说明文字、JSON里的字符串用了单引号、JSON里有注释、JSON不完整被截断了。解决方案是分层的第一层Prompt约束。在Prompt里明确说只返回JSON不要添加任何其他文字不要使用markdown代码块。同时给一个示例让模型知道你要的格式。第二层后处理清洗。拿到返回结果后先做一轮清洗去掉markdown代码块标记、去掉前后空白、找到第一个{和最后一个}之间的内容。第三层容错解析。用Jackson或者Gson解析如果失败尝试修复常见的格式问题比如单引号转双引号、去掉尾逗号。第四层降级处理。如果实在解析不了记录日志返回一个默认值或者错误提示不要让整个流程崩溃。public T T parseJson(String raw, ClassT clazz) { if (raw null || raw.isBlank()) { throw new IllegalArgumentException(模型返回为空); } // 去掉markdown代码块 String cleaned raw.replaceAll(json\\s*, ) .replaceAll(\\s*, ) .trim(); // 提取JSON部分 int start cleaned.indexOf({); int end cleaned.lastIndexOf(}); if (start 0 end start) { cleaned cleaned.substring(start, end 1); } try { return objectMapper.readValue(cleaned, clazz); } catch (JsonProcessingException e) { log.warn(JSON解析失败原始内容{}, raw); throw new RuntimeException(模型返回格式错误, e); } }6.2 工具调用陷入死循环的排查过程有一次我做一个自动回复邮件的Agent它需要调用查询邮件详情和发送回复两个工具。测试的时候发现Agent有时候会反复调用查询邮件详情就是不发送回复。排查过程是这样的第一步看日志。发现Agent确实在反复调用查询工具每次返回的结果都一样但模型就是决定再次调用。第二步看Prompt。发现Prompt里写的是先查询邮件详情然后根据内容回复。模型理解成了每次回复前都要查询于是在查询和回复之间反复横跳。第三步看工具描述。查询工具的描述是查询指定邮件的详细内容没有说明查询一次就够了。第四步定位原因。模型缺乏已经查询过了的状态感知它不知道这个工具已经被调用过了。解决方案是在Prompt里加一条规则如果已经查询过邮件详情直接使用查询结果生成回复不要重复查询。同时在工具执行层加一个去重逻辑同一个工具用同样的参数在短时间内重复调用直接返回缓存结果。这个坑让我明白了一件事模型没有记忆它只能看到当前上下文。你需要通过Prompt或者工程手段把已经做过什么这个信息明确地告诉它。6.3 Token消耗失控一个被忽视的成本黑洞刚开始做Agent的时候我完全没关注token消耗。直到月底看账单发现一个测试用的Agent花了几十美元才意识到问题的严重性。Token消耗主要来自几个地方系统Prompt每次调用都要带上、对话历史越积越多、工具定义每次调用都要带上、工具返回结果可能很长。优化手段包括精简系统Prompt。不要写一大堆废话只保留必要的信息。我见过有人在系统Prompt里写了几千字的角色设定每次调用都要消耗这些token。控制对话历史长度。前面说的滑动窗口、摘要压缩都是为了这个。工具定义按需加载。如果工具很多不要一次性把所有工具定义都传给模型。可以根据当前对话的意图只传相关的工具。工具返回结果截断。工具返回的结果如果太长截断或者摘要后再传给模型。选择合适的模型。不是所有任务都需要用最贵的模型。简单的分类、提取任务用便宜的小模型就够了。我现在的做法是给每个Agent设置一个token预算超过预算就触发告警。同时在日志里记录每次调用的token消耗方便分析和优化。6.4 模型幻觉的工程化应对幻觉是LLM的固有缺陷你不可能完全消除它只能通过工程手段去降低它的影响。约束输出范围。如果答案是枚举值在Prompt里明确列出所有可能的值让模型从中选择而不是自由生成。要求引用来源。如果Agent需要基于文档回答问题要求模型在回答中引用具体的文档片段。这样你可以验证它的回答是否有依据。交叉验证。对于关键信息可以让模型多次生成取多数结果。或者用不同的Prompt模板生成对比结果。人工审核。对于高风险的操作比如发送邮件、修改数据不要完全信任Agent的输出加一道人工审核环节。兜底回复。当模型表示不确定或者返回的内容明显不合理时触发兜底逻辑转人工处理。我在实际项目里的做法是Agent只负责生成建议最终的执行由人工确认。这样既提高了效率又避免了幻觉带来的风险。7. 转型路上的心态调整和节奏把控7.1 不要试图一次性学完所有东西AI Agent这个领域变化太快了今天出的框架明天可能就过时了。你不可能学完所有东西再开始做只能边做边学。我的建议是先跑通一个最小的Demo哪怕只是调用一次OpenAI API返回一句话。然后逐步加功能加一个工具、加记忆、加多轮对话。每加一个功能就深入理解这个功能背后的原理。这样学起来有成就感也不会被信息淹没。7.2 Java工程师的差异化竞争力在哪里现在做AI Agent的人很多但大部分是Python背景的算法工程师或者全栈开发者。Java工程师的优势在于工程化能力你能写出更健壮、更可维护、更容易集成的Agent系统。我见过很多Python写的Agent Demo功能很炫但代码结构一团糟异常处理几乎没有根本没法上生产。这正是Java工程师的机会把Agent从Demo变成生产级系统。所以我的建议是不要跟别人比谁更懂模型原理那是算法工程师的领域。你要比的是谁能把Agent做得更稳定、更可靠、更容易维护。这才是你的核心竞争力。7.3 一个可持续的学习节奏最后说一下学习节奏。我现在的习惯是每周花两个小时看最新的Agent相关文章和开源项目但不追每一个新框架。每个月动手做一个小项目把学到的东西落地。每季度复盘一次看看哪些技能用上了、哪些白学了。这个节奏不快但可持续。转型不是百米冲刺是马拉松。保持好奇心保持动手的习惯时间会给你答案。我在实际项目里最大的体会是Agent的难点不在模型在工程。模型的能力已经足够强了真正决定Agent好不好用的是你怎么设计工具、怎么管理上下文、怎么处理异常、怎么控制成本。这些东西恰恰是Java工程师最擅长的。所以别妄自菲薄你手里的工程能力在这个领域比你想的值钱得多。