perf: 流响应

This commit is contained in:
Archer
2023-03-14 14:58:35 +08:00
parent e12f97a73b
commit 6244f6c1fb
7 changed files with 160 additions and 167 deletions

View File

@@ -6,26 +6,19 @@ import { getOpenAIApi, authChat } from '@/service/utils/chat';
import { httpsAgent } from '@/service/utils/tools';
import { ChatCompletionRequestMessage, ChatCompletionRequestMessageRoleEnum } from 'openai';
import { ChatItemType } from '@/types/chat';
import { openaiError } from '@/service/errorCode';
import { jsonRes } from '@/service/response';
import { PassThrough } from 'stream';
/* 发送提示词 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
res.setHeader('Content-Type', 'text/event-stream;charset-utf-8');
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('X-Accel-Buffering', 'no');
res.setHeader('Cache-Control', 'no-cache, no-transform');
res.on('close', () => {
res.end();
});
req.on('error', () => {
res.end();
});
const { chatId, windowId } = req.query as { chatId: string; windowId: string };
const { chatId, windowId, prompt } = req.body as {
prompt: ChatItemType;
windowId: string;
chatId: string;
};
try {
if (!windowId || !chatId) {
if (!windowId || !chatId || !prompt) {
throw new Error('缺少参数');
}
@@ -35,15 +28,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const model: ModelType = chat.modelId;
const map = {
Human: ChatCompletionRequestMessageRoleEnum.User,
AI: ChatCompletionRequestMessageRoleEnum.Assistant,
SYSTEM: ChatCompletionRequestMessageRoleEnum.System
};
// 读取对话内容
const prompts: ChatItemType[] = (await ChatWindow.findById(windowId)).content;
prompts.push(prompt);
// 长度过滤
// 上下文长度过滤
const maxContext = model.security.contextMaxLen;
const filterPrompts =
prompts.length > maxContext + 2
@@ -51,6 +40,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
: prompts.slice(0, prompts.length);
// 格式化文本内容
const map = {
Human: ChatCompletionRequestMessageRoleEnum.User,
AI: ChatCompletionRequestMessageRoleEnum.Assistant,
SYSTEM: ChatCompletionRequestMessageRoleEnum.System
};
const formatPrompts: ChatCompletionRequestMessage[] = filterPrompts.map(
(item: ChatItemType) => ({
role: map[item.obj],
@@ -62,9 +56,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
role: ChatCompletionRequestMessageRoleEnum.System,
content: '如果你想返回代码,请务必声明代码的类型!并且在代码块前加一个换行符。'
});
// 获取 chatAPI
const chatAPI = getOpenAIApi(userApiKey);
// 发出请求
const chatResponse = await chatAPI.createChatCompletion(
{
model: model.service.chatModel,
@@ -84,58 +79,35 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
'response success'
);
let AIResponse = '';
// 创建响应流
const pass = new PassThrough();
pass.pipe(res);
// 解析数据
const decoder = new TextDecoder();
const onParse = async (event: ParsedEvent | ReconnectInterval) => {
if (event.type === 'event') {
const data = event.data;
if (data === '[DONE]') {
// 存入库
await ChatWindow.findByIdAndUpdate(windowId, {
$push: {
content: {
obj: 'AI',
value: AIResponse
}
},
updateTime: Date.now()
});
res.write('event: done\ndata: \n\n');
return;
}
try {
const json = JSON.parse(data);
const content: string = json?.choices?.[0].delta.content || '\n';
// console.log('content:', content)
res.write(`event: responseData\ndata: ${content.replace(/\n/g, '<br/>')}\n\n`);
AIResponse += content;
} catch (error) {
error;
}
if (event.type !== 'event') return;
const data = event.data;
if (data === '[DONE]') return;
try {
const json = JSON.parse(data);
const content: string = json?.choices?.[0].delta.content || '';
if (!content) return;
// console.log('content:', content)
pass.push(content.replace(/\n/g, '<br/>'));
} catch (error) {
error;
}
};
for await (const chunk of chatResponse.data as any) {
const parser = createParser(onParse);
parser.feed(decoder.decode(chunk));
parser.feed(decodeURIComponent(chunk));
}
pass.push(null);
} catch (err: any) {
console.log('error->', err?.response, '===');
let errorText = 'OpenAI 服务器访问超时';
if (err.code === 'ECONNRESET' || err?.response?.status === 502) {
errorText = '服务器代理出错';
} else if (err?.response?.statusText && openaiError[err.response.statusText]) {
errorText = openaiError[err.response.statusText];
}
console.log('error->', errorText);
res.write(`event: serviceError\ndata: ${errorText}\n\n`);
// 删除最一条数据库记录, 也就是预发送的那一条
await ChatWindow.findByIdAndUpdate(windowId, {
$pop: { content: 1 },
updateTime: Date.now()
res.status(500);
jsonRes(res, {
code: 500,
error: err
});
res.end();
}
}

View File

@@ -2,34 +2,31 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { ChatItemType } from '@/types/chat';
import { connectToDatabase, ChatWindow } from '@/service/mongo';
import type { ModelType } from '@/types/model';
import { authChat } from '@/service/utils/chat';
/* 聊天预请求,存储聊天内容 */
/* 聊天内容存存储 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { windowId, prompt, chatId } = req.body as {
const { windowId, prompts } = req.body as {
windowId: string;
prompt: ChatItemType;
chatId: string;
prompts: ChatItemType[];
};
if (!windowId || !prompt || !chatId) {
if (!windowId || !prompts) {
throw new Error('缺少参数');
}
await connectToDatabase();
const { chat } = await authChat(chatId);
// 长度校验
const model: ModelType = chat.modelId;
if (prompt.value.length > model.security.contentMaxLen) {
throw new Error('输入内容超长');
}
// 存入库
await ChatWindow.findByIdAndUpdate(windowId, {
$push: { content: prompt },
$push: {
content: {
$each: prompts.map((item) => ({
obj: item.obj,
value: item.value
}))
}
},
updateTime: Date.now()
});