Spring AI + ollama 本地搭建聊天 AI
Spring AI + ollama 本地搭建聊天 AI
不知道怎么搭建 ollama 的可以查看上一篇Spring AI 初学。
项目可以查看gitee
前期准备
添加依赖
创建 SpringBoot 项目,添加主要相关依赖(spring-boot-starter-web、spring-ai-ollama-spring-boot-starter)
Spring AI supports Spring Boot 3.2.x and 3.3.x
Spring Boot 3.2.11 requires at least Java 17 and is compatible with versions up to and including Java 23
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
<version>1.0.0-M3</version>
</dependency>
配置文件
application.properties、yml配置文件中添加,也可以在项目中指定模型等参数,具体参数可以参考 OllamaChatProperties
# properties,模型 qwen2.5:14b 根据自己下载的模型而定
spring.ai.ollama.chat.options.model=qwen2.5:14b
#yml
spring:
ai:
ollama:
chat:
model: qwen2.5:14b
聊天实现
主要使用 org.springframework.ai.chat.memory.ChatMemory 接口保存对话信息。
一、采用 Java 缓存对话信息
支持功能:聊天对话、切换对话、删除对话
controller
import com.yb.chatai.domain.ChatParam;
import jakarta.annotation.Resource;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.InMemoryChatMemory;
import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.ai.ollama.api.OllamaApi;
import org.springframework.ai.ollama.api.OllamaOptions;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import java.util.UUID;
/*
*@title Controller
*@description 使用内存进行对话
*@author yb
*@version 1.0
*@create 2024/11/12 14:39
*/
@Controller
public class ChatController {
//注入模型,配置文件中的模型,或者可以在方法中指定模型
@Resource
private OllamaChatModel model;
//聊天 client
private ChatClient chatClient;
// 模拟数据库存储会话和消息
private final ChatMemory chatMemory = new InMemoryChatMemory();
//首页
@GetMapping("/index")
public String index(){
return "index";
}
//开始聊天,生成唯一 sessionId
@GetMapping("/start")
public String start(Model model){
//新建聊天模型
// OllamaOptions options = OllamaOptions.builder();
// options.setModel("qwen2.5:14b");
// OllamaChatModel chatModel = new OllamaChatModel(new OllamaApi(), options);
//创建随机会话 ID
String sessionId = UUID.randomUUID().toString();
model.addAttribute("sessionId", sessionId);
//创建聊天client
chatClient = ChatClient.builder(this.model).defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory, sessionId, 10)).build();
return "chatPage";
}
//聊天
@PostMapping("/chat")
@ResponseBody
public String chat(@RequestBody ChatParam param){
//直接返回
return chatClient.prompt(param.getUserMsg()).call().content();
}
//删除聊天
@DeleteMapping("/clear/{id}")
@ResponseBody
public void clear(@PathVariable("id") String sessionId){
chatMemory.clear(sessionId);
}
}
效果图

