Spring?AI?+?ollama?本地搭建聊天?AI?功能
Spring AI + ollama 本地搭建聊天 AI
不知道怎么搭建 ollama 的可以查看上一篇Spring AI 初學(xué)。
項(xiàng)目可以查看gitee
前期準(zhǔn)備
添加依賴
創(chuàng)建 SpringBoot 項(xiàng)目,添加主要相關(guān)依賴(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配置文件中添加,也可以在項(xiàng)目中指定模型等參數(shù),具體參數(shù)可以參考 OllamaChatProperties
# properties,模型 qwen2.5:14b 根據(jù)自己下載的模型而定
spring.ai.ollama.chat.options.model=qwen2.5:14b
#yml
spring:
ai:
ollama:
chat:
model: qwen2.5:14b聊天實(shí)現(xiàn)
主要使用 org.springframework.ai.chat.memory.ChatMemory 接口保存對(duì)話信息。
一、采用 Java 緩存對(duì)話信息
支持功能:聊天對(duì)話、切換對(duì)話、刪除對(duì)話
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 使用內(nèi)存進(jìn)行對(duì)話
*@author yb
*@version 1.0
*@create 2024/11/12 14:39
*/
@Controller
public class ChatController {
//注入模型,配置文件中的模型,或者可以在方法中指定模型
@Resource
private OllamaChatModel model;
//聊天 client
private ChatClient chatClient;
// 模擬數(shù)據(jù)庫(kù)存儲(chǔ)會(huì)話和消息
private final ChatMemory chatMemory = new InMemoryChatMemory();
//首頁(yè)
@GetMapping("/index")
public String index(){
return "index";
}
//開(kāi)始聊天,生成唯一 sessionId
@GetMapping("/start")
public String start(Model model){
//新建聊天模型
// OllamaOptions options = OllamaOptions.builder();
// options.setModel("qwen2.5:14b");
// OllamaChatModel chatModel = new OllamaChatModel(new OllamaApi(), options);
//創(chuàng)建隨機(jī)會(huì)話 ID
String sessionId = UUID.randomUUID().toString();
model.addAttribute("sessionId", sessionId);
//創(chuàng)建聊天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);
}
}效果圖

二、采用數(shù)據(jù)庫(kù)保存對(duì)話信息
支持功能:聊天對(duì)話、切換對(duì)話、刪除對(duì)話、撤回消息
實(shí)體類
import lombok.Data;
import java.util.Date;
@Data
public class ChatEntity {
private String id;
/** 會(huì)話id */
private String sessionId;
/** 會(huì)話內(nèi)容 */
private String content;
/** AI、人 */
private String type;
/** 創(chuàng)建時(shí)間 */
private Date time;
/** 是否刪除,Y-是 */
private String beDeleted;
/** AI會(huì)話時(shí),獲取人對(duì)話ID */
private String userChatId;
}confiuration
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 實(shí)現(xiàn) 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實(shí)現(xiàn)類
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 保存用戶會(huì)話 service 實(shí)現(xiàn)類
*@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 保存數(shù)據(jù)庫(kù)
//模擬保存到數(shù)據(jù)庫(kù)
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 從數(shù)據(jù)庫(kù)獲取
//模擬從數(shù)據(jù)庫(kù)獲取
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 數(shù)據(jù)庫(kù)更新 beDeleted 字段
map.put(sessionId, new ArrayList<>());
}
@Override
public void deleteById(String id) {
//todo 數(shù)據(jù)庫(kù)直接將該 id 數(shù)據(jù) 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 使用數(shù)據(jù)庫(kù)(緩存)進(jìn)行對(duì)話
*@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;
//會(huì)話存儲(chǔ)方式
@Resource
private DBMemory dbMemory;
//開(kāi)始聊天,生成唯一 sessionId
@GetMapping("/start2")
public String start(Model model){
//新建聊天模型
// OllamaOptions options = OllamaOptions.builder();
// options.setModel("qwen2.5:14b");
// OllamaChatModel chatModel = new OllamaChatModel(new OllamaApi(), options);
//創(chuàng)建隨機(jī)會(huì)話 ID
String sessionId = UUID.randomUUID().toString();
model.addAttribute("sessionId", sessionId);
//創(chuàng)建聊天 client
chatClient = ChatClient.builder(this.model).defaultAdvisors(new MessageChatMemoryAdvisor(dbMemory, sessionId, 10)).build();
return "chatPage2";
}
//切換會(huì)話,需要傳入 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 是否返回會(huì)話,從而判斷用戶是否可以輸入
chatClient.prompt(param.getUserMsg()).call().content();
//獲取返回最新兩條,一條用戶問(wèn)題(用戶獲取用戶發(fā)送ID),一條 AI 返回結(jié)果
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);
}
}效果圖

總結(jié)
主要實(shí)現(xiàn) org.springframework.ai.chat.memory.ChatMemory 方法,實(shí)際項(xiàng)目過(guò)程需要實(shí)現(xiàn)該接口重寫方法。
到此這篇關(guān)于Spring AI + ollama 本地搭建聊天 AI 的文章就介紹到這了,更多相關(guān)Spring AI ollama聊天 AI 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot yaml語(yǔ)法與數(shù)據(jù)讀取操作詳解
YAML 是 “YAML Ain’t Markup Language”(YAML 不是一種標(biāo)記語(yǔ)言)的遞歸縮寫。在開(kāi)發(fā)的這種語(yǔ)言時(shí),YAML 的意思其實(shí)是:“Yet Another Markup Language”(仍是一種標(biāo)記語(yǔ)言),本文給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-07-07
使用遞歸刪除樹(shù)形結(jié)構(gòu)的所有子節(jié)點(diǎn)(java和mysql實(shí)現(xiàn))
下面小編就為大家?guī)?lái)一篇使用遞歸刪除樹(shù)形結(jié)構(gòu)的所有子節(jié)點(diǎn)(java和mysql實(shí)現(xiàn))。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-10-10
Spring Boot中整合Spring Security并自定義驗(yàn)證代碼實(shí)例
本篇文章主要介紹了Spring Boot中整合Spring Security并自定義驗(yàn)證代碼實(shí)例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04
SpringBoot基于redis自定義注解實(shí)現(xiàn)后端接口防重復(fù)提交校驗(yàn)
本文主要介紹了SpringBoot基于redis自定義注解實(shí)現(xiàn)后端接口防重復(fù)提交校驗(yàn),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01
JavaWeb監(jiān)聽(tīng)器Listener實(shí)例解析
這篇文章主要為大家詳細(xì)介紹了JavaWeb監(jiān)聽(tīng)器Listener實(shí)例,針對(duì)監(jiān)聽(tīng)器進(jìn)行進(jìn)行細(xì)致分析,感興趣的小伙伴們可以參考一下2016-08-08
springboot接口多實(shí)現(xiàn)類選擇性注入解決方案
這篇文章主要為大家介紹了springboot接口多實(shí)現(xiàn)類選擇性注入解決方案的四種方式,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2022-03-03

