ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

LangChain4j官方文档实战:Java大模型应用开发核心指南

LangChain4j官方文档实战:Java大模型应用开发核心指南

1. 为什么从官方文档入手?先看它到底在教你怎么用

这半年我在几个 Java 项目里深入用了 LangChain4j,也翻了不少网上教程,最后发现最有价值的反而是官方文档本身。标题里特意强调“从官方文档入手”,是因为 LangChain4j 这个 Java 生态的 LLM 应用框架,官方文档基本是按照“先跑通、再理解”的思路写的,每一节都带可执行示例,而不是丢给你一堆抽象概念。你跟着文档走一遍,就能清楚看见它在模型接入、消息流转、记忆管理、工具调用这些环节上到底替开发者做了什么。

我之前也试过完全不靠框架,直接封装 HTTP 调用大模型接口。最初一两个接口还好,一旦要加入流式响应、多轮记忆、工具调用、知识库检索,自己维护的代码就开始失控。LangChain4j 的价值恰恰是把这些高频场景抽象成统一的 Java API,你用的时候不用关心底层 JSON 结构、请求协议、回调拼接这些琐碎事。这也决定了这篇文章的定位:照着官方文档的主线,把“具体使用”这层内容讲透,让想上手的人少走弯路。

1.1 官方文档的组织逻辑:它不是在讲概念,而是在给你画路径

打开 LangChain4j 官方文档,你会发现它的目录结构非常清晰,不是罗列 API,而是按使用路径组织:

  • Getting Started:解决依赖怎么引入、第一个例子怎么跑起来。
  • Models:各厂商模型的接入方式,OpenAI、Mistral、Ollama 等都有对应模块。
  • Core Abstractions:消息对象、记忆、输出解析这些核心抽象。
  • Advanced / RAG:工具调用、检索增强、向量存储等进阶能力。

这个组织方式有一个好处:你不需要在一开始就搞懂全部内容,只看当前业务需要的那一节就行。比如你只想做一个最普通的问答接口,看完 Getting Started 就能交差;如果你想做客服机器人,再去翻 ChatMemory 和 AiServices 的部分。官方文档几乎每处都给出最小可运行代码,这种“例子驱动”的写法,比市面上大多数零散博客要可靠得多,至少 API 是跟着当前版本同步的。

从我带过的新人经验来看,最容易犯的错误是去网上复制一段几个月前的代码,然后发现编译都过不去。原因很简单,LangChain4j 进化速度很快,Builder 方法名、包路径、类命名都可能变动。所以我现在有个习惯:以官方文档当前版本为准,先跑示例,再修改成自己的业务场景。这样踩坑概率大幅下降。

1.2 模块化设计:一张表看懂该引入哪些依赖

LangChain4j 不是一个大而全的 jar,而是把能力拆成了很多模块。你只引入自己需要的部分,能明显控制依赖体积,也让项目结构更清爽。这也是官方文档一直在强调的思路。

使用场景依赖模块说明
核心基础langchain4j必选,提供消息、记忆、输出解析等核心 API
OpenAI 兼容模型langchain4j-open-ai支持 OpenAI 系列接口的模型接入
本地大模型langchain4j-ollama对接 Ollama 本地模型服务
向量计算langchain4j-embeddings内置 Embedding 模型相关支持
开箱即用 RAGlangchain4j-easy-rag官方封装好的简易检索增强链路
Spring Boot 整合langchain4j-spring-boot-starter通过配置文件自动装配相关 Bean

我在项目里最常搭配的是langchain4j加langchain4j-open-ai,如果是 Spring Boot 项目,再补一个 starter。还有一点需要特别提醒:不同模块之间的版本必须保持一致。我见过最典型的案例,是核心依赖是 0.35 版,open-ai 模块却用了 1.0 版,结果运行时直接抛 NoSuchMethodError。这类问题排查起来很费时间,最好在 pom 里用同一个版本属性统一管理。

2. 第一个能跑的示例:ChatLanguageModel 怎么用

具体使用当然要从模型接入开始。LangChain4j 把所有模型调用统一收敛在ChatLanguageModel接口后面,只要你拿到一个实现了这个接口的对象,后续业务代码几乎不用区分底层是哪家模型。这种抽象能力,正是从官方文档的 Getting Started 一步步引申出来的。

