⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 blog.csdn.net/weixin_42289362 「二流的吃货」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

概述

在大模型技术迅速普及的当下,如何在本地环境中以低成本部署高性能AI服务,成为了广大开发者关注的焦点。

本文将结合Spring AI、Ollama和DeepSeek-R1模型,通过Docker容器化部署和Redis持久化存储,手把手教你构建一个支持连续对话的AI应用。

这一方案特别适合需要私有化部署、数据安全可控的场景,如企业内部知识库、教育问答系统等。

这是一篇全家桶式教程,主要内容包括:

  • 环境搭建: 从安装Docker开始,到通过Docker安装Redis、Ollama,并部署DeepSeek模型,一步步带你搞定环境配置。
  • Spring AI集成: 详细讲解如何将Ollama和DeepSeek集成到Spring AI中,并实现连续对话功能。
  • 效果验证: 通过实际测试,展示系统的运行效果,让你直观感受这一方案的强大之处。

环境准备

在开始之前,请确保你的环境满足以下要求:

  • 操作系统: Windows 11
  • Java版本: JDK 17+(请注意Spring Boot 3.4.3的兼容性)
  • 依赖管理: Maven 3.8.3+

环境搭建(docker,redis, ollama 和deepseek)

1. 安装docker

本地环境使用Docker进行部署,可以大大节省环境配置的工作量,同时减少组件对系统性能的影响。在不开发时,关闭Docker,还能避免各种干扰。对于还不熟悉Docker的同学,建议尽快学习掌握这一强大工具。

由于很多教程都是基于Linux系统的,这里我们详细讲解一下在Windows本地环境下的搭建步骤。首先,访问docker官网,根据你的系统选择合适的版本进行下载和安装。安装完成后,进入PowerShell,输入指令docker ps,如果能看到相关输出,说明Docker安装成功。

安装后,进入PowerShell,输入指令 docker ps ,看到这个就OK了

2.Redis容器部署

说明:此命令从Docker Hub拉取指定版本的Redis镜像,为后续的容器部署做好准备。

docker pull redis:7.4.2

在本地创建文件夹C:\docker\redis\conf和C:\docker\redis\data,并在conf文件夹下创建文件redis.conf,内容如下:

bind 0.0.0.0
port 6379
requirepass 123123
dir /data
appendonly yes
  • bind 0.0.0.0:允许外部访问
  • requirepass 123123:设置访问密码,你可以根据需要自行设定,虽然是本地环境,但养成良好的安全习惯很重要
  • appendonly yes:开启AOF持久化

接下来,进行容器部署:

docker run -d \
-p 6579:6379 \
-v C:/docker/redis/data:/data \
-v C:/docker/redis/conf:/usr/local/etc/redis \
--name redis \
redis:7.4.2 redis-server /usr/local/etc/redis/redis.conf

说明:通过上述命令,我们基于刚才拉取的镜像创建并启动了一个Redis容器,同时将本地的配置文件和数据目录挂载到容器中,方便进行持久化存储和配置管理。

3. 安装ollama

下载ollama

docker pull ollama/ollama:0.6.2

创建本地文件夹C:\docker\ollama,然后运行以下命令:

docker run -d \
-v C:\docker\ollama:/root/.ollama \
-p 11434:11434 \
--name ollama \
ollama/ollama:0.6.2

功能说明:映射本地模型存储目录/root/.ollama,开放11434端口供API调用。你可以通过Deepseek模型版本查看了解更多相关信息。

4. 安装Deepseep

模型拉取,由于我没有显卡,因此最多也就拉取7b模型,大家也可以根据自己的需求选择合适的模型。

docker exec -it ollama ollama pull deepseek-r1:7b

说明:此命令在已启动的Ollama容器中执行模型拉取操作,将DeepSeek-R1的7b版本模型下载到本地,以便后续进行调用和测试。

Spring AI 集成与代码实现

1. maven的核心依赖

<!-- 全局属性管理 -->
<properties>
<java.version>23</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<!-- 自定义依赖版本 -->
<spring-boot.version>3.4.3</spring-boot.version>
<spring.ai.version>1.0.0-M6</spring.ai.version>
<maven.compiler.version>3.11.0</maven.compiler.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.32</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<!-- <version>1.0.0-SNAPSHOT</version>-->
<version>1.0.0-M6</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<!-- 构建配置 -->
<build>
<plugins>
<!-- 编译器插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven.compiler.version}</version>
<configuration>
<release>${java.version}</release>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.32</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>

