
1. 为什么 Java 工程师转 AI Agent 有天然优势1.1 从 CRUD 到智能体差的不是智商是视角这两年身边不少 Java 老哥都在焦虑同一件事干了五六年业务系统天天写 Controller、Service、Mapper突然满世界都在聊 AI Agent感觉自己积累的那点东西一夜之间要归零了。我一开始也这么想直到真正动手做了几个 Agent 项目之后才发现事情完全不是这样。Java 工程师转 AI Agent其实比很多纯算法背景的人更容易落地。原因很直接Agent 这东西本质上不是一个模型问题而是一个工程问题。它要处理工具调用、状态管理、异常重试、并发控制、上下文裁剪、日志追踪、权限校验——这些词你听着是不是特别耳熟对这就是我们写了这么多年的后端工程。模型只是 Agent 的“大脑”而让这个大脑真正能干活的那套骨架恰恰是 Java 工程师最擅长的部分。我见过太多 Python 写得飞起、模型调得贼溜的人一到要把 Agent 部署成稳定服务、要扛住几十上百并发、要做链路追踪和降级就抓瞎了。反过来一个熟悉 Spring 生态的 Java 工程师只要把 Agent 的核心原理搞明白落地速度往往快得惊人。所以这篇我不打算跟你讲什么高深的强化学习就讲一个 Java 工程师怎么从原理到落地把 AI Agent 真正跑起来。1.2 先搞清楚AI Agent 到底比普通调用强在哪很多人对 Agent 的理解还停留在“调个大模型 API 返回一段文本”。那不叫 Agent那叫聊天接口。真正的 Agent 有三个核心特征能思考、能行动、能根据结果调整。举个生活化的例子。你让普通大模型“帮我查一下明天北京的天气并提醒我带伞”它只能凭训练数据瞎编一个答案。但一个 Agent 会这么做先判断需要调用天气工具然后真的去调天气 API拿到真实数据再判断温度、降水概率最后决定要不要提醒你带伞甚至还能顺手帮你设个闹钟。这个“判断—调用—观察—再判断”的循环就是 Agent 的灵魂。在技术圈这套循环最经典的范式叫ReAct也就是 Reasoning Acting。模型先输出一段思考Reasoning决定要调用哪个工具Action工具返回结果Observation模型再基于结果继续思考直到任务完成。你把它理解成一个 while 循环就对了只要任务没结束就继续“想一步、做一步、看结果”。Java 工程师看到这个结构应该会心一笑这不就是我们写状态机、写工作流的思路吗1.3 技术选型LangChain4j 还是 Spring AI落到 Java 生态绕不开两个框架LangChain4j和Spring AI。这俩经常被拿来对比我的建议是别纠结先看你团队的技术栈。LangChain4j 更像是一个“全家桶”它把 Agent、RAG、工具调用、记忆管理、多路召回这些能力都封装好了API 设计也比较贴近 Python 版 LangChain 的思路。如果你之前看过 LangChain 的教程上手 LangChain4j 会非常快。它的AiServices可以把一个 Java 接口直接变成 Agent你只要定义好方法签名和注解框架帮你把提示词、工具调用、结果解析全串起来。Spring AI 则是 Spring 官方亲儿子最大的优势是和 Spring Boot 生态无缝集成。你熟悉的Bean、依赖注入、配置管理、Actuator 监控全都能直接用上。对于已经在用 Spring Boot 的团队引入 Spring AI 的迁移成本几乎为零。而且 Spring AI 对国内模型的支持也越来越好像阿里百炼、通义千问这些都能通过配置接进来。我的实际选择是这样的如果是快速验证想法、做原型我用 LangChain4j因为它抽象层次高代码量少如果是要做成生产级服务、要长期维护我用 Spring AI因为它的工程化程度和生态整合更让人放心。当然两者也不是非此即彼有些项目里我甚至混着用用 LangChain4j 做 RAG 检索用 Spring AI 做服务编排。2. 核心原理拆解Agent 的四大件2.1 大脑模型选型与提示词工程Agent 的大脑就是大模型。选模型这件事我的经验是别一上来就追求最强最贵。做 Agent 和做聊天不一样Agent 会在一轮任务里调用模型很多次成本是成倍放大的。所以要根据任务复杂度分层选型。简单任务比如意图识别、参数抽取、结果格式化用轻量模型就够了速度快、成本低。复杂推理任务比如多步规划、代码生成、复杂工具编排才需要上更强的模型。我一般会在配置里把这两类模型分开让框架根据场景自动路由。提示词工程这块Java 工程师容易犯的错是把它当成“写文档”写一大堆背景介绍。其实 Agent 的提示词核心就三件事角色定义、工具说明、输出格式约束。角色定义告诉模型它是谁、要干什么工具说明告诉它有哪些工具可用、什么时候用输出格式约束最关键必须明确要求模型按固定结构输出比如先输出 Thought再输出 Action这样你的 Java 代码才能稳定解析。提示输出格式约束一定要用强约束语言比如“必须严格按照以下 JSON 格式输出不要添加任何额外文字”。我踩过的坑就是模型偶尔会“自由发挥”加一句“好的我来帮你”结果解析直接崩掉。2.2 手脚工具调用的设计与实现工具Tool是 Agent 能“下地干活”的关键。在 Java 里一个工具本质上就是一个方法加上描述和参数说明。LangChain4j 用Tool注解Spring AI 用Tool或者函数式注册思路都一样。设计工具时有几个原则我反复验证过。第一工具粒度要适中。太细了模型要调很多次容易乱太粗了模型不知道怎么用。比如“查询订单”和“取消订单”应该分开但“查询订单列表”和“查询订单详情”可以合并成一个带参数的查询工具。第二工具描述要写清楚边界。模型判断用不用某个工具全靠描述。你要明确写“这个工具用于查询实时数据不适用于历史统计”否则模型会乱调。第三参数校验不能省。模型给的参数不一定合法你的工具方法里必须做校验该抛异常抛异常让 Agent 有机会根据错误信息重试。Tool(根据城市名称查询当前天气仅支持中国大陆城市) public String getWeather(P(城市名称如北京) String city) { if (city null || city.isBlank()) { throw new IllegalArgumentException(城市名称不能为空); } // 调用真实天气 API return weatherApi.query(city); }2.3 记忆上下文管理与多轮对话Agent 的记忆分两种短期记忆和长期记忆。短期记忆就是当前对话的上下文长期记忆则是跨会话的知识通常用向量数据库存。短期记忆最大的坑是上下文爆炸。Agent 一轮任务可能产生十几条消息多轮下来 token 轻松超限。我的做法是分层处理最近几轮完整保留更早的做摘要压缩再早的直接丢弃。LangChain4j 提供了MessageWindowChatMemory可以设置最大保留条数超出就自动淘汰最老的。但光淘汰不够关键信息会丢所以我还会在任务关键节点手动插入一条“进度摘要”消息把已完成的事项固化下来。长期记忆这块本质就是 RAG。把历史对话、业务文档、知识库切片存进向量库需要时做相似度检索召回。这里有个细节召回不是越多越好。我一般召回 top 3 到 top 5太多会稀释关键信息还会增加 token 消耗。LangChain4j 的多路召回能力可以同时从多个数据源检索再融合排序适合知识来源比较杂的场景。2.4 循环ReAct 执行引擎把大脑、手脚、记忆串起来的就是 ReAct 循环。用伪代码表示大概是这样while (!task.isDone() step maxSteps) { String thought model.reason(context); if (thought.needTool()) { Object result toolExecutor.execute(thought.action()); context.addObservation(result); } else { task.complete(thought.answer()); } step; }看起来简单但工程上有几个必须处理的点。最大步数限制是必须的否则模型可能陷入死循环一直调工具停不下来。我一般设 10 到 15 步超过就强制终止并返回当前结果。异常处理也要设计好工具调用失败不能让整个 Agent 崩掉要把错误信息作为 Observation 喂回给模型让它自己决定是重试还是换方案。超时控制同样重要每一步都要有超时避免某个工具卡死拖垮整个请求。3. 从零搭建一个可落地的 Agent 服务3.1 环境准备与依赖引入我以 Spring AI 为例走一遍完整的搭建流程。首先建一个标准的 Spring Boot 项目JDK 17 起步推荐 21。然后在pom.xml里引入 Spring AI 的 starter。dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-openai-spring-boot-starter/artifactId version1.0.0/version /dependency如果你用的是国内模型比如阿里百炼的通义千问把 starter 换成对应的实现然后在application.yml里配置 base-url 和 api-key 就行。这里要注意不同模型的接口协议可能有差异配置前先确认框架版本是否支持。spring: ai: openai: base-url: https://dashscope.aliyuncs.com/compatible-mode api-key: ${DASHSCOPE_API_KEY} chat: options: model: qwen-plus temperature: 0.7注意api-key 千万别硬编码在代码里用环境变量或者配置中心。我见过有人把 key 提交到 Git 仓库结果被人刷了几千块血的教训。3.2 定义工具与 Agent 配置接下来定义工具。我做一个简单的订单查询 Agent包含查询订单和取消订单两个工具。Component public class OrderTools { Tool(根据订单号查询订单详情) public Order queryOrder(P(订单号) String orderId) { return orderService.getById(orderId); } Tool(根据订单号取消订单仅未发货订单可取消) public String cancelOrder(P(订单号) String orderId) { return orderService.cancel(orderId); } }然后配置 ChatClient 和工具注册。Spring AI 的写法很 Spring用 builder 模式把模型、工具、记忆组装起来。Configuration public class AgentConfig { Bean public ChatClient chatClient(ChatClient.Builder builder, OrderTools orderTools) { return builder .defaultSystem(你是一个订单助手帮助用户查询和取消订单。回答要简洁准确。) .defaultTools(orderTools) .build(); } }3.3 实现 ReAct 循环与并发控制Spring AI 内部已经帮你实现了工具调用的循环你调用chatClient.prompt().user(input).call()时框架会自动处理“模型要调工具—执行工具—把结果喂回模型”这个过程。但如果你想自己控制循环、加最大步数限制可以用更底层的 API 手动编排。并发控制是 Java 工程师的强项但 Agent 场景有它的特殊性。模型调用是 IO 密集型所以线程池要按 IO 密集型配置核心线程数可以设大一些。但要注意同一个会话的请求必须串行否则上下文会乱。我的做法是按 sessionId 做分片同一个 session 的请求路由到同一个队列串行处理不同 session 之间并行。private final MapString, ExecutorService sessionExecutors new ConcurrentHashMap(); public String handle(String sessionId, String input) { ExecutorService executor sessionExecutors.computeIfAbsent( sessionId, k - Executors.newSingleThreadExecutor() ); return executor.submit(() - agent.chat(input)).get(); }3.4 接入 RAG 做知识增强很多 Agent 场景需要基于私有知识回答这就得上 RAG。流程是文档切片、向量化、存库、检索、拼进提示词。LangChain4j 的 Easy RAG 把这一套封装得很简单几行代码就能跑通。EmbeddingStoreTextSegment store new InMemoryEmbeddingStore(); EmbeddingStoreIngestor ingestor EmbeddingStoreIngestor.builder() .documentSplitter(DocumentSplitters.recursive(500, 50)) .embeddingModel(embeddingModel) .embeddingStore(store) .build(); ingestor.ingest(document);切片大小我一般设 500 字符重叠 50 字符。重叠是为了避免关键信息被切断。检索时召回 top 5再用一个重排序模型精排效果会明显提升。如果知识来源多可以用多路召回从不同数据源分别检索再融合。4. 生产环境踩坑与排查实录4.1 常见问题速查表问题现象可能原因排查方向解决方案模型不调用工具工具描述不清或提示词未强调检查工具描述和 system prompt补充工具使用场景说明工具调用参数错误参数描述不明确查看模型输出的参数细化 P 描述加示例响应超时工具执行慢或模型响应慢打点统计各阶段耗时加超时、异步化、换轻量模型上下文超限历史消息过多统计 token 数加记忆窗口、做摘要压缩死循环调工具缺少终止条件看调用步数设最大步数、优化提示词并发下上下文串了会话未隔离检查 session 管理按 session 分片串行4.2 几个我踩过的深坑第一个坑是工具返回结果太大。有次我写了个查询工具返回了一整个列表的 JSON几千个字符直接塞进上下文token 瞬间爆掉而且模型被无关信息干扰回答质量暴跌。后来我改成工具内部先做筛选和摘要只返回关键字段问题就解决了。工具返回给模型的内容一定要精简只给模型做决策需要的信息。第二个坑是模型幻觉调用不存在的工具。提示词里明明只注册了两个工具模型偶尔会编一个“sendEmail”出来。这种情况框架一般会报错但错误信息如果不处理整个请求就挂了。我的做法是在工具执行层做一层拦截遇到未知工具就返回一条“该工具不存在可用工具为xxx”的 Observation让模型自己纠正。第三个坑是并发下的资源竞争。多个请求同时调用同一个有状态的工具结果数据串了。Agent 的工具方法尽量设计成无状态的如果必须有状态一定要做隔离。我一般用 ThreadLocal 或者按请求传上下文对象避免共享可变状态。4.3 性能优化的几个实操技巧模型调用是最大的耗时点优化空间也最大。流式输出能显著提升用户感知速度虽然总耗时没变但首字返回快了很多。缓存也很关键相同或相似的请求可以缓存模型结果尤其是那些确定性的工具调用结果。批处理适合离线场景把多个请求合并成一次模型调用。还有一个容易被忽略的点是提示词长度。提示词越长模型处理越慢成本越高。我定期会 review 提示词删掉那些模型根本不看的冗余描述。实测下来精简提示词能省 20% 到 30% 的 token。提示上线前一定要做压测重点看并发下的 P99 延迟和错误率。Agent 服务的延迟波动比普通接口大得多因为模型响应时间本身就不稳定。5. 学习路线与进阶方向5.1 给 Java 工程师的三个月学习计划第一个月把 ReAct 原理搞透用 LangChain4j 或 Spring AI 跑通一个带工具调用的 Demo。别贪多就做一个天气查询或者计算器 Agent把循环、工具、记忆这三件事弄明白。第二个月加上 RAG。找一个你熟悉的领域文档做切片、向量化、检索让 Agent 能基于文档回答。这个阶段重点理解召回质量怎么评估、切片策略怎么调。第三个月做工程化。把 Agent 包装成 REST 服务加上并发控制、超时、重试、日志追踪、监控告警。这一步是 Java 工程师的舒适区做完你就有一个能拿得出手的生产级 Agent 项目了。5.2 后续可以扩展的方向Agent 做熟了之后可以往多 Agent 协作方向走。多个各司其职的 Agent 互相配合一个负责规划一个负责执行一个负责审核能处理更复杂的任务。这个思路在 Java 里可以用工作流引擎来编排把每个 Agent 当成一个节点。另一个方向是可观测性。Agent 的决策过程是个黑盒出了问题很难排查。可以引入链路追踪把每一步的 Thought、Action、Observation 都记录下来做成可视化的执行轨迹。这对调试和优化帮助极大也是生产环境必备的能力。最后再分享一个小技巧调试 Agent 时把每一步的中间结果都打日志包括完整的提示词和模型原始输出。我一开始嫌日志太多没打结果出问题完全不知道模型当时看到了什么、想了什么。后来加上详细日志排查效率提升了不止一个档次。这个习惯建议你从第一个 Demo 就养成。