Java 工程师转 AI Agent 最短路径:用 Spring 的思维理解 Spring AI 2.0(附 LangChain4j 对照)

写在前面:先说一件可能让你松口气的事------你不需要转 Python。

我是十年 Java 后端出身,前两篇写的都是 Python 生态(LangGraph、CrewAI、AutoGen 那一套)。评论区和私信里问得最多的一句话是:"我们团队全是 Java,Spring Boot 的老项目,AI Agent 是不是必须重开一个 Python 服务?"

不必须。而且在生产环境里,Java 反而有它的优势------这篇会讲清楚优势在哪、劣势在哪、什么时候该老实回 Python。

但有一件事必须先泼冷水:Spring AI 现在是 2.0.0,只支持 Spring Boot 4.0.x / 4.1.x。 你现在能搜到的中文教程、以及 AI 帮你生成的代码,几乎 100% 是 1.x 的写法,照抄过去大概率编译不过。这篇会把破坏性变更逐条列出来。

这篇的核心不是教你调 API,而是给你一张"翻译表":Agent 世界里的每一个新名词,都能对应到你已经用了十年的 Spring 概念。看完你会发现,你要学的新东西比想象中少得多。

关于验证方式,先声明清楚 :前两篇的代码我都在本地实跑过。这一篇不同------文中所有版本号、类名、方法签名、注解属性,都是我对照 2026 年 8 月的官方文档逐条核对 的,但受限于我当前环境的网络策略(Maven 中央仓库不可达),没有做本地编译验证 。文末我附了一个可直接 mvn compile 的验证工程结构,你可以自己跑一遍再决定信不信。我宁可把这句话写在开头,也不想让你在生产项目里翻车。

目录

  • 一、先给结论:Java 做 Agent,缺什么、不缺什么
  • 二、版本地基(2026.08 核对)
  • 三、核心翻译表:Agent 概念 ↔ 你已经会的 Spring
  • 四、逐个拆解:为什么这么映射
  • 五、多角色协作:Java 侧目前的真实水位
  • 六、Spring AI 2.0 破坏性变更清单
  • 七、什么时候老实回 Python
  • 八、Java 人最容易踩的 11 个坑
  • 附:可自行验证的工程结构
  • 结语

一、先给结论:Java 做 Agent,缺什么、不缺什么

先把话说透,省得你看完一万字才发现方向错了。

1.1 一个很多人没意识到的事实

做一个能上线的 Agent,模型调用只占工作量的 20%。剩下 80% 是什么?

  • 连你的业务库、你的 RPC、你的权限体系
  • 事务边界、连接池、超时、重试、熔断
  • 多租户隔离、审计日志、灰度发布
  • 监控告警、链路追踪、容量规划

这 80% 恰好是 Java 生态过去二十年最擅长的事。 Python 那边现在还在为"怎么把 Agent 塞进一个能扛住流量的服务"发明轮子,而你的 Spring Boot 项目里这些东西早就是标配了。

1.2 那 Java 缺什么

诚实地说,缺两样,而且都很关键:

缺的能力 Python 侧的对应物 Java 侧现状
检查点 / 断点续跑 / 时间旅行 LangGraph 的 Checkpointer 没有对等物。ChatMemory 只是"存消息",不是"存执行状态"
成熟的多智能体编排 LangGraph、AutoGen、CrewAI Spring AI 没有一等公民 ;LangChain4j 有 langchain4j-agentic,但官方标注 experimental

如果你的场景强依赖这两样,别硬扛,老实用 Python 写编排层,Java 那边做业务网关------这是很务实的架构,第七节会展开。

1.3 一句话判断

模型调用围绕你的业务系统转 → 用 Java。业务系统围绕模型编排转 → 用 Python。

大部分企业内部场景(智能客服、工单分派、报表问答、内部知识库、审批助手)都属于前者。


二、版本地基(2026.08 核对)

版本锁定是第一生产力,这是上一篇就说过的。先把地基钉死:

组件 版本 关键约束
Spring AI 2.0.0 只支持 Spring Boot 4.0.x / 4.1.x
Spring AI(旧线) 1.1.8 / 1.0.9 还在维护,Boot 3.x 项目走这条线
LangChain4j 1.19.0 最低 JDK 17
LangChain4j 反应式模块 langchain4j-reactor 1.19.0-beta29 想要 Flux<String> 才需要

