赞
踩
我在使用Vue3开发一个chatgpt工具类网站的时候,翻阅了不少博客和github上的一些相关项目,都没能找到适合Vue3去实现stream的流式数据处理。经过踩坑,最终实现了适用直接调chatgpt接口的方法以及改为调用Python后端接口的方法。
默认情况下,当用户从 OpenAI 请求完成时,会生成整个完成,然后再通过单个响应发回,这样可能会造成等待响应时间过长。
“流式传输”,需在调用聊天完成或完成端点时设置 stream=True,这将返回一个对象,该对象将响应作为仅数据服务器发送的事件流回。
messages: 必须是对象数组
类型 | 作用 |
---|---|
system | 设置chatgpt的角色 |
user | 用户输入的内容 |
assistant | chatgpt返回的内容 |
类型 | 默认值 | 取值范围 | 是否必填 |
---|---|---|---|
浮点数 | 1 | 0 - 2 | 否 |
类型 | 默认值 | 取值范围 | 是否必填 |
---|---|---|---|
浮点数 | 1 | 0 - 1 | 否 |
API文档:https://platform.openai.com/docs/api-reference/chat/create
有关实例代码:openai-cookbook/How_to_stream_completions.ipynb at main · openai/openai-cookbook · GitHub
相信你们早就阅读了上面的文档,但还是很迷茫,感觉无从下手...下面说说我的踩坑经历:
我在网上搜索到的信息是,需要一些流式处理库,我就问chatgpt,它给我推荐了以下几种
RxJS:是一个响应式编程库,支持流式处理和异步操作。
Bacon.js:也是一个响应式编程库,提供了一个功能强大的事件流模型,可以用来处理异步事件。
Highland.js:是一个基于流的函数式编程库,提供了广泛的流操作和管道组合功能。
Node.js的stream模块:是一个流式处理库,提供了流处理的核心功能。可以通过其定义自己的流转换器和消费者函数。
lodash-fp:是一个功能强大的函数式编程库,提供了一整套函数式的操作和工具,可以用来方便快捷地进行流处理。
我没走这条路,我重新查询了一波,网上的意思是,可以利用WebSocket方式或SSE的方式去实现长连接,但我都没采纳,最终使用的是fetch去实现请求即可,不用将问题复杂化哈哈哈
- // gpt.js
- import { CHATGPT_API_URL } from '@/common/config.js'
- const OPENAI_API_KEY = '你的接口'
- // TODO 适用直接调chatgpt接口
- export async function* getChatgpt_Multurn_qa(messages) {
- const response = await fetch(CHATGPT_API_URL + '你的url', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Authorization': `Bearer ${ OPENAI_API_KEY }`
- },
- body: JSON.stringify({
- model: 'gpt-3.5-turbo',
- stream: true,
- messages: messages
- })
- });
-
- if (!response.ok) {
- throw new Error(`HTTP error! status: ${response.status}`);
- }
-
- const reader = response.body.getReader();
- let decoder = new TextDecoder();
- let resultData = '';
-
- while (true) {
- const { done, value } = await reader.read();
- if (done) break;
- resultData += decoder.decode(value);
- while (resultData.includes('\n')) {
- const messageIndex = resultData.indexOf('\n');
- const message = resultData.slice(0, messageIndex);
- resultData = resultData.slice(messageIndex + 1);
- if (message.startsWith('data: ')) {
- const jsonMessage = JSON.parse(message.substring(5));
- if (resultData.includes('[DONE]')) {
- break
- }
- const createdID = jsonMessage.created
- yield {
- content: jsonMessage.choices[0]?.delta?.content || '',
- role: "assistant",
- id: createdID
- };
- }
- }
- }
- }
以上是利用迭代器的写法去实现流式输出,我上面的字符串其实是chatgpt响应输出的数据,例如:
{"id":"chatcmpl-7B48ttLhb1iR4JoaCzElQTvxyAgsw","object":"chat.completion.chunk","created":1682871887,"model":"gpt-3.5-turbo-0301","choices":[{"delta":{"content":"."},"index":0,"finish_reason":null}]}
- // vue组件部分代码
-
- const currentDialogId = ref(null)
-
- const dialogId = uniqueId()
- currentDialogId.value = dialogId
-
- // 获取聊天机器人的回复
- for await (const result of getChatgpt_Multurn_qa(messages.value)) {
- // 如果返回的结果 ID 与当前对话 ID 相同,则将聊天机器人的回复拼接到当前对话中
- if (result.id === currentDialogId.value) {
- const index = list.value.findIndex(item => item.id === currentDialogId.value)
- const dialog = list.value[index]
- dialog.content += result.content
- } else {
- currentDialogId.value = result.id
- list.value.push({
- content: result.content,
- role: "assistant",
- id: result.id,
- timestamp: Date.now()
- })
- messages.value.push({
- role: "assistant",
- content: result.content
- })
- }
- }
上面代码比较关键的点就是条件的判断 --- result.id === currentDialogId.value ,到这一步就可以实现chatgpt的流式输出啦,响应速度是非常快的!!!
补充:
1. list 是用户角色和AI角色的对话数组,可以传递给子组件去遍历渲染不同角色的聊天,在文章尾部将展示实现Markdown代码块的步骤
2. message 是将user以及assistant的所有历史记录push进去,是实现多轮对话的关键
- # ChatGPT流式输出接口
-
- ## 接口路径
-
- ```bash
- https://后端提供的url
- ```
-
- ## 请求方式
-
- **POST**
-
- ## 请求参数
-
-
- ```bash
- {
- "messages": [
- {
- "role": "user",
- "content": "你好"
- }
- ]
- }
- ```
-
- ## 请求参数说明
-
- ```bash
- messages: 消息体
- ```
-
-
- ## curl
-
- ```bash
- curl --location 'https://后端提供的url' \
- --header 'Content-Type: application/json' \
- --data '{
- "messages": [
- {
- "role": "user",
- "content": "你好"
- }
- ]
- }'
- ```
-
-
- ## 返回数据
-
- ```bash
- {
- "id": "chatcmpl-7GpjNUPkhPZF0MtJBqTMvW2bbWPPG",
- "object": "chat.completion.chunk",
- "created": 1684246457,
- "model": "gpt-3.5-turbo-0301",
- "choices": [
- {
- "delta": {
- "role": "assistant"
- },
- "index": 0,
- "finish_reason": null
- }
- ]
- }
- {
- "id": "chatcmpl-7GpjNUPkhPZF0MtJBqTMvW2bbWPPG",
- "object": "chat.completion.chunk",
- "created": 1684246457,
- "model": "gpt-3.5-turbo-0301",
- "choices": [
- {
- "delta": {
- "content": "你"
- },
- "index": 0,
- "finish_reason": null
- }
- ]
- }
- {
- "id": "chatcmpl-7GpjNUPkhPZF0MtJBqTMvW2bbWPPG",
- "object": "chat.completion.chunk",
- "created": 1684246457,
- "model": "gpt-3.5-turbo-0301",
- "choices": [
- {
- "delta": {
- "content": "好"
- },
- "index": 0,
- "finish_reason": null
- }
- ]
- }
- {
- "id": "chatcmpl-7GpjNUPkhPZF0MtJBqTMvW2bbWPPG",
- "object": "chat.completion.chunk",
- "created": 1684246457,
- "model": "gpt-3.5-turbo-0301",
- "choices": [
- {
- "delta": {
- "content": "!"
- },
- "index": 0,
- "finish_reason": null
- }
- ]
- }
- {
- "id": "chatcmpl-7GpjNUPkhPZF0MtJBqTMvW2bbWPPG",
- "object": "chat.completion.chunk",
- "created": 1684246457,
- "model": "gpt-3.5-turbo-0301",
- "choices": [
- {
- "delta": {},
- "index": 0,
- "finish_reason": "stop"
- }
- ]
- }
- ```
-
- ## 返回参数说明
-
- ```bash
- role = assistant, 开始输出
- finish_reason = stop, 输出结束
- finish_reason = null, 正在输出
- content 输出内容
- ```
根据文档,我们只需要小小改动代码
- // TODO 改用chatgpt接口
- import { _BASE_API_URL } from '@/common/config.js'
-
- // 流式输出接口
- export async function* getChatgpt_Multurn_qa(messages, onStreamDone) {
- const response = await fetch(_BASE_API_URL + `你的url`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({
- messages: messages
- })
- })
-
- if (!response.ok) {
- throw new Error(`HTTP error! status: ${response.status}`)
- }
-
- const reader = response.body.getReader()
-
- let result = ''
- let done = false
-
- while (!done) {
- const { value, done: streamDone } = await reader.read()
-
- if (value) {
- const decoder = new TextDecoder()
- result += decoder.decode(value)
- const lines = result.split('\n')
- result = lines.pop()
-
- for (const line of lines) {
- try {
- const json = JSON.parse(line)
- if (json.choices && json.choices.length > 0) {
- const content = json.choices[0].delta.content
- if (content) {
- yield { id: json.created, content }
- }
- }
- if (json.choices && json.choices[0].finish_reason === 'stop') {
- done = true
- onStreamDone()
- break
- }
- } catch (e) {
- console.error(e)
- }
- }
- }
- if (streamDone) {
- done = true;
- }
- }
- }
上面代码多了个onStreamDone参数,是我需要利用它处理响应完成的逻辑,没有这个需求的伙伴可以适当删改,接下来再看看父组件如何获取数据吧
- // vue父组件
- for await (const result of getChatgpt_Multurn_qa(messages.value, onStreamDone)) {
- if (currentConversationId.value === null) {
- currentConversationId.value = result.id;
- }
- if (result.id === currentConversationId.value) {
- const index = list.value.findIndex(item => item.id === currentConversationId.value);
- const dialog = list.value[index];
- dialog.content += result.content;
- } else {
- currentConversationId.value = result.id;
- list.value.push({
- content: result.content || '',
- role: "assistant",
- id: result.id,
- timestamp: Date.now()
- });
- messages.value.push({
- role: "assistant",
- content: result.content || ''
- });
- }
- }
- // 父组件
-
- <session-box :list="list" @sent="handleSent"></session-box>
- // 子组件
- const props = defineProps({
- list: {
- type: Array,
- default: []
- }
- })
-
- const { list } = toRefs(props)
-
- const sessionList = ref(null)
-
- const sortedList = computed(() => {
- return list.value.slice().sort((a, b) => a.timestamp - b.timestamp)
- })
说明:
通过
computed
创建了一个名为sortedList
的计算属性,该属性返回一个已排序的list
数组副本。在排序过程中,使用了slice
方法创建了一个数组副本,以避免直接修改原始数组。排序方式为按照每个数组元素的timestamp
属性升序排序。在模板中遍历循环
sortedList
的内容就能实现用户和ai对话啦
介绍一下md-editor-v3
官网:MdEditorV3 Documentation (imzbf.github.io)
文档说明:MdEditorV3 Documentation (imzbf.github.io)
它提供了一些基础的 Markdown 编辑功能,如加粗、斜体、标题、无序列表、有序列表、引用、代码块等。除此之外,它还支持上传图片、撤销/重做、全屏等功能。md-editor-v3 的优点是易于使用、易于扩展,并且提供了一些定制化的选项。但是我只是想实现代码块,故解构出MdPreview
使用:
- // 模板中
- <MdPreview
- :showCodeRowNumber="true" // 显示行号
- :modelValue="item.content"
- />
-
-
- import { MdPreview } from 'md-editor-v3'
- import 'md-editor-v3/lib/style.css'
效果图:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。