whatsmars-ai(LangChain4j)| spacecloud(Spring AI 2.0)| babi 系列(AgentScope / LangGraph)

不做框架评测,不写 Hello World。本文记录的是我在三个真实项目中使用三个 Java AI 框架的工程实践——为什么选、怎么用、差异在哪、以及如何通过 MCP 协议让它们跨框架互联。


为什么是三个框架?

2025 年,Java 生态的 AI 集成框架格局基本定格:LangChain4j 走声明式路线,Spring AI 走 Spring 生态融合路线,AgentScope 走 Agent-as-a-Service 路线。三者设计哲学不同,适用场景也不同。

我没有选一个"通吃",而是按技术方向拆分了三个项目,各司其职:

项目 聚焦方向 核心框架 语言 端口
whatsmars-ai AI 框架集成 LangChain4j Java 8887
spacecloud AI + 微服务 Spring AI 2.0 Java 8888/8889
babi 系列 AI Coding Agent AgentScope / LangGraph Java / Python -

这不是为了"都用一遍"而刻意拆分。三个项目的定位本身就不同:whatsmars-ai 探索 LangChain4j 的声明式 AI Service 能做到多优雅;spacecloud 验证 Spring AI 在微服务场景下的工程化能力;babi 系列则面向更上层的 Agent 架构,实践 ReAct 模式的 Coding Agent。

选型的核心逻辑是:用框架擅长的方式做它擅长的事。


三框架设计哲学:声明式、生态融合、Agent-as-a-Service

LangChain4j:声明式 AI Service

LangChain4j 最核心的抽象是 @AiService 接口。你定义一个 Java 接口,加一个 @SystemMessage,框架自动生成代理实现:

@AiService
public interface SimpleAssistant {
    @SystemMessage("你是一个专业的 Java 技术专家,回答要简洁、准确。")
    String chat(String userMessage);
}

不需要手动构建 ChatClient、不需要写 prompt 拼接逻辑、不需要管理请求对象。接口方法的参数就是用户输入,返回值就是 AI 回复。这种"接口即 AI"的设计让 AI 调用看起来像普通的 Java 方法调用。

当你需要更细粒度的控制时,可以切换到编程式 AiServices.builder()

return AiServices.builder(ToolCallingAssistant.class)
        .streamingChatModel(streamingChatModel)
        .tools(timeTool, httpRequestTool, webSearchTool, systemInfoTool)
        .hallucinatedToolNameStrategy(hallucinatedToolNameStrategy)
        .build();

LangChain4j 的设计理念是:让 AI 能力成为 Java 类型系统的一部分。接口签名即 AI 接口契约,编译器帮你检查参数类型,IDE 帮你自动补全。这对 Java 开发者来说是最自然的接入方式。

Spring AI 2.0:生态融合

Spring AI 不造新概念,而是把 AI 能力融入已有的 Spring 编程模型。核心是 ChatClient 的 fluent API:

chatClient.prompt()
    .system("你是一个智能助手...")
    .user(message)
    .tools(timeTool, httpRequestTool, webSearchTool)
    .call()
    .content();

看起来和 LangChain4j 差别不大?真正的差异在架构层面。Spring AI 2.0 把 Tool Calling 提升为 Advisor 链的一环——ToolCallingAdvisor(order +300)和 MessageChatMemoryAdvisor 一样,是 ChatClient 调用链上的一个拦截器:

ChatClient → [ToolCallingAdvisor (+300)] → [自定义 Advisor (+400)] → ChatModel

这意味着 Tool Calling 不再是黑盒,你可以插入自己的 Advisor 来观测、拦截、修改工具调用的全过程。在 spacecloud 中,我写了一个 ToolCallObservationAdvisor 来记录每轮 ReAct 循环的工具调用请求、响应和耗时:

@Override
public AdvisedResponse adviseRequest(AdvisedRequest request, AdvisorChain chain) {
    // 统计当前是第几轮工具调用
    int iteration = countToolResponseMessages(request.messages());
    log.info("ReAct 第 {} 轮: {}", iteration, request.toolContext());
    return chain.next(request);
}

Spring AI 的设计理念是:AI 是 Spring 生态的一等公民,用 Spring 的方式(Bean、Advisor、自动配置)来管理 AI 能力。如果你已经在用 Spring Boot,Spring AI 几乎零学习成本。

