赞
踩
应网友的需要,本篇将介绍下如何使用Vue3去调用Spring AI实现的流式输出接口。整个流程演示基于前后端分离。
目前大模型的流式响应接口所采用的技术主要是两种:WebSocket和SSE。这两种方式都支持服务端主动向客户端发送内容,其中,Websocket是一种双向通信协议,可以在一个连接上进行双向通信,即:客户端可以向服务端发送信息,服务端也可以主动向客户端发送信息。而SSE是基于标准的Http协议实现的,是一种单向信道,即:只支持服务端向客户端发送数据。一般我们主要是通过EventSource
事件源来监听并获取服务端的消息。
回顾流式对话的博客文章,我们使用Spring AI实现的流式接口如下:
// 流式调用 将produces声明为文本事件流
@GetMapping(value = "/stream",produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> stream(String prompt){
// 将流中的内容按顺序返回
return streamingChatClient.stream(prompt).flatMapSequential(Flux::just);
}
对于这个接口的定义,不言而喻是基于Http协议的,而我们使用produces将响应内容声明为事件流,其实也就说明我们的流式接口是一个SSE接口。
一般的,Websocket比较占用资源,因为是双向通信,一旦建立连接不断开,就会一直占用资源,所以不推荐使用Websocket。并且,Websocket的主要应用场景是在线聊天、实时游戏等,与我们的”请求AI,AI响应我们“的场景有所不同。
这里我就直接跳过对SSE请求的原理介绍,感兴趣的同学可以自行谷歌学习。前端发起请求无非就两种技术:axios和fetch,相信大部分人了解axios胜过fetch。但不幸的是,axios并不支持流事件,而fetch支持。因此对接SSE接口需要使用fetch或基于fetch实现的第三方请求库。
这里我推荐一个微软开源的事件流请求库:fetch-event-source
。
fetch-event-source
是基于fetch实现的、用于快速处理事件流请求的第三方库,它简化了事件流请求的操作,便于我们更好地处理这类请求。以发起SSE请求为例:
const ctrl = new AbortController(); fetchEventSource('/api/sse', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ foo: 'bar' }), signal: ctrl.signal, onmessage: (message)=>{ // 处理监听到的消息 }, onclose: ()=>{ // 连接关闭后处理逻辑 }, onerror: (err)=>{ // 发生错误后调用 } // Get请求处理如上相同 });
const url = new URL(baseUrl);
Object.keys(params).forEach(key => url.searchParams.append(key, params[key].toString()));
const requestUrl = url.toString();
fetchEventSource(requestUrl, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
body:null,
signal: ctrl.signal,
});
介绍完基于Post和Get的SSE请求操作后,我们再回过来看最开始的流式接口:
@GetMapping(value = "/stream",produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> stream(String prompt){
// 将流中的内容按顺序返回
return streamingChatClient.stream(prompt).flatMapSequential(Flux::just);
}
调用它,只需这样做:
const BaseUrl = "http://localhost:[port]/stream";
const prompt = "你的问题";
fetchEventSource(BaseUrl + "?prompt=" + prompt, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
body: null,
signal: ctrl.signal,
onmessage: (message)=>{
// 处理响应的数据,该数据是一段一段的
}
});
文章介绍到这里其实就差不多了,动手能力强的同学一定可以写出自己使用Spring AI实现的各种需要传参数的流式接口。在写这篇博客时,我已经实现了一个基于Vue3实现了一个前后端分离的Demo。请求的核心代码摘至于下:
详见可查看仓库代码:https://github.com/NingNing0111/spring-ai-zh-tutorial
import { fetchEventSource } from "@microsoft/fetch-event-source"; class FatalError extends Error {} class RetriableError extends Error {} type ResultCallBack = (e: any | null) => void; const BaseUrl = "http://localhost:8898"; export const postStreamChat = ( author: string, onMessage: ResultCallBack, onError: ResultCallBack, onClose: ResultCallBack ) => { const ctrl = new AbortController(); fetchEventSource(BaseUrl + "/post-chat", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ author: author, }), signal: ctrl.signal, onmessage: onMessage, onerror: (err: any) => { onError(err); }, onclose: () => { onClose(null); }, onopen: async (response: any) => { if (response.ok) { return; } else if ( response.status >= 400 && response.status < 500 && response.status !== 429 ) { throw new FatalError(); } else { throw new RetriableError(); } }, }); }; export const getStreamChat = ( author: string, onMessage: ResultCallBack, onError: ResultCallBack, onClose: ResultCallBack ) => { const ctrl = new AbortController(); fetchEventSource(BaseUrl + "/get-chat?author=" + author, { method: "GET", headers: { "Content-Type": "application/json", }, body: null, signal: ctrl.signal, onmessage: onMessage, onerror: (err: any) => { onError(err); }, onclose: () => { onClose(null); }, onopen: async (response: any) => { if (response.ok) { return; } else if ( response.status >= 400 && response.status < 500 && response.status !== 429 ) { throw new FatalError(); } else { throw new RetriableError(); } }, }); };
spring-ai-v1-stream-chat-demo
项目后,可直接通过http://localhost:8898/index.html
访问网页。Post请求:
Get请求:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。