二、采用数据库保存对话信息
支持功能:聊天对话、切换对话、删除对话、撤回消息
实体类
import lombok.Data;
import java.util.Date;
@Data
public class ChatEntity {
private String id;
/** 会话id */
private String sessionId;
/** 会话内容 */
private String content;
/** AI、人 */
private String type;
/** 创建时间 */
private Date time;
/** 是否删除,Y-是 */
private String beDeleted;
/** AI会话时,获取人对话ID */
private String userChatId;
}
configuration
import com.yb.chatai.domain.ChatEntity;
import com.yb.chatai.service.IChatService;
import jakarta.annotation.Resource;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.MessageType;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/*
*@title DBMemory
*@description 实现 ChatMemory,注入 spring,方便采用 service 方法
*@author yb
*@version 1.0
*@create 2024/11/12 16:15
*/
@Configuration
public class DBMemory implements ChatMemory {
@Resource
private IChatService chatService;
@Override
public void add(String conversationId, List<Message> messages) {
for (Message message : messages) {
chatService.saveMessage(conversationId, message.getContent(), message.getMessageType().getValue());
}
}
@Override
public List<Message> get(String conversationId, int lastN) {
List<ChatEntity> list = chatService.getLastN(conversationId, lastN);
if(list != null && !list.isEmpty()) {
return list.stream().map(l -> {
Message message = null;
if (MessageType.ASSISTANT.getValue().equals(l.getType())) {
message = new AssistantMessage(l.getContent());
} else if (MessageType.USER.getValue().equals(l.getType())) {
message = new UserMessage(l.getContent());
}
return message;
}).collect(Collectors.<Message>toList());
}else {
return new ArrayList<>();
}
}
@Override
public void clear(String conversationId) {
chatService.clear(conversationId);
}
}
services实现类
import com.yb.chatai.domain.ChatEntity;
import com.yb.chatai.service.IChatService;
import org.springframework.ai.chat.messages.MessageType;
import org.springframework.stereotype.Service;
import java.util.*;
/*
*@title ChatServiceImpl
*@description 保存用户会话 service 实现类
*@author yb
*@version 1.0
*@create 2024/11/12 15:50
*/
@Service
public class ChatServiceImpl implements IChatService {
Map<String, List<ChatEntity>> map = new HashMap<>();
@Override
public void saveMessage(String sessionId, String content, String type) {
ChatEntity entity = new ChatEntity();
entity.setId(UUID.randomUUID().toString());
entity.setContent(content);
entity.setSessionId(sessionId);
entity.setType(type);
entity.setTime(new Date());
//改成常量
entity.setBeDeleted("N");
if(MessageType.ASSISTANT.getValue().equals(type)){
entity.setUserChatId(getLastN(sessionId, 1).get(0).getId());
}
//todo 保存数据库
//模拟保存到数据库
List<ChatEntity> list = map.getOrDefault(sessionId, new ArrayList<>());
list.add(entity);
map.put(sessionId, list);
}
@Override
public List<ChatEntity> getLastN(String sessionId, Integer lastN) {
//todo 从数据库获取
//模拟从数据库获取
List<ChatEntity> list = map.get(sessionId);
return list != null ? list.stream().skip(Math.max(0, list.size() - lastN)).toList() : List.of();
}
@Override
public void clear(String sessionId) {
//todo 数据库更新 beDeleted 字段
map.put(sessionId, new ArrayList<>());
}
@Override
public void deleteById(String id) {
//todo 数据库直接将该 id 数据 beDeleted 改成 Y
for (Map.Entry<String, List<ChatEntity>> next : map.entrySet()) {
List<ChatEntity> list = next.getValue();
list.removeIf(chat -> id.equals(chat.getId()) || id.equals(chat.getUserChatId()));
}
}
}
controller
import com.yb.chatai.configuration.DBMemory;
import com.yb.chatai.domain.ChatEntity;
import com.yb.chatai.domain.ChatParam;
import com.yb.chatai.service.IChatService;
import jakarta.annotation.Resource;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.ai.ollama.api.OllamaApi;
import org.springframework.ai.ollama.api.OllamaOptions;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.UUID;
/*
*@title ChatController2
*@description 使用数据库(缓存)进行对话
*@author yb
*@version 1.0
*@create 2024/11/12 16:12
*/
@Controller
public class ChatController2 {
//注入模型,配置文件中的模型,或者可以在方法中指定模型
@Resource
private OllamaChatModel model;
//聊天 client
private ChatClient chatClient;
//操作聊天信息service
@Resource
private IChatService chatService;
//会话存储方式
@Resource
private DBMemory dbMemory;
//开始聊天,生成唯一 sessionId
@GetMapping("/start2")
public String start(Model model){
//新建聊天模型
// OllamaOptions options = OllamaOptions.builder();
// options.setModel("qwen2.5:14b");
// OllamaChatModel chatModel = new OllamaChatModel(new OllamaApi(), options);
//创建随机会话 ID
String sessionId = UUID.randomUUID().toString();
model.addAttribute("sessionId", sessionId);
//创建聊天 client
chatClient = ChatClient.builder(this.model).defaultAdvisors(new MessageChatMemoryAdvisor(dbMemory, sessionId, 10)).build();
return "chatPage2";
}
//切换会话,需要传入 sessionId
@GetMapping("/exchange2/{id}")
public String exchange(@PathVariable("id")String sessionId){
//切换聊天 client
chatClient = ChatClient.builder(this.model).defaultAdvisors(new MessageChatMemoryAdvisor(dbMemory, sessionId, 10)).build();
return "chatPage2";
}
//聊天
@PostMapping("/chat2")
@ResponseBody
public List<ChatEntity> chat(@RequestBody ChatParam param){
//todo 判断 AI 是否返回会话,从而判断用户是否可以输入
chatClient.prompt(param.getUserMsg()).call().content();
//获取返回最新两条,一条用户问题(用户获取用户发送ID),一条 AI 返回结果
return chatService.getLastN(param.getSessionId(), 2);
}
//撤回消息
@DeleteMapping("/revoke2/{id}")
@ResponseBody
public void revoke(@PathVariable("id") String id){
chatService.deleteById(id);
}
//清空消息
@DeleteMapping("/del2/{id}")
@ResponseBody
public void clear(@PathVariable("id") String sessionId){
dbMemory.clear(sessionId);
}
}
效果图

