【Spring AI】Spring AI Alibaba的简单使用

**提示:**文章最后有详细的参考文档。

前提条件

  • SpringBoot版本为3.x以上
  • JDK为17以上
  • 申请api-key,地址:百炼平台

引入依赖

说明:我的springboot版本为3.2.4,spring-ai-alibaba-starter版本为1.0.0-M2.1(对应spring-ai版本为1.0.0-M2),jdk版本为17。

1. pom.xml中引入

XML 复制代码
<dependency>
    <groupId>com.alibaba.cloud.ai</groupId>
    <artifactId>spring-ai-alibaba-starter</artifactId>
</dependency>

spring-ai-alibaba 基于 spring-ai 开发,由于 spring-ai 相关依赖包还没有发布到中央仓库,如出现 spring-ai-core 等相关依赖解析问题,请在您项目的 pom.xml 依赖中加入如下仓库配置。

XML 复制代码
<repositories>
    <repository>
        <id>maven2</id>
        <name>maven2</name>
        <url>https://repo1.maven.org/maven2/</url>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
    </repository>
    <repository>
        <id>spring-milestones</id>
        <name>Spring Milestones</name>
        <url>https://repo.spring.io/milestone</url>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
    </repository>
</repositories>

并且在maven的setting.xml中做出如下更改:

XML 复制代码
<mirror>  
    <id>alimaven</id>  
    <name>aliyun maven</name>  
    <url>https://maven.aliyun.com/repository/public</url> 
    <!-- 表示除了spring-milestones、maven2其它都走阿里云镜像  --> 
    <mirrorOf>*,!spring-milestones,!maven2</mirrorOf>  
</mirror>

2. application.yml中引入

XML 复制代码
spring:
    ai:
      dashscope:
        api-key: 申请的api-key
      chat:
        client:
          enabled: true

3. 代码中引入

java 复制代码
    @Resource
    private ChatModel chatModel;

开发示例

1. 简单的对话

java 复制代码
@GetMapping("/simple")
public String simpleChat(@RequestBody JSONObject param) {
    // 接收并校验参数
    String inputInfo = CommonUtil.getAndCheck(param, "inputInfo", "请输入内容!");
    // 构建chatClient
    ChatClient.Builder builder = ChatClient.builder(chatModel);
    ChatClient chatClient = builder.defaultSystem("你是一个精通Java、Python的程序大佬。").build();
    return chatClient.prompt().user(inputInfo).call().content();
}

2. 流式对话

使用Server Sent Event(SSE)事件返回,对应前端需要处理SSE事件的数据。

java 复制代码
@GetMapping("/stream")
public Flux<ServerSentEvent<String>> streamChat(@RequestBody JSONObject param) {
    // 接收并校验参数
    String inputInfo = CommonUtil.getAndCheck(param, "inputInfo", "请输入内容!");
    
    return chatModel.stream(new Prompt(inputInfo))
            .map(response -> ServerSentEvent.<String>builder()
                    .data(response.getResult().getOutput().getContent()).build())
            .doOnComplete(() -> log.info("响应完成!"))
            .doOnError(e -> log.error("响应异常:", e));
}

3. 带记忆对话(原生API存储)

使用原生的:MessageChatMemoryAdvisor

java 复制代码
private final ChatClient chatClient;
public AIChatController(ChatClient.Builder builder) {
    this.chatClient = builder
            .defaultSystem("你是一个精通Java、Python的程序大佬。")
            .defaultAdvisors(new MessageChatMemoryAdvisor(new InMemoryChatMemory()))
            .build();
}

@GetMapping("/memoryStreamWithApi")
public Flux<ServerSentEvent<String>> memoryStreamWithApi(@RequestBody JSONObject param) {
    // 接收并校验参数
    // 对话记忆ID,我是通过前端传参获取,你可以获取一个UUID
    String accessKey = CommonUtil.getAndCheck(param, "accessKey", "您没有改功能访问权限!");
    String inputInfo = CommonUtil.getAndCheck(param, "inputInfo", "请输入内容!");

    // 请求数据
    return chatClient.prompt()
            .user(inputInfo)
            .advisors(spec -> spec.param(CHAT_MEMORY_CONVERSATION_ID_KEY, accessKey)
                    .param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 5))    // 从上下文中检索聊天内存响应大小,具体参数定义可参考文章最后的Spring AI API文档
            .stream()
            .chatResponse()
            .map(response -> {
                return ServerSentEvent.<String>builder()
                        .data(response.getResult().getOutput().getContent())
                        .build();
            })
            .doOnComplete(() -> {
                log.info("响应完成!");
            })
            .doOnError((e) -> {
                log.warn("响应异常:", e);
            });
}

