第一次加载时需要拉取CDN的前端JS,尽量保持网络可以访问外网,避免显示效果不全
加载完成后形成前端缓存,后续就不用拉了

share-11.png
我们首先从一些Spring AI的基础概念开始
Advisor在spring ai中直译为顾问,其实本质上和Spring AOP切面是一种东西,内部是责任链实现,这部分Advisor主要是对chat交互过程中进行增强
比如最简单的内置日志Advisor
org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor

share-1.png
很明显看得处理adviseStream是前后增强的模式
Advisor可以扩展的很多,比如后面实现的Memory也是通过Advisor
transformer就是转化器的意思,一般使用在大模型api交互前,比如使用org.springframework.ai.rag.preretrieval.query.transformation.TranslationQueryTransformer
进行语言转化,比如讲用户输入的英文转化为中文
通常来讲,Spring AI内置的Transformer都是以定制Prompt的形式实现的,其中还需要过一遍大模型的交互,以TranslationQueryTransformer为例

share-2.png
提示词很直观,就是给定用户的提问,转化为对应的语言,如果已经是该语言,则不处理,如果不是就翻译

share-3.png
需要注意的是,目前官方实现的所有Transformer都是call()模式调用的大模型,及同步调用,这种调用在如果作为组件串联在异步流程中会报错,比如使用在后面的RAG工作流的RetrievalAugmentationAdvisor中,因为无法在完全异步流程中等待同步调用
实际上自己实现一个Stream流式类型的Transformer翻译完全只需要改动几行代码,比如我们项目中
com.demo.spring.ai.fullstack.advisor.pre.StreamTranslationQueryTransformer

share-4.png
将call同步调用变成stream流式,由于要保持格式统一,还可以block等待返回完毕了再出去
但由于改动相对简单,我们可以思考一下为什么Spring官方不实现这种模式
通过观察spring-ai及spring-ai-alibaba的issue和pr,我们可以发现的一个观点是
对于功能性确定,不需要对用户展示的转化,实现流式几乎没有意义,也就是说,作为交互的一个前置流程(比如将用户的提问翻译为特定语言,然后再提交给大模型),在翻译完成之前,让大模型就异步流式执行,是不必要的,等于翻译还没完,大模型已经开始吐字了,这种情况增加了实现难度,又让异步流变得没有意义
另外,在实际的测试过程中,虽然这种异步转同步的假异步方式Transformer实现,让后续的RAG工作流能够跑通了,但实际上耗时远远超过了纯使用call()方法同步的耗时,所以在这个项目中,stream的Transformer没有默认开启
本文采用Spring AI Alibaba Memory组件来实现记忆功能,并采用Redis记忆,具体需要引入如下两个包,且版本>=1.0.0.3
展开
代码语言:YAML
自动换行
AI代码解释
代码语言:javascript
AI代码解释
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-autoconfigure-memory</artifactId>
<version>1.0.0.3</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-memory-redis</artifactId>
<version>1.0.0.3</version>
</dependency>配置MessageWindowChatMemory,可以设置记忆的存储最大条数
展开
代码语言:Java
自动换行
AI代码解释
代码语言:javascript
AI代码解释
@Configuration
@Slf4j
public class MemoryConfig {
private final int MAX_MESSAGES = 100;
@Bean("messageMemory")
public MessageWindowChatMemory messageWindowChatMemory(RedissonChatMemoryRepository redissonChatMemoryRepository) {
log.info("Initializing MessageWindowChatMemory with max messages: {}", MAX_MESSAGES);
return MessageWindowChatMemory.builder()
.chatMemoryRepository(redissonChatMemoryRepository)
.maxMessages(MAX_MESSAGES)
.build();
}
}注册该记忆到对应的ChatClient

share-5.png
使用时可自定义参数,比如指明存储的会话id

share-6.png
展开
代码语言:Java
自动换行
AI代码解释
代码语言:javascript
AI代码解释
@RestController
@RequestMapping("/conversation")
@Slf4j
public class ConversationController {
@Resource
private MessageWindowChatMemory messageWindowChatMemory;
@GetMapping("/messages")
public List<Message> messages(@RequestParam(value = "sessionId") String sessionId) {
return messageWindowChatMemory.get(sessionId);
}
}由于demo创建的时间关系,历史会话没有去实现,以下给出实现思路

share-7.png

share-8.png
以上图为例
当用户和大模型发生交互后,历史会话会存储在redis中
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。