使用SpringAi+Deepseek接口进行简单聊天以及记忆持久化

1.获取DeepSeekApi

进入DeepSeek开放平台:DeepSeek 开放平台

充值后进行API key的创建

image.png

复制创建好的API后续在Spring项目中进行配置操作

2.创建Spring项目

选择Spring Web以及OpenAi (DeepSeekApi接口兼容OpenAiApi规范)依赖,

image.png

image.png

新建application.yml文件:

image.png

其中api-key改为先前获取到的apikey

base-url在spring中要求必须设置为:api.deepseek.com

image.png

model为模型选择使用deepseek-chat指定为DeepSeek-V3模型

temperature为控制生成的创造性,即数字越大生成的越具有创造性,默认为1

DeepSeek的api文档对temperature的设置如下:

image.png

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请求

image.png

返回json类型的回答

再对/ai/generateStream进行Get请求

image.png

发现其返回的为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 使浏览器边接收边渲染,所以能实时看到输出。

image.png

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的接口文档也是这样要求的。

image.png

在Spring中给出的示例如下:

image.png

我们可以在自己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);
}

测试

image.png

将记忆数据持久化

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博客

DeepSeek Chat :: Spring AI Reference

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