4. 带记忆对话(Redis存储)

使用redis存储:

java 复制代码
@GetMapping("/memoryStreamWithRedis")
public Flux<ServerSentEvent<String>> memoryStreamWithRedis(@RequestBody JSONObject param) {
    // 接收并校验参数
    // 对话记忆ID,我是通过前端传参获取,你可以获取一个UUID
    String accessKey = CommonUtil.getAndCheck(param, "accessKey", "您没有改功能访问权限!");
    String inputInfo = CommonUtil.getAndCheck(param, "inputInfo", "请输入内容!");

    List<Message> messageList = new ArrayList<>();
    // 拼接redis key
    String redisKey = CommonConst.MY_ACCESS_KEY_PREFIX + accessKey;
    // 判断是否存在聊天记忆,有的话将其带入本次对话中
    if (redisService.hasKey(redisKey)) {
        // 从redis中获取聊天记忆数据
        List<String> strList = redisService.getCacheList(redisKey);
        List<Message> memoryList = new ArrayList<>();
        if (!CollectionUtils.isEmpty(strList)) {
            strList.forEach(s -> memoryList.add(new UserMessage(s)));
            // 反转聊天记忆数据,我存入List数据时采用头插法,所以这里需要反转list,以保证聊天内容的先后顺序
            Collections.reverse(memoryList);
            messageList.addAll(memoryList);
        }
    }
    messageList.add(new UserMessage(inputInfo));

    StringBuilder resultStr = new StringBuilder();
    return chatModel.stream(new Prompt(messageList))
            .map(response -> {
                resultStr.append(response.getResult().getOutput().getContent());
                return ServerSentEvent.<String>builder()
                        .data(response.getResult().getOutput().getContent())
                        .build();
            })
            .doOnComplete(() -> {
                // 将LLM返回的文本存入redis,存储的数据类型是List,enqueue()方法的定义看下文
                redisService.enqueue(redisKey, resultStr.toString(), MAX_LENGTH);
                log.info("响应完成!");
            })
            .doOnError(e -> log.warn("响应异常:", e));
}

RedisService中部分代码:

java 复制代码
/**
 * 添加固定长度的队列,左添加
 *
 * @param key       key
 * @param value     value
 * @param maxLength 最大存储的聊天记录长度
 */
public void enqueue(String key, String value, int maxLength) {
    // 使用Redis的LPUSH命令添加元素,然后确保长度不超过最大值
    redisTemplate.opsForList().leftPush(key, value);
    // 保持队列长度不超过指定长度,LTRIM会自动删除超出长度的旧元素
    if (redisTemplate.opsForList().size(key) > maxLength) {
        redisTemplate.opsForList().trim(key, 0, maxLength - 1);
    }
}

/**
 * 获得缓存的list对象
 *
 * @param key 缓存的键值
 * @return 缓存键值对应的数据
 */
public <T> List<T> getCacheList(final String key) {
    return redisTemplate.opsForList().range(key, 0, -1);
}

/**
 * 判断 key 是否存在
 *
 * @param key 键
 * @return true 存在 false不存在
 */
public Boolean hasKey(String key) {
    return redisTemplate.hasKey(key);
}

参考文档

官方参考文档:Spring AI Alibaba 官方Spring AI 官方Spring AI API 文档

相关推荐
m0_748247804 分钟前
SpringBoot集成Flowable
java·spring boot·后端
小娄写码13 分钟前
线程池原理
java·开发语言·jvm
power-辰南1 小时前
机器学习之数据分析及特征工程详细分析过程
人工智能·python·机器学习·大模型·特征
少说多想勤做1 小时前
【前沿 热点 顶会】AAAI 2025中与目标检测有关的论文
人工智能·深度学习·神经网络·目标检测·计算机视觉·目标跟踪·aaai
橙子小哥的代码世界3 小时前
【计算机视觉基础CV-图像分类】05 - 深入解析ResNet与GoogLeNet:从基础理论到实际应用
图像处理·人工智能·深度学习·神经网络·计算机视觉·分类·卷积神经网络
leigm1233 小时前
深度学习使用Anaconda打开Jupyter Notebook编码
人工智能·深度学习·jupyter
陌上花开࿈5 小时前
调用第三方接口
java
Aileen_0v05 小时前
【玩转OCR | 腾讯云智能结构化OCR在图像增强与发票识别中的应用实践】
android·java·人工智能·云计算·ocr·腾讯云·玩转腾讯云ocr
阿正的梦工坊6 小时前
深入理解 PyTorch 的 view() 函数:以多头注意力机制(Multi-Head Attention)为例 (中英双语)
人工智能·pytorch·python
Ainnle6 小时前
GPT-O3:简单介绍
人工智能