
1. 为什么 Java 后端工程师在 2026 年必须重新思考 LLM 框架选型Spring AI 和 LangChain4j 这两个名字最近半年在我们团队的站会上出现频率已经超过了“线程池参数调优”和“OOM 分析”。不是因为它们多新鲜——LangChain4j 早在 2023 年底就发布了 0.1.0 版本Spring AI 更是 Spring 官方在 2024 年初高调推出的“LLM 首选集成层”。但真正让所有人坐直身子的是 2025 年底那场内部压测用同一套 RAG 流程、同一组 Embedding 模型BGE-M3、同一份 200 万条知识库切片在 Spring AI 1.0.0 和 LangChain4j 0.12.0 上跑完 1000 次并发问答平均首字延迟差了 317msP99 延迟差了 1.8 秒而错误率——Spring AI 是 0.3%LangChain4j 是 2.1%。这不是 Demo 级别的差异这是上线前必须拍板的 SLA 红线。我带的三个 Java 后端小组去年有两组用 Spring AI 快速搭出了客服知识助手 MVP另一组坚持用 LangChain4j 自研了一套调度器结果上线后发现Spring AI 小组花三天接入了阿里云百炼的 MCP 服务而 LangChain4j 小组花了三周才把 retry 逻辑和 token 计数对齐到生产环境要求。这不是框架好坏之争而是“Java 工程师的肌肉记忆”和“LLM 应用的现实水位线”之间的一次硬碰硬。Spring AI 天然带着 Spring Boot 的 auto-configuration 基因starter 一加Bean一写RestTemplate那套东西全都能无缝复用LangChain4j 则像一把瑞士军刀——它不强制你用什么但你要自己组装刀片、磨刃、校准角度。它给你ChatModel、EmbeddingModel、Retriever这些原子能力但怎么串成一条流水线怎么处理流式响应中断怎么把 Milvus 的混合检索结果喂进 RAG Chain全得你自己画图纸。所以这篇不是教你怎么“Hello World”而是带你站在 2026 年 Q1 的真实战场上看当你的需求是“把大模型能力嵌进现有订单系统支持销售 SOP 智能推荐”当你的运维要求是“所有外部 LLM 调用必须走公司统一网关并记录审计日志”当你的测试同学拿着 JMeter 报告说“第 37 次请求返回了空 JSON”这时候选 Spring AI 还是 LangChain4j本质是在选一种工程契约——前者承诺“你按 Spring 的路子走我保你八成稳”后者声明“我把所有扳手都给你修好修坏责任共担”。关键词里反复出现的langchain4j低级api和spring ai alibaba恰恰暴露了当前的真实分野前者是那些已经踩过坑、不想再被封装层遮蔽细节的团队在主动降级后者是那些需要快速对接国内厂商百炼、通义千问、Kimi且不愿重写适配器的团队在寻求确定性。而java面试题和java八股文里突然冒出spring ai和langchain4j说明这已不是可选项而是 Java 岗位的隐性准入门槛——就像五年前你不会 Spring Cloud Gateway简历可能直接被筛掉一样。2. 核心设计思路拆解抽象层级、扩展边界与运维成本的三角博弈2.1 Spring AI 的设计哲学做 Spring 生态的“LLM 适配器”而非通用 LLM 框架Spring AI 的核心定位从它第一个 commit 就写在 README 里“Spring for LLMs, not an LLM framework”。这句话不是谦虚是战略取舍。它不试图定义什么是Agent、什么是Tool Calling的标准范式而是把 LLM 当作一个新型的“远程服务”像 Redis、MySQL、RabbitMQ 一样纳入 Spring 的资源管理生命周期。所以你看它的AiModel接口只有call()和stream()两个方法连generate()这种语义化命名都刻意回避——因为它要兼容的不只是 ChatModel还有 TextToSpeechModel、ImageModel甚至未来可能出现的 VideoModel。这种设计让 Spring AI 在“接入速度”上拥有碾压级优势。举个真实例子我们有个老系统用的是 Spring Boot 2.7 MyBatisJDK 11连 WebFlux 都没上。要接入百炼的 Qwen2.5-72B 模型Spring AI 的做法是加spring-ai-alibaba-spring-boot-starter依赖application.yml里配spring.ai.alibaba.api-key和spring.ai.alibaba.model-name写一个Service类注入ChatClient直接chatClient.call(new Prompt(...))。整个过程没有 new 任何对象没有手动管理连接池没有写 retry 逻辑——因为ChatClient默认就集成了 Spring RetryRestTemplate的超时配置自动继承自spring.web.client.*。而 LangChain4j 做同样的事你得手动 newAlibabaQwenChatModel显式设置maxRetries3、timeout30s自己 wrap 一层RetryableChatModel把ChatModel注入到自己的 Service 里还得确保它是单例。这不是代码量的差异是心智负担的差异。Spring AI 把“LLM 调用”变成了 Spring 的“一次 HTTP 调用”而 LangChain4j 把它变成了“一次需要你理解底层协议的 SDK 调用”。但代价是什么是灵活性的收窄。Spring AI 的Prompt对象强制要求你用UserMessage、AiMessage、SystemMessage这三种角色如果你要用百炼的tool_choice: auto功能就必须等 Spring AI 官方在AlibabaQwenChatOptions里暴露这个字段。而 LangChain4j 的ChatModelRequest是个 Map你可以塞任何 key只要百炼 API 文档认它就行。这就是“抽象层级”的博弈Spring AI 用更高层的抽象换来了开箱即用LangChain4j 用更低层的 API 换来了绝对控制权。2.2 LangChain4j 的架构本质面向组合的“LLM 原语集合”拒绝魔法LangChain4j 的源码结构像一本严谨的教科书。core模块只定义接口ChatModel、EmbeddingModel、Retriever、OutputParser。runtime模块提供默认实现比如DefaultRetriever只是一个包装器真正的检索逻辑在VectorStore接口里。而vector-store-milvus这个独立模块就是专门对接 Milvus 的——它不关心你是用 Spring 还是 Quarkus不关心你用不用 Lombok它只保证add()、search()、delete()这三个方法语义正确。这种设计带来的最大红利是“混合检索”的落地成本断崖式下降。我们有个金融风控场景需要同时查向量相似度用户历史投诉文本和关键词匹配监管条例编号还要加时间衰减权重。用 Spring AI你得等它出HybridRetrieverstarter或者自己写一个CustomRetriever并绕过它的RetrievalAugmentor生命周期。而 LangChain4j三行代码搞定MilvusVectorStore milvusStore new MilvusVectorStore(...); ElasticsearchRetriever esRetriever new ElasticsearchRetriever(...); HybridRetriever hybridRetriever HybridRetriever.builder() .vectorStore(milvusStore) .keywordRetriever(esRetriever) .weightFunction((vectorScore, keywordScore) - vectorScore * 0.7 keywordScore * 0.3) .build();这里HybridRetriever不是 Spring AI 那种“内置组件”而是社区贡献的一个工具类你甚至可以把它 copy 到自己项目里改——因为它的所有依赖都是core模块的接口没有绑定任何具体实现。这就是 LangChain4j 的“扩展边界”它不提供解决方案只提供拼图块。你想要Multi-Agent官方没实现但AgentExecutor接口就在那儿你用ChatModel和ToolExecutor自己组合就行。spring ai multi agent这个热词背后其实是 Spring AI 用户在等官方发布而 LangChain4j 用户已经在 GitHub 上 fork 了十几个multi-agent-runtime仓库。2.3 运维成本的隐形天平日志、监控、灰度与回滚框架选型最终要回归到运维。我们做过一个对照实验在同一个 Kubernetes 集群里部署两套完全相同的 RAG 服务知识库相同、模型相同、前端流量相同唯一区别是后端框架。然后观察它们在以下维度的表现维度Spring AILangChain4j差异根源日志可追溯性spring.ai.chat.request-id自动生成贯穿整个调用链ELK 里搜request-id能看到完整输入/输出/token 数需手动在ChatModelRequest里塞traceId否则日志里只有modelId和timestampSpring AI 强制RequestContextLangChain4j 无状态熔断降级直接复用Resilience4j的CircuitBreakerRegistryCircuitBreaker(nameqwen)一行注解搞定需在ChatModel外包一层Resilience4jChatModel并确保所有调用路径都经过它Spring AI 与 Resilience4j 深度集成LangChain4j 需手动编织灰度发布ConditionalOnProperty(ai.model.versionqwen2.5)配合 Config Server 动态刷新需自己实现ModelRouter根据 header 或 query 参数路由到不同ChatModel实例Spring AI 支持条件化 BeanLangChain4j 需业务层路由回滚成本回退到上一版 starter重启应用所有配置自动生效需修改ChatModel初始化代码重新编译打包风险点更多Spring AI 的配置驱动 vs LangChain4j 的代码驱动这个表格里的每一项都对应着 SRE 同学深夜接到的告警电话。Spring AI 的优势在于“把运维问题变成配置问题”LangChain4j 的优势在于“把运维问题变成可审计的代码问题”。前者适合追求交付速度的业务线后者适合对稳定性有极致要求的中台部门。3. 核心细节解析与实操要点从依赖引入到生产就绪的 7 个关键决策点3.1 依赖引入starter 的甜头与陷阱Spring AI 的 starter 看似省事实则暗藏玄机。以spring-ai-alibaba-spring-boot-starter为例它默认引入alibaba-cloud-sdk-openapi5.0.0而这个版本和我们项目里已有的aliyun-java-sdk-ecs4.5.0 存在com.aliyun.tea.TeaException类冲突。解决方法不是升级 ECS SDK会引发连锁反应而是用 Mavenexclusiondependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-alibaba-spring-boot-starter/artifactId exclusions exclusion groupIdcom.aliyun/groupId artifactIdaliyun-java-sdk-openapi/artifactId /exclusion /exclusions /dependency然后单独引入兼容版本dependency groupIdcom.aliyun/groupId artifactIdaliyun-java-sdk-openapi/artifactId version4.8.0/version /dependencyLangChain4j 则相反它不提供 starter所有依赖都由你显式声明。langchain4j-core、langchain4j-milvus、langchain4j-qwen这三个 jar 包版本号必须严格对齐。我们吃过亏langchain4j-milvus0.12.0 依赖milvus-sdk-java2.4.0而langchain4j-qwen0.12.0 依赖alibaba-cloud-openapi-java-sdk5.0.0这两个 SDK 都用了okhttp但版本分别是 4.12.0 和 4.11.0导致运行时NoSuchMethodError。解决方案是强制指定okhttp版本properties okhttp.version4.12.0/okhttp.version /properties dependencyManagement dependencies dependency groupIdcom.squareup.okhttp3/groupId artifactIdokhttp/artifactId version${okhttp.version}/version /dependency /dependencies /dependencyManagement提示Spring AI 的 starter 是“便利性封装”LangChain4j 的 dependency 是“契约式声明”。前者让你少写代码后者让你清楚知道每个字节来自哪里。3.2 模型配置环境隔离与敏感信息治理生产环境必须区分 dev/staging/prod而 LLM 的 API Key 绝不能写死在代码里。Spring AI 提供了spring.ai.{provider}.api-key这种标准化配置配合 Spring Cloud ConfigKey 可以加密存储。但要注意Spring AI 1.x 版本不支持ConfigurationProperties的Validated所以spring.ai.alibaba.api-key如果为空它不会抛BindingValidationException而是静默 fallback 到null最终在调用时才报NullPointerException。必须手动加校验Component ConfigurationProperties(prefix spring.ai.alibaba) Validated public class AlibabaProperties { NotBlank(message API Key must not be blank) private String apiKey; // getter/setter }LangChain4j 没有配置绑定所有参数都在构造函数里传。我们的做法是写一个ModelFactoryComponent public class ModelFactory { Value(${alibaba.qwen.api-key}) private String apiKey; PostConstruct public void validate() { if (StringUtils.isBlank(apiKey)) { throw new IllegalArgumentException(Alibaba Qwen API Key is missing); } } public ChatModel createQwenModel() { return AlibabaQwenChatModel.builder() .apiKey(apiKey) .modelName(qwen2.5-72b-chat) .build(); } }这样既满足了 Spring 的Value注入又实现了启动时校验。关键点在于LangChain4j 的配置校验是你自己的责任Spring AI 的配置校验是框架的责任——但框架的责任有时会留白。3.3 Token 计数精度决定成本精度影响体验LLM 调用成本按 token 计费而 token 数不准轻则预算超支重则触发限流。Spring AI 的TokenCountEstimator默认用OpenAiTokenizer它对中文分词极不友好——“人工智能”会被切成 4 个 token“人”、“工”、“智”、“能”而实际百炼 API 返回的是 2 个 token。我们实测过Spring AI 估算的 1000 token 输入百炼实际计费 620 token误差率达 61%。LangChain4j 的QwenTokenizer则直接调用百炼的/v1/tokenize接口返回真实 token 数。但它有个坑QwenTokenizer是同步阻塞调用如果百炼 tokenize 接口抖动整个请求线程就会卡住。我们的解决方案是缓存 tokenizer 结果LRU Cachekey 是 text 的 SHA256设置超时HttpClient的connectTimeout和readTimeout降级策略超时后 fallback 到OpenAiTokenizer估算并打 warn 日志。public class RobustQwenTokenizer implements Tokenizer { private final QwenTokenizer realTokenizer; private final LoadingCacheString, Integer cache; private final Tokenizer fallbackTokenizer; public int countTokens(String text) { try { return cache.get(text); // 自动加载 } catch (ExecutionException e) { log.warn(Qwen tokenizer failed, fallback to OpenAI, e); return fallbackTokenizer.countTokens(text); } } }注意token 计数不是技术细节是成本管控的核心指标。Spring AI 的“估算”在 PoC 阶段够用LangChain4j 的“实测”在生产阶段必需。3.4 流式响应用户体验的生死线客服场景下用户盯着空白屏幕等待 3 秒流失率上升 40%。Spring AI 的StreamingChatClient默认开启sseServer-Sent Events但百炼的流式接口返回的是text/event-stream而 Spring AI 的EventSourceHttpMessageReader对 event 字段解析有 bug——当百炼返回event: message时它会把data:后面的 JSON 当作纯文本而不是反序列化成AiMessage。我们打了 patchpublic class FixedEventSourceHttpMessageReader extends EventSourceHttpMessageReader { Override protected Object readInternal(ResolvableType elementType, ReaderContext context) throws IOException { String data context.getData(); // 原始 data 字段 if (data.startsWith({) data.endsWith(})) { return objectMapper.readValue(data, AiMessage.class); // 强制 JSON 解析 } return super.readInternal(elementType, context); } }LangChain4j 的StreamingChatModel更简单它不封装流式协议只提供onPartialResponse()回调。你传一个ConsumerString进去百炼 SDK 就把每 chunk 的 raw text 丢给你。这意味着你可以自己做 JSON 解析{delta:{content:你好}}→ 提取content自己做防抖连续 200ms 没新 chunk就 flush 到前端自己做断句检测。后 flush避免单词被截断。这种“裸 API”看似麻烦实则给了你对用户体验的完全控制权。Spring AI 的“开箱即用”在这里变成了“开箱即受限”。3.5 RAG 实现向量库、检索器与提示工程的协同RAG 不是“向量搜索 prompt 拼接”这么简单。Spring AI 的RetrievalAugmentor是一个黑盒你只能配置topK5、queryTransformationtrue但无法干预检索后的重排序re-ranking。而 LangChain4j 的Retriever是一个明确的接口你可以自由组合// 步骤1从 Milvus 检索 top 20 ListDocument candidates milvusRetriever.retrieve(query); // 步骤2用 BGE-Reranker 重排序 ListDocument reranked bgeReranker.rerank(query, candidates, 5); // 步骤3用 Cohere Rerank API更准但更贵 ListDocument finalDocs cohereReranker.rerank(query, reranked);我们线上用的是三级漏斗Milvus 粗检快→ BGE-Reranker 本地精排准→ Cohere 最终确认贵但必要。Spring AI 无法插入中间环节LangChain4j 的Retriever链式调用天然支持。另一个关键是提示模板。Spring AI 的PromptTemplate用String.format而 LangChain4j 的PromptTemplate用Mustache。前者简单后者强大——{{#documents}}...{{/documents}}循环渲染{{^documents}}...{{/documents}}空值 fallback{{#if hasTool}}...{{/if}}条件判断。在复杂 RAG 场景下Mustache 的表达力是String.format无法比拟的。3.6 Agent 实现工具调用的可靠性鸿沟spring ai multi agent和langchain4j llm tool selector这两个热词指向同一个痛点如何让 LLM 安全、可靠地调用业务 API。Spring AI 的ToolExecutor是基于反射的它把Tool方法的参数名当作文档字段生成function_call的arguments。但问题来了如果业务方法参数是OrderQueryRequest requestSpring AI 会尝试把整个 JSON 对象塞进arguments而百炼的tool_choice要求arguments是扁平的 key-value。结果就是{error:invalid arguments}。LangChain4j 的ToolProvider要求你显式定义ToolSpecificationpublic ToolSpecification orderQueryTool() { return ToolSpecification.builder() .name(order_query) .description(查询用户订单状态输入必须包含 user_id 和 order_id) .parameters(JsonSchema.of( JsonSchemaBuilder.object() .add(user_id, JsonSchemaBuilder.string().required()) .add(order_id, JsonSchemaBuilder.string().required()) )) .build(); }然后ToolExecutor会严格按这个 schema 校验 LLM 输出。我们在线上加了双校验第一层LangChain4j 的ToolSpecification校验JSON Schema第二层业务代码里的Valid注解Hibernate Validator。这样即使 LLM 输出了user_id: 123整数也会在ToolExecutor层就被拦截不会走到业务方法里。Spring AI 的反射机制在这里成了脆弱点。3.7 监控埋点从 metrics 到 trace 的全链路可观测最后是监控。Spring AI 的ObservationRegistry自动注册spring.ai.chat.requests、spring.ai.chat.errors这些 Micrometer metricsGrafana 里开箱即用。但它的 trace span 名称是spring.ai.chat无法区分是调用了 Qwen 还是 Kimi。我们通过ObservationConvention自定义public class AiModelObservationConvention implements ObservationConventionChatModelRequest { Override public String getName(ChatModelRequest request) { return ai.chat. request.getModelName(); // ai.chat.qwen2.5 } }LangChain4j 没有内置 metrics但我们用 Micrometer 的Timer手动埋点Timer timer Timer.builder(ai.chat) .tag(model, model.getName()) .tag(type, streaming) .register(meterRegistry); timer.record(() - { model.generate(messages, callback); });关键区别在于Spring AI 的监控是“框架级埋点”LangChain4j 的监控是“代码级埋点”。前者省事但不够细后者费事但颗粒度可控。在排查“为什么 Qwen 调用慢”时LangChain4j 的Timed(ai.chat.qwen)能精准定位到是网络 IO 还是解析耗时而 Spring AI 的spring.ai.chat.requests只能告诉你“整体慢”。4. 实操过程与核心环节实现一个电商售后智能体的完整落地4.1 需求拆解不是“做个聊天机器人”而是“重构售后 SOP”客户提出的需求原文是“希望客服能自动回答‘我的订单为什么还没发货’”。这听起来简单但背后是完整的售后 SOPStep 1识别用户意图是催发货还是查物流还是退换货Step 2根据订单 ID 查询 ERP 系统获取订单状态、发货时间、物流单号Step 3如果未发货检查库存、采购单、供应商排期Step 4生成符合话术规范的回复不能说“系统故障”要说“正在紧急协调”Step 5如果用户情绪激烈含“投诉”、“12315”等词自动转人工并标记优先级。这个需求里LLM 不是主角而是“SOP 编排引擎”。它需要精确的意图分类不是 open-ended generation多数据源协同ERP、WMS、CRM严格的输出格式JSON 结构含action、reason、next_step字段可审计的决策链每一步推理都要 log。4.2 方案选型决策树为什么最终选 LangChain4j我们画了张决策树横轴是“需求确定性”纵轴是“团队 LLM 经验”高确定性SOP 清晰 高经验 → LangChain4j控制力优先 高确定性 低经验 → Spring AI快速验证 低确定性探索性需求 高经验 → LangChain4j迭代灵活 低确定性 低经验 → Spring AI先跑通再优化这个售后需求SOP 是法务部盖章的 PDF团队有 3 个成员做过 LLM 项目所以选 LangChain4j。但不是全盘否定 Spring AI而是用它做“胶水”用 Spring AI 的ChatClient做兜底 fallback当 LangChain4j 的 Agent 链失败时用 Spring AI 的ObservationRegistry做统一监控入口用 Spring Boot Actuator 的/actuator/ai端点暴露 LangChain4j 的健康状态。4.3 核心代码实现Agent 链的七层洋葱结构LangChain4j 的 Agent 不是单个类而是一层层包裹的洋葱。我们实现了七层Input Normalizer清洗用户输入移除 emoji标准化日期格式“昨天”→“2026-03-15”Intent Classifier用微调的小模型DistilBERT做 5 分类输出intent: shipping_delayOrder Extractor正则 NER 提取订单 ID失败时调用OrderSearchToolERP Connector调用 ERP REST API返回结构化 JSONRule Engine硬编码规则“库存10 → 延期发货”“采购单状态waiting → 供应商未确认”LLM Orchestrator把前 5 步结果喂给 Qwenprompt 里明确要求输出 JSONOutput Sanitizer校验 JSON schema过滤敏感字段如erp_password添加话术模板。每一层都是一个Chain用Chain.of()组合ChainInput, Output agentChain Chain.of( inputNormalizer, intentClassifier, orderExtractor, erpConnector, ruleEngine, llmOrchestrator, outputSanitizer );Spring AI 的ChatClient无法表达这种“条件分支数据转换”的复杂链路它更适合“单次调用简单后处理”。4.4 生产就绪配置从 Docker 到 K8s 的 12 项 checklist上线前我们列了 12 项必须验证的点全部针对 LangChain4j✅LANGCHAIN4J_LOG_LEVELDEBUG环境变量是否生效验证日志是否输出Retriever.search耗时✅JAVA_OPTS-Xmx4g -XX:UseZGC是否设置LLM 客户端内存占用大✅milvus.host和milvus.port是否通过 ConfigMap 注入非 hardcode✅qwen.api-key是否用 Kubernetes Secret 挂载非明文 config✅micrometer-registry-prometheus依赖是否加入metrics 暴露✅spring.sleuth.enabledfalse是否设置避免 OpenTelemetry 与 LangChain4j 的 tracer 冲突✅okhttp3.logging-interceptor是否禁用生产环境关闭 HTTP body log✅langchain4j.cache.size10000是否配置tokenizer LRU cache✅qwen.timeout60000是否大于 ERP 接口超时避免 cascade timeout✅retry.max-attempts2是否设置LangChain4j 的RetryableChatModel✅logback-spring.xml中ai.logger level 设为INFO避免 DEBUG 日志刷爆磁盘✅/health/ai端点是否返回{status:UP,details:{qwen:OK,milvus:OK}}。这些配置项Spring AI 的 starter 会帮你搞定 60%LangChain4j 需要你 100% 手动确认。但好处是你知道每一个配置项的作用而不是靠文档猜。4.5 性能压测实录从 100 QPS 到 1000 QPS 的瓶颈突破我们用 Gatling 做了三轮压测目标是 1000 QPSP95 800msRound 1Baseline纯 LangChain4j默认配置100 QPS 时 P951200ms错误率 5%主要是 Milvus 连接池耗尽Round 2Connection Poolmilvus-sdk-java的GrpcChannelPool从 10 改为 50P95 降到 950ms错误率 0.2%Round 3Async Cache把 ERP 调用改成CompletableFuturetokenizer 结果缓存P95680ms错误率 0%。关键发现Milvus 连接池是第一瓶颈GrpcChannelPool的maxSize必须大于 K8s Pod 数 × 每 Pod 线程数ERP 调用是第二瓶颈WebClient的maxInMemorySize要调大默认 256KBERP 返回 JSON 有时超 1MBLLM 调用本身不是瓶颈百炼的 Qwen2.5-72B 在 1000 QPS 下 P95420ms远低于目标。实操心得LangChain4j 的性能调优本质是“把每个依赖的连接池、缓冲区、超时参数都拉出来晒太阳”。Spring AI 的spring.ai.qwen.connect-timeout这种统一配置掩盖了底层差异反而不利于精准优化。5. 常见问题与排查技巧实录来自 17 个生产事故的血泪总结5.1 “No qualifying bean of type ‘ChatClient’” —— Spring AI 的 Classpath 陷阱现象加了spring-ai-alibaba-spring-boot-starter启动报NoSuchBeanDefinitionException。原因Spring AI 2.02025 年发布要求 Spring Boot 3.3而你的项目是 Spring Boot 2.7。starter 的AutoConfiguration类用到了ConditionalOnAvailableEndpoint这个注解在 2.7 不存在。解决方案降级到 Spring AI 1.0.0或升级 Spring Boot。别信“兼容性说明”亲自mvn dependency:tree看spring-boot-autoconfigure版本。5.2 “java.lang.NoClassDefFoundError: okhttp3/internal/connection/RealConnection” —— LangChain4j 的 OkHttp 版本战争现象langchain4j-milvus和langchain4j-qwen依赖不同版本 OkHttp运行时报错。根因OkHttp 4.11 和 4.12 的 internal 包结构变化RealConnection类被移到了okhttp3.internal.connection→okhttp3.internal.http2。终极解法在pom.xml里强制dependencyManagement锁定okhttp3为 4.12.0并排除所有 transitive 依赖中的旧版本。5.3 “Stream closed” —— 流式响应的线程安全之殇现象Spring AI 的StreamingChatClient在高并发下偶尔抛IOException: Stream closed。真相EventSourceHttpMessageReader的InputStream被多个线程共享而InputStream不是线程安全的。修复升级 Spring AI 到 1.0.3或自己写ThreadLocal包装的InputStream。5.4 “Empty response from LLM” —— 百炼 API 的 silent fail现象LangChain4j 调用百炼onPartialResponse()从不触发onComplete()也无回调。排查路径curl 百炼 endpoint确认返回200开启 OkHttp log发现百炼返回HTTP/1.1 200 OK但Content-Type: text/plain不是text/event-stream