拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

Java 工程师转 AI Agent:LangChain4j 与 Spring AI 实战指南

Java 工程师转 AI Agent:LangChain4j 与 Spring AI 实战指南

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 选型对照表与决策建议

维度LangChain4jSpring 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 这些年早就练熟了。

返回列表