提示:文章最后有詳細的參考文檔。
前提條件
- 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 文檔