LangChain4j从入门到实战:Java大模型应用开发指南

发布时间:2026/10/25 20:55:09
LangChain4j从入门到实战:Java大模型应用开发指南 LangChain4j 这个名字最近在 Java 技术社区里出现的频率明显高了起来。很多后端工程师应该都有同样的困惑Python 生态里大模型开发框架已经满天飞Java 这边却一直没什么趁手的工具直到 LangChain4j 出现才算是补齐了这块拼图。它不是一个简单的 API 封装而是把大模型接入、提示词管理、上下文记忆、工具调用、知识库检索这些能力以 Java 工程师熟悉的方式全部工程化了。如果你正在用 Spring Boot 做业务系统想把大语言模型的能力真正落到订单处理、客服问答、文档分析这些场景里这篇实战教程就是给你准备的。我会从一个最小可运行的 Demo 开始带你梳理清楚 AiServices、ChatMemory、Tool 注解这些核心概念最后用一个多路召回的知识库问答案例收尾。1. LangChain4j 为什么是Java工程师入坑大模型应用的第一站1.1 Java生态和LLM之间缺的那块拼图先说一个很现实的问题过去两年里Java 工程师想接入大模型最常见的做法是什么直接引入 HTTP 客户端对着 OpenAI 兼容接口拼 JSON 请求。项目规模小的时候还好一旦涉及多轮对话、工具调用、知识库召回你很快就会发现自己写了一大堆重复代码来维护消息列表、解析 JSON、处理超时和重试而且每换一家模型厂商就要重写一遍。Python 那边有 LangChain、LlamaIndex 这样的框架把这些问题都抽象好了。Java 这边长期处于裸调 SDK 的状态直到 LangChain4j 出现。它并不是把 Python 的 LangChain 机械翻译成 Java而是按照 Java 社区的工程习惯重新设计了一整套 API。核心目标只有一个让开发者用声明式的方式接入大模型而不是自己维护那些繁琐的底层逻辑。我在实际项目里最大的感受是LangChain4j 解决的不只是“调通 API”的问题而是“如何把大模型变成 Java 系统里一个标准组件”的问题。它提供了统一的模型接口、提示词模板、多轮记忆、工具调用、向量检索这些能力并且能和 Spring Boot 无缝整合。你完全可以把一个聊天助手、一个信息抽取服务、一个知识库问答接口当作普通的 Java Bean 去编写和注入。1.2 LangChain4j的核心组成与运行逻辑LangChain4j 的整体设计遵循一种模块化结构。langchain4j-core提供核心抽象接口langchain4j是聚合包方便一键引入。针对不同模型厂商它又提供了独立模块比如langchain4j-open-ai、langchain4j-ollama、langchain4j-azure-open-ai还包括国产模型厂商的适配模块。这种设计与 JDBC 的思路非常像上层业务只面向ChatLanguageModel这个统一接口编程底层实现由不同模块提供切换模型厂商时不需要改动业务代码。运行逻辑也不复杂。一次完整的大模型交互大致是这样的你构建一个包含系统提示词、历史消息、用户当前输入的消息列表交给ChatLanguageModel发送给模型模型返回文本响应如果涉及工具调用框架会解析模型输出的工具调用指令执行对应的 Java 方法把执行结果再作为一条消息发回给模型直到模型给出最终回答。理解这条链路后面看代码就不会觉得神奇了。LangChain4j 的所有高级功能比如记忆、工具、检索增强本质上都是在给这条消息链路增加“上下文”和“能力扩展”。它没有改变大模型本身的工作方式而是帮你管理了模型之外的所有工程细节。1.3 版本选择别再盯着旧教程抄了LangChain4j 的版本迭代速度相当快早期 0.x 版本和 1.x 版本之间的 API 差异不小。很多网上的旧教程还在用 0.3x 的写法如果你直接把代码抄到新版本项目里大概率会碰到编译错误。我的建议是新建项目时直接用 Maven Central 上的最新稳定版不要拘泥于某一个具体版本号。在 JDK 版本上LangChain4j 的 1.x 版本普遍要求 JDK 17 及以上。如果你们公司还在用 JDK 8也不是完全不能用但会遇到更多兼容性问题。我自己的习惯是本地开发用 JDK 17Spring Boot 项目保持 3.x 版本这样整合起来最顺滑。另外需要注意OpenAiChatModel这个核心类在旧版本里叫OpenAiChatModel但在更早的版本中有过构造方式的变化碰到编译报错时优先去官方 GitHub 仓库看最新的 README而不是去搜索引擎找那些两年前的帖子。2. 环境搭建与第一个能跑的Demo2.1 引入依赖最少需要哪几个包先用 Maven 项目演示。创建一个最基础的 Java 项目在pom.xml里加上 LangChain4j 的核心依赖和一个厂商适配模块dependency groupIddev.langchain4j/groupId artifactIdlangchain4j/artifactId version1.0.0/version /dependency dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-open-ai/artifactId version1.0.0/version /dependency这里的langchain4j-open-ai模块提供的其实是一个 OpenAI 兼容协议的客户端。现在很多本地模型服务比如 Ollama、vLLM、FastGPT都支持 OpenAI 兼容接口。也就是说你并不一定需要真的注册 OpenAI 的付费服务只要有一个兼容协议的 HTTP 端点LangChain4j 就能正常工作。如果要用本地模型还可以引入langchain4j-ollama模块。但我的建议是入门阶段先用 OpenAI 兼容协议把你的模型服务包一层这样后续切换非常灵活不需要改代码。等真正理解了核心概念再去看厂商定制模块。2.2 模型配置OpenAI兼容接口与本地模型配置模型的方式非常直观使用 Builder 模式ChatLanguageModel model OpenAiChatModel.builder() .apiKey(demo) .baseUrl(http://localhost:11434/v1) .modelName(qwen2.5:7b) .temperature(0.7) .timeout(Duration.ofSeconds(60)) .build();这段配置的意思是模型服务跑在本机的 11434 端口也就是 Ollama 的默认端口/v1路径让 Ollama 暴露 OpenAI 兼容接口modelName是本地已经拉取好的模型名称。这里有两个细节值得展开。第一apiKey在本地服务场景下可以随便填因为服务端不校验。如果你用的是云厂商服务这里必须正确填写你的密钥并且要保证你的运行环境网络策略允许访问该服务。第二temperature控制随机性值越大回答越发散值越小越确定。做知识库问答、信息抽取这类任务时我习惯调到 0.1 到 0.3做文案生成再调高一些。2.3 第一段聊天代码从字符串到多消息配置好模型对象后最简单的调用方式是这样String answer model.chat(用一句话解释什么是AOP); System.out.println(answer);一行代码就能得到模型的文本回答。源码层面这个重载内部会把纯字符串包装成UserMessage再调用完整的消息接口。真正生产级的调用方式应该从消息列表开始ListChatMessage messages List.of( SystemMessage.from(你是资深Java架构师回答要简洁、专业优先给出代码示例。), UserMessage.from(什么是面向切面编程) ); ChatResponse response model.chat(messages); System.out.println(response.aiMessage().text());SystemMessage用来设置系统级指令UserMessage代表用户输入AiMessage代表模型输出。这其实就是大模型对话的本质一个不断扩张的消息数组。LangChain4j 的ChatLanguageModel接口把这套过程规范化了你不需要手动拼接 JSON 请求体。2.4 为什么要做这一层抽象有同学可能会问我都已经会写 HTTP 客户端了直接用 RestTemplate 调用模型接口不就行了吗为什么要引入框架核心原因是可维护性。裸调接口时模型返回的内容你需要手动解析错误码要自己处理多轮消息要自己维护切换模型厂商要重写整个调用层。而ChatLanguageModel把这一切统一到了一个接口后面。你今天用 Ollama 本地模型做开发上线前换成一款商业模型服务只需要改配置业务代码一行都不用动。对于要长期维护的企业级系统来说这个价值怎么强调都不过分。3. AiServices把大模型改造成业务接口3.1 定义一个“会聊天”的Java接口LangChain4j 里最精彩的设计就是这个AiServices。它的思路是把大模型能力封装成一个 Java 接口你在接口上定义方法并标注提示词框架会在运行时生成代理实现。底层所有细节都被隐藏起来了调用方拿到的只是一个普通的 Java Bean。看一个最简单的例子interface Assistant { String chat(String message); } ChatLanguageModel model OpenAiChatModel.builder() .apiKey(demo) .baseUrl(http://localhost:11434/v1) .modelName(qwen2.5:7b) .build(); Assistant assistant AiServices.builder(Assistant.class) .chatLanguageModel(model) .build(); String answer assistant.chat(帮我写一个冒泡排序); System.out.println(answer);这里AiServices.builder()会检查接口的方法签名生成一个实现了Assistant接口的代理对象。当你调用assistant.chat(...)时框架自动把方法参数包装成用户消息、发送给模型、把模型输出转换成字符串返回。这个方法的好处是业务代码完全不感知“对面是一个大模型”。你今天可以把Assistant替换成另一个实现类明天也可以切换底层模型对上层调用方透明。3.2 SystemMessage与UserMessage模板分离真实场景里系统提示词通常很长用户输入格式也不是固定的一句话。LangChain4j 提供了SystemMessage和UserMessage注解来管理这两部分。interface Assistant { SystemMessage(你是订单客服助手只能回答与订单查询、退换货、物流相关的问题。) String chat(String userMessage); UserMessage(请从下面这段用户评价中提取商品名称、购买数量和对物流的满意度。评价内容{{text}}) String extractCommentInfo(String text); }{{text}}是模板变量会自动替换成方法参数的值。如果方法有多个参数可以给参数注解V(参数名)在模板中按名字引用。把系统提示词和用户提示词分开管理后后续调整提示词就不用改动 Java 代码里的业务逻辑也方便把模板内容挪到配置中心。一个小经验提示词模板不要写太长。一旦超过几百字模型的注意力可能被引导词干扰输出不稳定。保持指令明确、示例精简是最稳妥的做法。3.3 结构化输出的正确姿势调用大模型时最烦的一件事就是输出格式不可控。明明要求返回 JSON模型偶尔会在前后多输出几句解释。LangChain4j 对这件事的处理是让方法返回类型直接声明为 POJO 或ListPOJO框架会强制模型按 JSON 输出然后自动反序列化。record OrderInfo(String customerName, String productName, int quantity, String totalAmount) {} interface Assistant { UserMessage(从用户话术中抽取订单信息{{text}}) OrderInfo extractOrder(String text); } OrderInfo info assistant.extractOrder(帮我看看张三昨天买的那个机械键盘买了两个一共598元); System.out.println(info.productName());这段代码背后框架会给模型发送“你必须输出 JSON并且匹配给定 schema”的指令然后把模型返回的文本用 Jackson 解析成OrderInfo对象。如果解析失败框架还会做一次自动重试。字段命名越简单越好别用中文名也别塞复杂的嵌套结构复杂 JSON 不仅容易触发表字段歧义还会增加模型输出格式错误率。3.4 与Spring Boot结合使用的骨架在 Spring Boot 项目里你只需要把ChatLanguageModel和Assistant都声明成 BeanConfiguration public class LlmConfig { Bean public ChatLanguageModel chatLanguageModel( Value(${llm.base-url}) String baseUrl, Value(${llm.model-name}) String modelName, Value(${llm.api-key:demo}) String apiKey) { return OpenAiChatModel.builder() .baseUrl(baseUrl) .modelName(modelName) .apiKey(apiKey) .temperature(0.2) .timeout(Duration.ofSeconds(60)) .build(); } Bean public Assistant assistant(ChatLanguageModel model) { return AiServices.builder(Assistant.class) .chatLanguageModel(model) .build(); } }配置写在application.yml里不同环境切换模型服务时只改配置不重新打包。这是最基础也是最实用的整合姿势。4. 让AI记住上下文并学会使用工具4.1 ChatMemory从单轮到多轮默认情况下Assistant接口的每次调用都是独立无状态的模型不会记得上一轮说了什么。要想支持多轮对话必须引入ChatMemory。ChatMemory chatMemory MessageWindowChatMemory.withMaxMessages(20); Assistant assistant AiServices.builder(Assistant.class) .chatLanguageModel(model) .chatMemory(chatMemory) .build();MessageWindowChatMemory的含义是只保留最近 N 条消息超出范围的老消息自动丢弃。这个窗口大小很有讲究。设得太小模型记不住关键信息设得太大每次请求都会携带大量历史消息消耗更多 Token还可能撞上模型的上下文长度上限。我实测下来20 到 30 条是一个比较均衡的范围。如果系统里有多个用户就不能用单例的ChatMemory因为会话之间会互相串记忆。正确做法是使用ChatMemoryProvider按会话 ID 分别维护记忆ChatMemoryProvider provider chatMemoryId - MessageWindowChatMemory.withMaxMessages(20);这样一个用户一个记忆空间互不干扰。4.2 Tool给模型一把查询数据库的钥匙大模型最擅长的是语言理解但它没有访问你数据库的能力。LangChain4j 的做法是提供一个Tool注解你写好一个普通 Java 方法模型在回答过程中如果觉得需要外部信息可以主动“调用”这个方法。class OrderTools { Tool(根据订单号查询订单的状态返回订单状态文本) public String getOrderStatus(String orderId) { // 这里可以查数据库、调外部接口 return 订单已发货预计2天后送达; } }使用方式很简单构建代理时把工具对象传进去Assistant assistant AiServices.builder(Assistant.class) .chatLanguageModel(model) .tools(new OrderTools()) .build();当用户问“我的订单 od12345 现在什么状态”模型不会自己编造答案而是生成一个调用getOrderStatus的指令框架执行这个方法拿到返回值后再拼接到对话上下文中让模型基于真实数据组织回答。这个机制有个很形象的说法模型不直接干活的它是“项目经理”你写好的工具方法是“执行员工”。模型负责拆解任务、判断该调用哪个工具Java 代码负责真正执行操作。把数据库查询、API 请求、业务计算都封装成工具就等于无限扩展了模型的能力边界。顺着这个思路你可以把 MyBatis-Plus 的 Mapper 注入到工具类里让模型通过工具方法完成真实的数据查询。比如定义一个searchProductByKeyword方法内部执行productMapper.selectList(...)返回 JSON 字符串给模型。ToStringCalculator。整体效果比起裸调接口稳定性和可维护性都有了质的提升。4.3 工具调用的几个经验教训工具调用听起来简单实际踩坑的地方不少。我先说三个最常见的。第一工具描述必须非常具体。Tool注解里的描述是模型判断“何时调用这个工具”的唯一依据。你写“查询库存”太模糊模型可能在整个对话中根本不触发。写成“根据商品SKU编码查询该商品当前剩余库存数量返回数字字符串”这样带条件和返回格式的描述触发率会高很多。第二方法参数类型尽量简单。模型输出工具参数时本质是 JSON它会把自然语言转成匹配的字段。如果你的方法签名是复杂对象解析失败的概率会明显上升。字符串、整数、布尔值是最稳妥的选择。第三工具方法的返回值不能太长。如果你把整个数据库表记录都序列化成 JSON 返回给模型在线Token 限制会导致模型“记不住”信息。返回前先截断、聚合摘要只要保留模型组织回答所需的关键字段就行。比如查询商品列表时返回商品名、价格、库存三个字段而不是把所有明细字段全塞回去。5. 实战做一个多路召回的知识库问答5.1 多路召回到底在解决什么问题知识库问答是目前大模型落地最成熟的场景之一。用户提问后系统从知识库中检索出相关片段把片段作为参考上下文发给大模型让它基于这些内容作答。这里的核心难点在检索环节。单一路径的检索都有明显短板。纯向量检索用 Embedding 模型计算语义相似度优势是能处理“没提关键词但意思相近”的查询但对精确匹配很弱比如产品或合同编号这种字符串语义向量反而容易把相似的编号弄混。纯关键词检索精确匹配很强但遇到同义词、口语化表达就完全失效。多路召回的思路就是同一条用户问题同时走向量检索、关键词检索等多个通道每个通道返回一批候选文档然后用一个合并策略把这些结果综合排序取出最终的前 N 条。虽然实现上多写了一些代码但在真实业务里的命中率提升非常明显。这也是最近很多资深工程师面试里常被问到的一个设计题。5.2 文档切分与向量化做检索之前先把原始文档切成更小的片段。切分粒度决定了检索的精度切得太粗一个片段里有多个主题检索命中后噪音很大切得太细上下文碎片化模型理解困难。我常用的策略是按固定大小切分每个片段 500 到 800 个字符片段之间重叠 100 个字符保证跨边界的信息不丢失。Document document Document.from(articleText); DocumentSplitter splitter DocumentSplitters.recursive(800, 100); ListTextSegment segments splitter.split(document);向量化这一步需要选择一个 Embedding 模型。入门阶段可以用本地模型AllMiniLmL6V2EmbeddingModel它不需要联网语言学名兼容性也不错。如果是中文场景生产环境我更建议使用专门的中文 Embedding 模型比如 BGE 系列中文语义效果会明显更好。向量化后的结果要存到向量数据库里。最简单的实验方案是InMemoryEmbeddingStore重启数据就没了但用来验证流程很方便。生产环境有更多选择Qdrant、Milvus、Pinecone都可以通过 LangChain4j 的模块接进来。EmbeddingModel embeddingModel new AllMiniLmL6V2EmbeddingModel(); EmbeddingStoreTextSegment vectorStore new InMemoryEmbeddingStore(); // 入库 for (TextSegment segment : segments) { Embedding embedding embeddingModel.embed(segment).content(); vectorStore.add(embedding, segment); } // 向量召回 Embedding queryEmbedding embeddingModel.embed(question).content(); ListEmbeddingMatchTextSegment relevant vectorStore.findRelevant(queryEmbedding, 5);5.3 向量召回通道向量召回本身就是一条标准的检索通道。上面的代码已经完成了基本流程把文档片段向量化入库查询时把用户问题也转成向量然后用余弦相似度找最相近的 5 条记录。LangChain4j 还提供了一个更高级的封装叫ContentRetriever你可以把向量召回包装成一个检索器直接塞给AiServices。这样知识库问答的整体链路就更清晰了用户问题进来自动触发检索器把召回结果拼接到系统提示词里再交给模型回答。ContentRetriever retriever EmbeddingStoreContentRetriever.builder() .embeddingStore(vectorStore) .embeddingModel(embeddingModel) .maxResults(5) .minScore(0.6) .build();minScore是个容易踩坑的参数。官方默认值有时对某些模型太苛刻导致什么都召不回来。它的语义是相似度阈值但不同 Embedding 模型的分数分布差异很大我习惯先把日志打开看一批分数再设置合理的阈值而不是拍脑袋填一个数。5.4 关键词召回通道与评分合并关键词通道在生产环境通常用 Elasticsearch 的 BM25 评分或者 PostgreSQL、MySQL 的全文索引。为了演示方便这里用最简单的关键词命中计数来说明ListTextSegment keywordRetrieval(String question, ListTextSegment segments, int topK) { String[] keywords question.split([\\s。、,.?!]); return segments.stream() .sorted(Comparator.comparingInt((TextSegment s) - (int) Arrays.stream(keywords).filter(k - s.text().contains(k)).count()).reversed()) .limit(topK) .collect(Collectors.toList()); }这段代码虽然粗糙但能说明关键词召回通道的本质按文本命中情况给候选片段打分排序。真实项目里直接换成搜索引擎或数据库全文索引就可以了。多路召回以后最关键的步骤是合并两个通道的结果。直接对比向量相似度和关键词命中次数是不公平的因为两个通道的分数量纲完全不同。我推荐用 RRFReciprocal Rank Fusion方法它不看具体分数只看每条候选在各自通道里的排名对每条文档计算其排名倒数的和排名第 1 贡献 1 分第 2 贡献 0.5 分以此类推然后按总分重新排序。score(d) sum(1 / (k rank_i(d))) 其中 k 取 60这个公式的巧妙之处在于既不用考虑分数归一化也不用调权重只要多个通道的排名可靠融合效果就非常稳定。手动算一个例子某文档在向量通道排第 1在关键词通道排第 3则它的得分为1/61 1/63 ≈ 0.0323。另一文档在两个通道都排第 2得分为1/62 1/62 ≈ 0.0323两边基本持平。排名越靠前贡献越大并且不再受原始分数分布影响。5.5 在Spring Boot里跑通完整流程到这里整条多路召回链路已经清晰了。在 Spring Boot 中组装成服务大概长这样Service public class KnowledgeBaseService { private final Assistant assistant; public KnowledgeBaseService( ChatLanguageModel model, EmbeddingStoreTextSegment vectorStore, EmbeddingModel embeddingModel, ListTextSegment corpus) { ContentRetriever vectorRetriever EmbeddingStoreContentRetriever.builder() .embeddingStore(vectorStore) .embeddingModel(embeddingModel) .maxResults(5) .build(); // 两个召回通道的结果合并策略 ContentRetriever hybridRetriever (question) - { ListContent vectorResults vectorRetriever.retrieve(question); ListTextSegment keywordResults keywordRetrieval(question, corpus, 5); return mergeByRrf(vectorResults, keywordResults, 4); }; Assistant assistant AiServices.builder(Assistant.class) .chatLanguageModel(model) .contentRetriever(hybridRetriever) .build(); this.assistant assistant; } public String answer(String question) { return assistant.chat(question); } }mergeByRrf方法按照上一节讲的 RRF 思路实现把两路结果的排名合并取前 4 条再拼接到提示词里。系统提示词里建议加上一段约束“请仅根据提供的参考资料回答如果参考资料中没有答案请直接告诉用户不知道”。这样能明显降低模型编造答案的概率。我自己的经验是这个案例做成以后最好不要停在 Demo 层面。把文档库、索引构建、检索参数都从配置中心管理起来后续调整切分大小、召回数量、RRF 的参数就不用改代码重新发版了。这一步做到位才真正具备上线运维的价值。6. 常见问题与排查实录6.1 依赖冲突与版本坑Spring Boot 项目里引入 LangChain4j最常碰到的问题就是传递依赖冲突。尤其容易出在guava、jackson、reactor这几个库上。Spring Boot 自带了一套版本管理LangChain4j 的传递依赖可能和它不一致轻则运行时报错重则启动直接失败。遇到这类问题第一件事就是在项目根目录执行mvn dependency:tree把依赖树拉出来找到版本冲突的具体位置。然后根据冲突情况决定是排除传递依赖还是统一用更高版本。版本差异引起的 API 变化也要留个心眼。网上老教程里的model.chat(messages)用法在新版本里可能已经废弃换成了model.chat(ChatRequest.builder()...)这样的新写法。编译报错时先去官方仓库看最新文档不要盲目搜索历史帖子。6.2 上下文超长和幻觉模型服务报错里出现 “maximum context length” 之类的提示含义是发送给模型的消息总长度超过了模型支持的上下文窗口。解决办法从两个方向入手一是减少历史消息条数把MessageWindowChatMemory.withMaxMessages(20)改成更小的值二是缩短每条消息的内容比如工具返回的 JSON 做截断召回片段按数量限制。幻觉问题更隐蔽它不报错但回答是错的。我的排查顺序是先看提示词有没有强调“不知道就直说”再看召回结果里是否包含正确答案。很多时候模型产生幻觉是因为召回片段里根本没有相关信息模型被“逼”着编了一个答案。这种情况下调检索参数比调提示词更有效。6.3 工具调用“不听话”或者触发失败工具不触发的排查顺序我一般是这样第一步检查Tool描述是否足够具体。描述里包含“什么时候该调用”和“方法返回什么”比只写方法名称效果好得多。第二步确认使用的模型版本能力是否够强。某些小参数的模型在工具调用上表现很差换一个能力更强的模型往往立刻见效。第三步临时在系统提示词里明确要求“如果需要查询订单请调用 getOrderStatus 工具”先验证工具链路本身没问题再去掉提示词做真实测试。参数解析失败是另一个常见问题。模型生成的 JSON 参数和你的方法签名不匹配时框架会抛出解析错误。解决办法是把工具方法参数类型简化尽量用字符串接收在方法内部再做类型转换。6.4 召回结果融合分数不可比多路召回合并时如果直接用原始分数相加结果基本是错的。向量相似度可能是 0.7 到 0.9 的区间BM25 分数可能是几十关键词命中数可能是 0 到 10三个分数直接相加量纲大的通道会完全主导结果。我之前就踩过这个坑把余弦相似度和两路结果加权相加结果关键词通道排名前几的永远稳居前列向量通道根本插不进来。换用 RRF 只看排名就不会有这个问题。如果还是要用加权分数先做 Min-Max 归一化把每路分数压到 0 到 1 之间再给不同通道赋予权重。权重不要拍脑袋定拿一批真实问题的命中日志做实验对比不同权重下的 Top 挺准率选表现最好的配置。6.5 网络与超时配置模型服务第一次请求通常都比较慢因为需要加载模型、建立连接。timeout设置太短比如 10 秒经常会在模型还没返回时就被客户端判定超时。我建议把超时时间至少设到 60 秒然后再根据实际接口耗时调整。在 Spring Boot 里还可以配一个简单的重试策略遇到 5xx 或连接超时最多重试两三次但要注意别在业务高峰期造成模型服务的重复压力。如果调用云厂商的服务总是连接失败先检查运行环境的网络策略、出口防火墙和 SSL 证书配置这些环境问题排查起来往往比代码问题更耗时。本地模型服务就省心很多把baseUrl指向本机服务通网不通网都可以开发调试。我个人在实际摸索中的体会是LangChain4j 这把工具最大的价值不是省掉几行代码而是把大模型应用开发这件事重新拉回到了 Java 工程师熟悉的轨道上。先把最小链路跑通再把工具调用和多路召回这两个能力吃透就已经跨过入门到实战之间最大的坎了。最后分享一个小技巧给项目加一个--llm.model-name之类的启动参数或者直接用 Spring Boot 的 profile 区分本地模型和云端模型开发期用本地小模型调试逻辑上线切换时成本几乎为零。这一步看着不起眼长期调试下来能帮你省下大量时间和钱。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询