【深度实战】Spring AI + DeepSeek:打造工业级 Java AI 应用,彻底解决 WebFlux 阻塞难题

1. 时代背景:为什么是 Spring AI + DeepSeek?

2025 年是大模型落地的元年。DeepSeek 凭借极高的性价比和出色的推理能力,成为了国内开发者的首选。然而,对于习惯了 Spring 生态的 Java 程序员来说,如何优雅地接入大模型,而不是到处写 HttpClient 拼 JSON,成了一个现实问题。

Spring AI 应运而生。它是 Spring 官方推出的 AI 框架,旨在统一不同 AI 厂商的接口。更重要的是,它完美适配了 Spring Boot 3.x响应式编程(WebFlux),这正是构建高并发 AI 应用的基石。

本文将带你通过"工程化"的手段,集成 DeepSeek,并重点解决在响应式环境下的 线程阻塞 报错问题。


2. 核心架构设计

在构建一个真正的 AI 应用时,我们不能简单地将 Prompt 写死在代码里。下表展示了本文推荐的架构演进:

|---------------|---------------|------------------------|
| 维度 | 传统写法 (初级) | 工业级写法 (本文推荐) |
| Prompt 管理 | 硬编码在 String 中 | 外部 .st 资源文件,逻辑与内容解耦 |
| 通信模型 | 阻塞式 call() | 非阻塞流式 stream() (SSE) |
| 参数校验 | 无或简单校验 | 严格的响应式校验与错误捕获 |
| 线程模型 | Tomcat 阻塞线程 | Reactor 非阻塞线程池 (Netty) |


3. 环境准备与项目配置

3.1 基础版本要求

  • JDK: 17 或 21 (Spring Boot 3.x 的基石)

  • Maven: 3.8+

  • DeepSeek API Key : 从 DeepSeek 开放平台 获取。

3.2 依赖管理 (pom.xml)

Spring AI 目前处于快速迭代期,建议使用最新的里程碑版本。由于 DeepSeek API 与 OpenAI 协议 100% 兼容,我们直接使用 OpenAI 的 Starter。

复制代码
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-bom</artifactId>
            <version>1.0.0-M1</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependencies>
    <!-- WebFlux 响应式服务器 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webflux</artifactId>
    </dependency>
    <!-- Spring AI OpenAI Starter (兼容 DeepSeek) -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
    </dependency>
    <!-- Lombok & Log -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

3.3 配置文件 (application.yml)

千万不要把 API Key 直接上传到 Git,建议使用环境变量。

复制代码
spring:
  ai:
    openai:
      api-key: ${DEEPSEEK_API_KEY}
      base-url: https://api.deepseek.com
      chat:
        options:
          # 使用 deepseek-chat 或 deepseek-reasoner
          model: deepseek-chat
          temperature: 0.7

4. 深度进阶:Prompt 工程化解耦

在生产环境下,Prompt 往往需要频繁调优。如果写在代码里,每次修改都要重新打包发布。

4.1 创建 Prompt 资源文件

在 src/main/resources/prompts/ 下创建 code-expert.st。.st 文件遵循 StringTemplate 语法。

复制代码
# 角色
你是一名资深的 Java 架构师,擅长 Spring Boot 和微服务架构。

# 任务
请针对用户提供的技术主题 {topic},完成以下任务:
1. 深入浅出地解释该技术的核心底层原理;
2. 给出一段符合阿里巴巴 Java 开发规范的代码示例;
3. 列出 3 个在生产环境下常见的"坑"以及避坑方案。

# 约束
请使用 Markdown 格式输出,并确保代码部分有详细注释。

5. 核心代码实现:ChatController

针对你遇到的 ResourceAccessException (block() 报错),下方的代码给出了最完美的响应式解决方案

复制代码
@RestController
@RequestMapping("/ai")
@Slf4j
public class ChatController {

    private final ChatClient chatClient;

    // 注入外部 Prompt 资源文件
    @Value("classpath:/prompts/code-expert.st")
    private Resource expertPromptResource;

    /**
     * Spring AI 推荐使用 ChatClient.Builder 来构建客户端
     * 它会自动根据 application.yml 的配置进行初始化
     */
    public ChatController(ChatClient.Builder builder) {
        this.chatClient = builder.build();
    }

    /**
     * 【方案一:普通对话】
     * 痛点:WebFlux 环境下禁止阻塞。
     * 解决:通过 stream() 异步收集结果,避免 block()。
     */
    @GetMapping("/chat")
    public Mono<String> chat(@RequestParam("message") String message) {
        log.info("接收到普通对话请求: {}", message);
        return chatClient.prompt()
                .user(message)
                .stream()  // 采用流式获取,确保不阻塞 I/O 线程
                .content()
                .collectList() 
                .map(parts -> String.join("", parts))
                .doOnError(e -> log.error("对话发生异常", e));
    }

    /**
     * 【方案二:流式对话(SSE)】
     * 场景:类似 ChatGPT 逐字显示。
     * 优点:极高的响应速度,用户体验极佳。
     */
    @GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> chatStream(@RequestParam("message") String message) {
        return chatClient.prompt()
                .user(message)
                .stream()
                .content();
    }

