JOURNAL / 随笔

ShotAI-实现大模型对接

ShotAI-实现大模型对接 封面
本文目录
  1. ShotAI 大模型对接实现详解
    1. 📚 概述
    2. 🎯 技术选型
      1. 为什么选择 Spring AI?
      2. 模型选择
    3. 🔧 依赖配置
      1. Maven 依赖
      2. 配置文件
    4. 💻 核心实现
      1. 1. ChatService 接口定义
      2. 2. ChatServiceImpl 实现类
      3. 3. 停止生成功能
    5. 🌐 Controller 层实现
      1. ChatController
    6. 🔄 请求流程图
    7. 🎨 前端对接
      1. API 调用
      2. 使用示例
    8. 🔍 高级特性
      1. 1. 自定义提示词模板
      2. 2. 调整模型参数
      3. 3. 添加对话历史
    9. ⚠️ 注意事项
      1. 1. API Key 安全
      2. 2. 错误处理
      3. 3. 并发控制
    10. 📊 性能优化
      1. 1. 连接池配置
      2. 2. 缓存策略
      3. 3. 异步处理
    11. 🎯 总结
    12. 📚 参考资料

ShotAI 大模型对接实现详解

📚 概述

本文详细介绍 ShotAI 项目中如何对接大语言模型,包括 DeepSeek 对话模型和 Ollama 嵌入模型的集成方案。通过 Spring AI 框架,我们实现了优雅、简洁的 AI 集成方式。


🎯 技术选型

为什么选择 Spring AI?

Spring AI 是 Spring 官方推出的 AI 集成框架,具有以下优势:

  • 统一抽象:提供统一的 API 接口,轻松切换不同 AI 模型
  • 开箱即用:自动配置,无需复杂的初始化代码
  • 流式支持:原生支持流式响应,实现打字机效果
  • Spring 生态:完美融入 Spring Boot 生态系统

模型选择

模型 用途 特点
DeepSeek 对话生成 中文友好、响应快速、成本低
Ollama (bge-m3) 文本嵌入 本地部署、隐私安全、免费

🔧 依赖配置

Maven 依赖

pom.xml 中添加 Spring AI 相关依赖:

<properties>
<java.version>17</java.version>
<spring-ai.version>1.0.3</spring-ai.version>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.0.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<!-- DeepSeek 对话模型 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-deepseek</artifactId>
</dependency>

<!-- Ollama 嵌入模型 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-ollama</artifactId>
</dependency>
</dependencies>

配置文件

application.yml 中配置模型参数:

spring:
ai:
# DeepSeek 配置
deepseek:
api-key: sk-your-api-key-here
base-url: https://api.deepseek.com
chat:
options:
model: deepseek-chat

# Ollama 配置
ollama:
base-url: http://localhost:11434
embedding:
options:
model: bge-m3

💻 核心实现

1. ChatService 接口定义

public interface ChatService {
/**
* 创建 SSE 连接
*/
SseEmitter createSseConnection(String userId);

/**
* 异步发送消息
*/
void sendMessageAsync(String userId, String message, String mode);

/**
* 停止生成
*/
void stopGeneration(String userId);
}

2. ChatServiceImpl 实现类

2.1 依赖注入

@Slf4j
@Service
public class ChatServiceImpl implements ChatService {

// 注入 DeepSeek 对话模型
@Autowired
@Qualifier("deepSeekChatModel")
private ChatModel chatModel;

@Autowired
private RagService ragService;

private ChatClient chatClient;

// 存储用户的流订阅,用于取消
private final Map<String, Disposable> userSubscriptions = new ConcurrentHashMap<>();
}

关键点:

  • 使用 @Qualifier 指定注入的模型(Spring AI 自动配置)
  • ChatClient 是 Spring AI 提供的高级客户端
  • userSubscriptions 用于管理流式响应的生命周期

2.2 初始化 ChatClient

@Autowired
public void initChatClient() {
this.chatClient = ChatClient.builder(chatModel).build();
}