总结
主要实现 org.springframework.ai.chat.memory.ChatMemory 方法,实际项目过程需要实现该接口重写方法。
Spring AI + ollama 本地搭建聊天 AI的更多相关文章
- 实战!轻松搭建图像分类 AI 服务
人工智能技术(以下称 AI)是人类优秀的发现和创造之一,它代表着至少几十年的未来.在传统的编程中,工程师将自己的想法和业务变成代码,计算机会根据代码设定的逻辑运行.与之不同的是,AI 使计算机有了「属 ...
- 云上快速搭建Serverless AI实验室
Serverless Kubernetes和ACK虚拟节点都已基于ECI提供GPU容器实例功能,让用户在云上低成本快速搭建serverless AI实验室,用户无需维护服务器和GPU基础运行环境,极大 ...
- Spring MVC + jpa框架搭建,及全面分析
一,hibernate与jpa的关系 首先明确一点jpa是什么?以前我就搞不清楚jpa和hibernate的关系. 1,JPA(Java Persistence API)是Sun官方提出的Java持久 ...
- 【译文】用Spring Cloud和Docker搭建微服务平台
by Kenny Bastani Sunday, July 12, 2015 转自:http://www.kennybastani.com/2015/07/spring-cloud-docker-mi ...
- 手把手教你使用spring cloud+dotnet core搭建微服务架构:服务治理(-)
背景 公司去年开始使用dotnet core开发项目.公司的总体架构采用的是微服务,那时候由于对微服务的理解并不是太深,加上各种组件的不成熟,只是把项目的各个功能通过业务层面拆分,然后通过nginx代 ...
- spring cloud+dotnet core搭建微服务架构:配置中心(四)
前言 我们项目中有很多需要配置的地方,最常见的就是各种服务URL地址,这些地址针对不同的运行环境还不一样,不管和打包还是部署都麻烦,需要非常的小心.一般配置都是存储到配置文件里面,不管多小的配置变动, ...
- Spring Cloud 入门教程 - 搭建配置中心服务
简介 Spring Cloud 提供了一个部署微服务的平台,包括了微服务中常见的组件:配置中心服务, API网关,断路器,服务注册与发现,分布式追溯,OAuth2,消费者驱动合约等.我们不必先知道每个 ...
- spring cloud+.net core搭建微服务架构:服务注册(一)
背景 公司去年开始使用dotnet core开发项目.公司的总体架构采用的是微服务,那时候由于对微服务的理解并不是太深,加上各种组件的不成熟,只是把项目的各个功能通过业务层面拆分,然后通过nginx代 ...
- spring cloud+.net core搭建微服务架构:配置中心(四)
前言 我们项目中有很多需要配置的地方,最常见的就是各种服务URL地址,这些地址针对不同的运行环境还不一样,不管和打包还是部署都麻烦,需要非常的小心.一般配置都是存储到配置文件里面,不管多小的配置变动, ...
- Spring Cloud 5分钟搭建教程(附上一个分布式日志系统项目作为参考) - 推荐
http://blog.csdn.net/lc0817/article/details/53266212/ https://github.com/leoChaoGlut/log-sys 上面是我基于S ...
随机推荐
- 零基础学习人工智能—Python—Pytorch学习(八)
前言 本文介绍卷积神经网络的上半部分. 其实,学习还是需要老师的,因为我自己写文章的时候,就会想当然,比如下面的滑动窗口,我就会想当然的认为所有人都能理解,而实际上,我们在学习的过程中之所以卡顿的点多 ...
- 中考游记 & 暑假集训大记
中考游记 & 暑假集训大记 前言 如今已经回归 \(OI\) ,望着如烟的往事,或是将将知道的讯息,心中早是凄然. 我真的希望这世间有我所期望的浦岛隧道,带回所有的遗憾,同时带走迷茫与害怕,重 ...
- C 语言多文件编译
C 语言中的多文件编程通常涉及将代码分散在几个不同的源文件(.c 文件)和头文件(.h 文件)中.这么做可以帮助你组织大型项目,提高代码的重用性,便于团队合作,分离接口和实现,以及加快编译时间.下面是 ...
- spm 一阶分析的Microtime onset应该如何填写?
1. 如果对数据进行了slice timing, 那么在进行一阶分析时应该修改microtime onset和 microtime resolution这两个参数, 假设数据的slice order= ...
- C++: 16个基础的C++代码性能优化实例
前言 近期推动项目屎山代码进行了一波性能优化,实现了较大的性能提升.这里记录了部分近期代码优化的小技巧,这些例子仅从C++语言层面进行优化,主要在于优化类设计.减少隐含函数调用.减少拷贝等,较为基础实 ...
- Node.js开发博客项目笔记-搭建环境(2)
搭建环境 首先新建blog-1文件夹,在文件夹下初始化package.json,执行命令: npm init -y 生成的package.json文件中的main属性默认值index.js改成bin/ ...
- webpack笔记-webpack基础用法(二)
webpack 本质上是一个打包工具,它会根据代码的内容解析模块依赖,帮助我们把多个模块的代码打包. 一切文件:JavaScript.CSS.SCSS.图片.模板,在 Webpack 眼中都是一个个模 ...
- DOM & BOM – 冷知识 (新手)
JS 无法 query select 到伪元素 参考: 使用JS控制伪元素的几种方法 JS style remove property 是 kebab-case set property 是 came ...
- Angular 学习笔记 (Angular 12 get started)
Angular 12 视乎比以往更稳定了. 这里记入一般的 get started 结构和做法. 第 1 步, 创建项目. ng new project --create-application=fa ...
- nRF24L01芯片驱动记录
nRF24L01芯片驱动记录 学习完了usb,了解了部分元器件的功能以及用途后,打算在端午假期用一天的时间完成一个小目标,不过实际上是花了一天半才成功实现,现将驱动nRF24L01芯片的整个过程记 ...