2.1 Maven 项目引入依赖的最小配置

先准备一个 Java 17 以上的项目,Maven 坐标非常简单。以 OpenAI 兼容接口为例,pom.xml 里加入两块依赖:

<properties> <langchain4j.version>1.0.0-beta1</langchain4j.version> </properties> <dependencies> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j</artifactId> <version>${langchain4j.version}</version> </dependency> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-open-ai</artifactId> <version>${langchain4j.version}</version> </dependency> </dependencies>

这里有个细节值得展开。langchain4j本身是核心抽象,不绑定具体模型厂商;langchain4j-open-ai才提供了 OpenAI 兼容接口的客户端实现。也就是说,核心代码里只依赖抽象层,模型实现通过模块动态接入。这个设计让后续换模型变得非常容易,官方文档里也花了篇幅讲解这个模式,因为它是整个框架可扩展性的根基。

我建议在 properties 里单独维护一个版本号,后续升级时只改一处。不要直接写死版本散落各处,否则升级依赖时很容易遗漏。

2.2 同步调用:先理解 generate 到底做了什么

官方文档登场频率最高的代码就是ChatLanguageModel的generate方法。表面上看就是一句调用,实际上框架帮你做了大量工作。

import dev.langchain4j.model.chat.ChatLanguageModel; import dev.langchain4j.model.openai.OpenAiChatModel; ChatLanguageModel model = OpenAiChatModel.builder() .apiKey("your-api-key") .modelName("gpt-4o-mini") .logRequests(true) .logResponses(true) .build(); String answer = model.generate("用一句话解释什么是 LangChain4j"); System.out.println(answer);

我自己第一次跑通这个示例的时候,最大的收获不是“会调接口了”,而是理解了模型服务地址、API Key、模型名这些参数是如何通过 Builder 装配的。而且logRequests(true)和logResponses(true)这两个开关特别适合调试阶段,它们会把你实际发送的 Prompt 内容和模型返回的原始结果打印出来,让你清楚看到框架组装请求的具体形态。

很多新手会忽略一个点:generate方法接收的不一定只能是普通字符串;你传入的文本最终会被包装成一条UserMessage。当你后续需要同时传入系统指令、历史消息时,这个认知就显得格外重要。换句话说,理解消息结构是进阶使用的前提,而官方文档恰恰把这条主线安排得很清晰。

2.3 流式输出:对话场景的标配

做聊天类应用,等模型全部生成完再一次性返回,体验会很差。这里就必须用StreamingChatLanguageModel。官方文档的示例也很直观:

import dev.langchain4j.model.chat.StreamingChatLanguageModel; import dev.langchain4j.model.chat.StreamingResponseHandler; import dev.langchain4j.model.output.Response; import dev.langchain4j.model.openai.OpenAiStreamingChatModel; StreamingChatLanguageModel streamModel = OpenAiStreamingChatModel.builder() .apiKey("your-api-key") .modelName("gpt-4o-mini") .build(); streamModel.generate("写一段 200 字的产品介绍", new StreamingResponseHandler<>() { @Override public void onNext(String token) { System.out.print(token); } @Override public void onComplete(Response<AiMessage> response) { System.out.println("本次请求 Token 用量:" + response.tokenUsage().totalTokenCount()); } @Override public void onError(Throwable error) { error.printStackTrace(); } });

流式接口的核心是回调机制。onNext会在模型生成每个片段时触发,onComplete在全部生成完成后触发,onError处理异常。这里最容易被忽略的是:onNext的增量不一定是完整单词,可能只是 token 级别的片段,前端拼接时要注意连续性,避免出现“每个字都渲染一遍”的重复闪烁问题。

我在实际项目中几乎一律使用流式接口,即使用户不需要逐字展示,我也会在服务端先聚合完整结果再返回。原因很简单:流式接口能拿到实时的生成进度,后续如果要加“停止生成”按钮、Token 统计、耗时统计,实现起来都省事很多。

3. 官方文档里真正值得反复琢磨的核心抽象

LangChain4j 文档用了大量篇幅讲消息、记忆和输出解析。很多人一开始只关注“怎么调通模型”,等到做多轮对话时才发现上下文管理是个大问题。这部分的抽象设计,决定了你的应用到底是一个能持续对话的助手,还是一个单轮问答工具。