Spring AI 2.0 的 Maven 配置:

xml 复制代码
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.ai</groupId>
      <artifactId>spring-ai-bom</artifactId>
      <version>2.0.0</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

Starter 命名规范是 spring-ai-starter-model-{provider},比如 spring-ai-starter-model-openai。接国产模型(DeepSeek / 通义 / Kimi / 智谱)走 OpenAI 兼容协议即可,Spring AI 也有独立的 deepseek 模块。

LangChain4j 的 Maven 配置:

xml 复制代码
<dependency>
  <groupId>dev.langchain4j</groupId>
  <artifactId>langchain4j</artifactId>       <!-- 高层 AiServices API -->
  <version>1.19.0</version>
</dependency>
<dependency>
  <groupId>dev.langchain4j</groupId>
  <artifactId>langchain4j-open-ai</artifactId>  <!-- 具体模型集成 -->
  <version>1.19.0</version>
</dependency>

第一个决策点 :你的项目还在 Spring Boot 3.x 上?那 Spring AI 2.0 你升不上去 。要么走 Spring AI 1.1.x 老线,要么用 LangChain4j(它对 Boot 版本没这么强的绑定)。这个约束往往直接决定选型,建议先查你的 spring-boot-starter-parent 版本再往下看。


三、核心翻译表:Agent 概念 ↔ 你已经会的 Spring

这一节是全文的价值所在,建议单独截图存下来。

Agent 世界的名词 你已经用了十年的东西 说明
ChatClient 流式 API RestClient / WebClient 官方文档明说是照着这两个设计的,.prompt().user().call().content() 就是熟悉的味道
Advisor / AdvisorChain HandlerInterceptor 链 / Servlet Filter 一模一样的责任链模型
advisorChain.nextCall(req) filterChain.doFilter(req, res) 不调它,链就断在这儿
Advisor 实现 Ordered @Order / Ordered 值小的先执行,栈式结构:先处理请求的最后处理响应
BaseAdvisor.before/after @Before / @AfterReturning AOP 的前置/后置通知
@Tool 注解的方法 @RequestMapping 之于 HTTP 都是"把一个方法暴露出去",区别只是调用方从浏览器换成了模型
@ToolParam(required=false) @RequestParam(required=false) 连语义都一样
ToolContext RequestContextHolder / ThreadLocal tenantId 这种隐式参数,且永远不会发给模型
ToolExecutionExceptionProcessor @ControllerAdvice + @ExceptionHandler 统一异常处理,决定"抛出去"还是"转成消息"
returnDirect = true 提前 return,不走后续链路 工具结果直接返回调用方,不再回灌给模型
ChatMemory + conversationId HttpSession + sessionId 会话状态,靠 ID 隔离不同用户
JdbcChatMemoryRepository 你的 DAO 层 就是把会话落库,表结构官方给了
.entity(Person.class) Jackson 反序列化响应体 把模型输出直接映射成 POJO / record
VectorStore 一个"按语义查"的 Repository 心智模型就是 findBySimilarity(...)
ToolCallback HandlerMethod(可反射调用的方法句柄) Spring MVC 内部也是这么描述一个 handler 的
MCP Server / Client 面向模型的 RPC(≈ Dubbo / Feign) 只不过服务发现和调用决策交给了 LLM
Spring AI Observability Micrometer / Actuator 直接出 metrics 和 trace,不用另接一套
LangChain4j AiServices Spring Data JPA Repository / Feign Client / MyBatis Mapper 官方原话:"very similar to Spring Data JPA or Retrofit"
LangChain4j @MemoryId @SessionAttribute / 分片键 按 ID 路由到不同的会话上下文

看完这张表你应该有个感觉:没有一个概念是全新的。 全都是你写业务代码时天天在用的模式,只是换了个名字,服务对象从"人/系统"变成了"模型"。


四、逐个拆解:为什么这么映射

4.1 ChatClient ≈ RestClient

这个不用多讲,看一眼就懂:

java 复制代码
@RestController
class MyController {
    private final ChatClient chatClient;

    MyController(ChatClient.Builder builder) {   // 自动配置好的 prototype Bean
        this.chatClient = builder.build();
    }

    @GetMapping("/ai")
    String ask(String q) {
        return chatClient.prompt()
                .user(q)
                .call()
                .content();
    }
}

