文章目录
1 Ollama
1.1 简介
Ollama
是一个开源的大型语言模型(LLM
)平台,旨在让用户能够轻松地在本地运行、管理和与大型语言模型进行交互。
Ollama
的特点:
- 本地运行:支持在本地环境中部署和运行大型语言模型,确保数据隐私和安全。
- 简化部署:通过简单的安装和配置,即可快速启动模型,降低了技术门槛。
- 多模型支持:兼容多种预训练模型,满足不同的应用需求。
- 硬件友好:支持纯 CPU 推理,适用于各种硬件环境。
1.2 环境搭建
安装 Docker 点击了解 Docker之安装和常用命令操作
1.2.1 安装 Ollama
docker pull ollama/ollama:0.6.2
在本地创建用于存储 Ollama 数据的目录:
docker run -d \
-v C:\docker\ollama:/root/.ollama \
-p 11434:11434 \
--name ollama \
ollama/ollama:0.6.2
此命令将 Ollama 的数据目录挂载到主机,并映射 11434 端口供 API 调用。
1.2.2 下载 DeepSeek 模型
DeepSeek-R1
是一个高性能的开源语言模型。以下是在 Ollama 中下载并使用 DeepSeek-R1 模型的步骤:
进入正在运行的 Ollama 容器:docker exec -it ollama bash
在容器内执行以下命令拉取 DeepSeek-R1 模型(7B 参数量版本):
ollama pull deepseek-r1:7b
1.3 Spring AI 集成与代码实现
1.3.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>
1.3.2 配置文件
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
1.3.3 业务代码
1.3.3.1 控制器层
@Slf4j
@RestController
@RequestMapping("/ai/v1")
public class OllamaChatController {
private final ChatClient chatClient;
private final 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();
}
}
1.3.3.2 Redis持久化
@Slf4j
@Component
public class ChatRedisMemory implements ChatMemory {
private static final String KEY_PREFIX = "chat:history:";
private final 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()));
}else if(MessageType.ASSISTANT.getValue().equals(chat.getType())){
listOut.add(new AssistantMessage(chat.getText()));
}else if(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);
}
}
1.3.3.3 配置类与序列化
@Configuration
public class 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;
}
}
1.3.3.4 实体类
@NoArgsConstructor
@AllArgsConstructor
@Data
public class ChatEntity implements Serializable {
String chatId;
String type;
String text;
}
参考连接:https://blog.csdn.net/weixin_42289362/article/details/146422467?spm=1001.2014.3001.5501