Spring AI + MCP + DeepSeek-R1-7B + SSE 全流程實(shí)戰(zhàn)指南
作者:編程疏影
通過(guò)以上步驟,我們成功實(shí)現(xiàn)了:Ollama 部署并運(yùn)行 DeepSeek-R1-7B 本地大模型;Spring Boot 封裝 OpenAI 接口 ??stream: true??;實(shí)現(xiàn)后端 SSE 推流 + 前端實(shí)時(shí) Token 渲染;支持國(guó)產(chǎn)開(kāi)源模型的類 ChatGPT 對(duì)話功能?。
本教程將帶你從 0 到 1 實(shí)現(xiàn)一個(gè)完整的 AI 流式問(wèn)答應(yīng)用,整合以下組件:
- Spring Boot + Spring AI 構(gòu)建服務(wù)框架
- MCP(Model Connector Plugin) 用于統(tǒng)一管理本地/云端大模型
- DeepSeek-R1-7B 國(guó)產(chǎn)高性能大模型(OpenAI API 兼容)
- SSE(Server-Sent Events) 實(shí)現(xiàn)前后端實(shí)時(shí)流式響應(yīng)
- Ollama(可選) 更便捷地部署 DeepSeek-R1-7B 模型并提供 OpenAI 接口支持
模型部署方式推薦:Ollama 運(yùn)行 DeepSeek-R1-7B
安裝 Ollama
訪問(wèn):https://ollama.com
# macOS / Linux 安裝
curl-fsSL https://ollama.com/install.sh |sh
# Windows:安裝官方 MSI 安裝包
拉取模型(以 DeepSeek 為例)
ollama pull deepseek:chat
也可以加載其它模型,如
llama3
,qwen:chat
,yi:34b
,phi3
,mistral
等。
啟動(dòng) Ollama
ollama run deepseek:chat
Ollama 會(huì)自動(dòng)監(jiān)聽(tīng) OpenAI 風(fēng)格接口(http://localhost:11434/v1/chat/completions
),兼容 stream: true
。
Spring Boot 接入 SSE 流式輸出服務(wù)
添加依賴(pom.xml)
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
</dependencies>
WebClient 配置類
@Configuration
public class WebClientConfig {
@Bean
public WebClient webClient() {
return WebClient.builder()
.baseUrl("http://localhost:11434/v1") // Ollama API 地址
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build();
}
}
請(qǐng)求體結(jié)構(gòu)封裝
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ChatCompletionRequest {
private String model;
private List<Map<String, String>> messages;
private boolean stream = true;
private double temperature = 0.7;
}
DeepSeek-R1-7B 接口封裝(支持 stream: true
)
@Service
public class DeepSeekStreamingService {
@Autowired
private WebClient webClient;
public void streamChat(String userPrompt, SseEmitter emitter) {
ChatCompletionRequest request = new ChatCompletionRequest();
request.setModel("deepseek:chat");
request.setStream(true);
request.setMessages(List.of(
Map.of("role", "user", "content", userPrompt)
));
webClient.post()
.uri("/chat/completions")
.body(BodyInserters.fromValue(request))
.accept(MediaType.TEXT_EVENT_STREAM)
.retrieve()
.bodyToFlux(String.class)
.doOnNext(chunk -> {
try {
if (chunk.contains("[DONE]")) {
emitter.send(SseEmitter.event().data("[DONE]"));
emitter.complete();
} else if (chunk.startsWith("data:")) {
String json = chunk.replaceFirst("data: *", "");
String token = parseTokenFromJson(json);
emitter.send(SseEmitter.event().data(token));
}
} catch (Exception e) {
emitter.completeWithError(e);
}
})
.doOnError(emitter::completeWithError)
.subscribe();
}
private String parseTokenFromJson(String json) {
try {
ObjectMapper mapper = new ObjectMapper();
JsonNode node = mapper.readTree(json);
return node.path("choices").get(0).path("delta").path("content").asText("");
} catch (Exception e) {
return "";
}
}
}
控制器對(duì)外暴露 SSE 接口
@RestController
@RequestMapping("/api/ai")
public class ChatSseController {
@Autowired
private DeepSeekStreamingService streamingService;
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter stream(@RequestParam("prompt") String prompt) {
SseEmitter emitter = new SseEmitter(0L); // 永不超時(shí)
streamingService.streamChat(prompt, emitter);
return emitter;
}
}
前端 JS 接入 SSE 實(shí)現(xiàn)流式展示
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>AI 流式問(wèn)答</title>
</head>
<body>
<input id="prompt" placeholder="請(qǐng)輸入你的問(wèn)題...">
<button onclick="startStream()">提問(wèn)</button>
<div id="result"></div>
<script>
function startStream() {
const prompt = document.getElementById('prompt').value;
const eventSource = new EventSource(`/api/ai/stream?prompt=${encodeURIComponent(prompt)}`);
document.getElementById('result').innerHTML = '';
eventSource.onmessage = function (event) {
if (event.data === '[DONE]') {
eventSource.close();
} else {
document.getElementById('result').innerHTML += event.data;
}
};
}
</script>
</body>
</html>
總結(jié)
通過(guò)以上步驟,我們成功實(shí)現(xiàn)了:
- Ollama 部署并運(yùn)行 DeepSeek-R1-7B 本地大模型
- Spring Boot 封裝 OpenAI 接口
stream: true
- 實(shí)現(xiàn)后端 SSE 推流 + 前端實(shí)時(shí) Token 渲染
- 支持國(guó)產(chǎn)開(kāi)源模型的類 ChatGPT 對(duì)話功能
責(zé)任編輯:武曉燕
來(lái)源:
路條編程