构造器注入、Builder、流式链式调用------这段代码你不用学任何 AI 知识就能看懂,这就是 Spring AI 的设计目标。

call() 之后能拿到什么,也和你熟悉的 RestClient 一一对应:

方法 类比
.content() String 响应体
.chatResponse() 完整的 ResponseEntity(带元数据)
.entity(Xxx.class) getForObject(url, Xxx.class)
.entity(new ParameterizedTypeReference<List<X>>(){}) 泛型集合反序列化,连 API 都是同一个类
.stream().content() 返回 Flux<String>,就是 WebFlux

2.0 还给 entity() 加了两个很实用的选项:

java 复制代码
ActorFilms r = chatClient.prompt().user("...")
    .call()
    .entity(ActorFilms.class, spec -> spec
        .useProviderStructuredOutput()   // 用模型原生的结构化输出能力
        .validateSchema());              // 校验不过自动重试

validateSchema() 这个在生产上很值------它把"模型偶尔返回一段不合法 JSON"这个经典问题在框架层解决了,不用你自己写 try-catch-retry。

4.2 Advisor ≈ 拦截器链(本文最重要的一节)

如果只让我留一个映射,就是这个。

Spring AI 的 Advisor 就是拦截器链,源码级别的相似:

java 复制代码
public interface CallAdvisor extends Advisor {
    ChatClientResponse adviseCall(ChatClientRequest request,
                                  CallAdvisorChain chain);
}

public interface Advisor extends Ordered {
    String getName();
}

看到 extends Ordered 了吗?和你写 Filter 时用的 @Order 是同一个东西。值越小越先执行,链条是栈式的:第一个处理请求的,最后一个处理响应。

一个日志 Advisor,和你写过的日志 Filter 长得几乎一样:

java 复制代码
public class SimpleLoggerAdvisor implements CallAdvisor, StreamAdvisor {

    @Override public String getName() { return getClass().getSimpleName(); }
    @Override public int getOrder()   { return 0; }

    @Override
    public ChatClientResponse adviseCall(ChatClientRequest request,
                                         CallAdvisorChain chain) {
        logRequest(request);                                 // ≈ 前置处理
        ChatClientResponse response = chain.nextCall(request); // ≈ filterChain.doFilter()
        logResponse(response);                               // ≈ 后置处理
        return response;
    }
}

chain.nextCall(request) 就是 filterChain.doFilter(req, res)不调它,链就断在这里------这也意味着你可以直接短路返回,做熔断、做缓存、做敏感词拦截,全是你熟悉的套路。

如果你只想写前置/后置逻辑,用 BaseAdvisor 更省事,它把 before / after 拆开了,对应 AOP 的 @Before / @AfterReturning

java 复制代码
public class MyAdvisor implements BaseAdvisor {
    @Override public ChatClientRequest before(ChatClientRequest req, AdvisorChain chain) { ... }
    @Override public ChatClientResponse after(ChatClientResponse res, AdvisorChain chain) { ... }
    @Override public int getOrder() { return 0; }
}

框架自带的 Advisor,本质就是"官方写好的拦截器":

Advisor 干什么 类比
MessageChatMemoryAdvisor 把历史消息塞进请求 从 Session 里读上下文的拦截器
VectorStoreChatMemoryAdvisor 从向量库取记忆 从 Redis 取会话的拦截器
QuestionAnswerAdvisor 朴素 RAG 请求前查一次库再拼参数
RetrievalAugmentationAdvisor 完整模块化 RAG 上面那个的加强版
ToolCallingAdvisor 处理工具调用循环 2.0 起自动注册,见踩坑 ②
SafeGuardAdvisor 内容安全兜底 敏感词过滤器
SimpleLoggerAdvisor 打日志 日志 Filter

注册方式也是熟悉的 Builder:

java 复制代码
ChatClient.builder(chatModel)
    .defaultAdvisors(
        MessageChatMemoryAdvisor.builder(chatMemory).build(),
        QuestionAnswerAdvisor.builder(vectorStore).build())
    .build();

理解到这里,Spring AI 的"高级功能"对你就不神秘了------RAG、记忆、安全、日志,全部是往拦截器链上挂东西。你想加个"每次调用扣配额"的逻辑?写个 Advisor 就行,和你当年写限流 Filter 一模一样。

