4.SpringAI
SpringAI整合了全球(主要是国外)的大多数模型,而且对于大模型开发的三种技术架构都有⽐较
好的封装和支持,开发起来⾮常方便。不同的模型能够接收的输⼊类型、输出类型不⼀定相同。SpringAI根据模型的输⼊和输出类型不同对 模型进⾏了分类:
⼤模型应用开发大多数情况下使⽤的都是基于对话模型(Chat Model),也就是输出结果为⾃然语言或代码的模型
⽬前SpringAI⽀持的⼤约19种对话模型,这是SpringAi官方文档
其中功能最完整的就是OpenAI和Ollama平台的模型了。
5..SpringAI⼊⻔
5.1.创建⼯程
创建⼀个新的SpringBoot⼯程,勾选Web、MySQL驱动即可
注意jdk版本必须大于等于17
工程结构如图:
5.2引入依赖
SpringAI完全适配了SpringBoot的⾃动装配功能,⽽且给不同的⼤模型提供了不同的starter,比如:
我们可以根据⾃⼰选择的平台来选择引⼊不同的依赖。这⾥我们先以Ollama为例。
⾸先,在项⽬pom.xml中添加spring-ai的版本信息:
<spring-ai.version>1.0.0-M6</spring-ai.version>
然后,添加spring-ai的依赖管理项:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
最后,引⼊spring-ai-ollama的依赖:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
</dependency>
为了⽅便后续开发,我们再⼿动引⼊⼀个Lombok依赖:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
❗ 注意:千万不要⽤start.spring.io提供的lombok,有bug!!
5.3. 配置模型信息
-
配置
application.yaml:
设置Ollama服务地址和模型参数: -
spring: ai: ollama: base-url: http://localhost:11434 chat: model: deepseek-r1:7b options: temperature: 0.8
5.4. 定义ChatClient
创建配置类:
在CommonConfiguration中注入ChatClient:
@Configuration
public class CommonConfiguration {
@Bean
public ChatClient chatClient(OllamaChatModel model) {
return ChatClient.builder(model)
.defaultSystem("您的客服助手设定")
.build();
}
}
5.5. 同步调用接口
-
编写Controller:
处理用户输入并返回AI响应:@RestController @RequestMapping("/ai") public class ChatController { private final ChatClient chatClient; @RequestMapping("/chat") public String chat(@RequestParam String prompt) { return chatClient.prompt(prompt).call().content(); } }访问示例:
http://localhost:8080/ai/chat?prompt=你好。 -

