基于vue2 + flask实现对话大模型
文章目录
前言
这篇文章主要讲使用vue2 + flask实现对话大模型的实现历程。在实现过程中,我尝试了两种数据处理方式:
第一种方式是使用axios发送post请求,这种请求方式相对比较简单,前端只需要调用post接口,后端处理完毕后直接返回,前端拿到数据直接显示。
这种实现方式有一种弊端:在后台处理数据比较久时,前端一直会处于一个等待的状态,给用户带来不好的体验。为解决这个弊端,我又尝试了第二种方式。
第二种方式是使用fetch API进行流式返回和调用。这种方式就可以实现模型生成文本的同时逐步将结果发送给客户端,实现实时的交互体验。
注:查资料显示:Axios 是一个流行的 JavaScript HTTP 客户端,它本身并不直接支持流式数据处理
以下将介绍两种方式的实现方法:
一、通过axios请求的方式实现对话
后端
接口:
from flask import Flask
app = Flask(__name__)
app.secret_key = b'_dsay2L"F4Q8z\n\xec]/'
@app.route('/openAiDeekSeek', methods=['post'])
def getAisDataToOpenAi():
requestParams = request.get_json()
requestContext = requestParams.get('requestContext')
openAiResponse = openAi_entry(requestContext)
return jsonify(code=200, data=openAiResponse)
处理函数:
from openai import OpenAI
client = OpenAI(api_key="xxx", base_url="xxx")
def openAi_entry(requestContext):
response = client.chat.completions.create(
model="deepseek-chat",
messages=requestContext,
stream=False
)
# 正常输出
return response.choices[0].message.content
前端
<template>
<div class="diologMain" ref="diologMain">
<div v-for="(msgItem, index) in message" :key="index">
<div v-if="msgItem.role === 'user'" class="userContent">
<div class="msgContent">{{ msgItem.content }}</div>
</div>
<div v-else-if="msgItem.role === 'assistant'" class="assistantContent">
<div class="assisitantItem">
<img class="assisstantIcon" src="@/assets/img/ui/ai.png" alt="">
<span class="asssmsg">{{ msgItem.content }}</span>
</div>
</div>
</div>
</div>
</template>
<script>
import { conversationByOnce } from '@/request/modules/dataApi.js'
export default{
name: 'dialogIndex',
data() {
return {
message: []
}
},
methods: {
// 调用openai接口 - 一次性请求
sendToAiRequest(requestData) {
const that = this
conversationByOnce(requestData).then(res => {
if (res.status === 200) {
const responseData = res.data.data
const assistantMag = { 'role': 'assistant', content: responseData }
that.addChatMsgTo(assistantMag)
} else {
that.isDiologLoading = false
const errorMsg = { 'role': 'assistant', content: '请求失败,请重试' }
that.addChatMsgTo(errorMsg)
}
})
},
// 增加对话记录
addChatMsgTo(chatMsg) {
this.message.push(chatMsg)
this.userInput = ''
this.$nextTick(() => {
this.scrollToBottom()
})
},
// 自动跳转到对话框底部
scrollToBottom() {
const diologMainContainer = this.$refs.diologMain
diologMainContainer.scrollTop = diologMainContainer.scrollHeight
},
}
}
</script>
二、通过fetch API流式输出方式实现对话
后端
接口
from flask import Flask
app = Flask(__name__)
app.secret_key = b'_dsay2L"F4Q8z\n\xec]/'
@app.route('/openAiDeekSeek', methods=['post'])
def getAisDataToOpenAi():
requestParams = request.get_json()
requestContext = requestParams.get('requestContext')
return openAi_entry_by_stream(requestContext)
处理函数
from openai import OpenAI
client = OpenAI(api_key="xxx", base_url="xxx")
def openAi_entry(requestType, jsonData, shipInfo):
# 流式输出
response = client.chat.completions.create(
model="deepseek-chat",
messages=jsonData,
stream=True
)
def generate():
for chunk in response:
content = chunk.choices[0].delta.content
if content:
print("content:", content, end='', flush=True)
yield content
return Response(generate(), content_type="text/plain")
前端 - JavaScript
// 流式发送请求
sendToAiRequest(userMsg, requestData) {
const that = this
fetch('/local/openAiDeekSeek', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(requestData)
}).then(response => {
if (response.ok) return response.body
else throw new Error('Network response was not ok');
})
.then(stream => {
const reader = stream.getReader()
const decoder = new TextDecoder()
const assistantMsgIndex = that.message.push({ role: 'assistant', content: '' }) - 1;
async function readAndLog() {
const { value, done } = await reader.read();
if (done) return
const chunk = decoder.decode(value, { stream: true });
that.updateMessageAndScroll(chunk, assistantMsgIndex)
await readAndLog();
}
readAndLog().catch(err => console.error('Failed to read stream: ', err));
}).catch(error => console.error('Failed to fetch stream: ', error))
},
// 判断对话框是否在最底下
isScrolledToBottom(container) {
return container.scrollTop + container.clientHeight >= container.scrollHeight - 5
},
// 更新数据并滚动
updateMessageAndScroll(chunk, assistantMsgIndex) {
// 更新现有的对话对象的内容
this.$set(this.message, assistantMsgIndex, {
...this.message[assistantMsgIndex],
content: this.message[assistantMsgIndex].content + chunk
});
// 跳转到最下面
const diologMainContainer = this.$refs.diologMain
if (this.isScrolledToBottom(diologMainContainer)) {
this.$nextTick(() => {
diologMainContainer.scrollTop = diologMainContainer.scrollHeight;
});
}
},
总结
以上仅仅是部分代码作为参考,具体实现逻辑还需根据个人情况完善。总体来说,流式调用更加符合大模型对话中的用户体验,希望读者可以从中得到一些启发。如有问题欢迎各位大佬讨论。
2025年1月22日补充
记录一下部署到nginx服务器时,流式输出效果消失:
个人理解:nginx服务器默认并不支持流式接收数据,需要修改配置手动打开流式接收数据模式。
在服务器中,容器中的nginx配置项如果没有映射到宿主机上,则nginx配置项一般在容器中的路径/etc/nginx/nginx.conf,如果映射到宿主机上,在对应的代理处添加配置即可。
在配置中的代理添加以下配置:
proxy_buffering off; # 关键:关闭代理缓冲
proxy_http_version 1.1; # 使用 HTTP/1.1 支持分块传输
proxy_set_header Connection ""; # 清除 Connection 头,避免 close 导致问题
proxy_set_header Host $host;
- 配置入口文件:/etc/nginx/nginx.conf:
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
include /etc/nginx/conf.d/default.conf; # 默认的配置路径
}
- 默认配置文件:/etc/nginx/conf.d/default.conf
location /openApi/ {
proxy_pass http://xxx/;
proxy_buffering off;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
}