3.1 消息体系:为什么不是直接传字符串

官方文档把消息分为UserMessage、AiMessage、SystemMessage,工具调用场景下还有ToolMessage。为什么要设计这一层消息对象?

因为大模型理解的上下文本质上是一串带角色的消息序列。如果你只传一个字符串,框架不清楚这段内容是用户提问、系统指令还是历史回答;当需要构造带系统约束的多轮对话时,角色信息必须显式表达。看下面这个例子:

import dev.langchain4j.data.message.ChatMessage; import dev.langchain4j.data.message.SystemMessage; import dev.langchain4j.data.message.UserMessage; import dev.langchain4j.data.message.AiMessage; List<ChatMessage> messages = List.of( SystemMessage.from("你是一名严谨的 Java 技术顾问,回答必须给出原因分析。"), UserMessage.from("为什么我调用流式接口后 onNext 顺序偶尔变化?"), AiMessage.from("大概率是服务端分片导致的,建议检查网络链路。"), UserMessage.from("那有什么优化办法?") ); Response<AiMessage> response = model.generate(messages);

这段代码还原了“上下文接力”的形态。但我必须强调一个我踩过的坑:上面代码里的AiMessage是我手工构造的字符串,真正的生产场景里,这个AiMessage必须来自模型上一轮返回的原始对象,而不是自己拼文字。因为模型在理解上下文时,会把角色、内容、Token 用量等信息都纳入考量,手工构造容易让后续轮次的理解产生偏差。

这个设计看起来简单,实际上是整个多轮对话的地基。你一旦理解了“上下文 = 一组带角色的消息列表”,后面再用ChatMemory就会觉得非常顺理成章。

3.2 ChatMemory 与滑动窗口:多轮对话不再需要手工拼消息

如果每次请求都自己维护一个List<ChatMessage>,代码会越来越啰嗦,而且还要自己处理“只保留最近多少条”的逻辑。官方文档为此提供了一套记忆抽象,最常用的是MessageWindowChatMemory。

import dev.langchain4j.memory.ChatMemory; import dev.langchain4j.memory.chat.MessageWindowChatMemory; ChatMemory chatMemory = MessageWindowChatMemory.builder() .maxMessages(10) .build(); chatMemory.add(UserMessage.from("你好,帮我推荐一个 Java 单元测试框架")); chatMemory.add(model.generate(chatMemory.messages()).aiMessage()); chatMemory.add(UserMessage.from("它和另一个常用框架相比有什么优势?")); Response<AiMessage> answer = model.generate(chatMemory.messages());

MessageWindowChatMemory做的事情很朴素:内部维护一个消息列表,设置一个最大消息条数,超过就丢弃旧消息。注意,它只按“条数”裁剪,不理解消息重要程度。如果你的每轮消息里还夹带了长篇检索结果,窗口很容易被无意义内容占满。

我个人的经验是:长期记忆只存用户意图和结论性的消息,像文档检索片段、临时上下文这类内容应该直接拼进当次请求,而不是进入记忆窗口。调大maxMessages确实能记住更多内容,但每次请求携带的 Token 会同步增加,响应时间和费用都上去了。通用问答场景下设置 10 到 20 条比较合理,具体要看你的业务复杂度。

3.3 结构化输出:别再用字符串解析 JSON

模型返回的是自然语言,但业务系统通常需要“可用的数据”。官方文档里有一个模块叫AiServices,配合接口方法和 Java record,能直接把模型输出映射成结构化对象。这是我认为 LangChain4j 最实用的能力之一。

import dev.langchain4j.service.AiServices; import dev.langchain4j.service.SystemMessage; import dev.langchain4j.service.UserMessage; interface Assistant { @SystemMessage("抽取用户描述中的关键信息,以给定结构返回。") UserInfo extractUser(@UserMessage String text); } record UserInfo(String name, int age, String city) {} Assistant assistant = AiServices.builder(Assistant.class) .chatLanguageModel(model) .build(); UserInfo info = assistant.extractUser("我叫小李,今年二十八岁,住在杭州。");

代码执行后,info.name()大概率返回“小李”,info.age()返回 28。官方文档之所以推荐 record,是因为它的字段不可变、序列化友好,天然适合承载结构化的模型输出。如果你希望某些字段缺失时给默认值,可以在 record 里直接定义默认值,同时在系统提示词里说明规则,比如“如果城市缺失,填空字符串”。

