Spring AI整合Ollama避坑指南:从pom依赖到MySQL对话记忆的全流程解析
·
Spring AI与Ollama深度整合实战:企业级应用开发全流程解析
在企业级AI应用开发中,如何高效整合Spring AI与Ollama框架,同时规避常见陷阱,是许多开发者面临的挑战。本文将系统性地介绍从基础配置到高级功能的完整实现路径,特别针对生产环境中可能遇到的技术难点提供解决方案。
1. 环境准备与基础配置
1.1 依赖管理关键点
在pom.xml中配置依赖时,需要特别注意版本兼容性问题。以下是一个经过生产验证的依赖配置方案:
<dependencyManagement>
<dependencies>
<!-- Spring AI BOM -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- 核心依赖 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
</dependency>
<!-- 数据库集成 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 开发辅助 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
关键提示:避免混合使用不同来源的AI starter依赖,特别是同时引入Spring AI和第三方厂商的starter时,极易引发自动配置冲突。
1.2 配置优化实践
application.yml配置需要根据实际部署环境进行调整,以下是经过优化的配置模板:
spring:
ai:
ollama:
base-url: ${OLLAMA_SERVICE_URL:http://localhost:11434}
chat:
options:
model: deepseek-r1:7b
temperature: 0.7
top-p: 0.9
num-predict: 512
client:
connect-timeout: 30s
read-timeout: 300s
datasource:
url: jdbc:mysql://${DB_HOST:localhost}:3306/ai_chat?useSSL=false
username: ${DB_USER:root}
password: ${DB_PASSWORD:}
hikari:
maximum-pool-size: 10
connection-timeout: 30000
配置项说明表:
| 配置路径 | 推荐值 | 作用说明 |
|---|---|---|
| spring.ai.ollama.client.connect-timeout | 30s | 建立连接超时时间 |
| spring.ai.ollama.client.read-timeout | 300s | 读取响应超时时间 |
| spring.ai.ollama.chat.options.temperature | 0.5-0.8 | 控制输出随机性 |
| spring.ai.ollama.chat.options.top-p | 0.7-0.9 | 核采样阈值 |
| spring.datasource.hikari.maximum-pool-size | 10-20 | 数据库连接池大小 |
2. 核心功能实现与性能优化
2.1 流式对话接口设计
实现高效的流式对话接口需要考虑以下几个技术要点:
@GetMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> streamChat(
@RequestParam String message,
@RequestParam(required = false) String sessionId) {
// 1. 构建消息链
List<Message> messages = new ArrayList<>();
if (StringUtils.hasText(sessionId)) {
messages.addAll(chatHistoryService.loadHistory(sessionId));
}
messages.add(new UserMessage(message));
// 2. 创建Prompt对象
Prompt prompt = new Prompt(messages,
OllamaOptions.builder()
.temperature(0.7)
.topK(40)
.build());
// 3. 流式响应处理
return chatModel.stream(prompt)
.map(response -> {
String content = response.getResult().getOutput().getContent();
// 过滤掉思考过程标记
return content.replaceAll("<\\?think\\?>.+?<\\?/think\\?>", "");
})
.onErrorResume(e -> {
log.error("流式对话异常", e);
return Flux.just("系统处理您的请求时出现异常");
});
}
性能优化技巧:
- 使用
Reactor的bufferTimeout组合操作减少网络IO次数 - 对长文本响应实现自动分块处理
- 添加熔断机制防止服务雪崩
2.2 对话记忆持久化方案
2.2.1 数据库表设计
CREATE TABLE `chat_session` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`session_id` VARCHAR(64) NOT NULL,
`user_id` VARCHAR(64) DEFAULT NULL,
`title` VARCHAR(255) DEFAULT NULL,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_session` (`session_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `chat_message` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`session_id` VARCHAR(64) NOT NULL,
`role` ENUM('USER','ASSISTANT','SYSTEM') NOT NULL,
`content` TEXT NOT NULL,
`token_count` INT DEFAULT NULL,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_session_history` (`session_id`,`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2.2.2 JPA实现示例
@Entity
@Table(name = "chat_message")
public class ChatMessage {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "session_id", nullable = false)
private String sessionId;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private MessageRole role;
@Column(nullable = false, columnDefinition = "TEXT")
private String content;
@Column(name = "token_count")
private Integer tokenCount;
@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt = LocalDateTime.now();
}
public interface ChatMessageRepository extends JpaRepository<ChatMessage, Long> {
List<ChatMessage> findBySessionIdOrderByCreatedAtAsc(String sessionId);
@Query("SELECT COUNT(cm) FROM ChatMessage cm WHERE cm.sessionId = :sessionId")
int countBySessionId(@Param("sessionId") String sessionId);
@Modifying
@Query("DELETE FROM ChatMessage cm WHERE cm.sessionId = :sessionId")
void deleteBySessionId(@Param("sessionId") String sessionId);
}
重要提示:在实现自定义记忆存储时,务必在application.yml中禁用Spring AI的默认内存记忆功能:
spring: ai: model: chat: memory: enabled: false
3. 高级功能实现
3.1 多模态处理
实现图片和文本的联合处理需要特殊的数据结构和处理流程:
@PostMapping(value = "/multimodal", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public Flux<String> handleMultimodal(
@RequestPart String question,
@RequestPart(required = false) MultipartFile image) {
List<Message> messages = new ArrayList<>();
// 文本消息处理
messages.add(new SystemMessage("你是一个有帮助的助手,能够理解图片内容"));
messages.add(new UserMessage(question));
// 图片消息处理
if (image != null && !image.isEmpty()) {
try {
byte[] imageBytes = image.getBytes();
var imageResource = new ByteArrayResource(imageBytes);
var media = new Media(
MediaType.parseMediaType(image.getContentType()),
imageResource);
messages.add(new UserMessage("", List.of(media)));
} catch (IOException e) {
log.warn("图片处理失败", e);
}
}
return chatModel.stream(new Prompt(messages))
.map(response -> response.getResult().getOutput().getContent());
}
支持的多模态格式:
- PNG/JPG图片(base64编码)
- PDF文档(需要额外解析)
- 音频文件(需模型支持)
3.2 结构化输出控制
通过JSON Schema约束模型输出格式:
public String getStructuredResponse(String query) {
String schema = """
{
"type": "object",
"properties": {
"answer": {"type": "string"},
"confidence": {"type": "number"},
"sources": {
"type": "array",
"items": {"type": "string"}
}
},
"required": ["answer"]
}
""";
OllamaOptions options = OllamaOptions.builder()
.format(parseJsonSchema(schema))
.build();
Prompt prompt = new Prompt(query, options);
ChatResponse response = chatModel.call(prompt);
return response.getResult().getOutput().getContent();
}
private Map<String, Object> parseJsonSchema(String schema) {
try {
return new ObjectMapper().readValue(schema, Map.class);
} catch (JsonProcessingException e) {
throw new RuntimeException("Schema解析失败", e);
}
}
4. 生产环境最佳实践
4.1 性能监控与调优
关键指标监控项:
-
Ollama服务健康状态
- 模型加载时间
- 显存利用率
- 请求队列长度
-
Spring应用指标
- 接口响应时间P99
- 数据库连接池使用率
- JVM内存压力
推荐监控配置:
management:
endpoints:
web:
exposure:
include: health,metrics,prometheus
metrics:
export:
prometheus:
enabled: true
tags:
application: ${spring.application.name}
4.2 安全加固措施
必须实施的安全策略:
-
接口认证
@Configuration @EnableWebSecurity public class SecurityConfig { @Bean SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception { http .securityMatcher("/api/**") .authorizeHttpRequests(auth -> auth.anyRequest().authenticated()) .addFilterBefore(new ApiKeyFilter(), UsernamePasswordAuthenticationFilter.class) .csrf().disable(); return http.build(); } } -
输入验证
@GetMapping("/chat/safe") public Flux<String> safeChat(@RequestParam @Size(max = 500) String message) { // 处理逻辑 } -
日志脱敏
@Aspect @Component public class LoggingAspect { @Around("execution(* com..chat..*(..))") public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { // 实现敏感信息过滤 } }
4.3 容灾与降级方案
推荐架构设计:
[客户端] -> [API网关] -> [Spring AI服务] -> [Ollama集群]
↘_________[Fallback缓存] ↗
降级策略实现:
@Service
public class ChatService {
@Autowired
private OllamaChatModel primaryModel;
@Autowired(required = false)
private OllamaChatModel secondaryModel;
@Autowired
private CacheService cacheService;
public Flux<String> chatWithFallback(String message) {
return primaryModel.stream(new Prompt(message))
.onErrorResume(e -> {
log.warn("主服务异常,尝试备用服务", e);
return secondaryModel != null ?
secondaryModel.stream(new Prompt(message)) :
Flux.just(cacheService.getCachedResponse(message));
});
}
}
在实际企业级应用中,Spring AI与Ollama的深度整合需要考虑的远不止基础功能实现。从我的项目经验来看,最难调试的问题往往出现在环境差异和版本兼容性上。建议在预发环境充分测试各种边界条件,特别是内存管理和会话持久化部分,这些模块最容易在流量突增时暴露问题。
更多推荐
所有评论(0)