Java AI框架停更不用慌:应用层开发与替代方案全解析
1. 一个框架停更引发的技术选型焦虑前几天在技术群里看到有人转了一条消息说某个Java生态的AI应用框架停更了群里瞬间炸了锅。有人感叹“Java果然不适合搞AI”有人开始盘算要不要转Go或者Python还有人直接甩出一句“Java已死有事烧纸”。我当时正在用Java写一个RAG检索增强生成的服务端模块看到这些讨论说实话心里也咯噔了一下。但冷静下来之后我把手上的活儿干完了服务跑得稳稳当当该调的接口一个没少。这件事让我意识到一个很普遍的问题很多开发者把某个具体框架的生死等同于整个技术栈的生死。某个开源项目停更了就觉得天塌了觉得自己选错了路。这种焦虑我完全理解毕竟谁都不想在一个“没有未来”的技术栈上投入时间。但事实真的如此吗Java在AI应用开发这条路上到底还有没有戏如果某个框架不维护了我们这些写Java的人该怎么办这篇文章就是想把这个问题掰开揉碎了聊一聊。我会从Java在AI领域的实际定位讲起分析一个框架停更到底意味着什么然后给出几条切实可行的替代路径和实操方案。无论你是刚接触AI应用开发的Java程序员还是正在做技术选型的技术负责人这篇文章都能给你一些参考。我不会给你灌鸡汤说“Java永远滴神”也不会劝你“赶紧转Python保平安”咱们就用工程师的方式把问题拆开看把方案列出来把代码跑起来。2. Java在AI应用层的真实生态位2.1 先搞清楚Java在AI领域到底干什么很多人一提到“Java搞AI”脑子里浮现的画面是用Java训练大模型。这个方向确实存在比如Deeplearning4j、DJL这些框架但说实话用Java做模型训练从来都不是主流以后大概率也不会是。这不是Java的问题而是生态积累的问题——Python在数据科学和机器学习领域积累了二十年的库和工具链这个护城河太深了。但AI应用开发不等于模型训练。一个完整的AI系统模型训练只是其中一环而且往往是前期一次性投入的一环。真正长期运行、需要高并发、高可用、需要和企业现有系统集成的部分是应用层。这包括API网关、业务逻辑编排、数据管道、权限控制、监控告警、事务管理等等。这些恰恰是Java最擅长的领域。我参与过的一个智能客服项目就是典型例子。模型训练用Python做的但整个服务端是Java写的。为什么因为要对接企业已有的订单系统、用户系统、工单系统这些全是Java写的。用Java做应用层可以直接复用现有的SDK、现有的连接池、现有的事务管理机制不用重新造轮子。模型推理服务通过HTTP或者gRPC暴露接口Java这边调用就行了简单直接。所以第一个要建立的认知是Java在AI领域的核心价值在应用层不在训练层。一个AI应用框架停更了影响的是应用层开发的便利性而不是Java做AI应用的能力。这个区别很关键。2.2 框架停更到底意味着什么一个开源框架停更很多人第一反应是“不能用了”。但实际情况要分几种来看。第一种情况是框架完全停止维护issue没人回PR没人合依赖的底层库有安全漏洞也不修。这种确实要谨慎继续用下去会积累技术债。第二种情况是框架进入维护模式不再加新功能但关键bug还是会修安全补丁还是会打。这种其实可以用只是不要指望它跟进最新的模型能力。第三种情况是原作者不维护了但社区有人fork出来继续做这种情况在开源世界很常见。我经历过好几次框架停更或者半停更的情况。有一次是一个做工作流编排的库作者去创业了项目搁置了。我们当时的做法是先把版本锁死然后把核心逻辑抽出来自己做了一层薄封装这样即使以后要换框架改动量也可控。这个策略后来证明是对的半年后我们换了一个更活跃的框架因为之前做了隔离层迁移只花了三天。所以面对框架停更正确的姿势不是恐慌而是评估这个框架在你的系统里承担了什么角色这个角色有没有替代方案替换成本有多大如果替换成本可控那就从容切换如果替换成本很高那就先做隔离再慢慢迁移。最怕的是什么都不做既不敢用也不敢换卡在中间干着急。2.3 Java AI应用开发的替代路径盘点既然某个具体框架可能靠不住那Java开发者有哪些靠谱的选择我梳理了几条经过实际验证的路径。第一条路是直接用HTTP客户端调用模型API。这是最原始但也最稳定的方式。不管什么模型服务最终都是通过HTTP或者gRPC暴露接口。你用Java的HttpClient、OkHttp、WebClient直接发请求自己封装一层调用逻辑完全可控。这种方式的好处是零框架依赖坏处是要自己处理重试、限流、序列化、流式响应等细节。但对于有一定经验的开发者来说这些都不是难事。第二条路是使用Spring AI或者LangChain4j这类通用框架。Spring AI是Spring官方出的虽然年轻但背靠Spring生态长期维护有保障。LangChain4j是社区驱动的活跃度不错功能也比较全。这两个框架我都用过各有优劣后面会详细对比。第三条路是基于现有的Java生态自己组装。比如用Resilience4j做熔断限流用Jackson做序列化用Reactor做流式处理用Micrometer做监控。这些库都是经过大规模生产验证的组合起来就是一个高度定制化的AI应用框架。这种方式最灵活但前期投入也最大。第四条路是混合架构。核心业务用JavaAI相关的实验性功能用Python写微服务通过消息队列或者RPC通信。这种方式适合团队里既有Java又有Python开发者的场景各取所长。这四条路没有绝对的好坏关键看你的团队情况、项目阶段和长期规划。下面我会逐一展开给出具体的实操方案。3. 替代方案深度对比与选型实操3.1 Spring AI与LangChain4j的详细对比先说我自己的使用感受。Spring AI的优势在于和Spring Boot的无缝集成。如果你已经在用Spring Boot引入Spring AI就是加一个starter的事情配置方式、依赖注入、自动装配全都是熟悉的套路。它的ChatClient接口设计得比较简洁上手快。但Spring AI目前对复杂Agent编排的支持还比较弱如果你想做多步骤的工具调用链需要自己写不少代码。LangChain4j的功能更丰富支持的工具调用、记忆管理、RAG组件更多。它的AiServices注解式声明用起来很舒服定义一个接口加上注解就能生成实现类。但LangChain4j的版本迭代比较快API偶尔会有破坏性变更升级时需要留意。另外它和Spring Boot的集成虽然也有starter但不如Spring AI那么原生。下面这个表格是我在实际项目中总结的对比供你选型参考。对比维度Spring AILangChain4j维护方Spring官方团队社区驱动与Spring Boot集成原生无缝有starter但非原生核心抽象ChatClient、AdvisorAiServices、ChatLanguageModel工具调用支持较基础支持较完善RAG支持有VectorStore抽象有完整的RAG链路组件流式响应支持支持版本稳定性较稳定迭代快偶有破坏性变更学习曲线低中等适合场景已有Spring Boot项目快速接入需要复杂AI编排的场景我的建议是如果你的项目已经在Spring Boot体系内而且AI功能相对简单就是调用模型、做做RAG优先选Spring AI。如果你需要复杂的Agent编排、多轮工具调用、精细的记忆管理LangChain4j更合适。当然两者也可以混用比如用Spring AI做基础调用用LangChain4j的某些组件做特定功能但要注意依赖冲突。3.2 零框架依赖的纯HTTP调用方案有时候最笨的办法最可靠。如果你不想引入任何AI框架直接用Java的HTTP客户端调用模型API完全可行。我来演示一个完整的实现。首先定义一个配置类管理API地址和密钥。import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; Component ConfigurationProperties(prefix ai.model) public class ModelProperties { private String baseUrl; private String apiKey; private String modelName; private int timeoutSeconds 60; // getters and setters public String getBaseUrl() { return baseUrl; } public void setBaseUrl(String baseUrl) { this.baseUrl baseUrl; } public String getApiKey() { return apiKey; } public void setApiKey(String apiKey) { this.apiKey apiKey; } public String getModelName() { return modelName; } public void setModelName(String modelName) { this.modelName modelName; } public int getTimeoutSeconds() { return timeoutSeconds; } public void setTimeoutSeconds(int timeoutSeconds) { this.timeoutSeconds timeoutSeconds; } }然后写一个调用服务用Java 11自带的HttpClient。import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import org.springframework.stereotype.Service; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; Service public class RawModelService { private final ModelProperties props; private final HttpClient httpClient; private final ObjectMapper objectMapper; public RawModelService(ModelProperties props) { this.props props; this.objectMapper new ObjectMapper(); this.httpClient HttpClient.newBuilder() .connectTimeout(Duration.ofSeconds(10)) .build(); } public String chat(String userMessage) throws Exception { ObjectNode requestBody objectMapper.createObjectNode(); requestBody.put(model, props.getModelName()); ArrayNode messages requestBody.putArray(messages); ObjectNode userMsg messages.addObject(); userMsg.put(role, user); userMsg.put(content, userMessage); String json objectMapper.writeValueAsString(requestBody); HttpRequest request HttpRequest.newBuilder() .uri(URI.create(props.getBaseUrl() /chat/completions)) .header(Content-Type, application/json) .header(Authorization, Bearer props.getApiKey()) .timeout(Duration.ofSeconds(props.getTimeoutSeconds())) .POST(HttpRequest.BodyPublishers.ofString(json)) .build(); HttpResponseString response httpClient.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() ! 200) { throw new RuntimeException(API调用失败状态码 response.statusCode() 响应 response.body()); } return objectMapper.readTree(response.body()) .path(choices).path(0).path(message).path(content).asText(); } }这段代码没有任何AI框架依赖就是纯粹的HTTP调用。你可以完全控制请求参数、超时时间、重试逻辑。缺点是要自己处理流式响应、工具调用等高级功能但对于简单的对话场景这已经足够了。注意API密钥不要硬编码在代码里用环境变量或者配置中心管理。另外记得设置合理的超时时间模型推理有时候会比较慢超时太短会导致请求失败。3.3 自组装方案用成熟库搭建AI应用骨架如果你想要框架的便利性又不想被单一框架绑定可以自己用成熟的Java库组装一套。我推荐几个经过验证的组合。熔断限流用Resilience4j这是Hystrix停止维护后的主流选择。它的CircuitBreaker和RateLimiter模块可以很好地保护模型调用防止因为模型服务不稳定导致整个系统雪崩。流式处理用Reactor。模型返回的流式响应可以转成Flux然后和Spring WebFlux配合实现服务端的流式推送。这样前端可以一个字一个字地显示模型输出体验好很多。序列化用Jackson这个不用多说Java生态的事实标准。监控用Micrometer加Prometheus可以统计调用次数、延迟分布、错误率等关键指标。下面是一个用Resilience4j包装模型调用的示例。import io.github.resilience4j.circuitbreaker.CircuitBreaker; import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig; import io.github.resilience4j.ratelimiter.RateLimiter; import io.github.resilience4j.ratelimiter.RateLimiterConfig; import java.time.Duration; import java.util.function.Supplier; public class ResilientModelCaller { private final CircuitBreaker circuitBreaker; private final RateLimiter rateLimiter; private final RawModelService modelService; public ResilientModelCaller(RawModelService modelService) { this.modelService modelService; CircuitBreakerConfig cbConfig CircuitBreakerConfig.custom() .failureRateThreshold(50) .waitDurationInOpenState(Duration.ofSeconds(30)) .slidingWindowSize(10) .build(); this.circuitBreaker CircuitBreaker.of(modelApi, cbConfig); RateLimiterConfig rlConfig RateLimiterConfig.custom() .limitForPeriod(10) .limitRefreshPeriod(Duration.ofSeconds(1)) .timeoutDuration(Duration.ofMillis(500)) .build(); this.rateLimiter RateLimiter.of(modelApi, rlConfig); } public String call(String message) { SupplierString decorated CircuitBreaker.decorateSupplier( circuitBreaker, RateLimiter.decorateSupplier(rateLimiter, () - { try { return modelService.chat(message); } catch (Exception e) { throw new RuntimeException(e); } }) ); return decorated.get(); } }这套组合的好处是每个组件都是独立的、经过大规模验证的库你可以按需选用不会被某个AI框架的生死绑架。坏处是需要自己写一些胶水代码前期投入比直接用框架大。但对于长期维护的项目来说这个投入是值得的。4. 从零搭建一个可用的Java AI应用服务4.1 项目结构与依赖配置光说理论没意思咱们直接动手搭一个完整的项目。这个项目会包含模型调用、RAG检索、对话记忆、流式响应四个核心功能全部用Java实现不依赖任何可能停更的AI框架。项目结构如下java-ai-service/ ├── pom.xml ├── src/main/java/com/example/aiservice/ │ ├── AiServiceApplication.java │ ├── config/ │ │ ├── ModelProperties.java │ │ └── WebClientConfig.java │ ├── controller/ │ │ └── ChatController.java │ ├── service/ │ │ ├── RawModelService.java │ │ ├── RagService.java │ │ └── ConversationMemory.java │ └── model/ │ ├── ChatRequest.java │ └── ChatResponse.java └── src/main/resources/ └── application.ymlpom.xml的关键依赖dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-webflux/artifactId /dependency dependency groupIdio.github.resilience4j/groupId artifactIdresilience4j-spring-boot3/artifactId version2.1.0/version /dependency dependency groupIdcom.fasterxml.jackson.core/groupId artifactIdjackson-databind/artifactId /dependency /dependenciesapplication.yml配置ai: model: base-url: ${AI_BASE_URL:https://api.example.com/v1} api-key: ${AI_API_KEY:your-key-here} model-name: ${AI_MODEL_NAME:default-model} timeout-seconds: 60 resilience4j: circuitbreaker: instances: modelApi: failure-rate-threshold: 50 wait-duration-in-open-state: 30s sliding-window-size: 10 ratelimiter: instances: modelApi: limit-for-period: 10 limit-refresh-period: 1s timeout-duration: 500ms这个配置把模型地址、密钥、模型名都做成了环境变量方便在不同环境切换。Resilience4j的配置直接写在yml里Spring Boot会自动装配。4.2 对话记忆的Java实现对话记忆是AI应用的核心功能之一。没有记忆每次对话都是独立的模型不知道上下文。实现记忆的方式有很多种最简单的是在内存里维护一个滑动窗口只保留最近N轮对话。import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; Component public class ConversationMemory { private static final int MAX_HISTORY 20; private final MapString, ListMessage store new ConcurrentHashMap(); public record Message(String role, String content) {} public void addMessage(String sessionId, String role, String content) { store.computeIfAbsent(sessionId, k - new ArrayList()) .add(new Message(role, content)); ListMessage history store.get(sessionId); if (history.size() MAX_HISTORY) { history.subList(0, history.size() - MAX_HISTORY).clear(); } } public ListMessage getHistory(String sessionId) { return store.getOrDefault(sessionId, List.of()); } public void clear(String sessionId) { store.remove(sessionId); } }这个实现用ConcurrentHashMap保证线程安全每个sessionId对应一个消息列表。超过MAX_HISTORY条时从头部删除最旧的消息。这就是所谓的滑动窗口记忆。注意内存记忆在服务重启后会丢失而且多实例部署时无法共享。生产环境建议把记忆存到Redis里用List结构存储设置过期时间。这样既支持多实例共享又能自动清理不活跃的会话。如果要存Redis把上面的Map操作换成RedisTemplate的opsForList操作即可逻辑是一样的。关键是设置合理的过期时间比如30分钟避免无用数据堆积。4.3 RAG检索增强的完整链路RAG是当前AI应用最实用的技术之一。它的核心思路是先把知识库文档转成向量存起来用户提问时先检索最相关的文档片段然后把片段和问题一起发给模型让模型基于这些片段回答。这样模型就能回答它训练数据里没有的、你私有的知识。完整的RAG链路包括文档加载、文本分块、向量化、向量存储、相似度检索、上下文拼接。我用Java实现一个简化版。文本分块是最关键的一步。块太大检索不精准块太小上下文不完整。我的经验是每块500到800个字符块之间重叠100个字符左右。重叠是为了避免关键信息刚好被切在边界上。import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; Component public class TextChunker { private static final int CHUNK_SIZE 600; private static final int OVERLAP 100; public ListString chunk(String text) { ListString chunks new ArrayList(); int start 0; while (start text.length()) { int end Math.min(start CHUNK_SIZE, text.length()); // 尽量在句号、问号、换行处切分 if (end text.length()) { int lastBreak findLastBreak(text, start, end); if (lastBreak start) { end lastBreak; } } chunks.add(text.substring(start, end).trim()); start end - OVERLAP; if (start 0) start 0; if (end text.length()) break; } return chunks; } private int findLastBreak(String text, int start, int end) { for (int i end - 1; i start; i--) { char c text.charAt(i); if (c 。 || c || c || c \n || c .) { return i 1; } } return -1; } }向量化部分你可以调用模型服务提供的embedding接口也可以用本地的embedding模型。这里假设调用远程接口。import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import org.springframework.stereotype.Service; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.util.ArrayList; import java.util.List; Service public class EmbeddingService { private final ModelProperties props; private final HttpClient httpClient; private final ObjectMapper objectMapper; public EmbeddingService(ModelProperties props) { this.props props; this.httpClient HttpClient.newHttpClient(); this.objectMapper new ObjectMapper(); } public ListDouble embed(String text) throws Exception { ObjectNode body objectMapper.createObjectNode(); body.put(model, embedding-model); body.put(input, text); HttpRequest request HttpRequest.newBuilder() .uri(URI.create(props.getBaseUrl() /embeddings)) .header(Content-Type, application/json) .header(Authorization, Bearer props.getApiKey()) .POST(HttpRequest.BodyPublishers.ofString( objectMapper.writeValueAsString(body))) .build(); HttpResponseString response httpClient.send(request, HttpResponse.BodyHandlers.ofString()); var embeddingArray objectMapper.readTree(response.body()) .path(data).path(0).path(embedding); ListDouble result new ArrayList(); embeddingArray.forEach(node - result.add(node.asDouble())); return result; } }相似度计算用余弦相似度这是最常用的向量相似度度量。public double cosineSimilarity(ListDouble a, ListDouble b) { double dotProduct 0.0; double normA 0.0; double normB 0.0; for (int i 0; i a.size(); i) { dotProduct a.get(i) * b.get(i); normA a.get(i) * a.get(i); normB b.get(i) * b.get(i); } if (normA 0 || normB 0) return 0; return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB)); }检索的时候把用户问题也向量化然后和所有文档块的向量算相似度取Top K个最相似的块拼接到prompt里。public String buildRagPrompt(String question, ListString relevantChunks) { StringBuilder sb new StringBuilder(); sb.append(请基于以下参考资料回答问题。如果参考资料中没有相关信息); sb.append(请如实告知不要编造。\n\n); sb.append(参考资料\n); for (int i 0; i relevantChunks.size(); i) { sb.append([).append(i 1).append(] ); sb.append(relevantChunks.get(i)).append(\n\n); } sb.append(问题).append(question); return sb.toString(); }这套RAG链路完全用Java实现没有依赖任何AI框架。向量存储可以用内存的List也可以用Redis的向量搜索功能或者专门的向量数据库。对于中小规模的知识库内存存储加暴力检索完全够用。数据量大了再考虑上专门的向量数据库。4.4 流式响应的服务端实现流式响应能大幅提升用户体验让用户不用等模型全部生成完才看到内容。用Spring WebFlux的Flux来实现。import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; import reactor.core.publisher.Flux; RestController RequestMapping(/api/chat) public class ChatController { private final RawModelService modelService; private final ConversationMemory memory; public ChatController(RawModelService modelService, ConversationMemory memory) { this.modelService modelService; this.memory memory; } PostMapping(value /stream, produces MediaType.TEXT_EVENT_STREAM_VALUE) public FluxString streamChat(RequestBody ChatRequest request) { memory.addMessage(request.sessionId(), user, request.message()); return Flux.create(sink - { try { // 这里调用流式接口逐块推送 modelService.chatStream(request.message(), chunk - { sink.next(chunk); }); sink.complete(); } catch (Exception e) { sink.error(e); } }); } }服务端的流式实现关键是模型API要支持stream模式返回的是SSE格式的数据流。Java这边用HttpClient的BodyHandlers.ofLines()或者ofInputStream()来逐行读取解析出每个chunk的内容再推送给前端。注意流式响应要处理好连接断开的情况。用户关闭页面或者网络中断时要及时取消对模型API的请求避免资源浪费。可以用Flux的doOnCancel回调来做清理。5. 实操中的典型问题与排查经验5.1 模型调用超时与重试策略模型调用超时是最常见的问题。尤其是长文本生成有时候要几十秒才能返回。我的经验是超时时间设置要分场景简单对话30秒够了长文生成至少120秒RAG场景因为要检索再加生成给180秒比较稳妥。重试策略要小心。不是所有错误都适合重试。网络超时、连接被拒可以重试但如果是API返回了明确的错误码比如参数错误、余额不足重试没有意义。而且重试要注意幂等性对话场景重试可能导致重复生成。public String chatWithRetry(String message, int maxRetries) { Exception lastException null; for (int i 0; i maxRetries; i) { try { return modelService.chat(message); } catch (Exception e) { lastException e; // 只对超时和连接错误重试 if (!isRetryable(e)) { throw new RuntimeException(e); } // 指数退避 try { Thread.sleep((long) Math.pow(2, i) * 1000); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); throw new RuntimeException(ie); } } } throw new RuntimeException(重试 maxRetries 次后仍然失败, lastException); } private boolean isRetryable(Exception e) { String msg e.getMessage(); if (msg null) return false; return msg.contains(timeout) || msg.contains(Connection refused) || msg.contains(502) || msg.contains(503); }指数退避很重要。第一次等1秒第二次等2秒第三次等4秒。这样给服务端恢复的时间也避免大量请求同时重试造成雪崩。5.2 向量检索不准的排查思路RAG检索不准模型回答就会跑偏。我遇到过几次检索结果和问题完全不相关的情况排查下来通常是这几个原因。第一个原因是分块策略不合理。块太大一个块里混了好几个主题向量表征就不准。块太小信息不完整检索到了也没用。解决办法是调整块大小并且尽量在语义边界切分比如按段落、按标题切。第二个原因是embedding模型和检索场景不匹配。有些embedding模型擅长通用语义有些擅长特定领域。如果你的知识库是法律文书用通用embedding效果可能不好。解决办法是换一个在相关领域表现更好的embedding模型或者对embedding做微调。第三个原因是相似度阈值设置不当。阈值太高检索不到东西阈值太低检索出一堆不相关的。我的经验是先用一批测试问题跑一遍看看相似度分布然后取一个能过滤掉大部分不相关结果的值。通常余弦相似度0.7以上算比较相关0.5到0.7之间是弱相关0.5以下基本不相关。第四个原因是查询本身的问题。用户的问题太短或者太模糊向量表征就不准。解决办法是做查询改写用模型把用户问题改写成更适合检索的形式。比如用户问“这个怎么弄”改写成“XX功能的使用方法是什么”。5.3 并发场景下的资源管理AI应用通常是IO密集型的模型调用要等网络返回。这时候如果用传统的阻塞式编程线程会被大量占用。Tomcat默认200个线程如果每个请求都阻塞等模型返回并发量一上来线程池就满了。解决办法有几个。一是用WebFlux做响应式编程用少量线程处理大量并发。二是用异步Servlet把模型调用放到单独的线程池。三是用信号量做限流超过并发上限的请求直接拒绝或者排队。我一般用Resilience4j的Bulkhead做并发控制配合WebFlux使用。resilience4j: bulkhead: instances: modelApi: max-concurrent-calls: 20 max-wait-duration: 100ms这个配置限制同时最多20个模型调用超出的请求最多等100毫秒等不到就失败。这样可以保护模型服务不被压垮也保护自己的线程资源不被耗尽。注意max-concurrent-calls的值要根据模型服务的承载能力来设。设太小吞吐量上不去设太大模型服务可能被压垮。建议先压测找到模型服务的拐点然后设一个略低于拐点的值。5.4 常见问题速查表问题现象可能原因排查方法解决方案调用超时网络慢、模型负载高、超时设置太短看日志中的耗时分布调大超时时间加熔断降级返回乱码编码不一致检查请求和响应的Content-Type统一用UTF-8流式响应中断连接超时、缓冲区满看客户端和服务端日志调大超时加心跳保活检索结果不相关分块不合理、embedding不匹配人工检查检索结果调整分块策略换embedding模型内存溢出对话历史无限增长看堆内存和GC日志限制历史长度加过期清理并发上不去线程池满、连接池满看线程和连接池监控改异步、加限流、扩连接池成本超预期调用量太大、token消耗高统计调用量和token数加缓存、压缩prompt、限流这张表是我踩坑踩出来的基本上覆盖了日常开发中80%的问题。遇到问题先查表能省不少时间。6. 技术选型的长期主义思考6.1 不要把所有鸡蛋放在一个框架里这次框架停更事件给我最大的教训就是不要把系统的核心能力绑定在某个具体框架上。框架是工具工具会过时、会停更、会被替代但你的业务逻辑和核心能力应该保持独立。我的做法是在框架和业务之间加一层抽象。比如定义一个ModelClient接口框架的调用都通过这个接口。如果以后换框架只需要换接口的实现类业务代码不用动。public interface ModelClient { String chat(String message); FluxString chatStream(String message); ListDouble embed(String text); }然后针对不同的框架写不同的实现SpringAiModelClient、LangChain4jModelClient、RawHttpModelClient。业务代码只依赖ModelClient接口不依赖具体框架。这样框架的生死就影响不到你的核心业务了。这层抽象的成本很低就是一个接口加几个实现类但收益很大。我强烈建议所有用AI框架的项目都加上这层隔离。6.2 Java在AI时代的真正优势很多人唱衰Java在AI领域的前景我觉得他们看错了方向。Java的优势从来不是做模型训练而是做大规模、高可靠、长周期的企业级应用。AI要真正落地到企业业务里离不开这些能力。一个AI功能从demo到生产中间隔着权限控制、审计日志、灰度发布、监控告警、容灾降级、数据一致性、事务管理等等。这些恰恰是Java生态积累了二十年的强项。Python做demo快但要把demo变成7x24小时稳定运行的企业服务Java是更稳妥的选择。而且Java的AI生态也在发展。Spring AI有官方背书LangChain4j社区活跃DJL支持多种模型推理这些都在逐步完善。即使某个框架停更了也会有新的框架出来。Java的体量和生态决定了它不会在AI时代缺席。6.3 给不同阶段开发者的建议如果你是刚入行的Java开发者我的建议是先把Java基础打牢Spring Boot、并发编程、JVM调优这些基本功不能丢。AI应用开发只是Java的一个应用方向底层能力才是长期竞争力。同时花点时间了解AI的基本概念知道embedding、RAG、Agent这些是什么不用深入但要知道怎么用。如果你是有经验的Java开发者想转AI方向建议从应用层入手先做一个完整的RAG项目把文档处理、向量检索、模型调用、流式响应这条链路跑通。然后逐步深入了解prompt工程、工具调用、多Agent协作这些进阶话题。不要一上来就去啃模型训练那个投入产出比对你来说不划算。如果你是技术负责人做选型我的建议是核心业务用JavaAI能力通过接口隔离框架选型优先考虑维护活跃度同时做好替换预案。不要因为某个框架火就全盘押注也不要因为某个框架停更就全盘否定。技术选型是动态的保持灵活性和可替换性比选对某个具体框架更重要。我在实际项目中的体会是与其焦虑某个框架的生死不如把时间花在提升自己的架构能力和工程能力上。框架会变但解决问题的能力不会过时。把AI应用当成一个普通的分布式系统来设计考虑好可用性、可扩展性、可维护性剩下的就是选什么工具的问题了。工具不合适就换没什么大不了的。