import { isEmpty } from "lodash"; import { gptDefine } from "../../../define/gptDefine"; import axios from "axios"; import { RetryWithBackoff } from "../../../define/Tools/common"; import { Book } from "../../../model/book"; /** * 一些GPT相关的服务都在这边 */ export class GptService { gptUrl: string = undefined gptModel: string = undefined gptApiKey: string = undefined //#region GPT 设置 /** * 获取GPT的所有的服务商 * @param type 获取的类型,就是all * @param callback 这个是个回调函数,干嘛的不知道 * @returns */ private async GetGPTBusinessOption(type: string, callback: Function = null): Promise { let res = await gptDefine.getGptDataByTypeAndProperty(type, "gpt_options", []); if (res.code == 0) { throw new Error(res.message) } else { if (callback) { callback(res.data) } return res.data } } async RefreshGptSetting() { let all_options = await this.GetGPTBusinessOption("all", (value) => value.gpt_url); let index = all_options.findIndex(item => item.value == global.config.gpt_business && item.gpt_url) if (index < 0) { throw new Error("没有找到指定的GPT服务商的配置,请检查") } this.gptUrl = all_options[index].gpt_url; this.gptApiKey = global.config.gpt_key; this.gptModel = global.config.gpt_model; } /** * 初始化GPT的设置 */ async InitGptSetting(refresh = false) { if (refresh) { await this.RefreshGptSetting() } else { // 判断是不是存在必要信息 if (isEmpty(this.gptUrl) || isEmpty(this.gptModel) || isEmpty(this.gptApiKey)) { await this.RefreshGptSetting(); } } } /** * 适配一些请求体中的参数 * @param data * @param gpt_url * @returns */ ModifyData(data: any, gpt_url: string = null) { let res = data; if (!gpt_url) { gpt_url = this.gptUrl } if (gpt_url.includes("dashscope.aliyuncs.com")) { res = { "model": data.model, "input": { "messages": data.messages, }, "parameters": { "result_format": "message" } } } return res; } /** * 适配返回来的数据 * @param res 返回的数据 * @param gpt_url 请求的URL * @returns */ GetResponseContent(res: any, gpt_url: string = null) { let content = ""; if (!gpt_url) { gpt_url = this.gptUrl } if (gpt_url.includes("dashscope.aliyuncs.com")) { content = res.data.output.choices[0].message.content; } else { content = res.data.choices[0].message.content; } return content; } //#endregion //#region GPT 通用请求 /** * 发送GPT请求 * @param {*} message 请求的信息 * @param {*} gpt_url gpt的url,默认在global中取 * @param {*} gpt_key gpt的key,默认在global中取 * @param {*} gpt_model gpt的model,默认在global中取 * @returns */ async FetchGpt(message: any, gpt_model: string = null, gpt_key: string = null, gpt_url: string = null): Promise { try { await this.InitGptSetting(); let data = { "model": gpt_model ? gpt_model : this.gptModel, "messages": message }; data = this.ModifyData(data, gpt_url); let config = { method: 'post', maxBodyLength: Infinity, url: gpt_url ? gpt_url : this.gptUrl, headers: { 'Authorization': `Bearer ${gpt_key ? gpt_key : this.gptApiKey}`, 'Content-Type': 'application/json' }, data: JSON.stringify(data) }; let res = await axios.request(config); let content = this.GetResponseContent(res, this.gptUrl); return content; } catch (error) { throw error; } } //#endregion //#region 原创推理 /** * 获取当前分镜的上下文数据 * @param currentBookTaskDetail 当前分镜数据 * @param bookTaskDetails 所有的小说分镜数据 * @param contextCount 上下文行数 */ GetBookTaskDetailContextData(currentBookTaskDetail: Book.SelectBookTaskDetail, bookTaskDetails: Book.SelectBookTaskDetail[], contextCount: number): string { let prefix = ""; // 拼接一个word let i = currentBookTaskDetail.no - 1 if (i <= contextCount) { prefix = bookTaskDetails .filter((item, index) => index < i) .map((item) => item.afterGpt) .join('\r\n') } else if (i > contextCount) { prefix = bookTaskDetails .filter((item, index) => i - index <= contextCount && i - index > 0) .map((item) => item.afterGpt) .join('\r\n') } let suffix = ""; let o_i = bookTaskDetails.length - i if (o_i <= contextCount) { suffix = bookTaskDetails .filter((item, index) => index > i) .map((item) => item.afterGpt) .join('\r\n') } else if (o_i > contextCount) { suffix = bookTaskDetails .filter((item, index) => index - i <= contextCount && index - i > 0) .map((item) => item.afterGpt) .join('\r\n') } return `${prefix}\r\n${currentBookTaskDetail.afterGpt}\r\n${suffix}`; } /** * 返回当前推理数据的请求体中的message * @param currentBookTaskDetail 当前推理的提示词数据 * @param contextData 上下文数据 * @param autoAnalyzeCharacter 自动分析的角色数据 * @returns */ GetGPTRequestMessage(currentBookTaskDetail: Book.SelectBookTaskDetail, contextData: string, autoAnalyzeCharacter: string): any[] { let message = [] if ( ['superSinglePrompt', 'onlyPromptMJ', 'superSinglePromptChinese'].includes( global.config.gpt_auto_inference ) ) { // 有返回案例的 message = gptDefine.GetExamplePromptMessage(global.config.gpt_auto_inference) // 加当前提问的 message.push({ role: 'user', content: currentBookTaskDetail.afterGpt }) } else { // 直接返回,没有案例的 message = [ { role: 'system', content: gptDefine.getSystemContentByType(global.config.gpt_auto_inference, { textContent: contextData, characterContent: autoAnalyzeCharacter }) }, { role: 'user', content: gptDefine.getUserContentByType(global.config.gpt_auto_inference, { textContent: currentBookTaskDetail.afterGpt, wordCount: global.config.gpt_model && global.config.gpt_model.includes('gpt-4') ? '20' : '40' }) } ] } return message } /** * 原创推理提示词数据 * @param currentBookTaskDetail 要推理的小说分镜任务 * @param bookTaskDetails 所有的小说分镜任务 * @param contextCount 上下文的数量 * @param autoAnalyzeCharacter 自动分析的角色数据字符串 */ async OriginalInferencePrompt(currentBookTaskDetail: Book.SelectBookTaskDetail, bookTaskDetails: Book.SelectBookTaskDetail[], contextCount: number, autoAnalyzeCharacter: string) { let gptPromptType = global.config.gpt_auto_inference; let message = [] if (gptPromptType == "customize") { //自定义模式 // 自定义模式 // 获取当前自定义的推理提示词 let customize_gpt_prompt = ( await gptDefine.getGptDataByTypeAndProperty('dynamic', 'customize_gpt_prompt', []) ).data let index = customize_gpt_prompt.findIndex( (item: any) => item.id == global.config.customize_gpt_prompt ) if (global.config.customize_gpt_prompt && index < 0) { throw new Error('自定义推理时要选择对应的自定义推理词') } message = gptDefine.CustomizeGptPrompt(customize_gpt_prompt[index], currentBookTaskDetail.afterGpt) message.push({ role: 'user', content: currentBookTaskDetail.afterGpt }) } else { // 内置模式 let context = this.GetBookTaskDetailContextData(currentBookTaskDetail, bookTaskDetails, contextCount); message = this.GetGPTRequestMessage(currentBookTaskDetail, context, autoAnalyzeCharacter); } // 开始请求 let res = await RetryWithBackoff(async () => { return this.FetchGpt(message) }, 5, 1000) if (res) { if (res) { res = res .replace(/\)\s*\(/g, ', ') .replace(/^\(/, '') .replace(/\)$/, '') .replaceAll('*', '') .replaceAll('--', ' ') } } return res } //#endregion //#region 中文 繁体转简体 /** * 将繁体中文转换为简体中文 * @param traditionalText 繁体中文文本 * @param apiKey Lai API的 Key * @param baseUrl 请求的baseurl * @returns */ async ChineseTraditionalToSimplified(traditionalText: string, apiKey: string, baseUrl: string = null): Promise { try { let message = [ { "role": "system", "content": '我想让你充当中文繁体转简体专家,用简体中文100%还原繁体中文,不要加其他的联想,只把原有的繁体中文转换为简体中文,请检查所有信息是否准确,并在回答时保持简活,不需要任何其他反馈。' }, { "role": "user", "content": '上研究生後,發現導師竟然是曾經網戀的前男友。' }, { "role": "assistant", "content": '上研究生后,发现导师竟然是曾经网恋的前男友。' }, { "role": "user", "content": traditionalText } ] let baseSubUrl = baseUrl ? (baseUrl.endsWith('/') ? baseUrl + 'v1/chat/completions' : baseUrl + '/v1/chat/completions') : null; let url = baseSubUrl ? baseSubUrl : "https://api.laitool.cc/v1/chat/completions" // 开始请求,这个默认是使用的是LAI API的gpt-4o-mini let content = await RetryWithBackoff(async () => { return await this.FetchGpt(message, 'gpt-4o-mini', apiKey, url); }, 5, 2000) return content } catch (error) { throw error } } //#endregion }