在大模型会话中,会有一个功能是停止生成功能。这个功能如果在前端实现,既取消监听后端的流式返回事件,会导致后端日志中报错连接中断等错误。
由此引出的需求,我的接口A中使用了sse流式返回,需要做一个接口B,B的功能是中止第一个接口的流式返回,以下是核心代码和思路:
方案一:需要借助redis,在输出时循环判定来解决。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.concurrent.TimeUnit;
@Controller
public class MyController {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@RequestMapping("/startStreaming")
public SseEmitter startStreaming(HttpServletRequest request) throws IOException {
String requestId = request.getId(); // 获取请求的唯一标识符
String key = "shouldStopStreaming_" + requestId; // 生成唯一的key
SseEmitter emitter = new SseEmitter();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(/*输入流*/));
// SSE输出逻辑
String line;
while ((line = bufferedReader.readLine()) != null) {
Boolean shouldStop = (Boolean) redisTemplate.opsForValue().get(key);
if (shouldStop != null && shouldStop) {
break; // 检查shouldStopStreaming标志,若为true则中断循环
}
// 发送数据给客户端
emitter.send(line);
}
// 删除key,确保不再需要该key时将其移除
redisTemplate.delete(key);
return emitter;
}
@RequestMapping("/stopStreaming")
@ResponseBody
public String stopStreaming(HttpServletRequest request) {
String requestId = request.getId(); // 获取请求的唯一标识符
String key = "shouldStopStreaming_" + requestId; // 生成唯一的key
// 设置shouldStopStreaming为true,终止流式输出
redisTemplate.opsForValue().set(key, true, 1, TimeUnit.HOURS); // 设置过期时间为1小时(可根据需要调整)
return "Streaming stopped";
}
}
A接口定期从Redis中获取shouldStopStreaming的值,并检查是否应该中止流式输出。B接口使用RedisTemplate将shouldStopStreaming的值设置为true,以指示A接口中止输出。由于Redis的操作是原子性的,并且RedisTemplate提供了线程安全的访问,这样可以确保多个线程之间的协调和线程安全性。
方案二:使用本地缓存,结合SseEmitter特性实现(实际使用的此种方案)
private final Map<String, SseEmitter> sseCache = new ConcurrentHashMap<>(10);
## 对话接口中put一下前端随机生成的不唯一emitterId
sseCache.put(emitterId, emitter);
## 停止回答接口
@Override
public void stop(String emitterId) {
if (sseCache.containsKey(emitterId)) {
sseCache.get(emitterId).complete();
sseCache.remove(emitterId);
}
}