从零开始搭建AI网站(6):如何使用响应式编程

响应式编程(Reactive Programming)是一种编程范式,旨在处理异步数据流和事件流。它通过使用观察者模式和函数式编程的概念,将数据流和事件流抽象为可观察的序列,然后通过操作这些序列来实现各种功能。

在响应式编程中,数据流和事件流被视为连续的时间序列,可以通过操作符来转换、过滤和组合它们。这种编程范式的主要优势是它可以简化异步编程,并提供一种声明式的方式来处理数据流和事件流。它还可以提高代码的可读性和可维护性,因为它将复杂的异步逻辑封装在操作符中,使得代码更易于理解和修改。

响应式编程可以应用于各种领域,包括前端开发、后端开发、移动开发等。在前端开发中,响应式编程可以用于处理用户界面的事件流和数据流,使得界面能够动态地响应用户的操作。在后端开发中,响应式编程可以用于处理大量的异步请求和数据流,提高系统的吞吐量和响应速度。

常见的响应式编程框架包括RxJava、RxJS、ReactiveX等。这些框架提供了一系列的操作符和工具,用于处理数据流和事件流,并提供了一种简洁而强大的方式来处理异步编程。

当前响应式编程的典型例子莫过于最近炙手可热的ChatGPT的流式输出了。因为ChatGPT请求响应时间较长,如果采用传统的一直等待全部数据就绪,用户恐怕早就跑光了,而响应式方式则不需要等待所有数据就绪,而只需要有部分数据就绪即可输出,从而极大地提升了用户体验。下面以此为例,来说明实现这种效果的原理(开发语言Java)。

先来看看上文中提到的的三个响应式编程框架:RxJava、RxJS和ReactiveX。它们是三个相关的概念,同时也是不同平台上的实现。

  1. RxJava:RxJava是ReactiveX在Java平台上的实现,它提供了一套丰富的API和操作符,用于处理异步和事件驱动的编程。RxJava是基于观察者模式和迭代器模式的,可以用于处理数据流、事件流和异步任务等。
  2. RxJS:RxJS是ReactiveX在JavaScript平台上的实现,它提供了类似于RxJava的API和操作符,用于处理异步和事件驱动的编程。RxJS可以在浏览器端和Node.js环境中使用,可以处理DOM事件、AJAX请求、定时器等。
  3. ReactiveX:ReactiveX是一个跨平台的响应式编程库,它提供了一套统一的API和操作符,用于处理异步和事件驱动的编程。ReactiveX的目标是提供一种通用的编程模型,使得开发者可以在不同的平台和语言中共享代码和思想。

在Springboot中,另有WebFlux模块可供使用,同时它也可以跟上面的模块一起使用。说起Flux,这里也会涉及到另一个概念:Flowable。其实Flowable和Flux都是响应式流的实现,它们有以下关系:

  1. Flowable是RxJava的一部分,而Flux是Reactor的一部分。RxJava是一个用于Java的响应式编程库,而Reactor是一个用于Java的响应式编程框架。
  2. Flowable是RxJava中的一个类,它实现了Reactive-Streams规范,提供了对背压(backpressure)的支持。Flowable可以处理异步和并发的数据流,并且可以控制数据流的速率,以避免生产者和消费者之间的不匹配。
  3. Flux是Reactor中的一个类,它也实现了Reactive-Streams规范,提供了类似的功能。Flux可以处理异步和并发的数据流,并且可以控制数据流的速率。
  4. Flowable和Flux都提供了一系列的操作符,可以对数据流进行转换、过滤、映射等操作。这些操作符可以帮助开发者处理和操作数据流,使代码更加简洁和可读。

跟tRxJava和Reactor密切相关的开发库之一是WebClien。WebClient是一个用于发送HTTP请求的非阻塞的响应式客户端,它是Reactor项目的一部分。

WebClient提供了一种简洁、灵活和可组合的方式来发送HTTP请求,并处理响应。它可以与RxJava和Reactor的异步和响应式编程模型无缝集成,使得在响应式应用程序中处理HTTP请求变得更加方便和高效。

WebClient可以与RxJava的Flowable一起使用,通过toFlowable()方法将响应转换为Flowable流,从而实现对响应的处理和操作。

WebClient webClient = WebClient.create();
Flowable<String> response = webClient.get().uri("https://example.com").retrieve().bodyToFlux(String.class).toFlowable();

同样,WebClient也可以与Reactor的Flux一起使用,通过bodyToFlux()方法将响应转换为Flux流,从而实现对响应的处理和操作。

WebClient webClient = WebClient.create();
Flux<String> response = webClient.get().uri("https://example.com").retrieve().bodyToFlux(String.class);

