1. 为什么 Java 工程师转 AI Agent 有天然优势
1.1 从 CRUD 到智能体:一次能力栈的平移
做了五六年 Java 后端的人,心里多少都有点焦虑。Spring Boot 写熟了,MyBatis 写腻了,微服务那一套拆分、限流、熔断、链路追踪也玩得差不多了,突然发现招聘 JD 上开始频繁出现“有大模型应用经验优先”“熟悉 AI Agent 架构”这类字眼。很多人第一反应是:我是不是得从头学 Python,把 PyTorch 啃一遍?
我的判断是:不需要。Java 工程师转 AI Agent,本质上不是转行,而是能力栈的一次平移。你过去积累的东西——面向对象设计、依赖注入、接口抽象、状态机、并发控制、可观测性——在 Agent 开发里几乎全部用得上,而且用得非常狠。
Agent 是什么?说白了就是一个能“思考—行动—观察—再思考”的循环体。它需要调用工具、维护上下文、处理异常、控制超时、记录日志、做权限校验。这些东西,哪一样不是后端工程师天天在干的事?LangChain4j 和 Spring AI 之所以能在 Java 圈快速铺开,就是因为它们把 Agent 的编排逻辑做成了 Java 工程师熟悉的样子:注解、接口、Builder、配置类。
所以这篇文章我不打算讲“大模型原理入门”那种东西,而是站在一个写了多年 Java 的人的角度,把 Agent 的核心原理、主流框架选型、落地步骤、踩坑经验一次讲透。适合谁看?适合已经会 Spring Boot、想用 Java 技术栈把 Agent 真正跑起来的人。你要是连依赖注入都还没搞明白,建议先把 Spring 基础补一补再回来。
1.2 先搞清楚 Agent 和普通调 API 的区别
很多人以为“调个大模型接口”就叫 AI 应用了,这跟 Agent 差得远。普通调用是一问一答:你发 prompt,模型返回文本,结束。Agent 是多轮自主决策:模型自己判断要不要调工具、调哪个工具、拿到结果后要不要继续、什么时候停下来给最终答案。
这个区别决定了架构复杂度完全不是一个量级。一问一答你只需要一个 HTTP 客户端;Agent 你需要工具注册中心、执行循环、上下文管理、失败重试、最大步数限制、中间状态持久化。这些恰好是 Java 工程师的舒适区。
我见过不少团队,用 Python 快速搭了个 demo,一到生产就崩:并发上不去、状态丢了、工具调用超时没人管、日志查不到。最后发现还是得用工程化的方式重写。这时候 Java 生态的成熟度就体现出来了——Spring 的事务、线程池、Actuator、Micrometer,直接拿来就能用。
2. Agent 的核心原理:ReAct 到底在干什么
2.1 ReAct 模式拆解:思考与行动的交替
ReAct 这个词是 Reasoning + Acting 的缩写,是目前绝大多数 Agent 框架的底层范式。它的核心循环长这样:
- Thought(思考):模型根据当前上下文,推理下一步该做什么。
- Action(行动):模型决定调用某个工具,并给出参数。
- Observation(观察):工具执行返回结果,塞回上下文。
- 重复 1-3,直到模型认为可以给出 Final Answer。
举个具体例子。你问 Agent:“帮我查一下北京今天天气,如果下雨就提醒我带伞。”
- Thought:我需要先查天气。
- Action:调用
getWeather(city="北京") - Observation:
{"weather": "小雨", "temp": "18℃"} - Thought:下雨了,需要提醒带伞。
- Final Answer:北京今天小雨,18℃,记得带伞。
整个过程模型不是一次性输出答案,而是分步决策。这就是 Agent 和普通问答的本质差异。
2.2 工具调用是怎么实现的
工具调用(Tool Calling / Function Calling)是 Agent 的手脚。原理上,你把每个工具用 JSON Schema 描述出来,连同用户问题一起发给模型,模型如果决定调用,就返回一个结构化的调用请求,而不是自然语言。
一个工具描述大概长这样:
{ "name": "getWeather", "description": "查询指定城市的实时天气", "parameters": { "type": "object", "properties": { "city": { "type": "string", "description": "城市名称" } }, "required": ["city"] } }模型看到这个描述,就知道有这么个能力可用。关键在于description 写得越清楚,模型调用越准。我踩过的坑是:description 写得太模糊,模型要么不调,要么传错参数。后来我养成习惯,description 里把“什么时候用、什么时候不用、参数格式”都写清楚,调用准确率明显提升。
2.3 上下文管理与 Token 预算
Agent 每轮循环都会往上下文里塞东西:系统提示、历史对话、工具描述、工具返回结果。Token 消耗是线性增长的,多轮之后很容易爆。
这里必须理解Token 是什么:它是模型处理文本的最小单位,中文大概 1 个字 1-2 个 token,英文一个单词 1-2 个 token。上下文窗口是有上限的,超了就报错或者被截断。
Java 工程师处理这个的思路应该很熟悉——这不就是内存管理吗?我的做法是:
- 工具返回结果做裁剪:只保留关键字段,别把整个 JSON 塞回去。
- 历史对话做摘要:超过 N 轮后,用模型把前面的对话压缩成一段摘要。
- 系统提示精简:别写小作文,能一句话说清就别写三句。
LangChain4j 里有ChatMemory抽象,Spring AI 里有ChatMemory接口,都支持滑动窗口和摘要策略,别自己造轮子。
3. 框架选型:LangChain4j 还是 Spring AI
3.1 两个框架的定位差异
这是 Java 圈问得最多的问题。我的结论先给出来:新项目优先 Spring AI,存量 Spring 项目集成也优先 Spring AI,需要复杂 Agent 编排和丰富集成时看 LangChain4j。
| 维度 | Spring AI | LangChain4j |
|---|---|---|
| 出身 | Spring 官方 | 社区驱动 |
| 与 Spring Boot 集成 | 原生,自动配置 | 需要手动配置 |
| Agent 编排能力 | 逐步完善 | 更成熟,ReAct 开箱即用 |
| 模型支持 | 主流模型齐全 | 更广,长尾模型多 |
| 学习曲线 | 低,Spring 开发者友好 | 中等 |
| 版本节奏 | 跟随 Spring 发布 | 迭代快 |
Spring AI 2.0 之后,工具调用、结构化输出、RAG 这些能力都补齐了,配合 Spring Boot 的自动配置,写起来非常顺手。LangChain4j 的优势在于它的AiServices抽象,把接口和 Agent 绑定得非常优雅,多路召回、复杂 RAG 场景支持更好。
3.2 我的实际选型逻辑
我自己的项目里是这么分的:
- 企业内部的问答助手、工单分类、文档检索:Spring AI。因为要跟现有的 Spring Boot 服务打通,用它的
ChatClient和@Tool注解,几行代码就能接上。 - 需要多步推理、多工具协作的复杂 Agent:LangChain4j。它的
AiServices配合@Tool注解,能把一个 Java 接口直接变成 Agent。
顺便说一句,网上老有人问“Spring AI Alibaba 停更了吗”,这个我没法给确定答案,但我的建议是:别把宝押在某个特定厂商的封装上。核心能力用 Spring AI 或 LangChain4j 的原生抽象,厂商适配层只是可替换的插件。这样即使某个适配层不维护了,你换一个就行,业务代码不用动。
3.3 一个最小可跑的 Spring AI 示例
先看依赖:
<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-openai-spring-boot-starter</artifactId> </dependency>配置:
spring: ai: openai: api-key: ${API_KEY} base-url: ${BASE_URL} chat: options: model: qwen-plus temperature: 0.7一个带工具的 Agent:
@Service public class WeatherAgent { private final ChatClient chatClient; public WeatherAgent(ChatClient.Builder builder) { this.chatClient = builder .defaultSystem("你是一个天气助手,需要查天气时调用工具") .defaultTools(new WeatherTools()) .build(); } public String ask(String question) { return chatClient.prompt() .user(question) .call() .content(); } } @Component class WeatherTools { @Tool(description = "查询指定城市的实时天气,参数为城市中文名") public String getWeather(String city) { // 实际调用天气 API return city + " 今天小雨,18℃"; } }这段代码里,@Tool注解就是工具注册,Spring AI 会自动把方法签名转成 JSON Schema 发给模型。模型决定调用时,框架自动执行方法并把结果塞回上下文。整个过程你不需要手写 ReAct 循环,框架帮你做了。
4. 从零搭建一个可落地的 Agent
4.1 需求定义:先想清楚 Agent 干什么
别一上来就写代码。我见过太多人上来就搭框架,结果做出来的东西没人用。先回答三个问题:
- 它解决什么具体问题?比如“自动回复客户咨询”“从合同里抽取关键条款”“根据日志定位故障”。
- 它需要哪些工具?每个工具对应一个明确的动作,比如查数据库、调 API、读文件。
- 它的边界在哪?哪些事它不能做,哪些操作需要人工确认。
以“客服工单自动分类”为例:输入是工单文本,输出是分类标签和优先级。工具可能只需要一个“查询历史相似工单”的检索工具。边界是:不能自动关闭工单,只能打标签。
4.2 工具设计的三条铁律
工具设计是 Agent 成败的关键。我的三条经验:
第一,工具粒度要适中。太粗,模型不知道怎么用;太细,模型要调很多次。一个工具最好对应一个完整的业务动作,比如createOrder而不是insertOrderRow。
第二,参数要少而明确。参数越多,模型传错的概率越大。能用枚举就别用自由文本,能给默认值就给默认值。
第三,返回值要结构化且精简。返回一大坨 JSON,既浪费 token 又干扰模型判断。只返回模型决策需要的信息。
@Tool(description = "根据工单内容查询相似历史工单,返回最多3条,用于辅助分类") public List<SimilarTicket> findSimilar( @ToolParam(description = "工单正文,不超过500字") String content) { // 向量检索逻辑 }4.3 提示词工程:系统提示怎么写
系统提示(System Prompt)是 Agent 的“岗位说明书”。我一般按这个结构写:
- 角色:你是谁。
- 任务:你要完成什么。
- 工具使用规则:什么时候调工具,什么时候不调。
- 输出格式:最终答案长什么样。
- 约束:不能做什么。
一个实际例子:
你是一个工单分类助手。 任务:根据工单内容,输出分类标签和优先级。 工具:需要参考历史工单时,调用 findSimilar。 输出格式:JSON,包含 category 和 priority 两个字段。 约束:不确定时输出 category="其他",不要编造分类。注意最后那条约束,非常关键。模型有“幻觉”倾向,不给约束它就会瞎编。我踩过的坑就是没写约束,模型给工单编了个不存在的分类,下游系统直接报错。
4.4 完整执行流程与状态管理
一个生产级 Agent 的执行流程,我通常这么设计:
- 接收请求,做参数校验和权限检查。
- 加载上下文,从数据库或缓存取历史对话。
- 进入 Agent 循环,设置最大步数(比如 10 步)和超时(比如 30 秒)。
- 每步记录日志:Thought、Action、Observation 全部落库,方便排查。
- 循环结束,解析最终答案,做格式校验。
- 持久化结果,更新上下文。
状态管理这块,Java 工程师有天然优势。我用一个AgentContext对象贯穿全程,用 ThreadLocal 或者显式传递都行。关键是每一步都要可观测,不然出了问题你根本不知道模型在哪一步跑偏了。
public class AgentContext { private String sessionId; private List<Message> history; private int stepCount; private long startTime; private List<ToolCallRecord> toolCalls; // getter/setter 省略 }5. 常见问题与排查技巧实录
5.1 模型不调工具怎么办
这是最高频的问题。排查顺序:
- 工具描述是否清晰:description 太模糊,模型不知道啥时候用。
- 系统提示是否引导:明确告诉模型“需要 X 时调用 Y 工具”。
- 模型是否支持工具调用:不是所有模型都支持 Function Calling,选型时要确认。
- 参数 schema 是否有问题:required 字段缺失、类型不匹配都会导致调用失败。
我的经验是,90% 的不调工具问题出在 description 上。把 description 当成写给新同事看的文档来写,基本就能解决。
5.2 循环停不下来怎么破
模型有时候会陷入死循环,反复调同一个工具。解决办法:
- 设置最大步数:硬性限制,比如 10 步,超了强制返回。
- 检测重复调用:同样的工具+同样的参数连续出现两次,直接中断。
- 在系统提示里加约束:“如果工具返回结果已经足够回答,立即给出最终答案,不要重复调用。”
5.3 Token 超限的排查思路
| 现象 | 可能原因 | 解决方向 |
|---|---|---|
| 报 context length 错误 | 历史对话太长 | 加滑动窗口或摘要 |
| 响应变慢 | 上下文过大 | 裁剪工具返回值 |
| 成本飙升 | 每轮都带全量历史 | 只带最近 N 轮 |
| 模型答非所问 | 关键信息被截断 | 调整截断策略,保留系统提示 |
5.4 工具执行超时与异常处理
工具调用是外部依赖,一定会失败。我的处理原则:
- 每个工具设置独立超时,别让一个慢工具拖垮整个 Agent。
- 异常要转成模型能理解的信息,比如返回
{"error": "天气服务暂时不可用"},让模型决定是重试还是换方案。 - 关键操作加人工确认,比如涉及资金、删除数据的工具,不能让模型自主执行。
@Tool(description = "删除指定工单,需要人工确认后执行") public String deleteTicket(String ticketId) { // 实际项目中这里应该走审批流 throw new UnsupportedOperationException("需人工确认"); }6. 学习路线与进阶方向
6.1 分阶段的学习路径
我给 Java 工程师的路线是这样的:
第一阶段(1-2 周):搞懂 ReAct 原理,跑通一个 Spring AI 或 LangChain4j 的 demo,能调通模型、注册工具、完成一次多步推理。
第二阶段(2-4 周):做一个真实的小项目,比如文档问答或工单分类。重点练工具设计、提示词工程、上下文管理。
第三阶段(1-2 月):上生产要考虑的东西——并发、限流、可观测性、成本控制、评测体系。这块是 Java 工程师的强项,别浪费。
第四阶段:进阶方向,比如多 Agent 协作、RAG 多路召回、Agent 评测与回归测试。
6.2 别被“新概念”带偏
这个领域新词特别多,什么 Agentic RAG、Reflection、Plan-and-Execute,听着唬人。我的建议是:先把 ReAct 这一个模式吃透,其他模式都是在它基础上的变体。你把 ReAct 的工具调用、上下文管理、循环控制搞明白了,看其他模式就是换个编排方式而已。
至于那些“基于 Rust 的 Agent”“某平台工作流转 Spring AI 代码”之类的,工具会变,原理不变。抓住原理,工具随便换。
6.3 我个人的一点体会
转 Agent 这一年多,我最大的感受是:Java 工程师的工程能力在这个领域是稀缺的。Python 圈做 demo 快,但一到生产就露怯。而 Agent 恰恰是个工程问题——它要处理并发、要保证可靠性、要做可观测性、要控制成本。这些全是后端工程师的基本功。
所以别焦虑,也别盲目追新。把 Spring AI 或 LangChain4j 用熟,把 ReAct 循环理解透,把工具设计和提示词工程练扎实,你就能把 Agent 真正落地。剩下的,交给时间。