使用AiServices的好处是,你不必自己去解析模型返回的 JSON 字符串,错误处理也简单得多。但要注意,不同模型对 JSON 输出的稳定性差异很大。有的模型在复杂指令下偶尔会返回 Markdown 包裹的 JSON,解析就会失败。我处理这类问题的方法是:在系统提示词里强制声明“只返回 JSON,不要用 Markdown 代码块”,并在业务侧做好兜底解析。

3.4 组合实战:一个带记忆的聊天 Agent 骨架

把消息、记忆和模型串起来,就能拼出一个最简单的聊天 Agent。官方文档虽然没有直接给你一个“保姆级”最终的骨架代码,但把前面这些模块组合起来,正是它引导的方向。

ChatMemory memory = MessageWindowChatMemory.builder() .maxMessages(20) .build(); String userText = "帮我记录一下明天上午十点开会"; memory.add(UserMessage.from(userText)); Response<AiMessage> response = model.generate(memory.messages()); AiMessage aiMessage = response.aiMessage(); memory.add(aiMessage); // 下一轮用户继续提问时,直接重新调用 generate String userText2 = "会议主题是什么来着?"; memory.add(UserMessage.from(userText2)); Response<AiMessage> response2 = model.generate(memory.messages()); System.out.println(response2.aiMessage().text());

这段代码虽然简单,但它体现了一个完整的闭环逻辑:用户输入进入记忆,模型基于历史消息作答,回答再写回记忆,如此循环。我建议你第一次做多轮对话项目时,先把这段跑通,再往里面加工具调用或检索增强,否则容易在调试时搞不清问题出在记忆还是出在模型。这也是我看了官方文档之后总结出来的一个学习路径:先串骨架,再逐个换零件。

4. 工具调用与 RAG:文档里的进阶用法这样落地

工具调用和检索增强(RAG)是 LangChain4j 区别于普通 HTTP Client 封装的核心功能。官方文档中这两部分示例更有“业务味”,它们展示的不再是“给我讲个笑话”,而是“让模型在需要时动手执行真实函数”和“基于自有知识库回答问题”。

4.1 工具调用:让模型从对话者变成执行者

通常我们调用大模型只是拿到一段文字,但业务往往要求“拿到结果”。LangChain4j 用@Tool注解把普通 Java 方法暴露给模型,框架自动完成了函数调用的协议流转。

import dev.langchain4j.agent.tool.Tool; class CalculatorTool { @Tool("计算两个数字的和") public int add(int a, int b) { return a + b; } } CalculatorTool calculator = new CalculatorTool(); Assistant assistant = AiServices.builder(Assistant.class) .chatLanguageModel(model) .tools(calculator) .build(); String result = assistant.chat(UserMessage.from("请计算 123 加 456 等于多少")); System.out.println(result);

这里的机制是:模型在需要计算时,会返回一个“我想调用 add 方法,参数是 123 和 456”的结构化指令,LangChain4j 自动定位到CalculatorTool并执行方法,得到结果后再把结果作为新的消息发给模型,模型最终组织成一段回答返回给你。整个协议协商过程对开发者是透明的,文档里也把这一层封装解释得很清楚。

这里有几个实战中的注意事项值得单独拿出来讲:

第一,工具方法参数尽量简单。全部用基础类型或者简单的 record 封装,复杂嵌套对象容易让模型在参数映射时出错。第二,@Tool的描述信息写得越明确越好,因为模型是根据描述来决定要不要调用这个函数的。第三,工具方法应该保持“纯函数”特性,不要在里面做太重的业务副作用,否则模型重复调用同一个工具时,可能造成重复操作。

我在一个内部项目里做过类似的工具集,包括查库存、下单、计算运费等函数。实测下来,只要工具描述足够清楚,模型选择工具的准确率相当可观。但有些模型在“工具调用”和“严格 JSON 输出”同时开启时表现不稳定,我的建议是让工具调用和结构化输出分别放在独立的 Service 接口里,避免模型在两种指令之间摇摆。

4.2 RAG 完整链路:切块、向量化、检索、拼接

