**提示:**文章最后有详细的参考文档。
前提条件
- 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);
}