    /**
     * 【方案三:专家模式 - 外部 Prompt + 严格异常处理】
     * 重点:展示了如何动态填充外部模板并处理流式异常。
     */
    @GetMapping(value = "/expert", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> askExpert(@RequestParam String topic) {
        // 1. 严格的参数校验
        if (topic == null || topic.trim().isEmpty()) {
            return Flux.just("警告:问答主题不能为空!");
        }

        try {
            // 2. 加载并填充提示词模板
            PromptTemplate template = new PromptTemplate(expertPromptResource);
            Map<String, Object> model = Map.of("topic", topic.trim());
            Prompt prompt = template.create(model);
            
            log.info("专家模式启动,正在生成关于 [{}] 的深度分析...", topic);

            // 3. 核心:返回非阻塞响应流
            return chatClient.prompt(prompt)
                    .stream()
                    .content()
                    .doOnNext(part -> log.debug("Receiving chunk..."))
                    .onErrorResume(e -> {
                        log.error("DeepSeek 调用失败: {}", e.getMessage());
                        return Flux.just("【系统异常】AI 暂时掉线了,请稍后再试。详情:" + e.getMessage());
                    });
        } catch (Exception e) {
            log.error("Prompt 模板解析异常", e);
            return Flux.just("提示词解析失败,请检查配置文件。");
        }
    }
}

6. 技术内幕:为什么会报 block() 错误?

你在报错中提到的:

block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-nio-3

深度解析:

  1. Reactor 线程模型:WebFlux 运行在 Netty 之上,它的 NIO 线程池(如 reactor-http-nio-3)数量非常少(通常等于 CPU 核心数)。

  2. 禁令:为了保证高吞吐,Reactor 严禁在这些线程中执行任何"阻塞"操作(比如等待网络返回、查询数据库)。

  3. Spring AI 的特殊性:chatClient.call() 是同步阻塞的,它会强行霸占 NIO 线程。Spring 框架检测到这一行为后,为了防止系统卡死,会主动抛出异常"自杀"。

  4. 修复策略:使用 .stream() 返回 Flux。流式操作是异步的,当 DeepSeek 还没返回数据时,NIO 线程可以去干别的事,等数据到了再回来处理。


7. 生产环境避坑指南

  1. 超时时间设置:AI 响应通常很慢。在 WebFlux 中,确保你的 WebClient 或相关配置中有足够的超时时间,否则会频繁触发 504 错误。

  2. API 额度监控:DeepSeek 可能会因为欠费或达到限制而返回 429 错误。在 onErrorResume 中一定要做好降级逻辑。

  3. 敏感词过滤:在将用户输入送往大模型之前,建议通过接入公司内部的敏感词库做一层逻辑过滤,防止 Prompt 注入攻击。

  4. 资源解耦:像本文一样,坚持将 .st 文件与代码分离,这会让你在处理复杂 Prompt 时从容得多。


8. 总结

Spring AI 不仅仅是简单的接口封装,它通过响应式支持、Prompt 模板化管理,为 Java 开发者铺平了通往 AI 应用的道路。

通过本文的实战,我们不仅实现了 DeepSeek 的无缝接入 ,还解决了 WebFlux 线程阻塞 这一核心痛点。希望这篇文章能帮你构建出更加健壮的 Java AI 应用!

博主注:

如果觉得文章有用,欢迎 点赞、收藏、关注

完整代码已上传至 Gitee:https://gitee.com/zhang-jinlong1/spring-ai-deepseek-demo.git

如有疑问,评论区见!

相关推荐
九.九5 小时前
ops-transformer:AI 处理器上的高性能 Transformer 算子库
人工智能·深度学习·transformer
春日见5 小时前
拉取与合并:如何让个人分支既包含你昨天的修改,也包含 develop 最新更新
大数据·人工智能·深度学习·elasticsearch·搜索引擎
恋猫de小郭5 小时前
AI 在提高你工作效率的同时,也一直在增加你的疲惫和焦虑
前端·人工智能·ai编程
deephub5 小时前
Agent Lightning:微软开源的框架无关 Agent 训练方案,LangChain/AutoGen 都能用
人工智能·microsoft·langchain·大语言模型·agent·强化学习
大模型RAG和Agent技术实践6 小时前
从零构建本地AI合同审查系统:架构设计与流式交互实战(完整源代码)
人工智能·交互·智能合同审核
老邋遢6 小时前
第三章-AI知识扫盲看这一篇就够了
人工智能
互联网江湖6 小时前
Seedance2.0炸场:长短视频们“修坝”十年,不如AI放水一天?
人工智能
青云计划6 小时前
知光项目知文发布模块
java·后端·spring·mybatis
PythonPioneer6 小时前
在AI技术迅猛发展的今天,传统职业该如何“踏浪前行”?
人工智能
赶路人儿6 小时前
Jsoniter(java版本)使用介绍
java·开发语言