AgentScope:Agent-as-a-Service

AgentScope 走了一条完全不同的路。它不关注"如何调用大模型",而是关注"如何构建一个能自主工作的 Agent"。在 babi 系列中,AgentScope Java 的 HarnessAgent 内部实现了完整的 ReAct 循环:

HarnessAgent.builder()
    .name("BabiAgent")
    .model(DashScopeChatModel.builder().stream(true).build())
    .toolkit(toolkit)
    .workspace(workspacePath)
    .maxIters(20)
    .maxRetries(2)
    .fallbackModel("qwen-turbo")
    .enableTaskList()
    .enablePlanMode()
    .middleware(new ContextTruncateMiddleware(30))
    .build();

注意这里没有 .tools() 的手动注册——Read、Write、Edit、Grep、Glob、Bash 这六个文件系统工具是框架内置的,通过 LocalFilesystemSpec 自动提供。你只需要注册业务专属的 4 个自定义工具。

AgentScope 还提供了其他框架没有的 Agent 级能力:Plan Mode(先调查后执行)、Task List(任务拆解)、Model Fallback(主模型失败自动降级)、Middleware Pipeline(中间件链)。这些不是 API 层的封装,而是 Agent 层的基础设施。

AgentScope 的设计理念是:Agent 是一个独立的运行实体,配置好就能自主工作,开发者不应该手写 ReAct 循环。


同一能力,三种实现

理论的对比说完,来看实战。同一个 AI 能力在三个框架里怎么实现。

Tool Calling:注解风格 vs Advisor 链 vs 框架内置

Tool Calling 是三个框架差异最明显的能力。

LangChain4j@Tool + @P 注解,工具是普通 Java 类,通过 AiServices.builder().tools() 注册:

@Component
public class TimeTool {
    @Tool(name = "get_current_date_time")
    public String getCurrentDateTime() { ... }

    @Tool(name = "days_until")
    public String daysUntil(@P("目标日期,格式为 yyyy-MM-dd") String targetDate) { ... }
}

一个亮点:LangChain4j 支持 hallucinatedToolNameStrategy——当 LLM 幻觉出一个不存在的工具名时,不抛异常,而是返回错误信息让 LLM 自我纠正:

@Bean
public Function<ToolExecutionRequest, ToolExecutionResultMessage> hallucinatedToolNameStrategy() {
    return req -> ToolExecutionResultMessage.from(req,
        "错误:没有名为 " + req.name() + " 的工具,请检查可用工具列表");
}

Spring AI 也用 @Tool + @ToolParam 注解,但工具注册是通过 .tools() 方法挂到 ChatClient 上,背后由 ToolCallingAdvisor 驱动:

@Component
public class TimeTool {
    @Tool(name = "get_current_date_time", description = "获取当前的日期和时间")
    public String getCurrentDateTime() { ... }

    @Tool(name = "days_until", description = "计算今天距离目标日期还有多少天")
    public String daysUntil(@ToolParam(description = "目标日期") String targetDate) { ... }
}

// 调用时注册
chatClient.prompt()
    .user(message)
    .tools(timeTool, httpRequestTool, webSearchTool)
    .call()
    .content();

注意一个细节:Spring AI 的 @Tool 注解有独立的 description 字段,而 LangChain4j 的 @Toolvalue 描述。参数注解也不同:@ToolParam vs @P

AgentScope 则完全不同——6 个文件系统工具(Read/Write/Edit/Grep/Glob/Bash)是框架内置的,通过 LocalFilesystemSpec 自动注入,零代码。你只写业务工具:

Toolkit toolkit = Toolkit.builder()
    .with(FileSystemToolkitSpec.defaults())  // 内置 6 个文件系统工具
    .tool(new FetchUrlTool())
    .tool(new HttpRequestTool())
    .tool(new GitHubApiTool())
    .tool(new SkillTool())
    .build();

三种风格的本质差异:LangChain4j 是"接口+注解"的 Java 经典范式;Spring AI 是"Bean+Advisor"的 Spring 原生范式;AgentScope 是"配置即拥有"的框架内置范式。

Chat Memory:从内存到持久化

