之前通过Okhttp实现过对大模型的调用,感觉流程比较繁琐,现在一起来学习下如何通过Spring Ai的方式来实现,非常简单,只需要一个类就可以实现。

下面以调用DeepSeek模型为例!

先看效果,然后拆解步骤~

发起问答:
在这里插入图片描述
日志打印:
在这里插入图片描述

代码实现

PS:代码相关解释都放在代码段注释里~

1. 环境

JDK 17
SpringBoot 3.x.x 以上
SpringAI 版本 1.0.0-M6

2. 配置

引入SpringAI依赖

	<dependencies>
		<dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
            <version>1.0.0-M6</version>
        </dependency>
     </dependencies>
	<!-- 如果下载不了 再引入下面的配置 -->
    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>

springboot配置

server:
  port: 9093

spring:
  application:
    name: mdx-ai

# spring ai
  ai:
    openai:
      base-url: https://api.deepseek.com # deepseek base-url
      api-key: your-aip-key # 你的 deepseek api-key
      chat:
        options:
          model: deepseek-chat # 模型选择 deepseek-chat为通用模型
          temperature: 0.7 # 温度 介于 0 和 2 之间 值越高输出越随机
3. 实现
package com.mdx.mdxai.controller;
import com.mdx.mdxai.dto.ChatRequestDto;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * @author : jiagang
 * @date : Created in 2025/10/30 18:42
 */
@RestController
@RequestMapping("/chat")
public class AiChatController {
    private static final Logger logger = LoggerFactory.getLogger(AiChatController.class);

    private final ChatClient chatClient;
    private final List<Message> conversationHistory = new CopyOnWriteArrayList<>();

    public AiChatController(ChatClient.Builder chatClientBuilder) {
        this.chatClient = chatClientBuilder
                .defaultSystem("你是一个有用的AI助手")
                .build();
    }

    @PostMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> streamMessage(@RequestBody ChatRequestDto request) {
        logger.info("【AI助手】开始流式对话: {}", request.getMessage());

        return Flux.create(sink -> {
            try {
                UserMessage userMessage = new UserMessage(request.getMessage());
                // 上下文信息
                conversationHistory.add(userMessage);

                // 使用Spring AI的流式API
                Flux<String> contentStream = chatClient.prompt()
                        // 将上下文给到模型
                        .messages(conversationHistory)
                        .stream()
                        .content();

                // 订阅流式内容并转发给前端
                contentStream
                        // 接收模型的流式响应
                        .doOnNext(content -> {
                            logger.info("【AI助手】流式内容片段: {}", content);
                            // 发送SSE格式的数据
                            sink.next("data: " + content + "\n\n");
                        })
                        // 接收模型的流式完成响应
                        .doOnComplete(() -> {
                            logger.info("【AI助手】流式对话完成");
                            sink.next("data: [DONE]\n\n");
                            sink.complete();
                        })
                        // 接收模型的流式异常响应
                        .doOnError(error -> {
                            logger.error("【AI助手】流式对话错误: {}", error.getMessage());
                            sink.error(error);
                        })
                        // 订阅模型的sse流
                        .subscribe();

            } catch (Exception e) {
                logger.error("【AI助手】流式对话启动失败: {}", e.getMessage());
                sink.error(e);
            }
        });
    }

    // 清空对话历史
    @DeleteMapping("/history/clear")
    public ResponseEntity<String> clearHistory() {
        conversationHistory.clear();
        return ResponseEntity.ok("对话历史已清空");
    }

    private String getFullResponseFromStream() {
        // 这里需要实现从流中收集完整响应的方法
        // 实际实现可能需要更复杂的逻辑来收集流数据
        return "";
    }
}

这就结束了,这你受得了吗,从搭建项目到接收问答20分钟就能搞定。

当然,你可能还需要了解一些其他知识,比如Flux这玩意是啥?

答:
它属于 Spring WebFlux中的知识,也就是响应式编程,是和传统的Spring Mvc的方式做对比的,一般和Flux一起出现的还有Mono。
Mono:只包含0个或者1个元素,一般用于查询单个数据,比如我们之前的单个对象。
Flux:包含0个或者N个元素,一般用于返回多个数据,比如我们之前的集合,它支持异步处理,比如说处理消息流或者是本次的SSE事件。

其实对于概念我也比较懵,总结一段话就是:
对于WebFlux这种响应式编程,不会像传统mvc模式下一个请求占用一个线程,如果线程池耗尽那新的请求就要阻塞, 而对于WebFlux这种框架它一个线程就可以处理多个请求,比如某一个线程在处理一个io的耗时操作,传统框架这个线程就会阻塞等待,而阻塞就会操作系统内核,并且导致cpu时间片和上下文切换,非常消耗性能; 而WebFlux在遇到io操作时,线程不会阻塞,它会注册一个回调函数然后去处理其他任务,当上一个io完成之后底层系统会通知这个线程继续处理。

友情链接:
deepseek开放平台: https://platform.deepseek.com/usage

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