一、什么是 SSE
"SSE" 是指 "Server-Sent Events",它是一种用于在服务器和客户端之间实现单向实时通信的技术。通过 SSE,服务器可以向客户端推送数据,而不需要客户端发起请求。这种技术通常用于实现实时更新、通知和事件驱动的应用程序,例如实时聊天、股票市场更新、新闻通知等。
SSE 使用了 HTTP 协议,但与传统的请求-响应模式不同,它建立了一条持久的连接,允许服务器不断地将数据推送到客户端。这样,客户端就可以在服务器端有新数据时立即收到通知,从而实现实时更新。
SSE 的基本工作原理如下:
- 客户端通过发送一个 HTTP 请求到服务器来建立连接。
- 服务器接受连接请求后,保持连接打开,并开始向客户端发送数据。
- 服务器使用特殊的 MIME 类型 "text/event-stream" 来传输数据,每个数据块都被包装在特定的格式内,包含字段如 "event"、"data"、"id" 等。
- 客户端收到服务器发送的数据后,可以在 JavaScript 中处理这些数据,例如更新页面内容或触发特定的事件。
这种通信模式相对简单,适用于那些需要从服务器端实时获取数据的场景。然而,SSE 也有一些限制,比如它是单向的通信,只能由服务器向客户端推送数据,而不能由客户端向服务器发送数据。另外,不同浏览器对 SSE 的支持程度可能有所不同。
请注意,SSE 不同于 WebSocket,WebSocket 是一种全双工通信协议,可以在客户端和服务器之间实现双向的实时通信。
二、OpenAI的Chat接口
接口文档地址:https://platform.openai.com/docs/api-reference/chat/create
- 接口地址:https://api.openai.com/v1/chat/completions
- 请求方式:POST
核心参数如下:
{
"messages": [
{
"role": "user",
"content": "你好"
}
],
"stream": true,
"model": "gpt-3.5-turbo",
"temperature": 1,
"presence_penalty": 0
}
其中如果stream=true
,则接口会以SSE的形式多次返回Ai的回答内容,如下:
{
"id": "chatcmpl-7t7u1CvYTESm5cgf3wz5gd5S0vGxU",
"object": "chat.completion.chunk",
"created": 1693372773,
"model": "gpt-3.5-turbo-0613",
"choices": [
{
"index": 0,
"delta": {
"content": "你"
},
"finish_reason": null
}
]
}
其中content
字段就是回答内容的分词,我们获取到该字段后显示到界面,就实现了逐字回复的效果,类似ChatGPT。
如果回答内容返回结束,该接口会直接返回[DONE]
字符串。
三、Java 如何调用该接口?
基于以上对接口的解析,我们可以尝试使用 Java 来调用该接口。
我们可以借助 OkHttp
来实现:
<dependencys>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp-sse</artifactId>
<version>3.14.9</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>logging-interceptor</artifactId>
<version>3.14.9</version>
</dependency>
</dependencys>
测试代码如下:
package cn.junki.sukiserver;
import cn.hutool.http.ContentType;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import okhttp3.sse.EventSource;
import okhttp3.sse.EventSourceListener;
import okhttp3.sse.EventSources;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* SSE接口测试类
*
* @author Junki
* @date 2023-09-01 09:58:50
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SseTest {
@Test
void test01() {
// OkHttp客户端
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.writeTimeout(50, TimeUnit.SECONDS)
.readTimeout(50, TimeUnit.SECONDS)
.build();
// 事件源工厂
EventSource.Factory factory = EventSources.createFactory(okHttpClient);
// 请求体
String requestBody = "{\n" +
" \"messages\": [\n" +
" {\n" +
" \"role\": \"user\",\n" +
" \"content\": \"你好\"\n" +
" }\n" +
" ],\n" +
" \"stream\": true,\n" +
" \"model\": \"gpt-3.5-turbo\",\n" +
" \"temperature\": 1,\n" +
" \"presence_penalty\": 0\n" +
"}";
// 请求对象
Request request = new Request.Builder()
.url("https://api.openai.com/v1/chat/completions")
.header("authorization", "Bearer 你的API-Key")
.post(RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), requestBody))
.build();
// 自定义监听器
EventSourceListener eventSourceListener = new ConsoleEventSourceListener();
// 创建事件
EventSource eventSource = factory.newEventSource(request, eventSourceListener);
// 等待线程结束
CountDownLatch countDownLatch = new CountDownLatch(1);
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Slf4j
class ConsoleEventSourceListener extends EventSourceListener {
@Override
public void onOpen(EventSource eventSource, Response response) {
log.info("OpenAI建立sse连接...");
}
@Override
public void onEvent(EventSource eventSource, String id, String type, String data) {
log.info("OpenAI返回数据:{}", data);
if ("[DONE]".equals(data)) {
log.info("OpenAI返回数据结束了");
return;
}
}
@Override
public void onClosed(EventSource eventSource) {
log.info("OpenAI关闭sse连接...");
}
@SneakyThrows
@Override
public void onFailure(EventSource eventSource, Throwable t, Response response) {
if (Objects.isNull(response)) {
log.error("OpenAI sse连接异常:{}", t);
eventSource.cancel();
return;
}
ResponseBody body = response.body();
if (Objects.nonNull(body)) {
log.error("OpenAI sse连接异常data:{},异常:{}", body.string(), t);
} else {
log.error("OpenAI sse连接异常data:{},异常:{}", response, t);
}
eventSource.cancel();
}
}
运行结果如下:
OpenAI建立sse连接...
OpenAI返回数据:{"id":"chatcmpl-7toLbaSnmm67pMHSY2GMVESy6oMim","object":"chat.completion.chunk","created":1693535931,"model":"gpt-3.5-turbo-0613","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}
OpenAI返回数据:{"id":"chatcmpl-7toLbaSnmm67pMHSY2GMVESy6oMim","object":"chat.completion.chunk","created":1693535931,"model":"gpt-3.5-turbo-0613","choices":[{"index":0,"delta":{"content":"你"},"finish_reason":null}]}
OpenAI返回数据:{"id":"chatcmpl-7toLbaSnmm67pMHSY2GMVESy6oMim","object":"chat.completion.chunk","created":1693535931,"model":"gpt-3.5-turbo-0613","choices":[{"index":0,"delta":{"content":"好"},"finish_reason":null}]}
OpenAI返回数据:{"id":"chatcmpl-7toLbaSnmm67pMHSY2GMVESy6oMim","object":"chat.completion.chunk","created":1693535931,"model":"gpt-3.5-turbo-0613","choices":[{"index":0,"delta":{"content":"!"},"finish_reason":null}]}
OpenAI返回数据:{"id":"chatcmpl-7toLbaSnmm67pMHSY2GMVESy6oMim","object":"chat.completion.chunk","created":1693535931,"model":"gpt-3.5-turbo-0613","choices":[{"index":0,"delta":{"content":"有"},"finish_reason":null}]}
OpenAI返回数据:{"id":"chatcmpl-7toLbaSnmm67pMHSY2GMVESy6oMim","object":"chat.completion.chunk","created":1693535931,"model":"gpt-3.5-turbo-0613","choices":[{"index":0,"delta":{"content":"什"},"finish_reason":null}]}
OpenAI返回数据:{"id":"chatcmpl-7toLbaSnmm67pMHSY2GMVESy6oMim","object":"chat.completion.chunk","created":1693535931,"model":"gpt-3.5-turbo-0613","choices":[{"index":0,"delta":{"content":"么"},"finish_reason":null}]}
OpenAI返回数据:{"id":"chatcmpl-7toLbaSnmm67pMHSY2GMVESy6oMim","object":"chat.completion.chunk","created":1693535931,"model":"gpt-3.5-turbo-0613","choices":[{"index":0,"delta":{"content":"可以"},"finish_reason":null}]}
OpenAI返回数据:{"id":"chatcmpl-7toLbaSnmm67pMHSY2GMVESy6oMim","object":"chat.completion.chunk","created":1693535931,"model":"gpt-3.5-turbo-0613","choices":[{"index":0,"delta":{"content":"帮"},"finish_reason":null}]}
OpenAI返回数据:{"id":"chatcmpl-7toLbaSnmm67pMHSY2GMVESy6oMim","object":"chat.completion.chunk","created":1693535931,"model":"gpt-3.5-turbo-0613","choices":[{"index":0,"delta":{"content":"助"},"finish_reason":null}]}
OpenAI返回数据:{"id":"chatcmpl-7toLbaSnmm67pMHSY2GMVESy6oMim","object":"chat.completion.chunk","created":1693535931,"model":"gpt-3.5-turbo-0613","choices":[{"index":0,"delta":{"content":"你"},"finish_reason":null}]}
OpenAI返回数据:{"id":"chatcmpl-7toLbaSnmm67pMHSY2GMVESy6oMim","object":"chat.completion.chunk","created":1693535931,"model":"gpt-3.5-turbo-0613","choices":[{"index":0,"delta":{"content":"的"},"finish_reason":null}]}
OpenAI返回数据:{"id":"chatcmpl-7toLbaSnmm67pMHSY2GMVESy6oMim","object":"chat.completion.chunk","created":1693535931,"model":"gpt-3.5-turbo-0613","choices":[{"index":0,"delta":{"content":"吗"},"finish_reason":null}]}
OpenAI返回数据:{"id":"chatcmpl-7toLbaSnmm67pMHSY2GMVESy6oMim","object":"chat.completion.chunk","created":1693535931,"model":"gpt-3.5-turbo-0613","choices":[{"index":0,"delta":{"content":"?"},"finish_reason":null}]}
OpenAI返回数据:{"id":"chatcmpl-7toLbaSnmm67pMHSY2GMVESy6oMim","object":"chat.completion.chunk","created":1693535931,"model":"gpt-3.5-turbo-0613","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
OpenAI返回数据:[DONE]
OpenAI返回数据结束了
OpenAI关闭sse连接...
四、开源推荐
基于这个思路,我们可以轻松的封装 OpenAI 接口,目前 OpenAI 官方已经提供了开源 SDK :
https://github.com/openai/openai-java