Qwen2API-A / src /utils /chat-helpers.js
github-actions[bot]
Sync from GitHub Viciy2023/Qwen2API-A@ae093476e9bc5b0a599620b5925df3a20057038e
f120063
const { logger } = require('./logger');
const { sha256Encrypt, generateUUID } = require('./tools.js');
const { uploadFileToQwenOss } = require('./upload.js');
const accountManager = require('./account.js');
const CacheManager = require('./img-caches.js');
/**
* 判断聊天类型
* @param {string} model - 模型名称
* @param {boolean} search - 是否搜索模式
* @returns {string} 聊天类型 ('search' 或 't2t')
*/
const isChatType = (model) => {
if (!model) return 't2t';
if (model.includes('-search')) {
return 'search';
} else if (model.includes('-image-edit')) {
return 'image_edit';
} else if (model.includes('-image')) {
return 't2i';
} else if (model.includes('-video')) {
return 't2v';
} else if (model.includes('-deep-research')) {
return 'deep_research';
} else {
return 't2t';
}
}
/**
* 判断是否启用思考模式
* @param {string} model - 模型名称
* @param {boolean} enable_thinking - 是否启用思考
* @param {number} thinking_budget - 思考预算
* @returns {object} 思考配置对象
*/
const isThinkingEnabled = (model, enable_thinking, thinking_budget) => {
const thinking_config = {
"output_schema": "phase",
"thinking_enabled": false,
"thinking_budget": 81920
}
if (!model) return thinking_config;
if (model.includes('-thinking') || enable_thinking) {
thinking_config.thinking_enabled = true;
}
if (thinking_budget && Number(thinking_budget) !== Number.NaN && Number(thinking_budget) > 0 && Number(thinking_budget) < 38912) {
thinking_config.budget = Number(thinking_budget);
}
return thinking_config;
}
/**
* 解析模型名称,移除特殊后缀
* @param {string} model - 原始模型名称
* @returns {string} 解析后的模型名称
*/
const parserModel = (model) => {
if (!model) return 'qwen3-coder-plus';
try {
model = String(model);
model = model.replace('-search', '');
model = model.replace('-thinking', '');
model = model.replace('-edit', '');
model = model.replace('-video', '');
model = model.replace('-deep-research', '');
model = model.replace('-image', '');
return model;
} catch (e) {
return 'qwen3-coder-plus';
}
}
/**
* 从消息中提取文本内容
* @param {string|Array} content - 消息内容
* @returns {string} 提取的文本
*/
const extractTextFromContent = (content) => {
if (typeof content === 'string') {
return content;
} else if (Array.isArray(content)) {
const textParts = content
.filter(item => item.type === 'text')
.map(item => item.text || '');
return textParts.join(' ');
}
return '';
}
/**
* 格式化消息为文本(包含角色标注)
* @param {object} message - 单条消息
* @returns {string} 格式化后的消息文本
*/
const formatSingleMessage = (message) => {
const role = message.role;
const content = extractTextFromContent(message.content);
return content.trim() ? `${role}:${content}` : '';
}
/**
* 格式化历史消息为文本前缀
* @param {Array} messages - 消息数组(不包含最后一条)
* @returns {string} 格式化后的历史消息
*/
const formatHistoryMessages = (messages) => {
const formattedParts = [];
for (let message of messages) {
const formatted = formatSingleMessage(message);
if (formatted) {
formattedParts.push(formatted);
}
}
return formattedParts.length > 0 ? formattedParts.join(';') : '';
}
/**
* 解析消息格式,处理图片上传和消息结构
* @param {Array} messages - 原始消息数组
* @param {object} thinking_config - 思考配置
* @param {string} chat_type - 聊天类型
* @returns {Promise<Array>} 解析后的消息数组
*/
const parserMessages = async (messages, thinking_config, chat_type) => {
try {
const feature_config = thinking_config;
const imgCacheManager = new CacheManager();
// 如果只有一条消息,使用原有逻辑处理(不标注角色)
if (messages.length <= 1) {
logger.network('单条消息,使用原格式处理', 'PARSER');
return await processOriginalLogic(messages, thinking_config, chat_type, imgCacheManager);
}
// 多条消息的情况:分离历史消息和最后一条消息
logger.network('多条消息,格式化处理并标注角色', 'PARSER');
const historyMessages = messages.slice(0, -1);
const lastMessage = messages[messages.length - 1];
// 格式化历史消息为文本前缀
const historyText = formatHistoryMessages(historyMessages);
// 处理最后一条消息
let finalContent = [];
let lastMessageText = '';
const lastMessageRole = lastMessage.role;
if (typeof lastMessage.content === 'string') {
lastMessageText = lastMessage.content;
} else if (Array.isArray(lastMessage.content)) {
// 处理最后一条消息中的内容
for (let item of lastMessage.content) {
if (item.type === 'text') {
lastMessageText += item.text || '';
} else if (item.type === 'image' || item.type === 'image_url') {
// 处理图片上传
let base64 = null;
if (item.type === 'image_url') {
base64 = item.image_url.url;
}
if (base64) {
const regex = /data:(.+);base64,/;
const fileType = base64.match(regex);
const fileExtension = fileType && fileType[1] ? fileType[1].split('/')[1] || 'png' : 'png';
const filename = `${generateUUID()}.${fileExtension}`;
base64 = base64.replace(regex, '');
const signature = sha256Encrypt(base64);
try {
const buffer = Buffer.from(base64, 'base64');
const cacheIsExist = imgCacheManager.cacheIsExist(signature);
if (cacheIsExist) {
finalContent.push({
type: 'image',
image: imgCacheManager.getCache(signature).url
});
} else {
const uploadResult = await uploadFileToQwenOss(buffer, filename, accountManager.getAccountToken());
if (uploadResult && uploadResult.status === 200) {
finalContent.push({
type: 'image',
image: uploadResult.file_url
});
imgCacheManager.addCache(signature, uploadResult.file_url);
}
}
} catch (error) {
logger.error('图片上传失败', 'UPLOAD', '', error);
}
}
}
}
}
// 组合最终内容:历史文本 + 当前消息(带角色标注)
let combinedText = '';
if (historyText) {
combinedText = historyText + ';';
}
// 添加最后一条消息,带角色标注
if (lastMessageText.trim()) {
combinedText += `${lastMessageRole}:${lastMessageText}`;
}
// 如果有图片,创建包含文本和图片的content数组
if (finalContent.length > 0) {
finalContent.unshift({
type: 'text',
text: combinedText,
chat_type: 't2t',
feature_config: {
"output_schema": "phase",
"thinking_enabled": false,
}
});
return [
{
"role": "user",
"content": finalContent,
"chat_type": chat_type,
"extra": {},
"feature_config": feature_config
}
];
} else {
// 纯文本情况
return [
{
"role": "user",
"content": combinedText,
"chat_type": chat_type,
"extra": {},
"feature_config": feature_config
}
];
}
} catch (e) {
logger.error('消息解析失败', 'PARSER', '', e);
return [
{
"role": "user",
"content": "直接返回字符串: '聊天历史处理有误...'",
"chat_type": "t2t",
"extra": {},
"feature_config": {
"output_schema": "phase",
"enabled": false,
}
}
];
}
}
/**
* 原有的单条消息处理逻辑
* @param {Array} messages - 消息数组
* @param {object} thinking_config - 思考配置
* @param {string} chat_type - 聊天类型
* @param {object} imgCacheManager - 图片缓存管理器
* @returns {Promise<Array>} 处理后的消息数组
*/
const processOriginalLogic = async (messages, thinking_config, chat_type, imgCacheManager) => {
const feature_config = thinking_config;
for (let message of messages) {
if (message.role === 'user' || message.role === 'assistant') {
message.chat_type = "t2t";
message.extra = {};
message.feature_config = {
"output_schema": "phase",
"thinking_enabled": false,
};
if (!Array.isArray(message.content)) continue;
const newContent = [];
for (let item of message.content) {
if (item.type === 'image' || item.type === 'image_url') {
let base64 = null;
if (item.type === 'image_url') {
base64 = item.image_url.url;
}
if (base64) {
const regex = /data:(.+);base64,/;
const fileType = base64.match(regex);
const fileExtension = fileType && fileType[1] ? fileType[1].split('/')[1] || 'png' : 'png';
const filename = `${generateUUID()}.${fileExtension}`;
base64 = base64.replace(regex, '');
const signature = sha256Encrypt(base64);
try {
const buffer = Buffer.from(base64, 'base64');
const cacheIsExist = imgCacheManager.cacheIsExist(signature);
if (cacheIsExist) {
delete item.image_url;
item.type = 'image';
item.image = imgCacheManager.getCache(signature).url;
newContent.push(item);
} else {
const uploadResult = await uploadFileToQwenOss(buffer, filename, accountManager.getAccountToken());
if (uploadResult && uploadResult.status === 200) {
delete item.image_url;
item.type = 'image';
item.image = uploadResult.file_url;
imgCacheManager.addCache(signature, uploadResult.file_url);
newContent.push(item);
}
}
} catch (error) {
logger.error('图片上传失败', 'UPLOAD', '', error);
}
}
} else if (item.type === 'text') {
item.chat_type = 't2t';
item.feature_config = {
"output_schema": "phase",
"thinking_enabled": false,
};
if (newContent.length >= 2) {
messages.push({
"role": "user",
"content": item.text,
"chat_type": "t2t",
"extra": {},
"feature_config": {
"output_schema": "phase",
"thinking_enabled": false,
}
});
} else {
newContent.push(item);
}
}
}
} else {
if (Array.isArray(message.content)) {
let system_prompt = '';
for (let item of message.content) {
if (item.type === 'text') {
system_prompt += item.text;
}
}
if (system_prompt) {
message.content = system_prompt;
}
}
}
}
messages[messages.length - 1].feature_config = feature_config;
messages[messages.length - 1].chat_type = chat_type;
return messages;
}
module.exports = {
isChatType,
isThinkingEnabled,
parserModel,
parserMessages
}