阿里云通义千问ChatAPI事件流传输案例
核心方案:基于http text/event-stream 事件流
请求配置
/**
* 通义千问流式请求
* @param messages
* @param modelType
*/
async TYQWSteamRequest(messages: chatBaseMessages[], modelType: string) {
const payload = {
model: modelType || 'qwen-max', // 模型名称 必填
input: {
messages: messages.map(({ role, content }) => ({ role, content })),
},
stream: true,
};
// 请求埋点
this.logger.info(`TYQWSteamRequest: reqBody =>${JSON.stringify(payload)};`);
const response = await this.ctx.curl(
'https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'text/event-stream',
'X-DashScope-SSE': 'enable',
},
streaming: true, // 启用流式响应
// 请求数据类型
data: payload,
timeout: 60 * 1000,
}
);
// 处理流式响应
if (response.status === 200) {
// 获取node原生的响应对象
const stream = response.res;
return stream;
} else {
console.error(`请求失败,状态码: ${response.status}`);
}
}
服务中间流程处理
/**
* 提取stream流中的data部分
* @param streamData
* @returns
*/
export const getStreamDataWithJson = (streamData: string) => {
const dataRegex = /data:({.*})/;
const matches = streamData.match(dataRegex);
if (matches && matches.length > 1) {
return matches[1];
}
return null;
};
/**
* 统一处理stream流数据格式
* @param streamData
* @param modelType
*/
export const streamFormater = (streamData: string, modelType: string) => {
if (qytwModels.some(({ value }) => value === modelType)) {
const fromatData = getStreamDataWithJson(streamData);
const objectData = JSON.parse(fromatData);
return objectData?.output?.text;
}
throw new Error('模型暂不支持');
};
/**
* 数据流对话接口
* @param sendChatMessageParams
*/
@Post('/send_chat_message_by_stream')
async sendChatMessageByStream(@Body() body: sendChatMessageParams) {
const { sessionKey, input, modelType } = body;
if (!sessionKey) throw new Error('缺少参数[sessionKey]');
if (!input) throw new Error('缺少参数[input]');
if (!modelType) throw new Error('缺少参数[modelType]');
// 1.历史消息
const { messages, creator } = await this.AiChatService.getAiChatDetailData(
sessionKey
);
if (creator && creator !== this.ctx.user.workid)
throw new Error('禁止操作他人的会话');
// 2. 开始对话
let chatResult = ``;
const reqTime = moment().format('YYYY-MM-DD HH:mm:ss');
const stream = await this.AiChatService.sendChatMessageByStream(
messages,
input,
modelType
);
// 3.处理响应
// 3.1 设置客户端响应头,这里是返回给浏览器
this.ctx.status = 200;
this.ctx.set({
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive',
});
// 3.2 监听数据变化
stream.on('data', chunk => {
const textData = chunk.toString();
const formatData = streamFormater(textData, modelType);
chatResult = formatData;
this.ctx.res.write(JSON.stringify(this.HttpReturn.success(formatData)));
});
// 3.3 监听结束事件
stream.on('end', async () => {
const resTime = moment().format('YYYY-MM-DD HH:mm:ss');
// 3.3.1 记录对话信息
await this.AiChatService.setMessagesBySessionKey(sessionKey, {
modelType,
messages: [
...messages,
{
role: roleType.user,
content: input,
time: reqTime,
},
{
role: roleType.assistant,
content: chatResult,
time: resTime,
},
],
});
// 3.3.2 标记对话完成
this.ctx.res.write(
JSON.stringify(this.HttpReturn.success(`event-stream:end`))
);
});
// 3.4 监听错误事件
stream.on('error', err => {
this.ctx.res.write(`event-stream:error[${err.toString()}]`);
throw new Error(`Stream error: ${err}`);
});
// 保持长链
return new Promise(resolve => {
this.ctx.req.on('end', resolve);
});
}
客户端接收
import { fetchEventSource } from '@microsoft/fetch-event-source';
import { message as MessagePlugin } from 'antd';
export const streamRequest = (path: string, payLoad: any, { onOpen, onMessage, onClose }: any) => {
return new Promise((resolve, reject) => {
fetchEventSource(path, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payLoad),
async onopen(response) {
console.log('onopen', response);
const { status, statusText } = response;
if (status !== 200) {
MessagePlugin.error(statusText);
return;
}
onOpen && onOpen(response);
},
onmessage(msg) {
console.log('onmessage', msg);
onMessage && onMessage(msg);
},
onclose() {
console.log('onclose');
onClose && onClose();
resolve('close');
},
onerror(err) {
reject(err);
console.log(err);
throw new Error(err || 'onerror');
},
});
});
};