1、效果展示
项目链接:beite-ai-ui
2、运行环境
text
版本介绍
vue:3.5.13
node:18.20.7
运行:
npm i
npm run dev
http://localhost:5173/stream
3、遇到的问题
- 处理deepseek返回的数据直接展示格式的问题
在deepseek返回的数据消息看到,数据是一个md形式的返回,以及代码高亮,因为我也不是专业的前端,参考了若依大神开发的项目,项目地址:ruoyi-vue-pro
- 流式返回对话不显示问题
因为是使用自己后端跟deepseek对接的,一开始我是直接前端直接访问,是可以的,后面切换成访问自己的后端,发现不行,那我就观察了一下deepseek是怎么返回的,发现我的流返回的不对,折腾了一天,后面去访问各大大神,最终解决,这里附上代码 原来的代码,如果有大神知道是哪里的问题,望提出交流交流
java
@PostMapping(value = "/deep_seek/chat_stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter deepSeekChatSteam(@Validated @RequestBody DeepSeekQueryDTO deepSeekQueryDTO) {
DeepSeekModelDTO modelDTO = deepSeekModelConverter.queryDtoToDTO(deepSeekQueryDTO);
modelDTO.setTemperature(0.7);
modelDTO.setMaxTokens(2000);
modelDTO.setTopP(1.0);
modelDTO.setStream(Objects.nonNull(deepSeekQueryDTO.getStream()) ? deepSeekQueryDTO.getStream() : Boolean.FALSE);
// 流式返回
SseEmitter emitter = new SseEmitter();
// 调用 Service 层的 streamChat 方法
objectProvider.stream().forEach(item -> {
if (item.support(AiModelOptions.DEEP_SEEK)) {
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.execute(() -> {
item.streamChat(modelDTO, new EventSourceListener() {
@Override
public void onEvent(@NotNull EventSource eventSource, String id, String type, @NotNull String data) {
System.out.println(JSON.isValid(data));
System.out.println(data);
try {
emitter.send(SseEmitter.event()
.name("message") // 事件名称(前端可通过 addEventListener 监听)
.data(data));
} catch (IOException e) {
emitter.completeWithError(e);
} finally {
emitter.complete();
executor.shutdown();
}
}
@Override
public void onClosed(@NotNull EventSource eventSource) {
emitter.complete();
}
@Override
public void onFailure(@NotNull EventSource eventSource, Throwable t, okhttp3.Response response) {
emitter.completeWithError(t); // 处理失败
}
});
});
}
});
return emitter;
}
后来改成这个可以了:
java
@PostMapping(value = "/deep_seek/chat_stream")
public void deepSeekChatStream(
@Validated @RequestBody DeepSeekQueryDTO deepSeekQueryDTO,
HttpServletResponse httpServletResponse) throws IOException, InterruptedException {
httpServletResponse.setContentType("text/event-stream");
httpServletResponse.setCharacterEncoding("utf-8");
DeepSeekModelDTO modelDTO = deepSeekModelConverter.queryDtoToDTO(deepSeekQueryDTO);
modelDTO.setTemperature(0.7);
modelDTO.setMaxTokens(2000);
modelDTO.setTopP(1.0);
modelDTO.setStream(Objects.nonNull(deepSeekQueryDTO.getStream()) ? deepSeekQueryDTO.getStream() : Boolean.FALSE);
// 使用 CountDownLatch 同步流式输出完成
CountDownLatch completionLatch = new CountDownLatch(1);
PrintWriter pw = httpServletResponse.getWriter(); // 提前获取 Writer
objectProvider.stream().forEach(item -> {
if (item.support(AiModelOptions.DEEP_SEEK)) {
item.streamChat(modelDTO, new EventSourceListener() {
@Override
public void onEvent(
@NotNull EventSource eventSource,
String id,
String type,
@NotNull String data) {
String sendData = "data: " + data + "\n\n";
pw.println(sendData);
pw.flush();
}
@Override
public void onClosed(@NotNull EventSource eventSource) {
completionLatch.countDown(); // 流关闭时释放锁
}
@Override
public void onFailure(
@NotNull EventSource eventSource,
Throwable t,
okhttp3.Response response) {
completionLatch.countDown(); // 出错时也释放锁
// 可选:记录错误或发送错误信息到客户端
try {
pw.println("data: [ERROR] " + t.getMessage() + "\n\n");
pw.flush();
} catch (Exception e) {
// 处理异常
}
}
});
}
});
// 等待流完成(超时时间根据业务需求设置)
completionLatch.await(1000, TimeUnit.SECONDS);
}