0


SpringBoot接入通义千问实现个人ChatGPT

1、ChatGPT的热度

ChatGPT是由美国人工智能实验室OpenAI开发的一个对话AI模型,于2022年11月正式推出。自推出以来,ChatGPT因其出色的文本生成和对话交互能力而在全球范围内迅速走红。上线短短两个月,ChatGPT已获得1亿月度活跃用户,成为历史上增长最快的面向消费者的应用。

ChatGPT的爆火在业界掀起了惊涛骇浪,其用户增长速度刷新了消费级应用程序的记录。不少和ChatGPT“聊过天”的网友纷纷感叹,“只有你想不到,没有ChatGPT办不成的”。在一位工程师的诱导下,ChatGPT竟写出了毁灭人类的计划书,这进一步引发了人们对其潜在危险性的担忧。

ChatGPT的火热也带动了资本市场相关上市公司股票的普涨,包括AIGC、芯片算力、光模块等板块的普遍上涨。同时,国内互联网公司接连宣布类似ChatGPT的项目存在,如百度的类ChatGPT项目“文心一言”、阿里的“通义千问”。

2、前言准备

在国内也有许多的GPT平台,要使用的步骤都是一样的,先开通服务,再申请Key。
在使用的过程中,需要与流式编程搭配使用才能得到最好的效果,所以了解和掌握流式编程也是很重要的一步。

2.1、开通服务

(1)登录“阿里云”官网。
(2)搜索“通义千问”
在这里插入图片描述(3)开通服务
在这里插入图片描述
确认开通
在这里插入图片描述
开通成功
在这里插入图片描述

2.2、reactor流式响应

Spring流式编程是一种基于流的处理方式,它将数据流作为主要处理对象。

功能:

  • 数据处理:Spring流式编程能够处理大量的数据,并将数据转换成所需的形式或结构。
  • 异步处理:Spring流式编程支持异步处理,能够并行处理多个数据流,提高系统的吞吐量和响应速度。
  • 实时性:由于Spring流式编程支持异步处理和并行处理,因此它能够实现实时数据处理。

好处:

  • 提高性能:由于Spring流式编程采用异步处理和并行处理,因此它能够提高系统的性能和响应速度。
  • 简化开发:Spring流式编程提供了丰富的API和工具,简化了流式处理应用程序的开发过程。
  • 易于维护:由于Spring流式编程采用声明式编程风格,代码结构清晰简洁,易于维护和调试。
  • 灵活性强:Spring流式编程具有很强的灵活性,能够处理各种不同形式和结构的数据。

特点:

  • 流式处理:Spring流式编程将数据看作流来处理,可以同时处理多个数据流。
  • 事件驱动:Spring流式编程采用事件驱动的架构,能够快速响应用户输入和系统事件。
  • 异步处理:Spring流式编程支持异步处理,能够并行处理多个数据流,提高系统的吞吐量和响应速度。
  • 声明式编程:Spring流式编程采用声明式编程风格,通过简单的注解和XML配置来简化开发过程。

Flux 和 Mono 是 Reactor 中两个最基本的类型,是 Spring WebFlux 核心概念,表示 Reactor 中的数据流。

Flux和Mono本质上也是两个Publisher。

2.2.1、Flux流式对象

Flux 是 Project Reactor 中用于表示非确定性、0 到多个元素的类型。也就是说,Flux 可以是空的,也可以有一个或多个元素。它是响应式编程中的"热"流,类似于传统的迭代器,但更加强大和灵活。你可以把它想象成从一个数据源不断地流出的数据,可以监听这个数据流,当有新的数据出现时,会收到通知。
静态创建 Flux 的方法常见的包括 just()、range()、interval() 以及各种以 from- 为前缀的方法组等。

(1)combineLatest方法
用于组合多个 Flux(反应式流)的值,当这些流中的任何一个发出新的值时,它就会发射一个新的组合值。

publicstaticvoidmain(String[] args){// 创建三个 Flux  Flux<String> flux1 =Flux.just("Hello");Flux<String> flux2 =Flux.just("World");Flux<String> flux3 =Flux.just("!");// 使用 combineLatest 组合这三个 Flux  Flux<String> combined =Operators.combineLatest(flux1, flux2, flux3,(s1, s2, s3)-> s1 + s2 + s3);// 输出结果:HelloWorld!  
   combined.subscribe(System.out::println);}

(2)concat类型方法
Flux对象的一个操作符,用于按顺序连接两个或多个Flux流,以便它们可以像单个流一样被消费。这意味着第一个Flux流的所有元素都被发射后,第二个Flux流的元素才会开始发射,依此类推,直到所有的Flux流都被完全消费。

