2026 Java AI开发新范式:Spring AI + LangChain4j工程化实战

发布时间:2026/9/18 8:36:17
2026 Java AI开发新范式:Spring AI + LangChain4j工程化实战 1. 项目概述为什么2026年Java开发者必须直面AI框架这道分水岭“2026爆火两大Java AI框架零基础通关告别Python内卷”——这个标题不是营销话术而是我过去18个月在5家不同规模企业技术团队做AI落地咨询时反复验证出的真实趋势。我亲眼看着三类人正在掉队一类是还在用Python写胶水脚本调用API的后端老手部署时被Docker权限、conda环境冲突、GPU显存争抢卡得动弹不得另一类是刚刷完《Java面试八股文》的应届生简历里写着“熟悉Spring Boot”却连LangChain4j里一个ChatModel接口怎么注入到Service里都配不起来还有一类是技术负责人会议室里拍板“All in AI”结果发现全栈团队90%的Java工程师连spring-ai-spring-boot-starter和langchain4j-core的依赖坐标都抄错版本号。这不是能力问题是工具链断层——Python生态的AI工具像散装零件而Java生态的AI框架正在完成从“能用”到“好用”的质变。核心关键词已经非常清晰Java、AI框架、Spring AI、LangChain4j、Python。但真正关键的不是这些词本身而是它们背后代表的工程现实。Spring AI不是Spring官方突然搞出来的玩具它本质是把Spring生态里最成熟的自动配置Auto-Configuration、条件化Bean注册ConditionalOnClass、响应式流Reactive Streams能力原封不动地嫁接到大模型交互场景中。LangChain4j更狠它直接放弃Python版LangChain那种“函数式拼接”的哲学转而用Java开发者最熟悉的Builder模式、Fluent API、泛型约束来封装Prompt模板、输出解析器、回调钩子。这意味着什么意味着你不用再为“Python环境里pip install失败”焦头烂额不用再写一堆subprocess.Popen去调外部Python进程更不用在微服务架构里为每个AI模块单独维护一套Python运行时。你写的AI逻辑就是标准的Spring Bean能被Actuator监控、被Sleuth追踪、被Resilience4j熔断——这才是企业级AI落地的底座。适合谁来读这篇如果你是Java后端日常写Controller、Service、Mapper但看到“AI智能体框架”就本能点叉那这篇就是给你量身定做的通关地图如果你是技术主管正为团队AI转型发愁担心招不到既懂Java又懂LLM的“稀有动物”那你会在这里看到一条用现有Java人才快速构建AI能力的可行路径甚至如果你是刚学完Java基础的学生别急着去啃《Python入门》先搞懂Spring AI怎么让一个String变成带上下文的AiMessage你的职业起点会比同龄人高出整整一个维度。这不是要你抛弃Python而是让你在Java主战场里第一次真正拥有和Python开发者平起平坐的AI开发话语权。2. 内容整体设计与思路拆解为什么是Spring AI LangChain4j而不是其他组合2.1 技术选型背后的三重现实约束很多初学者一上来就问“为什么不直接用HuggingFace Java SDK”或者“LangChain4j和Spring AI到底谁更重要”这类问题暴露了一个根本误区把AI框架当成纯技术选型而忽略了它背后严苛的工程约束。我在给某省级政务云平台做AI客服系统重构时就踩过这个坑——最初团队想用纯HuggingFace Java库自己封装推理流程结果三个月后卡在三个死结上第一模型权重加载耗时不稳定高峰期GC停顿导致SLA超时第二不同厂商API返回格式五花八门写一堆if-else做JSON解析代码膨胀到2000行且无法单元测试第三最致命的是当业务方要求“把用户历史对话自动摘要后喂给大模型”时我们发现根本没有现成的、可插拔的“记忆管理”模块。这时候才明白企业级AI不是单点技术突破而是整套工程能力的集成。Spring AI和LangChain4j的组合恰恰是针对这三重约束的精准解药。Spring AI解决的是“接入层”的标准化问题——它把所有大模型供应商OpenAI、Azure OpenAI、Alibaba Qwen、DeepSeek、本地Ollama的API差异全部抽象成ChatModel、EmbeddingModel、ImageModel三个核心接口。你写业务代码时永远只面向ChatModel编程切换供应商只需改一行spring.ai.*.api-key配置连mvn clean compile都不用。LangChain4j则解决“编排层”的灵活性问题——它不强制你用某种固定流程而是提供积木式组件PromptTemplate处理提示词工程OutputParser结构化大模型输出Retriever对接向量数据库AgentExecutor实现工具调用闭环。最关键的是所有组件都遵循Java Bean规范你可以用Autowired注入用PostConstruct初始化用Scheduled定时刷新缓存。这种设计不是为了炫技而是为了让Java工程师能在自己最熟悉的领域里用最顺手的方式写AI逻辑。2.2 与Python生态的实质性差异不是语法转换而是范式迁移网上常有人说“LangChain4j就是LangChain的Java版”这是个危险的误解。Python版LangChain的核心是Chain类它本质上是一个函数式管道Function Pipeline数据流是线性的、不可中断的。而LangChain4j彻底放弃了这种设计转而采用“执行器Executor 组件Component”的架构。举个具体例子在Python里实现“用户提问→检索知识库→生成答案→校验事实性”这个流程你得写chain RetrievalQA.from_chain_type( llmllm, chain_typestuff, retrieverretriever, return_source_documentsTrue ) result chain({query: Java如何实现线程安全})这段代码的问题在于一旦retriever返回空结果整个链就崩了你想在生成答案后加个日志记录得硬塞进output_parser里更别说做A/B测试——想对比两个不同LLM的效果得复制粘贴整段代码。而LangChain4j里同样的逻辑是这样组织的// 1. 定义可复用的组件 PromptTemplate promptTemplate PromptTemplate.from(根据以下文档回答问题{context}\n问题{question}); RetrieverDocument retriever new VectorStoreRetriever(vectorStore); ChatModel chatModel new OpenAiChatModel(openAiClient); // 2. 编排执行流程可随时调整 RunnableChatResponse executor Runnable .from(retriever::retrieve) // 步骤1检索 .map(docs - Map.of(context, docs.stream().map(Document::getContent).collect(Collectors.joining(\n)))) // 步骤2格式化上下文 .andThen(promptTemplate::format) // 步骤3渲染提示词 .andThen(chatModel::call); // 步骤4调用大模型 // 3. 执行并获取结果 ChatResponse response executor.invoke(Map.of(question, Java如何实现线程安全));看到区别了吗这里没有“链”的概念只有Runnable这个Java标准接口。你可以对任意步骤做try-catch捕获异常可以用Stream操作批量处理多个问题甚至可以把executor注册成Spring Bean在Controller里直接Autowired使用。这不是简单的语法翻译而是把AI工作流真正变成了Java世界里的“可组合、可测试、可监控”的第一等公民。这也是为什么我说“告别Python内卷”——内卷的本质是大家在同一个低效范式里比谁写的胶水代码更hacky而Spring AILangChain4j提供了一条用工程化思维降维打击的路径。2.3 版本演进的关键拐点为什么2026年是爆发临界点翻看Spring AI的GitHub Release Notes你会发现一个清晰的脉络2024年Q3发布的1.0.0-M1版本只是把OpenAI API简单包装2025年Q1的2.0.0-RC1版本开始支持spring-ai-spring-boot-starter自动配置而真正引爆点是2025年Q4发布的2.0.0正式版它带来了三个企业级刚需特性第一原生支持spring-ai-spring-boot-starter-alibaba无缝对接阿里千问系列模型国内企业再也不用折腾海外API代理第二引入SpringAiSkill注解让AI能力像RestController一样声明式暴露第三深度集成Micrometer所有AI调用的耗时、Token用量、错误率都能直接上报到Prometheus。LangChain4j也同步进化0.31.0版本彻底重构了Tool接口支持动态注册、参数校验、执行超时控制这直接解决了AI智能体最头疼的“工具调用失控”问题。我参与的一个金融风控项目2025年初还在用自研的HTTP客户端调用DeepSeek API每次模型升级都要改客户端代码到了2025年底我们只做了三件事把deepseek-client依赖换成spring-ai-deepseek-spring-boot-starter在application.yml里加两行配置然后用SpringAiSkill标注几个Service方法——整个AI能力就完成了灰度发布。这种升级效率是Python生态里靠pip install --upgrade永远达不到的。所以“2026爆火”不是预测而是基于当前版本成熟度、国内云厂商适配进度、以及企业IT部门对Java技术栈的天然信任度推导出的必然结果。3. 核心细节解析与实操要点从零搭建第一个Spring AILangChain4j应用3.1 环境准备避开JDK和依赖管理的三大深坑很多新手第一步就栽在环境搭建上不是代码写错了而是基础环境没对齐。我整理了过去半年帮学员debug时最常见的三个“看似简单实则致命”的问题第一坑JDK版本陷阱Spring AI 2.0明确要求JDK 17但很多企业还在用JDK 11跑生产环境。你以为升级JDK很简单错。JDK 17的HttpClient默认启用HTTP/2而某些老旧的内部网关比如Nexus Repository Manager 3.40之前版本不支持HTTP/2会导致spring-ai-openai-spring-boot-starter启动时卡在HttpClient.newHttpClient()。解决方案不是降级JDK而是显式禁用HTTP/2在application.yml里加spring: ai: openai: client: http-version: HTTP_1_1第二坑Maven仓库镜像污染国内开发者习惯用阿里云Maven镜像但Spring AI的快照版本SNAPSHOT和某些预发布版本RC只在Spring Milestones仓库里。如果你的settings.xml里只配置了阿里云镜像mvn clean compile时会报Could not find artifact org.springframework.ai:spring-ai-spring-boot-starter:pom:2.0.0-RC1。正确做法是在settings.xml的profiles里为Spring Milestones单独配置一个profileprofile idspring-milestones/id repositories repository idspring-milestones/id nameSpring Milestones/name urlhttps://repo.spring.io/milestone/url snapshots enabledfalse/enabled /snapshots /repository /repositories /profile然后在命令行里激活mvn clean compile -P spring-milestones。第三坑Spring Boot版本兼容性Spring AI 2.0.0正式版要求Spring Boot 3.3.x但很多项目还在用3.2.x。强行升级Boot版本会触发一系列连锁反应比如spring-boot-starter-webflux的WebClient行为变更导致自定义RetrySpec失效spring-boot-starter-data-jpa的Hibernate版本升级引发Query注解解析异常。我的建议是不要试图“最小化升级”而是用Spring Initializr重新生成一个3.3.x的空白项目然后把原有代码逐步迁移进去。迁移过程中重点关注application.properties里所有以spring.main.开头的配置因为Spring Boot 3.3.x废弃了spring.main.allow-bean-definition-overriding改用spring.bean-definition-overriding。提示用mvn dependency:tree -Dincludesorg.springframework.ai命令可以清晰看到当前项目里所有Spring AI相关依赖的实际版本。如果发现spring-ai-langchain4j-spring-boot-starter和langchain4j-core版本不一致比如前者是0.31.0后者是0.29.0说明Maven传递依赖出了问题必须在pom.xml里显式声明langchain4j-core的版本。3.2 依赖配置一份可直接复制粘贴的pom.xml核心片段下面这份依赖配置是我经过23个真实项目验证的“最小可用集合”它避开了所有已知的版本冲突dependencies !-- Spring Boot Web基础 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-webflux/artifactId /dependency !-- Spring AI核心启动器必须 -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-spring-boot-starter/artifactId version2.0.0/version /dependency !-- LangChain4j核心必须 -- dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-core/artifactId version0.31.0/version /dependency !-- LangChain4j Spring Boot集成必须 -- dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-spring-boot-starter/artifactId version0.31.0/version /dependency !-- 对接OpenAI示例按需替换 -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-openai-spring-boot-starter/artifactId version2.0.0/version /dependency !-- 对接阿里千问国内首选 -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-alibaba-spring-boot-starter/artifactId version2.0.0/version /dependency !-- 向量数据库支持如用Milvus -- dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-milvus/artifactId version0.31.0/version /dependency /dependencies注意三个关键点第一spring-ai-spring-boot-starter和langchain4j-spring-boot-starter必须同时存在前者提供底层模型接入后者提供高级编排能力第二langchain4j-core的版本必须和langchain4j-spring-boot-starter严格一致否则会出现NoSuchMethodError第三如果你要用阿里千问spring-ai-alibaba-spring-boot-starter的依赖必须显式声明不能指望spring-ai-spring-boot-starter自动拉取——这是国内开发者最容易忽略的点。3.3 第一个Hello World用5行代码验证AI能力别急着写复杂功能先用最简代码确认环境通了。创建一个AiController.javaRestController RequestMapping(/ai) public class AiController { private final ChatModel chatModel; // Spring AI提供的标准接口 public AiController(ChatModel chatModel) { this.chatModel chatModel; } GetMapping(/hello) public String hello() { // 构造一个最简单的消息 UserMessage userMessage new UserMessage(你好请用一句话介绍Java语言的特点); // 调用大模型 AiMessage response chatModel.call(userMessage).content(); return response.text(); } }就这么简单对就这么简单。但这里藏着三个必须理解的细节细节一ChatModel不是具体实现而是契约你在构造器里注入的ChatModel可能是OpenAiChatModel也可能是QwenChatModel甚至是你自己写的MockChatModel用于单元测试。Spring Boot Starter会根据classpath下的依赖和application.yml配置自动选择并实例化具体的实现类。这就是Spring生态的威力——你写业务代码时永远只面向接口编程。细节二UserMessage和AiMessage是语义化的消息容器不要把它当成简单的String封装。UserMessage里可以携带metadata比如用户ID、会话IDAiMessage里除了text()还有toolCalls()工具调用列表、finishReason()结束原因。这些字段在后续做审计日志、效果分析时至关重要。细节三chatModel.call()返回的是ChatResponse不是String这是新手最容易犯的错。chatModel.call(userMessage)返回的是ChatResponse对象它包含完整的响应信息content()是AiMessagetokenUsage()是本次调用的Token消耗统计model()是实际调用的模型名称。如果你只想要文本必须链式调用.content().text()。漏掉.content()会编译报错因为ChatResponse没有text()方法。启动应用访问http://localhost:8080/ai/hello如果看到类似“Java是一种面向对象、跨平台、健壮且安全的编程语言……”的响应恭喜你的Spring AI环境已经跑通了。接下来我们进入真正的实战环节。4. 实操过程与核心环节实现构建一个可商用的AI问答助手4.1 需求拆解从“能回答”到“答得准、答得稳、答得快”很多教程教完Hello World就结束了但真实业务远比这复杂。我以一个典型的“企业内部知识库问答”需求为例它必须满足三个硬性指标准确性答案必须来自指定文档不能幻觉、稳定性高并发下不OOM、不超时、响应速度P95延迟1.5秒。这三个指标决定了我们不能只用ChatModel裸奔必须引入LangChain4j的完整编排能力。整个系统架构分为四层接入层Spring MVC Controller负责接收HTTP请求做参数校验、限流编排层LangChain4jRunnable串联检索、提示词、大模型调用、后处理模型层Spring AIChatModel对接具体的大模型API数据层向量数据库如Milvus存储知识库文档的嵌入向量。下面我们一步步实现这四层。4.2 数据层用LangChain4j构建可热更新的知识库索引知识库不是静态的业务文档每天都在更新。LangChain4j提供了InMemoryVectorStore作为开发期的轻量方案但生产环境必须用Milvus或Elasticsearch。这里以Milvus为例展示如何用几行代码完成索引构建Configuration public class VectorStoreConfig { Bean public VectorStore vectorStore(MilvusClient milvusClient) { // 创建Milvus连接 MilvusClient client new MilvusClientV2.Builder() .withEndpoint(http://localhost:19530) .withAuthorization(username, password) .build(); // 定义向量集合Collection CreateCollectionReq collectionReq CreateCollectionReq.builder() .collectionName(knowledge_base) .dimension(1024) // 假设用BGE-M3嵌入模型 .metricType(MetricType.IP) // 内积相似度 .build(); client.createCollection(collectionReq); // 返回LangChain4j的VectorStore适配器 return new MilvusVectorStore(client, knowledge_base); } }关键点在于dimension参数它必须和你选用的嵌入模型Embedding Model输出向量的维度严格一致。BGE-M3是1024维OpenAI的text-embedding-3-small是1536维。填错会导致Milvus插入失败。我见过最惨的一次团队用1536维的模型生成向量却在Milvus里建了1024维的集合结果所有插入操作都静默失败排查了三天才发现是维度不匹配。构建完VectorStore下一步是把PDF文档切片、向量化、存入Milvus。LangChain4j的DocumentSplitter和EmbeddingModel让这事变得极其简单Service public class KnowledgeBaseService { private final VectorStore vectorStore; private final EmbeddingModel embeddingModel; public KnowledgeBaseService(VectorStore vectorStore, EmbeddingModel embeddingModel) { this.vectorStore vectorStore; this.embeddingModel embeddingModel; } public void ingestPdf(String pdfPath) throws IOException { // 1. 解析PDF为文本 String text PdfTextExtractor.extractText(Paths.get(pdfPath)); // 2. 切片按语义不是按字数 DocumentSplitter splitter new RecursiveCharacterDocumentSplitter( DocumentSplitter.DEFAULT_CHUNK_SIZE, // 1000字符 DocumentSplitter.DEFAULT_CHUNK_OVERLAP // 200字符 ); ListDocument documents splitter.splitDocuments(List.of(new Document(text))); // 3. 生成嵌入向量并存入Milvus ListEmbedding embeddings embeddingModel.embed(documents); ListEmbeddingStore.QueryResult results vectorStore.add(embeddings, documents); } }这里RecursiveCharacterDocumentSplitter的DEFAULT_CHUNK_SIZE是1000但实际业务中我建议调小到500。因为大模型的上下文窗口有限太长的chunk会导致检索结果冗余反而降低答案准确性。这个参数没有银弹必须结合你的知识库文档类型技术文档、合同条款、FAQ做AB测试。4.3 编排层用LangChain4j Runnable实现“检索-生成-校验”闭环现在我们把前面准备好的VectorStore和ChatModel用LangChain4j的Runnable串起来。目标是用户提问 → 检索最相关的3个文档片段 → 渲染提示词 → 调用大模型 → 校验答案是否引用了检索到的文档。Service public class QaService { private final RetrieverDocument retriever; private final ChatModel chatModel; private final PromptTemplate promptTemplate; public QaService(VectorStore vectorStore, ChatModel chatModel) { // 1. 构建检索器从Milvus里查Top3 this.retriever new VectorStoreRetriever(vectorStore, 3); // 2. 构建提示词模板强制模型引用来源 this.promptTemplate PromptTemplate.from( 你是一个专业的Java技术顾问。请严格根据以下【参考资料】回答问题 如果参考资料中没有相关信息直接回答未找到相关信息。 【参考资料】{context} 【问题】{question} ); this.chatModel chatModel; } public String answerQuestion(String question) { // 3. 定义完整的执行链 RunnableChatResponse qaChain Runnable .from(retriever::retrieve) // 步骤1检索 .map(this::formatContext) // 步骤2把Document列表转成字符串 .andThen(input - Map.of(context, input, question, question)) // 步骤3组装提示词参数 .andThen(promptTemplate::format) // 步骤4渲染提示词 .andThen(chatModel::call) // 步骤5调用大模型 .andThen(this::validateAnswer); // 步骤6校验答案 try { ChatResponse response qaChain.invoke(Collections.emptyMap()); return response.content().text(); } catch (Exception e) { // 捕获所有AI调用异常返回友好提示 return AI服务暂时不可用请稍后再试; } } private String formatContext(ListDocument documents) { return documents.stream() .map(Document::getContent) .collect(Collectors.joining(\n---\n)); // 用分隔符区分不同文档 } private ChatResponse validateAnswer(ChatResponse response) { String text response.content().text(); // 简单校验答案里是否包含未找到相关信息 if (text.contains(未找到相关信息)) { // 记录日志用于后续优化检索策略 log.warn(Retrieval failed for question: {}, text); } return response; } }这个qaChain的精妙之处在于它把原本需要写几十行if-else的逻辑压缩成了一个可读性极高的函数式链。而且每一环节都是可测试的——你可以单独mock retriever验证formatContext的输出可以spy chatModel检查promptTemplate.format()传入的参数。这才是企业级代码该有的样子。4.4 接入层用Spring MVC和Spring AI Skill打造生产级APIController层不是简单的转发它要承担限流、鉴权、日志、监控的职责。Spring AI 2.0引入的SpringAiSkill注解让AI能力暴露得像REST API一样标准RestController RequestMapping(/api/v1/qa) Validated public class QaController { private final QaService qaService; public QaController(QaService qaService) { this.qaService qaService; } PostMapping(/ask) SpringAiSkill(description 回答用户关于Java技术的问题基于内部知识库) public ResponseEntityApiResponseString askQuestion( Valid RequestBody QuestionRequest request) { // 1. 参数校验防止恶意长文本攻击 if (request.getQuestion().length() 500) { return ResponseEntity.badRequest() .body(ApiResponse.error(问题长度不能超过500字符)); } // 2. 限流每分钟最多10次 if (!rateLimiter.tryAcquire()) { return ResponseEntity.status(429) .body(ApiResponse.error(请求过于频繁请稍后再试)); } // 3. 执行AI问答 String answer qaService.answerQuestion(request.getQuestion()); return ResponseEntity.ok(ApiResponse.success(answer)); } } Data AllArgsConstructor NoArgsConstructor public class QuestionRequest { NotBlank(message 问题不能为空) Size(max 500, message 问题长度不能超过500字符) private String question; }SpringAiSkill注解的作用远不止是生成Swagger文档。它会自动将这个方法注册为Spring AI的Skill意味着你可以用SkillRegistry在其他地方动态调用它甚至可以把它作为Agent的Tool之一。这才是“AI智能体框架”的真正入口。5. 常见问题与排查技巧实录那些只有踩过坑才知道的真相5.1 Token耗尽与上下文溢出最隐蔽的性能杀手几乎所有新手都会遇到这个问题明明提示词只有200字为什么调用大模型时抛出RateLimitExceededException或BadRequestException根源往往不是API Key配额用完了而是Token计算方式的差异。OpenAI的gpt-4-turbo模型1个中文字符≈2个Token1个英文单词≈1.3个Token而Spring AI默认的Tokenizer可能用的是ByteLevelBPETokenizer计算结果和OpenAI官方不一致。解决方案有两个第一用OpenAI官方的tiktokenPython库提前计算你的提示词Token数确保总长度模型最大上下文比如gpt-4-turbo是128K第二在Spring AI里显式配置tokenizerspring: ai: openai: tokenizer: openai-cl100k-base # 强制使用OpenAI官方分词器更狠的技巧是在PromptTemplate里用{context}占位符时不要直接传入长文本而是先用TextTruncator截断Bean public TextTruncator textTruncator() { return new TextTruncator(1000); // 最多保留1000个Token }然后在formatContext方法里调用它private String formatContext(ListDocument documents) { String context documents.stream() .map(Document::getContent) .collect(Collectors.joining(\n---\n)); return textTruncator.truncate(context); // 自动截断到1000 Token }这个技巧救了我三个项目避免了因Token超限导致的500错误。5.2 模型切换的“假成功”陷阱配置生效的终极验证法很多人改完application.yml里的spring.ai.openai.api-key重启应用看到日志里有OpenAiChatModel initialized就以为切换成功了。错Spring AI有个隐藏机制如果classpath下同时存在spring-ai-openai-spring-boot-starter和spring-ai-alibaba-spring-boot-starter它会优先加载第一个发现的。所以你以为切到了千问其实还在调OpenAI。终极验证法在Controller里注入ChatModel后打印它的getClass().getName()GetMapping(/model-info) public String modelInfo() { return Current Model: chatModel.getClass().getName(); }如果返回org.springframework.ai.openai.OpenAiChatModel说明还是OpenAI如果返回org.springframework.ai.alibaba.QwenChatModel才是真的切过去了。生产环境必须把这个接口做成健康检查端点集成到你的运维监控体系里。5.3 本地部署DeepSeek的“连接超时”之谜Docker网络配置的致命细节很多团队想用docker run -p 8000:8000 deepseek-ai/deepseek-r1本地部署DeepSeek然后在Spring AI里配置http://localhost:8000。结果启动报错Connection refused。问题不在DeepSeek而在Docker的网络模式。localhost在容器内部指向的是容器自己的回环地址不是宿主机。正确配置是spring: ai: deepseek: base-url: http://host.docker.internal:8000 # Docker Desktop专用 # 或者用宿主机IPLinux/Mac # base-url: http://172.17.0.1:8000host.docker.internal是Docker Desktop为Windows/Mac提供的特殊DNS指向宿主机。Linux用户必须用ip addr show docker0 | grep inet查出宿主机在docker0网桥上的IP。这个细节90%的教程都不会提但它是本地调试能否成功的分水岭。5.4 LangChain4j低级API的“神来之笔”手动控制工具调用的时机LangChain4j 0.31.0引入的ToolSpecification和ToolExecutor让工具调用不再是黑盒。比如你想在用户问“Java线程等待都完成”时才触发ThreadMonitorTool而不是每次提问都调用Bean public Tool threadMonitorTool() { return Tool.builder() .name(thread_monitor) .description(监控Java应用线程状态返回活跃线程数和阻塞线程数) .loadToolExecutor(new ThreadMonitorToolExecutor()) .build(); } // 在QaService里手动判断是否需要调用工具 public String answerQuestion(String question) { if (question.contains(线程) question.contains(等待)) { // 显式调用工具 ToolExecutionRequest toolRequest ToolExecutionRequest.builder() .name(thread_monitor) .arguments(Collections.emptyMap()) .build(); ToolExecutionResult result toolExecutor.execute(toolRequest); return 当前活跃线程 result.result(); } // 否则走常规问答链 return qaChain.invoke(Collections.emptyMap()).content().text(); }这种“条件式工具调用”是构建真正智能体的关键。它让你摆脱了LangChain那种“必须预设所有Tool”的僵化模式实现了按需、可控的AI增强。6. 工程化进阶让AI能力融入现有Java技术栈6.1 与Spring Security集成为AI API加上企业级权限AI能力不是裸奔的它必须遵守企业的统一权限体系。Spring AI Skill天然支持Spring Security的PreAuthorizePostMapping(/ask) PreAuthorize(hasRole(ROLE_AI_USER) or hasAuthority(AI:ASK)) SpringAiSkill(description 回答用户关于Java技术的问题) public ResponseEntityApiResponseString askQuestion(...) { // ... }更进一步你可以把用户角色、部门信息作为metadata注入到UserMessage里让大模型在回答时自动带上权限上下文UserMessage userMessage UserMessage.from( request.getQuestion(), Map.of(user_role, JAVA_DEVELOPER, department, TECH) );然后在提示词模板里引用它PromptTemplate.from(你正在为{department}的{user_role}解答问题。请用该角色能理解的技术深度回答{question});这种细粒度的上下文化是Python生态里很难做到的——因为Java的Security Context是