下面我们将关注点放在Reactor框架中,在Reactor中,不得不提另一个跟Flux相对的概念:Mono。Flux和Mono是Reactor框架中的两个关键类,它们都是用于处理响应式流的。

  1. Flux是一个表示0到N个元素的响应式流。它可以发出多个元素,并以异步的方式产生这些元素。Flux可以用于处理多个值的数据流,例如从数据库查询结果、文件读取等。
  2. Mono是一个表示0或1个元素的响应式流。它要么发出一个元素,要么不发出任何元素。Mono可以用于处理单个值的数据流,例如从缓存中获取数据、获取单个实体等。
  3. Flux和Mono之间有以下关系:
  • Flux可以被转换成Mono。
Flux<Integer> flux = Flux.just(1, 2, 3);
Mono<Integer> mono = flux.single();
    • Mono可以被转换成Flux。
Mono<Integer> mono = Mono.just(1);
Flux<Integer> flux = mono.flux();

Flux和Mono可以通过一系列的操作符进行转换、过滤、映射等操作,使得对响应式流的处理变得更加灵活和方便。它们是Reactor框架中的核心类,用于构建响应式应用程序。

webClient可以实现复杂的处理逻辑,比如异常处理:

webClient.get().uri(url).retrieve().onStatus(HttpStatus::is4xxClientError, response -> Mono.error(new CustomException("客户端错误"))).onStatus(HttpStatus::is5xxServerError, response -> Mono.error(new CustomException("服务器错误"))).bodyToMono(String.class).onErrorResume(throwable -> {if (throwable instanceof WebClientResponseException) {WebClientResponseException ex = (WebClientResponseException) throwable;// 处理响应异常} else {// 处理其他异常}});

在使用 Spring Boot 的 WebClient 时,bodyToMono 和 bodyToFlux 方法都可以用于将响应体转换为 Mono 或 Flux 对象。

bodyToMono 方法用于将响应体转换为 Mono 对象,适用于响应体只有一个元素的情况,例如返回一个 JSON 对象或者一个字符串。

bodyToFlux 方法用于将响应体转换为 Flux 对象,适用于响应体有多个元素的情况,例如返回一个 JSON 数组或者一个流式数据。

因此,当我们需要处理的响应体只有一个元素时,应该使用 bodyToMono 方法;当我们需要处理的响应体有多个元素时,应该使用 bodyToFlux 方法。

在 Reactor 中,Flux 流结束的实现原理是通过发送一个 onComplete 信号来通知订阅者流已经结束。当 Flux 流中的所有元素都被消费完毕时,会自动发送一个 onComplete 信号。

例如,当我们使用 Flux.range(1, 10) 创建一个包含 1 到 10 的整数序列的 Flux 流时,当订阅者订阅该流并消费完所有元素后,会自动发送一个 onComplete 信号来通知订阅者流已经结束。

在使用 Spring Boot 的 WebClient 时,当我们使用 bodyToFlux 方法将响应体转换为 Flux 对象时,如果响应体是一个流式数据,那么当流式数据传输完毕后,会自动发送一个 onComplete 信号来通知订阅者流已经结束。

webClient.get().uri(url).retrieve().bodyToFlux(String.class).doFinally(signalType -> {if (signalType == SignalType.ON_COMPLETE) {System.out.println("流已结束");}}).subscribe();

有了这些基础知识的准备,我们再来看看ChatGPT的响应结果样例。OpenAI的聊天接口是:

http://api.openai.com/v1/chat/completitions。

该接口接受这样的一个请求数据结构:ChatCompletionRequest。其中有个属性stream 可以设定是否采用流输出。默认false。

这个例子是非stream输出,输出格式为:ChatCompletionResponse

$ curl https://api.openai.com/v1/chat/completions -H 'Content-Type: application/json' -H "Authorization: Bearer sk-zDxkX0Na0e63B18c9c6bT3BlBkFJf3De387b398749c5bD1d" -d '{"model": "gpt-3.5-turbo","stream":"false","messages": [{"role": "user", "content": "Hello!"}]}'
{"id":"chatcmpl-7tywVQ4vSPzs8yuZy5FqvL0CX07W0","object":"chat.completion","created":1693576659,"model":"gpt-3.5-turbo-0613","choices":[{"index":0,"message":{"role":"assistant","content":"Hello

这个例子是stream输出,输出结构体为:字符串格式的ChatCompletionResponse:

curl https://api.openai.com/v1/chat/completions -H 'Content-Type: application/json' -H "Authorization: Bearer sk-zDxkX0Na0e63B18c9c6bT3BlBkFJf3De387b398749c5bD1d" -d '{"model": "gpt-3.5-turbo","stream":"true","messages": [{"role": "user", "content": "Hello!"}]}'

比较stream和非stream的输出区别,有一下几点:

1.非stream 输出只有一条记录;stream 有若干条,取决于响应内容大小;

2. 非stream 输出包含消耗的tokens数量,stream 没有;

3. 非stream 输出结果是json格式的ChatCompletionResponse结构,stream 输出j格式类似:data:str(ChatCompletionResponse),同时以data:[NONE]结尾;

结合上面的知识,我们就能实现上述功能:

public Publisher<String> generateChatCompletion(ChatCompletionRequest chatCompletionRequest) {WebClient.ResponseSpec responseSpec = webClient.post().uri(this.apiUrl + "/chat/completions").header("Authorization", "Bearer " + this.apiKey)//                .accept(MediaType.TEXT_EVENT_STREAM)                .bodyValue(chatCompletionRequest).retrieve();if (chatCompletionRequest.getStream())return                    responseSpec.bodyToFlux(ChatCompletionResponse.class).onErrorResume(error -> {// 异常处理逻辑 logger.error("bodyToFlux error: {}", error);return Flux.empty();}).filter(response -> {ChatMessage message = response.getChoices().get(0).getMessage();if (message != null) {String content = message.getContent();return StringUtils.isNotEmpty(StringUtils.trim(content));}return false;}).mapNotNull(response -> {try {return objectMapper.writeValueAsString(response);} catch (JsonProcessingException e) {logger.error(e);return null;}}).concatWithValues("[DONE]");else            return     responseSpec.bodyToMono(ChatCompletionResponse.class).onErrorResume(error -> {// 异常处理逻辑   logger.error("bodyToMono error: {}", error);return Mono.empty();}).mapNotNull(response -> {try {return objectMapper.writeValueAsString(response);} catch (JsonProcessingException e) {logger.error(e);return null;}});}

Publisher是一个通用的概念,它代表一个发布者,可以发布数据或事件。在Spring WebFlux中,Flux和Mono都是Publisher的实现类。

试用地址:https://chatgpt-discount.zeabur.app

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/118666.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python+requests实现接口自动化测试

这两天一直在找直接用python做接口自动化的方法&#xff0c;在网上也搜了一些博客参考&#xff0c;今天自己动手试了一下。 一、整体结构 上图是项目的目录结构&#xff0c;下面主要介绍下每个目录的作用。 Common:公共方法:主要放置公共的操作的类&#xff0c;比如数据库sql…

css 左右宽固定,中间自适应——双飞翼布局

最近面试的时候遇到一个提问说&#xff0c;如何做到一个左右宽度固定&#xff0c;中间自适应的布局&#xff0c;我的答案不重要&#xff0c;重要的是不是面试官想听到的答案&#xff0c;这样问大概率他想听到的答案一定是双飞翼布局&#xff0c;所以今天就手敲一个双飞翼布局让…

Tauri打包windows应用配置中文界面

使用 Tauri Rust 开发桌面应用&#xff0c;在 windows 系统上&#xff0c;打包后安装包名称后缀、安装界面、相关说明默认都是英文的。如果要默认显示为中文&#xff0c;则需要在 tauri.conf.json 中配置相应参数。 前言 默认情况下&#xff0c;在 windows 系统打完的 mis 包…

Linux串口驱动

《I.MX6ULL 参考手册》第 3561 页的“Chapter 55 Universal Asynchronous Receiver/Transmitter(UART) I.MX6ULL串口原理 1.1UART与USART UART是异步通信&#xff0c;USART是异步/同步通信&#xff0c;比UART多了一条时钟线 USART 的全称是 Universal Synchronous/Asynchr…

vim练级攻略(精简版)

vim推荐配置: curl -sLf https://gitee.com/HGtz2222/VimForCpp/raw/master/install.sh -o ./install.sh && bash ./install.sh 0. 规定 Ctrl-λ 等价于 <C-λ> :command 等价于 :command <回车> n 等价于 数字 blank字符 等价于 空格&#xff0c;tab&am…

Code Snippet的使用

文章目录 前言Code Snippet:就是咱们在VS中敲的prop、propfull、ctol【构造器快捷键】、for等快捷键&#xff0c;然后按tab键自动生成代码1.VS自带的&#xff1a;prop、propfull、ctol【构造器快捷键】、for等快捷键&#xff0c;直接使用2.自定义Snippet&#xff1a; 巨人的肩膀…

macOS上制作arm64的jdk17镜像

公司之前一直用的openjdk17的镜像&#xff0c;docker官网可以直接下载&#xff0c;但是最近对接的一个项目&#xff0c;对方用的是jdk17&#xff0c;在对接的时候有加解密异常的问题&#xff0c;为了排查是不是jdk版本的问题&#xff0c;需要制作jdk17的镜像。docker官网上的第…

ISO/IEC标准之Mpeg-1到Mpeg21对应哪些ISO/IEC标准(三十八)

简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 人生格言: 人生从来没有捷径,只有行动才是治疗恐惧和懒惰的唯一良药. 更多原创,欢迎关注:Android…

无涯教程-JavaScript - NORMINV函数

NORMINV函数取代了Excel 2010中的NORM.INV函数。 描述 对于指定的平均值和标准差,该函数返回正态累积分布的反函数。 语法 NORMINV (probability,mean,standard_dev)争论 Argument描述Required/OptionalProbabilityA probability corresponding to the normal distributio…

YOLO目标检测——室内场景识别数据集下载分享

目标检测室内场景识别数据集可以广泛应用于各种需要对室内场景进行目标识别和跟踪的领域&#xff0c;包括安防监控、智能家居、物流仓储管理等 数据集点击下载&#xff1a; YOLO室内场景识别数据集&#xff08;一&#xff09;5950图片26类别.rar YOLO室内场景识别数据集&#…

FFmpeg4.3.1+h264在windows下编译与VS2017项目集成

前言 在Android音视频开发中&#xff0c;网上知识点过于零碎&#xff0c;自学起来难度非常大&#xff0c;不过音视频大牛Jhuster提出了《Android 音视频从入门到提高 - 任务列表》&#xff0c;结合我自己的工作学习经历&#xff0c;我准备写一个音视频系列blog。本文是音视频系…

成集云 | 多维表自动查询快递100信息 | 解决方案

源系统成集云目标系统 方案介绍 产品详情 维格表是一种新一代的团队数据协作和项目管理工具&#xff0c;它结合了可视化数据库、电子表格、实时网络协同、低代码开发技术四项功能&#xff0c;且支持API与可视化看板&#xff0c;操作简单&#xff0c;能提升中小企业的数字化生…

【USRP】调制解调系列4:BPSK、QPSK、8PSK、OQPSK、Pi/4DQPSK,基于labview的实现

PSK Phase Shift Keying – 相移键控 在某些调制解调器中用于数据传输的调制系统&#xff0c;在最简单的方式中&#xff0c;二进制调制信号产生0和1。载波相位来表示信号占和空或者二进制1和O。对于有线线路上较高的数据传输速率&#xff0c;可能发生4个或8个不同的相移&…

STM32基础知识点总结

一、基础知识点 1、课程体系介绍 单片机概述arm体系结构STM32开发环境搭建 STM32-GPIO编程-点亮世界的那盏灯 STM32-USART串口应用SPI液晶屏 STM32-中断系统 STM32-时钟系统 STM32-ADC DMA 温湿度传感器-DHT11 2.如何学习单片机课程 多听理论、多理解、有问题及时提问 自己多…

electron笔记无边框窗口、DLL调用、DLL函数返回指针

无边框 const win new BrowserWindow({width: 1290,height: 736,minHeight: 736,minWidth: 1040,maxHeight: 736,maxWidth: 1290,frame: false, // 无边框webPreferences: {// preload: process.env.WEBPACK_DEV_SERVER_URL ? __dirname /preload.js : app://./preload.js,…

进程、操作系统

文章目录 一、冯诺依曼体系&#xff08;Von Neumann Architecture&#xff09;1. 概述2. CPU 二、操作系统&#xff08;Operating System&#xff09;三、进程(process)/任务(task) 一、冯诺依曼体系&#xff08;Von Neumann Architecture&#xff09; 1. 概述 分类 CPU 中央处…

基于图像切割计算轨迹相似度

目录 背景思路与核心代码数值实验优缺点分析参考文献 背景 在前面2文&#xff0c;我们分别讨论了利用夹角余弦来计算轨迹相似度和利用缓冲原理来计算轨迹相似度两种方法&#xff0c;前者可以作为一个baseline提供参考&#xff0c;后者的计算更符合人们的感官和事实&#xff0c…

【ES】笔记-Promise基本使用

笔记-基本使用 一、初始Promise1. 抽象表达:2. 具体表达:为什么要用 Promise?promise的基本流程 二、fs读取文件三、AJAX请求四、Promise封装fs模块五、util.promisify方法六、Promise封装AJAX操作 一、初始Promise 1. 抽象表达: 1. Promise 是一门新的技术(ES6 规范) 2. Pr…

性能提升3-4倍!贝壳基于Flink + OceanBase的实时维表服务

作者介绍&#xff1a;肖赞&#xff0c;贝壳找房&#xff08;北京&#xff09;科技有限公司 OLAP 平台负责人&#xff0c;基础研发线大数据平台部架构师。 贝壳找房是中国最大的居住服务平台。作为居住产业数字化服务平台&#xff0c;贝壳致力于推进居住服务的产业数字化、智能…

2023-9-2 染色法判定二分图

题目链接&#xff1a;染色法判定二分图 #include <iostream> #include <cstring> #include <algorithm>using namespace std;const int N 100010l, M 200010;int n, m; int h[N], e[M], ne[M], idx;int color[N];void add(int a, int b) {e[idx] b, ne[id…