publicstaticvoidmain(String[] args){Flux<Integer> flux1 =Flux.just(1,2,3);Flux<Integer> flux2 =Flux.just(4,5,6);// 使用concat按顺序连接flux1和flux2  Flux<Integer> concatenatedFlux =Flux.concat(flux1, flux2);// 订阅并打印结果  
      concatenatedFlux.subscribe(System.out::println);// 输出将是:1, 2, 3, 4, 5, 6  }

(3)create方法
这个方法允许你创建一个新的 Flux,并允许你直接控制其发射的元素。

publicstaticvoidmain(String[] args){Flux<Object> objectFlux =Flux.create(c ->{for(int i =0; i <5; i++) c.next(i);// 添加元素
         c.complete();// 添加完成});// 01234
     objectFlux.subscribe(System.out::println);}

(4)push方法
用于将元素推入到Flux中。与传统的Flux.next方法不同,Flux.push方法允许更低级别的控制和优化。
Flux.push方法的使用需要具备一定的反应式编程经验和技能,因为它涉及到低级别的并发控制和线程安全问题。在大多数情况下,使用Flux.next方法已经足够满足需求,而Flux.push方法更适合于需要更精细控制或优化性能的场景。

publicstaticvoidmain(String[] args){Flux<Object> push =Flux.push(emitter ->{for(int i =0; i <5; i++) emitter.next(i);// 添加元素
        emitter.complete();// 添加完成});// 01234
    push.subscribe(System.out::println);}

(5)defer方法
Flux.defer()方法在Reactor中是用来延迟创建Flux的。这个方法返回一个新的Flux,这个Flux在订阅发生时才开始创建并执行原始的Flux。

publicstaticvoidmain(String[] args){Flux<String> flux =Flux.defer(()->Flux.just("create and executor"));// 此时才会真的创建并执行:01234
    flux.subscribe(System.out::println);}

(6)empty方法
创建一个空的Flux对象。

(7)error方法
Flux.error()方法在Reactor中是用来创建一个在订阅后立即发射一个错误的Flux的。这个方法接收一个Throwable参数,这个参数表示错误。当订阅这个Flux时,它会立即发射这个错误给订阅者。

publicFlux<String>getFlux(){returnFlux.just("Request").flatMap(request ->{if(request.equals("Invalid")){returnFlux.error(newIllegalArgumentException("Invalid request"));}else{returnFlux.just("Response");}});}

(8)from类型方法
Flux.from()方法是一个将其他数据源转换为Flux流的方法。它可以将各种数据源(如集合、迭代器、异步数据源等)转换为Flux对象,以便在反应式编程中使用。

publicstaticvoidmain(String[] args){Integer[] array =newInteger[]{1,2,3,4,5};// from、fromArray、fromStream、fromIteratorFlux<Integer> flux =Flux.fromArray(array);
    flux.subscribe(System.out::println);}

(9)just方法
用于创建一个包含指定元素的Flux。这个方法可以指定序列中包含的所有元素,并且创建出来的Flux序列在发布这些元素之后会自动结束。

Flux<String> flux =Flux.just("Hello","World","!");

(10)其他常用方法
方法名称描述Flux.merge用于合并多个Flux流Flux.range用于生成指定范围内整数序列的FluxFlux.using用于在Flux的生命周期内使用一个外部资源Flux.collect用于将Flux中的元素收集到某种容器或数据结构中Flux.distinct用于从Flux中过滤掉重复的元素Flux.doOnEach用于在Flux中的每个元素上执行特定的操作 ,这些操作将在每个元素上单独执行,并且不会影响Flux流的其他操作。Flux.filter用于对Flux中的元素进行过滤Flux.flatMap用于将Flux中的每个元素进行一对多的转换。它可以将每个元素映射成一个新的Flux,然后将所有这些Flux合并成一个单一的Flux。Flux.groupBy用于将Flux中的元素按照指定的键进行分组

2.2.2、Mono流式对象

Mono 是 Project Reactor 中用于表示 0 或 1 个元素的类型。也就是说,Mono 可以是空的,也可以有一个元素。它是响应式编程中的"冷"流,它可能不会产生任何数据,或者在某些情况下可能会产生大量的数据。你可以把它想象成从数据源获取一个数据,然后你可以在任何时候获取这个数据。

Flux对象有的方法Mono也基本都有。

2.3、前端EventSource

EventSource是一种在HTML5中用于实现服务器推送事件的技术。它允许服务器发送事件流(Server-Sent Events)到客户端,而无需客户端主动向服务器发送请求。

EventSource提供了一种简单的方式来接收服务器端发送的事件数据。它通过建立长连接,在服务器有新的数据时,会自动将数据推送给客户端。与传统的轮询方式相比,EventSource使用了长连接,可以节省带宽和资源,同时提供更好的实时性。

在HTML中,使用EventSource可以通过创建一个EventSource对象来实现。该对象可以指定服务器的URL,然后通过监听不同的事件来接收服务器发送的数据。例如,当服务器发送一个名为"message"的事件时,客户端可以监听该事件并执行相应的操作。

newEventSource(url,?EventSourceInitDict);// url:需要监听的地址// EventSourceInitDict:携带的参数

3、接入通义千问

通义千问是阿里云推出的一个超大规模的语言模型,具有多轮对话、文案创作、逻辑推理、多模态理解、多语言支持等多种功能。它能够跟人类进行多轮的交互,也融入了多模态的知识理解,且有文案创作能力,能够续写小说、编写邮件等。通义千问在2023年4月7日开始邀请测试,4月11日在2023阿里云峰会上揭晓。4月18日,钉钉正式接入阿里巴巴“通义千问”大模型。2023年9月13日,阿里云宣布通义千问大模型已首批通过备案,并正式向公众开放。通义千问APP在各大手机应用市场正式上线,所有人都可以通过APP直接体验最新模型能力。此外,通义千问在2023年12月22日成为首个“大模型标准符合性评测”中首批通过评测的四款国产大模型之一,在通用性、智能性等维度均达到国家相关标准要求。

3.1、后端

后端使用SpringBoot + Reactor实现。

3.1.1、导入依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--导入流式编程依赖--><dependency><groupId>io.projectreactor</groupId><artifactId>reactor-core</artifactId></dependency><!--导入通义千问 DK --><dependency><groupId>com.alibaba</groupId><artifactId>dashscope-sdk-java</artifactId><version>2.10.1</version></dependency>

3.1.2、配置

(1)在application.yaml文件中编写API-KEY。

server:port:8081ai-api-key: YOUR KEY

(2)注入Generation对象

用户可以通过与Generation对象进行交互,获得自然、流畅、准确的回答或任务完成结果,从而更加高效地与机器进行交互。这种交互方式能够减少用户对传统搜索引擎或问答系统的依赖,提高信息获取和任务完成的效率。同时,Generation对象也可以用于实现自然语言生成、对话生成、文本摘要、文本改写等多种应用场景。

@ConfigurationpublicclassAiConfig{@BeanpublicGenerationgeneration(){returnnewGeneration();}}

3.1.3、编写接口

@RestController@RequestMapping(value ="/ai")publicclassTestAi{@Value("${ai-api-key}")privateString appKey;@ResourceprivateGeneration generation;@PostMapping(value ="/send", produces =MediaType.TEXT_EVENT_STREAM_VALUE)publicFlux<ServerSentEvent<String>>aiTalk(@RequestBodyString question,HttpServletResponse response)throwsNoApiKeyException,InputRequiredException{Message message =Message.builder().role(Role.USER.getValue()).content(question).build();QwenParam qwenParam =QwenParam.builder().model(Generation.Models.QWEN_PLUS).messages(Collections.singletonList(message)).topP(0.8).resultFormat(QwenParam.ResultFormat.MESSAGE).enableSearch(true).apiKey(appKey).incrementalOutput(true).build();Flowable<GenerationResult> result = generation.streamCall(qwenParam);returnFlux.from(result).map(m ->{// GenerationResult对象中输出流(GenerationOutput)的choices是一个列表,存放着生成的数据。String content = m.getOutput().getChoices().get(0).getMessage().getContent();returnServerSentEvent.<String>builder().data(content).build();}).publishOn(Schedulers.boundedElastic()).doOnError(e ->{Map<String,Object> map =newHashMap<>(){{put("code","400");put("message","出现了异常,请稍后重试");}};try{
                        response.getOutputStream().print(JSONObject.toJSONString(map));}catch(IOException ex){thrownewRuntimeException(ex);}});}}

(1)Message对象
用户与模型的对话历史。list中的每个元素形式为{“role”:角色, “content”: 内容}。
role可以选值:

publicenumRole{USER("user"),ASSISTANT("assistant"),BOT("bot"),SYSTEM("system"),ATTACHMENT("attachment");privatefinalString value;privateRole(String value){this.value = value;}publicStringgetValue(){returnthis.value;}}

role 方法是用于设置消息的角色(或类型)的方法。这个方法允许您为消息指定一个特定的角色,以便在处理消息时可以对其进行分类或特殊处理。

(2)Model对象

指定用于对话的通义千问模型名。

publicstaticclassModels{/** @deprecated */@DeprecatedpublicstaticfinalStringQWEN_V1="qwen-v1";publicstaticfinalStringQWEN_TURBO="qwen-turbo";publicstaticfinalStringBAILIAN_V1="bailian-v1";publicstaticfinalStringDOLLY_12B_V2="dolly-12b-v2";/** @deprecated */@DeprecatedpublicstaticfinalStringQWEN_PLUS_V1="qwen-plus-v1";publicstaticfinalStringQWEN_PLUS="qwen-plus";publicstaticfinalStringQWEN_MAX="qwen-max";publicModels(){}}

(3)topP/topK方法
topP:生成过程中核采样方法概率阈值,例如,取值为0.8时,仅保留概率加起来大于等于0.8的最可能token的最小集合作为候选集。取值范围为(0,1.0),取值越大,生成的随机性越高;取值越低,生成的确定性越高。

topK:生成时,采样候选集的大小。例如,取值为50时,仅将单次生成中得分最高的50个token组成随机采样的候选集。取值越大,生成的随机性越高;取值越小,生成的确定性越高。默认不传递该参数,取值为None或当top_k大于100时,表示不启用top_k策略,此时,仅有top_p策略生效。

(4)enableSearch方法
模型内置了互联网搜索服务,该参数控制模型在生成文本时是否参考使用互联网搜索结果。取值如下:

  • True:启用互联网搜索,模型会将搜索结果作为文本生成过程中的参考信息,但模型会基于其内部逻辑“自行判断”是否使用互联网搜索结果。
  • False(默认):关闭互联网搜索。

(5)incrementalOutput方法
控制流式输出模式,即后面内容会包含已经输出的内容;设置为True,将开启增量输出模式,后面输出不会包含已经输出的内容,您需要自行拼接整体输出。默认是false;

False:
I
I like
i like apple
True:
I
like
apple

该参数只能与stream输出模式配合使用。

更多参数描述请浏览阿里官方文档:https://help.aliyun.com/zh/dashscope/developer-reference/api-details?spm=a2c4g.11186623.0.nextDoc.24ba12b0zyzTIv

3.2、前端

前端使用的是目前市面上流行的框架-Vue。

3.2.1、安装EventSource

EventSource是HTML5内置的一个对象,但是EventSource只支持Get请求,在很多情况下Get请求并不能满足要求,所以我们需要安装支持Post请求的EventSource。

npminstall @microsoft/fetch-event-source

使用

import{ fetchEventSource }from'@microsoft/fetch-event-source';exportdefault{data(){return{show:false,list:[],}},mounted(){fetchEventSource('http://localhost:8000/user/ai/chat',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({"question":"java是什么?"}),onmessage(event){// 接收数据
                console.log(event);},onclose(){// 数据传输完毕后就会关闭流}})}}

3.2.2、安装Markdown

为啥要安装Markdown咧?因为在AI生成的数据中,会有一些特殊的语法需要文本编辑器才能解析,所以就用Markdown才能更好的展示。

npminstall markdown-it --save
<divv-html="markdown.render(item.answer)"class="answer_message"></div>
import MarkdownIt from'markdown-it'exportdefault{data(){return{markdown:newMarkdownIt(),}},}

Markdown-it官网:https://markdown-it.docschina.org/

3.2.3、实现事件监听

search(){if(this.query.trim().length ==0){showNotify({type:'warning',message:'消息内容不能为空'});return;}this.historyList.push({question:this.query,answer:''});this.query ="";let thiz =this;let length =this.historyList.length;fetchEventSource(this.$api.CHAT,{method:'POST',headers:{'Content-Type':'application/json',},body:JSON.stringify({question: thiz.historyList[length -1].question}),onmessage(event){//在此处的this不是外部的this,而是方法的调用者的this,所以需要在外部定义一个变量指向this
            thiz.historyList[length -1].answer += event.data;},onclose(){let temp = thiz.historyList[thiz.historyList.length -1];let body ={sessionId: thiz.$route.params.sessionId,question: temp.question,answer: temp.answer
            }
            thiz.$http.post(thiz.$api.SYNC_MESSAGE, body).then(result=>{
                console.log(result);})}})}

3.3、效果

省略了CSS样式。
在这里插入图片描述

4、总结

SpringBoot接入通义千问的实践过程,是一个富有挑战和收获的技术之旅。首先,我们需要理解通义千问的API接口和数据格式,这涉及到对其功能和数据模型的深入了解。在接入过程中,我们主要使用了SpringBoot提供的RestTemplate或WebClient进行API请求,通过JSON数据格式进行数据交互。

在这个过程中,我们面临的主要挑战是网络延迟和数据同步的问题。为了解决这些问题,我们采用了异步处理和缓存策略,优化了API请求的频率,提升了数据获取的效率。

从这次实践中,我们深刻体会到技术发展的快速和多变。未来,我们将继续关注通义千问的新特性和API变化,不断优化我们的接入方案,提升系统的稳定性和效率。同时,我们也会将这种技术应用于更多的业务场景,推动业务的智能化发展。


本文转载自: https://blog.csdn.net/qq_45515182/article/details/135820526
版权归原作者 中国胖子风清扬 所有, 如有侵权,请联系我们删除。

“SpringBoot接入通义千问实现个人ChatGPT”的评论:

还没有评论