2.3 处理普通对话

private void processChat(String userId, String message) {
try {
log.info("【普通对话】用户: {}, 消息: {}", userId, message);

// 1. 创建提示词
Prompt prompt = new Prompt(message);

// 2. 获取流式响应
Flux<String> stream = chatClient.prompt(prompt).stream().content();

// 3. 订阅流并处理
Disposable subscription = stream
.doOnError(throwable -> {
log.error("【对话错误】用户: {}, 错误: {}", userId, throwable.getMessage());
SSEServer.sendMsg(userId, "抱歉,服务出现了一点问题", SSEMsgType.FINISH);
userSubscriptions.remove(userId);
})
.subscribe(
// onNext: 接收每个文本片段
content -> {
if (SSEServer.exists(userId)) {
SSEServer.sendMsg(userId, content, SSEMsgType.ADD);
}
},
// onError: 处理错误
error -> {
log.error("【对话流失败】用户: {}, 错误: {}", userId, error.getMessage());
userSubscriptions.remove(userId);
},
// onComplete: 完成回调
() -> {
log.info("【对话完成】用户: {}", userId);
SSEServer.sendMsg(userId, "done", SSEMsgType.FINISH);
SSEServer.close(userId);
userSubscriptions.remove(userId);
}
);

// 4. 保存订阅引用,用于后续取消
userSubscriptions.put(userId, subscription);

} catch (Exception e) {
log.error("【对话异常】用户: {}, 错误: {}", userId, e.getMessage());
SSEServer.sendMsg(userId, "系统错误,请稍后重试", SSEMsgType.ERROR);
}
}

核心流程:

用户消息 → Prompt → ChatClient → Flux<String> → 订阅处理 → SSE 推送

技术亮点:

  1. 响应式编程:使用 Reactor 的 Flux 处理流式数据
  2. 错误处理doOnError 捕获异常,确保用户体验
  3. 资源管理:保存 Disposable 引用,支持取消操作
  4. 状态检查SSEServer.exists() 避免向已断开的连接发送数据

3. 停止生成功能

@Override
public void stopGeneration(String userId) {
log.info("【停止生成】用户: {}", userId);

// 取消当前的订阅
Disposable subscription = userSubscriptions.get(userId);
if (subscription != null && !subscription.isDisposed()) {
subscription.dispose();
userSubscriptions.remove(userId);
}

// 发送停止事件
SSEServer.sendMsg(userId, "stopped", SSEMsgType.FINISH);
}

实现原理:

  • 调用 dispose() 取消 Reactor 流的订阅
  • 立即停止 AI 生成,节省 API 调用成本

🌐 Controller 层实现

ChatController

@Slf4j
@RestController
@RequestMapping("/api/chat")
public class ChatController {

@Autowired
private ChatService chatService;

/**
* 发送聊天消息
*/
@PostMapping("/send")
public ChatResponse sendMessage(@RequestBody ChatRequest request) {
log.info("收到聊天请求,用户: {}, 消息: {}, 模式: {}",
request.getCurrentUserName(), request.getMessage(), request.getMode());

try {
// 异步处理聊天,通过 SSE 推送结果
chatService.sendMessageAsync(
request.getCurrentUserName(),
request.getMessage(),
request.getMode()
);
return ChatResponse.success();
} catch (Exception e) {
log.error("处理聊天请求失败", e);
return ChatResponse.error("处理请求失败: " + e.getMessage());
}
}
}

设计思路:

  • Controller 只负责接收请求和返回响应
  • 真正的处理逻辑在 Service 层异步执行
  • 立即返回成功响应,避免前端等待

🔄 请求流程图

┌─────────┐
│ 前端发送 │
│ 消息 │
└────┬────┘


┌─────────────────┐
│ ChatController │
│ /api/chat/send │
└────┬────────────┘


┌─────────────────┐
│ ChatServiceImpl │
│ @Async 异步处理 │
└────┬────────────┘