4.3 @Tool ≈ 把 Service 方法暴露给模型

java 复制代码
class DateTimeTools {

    @Tool(description = "获取用户当前时区的日期和时间")
    String getCurrentDateTime() {
        return LocalDateTime.now()
                .atZone(LocaleContextHolder.getTimeZone().toZoneId())
                .toString();
    }

    @Tool(description = "设置闹钟", returnDirect = true)
    void setAlarm(@ToolParam(description = "ISO-8601 格式的时间") String time) {
        // ...
    }
}

心智模型:@RequestMapping 是把方法暴露给 HTTP 调用方,@Tool 是把方法暴露给模型。

一个重要区别,也是 Java 人最容易忽略的:description 不是注释,它是"接口文档",而且是模型唯一能看到的信息。 你写 @RequestMapping 时可以靠路径名让前端猜出用途,但模型只能读 description。写得含糊,模型就选错工具。

注册方式:

java 复制代码
// 单次请求
chatClient.prompt("明天几号?").tools(new DateTimeTools()).call().content();

// 全局默认
ChatClient.builder(chatModel).defaultTools(new DateTimeTools()).build();

注意 :单次请求的 tools()完全覆盖 defaultTools(),不是追加。这个语义和你以为的可能不一样。

4.4 ToolContext ≈ ThreadLocal(多租户的关键)

这个设计我很喜欢,做过 SaaS 的人一看就懂:

java 复制代码
class CustomerTools {
    @Tool(description = "查询客户信息")
    Customer getCustomerInfo(Long id, ToolContext toolContext) {
        String tenantId = (String) toolContext.getContext().get("tenantId");
        return customerRepository.findById(id, tenantId);
    }
}

chatClient.prompt("查一下 42 号客户")
    .tools(new CustomerTools())
    .toolContext(Map.of("tenantId", "acme"))   // 挂上下文
    .call().content();

关键点:ToolContext 里的数据永远不会发给模型。

这解决了一个真实的安全问题------租户 ID、用户 ID、内部 token 这类东西,你既需要它在工具里可用,又绝对不能让模型看见 (模型看见了就可能在回答里吐出来,或者被提示词注入骗着换一个租户去查)。这就是 RequestContextHolder 的角色,只不过边界更硬。

4.5 工具异常 ≈ @ControllerAdvice

工具报错了怎么办?Spring AI 的答案很 Spring:

java 复制代码
@FunctionalInterface
public interface ToolExecutionExceptionProcessor {
    String process(ToolExecutionException exception);
}

默认行为(DefaultToolExecutionExceptionProcessor):

  • RuntimeException 的 message → 转成文本发回给模型,让它自己调整参数重试
  • 受检异常(IOException 等)→ 直接抛出
  • Error(如 OutOfMemoryError)→ 直接抛出

一行配置切换全局策略:

properties 复制代码
spring.ai.tools.throw-exception-on-error=false   # false=转成消息给模型;true=抛给调用方

这个设计比 Python 那边优雅。LangChain 系要么让你自己写中间件,要么工具报错后模型开始疯狂重试同一个参数。这里直接给了框架级的策略开关。

4.6 ChatMemory ≈ HttpSession

java 复制代码
chatClient.prompt()
    .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, "user-123-session-456"))
    .user(userText)
    .call().content();

conversationId 就是 sessionId。存储实现有内存版、JDBC 版、向量库版,对应你的 Session 存内存 / 存 MySQL / 存 Redis。

Spring AI 2.0 在这里做了一个我认为很正确的破坏性变更 :删掉了 ChatMemory.DEFAULT_CONVERSATION_ID,也删掉了在 Builder 上设 conversationId 的能力,强制你每次调用显式传

为什么正确?因为 1.x 那个默认值就是个定时炸弹------你本地单人测试一切正常,一上线多用户共用同一个 default 会话,所有人的对话串在一起。这个坑我见过不止一次。2.0 用编译期/运行期强制的方式让你没法犯这个错。

4.7 LangChain4j 的 AiServices ≈ Spring Data JPA

现在换 LangChain4j。它的高层 API 叫 AiServices官方文档原话是:"This approach is very similar to Spring Data JPA or Retrofit"------你定义接口,框架用动态代理给你实现。

java 复制代码
interface Assistant {
    @SystemMessage("你是一个礼貌的客服助手。")
    String chat(@MemoryId String userId, @UserMessage String message);
}

