这几年Java生态里聊LLM应用开发的人越来越多了,但大部分教程都围着Python转,Java开发者想在大模型应用里动动手,总有一种插不上话的感觉。LangChain4j就是为了补上这块空缺而出现的,它把大模型接入、提示词管理、结构化输出、工具调用、RAG这些能力,用Javaer熟悉的语法封装起来,让你不用去啃Python那套生态也能做出能落地的AI功能。这篇教程我会从一开始的依赖配置讲起,逐步走到一个带工具的完整Demo,最后把我实际踩过的坑和排查思路一并整理出来,适合刚接触LLM应用开发、有Java基础的开发者参考。
1. 项目概述与方案选型
1.1 LangChain4j到底解决了什么问题
我们平时写的Java业务系统,不管是Spring Boot还是纯Java服务,本质都是在处理“确定的逻辑”:传参、校验、查库、返回结果。但一旦想接入大语言模型,事情就变得不太一样了——模型是一个外部服务,它拿到的是文本输入,返回的是文本输出,而且输出结果是概率性的,不是强类型对象。这就带来一堆麻烦事:怎么管理多轮对话上下文?怎么把模型的文本输出安全地转成业务对象?怎么让模型调用你自己的业务方法?怎么把企业内部的文档库变成模型的知识来源?
这些问题如果全靠自己写,你得处理HTTP请求封装、JSON序列化、token统计、对话历史截断、函数协议定义……每一项都是重复劳动,而且很容易写出别人看不懂的胶水代码。LangChain4j的出现,就是把这层胶水统一封装好,让你以接近普通Java开发的方式去使用大模型能力。它的设计初衷并不是让你背一堆新概念,而是把大模型接入压缩成“创建模型对象、调用方法、拿到结果”这种与业务集成很自然的流程。实际用下来,大多数需求确实可以这样做。
1.2 为什么Java项目更关注LangChain4j
一个很现实的原因是存量系统。很多公司和团队的核心服务是Java技术栈,既有权限体系、数据层、消息队列、监控告警,也有大量历史代码。如果做AI功能必须引入Python服务,那就面临跨语言调用的复杂度:Java服务里多一个微服务调用,多一层网络开销,多一套部署运维任务。而直接用LangChain4j嵌入现有Java服务,可以让AI能力跟着业务代码走,同一个发布流程、同一套配置中心、同一个监控大盘,后续再把模型调用量单独拆分也来得及。
另一个好处是类型安全。Java是静态类型语言,LangChain4j在设计上尽可能让大模型输出能落到POJO上。你定义一个Java接口方法,返回一个自定义类型,框架会去处理模型返回的文本到Java对象的映射,而不是给你一个笼统的JSON让你到处写解析逻辑。这个特性对长期维护非常友好——代码重构时编译器就能帮你检查接口和字段的匹配问题,这在Python那种动态类型环境里很难做到。
1.3 技术选型的取舍:什么时候用LangChain4j
任何技术方案都有边界。LangChain4j不是万能的,遇到适合它的场景,开发效率提升非常明显;用在不合适的场景,也有可能帮倒忙。我做选型时一般看三个方面。
适合用LangChain4j的场景通常是这几类:对话机器人类应用,比如客服助手、智能问答、知识库检索;需要把模型接到自己业务系统里的场景,比如让模型查订单状态、调用内部接口完成操作;需要结构化输出的场景,比如从合同里抽取关键字段、把用户自然语言转成查询条件。
不太适合或者需要谨慎考虑的场景包括:对响应延迟极端敏感的高并发接口,因为模型推理速度本来就不快,框架本身再怎么优化也没法让网络往返消失;对模型能力要求超高、频繁需要自定义复杂编排的场景,比如并行调用多个模型、动态路由、复杂的条件分支,这种更建议直接用底层模型API配合自己的工作流引擎。框架能让你走得快,但真正能不能走得远,还是取决于你对自己业务的理解和设计。
我用一个表格把几种常见技术路线的差异列出来,方便做选型时参考:
| 技术方案 | 开发速度 | 类型安全 | 适用场景 | 维护成本 |
|---|---|---|---|---|
| LangChain4j | 快 | 强 | 对话、工具调用、RAG、结构化输出 | 中低 |
| 直接调用模型HTTP API | 中 | 弱 | 简单一次性请求、自定义编排 | 高 |
| 自研封装层 | 慢 | 中 | 公司内有统一AI平台需求 | 高 |
| Python生态框架 | 快 | 弱 | 纯AI原型、算法团队主导 | 中(但涉及跨语言) |
从这张表能看出来,Java团队的第一优先级通常是开发速度和长期维护成本,LangChain4j在这两个维度上表现都不错。下面进入实操环节,先把环境跑通。
2. 环境准备与核心依赖
2.1 基础环境要求
LangChain4j目前对Java版本的要求是Java 8及以上,但如果你在用Spring Boot 3.x,那就需要Java 17。我个人的建议是直接上Java 17,一方面是新项目没有历史包袱,另一方面框架本身和依赖库对高版本JDK的支持明显更积极,像虚拟线程、records这类特性用起来也更顺手。
构建工具方面Maven和Gradle都行,我个人习惯用Maven,毕竟Spring Boot项目的默认选择。构建版本建议JDK 8+,如果你是Java 8的用户,需要注意框架版本选择,因为新版LangChain4j已要求JDK 17,如果项目还停留在Java 8,可以选择1.x版本线,或者考虑升级JDK。
模型服务方面,LangChain4j支持OpenAI协议、Anthropic、Ollama、HuggingFace等主流服务。教程里我用OpenAI协议兼容服务举例,因为各家兼容服务在接口上基本一致,换到国产模型或本地模型时,只改配置不改代码,实测下来很省心。如果你用的是Ollama本地模型,那底层的依赖需要换成langchain4j-ollama模块,但上层调用逻辑基本一样。
2.2 最小依赖清单与快速初始化
创建一个普通的Maven项目,然后在pom.xml里引入核心依赖。以Spring Boot 3.2为例,使用BOM管理版本号是最稳妥的方式,避免各种传递依赖版本打架:
<properties> <langchain4j.version>1.0.0-beta1</langchain4j.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-bom</artifactId> <version>${langchain4j.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j</artifactId> </dependency> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-open-ai</artifactId> </dependency> </dependencies>这里有两个容易踩的坑,提前说一下:
第一,不要直接写一个具体版本号给langchain4j核心包,而不声明BOM。因为LangChain4j的各模块是一起发布的,版本不齐会出现NoSuchMethodError这种比较难排查的问题。第二,如果只引入langchain4j而不引入具体的模型模块,项目能启动,但一调用就会提示找不到模型实现类,这个错误信息有时候不太直观,容易浪费不少时间。
2.3 配置承载与密钥管理
模型API密钥不要硬编码在代码里,这个习惯必须养成。在Spring Boot项目里,可以把配置写在application.yml中,通过环境变量或配置中心注入:
langchain4j: open-ai: api-key: ${LLM_API_KEY} base-url: ${LLM_BASE_URL:https://api.openai.com/v1} chat-model: model-name: gpt-4o-mini temperature: 0.2上面这段配置中,${LLM_API_KEY}表示从环境变量读取密钥,${LLM_BASE_URL}允许你切换模型服务的访问地址。如果是本地开发,直接在IDE的环境变量里配置就行了。如果你们已经上了K8s,用Secret挂载环境变量更规范。密钥绝对不能提交到Git库,我见过不止一个项目因为把密钥提交到代码仓库,几分钟内就被别人扫描到并盗刷,损失不小。
接下来我们开始写第一段能跑的代码。
3. 核心概念与入门实操
3.1 从最简单的聊天调用开始
LangChain4j最核心的对象是ChatLanguageModel,它就是你在代码里用来发起对话的入口。它屏蔽了底层HTTP调用、鉴权、超时重试这些细节,你只需要拿它调一个方法。
在Spring Boot环境下,框架提供了自动配置,你只要在前面配置好,就可以直接注入使用:
import dev.langchain4j.model.chat.ChatLanguageModel; import org.springframework.stereotype.Service; @Service public class ChatService { private final ChatLanguageModel chatLanguageModel; public ChatService(ChatLanguageModel chatLanguageModel) { this.chatLanguageModel = chatLanguageModel; } public String ask(String question) { return chatLanguageModel.chat(question); } }这里的chat(String)方法会返回模型生成的纯文本内容。跑一个测试调用看看效果:
String answer = chatLanguageModel.chat("用一句话解释什么是数据库索引"); System.out.println(answer);这段代码能让你在五分钟内跑通第一个LLM调用。但别急着高兴,实际项目中往往需要更精细的控制,比如系统提示词、多轮上下文、结构化返回。从第二个小技巧开始说。
技术细节说明:ChatLanguageModel接口有多种重载,除了最简单的输入字符串,还支持传入ChatMessage列表,用于多轮对话场景。这个接口在框架里的地位类似JDBC里的Connection,几乎所有高级功能最终都会回归到它上面。
3.2 理解Message与对话记忆
简单调用虽然快,但撑不起业务需求。因为模型本身是无状态的,你在第一次问它“我叫周明”,第二次再问“我叫什么”,它根本不知道。要实现多轮对话,必须把历史消息一起发给模型。LangChain4j把对话消息抽象成了几个类型:SystemMessage(系统提示词)、UserMessage(用户输入)、AiMessage(模型回复)。一次请求可以包含多条消息,模型会基于全部消息来生成回答。
import dev.langchain4j.data.message.AiMessage; import dev.langchain4j.data.message.SystemMessage; import dev.langchain4j.data.message.UserMessage; import dev.langchain4j.model.chat.ChatLanguageModel; List<ChatMessage> messages = new ArrayList<>(); messages.add(new SystemMessage("你是一位资深的Java架构师,回答要简洁、专业。")); messages.add(new UserMessage("介绍一下Spring Boot自动配置的原理")); messages.add(new AiMessage("自动配置是Spring Boot基于条件注解在启动时自动装配Bean的机制。")); messages.add(new UserMessage("那它和Spring Framework的编程式配置有什么区别?")); String answer = chatLanguageModel.chat(messages);如果你自己管理消息列表,要特别小心上下文长度问题。模型虽然支持长对话,但token是有限度的。超过上下文窗口之后就报错,或者更糟糕——模型把很早之前的消息忘了。所以实战中推荐使用MessageWindowChatMemory来做自动裁剪:
import dev.langchain4j.memory.chat.MessageWindowChatMemory; ChatMemory chatMemory = MessageWindowChatMemory.builder() .maxMessages(10) .build(); String answer = chatLanguageModel.chat(chatMemory.messages(), "请基于我们之前的对话,总结一下要点");maxMessages(10)会保留最近10条消息,超出部分会自动丢弃,这样就避免了无脑增长。不过要注意,这里说的最大消息数指每条消息都算,如果某条消息特别长,还是需要额外关注token数量。这是一个最容易忽视的细节。
3.3 结构化输出:远离JSON手工解析
大模型返回的是文本,但业务系统需要的是对象。最常见的做法让模型返回JSON字符串,然后自己解析。这里面有一个隐藏的坑:模型偶尔会输出多余的说明文字,或者JSON格式不规范,直接解析很容易失败。LangChain4j针对这个场景提供了结构化输出能力,你只要定义好目标类,框架会自动处理模型和对象之间的转换。
import dev.langchain4j.service.AiServices; public record Person(String name, int age, String city) { } public interface PersonExtractor { @UserMessage("从{{text}}中提取人物信息,并返回Person对象") Person extractPerson(@V("text") String text); } PersonExtractor extractor = AiServices.create(PersonExtractor.class, chatLanguageModel); Person person = extractor.extractPerson("张三今年28岁,住在杭州。"); System.out.println(person.name());先解释一下上面这段代码做了什么:定义了一个PersonExtractor接口,接口方法上的注解是提示词模板,{{text}}是参数占位符。调用时,LangChain4j会把参数值填充进模板,请求模型返回JSON,然后反序列化成Person对象。关键是模型输出格式由框架约束,不用自己写解析逻辑。
结构化输出特别适合信息抽取、意图识别这种场景。比如客服工单系统里,用户说“我想改一下收货地址”,就可以让模型返回一个意图对象,包含action和参数。这样后面接业务逻辑就非常自然。
小提示:如果字段是日期、枚举这种复杂类型,建议在POJO里用String接收,再自己转换一遍。因为模型输出毕竟有随机性,直接映射到强类型时,一旦枚举值对不上,框架会抛出转换异常,调试起来稍麻烦。
3.4 用AI Service封装业务能力
上面提到的PersonExtractor其实已经展示了LangChain4j里一个核心高阶抽象:AiServices。它和Spring Data JPA的思路很像——你只要定义接口,框架帮你生成实现。除了简单对话,AiServices还能注入工具方法、RAG组件,这是从Demo走向实战的关键。
public interface CustomerAssistant { @SystemMessage("你是客服助手,回答用户问题时只能使用提供的工具获得信息,不能编造。") String chat(@V("message") String userMessage); }然后通过AiServices.builder(CustomerAssistant.class)进行构建,并传入ChatLanguageModel和ChatMemory。这样在服务层,你拿到的是一个干净的接口,内部怎么和大模型交互、怎么维护上下文,全部被封装起来。这套模式对项目结构是很友好的:上层业务代不关心AI细节,下层具体实现可以随时替换模型供应商,只要改配置就行。
到这里,一个基础的LLM应用已经从“能用”迈向“好用了”。下一节来看怎么把模型接入真实业务系统。
4. 从Demo走向实战:连接业务
4.1 一个完整的项目场景
只靠模型的知识,生成的内容可能存在幻觉或过时。要解决这个问题,通常会让模型调用你提供的工具方法,从业务系统里获取真实数据,再生成回答。这就像给模型一双“手”,让它自己拿资料,而不是凭空想象。
下面用一个模拟的售后客服场景来说。假设某公司的客服机器人需要处理三种请求:查订单状态、修改收货地址、计算退款金额。这三个操作后端已经提供了相应的方法,现在要教AI调用它们。
第一步,在已有的Spring服务里定义工具类。LangChain4j里给方法加上@Tool注解,它就会成为一个可被模型调用的工具:
import dev.langchain4j.agent.tool.Tool; import org.springframework.stereotype.Component; @Component public class OrderTools { @Tool("查询指定订单的当前状态") public String queryOrderStatus(String orderId) { // 这里实际去查数据库或调用远程接口 return "订单" + orderId + "当前状态:已发货,预计3天内送达。"; } @Tool("修改订单的收货地址,更新成功返回true,失败返回false") public boolean updateShippingAddress(String orderId, String newAddress) { // 调用订单服务更新地址 return true; } @Tool("根据订单号和退款原因计算退款金额,返回带单位的金额文本") public String calculateRefund(String orderId, String reason) { // 根据商品价格和退款原因计算 return "退款金额:199元"; } }@Tool注解里的描述非常重要,因为模型是根据描述来决定要不要调用该工具的。描述写得越准确,模型越不容易用错。比如上面描述里特意写了“带单位的金额文本”,就是约束模型的输出格式,避免它把数字给裸返回。
第二步,把工具注入到AiServices里:
public interface CustomerAssistant { String chat(@V("message") String userMessage); } CustomerAssistant assistant = AiServices.builder(CustomerAssistant.class) .chatLanguageModel(chatLanguageModel) .chatMemory(MessageWindowChatMemory.withMaxMessages(20)) .tools(new OrderTools()) .build();第三步,就是对用户提问进行测试了。用户说“帮我看看订单ORD123456什么状态”,框架会把工具列表发送给模型,模型判断这个请求应该调用queryOrderStatus,然后框架负责执行方法,把返回结果传回给模型,最后模型生成自然语言回答给用户。整个过程对外部调用方来说,就像一次正常的接口请求。
4.2 Tool调用的工作原理与注意事项
理解工具调用的执行链路有助于排查问题。整个流程大致是:模型收到用户问题和工具定义列表;模型判断需要调用哪个工具,返回带工具名和参数的指令给框架;框架执行对应Java方法,拿到结果;框架把工具执行结果作为一条消息再发给模型;模型生成最终回答。
这里有个容易踩的坑:工具方法如果耗时较长,模型可能会等待超时。所以工具方法里尽量只做快速操作,比如查缓存、查库、调内部接口;如果要做慢操作,例如发邮件、生成PDF,应该把任务发给消息队列后立刻返回“已受理”,不要让模型等着你干重活。
还有个安全问题是权限校验。让模型调用工具,相当于把业务系统的操作权限交给了模型。如果用户在对话中问“顺便帮我把订单退掉”,模型可能会尝试调用退款工具。所以工具方法里一定要做权限校验,最好基于会话中的用户身份来校验,不能直接信任模型的输入。这是实战和Demo最大的区别。
4.3 RAG的第一步:加载文档与向量化
工具调用适合获取结构化业务数据,但如果是非结构化文档,比如产品手册、政策文件,就需要用RAG(检索增强生成)。RAG的流程概括成一句话:先把文档切成小块,向量化后存进向量数据库;用户提问时,先从向量库检索相关片段,再把片段和问题一起交给模型回答。LangChain4j对RAG的封装程度相当高,你用现成组件拼起来就能跑通,不过搞清楚每一步在做什么仍然很有必要。
先引入额外的模块依赖:
<dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-easy-rag</artifactId> </dependency> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-embeddings</artifactId> </dependency>然后加载一个文本文件,切块、向量化、存储到内存向量库:
import dev.langchain4j.data.document.Document; import dev.langchain4j.data.document.loader.FileSystemDocumentLoader; import dev.langchain4j.data.segment.TextSegment; import dev.langchain4j.model.embedding.EmbeddingModel; import dev.langchain4j.store.embedding.EmbeddingStore; import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore; Document document = FileSystemDocumentLoader.loadDocument("faq.txt"); EmbeddingModel embeddingModel = new OpenAiEmbeddingModel(...); EmbeddingStore<TextSegment> store = new InMemoryEmbeddingStore<>(); // 文档切块后用embedding模型向量化,再存入向量库 // LangChain4j 提供了 DocumentSplitter 来处理切块逻辑实际开发中,一般不会用内存向量库,而是接PostgreSQL的pgvector、Elasticsearch向量插件或专门的向量数据库。但原理是一样的:有索引才能检索。文档切块的大小也要根据模型上下文和业务来调整,切得太小语义不完整,切得太大检索不准。
RAG的常见坑是检索质量不佳。三步就可以帮排查:先看看文档切块大小是否合适;再看看embedding模型对你的语言和领域是否友好;最后看提示词里是否说明“只能基于给定资料回答”。很多回答质量差的问题,不是模型不行,而是提示词没把约束说清楚。
5. 常见问题与排查技巧实录
5.1 依赖冲突与版本不一致
这个问题排第一,因为它出现概率太高了。集成LangChain4j时容易出现NoSuchMethodError、ClassNotFoundException,多半是版本不一致。解决办法就是我前面提到的,把langchain4j-bom放到dependencyManagement统一管理。还有一个检查思路:在IDE里打开依赖图,看是否有多个不同版本的LangChain4j模块被Maven的dependency mediation拉进来。如果有,找到引入间接依赖的模块,用exclusions排除掉旧版本。
另外一个常见问题是Slf4j绑定冲突。Spring Boot自带日志实现,有时候直接引入LangChain4j会带入别的日志实现。日志看起来像挂了但没输出,实际上已经打印到了别的实现去了。一般通过排除多余的日志依赖可以解决。
5.2 模型调用超时与限流
大模型服务的响应时间波动较大,高峰期可能超过十秒才返回。如果系统里用的是默认超时配置,很可能接口还没等模型回答就超时了。建议根据业务容忍度来配置超时时间。比如在线对话场景,可以给5秒到15秒;如果只是异步导出,可以给更长的超时。
限流的处理也比较关键。很多模型服务有RPM或TPM限制,一旦超过就返回429。建议在应用层面做线程池隔离和信号量限流,避免AI功能把核心业务的线程池占满了。实际项目中,我是把AI调用放在独立的线程池里,核心线程数按模型服务的并发上限来设定。
5.3 输出格式不稳定
即使使用了结构化输出,偶尔也会遇到模型返回格式不符合预期的情况。LangChain4j内置了重试机制,但它不是万能的。我自己排查时按下面顺序来:
第一先看提示词里对格式的约束是否足够明确。第二看模型服务是否开启了温度控制,温度调高到0.7以上后,输出的随机性会显著增加。结构化输出场景建议温度为0。第三,如果仍然失败,在接口层面加一个降级策略,返回默认值或友好错误提示,不要让它把异常抛给用户。
我用一个表格把最常遇到的问题和排查建议整理出来:
| 现象 | 可能原因 | 处理建议 |
|---|---|---|
| 调用时抛出NoSuchMethodError | 依赖版本不一致 | 统一使用LangChain4j BOM管理版本 |
| 返回内容里包含JSON外的解释文字 | 模型未严格按格式输出 | 调整提示词、降低temperature,加结构化输出重试 |
| 工具调用一直没有触发 | 工具描述不清晰 | 完善@Tool方法描述,让模型能准确理解 |
| 多轮对话时上下文越来越长 | 没有配置ChatMemory | 加入MessageWindowChatMemory并限制消息数量 |
| 模型回答的内容数据过时 | 仅依赖模型自身知识 | 引入工具调用或RAG,让模型先检索再回答 |
5.4 对话记忆在异步场景下的丢失
有时候会话状态保存在内存里,但服务重启或扩容后,历史消息就丢了。解决思路有两个层次:如果只要求单机重启不丢,把ChatMemory序列化存到Redis或数据库即可;如果要求多实例共享,需要实现一个基于Redis的ChatMemory。
LangChain4j提供了ChatMemoryStore接口,你可以自己实现存储逻辑。核心就是把ChatMemory里的消息列表存起来,读取时再恢复。日常Demo先不用做这一步,但一旦要上生产环境,还是建议提前做好方案。
5.5 成本控制与可观测性
模型调用是有成本的,而且随着用户量上升,成本会增长得比较快。建议在生产环境加一层日志,记录每次调用的模型名称、token使用量、响应耗时。LangChain4j提供的TokenCountEstimator可以帮你预估使用量。另外,可以使用框架的监听器机制来统一记录日志和埋点,这样后续做成本分析和告警都有数据支撑。
我自己的习惯是每次模型调用都记录一个traceId,和业务请求ID关联起来。遇到用户反馈回答不对时,直接根据请求ID拉出当时发给模型的完整上下文,排查效率高很多。这个习惯很值得养成。
5.6 流式输出集成
对话类应用往往需要打字机效果,不能等模型全部生成完再一次性返回。LangChain4j对流式输出有支持,核心是StreamingChatLanguageModel接口。它在Spring Boot自动配置中也提供了Bean,可以直接注入使用。与同步调用不同,流式接口把token一块块回调给使用者,你需要在回调里把内容推送到前端。
这里特意提醒一句:流式调用的线程模型和非流式不一样,它是回调驱动的,记得在处理完token之后关闭相关的流或线程池,否则线程池耗尽的问题会在高并发场景下暴露出来。我在一个项目里就遇到过类似情况,线上服务突然莫名其妙变慢,最后排查发现是流式响应未释放连接导致的。
结尾:一些实际操作中的体会
最后分享几个我长期实践下来的个人体会。
第一,不要一上来就把LangChain4j的所有模块都引入。先跑通最简单的对话,再加工具,再加RAG,每一步都确认没问题后再往上叠加。框架功能很多,但不是说用上所有功能才是最佳实践,按需引入才是正道。
第二,提示词调优永远比换模型便宜。很多人遇到效果不好,第一反应是换大模型,成本高且不解决问题。先用LangChain4j的提示词模板把系统提示词和工具描述打磨好,再用几个真实问题跑一遍对比,大多数场景都能在原有模型上得到改善。
第三,AI功能的代码结构要单独隔离,不要散落在业务代码里。建议单独开一个包来管理模型交互、提示词模板和工具类,后面升级模型或更换服务商时,改动范围能控制在一个包里,不至于牵一发动全身。
补充一个小技巧:LangChain4j的提示词模板里不只有{{text}}这种简单占位,还可以在模板里写条件逻辑或循环片段。这个功能适合生成复杂系统提示词时使用。我自己就习惯用这种方式把用户画像动态拼进提示词,让模型回答更贴合上下文。
整体来看,LangChain4j是一个值得Java团队认真了解的项目。它未必适合所有项目,但只要是Java生态内需要接入大模型能力的场景,它基本能把开发成本压到很低。接下来你可以拿自己的一个真实业务场景试试手,做个带工具调用的小功能,体会一下模型接入业务系统的完整链路。踩坑是正常的,关键是踩完之后能总结出自己的排查路径。