<!-- Spring Boot打包插件 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>

<!-- 仓库配置 -->
<repositories>
<repository>
<id>alimaven</id>
<name>aliyun maven</name>
<url>https://maven.aliyun.com/repository/public</url>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</repository>
<repository>
<name>Central Portal Snapshots</name>
<id>central-portal-snapshots</id>
<url>https://central.sonatype.com/repository/maven-snapshots/</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>

说明:上述Maven配置文件中,我们引入了Spring Boot、Spring Data Redis、Spring AI以及Lombok等依赖,为项目提供了Web开发、Redis数据操作、AI模型集成以及代码简化等功能支持。同时,通过插件配置和仓库配置,确保项目的构建和依赖管理能够顺利进行。

2. 核心配置(application.yml)

server:
port:8083
spring:
application:
name:Ollama-AI
data:
redis:
host:127.0.0.1
port:6579
password:123123
database:0
ai:
ollama:
base-url:http://127.0.0.1:11434
chat:
model:deepseek-r1:7b

说明:在application.yml文件中,我们配置了服务器端口、Spring应用名称、Redis连接信息以及AI相关配置。其中,Redis的主机、端口、密码等参数需要与实际部署的Redis容器相匹配,而AI部分则指定了Ollama的基地址和使用的模型版本,确保系统能够正确连接和调用相应的AI服务。

3. 实现连续对话

3.1 控制器层(OllamaChatController.java)

@Slf4j
@RestController
@RequestMapping("/ai/v1")
publicclass OllamaChatController {

privatefinal ChatClient chatClient;
privatefinal ChatMemory chatMemory;

public OllamaChatController(ChatClient.Builder builder, ChatMemory chatMemory) {
this.chatClient = builder
.defaultSystem("只回答问题,不进行解释")
.defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory))
.build();
this.chatMemory = chatMemory;
}

@GetMapping("/ollama/redis/chat")
public String chat(@RequestParam String userId, @RequestParam String input) {
log.info("/ollama/redis/chat input: [{}]", input);

String text = chatClient.prompt()
.user(input)
.advisors(spec -> spec.param(AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY, userId)
.param(AbstractChatMemoryAdvisor.CHAT_MEMORY_RETRIEVE_SIZE_KEY, 100))
.call()
.content();

return text.split("</think>")[1].trim();
}
}

说明:该控制器类定义了一个RESTful接口,用于接收用户的输入并返回AI模型的回复。通过依赖注入获取ChatClient和ChatMemory实例,构建聊天客户端,并在chat方法中处理用户的输入,调用AI模型进行对话,并将结果返回给前端。

3.2 Redis持久化(ChatRedisMemory.java)

@Slf4j
@Component
publicclass ChatRedisMemory implements ChatMemory {

privatestaticfinal String KEY_PREFIX = "chat:history:";
privatefinal RedisTemplate<String, Object> redisTemplate;

public ChatRedisMemory(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}

@Override
public void add(String conversationId, List<Message> messages) {
String key = KEY_PREFIX + conversationId;
List<ChatEntity> listIn = new ArrayList<>();
for(Message msg: messages){
String[] strs = msg.getText().split("</think>");
String text = strs.length==2?strs[1]:strs[0];

ChatEntity ent = new ChatEntity();
ent.setChatId(conversationId);
ent.setType(msg.getMessageType().getValue());
ent.setText(text);
listIn.add(ent);
}
redisTemplate.opsForList().rightPushAll(key,listIn.toArray());
redisTemplate.expire(key, 30, TimeUnit.MINUTES);
}

@Override
public List<Message> get(String conversationId, int lastN) {
String key = KEY_PREFIX + conversationId;
Long size = redisTemplate.opsForList().size(key);
if (size == null || size == 0){
return Collections.emptyList();
}

int start = Math.max(0, (int) (size - lastN));
List<Object> listTmp = redisTemplate.opsForList().range(key, start, -1);
List<Message> listOut = new ArrayList<>();
ObjectMapper objectMapper = new ObjectMapper();
for(Object obj: listTmp){
ChatEntity chat = objectMapper.convertValue(obj, ChatEntity.class);
// log.info("MessageType.USER [{}], chat.getType [{}]",MessageType.USER, chat.getType());
if(MessageType.USER.getValue().equals(chat.getType())){
listOut.add(new UserMessage(chat.getText()));
}elseif(MessageType.ASSISTANT.getValue().equals(chat.getType())){
listOut.add(new AssistantMessage(chat.getText()));
}elseif(MessageType.SYSTEM.getValue().equals(chat.getType())){
listOut.add(new SystemMessage(chat.getText()));
}
}
return listOut;
}

@Override
public void clear(String conversationId) {
redisTemplate.delete(KEY_PREFIX + conversationId);
}
}

