
1. 从一堆“AI 项目”说起为什么企业需要一个应用底座过去一年多我参与过好几个企业内部的 AI 项目从智能客服、知识库问答到合同审查、报表生成几乎每个业务线都在喊“我们要上 AI”。但真正落地的时候问题就来了每个团队各写各的有的用 Python 起个 Flask 服务有的在 Java 项目里直接调模型接口前端更是五花八门有的用 React有的用 Vue还有的干脆写个 HTML 页面凑合。结果就是模型换了要改一遍代码权限体系各搞一套日志格式对不上运维根本没法统一管理。这就是“AI 应用底座”要解决的问题。QuickBlue 就是这样一个东西——它不是某个具体的 AI 功能而是一套让企业能够快速、规范、可维护地构建 AI 应用的底层框架。你可以把它理解成“AI 应用的操作系统”上面跑的是各种业务场景下面托着的是模型接入、权限、日志、配置、前端组件这些公共能力。为什么企业需要它因为当 AI 从“ demo 阶段”进入“生产阶段”拼的就不再是谁能调通模型接口而是谁能把 AI 能力稳定、安全、低成本地交付给业务。QuickBlue 这类底座的价值就在于把那些每个项目都要重复造一遍的轮子一次性做好、做扎实。这篇文章我会从实际落地的角度拆解 QuickBlue 这类 AI 应用底座的核心设计思路、技术选型逻辑、关键实现细节以及我在类似项目中踩过的坑。无论你是正在选型的技术负责人还是准备动手搭框架的一线开发都能从中找到可以直接参考的东西。2. QuickBlue 到底是什么核心能力与设计思路拆解2.1 一句话说清楚它不是模型是“模型的外壳”很多人第一次听到 QuickBlue会以为它是一个 AI 模型或者一个 AI 平台。其实不是。QuickBlue 本身不训练模型也不提供模型能力它做的事情是把模型能力包装成企业应用可以直接使用的服务。打个比方模型就像发动机QuickBlue 就像底盘和传动系统。发动机再好没有底盘车也跑不起来。QuickBlue 要解决的就是“怎么让发动机装到车上还能跑得稳、跑得久、跑得安全”。具体来说它通常包含这几个核心模块统一模型接入层不管是 OpenAI 风格的接口、国内大模型接口还是企业自己部署的模型都通过一套统一的 API 接入业务代码不直接依赖具体模型。应用运行时框架基于 Spring Cloud 2025 和 JDK 21 构建提供配置管理、服务发现、熔断限流、链路追踪等能力。前端应用脚手架基于 Vite 8 构建提供开箱即用的 AI 交互组件比如对话窗口、流式输出、文件上传、结果渲染等。权限与审计体系AI 应用涉及数据敏感性和合规要求谁问了什么、模型答了什么、用了多少 token都要有记录。部署与运维规范容器化、健康检查、灰度发布、模型切换不停机这些都要在底座层面解决。2.2 为什么是 JDK 21 Spring Cloud 2025 Vite 8技术选型这件事我向来主张“不追新但也不守旧”。QuickBlue 选择 JDK 21、Spring Cloud 2025 和 Vite 8背后是有明确逻辑的。先说 JDK 21。它是 LTS 版本虚拟线程Virtual Threads已经正式可用。AI 应用有一个很典型的特征大量 IO 等待。比如调用模型接口可能要等几秒甚至几十秒。传统线程模型下每个请求占一个线程并发一高线程池就爆了。虚拟线程可以让每个请求用一个轻量级线程IO 等待时自动让出资源吞吐量提升非常明显。我在一个内部项目里做过对比同样的模型调用场景JDK 21 虚拟线程模式下单机并发能力比 JDK 17 传统线程池提升了将近 3 倍。再说 Spring Cloud 2025。它和 JDK 21 的适配已经非常成熟而且对 GraalVM 原生镜像的支持也更好了。AI 应用底座需要快速启动、低内存占用原生镜像是一个很实际的选择。另外Spring Cloud 2025 在配置管理、服务治理方面的生态依然是最完整的企业里已有的 Java 团队可以几乎零成本上手。最后是 Vite 8。前端构建工具这几年变化很快但 Vite 在开发体验和构建速度上的优势一直很稳。Vite 8 对模块联邦、SSR、流式渲染的支持更完善这对于 AI 应用的前端非常重要。AI 交互经常涉及流式输出用户希望看到文字一个字一个字蹦出来而不是等半天一次性显示。Vite 8 配合现代前端框架可以很自然地实现这种体验。注意技术选型不是越新越好而是要看团队能不能驾驭、生态能不能支撑、未来两三年会不会被淘汰。JDK 21 Spring Cloud 2025 Vite 8 这个组合在我看来是当前企业级 AI 应用底座比较稳妥的选择。2.3 它解决了哪些“不做底座就一定会痛”的问题我总结过没有底座的情况下企业做 AI 应用通常会遇到五个典型问题第一模型切换成本极高。今天用 A 模型明天想换 B 模型发现业务代码里到处都是模型厂商的 SDK 调用改起来伤筋动骨。第二权限体系缺失。AI 应用往往能接触到企业核心数据但很多团队一开始只管功能不管权限谁都能问、谁都能看出了事没法追溯。第三前端重复建设。每个 AI 应用都要重新写对话界面、文件上传、结果展示浪费大量人力。第四运维黑盒。模型调用失败率多少、平均响应时间多长、token 消耗多少没有统一监控出了问题只能靠猜。第五部署不规范。有的用 jar 包直接跑有的用 Docker有的用 K8s配置散落各处环境不一致导致的问题层出不穷。QuickBlue 这类底座的价值就是把这些问题在框架层面一次性解决让业务团队专注于业务逻辑而不是重复踩坑。3. 核心细节解析一个 AI 应用底座的骨架长什么样3.1 模型接入层怎么做到“换模型不改业务代码”模型接入层是整个底座最核心的部分。设计目标很简单业务代码只依赖抽象接口不依赖具体模型实现。我通常会用这样的结构public interface AiModelService { AiResponse chat(AiRequest request); FluxAiResponse streamChat(AiRequest request); }然后针对不同模型厂商提供实现类比如OpenAiCompatibleService、InternalModelService等。业务代码注入的是AiModelService具体用哪个实现通过配置决定。这里有一个关键细节不同模型的请求参数和返回格式差异很大。比如有的模型支持temperature有的支持top_p有的返回结构里content字段位置不一样。底座需要做一层“参数归一化”和“结果归一化”把外部差异屏蔽掉。我的做法是定义一个统一的AiRequest和AiResponse然后在每个实现类里做转换。转换过程中要注意参数默认值要合理。比如temperature默认 0.7max_tokens默认 2048避免业务方不传参数时行为不可预期。异常要统一包装。模型调用可能超时、可能限流、可能返回格式错误这些都要转换成底座定义的异常类型方便上层统一处理。流式输出要统一协议。不同模型的流式返回格式不同底座要统一成 SSE 或 WebSocket 格式前端才能用同一套逻辑处理。实操心得模型接入层一定要做“降级策略”。比如主模型调用失败时自动切换到备用模型或者返回缓存结果。这个能力在底座层面实现一次所有业务都受益。3.2 应用运行时JDK 21 虚拟线程怎么用才不踩坑JDK 21 的虚拟线程很香但也不是银弹。我在实际项目里总结了几条经验第一虚拟线程适合 IO 密集型任务不适合 CPU 密集型任务。AI 应用大部分时间在等模型返回所以很适合。但如果你在虚拟线程里做大量计算反而会因为调度开销导致性能下降。第二不要池化虚拟线程。虚拟线程的设计初衷就是“用完就扔”创建成本极低。如果你还用传统线程池的思路去管理它就失去了意义。第三注意 synchronized 的 pinning 问题。虚拟线程在遇到 synchronized 块时可能会被“钉”在载体线程上导致无法让出。JDK 21 对这个问题做了优化但并没有完全消除。在 AI 应用里如果模型调用链路中有 synchronized 代码要特别小心。我的建议是尽量用ReentrantLock替代synchronized。第四配合 Spring Boot 3.2 使用。Spring Boot 3.2 开始正式支持虚拟线程只需要在配置里开启spring: threads: virtual: enabled: true开启之后Tomcat 的请求处理会自动使用虚拟线程。但要注意如果你用了自定义线程池需要手动替换成虚拟线程执行器。3.3 前端脚手架Vite 8 下怎么做出“丝滑”的 AI 交互AI 应用的前端体验核心就两个字流畅。用户问一个问题如果等 10 秒才看到结果体验就很差。所以流式输出几乎是标配。Vite 8 在这方面提供了很好的支持。我的做法是用EventSource或fetch的ReadableStream接收流式数据。用前端框架的响应式能力把接收到的内容实时追加到界面上。对 Markdown 内容做增量渲染避免每次全量重新解析。这里有一个坑流式输出时如果模型返回的是 Markdown 格式直接追加到 DOM 里会导致格式错乱。比如代码块还没闭合就被渲染成了普通文本。我的解决方案是维护一个缓冲区每次收到新内容后对完整缓冲区做一次 Markdown 解析然后整体替换渲染结果。虽然有一点性能开销但体验最稳定。另外Vite 8 的模块联邦能力可以让 AI 组件独立发布、独立升级。比如对话窗口组件升级了不需要整个应用重新构建只需要更新远程模块即可。这对于企业里多个 AI 应用共享同一套组件库的场景非常实用。3.4 权限与审计AI 应用不能是“法外之地”AI 应用和普通应用最大的区别在于它处理的是自然语言而自然语言里可能包含敏感信息。所以权限和审计必须从底座层面解决。我的设计思路是三层控制第一层接口级权限。哪些用户、哪些角色可以调用哪些 AI 接口通过网关统一控制。第二层数据级权限。用户只能查询自己有权访问的数据。比如一个知识库问答应用用户问“上季度销售数据”底座要能判断这个用户有没有权限看销售数据。第三层内容审计。所有请求和响应都要记录包括用户 ID、时间、模型、输入内容、输出内容、token 消耗。记录要脱敏不能明文存储敏感信息。注意审计日志的存储要考虑成本和合规。全量存储可能很贵可以按策略采样但涉及敏感操作的必须全量记录。4. 实操过程从零搭一个最小可用的 AI 应用底座4.1 环境准备与项目结构先列一下我用的环境组件版本说明JDK21LTS支持虚拟线程Spring Boot3.2支持虚拟线程配置Spring Cloud2025服务治理Node.js20前端构建Vite8前端脚手架Docker24容器化部署项目结构我习惯这样组织quickblue-parent/ ├── quickblue-core/ # 核心抽象模型接口、异常、工具类 ├── quickblue-model/ # 模型接入实现 ├── quickblue-runtime/ # 应用运行时配置、权限、审计 ├── quickblue-web/ # 前端脚手架 └── quickblue-demo/ # 示例应用这样分层的目的是核心抽象不依赖具体实现模型接入可以按需引入运行时能力可以独立升级。4.2 核心接口定义与模型接入实现先定义核心接口。除了前面提到的AiModelService还需要定义请求和响应对象public class AiRequest { private String model; private ListMessage messages; private Double temperature 0.7; private Integer maxTokens 2048; private Boolean stream false; // getter/setter 省略 } public class AiResponse { private String id; private String content; private Integer promptTokens; private Integer completionTokens; private String finishReason; // getter/setter 省略 }然后实现一个 OpenAI 兼容的模型服务Service public class OpenAiCompatibleService implements AiModelService { private final WebClient webClient; public OpenAiCompatibleService(WebClient.Builder builder) { this.webClient builder.baseUrl(https://api.example.com).build(); } Override public AiResponse chat(AiRequest request) { return webClient.post() .uri(/v1/chat/completions) .bodyValue(convertToOpenAiFormat(request)) .retrieve() .bodyToMono(OpenAiResponse.class) .map(this::convertToAiResponse) .block(); } Override public FluxAiResponse streamChat(AiRequest request) { return webClient.post() .uri(/v1/chat/completions) .bodyValue(convertToOpenAiFormat(request)) .retrieve() .bodyToFlux(String.class) .filter(line - line.startsWith(data: )) .map(line - line.substring(6)) .filter(data - ![DONE].equals(data)) .map(this::parseStreamChunk); } }这里用 WebClient 而不是 RestTemplate是因为 WebClient 对响应式流式处理支持更好。JDK 21 虚拟线程下WebClient 的阻塞调用也不会占用平台线程。4.3 配置管理与模型切换模型切换通过配置文件实现quickblue: ai: default-model: openai-compatible models: openai-compatible: type: openai base-url: https://api.example.com api-key: ${AI_API_KEY} timeout: 30s internal-model: type: internal endpoint: http://internal-model:8080 timeout: 60s然后在运行时根据配置选择实现类Configuration public class AiModelConfig { Bean ConditionalOnProperty(name quickblue.ai.default-model, havingValue openai-compatible) public AiModelService openAiModelService(WebClient.Builder builder) { return new OpenAiCompatibleService(builder); } Bean ConditionalOnProperty(name quickblue.ai.default-model, havingValue internal-model) public AiModelService internalModelService() { return new InternalModelService(); } }这样切换模型只需要改配置不需要改代码。4.4 前端流式对话实现前端用 Vite 8 Vue 3 举例async function streamChat(message) { const response await fetch(/api/ai/chat, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ message, stream: true }) }); const reader response.body.getReader(); const decoder new TextDecoder(); let buffer ; while (true) { const { done, value } await reader.read(); if (done) break; buffer decoder.decode(value, { stream: true }); const lines buffer.split(\n); buffer lines.pop(); for (const line of lines) { if (line.startsWith(data: )) { const data line.slice(6); if (data [DONE]) return; const chunk JSON.parse(data); appendToDisplay(chunk.content); } } } }关键点是维护一个 buffer处理跨 chunk 的数据。因为网络传输不一定按行分割可能一行数据被拆成两个 chunk也可能两个 chunk 合在一起。4.5 部署与健康检查底座本身要支持容器化部署。Dockerfile 大概这样FROM eclipse-temurin:21-jre WORKDIR /app COPY target/quickblue-runtime.jar app.jar EXPOSE 8080 HEALTHCHECK --interval30s --timeout3s \ CMD curl -f http://localhost:8080/actuator/health || exit 1 ENTRYPOINT [java, -jar, app.jar]健康检查要包含模型连通性检测。Spring Boot Actuator 可以自定义 HealthIndicatorComponent public class AiModelHealthIndicator implements HealthIndicator { private final AiModelService aiModelService; Override public Health health() { try { AiRequest request new AiRequest(); request.setMessages(List.of(new Message(user, ping))); request.setMaxTokens(1); aiModelService.chat(request); return Health.up().build(); } catch (Exception e) { return Health.down().withDetail(error, e.getMessage()).build(); } } }实操心得健康检查不要频繁调用模型成本很高。可以设置一个较长的缓存时间比如 5 分钟检查一次或者只在启动时检查一次。5. 常见问题与排查技巧实录5.1 模型调用超时怎么办这是最常见的问题。模型响应时间不稳定有时候几秒有时候几十秒。我的处理策略是设置合理的超时时间。一般对话场景 30 秒复杂推理场景 60 秒。实现超时降级。超时后返回一个友好的提示而不是让用户一直等。对于流式输出设置首字节超时。如果模型 10 秒还没返回第一个 token就认为失败。排查超时问题时先看网络再看模型服务端最后看自己的代码。我遇到过因为 WebClient 默认缓冲区太小导致大响应被截断的情况调整maxInMemorySize后解决。5.2 流式输出中断怎么排查流式输出中断通常有几个原因现象可能原因排查方法输出到一半停止模型服务端超时查看模型服务日志输出内容乱码编码不一致检查 Content-Type 和字符集前端不显示SSE 格式错误抓包看原始数据间歇性中断网络不稳定增加重试和断点续传我的经验是流式输出一定要加心跳机制。每隔几秒发一个空数据包保持连接活跃。同时前端要处理连接断开后的自动重连。5.3 虚拟线程下的事务问题JDK 21 虚拟线程和数据库事务一起用时要特别注意。传统的事务上下文通常绑定在 ThreadLocal 上虚拟线程切换时可能导致上下文丢失。我的建议是尽量让事务范围小不要在事务里调用模型接口。如果必须在事务里调用考虑用编程式事务手动控制边界。测试阶段一定要做并发测试模拟虚拟线程切换场景。5.4 前端 Markdown 渲染性能问题流式输出时如果每次都全量解析 Markdown内容长了之后会卡。我的优化方案是分块解析。把内容按段落分块只重新解析变化的块。延迟渲染。对于非关键内容可以攒一批再渲染。用 Web Worker。把 Markdown 解析放到 Worker 里不阻塞主线程。实测下来分块解析能把长对话的渲染性能提升 50% 以上。5.5 模型切换后行为不一致不同模型对同一个提示词的响应可能差异很大。底座层面能做的是提供提示词模板管理不同模型可以用不同模板。记录模型版本方便对比效果。提供 A/B 测试能力让业务方自己选择。这个问题没有银弹只能通过工程手段降低切换成本。6. 我在实际项目中的几点体会做 AI 应用底座这件事技术难度其实不是最大的最大的挑战是“克制”。底座团队很容易陷入一个误区什么功能都想做什么场景都想覆盖最后变成一个臃肿的怪物。我的原则是底座只做“所有 AI 应用都必须要有的东西”业务特有的东西坚决不放。比如对话界面组件可以放但具体的业务逻辑不能放模型接入可以放但提示词优化不能放。另外底座一定要有“逃生舱”。也就是说业务方如果觉得底座某部分不好用可以绕过它自己实现。比如模型接入层如果业务方想直接调某个模型的特殊接口底座应该允许而不是强制走统一接口。强制统一的结果往往是业务方干脆不用底座了。最后底座的版本升级要极其谨慎。AI 应用底座一旦被多个业务依赖升级就不是技术问题而是组织问题。我的做法是核心接口保持稳定新功能通过扩展点提供重大变更走新版本并行给业务方足够的迁移时间。这个领域变化很快今天的最佳实践明天可能就过时了。但“让业务专注于业务让底座解决公共问题”这个思路我觉得在未来几年都不会变。QuickBlue 这类产品的价值也正在于此。