LangChain4j 的 Chat Memory 通过 @MemoryId 注解实现会话隔离,ChatMemoryStore 接口实现持久化:

@AiService
public interface ChatMemoryAssistant {
    @SystemMessage("你是一个友好的技术助手...")
    TokenStream chat(@MemoryId String sessionId, @UserMessage String message);
}

在 whatsmars-ai 中,我实现了 JpaChatMemoryStore——用 JPA + PostgreSQL 持久化对话历史,每条消息序列化为 JSON 存入 chat_memory 表,按 messageIndex 保序:

@Bean
public ChatMemoryProvider chatMemoryProvider(ChatMemoryStore chatMemoryStore) {
    return memoryId -> MessageWindowChatMemory.builder()
            .id(memoryId)
            .maxMessages(20)
            .chatMemoryStore(chatMemoryStore)
            .build();
}

langchain4j-spring-boot-starter 检测到 ChatMemoryProvider Bean 后,自动注入到所有使用 @MemoryId@AiService 接口中——零配置。

Spring AI 的 Chat Memory 通过 MessageChatMemoryAdvisor 实现,会话 ID 通过 Advisor 参数传递:

this.chatMemory = MessageWindowChatMemory.builder()
        .chatMemoryRepository(new InMemoryChatMemoryRepository())
        .maxMessages(20)
        .build();

this.chatClient = chatClientBuilder
        .defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
        .build();

// 每次请求指定会话 ID
chatClient.prompt()
        .user(userMessage)
        .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId))
        .call()
        .content();

在 spacecloud 的 RAG 模块中,Chat Memory 切换为 JDBC 持久化——只需把 InMemoryChatMemoryRepository 换成自动配置的 JdbcChatMemoryRepository

public ChatMemoryService(ChatClient.Builder chatClientBuilder,
                         ChatMemoryRepository chatMemoryRepository) {  // JDBC 自动注入
    this.chatMemory = MessageWindowChatMemory.builder()
            .chatMemoryRepository(chatMemoryRepository)  // PostgreSQL
            .maxMessages(20)
            .build();
}

同一个接口 ChatMemoryRepository,内存版和 JDBC 版一键切换,这就是 Spring AI 自动配置的优势。

AgentScope 的会话持久化是 Agent 级别的,不是对话级别的。babi-agent 用 JsonFileAgentStateStore 把整个 Agent 状态(包括对话历史)序列化为 JSON 文件,重启后恢复:

.stateStore(new JsonFileAgentStateStore(
        Paths.get(System.getProperty("user.home"), ".babi", "sessions")))

Python 版的 babi-langgraph 用 PostgreSQL Checkpointer,babi-agentscope 用 Redis Storage——不同框架有不同的持久化后端,但都是 Agent 状态级别的,比单纯的 Chat Memory 粒度更大。

RAG:自动注入 vs 手动编排

LangChain4j 的 RAG 是声明式的——@AiService 接口配合 ContentRetriever Bean,框架自动完成"检索→注入上下文→生成回答"的全流程:

@AiService
public interface RagAssistant {
    @SystemMessage("请严格基于检索到的知识库内容回答问题...")
    TokenStream chat(String userMessage);
}

// 配置 ContentRetriever
@Bean
public ContentRetriever contentRetriever(EmbeddingModel model, EmbeddingStore<TextSegment> store) {
    return EmbeddingStoreContentRetriever.builder()
            .embeddingStore(store)
            .embeddingModel(model)
            .maxResults(5)
            .minScore(0.5)
            .build();
}

RagAssistant.chat() 被调用时,LangChain4j 自动 embed 用户查询、搜索向量库、将 top-5 结果注入 prompt——开发者完全不用管 RAG 的流程编排。

Spring AI 的 RAG 则是手动编排的——开发者显式地检索、拼接、生成:

public String query(String question, int topK) {
    List<Document> docs = vectorStore.similaritySearch(
            SearchRequest.builder().query(question).topK(topK).build());
    String context = docs.stream().map(Document::getText).collect(joining("\n\n---\n\n"));
    String prompt = "基于以下参考资料回答:\n" + context + "\n\n问题:" + question;
    return chatClient.prompt().user(prompt).call().content();
}

哪种更好?LangChain4j 的自动注入更简洁,适合标准 RAG 场景;Spring AI 的手动编排更灵活,适合需要对检索结果做二次处理(如重排序、过滤、混合检索)的场景。