Assistant assistant = AiServices.builder(Assistant.class)
        .chatModel(model)
        .chatMemoryProvider(id -> MessageWindowChatMemory.withMaxMessages(10))
        .tools(new OrderTools())
        .contentRetriever(contentRetriever)   // RAG
        .build();

如果你写过 MyBatis 的 Mapper、Feign 的 Client、或者 Spring Data 的 Repository,这个模式对你是零学习成本的。 在 Spring Boot 里有 starter,直接 @Autowired 注入就行,连 AiServices.create() 都不用写。

返回类型的设计也很 Java:

java 复制代码
interface SentimentAnalyzer {
    @UserMessage("下面这段文本情绪是正面的吗?{{it}}")
    boolean isPositive(String text);          // 返回 boolean!
}

interface PriorityAnalyzer {
    @UserMessage("分析这个工单的优先级:{{it}}")
    Priority analyzePriority(String issue);   // 返回枚举!
}

返回 booleanenum 这一手非常关键 ------它意味着你可以把 LLM 直接嵌进普通的 if / switch 里,作为一个"有点智能的判断函数",而不是非要把整个应用改造成 Agent。这是很多团队落地的最佳切入点:先用 LLM 替换掉一两个规则难写的判断,而不是一上来重构架构。

想拿元数据就包一层 Result<T>

java 复制代码
Result<List<String>> result = assistant.generateOutlineFor("Java");
List<String> outline   = result.content();
TokenUsage  usage      = result.tokenUsage();      // 算成本用
List<Content> sources  = result.sources();         // RAG 命中了哪些片段
List<ToolExecution> tx = result.toolExecutions();  // 调了哪些工具

tokenUsage()toolExecutions() 直接暴露出来,做成本核算和审计非常方便,这点比 Spring AI 顺手。


五、多角色协作:Java 侧目前的真实水位

上一篇整篇在讲多智能体,这里必须给 Java 人一个诚实的现状。

5.1 Spring AI:没有一等公民

Spring AI 官方文档有一页叫《Building Effective Agents》,覆盖了 Anthropic 提出的五种模式:

模式 官方给的类
Chain Workflow(链式) ChainWorkflow
Parallelization(并行) ParallelizationWorkflow
Routing(路由) RoutingWorkflow
Orchestrator-Workers(编排者-工人) OrchestratorWorkersWorkflow
Evaluator-Optimizer(评估-优化循环) EvaluatorOptimizerWorkflow

但请注意:这些不是框架里的 API,而是示例仓库(spring-ai-examples/agentic-patterns)里的手写类。 你看一眼 ChainWorkflow 的实现就明白了:

java 复制代码
public String chain(String userInput) {
    String response = userInput;
    for (String prompt : systemPrompts) {
        response = chatClient.prompt(String.format("{%s}\n{%s}", prompt, response))
                             .call().content();
    }
    return response;
}

一个 for 循环。这不是贬低------恰恰相反,它诚实地告诉你:所谓"链式多 Agent",本质就是一个 for 循环。 你完全有能力自己写,而且写出来的可控性比黑盒框架更好。

官方那页的"Future Work"里明确写着模式组合、MCP 集成、高级记忆管理还在路上。所以现状是:Spring AI 给你打好了单 Agent 的地基,多角色编排要你自己搭。

5.2 LangChain4j:有 langchain4j-agentic,但标注实验性

LangChain4j 走得更远一些,有一个专门的 langchain4j-agentic 模块:

java 复制代码
CreativeWriter writer = AgenticServices.agentBuilder(CreativeWriter.class)
        .chatModel(BASE_MODEL)
        .outputKey("story")
        .build();

UntypedAgent novelCreator = AgenticServices.sequenceBuilder()
        .subAgents(writer, audienceEditor, styleEditor)
        .outputKey("story")
        .build();

String story = (String) novelCreator.invoke(Map.of(
        "topic", "dragons and wizards",
        "style", "fantasy",
        "audience", "young adults"));

提供的构建器有 sequenceBuilder(顺序)、parallelBuilder(并行)、loopBuilder(循环)、conditionalBuilder(条件)、parallelMapperBuilder,另外还有 SupervisorAgent(主管制)和一套注解(@Agent@ChatModelSupplier@PlannerSupplier@SupervisorRequest)。