知识库问答是 LangChain4j 文档里另一个重点。它的思想不复杂:大模型不知道你内部文档的内容,那就先把你自己的文档切成小块,向量化后存入向量库;用户提问时,先检索出与问题相关的片段,再把片段拼进 Prompt 一起送给模型。

官方文档里有个 Easy RAG 模块,把这条链路封装成了更少的代码。但我觉得第一次上手,还是应该用拆开的方式写一遍,才能理解每个环节在做什么。

import dev.langchain4j.data.document.Document; import dev.langchain4j.data.document.splitter.DocumentSplitters; import dev.langchain4j.data.document.splitter.DocumentSplitter; import dev.langchain4j.model.embedding.EmbeddingModel; import dev.langchain4j.model.openai.OpenAiEmbeddingModel; import dev.langchain4j.store.embedding.EmbeddingStore; import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore; import dev.langchain4j.data.segment.TextSegment; Document document = Document.from("我司产品的退款政策:签收后 7 天内支持无理由退换。"); DocumentSplitter splitter = DocumentSplitters.recursive(300, 50); List<Document> chunks = splitter.split(document); EmbeddingModel embeddingModel = OpenAiEmbeddingModel.builder() .apiKey("your-api-key") .modelName("text-embedding-3-small") .build(); EmbeddingStore<TextSegment> store = new InMemoryEmbeddingStore<>(); for (Document chunk : chunks) { var embedding = embeddingModel.embed(chunk.text()).content(); store.add(embedding, TextSegment.from(chunk.text())); }

检索阶段的大致动作是:把用户问题向量化,然后在向量库里按相似度找出最相关的片段,最后把这些片段作为上下文拼进 Prompt。我在这里最想强调一个官方文档中容易看漏的点:EmbeddingModel和ChatLanguageModel是两个完全不同的接口,承担的责任也不一样。一个负责把文本转成向量,一个负责生成对话内容。有些人照着文档跑 RAG 示例时,发现同一个 model 对象既有 generate 方法又有 embed 方法,以为它们是一回事,其实是因为不同的实现类让接口看起来变多了,底层是不同的能力封装。

另外,选向量存储时不要一开始就上重型中间件。先用InMemoryEmbeddingStore跑通链路,再考虑换成更可靠的外部存储。原理完全一样,只是持久化和扩展能力不同。过早引入复杂组件,排查问题的范围会一下子变大。

4.3 检索结果与 Prompt 的拼接细节

很多人以为 RAG 只是“检索到内容塞给模型”这么简单,实际使用中还有一个关键点:怎么把检索片段拼成模型容易理解的上下文格式。文档在这个环节没有给出极其死板的模板,但官方示例通常会在系统提示词里写清楚“以下是知识库片段,请基于这些内容回答”。

我在项目里常用的做法是,在AiServices接口方法里标注系统提示词:

interface KnowledgeAssistant { @SystemMessage("你是企业内部知识库助手。请基于下面提供的资料回答问题,如果资料中没有相关信息,请明确说明。\n\n资料:{{context}}") String answer(@UserMessage String question, @V("context") String context); }

检索出来的片段拼接成一段字符串传入context变量。这里有个技巧:不要把所有命中的片段全部塞进去,一般取相似度最高的前 3 到 5 条,控制好上下文长度。片段太多不仅增加 Token 成本,还容易让模型被无关信息干扰,回答准确率反而下降。我在实测中遇到过几次:加入大量边缘相关片段后,模型开始“过度引用”文档,答得反而啰嗦。控制片段数量、明确提示词边界,效果更稳。

5. 官方文档没说透,但实战一定会踩的坑

文档讲的是“正常路径”,工程上的“异常路径”只能靠实战摸索。这一部分我整理了最常遇到的一些问题,每条都是我至少踩过一次才总结出来的经验。

5.1 版本不匹配是隐形杀手

LangChain4j 迭代快,不同版本的 API 差异非常大。刚写好的代码换一个版本,Builder 方法名、依赖包路径、泛型签名都可能变化。我在升级某个模块时,出现过一次核心包和扩展包版本不一致,编译没问题,运行到工具调用时就抛NoSuchMethodError,排查了很久才发现是两个 jar 版本错位。

建议每次升级依赖后,先用mvn dependency:tree看看实际解析出来的版本,再对照官方文档中对应版本的 Javadoc。尤其是不要直接复制网上旧代码,网上的代码很可能来自一个多月前的旧版本文档,API 可能已经变了。这也是我一直强调“从官方文档入手”的原因。