流式输出:TokenStream vs Flux vs EventStream

三个框架都支持流式输出,但 API 风格迥异。

LangChain4j 返回 TokenStream 对象,通过回调链消费:

TokenStream stream = assistant.chat(message);
stream.onPartialResponse(token -> emitter.send(token))
      .onCompleteResponse(response -> emitter.complete())
      .onError(emitter::completeWithError)
      .start();

Spring AI 返回 Reactor Flux<String>,直接对接 WebFlux/SSE:

return chatClient.prompt()
        .user(message)
        .stream()
        .content();  // Flux<String>

AgentScope 返回事件流,通过 streamEvents() 迭代:

agent.streamEvents(userMessage)
    .filter(e -> e.type() == EventType.TEXT_BLOCK_DELTA)
    .map(e -> e.delta().text())
    .subscribe(emitter::send);

Spring AI 的 Flux<String> 最简洁,直接返回给 Controller 就是 SSE 接口;LangChain4j 的回调链需要手动桥接到 SSE;AgentScope 的事件流最灵活,可以同时处理文本 token 和工具调用事件。


MCP:让 LangChain4j 和 Spring AI 跨框架互联

前面讲的都是在各自框架内部的能力实现。但三个框架之间不是孤岛——MCP 协议让它们可以跨框架互联。

在我的项目矩阵中,有一条 MCP 通信链:

whatsmars-ai (LangChain4j MCP Client, 端口 8887)
    ↓ Streamable HTTP
whatsmars-mcp (Spring AI MCP Server, 端口 8886)
    暴露工具: get_weather, show_location, plan_route, search_place

MCP Server 端(Spring AI)的配置极其简洁——MethodToolCallbackProvider@Tool 方法自动注册为 MCP 工具:

@Configuration
public class McpServerConfig {
    @Bean
    public ToolCallbackProvider mcpToolProvider(WeatherTool weatherTool, MapTool mapTool) {
        return MethodToolCallbackProvider.builder()
                .toolObjects(weatherTool, mapTool)
                .build();
    }
}

application.yml 指定 Streamable HTTP 协议:

spring:
  ai:
    mcp:
      server:
        name: whatsmars-mcp
        version: 1.0.0
        type: SYNC
        protocol: streamable

MCP Client 端(LangChain4j)通过 StreamableHttpMcpTransport 连接 Server,再用 McpToolProvider 把远程工具适配为 LangChain4j 的工具:

@Bean(destroyMethod = "close")
public McpClient mcpClient() {
    McpTransport transport = StreamableHttpMcpTransport.builder()
            .url("http://localhost:8886/mcp")
            .logRequests(true)
            .logResponses(true)
            .build();
    return new DefaultMcpClient.Builder()
            .key("whatsmars-mcp")
            .transport(transport)
            .build();
}

@Bean
public McpToolProvider mcpToolProvider(McpClient mcpClient) {
    return McpToolProvider.builder().mcpClients(mcpClient).build();
}

@Bean
public McpAssistant mcpAssistant(StreamingChatModel model, McpToolProvider provider) {
    return AiServices.builder(McpAssistant.class)
            .streamingChatModel(model)
            .toolProvider(provider)  // MCP 远程工具作为 LangChain4j 工具
            .build();
}

从 McpAssistant 的视角看,它不知道这些工具是本地的 Java 方法还是远程的 MCP 服务——LangChain4j 的 ToolProvider 接口抹平了这个差异。用户问"北京今天天气怎么样",LangChain4j 的 LLM 决定调用 get_weather 工具,McpToolProvider 通过 HTTP 请求 whatsmars-mcp Server,Spring AI 执行 WeatherTool.getWeather() 方法,结果原路返回。

一份 @Tool 代码,两个框架共享。 这就是 MCP 的价值——不是替代 Tool Calling,而是让 Tool Calling 跨越框架边界。

在 spacecloud 的 cloud-ai-sample 模块中,同样配置了一个 MCP Server(暴露 TimeTool、HttpRequestTool、WebSearchTool),这意味着任何 MCP Client(不管是 LangChain4j、Claude、还是其他 Agent)都可以调用 spacecloud 的工具能力。