角色之间靠 AgenticScope 共享变量 传值------用 outputKey 写入,用 @V("story") 读出。这个心智模型和上一篇里 Google ADK 的 output_key + {key} 插值几乎一样。

但官方文档开篇就写着:"the whole module has to be considered experimental and is subject to change in future releases." 翻译成人话:随时可能改 API。

5.3 结论

你的需求 建议
单 Agent + 工具 + RAG + 记忆 Java 完全够用,Spring AI 2.0 首选
固定流程的多步编排 Java 够用,自己写 for 循环 / 状态机,比黑盒框架可控
动态多角色、断点续跑、人工审批、时间旅行 老实用 Python(LangGraph),Java 侧还没有对等物

六、Spring AI 2.0 破坏性变更清单

这一节可能是全文对你最实用的部分。 网上的教程、以及 AI 生成的代码,绝大多数还是 1.x 写法。以下是升级到 2.0 时会让你编译不过或行为变化的地方,逐条对照:

6.1 工具注册方式变了(影响最大)

@Bean + @Description 注册 Function 的方式被移除 了(SpringBeanToolCallbackResolvertoolNames() 都没了):

java 复制代码
// ❌ 1.x 写法,2.0 不再工作
@Bean
@Description("Get the weather in location")
Function<WeatherRequest, WeatherResponse> currentWeather() {
    return weatherService::getWeather;
}

// ✅ 2.0 写法
@Bean
ToolCallback currentWeather() {
    return FunctionToolCallback.builder("currentWeather", weatherService::getWeather)
            .description("Get the weather in location")
            .inputType(WeatherRequest.class)
            .build();
}

6.2 ToolCallingAdvisor 现在自动注册

java 复制代码
// ❌ 2.0 里再手动注册会产生重复
chatClient.prompt("...").tools(weatherTool)
    .advisors(ToolCallingAdvisor.builder().build())
    .call().content();

// ✅ 直接用就行
chatClient.prompt("...").tools(weatherTool).call().content();

6.3 conversationId 必须显式传

见 4.6。ChatMemory.DEFAULT_CONVERSATION_ID 常量和 .conversationId() Builder 方法都被删了。另外 PromptChatMemoryAdvisor 被移除 ,统一用 MessageChatMemoryAdvisor

6.4 options() 现在要传 Builder,不是构建好的对象

java 复制代码
// ❌ 1.x
ChatOptions opts = AnthropicChatOptions.builder().maxTokens(100).build();
chatClient.prompt("...").options(opts).call().content();

// ✅ 2.0:不要 .build()
chatClient.prompt("...")
    .options(AnthropicChatOptions.builder().maxTokens(100).temperature(0.7))
    .call().content();

6.5 配置属性拍平了,去掉 .options 前缀

properties 复制代码
# ❌ 1.x
spring.ai.openai.embedding.options.model=text-embedding-3-small
# ✅ 2.0
spring.ai.openai.embedding.model=text-embedding-3-small

对应的 Java 代码 properties.getOptions().getModel()properties.getModel()

6.6 Options 变成不可变

copy()fromOptions() 被移除,改用 mutate();getter 返回的集合改动会抛 UnsupportedOperationException

java 复制代码
// ❌ OllamaChatOptions o = original.copy(); o.setFoo("...");
// ✅
OllamaChatOptions o = original.mutate().foo("...").build();

6.7 默认 temperature 0.7 取消了

2.0 不再统一给 0.7,用各家 provider 的原生默认值。如果你的提示词是按 0.7 调优的,升级后输出会变------需要显式配置:

properties 复制代码
spring.ai.openai.chat.temperature=0.7

6.8 Anthropic 模块换成官方 SDK

底层从自研 RestClient 换成了 com.anthropic:anthropic-javaAnthropicApi 等类被移除,构造方式改为 Builder。并且默认 maxTokens 从 500 变成了 4096------如果你之前依赖那个 500 的默认值来控成本,升级后账单会涨。

6.9 JDBC ChatMemory 表结构变了

新增 sequence_id BIGINT 列用于确定性排序,需要手动执行迁移 SQL(官方给了 PostgreSQL 版本):

sql 复制代码
ALTER TABLE SPRING_AI_CHAT_MEMORY ADD COLUMN sequence_id BIGINT;
-- 回填 + NOT NULL + 建索引,详见官方 Upgrade Notes