5.2 同步与流式接口不要混用

同步模型对象和流式模型对象是两套不同的客户端实现。很多人在一个项目里“顺手”把同步对象复制到流式调用上,然后发现根本没有对应方法,或者直接就编译失败。我建议写成两个独立的工厂方法,分别返回ChatLanguageModel和StreamingChatLanguageModel接口类型,这样业务代码里用哪个都很清楚,也不会混。

5.3 Memory 的清理策略必须提前设计

MessageWindowChatMemory默认只按消息条数裁剪,不理解消息重要程度。长对话里如果塞了大量检索片段,窗口很快就会满。比较合理的做法是:把文档检索片段当作“当次临时上下文”,不进长期记忆;记忆窗口只保留用户意图和结论消息。同时,不同用户必须使用独立的ChatMemory实例,否则会出现典型的“记忆串台”问题。

5.4 模型兼容性差异会影响结构化输出

不是所有模型在“工具调用”、“严格 JSON 输出”、“多轮记忆”同时开启时都能稳定工作。我遇到过几种典型表现:模型偶尔返回 Markdown 包裹的 JSON、工具调用触发了但参数完全对不上、上下文太长时响应格式开始不稳定。解决思路是不要在一个接口方法里叠加太多能力,拆成不同 Service,每个 Service 只负责一种职责。如果对结构化输出要求很高,就在系统提示词里反复强调输出格式,并在解析层做一层兜底兼容。

下面这组速查表是我实际用到最多的排查方向:

常见问题现象排查方向
类找不到或方法不存在编译失败,提示找不到接口检查模块依赖版本,确保核心包与扩展包一致
请求超时大上下文调用经常超时对长文档做切块,缩减上下文体积
响应格式不稳定模型偶尔输出非 JSON增加系统提示词约束,降低输出长度限制
记忆串台多用户共用同一 ChatMemory按用户 ID 创建独立 ChatMemory 实例
工具参数错误模型调用了工具但参数不合理简化方法参数,尽量用基础类型,强化工具描述

6. 我建议的学习路线与几条原则

写了这么多具体使用细节,最后聊一聊我自己的实践节奏。如果你正准备正式用 LangChain4j 做项目,我不建议一口气把文档全部读完。按阶段推进,效果会好很多。

6.1 四周学习路线可以这样安排

第一周,只看 Getting Started 和 Models 部分,目标是跑通同步和流式的模型调用,理解 API Key、模型名、日志开关这些基础配置。第二周,重点研究消息与记忆,做一个能持续多轮对话的 Demo,在这个阶段把ChatMemory的窗口策略调明白。第三周,引入AiServices、结构化输出和工具调用,做一个能调用外部函数、返回结构化数据的小助手原型。最后才接触 RAG,因为它涉及的组件最多,需要同时理解切块、向量化、存储、检索、上下文拼接这些环节。

按这个顺序走,每一步都有清晰的验证标准,不会出现“文档都看完了但还是不会写”的情况。

6.2 项目里值得长期坚持的几个习惯

我最后想分享几条实操中的心得,这些不是文档里直接告诉你的,而是我踩过不少坑之后沉淀下来的习惯。

第一,所有模型配置都外部化。API Key、模型名、服务地址不要写死在代码里,用环境变量或配置文件管理。否则换环境就是一场灾难。第二,模型访问封装成独立 Service。业务代码只面向接口,不直接操作ChatLanguageModel和ChatMemory的组合,后续替换模型厂商时改动面会被牢牢限制住。第三,所有返回结果都要做兜底解析。模型输出天然带不确定性,即使是结构化输出,也要在业务侧预留格式异常的降级方案。

LangChain4j 带给我的最大价值,不是某一个 API 封得有多漂亮,而是它能让我用一套稳定的 Java 抽象去应对快速变化的 LLM 生态。今天接的是某个 OpenAI 兼容接口,明天想换本地模型,改的可能只是一个模型工厂,核心业务代码基本不用动。从长期维护的角度来说,这种收益非常实在。

如果你也在项目里用了 LangChain4j,欢迎把你这边的冷门问题分享出来,尤其是我上面没写到的场景,咱们可以慢慢交流。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进