Junki
Junki
Published on 2025-01-17 / 104 Visits
0
0

Java 调用 OpenAI 的 SSE 聊天接口

一、什么是 SSE

"SSE" 是指 "Server-Sent Events",它是一种用于在服务器和客户端之间实现单向实时通信的技术。通过 SSE,服务器可以向客户端推送数据,而不需要客户端发起请求。这种技术通常用于实现实时更新、通知和事件驱动的应用程序,例如实时聊天、股票市场更新、新闻通知等。

SSE 使用了 HTTP 协议,但与传统的请求-响应模式不同,它建立了一条持久的连接,允许服务器不断地将数据推送到客户端。这样,客户端就可以在服务器端有新数据时立即收到通知,从而实现实时更新。

SSE 的基本工作原理如下:

  1. 客户端通过发送一个 HTTP 请求到服务器来建立连接。
  2. 服务器接受连接请求后,保持连接打开,并开始向客户端发送数据。
  3. 服务器使用特殊的 MIME 类型 "text/event-stream" 来传输数据,每个数据块都被包装在特定的格式内,包含字段如 "event"、"data"、"id" 等。
  4. 客户端收到服务器发送的数据后,可以在 JavaScript 中处理这些数据,例如更新页面内容或触发特定的事件。

这种通信模式相对简单,适用于那些需要从服务器端实时获取数据的场景。然而,SSE 也有一些限制,比如它是单向的通信,只能由服务器向客户端推送数据,而不能由客户端向服务器发送数据。另外,不同浏览器对 SSE 的支持程度可能有所不同。

请注意,SSE 不同于 WebSocket,WebSocket 是一种全双工通信协议,可以在客户端和服务器之间实现双向的实时通信。

二、OpenAI的Chat接口

接口文档地址:https://platform.openai.com/docs/api-reference/chat/create

核心参数如下:

{
  "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


Comment