6.10 MCP 注解全部换包

java 复制代码
// ❌ org.springaicommunity.mcp.annotation.McpTool
// ✅ org.springframework.ai.mcp.annotation.McpTool

MCP 传输模块的 groupId 也从 io.modelcontextprotocol.sdk 改成了 org.springframework.ai

6.11 好消息:官方提供 OpenRewrite 自动迁移

不用全手改:

bash 复制代码
mvn org.openrewrite.maven:rewrite-maven-plugin:6.32.0:run \
  -Drewrite.configLocation=https://raw.githubusercontent.com/spring-projects/spring-ai/refs/heads/main/src/rewrite/migrate-to-2-0-0-M3.yaml \
  -Drewrite.activeRecipes=org.springframework.ai.migration.M3MigrateMcpAnnotations \
  -Dmaven.compiler.failOnError=false

七、什么时候老实回 Python

这一节我尽量不带立场。

7.1 该用 Java 的信号

  • Agent 需要大量调用你现有的业务系统(数据库、RPC、内部 API)
  • 需要事务、权限、多租户、审计这些企业级能力
  • 团队是 Java 团队,运维体系、监控、发布流程都是围绕 JVM 建的
  • 场景是"给现有系统加一层智能入口",而不是"从零做一个 AI 产品"

7.2 该用 Python 的信号

  • 需要检查点、断点续跑、时间旅行 (LangGraph 的 Checkpointer,Java 侧没有对等物)
  • 需要成熟的动态多角色编排(见第五节)
  • 需要人工审批(HITL)中断再恢复------这依赖检查点能力
  • 需要跟上最新的研究型能力(各种 retriever、评测框架、新范式),Python 生态永远快半年到一年

7.3 我实际会推荐的架构

大多数企业其实不用二选一:

复制代码
[ Spring Boot 业务系统 ]                    ← 权限 / 事务 / 业务数据 / 现有 RPC
        │
        │  MCP(面向模型的 RPC)
        ↓
[ Python 编排层(LangGraph 等) ]           ← 只做多角色编排、状态机、检查点

用 MCP 做两边的边界:Java 侧把业务能力包装成 MCP Server(Spring AI 2.0 的 MCP 支持相当完整),Python 侧做编排逻辑当 MCP Client。

这样各自发挥优势:Java 管"能做什么、谁能做、做了要留痕",Python 管"什么时候做、按什么顺序做"。而且哪天 Java 侧的编排能力成熟了,你把 Python 那层换掉就行,业务能力层一行不用动。


八、Java 人最容易踩的 11 个坑

  1. Spring AI 2.0 只支持 Spring Boot 4.0.x / 4.1.x。Boot 3.x 项目升不上去,走 1.1.x 老线或改用 LangChain4j
  2. @Bean + @Description 注册工具的方式在 2.0 被移除 ,改用 FunctionToolCallback.builder(...)
  3. ToolCallingAdvisor 2.0 起自动注册,再手动注册会重复
  4. conversationId 必须每次显式传DEFAULT_CONVERSATION_ID 已删除(这其实救了你,见 4.6)
  5. .options() 要传 Builder 而不是 .build() 后的对象 ,写惯 1.x 的手会自动多打一个 .build()
  6. 默认 temperature 0.7 取消了,升级后模型输出风格会变,别以为是提示词的问题
  7. Anthropic 默认 maxTokens 从 500 变成 4096,成本会涨
  8. 单次 tools() 会完全覆盖 defaultTools(),不是追加
  9. @Tooldescription 是给模型看的接口文档,不是注释。写含糊模型就选错工具
  10. LangChain4j:同一个 @MemoryId 不能并发调用 。官方明确说了框架不做并发保护,会污染 ChatMemory------Java 人写惯了线程安全的 Service,这里特别容易翻车。建议按 memoryId 加分布式锁或串行化队列
  11. LangChain4j:没开 -parameters 编译选项时 @V 注解必填 。Spring Boot 项目默认开了,纯 Maven 项目要自己在 maven-compiler-plugin 里加

附:可自行验证的工程结构

前面说过这篇没做本地编译验证,所以给你一个能自己验证的最小工程。建议先跑通再决定要不要在项目里推

