1. 从 Spring Bean 到 Agent:一个 Java 老兵需要跨过的认知鸿沟
做了七八年 Java 后端的人,第一次听到“Agent”这个词,脑子里蹦出来的大概率是 Java Agent——也就是java.lang.instrument那套东西,配合premain、agentmain、字节码增强,用来做 APM 监控、链路追踪、热部署。我当初也是这么想的,直到有人跟我说“你用 Java 写个 Agent 吧”,我才发现我们说的根本不是一回事。
这两个“Agent”的差别,比HashMap和ConcurrentHashMap的差别大得多。Java Agent 是 JVM 层面的探针,它寄生在进程里,靠 Instrumentation API 改字节码;而 AI Agent 是一个能自主决策、调用工具、循环执行直到完成目标的软件实体。前者是“挂件”,后者是“员工”。这个认知不转过来,后面所有的学习都会跑偏。
我写这篇文章的出发点很具体:市面上讲 Agent 的内容,要么是 Python 视角的 LangChain 教程,要么是产品经理视角的概念科普,很少有专门写给 Java 后端看的。Javaer 有自己的思维惯性——习惯强类型、习惯依赖注入、习惯分层架构、习惯把一切抽象成接口和实现。这些惯性在转 Agent 的时候,一半是资产,一半是负债。资产在于工程化能力强,负债在于容易把 Agent 当成一个普通的 Service 去设计,结果做出来的东西“能跑但不像 Agent”。
所以这篇内容适合三类人:第一类是有 Java 后端背景、想搞清楚 Agent 到底是什么的开发者;第二类是在做 Spring AI、ADK 这类 JVM 生态 Agent 框架落地、需要建立整体认知的工程师;第三类是准备面试 Agent 相关岗位、需要把概念和工程实践串起来的人。我会从“Agent 的本质是什么”讲起,一路讲到 Javaer 转型时最容易踩的坑,尽量把每个“为什么”都说透。
2. Agent 到底是什么:拆掉营销话术后的最小定义
2.1 一个 Agent 的三个必要构件
抛开所有框架和产品包装,一个 AI Agent 的最小定义其实很朴素:它是一个由大模型驱动、能够自主选择并调用工具、通过循环迭代来达成目标的程序。这里面有三个关键词,缺一个都不算 Agent。
第一个是大模型驱动。模型是 Agent 的“大脑”,负责理解意图、做决策、生成内容。没有模型,你写的就是一个普通的 if-else 工作流。第二个是工具调用。Agent 必须能对外部世界产生作用——查数据库、调 API、读写文件、执行代码。没有工具,模型只能“说”,不能“做”,那它就是个聊天机器人。第三个是循环迭代。Agent 不是一问一答就结束,它会根据工具返回的结果判断“目标达成了吗”,没达成继续下一轮,直到完成或者触发终止条件。
我用一个生活化的类比:普通的大模型调用像是你去餐厅点菜,服务员(模型)听完你的需求,直接给你一个答复,结束。而 Agent 像是你雇了一个助理,你说“帮我订一张明天去上海的高铁票”,助理会打开 12306 查班次、对比时间、下单、付款、把订单截图发给你,中间遇到“没票了”还会自动换一班。这个“查—判断—再查—再判断”的过程,就是 Agent 的循环。
2.2 和 Workflow、Chain 的本质区别
很多人分不清 Agent 和 Workflow。我见过不少号称“Agent 项目”的东西,打开代码一看,是一串写死的步骤:先调 A 接口,把结果喂给模型,模型输出再调 B 接口。这是 Workflow,不是 Agent。
区别在于决策权在谁手里。Workflow 的流程是开发者预先编排好的,模型只是流程中的一个“文本处理节点”,它不决定下一步走哪。Agent 的流程是模型在运行时动态决定的,开发者只提供工具集和目标,具体调哪个工具、调几次、什么时候停,由模型自己判断。用一句话概括:Workflow 是“人编排流程,模型填内容”,Agent 是“人给目标和工具,模型编排流程”。
这个区别在工程上的影响是巨大的。Workflow 的行为可预测、可测试、可回归,但灵活性差,遇到没预设的情况就卡住。Agent 灵活,能处理开放性问题,但行为不确定,测试和调试难度陡增。我在实际项目里的经验是:能用 Workflow 解决的,不要上 Agent。Agent 的价值在于处理那些“步骤无法预先穷举”的任务,比如“帮我分析这份财报里有没有风险点”,你没法提前写死它会查哪几个指标、查几轮。
2.3 为什么现在才火:三个前提条件同时成熟
Agent 这个概念其实不新,早在上世纪就有“智能体”的研究。但它真正能落地,是最近两三年的事,因为三个前提条件同时成熟了。
第一是模型的推理和工具调用能力。早期的模型只能做文本补全,你让它输出一个结构化的函数调用参数,它经常给你编。现在的模型原生支持 function calling / tool use,能稳定输出符合 schema 的 JSON,这是 Agent 能跑起来的基础。第二是上下文窗口的扩大。Agent 循环会产生大量中间结果,窗口太小,几轮下来历史就被截断了,Agent 会“失忆”。第三是工程范式的沉淀。像 ReAct、Plan-and-Execute、Reflection 这些模式被验证有效,框架层面也有了 LangChain、Spring AI、ADK 这些工具,不用从零造轮子。
对 Javaer 来说,第三点尤其重要。以前做 Agent 基本只能用 Python,现在 Spring AI、ADK 的 Kotlin/Java 支持、LangChain4j 这些项目让 JVM 生态也能玩起来。虽然生态成熟度还不如 Python,但至少不用为了做个 Agent 去学一门新语言了。
3. Javaer 的思维惯性:哪些是资产,哪些是负债
3.1 强类型和接口抽象:天然适合定义 Tool
Java 的强类型系统在 Agent 开发里是个大优势,尤其是在定义 Tool(工具)的时候。一个 Tool 本质上就是“一个有明确输入输出契约的函数”,这跟 Java 的接口定义天然契合。
比如你要定义一个“查询订单”的工具,在 Java 里你会这么写:定义一个OrderQueryTool接口,输入是一个OrderQueryRequest(包含订单号、用户 ID 等字段),输出是一个OrderQueryResponse。这个接口的 schema 可以直接映射成模型能理解的 JSON Schema,模型根据字段名和类型描述来决定怎么填参数。强类型带来的好处是:参数校验在编译期和运行期都能做,模型填错了字段类型,反序列化直接报错,不会像动态语言那样悄悄传个字符串进去导致后面出问题。
但这里有个坑:Java 的 POJO 字段名和模型理解的语义之间需要一层映射。你写个字段叫ordNo,模型不一定知道这是订单号。所以定义 Tool 参数时,字段命名要尽量语义化,或者通过注解补充描述。Spring AI 的@ToolParam、LangChain4j 的@P就是干这个的。
3.2 依赖注入和分层:容易把 Agent 写成 Service
这是 Javaer 最容易踩的坑。我们习惯了 Controller-Service-DAO 的分层,习惯了用 Spring 管理 Bean,于是很自然地想把 Agent 也做成一个 Service:AgentService注入LlmClient、ToolRegistry、MemoryStore,然后暴露一个execute(task)方法。
这么写本身没错,但问题在于思维上会把 Agent 当成一个确定性的函数。你会下意识地认为“输入 task,输出 result”,中间的过程是黑盒。但 Agent 的中间过程恰恰是最需要被观测和干预的。它可能调了 5 次工具,可能在第 3 轮走错了方向,可能陷入了死循环。如果你把它当成一个普通 Service,你就不会去设计“中间步骤的日志、追踪、中断、重试”这些机制,等线上出问题的时候你会一脸懵。
我的建议是:把 Agent 的执行过程当成一个状态机或者工作流引擎来设计,而不是一个函数调用。每一轮循环的输入、模型的思考、工具的选择、工具的结果,都应该被记录下来,可回放、可中断、可恢复。这一点上,Java 生态里的状态机框架(如 Spring StateMachine)或者工作流引擎的思路反而能帮上忙。
3.3 异常处理和事务:Agent 的失败是常态
Java 后端对异常的处理是“异常即错误”,要么捕获降级,要么往上抛。但 Agent 的失败是常态,不是异常。模型可能选错工具、可能参数填错、可能工具返回了它没预料到的结果、可能陷入循环。这些都不是“bug”,而是 Agent 运行的自然组成部分。
所以你不能用传统的 try-catch 思维来处理。你需要的是容错和自愈机制:工具调用失败时,把错误信息作为观察结果喂回给模型,让它自己决定重试还是换方案;循环次数超限时,触发一个“总结当前进展并请求人工介入”的兜底逻辑。这跟微服务里的熔断降级思路有点像,但决策者是模型而不是预设的规则。
还有一个 Javaer 容易忽略的点:Agent 没有事务。你在一个循环里调了三个工具,前两个成功了,第三个失败了,你没法像数据库事务那样回滚。所以设计工具时,要尽量让每个工具是幂等的,或者把“补偿逻辑”也做成一个工具让 Agent 自己调。
4. 一个 Agent 跑起来时,内部到底发生了什么
4.1 从用户输入到第一次模型调用
假设用户输入“帮我查一下上个月销售额最高的三个产品,并生成一份简报”。Agent 收到这个输入后,第一件事不是直接调模型,而是组装上下文。这个上下文包括:系统提示词(定义 Agent 的角色、可用工具、行为约束)、历史对话(如果有)、当前用户输入。
系统提示词是 Agent 的“岗位说明书”,写得好不好直接决定 Agent 的表现。我见过很多 Agent 效果差,根因就是系统提示词太随意。一个好的系统提示词应该包含:角色定义(你是一个数据分析助理)、工具清单及使用场景(什么时候用哪个工具)、输出格式要求、边界约束(不能做什么)。这些内容在 Java 里通常是一个模板文件,通过PromptTemplate渲染。
组装好上下文后,调用模型。模型返回的内容有两种可能:一种是直接给出最终答案(如果它觉得不需要工具),另一种是返回一个工具调用请求(tool call),包含工具名和参数。这就是 Agent 循环的起点。
4.2 工具调用的完整链路
模型返回工具调用请求后,Agent 框架要做几件事。首先是解析和校验:把模型返回的 JSON 反序列化成工具的参数对象,校验必填字段、类型是否正确。这一步在 Java 里靠 Jackson 加 Bean Validation 就能做,比 Python 的手动校验靠谱得多。
然后是执行工具。这里有个工程细节:工具执行可能是耗时的(比如查数据库、调外部 API),也可能是危险的(比如写文件、发请求)。所以工具执行通常要放在一个受控的执行器里,带超时、带并发限制、带权限校验。我在项目里会把工具分成“只读工具”和“写入工具”,只读工具可以放开并发,写入工具要串行或者加锁。
工具执行完,返回一个结果。这个结果会被格式化后追加到上下文里,作为下一轮模型调用的输入。注意,工具返回的结果不能原样塞进去,尤其是返回大量数据的时候。你要做截断、摘要或者结构化处理,否则上下文很快就被撑爆了。我一般会限制单个工具返回的 token 数,超了就截断并提示模型“结果已截断”。
4.3 循环终止的三种方式
Agent 的循环不会永远跑下去,它有三种终止方式。第一种是模型主动结束:模型认为目标已达成,返回一个不含工具调用的最终答案。这是最理想的情况。第二种是达到最大轮次:开发者预设一个上限(比如 10 轮),到了就强制停止,返回当前进展。这是防止死循环的兜底。第三种是触发终止条件:比如工具返回了致命错误、或者检测到模型在重复同样的动作。
这里有个经验:最大轮次不要设太大。我见过有人设 50 轮,结果 Agent 在第 20 轮开始胡言乱语,白白烧了一堆 token。一般任务 5 到 10 轮足够,复杂任务可以到 15 轮。超过这个数还没完成,大概率是任务定义有问题或者工具设计有问题,继续跑也是浪费。
5. Java 生态里做 Agent,框架怎么选
5.1 Spring AI:Spring 老兵的舒适区
如果你是一个重度 Spring 用户,Spring AI 是最自然的选择。它把 Agent 相关的概念都做成了 Spring 风格的抽象:ChatClient负责和模型交互,ToolCallback定义工具,ChatMemory管理记忆。你可以用@Bean注册工具,用@Tool注解标记方法,依赖注入那一套完全复用。
Spring AI 的优势是和现有 Spring 项目无缝集成。你的 Agent 可以直接注入现有的 Service、Repository,不用重新搭一套。对于企业级应用来说,这个优势很大。缺点是它的 Agent 编排能力相对弱一些,复杂的多 Agent 协作、动态规划这些场景,Spring AI 目前支持得还不够成熟,需要自己补不少代码。
5.2 LangChain4j:更接近 Python 生态的体验
LangChain4j 是 LangChain 的 Java 移植版,概念和 Python 版基本对齐:AiServices、Tools、Memory、Chains。如果你看过 Python 的 LangChain 教程,转过来会很快。它的工具定义用注解,@Tool描述方法用途,@P描述参数,框架自动生成 schema。
LangChain4j 的生态比 Spring AI 丰富一些,支持更多的模型提供商和向量库。但它的文档和社区还不如 Spring AI 活跃,遇到问题可能需要翻源码。另外它的版本迭代比较快,API 偶尔会有 breaking change,升级时要小心。
5.3 ADK 与其他 JVM 方案
ADK(Agent Development Kit)是 Google 推出的 Agent 开发套件,有 Kotlin 和 Java 的支持。它的设计理念更偏向“多 Agent 协作”,内置了 Agent 之间的通信、编排、层级结构。如果你要做的是复杂的多 Agent 系统,ADK 的抽象会更合适。
除此之外,还有一些更轻量的选择,比如直接用 HTTP 客户端调模型 API,自己实现循环逻辑。这种方式最灵活,但什么都得自己写,适合对 Agent 原理已经吃透、需要极致定制的人。我的建议是:新手从 Spring AI 或 LangChain4j 入手,把概念跑通;有特殊需求再考虑 ADK 或自研。
| 框架 | 优势 | 劣势 | 适合场景 |
|---|---|---|---|
| Spring AI | 与 Spring 无缝集成,依赖注入友好 | 复杂编排能力弱 | 企业级应用,已有 Spring 体系 |
| LangChain4j | 概念对齐 Python 生态,工具丰富 | 文档社区一般,API 变动快 | 快速原型,参考 Python 教程 |
| ADK | 多 Agent 协作抽象好 | 生态较新,资料少 | 复杂多 Agent 系统 |
| 自研 | 完全可控,无框架约束 | 工作量大,轮子多 | 深度定制,原理已吃透 |
6. 转型路上最容易踩的几个坑
6.1 把 Prompt 当代码写,改一次崩一次
Javaer 习惯把逻辑写在代码里,于是很自然地想把 Agent 的行为逻辑也写死在 Prompt 里,用一堆 if-else 拼字符串。结果就是 Prompt 越来越长,改一个地方影响一片,测试也没法测。
正确的做法是把 Prompt 当成配置来管理:模板文件化、版本化、参数化。系统提示词、工具描述、few-shot 示例,都应该独立于代码,可以单独修改和回归。我一般会把 Prompt 放在 resources 目录下,用模板引擎渲染,每次修改都走 code review,并且准备一组固定的测试用例来验证改动效果。
6.2 工具设计得太“重”,模型用不明白
工具设计是 Agent 效果的关键,但很多人把它当成普通的 API 来设计。一个工具如果参数太多、职责太杂,模型就很难用对。比如你设计一个executeQuery工具,参数是 SQL 字符串,这看起来灵活,实际上很危险——模型可能生成错误的 SQL,甚至搞出注入问题。
好的工具设计原则是单一职责、参数精简、语义明确。与其给一个万能的executeQuery,不如给getOrderById、getOrdersByUser、getTopProducts这样几个专用工具。参数控制在 3 到 5 个以内,每个参数都有清晰的描述。工具的名字和描述要能让模型一眼看懂“什么时候该用它”。
6.3 忽略 Token 成本,月底账单吓一跳
Agent 循环会反复调用模型,每次调用都带上完整上下文,token 消耗是普通对话的好几倍。我见过一个 Agent 处理一个任务烧掉几十万 token 的案例,根因是上下文没有做压缩,历史消息无限累积。
控制成本的手段有几个:限制历史窗口,只保留最近 N 轮对话;压缩工具结果,大结果做摘要或截断;缓存重复调用,相同参数的模型调用可以缓存;选择合适的模型,简单任务用小模型,复杂任务才上大模型。这些手段组合起来,成本能降一个数量级。
6.4 没有可观测性,出问题只能靠猜
Agent 的行为不确定,没有可观测性就是灾难。你必须能回答这些问题:这一轮模型为什么选了这个工具?工具返回了什么?模型看到结果后是怎么想的?如果这些信息没有记录,出问题你只能靠猜。
我的做法是给 Agent 的每一轮循环打结构化日志:轮次编号、模型输入摘要、模型输出、工具调用、工具结果、耗时、token 消耗。这些日志可以接入现有的监控体系,也可以做成一个可视化的 trace 界面。调试的时候,把 trace 拉出来一看,问题一目了然。
7. 从“看懂”到“写出来”:一条可执行的练习路径
7.1 第一个 Agent:从单工具开始
不要一上来就搞多 Agent 协作,先从最简单的开始:一个模型、一个工具、一个循环。比如做一个“天气查询 Agent”,工具是查天气的 API,用户问“北京今天天气怎么样”,Agent 调用工具返回结果。这个练习的目的是把“模型调用—工具解析—工具执行—结果回填—再次调用”这个循环跑通。
跑通之后,加第二个工具,比如“查空气质量”。然后观察模型怎么在两个工具之间做选择。这时候你会遇到第一个真实问题:模型有时候会同时调两个工具,有时候只调一个。这就是 Agent 的不确定性,你要学会接受它并设计相应的处理逻辑。
7.2 第二个 Agent:加入记忆和状态
第一个 Agent 是无状态的,每次对话都是全新的。第二个练习是加入记忆:让 Agent 记住之前的对话内容。这里要处理的核心问题是记忆的存储和检索。短期记忆就是对话历史,直接放在上下文里;长期记忆需要向量化存储,用的时候检索相关片段。
在 Java 里,短期记忆可以用ChatMemory接口实现,长期记忆可以接向量数据库(如 Milvus、PgVector)。这个练习会让你理解“上下文管理”为什么是 Agent 工程的核心难点之一。
7.3 第三个 Agent:处理失败和重试
前两个练习都是“顺利路径”,第三个练习专门处理失败。故意让工具抛异常、返回空结果、返回格式错误的数据,观察 Agent 怎么反应。然后设计容错逻辑:工具失败时把错误信息喂回模型、循环超限时触发兜底、模型输出格式错误时重试。
这个练习最接近真实生产环境。因为线上什么都会发生:API 超时、数据库连接池满、模型返回乱码。你的 Agent 能不能优雅地处理这些,决定了它能不能上生产。
7.4 面试里常问的几个 Agent 问题
如果你在准备 Agent 相关的面试,有几个问题几乎必问。第一个是“Agent 和 Workflow 的区别”,这个前面讲过,核心是决策权归属。第二个是“怎么防止 Agent 死循环”,答案是多层防护:最大轮次限制、重复动作检测、超时中断。第三个是“Agent 的记忆怎么设计”,要分短期和长期,短期用上下文,长期用向量库。第四个是“怎么评估 Agent 的效果”,这个最难,通常要结合人工评估和自动化指标(任务完成率、平均轮次、token 消耗)。
还有一个高频问题是“多 Agent 怎么协作”。常见的模式有:主从模式(一个协调者 Agent 分配任务给执行者 Agent)、流水线模式(Agent 按顺序处理)、辩论模式(多个 Agent 互相质疑)。选哪种取决于任务性质,没有银弹。
8. 我对 Javaer 转 Agent 的一点个人看法
写了这么多,最后说点掏心窝的话。Javaer 转 Agent,最大的障碍不是技术,是心态。Java 生态讲究稳定、可预测、强约束,而 Agent 这个领域恰恰充满了不确定性。模型会犯错、工具会失败、结果不可复现。如果你带着“我要写一个完全可控的系统”的心态进来,会非常痛苦。
我的体会是:把 Agent 当成一个“需要管理的员工”,而不是一个“需要实现的函数”。你不会要求员工每一步都按你的指令走,你会给他目标、给他工具、给他反馈,然后观察他的表现,不断调整。Agent 开发也是一样,你的工作重心从“写逻辑”变成了“设计工具、写提示词、搭观测、调参数”。这个转变不容易,但转过来了,你会发现这是一片全新的、很有意思的领域。
另外,别被那些花哨的概念吓到。什么 multi-agent、agent harness、agent skill,本质上都是在“模型 + 工具 + 循环”这个最小模型上的组合和扩展。把最小模型吃透,剩下的都是排列组合。我见过太多人一上来就研究复杂框架,结果连最基本的工具调用都没跑明白。先把一个最简单的 Agent 跑起来,比看一百篇概念文章都有用。