5.6. 流式调用优化
-
修改Controller:
使用Flux实现流式响应:
@RequestMapping(value = "/chat", produces = "text/html;charset=UTF-8")
public Flux<String> chat(@RequestParam String prompt) {
return chatClient.prompt(prompt).stream().content();
}
优势:逐字返回结果,提升用户体验。
5.7.System设定
可以发现,当我们询问AI你是谁的时候,它回答⾃⼰是DeepSeek-R1,这是⼤模型底层的设定。如果 我们希望AI按照新的设定⼯作,就需要给它设置System背景信息。 在SpringAI中,设置System信息⾮常⽅便,不需要在每次发送时封装到Message,⽽是创建 ChatClient时指定即可。
我们修改
CommonConfiguration
中的代码,给
ChatClient
设定默认的System信息:
@Bean
public ChatClient chatClient(OllamaChatModel model) {
return ChatClient.builder(model) // 创建ChatClient⼯⼚实例
.defaultSystem("您是一位大学生,你的名字叫⼩⿊。请以友好、乐于助⼈和愉快的⽅式解答学⽣的各种问题。")
.defaultAdvisors(new SimpleLoggerAdvisor())
.build(); // 构建ChatClient实例
}
5.8.⽇志功能
默认情况下,应用于AI的交互时不记录⽇志的,我们无法得知SpringAI组织的提⽰词到底⻓什么样,有 没有问题。这样不⽅便我们调试。
Spring提供了⼀些Advisor的默认实现,来实现⼀些基本的增强功能:
- SimpleLoggerAdvisor:⽇志记录的Advisor
- MessageChatMemoryAdvisor:会话记忆的Advisor
- QuestionAnswerAdvisor:实现RAG的Advisor
5.8.1.添加⽇志Advisor
⾸先,我们需要修改
CommonConfiguration
,给
ChatClient
添加⽇志Advisor:
@Bean
public ChatClient chatClient(OllamaChatModel model) {
return ChatClient.builder(model) // 创建ChatClient⼯⼚实例
.defaultSystem("你是⼀个热⼼、可爱的智能助⼿,你的名字叫⼩团团,请以⼩团团的⾝份和语⽓回答问题。")
.defaultAdvisors(new SimpleLoggerAdvisor()) // 添加默认的Advisor,记录⽇志
.build(); // 构建ChatClient实例
}
5.8.2.修改⽇志级别
接下来,我们在
application.yaml
中添加⽇志配置,更新⽇志级别:
logging:
level:
org.springframework.ai: debug # AI对话的⽇志级别
com.itheima.ai: debug # 本项⽬的⽇志级别
5.9前端
前端这个可以根据自己需求自己编写要注意跨域问题
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*") // 允许所有域名访问
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*");
}
}
5.10.会话记忆功能
现在,我们的AI聊天机器⼈是没有记忆功能的,上⼀次聊天的内容,下⼀次就忘掉了。 我们之前说过,让AI有会话记忆的⽅式就是把每⼀次历史对话内容拼接到Prompt中,⼀起发送过去。
是不是还挺麻烦的。 别担心,好消息是,我们并不需要自己来拼接,SpringAI⾃带了会话记忆功能,可以帮我们把历史会 话保存下来,下⼀次请求AI时会⾃动拼接,⾮常⽅便。
5.10.1.ChatMemory
会话记忆功能同样是基于AOP实现,Spring提供了⼀个
MessageChatMemoryAdvisor
的通知,我
们可以像之前添加⽇志通知⼀样添加到
ChatClient
即可。
不过,要注意的是,
MessageChatMemoryAdvisor
需要指定⼀个
ChatMemory
实例,也就是会
话历史保存的⽅式。
public interface ChatMemory {
// TODO: consider a non-blocking interface for streaming usages
default void add(String conversationId, Message message) {
this.add(conversationId, List.of(message));
}
// 添加会话信息到指定conversationId的会话历史中
void add(String conversationId, List<Message> messages);
// 根据conversationId查询历史会话
List<Message> get(String conversationId, int lastN);
// 清除指定conversationId的会话历史
void clear(String conversationId);
}
可以看到,所有的会话记忆都是与
conversationId
有关联的,也就是会话Id,将来不同会话id的
记忆自然是分开管理的。
⽬前,在SpringAI中有两个ChatMemory的实现:
- InMemoryChatMemory :会话历史保存在内存中
- CassandraChatMemory :会话保存在Cassandra数据库中(需要引入额外依赖,并且绑定了向量数据库,不够灵活)
我们暂时选择⽤
InMemoryChatMemory
来实现
在 CommonConfiguration 中注册 ChatMemory 对象:
@Bean
public ChatMemory chatMemory() {
return new InMemoryChatMemory();
}
然后添加 MessageChatMemoryAdvisor 到 ChatClient :
@Bean
public ChatClient chatClient(OllamaChatModel model, ChatMemory chatMemory) {
return ChatClient.builder(model) // 创建ChatClient⼯⼚实例
.defaultSystem("您是以为大学生,你" +
"的名字叫⼩⿊。请以友好、乐于助⼈和愉快的⽅式解答学⽣的各种问题。")
.defaultAdvisors(new SimpleLoggerAdvisor()) // 添加默认的Advisor,记录⽇志
.defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory))
.build(); // 构建ChatClient实例
}
OK,现在聊天会话已经有记忆功能了,不过现在的会话记忆还是不完善的,我们还有继续补充。
5.11.会话历史
会话历史与会话记忆是两个不同的事情:
- 会话记忆:是指让⼤模型记住每⼀轮对话的内容,不⾄于前⼀句刚问完,下⼀句就忘了。
- 会话历史:是指要记录总共有多少不同的对话
5.11.1管理会话id(会话历史)
由于会话记忆是以
conversationId
来管理的,也就是会话id(以后简称为chatId)。将来要查询
会话历史,其实就是查询历史中有哪些chatId. 因此,为了实现查询会话历史记录,我们必须记录所有的chatId,我们需要定义⼀个管理会话历史的 标准接口。
public interface ChatHistoryRepository {
/**
* 保存对话记录
* @param type 业务类型
* @param chatId 会话id
*/
void save(String type,String chatId);
/**
* 获取会话id列表
* @param type 业务类型
* @return 会话ID列表
*/
List<String> getChatIdList(String type);
}
然后定义⼀个实现类
InMemoryChatHistoryRepository
:
@Component
public class InMemoryChatRepository implements ChatHistoryRepository{
private final Map<String,List<String>> chatHistory =new HashMap<>();
@Override
public void save(String type, String chatId) {
if(!chatHistory.containsKey(type)){
chatHistory.put(type,new ArrayList<>());
}
List<String> chatIdList= chatHistory.get(type);
if(chatIdList.contains(chatId)){
return;
}
chatIdList.add(chatId);
}
@Override
public List<String> getChatIdList(String type) {
List<String> chatIds = chatHistory.get(type);
return chatIds!=null?chatIds:new ArrayList<>();
}
}
❗ 注意:⽬前我们业务⽐较简单,没有⽤⼾概念,但是将来会有不同业务,因此简单采⽤内存保存type与chatId关系。将来⼤家也可以根据业务需要把会话id持久化保存到Redis、MongoDB、MySQL等数据库。如果业务中有user的概念,还需要记录userId、chatId、time等关联关系
5.11.2保存会话id
接下来,修改ChatController中的chat⽅法,做到3点:
- 添加⼀个请求参数:chatId,每次前端请求AI时都需要传递chatId
- 每次处理请求时,将chatId存储到ChatRepository
- 每次发请求到AI⼤模型时,都传递⾃定义的chatId
import static org.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor.CHAT_MEMO RY_CONVERSATION_ID_KEY;
@CrossOrigin("*")
@RequiredArgsConstructor
@RestController
@RequestMapping("/ai")
public class ChatController {
private final ChatClient chatClient;
private final ChatMemory chatMemory;
private final ChatHistoryRepository chatHistoryRepository;
@RequestMapping(value = "/chat", produces = "text/html;charset=UTF-8")
public Flux<String> chat(@RequestParam(defaultValue = "讲个笑话") String
prompt, String chatId) {
chatHistoryRepository.addChatId(chatId);
return chatClient
.prompt(prompt)
.advisors(a -> a.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId))
.stream()
.content();
}
}
注意,这⾥传递chatId给Advisor的⽅式是通过AdvisorContext,也就是以key-value形式存⼊上下文chatClient.advisors(a -> a.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId))
其中的
CHAT_MEMORY_CONVERSATION_ID_KEY
是AbstractChatMemoryAdvisor中定义的常量 key,将来 MessageChatMemoryAdvisor
执⾏的过程中就可以拿到这个chatId了
。
5.11.3.查询会话历史
接着,我们定义⼀个新的Controller,专⻔实现会话历史的查询。包含两个接⼝:
- 根据业务类型查询会话历史列表(我们将来有3个不同业务,需要分别记录历史。⼤家的业务可能 是按userId记录,根据UserId查询)
- 根据chatId查询指定会话的历史消息
其中,查询会话历史消息,也就是Message集合。但是由于Message并不符合页面的需要,我们需要自己定义⼀个VO
@NoArgsConstructor
@Data
public class MessageVO {
private String role;
private String content;
public MessageVO(Message message) {
switch (message.getMessageType()) {
case USER:
role = "user";
break;
case ASSISTANT:
role = "assistant";
break;
default:
role = "unknown";
break;
}
this.role = role;
this.content = message.getText();
}
}
然后在
新建⼀个
ChatHistoryController
:
@RestController
@RequestMapping("/ai/history")
@RequiredArgsConstructor
public class ChatHistoryController {
public final ChatHistoryRepository chatHistoryRepository;
private final ChatMemory chatMemory;
@GetMapping("{type}")
public List<String> getChatIdList(@PathVariable("type") String type) {
return chatHistoryRepository.getChatIdList(type);
}
@GetMapping("{type}/{chatId}")
public List<MessageVO> getChatHistory(@PathVariable("type") String type, @PathVariable("chatId") String chatId) {
List<Message> messages = chatMemory.get(chatId, Integer.MAX_VALUE);
if(messages.isEmpty()){
return null;
}
return messages.stream().map(MessageVO::new).toList();
}
}

被折叠的 条评论
为什么被折叠?