复制代码
java-agent-demo/
├── pom.xml                      # spring-ai-bom 2.0.0 + Spring Boot 4.x
└── src/main/java/com/example/
    ├── DemoApplication.java
    ├── config/ChatClientConfig.java      # defaultAdvisors / defaultTools
    ├── advisor/BudgetGuardAdvisor.java   # 自己写的"配额熔断"拦截器
    ├── tools/OrderTools.java             # @Tool + ToolContext 多租户
    └── controller/AskController.java
bash 复制代码
mvn -q compile     # 只要能编译过,说明本文所有 API 签名都对得上
mvn spring-boot:run

BudgetGuardAdvisor 是我推荐每个人第一个动手写的 Advisor------它只有二十几行,但写完你就彻底理解拦截器链了,而且是生产上真正需要的东西(防止某个会话把 token 烧穿):

java 复制代码
public class BudgetGuardAdvisor implements CallAdvisor {
    private final int maxTokens;
    private final AtomicInteger used = new AtomicInteger();

    @Override public String getName() { return "budgetGuard"; }
    @Override public int getOrder()   { return Ordered.HIGHEST_PRECEDENCE; }

    @Override
    public ChatClientResponse adviseCall(ChatClientRequest req, CallAdvisorChain chain) {
        if (used.get() > maxTokens) {
            throw new IllegalStateException("预算熔断:已消耗 " + used.get() + " tokens");
        }
        ChatClientResponse res = chain.nextCall(req);
        // 从 res 的 usage 元数据里累加,具体字段以你使用的 provider 为准
        return res;
    }
}

结语

如果只让我留一句话给正在观望的 Java 工程师:

你不缺学 Agent 的能力,你缺的只是一张翻译表。

Advisor 就是拦截器,@Tool 就是把 Service 方法暴露出去,ChatMemory 就是 Session,AiServices 就是 Mapper。这些模式你已经用了十年,只是这次调用方从人变成了模型。

真正需要重新学的只有三件事,而且都不是 Java 知识:

  1. description 是写给模型看的接口文档,含糊了模型就选错------这是产品思维,不是编程思维
  2. 模型的输出是不确定的 ,所以要有 validateSchema()、要有重试、要有护栏------这是你写幂等接口时的思维,只是强度要拉满
  3. token 是会烧钱的,每一次多余的上下文都在花钱------这是你做性能优化时的思维,换了个计量单位

最后提醒一次:先去查你项目的 Spring Boot 版本。 如果还在 3.x,Spring AI 2.0 这条路今天走不通,别浪费一周才发现。

下一篇我打算写**《用 Spring AI 把老项目包装成 MCP Server》**------怎么让你那套写了五年的业务接口,一行业务代码不改,直接变成模型能调用的能力。这是我认为 Java 团队接入 AI 最实际、ROI 最高的一条路。

觉得有用的话点个赞 + 收藏。评论区聊聊你们团队现在卡在哪一步------是 Boot 版本升不上去,还是没想清楚该从哪个场景切入。

相关推荐
AI人工智能+电脑小能手1 小时前
大白话说Java设计模式-26-策略模式(业务实战篇)
java·spring·设计模式·策略模式·支付系统·算法切换
不是光头 强1 小时前
Java 后端 AI 技术选型与学习路线
java·人工智能·学习
liutao8412041 小时前
TrainEye源码解读-01. 登录认证 · 前端篇
人工智能
船厂电气自动化ai大模型1 小时前
AI大模型与数学第42课:泰勒级数完整展开(神经网络近似核心)
人工智能·python·深度学习·算法·机器学习
老郑聊AI业财智造1 小时前
Qwen技术架构与源码深度剖析
人工智能·语言模型·架构·系统架构·软件工程
甲维斯1 小时前
Opus5自主解决Qwen3.8 27B本地接入Claude Code的BUG!
人工智能
晴天161 小时前
智能体 Harness 工程指南-Day24
人工智能
格林威1 小时前
C#图像像素放大:邻域平均、双线性插值实现像素放大的C#实现代码
开发语言·人工智能·数码相机·计算机视觉·c#·视觉检测·工业相机
阿里云大数据AI技术1 小时前
一句话即可用好 MaxCompute:AI 全能搭子 MaxAgent 来了——会运维、能分析
人工智能·agent
野小生2 小时前
从零搭建越用越聪明的 AI 第二大脑:Claude Code + Obsidian 9 步实战
人工智能