Langchain4j VS SpringAI前情如果要使用Java进行Agent应用开发Langchain4j和SpringAI是绕不过的话题。一个是轻量化Agent框架即开即用。而另一个则需以来Spring框架实现企业化生产。本博客主要讨论的是Langchain4j的核心框架层其中会穿插与SpringAI的对比。Langchain4j核心框架设计Builder模式ChatModel初始化过程使用Builder模式动态代理使用Java动态代理机制实现AiServices接口责任链策略模式创建AiServices实例时使用责任链模式和策略模式组装参数实现LLM交互SPI设计详解Builder模式Langchain的设计方案Builder模式可以说贯穿了整个项目。保障参数正确。以下是Builder实现的核心部分。publicclassDeepseekChatModelimplementsChatModel{privatestaticfinalStringDEFAULT_BASE_URLhttps://api.deepseek.com/v1;privatestaticfinalStringDEFAULT_MODELdeepseek-chat;privatefinalStringbaseUrl;privatefinalStringapiKey;privatefinalStringmodelName;privatefinalDoubletemperature;privatefinalIntegermaxTokens;privatefinalHttpClienthttpClient;privatefinalDurationtimeout;publicstaticDeepseekChatModelBuilderbuilder(){returnnewDeepseekChatModelBuilder();}publicstaticclassDeepseekChatModelBuilder{privateStringbaseUrl;privateStringapiKey;privateStringmodelName;privateDoubletemperature;privateIntegermaxTokens;privateDurationtimeout;publicDeepseekChatModelBuilderbaseUrl(StringbaseUrl){this.baseUrlbaseUrl;returnthis;}publicDeepseekChatModelBuilderapiKey(StringapiKey){this.apiKeyapiKey;returnthis;}publicDeepseekChatModelBuildermodelName(StringmodelName){this.modelNamemodelName;returnthis;}publicDeepseekChatModelBuildertemperature(Doubletemperature){this.temperaturetemperature;returnthis;}publicDeepseekChatModelBuildermaxTokens(IntegermaxTokens){this.maxTokensmaxTokens;returnthis;}publicDeepseekChatModelBuildertimeout(Durationtimeout){this.timeouttimeout;returnthis;}publicDeepseekChatModelbuild(){returnnewDeepseekChatModel(this);}}}第一眼看上去感觉是多此一举但是在Langchain4j中ChatModel的参数不可能一次性准备完毕因此需要一个对象先收集参数等所有参数收集完成后通过Builder设置。这里还有一个点就是Builder一般配合final字段使用。SpringAI的解决方案以下是SpringAI的源码我在其中也找到了相似的设计publicfinalclassOpenAiChatModelimplementsChatModel{privatefinalLogloggerLogFactory.getLog(OpenAiChatModel.class);privatefinalOpenAIClientopenAiClient;privatefinalOpenAIClientAsyncopenAiClientAsync;privatefinalOpenAiChatOptionsoptions;privatefinalObservationRegistryobservationRegistry;privatefinalToolCallingManagertoolCallingManager;privateChatModelObservationConventionobservationConventionDEFAULT_OBSERVATION_CONVENTION;publicstaticBuilderbuilder(){returnnewBuilder();}publicstaticfinalclassBuilder{privateNullableOpenAIClientopenAiClient;privateNullableOpenAIClientAsyncopenAiClientAsync;privateNullableOpenAiChatOptionsoptions;privateNullableToolCallingManagertoolCallingManager;privateNullableObservationRegistryobservationRegistry;privateNullableMeterRegistrymeterRegistry;privateListOpenAiHttpClientBuilderCustomizerhttpClientCustomizersnewArrayList();privateBuilder(){}publicBuilderopenAiClient(OpenAIClientopenAiClient){this.openAiClientopenAiClient;returnthis;}publicBuilderopenAiClientAsync(OpenAIClientAsyncopenAiClientAsync){this.openAiClientAsyncopenAiClientAsync;returnthis;}publicBuilderoptions(OpenAiChatOptionsoptions){this.optionsoptions;returnthis;}Deprecated(since2.0.0,forRemovaltrue)publicBuildertoolCallingManager(ToolCallingManagertoolCallingManager){this.toolCallingManagertoolCallingManager;returnthis;}publicBuilderobservationRegistry(ObservationRegistryobservationRegistry){this.observationRegistryobservationRegistry;returnthis;}publicBuildermeterRegistry(NullableMeterRegistrymeterRegistry){this.meterRegistrymeterRegistry;returnthis;}publicBuilderhttpClientBuilderCustomizer(OpenAiHttpClientBuilderCustomizercustomizer){Assert.notNull(customizer,customizer cannot be null);this.httpClientCustomizers.add(customizer);returnthis;}publicBuilderhttpClientBuilderCustomizers(ListOpenAiHttpClientBuilderCustomizercustomizers){Assert.notNull(customizers,customizers cannot be null);this.httpClientCustomizersnewArrayList(customizers);returnthis;}publicOpenAiChatModelbuild(){// 省略复杂内容}}}虽然上述代码看似复杂实际上也是在做两件事1.收集参数2.设置参数。对比两者在设计思路上高度雷同。值得注意的是无论是Langchain4j还是SpringAIChatModel对象中某些关键属性都使用了final字段。在我看来这些Java框架和LLM想要可靠交互首先要确保的是数据不可变和安全性。而恰好final字段适配这种业务场景。而Builder模式恰好解决final字段不可变的问题。总结回顾整个框架都在大量使用这种模式。不仅仅是要维护LLM交互可靠性在某些业务流程中所需参数也无法一步到位而使用Builder模式这种逐步收集参数一次性设置的模式不仅能够保证数据在初始化前依然保留自由修改的机会也保证了最终数据的不可变性。动态代理使用Langchain的设计方案这个功能是为了Langchain4j声明式AI接口实现的让框架拥有简单调用精简代码的作用。以下是实现方式具体方法为1.创建接口2.初始化ChatModel3.创建AiServices4.与LLM交互。核心思想是将与 LLM 和其他组件交互的复杂性隐藏在一个简单的 API 后面publicinterfaceAssistant{Stringchat(Stringmessage);}// -----------------我是分隔线-----------------AssistantassistantAiServices.create(Assistant.class,chatModel);在翻阅Langchain4j文档后发现AI服务旨在让用户更简洁、更优雅地使用LLM。而这里动态代理则是为此实现的。框架将用户的行为统一拦截并处理统一调用LLM并返回结果。如此实现不仅实现框架透明性同时也让框架能够灵活扩展。以下是在不使用动态代理时的状态// 没有动态代理的繁琐实现classAssistantImplimplementsAssistant{privatefinalChatModelchatModel;privatefinalChatMemorychatMemory;publicStringchat(Stringquestion){// 1. 手动构建消息UserMessageuserMessageUserMessage.from(请回答: question);// 2. 手动管理记忆chatMemory.add(userMessage);ListChatMessagemessageschatMemory.messages();// 3. 手动构建请求ChatRequestrequestChatRequest.builder().messages(messages).build();// 4. 调用模型ChatResponseresponsechatModel.chat(request);// 5. 手动保存结果chatMemory.add(response.aiMessage());// 6. 返回结果returnresponse.aiMessage().text();}}SpringAI的解决方案SpringAI在使用类似Langchain4j这种AI服务接口时可以像以前开发后端项目的样子直接通过Controller执行GetMapping(ai)publicStringgeneration(StringuserInput){returnthis.chatClient.prompt().user(userInput).call().content();}SpringAI将LLM交互的准备工作通过AOP进行处理责任链策略模式Langchain4j的实现上面我们了解了LLM交互业务流程时通过动态代理的方式交由框架处理。其本质就是AIServices的参数逐步组装。可以将整个AI交互业务流程看作一个流水线在流转至每个节点时都有对应的步骤需要执行。// 创建系统消息OptionalSystemMessagesystemMessageprepareSystemMessage(memoryId,method,args);if(context.systemMessageTransformer!null){StringtransformedSystemMessagecontext.systemMessageTransformer.apply(systemMessage.map(SystemMessage::text).orElse(null),invocationContext);systemMessagetransformedSystemMessage!null?Optional.of(SystemMessage.from(transformedSystemMessage)):Optional.empty();}// 用户信息准备varuserMessageTemplategetUserMessageTemplate(memoryId,method,args);varvariablesInternalReflectionVariableResolver.findTemplateVariables(userMessageTemplate,method,args);UserMessageoriginalUserMessageprepareUserMessage(method,args,userMessageTemplate,variables);在查看这些业务流程时每个业务节点都是相对独立的同时又通过责任链模式将业务串联起来。SpringAI的实现SpringAI在设计思路上与Langchain4j相似但是在具体实现过程中划分了领域边界。// 每个环节都有专门的领域对象ChatClientchatClientChatClient.builder(chatModel).advisor(newLoggingAdvisor())// ← 领域对象日志策略.advisor(newChatMemoryAdvisor())// ← 领域对象记忆策略.advisor(newQuestionAnswerAdvisor())// ← 领域对象RAG策略.build();可以看出来SpringAI在执行AI业务流程时流程步骤会比Langchain4j清晰同时代码也会简洁很多。这是由于在SpringAI的设计中每个业务节点都会划分清楚并预留相关上下文。而具体业务实现只需交由当前节点的领域对象处理即可。Advisor本身可以实现为多种模式如MessageChatMemoryAdvisor、ToolCallingAdvisor等。因此可以在处理业务流程时实现不同的策略从而实现LLM交互的自定义。总结在框架策略模式的设计过程中我更倾向于SpringAI的实现方式。在梳理Langchain4j源码过程中发现所有LLM交互的业务逻辑都封装在AIServices中虽然思路是清晰的但是整体代码会变得很复杂。从代码维护方面来看后期如果需要再加入新的业务逻辑那么会使得整体代码更复杂。而SpringAI这种可插拔的处理逻辑可以更灵活地扩展框架功能。思考在对比两个框架后发现其实核心业务逻辑的处理方式都很相似基本都采用了责任链策略模式的设计。深挖后总结了如下的点LLM 是无状态的 → 需要手动管理上下文 LLM 可能幻觉 → 需要 RAG 注入真实数据 LLM 可以调用工具 → 需要工具编排 LLM 返回文本 → 需要结构化解析SPI实现Langchain4j的实现Langchain4j的SPI实现在AIServices中定义了SPI接口在AIServices的实现类中定义了SPI实现类。在AIServices的实现类中通过SPI接口获取SPI实现类并调用其方法。// JSON编解码器privatestaticJsonCodecfactory(){for(JsonCodecFactoryfactory:loadFactories(JsonCodecFactory.class)){returnfactory.create();}returnnewDefaultJsonCodec();// 默认使用 Jackson}对比特性Langchain4jSpringAI扩展机制SPIBuilderBeanAOP配置方式静态工厂方法依赖注入灵活性编译时绑定运行时动态总结框架设计方面框架设计并没有说谁一定就是最好的首先需要侧重的是应用场景。SpringAI背靠Spring这层框架某些功能在实现的过程中相比Langchain4j可能会更优雅。在面对企业级应用开发时SpringAI也预留了丰富的扩展点如工具调用、RAG注入、日志记录等。Langchain4j的开发场景并没有基于Java框架其目标是简化在 Java 应用中集成大语言模型的过程因此迅捷开发很重要。而Langchain4j的做法是将优雅的调用交给用户复杂的业务逻辑交给框架处理。在对比学习完两个框架后我的感受就是不要为了炫技而使用高端的技术或框架而是应该基于自己的业务场景理性选择。对Agent发展的看法扒开框架看底层逻辑其实这些框架都在想办法如何用好LLM。从UserMessage到ToolCalling再到RAG。都是为了保证LLM的输出质量。而现在Agent的发展趋势个人认为应该要向自主学习成长和自我感知决策这个方向发展。让一个Agent真正成为个人助理。