说明:此组件实现了ChatMemory接口,利用Redis进行对话历史记录的持久化存储。通过RedisTemplate操作Redis列表,实现了对话记录的添加、获取和清除功能,确保对话上下文能够在多次请求之间保持连贯,从而支持连续对话。

3.3 配置类与序列化(RedisConfig.java)

@Configuration
publicclass RedisConfig {

@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
//生成整个 RedisTemplate
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(factory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}

说明:该配置类用于创建RedisTemplate Bean,通过设置不同的序列化方式,确保在与Redis进行数据交互时,键和值能够正确地进行序列化和反序列化操作,从而保证数据的完整性和可读性。

3.4. 实体类(ChatEntity.java)

@NoArgsConstructor
@AllArgsConstructor
@Data
public class ChatEntity implements Serializable {
String chatId;
String type;
String text;
}

说明:这是一个简单的Java实体类,用于表示对话中的每一条消息记录。包含对话ID、消息类型和消息文本三个属性,通过Lombok的注解自动生成构造方法、getter和setter方法,简化了代码编写。

3.5. 启动类(OllamaChatDemoApplication.java)

@EnableCaching
@SpringBootApplication
public class OllamaChatDemoApplication {

public static void main(String[] args) {
SpringApplication.run(OllamaChatDemoApplication.class, args);
}
}

测试与验证

环境启动

确保docker部署的redis和ollama服务都正常运行,通过 docker ps查看

如果没有的话, 可以 通过docker start redis, docker start ollama来启动

Spring 服务启动后,我们看看效果吧

我们Spring 项目中,开放了一个接口

http://127.0.0.1:8083/ai/v1/ollama/redis/chat

接下来我们问几个问题, 这些问题一环套一环, 看看他回答的怎么样吧.

  • 你是谁
  • 列举3个中国文学家
  • 他们的出生地在哪
  • 这些地方都有什么特产

不过,由于部署的版本较低,回答速度可能较慢,且可能出现中英文混杂的情况。在生产环境中,建议部署高算力的版本以获得更好的性能。

另外,我们看看redis中的存储

补充

本文通过完整的Docker部署、Spring AI集成和Redis持久化方案,详细展示了如何低成本构建本地化AI服务。这一方案不仅支持连续对话等复杂场景,还通过容器化技术实现了环境隔离。

对于开发者而言,掌握这一技术栈将显著提升私有化AI应用的开发效率,为各种需要数据安全和隐私保护的应用场景提供有力支持。

文章目录
  1. 1. 概述
  2. 2. 环境准备
  3. 3. 环境搭建(docker,redis, ollama 和deepseek)
    1. 3.1. 1. 安装docker
    2. 3.2. 2.Redis容器部署
    3. 3.3. 3. 安装ollama
    4. 3.4. 4. 安装Deepseep
  4. 4. Spring AI 集成与代码实现
    1. 4.1. 1. maven的核心依赖
    2. 4.2. 2. 核心配置(application.yml)
    3. 4.3. 3. 实现连续对话
      1. 4.3.1. 3.1 控制器层(OllamaChatController.java)
      2. 4.3.2. 3.2 Redis持久化(ChatRedisMemory.java)
      3. 4.3.3. 3.3 配置类与序列化(RedisConfig.java)
      4. 4.3.4. 3.4. 实体类(ChatEntity.java)
      5. 4.3.5. 3.5. 启动类(OllamaChatDemoApplication.java)
  5. 5. 测试与验证
    1. 5.1. 环境启动
    2. 5.2. Spring 服务启动后,我们看看效果吧
  6. 6. 补充