提示:文章最后有详细的参考文档。
前提条件
- 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中引入
<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 依赖中加入如下仓库配置。
<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中做出如下更改:
<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中引入
spring:ai:dashscope:api-key: 申请的api-keychat:client:enabled: true
3. 代码中引入
@Resourceprivate ChatModel chatModel;
开发示例
1. 简单的对话
@GetMapping("/simple")
public String simpleChat(@RequestBody JSONObject param) {// 接收并校验参数String inputInfo = CommonUtil.getAndCheck(param, "inputInfo", "请输入内容!");// 构建chatClientChatClient.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事件的数据。
@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
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,我是通过前端传参获取,你可以获取一个UUIDString 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存储:
@GetMapping("/memoryStreamWithRedis")
public Flux<ServerSentEvent<String>> memoryStreamWithRedis(@RequestBody JSONObject param) {// 接收并校验参数// 对话记忆ID,我是通过前端传参获取,你可以获取一个UUIDString accessKey = CommonUtil.getAndCheck(param, "accessKey", "您没有改功能访问权限!");String inputInfo = CommonUtil.getAndCheck(param, "inputInfo", "请输入内容!");List<Message> messageList = new ArrayList<>();// 拼接redis keyString 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中部分代码:
/*** 添加固定长度的队列,左添加** @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 文档