选型建议:不要用框架不擅长的方式

经过三个项目的实践,我的选型建议很明确:

选 LangChain4j,如果你追求声明式的优雅。 @AiService 接口让 AI 调用像写 Java 方法一样自然,@MemoryIdContentRetriever 的自动注入让多轮对话和 RAG 几乎零配置。它最适合"AI 能力集成"场景——在你的应用里加一个 AI 对话、RAG 问答、工具调用的模块。whatsmars-ai 就是这种定位。

选 Spring AI,如果你的项目已经在 Spring 生态里。 ChatClient 的 fluent API、Advisor 链的拦截能力、Spring Boot 的自动配置——如果你已经在用 Spring Boot 微服务,Spring AI 是最自然的选择。它的优势不在 API 简洁度(这方面 LangChain4j 更胜一筹),而在和 Spring Cloud、Spring Data、Spring Security 等生态组件的融合。spacecloud 把 AI 能力嵌入 16 个微服务模块中,Spring AI 的生态融合优势在这里体现得淋漓尽致。

选 AgentScope,如果你要构建自主 Agent。 HarnessAgent 内置的 ReAct 循环、Plan Mode、Task List、Model Fallback、Middleware Pipeline——这些是其他两个框架不提供的 Agent 级基础设施。如果你要做的不是"在应用里加 AI 能力",而是"构建一个 AI Agent",AgentScope 让你不用手写 Agent 的底层循环。babi 系列就是这种定位。

不要用框架不擅长的方式。 在 babi-spring(Spring AI 版 Coding Agent)中,我试图用 ChatClient + ToolCallingAdvisor 实现 Coding Agent,但遇到了一个硬伤:Spring AI 在工具调用期间会阻塞流式输出——Agent 在执行工具时,用户看不到任何中间反馈。而 AgentScope 的 streamEvents() 可以同时推送文本 token 和工具调用事件,用户体验完全不同。

这不是 Spring AI 的"bug",而是设计取向的差异:Spring AI 优化的是"在 Spring 应用里调用 AI"的体验,不是"构建自主 Agent"的体验。用 Spring AI 写 Agent,就像用 Spring MVC 写实时游戏服务器——能跑,但不是它擅长的。


技术栈全景

三个项目共享相同的大模型和基础设施:

维度 选型 说明
大模型 通义千问 qwen-plus 通过 DashScope OpenAI 兼容 API 接入
视觉模型 qwen3.7-plus 多模态图片理解
Embedding text-embedding-v3 1024 维,中文语义理解优秀
向量数据库 PostgreSQL + pgvector 关系数据和向量存储共库
MCP 协议 Streamable HTTP 替代早期 SSE,支持有状态会话

不同框架的依赖版本:

框架 版本 所属项目
LangChain4j 1.18.0 whatsmars-ai
Spring AI 2.0.0 spacecloud
AgentScope Java 2.0 babi-agent
LangGraph4J 1.8.20 babi-langgraph4j
Spring Boot 3.5.14 / 4.1.0 whatsmars-ai / spacecloud

项目地址


写在最后

三个框架,三个项目,不是在做"框架评测",而是在用不同的工具解决不同的问题。

LangChain4j 让 AI 调用像 Java 接口一样优雅,Spring AI 让 AI 能力像 Spring Bean 一样融入应用,AgentScope 让 Agent 构建像搭积木一样简单。它们不是竞争关系,而是分工关系。

而 MCP 协议的存在,让这种分工不再是技术债——LangChain4j 的 Client 可以调用 Spring AI 的 Server 暴露的工具,不同框架的 Agent 可以共享工具能力。框架选型不应该是一个非此即彼的决定,而是一个组合拳的设计。

如果你也在 Java 生态做 AI 集成,希望这篇实践记录能给你一些参考。不是告诉你"该选哪个",而是告诉你"每个框架擅长什么"——然后你根据自己的场景做判断。

git clone https://github.com/javahongxi/whatsmars.git    # LangChain4j
git clone https://github.com/javahongxi/spacecloud.git   # Spring AI 2.0
git clone https://github.com/javahongxi/babi.git         # AgentScope / LangGraph

Star ⭐ 你感兴趣的项目,后续会持续更新三个框架的实践。


© hongxi.org | 用三个框架,做三件不同的事

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