SpringBoot имитирует передачу потока данных GPT
SpringBoot имитирует передачу потока данных GPT

Ответ потоковой передачи данных Java

помещение

Размышляя об интеграции ChatGpt в проект SpringBoot, я обнаружил, что API ChatGpt имеет два метода возврата при возврате данных: один — использовать потоковую передачу, а другой — напрямую возвращать все данные. Если используется потоковая передача, скорость ответа очень высокая. Нет необходимости получать все содержимое ответа перед началом возврата ответа. Однако можно добиться эффекта возврата ответа, как на пишущей машинке, когда сервер возвращает данные. , если все данные возвращаются напрямую, их необходимо вернуть на стороне сервера. После получения всех результатов ChatGpt все ответы с данными сразу возвращаются клиенту для отображения. Недостаток заключается в том, что это очень медленно и очень медленно. результат может занять до 10 секунд максимум. Поэтому в этой статье делается попытка имитировать использование ChatGpt потоковой передачи данных для возврата данных клиенту.

Ответ текстового потока Springboot

Сначала протестируйте серверную часть, используя поток для ответа на данные фиксированной текстовой строки. Основной метод — использовать поток ответа HttpServletResponse. Вам необходимо установить заголовок ответа следующим образом:

Язык кода:javascript
копировать
res.setHeader("Content-Type", "text/event-stream");
res.setContentType("text/event-stream");
res.setCharacterEncoding("UTF-8");
res.setHeader("Pragma", "no-cache");

Тестовый интерфейс выглядит следующим образом:

Язык кода:javascript
копировать
// Последовательность тестового ответа
    @GetMapping("/api/test/sss")
    @AnonymousAccess
    public void test(String prompt, HttpServletResponse res) throws IOException, InterruptedException {
        log.info("[содержимое запроса]: {}", prompt);
        String str = "       Что такое любовь, которую невозможно получить? \n" +
                «Солнце встает на востоке, а дождь идет на западе. Солнца нет, но есть солнце.\n» +
                "Если бы они вместе оказались под снегом, они бы были вместе до конца своей жизни.\n" +
                "Изначально я стремился к яркой луне, но яркая луна светит над канавой.\n" +
                «В это время мы смотрим друг на друга, но не слышим друг друга. Надеюсь, лунный свет осветит тебя.\n» +
                «Пояс становится все шире и шире, но я больше об этом не жалею. Я чувствую себя изможденным из-за Йи.\n» +
                "Это чувство можно будет вспомнить позже, но оно уже было в растерянности.\n" +
                «Если жизнь похожа на первую встречу, то что случилось с Грустным поклонником рисования Западного Ветра.\n» +
                «Давным-давно с морем было трудно справиться, за исключением Ушаня, оно не было облаком.\n» +
                «Почему мы должны вместе вырезать свечи из западного окна и говорить о дождливой ночи в Васане?\n» +
                "Вечность неба и земли рано или поздно закончится, и эта ненависть будет длиться вечно.\n" +
                "\n";
        // поток ответов
        res.setHeader("Content-Type", "text/event-stream");
        res.setContentType("text/event-stream");
        res.setCharacterEncoding("UTF-8");
        res.setHeader("Pragma", "no-cache");
        ServletOutputStream out = null;
        try {
            out = res.getOutputStream();
            for (int i = 0; i < str.length(); i++) {
                out.write(String.valueOf(str.charAt(i)).getBytes());
                // возобновлятьданныепоток                out.flush();
                Thread.sleep(100);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (out != null) out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

Используя этот интерфейс, возвращаемые данные необходимо обрабатывать с помощью потока. Если вы напрямую запросите этот интерфейс в браузере, эффект будет следующим:

Веб-клиент получает потоковые данные и отображает их.

Чтобы получать данные текстового потока в js, вам необходимо установить тип ответа: xhr.setRequestHeader("Content-Type", "text/event-stream");

Конкретная реализация кода приема данных выглядит следующим образом:

Язык кода:javascript
копировать
        // создавать XMLHttpRequest объект
        const xhr = new XMLHttpRequest();
        // Установите запрошенное URL
        xhr.open(
            "GET",
            `http://localhost:8080/api/test/sss`
        );
        // Установите тип ответа на text/event-stream
        xhr.setRequestHeader("Content-Type", "text/event-stream");
        // монитор readyStateChange событие
        xhr.onreadystatechange = () => {
            // если readyState да 3. Указывает, что данные получены.
            if (xhr.readyState === 3) {
                // Добавьте данные в текстовое поле
                console.log('xhr.responseText :>> ', xhr.responseText);
                reply("images/touxiang.png", xhr.responseText, randomStr)
                var height = $("#message").height();
                $("html").scrollTop(height)
            }
        };
        // Отправить запрос
        xhr.send();

Эффект следующий:

Этот эффект обеспечивает эффект потоковой передачи ChatGpt.

SpingBoot интегрирует ChatGPT для использования результатов потоковой передачи ответов.

Специальная интеграция ChatGPT SDKУчебные пособия можно просмотреть в официальной документации.:https://gitcode.net/mirrors/grt1228/chatgpt-java

Импортировать зависимости pom:

Язык кода:javascript
копировать
<dependency>
    <groupId>com.unfbx</groupId>
    <artifactId>chatgpt-java</artifactId>
    <version>1.0.13</version>
</dependency>

использоватьChatGptпотокпереданныйdemoПримеры можно посмотреть:https://gitcode.net/mirrors/grt1228/chatgpt-java/blob/main/src/test/java/com/unfbx/chatgpt/OpenAiStreamClientTest.java

Учебное пособие по подключению официального Demo SDK к ChatGPT очень подробное. Для получения конкретных руководств просто прочитайте демонстрационный документ выше. Здесь мы в основном говорим о деталях получения данных и о том, как ответить клиенту с полученными потоковыми данными.

Ниже приведен пример метода, вызываемого SDK.

Язык кода:javascript
копировать
 public static void ChatGptSendV1(String prompt, ChatSocketVo chatSocketVo) throws IOException {
        OpenAiConfig openAiConfig = new OpenAiConfig();
        OpenAiStreamClient openAiClient = OpenAiStreamClient.builder()
                .apiKey(Collections.singletonList(openAiConfig.getTkoen()))
                //Если вы сами являетесь прокси, просто передайте адрес прокси. Если у вас его нет, не передавайте его.
                .apiHost(openAiConfig.getDomain())
                .build();
        //Модель чата: gpt-3.5
        ConsoleEventSourceListener eventSourceListener = new ConsoleEventSourceListener();
        Message message = Message.builder().role(Message.Role.USER).content(prompt).build();
        ChatCompletion chatCompletion = ChatCompletion
                .builder()
                .model(ChatCompletion.Model.GPT_3_5_TURBO.getName())
                .temperature(0.2)
                .maxTokens(2048)
                .messages(Arrays.asList(message))
                .stream(true)
                .build();

        openAiClient.streamChatCompletion(chatCompletion, eventSourceListener);
        CountDownLatch countDownLatch = new CountDownLatch(1);
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

Метод обработки обратного вызова сообщения в коде: ConsoleEventSourceListener eventSourceListener = new ConsoleEventSourceListener();. В методе вызова передачи потока в openAiClient.streamChatCompletion(chatCompletion, eventSourceListener); передается SSE EventSourceListener. Код выглядит следующим образом:

Язык кода:javascript
копировать
package com.unfbx.chatgpt.sse;

import java.util.Objects;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.sse.EventSource;
import okhttp3.sse.EventSourceListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ConsoleEventSourceListener extends EventSourceListener {
    private static final Logger log = LoggerFactory.getLogger(ConsoleEventSourceListener.class);

    public ConsoleEventSourceListener() {
    }

    public void onOpen(EventSource eventSource, Response response) {
        log.info("OpenAI устанавливает соединение...");
    }

    public void onEvent(EventSource eventSource, String id, String type,String data) {
        log.info("OpenAIвозвращатьсяданные:{}", data);
        if (data.equals("[DONE]")) {
            log.info("Возврат данных OpenAI завершен");
        }
    }

    public void onClosed(EventSource eventSource) {
        log.info("OpenAI закрыт ссесоединять...");
    }

    public void onFailure(EventSource eventSource, Throwable t, Response response) {
        try {
            if (Objects.isNull(response)) {
                log.error("OpenAI  sseсоединятьаномальный:{}", t);
                eventSource.cancel();
            } else {
                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();
            }
        } catch (Throwable var5) {
            throw var5;
        }
    }
}

Легко видеть, что способ обработки сообщений потока обратного вызова OpenAI заключается в установлении sse-соединения. Однако использование этого sse-соединения очень похоже на WebSocket. В методе onEvent данные — это содержимое сообщения, на которое отвечает AI. Просто вывод сообщений по умолчанию выводит только логи. Итак, мы можем справиться с этим следующим образом:

  1. Создайте новый класс самостоятельно для интеграции EventSourceListener.,Имитируйте ConsoleEventSourceListener выше.,Перепишите соответствующий результат, чтобы создать соединение,возвращатьсяданные,Тесное соединение и другие методы.
  2. В конструктор класса EventSourceListener вы можете передать нужные вам значения сцены и т.д.,Например, сеанс веб-сокета,Затем каждый раз, когда приходит сообщение,Используйте веб-сокет для немедленной отправки сообщений клиенту.
  3. Обратите внимание на проблемы многопоточной безопасности глобальных параметров.,С момента создания долгое соединение,Значения сцены, передаваемые в качестве параметров конструкции, должны быть определены как глобальные переменные.,нодаеслис несколькими людьми одновременноиспользовать При смене интерфейса,Значения сцены будут перепутаны и возникнут проблемы с потокобезопасностью. Решением может быть добавление аннотации @Autowired при определении глобальных переменных.,Вы можете обратиться к другим руководствам за принципами.

Пример пользовательского кода EventSourceListener выглядит следующим образом (добавлена ​​некоторая логика обработки записей сообщений):

Язык кода:javascript
копировать
package com.team.modules.system.Utils;

import java.util.Date;
import java.util.Objects;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.team.modules.system.domain.ChatgptInfo;
import com.team.modules.system.domain.vo.ChatSocketVo;
import com.team.modules.system.domain.vo.IpDataVo;
import com.team.modules.websocket.WebSocketChatServe;
import com.team.utils.CDKeyUtil;
import com.unfbx.chatgpt.entity.chat.ChatCompletionResponse;
import lombok.SneakyThrows;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.sse.EventSource;
import okhttp3.sse.EventSourceListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * Created by tao.
 * Date: 2023/6/8 15:51
 * описывать:
 */
public class ChatEventSourceListener extends EventSourceListener {
    private static final Logger log = LoggerFactory.getLogger(com.unfbx.chatgpt.sse.ConsoleEventSourceListener.class);
    private static WebSocketChatServe webSocketChatServe = new WebSocketChatServe();
    @Autowired
    private String resContent;
    @Autowired
    private ChatSocketVo chatSocketVo;

    public ChatEventSourceListener(ChatSocketVo socketVo) {
        chatSocketVo = socketVo;
        resContent = CDKeyUtil.getSequence() + ":::";
    }

    public void onOpen(EventSource eventSource, Response response) {
        log.info("OpenAI устанавливает соединение...");
    }

    int i = 0;

    @SneakyThrows
    public void onEvent(EventSource eventSource, String id, String type,String data) {
        // OpenAIиметь дело сданные
//        log.info(i + "---------OpenAIвозвращатьсяданные:{}", data);
//        i++;
        if (!data.equals("[DONE]")) {
            ObjectMapper mapper = new ObjectMapper();
            ChatCompletionResponse completionResponse = mapper.readValue(data, ChatCompletionResponse.class); // Чтение Json
            String content = mapper.writeValueAsString(completionResponse.getChoices().get(0).getDelta());
            resContent = resContent + content;
            // передача данных с помощью ws
            webSocketChatServe.sendMessageByKey(resContent, chatSocketVo.getKey());
        } else {
            log.info("Возврат данных OpenAI завершен");
            String[] split = resContent.split(":::");
            resContent = CDKeyUtil.getSequence() + ":::";
            // Записывайте контент, информацию об IP и т. д.
            String ip = chatSocketVo.getIpAddr();
            String ua = chatSocketVo.getUa();
            // Получите актуальную информацию
            IpDataVo ipData = chatSocketVo.getIpDataVo();
            String address = ipData.getCountry() + " " + ipData.getProvince() + " " + ipData.getCity() + " " + ipData.getDistrict();
//            String address = "";
            ChatgptInfo chatgptInfo = new ChatgptInfo(chatSocketVo.getPrompt(), split[1], ip, address, ua, new Date(), ipData.getLocation());
            chatSocketVo.getChatgptService().save(chatgptInfo);
        }
    }

    public void onClosed(EventSource eventSource) {
        log.info("OpenAI закрыт ссесоединять...");
    }

    @SneakyThrows
    public void onFailure(EventSource eventSource, Throwable t, Response response) {
        try {
            if (Objects.isNull(response)) {
                log.error("OpenAI  sseсоединятьаномальный:{}", t);
                eventSource.cancel();
            } else {
                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();
            }
        } catch (Throwable var5) {
            throw var5;
        }
    }
}

Конечно, способ возврата данных в ответ также может быть реализован с помощью потока ответов, представленного в начале статьи. Недостатком является то, что вам все равно придется избегать проблем с безопасностью потоков. Я попробовал добавить аннотацию @Async; используя этот метод локально, проблем не обнаружено, но при развертывании на сервере обнаруживается, что этот метод не работает. Прежде чем возвращать данные, он будет ждать, пока не будут возвращены все данные.

boy illustration
RasaGpt — платформа чат-ботов на основе Rasa и LLM.
boy illustration
Nomic Embed: воспроизводимая модель внедрения SOTA с открытым исходным кодом.
boy illustration
Улучшение YOLOv8: EMA основана на эффективном многомасштабном внимании, основанном на межпространственном обучении, и эффект лучше, чем у ECA, CBAM и CA. Малые цели имеют очевидные преимущества | ICASSP2023
boy illustration
Урок 1 серии Libtorch: Тензорная библиотека Silky C++
boy illustration
Руководство по локальному развертыванию Stable Diffusion: подробные шаги и анализ распространенных проблем
boy illustration
Полностью автоматический инструмент для работы с видео в один клик: VideoLingo
boy illustration
Улучшения оптимизации RT-DETR: облегченные улучшения магистрали | Support Paddle облегченный rtdetr-r18, rtdetr-r34, rtdetr-r50, rtdet
boy illustration
Эксклюзивное оригинальное улучшение YOLOv8: собственная разработка SPPF | Деформируемое внимание с большим ядром (D-LKA Attention), большое ядро ​​​​свертки улучшает механизм внимания восприимчивых полей с различными функциями
boy illustration
Создано Datawhale: выпущено «Руководство по тонкой настройке развертывания большой модели GLM-4»!
boy illustration
7B превышает десятки миллиардов, aiXcoder-7B с открытым исходным кодом Пекинского университета — это самая мощная модель большого кода, лучший выбор для корпоративного развертывания.
boy illustration
Используйте модель Huggingface, чтобы заменить интерфейс внедрения OpenAI в китайской среде.
boy illustration
Оригинальные улучшения YOLOv8: несколько новых улучшений | Сохранение исходной информации — алгоритм отделяемой по глубине свертки (MDSConv) |
boy illustration
Второй пилот облачной разработки | Быстро поиграйте со средствами разработки на базе искусственного интеллекта
boy illustration
Бесшовная интеграция, мгновенный интеллект [1]: платформа больших моделей Dify-LLM, интеграция с нулевым кодированием и встраивание в сторонние системы, более 42 тысяч звезд, чтобы стать свидетелями эксклюзивных интеллектуальных решений.
boy illustration
Решенная Ошибка | Загрузка PyTorch медленная: TimeoutError: [Errno 110] При загрузке факела истекло время ожидания — Cat Head Tiger
boy illustration
Brother OCR, библиотека с открытым исходным кодом для Python, которая распознает коды проверки.
boy illustration
Новейшее подробное руководство по загрузке и использованию последней демонстрационной версии набора данных COCO.
boy illustration
Выпущен отчет о крупной модели финансовой отрасли за 2023 год | Полный текст включен в загрузку |
boy illustration
Обычные компьютеры также могут работать с большими моделями, и вы можете получить личного помощника с искусственным интеллектом за три шага | Руководство для начинающих по локальному развертыванию LLaMA-3
boy illustration
Одной статьи достаточно для анализа фактора транскрипции SCENIC на Python (4)
boy illustration
Бросая вызов ограничениям производительности небольших видеокарт, он научит вас запускать большие модели глубокого обучения с ограниченными ресурсами, а также предоставит полное руководство по оценке и эффективному использованию памяти графического процессора!
boy illustration
Команда Fudan NLP опубликовала 80-страничный обзор крупномасштабных модельных агентов, в котором в одной статье представлен обзор текущего состояния и будущего агентов ИИ.
boy illustration
[Эксклюзив] Вы должны знать о новой функции JetBrains 2024.1 «Полнострочное завершение кода», чтобы решить вашу путаницу!
boy illustration
Краткое изложение базовых знаний о регистрации изображений 1.0
boy illustration
Новейшее подробное руководство по установке и использованию библиотеки cv2 (OpenCV, opencv-python) в Python.
boy illustration
Легко создайте локальную базу знаний для крупных моделей на основе Ollama+AnythingLLM.
boy illustration
[Решено] ошибка установки conda. Среда решения: не удалось выполнить первоначальное зависание решения. Повторная попытка с помощью файла (графическое руководство).
boy illustration
Одна статья поможет вам понять RAG (Retrival Enhanced Generation) | Введение в концепцию и теорию + практику работы с кодом (включая исходный код).
boy illustration
Эволюция архитектуры шлюза облачной разработки
boy illustration
Docker и Kubernetes [Разработка контейнерных приложений с помощью Python]