diff --git a/header.txt b/header.txt index 484c98b..43f47dd 100644 --- a/header.txt +++ b/header.txt @@ -2,7 +2,7 @@ // @name AI骰娘4 // @author 错误、白鱼 // @version 4.10.1 -// @description 适用于大部分OpenAI API兼容格式AI的模型插件,测试环境为 Deepseek AI (https://platform.deepseek.com/),用于与 AI 进行对话,并根据特定关键词触发回复。使用.ai help查看使用方法。具体配置查看插件配置项。\nopenai标准下的function calling功能已进行适配,选用模型若不支持该功能,可以开启迁移到提示词工程的开关,即可使用调用函数功能。\n交流答疑QQ群:940049120 +// @description 适用于大部分OpenAI API兼容格式AI的模型插件,测试环境为 Deepseek AI (https://platform.deepseek.com/),用于与 AI 进行对话,并根据特定关键词触发回复。使用.ai help查看使用方法。具体配置查看插件配置项。\nopenai标准下的function calling功能已进行适配,选用模型若不支持该功能,可以开启迁移到提示词工程的开关,即可使用调用函数功能。\n交流答疑QQ群:143412516 // @timestamp 1733387279 // 2024-12-05 16:27:59 // @license MIT diff --git a/src/AI/AI.ts b/src/AI/AI.ts index 2fb8331..3ccbd93 100644 --- a/src/AI/AI.ts +++ b/src/AI/AI.ts @@ -134,10 +134,6 @@ export class AI { return; } - const timeout = setTimeout(() => { - logger.warning(this.id, `处理消息超时`); - }, 60 * 1000); - let result = { contextArray: [], replyArray: [], @@ -185,7 +181,6 @@ export class AI { } } - clearTimeout(timeout); AIManager.saveAI(this.id); } @@ -196,6 +191,9 @@ export class AI { const messages = handleMessages(ctx, this); const id = await startStream(messages); + if (id === '') { + return; + } this.stream.id = id; let status = 'processing'; diff --git a/src/AI/memory.ts b/src/AI/memory.ts index fa9fb22..f5fc558 100644 --- a/src/AI/memory.ts +++ b/src/AI/memory.ts @@ -78,8 +78,8 @@ export class Memory { time: new Date().toLocaleString(), createTime: Math.floor(Date.now() / 1000), lastMentionTime: Math.floor(Date.now() / 1000), - keywords: kws, - content: content, + keywords: kws || [], + content: content || '', weight: 0 }; diff --git a/src/config/config_backend.ts b/src/config/config_backend.ts index a1a406f..66a83d3 100644 --- a/src/config/config_backend.ts +++ b/src/config/config_backend.ts @@ -10,7 +10,7 @@ export class BackendConfig { seal.ext.registerStringConfig(BackendConfig.ext, "图片转base64", "https://urltobase64.fishwhite.top", '可自行搭建'); seal.ext.registerStringConfig(BackendConfig.ext, "联网搜索", "https://searxng.fishwhite.top", '可自行搭建'); seal.ext.registerStringConfig(BackendConfig.ext, "网页读取", "https://webread.fishwhite.top", '可自行搭建'); - seal.ext.registerStringConfig(BackendConfig.ext, "用量图表", "http://localhost:3009", '可自行搭建'); + seal.ext.registerStringConfig(BackendConfig.ext, "用量图表", "http://chat.error2913.com", '可自行搭建'); } static get() { diff --git a/src/config/config_request.ts b/src/config/config_request.ts index 18d92db..b92a9c1 100644 --- a/src/config/config_request.ts +++ b/src/config/config_request.ts @@ -18,13 +18,15 @@ export class RequestConfig { `"temperature":1`, `"top_p":1` ], "messages,tools,tool_choice不存在时,将会自动替换。具体参数请参考你所使用模型的接口文档"); + seal.ext.registerIntConfig(RequestConfig.ext, "请求超时时限/ms", 180000, ''); } static get() { return { url: seal.ext.getStringConfig(RequestConfig.ext, "url地址"), apiKey: seal.ext.getStringConfig(RequestConfig.ext, "API Key"), - bodyTemplate: seal.ext.getTemplateConfig(RequestConfig.ext, "body") + bodyTemplate: seal.ext.getTemplateConfig(RequestConfig.ext, "body"), + timeout: seal.ext.getIntConfig(RequestConfig.ext, "请求超时时限/ms") } } } \ No newline at end of file diff --git a/src/service.ts b/src/service.ts index 110cd61..35de96d 100644 --- a/src/service.ts +++ b/src/service.ts @@ -4,6 +4,7 @@ import { ConfigManager } from "./config/config"; import { handleMessages, parseBody } from "./utils/utils_message"; import { ImageManager } from "./AI/image"; import { logger } from "./logger"; +import { withTimeout } from "./utils/utils"; export async function sendChatRequest(ctx: seal.MsgContext, msg: seal.Message, ai: AI, messages: { role: string, @@ -11,7 +12,7 @@ export async function sendChatRequest(ctx: seal.MsgContext, msg: seal.Message, a tool_calls?: ToolCall[], tool_call_id?: string }[], tool_choice: string): Promise { - const { url, apiKey, bodyTemplate } = ConfigManager.request; + const { url, apiKey, bodyTemplate, timeout } = ConfigManager.request; const { isTool, usePromptEngineering } = ConfigManager.tool; const tools = ai.tool.getToolsInfo(msg.messageType); @@ -19,7 +20,7 @@ export async function sendChatRequest(ctx: seal.MsgContext, msg: seal.Message, a const bodyObject = parseBody(bodyTemplate, messages, tools, tool_choice); const time = Date.now(); - const data = await fetchData(url, apiKey, bodyObject); + const data = await withTimeout(() => fetchData(url, apiKey, bodyObject), timeout); if (data.choices && data.choices.length > 0) { AIManager.updateUsage(data.model, data.usage); @@ -44,7 +45,7 @@ export async function sendChatRequest(ctx: seal.MsgContext, msg: seal.Message, a try { await ToolManager.handlePromptToolCall(ctx, msg, ai, match[1]); } catch (e) { - logger.error(`在handlePromptToolCall中出错:`, e.message); + logger.error(`在handlePromptToolCall中出错:`, e.message); return ''; } @@ -61,7 +62,7 @@ export async function sendChatRequest(ctx: seal.MsgContext, msg: seal.Message, a try { tool_choice = await ToolManager.handleToolCalls(ctx, msg, ai, message.tool_calls); } catch (e) { - logger.error(`在handleToolCalls中出错:`, e.message); + logger.error(`在handleToolCalls中出错:`, e.message); return ''; } @@ -75,8 +76,8 @@ export async function sendChatRequest(ctx: seal.MsgContext, msg: seal.Message, a } else { throw new Error(`服务器响应中没有choices或choices为空\n响应体:${JSON.stringify(data, null, 2)}`); } - } catch (error) { - logger.error("在sendChatRequest中出错:", error); + } catch (e) { + logger.error("在sendChatRequest中出错:", e.message); return ''; } } @@ -89,13 +90,14 @@ export async function sendITTRequest(messages: { text?: string }[] }[], useBase64: boolean): Promise { + const { timeout } = ConfigManager.request; const { url, apiKey, bodyTemplate, urlToBase64 } = ConfigManager.image; try { const bodyObject = parseBody(bodyTemplate, messages, null, null); const time = Date.now(); - const data = await fetchData(url, apiKey, bodyObject); + const data = await withTimeout(() => fetchData(url, apiKey, bodyObject), timeout); if (data.choices && data.choices.length > 0) { AIManager.updateUsage(data.model, data.usage); @@ -109,8 +111,8 @@ export async function sendITTRequest(messages: { } else { throw new Error(`服务器响应中没有choices或choices为空\n响应体:${JSON.stringify(data, null, 2)}`); } - } catch (error) { - logger.error("在sendITTRequest中请求出错:", error); + } catch (e) { + logger.error("在sendITTRequest中请求出错:", e.message); if (urlToBase64 === '自动' && !useBase64) { logger.info(`自动尝试使用转换为base64`); @@ -173,7 +175,7 @@ export async function fetchData(url: string, apiKey: string, bodyObject: any): P } return data; } catch (e) { - throw new Error(`解析响应体时出错:${e}\n响应体:${text}`); + throw new Error(`解析响应体时出错:${e.message}\n响应体:${text}`); } } @@ -181,7 +183,7 @@ export async function startStream(messages: { role: string, content: string }[]): Promise { - const { url, apiKey, bodyTemplate } = ConfigManager.request; + const { url, apiKey, bodyTemplate, timeout } = ConfigManager.request; const { streamUrl } = ConfigManager.backend; try { @@ -196,7 +198,7 @@ export async function startStream(messages: { }); logger.info(`请求发送前的上下文:\n`, s); - const response = await fetch(`${streamUrl}/start`, { + const response = await withTimeout(() => fetch(`${streamUrl}/start`, { method: 'POST', headers: { "Content-Type": "application/json", @@ -207,7 +209,7 @@ export async function startStream(messages: { api_key: apiKey, body_obj: bodyObject }) - }); + }), timeout); // logger.info("响应体", JSON.stringify(response, null, 2)); @@ -231,8 +233,8 @@ export async function startStream(messages: { } catch (e) { throw new Error(`解析响应体时出错:${e}\n响应体:${text}`); } - } catch (error) { - logger.error("在startStream中出错:", error); + } catch (e) { + logger.error("在startStream中出错:", e.message); return ''; } } @@ -274,8 +276,8 @@ export async function pollStream(id: string, after: number): Promise<{ status: s } catch (e) { throw new Error(`解析响应体时出错:${e}\n响应体:${text}`); } - } catch (error) { - logger.error("在pollStream中出错:", error); + } catch (e) { + logger.error("在pollStream中出错:", e.message); return { status: 'failed', reply: '', nextAfter: 0 }; } } @@ -317,8 +319,8 @@ export async function endStream(id: string): Promise { } catch (e) { throw new Error(`解析响应体时出错:${e}\n响应体:${text}`); } - } catch (error) { - logger.error("在endStream中出错:", error); + } catch (e) { + logger.error("在endStream中出错:", e.message); return ''; } } @@ -360,8 +362,8 @@ export async function get_chart_url(chart_type: string, usage_data: { } catch (e) { throw new Error(`解析响应体时出错:${e}\n响应体:${text}`); } - } catch (error) { - logger.error("在get_chart_url中请求出错:", error); + } catch (e) { + logger.error("在get_chart_url中请求出错:", e.message); return ''; } } \ No newline at end of file diff --git a/src/tool/tool_memory.ts b/src/tool/tool_memory.ts index 1e0015e..e6d80aa 100644 --- a/src/tool/tool_memory.ts +++ b/src/tool/tool_memory.ts @@ -67,7 +67,7 @@ export function registerAddMemory() { } //记忆相关处理 - ai.memory.addMemory(ctx, keywords, content); + ai.memory.addMemory(ctx, Array.isArray(keywords) ? keywords : [], content); AIManager.saveAI(ai.id); return `添加记忆成功`; diff --git a/src/update.ts b/src/update.ts index 6613ca3..6b55db3 100644 --- a/src/update.ts +++ b/src/update.ts @@ -1,7 +1,8 @@ // 版本更新日志,格式为 "版本号": "更新内容",版本号格式为 "x.y.z",按照时间顺序从新到旧排列。 export const updateInfo = { - "4.10.1": ` -- 可能修复了非指令无法响应的问题 + "4.10.2":`- 新增请求超时相关 +- 修复addMemory时,keywords可以为null的问题`, + "4.10.1": `- 可能修复了非指令无法响应的问题 - 修复了构建ctx时,isPrivate始终为0的问题 - 新增保存图片功能 - 重构了定时任务的执行 diff --git a/src/utils/utils.ts b/src/utils/utils.ts index f1294cc..f048be2 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -80,4 +80,13 @@ export async function replyToSender(ctx: seal.MsgContext, msg: seal.Message, ai: seal.replyToSender(ctx, msg, s); return ''; } +} + +export function withTimeout(asyncFunc: () => Promise, timeoutMs: number): Promise { + return Promise.race([ + asyncFunc(), + new Promise((_, reject) => { + setTimeout(() => reject(new Error(`操作超时 (${timeoutMs}ms)`)), timeoutMs); + }) + ]); } \ No newline at end of file