
使用SpringAi+Deepseek接口进行简单聊天以及记忆持久化 1c79ec73f50d802fb475e6b12b5b8051
充值后进行API key的创建复制创建好的API后续在Spring项目中进行配置操作。
使用SpringAi+Deepseek接口进行简单聊天以及记忆持久化
1.获取DeepSeekApi
进入DeepSeek开放平台:DeepSeek 开放平台
充值后进行API key的创建
复制创建好的API后续在Spring项目中进行配置操作
2.创建Spring项目
选择Spring Web以及OpenAi (DeepSeekApi接口兼容OpenAiApi规范)依赖,
新建application.yml文件:
其中api-key改为先前获取到的apikey
base-url在spring中要求必须设置为:api.deepseek.com
model为模型选择使用deepseek-chat指定为DeepSeek-V3模型
temperature为控制生成的创造性,即数字越大生成的越具有创造性,默认为1
DeepSeek的api文档对temperature的设置如下:
3.普通对话实现
Spring在文档中提供了两种简单对话的例子:DeepSeek 聊天 :: Spring AI 参考
@RestController
public class ChatController {
private final OpenAiChatModel chatModel;
@Autowired
public ChatController(OpenAiChatModel chatModel) {
this.chatModel = chatModel;
}
@GetMapping("/ai/generate")
public Map generate(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message) {
return Map.of("generation", this.chatModel.call(message));
}
@GetMapping("/ai/generateStream")
public Flux<ChatResponse> generateStream(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message) {
Prompt prompt = new Prompt(new UserMessage(message));
return this.chatModel.stream(prompt);
}
}
启动Spring项目后对/ai/generate进行Get请求
返回json类型的回答
再对/ai/generateStream进行Get请求
发现其返回的为json列表格式,这是因为Flux
是流式数据,它会一条条发送,但浏览器默认不会按流格式解析,而是等数据完全返回后再处理。
如果想让浏览器对其进行渲染,可以更改为Flux<String>
,返回 纯文本数据,浏览器能直接解析文本并显示出来
代码如下:
@GetMapping(value = "/ai/chatStream", produces = "text/html;charset=UTF-8")
public ResponseEntity<Flux<String>> chat(@RequestParam(value="message") String message) {
try{
Flux<String> response = chatclient.prompt(message).stream().content();
returnResponseEntity.ok()
//设置内容类型为文本事件流
.contentType(MediaType.parseMediaType("text/html;charset=UTF-8"))
.body(response);
}catch(Exception e) {
returnResponseEntity.badRequest().build();
}
}
这里我使用了ChatClient提供的功能
ChatClient可以新建一个配置类里进行设定配置
@Configuration
public classAiConfig {
//建立ai设定
@Bean
ChatClientchatClient(ChatClient.Builderbuilder) {
returnbuilder.defaultSystem("现在你的名字叫天外来物").build();
}
}
controller中可以这样进行构造
private final OpenAiChatModel chatModel;
private final ChatClient chatclient;
@Autowired(required = false)
public AiController(OpenAiChatModel chatModel,ChatClient chatClient) {
this.chatModel = chatModel;
this.chatclient = chatClient;
}
测试可正常渲染到浏览器,同时Flux<String>
可以让数据逐步返回, text/html
使浏览器边接收边渲染,所以能实时看到输出。
4.多轮对话实现
在SpringAi中提供了一个ChatMemory的接口向对话添加消息、从对话中检索消息以及清除对话历史记录的方法。
ChatMemory的源代码如下:
public interf
aceChatMemory{
// TODO: consider a non-blocking interface for streaming usages
default void add(String conversationId,Messagemessage) {
this.add(conversationId,List.of(message));
}
voidadd(String conversationId,List<Message> messages);
List<Message> get(String conversationId,intlastN);
voidclear(String conversationId);
}
原理是根据conversationId来将每一轮对话保存到一个List中,在下一次会话中将储存先前对话的message列表一同传过去,在DeepSeek的接口文档也是这样要求的。
在Spring中给出的示例如下:
我们可以在自己config配置中对进行如下配置:
@Configuration
public class AiConfig {
@Bean
public ChatMemory chatMemory() {
return newInMemoryChatMemory();
}
@Bean
public ChatClient chatClient(ChatClient.Builderbuilder,ChatMemory chatMemory) {
return builder
.defaultSystem("你现在是一个客服,我问什么都要耐心回答")
.defaultAdvisors(
new MessageChatMemoryAdvisor(chatMemory),
new SimpleLoggerAdvisor()
)
.build();
}
}
controller中可以改为如下:
@GetMapping(value = "/chatStream", produces = "text/html;charset=UTF-8")
public ResponseEntity<Flux<String>> moreChat(@RequestParam(value = "chatId",defaultValue = "1") String chatId, @RequestParam(value="message") String message) {
try {
Flux<String> response = chatclient.prompt()
.user(message)
.advisors(advisorSpec -> advisorSpec
.param(CHAT_MEMORY_CONVERSATION_ID_KEY,chatId)
.param(CHAT_MEMORY_RETRIEVE_SIZE_KEY,100))
.stream().content();
return ResponseEntity.ok()
// 设置内容类型为文本事件流
.contentType(MediaType.parseMediaType("text/html;charset=UTF-8"))
.body(response);
} catch (Exception e) {
return ResponseEntity.badRequest().build();
}
}
CHAT_MEMORY_CONVERSATION_ID_KEY和CHAT_MEMORY_RETRIEVE_SIZE_KEY需要import
import staticorg.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY;
import staticorg.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor.CHAT_MEMORY_RETRIEVE_SIZE_KEY;
同时再写一个getMessage查看是否有存入chatMemory中
@GetMapping("/getMessages")
publicList<Message> getMessages(@RequestParam (value = "sessionId",defaultValue = "1")String sessionId) {
returnchatMemory.get(sessionId,5);
}
测试
将记忆数据持久化
ChatMemory中的所有数据都是存入内存中的,如果项目重启就会全部消失,可以将数据存入Redis中进行持久化操作,具体方法可以参考:Spring AI进阶:AI聊天机器人之ChatMemory持久化(二)_springai chatclient 向量数据库-CSDN博客
简单概要为:
1.重写ChatMemory:
@Component
@RequiredArgsConstructor
public classMyChatMemoryimplementsChatMemory{
private static finalString REDIS_KEY_PREFIX = "chatmemory:";
private finalRedisTemplate<String,Message> redisTemplate;
@Override
public voidadd(String conversationId,List<Message> messages) {
String key = REDIS_KEY_PREFIX + conversationId;
//存储到 Redis
redisTemplate.opsForList().rightPushAll(key, messages);
}
@Override
publicList<Message> get(String conversationId,intlastN) {
String key = REDIS_KEY_PREFIX + conversationId;
//从 Redis获取最新的 lastN条消息
List<Message> serializedMessages = redisTemplate.opsForList().range(key, -lastN, -1);
if(serializedMessages !=null) {
returnserializedMessages;
}
returnList.of();
}
@Override
public voidclear(String conversationId) {
redisTemplate.delete(REDIS_KEY_PREFIX + conversationId);
}
}
2.根据Message自定义序列化器:
public classMessageRedisSerializ
er implements RedisSerializer<Message> {
private finalObjectMapper objectMapper;
private finalJsonDeserializer<Message> messageDeserializer;
publicMessageRedisSerializer(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
this.messageDeserializer =newJsonDeserializer<>() {
@Override
publicMessagedeserialize(JsonParser jp, DeserializationContext ctx)
throwsIOException {
ObjectNode root = jp.readValueAsTree();
String type = root.get("messageType").asText();
return switch(type) {
case"USER" ->newUserMessage(root.get("text").asText());
case"ASSISTANT" ->newAssistantMessage(root.get("text").asText());
default->throw newUnsupportedOperationException("未知的消息类型");
};
}
};
}
@Override
public byte[] serialize(Messagemessage) {
try{
returnobjectMapper.writeValueAsBytes(message);
}catch(JsonProcessingException e) {
throw newRuntimeException("无法序列化", e);
}
}
@Override
publicMessagedeserialize(byte[] bytes) {
if(bytes ==null|| bytes.length == 0) {
return null;
}
try{
returnmessageDeserializer.deserialize(objectMapper.getFactory().createParser(bytes), objectMapper.getDeserializationContext());
}catch(Exception e) {
throw newRuntimeException("无法反序列化", e);
}
}
}
3.配置RedisConfig:
@Configuration
public classRedisConfig {
@Bean
publicRedisTemplate<String,Message> messageRedisTemplate(RedisConnectionFactoryfactory, Jackson2ObjectMapperBuilder builder) {
RedisTemplate<String,Message> template =newRedisTemplate<>();
template.setConnectionFactory(factory);
//使用String序列化器作为key的序列化方式
template.setKeySerializer(newStringRedisSerializer());
//使用自定义的Message序列化器作为value的序列化方式
template.setValueSerializer(newMessageRedisSerializer(builder.build()));
//设置hash类型的key和value序列化方式
template.setHashKeySerializer(newStringRedisSerializer());
template.setHashValueSerializer(newMessageRedisSerializer(builder.build()));
template.afterPropertiesSet();
returntemplate;
}
@Bean
publicObjectMapper objectMapper() {
return newObjectMapper().registerModule(newJavaTimeModule());
}
}
4.最后将AiConfig中以及Controller中的ChatMemory替换为MyChatMemory
参考文献
Spring AI进阶:AI聊天机器人之ChatMemory持久化(二)_springai chatclient 向量数据库-CSDN博客
更多推荐
所有评论(0)