1. 为什么 Java 工程师转 AI Agent 有天然优势
这两年身边不少写 Java 的朋友都在焦虑同一件事:AI Agent 这么火,自己是不是要被时代甩下去了。我的判断恰恰相反——Java 工程师转 AI Agent,起点比大多数人都高,只是很多人没意识到自己手里已经握着什么牌。
先把概念对齐。AI Agent不是简单的“调个大模型接口返回一段文本”,而是一个能自主感知输入、规划步骤、调用工具、观察结果、再决定下一步的闭环系统。它和普通 Chatbot 的本质区别在于“行动能力”:Chatbot 只会说,Agent 会做。而“会做”这件事,落到工程上就是一堆服务编排、状态管理、异常重试、并发控制、可观测性——这些恰好是 Java 后端工程师干了十几年的活。
我见过太多 Python 背景的算法同学,模型调得飞起,但一让他把 Agent 部署成能扛住生产流量的服务,线程池、连接池、超时熔断、幂等设计全抓瞎。反过来,Java 工程师缺的往往只是“大模型这一层”的认知:Prompt 怎么组织、工具怎么描述、上下文怎么裁剪、多轮推理怎么收敛。这一层是可以在几周内补上的,而工程能力是几年攒出来的。
所以这篇东西我打算讲透三件事:Agent 的底层原理到底是怎么回事,Java 生态里LangChain4j和Spring AI这两条主流路线怎么选、怎么落地,以及ReAct这种“边想边做”的模式在 Java 里怎么实现、怎么扛并发。适合有 Java 基础、想切进 AI Agent 方向但不知道从哪下手的同学,也适合已经在写 Agent 但被工程问题卡住的人。
提示:本文所有代码和配置都基于常见实践整理,具体版本 API 可能随迭代变化,落地时以你项目锁定的版本为准。
2. 先把 AI Agent 的原理拆到能动手的程度
2.1 Agent 和普通大模型调用的本质区别
普通调用大模型,流程是线性的:用户输入 → 拼 Prompt → 请求模型 → 拿到文本 → 返回。整个过程是一次性的,模型没有“下一步”的概念。
Agent 不一样,它是一个循环。核心可以抽象成四步:感知(Perception)→ 规划(Planning)→ 行动(Action)→ 观察(Observation),然后拿着观察结果回到规划,直到任务完成或达到终止条件。这个循环就是所谓的 Agent Loop。
举个具体例子。用户说“帮我查一下北京明天天气,如果下雨就提醒我带伞”。普通模型只能回你一句“建议你查一下天气预报”。Agent 会这样跑:先规划“我需要调用天气查询工具”,行动——调用天气 API,观察——返回“明天中雨”,再规划“需要给出带伞提醒”,最后输出结论。中间可能还涉及多轮工具调用,比如先查城市编码再查天气。
理解这个循环之后你会发现,Agent 的工程难点根本不在模型本身,而在于:循环怎么控制不失控、工具调用失败怎么办、上下文越来越长怎么裁剪、多个用户并发跑循环怎么隔离状态。这些全是 Java 工程师的主场。
2.2 ReAct 模式:让模型“边想边做”的标准范式
ReAct是 Reasoning + Acting 的缩写,是目前最主流的 Agent 推理范式。它的核心思想是让模型在每一步都显式输出“思考”和“行动”,而不是直接给答案。
一个典型的 ReAct 输出长这样:
Thought: 我需要先查询北京的城市编码 Action: getCityCode Action Input: {"city": "北京"} Observation: {"code": "101010100"} Thought: 拿到编码了,现在查天气 Action: getWeather Action Input: {"code": "101010100"} Observation: {"weather": "中雨", "temp": "18-24℃"} Thought: 明天有雨,需要提醒带伞 Final Answer: 北京明天中雨,气温18-24℃,记得带伞。模型每输出一段,框架就解析出 Action,执行对应工具,把结果作为 Observation 塞回上下文,再让模型继续。这个“解析-执行-回填”的循环,就是 ReAct 的骨架。
为什么这个范式好用?因为它把“推理”和“行动”解耦了。模型负责想,工具负责做,框架负责调度。你不需要训练模型,只需要把工具描述清楚、把循环控制好。对 Java 工程师来说,这就是一个标准的“策略模式 + 状态机”问题。
2.3 工具调用(Function Calling)的底层机制
ReAct 是“文本层面”的推理范式,而现代大模型普遍支持的Function Calling是“协议层面”的工具调用能力。两者经常配合使用。
Function Calling 的机制是:你在请求里带上工具的定义(JSON Schema 描述函数名、参数、用途),模型如果判断需要调用工具,就不再返回自然语言,而是返回一个结构化的调用请求,比如{"name": "getWeather", "arguments": {"city": "北京"}}。你的代码解析这个结构,执行真实函数,把结果再发回去。
这里有个关键认知:模型本身不执行任何函数,它只是“决定调用哪个函数、传什么参数”。真正的执行永远在你的 Java 代码里。这意味着安全性、权限、限流、审计全部由你掌控——这对企业级应用是刚需。
工具描述写得好不好,直接决定 Agent 的智商。我踩过的坑是:工具描述太简略,模型经常选错工具;参数描述不写清楚格式,模型传参乱七八糟。后面会专门讲怎么写工具描述。
2.4 记忆与上下文管理:Agent 的“工作台”
Agent 跑多轮循环,上下文会迅速膨胀。每一轮的 Thought、Action、Observation 都要塞进对话历史,几轮下来轻松几千 token。如果不管理,成本和延迟都会爆炸。
记忆一般分三层:短期记忆(当前任务的对话历史)、长期记忆(跨会话的用户偏好、知识库)、工作记忆(当前循环的中间状态)。Java 里实现短期记忆最直接的方式就是维护一个消息列表,但必须做窗口裁剪或摘要压缩。
常见的裁剪策略有三种:滑动窗口(只保留最近 N 轮)、摘要压缩(把早期对话让模型总结成一段话)、关键信息提取(只保留工具调用结果,丢掉中间推理)。我实测下来,滑动窗口 + 工具结果保留的组合性价比最高,简单可靠。
3. Java 生态两条路线:LangChain4j 与 Spring AI 怎么选
3.1 LangChain4j:轻量灵活,适合快速验证
LangChain4j的定位很像 Python 的 LangChain,把 Agent、RAG、工具调用、记忆这些概念都做了 Java 封装。它的优点是抽象层次清晰,API 直观,上手快。
一个最小的 Agent 定义大概是这样:
interface Assistant { String chat(String userMessage); } Assistant assistant = AiServices.builder(Assistant.class) .chatLanguageModel(model) .tools(new WeatherTools()) .chatMemory(MessageWindowChatMemory.withMaxMessages(10)) .build(); String answer = assistant.chat("北京明天天气怎么样?");AiServices是 LangChain4j 的核心,它用动态代理把接口方法映射成“带工具和记忆的模型调用”。你只需要定义接口和工具类,框架帮你处理 ReAct 循环、工具解析、记忆管理。
LangChain4j 的强项在于多路召回和Easy RAG。做知识库问答时,它内置了文档加载、切分、向量化、检索的完整链路,几行代码就能跑通一个 RAG。如果你要做的是“Agent + 知识库”的组合,LangChain4j 的开发效率很高。
但它也有短板:生态相对分散,企业级治理能力(比如统一的配置中心、监控埋点、多租户隔离)需要自己补。另外版本迭代较快,API 偶有破坏性变更,锁版本很重要。
3.2 Spring AI:企业级基因,适合长期维护
Spring AI走的是另一条路——把 AI 能力做成 Spring 生态的一等公民。它的最大价值是“和现有 Spring 项目无缝融合”:依赖注入、配置管理、Actuator 监控、事务、AOP 全部复用。
Spring AI 里定义一个带工具的 ChatClient:
@Bean ChatClient chatClient(ChatClient.Builder builder, WeatherTools weatherTools) { return builder .defaultSystem("你是一个能调用工具的助手") .defaultTools(weatherTools) .build(); }调用时:
String response = chatClient.prompt() .user("北京明天天气怎么样?") .call() .content();Spring AI 的优势在于可观测性和可治理性。它天然接入 Micrometer,工具调用次数、模型延迟、token 消耗都能打点;配置通过application.yml管理,多环境切换很自然;结合 Spring Security 做工具级权限控制也很顺。
关于Spring AI Alibaba,社区里经常有人问“是不是停更了”。实际情况是它在持续演进,主要价值是打通了国内主流模型平台的适配,比如对接百炼上的 Qwen 系列。如果你用的是国内模型,Spring AI Alibaba 能省掉不少适配工作。但要注意版本匹配,Spring AI 2.0.x 和对应 Alibaba 适配层的版本要对应上,否则会出现自动配置不生效的问题。
3.3 选型对照表与决策建议
| 维度 | LangChain4j | Spring AI |
|---|---|---|
| 上手速度 | 快,API 直观 | 中等,需熟悉 Spring 风格 |
| 与现有 Spring 项目融合 | 一般,需手动整合 | 极佳,原生集成 |
| RAG 能力 | 内置 Easy RAG,开箱即用 | 需自行组装,灵活度高 |
| 可观测性 | 需自行埋点 | 原生 Micrometer 支持 |
| 多模型适配 | 支持广泛 | 通过 starter 适配,国内模型靠 Alibaba |
| 版本稳定性 | 迭代快,需锁版本 | 相对稳健 |
| 适合场景 | 快速验证、RAG 为主 | 企业级、长期维护、多租户 |
我的建议很直接:如果是新项目、要长期维护、团队本来就是 Spring 技术栈,选 Spring AI;如果是做原型验证、或者核心诉求是 RAG 知识库,选 LangChain4j。两者不是非此即彼,实际项目里我也见过用 Spring AI 做服务骨架、用 LangChain4j 的检索组件做 RAG 的混搭方案。
4. 从零搭一个能跑的 Java Agent
4.1 环境准备与依赖配置
先明确技术栈:JDK 17 起步(Spring AI 对 17+ 支持最好),Maven 或 Gradle 都行,模型侧需要一个可用的 API Key。
Spring AI 的核心依赖:
<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-openai-spring-boot-starter</artifactId> </dependency>如果对接国内模型平台,换成对应的 starter。配置写在application.yml:
spring: ai: openai: api-key: ${AI_API_KEY} base-url: https://your-endpoint/v1 chat: options: model: your-model-name temperature: 0.3这里有个经验:temperature 在 Agent 场景不要设太高。Agent 需要稳定地选择工具、输出结构化内容,temperature 超过 0.7 后工具选择会变得随机,我一般压在 0.2 到 0.4 之间。
4.2 定义工具:Agent 的“手脚”
工具就是普通的 Spring Bean,用注解标记:
@Component public class WeatherTools { @Tool(description = "根据城市名称查询该城市未来天气,返回天气状况和温度区间") public WeatherResult getWeather( @ToolParam(description = "城市名称,例如:北京、上海") String city) { // 真实调用天气服务 return weatherService.query(city); } }工具描述是重中之重。我总结了几条硬规则:
- 描述里写清楚“什么时候用”,而不只是“这是什么”。比如“当用户询问天气、气温、是否下雨时使用”。
- 参数描述给出格式示例,模型对示例的敏感度远高于抽象说明。
- 一个工具只做一件事。把“查天气和查空气质量”塞进一个工具,模型会经常传错参数。
- 返回值尽量结构化,避免返回一大段自然语言,否则 Observation 会污染上下文。
4.3 组装 Agent 与 ReAct 循环
Spring AI 的ChatClient已经内置了工具调用循环,你不需要手写 while。但如果你想理解底层,或者需要自定义循环控制(比如限制最大轮数),可以手动实现:
public String runAgent(String userInput, int maxSteps) { List<Message> messages = new ArrayList<>(); messages.add(new SystemMessage(SYSTEM_PROMPT)); messages.add(new UserMessage(userInput)); for (int step = 0; step < maxSteps; step++) { ChatResponse response = chatModel.call(new Prompt(messages, toolOptions)); AssistantMessage aiMsg = response.getResult().getOutput(); messages.add(aiMsg); if (aiMsg.getToolCalls().isEmpty()) { return aiMsg.getText(); } for (ToolCall call : aiMsg.getToolCalls()) { String result = toolExecutor.execute(call); messages.add(new ToolResponseMessage(call.id(), result)); } } return "达到最大步数限制,任务未完成"; }maxSteps是必须的保险丝。没有它,模型可能陷入“调用工具→结果不满意→再调用”的死循环,烧钱又烧时间。我一般设 5 到 8 步,复杂任务放宽到 10。
4.4 记忆管理落地
短期记忆用消息窗口:
MessageWindowChatMemory memory = MessageWindowChatMemory.builder() .maxMessages(20) .build();但纯窗口有个问题:工具调用的中间结果被裁掉后,模型可能“忘记”已经查过的信息,导致重复调用。我的做法是对工具结果做单独保留,在裁剪时优先保留 ToolResponseMessage,优先丢弃早期的 Thought 文本。
长期记忆则落到向量库,用户的历史偏好、常见问题答案存进去,每次对话前做一次相似度检索,把 Top-K 结果拼进 System Prompt。这就是 RAG 的思路,LangChain4j 的 Easy RAG 把这条链路封装得很省事。
5. 扛并发:Java Agent 的生产级考验
5.1 并发场景下 Agent 的真实瓶颈
“AI Agent 怎么扛并发”是搜索热词,说明这是真痛点。Agent 的并发瓶颈和普通接口完全不同,主要有三个:
第一,模型调用是长耗时 IO。一次带工具调用的 Agent 请求,可能涉及 3 到 5 次模型调用,每次几百毫秒到几秒,总耗时轻松超过 10 秒。这意味着线程会被长时间占用。
第二,上下文是有状态的。每个用户的对话历史必须隔离,不能串。用错共享变量,A 用户的工具结果可能出现在 B 用户的上下文里,这是灾难级 bug。
第三,模型侧有速率限制。你的服务能扛住,模型平台的 QPS 配额未必扛得住,超了就是 429。
5.2 线程模型与资源隔离
Java 里处理长耗时 IO,第一反应应该是异步 + 虚拟线程。JDK 21 的虚拟线程在 Agent 场景简直是量身定做:每个 Agent 循环跑在虚拟线程上,阻塞等待模型响应时自动让出载体线程,几千并发也不会把平台线程池打爆。
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { Future<String> future = executor.submit(() -> agent.run(userInput)); return future.get(30, TimeUnit.SECONDS); }如果还在 JDK 17,用CompletableFuture+ 独立线程池,但一定要给 Agent 单独开池,别和业务线程池混用,否则 Agent 的长任务会把普通接口拖垮。
会话状态隔离用ThreadLocal或显式传参都行,但我更推荐显式传参——把会话上下文作为方法参数一路传下去,避免 ThreadLocal 在异步切换时丢失或串号。
5.3 限流、降级与超时设计
三层防护缺一不可:
- 入口限流:用 Sentinel 或 Resilience4j 对 Agent 接口做 QPS 限制,保护后端。
- 模型调用超时:单次模型调用设 15 到 30 秒超时,整个 Agent 循环设总超时 60 秒。
- 降级策略:模型不可用时,降级到“无工具的纯问答”或返回缓存答案,而不是直接报错。
CircuitBreaker breaker = CircuitBreaker.ofDefaults("agent"); Supplier<String> decorated = CircuitBreaker .decorateSupplier(breaker, () -> agent.run(input));5.4 成本与延迟优化
并发上来了,成本就是真金白银。几个实测有效的优化:
- Prompt 缓存:System Prompt 固定不变的部分,很多模型平台支持缓存,能省不少 token。
- 工具结果缓存:天气、汇率这类短时间不变的数据,缓存 5 分钟,避免重复调用。
- 小模型分流:意图识别、简单问答用便宜的小模型,复杂推理才上大模型。
- 流式输出:用 SSE 把模型的流式响应推给前端,用户感知延迟大幅下降,虽然总耗时没变。
6. 常见问题与排查技巧实录
6.1 工具调用失败排查表
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 模型不调用工具 | 工具描述不清、System Prompt 未引导 | 检查描述是否说明使用场景 |
| 调用工具但参数错误 | 参数描述缺格式示例 | 补充示例,加参数校验 |
| 工具执行报错后模型卡死 | 错误信息未回填或格式不对 | 把错误作为 Observation 回填 |
| 循环不终止 | 缺 maxSteps 或终止条件 | 加最大步数硬限制 |
| 上下文超长报错 | 记忆未裁剪 | 加窗口裁剪或摘要压缩 |
6.2 几个我踩过的坑
坑一:工具抛异常直接中断循环。早期我没做异常捕获,工具一报错整个 Agent 就挂了。正确做法是把异常信息包装成 Observation 回填给模型,让它自己决定重试还是换方案。
坑二:System Prompt 写太长。我一度把几十条规则全塞进 System Prompt,结果模型注意力被稀释,工具选择准确率反而下降。后来精简到核心 5 条,效果明显变好。
坑三:忽略 token 计费。上线第一周账单超预期,排查发现是工具结果返回了完整 JSON,包含大量无用字段。裁剪返回值后成本降了一半。
坑四:并发下会话串号。用了一个共享的ChatMemoryBean,结果所有用户共享历史。改成每次请求创建独立 memory 实例后解决。
6.3 可观测性建设
Agent 上线后必须能回答三个问题:这次请求调了几次模型、调了哪些工具、每步耗时多少。Spring AI 接 Micrometer 后,这些指标都能打点。我还会把完整的 Agent 轨迹(每步的 Thought/Action/Observation)落到日志,出问题时能完整回放。
7. 学习路线与后续扩展方向
如果你是从 Java 后端转过来,我建议的路线是:先用 Spring AI 或 LangChain4j 跑通一个带单个工具的 Agent,理解 ReAct 循环;然后加记忆和 RAG,做知识库问答;接着上并发和限流,把它变成能上生产的服务;最后再研究多 Agent 协作、工作流编排这些进阶话题。
关于Dify 工作流转成 Spring AI Java 代码这类需求,本质是把可视化编排的节点翻译成 Java 的链式调用或状态机。思路是:每个节点对应一个Function或Processor,节点间的连线对应数据流转,条件分支用策略模式实现。这块没有银弹,但理解了 Agent Loop 之后,翻译工作就是体力活。
至于“个人用 AI Agent 做期货交易”这种想法,我的态度是谨慎。Agent 能帮你收集信息、做初步分析,但金融决策涉及的风险和合规问题远超技术范畴,别把 Agent 当成稳赚工具。技术归技术,边界要清楚。
最后分享一个我自己的体会:Java 工程师转 AI Agent,最大的障碍从来不是技术,而是心态——总觉得自己“不懂 AI”。其实你不需要懂模型怎么训练,你只需要懂怎么把模型当成一个能力很强但不太靠谱的同事来管理:给它清晰的指令、给它趁手的工具、给它必要的约束、盯着它的每一步。这套管理方法论,你写 Java 这些年早就练熟了。