┌─────────────────┐
│ ChatClient │
│ 构建 Prompt │
└────┬────────────┘


┌─────────────────┐
│ DeepSeek API │
│ 流式返回 │
└────┬────────────┘


┌─────────────────┐
│ Flux<String> │
│ 响应式流处理 │
└────┬────────────┘


┌─────────────────┐
│ SSEServer │
│ 推送到前端 │
└─────────────────┘

🎨 前端对接

API 调用

// src/api/chat.js
export const sendMessage = async (userId, message, mode) => {
try {
const response = await api.post('/chat/send', {
currentUserName: userId,
message,
mode,
});
if (response.status === 200) {
return { error: false };
} else {
return { error: true, message: '消息发送失败' };
}
} catch (error) {
return { error: true, message: '消息发送失败,请检查后端服务' };
}
};

使用示例

// 发送消息
const result = await sendMessage('user-123', '你好', 'normal');
if (!result.error) {
console.log('消息发送成功,等待 SSE 推送结果');
}

🔍 高级特性

1. 自定义提示词模板

// 使用 PromptTemplate 构建复杂提示词
PromptTemplate promptTemplate = new PromptTemplate(
"你是一个{role},请回答以下问题:{question}"
);

Prompt prompt = promptTemplate.create(Map.of(
"role", "股票分析师",
"question", "如何分析一只股票?"
));

2. 调整模型参数

// 通过 ChatOptions 调整模型参数
ChatOptions options = DeepSeekChatOptions.builder()
.temperature(0.7) // 温度:控制随机性
.maxTokens(2000) // 最大 token 数
.topP(0.9) // Top-P 采样
.build();

Flux<String> stream = chatClient
.prompt(prompt)
.options(options)
.stream()
.content();

3. 添加对话历史

// 构建多轮对话
List<Message> messages = List.of(
new UserMessage("你好"),
new AssistantMessage("你好!有什么可以帮助你的?"),
new UserMessage("介绍一下你自己")
);

Prompt prompt = new Prompt(messages);

⚠️ 注意事项

1. API Key 安全

# 生产环境使用环境变量
spring:
ai:
deepseek:
api-key: ${DEEPSEEK_API_KEY}

2. 错误处理

// 添加重试机制
stream
.retry(3) // 失败时重试 3 次
.timeout(Duration.ofSeconds(30)) // 30 秒超时
.doOnError(error -> {
// 记录错误日志
log.error("AI 调用失败", error);
});

3. 并发控制

// 限制同时进行的对话数量
private final Semaphore semaphore = new Semaphore(10);

public void processChat(String userId, String message) {
if (!semaphore.tryAcquire()) {
throw new RuntimeException("系统繁忙,请稍后重试");
}

try {
// 处理对话
} finally {
semaphore.release();
}
}

📊 性能优化

1. 连接池配置

spring:
ai:
deepseek:
chat:
options:
connection-timeout: 5000
read-timeout: 30000

2. 缓存策略

@Cacheable(value = "ai-responses", key = "#message")
public String getCachedResponse(String message) {
// 对于常见问题,使用缓存避免重复调用 API
}

3. 异步处理

@Async
@Override
public void sendMessageAsync(String userId, String message, String mode) {
// 使用 @Async 注解,避免阻塞主线程
}

🎯 总结

通过 Spring AI 框架,我们实现了:

  • 简洁的代码:无需手动处理 HTTP 请求和响应
  • 流式响应:原生支持 Reactor,轻松实现打字机效果
  • 易于扩展:统一接口,切换模型只需修改配置
  • 生产就绪:完善的错误处理和资源管理

这种实现方式不仅代码优雅,而且性能出色,是构建 AI 应用的最佳实践。

📚 参考资料


下一篇:SSE 聊天流的实现

分享ShotAI-实现大模型对接

交流与讨论

评论由 Valine / LeanCloud 提供,点击后连接第三方服务。

搜索文章