支持AI绘图

This commit is contained in:
Vinlic 2024-03-25 03:58:15 +08:00
parent d6a799bc87
commit 92712eeba3
2 changed files with 92 additions and 73 deletions

View File

@ -5,7 +5,7 @@
![](https://img.shields.io/github/forks/llm-red-team/qwen-free-api.svg) ![](https://img.shields.io/github/forks/llm-red-team/qwen-free-api.svg)
![](https://img.shields.io/docker/pulls/vinlic/qwen-free-api.svg) ![](https://img.shields.io/docker/pulls/vinlic/qwen-free-api.svg)
支持高速流式输出、支持多轮对话、支持长文档解读、支持图像解析零配置部署多路token支持自动清理会话痕迹。 支持高速流式输出、支持多轮对话、支持AI绘图、支持长文档解读(正在开发)、图像解析(正在开发)零配置部署多路token支持自动清理会话痕迹。
与ChatGPT接口完全兼容。 与ChatGPT接口完全兼容。

View File

@ -1,3 +1,4 @@
import { URL } from "url";
import { PassThrough } from "stream"; import { PassThrough } from "stream";
import path from "path"; import path from "path";
import _ from "lodash"; import _ from "lodash";
@ -56,7 +57,7 @@ async function removeConversation(convId: string, ticket: string) {
{ {
headers: { headers: {
Cookie: generateCookie(ticket), Cookie: generateCookie(ticket),
...FAKE_HEADERS ...FAKE_HEADERS,
}, },
timeout: 15000, timeout: 15000,
validateStatus: () => true, validateStatus: () => true,
@ -122,9 +123,7 @@ async function createCompletion(
); );
// 异步移除会话,如果消息不合规,此操作可能会抛出数据库错误异常,请忽略 // 异步移除会话,如果消息不合规,此操作可能会抛出数据库错误异常,请忽略
removeConversation(answer.id, ticket).catch((err) => removeConversation(answer.id, ticket).catch((err) => console.error(err));
console.error(err)
);
return answer; return answer;
})().catch((err) => { })().catch((err) => {
@ -197,9 +196,7 @@ async function createCompletionStream(
`Stream has completed transfer ${util.timestamp() - streamStartTime}ms` `Stream has completed transfer ${util.timestamp() - streamStartTime}ms`
); );
// 流传输结束后异步移除会话,如果消息不合规,此操作可能会抛出数据库错误异常,请忽略 // 流传输结束后异步移除会话,如果消息不合规,此操作可能会抛出数据库错误异常,请忽略
removeConversation(convId, ticket).catch((err) => removeConversation(convId, ticket).catch((err) => console.error(err));
console.error(err)
);
}); });
})().catch((err) => { })().catch((err) => {
if (retryCount < MAX_RETRY_COUNT) { if (retryCount < MAX_RETRY_COUNT) {
@ -207,12 +204,7 @@ async function createCompletionStream(
logger.warn(`Try again after ${RETRY_DELAY / 1000}s...`); logger.warn(`Try again after ${RETRY_DELAY / 1000}s...`);
return (async () => { return (async () => {
await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY));
return createCompletionStream( return createCompletionStream(model, messages, ticket, retryCount + 1);
model,
messages,
ticket,
retryCount + 1
);
})(); })();
} }
throw err; throw err;
@ -268,13 +260,17 @@ function messagesPrepare(messages: any[]) {
return _content + (v["text"] || ""); return _content + (v["text"] || "");
}, content); }, content);
} }
return (content += `<|im_start|>${message.role || "user"}\n${message.content}<|im_end|>\n`); return (content += `<|im_start|>${message.role || "user"}\n${
message.content
}<|im_end|>\n`);
}, ""); }, "");
return [{ return [
role: "user", {
contentType: "text", role: "user",
content contentType: "text",
}]; content,
},
];
} }
/** /**
@ -324,21 +320,34 @@ async function receiveStream(stream: any): Promise<any> {
if (_.isError(result)) if (_.isError(result))
throw new Error(`Stream response invalid: ${event.data}`); throw new Error(`Stream response invalid: ${event.data}`);
if (!data.id && result.sessionId) data.id = result.sessionId; if (!data.id && result.sessionId) data.id = result.sessionId;
if (result.msgStatus != "finished") { const text = (result.contents || []).reduce((str, part) => {
const text = (result.contents || []).reduce((str, part) => { const { role, content } = part;
const { role, content } = part; if (role != "assistant" && !_.isString(content)) return str;
if (role != "assistant" && !_.isString(content)) return str; return str + content;
return str + content; }, "");
}, ""); let chunk = text.substring(
const chunk = text.substring( data.choices[0].message.content.length - textOffset,
data.choices[0].message.content.length - textOffset, text.length
text.length );
if (chunk && result.contentType == "text2image") {
chunk = chunk.replace(
/https?:\/\/[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=\,]*)/gi,
(url) => {
const urlObj = new URL(url);
urlObj.search = "";
return urlObj.toString();
}
); );
data.choices[0].message.content += chunk; }
if (result.msgStatus != "finished") {
if (result.contentType == "text")
data.choices[0].message.content += chunk;
} else { } else {
if(!result.canShare) data.choices[0].message.content += chunk;
data.choices[0].message.content += '\n[内容由于不合规被停止生成,我们换个话题吧]'; if (!result.canShare)
if(result.errorCode) data.choices[0].message.content +=
"\n[内容由于不合规被停止生成,我们换个话题吧]";
if (result.errorCode)
data.choices[0].message.content += `服务暂时不可用,第三方响应错误:${result.errorCode}`; data.choices[0].message.content += `服务暂时不可用,第三方响应错误:${result.errorCode}`;
resolve(data); resolve(data);
} }
@ -367,7 +376,7 @@ function createTransStream(stream: any, endCallback?: Function) {
const created = util.unixTimestamp(); const created = util.unixTimestamp();
// 创建转换流 // 创建转换流
const transStream = new PassThrough(); const transStream = new PassThrough();
let content = ''; let content = "";
!transStream.closed && !transStream.closed &&
transStream.write( transStream.write(
`data: ${JSON.stringify({ `data: ${JSON.stringify({
@ -392,49 +401,59 @@ function createTransStream(stream: any, endCallback?: Function) {
const result = _.attempt(() => JSON.parse(event.data)); const result = _.attempt(() => JSON.parse(event.data));
if (_.isError(result)) if (_.isError(result))
throw new Error(`Stream response invalid: ${event.data}`); throw new Error(`Stream response invalid: ${event.data}`);
const text = (result.contents || []).reduce((str, part) => {
const { role, content } = part;
if (role != "assistant" && !_.isString(content)) return str;
return str + content;
}, "");
let chunk = text.substring(content.length, text.length);
if (chunk && result.contentType == "text2image") {
chunk = chunk.replace(
/https?:\/\/[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=\,]*)/gi,
(url) => {
const urlObj = new URL(url);
urlObj.search = "";
return urlObj.toString();
}
);
}
if (result.msgStatus != "finished") { if (result.msgStatus != "finished") {
const text = (result.contents || []).reduce((str, part) => { if (chunk && result.contentType == "text") {
const { role, content } = part;
if (role != "assistant" && !_.isString(content)) return str;
return str + content;
}, "");
const chunk = text.substring(content.length, text.length);
if(chunk) {
content += chunk; content += chunk;
const data = `data: ${JSON.stringify({ const data = `data: ${JSON.stringify({
id: result.conversation_id, id: result.sessionId,
model: MODEL_NAME, model: MODEL_NAME,
object: 'chat.completion.chunk', object: "chat.completion.chunk",
choices: [ choices: [
{ index: 0, delta: { content: chunk }, finish_reason: null } { index: 0, delta: { content: chunk }, finish_reason: null },
], ],
created created,
})}\n\n`; })}\n\n`;
!transStream.closed && transStream.write(data); !transStream.closed && transStream.write(data);
} }
} else { } else {
let delta = {}; const delta = { content: chunk || "" };
if(!result.canShare) if (!result.canShare)
delta = { content: '\n[内容由于不合规被停止生成,我们换个话题吧]' } delta.content += "\n[内容由于不合规被停止生成,我们换个话题吧]";
if(result.errorCode) if (result.errorCode)
delta = { content: `服务暂时不可用,第三方响应错误:${result.errorCode}` }; delta.content += `服务暂时不可用,第三方响应错误:${result.errorCode}`;
const data = `data: ${JSON.stringify({ const data = `data: ${JSON.stringify({
id: result.conversation_id, id: result.sessionId,
model: MODEL_NAME, model: MODEL_NAME,
object: 'chat.completion.chunk', object: "chat.completion.chunk",
choices: [ choices: [
{ {
index: 0, index: 0,
delta, delta,
finish_reason: 'stop' finish_reason: "stop",
} },
], ],
usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 }, usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },
created created,
})}\n\n`; })}\n\n`;
!transStream.closed && transStream.write(data); !transStream.closed && transStream.write(data);
!transStream.closed && transStream.end('data: [DONE]\n\n'); !transStream.closed && transStream.end("data: [DONE]\n\n");
content = ''; content = "";
endCallback && endCallback(result.sessionId); endCallback && endCallback(result.sessionId);
} }
// else // else
@ -468,27 +487,27 @@ function tokenSplit(authorization: string) {
/** /**
* Cookies * Cookies
* *
* @param ticket login_tongyi_ticket值 * @param ticket login_tongyi_ticket值
*/ */
function generateCookie(ticket: string) { function generateCookie(ticket: string) {
return [ return [
// `login_aliyunid_csrf=_csrf_tk_1338411200877960`,
// '_samesite_flag_=true',
// 'cookie2=17f5d8b4b310ced751b4884bf6f90a59',
// `munb=2205561979317`,
// `csg=d2a7667c`,
// `t=b147e56818bb17773dbbac0b0f5124d4`,
// `_tb_token_=e0e1e0eaf6397`,
`login_tongyi_ticket=${ticket}`, `login_tongyi_ticket=${ticket}`,
// `cna=2BIPGQLBaUoCAXF0dTVJX4J7`, '_samesite_flag_=true',
// `cnaui=1717007863755884`, `t=${util.uuid(false)}`,
// `atpsida=96034589ac4b10f25d62eeb3_1711200918_2`, // `login_aliyunid_csrf=_csrf_tk_${util.generateRandomString({ charset: 'numeric', length: 15 })}`,
// `isg=BJeXsmIBvhDupD4W9uGbnvw8Jg3h3Gs-fDl6NunHdWZIGLxa9a2mj8r6e7gGxkO2`, // `cookie2=${util.uuid(false)}`,
// `tfstk=crDFBRcFK801Valh3RezgJtCWRxdaCMmeNr7teNJ-WUBJiF8usY1ylAoB5PfbaVh.`, // `munb=22${util.generateRandomString({ charset: 'numeric', length: 11 })}`,
// `aui=1717007863755884`, // `csg=`,
// `sca=48fca504` // `_tb_token_=${util.generateRandomString({ length: 10, capitalization: 'lowercase' })}`,
].join('; '); // `cna=`,
// `cnaui=`,
// `atpsida=`,
// `isg=`,
// `tfstk=`,
// `aui=`,
// `sca=`
].join("; ");
} }
export default { export default {