From 457c5c050babc35114ad410f010ba7cfac838d2a Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Wed, 4 Mar 2026 19:59:24 +0800 Subject: [PATCH 01/33] =?UTF-8?q?=E8=BF=99=E6=98=AF=E5=A4=A7=E5=8F=98?= =?UTF-8?q?=E5=95=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/{AI => agent}/AI.ts | 2 +- src/agent/agent.ts | 16 ++++++++++++++++ src/agent/model.ts | 19 +++++++++++++++++++ src/{ => agent}/service.ts | 12 ++++++------ src/cmd/sub_cmd/image.ts | 2 +- src/cmd/sub_cmd/token.ts | 2 +- src/{AI => image}/image.ts | 2 +- src/index.ts | 2 +- src/{AI => session}/context.ts | 2 +- src/{AI => session}/memory.ts | 4 ++-- src/session/session.ts | 13 +++++++++++++ src/tool/tool.ts | 2 +- src/tool/tool_essence_msg.ts | 2 +- src/tool/tool_image.ts | 2 +- src/tool/tool_meme.ts | 2 +- src/tool/tool_memory.ts | 2 +- src/tool/tool_message.ts | 2 +- src/tool/tool_render.ts | 2 +- src/utils/utils_message.ts | 4 ++-- src/utils/utils_string.ts | 4 ++-- 20 files changed, 73 insertions(+), 25 deletions(-) rename src/{AI => agent}/AI.ts (99%) create mode 100644 src/agent/agent.ts create mode 100644 src/agent/model.ts rename src/{ => agent}/service.ts (97%) rename src/{AI => image}/image.ts (99%) rename src/{AI => session}/context.ts (99%) rename src/{AI => session}/memory.ts (99%) create mode 100644 src/session/session.ts diff --git a/src/AI/AI.ts b/src/agent/AI.ts similarity index 99% rename from src/AI/AI.ts rename to src/agent/AI.ts index b9e3ae1..bcc18ec 100644 --- a/src/AI/AI.ts +++ b/src/agent/AI.ts @@ -1,7 +1,7 @@ import { Image, ImageManager } from "./image"; import { ConfigManager } from "../config/configManager"; import { replyToSender, revive, transformMsgId } from "../utils/utils"; -import { endStream, pollStream, sendChatRequest, startStream } from "../service"; +import { endStream, pollStream, sendChatRequest, startStream } from "../agent/service"; import { Context } from "./context"; import { MemoryManager } from "./memory"; import { handleMessages, parseBody } from "../utils/utils_message"; diff --git a/src/agent/agent.ts b/src/agent/agent.ts new file mode 100644 index 0000000..2268730 --- /dev/null +++ b/src/agent/agent.ts @@ -0,0 +1,16 @@ +import { Model } from "./model"; + +export class Agent { + model: Model; + desc: string; + instruction: string; + sessionService: SessionService; + + constructor(model: Model) { + this.model = model; + } + + async chat() { + + } +} \ No newline at end of file diff --git a/src/agent/model.ts b/src/agent/model.ts new file mode 100644 index 0000000..1e65fac --- /dev/null +++ b/src/agent/model.ts @@ -0,0 +1,19 @@ +export class Model { + name: string; + provider: string; + base_url: string; + api_key: string; + type: 'chat' | 'code' | 'image' | 'embedding' | 'audio' | 'video'; + + constructor(name: string, provider: string, base_url: string, api_key: string, type: 'chat' | 'code' | 'image' | 'embedding' | 'audio' | 'video') { + this.name = name; + this.provider = provider; + this.base_url = base_url; + this.api_key = api_key; + this.type = type; + } + + async call() { + + } +} \ No newline at end of file diff --git a/src/service.ts b/src/agent/service.ts similarity index 97% rename from src/service.ts rename to src/agent/service.ts index 1ed8e0e..e8aa4c8 100644 --- a/src/service.ts +++ b/src/agent/service.ts @@ -1,9 +1,9 @@ -import { AIManager } from "./AI/AI"; -import { ToolCall, ToolInfo } from "./tool/tool"; -import { ConfigManager } from "./config/configManager"; -import { parseBody, parseEmbeddingBody } from "./utils/utils_message"; -import { logger } from "./logger"; -import { withTimeout } from "./utils/utils"; +import { AIManager } from "../AI/AI"; +import { ToolCall, ToolInfo } from "../tool/tool"; +import { ConfigManager } from "../config/configManager"; +import { parseBody, parseEmbeddingBody } from "../utils/utils_message"; +import { logger } from "../logger"; +import { withTimeout } from "../utils/utils"; export async function sendChatRequest(messages: { role: string, diff --git a/src/cmd/sub_cmd/image.ts b/src/cmd/sub_cmd/image.ts index f03ae33..4130d00 100644 --- a/src/cmd/sub_cmd/image.ts +++ b/src/cmd/sub_cmd/image.ts @@ -1,5 +1,5 @@ import { AIManager } from "../../AI/AI"; -import { ImageManager } from "../../AI/image"; +import { ImageManager } from "../../image/image"; import { aliasToCmd } from "../../utils/utils"; import { transformArrayToContent, transformTextToArray } from "../../utils/utils_string"; import { I, M, U } from "../privilege"; diff --git a/src/cmd/sub_cmd/token.ts b/src/cmd/sub_cmd/token.ts index 6587e98..fe2207f 100644 --- a/src/cmd/sub_cmd/token.ts +++ b/src/cmd/sub_cmd/token.ts @@ -1,5 +1,5 @@ import { AIManager } from "../../AI/AI"; -import { get_chart_url } from "../../service"; +import { get_chart_url } from "../../agent/service"; import { aliasToCmd } from "../../utils/utils"; import { S, U } from "../privilege"; import { SubCmd, SubCmdContext } from "../root"; diff --git a/src/AI/image.ts b/src/image/image.ts similarity index 99% rename from src/AI/image.ts rename to src/image/image.ts index 9b72daa..26d495a 100644 --- a/src/AI/image.ts +++ b/src/image/image.ts @@ -1,5 +1,5 @@ import { ConfigManager } from "../config/configManager"; -import { sendITTRequest } from "../service"; +import { sendITTRequest } from "../agent/service"; import { generateId } from "../utils/utils"; import { logger } from "../logger"; import { AI } from "./AI"; diff --git a/src/index.ts b/src/index.ts index eb0a53f..0249500 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,7 +8,7 @@ import { checkUpdate } from "./utils/utils_update"; import { TimerManager } from "./timer"; import { createMsg } from "./utils/utils_seal"; import { PrivilegeManager } from "./cmd/privilege"; -import { knowledgeMM } from "./AI/memory"; +import { knowledgeMM } from "./session/memory"; import { CQTYPESALLOW } from "./config/config"; import { registerCmd } from "./cmd/root"; diff --git a/src/AI/context.ts b/src/session/context.ts similarity index 99% rename from src/AI/context.ts rename to src/session/context.ts index 7c3b81a..06dce16 100644 --- a/src/AI/context.ts +++ b/src/session/context.ts @@ -1,6 +1,6 @@ import { ToolCall } from "../tool/tool"; import { ConfigManager } from "../config/configManager"; -import { Image, ImageManager } from "./image"; +import { Image, ImageManager } from "../image/image"; import { getCtxAndMsg } from "../utils/utils_seal"; import { levenshteinDistance } from "../utils/utils_string"; import { AI, AIManager, GroupInfo, UserInfo } from "./AI"; diff --git a/src/AI/memory.ts b/src/session/memory.ts similarity index 99% rename from src/AI/memory.ts rename to src/session/memory.ts index 9d074b5..fe1e55c 100644 --- a/src/AI/memory.ts +++ b/src/session/memory.ts @@ -3,11 +3,11 @@ import { AI, AIManager, GroupInfo, SessionInfo, UserInfo } from "./AI"; import { Context } from "./context"; import { cosineSimilarity, generateId, getCommonGroup, getCommonKeyword, getCommonUser, revive } from "../utils/utils"; import { logger } from "../logger"; -import { fetchData, getEmbedding } from "../service"; +import { fetchData, getEmbedding } from "../agent/service"; import { buildContent, getRoleSetting, parseBody } from "../utils/utils_message"; import { ToolManager } from "../tool/tool"; import { fmtDate } from "../utils/utils_string"; -import { Image, ImageManager } from "./image"; +import { Image, ImageManager } from "../image/image"; export interface searchOptions { topK: number; diff --git a/src/session/session.ts b/src/session/session.ts new file mode 100644 index 0000000..4fbec8d --- /dev/null +++ b/src/session/session.ts @@ -0,0 +1,13 @@ +export class Session { + sid: string; + state: any; + memory: Memory; +} + +export class SessionService { + sessionMap: { [key: string]: Session }; + + constructor() { + + } +} \ No newline at end of file diff --git a/src/tool/tool.ts b/src/tool/tool.ts index 299f30b..bc73f1f 100644 --- a/src/tool/tool.ts +++ b/src/tool/tool.ts @@ -23,7 +23,7 @@ import { registerMusicPlay } from "./tool_music" import { registerMeme } from "./tool_meme" import { registerRender } from "./tool_render" import { logger } from "../logger" -import { Image } from "../AI/image"; +import { Image } from "../image/image"; import { fixJsonString } from "../utils/utils_string"; export interface ToolInfoString { diff --git a/src/tool/tool_essence_msg.ts b/src/tool/tool_essence_msg.ts index dde6c08..68f7db9 100644 --- a/src/tool/tool_essence_msg.ts +++ b/src/tool/tool_essence_msg.ts @@ -1,6 +1,6 @@ import { transformMsgIdBack, transformMsgId } from "../utils/utils"; import { Tool } from "./tool"; -import { Image } from "../AI/image"; +import { Image } from "../image/image"; import { transformArrayToContent } from "../utils/utils_string"; import { deleteEssenceMsg, getEssenceMsgList, getGroupMemberInfo, netExists, setEssenceMsg } from "../utils/utils_ob11"; diff --git a/src/tool/tool_image.ts b/src/tool/tool_image.ts index 4d4698e..76aa109 100644 --- a/src/tool/tool_image.ts +++ b/src/tool/tool_image.ts @@ -1,5 +1,5 @@ import { AIManager } from "../AI/AI"; -import { Image } from "../AI/image"; +import { Image } from "../image/image"; import { logger } from "../logger"; import { ConfigManager } from "../config/configManager"; import { Tool } from "./tool"; diff --git a/src/tool/tool_meme.ts b/src/tool/tool_meme.ts index 2cfef4a..d41550a 100644 --- a/src/tool/tool_meme.ts +++ b/src/tool/tool_meme.ts @@ -1,5 +1,5 @@ import { AIManager, GroupInfo, UserInfo } from "../AI/AI"; -import { Image, ImageManager } from "../AI/image"; +import { Image, ImageManager } from "../image/image"; import { ConfigManager } from "../config/configManager"; import { logger } from "../logger"; import { generateId } from "../utils/utils"; diff --git a/src/tool/tool_memory.ts b/src/tool/tool_memory.ts index 6ac197d..cfa61b1 100644 --- a/src/tool/tool_memory.ts +++ b/src/tool/tool_memory.ts @@ -2,7 +2,7 @@ import { AIManager, GroupInfo, SessionInfo, UserInfo } from "../AI/AI"; import { ConfigManager } from "../config/configManager"; import { getCtxAndMsg } from "../utils/utils_seal"; import { Tool } from "./tool"; -import { knowledgeMM, searchOptions as SearchOptions } from "../AI/memory"; +import { knowledgeMM, searchOptions as SearchOptions } from "../session/memory"; import { getRoleSetting } from "../utils/utils_message"; export function registerMemory() { diff --git a/src/tool/tool_message.ts b/src/tool/tool_message.ts index ce6937b..4cf4c99 100644 --- a/src/tool/tool_message.ts +++ b/src/tool/tool_message.ts @@ -7,7 +7,7 @@ import { Tool, ToolManager } from "./tool"; import { CQTYPESALLOW, faceMap } from "../config/config"; import { deleteMsg, getGroupMemberInfo, getMsg, sendGroupForwardMsg, sendPrivateForwardMsg, netExists } from "../utils/utils_ob11"; import { logger } from "../logger"; -import { Image } from "../AI/image"; +import { Image } from "../image/image"; export function registerMessage() { const toolSend = new Tool({ diff --git a/src/tool/tool_render.ts b/src/tool/tool_render.ts index 334fa8a..316444d 100644 --- a/src/tool/tool_render.ts +++ b/src/tool/tool_render.ts @@ -2,7 +2,7 @@ import { logger } from "../logger"; import { Tool } from "./tool"; import { ConfigManager } from "../config/configManager"; import { AI, AIManager } from "../AI/AI"; -import { Image } from "../AI/image"; +import { Image } from "../image/image"; import { generateId } from "../utils/utils"; import { parseSpecialTokens } from "../utils/utils_string"; diff --git a/src/utils/utils_message.ts b/src/utils/utils_message.ts index 1f812ba..4667bb0 100644 --- a/src/utils/utils_message.ts +++ b/src/utils/utils_message.ts @@ -1,9 +1,9 @@ import { AI, GroupInfo, UserInfo } from "../AI/AI"; -import { Message } from "../AI/context"; +import { Message } from "../session/context"; import { ConfigManager } from "../config/configManager"; import { ToolInfo } from "../tool/tool"; import { fmtDate } from "./utils_string"; -import { knowledgeMM } from "../AI/memory"; +import { knowledgeMM } from "../session/memory"; export async function buildSystemMessage(ctx: seal.MsgContext, ai: AI): Promise { const { systemMessageTemplate, isPrefix, showNumber, showMsgId, showTime } = ConfigManager.message; diff --git a/src/utils/utils_string.ts b/src/utils/utils_string.ts index f766910..4059eef 100644 --- a/src/utils/utils_string.ts +++ b/src/utils/utils_string.ts @@ -1,5 +1,5 @@ -import { Context } from "../AI/context"; -import { Image } from "../AI/image"; +import { Context } from "../session/context"; +import { Image } from "../image/image"; import { logger } from "../logger"; import { ConfigManager } from "../config/configManager"; import { transformMsgId, transformMsgIdBack } from "./utils"; From 831d6d58da49334a601c158bca7066fd0ae467c2 Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Wed, 4 Mar 2026 21:04:03 +0800 Subject: [PATCH 02/33] =?UTF-8?q?=E8=BF=99=E4=B8=80=E5=9D=A8revive?= =?UTF-8?q?=E5=87=BD=E6=95=B0=E7=BB=99=E6=94=B9=E4=BA=86=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 使其能够嵌套,希望最后能跑起来。 --- src/utils/utils.ts | 54 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 185a9ff..bee52bc 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -79,23 +79,57 @@ export function withTimeout(asyncFunc: () => Promise, timeoutMs: number): ]); } +type TypeDescriptor = + | 'string' + | 'number' + | 'boolean' + | TypeDescriptor[] // 元组元素类型 + | { array: TypeDescriptor } // 数组元素类型 + | RevivableConstructor; // 嵌套类 + +interface RevivableConstructor { + new(): T; + validKeysMap: { [key in keyof T]?: TypeDescriptor }; +} + /** - * 恢复一个对象,只恢复构造函数中定义的属性,暂不支持嵌套属性 + * 恢复一个对象,只恢复构造函数中定义的属性,支持嵌套属性 希望没有bug * @param constructor 传入构造函数,必须有 validKeys 属性 * @param value 要恢复的对象 * @returns 恢复后的对象 */ -export function revive(constructor: { new(): T, validKeys: (keyof T)[] }, value: any): T { - const obj = new constructor(); - - if (!constructor.validKeys) { - logger.error(`revive: ${constructor.name} 没有 validKeys 属性`); - return obj; +export function revive(constructor: RevivableConstructor, value: any): T { + function reviveItem(descriptor: TypeDescriptor, defaultValue: any, value: any): any { + if (descriptor === 'string') { + if (typeof value === 'string') return value; + } else if (descriptor === 'number') { + if (typeof value === 'number') return value; + } else if (descriptor === 'boolean') { + if (typeof value === 'boolean') return value; + } else if (Array.isArray(descriptor)) { + if (Array.isArray(value)) return descriptor.map((d: any, index: number) => { + if (index < value.length && index < defaultValue.length) return reviveItem(d, defaultValue[index], value[index]); + if (index < defaultValue.length) return defaultValue[index]; + return undefined; + }); + } else if (typeof descriptor === 'object' && 'array' in descriptor) { + if (Array.isArray(value)) return value.map((item: any) => reviveItem(descriptor.array, defaultValue[0], item)); + } else if (typeof descriptor === 'function') { + return revive(descriptor, value); + } else { + return value; + } } - for (const k of constructor.validKeys) { - if (value.hasOwnProperty(k)) { - obj[k] = value[k]; + const obj: any = new constructor(); + + if (constructor.validKeysMap) { + for (const k in constructor.validKeysMap) { + const descriptor = constructor.validKeysMap[k]; + if (value.hasOwnProperty(k)) { + const item = reviveItem(descriptor, obj[k], value[k]); + if (item !== undefined) obj[k] = item; + } } } From a5cc44f74f6f796ac3aeeeb57e3a9304d4c5d9b3 Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Wed, 4 Mar 2026 21:20:18 +0800 Subject: [PATCH 03/33] =?UTF-8?q?=E5=95=A5=E4=B8=9C=E4=B8=9C=E8=BF=99?= =?UTF-8?q?=E6=98=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/session/session.ts | 36 ++++++++++++++++++++++++++++++++++-- src/session/user.ts | 5 +++++ src/trigger.ts | 1 + 3 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 src/session/user.ts create mode 100644 src/trigger.ts diff --git a/src/session/session.ts b/src/session/session.ts index 4fbec8d..fe276ec 100644 --- a/src/session/session.ts +++ b/src/session/session.ts @@ -1,11 +1,43 @@ +import { GroupInfo } from "../agent/AI"; +import { Context } from "./context"; +import { User } from "./user"; + +export class State{ + isPrivate: boolean; + group: { + groupId: string; + groupName: string; + owner: string; + adminList: string[]; + memberList: string[]; + description: string; // 自定义描述 + impression: string; // ai可修改的印象 + } + user: { + userId: string; + userName: string; + description: string; // 自定义描述 + impression: string; // ai可修改的印象 + } + tool: {} + [key: string]: any; +} + export class Session { - sid: string; - state: any; + sessionId: string; + state: { //储存状态信息 + groupId: string; + userIdArray: string[]; + [key: string]: any; + } + context: Context; memory: Memory; + image; } export class SessionService { sessionMap: { [key: string]: Session }; + userMap: { [key: string]: User }; constructor() { diff --git a/src/session/user.ts b/src/session/user.ts new file mode 100644 index 0000000..bc5752d --- /dev/null +++ b/src/session/user.ts @@ -0,0 +1,5 @@ +export class User { + uid: string; + state: any; //储存状态信息 + memory: Memory; +} \ No newline at end of file diff --git a/src/trigger.ts b/src/trigger.ts new file mode 100644 index 0000000..0049927 --- /dev/null +++ b/src/trigger.ts @@ -0,0 +1 @@ +// 把AI触发逻辑解耦到这里 \ No newline at end of file From 41d5e8b866888e879f38b60869b9a69728a8a7a9 Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Thu, 5 Mar 2026 01:01:41 +0800 Subject: [PATCH 04/33] =?UTF-8?q?=E5=81=B7=E5=81=B7=E7=BB=99=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E9=A1=B9=E7=BC=93=E5=AD=98=E5=8A=A0=E5=88=B060?= =?UTF-8?q?=E7=A7=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config/configManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/configManager.ts b/src/config/configManager.ts index 018cd92..000db96 100644 --- a/src/config/configManager.ts +++ b/src/config/configManager.ts @@ -35,7 +35,7 @@ export class ConfigManager { static getCache(key: string, getFunc: () => T): T { const timestamp = Date.now() - if (this.cache?.[key] && timestamp - this.cache[key].timestamp < 3000) { + if (this.cache?.[key] && timestamp - this.cache[key].timestamp < 60000) { return this.cache[key].data; } From 36301c9f461c2d6ebc46a2e342e560b8bb7c7b52 Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Thu, 5 Mar 2026 01:27:10 +0800 Subject: [PATCH 05/33] =?UTF-8?q?=E7=A8=8D=E5=BE=AE=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E4=BA=86=E4=B8=80=E4=B8=8Bmemory=EF=BC=8C=E7=9C=9F=E6=98=AF?= =?UTF-8?q?=E8=BE=9B=E8=8B=A6=E6=88=91=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 好多报错啊。 --- src/session/memory.ts | 102 ++++++++++++++++++++++------------------- src/session/session.ts | 3 +- src/utils/utils.ts | 3 +- 3 files changed, 56 insertions(+), 52 deletions(-) diff --git a/src/session/memory.ts b/src/session/memory.ts index fe1e55c..db06461 100644 --- a/src/session/memory.ts +++ b/src/session/memory.ts @@ -1,7 +1,7 @@ import { ConfigManager } from "../config/configManager"; import { AI, AIManager, GroupInfo, SessionInfo, UserInfo } from "./AI"; import { Context } from "./context"; -import { cosineSimilarity, generateId, getCommonGroup, getCommonKeyword, getCommonUser, revive } from "../utils/utils"; +import { cosineSimilarity, generateId, getCommonGroup, getCommonKeyword, getCommonUser, revive, TypeDescriptor } from "../utils/utils"; import { logger } from "../logger"; import { fetchData, getEmbedding } from "../agent/service"; import { buildContent, getRoleSetting, parseBody } from "../utils/utils_message"; @@ -18,51 +18,57 @@ export interface searchOptions { method: 'weight' | 'similarity' | 'score' | 'early' | 'late' | 'recent'; } -export class Memory { - static validKeys: (keyof Memory)[] = ['id', 'vector', 'text', 'sessionInfo', 'userList', 'groupList', 'createTime', 'lastMentionTime', 'keywords', 'weight', 'images']; +export class MemoryItem { + static validKeysMap: { [key in keyof MemoryItem]: TypeDescriptor } = { + 'id': 'string', + 'sourceSessionId': 'string', + 'createTime': 'number', + 'lastMentionTime': 'number', + 'weight': 'number', + 'content': 'string', + 'vector': { array: 'number' }, + 'keywords': { array: 'string' }, + 'userIdList': { array: 'string' }, + 'imageIdList': { array: 'string' } + }; + id: string; // 记忆ID - vector: number[]; // 记忆向量 - text: string; // 记忆内容 - sessionInfo: SessionInfo; - userList: UserInfo[]; - groupList: GroupInfo[]; + sourceSessionId: string; // 记忆来源会话ID createTime: number; // 秒级时间戳 - lastMentionTime: number; - keywords: string[]; + lastMentionTime: number; // 最后被提及时间,秒级时间戳 weight: number; // 记忆权重,0-10 - images: Image[]; + + content: string; // 记忆内容 + vector: number[]; // 记忆向量 + keywords: string[]; // 记忆关键词列表 + userIdList: string[]; // 记忆相关用户ID列表 + imageIdList: string[]; // 记忆相关图片ID列表 constructor() { this.id = ''; - this.vector = []; - this.text = ''; - this.sessionInfo = { - id: '', - isPrivate: false, - name: '', - }; - this.userList = []; - this.groupList = []; + this.sourceSessionId = ''; this.createTime = 0; this.lastMentionTime = 0; - this.keywords = []; this.weight = 0; - this.images = []; + this.content = ''; + this.vector = []; + this.keywords = []; + this.userIdList = []; + this.imageIdList = []; } - get copy(): Memory { - const m = new Memory(); + get copy(): MemoryItem { + const m = new MemoryItem(); m.id = this.id; - m.vector = [...this.vector]; - m.text = this.text; - m.sessionInfo = JSON.parse(JSON.stringify(this.sessionInfo)); - m.userList = JSON.parse(JSON.stringify(this.userList)); - m.groupList = JSON.parse(JSON.stringify(this.groupList)); + m.sourceSessionId = this.sourceSessionId; m.createTime = this.createTime; m.lastMentionTime = this.lastMentionTime; - m.keywords = [...this.keywords]; m.weight = this.weight; - m.images = [...this.images]; + m.content = this.content; + m.vector = [...this.vector]; + m.keywords = [...this.keywords]; + m.userIdList = [...this.userIdList]; + m.imageIdList = [...this.imageIdList]; return m; } @@ -127,7 +133,7 @@ export class Memory { const { isMemoryVector, embeddingDimension } = ConfigManager.memory; if (isMemoryVector) { logger.info(`更新记忆向量: ${this.id}`); - const vector = await getEmbedding(this.text); + const vector = await getEmbedding(this.content); if (!vector.length) { logger.error('返回向量为空'); return; @@ -144,7 +150,7 @@ export class Memory { export class MemoryManager { static validKeys: (keyof MemoryManager)[] = ['persona', 'memoryMap', 'useShortMemory', 'shortMemoryList']; persona: string; - memoryMap: { [id: string]: Memory }; + memoryMap: { [id: string]: MemoryItem }; useShortMemory: boolean; shortMemoryList: string[]; @@ -157,8 +163,8 @@ export class MemoryManager { reviveMemoryMap() { for (const id in this.memoryMap) { - this.memoryMap[id] = revive(Memory, this.memoryMap[id]); - if (!this.memoryMap[id].text) { + this.memoryMap[id] = revive(MemoryItem, this.memoryMap[id]); + if (!this.memoryMap[id].content) { delete this.memoryMap[id]; continue; } @@ -194,7 +200,7 @@ export class MemoryManager { for (const id of this.memoryIds) { const m = this.memoryMap[id]; - if (text === m.text && m.sessionInfo.id === ai.id && getCommonUser(ul, m.userList).length > 0 && getCommonGroup(gl, m.groupList).length > 0) { + if (text === m.content && m.sessionInfo.id === ai.id && getCommonUser(ul, m.userList).length > 0 && getCommonGroup(gl, m.groupList).length > 0) { m.keywords = Array.from(new Set([...m.keywords, ...kws])); logger.info(`记忆已存在,id:${id},合并关键词:${m.keywords.join(',')}`); return; @@ -210,9 +216,9 @@ export class MemoryManager { }); const now = Math.floor(Date.now() / 1000); - const m = new Memory(); + const m = new MemoryItem(); m.id = id; - m.text = text; + m.content = text; m.sessionInfo = { id: ai.id, isPrivate: ctx.isPrivate, @@ -478,7 +484,7 @@ export class MemoryManager { return this.buildMemory(si, latestMemoryList) + `\n当前页码: ${p}/${Math.ceil(this.memoryList.length / 5)}`; } - buildMemory(si: SessionInfo, ml: Memory[]): string { + buildMemory(si: SessionInfo, ml: MemoryItem[]): string { if (this.persona === '无' && ml.length === 0) return ''; const { showNumber } = ConfigManager.message; const { memoryShowTemplate, memorySingleShowTemplate } = ConfigManager.memory; @@ -501,7 +507,7 @@ export class MemoryManager { "相关用户": m.userList.map(u => u.name + (showNumber ? `(${u.id.replace(/^.+:/, '')})` : '')).join(';'), "相关群聊": m.groupList.map(g => g.name + (showNumber ? `(${g.id.replace(/^.+:/, '')})` : '')).join(';'), "关键词": m.keywords.join(';'), - "记忆内容": m.text + "记忆内容": m.content }); }).join('\n'); } @@ -571,7 +577,7 @@ export class MemoryManager { return null; } - findMemoryAndImageByImageIdPrefix(id: string): { memory: Memory, image: Image } | null { + findMemoryAndImageByImageIdPrefix(id: string): { memory: MemoryItem, image: Image } | null { for (const m of this.memoryList) { const image = m.images.find(img => img.id.replace(/_\d+$/, "") === id); if (image) { @@ -603,7 +609,7 @@ export class KnowledgeMemoryManager extends MemoryManager { const s = knowledgeMemoryStringList[roleIndex]; if (!s) return; - const memoryMap: { [id: string]: Memory } = {} + const memoryMap: { [id: string]: MemoryItem } = {} const segs = s.split(/\n-{3,}\n/); for (const seg of segs) { if (!seg.trim()) continue; @@ -611,7 +617,7 @@ export class KnowledgeMemoryManager extends MemoryManager { const lines = seg.split('\n'); if (lines.length === 0) continue; - const m = new Memory(); + const m = new MemoryItem(); for (let i = 0; i < lines.length; i++) { const match = lines[i].match(/^\s*?(ID|用户|群聊|关键词|图片|内容)\s*?[::](.*)/); if (!match) { @@ -665,14 +671,14 @@ export class KnowledgeMemoryManager extends MemoryManager { break; } case '内容': { - m.text = lines.slice(i).join('\n').trim().replace(/^内容[::]/, ''); + m.content = lines.slice(i).join('\n').trim().replace(/^内容[::]/, ''); break; } default: continue; } } - if (!m.id && !m.text) continue; + if (!m.id && !m.content) continue; memoryMap[m.id] = m; } @@ -682,7 +688,7 @@ export class KnowledgeMemoryManager extends MemoryManager { if (this.memoryMap.hasOwnProperty(m.id)) { const m2 = this.memoryMap[m.id]; m.vector = m2.vector; - if (m2.text !== m.text) await m.updateVector(); + if (m2.content !== m.content) await m.updateVector(); m.createTime = m2.createTime; m.lastMentionTime = m2.lastMentionTime; m.weight = m2.weight; @@ -698,7 +704,7 @@ export class KnowledgeMemoryManager extends MemoryManager { this.save(); } - buildKnowledgeMemory(memoryList: Memory[]) { + buildKnowledgeMemory(memoryList: MemoryItem[]) { const { showNumber } = ConfigManager.message; const { knowledgeMemorySingleShowTemplate } = ConfigManager.memory; if (memoryList.length === 0) return ''; @@ -715,7 +721,7 @@ export class KnowledgeMemoryManager extends MemoryManager { "用户列表": m.userList.map(u => u.name + (showNumber ? `(${u.id.replace(/^.+:/, '')})` : '')).join(';'), "群聊列表": m.groupList.map(g => g.name + (showNumber ? `(${g.id.replace(/^.+:/, '')})` : '')).join(';'), "关键词": m.keywords.join(';'), - "记忆内容": m.text + "记忆内容": m.content }); }).join('\n'); } diff --git a/src/session/session.ts b/src/session/session.ts index fe276ec..fd83d54 100644 --- a/src/session/session.ts +++ b/src/session/session.ts @@ -1,8 +1,7 @@ -import { GroupInfo } from "../agent/AI"; import { Context } from "./context"; import { User } from "./user"; -export class State{ +export class State { isPrivate: boolean; group: { groupId: string; diff --git a/src/utils/utils.ts b/src/utils/utils.ts index bee52bc..feadf7a 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -78,8 +78,7 @@ export function withTimeout(asyncFunc: () => Promise, timeoutMs: number): }) ]); } - -type TypeDescriptor = +export type TypeDescriptor = | 'string' | 'number' | 'boolean' From 58d29e4d873ba78a4f222f131a2558394acdcb6f Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Thu, 5 Mar 2026 14:14:48 +0800 Subject: [PATCH 06/33] =?UTF-8?q?=E5=AE=8C=E6=88=90=E9=83=A8=E5=88=86memor?= =?UTF-8?q?y=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/index.ts | 4 +- src/session/memory.ts | 420 +++++++++++++++++++------------------ src/tool/tool_memory.ts | 18 +- src/utils/utils.ts | 14 +- src/utils/utils_message.ts | 4 +- 5 files changed, 233 insertions(+), 227 deletions(-) diff --git a/src/index.ts b/src/index.ts index 0249500..273c7bf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,7 +8,7 @@ import { checkUpdate } from "./utils/utils_update"; import { TimerManager } from "./timer"; import { createMsg } from "./utils/utils_seal"; import { PrivilegeManager } from "./cmd/privilege"; -import { knowledgeMM } from "./session/memory"; +import { knowledgeService } from "./session/memory"; import { CQTYPESALLOW } from "./config/config"; import { registerCmd } from "./cmd/root"; @@ -17,7 +17,7 @@ function main() { checkUpdate(); ToolManager.registerTool(); TimerManager.init(); - knowledgeMM.init(); + knowledgeService.init(); const ext = ConfigManager.ext; diff --git a/src/session/memory.ts b/src/session/memory.ts index db06461..cdfbd1c 100644 --- a/src/session/memory.ts +++ b/src/session/memory.ts @@ -1,5 +1,4 @@ import { ConfigManager } from "../config/configManager"; -import { AI, AIManager, GroupInfo, SessionInfo, UserInfo } from "./AI"; import { Context } from "./context"; import { cosineSimilarity, generateId, getCommonGroup, getCommonKeyword, getCommonUser, revive, TypeDescriptor } from "../utils/utils"; import { logger } from "../logger"; @@ -7,19 +6,19 @@ import { fetchData, getEmbedding } from "../agent/service"; import { buildContent, getRoleSetting, parseBody } from "../utils/utils_message"; import { ToolManager } from "../tool/tool"; import { fmtDate } from "../utils/utils_string"; -import { Image, ImageManager } from "../image/image"; +import { Image } from "../image/image"; export interface searchOptions { topK: number; - userList: UserInfo[]; - groupList: GroupInfo[]; + userIdList: string[]; + groupIdList: string[]; keywords: string[]; includeImages: boolean; method: 'weight' | 'similarity' | 'score' | 'early' | 'late' | 'recent'; } export class MemoryItem { - static validKeysMap: { [key in keyof MemoryItem]: TypeDescriptor } = { + static validKeysMap: { [key in keyof MemoryItem]?: TypeDescriptor } = { 'id': 'string', 'sourceSessionId': 'string', 'createTime': 'number', @@ -29,6 +28,7 @@ export class MemoryItem { 'vector': { array: 'number' }, 'keywords': { array: 'string' }, 'userIdList': { array: 'string' }, + 'groupIdList': { array: 'string' }, 'imageIdList': { array: 'string' } }; @@ -42,6 +42,7 @@ export class MemoryItem { vector: number[]; // 记忆向量 keywords: string[]; // 记忆关键词列表 userIdList: string[]; // 记忆相关用户ID列表 + groupIdList: string[]; // 记忆相关群组ID列表 imageIdList: string[]; // 记忆相关图片ID列表 constructor() { @@ -54,6 +55,7 @@ export class MemoryItem { this.vector = []; this.keywords = []; this.userIdList = []; + this.groupIdList = []; this.imageIdList = []; } @@ -68,6 +70,7 @@ export class MemoryItem { m.vector = [...this.vector]; m.keywords = [...this.keywords]; m.userIdList = [...this.userIdList]; + m.groupIdList = [...this.groupIdList]; m.imageIdList = [...this.imageIdList]; return m; } @@ -96,18 +99,18 @@ export class MemoryItem { * @param kws 查询关键词列表 * @returns 相似度分数(0-1) */ - calculateSimilarity(v: number[], ul: UserInfo[], gl: GroupInfo[], kws: string[]): number { + calculateSimilarity(v: number[], ul: string[], gl: string[], kws: string[]): number { // 总权重 0-1 const totalWeight = (v.length ? 0.4 : 0) + (ul.length ? 0.2 : 0) + (gl.length ? 0.2 : 0) + (kws.length ? 0.2 : 0); if (totalWeight === 0) return 0; // 向量相似度分数(如果提供了向量v) 0-1 const vectorSimilarity = (v && v.length > 0 && this.vector && this.vector.length > 0) ? (cosineSimilarity(v, this.vector) + 1) / 2 : 0; // 用户相似度分数 0-1 - const commonUser = getCommonUser(this.userList, ul); - const userSimilarity = (ul && ul.length > 0) ? commonUser.length / (this.userList.length + ul.length - commonUser.length) : 0; + const commonUser = getCommonUser(this.userIdList, ul); + const userSimilarity = (ul && ul.length > 0) ? commonUser.length / ul.length : 0; // 群组相似度分数 0-1 - const commonGroup = getCommonGroup(this.groupList, gl); - const groupSimilarity = (gl && gl.length > 0) ? commonGroup.length / (this.groupList.length + gl.length - commonGroup.length) : 0; + const commonGroup = getCommonGroup(this.groupIdList, gl); + const groupSimilarity = (gl && gl.length > 0) ? commonGroup.length / gl.length : 0; // 关键词匹配分数 0-1 const commonKeyword = getCommonKeyword(this.keywords, kws); const keywordSimilarity = (kws && kws.length > 0) ? commonKeyword.length / kws.length : 0; @@ -125,7 +128,7 @@ export class MemoryItem { * @param kws 查询关键词列表 * @returns 相似度分数(0-1) */ - calculateScore(v: number[], ul: UserInfo[], gl: GroupInfo[], kws: string[]): number { + calculateScore(v: number[], ul: string[], gl: string[], kws: string[]): number { return this.weight * 0.03 + this.calculateSimilarity(v, ul, gl, kws) * 0.7; } @@ -147,33 +150,17 @@ export class MemoryItem { } } -export class MemoryManager { - static validKeys: (keyof MemoryManager)[] = ['persona', 'memoryMap', 'useShortMemory', 'shortMemoryList']; - persona: string; +export class MemoryService { + static validKeysMap: { [key in keyof MemoryService]?: TypeDescriptor } = { + memoryMap: { array: MemoryItem } + }; memoryMap: { [id: string]: MemoryItem }; - useShortMemory: boolean; - shortMemoryList: string[]; constructor() { - this.persona = '无'; this.memoryMap = {}; - this.useShortMemory = false; - this.shortMemoryList = []; } - reviveMemoryMap() { - for (const id in this.memoryMap) { - this.memoryMap[id] = revive(MemoryItem, this.memoryMap[id]); - if (!this.memoryMap[id].content) { - delete this.memoryMap[id]; - continue; - } - if (!this.memoryMap[id].hasOwnProperty('images')) this.memoryMap[id].images = []; - this.memoryMap[id].images = this.memoryMap[id].images.map(image => revive(Image, image)); - } - } - - get memoryIds() { + get memoryIdList() { return Object.keys(this.memoryMap); } @@ -187,20 +174,10 @@ export class MemoryManager { return Array.from(keywords); } - async addMemory(ctx: seal.MsgContext, ai: AI, ul: UserInfo[], gl: GroupInfo[], kws: string[], images: Image[], text: string) { - let id = generateId(), a = 0; - while (this.memoryMap.hasOwnProperty(id)) { - id = generateId(); - a++; - if (a > 1000) { - logger.error(`生成记忆id失败,已尝试1000次,放弃`); - return; - } - } - - for (const id of this.memoryIds) { + async addMemory(sid: string, ul: string[], gl: string[], kws: string[], il: string[], content: string) { + for (const id of this.memoryIdList) { const m = this.memoryMap[id]; - if (text === m.content && m.sessionInfo.id === ai.id && getCommonUser(ul, m.userList).length > 0 && getCommonGroup(gl, m.groupList).length > 0) { + if (content === m.content && sid === m.sourceSessionId && getCommonUser(ul, m.userIdList).length > 0 && getCommonGroup(gl, m.groupIdList).length > 0) { m.keywords = Array.from(new Set([...m.keywords, ...kws])); logger.info(`记忆已存在,id:${id},合并关键词:${m.keywords.join(',')}`); return; @@ -208,38 +185,45 @@ export class MemoryManager { } // 添加文本内插入的图片 - const imgIdSet = new Set(images.map(img => img.id)); - (await ImageManager.extractExistingImagesToSave(ctx, ai, text)).forEach(img => { + const imgIdSet = new Set(il); + (await ImageService.extractExistingImagesToSave(content)).forEach(img => { if (imgIdSet.has(img.id)) return; imgIdSet.add(img.id); - images.push(img); + il.push(img.id); }); + let id = generateId(), a = 0; + while (this.memoryMap.hasOwnProperty(id)) { + id = generateId(); + a++; + if (a > 1000) { + logger.error(`生成记忆id失败,已尝试1000次,放弃`); + return; + } + } + const now = Math.floor(Date.now() / 1000); const m = new MemoryItem(); m.id = id; - m.content = text; - m.sessionInfo = { - id: ai.id, - isPrivate: ctx.isPrivate, - name: ctx.isPrivate ? ctx.player.name : ctx.group.groupName, - }; - m.userList = ul; - m.groupList = gl; + m.sourceSessionId = sid; m.createTime = now; m.lastMentionTime = now; - m.keywords = kws; m.weight = 5; - m.images = images; + m.content = content; + m.keywords = kws; + m.userIdList = ul; + m.groupIdList = gl; + m.imageIdList = il; + await m.updateVector(); this.limitMemory(); this.memoryMap[id] = m; } - deleteMemory(ids: string[] = [], kws: string[] = []) { - if (ids.length === 0 && kws.length === 0) return; + deleteMemory(ml: string[] = [], kws: string[] = []) { + if (ml.length === 0 && kws.length === 0) return; - ids.forEach(id => delete this.memoryMap?.[id]) + ml.forEach(id => delete this.memoryMap?.[id]) if (kws.length > 0) { for (const id in this.memoryMap) { @@ -269,135 +253,16 @@ export class MemoryManager { this.memoryMap = {}; } - limitShortMemory() { - const { shortMemoryLimit } = ConfigManager.memory; - if (this.shortMemoryList.length > shortMemoryLimit) { - this.shortMemoryList.splice(0, this.shortMemoryList.length - shortMemoryLimit); - } - } - - clearShortMemory() { - this.shortMemoryList = []; - } - - async updateShortMemory(ctx: seal.MsgContext, msg: seal.Message, ai: AI) { - if (!this.useShortMemory) { - return; - } - - const { url: chatUrl, apiKey: chatApiKey } = ConfigManager.request; - const { isPrefix, showNumber, showMsgId, showTime } = ConfigManager.message; - const { shortMemorySummaryRound, memoryUrl, memoryApiKey, memoryBodyTemplate, memoryPromptTemplate } = ConfigManager.memory; - - const { roleSetting } = getRoleSetting(ctx); - - const messages = ai.context.messages; - let sumMessages = messages.slice(); - let round = 0; - for (let i = 0; i < messages.length; i++) { - if (messages[i].role === 'user' && !messages[i].name.startsWith('_')) { - round++; - } - if (round > shortMemorySummaryRound) { - sumMessages = messages.slice(0, i); // 只保留最近的shortMemorySummaryRound轮对话 - break; - } - } - - if (sumMessages.length === 0) { - return; - } - - let url = chatUrl; - let apiKey = chatApiKey; - if (memoryUrl.trim()) { - url = memoryUrl; - apiKey = memoryApiKey; - } - - try { - const prompt = memoryPromptTemplate({ - "角色设定": roleSetting, - "平台": ctx.endPoint.platform, - "私聊": ctx.isPrivate, - "展示号码": showNumber, - "用户名称": ctx.player.name, - "用户号码": ctx.player.userId.replace(/^.+:/, ''), - "群聊名称": ctx.group.groupName, - "群聊号码": ctx.group.groupId.replace(/^.+:/, ''), - "添加前缀": isPrefix, - "展示消息ID": showMsgId, - "展示时间": showTime, - "对话内容": isPrefix ? sumMessages.map(message => { - if (message.role === 'assistant' && message?.tool_calls && message?.tool_calls.length > 0) { - return `\n[function_call]: ${message.tool_calls.map((tool_call, index) => `${index + 1}. ${JSON.stringify(tool_call.function, null, 2)}`).join('\n')}`; - } - - return `[${message.role}]: ${buildContent(message)}`; - }).join('\n') : JSON.stringify(sumMessages) - }) - - logger.info(`记忆总结prompt:\n`, prompt); - - const messages = [ - { - role: "system", - content: prompt - } - ] - const bodyObject = parseBody(memoryBodyTemplate, messages, [], "none"); - - const time = Date.now(); - const data = await fetchData(url, apiKey, bodyObject); - - if (data.choices && data.choices.length > 0) { - AIManager.updateUsage(data.model, data.usage); - - const message = data.choices[0].message; - const finish_reason = data.choices[0].finish_reason; - - if (message.hasOwnProperty('reasoning_content')) { - logger.info(`思维链内容:`, message.reasoning_content); - } - - const reply = message.content || ''; - logger.info(`响应内容:`, reply, '\nlatency:', Date.now() - time, 'ms', '\nfinish_reason:', finish_reason); - - const memoryData = JSON.parse(reply) as { - content: string, - memories: { - memory_type: 'private' | 'group', - name: string, - text: string, - keywords?: string[], - userList?: string[], - groupList?: string[], - }[] - }; - - - this.shortMemoryList.push(memoryData.content); - this.limitShortMemory(); - - memoryData.memories.forEach(m => { - ToolManager.toolMap["add_memory"].solve(ctx, msg, ai, m); - }); - } - } catch (e) { - logger.error(`更新短期记忆失败: ${e.message}`); - } - } - async search(query: string, options: searchOptions = { topK: 10, - userList: [], - groupList: [], + userIdList: [], + groupIdList: [], keywords: [], includeImages: false, method: 'score' }) { if (!this.memoryList.length) return []; - const { userList: ul, groupList: gl, keywords: kws, includeImages, method } = options; + const { userIdList: ul, groupIdList: gl, keywords: kws, includeImages, method } = options; const { isMemoryVector, embeddingDimension } = ConfigManager.memory; let qv: number[] = []; @@ -417,7 +282,7 @@ export class MemoryManager { return this.memoryList .map(m => { - if (includeImages && m.images.length === 0) return null; + if (includeImages && m.imageIdList.length === 0) return null; const mc = m.copy; if (mc.keywords.some(kw => query.includes(kw))) mc.weight += 10; //提权 return mc; @@ -452,40 +317,42 @@ export class MemoryManager { } } + // wip updateRelatedMemoryWeight(ctx: seal.MsgContext, context: Context, s: string, role: 'user' | 'assistant') { // bot记忆权重更新 AIManager.getAI(ctx.endPoint.userId).memory.updateMemoryWeight(s, role); // 知识库记忆权重更新 - knowledgeMM.updateMemoryWeight(s, role); + knowledgeService.updateMemoryWeight(s, role); // 会话自身记忆权重更新 this.updateMemoryWeight(s, role); // 群内用户的记忆权重更新 if (!ctx.isPrivate) context.userInfoList.forEach(ui => AIManager.getAI(ui.id).memory.updateMemoryWeight(s, role)); } - async getTopScoreMemoryList(text: string = '', ui: UserInfo = null, gi: GroupInfo = null) { + async getTopScoreMemoryList(text: string = '', uid: string = '', gid: string = '') { const { memoryShowNumber } = ConfigManager.memory; return await this.search(text, { topK: memoryShowNumber, - userList: ui ? [ui] : [], - groupList: gi ? [gi] : [], + userIdList: uid ? [uid] : [], + groupIdList: gid ? [gid] : [], keywords: [], includeImages: false, method: 'score' }); } - getLatestMemoryListText(si: SessionInfo, p: number = 1): string { + getLatestMemoryListText(sid: string, p: number = 1): string { if (this.memoryList.length === 0) return ''; if (p > Math.ceil(this.memoryList.length / 5)) p = Math.ceil(this.memoryList.length / 5); const latestMemoryList = this.memoryList .sort((a, b) => b.createTime - a.createTime) .slice((p - 1) * 5, p * 5); - return this.buildMemory(si, latestMemoryList) + `\n当前页码: ${p}/${Math.ceil(this.memoryList.length / 5)}`; + return this.buildMemory(sid, latestMemoryList) + `\n当前页码: ${p}/${Math.ceil(this.memoryList.length / 5)}`; } - buildMemory(si: SessionInfo, ml: MemoryItem[]): string { - if (this.persona === '无' && ml.length === 0) return ''; + // wip 和默认配置一起改 + buildMemory(sid: string, ml: MemoryItem[]): string { + if (ml.length === 0) return ''; const { showNumber } = ConfigManager.message; const { memoryShowTemplate, memorySingleShowTemplate } = ConfigManager.memory; @@ -524,6 +391,7 @@ export class MemoryManager { }) + '\n'; } + // wip async buildMemoryPrompt(ctx: seal.MsgContext, context: Context, text: string, ui: UserInfo, gi: GroupInfo): Promise { const ai = AIManager.getAI(ctx.endPoint.userId); let s = ai.memory.buildMemory({ @@ -566,43 +434,179 @@ export class MemoryManager { } } - findImage(id: string): Image | null { + includedImage(id: string): boolean { for (const m of this.memoryList) { - const image = m.images.find(img => img.id === id); + const image = m.imageIdList.find(i => i === id); if (image) { m.weight += 0.2; - return image; + return true; } } - return null; + return false; } - findMemoryAndImageByImageIdPrefix(id: string): { memory: MemoryItem, image: Image } | null { + findMemoryByImageIdPrefix(id: string): MemoryItem | null { for (const m of this.memoryList) { - const image = m.images.find(img => img.id.replace(/_\d+$/, "") === id); + const image = m.imageIdList.find(img => img.replace(/_\d+$/, "") === id); if (image) { m.weight += 0.2; - return { memory: m, image }; + return m; } } return null; } } -export class KnowledgeMemoryManager extends MemoryManager { +export class SessionMemoryService extends MemoryService { + static validKeysMap: { [key in keyof SessionMemoryService]?: TypeDescriptor } = { + memoryMap: { array: MemoryItem }, + summaryStatus: 'boolean', + summaryList: { array: 'string' } + }; + summaryStatus: boolean; + summaryList: string[]; + + constructor() { + super(); + this.summaryStatus = false; + this.summaryList = []; + } + + limitSummary() { + const { SummaryLimit } = ConfigManager.memory; + if (this.summaryList.length > SummaryLimit) { + this.summaryList.splice(0, this.summaryList.length - SummaryLimit); + } + } + + clearSummary() { + this.summaryList = []; + } + + // wip + async updateSummary(ctx: seal.MsgContext, msg: seal.Message, ai: AI) { + if (!this.summaryStatus) return; + + const { url: chatUrl, apiKey: chatApiKey } = ConfigManager.request; + const { isPrefix, showNumber, showMsgId, showTime } = ConfigManager.message; + const { shortMemorySummaryRound, memoryUrl, memoryApiKey, memoryBodyTemplate, memoryPromptTemplate } = ConfigManager.memory; + + const { roleSetting } = getRoleSetting(ctx); + + const messages = ai.context.messages; + let sumMessages = messages.slice(); + let round = 0; + for (let i = 0; i < messages.length; i++) { + if (messages[i].role === 'user' && !messages[i].name.startsWith('_')) { + round++; + } + if (round > shortMemorySummaryRound) { + sumMessages = messages.slice(0, i); // 只保留最近的shortMemorySummaryRound轮对话 + break; + } + } + + if (sumMessages.length === 0) { + return; + } + + let url = chatUrl; + let apiKey = chatApiKey; + if (memoryUrl.trim()) { + url = memoryUrl; + apiKey = memoryApiKey; + } + + try { + const prompt = memoryPromptTemplate({ + "角色设定": roleSetting, + "平台": ctx.endPoint.platform, + "私聊": ctx.isPrivate, + "展示号码": showNumber, + "用户名称": ctx.player.name, + "用户号码": ctx.player.userId.replace(/^.+:/, ''), + "群聊名称": ctx.group.groupName, + "群聊号码": ctx.group.groupId.replace(/^.+:/, ''), + "添加前缀": isPrefix, + "展示消息ID": showMsgId, + "展示时间": showTime, + "对话内容": isPrefix ? sumMessages.map(message => { + if (message.role === 'assistant' && message?.tool_calls && message?.tool_calls.length > 0) { + return `\n[function_call]: ${message.tool_calls.map((tool_call, index) => `${index + 1}. ${JSON.stringify(tool_call.function, null, 2)}`).join('\n')}`; + } + + return `[${message.role}]: ${buildContent(message)}`; + }).join('\n') : JSON.stringify(sumMessages) + }) + + logger.info(`记忆总结prompt:\n`, prompt); + + const messages = [ + { + role: "system", + content: prompt + } + ] + const bodyObject = parseBody(memoryBodyTemplate, messages, [], "none"); + + const time = Date.now(); + const data = await fetchData(url, apiKey, bodyObject); + + if (data.choices && data.choices.length > 0) { + AIManager.updateUsage(data.model, data.usage); + + const message = data.choices[0].message; + const finish_reason = data.choices[0].finish_reason; + + if (message.hasOwnProperty('reasoning_content')) { + logger.info(`思维链内容:`, message.reasoning_content); + } + + const reply = message.content || ''; + logger.info(`响应内容:`, reply, '\nlatency:', Date.now() - time, 'ms', '\nfinish_reason:', finish_reason); + + const memoryData = JSON.parse(reply) as { + content: string, + memories: { + memory_type: 'private' | 'group', + name: string, + text: string, + keywords?: string[], + userList?: string[], + groupList?: string[], + }[] + }; + + + this.shortMemoryList.push(memoryData.content); + this.limitShortMemory(); + + memoryData.memories.forEach(m => { + ToolManager.toolMap["add_memory"].solve(ctx, msg, ai, m); + }); + } + } catch (e) { + logger.error(`更新短期记忆失败: ${e.message}`); + } + } +} + +export class KnowledgeService extends MemoryService { constructor() { super(); } init() { - this.memoryMap = JSON.parse(ConfigManager.ext.storageGet('knowledgeMemoryMap') || '{}'); - this.reviveMemoryMap(); + const data = JSON.parse(ConfigManager.ext.storageGet('knowledge') || '{}'); + const ms = revive(MemoryService, data); + this.memoryMap = ms.memoryMap; } save() { - ConfigManager.ext.storageSet('knowledgeMemoryMap', JSON.stringify(this.memoryMap)); + ConfigManager.ext.storageSet('knowledge', JSON.stringify(this.memoryMap)); } + // wip 和配置一起改 async updateKnowledgeMemory(roleIndex: number) { const { knowledgeMemoryStringList } = ConfigManager.memory; if (roleIndex < 0 || roleIndex >= knowledgeMemoryStringList.length) return; @@ -704,6 +708,7 @@ export class KnowledgeMemoryManager extends MemoryManager { this.save(); } + // wip buildKnowledgeMemory(memoryList: MemoryItem[]) { const { showNumber } = ConfigManager.message; const { knowledgeMemorySingleShowTemplate } = ConfigManager.memory; @@ -729,15 +734,16 @@ export class KnowledgeMemoryManager extends MemoryManager { return prompt; } + // wip async buildKnowledgeMemoryPrompt(roleIndex: number, text: string, ui: UserInfo, gi: GroupInfo): Promise { await this.updateKnowledgeMemory(roleIndex); - if (this.memoryIds.length === 0) return ''; + if (this.memoryIdList.length === 0) return ''; const { knowledgeMemoryShowNumber } = ConfigManager.memory; const memoryList = await this.search(text, { topK: knowledgeMemoryShowNumber, - userList: ui ? [ui] : [], - groupList: gi ? [gi] : [], + userIdList: ui ? [ui] : [], + groupIdList: gi ? [gi] : [], keywords: [], includeImages: false, method: 'score' @@ -747,7 +753,7 @@ export class KnowledgeMemoryManager extends MemoryManager { } } -export const knowledgeMM = new KnowledgeMemoryManager(); +export const knowledgeService = new KnowledgeService(); // 可以通过维护一组索引来优化搜索性能。 // 好麻烦,不想弄 diff --git a/src/tool/tool_memory.ts b/src/tool/tool_memory.ts index cfa61b1..cb94e37 100644 --- a/src/tool/tool_memory.ts +++ b/src/tool/tool_memory.ts @@ -2,7 +2,7 @@ import { AIManager, GroupInfo, SessionInfo, UserInfo } from "../AI/AI"; import { ConfigManager } from "../config/configManager"; import { getCtxAndMsg } from "../utils/utils_seal"; import { Tool } from "./tool"; -import { knowledgeMM, searchOptions as SearchOptions } from "../session/memory"; +import { knowledgeService, searchOptions as SearchOptions } from "../session/memory"; import { getRoleSetting } from "../utils/utils_message"; export function registerMemory() { @@ -242,20 +242,20 @@ export function registerMemory() { const options: SearchOptions = { topK: topK, keywords: keywords, - userList: userList, - groupList: groupList, + userIdList: userList, + groupIdList: groupList, includeImages: includeImages, method: method } const { roleIndex } = getRoleSetting(ctx); - await knowledgeMM.updateKnowledgeMemory(roleIndex); - if (knowledgeMM.memoryIds.length === 0) return { content: `暂无记忆`, images: [] }; + await knowledgeService.updateKnowledgeMemory(roleIndex); + if (knowledgeService.memoryIdList.length === 0) return { content: `暂无记忆`, images: [] }; - const memoryList = await knowledgeMM.search(query, options); + const memoryList = await knowledgeService.search(query, options); const images = Array.from(new Set([].concat(...memoryList.map(m => m.images)))); - return { content: knowledgeMM.buildKnowledgeMemory(memoryList) || '暂无记忆', images: images }; + return { content: knowledgeService.buildKnowledgeMemory(memoryList) || '暂无记忆', images: images }; } else { return { content: `未知的记忆类型<${memory_type}>`, images: [] }; } @@ -276,8 +276,8 @@ export function registerMemory() { const options: SearchOptions = { topK: topK, keywords: keywords, - userList: userList, - groupList: groupList, + userIdList: userList, + groupIdList: groupList, includeImages: includeImages, method: method } diff --git a/src/utils/utils.ts b/src/utils/utils.ts index feadf7a..0a2132a 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -87,7 +87,7 @@ export type TypeDescriptor = | RevivableConstructor; // 嵌套类 interface RevivableConstructor { - new(): T; + new(): T; // 构造函数必须无参数 validKeysMap: { [key in keyof T]?: TypeDescriptor }; } @@ -160,15 +160,15 @@ export function cosineSimilarity(a: number[], b: number[]): number { return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB)); } -export function getCommonUser(a: UserInfo[], b: UserInfo[]): UserInfo[] { +export function getCommonUser(a: string[], b: string[]): string[] { if (a.length === 0 || b.length === 0) return []; - const aid = new Set(a.map(u => u.id)); - return b.filter(u => aid.has(u.id)); + const aid = new Set(a); + return b.filter(u => aid.has(u)); } -export function getCommonGroup(a: GroupInfo[], b: GroupInfo[]): GroupInfo[] { +export function getCommonGroup(a: string[], b: string[]): string[] { if (a.length === 0 || b.length === 0) return []; - const aid = new Set(a.map(g => g.id)); - return b.filter(g => aid.has(g.id)); + const aid = new Set(a); + return b.filter(g => aid.has(g)); } export function getCommonKeyword(a: string[], b: string[]): string[] { if (a.length === 0 || b.length === 0) return []; diff --git a/src/utils/utils_message.ts b/src/utils/utils_message.ts index 4667bb0..2edcd5d 100644 --- a/src/utils/utils_message.ts +++ b/src/utils/utils_message.ts @@ -3,7 +3,7 @@ import { Message } from "../session/context"; import { ConfigManager } from "../config/configManager"; import { ToolInfo } from "../tool/tool"; import { fmtDate } from "./utils_string"; -import { knowledgeMM } from "../session/memory"; +import { knowledgeService } from "../session/memory"; export async function buildSystemMessage(ctx: seal.MsgContext, ai: AI): Promise { const { systemMessageTemplate, isPrefix, showNumber, showMsgId, showTime } = ConfigManager.message; @@ -38,7 +38,7 @@ export async function buildSystemMessage(ctx: seal.MsgContext, ai: AI): Promise< } // 知识库 - const knowledgePrompt = await knowledgeMM.buildKnowledgeMemoryPrompt(roleIndex, text, ui, gi); + const knowledgePrompt = await knowledgeService.buildKnowledgeMemoryPrompt(roleIndex, text, ui, gi); // 记忆 const memoryPrompt = isMemory ? await ai.memory.buildMemoryPrompt(ctx, ai.context, text, ui, gi) : ''; // 短期记忆 From 0cd566048d5b2e6e6ed5f0165aa89c18fbb1522d Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Thu, 5 Mar 2026 16:25:42 +0800 Subject: [PATCH 07/33] =?UTF-8?q?=E6=95=B4=E4=B8=80=E5=B0=8F=E5=9D=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 把user和group对象整出来说是。 --- src/session/group.ts | 47 +++++++++++++++++++++++++++++++++++ src/session/session.ts | 56 ++++++++++++++++++++++-------------------- src/session/user.ts | 42 ++++++++++++++++++++++++++++--- src/utils/utils.ts | 10 +++++++- 4 files changed, 125 insertions(+), 30 deletions(-) create mode 100644 src/session/group.ts diff --git a/src/session/group.ts b/src/session/group.ts new file mode 100644 index 0000000..e1c5cce --- /dev/null +++ b/src/session/group.ts @@ -0,0 +1,47 @@ +import { ConfigManager } from "../config/configManager"; +import { logger } from "../logger"; +import { revive, TypeDescriptor } from "../utils/utils"; +import { MemoryService } from "./memory"; +import { State } from "./session"; + +export class Group { + static validKeysMap: { [key in keyof Group]?: TypeDescriptor } = { + groupId: 'string', + groupName: 'string', + owner: 'string', + adminList: { array: 'string' }, + memberList: { array: 'string' }, + description: 'string', + impression: 'string', + state: 'any', + memory: MemoryService + } + groupId: string; + groupName: string; + owner: string; + adminList: string[]; + memberList: string[]; + description: string; // 自定义描述 + impression: string; // ai可修改的印象 + + state: State; //储存状态信息 + memory: MemoryService; +} + +export class GroupManager { + static groupMap: { [key: string]: Group }; + + static getGroup(groupId: string): Group { + if (!this.groupMap.hasOwnProperty(groupId)) { + let group = new Group(); + try { + const data = JSON.parse(ConfigManager.ext.storageGet(`group_${groupId}`) || '{}'); + group = revive(Group, data); + } catch (error) { + logger.error(`加载群${groupId}失败: ${error}`); + } + this.groupMap[groupId] = group; + } + return this.groupMap[groupId]; + } +} diff --git a/src/session/session.ts b/src/session/session.ts index fd83d54..5043c17 100644 --- a/src/session/session.ts +++ b/src/session/session.ts @@ -1,44 +1,48 @@ +import { ConfigManager } from "../config/configManager"; +import { logger } from "../logger"; +import { revive, TypeDescriptor } from "../utils/utils"; import { Context } from "./context"; -import { User } from "./user"; +import { MemoryService } from "./memory"; export class State { - isPrivate: boolean; - group: { - groupId: string; - groupName: string; - owner: string; - adminList: string[]; - memberList: string[]; - description: string; // 自定义描述 - impression: string; // ai可修改的印象 - } - user: { - userId: string; - userName: string; - description: string; // 自定义描述 - impression: string; // ai可修改的印象 - } - tool: {} [key: string]: any; } export class Session { - sessionId: string; - state: { //储存状态信息 - groupId: string; - userIdArray: string[]; - [key: string]: any; + static validKeysMap: { [key in keyof Session]?: TypeDescriptor } = { + isPrivate: 'boolean', + sessionId: 'string', + state: 'any', + context: Context, + memory: MemoryService, + // tool: ToolState; wip } + isPrivate: boolean; + sessionId: string; + state: State; context: Context; - memory: Memory; - image; + memory: MemoryService; + // tool: ToolState; wip } export class SessionService { sessionMap: { [key: string]: Session }; - userMap: { [key: string]: User }; constructor() { + this.sessionMap = {}; + } + getSession(sessionId: string): Session { + if (!this.sessionMap.hasOwnProperty(sessionId)) { + let session = new Session(); + try { + const data = JSON.parse(ConfigManager.ext.storageGet(`session_${sessionId}`) || '{}'); + session = revive(Session, data); + } catch (error) { + logger.error(`加载会话${sessionId}失败: ${error}`); + } + this.sessionMap[sessionId] = session; + } + return this.sessionMap[sessionId]; } } \ No newline at end of file diff --git a/src/session/user.ts b/src/session/user.ts index bc5752d..e6bc27a 100644 --- a/src/session/user.ts +++ b/src/session/user.ts @@ -1,5 +1,41 @@ +import { ConfigManager } from "../config/configManager"; +import { logger } from "../logger"; +import { revive, TypeDescriptor } from "../utils/utils"; +import { MemoryService } from "./memory"; +import { State } from "./session"; + export class User { - uid: string; - state: any; //储存状态信息 - memory: Memory; + static validKeysMap: { [key in keyof User]?: TypeDescriptor } = { + userId: 'string', + userName: 'string', + description: 'string', + impression: 'string', + state: 'any', + memory: MemoryService + } + userId: string; + userName: string; + description: string; + impression: string; // ai可修改的印象 + + state: State; //储存状态信息 + memory: MemoryService; +} + +export class UserManager { + static userMap: { [key: string]: User }; + + static getUser(userId: string): User { + if (!this.userMap.hasOwnProperty(userId)) { + let user = new User(); + try { + const data = JSON.parse(ConfigManager.ext.storageGet(`user_${userId}`) || '{}'); + user = revive(User, data); + } catch (error) { + logger.error(`加载用户${userId}失败: ${error}`); + } + this.userMap[userId] = user; + } + return this.userMap[userId]; + } } \ No newline at end of file diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 0a2132a..1d4cd22 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -1,4 +1,4 @@ -import { AI, GroupInfo, UserInfo } from "../AI/AI"; +import { AI } from "../AI/AI"; import { logger } from "../logger"; import { ConfigManager } from "../config/configManager"; import { transformTextToArray } from "./utils_string"; @@ -78,10 +78,12 @@ export function withTimeout(asyncFunc: () => Promise, timeoutMs: number): }) ]); } + export type TypeDescriptor = | 'string' | 'number' | 'boolean' + | 'any' | TypeDescriptor[] // 元组元素类型 | { array: TypeDescriptor } // 数组元素类型 | RevivableConstructor; // 嵌套类 @@ -105,6 +107,8 @@ export function revive(constructor: RevivableConstructor, value: any): T { if (typeof value === 'number') return value; } else if (descriptor === 'boolean') { if (typeof value === 'boolean') return value; + } else if (descriptor === 'any') { + return value; } else if (Array.isArray(descriptor)) { if (Array.isArray(value)) return descriptor.map((d: any, index: number) => { if (index < value.length && index < defaultValue.length) return reviveItem(d, defaultValue[index], value[index]); @@ -130,6 +134,10 @@ export function revive(constructor: RevivableConstructor, value: any): T { if (item !== undefined) obj[k] = item; } } + } else { // 没有定义 validKeysMap,直接赋值 + for (const k in value) { + obj[k] = value[k]; + } } return obj; From 2e54cb9d3ed2c8dd6dced5704cffdf86d986e2df Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Thu, 5 Mar 2026 17:20:34 +0800 Subject: [PATCH 08/33] =?UTF-8?q?=E9=87=8D=E6=9E=84=E4=BA=86=E4=B8=80?= =?UTF-8?q?=E7=82=B9context?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 等agent写好了再去写它的方法 --- src/cmd/sub_cmd/prompt.ts | 4 +- src/note.txt | 10 ++++ src/session/context.ts | 115 +++++++++++++++---------------------- src/session/session.ts | 12 ++++ src/utils/utils_message.ts | 8 +-- src/utils/utils_string.ts | 2 +- 6 files changed, 76 insertions(+), 75 deletions(-) create mode 100644 src/note.txt diff --git a/src/cmd/sub_cmd/prompt.ts b/src/cmd/sub_cmd/prompt.ts index 1b10b29..883d045 100644 --- a/src/cmd/sub_cmd/prompt.ts +++ b/src/cmd/sub_cmd/prompt.ts @@ -11,8 +11,8 @@ export function registerCmdPrompt() { cmd.solve = async (scc: SubCmdContext) => { const { ctx, msg, ai, ret } = scc; const systemMessage = await buildSystemMessage(ctx, ai); - logger.info(`system prompt:\n`, systemMessage.msgArray[0].content); - seal.replyToSender(ctx, msg, systemMessage.msgArray[0].content); + logger.info(`system prompt:\n`, systemMessage.msgArray[0].text); + seal.replyToSender(ctx, msg, systemMessage.msgArray[0].text); return ret; } } diff --git a/src/note.txt b/src/note.txt new file mode 100644 index 0000000..266b0d6 --- /dev/null +++ b/src/note.txt @@ -0,0 +1,10 @@ +tool state + +summary count + +last reply 放进 send 里,所有send换成一个,除了指令调用。 + +计数器、计时器逻辑移到trigger + +autoNameMod: number; // 自动修改上下文里的名字,0:不自动修改,1:修改为昵称,2:修改为群名片 +放到user manager \ No newline at end of file diff --git a/src/session/context.ts b/src/session/context.ts index 06dce16..8bf9076 100644 --- a/src/session/context.ts +++ b/src/session/context.ts @@ -6,79 +6,58 @@ import { levenshteinDistance } from "../utils/utils_string"; import { AI, AIManager, GroupInfo, UserInfo } from "./AI"; import { logger } from "../logger"; import { netExists, getFriendList, getGroupList, getGroupMemberInfo, getGroupMemberList, getStrangerInfo } from "../utils/utils_ob11"; -import { revive } from "../utils/utils"; +import { revive, TypeDescriptor } from "../utils/utils"; -export interface MessageInfo { - msgId: string; +export class MessageItem { time: number; // 秒 - content: string; + text: string; } -export interface Message { - role: string; - tool_calls?: ToolCall[]; - tool_call_id?: string; - - uid: string; - name: string; - images: Image[]; - msgArray: MessageInfo[]; +export class UserMessageItem extends MessageItem { + userId: string; + messageId: string; } -export class Context { - static validKeys: (keyof Context)[] = ['messages', 'ignoreList', 'summaryCounter', 'autoNameMod']; - messages: Message[]; - ignoreList: string[]; - summaryCounter: number; // 用于短期记忆自动总结计数 - autoNameMod: number; // 自动修改上下文里的名字,0:不自动修改,1:修改为昵称,2:修改为群名片 - - lastReply: string; - counter: number; - timer: number; - - constructor() { - this.messages = []; - this.ignoreList = []; - this.summaryCounter = 0; - this.lastReply = ''; - this.counter = 0; - this.timer = null; - } +export class AssistantMessageItem extends MessageItem { + messageId: string; +} - reviveMessages() { - this.messages = this.messages.map(message => { - if (!message.hasOwnProperty('role')) return null; - if (!message.hasOwnProperty('uid')) return null; - if (!message.hasOwnProperty('name')) return null; - if (!message.hasOwnProperty('images')) return null; - if (!message.hasOwnProperty('msgArray')) return null; +export class SystemMessageItem extends MessageItem { + tip: string; +} - message.msgArray = message.msgArray.map(msgInfo => { - if (!msgInfo.hasOwnProperty('msgId')) return null; - if (!msgInfo.hasOwnProperty('time')) return null; - if (!msgInfo.hasOwnProperty('content')) return null; +export class ToolCallsMessageItem extends MessageItem { + tool_calls: ToolCall[]; +} - return msgInfo; - }).filter(msgInfo => msgInfo); +export class ToolCallbackMessageItem extends MessageItem { + tool_call_id: string; +} - message.images = message.images.map(image => revive(Image, image)); +export class Context { + static validKeysMap: {[key in keyof Context]?: TypeDescriptor} = { + messages: 'any' + } + messages: MessageItem[]; - return message; - }).filter(message => message); + constructor() { + this.messages = []; } - clearMessages(...roles: string[]) { - if (roles.length === 0) { - this.summaryCounter = 0; - this.messages = []; - } else { - this.messages = this.messages.filter(message => { - if (roles.includes(message.role)) { - this.summaryCounter--; - return false; - } - return true; - }); + clearMessages(role?: 'user' | 'assistant') { + switch (role) { + case 'user': { + this.messages = this.messages.filter(m => !m.hasOwnProperty('userId')); + break; + } + case 'assistant': { + this.messages = this.messages.filter(m => m.hasOwnProperty('userId')); + break; + } + default: { + this.messages = []; + break; + } } } @@ -123,9 +102,9 @@ export class Context { if (length !== 0 && messages[length - 1].uid === uid && !/<[\|│|]?function(?:_call)?>/.test(content)) { messages[length - 1].images.push(...images); messages[length - 1].msgArray.push({ - msgId: msgId, + messageId: msgId, time: now, - content: content + text: content }); } else { const message: Message = { @@ -134,9 +113,9 @@ export class Context { name: name, images: images, msgArray: [{ - msgId: msgId, + messageId: msgId, time: now, - content: content + text: content }] }; messages.push(message); @@ -181,9 +160,9 @@ export class Context { name: '', images: images, msgArray: [{ - msgId: '', + messageId: '', time: now, - content: s + text: s }] }; @@ -205,9 +184,9 @@ export class Context { name: `_${name}`, images: images, msgArray: [{ - msgId: '', + messageId: '', time: now, - content: s + text: s }] }; this.messages.push(message); diff --git a/src/session/session.ts b/src/session/session.ts index 5043c17..b7192c5 100644 --- a/src/session/session.ts +++ b/src/session/session.ts @@ -16,6 +16,7 @@ export class Session { context: Context, memory: MemoryService, // tool: ToolState; wip + ignoredUserIdList: { array: 'string' }, } isPrivate: boolean; sessionId: string; @@ -23,6 +24,17 @@ export class Session { context: Context; memory: MemoryService; // tool: ToolState; wip + ignoredUserIdList: string[]; + + constructor() { + this.isPrivate = false; + this.sessionId = ''; + this.state = {}; + this.context = new Context(); + this.memory = new MemoryService(); + // this.tool = new ToolState(); + this.ignoredUserIdList = []; + } } export class SessionService { diff --git a/src/utils/utils_message.ts b/src/utils/utils_message.ts index 2edcd5d..22d8ae7 100644 --- a/src/utils/utils_message.ts +++ b/src/utils/utils_message.ts @@ -77,9 +77,9 @@ export async function buildSystemMessage(ctx: seal.MsgContext, ai: AI): Promise< name: '', images: [], msgArray: [{ - msgId: '', + messageId: '', time: Math.floor(Date.now() / 1000), - content: content + text: content }] }; @@ -297,9 +297,9 @@ export function buildContent(message: Message): string { `<|from:${message.name}${showNumber ? `(${message.uid.replace(/^.+:/, '')})` : ``}|>` ) : ''; const content = message.msgArray.map(m => - ((showMsgId && m.msgId) ? `<|msg_id:${m.msgId}|>` : '') + + ((showMsgId && m.messageId) ? `<|msg_id:${m.messageId}|>` : '') + (showTime ? `<|time:${fmtDate(m.time)}|>` : '') + - m.content + m.text ).join('\f'); return prefix + content; } diff --git a/src/utils/utils_string.ts b/src/utils/utils_string.ts index 4059eef..d93cbfb 100644 --- a/src/utils/utils_string.ts +++ b/src/utils/utils_string.ts @@ -348,7 +348,7 @@ export function checkRepeat(context: Context, s: string) { const message = messages[i]; // 寻找最后一条文本消息 if (message.role === 'assistant' && !message?.tool_calls) { - const content = message.msgArray[message.msgArray.length - 1].content || ''; + const content = message.msgArray[message.msgArray.length - 1].text || ''; const similarity = calculateSimilarity(content.trim(), s.trim()); logger.info(`复读相似度:${similarity}`); From a0151e7597ae613bf7e5f75af3a08b75fb1e2343 Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Thu, 5 Mar 2026 21:43:11 +0800 Subject: [PATCH 09/33] =?UTF-8?q?=E5=AE=8C=E6=88=90=E4=BA=86image=E9=83=A8?= =?UTF-8?q?=E5=88=86=E7=9A=84=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/agent/agent.ts | 49 ++++++++-- src/agent/model.ts | 51 +++++++++- src/cmd/sub_cmd/image.ts | 2 +- src/image/image.ts | 205 ++++++++++++++++++++------------------- src/note.txt | 12 ++- src/session/group.ts | 1 + src/session/session.ts | 4 + src/session/user.ts | 1 + src/tool/tool_image.ts | 8 +- src/tool/tool_meme.ts | 10 +- src/tool/tool_render.ts | 16 +-- src/utils/utils.ts | 17 +++- src/utils/utils_seal.ts | 6 +- 13 files changed, 248 insertions(+), 134 deletions(-) diff --git a/src/agent/agent.ts b/src/agent/agent.ts index 2268730..e8f213f 100644 --- a/src/agent/agent.ts +++ b/src/agent/agent.ts @@ -1,16 +1,53 @@ -import { Model } from "./model"; +import { ConfigManager } from "../config/configManager"; +import { logger } from "../logger"; +import { SessionService } from "../session/session"; +import { revive, TypeDescriptor } from "../utils/utils"; export class Agent { - model: Model; - desc: string; + static validKeysMap: { [key in keyof Agent]?: TypeDescriptor } = { + name: 'string', + description: 'string', + instruction: 'string', + sessionService: SessionService, + tool: { array: 'string' }, + } + name: string; + description: string; instruction: string; sessionService: SessionService; + tool: string[]; - constructor(model: Model) { - this.model = model; + constructor() { + this.description = ""; + this.instruction = ""; + this.sessionService = new SessionService(); + this.tool = []; } async chat() { - + + } +} + +export class AgentManager { + static agentMap: { [key: string]: Agent } = {}; + + static getAgent(name: string): Agent { + if (!this.agentMap.hasOwnProperty(name)) { + let agent = new Agent(); + try { + const data = JSON.parse(ConfigManager.ext.storageGet(`agent_${name}`) || '{}'); + agent = revive(Agent, data); + } catch (error) { + logger.error(`加载智能体${name}失败: ${error}`); + } + agent.name = name; + this.agentMap[name] = agent; + } + return this.agentMap[name]; + } + + static save() { + } } \ No newline at end of file diff --git a/src/agent/model.ts b/src/agent/model.ts index 1e65fac..e1241c0 100644 --- a/src/agent/model.ts +++ b/src/agent/model.ts @@ -3,17 +3,62 @@ export class Model { provider: string; base_url: string; api_key: string; - type: 'chat' | 'code' | 'image' | 'embedding' | 'audio' | 'video'; - constructor(name: string, provider: string, base_url: string, api_key: string, type: 'chat' | 'code' | 'image' | 'embedding' | 'audio' | 'video') { + max_tokens: number; + stop: string[] | null; + stream: boolean; + temperature: number; + top_p: number; + + constructor(name: string, provider: string, base_url: string, api_key: string) { this.name = name; this.provider = provider; this.base_url = base_url; this.api_key = api_key; - this.type = type; + + this.max_tokens = 1024; + this.stop = null; + this.stream = false; + this.temperature = 1; + this.top_p = 1; } +} +export class ChatModel extends Model { + constructor(name: string, provider: string, base_url: string, api_key: string) { + super(name, provider, base_url, api_key); + } + + // wip async call() { } +} + +export class ImageModel extends Model { + constructor(name: string, provider: string, base_url: string, api_key: string) { + super(name, provider, base_url, api_key); + } + + // wip + async call() { + + } +} + +export class EmbeddingModel extends Model { + constructor(name: string, provider: string, base_url: string, api_key: string) { + super(name, provider, base_url, api_key); + } + + // wip + async call() { + + } +} + +export class ModelManager { + chatModels: ChatModel[] = []; + imageModels: ImageModel[] = []; + embeddingModels: EmbeddingModel[] = []; } \ No newline at end of file diff --git a/src/cmd/sub_cmd/image.ts b/src/cmd/sub_cmd/image.ts index 4130d00..4c2372b 100644 --- a/src/cmd/sub_cmd/image.ts +++ b/src/cmd/sub_cmd/image.ts @@ -88,7 +88,7 @@ export function registerCmdImage() { if (images.length === 0) seal.replyToSender(ctx, msg, '请附带图片'); const img = images[0]; await img.imageToText(cmdArgs.getRestArgsFrom(4)) - seal.replyToSender(ctx, msg, img.CQCode + `\n` + img.content); + seal.replyToSender(ctx, msg, img.CQCode + `\n` + img.description); return ret; } case 'find': { diff --git a/src/image/image.ts b/src/image/image.ts index 26d495a..744a521 100644 --- a/src/image/image.ts +++ b/src/image/image.ts @@ -1,45 +1,46 @@ import { ConfigManager } from "../config/configManager"; import { sendITTRequest } from "../agent/service"; -import { generateId } from "../utils/utils"; +import { generateId, revive, TypeDescriptor } from "../utils/utils"; import { logger } from "../logger"; -import { AI } from "./AI"; import { MessageSegment, parseSpecialTokens } from "../utils/utils_string"; +import { getSessionId } from "../utils/utils_seal"; export class Image { - static validKeys: (keyof Image)[] = ['id', 'file', 'content']; - id: string; - file: string; // 图片url或本地路径 - content: string; + static validKeysMap: { [key in keyof Image]?: TypeDescriptor } = { + imageId: 'string', + sourceSessionId: 'string', + path: 'string', + url: 'string', + base64: 'string', + format: 'string', + description: 'string', + } + imageId: string; + sourceSessionId: string; + path: string; + url: string; + base64: string; + format: string; + description: string; constructor() { - this.id = generateId(); - this.file = ''; - this.content = ''; + this.imageId = ''; + this.sourceSessionId = ''; + this.path = ''; + this.url = ''; + this.base64 = ''; + this.format = ''; + this.description = ''; } get type(): 'url' | 'local' | 'base64' { - if (this.file.startsWith('http')) return 'url'; - if (this.format) return 'base64'; + if (this.base64) return 'base64'; + if (this.url.startsWith('http')) return 'url'; return 'local'; } - get base64(): string { - return ConfigManager.ext.storageGet(`base64_${this.id}`) || ''; - } - set base64(value: string) { - this.file = ''; - ConfigManager.ext.storageSet(`base64_${this.id}`, value); - } - - get format(): string { - return ConfigManager.ext.storageGet(`format_${this.id}`) || ''; - } - set format(value: string) { - ConfigManager.ext.storageSet(`format_${this.id}`, value); - } - get CQCode(): string { - const file = this.type === 'base64' ? seal.base64ToImage(this.base64) : this.file; + const file = this.type === 'base64' ? seal.base64ToImage(this.base64) : this.url; return `[CQ:image,file=${file}]`; } @@ -52,15 +53,15 @@ export class Image { /** * 获取图片的URL,若为base64则返回base64Url */ - get url(): string { - return this.type === 'base64' ? this.base64Url : this.file; + get src(): string { + return this.type === 'base64' ? this.base64Url : this.url; } async checkImageUrl(): Promise { if (this.type !== 'url') return true; let isValid = false; try { - const response = await fetch(this.file, { method: 'GET' }); + const response = await fetch(this.url, { method: 'GET' }); if (response.ok) { const contentType = response.headers.get('Content-Type'); @@ -94,7 +95,7 @@ export class Image { "Content-Type": "application/json", "Accept": "application/json" }, - body: JSON.stringify({ url: this.file }) + body: JSON.stringify({ url: this.src }) }); const text = await response.text(); @@ -113,8 +114,11 @@ export class Image { } catch (error) { logger.error("在imageUrlToBase64中请求出错:", error); } + + ImageManager.saveImage(this); } + // wip 接到imageAgent,然后加个save async imageToText(prompt = '') { const { defaultPrompt, urlToBase64, maxChars } = ConfigManager.image; @@ -124,113 +128,118 @@ export class Image { role: "user", content: [{ "type": "image_url", - "image_url": { "url": this.url } + "image_url": { "url": this.src } }, { "type": "text", "text": prompt ? prompt : defaultPrompt }] }] - this.content = (await sendITTRequest(messages)).slice(0, maxChars); + this.description = (await sendITTRequest(messages)).slice(0, maxChars); - if (!this.content && urlToBase64 === '自动' && this.type === 'url') { - logger.info(`图片${this.id}第一次识别失败,自动尝试使用转换为base64`); + if (!this.description && urlToBase64 === '自动' && this.type === 'url') { + logger.info(`图片${this.imageId}第一次识别失败,自动尝试使用转换为base64`); await this.urlToBase64(); messages[0].content[0].image_url.url = this.base64Url; - this.content = (await sendITTRequest(messages)).slice(0, maxChars); + this.description = (await sendITTRequest(messages)).slice(0, maxChars); } - if (!this.content) logger.error(`图片${this.id}识别失败`); + if (!this.description) logger.error(`图片${this.imageId}识别失败`); } } export class ImageManager { - static validKeys: (keyof ImageManager)[] = ['stolenImages', 'stealStatus']; - stolenImages: Image[]; - stealStatus: boolean; + static imageMap: { [key: string]: Image } = {}; + + static generateImageId(): string { + let id = generateId(), a = 0; + while (this.getImage(id)) { + id = generateId(); + a++; + if (a > 1000) { + logger.error(`生成记忆id失败,已尝试1000次,放弃`); + throw new Error(`生成记忆id失败,已尝试1000次,放弃`); + } + } + return id; + } - constructor() { - this.stolenImages = []; - this.stealStatus = false; + static createUrlImage(sourceSessionId: string, url: string, imageId?: string): Image { + imageId = imageId || this.generateImageId(); + const img = new Image(); + img.imageId = imageId; + img.sourceSessionId = sourceSessionId; + img.url = url; + this.imageMap[imageId] = img; + return img; + } + + static createLocalImage(imageId: string, path: string): Image { + const img = new Image(); + img.imageId = imageId; + img.path = path; + this.imageMap[imageId] = img; + return img; + } + + static getImage(imageId: string): Image | null { + if (!this.imageMap.hasOwnProperty(imageId)) { + let img = new Image(); + try { + const text = ConfigManager.ext.storageGet(`image_${imageId}`); + if (!text) return null; + const data = JSON.parse(text || '{}'); + img = revive(Image, data); + } catch (error) { + logger.error(`加载图片${imageId}失败: ${error}`); + return null; + } + this.imageMap[imageId] = img; + } + return this.imageMap[imageId]; + } + + static saveImage(img: Image) { + ConfigManager.ext.storageSet(`image_${img.imageId}`, JSON.stringify(img)); } static getUserAvatar(uid: string): Image { const img = new Image(); - img.id = `user_avatar:${uid}`; - img.file = `https://q1.qlogo.cn/g?b=qq&nk=${uid.replace(/^.+:/, '')}&s=640`; + img.imageId = `user_avatar:${uid}`; + img.url = `https://q1.qlogo.cn/g?b=qq&nk=${uid.replace(/^.+:/, '')}&s=640`; return img; } static getGroupAvatar(gid: string): Image { const img = new Image(); - img.id = `group_avatar:${gid}`; - img.file = `https://p.qlogo.cn/gh/${gid.replace(/^.+:/, '')}/${gid.replace(/^.+:/, '')}/640`; + img.imageId = `group_avatar:${gid}`; + img.url = `https://p.qlogo.cn/gh/${gid.replace(/^.+:/, '')}/${gid.replace(/^.+:/, '')}/640`; return img; } - stealImages(images: Image[]) { - const { maxStolenImageNum } = ConfigManager.image; - this.stolenImages = this.stolenImages.concat(images).slice(-maxStolenImageNum); + static get LocalImageList() { + const { localImagePathMap } = ConfigManager.image; + return Object.keys(localImagePathMap).map(id => this.createLocalImage(id, localImagePathMap[id])); } static getLocalImageListText(p: number = 1): string { - const { localImagePathMap } = ConfigManager.image; - const images = Object.keys(localImagePathMap).map(id => { - const image = new Image(); - image.id = id; - image.file = localImagePathMap[id]; - return image; - }); + const images = this.LocalImageList; if (images.length == 0) return ''; if (p > Math.ceil(images.length / 5)) p = Math.ceil(images.length / 5); return images.slice((p - 1) * 5, p * 5) .map((img, i) => { - return `${i + 1 + (p - 1) * 5}. 名称:${img.id} + return `${i + 1 + (p - 1) * 5}. 名称:${img.imageId} ${img.CQCode}`; }).join('\n') + `\n当前页码:${p}/${Math.ceil(images.length / 5)}`; } - async drawStolenImage(): Promise { - if (this.stolenImages.length === 0) return null; - const index = Math.floor(Math.random() * this.stolenImages.length); - const img = this.stolenImages.splice(index, 1)[0]; - if (!await img.checkImageUrl()) { - await new Promise(resolve => setTimeout(resolve, 500)); - return await this.drawStolenImage(); - } - return img; - } - - getStolenImageListText(p: number = 1): string { - if (this.stolenImages.length == 0) return ''; - if (p > Math.ceil(this.stolenImages.length / 5)) p = Math.ceil(this.stolenImages.length / 5); - return this.stolenImages.slice((p - 1) * 5, p * 5) - .map((img, i) => { - return `${i + 1 + (p - 1) * 5}. ID:${img.id} -${img.CQCode}`; - }).join('\n') + `\n当前页码:${p}/${Math.ceil(this.stolenImages.length / 5)}`; - } - - async drawImage(): Promise { - const { localImagePathMap } = ConfigManager.image; - const localImages = Object.keys(localImagePathMap).map(id => { - const image = new Image(); - image.id = id; - image.file = localImagePathMap[id]; - return image; - }); - if (this.stolenImages.length == 0 && localImages.length == 0) return null; - const index = Math.floor(Math.random() * (localImages.length + this.stolenImages.length)); - return index < localImages.length ? localImages[index] : await this.drawStolenImage(); - } - /** * 提取并替换CQ码中的图片 * @param ctx * @param message * @returns */ - async handleImageMessageSegment(ctx: seal.MsgContext, seg: MessageSegment): Promise<{ content: string, images: Image[] }> { + static async handleImageMessageSegment(ctx: seal.MsgContext, seg: MessageSegment): Promise<{ content: string, images: Image[] }> { const { receiveImage } = ConfigManager.image; if (!receiveImage || seg.type !== 'image') return { content: '', images: [] }; @@ -240,30 +249,28 @@ ${img.CQCode}`; const file = seg.data.url || seg.data.file || ''; if (!file) return { content: '', images: [] }; - const image = new Image(); - image.file = file; + const image = this.createUrlImage(getSessionId(ctx), file); const { condition } = ConfigManager.image; const fmtCondition = parseInt(seal.format(ctx, `{${condition}}`)); if (fmtCondition === 1) await image.imageToText(); - content += image.content ? `<|img:${image.id}:${image.content}|>` : `<|img:${image.id}|>`; + content += image.description ? `<|img:${image.imageId}:${image.description}|>` : `<|img:${image.imageId}|>`; images.push(image); } catch (error) { logger.error('在handleImageMessage中处理图片时出错:', error); } - if (this.stealStatus) this.stealImages(images); return { content, images }; } - static async extractExistingImagesToSave(ctx: seal.MsgContext, ai: AI, s: string): Promise { + static async extractExistingImagesToSave(s: string): Promise { const segs = parseSpecialTokens(s); const images: Image[] = []; for (const seg of segs) { switch (seg.type) { case 'img': { const id = seg.content; - const image = await ai.context.findImage(ctx, id); + const image = this.getImage(id); if (image) { if (image.type === 'url') await image.urlToBase64(); diff --git a/src/note.txt b/src/note.txt index 266b0d6..25deccf 100644 --- a/src/note.txt +++ b/src/note.txt @@ -2,9 +2,17 @@ tool state summary count -last reply 放进 send 里,所有send换成一个,除了指令调用。 +last reply 放进 sendService 里,所有send换成一个,除了指令调用。 计数器、计时器逻辑移到trigger autoNameMod: number; // 自动修改上下文里的名字,0:不自动修改,1:修改为昵称,2:修改为群名片 -放到user manager \ No newline at end of file +放到user manager + +tool分为触发callback和不触发callback两种 + +请求时多条消息合并到一条user消息中 + +把生成本地图片列表的逻辑也做进config + +把所有new Image()的地方整进imageMana \ No newline at end of file diff --git a/src/session/group.ts b/src/session/group.ts index e1c5cce..b34332e 100644 --- a/src/session/group.ts +++ b/src/session/group.ts @@ -40,6 +40,7 @@ export class GroupManager { } catch (error) { logger.error(`加载群${groupId}失败: ${error}`); } + group.groupId = groupId; this.groupMap[groupId] = group; } return this.groupMap[groupId]; diff --git a/src/session/session.ts b/src/session/session.ts index b7192c5..f2c9581 100644 --- a/src/session/session.ts +++ b/src/session/session.ts @@ -38,6 +38,9 @@ export class Session { } export class SessionService { + static validKeysMap: { [key in keyof SessionService]?: TypeDescriptor } = { + sessionMap: { objectValue: Session }, + } sessionMap: { [key: string]: Session }; constructor() { @@ -53,6 +56,7 @@ export class SessionService { } catch (error) { logger.error(`加载会话${sessionId}失败: ${error}`); } + session.sessionId = sessionId; this.sessionMap[sessionId] = session; } return this.sessionMap[sessionId]; diff --git a/src/session/user.ts b/src/session/user.ts index e6bc27a..e129489 100644 --- a/src/session/user.ts +++ b/src/session/user.ts @@ -34,6 +34,7 @@ export class UserManager { } catch (error) { logger.error(`加载用户${userId}失败: ${error}`); } + user.userId = userId; this.userMap[userId] = user; } return this.userMap[userId]; diff --git a/src/tool/tool_image.ts b/src/tool/tool_image.ts index 76aa109..708e37b 100644 --- a/src/tool/tool_image.ts +++ b/src/tool/tool_image.ts @@ -87,7 +87,7 @@ export function registerImage() { if (globalThis.aiDrawing && typeof globalThis.aiDrawing.sendImageRequest === 'function') { const result = await globalThis.aiDrawing.sendImageRequest(prompt, negative_prompt); const img = new Image(); - img.id = `${name}_${generateId()}`; + img.imageId = `${name}_${generateId()}`; if (result.startsWith("http://") || result.startsWith("https://")) { try { await img.urlToBase64(); @@ -100,11 +100,11 @@ export function registerImage() { } img.format = img.format || 'unknown'; - img.content = `AI绘图<|img:${img.id}|>\n${prompt ? `描述: ${prompt}` : ''}\n${negative_prompt ? `不希望出现: ${negative_prompt}` : ''}`; + img.description = `AI绘图<|img:${img.imageId}|>\n${prompt ? `描述: ${prompt}` : ''}\n${negative_prompt ? `不希望出现: ${negative_prompt}` : ''}`; - if (save) ai.memory.addMemory(ctx, ai, [], [], kws, [img], img.content); + if (save) ai.memory.addMemory(ctx, ai, [], [], kws, [img], img.description); - return { content: `生成成功,请使用<|img:${img.id}|>发送`, images: [img] }; + return { content: `生成成功,请使用<|img:${img.imageId}|>发送`, images: [img] }; } // 兼容旧版 AIDrawing diff --git a/src/tool/tool_meme.ts b/src/tool/tool_meme.ts index d41550a..376070b 100644 --- a/src/tool/tool_meme.ts +++ b/src/tool/tool_meme.ts @@ -180,7 +180,7 @@ export function registerMeme() { const result = ai.memory.findMemoryAndImageByImageIdPrefix(name); if (result) { const { memory, image } = result; - if (memory.keywords.every((v, i) => v === kws[i]) && memory.images.slice(1).every((v, i) => v.id === images[i].id)) { + if (memory.keywords.every((v, i) => v === kws[i]) && memory.images.slice(1).every((v, i) => v.id === images[i].imageId)) { return { content: `${s}生成成功,请使用<|img:${image.id}|>发送`, images: [image] }; } } @@ -208,16 +208,16 @@ export function registerMeme() { const imageText = image_ids.join(';'); const img = new Image(); - img.id = `${name}_${generateId()}`; + img.imageId = `${name}_${generateId()}`; img.base64 = base64; img.format = 'unknown'; - img.content = `表情包<|img:${img.id}|> + img.description = `表情包<|img:${img.imageId}|> ${textText ? `文字:${textText}` : ''} ${imageText ? `图片:${imageText}` : ''}`; - if (save) ai.memory.addMemory(ctx, ai, uiList, giList, kws, [img, ...images], img.content); + if (save) ai.memory.addMemory(ctx, ai, uiList, giList, kws, [img, ...images], img.description); - return { content: `${s}生成成功,请使用<|img:${img.id}|>发送`, images: [img] }; + return { content: `${s}生成成功,请使用<|img:${img.imageId}|>发送`, images: [img] }; } else { throw new Error(json.message); } diff --git a/src/tool/tool_render.ts b/src/tool/tool_render.ts index 316444d..074053e 100644 --- a/src/tool/tool_render.ts +++ b/src/tool/tool_render.ts @@ -139,15 +139,15 @@ export function registerRender() { } const img = new Image(); - img.id = `${name}_${generateId()}`; + img.imageId = `${name}_${generateId()}`; img.base64 = base64; img.format = 'unknown'; - img.content = `Markdown 渲染图片<|img:${img.id}|> + img.description = `Markdown 渲染图片<|img:${img.imageId}|> 主题:${theme}`; - if (save) ai.memory.addMemory(ctx, ai, [], [], kws, [img], img.content); + if (save) ai.memory.addMemory(ctx, ai, [], [], kws, [img], img.description); - return { content: `渲染成功,请使用<|img:${img.id}|>发送`, images: [img] }; + return { content: `渲染成功,请使用<|img:${img.imageId}|>发送`, images: [img] }; } else { throw new Error(result.message || "渲染失败"); } @@ -206,14 +206,14 @@ export function registerRender() { } const img = new Image(); - img.id = `${name}_${generateId()}`; + img.imageId = `${name}_${generateId()}`; img.base64 = base64; img.format = 'unknown'; - img.content = `HTML 渲染图片<|img:${img.id}|>`; + img.description = `HTML 渲染图片<|img:${img.imageId}|>`; - if (save) ai.memory.addMemory(ctx, ai, [], [], kws, [img], img.content); + if (save) ai.memory.addMemory(ctx, ai, [], [], kws, [img], img.description); - return { content: `渲染成功,请使用<|img:${img.id}|>发送`, images: [img] }; + return { content: `渲染成功,请使用<|img:${img.imageId}|>发送`, images: [img] }; } else { throw new Error(result.message || "渲染失败"); } diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 1d4cd22..286927b 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -1,4 +1,3 @@ -import { AI } from "../AI/AI"; import { logger } from "../logger"; import { ConfigManager } from "../config/configManager"; import { transformTextToArray } from "./utils_string"; @@ -86,6 +85,7 @@ export type TypeDescriptor = | 'any' | TypeDescriptor[] // 元组元素类型 | { array: TypeDescriptor } // 数组元素类型 + | { objectValue: TypeDescriptor } // 对象值类型 | RevivableConstructor; // 嵌套类 interface RevivableConstructor { @@ -111,12 +111,19 @@ export function revive(constructor: RevivableConstructor, value: any): T { return value; } else if (Array.isArray(descriptor)) { if (Array.isArray(value)) return descriptor.map((d: any, index: number) => { - if (index < value.length && index < defaultValue.length) return reviveItem(d, defaultValue[index], value[index]); - if (index < defaultValue.length) return defaultValue[index]; - return undefined; + if (index < value.length) return reviveItem(d, defaultValue?.[index], value[index]); + return defaultValue?.[index]; }); } else if (typeof descriptor === 'object' && 'array' in descriptor) { - if (Array.isArray(value)) return value.map((item: any) => reviveItem(descriptor.array, defaultValue[0], item)); + if (Array.isArray(value)) return value.map((item: any) => reviveItem(descriptor.array, defaultValue?.[0], item)); + } else if (typeof descriptor === 'object' && 'objectValue' in descriptor) { + if (typeof value === 'object' && value !== null) { + const obj: { [key: string]: any } = {}; + for (const k in value) { + obj[k] = reviveItem(descriptor.objectValue, defaultValue?.[k], value[k]); + } + return obj; + } } else if (typeof descriptor === 'function') { return revive(descriptor, value); } else { diff --git a/src/utils/utils_seal.ts b/src/utils/utils_seal.ts index afa0eca..d97b6e8 100644 --- a/src/utils/utils_seal.ts +++ b/src/utils/utils_seal.ts @@ -33,4 +33,8 @@ export function getSessionCtxAndMsg(epId: string, sid: string, isPrivate: boolea const msg = createMsg(...args); const ctx = createCtx(epId, msg); return { ctx, msg }; -} \ No newline at end of file +} + +export function getSessionId(ctx: seal.MsgContext): string { + return ctx.isPrivate ? ctx.player.userId : ctx.group.groupId; +} From a31f1a9a4ba68471c22db2852c994175dfac69d4 Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Fri, 6 Mar 2026 13:49:35 +0800 Subject: [PATCH 10/33] =?UTF-8?q?=E9=87=8D=E6=9E=84=E4=BA=86=E4=B8=80?= =?UTF-8?q?=E7=82=B9model=EF=BC=8C=E7=9C=9F=E6=98=AF=E8=BE=9B=E8=8B=A6?= =?UTF-8?q?=E6=88=91=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/agent/agent.ts | 4 + src/agent/model.ts | 203 +++++++++++++++++++++++++++++++++++++---- src/agent/usage.ts | 3 + src/note.txt | 12 ++- src/session/session.ts | 13 +++ src/utils/utils.ts | 16 ++-- 6 files changed, 226 insertions(+), 25 deletions(-) create mode 100644 src/agent/usage.ts diff --git a/src/agent/agent.ts b/src/agent/agent.ts index e8f213f..61856fb 100644 --- a/src/agent/agent.ts +++ b/src/agent/agent.ts @@ -24,6 +24,10 @@ export class Agent { this.tool = []; } + // wip + getTools() { + } + async chat() { } diff --git a/src/agent/model.ts b/src/agent/model.ts index e1241c0..63797ab 100644 --- a/src/agent/model.ts +++ b/src/agent/model.ts @@ -1,49 +1,134 @@ +import { logger } from "../logger"; +import { ToolCall } from "../tool/tool"; +import { withTimeout } from "../utils/utils"; +import { Agent } from "./agent"; +import { UsageManager } from "./usage"; + +export type ModelUse = 'chat' | 'image-understanding' | 'text-embedding' + +export interface ModelBody { + max_tokens?: number, + stop?: string[] | null, + stream?: boolean, + temperature?: number, + top_p?: number, + [key: string]: any +} + export class Model { name: string; + use: ModelUse; provider: string; base_url: string; api_key: string; + body: ModelBody; - max_tokens: number; - stop: string[] | null; - stream: boolean; - temperature: number; - top_p: number; - - constructor(name: string, provider: string, base_url: string, api_key: string) { + constructor(name: string, use: ModelUse, provider: string, base_url: string, api_key: string, body: ModelBody) { this.name = name; + this.use = use; this.provider = provider; this.base_url = base_url; this.api_key = api_key; + this.body = body; + } - this.max_tokens = 1024; - this.stop = null; - this.stream = false; - this.temperature = 1; - this.top_p = 1; + buildBody(args: { [key: string]: any }) { + const body = JSON.parse(JSON.stringify(this.body)); + for (const key in args) { + if (!args.hasOwnProperty(key)) body[key] = args[key]; + } + return body; } } export class ChatModel extends Model { - constructor(name: string, provider: string, base_url: string, api_key: string) { - super(name, provider, base_url, api_key); + constructor(name: string, use: ModelUse, provider: string, base_url: string, api_key: string, body: ModelBody) { + super(name, use, provider, base_url, api_key, body); + } + + get url() { + return `${this.base_url}/chat/completions`; } // wip - async call() { + async call(agent: Agent, sessionId: string): Promise<{ content: string, tool_calls: ToolCall[] }> { + try { + const time = Date.now(); + + const data = await withTimeout(() => fetchData(this.url, this.api_key, this.buildBody({ + messages: agent.sessionService.getSession(sessionId).getMessages(), + tools: agent.getTools() + })), 10000); + if (data.choices && data.choices.length > 0) { + UsageManager.updateUsage(data.model, data.usage); + + const message = data.choices[0].message; + const finish_reason = data.choices[0].finish_reason; + + if (message.hasOwnProperty('reasoning_content')) { + logger.info(`思维链内容:`, message.reasoning_content); + } + + const content = message.content || ''; + + logger.info(`响应内容:`, content, '\nlatency:', Date.now() - time, 'ms', '\nfinish_reason:', finish_reason); + + return { content, tool_calls: message.tool_calls || [] }; + } else { + throw new Error(`服务器响应中没有choices或choices为空\n响应体:${JSON.stringify(data, null, 2)}`); + } + } catch (e) { + logger.error(`在调用模型${this.name}中出错:`, e.message); + return { content: '', tool_calls: [] }; + } } } export class ImageModel extends Model { - constructor(name: string, provider: string, base_url: string, api_key: string) { - super(name, provider, base_url, api_key); + constructor(name: string, use: ModelUse, provider: string, base_url: string, api_key: string, body: ModelBody) { + super(name, use, provider, base_url, api_key, body); } // wip async call() { } + + export async function sendITTRequest(messages: { + role: string, + content: { + type: string, + image_url?: { url: string } + text?: string + }[] + }[]): Promise { + const { timeout } = ConfigManager.request; + const { url, apiKey, bodyTemplate } = ConfigManager.image; + + try { + const bodyObject = parseBody(bodyTemplate, messages, null, null); + const time = Date.now(); + + const data = await withTimeout(() => fetchData(url, apiKey, bodyObject), timeout); + + if (data.choices && data.choices.length > 0) { + AIManager.updateUsage(data.model, data.usage); + + const message = data.choices[0].message; + const content = message.content || ''; + + logger.info(`响应内容:`, content, '\nlatency', Date.now() - time, 'ms'); + + return content; + } else { + throw new Error(`服务器响应中没有choices或choices为空\n响应体:${JSON.stringify(data, null, 2)}`); + } + } catch (e) { + logger.error("在sendITTRequest中请求出错:", e.message); + return ''; + } +} } export class EmbeddingModel extends Model { @@ -55,10 +140,94 @@ export class EmbeddingModel extends Model { async call() { } + + const vectorCache: { text: string, vector: number[] } = { text: '', vector: [] }; + + export async function getEmbedding(text: string): Promise { + if (!text) { + logger.warning(`getEmbedding: 文本为空`); + return []; + } + + const { timeout } = ConfigManager.request; + const { embeddingDimension, embeddingUrl, embeddingApiKey, embeddingBodyTemplate } = ConfigManager.memory; + + if (vectorCache.text === text && vectorCache.vector.length === embeddingDimension) { + const v = vectorCache.vector; + return v; + } + + try { + const bodyObject = parseEmbeddingBody(embeddingBodyTemplate, text, embeddingDimension); + const time = Date.now(); + + const data = await withTimeout(() => fetchData(embeddingUrl, embeddingApiKey, bodyObject), timeout); + + if (data.data && data.data.length > 0) { + AIManager.updateUsage(data.model, data.usage); + + const embedding = data.data[0].embedding; + + logger.info(`文本:`, text, `\n响应embedding长度:`, embedding.length, '\nlatency:', Date.now() - time, 'ms'); + vectorCache.text = text; + vectorCache.vector = embedding; + + return embedding; + } else { + throw new Error(`服务器响应中没有data或data为空\n响应体:${JSON.stringify(data, null, 2)}`); + } + } catch (e) { + logger.error("在getEmbedding中出错:", e.message); + return []; + } +} } export class ModelManager { chatModels: ChatModel[] = []; imageModels: ImageModel[] = []; embeddingModels: EmbeddingModel[] = []; +} + +export async function fetchData(url: string, apiKey: string, bodyObject: any): Promise { + // 打印请求发送前的上下文 + if (bodyObject.hasOwnProperty('messages')) { + const s = JSON.stringify(bodyObject.messages, (key, value) => { + if (key === "" && Array.isArray(value)) { + return value.filter(item => item.role !== "system"); + } + return value; + }); + logger.info(`请求发送前的上下文:\n`, s); + } + + const response = await fetch(url, { + method: 'POST', + headers: { + "Authorization": `Bearer ${apiKey}`, + "Content-Type": "application/json", + "Accept": "application/json" + }, + body: JSON.stringify(bodyObject) + }); + + // logger.info("响应体", JSON.stringify(response, null, 2)); + + const text = await response.text(); + if (!response.ok) { + throw new Error(`请求失败! 状态码: ${response.status}\n响应体:${text}`); + } + if (!text) { + throw new Error("响应体为空"); + } + + try { + const data = JSON.parse(text); + if (data.error) { + throw new Error(`请求失败! 错误信息: ${data.error.message}`); + } + return data; + } catch (e) { + throw new Error(`解析响应体时出错:${e.message}\n响应体:${text}`); + } } \ No newline at end of file diff --git a/src/agent/usage.ts b/src/agent/usage.ts new file mode 100644 index 0000000..8d2b402 --- /dev/null +++ b/src/agent/usage.ts @@ -0,0 +1,3 @@ +export class UsageManager { + static usageMap: { [key: string]: Usage } = {}; +} \ No newline at end of file diff --git a/src/note.txt b/src/note.txt index 25deccf..6059a5a 100644 --- a/src/note.txt +++ b/src/note.txt @@ -9,10 +9,18 @@ last reply 放进 sendService 里,所有send换成一个,除了指令调用 autoNameMod: number; // 自动修改上下文里的名字,0:不自动修改,1:修改为昵称,2:修改为群名片 放到user manager -tool分为触发callback和不触发callback两种 +tool分为触发callback和不触发callback两种,将tool choice删除 请求时多条消息合并到一条user消息中 把生成本地图片列表的逻辑也做进config -把所有new Image()的地方整进imageMana \ No newline at end of file +把所有new Image()的地方整进imageMana + +model可以设置其提供给特定agent或all + +ai读取所有插件的指令并调用的功能 + +连续多条user时,对后面几条进行压缩 + +提供api给其他插件,实现kp agent \ No newline at end of file diff --git a/src/session/session.ts b/src/session/session.ts index f2c9581..0634a52 100644 --- a/src/session/session.ts +++ b/src/session/session.ts @@ -1,5 +1,6 @@ import { ConfigManager } from "../config/configManager"; import { logger } from "../logger"; +import { ToolCall } from "../tool/tool"; import { revive, TypeDescriptor } from "../utils/utils"; import { Context } from "./context"; import { MemoryService } from "./memory"; @@ -8,6 +9,13 @@ export class State { [key: string]: any; } +export interface RequestMessage { + role: 'user' | 'assistant' | 'system' | 'tool'; + content?: string; + tool_calls?: ToolCall[]; + tool_call_id?: string; +} + export class Session { static validKeysMap: { [key in keyof Session]?: TypeDescriptor } = { isPrivate: 'boolean', @@ -35,6 +43,11 @@ export class Session { // this.tool = new ToolState(); this.ignoredUserIdList = []; } + + // wip + getMessages(): RequestMessage[] { + return []; + } } export class SessionService { diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 286927b..a774e4a 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -85,6 +85,7 @@ export type TypeDescriptor = | 'any' | TypeDescriptor[] // 元组元素类型 | { array: TypeDescriptor } // 数组元素类型 + | { object: { [key: string]: TypeDescriptor } } // 对象键值对类型 | { objectValue: TypeDescriptor } // 对象值类型 | RevivableConstructor; // 嵌套类 @@ -116,14 +117,17 @@ export function revive(constructor: RevivableConstructor, value: any): T { }); } else if (typeof descriptor === 'object' && 'array' in descriptor) { if (Array.isArray(value)) return value.map((item: any) => reviveItem(descriptor.array, defaultValue?.[0], item)); + } else if (typeof descriptor === 'object' && 'object' in descriptor) { + if (typeof value === 'object' && value !== null) return Object.keys(descriptor.object).reduce((obj: any, k: string) => { + if (value.hasOwnProperty(k)) obj[k] = reviveItem(descriptor.object[k], defaultValue?.[k], value[k]); + else obj[k] = defaultValue?.[k]; + return obj; + }, {}); } else if (typeof descriptor === 'object' && 'objectValue' in descriptor) { - if (typeof value === 'object' && value !== null) { - const obj: { [key: string]: any } = {}; - for (const k in value) { - obj[k] = reviveItem(descriptor.objectValue, defaultValue?.[k], value[k]); - } + if (typeof value === 'object' && value !== null) return Object.keys(value).reduce((obj: any, k: string) => { + obj[k] = reviveItem(descriptor.objectValue, defaultValue?.[k], value[k]); return obj; - } + }, {}); } else if (typeof descriptor === 'function') { return revive(descriptor, value); } else { From 167d9f825041093f62082c10f4b0a72677202e3b Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Fri, 6 Mar 2026 18:28:23 +0800 Subject: [PATCH 11/33] =?UTF-8?q?=E5=AE=8C=E6=88=90model=E9=83=A8=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/agent/model.ts | 243 +++++++++++++++++++++++++++++-------------- src/agent/service.ts | 161 +--------------------------- src/image/image.ts | 23 ++-- 3 files changed, 174 insertions(+), 253 deletions(-) diff --git a/src/agent/model.ts b/src/agent/model.ts index 63797ab..ace847f 100644 --- a/src/agent/model.ts +++ b/src/agent/model.ts @@ -1,10 +1,11 @@ +import { ConfigManager } from "../config/configManager"; import { logger } from "../logger"; import { ToolCall } from "../tool/tool"; import { withTimeout } from "../utils/utils"; import { Agent } from "./agent"; import { UsageManager } from "./usage"; -export type ModelUse = 'chat' | 'image-understanding' | 'text-embedding' +export type ModelUse = 'any' | 'chat' | 'image-understanding' | 'text-embedding' export interface ModelBody { max_tokens?: number, @@ -17,13 +18,13 @@ export interface ModelBody { export class Model { name: string; - use: ModelUse; + use: ModelUse[]; provider: string; base_url: string; api_key: string; body: ModelBody; - constructor(name: string, use: ModelUse, provider: string, base_url: string, api_key: string, body: ModelBody) { + constructor(name: string, use: ModelUse[], provider: string, base_url: string, api_key: string, body: ModelBody) { this.name = name; this.use = use; this.provider = provider; @@ -42,7 +43,7 @@ export class Model { } export class ChatModel extends Model { - constructor(name: string, use: ModelUse, provider: string, base_url: string, api_key: string, body: ModelBody) { + constructor(name: string, use: ModelUse[], provider: string, base_url: string, api_key: string, body: ModelBody) { super(name, use, provider, base_url, api_key, body); } @@ -50,15 +51,15 @@ export class ChatModel extends Model { return `${this.base_url}/chat/completions`; } - // wip - async call(agent: Agent, sessionId: string): Promise<{ content: string, tool_calls: ToolCall[] }> { + async callChat(agent: Agent, sessionId: string): Promise<{ content: string, tool_calls: ToolCall[] }> { + const { timeout } = ConfigManager.request; try { const time = Date.now(); const data = await withTimeout(() => fetchData(this.url, this.api_key, this.buildBody({ messages: agent.sessionService.getSession(sessionId).getMessages(), tools: agent.getTools() - })), 10000); + })), timeout); if (data.choices && data.choices.length > 0) { UsageManager.updateUsage(data.model, data.usage); @@ -86,113 +87,199 @@ export class ChatModel extends Model { } export class ImageModel extends Model { - constructor(name: string, use: ModelUse, provider: string, base_url: string, api_key: string, body: ModelBody) { + constructor(name: string, use: ModelUse[], provider: string, base_url: string, api_key: string, body: ModelBody) { super(name, use, provider, base_url, api_key, body); } - // wip - async call() { + get url() { + return `${this.base_url}/chat/completions`; + } + + async callITT(src: string, prompt = ''): Promise { + const { timeout } = ConfigManager.request; + try { + const time = Date.now(); + + const data = await withTimeout(() => fetchData(this.url, this.api_key, this.buildBody({ + messages: [{ + role: "user", + content: [{ + "type": "image_url", + "image_url": { "url": src } + }, { + "type": "text", + "text": prompt + }] + }] + })), timeout); + + if (data.choices && data.choices.length > 0) { + UsageManager.updateUsage(data.model, data.usage); + + const message = data.choices[0].message; + const content = message.content || ''; + + logger.info(`响应内容:`, content, '\nlatency', Date.now() - time, 'ms'); + return content; + } else { + throw new Error(`服务器响应中没有choices或choices为空\n响应体:${JSON.stringify(data, null, 2)}`); + } + } catch (e) { + logger.error(`在调用模型${this.name}中出错:`, e.message); + return ''; + } } - export async function sendITTRequest(messages: { - role: string, - content: { - type: string, - image_url?: { url: string } - text?: string - }[] - }[]): Promise { - const { timeout } = ConfigManager.request; - const { url, apiKey, bodyTemplate } = ConfigManager.image; + async callChat(agent: Agent, sessionId: string): Promise<{ content: string, tool_calls: ToolCall[] }> { + const { timeout } = ConfigManager.request; + try { + const time = Date.now(); - try { - const bodyObject = parseBody(bodyTemplate, messages, null, null); - const time = Date.now(); + const data = await withTimeout(() => fetchData(this.url, this.api_key, this.buildBody({ + messages: agent.sessionService.getSession(sessionId).getImageMessages(), + tools: agent.getTools() + })), timeout); - const data = await withTimeout(() => fetchData(url, apiKey, bodyObject), timeout); + if (data.choices && data.choices.length > 0) { + UsageManager.updateUsage(data.model, data.usage); + + const message = data.choices[0].message; + const finish_reason = data.choices[0].finish_reason; - if (data.choices && data.choices.length > 0) { - AIManager.updateUsage(data.model, data.usage); + if (message.hasOwnProperty('reasoning_content')) { + logger.info(`思维链内容:`, message.reasoning_content); + } - const message = data.choices[0].message; - const content = message.content || ''; + const content = message.content || ''; - logger.info(`响应内容:`, content, '\nlatency', Date.now() - time, 'ms'); + logger.info(`响应内容:`, content, '\nlatency:', Date.now() - time, 'ms', '\nfinish_reason:', finish_reason); - return content; - } else { - throw new Error(`服务器响应中没有choices或choices为空\n响应体:${JSON.stringify(data, null, 2)}`); + return { content, tool_calls: message.tool_calls || [] }; + } else { + throw new Error(`服务器响应中没有choices或choices为空\n响应体:${JSON.stringify(data, null, 2)}`); + } + } catch (e) { + logger.error(`在调用模型${this.name}中出错:`, e.message); + return { content: '', tool_calls: [] }; } - } catch (e) { - logger.error("在sendITTRequest中请求出错:", e.message); - return ''; } } -} export class EmbeddingModel extends Model { - constructor(name: string, provider: string, base_url: string, api_key: string) { - super(name, provider, base_url, api_key); - } - - // wip - async call() { + static vectorCache: { text: string, vector: number[] } = { text: '', vector: [] }; + constructor(name: string, use: ModelUse[], provider: string, base_url: string, api_key: string, body) { + super(name, use, provider, base_url, api_key, body); } - const vectorCache: { text: string, vector: number[] } = { text: '', vector: [] }; - - export async function getEmbedding(text: string): Promise { - if (!text) { - logger.warning(`getEmbedding: 文本为空`); - return []; + get url() { + return `${this.base_url}/embeddings`; } - const { timeout } = ConfigManager.request; - const { embeddingDimension, embeddingUrl, embeddingApiKey, embeddingBodyTemplate } = ConfigManager.memory; + async callEmbedding(text: string): Promise { + if (!text) { + logger.warning(`getEmbedding: 文本为空`); + return []; + } - if (vectorCache.text === text && vectorCache.vector.length === embeddingDimension) { - const v = vectorCache.vector; - return v; - } + const { timeout } = ConfigManager.request; - try { - const bodyObject = parseEmbeddingBody(embeddingBodyTemplate, text, embeddingDimension); - const time = Date.now(); + if (EmbeddingModel.vectorCache.text === text && EmbeddingModel.vectorCache.vector.length === this.body.dimensions) { + const v = EmbeddingModel.vectorCache.vector; + return v; + } + + try { + const time = Date.now(); - const data = await withTimeout(() => fetchData(embeddingUrl, embeddingApiKey, bodyObject), timeout); + const data = await withTimeout(() => fetchData(this.url, this.api_key, this.buildBody({ + input: text + })), timeout); - if (data.data && data.data.length > 0) { - AIManager.updateUsage(data.model, data.usage); + if (data.data && data.data.length > 0) { + UsageManager.updateUsage(data.model, data.usage); - const embedding = data.data[0].embedding; + const embedding = data.data[0].embedding; - logger.info(`文本:`, text, `\n响应embedding长度:`, embedding.length, '\nlatency:', Date.now() - time, 'ms'); - vectorCache.text = text; - vectorCache.vector = embedding; + logger.info(`文本:`, text, `\n响应embedding长度:`, embedding.length, '\nlatency:', Date.now() - time, 'ms'); + EmbeddingModel.vectorCache.text = text; + EmbeddingModel.vectorCache.vector = embedding; - return embedding; - } else { - throw new Error(`服务器响应中没有data或data为空\n响应体:${JSON.stringify(data, null, 2)}`); + return embedding; + } else { + throw new Error(`服务器响应中没有data或data为空\n响应体:${JSON.stringify(data, null, 2)}`); + } + } catch (e) { + logger.error(`在调用模型${this.name}中出错:`, e.message); + return []; } - } catch (e) { - logger.error("在getEmbedding中出错:", e.message); - return []; + } } -} export class ModelManager { - chatModels: ChatModel[] = []; - imageModels: ImageModel[] = []; - embeddingModels: EmbeddingModel[] = []; + static chatModels: ChatModel[] = []; + static imageModels: ImageModel[] = []; + static embeddingModels: EmbeddingModel[] = []; + + static getChatModel(use: ModelUse): ChatModel | ImageModel | null { + const chatModelList = ModelManager.chatModels.filter(model => model.use.includes(use)); + if (chatModelList.length > 0) { + const randomIndex = Math.floor(Math.random() * chatModelList.length); + return chatModelList[randomIndex]; + } + const chatModelAnyList = ModelManager.chatModels.filter(model => model.use.includes('any')); + if (chatModelAnyList.length > 0) { + const randomIndex = Math.floor(Math.random() * chatModelAnyList.length); + return chatModelAnyList[randomIndex]; + } + const ImageModelList = ModelManager.imageModels.filter(model => model.use.includes(use)); + if (ImageModelList.length > 0) { + const randomIndex = Math.floor(Math.random() * ImageModelList.length); + return ImageModelList[randomIndex]; + } + const ImageModelAnyList = ModelManager.imageModels.filter(model => model.use.includes('any')); + if (ImageModelAnyList.length > 0) { + const randomIndex = Math.floor(Math.random() * ImageModelAnyList.length); + return ImageModelAnyList[randomIndex]; + } + return null; + } + + static getImageModel(use: ModelUse): ImageModel | null { + const ImageModelList = ModelManager.imageModels.filter(model => model.use.includes(use)); + if (ImageModelList.length > 0) { + const randomIndex = Math.floor(Math.random() * ImageModelList.length); + return ImageModelList[randomIndex]; + } + const ImageModelAnyList = ModelManager.imageModels.filter(model => model.use.includes('any')); + if (ImageModelAnyList.length > 0) { + const randomIndex = Math.floor(Math.random() * ImageModelAnyList.length); + return ImageModelAnyList[randomIndex]; + } + return null; + } + + static getEmbeddingModel(use: ModelUse): EmbeddingModel | null { + const EmbeddingModelList = ModelManager.embeddingModels.filter(model => model.use.includes(use)); + if (EmbeddingModelList.length > 0) { + const randomIndex = Math.floor(Math.random() * EmbeddingModelList.length); + return EmbeddingModelList[randomIndex]; + } + const EmbeddingModelAnyList = ModelManager.embeddingModels.filter(model => model.use.includes('any')); + if (EmbeddingModelAnyList.length > 0) { + const randomIndex = Math.floor(Math.random() * EmbeddingModelAnyList.length); + return EmbeddingModelAnyList[randomIndex]; + } + return null; + } } -export async function fetchData(url: string, apiKey: string, bodyObject: any): Promise { +export async function fetchData(url: string, apiKey: string, body: any): Promise { // 打印请求发送前的上下文 - if (bodyObject.hasOwnProperty('messages')) { - const s = JSON.stringify(bodyObject.messages, (key, value) => { + if (body.hasOwnProperty('messages')) { + const s = JSON.stringify(body.messages, (key, value) => { if (key === "" && Array.isArray(value)) { return value.filter(item => item.role !== "system"); } @@ -208,7 +295,7 @@ export async function fetchData(url: string, apiKey: string, bodyObject: any): P "Content-Type": "application/json", "Accept": "application/json" }, - body: JSON.stringify(bodyObject) + body: JSON.stringify(body) }); // logger.info("响应体", JSON.stringify(response, null, 2)); diff --git a/src/agent/service.ts b/src/agent/service.ts index e8aa4c8..d0370e8 100644 --- a/src/agent/service.ts +++ b/src/agent/service.ts @@ -1,167 +1,8 @@ -import { AIManager } from "../AI/AI"; -import { ToolCall, ToolInfo } from "../tool/tool"; import { ConfigManager } from "../config/configManager"; -import { parseBody, parseEmbeddingBody } from "../utils/utils_message"; +import { parseBody } from "../utils/utils_message"; import { logger } from "../logger"; import { withTimeout } from "../utils/utils"; -export async function sendChatRequest(messages: { - role: string, - content: string, - tool_calls?: ToolCall[], - tool_call_id?: string -}[], tools: ToolInfo[], tool_choice: string): Promise<{ content: string, tool_calls: ToolCall[] }> { - const { url, apiKey, bodyTemplate, timeout } = ConfigManager.request; - - try { - const bodyObject = parseBody(bodyTemplate, messages, tools, tool_choice); - const time = Date.now(); - - const data = await withTimeout(() => fetchData(url, apiKey, bodyObject), timeout); - - if (data.choices && data.choices.length > 0) { - AIManager.updateUsage(data.model, data.usage); - - const message = data.choices[0].message; - const finish_reason = data.choices[0].finish_reason; - - if (message.hasOwnProperty('reasoning_content')) { - logger.info(`思维链内容:`, message.reasoning_content); - } - - const content = message.content || ''; - - logger.info(`响应内容:`, content, '\nlatency:', Date.now() - time, 'ms', '\nfinish_reason:', finish_reason); - - return { content, tool_calls: message.tool_calls || [] }; - } else { - throw new Error(`服务器响应中没有choices或choices为空\n响应体:${JSON.stringify(data, null, 2)}`); - } - } catch (e) { - logger.error("在sendChatRequest中出错:", e.message); - return { content: '', tool_calls: [] }; - } -} - -export async function sendITTRequest(messages: { - role: string, - content: { - type: string, - image_url?: { url: string } - text?: string - }[] -}[]): Promise { - const { timeout } = ConfigManager.request; - const { url, apiKey, bodyTemplate } = ConfigManager.image; - - try { - const bodyObject = parseBody(bodyTemplate, messages, null, null); - const time = Date.now(); - - const data = await withTimeout(() => fetchData(url, apiKey, bodyObject), timeout); - - if (data.choices && data.choices.length > 0) { - AIManager.updateUsage(data.model, data.usage); - - const message = data.choices[0].message; - const content = message.content || ''; - - logger.info(`响应内容:`, content, '\nlatency', Date.now() - time, 'ms'); - - return content; - } else { - throw new Error(`服务器响应中没有choices或choices为空\n响应体:${JSON.stringify(data, null, 2)}`); - } - } catch (e) { - logger.error("在sendITTRequest中请求出错:", e.message); - return ''; - } -} - -const vectorCache: { text: string, vector: number[] } = { text: '', vector: [] }; - -export async function getEmbedding(text: string): Promise { - if (!text) { - logger.warning(`getEmbedding: 文本为空`); - return []; - } - - const { timeout } = ConfigManager.request; - const { embeddingDimension, embeddingUrl, embeddingApiKey, embeddingBodyTemplate } = ConfigManager.memory; - - if (vectorCache.text === text && vectorCache.vector.length === embeddingDimension) { - const v = vectorCache.vector; - return v; - } - - try { - const bodyObject = parseEmbeddingBody(embeddingBodyTemplate, text, embeddingDimension); - const time = Date.now(); - - const data = await withTimeout(() => fetchData(embeddingUrl, embeddingApiKey, bodyObject), timeout); - - if (data.data && data.data.length > 0) { - AIManager.updateUsage(data.model, data.usage); - - const embedding = data.data[0].embedding; - - logger.info(`文本:`, text, `\n响应embedding长度:`, embedding.length, '\nlatency:', Date.now() - time, 'ms'); - vectorCache.text = text; - vectorCache.vector = embedding; - - return embedding; - } else { - throw new Error(`服务器响应中没有data或data为空\n响应体:${JSON.stringify(data, null, 2)}`); - } - } catch (e) { - logger.error("在getEmbedding中出错:", e.message); - return []; - } -} - -export async function fetchData(url: string, apiKey: string, bodyObject: any): Promise { - // 打印请求发送前的上下文 - if (bodyObject.hasOwnProperty('messages')) { - const s = JSON.stringify(bodyObject.messages, (key, value) => { - if (key === "" && Array.isArray(value)) { - return value.filter(item => item.role !== "system"); - } - return value; - }); - logger.info(`请求发送前的上下文:\n`, s); - } - - const response = await fetch(url, { - method: 'POST', - headers: { - "Authorization": `Bearer ${apiKey}`, - "Content-Type": "application/json", - "Accept": "application/json" - }, - body: JSON.stringify(bodyObject) - }); - - // logger.info("响应体", JSON.stringify(response, null, 2)); - - const text = await response.text(); - if (!response.ok) { - throw new Error(`请求失败! 状态码: ${response.status}\n响应体:${text}`); - } - if (!text) { - throw new Error("响应体为空"); - } - - try { - const data = JSON.parse(text); - if (data.error) { - throw new Error(`请求失败! 错误信息: ${data.error.message}`); - } - return data; - } catch (e) { - throw new Error(`解析响应体时出错:${e.message}\n响应体:${text}`); - } -} - export async function startStream(messages: { role: string, content: string diff --git a/src/image/image.ts b/src/image/image.ts index 744a521..8b795ca 100644 --- a/src/image/image.ts +++ b/src/image/image.ts @@ -1,9 +1,9 @@ import { ConfigManager } from "../config/configManager"; -import { sendITTRequest } from "../agent/service"; import { generateId, revive, TypeDescriptor } from "../utils/utils"; import { logger } from "../logger"; import { MessageSegment, parseSpecialTokens } from "../utils/utils_string"; import { getSessionId } from "../utils/utils_seal"; +import { ModelManager } from "../agent/model"; export class Image { static validKeysMap: { [key in keyof Image]?: TypeDescriptor } = { @@ -118,30 +118,23 @@ export class Image { ImageManager.saveImage(this); } - // wip 接到imageAgent,然后加个save async imageToText(prompt = '') { const { defaultPrompt, urlToBase64, maxChars } = ConfigManager.image; if (urlToBase64 == '总是' && this.type === 'url') await this.urlToBase64(); - const messages = [{ - role: "user", - content: [{ - "type": "image_url", - "image_url": { "url": this.src } - }, { - "type": "text", - "text": prompt ? prompt : defaultPrompt - }] - }] + const model = ModelManager.getImageModel('image-understanding'); + if (!model) { + logger.error(`未找到支持image-understanding的模型`); + return; + } - this.description = (await sendITTRequest(messages)).slice(0, maxChars); + this.description = (await model.callITT(this.src, prompt ? prompt : defaultPrompt)).slice(0, maxChars); if (!this.description && urlToBase64 === '自动' && this.type === 'url') { logger.info(`图片${this.imageId}第一次识别失败,自动尝试使用转换为base64`); await this.urlToBase64(); - messages[0].content[0].image_url.url = this.base64Url; - this.description = (await sendITTRequest(messages)).slice(0, maxChars); + this.description = (await model.callITT(this.src, prompt ? prompt : defaultPrompt)).slice(0, maxChars); } if (!this.description) logger.error(`图片${this.imageId}识别失败`); From d8eb50d61315b52319591556dd543cbe24d26a47 Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Sat, 7 Mar 2026 14:04:39 +0800 Subject: [PATCH 12/33] =?UTF-8?q?=E5=AF=B9stream=E9=83=A8=E5=88=86?= =?UTF-8?q?=E8=BF=9B=E8=A1=8C=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/agent/model.ts | 52 ++++++----- src/agent/service.ts | 193 ----------------------------------------- src/agent/stream.ts | 151 ++++++++++++++++++++++++++++++++ src/agent/usage.ts | 50 +++++++++++ src/session/session.ts | 5 ++ 5 files changed, 236 insertions(+), 215 deletions(-) delete mode 100644 src/agent/service.ts create mode 100644 src/agent/stream.ts diff --git a/src/agent/model.ts b/src/agent/model.ts index ace847f..23d27a9 100644 --- a/src/agent/model.ts +++ b/src/agent/model.ts @@ -20,16 +20,16 @@ export class Model { name: string; use: ModelUse[]; provider: string; - base_url: string; - api_key: string; + baseUrl: string; + apiKey: string; body: ModelBody; constructor(name: string, use: ModelUse[], provider: string, base_url: string, api_key: string, body: ModelBody) { this.name = name; this.use = use; this.provider = provider; - this.base_url = base_url; - this.api_key = api_key; + this.baseUrl = base_url; + this.apiKey = api_key; this.body = body; } @@ -48,7 +48,14 @@ export class ChatModel extends Model { } get url() { - return `${this.base_url}/chat/completions`; + return `${this.baseUrl}/chat/completions`; + } + + buildChatBody(agent: Agent, sessionId: string) { + return this.buildBody({ + messages: agent.sessionService.getSession(sessionId).getMessages(), + tools: agent.getTools() + }); } async callChat(agent: Agent, sessionId: string): Promise<{ content: string, tool_calls: ToolCall[] }> { @@ -56,10 +63,7 @@ export class ChatModel extends Model { try { const time = Date.now(); - const data = await withTimeout(() => fetchData(this.url, this.api_key, this.buildBody({ - messages: agent.sessionService.getSession(sessionId).getMessages(), - tools: agent.getTools() - })), timeout); + const data = await withTimeout(() => fetchData(this.url, this.apiKey, this.buildChatBody(agent, sessionId)), timeout); if (data.choices && data.choices.length > 0) { UsageManager.updateUsage(data.model, data.usage); @@ -92,7 +96,14 @@ export class ImageModel extends Model { } get url() { - return `${this.base_url}/chat/completions`; + return `${this.baseUrl}/chat/completions`; + } + + buildChatBody(agent: Agent, sessionId: string) { + return this.buildBody({ + messages: agent.sessionService.getSession(sessionId).getImageMessages(), + tools: agent.getTools() + }); } async callITT(src: string, prompt = ''): Promise { @@ -100,7 +111,7 @@ export class ImageModel extends Model { try { const time = Date.now(); - const data = await withTimeout(() => fetchData(this.url, this.api_key, this.buildBody({ + const data = await withTimeout(() => fetchData(this.url, this.apiKey, this.buildBody({ messages: [{ role: "user", content: [{ @@ -136,10 +147,7 @@ export class ImageModel extends Model { try { const time = Date.now(); - const data = await withTimeout(() => fetchData(this.url, this.api_key, this.buildBody({ - messages: agent.sessionService.getSession(sessionId).getImageMessages(), - tools: agent.getTools() - })), timeout); + const data = await withTimeout(() => fetchData(this.url, this.apiKey, this.buildChatBody(agent, sessionId)), timeout); if (data.choices && data.choices.length > 0) { UsageManager.updateUsage(data.model, data.usage); @@ -174,7 +182,7 @@ export class EmbeddingModel extends Model { } get url() { - return `${this.base_url}/embeddings`; + return `${this.baseUrl}/embeddings`; } async callEmbedding(text: string): Promise { @@ -193,7 +201,7 @@ export class EmbeddingModel extends Model { try { const time = Date.now(); - const data = await withTimeout(() => fetchData(this.url, this.api_key, this.buildBody({ + const data = await withTimeout(() => fetchData(this.url, this.apiKey, this.buildBody({ input: text })), timeout); @@ -229,16 +237,16 @@ export class ModelManager { const randomIndex = Math.floor(Math.random() * chatModelList.length); return chatModelList[randomIndex]; } - const chatModelAnyList = ModelManager.chatModels.filter(model => model.use.includes('any')); - if (chatModelAnyList.length > 0) { - const randomIndex = Math.floor(Math.random() * chatModelAnyList.length); - return chatModelAnyList[randomIndex]; - } const ImageModelList = ModelManager.imageModels.filter(model => model.use.includes(use)); if (ImageModelList.length > 0) { const randomIndex = Math.floor(Math.random() * ImageModelList.length); return ImageModelList[randomIndex]; } + const chatModelAnyList = ModelManager.chatModels.filter(model => model.use.includes('any')); + if (chatModelAnyList.length > 0) { + const randomIndex = Math.floor(Math.random() * chatModelAnyList.length); + return chatModelAnyList[randomIndex]; + } const ImageModelAnyList = ModelManager.imageModels.filter(model => model.use.includes('any')); if (ImageModelAnyList.length > 0) { const randomIndex = Math.floor(Math.random() * ImageModelAnyList.length); diff --git a/src/agent/service.ts b/src/agent/service.ts deleted file mode 100644 index d0370e8..0000000 --- a/src/agent/service.ts +++ /dev/null @@ -1,193 +0,0 @@ -import { ConfigManager } from "../config/configManager"; -import { parseBody } from "../utils/utils_message"; -import { logger } from "../logger"; -import { withTimeout } from "../utils/utils"; - -export async function startStream(messages: { - role: string, - content: string -}[]): Promise { - const { url, apiKey, bodyTemplate, timeout } = ConfigManager.request; - const { streamUrl } = ConfigManager.backend; - - try { - const bodyObject = parseBody(bodyTemplate, messages, null, null); - - // 打印请求发送前的上下文 - const s = JSON.stringify(bodyObject.messages, (key, value) => { - if (key === "" && Array.isArray(value)) { - return value.filter(item => item.role !== "system"); - } - return value; - }); - logger.info(`请求发送前的上下文:\n`, s); - - const response = await withTimeout(() => fetch(`${streamUrl}/start`, { - method: 'POST', - headers: { - "Content-Type": "application/json", - "Accept": "application/json" - }, - body: JSON.stringify({ - url: url, - api_key: apiKey, - body_obj: bodyObject - }) - }), timeout); - - // logger.info("响应体", JSON.stringify(response, null, 2)); - - const text = await response.text(); - if (!response.ok) { - throw new Error(`请求失败! 状态码: ${response.status}\n响应体:${text}`); - } - if (!text) { - throw new Error("响应体为空"); - } - - try { - const data = JSON.parse(text); - if (data.error) { - throw new Error(`请求失败! 错误信息: ${data.error.message}`); - } - if (!data.id) { - throw new Error("服务器响应中没有id字段"); - } - return data.id; - } catch (e) { - throw new Error(`解析响应体时出错:${e}\n响应体:${text}`); - } - } catch (e) { - logger.error("在startStream中出错:", e.message); - return ''; - } -} - -export async function pollStream(id: string, after: number): Promise<{ status: string, reply: string, nextAfter: number }> { - const { streamUrl } = ConfigManager.backend; - - try { - const response = await fetch(`${streamUrl}/poll?id=${id}&after=${after}`, { - method: 'GET', - headers: { - "Accept": "application/json" - } - }); - - // logger.info("响应体", JSON.stringify(response, null, 2)); - - const text = await response.text(); - if (!response.ok) { - throw new Error(`请求失败! 状态码: ${response.status}\n响应体:${text}`); - } - if (!text) { - throw new Error("响应体为空"); - } - - try { - const data = JSON.parse(text); - if (data.error) { - throw new Error(`请求失败! 错误信息: ${data.error.message}`); - } - if (!data.status) { - throw new Error("服务器响应中没有status字段"); - } - return { - status: data.status, - reply: data.results.join(''), - nextAfter: data.next_after - }; - } catch (e) { - throw new Error(`解析响应体时出错:${e}\n响应体:${text}`); - } - } catch (e) { - logger.error("在pollStream中出错:", e.message); - return { status: 'failed', reply: '', nextAfter: 0 }; - } -} - -export async function endStream(id: string): Promise { - const { streamUrl } = ConfigManager.backend; - - try { - const response = await fetch(`${streamUrl}/end?id=${id}`, { - method: 'GET', - headers: { - "Accept": "application/json" - } - }); - - // logger.info("响应体", JSON.stringify(response, null, 2)); - - const text = await response.text(); - if (!response.ok) { - throw new Error(`请求失败! 状态码: ${response.status}\n响应体:${text}`); - } - if (!text) { - throw new Error("响应体为空"); - } - - try { - const data = JSON.parse(text); - if (data.error) { - throw new Error(`请求失败! 错误信息: ${data.error.message}`); - } - if (!data.status) { - throw new Error("服务器响应中没有status字段"); - } - logger.info('对话结束', data.status === 'success' ? '成功' : '失败'); - if (data.status === 'success') { - AIManager.updateUsage(data.model, data.usage); - } - return data.status; - } catch (e) { - throw new Error(`解析响应体时出错:${e}\n响应体:${text}`); - } - } catch (e) { - logger.error("在endStream中出错:", e.message); - return ''; - } -} - -export async function get_chart_url(chart_type: string, usage_data: { - [key: string]: { - prompt_tokens: number; - completion_tokens: number; - } -}) { - const { usageChartUrl } = ConfigManager.backend; - try { - const response = await fetch(`${usageChartUrl}/chart`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json' - }, - body: JSON.stringify({ - chart_type: chart_type, - data: usage_data - }) - }) - - const text = await response.text(); - if (!response.ok) { - throw new Error(`请求失败! 状态码: ${response.status}\n响应体: ${text}`); - } - if (!text) { - throw new Error("响应体为空"); - } - - try { - const data = JSON.parse(text); - if (data.error) { - throw new Error(`请求失败! 错误信息: ${data.error.message}`); - } - return data.image_url; - } catch (e) { - throw new Error(`解析响应体时出错:${e}\n响应体:${text}`); - } - } catch (e) { - logger.error("在get_chart_url中请求出错:", e.message); - return ''; - } -} \ No newline at end of file diff --git a/src/agent/stream.ts b/src/agent/stream.ts new file mode 100644 index 0000000..9159bf9 --- /dev/null +++ b/src/agent/stream.ts @@ -0,0 +1,151 @@ +import { ConfigManager } from "../config/configManager"; +import { logger } from "../logger"; +import { withTimeout } from "../utils/utils"; +import { Agent } from "./agent"; +import { ModelManager } from "./model"; +import { UsageManager } from "./usage"; + +export class streamService { + static async startStream(agent: Agent, sessionId: string): Promise { + const { timeout } = ConfigManager.request; + const { streamUrl } = ConfigManager.backend; + const model = ModelManager.getChatModel('chat'); + try { + const body = model.buildChatBody(agent, sessionId); + + // 打印请求发送前的上下文 + const s = JSON.stringify(body.messages, (key, value) => { + if (key === "" && Array.isArray(value)) { + return value.filter(item => item.role !== "system"); + } + return value; + }); + logger.info(`请求发送前的上下文:\n`, s); + + const response = await withTimeout(() => fetch(`${streamUrl}/start`, { + method: 'POST', + headers: { + "Content-Type": "application/json", + "Accept": "application/json" + }, + body: JSON.stringify({ + url: model.url, + api_key: model.apiKey, + body_obj: body + }) + }), timeout); + + // logger.info("响应体", JSON.stringify(response, null, 2)); + + const text = await response.text(); + if (!response.ok) { + throw new Error(`请求失败! 状态码: ${response.status}\n响应体:${text}`); + } + if (!text) { + throw new Error("响应体为空"); + } + + try { + const data = JSON.parse(text); + if (data.error) { + throw new Error(`请求失败! 错误信息: ${data.error.message}`); + } + if (!data.id) { + throw new Error("服务器响应中没有id字段"); + } + return data.id; + } catch (e) { + throw new Error(`解析响应体时出错:${e}\n响应体:${text}`); + } + } catch (e) { + logger.error("在startStream中出错:", e.message); + return ''; + } + } + + static async pollStream(streamId: string, after: number): Promise<{ status: string, reply: string, nextAfter: number }> { + const { streamUrl } = ConfigManager.backend; + + try { + const response = await fetch(`${streamUrl}/poll?id=${streamId}&after=${after}`, { + method: 'GET', + headers: { + "Accept": "application/json" + } + }); + + // logger.info("响应体", JSON.stringify(response, null, 2)); + + const text = await response.text(); + if (!response.ok) { + throw new Error(`请求失败! 状态码: ${response.status}\n响应体:${text}`); + } + if (!text) { + throw new Error("响应体为空"); + } + + try { + const data = JSON.parse(text); + if (data.error) { + throw new Error(`请求失败! 错误信息: ${data.error.message}`); + } + if (!data.status) { + throw new Error("服务器响应中没有status字段"); + } + return { + status: data.status, + reply: data.results.join(''), + nextAfter: data.next_after + }; + } catch (e) { + throw new Error(`解析响应体时出错:${e}\n响应体:${text}`); + } + } catch (e) { + logger.error("在pollStream中出错:", e.message); + return { status: 'failed', reply: '', nextAfter: 0 }; + } + } + + static async endStream(streamId: string): Promise { + const { streamUrl } = ConfigManager.backend; + + try { + const response = await fetch(`${streamUrl}/end?id=${streamId}`, { + method: 'GET', + headers: { + "Accept": "application/json" + } + }); + + // logger.info("响应体", JSON.stringify(response, null, 2)); + + const text = await response.text(); + if (!response.ok) { + throw new Error(`请求失败! 状态码: ${response.status}\n响应体:${text}`); + } + if (!text) { + throw new Error("响应体为空"); + } + + try { + const data = JSON.parse(text); + if (data.error) { + throw new Error(`请求失败! 错误信息: ${data.error.message}`); + } + if (!data.status) { + throw new Error("服务器响应中没有status字段"); + } + logger.info('对话结束', data.status === 'success' ? '成功' : '失败'); + if (data.status === 'success') { + UsageManager.updateUsage(data.model, data.usage); + } + return data.status; + } catch (e) { + throw new Error(`解析响应体时出错:${e}\n响应体:${text}`); + } + } catch (e) { + logger.error("在endStream中出错:", e.message); + return ''; + } + } +} \ No newline at end of file diff --git a/src/agent/usage.ts b/src/agent/usage.ts index 8d2b402..a69fb57 100644 --- a/src/agent/usage.ts +++ b/src/agent/usage.ts @@ -1,3 +1,53 @@ +import { ConfigManager } from "../config/configManager"; +import { logger } from "../logger"; + export class UsageManager { static usageMap: { [key: string]: Usage } = {}; + + static updateUsage(model: string, usage: Usage) { + + } +} + +export async function get_chart_url(chart_type: string, usage_data: { + [key: string]: { + prompt_tokens: number; + completion_tokens: number; + } +}) { + const { usageChartUrl } = ConfigManager.backend; + try { + const response = await fetch(`${usageChartUrl}/chart`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }, + body: JSON.stringify({ + chart_type: chart_type, + data: usage_data + }) + }) + + const text = await response.text(); + if (!response.ok) { + throw new Error(`请求失败! 状态码: ${response.status}\n响应体: ${text}`); + } + if (!text) { + throw new Error("响应体为空"); + } + + try { + const data = JSON.parse(text); + if (data.error) { + throw new Error(`请求失败! 错误信息: ${data.error.message}`); + } + return data.image_url; + } catch (e) { + throw new Error(`解析响应体时出错:${e}\n响应体:${text}`); + } + } catch (e) { + logger.error("在get_chart_url中请求出错:", e.message); + return ''; + } } \ No newline at end of file diff --git a/src/session/session.ts b/src/session/session.ts index 0634a52..681982c 100644 --- a/src/session/session.ts +++ b/src/session/session.ts @@ -48,6 +48,11 @@ export class Session { getMessages(): RequestMessage[] { return []; } + + // wip + getImageMessages(): RequestMessage[] { + return []; + } } export class SessionService { From f3bcf9c132802f837cf43ce5940ea635df3c6809 Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Sat, 7 Mar 2026 16:02:18 +0800 Subject: [PATCH 13/33] =?UTF-8?q?=E5=A5=BD=E4=B9=B1=E5=95=8A=E5=95=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/agent/AI.ts | 4 +- src/agent/agent.ts | 25 +- src/agent/agents/compress_agent.ts | 10 + src/agent/agents/root_agent.ts | 10 + src/agent/agents/samples.ts | 10 + src/agent/model.ts | 4 +- src/cmd/privilege.ts | 2 +- src/cmd/{root.ts => root_cmd.ts} | 0 src/cmd/sub_cmd/ctxn.ts | 2 +- src/cmd/sub_cmd/forget.ts | 2 +- src/cmd/sub_cmd/ignore.ts | 2 +- src/cmd/sub_cmd/image.ts | 4 +- src/cmd/sub_cmd/memory.ts | 2 +- src/cmd/sub_cmd/off.ts | 2 +- src/cmd/sub_cmd/on.ts | 2 +- src/cmd/sub_cmd/privilege.ts | 4 +- src/cmd/sub_cmd/prompt.ts | 4 +- src/cmd/sub_cmd/role.ts | 4 +- src/cmd/sub_cmd/sample.ts | 2 +- src/cmd/sub_cmd/shut.ts | 2 +- src/cmd/sub_cmd/standby.ts | 2 +- src/cmd/sub_cmd/status.ts | 2 +- src/cmd/sub_cmd/timer.ts | 2 +- src/cmd/sub_cmd/token.ts | 2 +- src/cmd/sub_cmd/tool.ts | 14 +- src/config/configManager.ts | 2 +- src/config/{ => configs}/config_backend.ts | 0 src/config/{ => configs}/config_image.ts | 0 src/config/{ => configs}/config_log.ts | 0 src/config/{ => configs}/config_memory.ts | 0 src/config/{ => configs}/config_message.ts | 0 src/config/{ => configs}/config_received.ts | 0 src/config/{ => configs}/config_reply.ts | 0 src/config/{ => configs}/config_request.ts | 0 src/config/{ => configs}/config_tool.ts | 0 src/config/{ => configs}/sample.ts | 0 src/config/{config.ts => static_config.ts} | 0 src/image/image.ts | 4 +- src/index.ts | 10 +- src/logger.ts | 2 +- src/note.txt | 49 ++ src/session/context.ts | 148 ++----- src/session/memory.ts | 4 +- src/timer.ts | 4 +- src/tool/tool.ts | 72 +-- src/tool/{ => tools}/sample.ts | 0 src/tool/{ => tools}/tool_attr.ts | 10 +- src/tool/{ => tools}/tool_ban.ts | 6 +- src/tool/{ => tools}/tool_context.ts | 0 src/tool/{ => tools}/tool_deck.ts | 0 src/tool/{ => tools}/tool_essence_msg.ts | 0 src/tool/{ => tools}/tool_group_sign.ts | 2 +- src/tool/{ => tools}/tool_image.ts | 0 src/tool/{ => tools}/tool_jrrp.ts | 10 +- src/tool/{ => tools}/tool_meme.ts | 468 ++++++++++---------- src/tool/{ => tools}/tool_memory.ts | 0 src/tool/{ => tools}/tool_message.ts | 2 +- src/tool/{ => tools}/tool_modu.ts | 20 +- src/tool/{ => tools}/tool_music.ts | 0 src/tool/{ => tools}/tool_person_info.ts | 0 src/tool/{ => tools}/tool_qq_list.ts | 0 src/tool/{ => tools}/tool_rename.ts | 2 +- src/tool/{ => tools}/tool_render.ts | 0 src/tool/{ => tools}/tool_roll_check.ts | 20 +- src/tool/{ => tools}/tool_time.ts | 0 src/tool/{ => tools}/tool_trigger.ts | 0 src/tool/{ => tools}/tool_voice.ts | 0 src/tool/{ => tools}/tool_web.ts | 0 src/utils/{utils_message.ts => message.ts} | 2 +- src/utils/{utils_ob11.ts => ob11.ts} | 2 +- src/utils/{utils_seal.ts => seal.ts} | 0 src/utils/{utils_string.ts => string.ts} | 4 +- src/utils/{utils_update.ts => update.ts} | 2 +- src/utils/utils.ts | 6 +- 74 files changed, 490 insertions(+), 480 deletions(-) create mode 100644 src/agent/agents/compress_agent.ts create mode 100644 src/agent/agents/root_agent.ts create mode 100644 src/agent/agents/samples.ts rename src/cmd/{root.ts => root_cmd.ts} (100%) rename src/config/{ => configs}/config_backend.ts (100%) rename src/config/{ => configs}/config_image.ts (100%) rename src/config/{ => configs}/config_log.ts (100%) rename src/config/{ => configs}/config_memory.ts (100%) rename src/config/{ => configs}/config_message.ts (100%) rename src/config/{ => configs}/config_received.ts (100%) rename src/config/{ => configs}/config_reply.ts (100%) rename src/config/{ => configs}/config_request.ts (100%) rename src/config/{ => configs}/config_tool.ts (100%) rename src/config/{ => configs}/sample.ts (100%) rename src/config/{config.ts => static_config.ts} (100%) rename src/tool/{ => tools}/sample.ts (100%) rename src/tool/{ => tools}/tool_attr.ts (96%) rename src/tool/{ => tools}/tool_ban.ts (97%) rename src/tool/{ => tools}/tool_context.ts (100%) rename src/tool/{ => tools}/tool_deck.ts (100%) rename src/tool/{ => tools}/tool_essence_msg.ts (100%) rename src/tool/{ => tools}/tool_group_sign.ts (96%) rename src/tool/{ => tools}/tool_image.ts (100%) rename src/tool/{ => tools}/tool_jrrp.ts (90%) rename src/tool/{ => tools}/tool_meme.ts (97%) rename src/tool/{ => tools}/tool_memory.ts (100%) rename src/tool/{ => tools}/tool_message.ts (99%) rename src/tool/{ => tools}/tool_modu.ts (82%) rename src/tool/{ => tools}/tool_music.ts (100%) rename src/tool/{ => tools}/tool_person_info.ts (100%) rename src/tool/{ => tools}/tool_qq_list.ts (100%) rename src/tool/{ => tools}/tool_rename.ts (98%) rename src/tool/{ => tools}/tool_render.ts (100%) rename src/tool/{ => tools}/tool_roll_check.ts (94%) rename src/tool/{ => tools}/tool_time.ts (100%) rename src/tool/{ => tools}/tool_trigger.ts (100%) rename src/tool/{ => tools}/tool_voice.ts (100%) rename src/tool/{ => tools}/tool_web.ts (100%) rename src/utils/{utils_message.ts => message.ts} (99%) rename src/utils/{utils_ob11.ts => ob11.ts} (99%) rename src/utils/{utils_seal.ts => seal.ts} (100%) rename src/utils/{utils_string.ts => string.ts} (99%) rename src/utils/{utils_update.ts => update.ts} (97%) diff --git a/src/agent/AI.ts b/src/agent/AI.ts index bcc18ec..f352811 100644 --- a/src/agent/AI.ts +++ b/src/agent/AI.ts @@ -4,10 +4,10 @@ import { replyToSender, revive, transformMsgId } from "../utils/utils"; import { endStream, pollStream, sendChatRequest, startStream } from "../agent/service"; import { Context } from "./context"; import { MemoryManager } from "./memory"; -import { handleMessages, parseBody } from "../utils/utils_message"; +import { handleMessages, parseBody } from "../utils/message"; import { ToolManager } from "../tool/tool"; import { logger } from "../logger"; -import { checkRepeat, handleReply, MessageSegment, transformArrayToContent } from "../utils/utils_string"; +import { checkRepeat, handleReply, MessageSegment, transformArrayToContent } from "../utils/string"; import { TimerManager } from "../timer"; export interface GroupInfo { diff --git a/src/agent/agent.ts b/src/agent/agent.ts index 61856fb..6610745 100644 --- a/src/agent/agent.ts +++ b/src/agent/agent.ts @@ -5,27 +5,30 @@ import { revive, TypeDescriptor } from "../utils/utils"; export class Agent { static validKeysMap: { [key in keyof Agent]?: TypeDescriptor } = { - name: 'string', - description: 'string', - instruction: 'string', sessionService: SessionService, - tool: { array: 'string' }, + tools: { array: 'string' }, + subAgents: { array: 'string' } } + name: string; description: string; - instruction: string; + instruction: string | ((sessionService: SessionService) => string); + sessionService: SessionService; - tool: string[]; + tools: string[]; + subAgents: string[]; constructor() { + this.name = ""; this.description = ""; this.instruction = ""; this.sessionService = new SessionService(); - this.tool = []; + this.tools = []; + this.subAgents = []; } // wip - getTools() { + getRequestTools() { } async chat() { @@ -51,7 +54,11 @@ export class AgentManager { return this.agentMap[name]; } - static save() { + static saveAgent(agent: Agent) { + ConfigManager.ext.storageSet(`agent_${agent.name}`, JSON.stringify(agent)); + } + + static initAgent() { } } \ No newline at end of file diff --git a/src/agent/agents/compress_agent.ts b/src/agent/agents/compress_agent.ts new file mode 100644 index 0000000..8d0c210 --- /dev/null +++ b/src/agent/agents/compress_agent.ts @@ -0,0 +1,10 @@ +import { AgentManager } from "../agent"; + +export function initCompressAgent() { + const agent = AgentManager.agentMap["compress_agent"]; + agent.name = "compress_agent"; + agent.description = "压缩智能体"; + agent.instruction = "你是一个压缩智能体,你可以压缩文本。"; + AgentManager.agentMap[agent.name] = agent; + AgentManager.saveAgent(agent); +} \ No newline at end of file diff --git a/src/agent/agents/root_agent.ts b/src/agent/agents/root_agent.ts new file mode 100644 index 0000000..91b13a9 --- /dev/null +++ b/src/agent/agents/root_agent.ts @@ -0,0 +1,10 @@ +import { AgentManager } from "../agent"; + +export function initRootAgent() { + const agent = AgentManager.agentMap["root_agent"]; + agent.name = "root_agent"; + agent.description = "根智能体"; + agent.instruction = "你是一个根智能体,你可以调用其他智能体。"; + AgentManager.agentMap[agent.name] = agent; + AgentManager.saveAgent(agent); +} \ No newline at end of file diff --git a/src/agent/agents/samples.ts b/src/agent/agents/samples.ts new file mode 100644 index 0000000..22147b1 --- /dev/null +++ b/src/agent/agents/samples.ts @@ -0,0 +1,10 @@ +import { AgentManager } from "../agent"; + +export function initSampleAgent() { + const agent = AgentManager.agentMap["sample_agent"]; + agent.name = "sample_agent"; + agent.description = "示例智能体"; + agent.instruction = "你是一个示例智能体。"; + AgentManager.agentMap[agent.name] = agent; + AgentManager.saveAgent(agent); +} \ No newline at end of file diff --git a/src/agent/model.ts b/src/agent/model.ts index 23d27a9..2c7cdf1 100644 --- a/src/agent/model.ts +++ b/src/agent/model.ts @@ -54,7 +54,7 @@ export class ChatModel extends Model { buildChatBody(agent: Agent, sessionId: string) { return this.buildBody({ messages: agent.sessionService.getSession(sessionId).getMessages(), - tools: agent.getTools() + tools: agent.getRequestTools() }); } @@ -102,7 +102,7 @@ export class ImageModel extends Model { buildChatBody(agent: Agent, sessionId: string) { return this.buildBody({ messages: agent.sessionService.getSession(sessionId).getImageMessages(), - tools: agent.getTools() + tools: agent.getRequestTools() }); } diff --git a/src/cmd/privilege.ts b/src/cmd/privilege.ts index 4ec7854..bc799c4 100644 --- a/src/cmd/privilege.ts +++ b/src/cmd/privilege.ts @@ -2,7 +2,7 @@ import { AI } from "../AI/AI"; import { logger } from "../logger"; import { ConfigManager } from "../config/configManager"; import { aliasToCmd } from "../utils/utils"; -import { PRIVILEGELEVELMAP } from "../config/config"; +import { PRIVILEGELEVELMAP } from "../config/static_config"; export interface CmdPrivInfo { diff --git a/src/cmd/root.ts b/src/cmd/root_cmd.ts similarity index 100% rename from src/cmd/root.ts rename to src/cmd/root_cmd.ts diff --git a/src/cmd/sub_cmd/ctxn.ts b/src/cmd/sub_cmd/ctxn.ts index 352b18d..82705fa 100644 --- a/src/cmd/sub_cmd/ctxn.ts +++ b/src/cmd/sub_cmd/ctxn.ts @@ -1,6 +1,6 @@ import { aliasToCmd } from "../../utils/utils"; import { I, U } from "../privilege"; -import { SubCmd, SubCmdContext } from "../root"; +import { SubCmd, SubCmdContext } from "../root_cmd"; export function registerCmdCtxn() { const cmd = new SubCmd('ctxn'); diff --git a/src/cmd/sub_cmd/forget.ts b/src/cmd/sub_cmd/forget.ts index 59b7c52..99a39e1 100644 --- a/src/cmd/sub_cmd/forget.ts +++ b/src/cmd/sub_cmd/forget.ts @@ -1,7 +1,7 @@ import { AIManager } from "../../AI/AI"; import { aliasToCmd } from "../../utils/utils"; import { I, U } from "../privilege"; -import { SubCmd, SubCmdContext } from "../root"; +import { SubCmd, SubCmdContext } from "../root_cmd"; export function registerCmdForget() { const cmd = new SubCmd('forget'); diff --git a/src/cmd/sub_cmd/ignore.ts b/src/cmd/sub_cmd/ignore.ts index 728213b..8935818 100644 --- a/src/cmd/sub_cmd/ignore.ts +++ b/src/cmd/sub_cmd/ignore.ts @@ -1,7 +1,7 @@ import { AIManager } from "../../AI/AI"; import { aliasToCmd } from "../../utils/utils"; import { U } from "../privilege"; -import { SubCmd, SubCmdContext } from "../root"; +import { SubCmd, SubCmdContext } from "../root_cmd"; export function registerCmdIgnore() { const cmd = new SubCmd('ignore'); diff --git a/src/cmd/sub_cmd/image.ts b/src/cmd/sub_cmd/image.ts index 4c2372b..57a72c6 100644 --- a/src/cmd/sub_cmd/image.ts +++ b/src/cmd/sub_cmd/image.ts @@ -1,9 +1,9 @@ import { AIManager } from "../../AI/AI"; import { ImageManager } from "../../image/image"; import { aliasToCmd } from "../../utils/utils"; -import { transformArrayToContent, transformTextToArray } from "../../utils/utils_string"; +import { transformArrayToContent, transformTextToArray } from "../../utils/string"; import { I, M, U } from "../privilege"; -import { SubCmd, SubCmdContext } from "../root"; +import { SubCmd, SubCmdContext } from "../root_cmd"; export function registerCmdImage() { const cmd = new SubCmd('image'); diff --git a/src/cmd/sub_cmd/memory.ts b/src/cmd/sub_cmd/memory.ts index 7b59807..16c4de0 100644 --- a/src/cmd/sub_cmd/memory.ts +++ b/src/cmd/sub_cmd/memory.ts @@ -2,7 +2,7 @@ import { AIManager } from "../../AI/AI"; import { ConfigManager } from "../../config/configManager"; import { aliasToCmd } from "../../utils/utils"; import { I, S, U } from "../privilege"; -import { SubCmd, SubCmdContext } from "../root"; +import { SubCmd, SubCmdContext } from "../root_cmd"; export function registerCmdMemory() { const cmd = new SubCmd('memory'); diff --git a/src/cmd/sub_cmd/off.ts b/src/cmd/sub_cmd/off.ts index e52452d..51900ea 100644 --- a/src/cmd/sub_cmd/off.ts +++ b/src/cmd/sub_cmd/off.ts @@ -1,7 +1,7 @@ import { AIManager } from "../../AI/AI"; import { TimerManager } from "../../timer"; import { I } from "../privilege"; -import { SubCmd, SubCmdContext } from "../root"; +import { SubCmd, SubCmdContext } from "../root_cmd"; export function registerCmdOff() { const cmd = new SubCmd('off'); diff --git a/src/cmd/sub_cmd/on.ts b/src/cmd/sub_cmd/on.ts index 68e791c..b7db8dd 100644 --- a/src/cmd/sub_cmd/on.ts +++ b/src/cmd/sub_cmd/on.ts @@ -1,7 +1,7 @@ import { AIManager } from "../../AI/AI"; import { TimerManager } from "../../timer"; import { S } from "../privilege"; -import { SubCmd, SubCmdContext } from "../root"; +import { SubCmd, SubCmdContext } from "../root_cmd"; export function registerCmdOn() { const cmd = new SubCmd('on'); diff --git a/src/cmd/sub_cmd/privilege.ts b/src/cmd/sub_cmd/privilege.ts index 8646b58..3b35c59 100644 --- a/src/cmd/sub_cmd/privilege.ts +++ b/src/cmd/sub_cmd/privilege.ts @@ -1,8 +1,8 @@ import { AIManager } from "../../AI/AI"; -import { HELPMAP } from "../../config/config"; +import { HELPMAP } from "../../config/static_config"; import { aliasToCmd } from "../../utils/utils"; import { M, PrivilegeManager, U } from "../privilege"; -import { SubCmd, SubCmdContext } from "../root"; +import { SubCmd, SubCmdContext } from "../root_cmd"; export function registerCmdPrivilege() { const cmd = new SubCmd('privilege'); diff --git a/src/cmd/sub_cmd/prompt.ts b/src/cmd/sub_cmd/prompt.ts index 883d045..f7b4aa0 100644 --- a/src/cmd/sub_cmd/prompt.ts +++ b/src/cmd/sub_cmd/prompt.ts @@ -1,7 +1,7 @@ import { logger } from "../../logger"; -import { buildSystemMessage } from "../../utils/utils_message"; +import { buildSystemMessage } from "../../utils/message"; import { M } from "../privilege"; -import { SubCmd, SubCmdContext } from "../root"; +import { SubCmd, SubCmdContext } from "../root_cmd"; export function registerCmdPrompt() { const cmd = new SubCmd('prompt'); diff --git a/src/cmd/sub_cmd/role.ts b/src/cmd/sub_cmd/role.ts index 6550f6a..79a1aba 100644 --- a/src/cmd/sub_cmd/role.ts +++ b/src/cmd/sub_cmd/role.ts @@ -1,7 +1,7 @@ import { ConfigManager } from "../../config/configManager"; -import { getRoleSetting } from "../../utils/utils_message"; +import { getRoleSetting } from "../../utils/message"; import { I } from "../privilege"; -import { SubCmd, SubCmdContext } from "../root"; +import { SubCmd, SubCmdContext } from "../root_cmd"; export function registerCmdRole() { const cmd = new SubCmd('role'); diff --git a/src/cmd/sub_cmd/sample.ts b/src/cmd/sub_cmd/sample.ts index 1bf6927..5a8a944 100644 --- a/src/cmd/sub_cmd/sample.ts +++ b/src/cmd/sub_cmd/sample.ts @@ -1,5 +1,5 @@ import { U } from "../privilege"; -import { SubCmd, SubCmdContext } from "../root"; +import { SubCmd, SubCmdContext } from "../root_cmd"; export function registerCmdSample() { const cmd = new SubCmd('sample'); diff --git a/src/cmd/sub_cmd/shut.ts b/src/cmd/sub_cmd/shut.ts index 6e6a1b0..1050c77 100644 --- a/src/cmd/sub_cmd/shut.ts +++ b/src/cmd/sub_cmd/shut.ts @@ -1,5 +1,5 @@ import { U } from "../privilege"; -import { SubCmd, SubCmdContext } from "../root"; +import { SubCmd, SubCmdContext } from "../root_cmd"; export function registerCmdShut() { const cmd = new SubCmd('shut'); diff --git a/src/cmd/sub_cmd/standby.ts b/src/cmd/sub_cmd/standby.ts index 9d16024..7e817c4 100644 --- a/src/cmd/sub_cmd/standby.ts +++ b/src/cmd/sub_cmd/standby.ts @@ -1,7 +1,7 @@ import { AIManager } from "../../AI/AI"; import { TimerManager } from "../../timer"; import { I } from "../privilege"; -import { SubCmd, SubCmdContext } from "../root"; +import { SubCmd, SubCmdContext } from "../root_cmd"; export function registerCmdStandby() { const cmd = new SubCmd('standby'); diff --git a/src/cmd/sub_cmd/status.ts b/src/cmd/sub_cmd/status.ts index 0ff4ec2..0bce740 100644 --- a/src/cmd/sub_cmd/status.ts +++ b/src/cmd/sub_cmd/status.ts @@ -1,5 +1,5 @@ import { U } from "../privilege"; -import { SubCmd, SubCmdContext } from "../root"; +import { SubCmd, SubCmdContext } from "../root_cmd"; export function registerCmdStatus() { const cmd = new SubCmd('status'); diff --git a/src/cmd/sub_cmd/timer.ts b/src/cmd/sub_cmd/timer.ts index 9ff7f77..185f482 100644 --- a/src/cmd/sub_cmd/timer.ts +++ b/src/cmd/sub_cmd/timer.ts @@ -1,7 +1,7 @@ import { TimerManager } from "../../timer"; import { aliasToCmd } from "../../utils/utils"; import { I, U } from "../privilege"; -import { SubCmd, SubCmdContext } from "../root"; +import { SubCmd, SubCmdContext } from "../root_cmd"; export function registerCmdTimer() { const cmd = new SubCmd('timer'); diff --git a/src/cmd/sub_cmd/token.ts b/src/cmd/sub_cmd/token.ts index fe2207f..38193c1 100644 --- a/src/cmd/sub_cmd/token.ts +++ b/src/cmd/sub_cmd/token.ts @@ -2,7 +2,7 @@ import { AIManager } from "../../AI/AI"; import { get_chart_url } from "../../agent/service"; import { aliasToCmd } from "../../utils/utils"; import { S, U } from "../privilege"; -import { SubCmd, SubCmdContext } from "../root"; +import { SubCmd, SubCmdContext } from "../root_cmd"; export function registerCmdToken() { const cmd = new SubCmd('token'); diff --git a/src/cmd/sub_cmd/tool.ts b/src/cmd/sub_cmd/tool.ts index fcf50e0..50d8eb9 100644 --- a/src/cmd/sub_cmd/tool.ts +++ b/src/cmd/sub_cmd/tool.ts @@ -4,7 +4,7 @@ import { logger } from "../../logger"; import { ToolManager } from "../../tool/tool"; import { aliasToCmd } from "../../utils/utils"; import { I, M, U } from "../privilege"; -import { SubCmd, SubCmdContext } from "../root"; +import { SubCmd, SubCmdContext } from "../root_cmd"; export function registerCmdTool() { const cmd = new SubCmd('tool'); @@ -78,13 +78,13 @@ export function registerCmdTool() { } const tool = ToolManager.toolMap[val3]; - const s = `${tool.info.function.name} - 描述:${tool.info.function.description} + const s = `${tool.toolInfo.function.name} + 描述:${tool.toolInfo.function.description} 参数信息: - ${JSON.stringify(tool.info.function.parameters.properties, null, 2)} + ${JSON.stringify(tool.toolInfo.function.parameters.properties, null, 2)} - 必需参数:${tool.info.function.parameters.required.join(',')}`; + 必需参数:${tool.toolInfo.function.parameters.required.join(',')}`; seal.replyToSender(ctx, msg, s); return ret; @@ -100,7 +100,7 @@ export function registerCmdTool() { return ret; } const tool = ToolManager.toolMap[val3]; - if (tool.cmdInfo.ext !== '' && ToolManager.cmdArgs == null) { + if (tool.ExtCmdInfo.extName !== '' && ToolManager.cmdArgs == null) { seal.replyToSender(ctx, msg, `暂时无法调用函数,请先使用 .r 指令`); return ret; } @@ -116,7 +116,7 @@ export function registerCmdTool() { return acc; }, {}); - for (const key of tool.info.function.parameters.required) { + for (const key of tool.toolInfo.function.parameters.required) { if (!args.hasOwnProperty(key)) { logger.warning(`调用函数失败:缺少必需参数 ${key}`); seal.replyToSender(ctx, msg, `调用函数失败:缺少必需参数 ${key}`); diff --git a/src/config/configManager.ts b/src/config/configManager.ts index 000db96..b5b189d 100644 --- a/src/config/configManager.ts +++ b/src/config/configManager.ts @@ -1,6 +1,6 @@ import Handlebars from "handlebars"; import { logger } from "../logger"; -import { AUTHOR, NAME, VERSION } from "./config"; +import { AUTHOR, NAME, VERSION } from "./static_config"; import { BackendConfig } from "./config_backend"; import { ImageConfig } from "./config_image"; import { LogConfig } from "./config_log"; diff --git a/src/config/config_backend.ts b/src/config/configs/config_backend.ts similarity index 100% rename from src/config/config_backend.ts rename to src/config/configs/config_backend.ts diff --git a/src/config/config_image.ts b/src/config/configs/config_image.ts similarity index 100% rename from src/config/config_image.ts rename to src/config/configs/config_image.ts diff --git a/src/config/config_log.ts b/src/config/configs/config_log.ts similarity index 100% rename from src/config/config_log.ts rename to src/config/configs/config_log.ts diff --git a/src/config/config_memory.ts b/src/config/configs/config_memory.ts similarity index 100% rename from src/config/config_memory.ts rename to src/config/configs/config_memory.ts diff --git a/src/config/config_message.ts b/src/config/configs/config_message.ts similarity index 100% rename from src/config/config_message.ts rename to src/config/configs/config_message.ts diff --git a/src/config/config_received.ts b/src/config/configs/config_received.ts similarity index 100% rename from src/config/config_received.ts rename to src/config/configs/config_received.ts diff --git a/src/config/config_reply.ts b/src/config/configs/config_reply.ts similarity index 100% rename from src/config/config_reply.ts rename to src/config/configs/config_reply.ts diff --git a/src/config/config_request.ts b/src/config/configs/config_request.ts similarity index 100% rename from src/config/config_request.ts rename to src/config/configs/config_request.ts diff --git a/src/config/config_tool.ts b/src/config/configs/config_tool.ts similarity index 100% rename from src/config/config_tool.ts rename to src/config/configs/config_tool.ts diff --git a/src/config/sample.ts b/src/config/configs/sample.ts similarity index 100% rename from src/config/sample.ts rename to src/config/configs/sample.ts diff --git a/src/config/config.ts b/src/config/static_config.ts similarity index 100% rename from src/config/config.ts rename to src/config/static_config.ts diff --git a/src/image/image.ts b/src/image/image.ts index 8b795ca..0c75e72 100644 --- a/src/image/image.ts +++ b/src/image/image.ts @@ -1,8 +1,8 @@ import { ConfigManager } from "../config/configManager"; import { generateId, revive, TypeDescriptor } from "../utils/utils"; import { logger } from "../logger"; -import { MessageSegment, parseSpecialTokens } from "../utils/utils_string"; -import { getSessionId } from "../utils/utils_seal"; +import { MessageSegment, parseSpecialTokens } from "../utils/string"; +import { getSessionId } from "../utils/seal"; import { ModelManager } from "../agent/model"; export class Image { diff --git a/src/index.ts b/src/index.ts index 273c7bf..fb370a1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,14 +3,14 @@ import { ToolManager } from "./tool/tool"; import { ConfigManager } from "./config/configManager"; import { triggerConditionMap } from "./tool/tool_trigger"; import { logger } from "./logger"; -import { transformTextToArray } from "./utils/utils_string"; -import { checkUpdate } from "./utils/utils_update"; +import { transformTextToArray } from "./utils/string"; +import { checkUpdate } from "./utils/update"; import { TimerManager } from "./timer"; -import { createMsg } from "./utils/utils_seal"; +import { createMsg } from "./utils/seal"; import { PrivilegeManager } from "./cmd/privilege"; import { knowledgeService } from "./session/memory"; -import { CQTYPESALLOW } from "./config/config"; -import { registerCmd } from "./cmd/root"; +import { CQTYPESALLOW } from "./config/static_config"; +import { registerCmd } from "./cmd/root_cmd"; function main() { ConfigManager.registerConfig(); diff --git a/src/logger.ts b/src/logger.ts index 7565f99..3c77cd4 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -1,4 +1,4 @@ -import { NAME } from "./config/config"; +import { NAME } from "./config/static_config"; import { ConfigManager } from "./config/configManager"; class Logger { diff --git a/src/note.txt b/src/note.txt index 6059a5a..e3f2b15 100644 --- a/src/note.txt +++ b/src/note.txt @@ -7,6 +7,55 @@ last reply 放进 sendService 里,所有send换成一个,除了指令调用 计数器、计时器逻辑移到trigger autoNameMod: number; // 自动修改上下文里的名字,0:不自动修改,1:修改为昵称,2:修改为群名片 + async setName(epId: string, gid: string, uid: string, mod: 'nickname' | 'card') { + let name = ''; + switch (mod) { + case 'nickname': { + const strangerInfo = await getStrangerInfo(epId, uid.replace(/^.+:/, '')); + if (!strangerInfo || !strangerInfo.nickname) { + logger.warning(`未找到用户<${uid}>的昵称`); + break; + } + name = strangerInfo.nickname; + break; + } + case 'card': { + if (!gid) break; + const memberInfo = await getGroupMemberInfo(epId, gid.replace(/^.+:/, ''), uid.replace(/^.+:/, '')); + if (!memberInfo) { + logger.warning(`获取用户<${uid}>的群成员信息失败,尝试使用昵称`); + this.setName(epId, gid, uid, 'nickname'); + break; + } + name = memberInfo.card || memberInfo.nickname; + if (!name) { + this.setName(epId, gid, uid, 'nickname'); + return; + } + break; + } + } + if (!name) { + logger.warning(`用户<${uid}>未设置昵称或群名片`); + return; + } + const { ctx } = getCtxAndMsg(epId, uid, gid); + ctx.player.name = name; + this.messages.forEach(message => message.name = message.uid === uid ? name : message.name); + } + + async updateName(epId: string, gid: string, uid: string) { + switch (this.autoNameMod) { + case 1: { + await this.setName(epId, gid, uid, 'nickname'); + break; + } + case 2: { + await this.setName(epId, gid, uid, 'card'); + break; + } + } + } 放到user manager tool分为触发callback和不触发callback两种,将tool choice删除 diff --git a/src/session/context.ts b/src/session/context.ts index 8bf9076..972b59a 100644 --- a/src/session/context.ts +++ b/src/session/context.ts @@ -1,42 +1,43 @@ import { ToolCall } from "../tool/tool"; import { ConfigManager } from "../config/configManager"; import { Image, ImageManager } from "../image/image"; -import { getCtxAndMsg } from "../utils/utils_seal"; -import { levenshteinDistance } from "../utils/utils_string"; -import { AI, AIManager, GroupInfo, UserInfo } from "./AI"; +import { getCtxAndMsg } from "../utils/seal"; +import { levenshteinDistance } from "../utils/string"; import { logger } from "../logger"; -import { netExists, getFriendList, getGroupList, getGroupMemberInfo, getGroupMemberList, getStrangerInfo } from "../utils/utils_ob11"; -import { revive, TypeDescriptor } from "../utils/utils"; +import { netExists, getFriendList, getGroupList, getGroupMemberInfo, getGroupMemberList, getStrangerInfo } from "../utils/ob11"; +import { TypeDescriptor } from "../utils/utils"; -export class MessageItem { +export interface BaseMessageItem { time: number; // 秒 text: string; } -export class UserMessageItem extends MessageItem { +export interface UserMessageItem extends BaseMessageItem { userId: string; messageId: string; } -export class AssistantMessageItem extends MessageItem { +export interface AssistantMessageItem extends BaseMessageItem { messageId: string; } -export class SystemMessageItem extends MessageItem { +export interface SystemUserMessageItem extends BaseMessageItem { tip: string; } -export class ToolCallsMessageItem extends MessageItem { +export interface ToolCallsMessageItem extends BaseMessageItem { tool_calls: ToolCall[]; } -export class ToolCallbackMessageItem extends MessageItem { +export interface ToolCallbackMessageItem extends BaseMessageItem { tool_call_id: string; } +export type MessageItem = UserMessageItem | AssistantMessageItem | SystemUserMessageItem | ToolCallsMessageItem | ToolCallbackMessageItem; + export class Context { - static validKeysMap: {[key in keyof Context]?: TypeDescriptor} = { - messages: 'any' + static validKeysMap: { [key in keyof Context]?: TypeDescriptor } = { + messages: { array: 'any' } } messages: MessageItem[]; @@ -61,6 +62,28 @@ export class Context { } } + // 添加后检查压缩条件,并对过长user进行压缩 + addUserMessage() { + + } + + addAssistantMessage() { + + } + + addSystemUserMessage() { + + } + + addToolCallsMessage() { + + } + + // 同理,进行压缩 + addToolCallbackMessage() { + + } + async addMessage(ctx: seal.MsgContext, msg: seal.Message, ai: AI, content: string, images: Image[], role: 'user' | 'assistant', msgId: string = '') { const { isShortMemory, shortMemorySummaryRound } = ConfigManager.memory; const messages = this.messages; @@ -344,55 +367,6 @@ export class Context { return null; } - async findImage(ctx: seal.MsgContext, id: string): Promise { - // 从用户头像中查找图片 - if (/^user_avatar[::]/.test(id)) { - const ui = await this.findUserInfo(ctx, id.replace(/^user_avatar[::]/, '')); - if (ui) return ImageManager.getUserAvatar(ui.id); - } - // 从群聊头像中查找图片 - if (/^group_avatar[::]/.test(id)) { - const gi = await this.findGroupInfo(ctx, id.replace(/^group_avatar[::]/, '')); - if (gi) return ImageManager.getGroupAvatar(gi.id); - } - - // 从上下文中查找图片 - const messages = this.messages; - const userSet = new Set(); - for (let i = messages.length - 1; i >= 0; i--) { - const image = messages[i].images.find(item => item.id === id); - if (image) return image; - - const uid = messages[i].uid; - if (userSet.has(uid) || messages[i].role !== 'user') continue; - const name = messages[i].name; - if (name.startsWith('_')) continue; - - const image2 = AIManager.getAI(uid).memory.findImage(id); - if (image2) return image2; - } - - if (!ctx.isPrivate) { - const image = AIManager.getAI(ctx.group.groupId).memory.findImage(id); - if (image) return image; - } - - // 从自己记忆中查找图片 - const image = AIManager.getAI(ctx.endPoint.userId).memory.findImage(id); - if (image) return image; - - // 从本地图片库中查找图片 - const { localImagePathMap } = ConfigManager.image; - if (localImagePathMap.hasOwnProperty(id)) { - const image = new Image(); - image.file = localImagePathMap[id]; - return image; - } - - logger.warning(`未找到图片<${id}>`); - return null; - } - get userInfoList(): UserInfo[] { const userMap: { [key: string]: UserInfo } = {}; this.messages.forEach(message => { @@ -406,54 +380,4 @@ export class Context { }); return Object.values(userMap); } - - async setName(epId: string, gid: string, uid: string, mod: 'nickname' | 'card') { - let name = ''; - switch (mod) { - case 'nickname': { - const strangerInfo = await getStrangerInfo(epId, uid.replace(/^.+:/, '')); - if (!strangerInfo || !strangerInfo.nickname) { - logger.warning(`未找到用户<${uid}>的昵称`); - break; - } - name = strangerInfo.nickname; - break; - } - case 'card': { - if (!gid) break; - const memberInfo = await getGroupMemberInfo(epId, gid.replace(/^.+:/, ''), uid.replace(/^.+:/, '')); - if (!memberInfo) { - logger.warning(`获取用户<${uid}>的群成员信息失败,尝试使用昵称`); - this.setName(epId, gid, uid, 'nickname'); - break; - } - name = memberInfo.card || memberInfo.nickname; - if (!name) { - this.setName(epId, gid, uid, 'nickname'); - return; - } - break; - } - } - if (!name) { - logger.warning(`用户<${uid}>未设置昵称或群名片`); - return; - } - const { ctx } = getCtxAndMsg(epId, uid, gid); - ctx.player.name = name; - this.messages.forEach(message => message.name = message.uid === uid ? name : message.name); - } - - async updateName(epId: string, gid: string, uid: string) { - switch (this.autoNameMod) { - case 1: { - await this.setName(epId, gid, uid, 'nickname'); - break; - } - case 2: { - await this.setName(epId, gid, uid, 'card'); - break; - } - } - } } diff --git a/src/session/memory.ts b/src/session/memory.ts index cdfbd1c..6cf7837 100644 --- a/src/session/memory.ts +++ b/src/session/memory.ts @@ -3,9 +3,9 @@ import { Context } from "./context"; import { cosineSimilarity, generateId, getCommonGroup, getCommonKeyword, getCommonUser, revive, TypeDescriptor } from "../utils/utils"; import { logger } from "../logger"; import { fetchData, getEmbedding } from "../agent/service"; -import { buildContent, getRoleSetting, parseBody } from "../utils/utils_message"; +import { buildContent, getRoleSetting, parseBody } from "../utils/message"; import { ToolManager } from "../tool/tool"; -import { fmtDate } from "../utils/utils_string"; +import { fmtDate } from "../utils/string"; import { Image } from "../image/image"; export interface searchOptions { diff --git a/src/timer.ts b/src/timer.ts index 791683d..787c646 100644 --- a/src/timer.ts +++ b/src/timer.ts @@ -1,8 +1,8 @@ import { ConfigManager } from "./config/configManager"; -import { getSessionCtxAndMsg } from "./utils/utils_seal"; +import { getSessionCtxAndMsg } from "./utils/seal"; import { AI, AIManager } from "./AI/AI"; import { logger } from "./logger"; -import { fmtDate } from "./utils/utils_string"; +import { fmtDate } from "./utils/string"; import { revive } from "./utils/utils"; export class TimerInfo { diff --git a/src/tool/tool.ts b/src/tool/tool.ts index bc73f1f..e6527e9 100644 --- a/src/tool/tool.ts +++ b/src/tool/tool.ts @@ -1,4 +1,3 @@ -import { AI } from "../AI/AI" import { ConfigManager } from "../config/configManager" import { registerAttr } from "./tool_attr" import { registerBan } from "./tool_ban" @@ -24,7 +23,8 @@ import { registerMeme } from "./tool_meme" import { registerRender } from "./tool_render" import { logger } from "../logger" import { Image } from "../image/image"; -import { fixJsonString } from "../utils/utils_string"; +import { fixJsonString } from "../utils/string"; +import { Agent } from "../agent/agent" export interface ToolInfoString { type: "string"; @@ -115,27 +115,27 @@ export interface ToolCall { } } -export interface CmdInfo { - ext: string, // 使用的扩展名称 - name: string, // 指令名称 - fixedArgs: string[] // 参数 +export interface ExtCmdInfo { + extName: string, // 使用的扩展名称 + cmd: string, // 指令名称 + staticArgs: string[] // 参数 } export class Tool { - info: ToolInfo; - cmdInfo: CmdInfo; // 海豹指令信息 - type: string; // 可使用函数的聊天场景类型:"private" | "group" | "all" - tool_choice: string; // 是否可以继续调用函数:"none" | "auto" | "required" - solve: (ctx: seal.MsgContext, msg: seal.Message, ai: AI, args: { [key: string]: any }) => Promise<{ content: string, images: Image[] }>; + toolInfo: ToolInfo; + ExtCmdInfo: ExtCmdInfo; // 海豹指令信息 + sessionType: 'any' | 'user' | 'group'; // 可使用函数的会话类型 + callBack: boolean; // 是否回调函数 + solve: (ctx: seal.MsgContext, msg: seal.Message, agent: Agent, args: { [key: string]: any }) => Promise<{ content: string, images: Image[] }>; constructor(info: ToolInfo) { - this.info = info; - this.cmdInfo = { - ext: '', - name: '', - fixedArgs: [] + this.toolInfo = info; + this.ExtCmdInfo = { + extName: '', + cmd: '', + staticArgs: [] } - this.type = "all" + this.sessionType = "all" this.tool_choice = 'auto'; this.solve = async (_, __, ___, ____) => ({ content: "函数未实现", images: [] }); @@ -212,10 +212,10 @@ export class ToolManager { * @param cmdArgs * @param args */ - static async extensionSolve(ctx: seal.MsgContext, msg: seal.Message, ai: AI, cmdInfo: CmdInfo, args: string[], kwargs: seal.Kwarg[], at: seal.AtInfo[]): Promise<[string, boolean]> { + static async extensionSolve(ctx: seal.MsgContext, msg: seal.Message, ai: AI, cmdInfo: ExtCmdInfo, args: string[], kwargs: seal.Kwarg[], at: seal.AtInfo[]): Promise<[string, boolean]> { const cmdArgs = this.cmdArgs; - cmdArgs.command = cmdInfo.name; - cmdArgs.args = cmdInfo.fixedArgs.concat(args); + cmdArgs.command = cmdInfo.cmd; + cmdArgs.args = cmdInfo.staticArgs.concat(args); cmdArgs.kwargs = kwargs; cmdArgs.at = at; cmdArgs.rawArgs = `${cmdArgs.args.join(' ')} ${kwargs.map(item => `--${item.name}${item.valueExists ? `=${item.value}` : ``}`).join(' ')}`; @@ -225,9 +225,9 @@ export class ToolManager { cmdArgs.specialExecuteTimes = 0; cmdArgs.rawText = `.${cmdArgs.command} ${cmdArgs.rawArgs} ${at.map(item => `[CQ:at,qq=${item.userId.replace(/^.+:/, '')}]`).join(' ')}`; - const ext = seal.ext.find(cmdInfo.ext); - if (!ext.cmdMap.hasOwnProperty(cmdInfo.name)) { - logger.warning(`扩展${cmdInfo.ext}中未找到指令:${cmdInfo.name}`); + const ext = seal.ext.find(cmdInfo.extName); + if (!ext.cmdMap.hasOwnProperty(cmdInfo.cmd)) { + logger.warning(`扩展${cmdInfo.extName}中未找到指令:${cmdInfo.cmd}`); return ['', false]; } @@ -253,7 +253,7 @@ export class ToolManager { }; try { - ext.cmdMap[cmdInfo.name].solve(ctx, msg, cmdArgs); + ext.cmdMap[cmdInfo.cmd].solve(ctx, msg, cmdArgs); } catch (err) { reject(new Error(`solve中发生错误:${err.message}`)); ai.tool.listen.cleanup(); @@ -339,14 +339,14 @@ export class ToolManager { const tool = this.toolMap[name]; - if (tool.cmdInfo.ext !== '' && this.cmdArgs == null) { + if (tool.ExtCmdInfo.extName !== '' && this.cmdArgs == null) { logger.warning(`暂时无法调用函数,请先使用 .r 指令`); await ai.context.addToolMessage(tool_call.id, `暂时无法调用函数,请先提示用户使用 .r 指令`, []); return "none"; } - if (tool.type !== "all" && tool.type !== msg.messageType) { - logger.warning(`调用函数失败:函数${name}可使用的场景类型为${tool.type},当前场景类型为${msg.messageType}`); - await ai.context.addToolMessage(tool_call.id, `调用函数失败:函数${name}可使用的场景类型为${tool.type},当前场景类型为${msg.messageType}`, []); + if (tool.sessionType !== "all" && tool.sessionType !== msg.messageType) { + logger.warning(`调用函数失败:函数${name}可使用的场景类型为${tool.sessionType},当前场景类型为${msg.messageType}`); + await ai.context.addToolMessage(tool_call.id, `调用函数失败:函数${name}可使用的场景类型为${tool.sessionType},当前场景类型为${msg.messageType}`, []); return "none"; } @@ -376,7 +376,7 @@ export class ToolManager { await ai.context.addToolMessage(tool_call.id, `调用函数失败:arguement不是一个object`, []); return "auto"; } - for (const key of tool.info.function.parameters.required) { + for (const key of tool.toolInfo.function.parameters.required) { if (!args.hasOwnProperty(key)) { logger.warning(`调用函数失败:缺少必需参数 ${key}`); await ai.context.addToolMessage(tool_call.id, `调用函数失败:缺少必需参数 ${key}`, []); @@ -453,14 +453,14 @@ export class ToolManager { const tool = this.toolMap[name]; - if (tool.cmdInfo.ext !== '' && this.cmdArgs == null) { + if (tool.ExtCmdInfo.extName !== '' && this.cmdArgs == null) { logger.warning(`暂时无法调用函数,请先使用 .r 指令`); await ai.context.addSystemUserMessage('调用函数返回', `暂时无法调用函数,请先提示用户使用 .r 指令`, []); return; } - if (tool.type !== "all" && tool.type !== msg.messageType) { - logger.warning(`调用函数失败:函数${name}可使用的场景类型为${tool.type},当前场景类型为${msg.messageType}`); - await ai.context.addSystemUserMessage('调用函数返回', `调用函数失败:函数${name}可使用的场景类型为${tool.type},当前场景类型为${msg.messageType}`, []); + if (tool.sessionType !== "all" && tool.sessionType !== msg.messageType) { + logger.warning(`调用函数失败:函数${name}可使用的场景类型为${tool.sessionType},当前场景类型为${msg.messageType}`); + await ai.context.addSystemUserMessage('调用函数返回', `调用函数失败:函数${name}可使用的场景类型为${tool.sessionType},当前场景类型为${msg.messageType}`, []); return; } @@ -471,7 +471,7 @@ export class ToolManager { await ai.context.addSystemUserMessage('调用函数返回', `调用函数失败:arguement不是一个object`, []); return; } - for (const key of tool.info.function.parameters.required) { + for (const key of tool.toolInfo.function.parameters.required) { if (!args.hasOwnProperty(key)) { logger.warning(`调用函数失败:缺少必需参数 ${key}`); await ai.context.addSystemUserMessage('调用函数返回', `调用函数失败:缺少必需参数 ${key}`, []); @@ -515,10 +515,10 @@ export class ToolManager { return null; } const tool = ToolManager.toolMap[key]; - if (tool.type !== "all" && tool.type !== type) { + if (tool.sessionType !== "all" && tool.sessionType !== type) { return null; } - return tool.info; + return tool.toolInfo; } else { return null; } diff --git a/src/tool/sample.ts b/src/tool/tools/sample.ts similarity index 100% rename from src/tool/sample.ts rename to src/tool/tools/sample.ts diff --git a/src/tool/tool_attr.ts b/src/tool/tools/tool_attr.ts similarity index 96% rename from src/tool/tool_attr.ts rename to src/tool/tools/tool_attr.ts index db50000..2e07f29 100644 --- a/src/tool/tool_attr.ts +++ b/src/tool/tools/tool_attr.ts @@ -20,10 +20,10 @@ export function registerAttr() { } } }); - toolShow.cmdInfo = { - ext: 'coc7', - name: 'st', - fixedArgs: ['show'] + toolShow.ExtCmdInfo = { + extName: 'coc7', + cmd: 'st', + staticArgs: ['show'] } toolShow.solve = async (ctx, msg, ai, args) => { const { name } = args; @@ -33,7 +33,7 @@ export function registerAttr() { ({ ctx, msg } = getCtxAndMsg(ctx.endPoint.userId, ui.id, ctx.group.groupId)); - const [s, success] = await ToolManager.extensionSolve(ctx, msg, ai, toolShow.cmdInfo, [], [], []); + const [s, success] = await ToolManager.extensionSolve(ctx, msg, ai, toolShow.ExtCmdInfo, [], [], []); if (!success) return { content: '展示失败', images: [] }; return { content: s, images: [] }; diff --git a/src/tool/tool_ban.ts b/src/tool/tools/tool_ban.ts similarity index 97% rename from src/tool/tool_ban.ts rename to src/tool/tools/tool_ban.ts index 10c846f..8b91651 100644 --- a/src/tool/tool_ban.ts +++ b/src/tool/tools/tool_ban.ts @@ -25,7 +25,7 @@ export function registerBan() { } } }); - toolBan.type = 'group'; + toolBan.sessionType = 'group'; toolBan.solve = async (ctx, _, ai, args) => { const { name, duration } = args; @@ -65,7 +65,7 @@ export function registerBan() { } } }); - toolWhole.type = 'group'; + toolWhole.sessionType = 'group'; toolWhole.solve = async (ctx, _, __, args) => { const { enable } = args; @@ -91,7 +91,7 @@ export function registerBan() { } } }); - toolList.type = 'group'; + toolList.sessionType = 'group'; toolList.solve = async (ctx, _, __, ___) => { if (!netExists()) return { content: `未找到ob11网络连接依赖,请提示用户安装`, images: [] }; diff --git a/src/tool/tool_context.ts b/src/tool/tools/tool_context.ts similarity index 100% rename from src/tool/tool_context.ts rename to src/tool/tools/tool_context.ts diff --git a/src/tool/tool_deck.ts b/src/tool/tools/tool_deck.ts similarity index 100% rename from src/tool/tool_deck.ts rename to src/tool/tools/tool_deck.ts diff --git a/src/tool/tool_essence_msg.ts b/src/tool/tools/tool_essence_msg.ts similarity index 100% rename from src/tool/tool_essence_msg.ts rename to src/tool/tools/tool_essence_msg.ts diff --git a/src/tool/tool_group_sign.ts b/src/tool/tools/tool_group_sign.ts similarity index 96% rename from src/tool/tool_group_sign.ts rename to src/tool/tools/tool_group_sign.ts index 046b26a..2b1f679 100644 --- a/src/tool/tool_group_sign.ts +++ b/src/tool/tools/tool_group_sign.ts @@ -15,7 +15,7 @@ export function registerGroupSign() { } } }); - tool.type = 'group'; + tool.sessionType = 'group'; tool.solve = async (ctx, _, __, ___) => { if (ctx.isPrivate) { return { content: `群打卡只能在群聊中使用`, images: [] }; diff --git a/src/tool/tool_image.ts b/src/tool/tools/tool_image.ts similarity index 100% rename from src/tool/tool_image.ts rename to src/tool/tools/tool_image.ts diff --git a/src/tool/tool_jrrp.ts b/src/tool/tools/tool_jrrp.ts similarity index 90% rename from src/tool/tool_jrrp.ts rename to src/tool/tools/tool_jrrp.ts index cb614f2..99fe04e 100644 --- a/src/tool/tool_jrrp.ts +++ b/src/tool/tools/tool_jrrp.ts @@ -20,10 +20,10 @@ export function registerJrrp() { } } }); - tool.cmdInfo = { - ext: 'fun', - name: 'jrrp', - fixedArgs: [] + tool.ExtCmdInfo = { + extName: 'fun', + cmd: 'jrrp', + staticArgs: [] } tool.solve = async (ctx, msg, ai, args) => { const { name } = args; @@ -32,7 +32,7 @@ export function registerJrrp() { if (ui === null) return { content: `未找到<${name}>`, images: [] }; ({ ctx, msg } = getCtxAndMsg(ctx.endPoint.userId, ui.id, ctx.group.groupId)); - const [s, success] = await ToolManager.extensionSolve(ctx, msg, ai, tool.cmdInfo, [], [], []); + const [s, success] = await ToolManager.extensionSolve(ctx, msg, ai, tool.ExtCmdInfo, [], [], []); if (!success) return { content: '今日人品查询失败', images: [] }; return { content: s, images: [] }; diff --git a/src/tool/tool_meme.ts b/src/tool/tools/tool_meme.ts similarity index 97% rename from src/tool/tool_meme.ts rename to src/tool/tools/tool_meme.ts index 376070b..0ffc849 100644 --- a/src/tool/tool_meme.ts +++ b/src/tool/tools/tool_meme.ts @@ -1,235 +1,235 @@ -import { AIManager, GroupInfo, UserInfo } from "../AI/AI"; -import { Image, ImageManager } from "../image/image"; -import { ConfigManager } from "../config/configManager"; -import { logger } from "../logger"; -import { generateId } from "../utils/utils"; -import { Tool } from "./tool"; - -const baseurl = "http://meme.lovesealdice.online/"; - -interface MemeInfo { - params_type: { - min_texts: number, - max_texts: number, - min_images: number, - max_images: number, - } -} - -async function getInfo(name: string): Promise<{ key: string, info: MemeInfo }> { - try { - const res1 = await fetch(baseurl + name + "/key"); - const json1 = await res1.json(); - const key = json1.result; - const res2 = await fetch(baseurl + key + "/info"); - const json2 = await res2.json(); - return { key, info: json2 }; - } catch (err) { - throw new Error("获取表情包信息失败"); - } -} - -export function registerMeme() { - const toolList = new Tool({ - type: "function", - function: { - name: "meme_list", - description: `访问可用表情包列表`, - parameters: { - type: "object", - properties: { - }, - required: [] - } - } - }); - toolList.solve = async (_, __, ___, ____) => { - try { - const res = await fetch(baseurl + "get_command"); - const json = await res.json(); - return { content: json.map((item: string[]) => item[0]).join("、"), images: [] }; - } catch (err) { - return { content: "获取表情包列表失败:" + err.message, images: [] }; - } - } - - const toolGet = new Tool({ - type: "function", - function: { - name: "get_meme_info", - description: `获取表情包制作信息`, - parameters: { - type: "object", - properties: { - name: { - type: "string", - description: "表情包名字,为 meme_list 返回的结果" - } - }, - required: ["name"] - } - } - }); - toolGet.solve = async (_, __, ___, args) => { - const { name } = args; - - const { info } = await getInfo(name); - const { max_images, max_texts, min_images, min_texts } = info.params_type; - const image_text = min_images === max_images ? `用户数量为 ${min_images} 名` : `用户数量范围为 ${min_images} - ${max_images} 名`; - const text_text = min_texts === max_texts ? `文字数量为 ${min_texts} 段` : `文字数量范围为 ${min_texts} - ${max_texts} 段`; - - return { content: `该表情包需要:${image_text},${text_text}`, images: [] }; - } - - const toolGenerator = new Tool({ - type: "function", - function: { - name: "meme_generator", - description: `制作表情包,使用之前需要调用meme_list获取可用表情包列表,调用get_meme_info获取制作信息`, - parameters: { - type: "object", - properties: { - name: { - type: "string", - description: "表情包名字,为 meme_list 返回的结果" - }, - text: { - type: "array", - items: { type: "string" }, - description: "文字信息,不能插入图片" - }, - image_ids: { - type: "array", - items: { type: "string" }, - description: `图片id,或user_avatar:用户名称` + (ConfigManager.message.showNumber ? '或纯数字QQ号' : '') + `,或group_avatar:群聊名称` + (ConfigManager.message.showNumber ? '或纯数字群号' : '') - }, - save: { - type: "boolean", - description: "是否保存图片" - } - }, - required: ["name", "text", "image_ids", "save"] - } - } - }); - toolGenerator.solve = async (ctx, _, ai, args) => { - const { name, text = [], image_ids = [], save } = args; - - // 切换到当前会话ai - if (!ctx.isPrivate) ai = AIManager.getAI(ctx.group.groupId); - - let s = ''; - - const { key, info } = await getInfo(name); - const { max_images, max_texts, min_images, min_texts } = info.params_type; - const image_text = min_images === max_images ? `用户数量为 ${min_images} 名` : `用户数量范围为 ${min_images} - ${max_images} 名`; - const text_text = min_texts === max_texts ? `文字数量为 ${min_texts} 段` : `文字数量范围为 ${min_texts} - ${max_texts} 段`; - if (text.length > max_texts || text.length < min_texts) { - if (max_texts === 0) { - text.length = 0; - s += `该表情包不需要文字信息,已舍弃。`; - } else { - return { content: `文字数量错误,${text_text},${image_text}`, images: [] }; - } - } - if (image_ids.length > max_images || image_ids.length < min_images) { - if (max_images === 0) { - image_ids.length = 0; - s += `该表情包不需要图片,已舍弃。`; - } else { - return { content: `图片数量错误,${image_text},${text_text}`, images: [] }; - } - } - - const images: Image[] = [] - const uiList: UserInfo[] = []; - const giList: GroupInfo[] = []; - for (const id of image_ids) { - if (/^user_avatar[::]/.test(id)) { - const ui = await this.findUserInfo(ctx, id.replace(/^user_avatar[::]/, '')); - if (ui) { - uiList.push(ui); - images.push(ImageManager.getUserAvatar(ui.id)); - } else { - return { content: `用户 ${id} 不存在`, images: [] }; - } - continue; - } - if (/^group_avatar[::]/.test(id)) { - const gi = await this.findGroupInfo(ctx, id.replace(/^group_avatar[::]/, '')); - if (gi) { - giList.push(gi); - images.push(ImageManager.getGroupAvatar(gi.id)); - } else { - return { content: `群聊 ${id} 不存在`, images: [] }; - } - continue; - } - const img = await ai.context.findImage(ctx, id); - if (img) { - if (img.type === 'url') images.push(img); - else return { content: `图片 ${id} 类型错误,仅支持url类型`, images: [] }; - } else { - return { content: `图片 ${id} 不存在`, images: [] }; - } - } - - const kws = ["meme", name, ...text, ...image_ids]; - - // 图片存在则直接返回 - const result = ai.memory.findMemoryAndImageByImageIdPrefix(name); - if (result) { - const { memory, image } = result; - if (memory.keywords.every((v, i) => v === kws[i]) && memory.images.slice(1).every((v, i) => v.id === images[i].imageId)) { - return { content: `${s}生成成功,请使用<|img:${image.id}|>发送`, images: [image] }; - } - } - - try { - const res = await fetch(baseurl + "meme_generate", { - method: "POST", - body: JSON.stringify({ - key, - text, - image: images.map(img => img.file), - args: {} - }), - }); - - const json = await res.json(); - if (json.status == "success") { - const base64 = json.message; - if (!base64) { - logger.error(`生成的base64为空`); - return { content: "生成的base64为空", images: [] }; - } - - const textText = text.join(';'); - const imageText = image_ids.join(';'); - - const img = new Image(); - img.imageId = `${name}_${generateId()}`; - img.base64 = base64; - img.format = 'unknown'; - img.description = `表情包<|img:${img.imageId}|> -${textText ? `文字:${textText}` : ''} -${imageText ? `图片:${imageText}` : ''}`; - - if (save) ai.memory.addMemory(ctx, ai, uiList, giList, kws, [img, ...images], img.description); - - return { content: `${s}生成成功,请使用<|img:${img.imageId}|>发送`, images: [img] }; - } else { - throw new Error(json.message); - } - } catch (err) { - return { content: "生成表情包失败:" + err.message, images: [] }; - } - } -} - -// 说实话感觉并不是最完美的状态 -// 感觉应该先把meme_list和meme_info本地化 -// 然后给出一个选择meme模板的模板配置项,毕竟有的人设并不适合所有的表情包 -// 再把选中的meme模板构建prompt,另外我注意到有的模板应该是有默认文本的,这其实也可以提示ai要输入什么文本,而不是牛头不对马嘴 -// 这样只需保留meme_generator的实现 +import { AIManager, GroupInfo, UserInfo } from "../AI/AI"; +import { Image, ImageManager } from "../image/image"; +import { ConfigManager } from "../config/configManager"; +import { logger } from "../logger"; +import { generateId } from "../utils/utils"; +import { Tool } from "./tool"; + +const baseurl = "http://meme.lovesealdice.online/"; + +interface MemeInfo { + params_type: { + min_texts: number, + max_texts: number, + min_images: number, + max_images: number, + } +} + +async function getInfo(name: string): Promise<{ key: string, info: MemeInfo }> { + try { + const res1 = await fetch(baseurl + name + "/key"); + const json1 = await res1.json(); + const key = json1.result; + const res2 = await fetch(baseurl + key + "/info"); + const json2 = await res2.json(); + return { key, info: json2 }; + } catch (err) { + throw new Error("获取表情包信息失败"); + } +} + +export function registerMeme() { + const toolList = new Tool({ + type: "function", + function: { + name: "meme_list", + description: `访问可用表情包列表`, + parameters: { + type: "object", + properties: { + }, + required: [] + } + } + }); + toolList.solve = async (_, __, ___, ____) => { + try { + const res = await fetch(baseurl + "get_command"); + const json = await res.json(); + return { content: json.map((item: string[]) => item[0]).join("、"), images: [] }; + } catch (err) { + return { content: "获取表情包列表失败:" + err.message, images: [] }; + } + } + + const toolGet = new Tool({ + type: "function", + function: { + name: "get_meme_info", + description: `获取表情包制作信息`, + parameters: { + type: "object", + properties: { + name: { + type: "string", + description: "表情包名字,为 meme_list 返回的结果" + } + }, + required: ["name"] + } + } + }); + toolGet.solve = async (_, __, ___, args) => { + const { name } = args; + + const { info } = await getInfo(name); + const { max_images, max_texts, min_images, min_texts } = info.params_type; + const image_text = min_images === max_images ? `用户数量为 ${min_images} 名` : `用户数量范围为 ${min_images} - ${max_images} 名`; + const text_text = min_texts === max_texts ? `文字数量为 ${min_texts} 段` : `文字数量范围为 ${min_texts} - ${max_texts} 段`; + + return { content: `该表情包需要:${image_text},${text_text}`, images: [] }; + } + + const toolGenerator = new Tool({ + type: "function", + function: { + name: "meme_generator", + description: `制作表情包,使用之前需要调用meme_list获取可用表情包列表,调用get_meme_info获取制作信息`, + parameters: { + type: "object", + properties: { + name: { + type: "string", + description: "表情包名字,为 meme_list 返回的结果" + }, + text: { + type: "array", + items: { type: "string" }, + description: "文字信息,不能插入图片" + }, + image_ids: { + type: "array", + items: { type: "string" }, + description: `图片id,或user_avatar:用户名称` + (ConfigManager.message.showNumber ? '或纯数字QQ号' : '') + `,或group_avatar:群聊名称` + (ConfigManager.message.showNumber ? '或纯数字群号' : '') + }, + save: { + type: "boolean", + description: "是否保存图片" + } + }, + required: ["name", "text", "image_ids", "save"] + } + } + }); + toolGenerator.solve = async (ctx, _, ai, args) => { + const { name, text = [], image_ids = [], save } = args; + + // 切换到当前会话ai + if (!ctx.isPrivate) ai = AIManager.getAI(ctx.group.groupId); + + let s = ''; + + const { key, info } = await getInfo(name); + const { max_images, max_texts, min_images, min_texts } = info.params_type; + const image_text = min_images === max_images ? `用户数量为 ${min_images} 名` : `用户数量范围为 ${min_images} - ${max_images} 名`; + const text_text = min_texts === max_texts ? `文字数量为 ${min_texts} 段` : `文字数量范围为 ${min_texts} - ${max_texts} 段`; + if (text.length > max_texts || text.length < min_texts) { + if (max_texts === 0) { + text.length = 0; + s += `该表情包不需要文字信息,已舍弃。`; + } else { + return { content: `文字数量错误,${text_text},${image_text}`, images: [] }; + } + } + if (image_ids.length > max_images || image_ids.length < min_images) { + if (max_images === 0) { + image_ids.length = 0; + s += `该表情包不需要图片,已舍弃。`; + } else { + return { content: `图片数量错误,${image_text},${text_text}`, images: [] }; + } + } + + const images: Image[] = [] + const uiList: UserInfo[] = []; + const giList: GroupInfo[] = []; + for (const id of image_ids) { + if (/^user_avatar[::]/.test(id)) { + const ui = await this.findUserInfo(ctx, id.replace(/^user_avatar[::]/, '')); + if (ui) { + uiList.push(ui); + images.push(ImageManager.getUserAvatar(ui.id)); + } else { + return { content: `用户 ${id} 不存在`, images: [] }; + } + continue; + } + if (/^group_avatar[::]/.test(id)) { + const gi = await this.findGroupInfo(ctx, id.replace(/^group_avatar[::]/, '')); + if (gi) { + giList.push(gi); + images.push(ImageManager.getGroupAvatar(gi.id)); + } else { + return { content: `群聊 ${id} 不存在`, images: [] }; + } + continue; + } + const img = await ai.context.findImage(ctx, id); + if (img) { + if (img.type === 'url') images.push(img); + else return { content: `图片 ${id} 类型错误,仅支持url类型`, images: [] }; + } else { + return { content: `图片 ${id} 不存在`, images: [] }; + } + } + + const kws = ["meme", name, ...text, ...image_ids]; + + // 图片存在则直接返回 + const result = ai.memory.findMemoryAndImageByImageIdPrefix(name); + if (result) { + const { memory, image } = result; + if (memory.keywords.every((v, i) => v === kws[i]) && memory.images.slice(1).every((v, i) => v.id === images[i].imageId)) { + return { content: `${s}生成成功,请使用<|img:${image.id}|>发送`, images: [image] }; + } + } + + try { + const res = await fetch(baseurl + "meme_generate", { + method: "POST", + body: JSON.stringify({ + key, + text, + image: images.map(img => img.file), + args: {} + }), + }); + + const json = await res.json(); + if (json.status == "success") { + const base64 = json.message; + if (!base64) { + logger.error(`生成的base64为空`); + return { content: "生成的base64为空", images: [] }; + } + + const textText = text.join(';'); + const imageText = image_ids.join(';'); + + const img = new Image(); + img.imageId = `${name}_${generateId()}`; + img.base64 = base64; + img.format = 'unknown'; + img.description = `表情包<|img:${img.imageId}|> +${textText ? `文字:${textText}` : ''} +${imageText ? `图片:${imageText}` : ''}`; + + if (save) ai.memory.addMemory(ctx, ai, uiList, giList, kws, [img, ...images], img.description); + + return { content: `${s}生成成功,请使用<|img:${img.imageId}|>发送`, images: [img] }; + } else { + throw new Error(json.message); + } + } catch (err) { + return { content: "生成表情包失败:" + err.message, images: [] }; + } + } +} + +// 说实话感觉并不是最完美的状态 +// 感觉应该先把meme_list和meme_info本地化 +// 然后给出一个选择meme模板的模板配置项,毕竟有的人设并不适合所有的表情包 +// 再把选中的meme模板构建prompt,另外我注意到有的模板应该是有默认文本的,这其实也可以提示ai要输入什么文本,而不是牛头不对马嘴 +// 这样只需保留meme_generator的实现 // 另外可以把url加进后端配置中,这个的后端是哪个项目啊———— \ No newline at end of file diff --git a/src/tool/tool_memory.ts b/src/tool/tools/tool_memory.ts similarity index 100% rename from src/tool/tool_memory.ts rename to src/tool/tools/tool_memory.ts diff --git a/src/tool/tool_message.ts b/src/tool/tools/tool_message.ts similarity index 99% rename from src/tool/tool_message.ts rename to src/tool/tools/tool_message.ts index 4cf4c99..f447c65 100644 --- a/src/tool/tool_message.ts +++ b/src/tool/tools/tool_message.ts @@ -4,7 +4,7 @@ import { replyToSender, transformMsgIdBack } from "../utils/utils"; import { getCtxAndMsg } from "../utils/utils_seal"; import { handleReply, MessageSegment, parseSpecialTokens, transformArrayToContent } from "../utils/utils_string"; import { Tool, ToolManager } from "./tool"; -import { CQTYPESALLOW, faceMap } from "../config/config"; +import { CQTYPESALLOW, faceMap } from "../config/static_config"; import { deleteMsg, getGroupMemberInfo, getMsg, sendGroupForwardMsg, sendPrivateForwardMsg, netExists } from "../utils/utils_ob11"; import { logger } from "../logger"; import { Image } from "../image/image"; diff --git a/src/tool/tool_modu.ts b/src/tool/tools/tool_modu.ts similarity index 82% rename from src/tool/tool_modu.ts rename to src/tool/tools/tool_modu.ts index 0ed9e39..47b2c6e 100644 --- a/src/tool/tool_modu.ts +++ b/src/tool/tools/tool_modu.ts @@ -13,13 +13,13 @@ export function registerModu() { } } }); - toolRoll.cmdInfo = { - ext: 'story', - name: 'modu', - fixedArgs: ['roll'] + toolRoll.ExtCmdInfo = { + extName: 'story', + cmd: 'modu', + staticArgs: ['roll'] } toolRoll.solve = async (ctx, msg, ai, _) => { - const [s, success] = await ToolManager.extensionSolve(ctx, msg, ai, toolRoll.cmdInfo, [], [], []); + const [s, success] = await ToolManager.extensionSolve(ctx, msg, ai, toolRoll.ExtCmdInfo, [], [], []); if (!success) { return { content: '今日人品查询失败', images: [] }; } @@ -44,15 +44,15 @@ export function registerModu() { } } }); - toolSearch.cmdInfo = { - ext: 'story', - name: 'modu', - fixedArgs: ['search'] + toolSearch.ExtCmdInfo = { + extName: 'story', + cmd: 'modu', + staticArgs: ['search'] } toolSearch.solve = async (ctx, msg, ai, args) => { const { name } = args; - const [s, success] = await ToolManager.extensionSolve(ctx, msg, ai, toolSearch.cmdInfo, [name], [], []); + const [s, success] = await ToolManager.extensionSolve(ctx, msg, ai, toolSearch.ExtCmdInfo, [name], [], []); if (!success) { return { content: '今日人品查询失败', images: [] }; } diff --git a/src/tool/tool_music.ts b/src/tool/tools/tool_music.ts similarity index 100% rename from src/tool/tool_music.ts rename to src/tool/tools/tool_music.ts diff --git a/src/tool/tool_person_info.ts b/src/tool/tools/tool_person_info.ts similarity index 100% rename from src/tool/tool_person_info.ts rename to src/tool/tools/tool_person_info.ts diff --git a/src/tool/tool_qq_list.ts b/src/tool/tools/tool_qq_list.ts similarity index 100% rename from src/tool/tool_qq_list.ts rename to src/tool/tools/tool_qq_list.ts diff --git a/src/tool/tool_rename.ts b/src/tool/tools/tool_rename.ts similarity index 98% rename from src/tool/tool_rename.ts rename to src/tool/tools/tool_rename.ts index 18a1fb7..7ed0d55 100644 --- a/src/tool/tool_rename.ts +++ b/src/tool/tools/tool_rename.ts @@ -26,7 +26,7 @@ export function registerRename() { } } }); - tool.type = 'group'; + tool.sessionType = 'group'; tool.solve = async (ctx, msg, ai, args) => { const { name, new_name } = args; diff --git a/src/tool/tool_render.ts b/src/tool/tools/tool_render.ts similarity index 100% rename from src/tool/tool_render.ts rename to src/tool/tools/tool_render.ts diff --git a/src/tool/tool_roll_check.ts b/src/tool/tools/tool_roll_check.ts similarity index 94% rename from src/tool/tool_roll_check.ts rename to src/tool/tools/tool_roll_check.ts index d630cfa..79b96fd 100644 --- a/src/tool/tool_roll_check.ts +++ b/src/tool/tools/tool_roll_check.ts @@ -41,10 +41,10 @@ export function registerRollCheck() { } } }); - toolRoll.cmdInfo = { - ext: 'coc7', - name: 'ra', - fixedArgs: [] + toolRoll.ExtCmdInfo = { + extName: 'coc7', + cmd: 'ra', + staticArgs: [] } toolRoll.solve = async (ctx, msg, ai, args) => { const { name, expression, rank = '', times = 1, additional_dice = '', reason = '' } = args; @@ -68,7 +68,7 @@ export function registerRollCheck() { if (parseInt(times) !== 1 && !isNaN(parseInt(times))) ToolManager.cmdArgs.specialExecuteTimes = parseInt(times); - const [s, success] = await ToolManager.extensionSolve(ctx, msg, ai, toolRoll.cmdInfo, args2, [], []); + const [s, success] = await ToolManager.extensionSolve(ctx, msg, ai, toolRoll.ExtCmdInfo, args2, [], []); ToolManager.cmdArgs.specialExecuteTimes = 1; if (!success) return { content: '检定执行失败', images: [] }; return { content: s, images: [] }; @@ -103,10 +103,10 @@ export function registerRollCheck() { } } }) - tool.cmdInfo = { - ext: 'coc7', - name: 'sc', - fixedArgs: [] + tool.ExtCmdInfo = { + extName: 'coc7', + cmd: 'sc', + staticArgs: [] } tool.solve = async (ctx, msg, ai, args) => { const { name, expression, additional_dice } = args; @@ -123,7 +123,7 @@ export function registerRollCheck() { if (additional_dice) args2.push(additional_dice); args2.push(expression); - const [s, success] = await ToolManager.extensionSolve(ctx, msg, ai, tool.cmdInfo, args2, [], []); + const [s, success] = await ToolManager.extensionSolve(ctx, msg, ai, tool.ExtCmdInfo, args2, [], []); if (!success) return { content: 'san check执行失败', images: [] }; return { content: s, images: [] }; } diff --git a/src/tool/tool_time.ts b/src/tool/tools/tool_time.ts similarity index 100% rename from src/tool/tool_time.ts rename to src/tool/tools/tool_time.ts diff --git a/src/tool/tool_trigger.ts b/src/tool/tools/tool_trigger.ts similarity index 100% rename from src/tool/tool_trigger.ts rename to src/tool/tools/tool_trigger.ts diff --git a/src/tool/tool_voice.ts b/src/tool/tools/tool_voice.ts similarity index 100% rename from src/tool/tool_voice.ts rename to src/tool/tools/tool_voice.ts diff --git a/src/tool/tool_web.ts b/src/tool/tools/tool_web.ts similarity index 100% rename from src/tool/tool_web.ts rename to src/tool/tools/tool_web.ts diff --git a/src/utils/utils_message.ts b/src/utils/message.ts similarity index 99% rename from src/utils/utils_message.ts rename to src/utils/message.ts index 22d8ae7..38f9c61 100644 --- a/src/utils/utils_message.ts +++ b/src/utils/message.ts @@ -2,7 +2,7 @@ import { AI, GroupInfo, UserInfo } from "../AI/AI"; import { Message } from "../session/context"; import { ConfigManager } from "../config/configManager"; import { ToolInfo } from "../tool/tool"; -import { fmtDate } from "./utils_string"; +import { fmtDate } from "./string"; import { knowledgeService } from "../session/memory"; export async function buildSystemMessage(ctx: seal.MsgContext, ai: AI): Promise { diff --git a/src/utils/utils_ob11.ts b/src/utils/ob11.ts similarity index 99% rename from src/utils/utils_ob11.ts rename to src/utils/ob11.ts index f2d0e5d..ee58703 100644 --- a/src/utils/utils_ob11.ts +++ b/src/utils/ob11.ts @@ -1,5 +1,5 @@ import { logger } from "../logger"; -import { MessageSegment } from "./utils_string"; +import { MessageSegment } from "./string"; export function getNet() { const net = globalThis.net || globalThis.http; diff --git a/src/utils/utils_seal.ts b/src/utils/seal.ts similarity index 100% rename from src/utils/utils_seal.ts rename to src/utils/seal.ts diff --git a/src/utils/utils_string.ts b/src/utils/string.ts similarity index 99% rename from src/utils/utils_string.ts rename to src/utils/string.ts index d93cbfb..02dbeb2 100644 --- a/src/utils/utils_string.ts +++ b/src/utils/string.ts @@ -4,8 +4,8 @@ import { logger } from "../logger"; import { ConfigManager } from "../config/configManager"; import { transformMsgId, transformMsgIdBack } from "./utils"; import { AI } from "../AI/AI"; -import { getCtxAndMsg } from "./utils_seal"; -import { faceMap } from "../config/config"; +import { getCtxAndMsg } from "./seal"; +import { faceMap } from "../config/static_config"; /* 先丢这一坨东西在这。之所以不用是因为被类型检查整烦了 diff --git a/src/utils/utils_update.ts b/src/utils/update.ts similarity index 97% rename from src/utils/utils_update.ts rename to src/utils/update.ts index f46b4c1..75432ae 100644 --- a/src/utils/utils_update.ts +++ b/src/utils/update.ts @@ -1,7 +1,7 @@ import { logger } from "../logger"; import { updateInfo } from "../update"; import { ConfigManager } from "../config/configManager"; -import { VERSION } from "../config/config"; +import { VERSION } from "../config/static_config"; /** * 比较两个版本号的大小。 diff --git a/src/utils/utils.ts b/src/utils/utils.ts index a774e4a..4f37eb6 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -1,8 +1,8 @@ import { logger } from "../logger"; import { ConfigManager } from "../config/configManager"; -import { transformTextToArray } from "./utils_string"; -import { aliasMap } from "../config/config"; -import { netExists, sendGroupMsg, sendPrivateMsg } from "./utils_ob11"; +import { transformTextToArray } from "./string"; +import { aliasMap } from "../config/static_config"; +import { netExists, sendGroupMsg, sendPrivateMsg } from "./ob11"; export function transformMsgId(msgId: string | number | null): string { if (msgId === null) { From b510fe81bbcac5807ee000780500efc8e5241d92 Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Sat, 7 Mar 2026 21:08:04 +0800 Subject: [PATCH 14/33] =?UTF-8?q?=E7=99=BD=E9=B1=BC=E5=BF=AB=E6=9D=A5?= =?UTF-8?q?=E7=9C=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cmd/privilege.ts | 10 +- src/cmd/sub_cmd/privilege.ts | 26 ++--- src/config/configs/model.ts | 19 ++++ src/config/configs/sample.ts | 4 +- src/config/static_config.ts | 179 +++++++++++++++++++++++++++++++++-- src/index.ts | 8 +- src/note.txt | 4 +- src/utils/string.ts | 6 +- src/utils/utils.ts | 4 +- 9 files changed, 224 insertions(+), 36 deletions(-) create mode 100644 src/config/configs/model.ts diff --git a/src/cmd/privilege.ts b/src/cmd/privilege.ts index bc799c4..931d247 100644 --- a/src/cmd/privilege.ts +++ b/src/cmd/privilege.ts @@ -2,7 +2,7 @@ import { AI } from "../AI/AI"; import { logger } from "../logger"; import { ConfigManager } from "../config/configManager"; import { aliasToCmd } from "../utils/utils"; -import { PRIVILEGELEVELMAP } from "../config/static_config"; +import { PRIVILEGE_LEVEL_MAP } from "../config/static_config"; export interface CmdPrivInfo { @@ -12,10 +12,10 @@ export interface CmdPrivInfo { export interface CmdPriv { [key: string]: CmdPrivInfo }; -export const U: [number, number, number] = [0, PRIVILEGELEVELMAP.user, PRIVILEGELEVELMAP.user]; // user -export const M: [number, number, number] = [0, PRIVILEGELEVELMAP.master, PRIVILEGELEVELMAP.master]; // master -export const I: [number, number, number] = [0, PRIVILEGELEVELMAP.inviter, PRIVILEGELEVELMAP.inviter]; // inviter -export const S: [number, number, number] = [1, PRIVILEGELEVELMAP.inviter, PRIVILEGELEVELMAP.master]; // spesial,会话所需权限为1,是才能被邀请者使用,否则需为骰主 +export const U: [number, number, number] = [0, PRIVILEGE_LEVEL_MAP.user, PRIVILEGE_LEVEL_MAP.user]; // user +export const M: [number, number, number] = [0, PRIVILEGE_LEVEL_MAP.master, PRIVILEGE_LEVEL_MAP.master]; // master +export const I: [number, number, number] = [0, PRIVILEGE_LEVEL_MAP.inviter, PRIVILEGE_LEVEL_MAP.inviter]; // inviter +export const S: [number, number, number] = [1, PRIVILEGE_LEVEL_MAP.inviter, PRIVILEGE_LEVEL_MAP.master]; // spesial,会话所需权限为1,是才能被邀请者使用,否则需为骰主 export const defaultCmdPriv: CmdPriv = { ai: { priv: U } }; diff --git a/src/cmd/sub_cmd/privilege.ts b/src/cmd/sub_cmd/privilege.ts index 3b35c59..0453159 100644 --- a/src/cmd/sub_cmd/privilege.ts +++ b/src/cmd/sub_cmd/privilege.ts @@ -1,5 +1,5 @@ import { AIManager } from "../../AI/AI"; -import { HELPMAP } from "../../config/static_config"; +import { HELP_MAP } from "../../config/static_config"; import { aliasToCmd } from "../../utils/utils"; import { M, PrivilegeManager, U } from "../privilege"; import { SubCmd, SubCmdContext } from "../root_cmd"; @@ -13,10 +13,10 @@ export function registerCmdPrivilege() { 【.ai priv st <指令> <权限限制>】修改指令权限 【.ai priv show <指令>】检查指令权限 【.ai priv reset】重置指令权限 -${HELPMAP["ID"]} -${HELPMAP["会话权限"]} -${HELPMAP["指令"]} -${HELPMAP["权限限制"]}`; +${HELP_MAP["ID"]} +${HELP_MAP["会话权限"]} +${HELP_MAP["指令"]} +${HELP_MAP["权限限制"]}`; cmd.priv = { priv: M, args: { session: { @@ -43,8 +43,8 @@ ${HELPMAP["权限限制"]}`; if (!val4 || val4 == 'help') { seal.replyToSender(ctx, msg, `帮助: 【.ai priv ses st <会话权限>】修改会话权限 -${HELPMAP["ID"]} -${HELPMAP["会话权限"]}`); +${HELP_MAP["ID"]} +${HELP_MAP["会话权限"]}`); return ret; } @@ -69,7 +69,7 @@ ${HELPMAP["会话权限"]}`); if (!val4 || val4 == 'help') { seal.replyToSender(ctx, msg, `帮助: 【.ai priv ses ck 】检查会话权限 -${HELPMAP["ID"]}`); +${HELP_MAP["ID"]}`); return ret; } @@ -82,8 +82,8 @@ ${HELPMAP["ID"]}`); seal.replyToSender(ctx, msg, `帮助: 【.ai priv ses st <会话权限>】修改会话权限 【.ai priv ses ck 】检查会话权限 -${HELPMAP["ID"]} -${HELPMAP["会话权限"]}`); +${HELP_MAP["ID"]} +${HELP_MAP["会话权限"]}`); return ret; } } @@ -93,8 +93,8 @@ ${HELPMAP["会话权限"]}`); if (!val3 || val3 == 'help') { seal.replyToSender(ctx, msg, `帮助: 【.ai priv st <指令> <权限限制>】修改指令权限 -${HELPMAP["指令"]} -${HELPMAP["权限限制"]}`); +${HELP_MAP["指令"]} +${HELP_MAP["权限限制"]}`); return ret; } const cmdChain = val3.split('-').map(cmd => aliasToCmd(cmd)); @@ -129,7 +129,7 @@ ${HELPMAP["权限限制"]}`); if (!val3 || val3 == 'help') { seal.replyToSender(ctx, msg, `帮助: 【.ai priv show <指令>】检查指令权限 -${HELPMAP["指令"]}`); +${HELP_MAP["指令"]}`); return ret; } const cmdChain = val3.split('-'); diff --git a/src/config/configs/model.ts b/src/config/configs/model.ts new file mode 100644 index 0000000..e55d750 --- /dev/null +++ b/src/config/configs/model.ts @@ -0,0 +1,19 @@ +import { ConfigManager } from "../configManager"; + +export class ModelConfig { + static ext: seal.ExtInfo; + + static register() { + ModelConfig.ext = ConfigManager.getExt('aiplugin4:模型'); + + seal.ext.registerTemplateConfig(ModelConfig.ext, "对话模型", [""], ''); + seal.ext.registerTemplateConfig(ModelConfig.ext, "图片模型", [""], ''); + seal.ext.registerTemplateConfig(ModelConfig.ext, "嵌入模型", [""], ''); + } + + static get() { + return { + enabled: seal.ext.getBoolConfig(ModelConfig.ext, "是否启用"), + } + } +} \ No newline at end of file diff --git a/src/config/configs/sample.ts b/src/config/configs/sample.ts index 272b9c5..4236c0e 100644 --- a/src/config/configs/sample.ts +++ b/src/config/configs/sample.ts @@ -1,10 +1,10 @@ -import { ConfigManager } from "./configManager"; +import { ConfigManager } from "../configManager"; export class SampleConfig { static ext: seal.ExtInfo; static register() { - SampleConfig.ext = ConfigManager.getExt('aiplugin4_0:示例'); + SampleConfig.ext = ConfigManager.getExt('aiplugin4:示例'); seal.ext.registerBoolConfig(SampleConfig.ext, "是否启用", true, ''); } diff --git a/src/config/static_config.ts b/src/config/static_config.ts index f3f6c1d..ede743c 100644 --- a/src/config/static_config.ts +++ b/src/config/static_config.ts @@ -2,9 +2,9 @@ export const VERSION = "4.12.0"; export const AUTHOR = "baiyu&错误"; export const NAME = "aiplugin4"; -export const CQTYPESALLOW = ["at", "image", "reply", "face", "poke"]; +export const CQ_TYPES_ALLOW = ["at", "image", "reply", "face", "poke"]; -export const PRIVILEGELEVELMAP = { +export const PRIVILEGE_LEVEL_MAP = { "master": 100, "whitelist": 70, "owner": 60, @@ -14,7 +14,7 @@ export const PRIVILEGELEVELMAP = { "blacklist": -30 } -export const HELPMAP = { +export const HELP_MAP = { "ID": `: 【QQ:1234567890】 私聊窗口 【QQ-Group:1234】 群聊窗口 @@ -41,7 +41,7 @@ export const HELPMAP = { 格式为"开始时间-结束时间-活跃次数"(如"09:00-18:00-5")` } -export const aliasMap = { +export const ALIAS_MAP = { "AI": "ai", "priv": "privilege", "ses": "session", @@ -69,7 +69,7 @@ export const aliasMap = { "nick": "nickname" } -export const faceMap = { +export const FACE_MAP = { "0": "惊讶", "1": "撇嘴", "2": "色", @@ -366,4 +366,171 @@ export const faceMap = { "430": "蛇身", "431": "蛇尾", "432": "灵蛇献瑞" -} \ No newline at end of file +} + +export interface ModelInfo { + provider: string; + model: string[]; + baseUrl: string; +} + +export const CHAT_MODEL_MAP = { + // 海外厂商 + "openai": { + provider: "openai", + model: ["gpt-4o", "gpt-4-turbo", "gpt-4", "gpt-3.5-turbo", "o1-mini", "o1-preview"], + baseUrl: "https://api.openai.com/v1" + }, + "anthropic": { + provider: "anthropic", + model: ["claude-3-5-sonnet-20241022", "claude-3-opus-20240229", "claude-3-sonnet-20240229", "claude-3-haiku-20240307"], + baseUrl: "https://api.anthropic.com/v1" + }, + "google": { + provider: "google", + model: ["gemini-2.5-pro-exp-03-25", "gemini-2.0-flash-exp", "gemini-1.5-pro", "gemini-1.5-flash"], + baseUrl: "https://generativelanguage.googleapis.com/v1beta" // 或使用 Vertex AI 的端点 + }, + "meta": { // 通过特定服务商调用,如Replicate, Together AI,或自托管 + provider: "meta", + model: ["llama-3.1-405b-instruct", "llama-3.1-70b-instruct", "llama-3.1-8b-instruct"], + baseUrl: "https://api.together.xyz/v1" // 示例:使用 Together AI 作为代理 + }, + "mistralai": { + provider: "mistralai", + model: ["mistral-large-latest", "mistral-small-latest", "codestral-latest"], + baseUrl: "https://api.mistral.ai/v1" + }, + "cohere": { + provider: "cohere", + model: ["command-r-plus", "command-r", "command-light"], + baseUrl: "https://api.cohere.ai/v1" + }, + "xai": { + provider: "xai", + model: ["grok-2", "grok-2-mini"], + baseUrl: "https://api.x.ai/v1" // 示例地址,实际需确认 + }, + "deepseek": { // 深度求索,以推理能力见长 [citation:8] + provider: "deepseek", + model: ["deepseek-chat", "deepseek-reasoner"], // 对应 V3 和 R1 系列 + baseUrl: "https://api.deepseek.com/v1" + }, + // 国内厂商 + "alibaba": { + provider: "alibaba", + model: ["qwen-max", "qwen-plus", "qwen-turbo", "qwen2.5-72b-instruct"], + baseUrl: "https://dashscope.aliyuncs.com/compatible-mode/v1" // 通义千问 DashScope 兼容OpenAI的地址 + }, + "baidu": { + provider: "baidu", + model: ["ernie-4.0-turbo-8k", "ernie-3.5-8k", "ernie-lite-8k"], + baseUrl: "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop" // 文心一言 API 地址 + }, + "tencent": { + provider: "tencent", + model: ["hunyuan-pro", "hunyuan-standard", "hunyuan-lite"], + baseUrl: "https://api.hunyuan.cloud.tencent.com/v1" // 示例地址,实际需确认 + }, + "zhipu": { // 智谱AI [citation:8] + provider: "zhipu", + model: ["glm-4-plus", "glm-4-0520", "glm-4-air", "glm-3-turbo"], + baseUrl: "https://open.bigmodel.cn/api/paas/v4" // 智谱AI API 地址 + }, + "minimax": { + provider: "minimax", + model: ["abab6.5s-chat", "abab5.5s-chat"], + baseUrl: "https://api.minimax.chat/v1" // 示例地址 + }, + "moonshot": { // 月之暗面 Kimi [citation:8] + provider: "moonshot", + model: ["moonshot-v1-128k", "moonshot-v1-32k", "moonshot-v1-8k"], + baseUrl: "https://api.moonshot.cn/v1" + } +}; + +export const IMAGE_MODEL_MAP = { + "openai": { + provider: "openai", + model: ["dall-e-3", "dall-e-2", "gpt-image-1.5"], // GPT Image 1.5 是2025年底的新模型 [citation:2] + baseUrl: "https://api.openai.com/v1" + }, + "google": { + provider: "google", + model: ["imagen-3.0-generate-001", "imagen-3.0-fast-001"], // Imagen 3 系列 [citation:2][citation:10] + baseUrl: "https://us-central1-aiplatform.googleapis.com/v1" // Vertex AI 端点 + }, + "stabilityai": { + provider: "stabilityai", + model: ["stable-diffusion-3-5-large", "stable-diffusion-3-5-large-turbo", "stable-diffusion-3-medium"], + baseUrl: "https://api.stability.ai/v2beta" // Stability AI 官方API + }, + "black-forest-labs": { // 黑森林实验室,由前 Stability AI 成员创建,Flux 模型表现优异 [citation:2] + provider: "black-forest-labs", + model: ["flux-1.1-pro", "flux-1-pro", "flux-1-dev"], + baseUrl: "https://api.bfl.ml/v1" // Black Forest Labs 官方API + }, + "ideogram": { + provider: "ideogram", + model: ["ideogram-v2", "ideogram-v2-turbo"], + baseUrl: "https://api.ideogram.ai/v1" + }, + "midjourney": { // Midjourney 通常通过 Discord 调用,或通过第三方API [citation:5][citation:10] + provider: "midjourney", + model: ["midjourney-v7", "midjourney-v6"], + baseUrl: "https://api.midjourney.com/v1" // 官方API,可能需要申请 + }, + "bytedance": { // 字节跳动 [citation:2] + provider: "bytedance", + model: ["seedream-4.5", "seedream-3.0"], + baseUrl: "https://api.bytedance.com/v1" // 示例地址 + }, + "tencent": { + provider: "tencent", + model: ["hunyuan-image-3.0"], + baseUrl: "https://api.hunyuan.cloud.tencent.com/v1" // 示例地址 [citation:2] + } +}; + +export const EMBEDDING_MODEL_MAP = { + "openai": { + provider: "openai", + model: ["text-embedding-3-large", "text-embedding-3-small", "text-embedding-ada-002"], + baseUrl: "https://api.openai.com/v1" + }, + "google": { + provider: "google", + model: ["text-embedding-004", "text-multilingual-embedding-002"], + baseUrl: "https://generativelanguage.googleapis.com/v1beta" // 或 Vertex AI + }, + "cohere": { + provider: "cohere", + model: ["embed-english-v3.0", "embed-multilingual-v3.0"], + baseUrl: "https://api.cohere.ai/v1" + }, + "alibaba": { + provider: "alibaba", + model: ["text-embedding-v3", "text-embedding-v2"], + baseUrl: "https://dashscope.aliyuncs.com/compatible-mode/v1" // DashScope 兼容地址 + }, + "baidu": { + provider: "baidu", + model: ["embedding-v1"], + baseUrl: "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop" // 文心 Embedding API + }, + "zhipu": { + provider: "zhipu", + model: ["embedding-3", "embedding-2"], + baseUrl: "https://open.bigmodel.cn/api/paas/v4" // 智谱AI API + }, + "siliconflow": { // SiliconFlow 提供多种开源嵌入模型的托管服务 [citation:6] + provider: "siliconflow", + model: ["BAAI/bge-large-zh-v1.5", "BAAI/bge-large-en-v1.5", "Pro/BAAI/bge-m3"], + baseUrl: "https://api.siliconflow.cn/v1" + }, + "huggingface": { // Hugging Face 的 Inference API 可以调用多种嵌入模型 [citation:6] + provider: "huggingface", + model: ["sentence-transformers/all-MiniLM-L6-v2", "intfloat/multilingual-e5-large-instruct"], + baseUrl: "https://api-inference.huggingface.co/models/" + } +}; \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index fb370a1..2324134 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,7 +9,7 @@ import { TimerManager } from "./timer"; import { createMsg } from "./utils/seal"; import { PrivilegeManager } from "./cmd/privilege"; import { knowledgeService } from "./session/memory"; -import { CQTYPESALLOW } from "./config/static_config"; +import { CQ_TYPES_ALLOW } from "./config/static_config"; import { registerCmd } from "./cmd/root_cmd"; function main() { @@ -58,7 +58,7 @@ function main() { // 检查CQ码 const CQTypes = messageArray.filter(item => item.type !== 'text').map(item => item.type); - if (CQTypes.length === 0 || CQTypes.every(item => CQTYPESALLOW.includes(item))) { + if (CQTypes.length === 0 || CQTypes.every(item => CQ_TYPES_ALLOW.includes(item))) { clearTimeout(ai.context.timer); ai.context.timer = null; @@ -144,7 +144,7 @@ function main() { const messageArray = transformTextToArray(message); const CQTypes = messageArray.filter(item => item.type !== 'text').map(item => item.type); - if (CQTypes.length === 0 || CQTypes.every(item => CQTYPESALLOW.includes(item))) { + if (CQTypes.length === 0 || CQTypes.every(item => CQ_TYPES_ALLOW.includes(item))) { const setting = ai.setting; if (setting.standby) { ai.handleReceipt(ctx, msg, ai, messageArray); @@ -180,7 +180,7 @@ function main() { } const CQTypes = messageArray.filter(item => item.type !== 'text').map(item => item.type); - if (CQTypes.length === 0 || CQTypes.every(item => CQTYPESALLOW.includes(item))) { + if (CQTypes.length === 0 || CQTypes.every(item => CQ_TYPES_ALLOW.includes(item))) { const setting = ai.setting; if (setting.standby) { ai.handleReceipt(ctx, msg, ai, messageArray); diff --git a/src/note.txt b/src/note.txt index e3f2b15..e0fcd2b 100644 --- a/src/note.txt +++ b/src/note.txt @@ -72,4 +72,6 @@ ai读取所有插件的指令并调用的功能 连续多条user时,对后面几条进行压缩 -提供api给其他插件,实现kp agent \ No newline at end of file +提供api给其他插件,实现kp agent + +kwarg,p,存在页码和概率撞车的情况 \ No newline at end of file diff --git a/src/utils/string.ts b/src/utils/string.ts index 02dbeb2..c7fe1de 100644 --- a/src/utils/string.ts +++ b/src/utils/string.ts @@ -5,7 +5,7 @@ import { ConfigManager } from "../config/configManager"; import { transformMsgId, transformMsgIdBack } from "./utils"; import { AI } from "../AI/AI"; import { getCtxAndMsg } from "./seal"; -import { faceMap } from "../config/static_config"; +import { FACE_MAP } from "../config/static_config"; /* 先丢这一坨东西在这。之所以不用是因为被类型检查整烦了 @@ -212,7 +212,7 @@ export async function transformArrayToContent(ctx: seal.MsgContext, ai: AI, mess break; } case 'face': { - const faceName = faceMap[seg.data.id] || ''; + const faceName = FACE_MAP[seg.data.id] || ''; content += faceName ? `<|face:${faceName}|>` : ''; break; } @@ -277,7 +277,7 @@ async function transformContentToText(ctx: seal.MsgContext, ai: AI, content: str break; } case 'face': { - const faceId = Object.keys(faceMap).find(key => faceMap[key] === seg.content) || ''; + const faceId = Object.keys(FACE_MAP).find(key => FACE_MAP[key] === seg.content) || ''; text += faceId ? `[CQ:face,id=${faceId}]` : ''; break; } diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 4f37eb6..b1a372c 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -1,7 +1,7 @@ import { logger } from "../logger"; import { ConfigManager } from "../config/configManager"; import { transformTextToArray } from "./string"; -import { aliasMap } from "../config/static_config"; +import { ALIAS_MAP } from "../config/static_config"; import { netExists, sendGroupMsg, sendPrivateMsg } from "./ob11"; export function transformMsgId(msgId: string | number | null): string { @@ -155,7 +155,7 @@ export function revive(constructor: RevivableConstructor, value: any): T { } export function aliasToCmd(val: string) { - return aliasMap[val] || val; + return ALIAS_MAP[val] || val; } // 计算余弦相似度 From 4eaa20b72064224c8176af6172661229ce521773 Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Sun, 8 Mar 2026 14:53:03 +0800 Subject: [PATCH 15/33] =?UTF-8?q?=E9=87=8D=E6=9E=84=E9=83=A8=E5=88=86confi?= =?UTF-8?q?g=EF=BC=8Clogger=E6=96=B0=E5=A2=9Edebug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/agent/model.ts | 10 +- src/config/configManager.ts | 29 +-- src/config/configs/{config_log.ts => log.ts} | 6 +- src/config/configs/model.ts | 186 ++++++++++++++++++- src/config/static_config.ts | 8 +- src/logger.ts | 12 +- 6 files changed, 210 insertions(+), 41 deletions(-) rename src/config/configs/{config_log.ts => log.ts} (64%) diff --git a/src/agent/model.ts b/src/agent/model.ts index 2c7cdf1..4794782 100644 --- a/src/agent/model.ts +++ b/src/agent/model.ts @@ -16,7 +16,7 @@ export interface ModelBody { [key: string]: any } -export class Model { +export class BaseModel { name: string; use: ModelUse[]; provider: string; @@ -42,7 +42,7 @@ export class Model { } } -export class ChatModel extends Model { +export class ChatModel extends BaseModel { constructor(name: string, use: ModelUse[], provider: string, base_url: string, api_key: string, body: ModelBody) { super(name, use, provider, base_url, api_key, body); } @@ -90,7 +90,7 @@ export class ChatModel extends Model { } } -export class ImageModel extends Model { +export class ImageModel extends BaseModel { constructor(name: string, use: ModelUse[], provider: string, base_url: string, api_key: string, body: ModelBody) { super(name, use, provider, base_url, api_key, body); } @@ -174,7 +174,7 @@ export class ImageModel extends Model { } } -export class EmbeddingModel extends Model { +export class EmbeddingModel extends BaseModel { static vectorCache: { text: string, vector: number[] } = { text: '', vector: [] }; constructor(name: string, use: ModelUse[], provider: string, base_url: string, api_key: string, body) { @@ -226,6 +226,8 @@ export class EmbeddingModel extends Model { } } +export type Model = ChatModel | ImageModel | EmbeddingModel; + export class ModelManager { static chatModels: ChatModel[] = []; static imageModels: ImageModel[] = []; diff --git a/src/config/configManager.ts b/src/config/configManager.ts index b5b189d..266cdc3 100644 --- a/src/config/configManager.ts +++ b/src/config/configManager.ts @@ -1,15 +1,8 @@ import Handlebars from "handlebars"; import { logger } from "../logger"; import { AUTHOR, NAME, VERSION } from "./static_config"; -import { BackendConfig } from "./config_backend"; -import { ImageConfig } from "./config_image"; -import { LogConfig } from "./config_log"; -import { MemoryConfig } from "./config_memory"; -import { MessageConfig } from "./config_message"; -import { ReceivedConfig } from "./config_received"; -import { ReplyConfig } from "./config_reply"; -import { RequestConfig } from "./config_request"; -import { ToolConfig } from "./config_tool"; +import { LogConfig } from "./configs/log"; +import { ModelConfig } from "./configs/model"; export class ConfigManager { static ext: seal.ExtInfo; @@ -23,14 +16,7 @@ export class ConfigManager { static registerConfig() { this.ext = ConfigManager.getExt(NAME); LogConfig.register(); - RequestConfig.register(); - MessageConfig.register(); - ToolConfig.register(); - ReceivedConfig.register(); - ReplyConfig.register(); - ImageConfig.register(); - BackendConfig.register(); - MemoryConfig.register(); + ModelConfig.register(); } static getCache(key: string, getFunc: () => T): T { @@ -49,14 +35,7 @@ export class ConfigManager { } static get log() { return this.getCache('log', LogConfig.get) } - static get request() { return this.getCache('request', RequestConfig.get) } - static get message() { return this.getCache('message', MessageConfig.get) } - static get tool() { return this.getCache('tool', ToolConfig.get) } - static get received() { return this.getCache('received', ReceivedConfig.get) } - static get reply() { return this.getCache('reply', ReplyConfig.get) } - static get image() { return this.getCache('image', ImageConfig.get) } - static get backend() { return this.getCache('backend', BackendConfig.get) } - static get memory() { return this.getCache('memory', MemoryConfig.get) } + static get model() { return this.getCache('model', ModelConfig.get) } static getExt(name: string): seal.ExtInfo { if (name == NAME && ConfigManager.ext) { diff --git a/src/config/configs/config_log.ts b/src/config/configs/log.ts similarity index 64% rename from src/config/configs/config_log.ts rename to src/config/configs/log.ts index bcc4e38..0df5e52 100644 --- a/src/config/configs/config_log.ts +++ b/src/config/configs/log.ts @@ -1,4 +1,4 @@ -import { ConfigManager } from "./configManager"; +import { ConfigManager } from "../configManager"; export class LogConfig { static ext: seal.ExtInfo; @@ -6,12 +6,12 @@ export class LogConfig { static register() { LogConfig.ext = ConfigManager.getExt('aiplugin4'); - seal.ext.registerOptionConfig(LogConfig.ext, "日志打印方式", "简短", ["永不", "简短", "详细"]); + seal.ext.registerOptionConfig(LogConfig.ext, "日志打印方式", "简短", ["永不", "简短", "详细", "调试"]); } static get() { return { - logLevel: seal.ext.getOptionConfig(LogConfig.ext, "日志打印方式") + logLevel: seal.ext.getOptionConfig(LogConfig.ext, "日志打印方式") as "永不" | "简短" | "详细" | "调试" } } } \ No newline at end of file diff --git a/src/config/configs/model.ts b/src/config/configs/model.ts index e55d750..1dc48d7 100644 --- a/src/config/configs/model.ts +++ b/src/config/configs/model.ts @@ -1,4 +1,7 @@ +import { ChatModel, EmbeddingModel, ImageModel } from "../../agent/model"; +import { logger } from "../../logger"; import { ConfigManager } from "../configManager"; +import { CHAT_MODEL_MAP, EMBEDDING_MODEL_MAP, IMAGE_MODEL_MAP } from "../static_config"; export class ModelConfig { static ext: seal.ExtInfo; @@ -6,14 +9,189 @@ export class ModelConfig { static register() { ModelConfig.ext = ConfigManager.getExt('aiplugin4:模型'); - seal.ext.registerTemplateConfig(ModelConfig.ext, "对话模型", [""], ''); - seal.ext.registerTemplateConfig(ModelConfig.ext, "图片模型", [""], ''); - seal.ext.registerTemplateConfig(ModelConfig.ext, "嵌入模型", [""], ''); + seal.ext.registerOptionConfig(ModelConfig.ext, "快速选择对话模型", "", getChatModelOptions(), ''); + seal.ext.registerStringConfig(ModelConfig.ext, "快速填入对话模型api key", "", ''); + seal.ext.registerOptionConfig(ModelConfig.ext, "快速选择图片模型", "", getImageModelOptions(), ''); + seal.ext.registerStringConfig(ModelConfig.ext, "快速填入图片模型api key", "", ''); + seal.ext.registerOptionConfig(ModelConfig.ext, "快速选择嵌入模型", "", getEmbeddingModelOptions(), ''); + seal.ext.registerStringConfig(ModelConfig.ext, "快速填入嵌入模型api key", "", ''); + seal.ext.registerTemplateConfig(ModelConfig.ext, "对话模型", [`{ + "name": "deepseek-chat", + "use": ["chat"], + "api_key": "sk-xxxx", + "body": ${JSON.stringify(DEFAULT_CHAT_MODEL_BODY)} +}`], ''); + seal.ext.registerTemplateConfig(ModelConfig.ext, "图片模型", [`{ + "name": "glm-4v", + "use": ["any"], + "api_key": "sk-xxxx", + "base_url": "https://open.bigmodel.cn/api/paas/v4", + "body": ${JSON.stringify(DEFAULT_IMAGE_MODEL_BODY)} +}`], ''); + seal.ext.registerTemplateConfig(ModelConfig.ext, "嵌入模型", [`{ + "name": "text-embedding-v4", + "use": ["any"], + "api_key": "sk-xxxx", + "body": ${JSON.stringify(DEFAULT_EMBEDDING_MODEL_BODY)} +}`], ''); } static get() { return { - enabled: seal.ext.getBoolConfig(ModelConfig.ext, "是否启用"), + CHAT_MODELS: [getFastChatModel(), ...getChatModelsConfig()].filter(model => model !== null), + IMAGE_MODELS: [getFastImageModel(), ...getImageModelsConfig()].filter(model => model !== null), + EMBEDDING_MODELS: [getFastEmbeddingModel(), ...getEmbeddingModelsConfig()].filter(model => model !== null), } } +} + +const DEFAULT_CHAT_MODEL_BODY = { + "max_tokens": 4096, + "stop": null, + "stream": false, + "temperature": 1, + "top_p": 1 +} +const DEFAULT_IMAGE_MODEL_BODY = { + "max_tokens": 4096, + "stop": null, + "stream": false +} +const DEFAULT_EMBEDDING_MODEL_BODY = { + "encoding_format": "float" +} + +function getChatModelOptions() { + const op: string[] = []; + Object.keys(CHAT_MODEL_MAP).forEach(provider => { + CHAT_MODEL_MAP[provider].model.forEach(model => { + op.push(`${provider}/${model}`); + }); + }); + return op; +} + +function getImageModelOptions() { + const op: string[] = []; + Object.keys(IMAGE_MODEL_MAP).forEach(provider => { + IMAGE_MODEL_MAP[provider].model.forEach(model => { + op.push(`${provider}/${model}`); + }); + }); + return op; +} + +function getEmbeddingModelOptions() { + const op: string[] = []; + Object.keys(EMBEDDING_MODEL_MAP).forEach(provider => { + EMBEDDING_MODEL_MAP[provider].model.forEach(model => { + op.push(`${provider}/${model}`); + }); + }); + return op; +} + +function getFastChatModel(): ChatModel | null { + const model = seal.ext.getOptionConfig(ModelConfig.ext, "快速选择对话模型"); + const apiKey = seal.ext.getStringConfig(ModelConfig.ext, "快速填入对话模型api key"); + const [provider, name] = model.split('/'); + const baseUrl = CHAT_MODEL_MAP[provider].baseUrl; + return new ChatModel(name, ["any"], provider, baseUrl, apiKey, DEFAULT_CHAT_MODEL_BODY); +} + +function getFastImageModel(): ImageModel | null { + const model = seal.ext.getOptionConfig(ModelConfig.ext, "快速选择图片模型"); + const apiKey = seal.ext.getStringConfig(ModelConfig.ext, "快速填入图片模型api key"); + const [provider, name] = model.split('/'); + const baseUrl = IMAGE_MODEL_MAP[provider].baseUrl; + return new ImageModel(name, ["any"], provider, baseUrl, apiKey, DEFAULT_IMAGE_MODEL_BODY); +} + +function getFastEmbeddingModel(): EmbeddingModel | null { + const model = seal.ext.getOptionConfig(ModelConfig.ext, "快速选择嵌入模型"); + const apiKey = seal.ext.getStringConfig(ModelConfig.ext, "快速填入嵌入模型api key"); + const [provider, name] = model.split('/'); + const baseUrl = EMBEDDING_MODEL_MAP[provider].baseUrl; + return new EmbeddingModel(name, ["any"], provider, baseUrl, apiKey, DEFAULT_EMBEDDING_MODEL_BODY); +} + +function getChatModelsConfig(): ChatModel[] { + return seal.ext.getTemplateConfig(ModelConfig.ext, "对话模型").map(x => { + try { + const data = JSON.parse(x); + if (!data.hasOwnProperty('name')) throw new Error('缺失模型名称'); + if (!data.hasOwnProperty('api_key')) throw new Error('缺失模型API密钥'); + if (!data.hasOwnProperty('body')) data.body = DEFAULT_CHAT_MODEL_BODY; + if (!data.hasOwnProperty('use')) data.use = ["any"]; + if (!data.hasOwnProperty('provider')) data.provider = ""; + if (!data.hasOwnProperty('base_url')) { + for (const provider in CHAT_MODEL_MAP) { + if (CHAT_MODEL_MAP[provider].model.includes(data.name)) { + data.base_url = CHAT_MODEL_MAP[provider].baseUrl; + break; + } + } + if (!data.hasOwnProperty('base_url')) throw new Error('缺失模型基础URL'); + } + + return new ChatModel(data.name, data.use, data.provider, data.base_url, data.api_key, data.body); + } catch (e) { + logger.error(`对话模型解析错误,内容:${x},错误信息:${e.message}`); + return null; + } + }).filter(x => x !== null); +} + +function getImageModelsConfig(): ImageModel[] { + return seal.ext.getTemplateConfig(ModelConfig.ext, "图片模型").map(x => { + try { + const data = JSON.parse(x); + if (!data.hasOwnProperty('name')) throw new Error('缺失模型名称'); + if (!data.hasOwnProperty('api_key')) throw new Error('缺失模型API密钥'); + if (!data.hasOwnProperty('body')) data.body = DEFAULT_IMAGE_MODEL_BODY; + if (!data.hasOwnProperty('use')) data.use = ["any"]; + if (!data.hasOwnProperty('provider')) data.provider = ""; + if (!data.hasOwnProperty('base_url')) { + for (const provider in IMAGE_MODEL_MAP) { + if (IMAGE_MODEL_MAP[provider].model.includes(data.name)) { + data.base_url = IMAGE_MODEL_MAP[provider].baseUrl; + break; + } + } + if (!data.hasOwnProperty('base_url')) throw new Error('缺失模型基础URL'); + } + + return new ImageModel(data.name, data.use, data.provider, data.base_url, data.api_key, data.body); + } catch (e) { + logger.error(`图片模型解析错误,内容:${x},错误信息:${e.message}`); + return null; + } + }).filter(x => x !== null); +} + +function getEmbeddingModelsConfig(): EmbeddingModel[] { + return seal.ext.getTemplateConfig(ModelConfig.ext, "嵌入模型").map(x => { + try { + const data = JSON.parse(x); + if (!data.hasOwnProperty('name')) throw new Error('缺失模型名称'); + if (!data.hasOwnProperty('api_key')) throw new Error('缺失模型API密钥'); + if (!data.hasOwnProperty('body')) data.body = DEFAULT_EMBEDDING_MODEL_BODY; + if (!data.hasOwnProperty('use')) data.use = ["any"]; + if (!data.hasOwnProperty('provider')) data.provider = ""; + if (!data.hasOwnProperty('base_url')) { + for (const provider in EMBEDDING_MODEL_MAP) { + if (EMBEDDING_MODEL_MAP[provider].model.includes(data.name)) { + data.base_url = EMBEDDING_MODEL_MAP[provider].baseUrl; + break; + } + } + if (!data.hasOwnProperty('base_url')) throw new Error('缺失模型基础URL'); + } + + return new EmbeddingModel(data.name, data.use, data.provider, data.base_url, data.api_key, data.body); + } catch (e) { + logger.error(`嵌入模型解析错误,内容:${x},错误信息:${e.message}`); + return null; + } + }).filter(x => x !== null); } \ No newline at end of file diff --git a/src/config/static_config.ts b/src/config/static_config.ts index ede743c..105ae21 100644 --- a/src/config/static_config.ts +++ b/src/config/static_config.ts @@ -374,7 +374,7 @@ export interface ModelInfo { baseUrl: string; } -export const CHAT_MODEL_MAP = { +export const CHAT_MODEL_MAP: { [key: string]: ModelInfo } = { // 海外厂商 "openai": { provider: "openai", @@ -449,7 +449,7 @@ export const CHAT_MODEL_MAP = { } }; -export const IMAGE_MODEL_MAP = { +export const IMAGE_MODEL_MAP: { [key: string]: ModelInfo } = { "openai": { provider: "openai", model: ["dall-e-3", "dall-e-2", "gpt-image-1.5"], // GPT Image 1.5 是2025年底的新模型 [citation:2] @@ -492,7 +492,7 @@ export const IMAGE_MODEL_MAP = { } }; -export const EMBEDDING_MODEL_MAP = { +export const EMBEDDING_MODEL_MAP: { [key: string]: ModelInfo } = { "openai": { provider: "openai", model: ["text-embedding-3-large", "text-embedding-3-small", "text-embedding-ada-002"], @@ -510,7 +510,7 @@ export const EMBEDDING_MODEL_MAP = { }, "alibaba": { provider: "alibaba", - model: ["text-embedding-v3", "text-embedding-v2"], + model: ["text-embedding-v4", "text-embedding-v3"], baseUrl: "https://dashscope.aliyuncs.com/compatible-mode/v1" // DashScope 兼容地址 }, "baidu": { diff --git a/src/logger.ts b/src/logger.ts index 3c77cd4..aaa5a22 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -18,7 +18,7 @@ class Logger { } else { return s; } - } else if (logLevel === "详细") { + } else if (logLevel === "详细" || logLevel === "调试") { return data.map(item => `${item}`).join(" "); } else { return ''; @@ -48,6 +48,16 @@ class Logger { } console.error(`【${this.name}】: ${s}`); } + + debug(...data: any[]) { + const { logLevel } = ConfigManager.log; + if (logLevel !== "调试") return; + const s = this.handleLog(...data); + if (!s) { + return; + } + console.info(`【${this.name}】: ${s}`); + } } export const logger = new Logger(NAME); \ No newline at end of file From 6941c60e7b8ada836abf604006ed6753da498a03 Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Sun, 8 Mar 2026 17:57:54 +0800 Subject: [PATCH 16/33] =?UTF-8?q?=E7=BB=A7=E7=BB=AD=E9=87=8D=E6=9E=84confi?= =?UTF-8?q?g?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/agent/AI.ts | 22 ++-- src/agent/agent.ts | 6 +- src/agent/model.ts | 10 +- src/agent/stream.ts | 10 +- src/agent/usage.ts | 4 +- src/cmd/privilege.ts | 6 +- src/cmd/root_cmd.ts | 6 +- src/cmd/sub_cmd/memory.ts | 4 +- src/cmd/sub_cmd/role.ts | 4 +- src/cmd/sub_cmd/tool.ts | 6 +- src/config/config.ts | 116 ++++++++++++++++++ src/config/configManager.ts | 102 --------------- .../configs/{config_backend.ts => backend.ts} | 4 +- src/config/configs/base.ts | 19 +++ src/config/configs/config_request.ts | 32 ----- src/config/configs/log.ts | 17 --- src/config/configs/model.ts | 4 +- src/config/configs/sample.ts | 4 +- src/config/static_config.ts | 2 + src/image/image.ts | 16 +-- src/index.ts | 12 +- src/logger.ts | 6 +- src/session/context.ts | 6 +- src/session/group.ts | 4 +- src/session/memory.ts | 36 +++--- src/session/session.ts | 4 +- src/session/user.ts | 4 +- src/timer.ts | 6 +- src/tool/tool.ts | 16 +-- src/utils/message.ts | 22 ++-- src/utils/string.ts | 10 +- src/utils/update.ts | 6 +- src/utils/utils.ts | 4 +- 33 files changed, 258 insertions(+), 272 deletions(-) create mode 100644 src/config/config.ts delete mode 100644 src/config/configManager.ts rename src/config/configs/{config_backend.ts => backend.ts} (92%) create mode 100644 src/config/configs/base.ts delete mode 100644 src/config/configs/config_request.ts delete mode 100644 src/config/configs/log.ts diff --git a/src/agent/AI.ts b/src/agent/AI.ts index f352811..1d80cd3 100644 --- a/src/agent/AI.ts +++ b/src/agent/AI.ts @@ -1,5 +1,5 @@ import { Image, ImageManager } from "./image"; -import { ConfigManager } from "../config/configManager"; +import { Config } from "../config/config"; import { replyToSender, revive, transformMsgId } from "../utils/utils"; import { endStream, pollStream, sendChatRequest, startStream } from "../agent/service"; import { Context } from "./context"; @@ -112,7 +112,7 @@ export class AI { } //发送偷来的图片 - const { p } = ConfigManager.image; + const { p } = Config.image; if (Math.random() * 100 <= p) { const img = await this.imageManager.drawImage(); if (img) seal.replyToSender(ctx, msg, img.CQCode); @@ -123,7 +123,7 @@ export class AI { logger.info('触发回复:', reason || '未知原因'); if (reason !== '函数回调触发') { - const { bucketLimit, fillInterval } = ConfigManager.received; + const { bucketLimit, fillInterval } = Config.received; // 补充并检查触发次数 if (Date.now() - this.bucket.lastTime > fillInterval * 1000) { const fillCount = (Date.now() - this.bucket.lastTime) / (fillInterval * 1000); @@ -137,7 +137,7 @@ export class AI { } // 检查toolsNotAllow状态 - const { toolsNotAllow } = ConfigManager.tool; + const { toolsNotAllow } = Config.tool; toolsNotAllow.forEach(key => { if (this.tool.toolStatus.hasOwnProperty(key)) { this.tool.toolStatus[key] = false; @@ -150,7 +150,7 @@ export class AI { // 解析body,检查是否为流式 let stream = false; try { - const bodyTemplate = ConfigManager.request.bodyTemplate; + const bodyTemplate = Config.request.bodyTemplate; const bodyObject = parseBody(bodyTemplate, [], null, null); stream = bodyObject?.stream === true; } catch (err) { @@ -164,7 +164,7 @@ export class AI { } - const { isTool, usePromptEngineering } = ConfigManager.tool; + const { isTool, usePromptEngineering } = Config.tool; const toolInfos = this.tool.getToolsInfo(msg.messageType); let result = { contextArray: [], replyArray: [], images: [] }; @@ -238,7 +238,7 @@ export class AI { } async chatStream(ctx: seal.MsgContext, msg: seal.Message): Promise { - const { isTool, usePromptEngineering } = ConfigManager.tool; + const { isTool, usePromptEngineering } = Config.tool; await this.stopCurrentChatStream(); @@ -415,7 +415,7 @@ export class AIManager { static get usageMap(): { [model: string]: { [time: number]: UsageInfo } } { if (!this.usageMapCache) { try { - this.usageMapCache = JSON.parse(ConfigManager.ext.storageGet('usageMap') || '{}'); + this.usageMapCache = JSON.parse(Config.ext.storageGet('usageMap') || '{}'); } catch (error) { logger.error(`从数据库中获取usageMap失败:`, error); } @@ -432,7 +432,7 @@ export class AIManager { let ai = new AI(); try { - ai = JSON.parse(ConfigManager.ext.storageGet(`AI_${id}`) || '{}', (key, value) => { + ai = JSON.parse(Config.ext.storageGet(`AI_${id}`) || '{}', (key, value) => { if (key === "") { return revive(AI, value); } @@ -474,7 +474,7 @@ export class AIManager { static saveAI(id: string) { if (this.cache.hasOwnProperty(id)) { - ConfigManager.ext.storageSet(`AI_${id}`, JSON.stringify(this.cache[id])); + Config.ext.storageSet(`AI_${id}`, JSON.stringify(this.cache[id])); } } @@ -526,7 +526,7 @@ export class AIManager { } static saveUsageMap() { - ConfigManager.ext.storageSet('usageMap', JSON.stringify(this.usageMapCache)); + Config.ext.storageSet('usageMap', JSON.stringify(this.usageMapCache)); } static updateUsage(model: string, usage: { diff --git a/src/agent/agent.ts b/src/agent/agent.ts index 6610745..9a901b6 100644 --- a/src/agent/agent.ts +++ b/src/agent/agent.ts @@ -1,4 +1,4 @@ -import { ConfigManager } from "../config/configManager"; +import { Config } from "../config/config"; import { logger } from "../logger"; import { SessionService } from "../session/session"; import { revive, TypeDescriptor } from "../utils/utils"; @@ -43,7 +43,7 @@ export class AgentManager { if (!this.agentMap.hasOwnProperty(name)) { let agent = new Agent(); try { - const data = JSON.parse(ConfigManager.ext.storageGet(`agent_${name}`) || '{}'); + const data = JSON.parse(Config.ext.storageGet(`agent_${name}`) || '{}'); agent = revive(Agent, data); } catch (error) { logger.error(`加载智能体${name}失败: ${error}`); @@ -55,7 +55,7 @@ export class AgentManager { } static saveAgent(agent: Agent) { - ConfigManager.ext.storageSet(`agent_${agent.name}`, JSON.stringify(agent)); + Config.ext.storageSet(`agent_${agent.name}`, JSON.stringify(agent)); } static initAgent() { diff --git a/src/agent/model.ts b/src/agent/model.ts index 4794782..536e1cb 100644 --- a/src/agent/model.ts +++ b/src/agent/model.ts @@ -1,4 +1,4 @@ -import { ConfigManager } from "../config/configManager"; +import { Config } from "../config/config"; import { logger } from "../logger"; import { ToolCall } from "../tool/tool"; import { withTimeout } from "../utils/utils"; @@ -59,7 +59,7 @@ export class ChatModel extends BaseModel { } async callChat(agent: Agent, sessionId: string): Promise<{ content: string, tool_calls: ToolCall[] }> { - const { timeout } = ConfigManager.request; + const { timeout } = Config.request; try { const time = Date.now(); @@ -107,7 +107,7 @@ export class ImageModel extends BaseModel { } async callITT(src: string, prompt = ''): Promise { - const { timeout } = ConfigManager.request; + const { timeout } = Config.request; try { const time = Date.now(); @@ -143,7 +143,7 @@ export class ImageModel extends BaseModel { } async callChat(agent: Agent, sessionId: string): Promise<{ content: string, tool_calls: ToolCall[] }> { - const { timeout } = ConfigManager.request; + const { timeout } = Config.request; try { const time = Date.now(); @@ -191,7 +191,7 @@ export class EmbeddingModel extends BaseModel { return []; } - const { timeout } = ConfigManager.request; + const { timeout } = Config.request; if (EmbeddingModel.vectorCache.text === text && EmbeddingModel.vectorCache.vector.length === this.body.dimensions) { const v = EmbeddingModel.vectorCache.vector; diff --git a/src/agent/stream.ts b/src/agent/stream.ts index 9159bf9..ffca139 100644 --- a/src/agent/stream.ts +++ b/src/agent/stream.ts @@ -1,4 +1,4 @@ -import { ConfigManager } from "../config/configManager"; +import { Config } from "../config/config"; import { logger } from "../logger"; import { withTimeout } from "../utils/utils"; import { Agent } from "./agent"; @@ -7,8 +7,8 @@ import { UsageManager } from "./usage"; export class streamService { static async startStream(agent: Agent, sessionId: string): Promise { - const { timeout } = ConfigManager.request; - const { streamUrl } = ConfigManager.backend; + const { timeout } = Config.request; + const { streamUrl } = Config.backend; const model = ModelManager.getChatModel('chat'); try { const body = model.buildChatBody(agent, sessionId); @@ -64,7 +64,7 @@ export class streamService { } static async pollStream(streamId: string, after: number): Promise<{ status: string, reply: string, nextAfter: number }> { - const { streamUrl } = ConfigManager.backend; + const { streamUrl } = Config.backend; try { const response = await fetch(`${streamUrl}/poll?id=${streamId}&after=${after}`, { @@ -107,7 +107,7 @@ export class streamService { } static async endStream(streamId: string): Promise { - const { streamUrl } = ConfigManager.backend; + const { streamUrl } = Config.backend; try { const response = await fetch(`${streamUrl}/end?id=${streamId}`, { diff --git a/src/agent/usage.ts b/src/agent/usage.ts index a69fb57..3131597 100644 --- a/src/agent/usage.ts +++ b/src/agent/usage.ts @@ -1,4 +1,4 @@ -import { ConfigManager } from "../config/configManager"; +import { Config } from "../config/config"; import { logger } from "../logger"; export class UsageManager { @@ -15,7 +15,7 @@ export async function get_chart_url(chart_type: string, usage_data: { completion_tokens: number; } }) { - const { usageChartUrl } = ConfigManager.backend; + const { usageChartUrl } = Config.backend; try { const response = await fetch(`${usageChartUrl}/chart`, { method: 'POST', diff --git a/src/cmd/privilege.ts b/src/cmd/privilege.ts index 931d247..623c398 100644 --- a/src/cmd/privilege.ts +++ b/src/cmd/privilege.ts @@ -1,6 +1,6 @@ import { AI } from "../AI/AI"; import { logger } from "../logger"; -import { ConfigManager } from "../config/configManager"; +import { Config } from "../config/config"; import { aliasToCmd } from "../utils/utils"; import { PRIVILEGE_LEVEL_MAP } from "../config/static_config"; @@ -24,7 +24,7 @@ export class PrivilegeManager { static reviveCmdPriv() { try { - const cmdPriv = JSON.parse(ConfigManager.ext.storageGet('cmdPriv') || '{}'); + const cmdPriv = JSON.parse(Config.ext.storageGet('cmdPriv') || '{}'); if (typeof cmdPriv === 'object' && !Array.isArray(cmdPriv)) { this.cmdPriv = this.updateCmdPriv(cmdPriv, JSON.parse(JSON.stringify(defaultCmdPriv))); this.saveCmdPriv(); @@ -37,7 +37,7 @@ export class PrivilegeManager { } static saveCmdPriv() { - ConfigManager.ext.storageSet('cmdPriv', JSON.stringify(this.cmdPriv)); + Config.ext.storageSet('cmdPriv', JSON.stringify(this.cmdPriv)); } static updateCmdPriv(cp: CmdPriv, defaultCp: CmdPriv): CmdPriv { diff --git a/src/cmd/root_cmd.ts b/src/cmd/root_cmd.ts index b326d28..a4e6c12 100644 --- a/src/cmd/root_cmd.ts +++ b/src/cmd/root_cmd.ts @@ -1,5 +1,5 @@ import { AI, AIManager } from "../AI/AI"; -import { ConfigManager } from "../config/configManager"; +import { Config } from "../config/config"; import { logger } from "../logger"; import { CmdPrivInfo, defaultCmdPriv, PrivilegeManager, U } from "./privilege"; import { aliasToCmd } from "../utils/utils"; @@ -127,6 +127,6 @@ export function registerCmd() { } } - ConfigManager.ext.cmdMap['AI'] = cmd; - ConfigManager.ext.cmdMap['ai'] = cmd; + Config.ext.cmdMap['AI'] = cmd; + Config.ext.cmdMap['ai'] = cmd; } \ No newline at end of file diff --git a/src/cmd/sub_cmd/memory.ts b/src/cmd/sub_cmd/memory.ts index 16c4de0..e1fa414 100644 --- a/src/cmd/sub_cmd/memory.ts +++ b/src/cmd/sub_cmd/memory.ts @@ -1,5 +1,5 @@ import { AIManager } from "../../AI/AI"; -import { ConfigManager } from "../../config/configManager"; +import { Config } from "../../config/config"; import { aliasToCmd } from "../../utils/utils"; import { I, S, U } from "../privilege"; import { SubCmd, SubCmdContext } from "../root_cmd"; @@ -62,7 +62,7 @@ export function registerCmdMemory() { if (cmdArgs.at.length > 0 && (cmdArgs.at.length !== 1 || cmdArgs.at[0].userId !== epId)) { ai3 = ai2; } - const { isMemory, isShortMemory } = ConfigManager.memory; + const { isMemory, isShortMemory } = Config.memory; seal.replyToSender(ctx, msg, `${ai3.id} 长期记忆开启状态: ${isMemory ? '是' : '否'} 长期记忆条数: ${ai3.memory.memoryIds.length} diff --git a/src/cmd/sub_cmd/role.ts b/src/cmd/sub_cmd/role.ts index 79a1aba..7c2d239 100644 --- a/src/cmd/sub_cmd/role.ts +++ b/src/cmd/sub_cmd/role.ts @@ -1,4 +1,4 @@ -import { ConfigManager } from "../../config/configManager"; +import { Config } from "../../config/config"; import { getRoleSetting } from "../../utils/message"; import { I } from "../privilege"; import { SubCmd, SubCmdContext } from "../root_cmd"; @@ -11,7 +11,7 @@ export function registerCmdRole() { cmd.solve = (scc: SubCmdContext) => { const { ctx, msg, cmdArgs, ret } = scc; - const { roleSettingNames, roleSettingTemplate } = ConfigManager.message; + const { roleSettingNames, roleSettingTemplate } = Config.message; const { roleName } = getRoleSetting(ctx); const val2 = cmdArgs.getArgN(2); if (!val2) { diff --git a/src/cmd/sub_cmd/tool.ts b/src/cmd/sub_cmd/tool.ts index 50d8eb9..68e526d 100644 --- a/src/cmd/sub_cmd/tool.ts +++ b/src/cmd/sub_cmd/tool.ts @@ -1,5 +1,5 @@ import { AIManager } from "../../AI/AI"; -import { ConfigManager } from "../../config/configManager"; +import { Config } from "../../config/config"; import { logger } from "../../logger"; import { ToolManager } from "../../tool/tool"; import { aliasToCmd } from "../../utils/utils"; @@ -27,7 +27,7 @@ export function registerCmdTool() { case 'on': { const val3 = cmdArgs.getArgN(3); if (val3) { - const toolsNotAllow = ConfigManager.tool.toolsNotAllow; + const toolsNotAllow = Config.tool.toolsNotAllow; if (toolsNotAllow.includes(val3)) { seal.replyToSender(ctx, msg, `工具函数 ${val3} 不被允许开启`); return ret; @@ -38,7 +38,7 @@ export function registerCmdTool() { AIManager.saveAI(sid); return ret; } - const toolsNotAllow = ConfigManager.tool.toolsNotAllow; + const toolsNotAllow = Config.tool.toolsNotAllow; for (const key in ai.tool.toolStatus) { ai.tool.toolStatus[key] = toolsNotAllow.includes(key) ? false : true; } diff --git a/src/config/config.ts b/src/config/config.ts new file mode 100644 index 0000000..dcbc275 --- /dev/null +++ b/src/config/config.ts @@ -0,0 +1,116 @@ +import Handlebars from "handlebars"; +import { logger } from "../logger"; +import { AUTHOR, CONFIG_CACHE_TTL, NAME, VERSION } from "./static_config"; +import { BaseConfig } from "./configs/base"; +import { ModelConfig } from "./configs/model"; +import { BackendConfig } from "./configs/backend"; + +const configMap = { + 'base': BaseConfig, + 'model': ModelConfig, + 'backend': BackendConfig +} as const; + +type ConfigMap = typeof configMap; +type ConfigKey = keyof ConfigMap; +type ConfigProps = { [K in ConfigKey]: ReturnType }; +type ConfigProp = ConfigProps[ConfigKey]; + +interface ConfigCache { + timestamp: number, + data: ConfigProp +} + +class Config { + static ext: seal.ExtInfo; + static cache: { [K in ConfigKey]?: ConfigCache } = {} + + static registerConfig() { + this.ext = Config.getExt(NAME); + for (const k of Object.keys(configMap) as ConfigKey[]) { + configMap[k].register(); + Object.defineProperty(this, k, { + get: () => this.getCache(k, configMap[k].get) + }) + } + } + + static getCache(key: ConfigKey, getFunc: () => ConfigProp): ConfigProp { + const timestamp = Date.now() + if (this.cache?.[key] && timestamp - this.cache[key].timestamp < CONFIG_CACHE_TTL) { + return this.cache[key].data; + } + const data = getFunc(); + this.cache[key] = { + timestamp: timestamp, + data: data + } + return data; + } + + static getExt(name: string): seal.ExtInfo { + if (name == NAME && Config.ext) { + return Config.ext; + } + + let ext = seal.ext.find(name); + if (!ext) { + ext = seal.ext.new(name, AUTHOR, VERSION); + seal.ext.register(ext); + } + + return ext; + } +} + +const _Config = Config as typeof Config & ConfigProps; +export { _Config as Config }; + +export function getRegexConfig(ext: seal.ExtInfo, key: string): RegExp { + const patterns = seal.ext.getTemplateConfig(ext, key).filter(x => x); + const pattern = patterns.join('|'); + if (pattern) { + try { + return new RegExp(pattern); + } catch (e) { + logger.error(`正则表达式错误,内容:${pattern},错误信息:${e.message}`); + return /(?!)/; + } + } + return /(?!)/; +} + +export function getRegexesConfig(ext: seal.ExtInfo, key: string): RegExp[] { + return seal.ext.getTemplateConfig(ext, key).map(x => { + try { + return new RegExp(x); + } catch (e) { + logger.error(`正则表达式错误,内容:${x},错误信息:${e.message}`); + return /(?!)/; + } + }); +} + +export function getHandlebarsTemplateConfig(ext: seal.ExtInfo, key: string): HandlebarsTemplateDelegate { + return Handlebars.compile(seal.ext.getTemplateConfig(ext, key)[0] || ''); +} + +export function getHandlebarsTemplatesConfig(ext: seal.ExtInfo, key: string): HandlebarsTemplateDelegate[] { + return seal.ext.getTemplateConfig(ext, key).map(x => Handlebars.compile(x || '')); +} + +export function getPathMapConfig(ext: seal.ExtInfo, key: string): { [id: string]: string } { + const paths = seal.ext.getTemplateConfig(ext, key).filter(x => x); + const pathMap: { [id: string]: string } = paths.reduce((acc: { [id: string]: string }, path: string) => { + if (path.trim() === '') return acc; + try { + const id = path.split('/').pop().replace(/\.[^/.]+$/, ''); + if (!id) throw new Error(`本地路径格式错误:${path}`); + acc[id] = path; + } catch (e) { + logger.error(`本地路径格式错误:${path},错误信息:${e.message}`); + } + return acc; + }, {}); + return pathMap; +} \ No newline at end of file diff --git a/src/config/configManager.ts b/src/config/configManager.ts deleted file mode 100644 index 266cdc3..0000000 --- a/src/config/configManager.ts +++ /dev/null @@ -1,102 +0,0 @@ -import Handlebars from "handlebars"; -import { logger } from "../logger"; -import { AUTHOR, NAME, VERSION } from "./static_config"; -import { LogConfig } from "./configs/log"; -import { ModelConfig } from "./configs/model"; - -export class ConfigManager { - static ext: seal.ExtInfo; - static cache: { - [key: string]: { - timestamp: number, - data: any - } - } = {} - - static registerConfig() { - this.ext = ConfigManager.getExt(NAME); - LogConfig.register(); - ModelConfig.register(); - } - - static getCache(key: string, getFunc: () => T): T { - const timestamp = Date.now() - if (this.cache?.[key] && timestamp - this.cache[key].timestamp < 60000) { - return this.cache[key].data; - } - - const data = getFunc(); - this.cache[key] = { - timestamp: timestamp, - data: data - } - - return data; - } - - static get log() { return this.getCache('log', LogConfig.get) } - static get model() { return this.getCache('model', ModelConfig.get) } - - static getExt(name: string): seal.ExtInfo { - if (name == NAME && ConfigManager.ext) { - return ConfigManager.ext; - } - - let ext = seal.ext.find(name); - if (!ext) { - ext = seal.ext.new(name, AUTHOR, VERSION); - seal.ext.register(ext); - } - - return ext; - } - - static getRegexConfig(ext: seal.ExtInfo, key: string): RegExp { - const patterns = seal.ext.getTemplateConfig(ext, key).filter(x => x); - const pattern = patterns.join('|'); - if (pattern) { - try { - return new RegExp(pattern); - } catch (e) { - logger.error(`正则表达式错误,内容:${pattern},错误信息:${e.message}`); - return /(?!)/; - } - } - return /(?!)/; - } - - static getRegexesConfig(ext: seal.ExtInfo, key: string): RegExp[] { - return seal.ext.getTemplateConfig(ext, key).map(x => { - try { - return new RegExp(x); - } catch (e) { - logger.error(`正则表达式错误,内容:${x},错误信息:${e.message}`); - return /(?!)/; - } - }); - } - - static getHandlebarsTemplateConfig(ext: seal.ExtInfo, key: string): HandlebarsTemplateDelegate { - return Handlebars.compile(seal.ext.getTemplateConfig(ext, key)[0] || ''); - } - - static getHandlebarsTemplatesConfig(ext: seal.ExtInfo, key: string): HandlebarsTemplateDelegate[] { - return seal.ext.getTemplateConfig(ext, key).map(x => Handlebars.compile(x || '')); - } - - static getPathMapConfig(ext: seal.ExtInfo, key: string): { [id: string]: string } { - const paths = seal.ext.getTemplateConfig(ext, key).filter(x => x); - const pathMap: { [id: string]: string } = paths.reduce((acc: { [id: string]: string }, path: string) => { - if (path.trim() === '') return acc; - try { - const id = path.split('/').pop().replace(/\.[^/.]+$/, ''); - if (!id) throw new Error(`本地路径格式错误:${path}`); - acc[id] = path; - } catch (e) { - logger.error(`本地路径格式错误:${path},错误信息:${e.message}`); - } - return acc; - }, {}); - return pathMap; - } -} \ No newline at end of file diff --git a/src/config/configs/config_backend.ts b/src/config/configs/backend.ts similarity index 92% rename from src/config/configs/config_backend.ts rename to src/config/configs/backend.ts index 30cb262..5d70b81 100644 --- a/src/config/configs/config_backend.ts +++ b/src/config/configs/backend.ts @@ -1,10 +1,10 @@ -import { ConfigManager } from "./configManager"; +import { Config } from "../config"; export class BackendConfig { static ext: seal.ExtInfo; static register() { - BackendConfig.ext = ConfigManager.getExt('aiplugin4_6:后端'); + BackendConfig.ext = Config.getExt('aiplugin4:后端'); seal.ext.registerStringConfig(BackendConfig.ext, "流式输出", "http://localhost:3010", '自行搭建或使用他人提供的后端'); seal.ext.registerStringConfig(BackendConfig.ext, "图片转base64", "https://urltobase64.fishwhite.top", '可自行搭建'); diff --git a/src/config/configs/base.ts b/src/config/configs/base.ts new file mode 100644 index 0000000..001f70a --- /dev/null +++ b/src/config/configs/base.ts @@ -0,0 +1,19 @@ +import { Config } from "../config"; + +export class BaseConfig { + static ext: seal.ExtInfo; + + static register() { + BaseConfig.ext = Config.getExt('aiplugin4'); + + seal.ext.registerOptionConfig(BaseConfig.ext, "日志打印方式", "简短", ["永不", "简短", "详细", "调试"]); + seal.ext.registerIntConfig(BaseConfig.ext, "请求超时时限/ms", 180000, ''); + } + + static get() { + return { + logLevel: seal.ext.getOptionConfig(BaseConfig.ext, "日志打印方式") as "永不" | "简短" | "详细" | "调试", + timeout: seal.ext.getIntConfig(BaseConfig.ext, "请求超时时限/ms") + } + } +} \ No newline at end of file diff --git a/src/config/configs/config_request.ts b/src/config/configs/config_request.ts deleted file mode 100644 index 0a8b11b..0000000 --- a/src/config/configs/config_request.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { ConfigManager } from "./configManager"; - -export class RequestConfig { - static ext: seal.ExtInfo; - - static register() { - RequestConfig.ext = ConfigManager.getExt('aiplugin4'); - - seal.ext.registerStringConfig(RequestConfig.ext, "url地址", "https://api.deepseek.com/v1/chat/completions", ''); - seal.ext.registerStringConfig(RequestConfig.ext, "API Key", "你的API Key", ''); - seal.ext.registerTemplateConfig(RequestConfig.ext, "body", [ - `"model":"deepseek-chat"`, - `"max_tokens":1024`, - `"stop":null`, - `"stream":false`, - `"frequency_penalty":0`, - `"presence_penalty":0`, - `"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"), - timeout: seal.ext.getIntConfig(RequestConfig.ext, "请求超时时限/ms") - } - } -} \ No newline at end of file diff --git a/src/config/configs/log.ts b/src/config/configs/log.ts deleted file mode 100644 index 0df5e52..0000000 --- a/src/config/configs/log.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { ConfigManager } from "../configManager"; - -export class LogConfig { - static ext: seal.ExtInfo; - - static register() { - LogConfig.ext = ConfigManager.getExt('aiplugin4'); - - seal.ext.registerOptionConfig(LogConfig.ext, "日志打印方式", "简短", ["永不", "简短", "详细", "调试"]); - } - - static get() { - return { - logLevel: seal.ext.getOptionConfig(LogConfig.ext, "日志打印方式") as "永不" | "简短" | "详细" | "调试" - } - } -} \ No newline at end of file diff --git a/src/config/configs/model.ts b/src/config/configs/model.ts index 1dc48d7..c28715c 100644 --- a/src/config/configs/model.ts +++ b/src/config/configs/model.ts @@ -1,13 +1,13 @@ import { ChatModel, EmbeddingModel, ImageModel } from "../../agent/model"; import { logger } from "../../logger"; -import { ConfigManager } from "../configManager"; +import { Config } from "../config"; import { CHAT_MODEL_MAP, EMBEDDING_MODEL_MAP, IMAGE_MODEL_MAP } from "../static_config"; export class ModelConfig { static ext: seal.ExtInfo; static register() { - ModelConfig.ext = ConfigManager.getExt('aiplugin4:模型'); + ModelConfig.ext = Config.getExt('aiplugin4:模型'); seal.ext.registerOptionConfig(ModelConfig.ext, "快速选择对话模型", "", getChatModelOptions(), ''); seal.ext.registerStringConfig(ModelConfig.ext, "快速填入对话模型api key", "", ''); diff --git a/src/config/configs/sample.ts b/src/config/configs/sample.ts index 4236c0e..ca775e1 100644 --- a/src/config/configs/sample.ts +++ b/src/config/configs/sample.ts @@ -1,10 +1,10 @@ -import { ConfigManager } from "../configManager"; +import { Config } from "../config"; export class SampleConfig { static ext: seal.ExtInfo; static register() { - SampleConfig.ext = ConfigManager.getExt('aiplugin4:示例'); + SampleConfig.ext = Config.getExt('aiplugin4:示例'); seal.ext.registerBoolConfig(SampleConfig.ext, "是否启用", true, ''); } diff --git a/src/config/static_config.ts b/src/config/static_config.ts index 105ae21..9466317 100644 --- a/src/config/static_config.ts +++ b/src/config/static_config.ts @@ -2,6 +2,8 @@ export const VERSION = "4.12.0"; export const AUTHOR = "baiyu&错误"; export const NAME = "aiplugin4"; +export const CONFIG_CACHE_TTL = 60000; + export const CQ_TYPES_ALLOW = ["at", "image", "reply", "face", "poke"]; export const PRIVILEGE_LEVEL_MAP = { diff --git a/src/image/image.ts b/src/image/image.ts index 0c75e72..81089ef 100644 --- a/src/image/image.ts +++ b/src/image/image.ts @@ -1,4 +1,4 @@ -import { ConfigManager } from "../config/configManager"; +import { Config } from "../config/config"; import { generateId, revive, TypeDescriptor } from "../utils/utils"; import { logger } from "../logger"; import { MessageSegment, parseSpecialTokens } from "../utils/string"; @@ -87,7 +87,7 @@ export class Image { async urlToBase64() { if (this.type !== 'url') return; - const { imageTobase64Url } = ConfigManager.backend; + const { imageTobase64Url } = Config.backend; try { const response = await fetch(`${imageTobase64Url}/image-to-base64`, { method: 'POST', @@ -119,7 +119,7 @@ export class Image { } async imageToText(prompt = '') { - const { defaultPrompt, urlToBase64, maxChars } = ConfigManager.image; + const { defaultPrompt, urlToBase64, maxChars } = Config.image; if (urlToBase64 == '总是' && this.type === 'url') await this.urlToBase64(); @@ -179,7 +179,7 @@ export class ImageManager { if (!this.imageMap.hasOwnProperty(imageId)) { let img = new Image(); try { - const text = ConfigManager.ext.storageGet(`image_${imageId}`); + const text = Config.ext.storageGet(`image_${imageId}`); if (!text) return null; const data = JSON.parse(text || '{}'); img = revive(Image, data); @@ -193,7 +193,7 @@ export class ImageManager { } static saveImage(img: Image) { - ConfigManager.ext.storageSet(`image_${img.imageId}`, JSON.stringify(img)); + Config.ext.storageSet(`image_${img.imageId}`, JSON.stringify(img)); } static getUserAvatar(uid: string): Image { @@ -211,7 +211,7 @@ export class ImageManager { } static get LocalImageList() { - const { localImagePathMap } = ConfigManager.image; + const { localImagePathMap } = Config.image; return Object.keys(localImagePathMap).map(id => this.createLocalImage(id, localImagePathMap[id])); } @@ -233,7 +233,7 @@ ${img.CQCode}`; * @returns */ static async handleImageMessageSegment(ctx: seal.MsgContext, seg: MessageSegment): Promise<{ content: string, images: Image[] }> { - const { receiveImage } = ConfigManager.image; + const { receiveImage } = Config.image; if (!receiveImage || seg.type !== 'image') return { content: '', images: [] }; let content = ''; @@ -243,7 +243,7 @@ ${img.CQCode}`; if (!file) return { content: '', images: [] }; const image = this.createUrlImage(getSessionId(ctx), file); - const { condition } = ConfigManager.image; + const { condition } = Config.image; const fmtCondition = parseInt(seal.format(ctx, `{${condition}}`)); if (fmtCondition === 1) await image.imageToText(); diff --git a/src/index.ts b/src/index.ts index 2324134..14533ff 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ import { AIManager } from "./AI/AI"; import { ToolManager } from "./tool/tool"; -import { ConfigManager } from "./config/configManager"; +import { Config } from "./config/config"; import { triggerConditionMap } from "./tool/tool_trigger"; import { logger } from "./logger"; import { transformTextToArray } from "./utils/string"; @@ -13,13 +13,13 @@ import { CQ_TYPES_ALLOW } from "./config/static_config"; import { registerCmd } from "./cmd/root_cmd"; function main() { - ConfigManager.registerConfig(); + Config.registerConfig(); checkUpdate(); ToolManager.registerTool(); TimerManager.init(); knowledgeService.init(); - const ext = ConfigManager.ext; + const ext = Config.ext; registerCmd(); PrivilegeManager.reviveCmdPriv(); @@ -34,7 +34,7 @@ function main() { //接受非指令消息 ext.onNotCommandReceived = (ctx, msg): void | Promise => { try { - const { disabledInPrivate, globalStandby, triggerRegex, ignoreRegex, triggerCondition } = ConfigManager.received; + const { disabledInPrivate, globalStandby, triggerRegex, ignoreRegex, triggerCondition } = Config.received; if (ctx.isPrivate && disabledInPrivate) { return; } @@ -130,7 +130,7 @@ function main() { ToolManager.cmdArgs = cmdArgs; } - const { allcmd } = ConfigManager.received; + const { allcmd } = Config.received; if (allcmd) { const uid = ctx.player.userId; const gid = ctx.group.groupId; @@ -172,7 +172,7 @@ function main() { ai.tool.listen.resolve?.(message); // 将消息传递给监听工具 - const { allmsg } = ConfigManager.received; + const { allmsg } = Config.received; if (allmsg) { if (message === ai.context.lastReply) { ai.context.lastReply = ''; diff --git a/src/logger.ts b/src/logger.ts index aaa5a22..7ccfd8c 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -1,5 +1,5 @@ import { NAME } from "./config/static_config"; -import { ConfigManager } from "./config/configManager"; +import { Config } from "./config/config"; class Logger { name: string; @@ -8,7 +8,7 @@ class Logger { } handleLog(...data: any[]): string { - const { logLevel } = ConfigManager.log; + const { logLevel } = Config.base; if (logLevel === "永不") { return ''; } else if (logLevel === "简短") { @@ -50,7 +50,7 @@ class Logger { } debug(...data: any[]) { - const { logLevel } = ConfigManager.log; + const { logLevel } = Config.base; if (logLevel !== "调试") return; const s = this.handleLog(...data); if (!s) { diff --git a/src/session/context.ts b/src/session/context.ts index 972b59a..6a822f3 100644 --- a/src/session/context.ts +++ b/src/session/context.ts @@ -1,5 +1,5 @@ import { ToolCall } from "../tool/tool"; -import { ConfigManager } from "../config/configManager"; +import { Config } from "../config/config"; import { Image, ImageManager } from "../image/image"; import { getCtxAndMsg } from "../utils/seal"; import { levenshteinDistance } from "../utils/string"; @@ -85,7 +85,7 @@ export class Context { } async addMessage(ctx: seal.MsgContext, msg: seal.Message, ai: AI, content: string, images: Image[], role: 'user' | 'assistant', msgId: string = '') { - const { isShortMemory, shortMemorySummaryRound } = ConfigManager.memory; + const { isShortMemory, shortMemorySummaryRound } = Config.memory; const messages = this.messages; const now = Math.floor(Date.now() / 1000); @@ -216,7 +216,7 @@ export class Context { } limitMessages() { - const { maxRounds } = ConfigManager.message; + const { maxRounds } = Config.message; const messages = this.messages; let round = 0; for (let i = messages.length - 1; i >= 0; i--) { diff --git a/src/session/group.ts b/src/session/group.ts index b34332e..5e4b474 100644 --- a/src/session/group.ts +++ b/src/session/group.ts @@ -1,4 +1,4 @@ -import { ConfigManager } from "../config/configManager"; +import { Config } from "../config/config"; import { logger } from "../logger"; import { revive, TypeDescriptor } from "../utils/utils"; import { MemoryService } from "./memory"; @@ -35,7 +35,7 @@ export class GroupManager { if (!this.groupMap.hasOwnProperty(groupId)) { let group = new Group(); try { - const data = JSON.parse(ConfigManager.ext.storageGet(`group_${groupId}`) || '{}'); + const data = JSON.parse(Config.ext.storageGet(`group_${groupId}`) || '{}'); group = revive(Group, data); } catch (error) { logger.error(`加载群${groupId}失败: ${error}`); diff --git a/src/session/memory.ts b/src/session/memory.ts index 6cf7837..b0d10e8 100644 --- a/src/session/memory.ts +++ b/src/session/memory.ts @@ -1,4 +1,4 @@ -import { ConfigManager } from "../config/configManager"; +import { Config } from "../config/config"; import { Context } from "./context"; import { cosineSimilarity, generateId, getCommonGroup, getCommonKeyword, getCommonUser, revive, TypeDescriptor } from "../utils/utils"; import { logger } from "../logger"; @@ -133,7 +133,7 @@ export class MemoryItem { } async updateVector() { - const { isMemoryVector, embeddingDimension } = ConfigManager.memory; + const { isMemoryVector, embeddingDimension } = Config.memory; if (isMemoryVector) { logger.info(`更新记忆向量: ${this.id}`); const vector = await getEmbedding(this.content); @@ -235,7 +235,7 @@ export class MemoryService { } limitMemory() { - const { memoryLimit } = ConfigManager.memory; + const { memoryLimit } = Config.memory; const limit = memoryLimit > 0 ? memoryLimit - 1 : 0; // 预留1个位置用于存储最新记忆 if (this.memoryList.length <= limit) return; this.memoryList.map((m) => { @@ -264,7 +264,7 @@ export class MemoryService { if (!this.memoryList.length) return []; const { userIdList: ul, groupIdList: gl, keywords: kws, includeImages, method } = options; - const { isMemoryVector, embeddingDimension } = ConfigManager.memory; + const { isMemoryVector, embeddingDimension } = Config.memory; let qv: number[] = []; if (isMemoryVector && query) { qv = await getEmbedding(query); @@ -330,7 +330,7 @@ export class MemoryService { } async getTopScoreMemoryList(text: string = '', uid: string = '', gid: string = '') { - const { memoryShowNumber } = ConfigManager.memory; + const { memoryShowNumber } = Config.memory; return await this.search(text, { topK: memoryShowNumber, userIdList: uid ? [uid] : [], @@ -353,8 +353,8 @@ export class MemoryService { // wip 和默认配置一起改 buildMemory(sid: string, ml: MemoryItem[]): string { if (ml.length === 0) return ''; - const { showNumber } = ConfigManager.message; - const { memoryShowTemplate, memorySingleShowTemplate } = ConfigManager.memory; + const { showNumber } = Config.message; + const { memoryShowTemplate, memorySingleShowTemplate } = Config.memory; let memoryContent = ''; if (ml.length === 0) { @@ -473,7 +473,7 @@ export class SessionMemoryService extends MemoryService { } limitSummary() { - const { SummaryLimit } = ConfigManager.memory; + const { SummaryLimit } = Config.memory; if (this.summaryList.length > SummaryLimit) { this.summaryList.splice(0, this.summaryList.length - SummaryLimit); } @@ -487,9 +487,9 @@ export class SessionMemoryService extends MemoryService { async updateSummary(ctx: seal.MsgContext, msg: seal.Message, ai: AI) { if (!this.summaryStatus) return; - const { url: chatUrl, apiKey: chatApiKey } = ConfigManager.request; - const { isPrefix, showNumber, showMsgId, showTime } = ConfigManager.message; - const { shortMemorySummaryRound, memoryUrl, memoryApiKey, memoryBodyTemplate, memoryPromptTemplate } = ConfigManager.memory; + const { url: chatUrl, apiKey: chatApiKey } = Config.request; + const { isPrefix, showNumber, showMsgId, showTime } = Config.message; + const { shortMemorySummaryRound, memoryUrl, memoryApiKey, memoryBodyTemplate, memoryPromptTemplate } = Config.memory; const { roleSetting } = getRoleSetting(ctx); @@ -597,18 +597,18 @@ export class KnowledgeService extends MemoryService { } init() { - const data = JSON.parse(ConfigManager.ext.storageGet('knowledge') || '{}'); + const data = JSON.parse(Config.ext.storageGet('knowledge') || '{}'); const ms = revive(MemoryService, data); this.memoryMap = ms.memoryMap; } save() { - ConfigManager.ext.storageSet('knowledge', JSON.stringify(this.memoryMap)); + Config.ext.storageSet('knowledge', JSON.stringify(this.memoryMap)); } // wip 和配置一起改 async updateKnowledgeMemory(roleIndex: number) { - const { knowledgeMemoryStringList } = ConfigManager.memory; + const { knowledgeMemoryStringList } = Config.memory; if (roleIndex < 0 || roleIndex >= knowledgeMemoryStringList.length) return; const s = knowledgeMemoryStringList[roleIndex]; if (!s) return; @@ -661,7 +661,7 @@ export class KnowledgeService extends MemoryService { break; } case '图片': { - const { localImagePathMap } = ConfigManager.image; + const { localImagePathMap } = Config.image; m.images = value.split(/[,,]/).map(id => id.trim()).map(id => { if (localImagePathMap.hasOwnProperty(id)) { @@ -710,8 +710,8 @@ export class KnowledgeService extends MemoryService { // wip buildKnowledgeMemory(memoryList: MemoryItem[]) { - const { showNumber } = ConfigManager.message; - const { knowledgeMemorySingleShowTemplate } = ConfigManager.memory; + const { showNumber } = Config.message; + const { knowledgeMemorySingleShowTemplate } = Config.memory; if (memoryList.length === 0) return ''; let prompt = ''; @@ -739,7 +739,7 @@ export class KnowledgeService extends MemoryService { await this.updateKnowledgeMemory(roleIndex); if (this.memoryIdList.length === 0) return ''; - const { knowledgeMemoryShowNumber } = ConfigManager.memory; + const { knowledgeMemoryShowNumber } = Config.memory; const memoryList = await this.search(text, { topK: knowledgeMemoryShowNumber, userIdList: ui ? [ui] : [], diff --git a/src/session/session.ts b/src/session/session.ts index 681982c..60f95a7 100644 --- a/src/session/session.ts +++ b/src/session/session.ts @@ -1,4 +1,4 @@ -import { ConfigManager } from "../config/configManager"; +import { Config } from "../config/config"; import { logger } from "../logger"; import { ToolCall } from "../tool/tool"; import { revive, TypeDescriptor } from "../utils/utils"; @@ -69,7 +69,7 @@ export class SessionService { if (!this.sessionMap.hasOwnProperty(sessionId)) { let session = new Session(); try { - const data = JSON.parse(ConfigManager.ext.storageGet(`session_${sessionId}`) || '{}'); + const data = JSON.parse(Config.ext.storageGet(`session_${sessionId}`) || '{}'); session = revive(Session, data); } catch (error) { logger.error(`加载会话${sessionId}失败: ${error}`); diff --git a/src/session/user.ts b/src/session/user.ts index e129489..1874569 100644 --- a/src/session/user.ts +++ b/src/session/user.ts @@ -1,4 +1,4 @@ -import { ConfigManager } from "../config/configManager"; +import { Config } from "../config/config"; import { logger } from "../logger"; import { revive, TypeDescriptor } from "../utils/utils"; import { MemoryService } from "./memory"; @@ -29,7 +29,7 @@ export class UserManager { if (!this.userMap.hasOwnProperty(userId)) { let user = new User(); try { - const data = JSON.parse(ConfigManager.ext.storageGet(`user_${userId}`) || '{}'); + const data = JSON.parse(Config.ext.storageGet(`user_${userId}`) || '{}'); user = revive(User, data); } catch (error) { logger.error(`加载用户${userId}失败: ${error}`); diff --git a/src/timer.ts b/src/timer.ts index 787c646..e949cc2 100644 --- a/src/timer.ts +++ b/src/timer.ts @@ -1,4 +1,4 @@ -import { ConfigManager } from "./config/configManager"; +import { Config } from "./config/config"; import { getSessionCtxAndMsg } from "./utils/seal"; import { AI, AIManager } from "./AI/AI"; import { logger } from "./logger"; @@ -37,7 +37,7 @@ export class TimerManager { static getTimerQueue() { try { - const data = JSON.parse(ConfigManager.ext.storageGet(`timerQueue`) || '[]') + const data = JSON.parse(Config.ext.storageGet(`timerQueue`) || '[]') if (!Array.isArray(data)) throw new Error('timerQueue不是数组'); data.forEach((item: any) => { if (!item.hasOwnProperty('sessionId')) return; @@ -50,7 +50,7 @@ export class TimerManager { } static saveTimerQueue() { - ConfigManager.ext.storageSet(`timerQueue`, JSON.stringify(this.timerQueue)); + Config.ext.storageSet(`timerQueue`, JSON.stringify(this.timerQueue)); } static addTargetTimer(ctx: seal.MsgContext, ai: AI, target: number, content: string) { diff --git a/src/tool/tool.ts b/src/tool/tool.ts index e6527e9..c5f3c67 100644 --- a/src/tool/tool.ts +++ b/src/tool/tool.ts @@ -1,4 +1,4 @@ -import { ConfigManager } from "../config/configManager" +import { Config } from "../config/config" import { registerAttr } from "./tool_attr" import { registerBan } from "./tool_ban" import { registerDeck } from "./tool_deck" @@ -159,7 +159,7 @@ export class ToolManager { } constructor() { - const { toolsNotAllow, toolsDefaultClosed } = ConfigManager.tool; + const { toolsNotAllow, toolsDefaultClosed } = Config.tool; this.toolStatus = Object.keys(ToolManager.toolMap).reduce((acc, key) => { acc[key] = !toolsNotAllow.includes(key) && !toolsDefaultClosed.includes(key); return acc; @@ -273,7 +273,7 @@ export class ToolManager { * @returns tool_choice */ static async handleToolCalls(ctx: seal.MsgContext, msg: seal.Message, ai: AI, tool_calls: ToolCall[]): Promise { - const { maxCallCount } = ConfigManager.tool; + const { maxCallCount } = Config.tool; if (tool_calls.length !== 0) { logger.info(`调用函数:`, tool_calls.map((item, i) => { @@ -326,7 +326,7 @@ export class ToolManager { } }): Promise { const name = tool_call.function.name; - if (ConfigManager.tool.toolsNotAllow.includes(name)) { + if (Config.tool.toolsNotAllow.includes(name)) { logger.warning(`调用函数失败:禁止调用的函数:${name}`); await ai.context.addToolMessage(tool_call.id, `调用函数失败:禁止调用的函数:${name}`, []); return "none"; @@ -395,7 +395,7 @@ export class ToolManager { } static async handlePromptToolCall(ctx: seal.MsgContext, msg: seal.Message, ai: AI, tool_call_str: string): Promise { - const { maxCallCount } = ConfigManager.tool; + const { maxCallCount } = Config.tool; ai.tool.toolCallCount++; if (ai.tool.toolCallCount === maxCallCount) { @@ -440,7 +440,7 @@ export class ToolManager { } const name = tool_call.name; - if (ConfigManager.tool.toolsNotAllow.includes(name)) { + if (Config.tool.toolsNotAllow.includes(name)) { logger.warning(`调用函数失败:禁止调用的函数:${name}`); await ai.context.addSystemUserMessage('调用函数返回', `调用函数失败:禁止调用的函数:${name}`, []); return; @@ -488,7 +488,7 @@ export class ToolManager { } reviveToolStauts() { - const { toolsNotAllow, toolsDefaultClosed } = ConfigManager.tool; + const { toolsNotAllow, toolsDefaultClosed } = Config.tool; const toolStatus: { [key: string]: boolean } = {}; for (const k in ToolManager.toolMap) { if (!this.toolStatus.hasOwnProperty(k)) { @@ -533,7 +533,7 @@ export class ToolManager { } getToolsPrompt(ctx: seal.MsgContext): string { - const { toolsPromptTemplate } = ConfigManager.tool; + const { toolsPromptTemplate } = Config.tool; const tools = this.getToolsInfo(ctx.isPrivate ? 'private' : 'group'); if (tools && tools.length > 0) { diff --git a/src/utils/message.ts b/src/utils/message.ts index 38f9c61..3a77468 100644 --- a/src/utils/message.ts +++ b/src/utils/message.ts @@ -1,15 +1,15 @@ import { AI, GroupInfo, UserInfo } from "../AI/AI"; import { Message } from "../session/context"; -import { ConfigManager } from "../config/configManager"; +import { Config } from "../config/config"; import { ToolInfo } from "../tool/tool"; import { fmtDate } from "./string"; import { knowledgeService } from "../session/memory"; export async function buildSystemMessage(ctx: seal.MsgContext, ai: AI): Promise { - const { systemMessageTemplate, isPrefix, showNumber, showMsgId, showTime } = ConfigManager.message; - const { isTool, usePromptEngineering } = ConfigManager.tool; - const { localImagePathMap, receiveImage, condition } = ConfigManager.image; - const { isMemory, isShortMemory } = ConfigManager.memory; + const { systemMessageTemplate, isPrefix, showNumber, showMsgId, showTime } = Config.message; + const { isTool, usePromptEngineering } = Config.tool; + const { localImagePathMap, receiveImage, condition } = Config.image; + const { isMemory, isShortMemory } = Config.memory; // 可发送的图片提示 const sandableImagesPrompt: string = Object.keys(localImagePathMap) @@ -87,7 +87,7 @@ export async function buildSystemMessage(ctx: seal.MsgContext, ai: AI): Promise< } function buildSamplesMessages(ctx: seal.MsgContext): Message[] { - const { samples } = ConfigManager.message; + const { samples } = Config.message; const samplesMessages: Message[] = samples .map((item, index) => { @@ -125,7 +125,7 @@ function buildSamplesMessages(ctx: seal.MsgContext): Message[] { } function buildContextMessages(systemMessage: Message, messages: Message[]): Message[] { - const { insertCount } = ConfigManager.message; + const { insertCount } = Config.message; const contextMessages = messages.slice(); @@ -156,7 +156,7 @@ function buildContextMessages(systemMessage: Message, messages: Message[]): Mess } export async function handleMessages(ctx: seal.MsgContext, ai: AI) { - const { isMerge } = ConfigManager.message; + const { isMerge } = Config.message; const systemMessage = await buildSystemMessage(ctx, ai); const samplesMessages = buildSamplesMessages(ctx); @@ -219,7 +219,7 @@ export async function handleMessages(ctx: seal.MsgContext, ai: AI) { } export function parseBody(template: string[], messages: any[], tools: ToolInfo[], tool_choice: string) { - const { isTool, usePromptEngineering } = ConfigManager.tool; + const { isTool, usePromptEngineering } = Config.tool; const bodyObject: any = {}; for (let i = 0; i < template.length; i++) { @@ -290,7 +290,7 @@ export function parseEmbeddingBody(template: string[], input: string, dimensions } export function buildContent(message: Message): string { - const { isPrefix, showNumber, showMsgId, showTime } = ConfigManager.message; + const { isPrefix, showNumber, showMsgId, showTime } = Config.message; const prefix = (isPrefix && message.name) ? ( message.name.startsWith('_') ? `<|${message.name}|>` : @@ -305,7 +305,7 @@ export function buildContent(message: Message): string { } export function getRoleSetting(ctx: seal.MsgContext) { - const { roleSettingNames, roleSettingTemplate } = ConfigManager.message; + const { roleSettingNames, roleSettingTemplate } = Config.message; // 角色设定 const [roleName, exists] = seal.vars.strGet(ctx, "$gSYSPROMPT"); let roleIndex = 0; diff --git a/src/utils/string.ts b/src/utils/string.ts index c7fe1de..aaa3b69 100644 --- a/src/utils/string.ts +++ b/src/utils/string.ts @@ -1,7 +1,7 @@ import { Context } from "../session/context"; import { Image } from "../image/image"; import { logger } from "../logger"; -import { ConfigManager } from "../config/configManager"; +import { Config } from "../config/config"; import { transformMsgId, transformMsgIdBack } from "./utils"; import { AI } from "../AI/AI"; import { getCtxAndMsg } from "./seal"; @@ -174,7 +174,7 @@ export function transformArrayToText(messageArray: { type: string, data: { [key: } export async function transformArrayToContent(ctx: seal.MsgContext, ai: AI, messageArray: MessageSegment[]): Promise<{ content: string, images: Image[] }> { - const { showNumber, showMsgId } = ConfigManager.message; + const { showNumber, showMsgId } = Config.message; let content = ''; const images: Image[] = []; for (const seg of messageArray) { @@ -287,7 +287,7 @@ async function transformContentToText(ctx: seal.MsgContext, ai: AI, content: str } export async function handleReply(ctx: seal.MsgContext, msg: seal.Message, ai: AI, s: string): Promise<{ contextArray: string[], replyArray: string[], images: Image[] }> { - const { replymsg, isTrim } = ConfigManager.reply; + const { replymsg, isTrim } = Config.reply; // 分离AI臆想出来的多轮对话 const segments = s @@ -337,7 +337,7 @@ export async function handleReply(ctx: seal.MsgContext, msg: seal.Message, ai: A } export function checkRepeat(context: Context, s: string) { - const { stopRepeat, similarityLimit } = ConfigManager.reply; + const { stopRepeat, similarityLimit } = Config.reply; if (!stopRepeat) { return false; @@ -378,7 +378,7 @@ export function checkRepeat(context: Context, s: string) { } function filterString(s: string): { contextArray: string[], replyArray: string[] } { - const { maxChar, filterRegex, filterRegexes, contextTemplates, replyTemplates } = ConfigManager.reply; + const { maxChar, filterRegex, filterRegexes, contextTemplates, replyTemplates } = Config.reply; const contextArray: string[] = []; const replyArray: string[] = []; diff --git a/src/utils/update.ts b/src/utils/update.ts index 75432ae..a2755be 100644 --- a/src/utils/update.ts +++ b/src/utils/update.ts @@ -1,6 +1,6 @@ import { logger } from "../logger"; import { updateInfo } from "../update"; -import { ConfigManager } from "../config/configManager"; +import { Config } from "../config/config"; import { VERSION } from "../config/static_config"; /** @@ -31,11 +31,11 @@ export function compareVersions(version1: string, version2: string): number { } export function checkUpdate() { - const oldVersion = ConfigManager.ext.storageGet("version") || "0.0.0"; + const oldVersion = Config.ext.storageGet("version") || "0.0.0"; try { if (compareVersions(oldVersion, VERSION) < 0) { - ConfigManager.ext.storageSet("version", VERSION); + Config.ext.storageSet("version", VERSION); let info = []; for (const v in updateInfo) { if (compareVersions(oldVersion, v) >= 0) { diff --git a/src/utils/utils.ts b/src/utils/utils.ts index b1a372c..3546839 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -1,5 +1,5 @@ import { logger } from "../logger"; -import { ConfigManager } from "../config/configManager"; +import { Config } from "../config/config"; import { transformTextToArray } from "./string"; import { ALIAS_MAP } from "../config/static_config"; import { netExists, sendGroupMsg, sendPrivateMsg } from "./ob11"; @@ -29,7 +29,7 @@ export async function replyToSender(ctx: seal.MsgContext, msg: seal.Message, ai: return ''; } - const { showMsgId } = ConfigManager.message; + const { showMsgId } = Config.message; if (showMsgId && netExists()) { const rawMessageArray = transformTextToArray(s); const messageArray = rawMessageArray.filter(item => item.type !== 'poke'); From 84dc421b626298df71531dfc8dd26e054c8fa042 Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Sun, 8 Mar 2026 18:29:30 +0800 Subject: [PATCH 17/33] =?UTF-8?q?=E7=BB=A7=E7=BB=AD=E9=87=8D=E6=9E=84confi?= =?UTF-8?q?g?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/agent/stream.ts | 6 ++--- src/config/config.ts | 10 ++++--- src/config/configs/backend.ts | 12 ++++----- src/config/configs/base.ts | 8 ++++-- src/config/configs/config_image.ts | 4 +-- src/config/configs/config_memory.ts | 2 +- src/config/configs/config_message.ts | 2 +- src/config/configs/config_received.ts | 38 --------------------------- src/config/configs/config_reply.ts | 2 +- src/config/configs/config_tool.ts | 2 +- src/config/configs/received.ts | 27 +++++++++++++++++++ src/config/configs/sample.ts | 2 +- src/config/configs/trigger.ts | 34 ++++++++++++++++++++++++ src/image/image.ts | 2 +- src/logger.ts | 4 +-- 15 files changed, 93 insertions(+), 62 deletions(-) delete mode 100644 src/config/configs/config_received.ts create mode 100644 src/config/configs/received.ts create mode 100644 src/config/configs/trigger.ts diff --git a/src/agent/stream.ts b/src/agent/stream.ts index ffca139..5b9afe4 100644 --- a/src/agent/stream.ts +++ b/src/agent/stream.ts @@ -8,7 +8,7 @@ import { UsageManager } from "./usage"; export class streamService { static async startStream(agent: Agent, sessionId: string): Promise { const { timeout } = Config.request; - const { streamUrl } = Config.backend; + const { STREAM: streamUrl } = Config.backend; const model = ModelManager.getChatModel('chat'); try { const body = model.buildChatBody(agent, sessionId); @@ -64,7 +64,7 @@ export class streamService { } static async pollStream(streamId: string, after: number): Promise<{ status: string, reply: string, nextAfter: number }> { - const { streamUrl } = Config.backend; + const { STREAM: streamUrl } = Config.backend; try { const response = await fetch(`${streamUrl}/poll?id=${streamId}&after=${after}`, { @@ -107,7 +107,7 @@ export class streamService { } static async endStream(streamId: string): Promise { - const { streamUrl } = Config.backend; + const { STREAM: streamUrl } = Config.backend; try { const response = await fetch(`${streamUrl}/end?id=${streamId}`, { diff --git a/src/config/config.ts b/src/config/config.ts index dcbc275..9cf40b8 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -4,11 +4,15 @@ import { AUTHOR, CONFIG_CACHE_TTL, NAME, VERSION } from "./static_config"; import { BaseConfig } from "./configs/base"; import { ModelConfig } from "./configs/model"; import { BackendConfig } from "./configs/backend"; +import { ReceivedConfig } from "./configs/received"; +import { TriggerConfig } from "./configs/trigger"; const configMap = { - 'base': BaseConfig, - 'model': ModelConfig, - 'backend': BackendConfig + base: BaseConfig, + model: ModelConfig, + backend: BackendConfig, + received: ReceivedConfig, + trigger: TriggerConfig, } as const; type ConfigMap = typeof configMap; diff --git a/src/config/configs/backend.ts b/src/config/configs/backend.ts index 5d70b81..6c59d78 100644 --- a/src/config/configs/backend.ts +++ b/src/config/configs/backend.ts @@ -16,12 +16,12 @@ export class BackendConfig { static get() { return { - streamUrl: seal.ext.getStringConfig(BackendConfig.ext, "流式输出"), - imageTobase64Url: seal.ext.getStringConfig(BackendConfig.ext, "图片转base64"), - webSearchUrl: seal.ext.getStringConfig(BackendConfig.ext, "联网搜索"), - webReadUrl: seal.ext.getStringConfig(BackendConfig.ext, "网页读取"), - usageChartUrl: seal.ext.getStringConfig(BackendConfig.ext, "用量图表"), - renderUrl: seal.ext.getStringConfig(BackendConfig.ext, "md和html图片渲染") + STREAM: seal.ext.getStringConfig(BackendConfig.ext, "流式输出"), + IMAGE_TO_BASE64: seal.ext.getStringConfig(BackendConfig.ext, "图片转base64"), + WEB_SEARCH: seal.ext.getStringConfig(BackendConfig.ext, "联网搜索"), + WEB_READ: seal.ext.getStringConfig(BackendConfig.ext, "网页读取"), + USAGE_CHART: seal.ext.getStringConfig(BackendConfig.ext, "用量图表"), + RENDER: seal.ext.getStringConfig(BackendConfig.ext, "md和html图片渲染") } } } diff --git a/src/config/configs/base.ts b/src/config/configs/base.ts index 001f70a..b836dba 100644 --- a/src/config/configs/base.ts +++ b/src/config/configs/base.ts @@ -8,12 +8,16 @@ export class BaseConfig { seal.ext.registerOptionConfig(BaseConfig.ext, "日志打印方式", "简短", ["永不", "简短", "详细", "调试"]); seal.ext.registerIntConfig(BaseConfig.ext, "请求超时时限/ms", 180000, ''); + seal.ext.registerStringConfig(BaseConfig.ext, "海豹核心全局路径", "/root/sealdice", ''); + seal.ext.registerBoolConfig(BaseConfig.ext, "是否开启全局待机", false, "开启后,全局的ai将进入待机状态,可能造成性能问题"); } static get() { return { - logLevel: seal.ext.getOptionConfig(BaseConfig.ext, "日志打印方式") as "永不" | "简短" | "详细" | "调试", - timeout: seal.ext.getIntConfig(BaseConfig.ext, "请求超时时限/ms") + LOG_LEVEL: seal.ext.getOptionConfig(BaseConfig.ext, "日志打印方式") as "永不" | "简短" | "详细" | "调试", + TIMEOUT: seal.ext.getIntConfig(BaseConfig.ext, "请求超时时限/ms"), + SEALDICE_PATH: seal.ext.getStringConfig(BaseConfig.ext, "海豹核心全局路径"), + GLOBAL_STANDBY: seal.ext.getBoolConfig(BaseConfig.ext, "是否开启全局待机"), } } } \ No newline at end of file diff --git a/src/config/configs/config_image.ts b/src/config/configs/config_image.ts index 3ad1e4a..8e33b6a 100644 --- a/src/config/configs/config_image.ts +++ b/src/config/configs/config_image.ts @@ -1,10 +1,10 @@ -import { ConfigManager } from "./configManager"; +import { Config } from "../config"; export class ImageConfig { static ext: seal.ExtInfo; static register() { - ImageConfig.ext = ConfigManager.getExt('aiplugin4_5:图片'); + ImageConfig.ext = Config.getExt('aiplugin4_5:图片'); seal.ext.registerTemplateConfig(ImageConfig.ext, "本地图片路径", ['data/images/sealdice.png'], "如不需要可以不填写,修改完需要重载js"); seal.ext.registerBoolConfig(ImageConfig.ext, "是否接收图片", true, ""); diff --git a/src/config/configs/config_memory.ts b/src/config/configs/config_memory.ts index a6d0d45..0f7b028 100644 --- a/src/config/configs/config_memory.ts +++ b/src/config/configs/config_memory.ts @@ -1,4 +1,4 @@ -import { ConfigManager } from "./configManager"; +import { Config } from "../config"; export class MemoryConfig { static ext: seal.ExtInfo; diff --git a/src/config/configs/config_message.ts b/src/config/configs/config_message.ts index 2f122e5..890b845 100644 --- a/src/config/configs/config_message.ts +++ b/src/config/configs/config_message.ts @@ -1,4 +1,4 @@ -import { ConfigManager } from "./configManager"; +import { Config } from "../config"; export class MessageConfig { static ext: seal.ExtInfo; diff --git a/src/config/configs/config_received.ts b/src/config/configs/config_received.ts deleted file mode 100644 index 4fc53e5..0000000 --- a/src/config/configs/config_received.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { ConfigManager } from "./configManager"; - -export class ReceivedConfig { - static ext: seal.ExtInfo; - - static register() { - ReceivedConfig.ext = ConfigManager.getExt('aiplugin4_3:消息接收与触发'); - - seal.ext.registerBoolConfig(ReceivedConfig.ext, "是否录入指令消息", false, ""); - seal.ext.registerBoolConfig(ReceivedConfig.ext, "是否录入所有骰子发送的消息", false, ""); - seal.ext.registerBoolConfig(ReceivedConfig.ext, "私聊内不可用", false, ""); - seal.ext.registerBoolConfig(ReceivedConfig.ext, "是否开启全局待机", false, "开启后,全局的ai将进入待机状态,可能造成性能问题"); - seal.ext.registerStringConfig(ReceivedConfig.ext, "非指令触发需要满足的条件", '1', "使用豹语表达式,例如:$t群号_RAW=='2001'"); - seal.ext.registerTemplateConfig(ReceivedConfig.ext, "非指令消息触发正则表达式", [ - "\\[CQ:at,qq=748569109\\]", - "^正确.*[。?!?!]$" - ], ""); - seal.ext.registerTemplateConfig(ReceivedConfig.ext, "非指令消息忽略正则表达式", [ - "^忽略这句话$" - ], "匹配的消息不会接收录入上下文"); - seal.ext.registerIntConfig(ReceivedConfig.ext, "触发次数上限", 3, ""); - seal.ext.registerIntConfig(ReceivedConfig.ext, "触发次数补充间隔/s", 3, ""); - } - - static get() { - return { - allcmd: seal.ext.getBoolConfig(ReceivedConfig.ext, "是否录入指令消息"), - allmsg: seal.ext.getBoolConfig(ReceivedConfig.ext, "是否录入所有骰子发送的消息"), - disabledInPrivate: seal.ext.getBoolConfig(ReceivedConfig.ext, "私聊内不可用"), - globalStandby: seal.ext.getBoolConfig(ReceivedConfig.ext, "是否开启全局待机"), - triggerRegex: ConfigManager.getRegexConfig(ReceivedConfig.ext, "非指令消息触发正则表达式"), - ignoreRegex: ConfigManager.getRegexConfig(ReceivedConfig.ext, "非指令消息忽略正则表达式"), - triggerCondition: seal.ext.getStringConfig(ReceivedConfig.ext, "非指令触发需要满足的条件"), - bucketLimit: seal.ext.getIntConfig(ReceivedConfig.ext, "触发次数上限"), - fillInterval: seal.ext.getIntConfig(ReceivedConfig.ext, "触发次数补充间隔/s") - } - } - } \ No newline at end of file diff --git a/src/config/configs/config_reply.ts b/src/config/configs/config_reply.ts index 4e37eb8..9341e14 100644 --- a/src/config/configs/config_reply.ts +++ b/src/config/configs/config_reply.ts @@ -1,4 +1,4 @@ -import { ConfigManager } from "./configManager"; +import { Config } from "../config"; export class ReplyConfig { static ext: seal.ExtInfo; diff --git a/src/config/configs/config_tool.ts b/src/config/configs/config_tool.ts index aa7004b..971dac8 100644 --- a/src/config/configs/config_tool.ts +++ b/src/config/configs/config_tool.ts @@ -1,4 +1,4 @@ -import { ConfigManager } from "./configManager"; +import { Config } from "../config"; export class ToolConfig { static ext: seal.ExtInfo; diff --git a/src/config/configs/received.ts b/src/config/configs/received.ts new file mode 100644 index 0000000..615551d --- /dev/null +++ b/src/config/configs/received.ts @@ -0,0 +1,27 @@ +import { Config, getRegexConfig } from "../config"; + +export class ReceivedConfig { + static ext: seal.ExtInfo; + + static register() { + ReceivedConfig.ext = Config.getExt('aiplugin4:消息接收'); + + seal.ext.registerBoolConfig(ReceivedConfig.ext, "接收指令消息", false, ""); + seal.ext.registerBoolConfig(ReceivedConfig.ext, "接收骰子发送的消息", false, ""); + seal.ext.registerBoolConfig(ReceivedConfig.ext, "忽略私聊消息", false, ""); + seal.ext.registerStringConfig(ReceivedConfig.ext, "忽略消息豹语条件", '1', "使用豹语表达式,例如:$t群号_RAW=='2001'"); + seal.ext.registerTemplateConfig(ReceivedConfig.ext, "忽略消息正则表达式", [ + "^忽略这句话$" + ], "匹配的消息将被忽略"); + } + + static get() { + return { + RECEIVE_CMD: seal.ext.getBoolConfig(ReceivedConfig.ext, "接收指令消息"), + RECEIVE_MSG_BY_BOT: seal.ext.getBoolConfig(ReceivedConfig.ext, "接收骰子发送的消息"), + IGNORE_PRIVATE: seal.ext.getBoolConfig(ReceivedConfig.ext, "忽略私聊消息"), + IGNORE_CONDITION: seal.ext.getStringConfig(ReceivedConfig.ext, "忽略消息豹语条件"), + IGNORE_REGEX: getRegexConfig(ReceivedConfig.ext, "忽略消息正则表达式") + } + } +} \ No newline at end of file diff --git a/src/config/configs/sample.ts b/src/config/configs/sample.ts index ca775e1..7d33a27 100644 --- a/src/config/configs/sample.ts +++ b/src/config/configs/sample.ts @@ -11,7 +11,7 @@ export class SampleConfig { static get() { return { - enabled: seal.ext.getBoolConfig(SampleConfig.ext, "是否启用"), + ENABLED: seal.ext.getBoolConfig(SampleConfig.ext, "是否启用"), } } } \ No newline at end of file diff --git a/src/config/configs/trigger.ts b/src/config/configs/trigger.ts new file mode 100644 index 0000000..307a2b0 --- /dev/null +++ b/src/config/configs/trigger.ts @@ -0,0 +1,34 @@ +import { Config, getRegexConfig } from "../config"; + +export class TriggerConfig { + static ext: seal.ExtInfo; + + static register() { + TriggerConfig.ext = Config.getExt('aiplugin4:消息触发'); + + seal.ext.registerIntConfig(TriggerConfig.ext, "默认计数器", 10, ""); + seal.ext.registerFloatConfig(TriggerConfig.ext, "默认计时器/s", 60, ""); + seal.ext.registerFloatConfig(TriggerConfig.ext, "默认概率/%", 10, ""); + seal.ext.registerStringConfig(TriggerConfig.ext, "默认触发活跃时间", "10:00-20:00-5", ""); + seal.ext.registerFloatConfig(TriggerConfig.ext, "默认向量相似度", 0.8, ""); + seal.ext.registerTemplateConfig(TriggerConfig.ext, "触发正则表达式", [ + "\\[CQ:at,qq=748569109\\]", + "^正确.*[。?!?!]$" + ], ""); + seal.ext.registerIntConfig(TriggerConfig.ext, "触发次数上限", 3, ""); + seal.ext.registerIntConfig(TriggerConfig.ext, "触发次数补充间隔/s", 3, ""); + } + + static get() { + return { + COUNTER: seal.ext.getIntConfig(TriggerConfig.ext, "默认计数器"), + TIMER: seal.ext.getFloatConfig(TriggerConfig.ext, "默认计时器/s"), + PROBABILITY: seal.ext.getFloatConfig(TriggerConfig.ext, "默认概率/%"), + ACTIVE_TIME: seal.ext.getStringConfig(TriggerConfig.ext, "默认触发活跃时间"), + VECTOR_SIMILARITY: seal.ext.getFloatConfig(TriggerConfig.ext, "默认向量相似度"), + TRIGGER_REGEX: getRegexConfig(TriggerConfig.ext, "触发正则表达式"), + BUCKET_LIMIT: seal.ext.getIntConfig(TriggerConfig.ext, "触发次数上限"), + FILL_INTERVAL: seal.ext.getIntConfig(TriggerConfig.ext, "触发次数补充间隔/s") + } + } +} \ No newline at end of file diff --git a/src/image/image.ts b/src/image/image.ts index 81089ef..e86592d 100644 --- a/src/image/image.ts +++ b/src/image/image.ts @@ -87,7 +87,7 @@ export class Image { async urlToBase64() { if (this.type !== 'url') return; - const { imageTobase64Url } = Config.backend; + const { IMAGE_TO_BASE64: imageTobase64Url } = Config.backend; try { const response = await fetch(`${imageTobase64Url}/image-to-base64`, { method: 'POST', diff --git a/src/logger.ts b/src/logger.ts index 7ccfd8c..10861ef 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -8,7 +8,7 @@ class Logger { } handleLog(...data: any[]): string { - const { logLevel } = Config.base; + const { LOG_LEVEL: logLevel } = Config.base; if (logLevel === "永不") { return ''; } else if (logLevel === "简短") { @@ -50,7 +50,7 @@ class Logger { } debug(...data: any[]) { - const { logLevel } = Config.base; + const { LOG_LEVEL: logLevel } = Config.base; if (logLevel !== "调试") return; const s = this.handleLog(...data); if (!s) { From ec485d98cb6e44a5ed7ad0112289b01c83003b05 Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Sun, 8 Mar 2026 22:45:53 +0800 Subject: [PATCH 18/33] =?UTF-8?q?=E6=94=B9=E6=9D=A5=E6=94=B9=E5=8E=BB?= =?UTF-8?q?=E6=88=91=E8=87=AA=E5=B7=B1=E9=83=BD=E6=99=95=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/agent/AI.ts | 14 +-- src/agent/agents/compress_agent.ts | 14 ++- src/agent/agents/root_agent.ts | 14 ++- src/agent/agents/samples.ts | 14 ++- src/agent/model.ts | 18 ++-- src/cmd/sub_cmd/image.ts | 4 +- src/cmd/sub_cmd/tool.ts | 12 +-- src/image/image.ts | 4 +- src/index.ts | 8 +- src/session/context.ts | 32 +------ src/session/memory.ts | 4 +- src/session/types.ts | 29 ++++++ src/tool/tool.ts | 142 +++-------------------------- src/tool/tools/sample.ts | 46 +++++----- src/tool/types.ts | 94 +++++++++++++++++++ 15 files changed, 212 insertions(+), 237 deletions(-) create mode 100644 src/session/types.ts create mode 100644 src/tool/types.ts diff --git a/src/agent/AI.ts b/src/agent/AI.ts index 1d80cd3..2feeb53 100644 --- a/src/agent/AI.ts +++ b/src/agent/AI.ts @@ -5,7 +5,7 @@ import { endStream, pollStream, sendChatRequest, startStream } from "../agent/se import { Context } from "./context"; import { MemoryManager } from "./memory"; import { handleMessages, parseBody } from "../utils/message"; -import { ToolManager } from "../tool/tool"; +import { ToolService } from "../tool/tool"; import { logger } from "../logger"; import { checkRepeat, handleReply, MessageSegment, transformArrayToContent } from "../utils/string"; import { TimerManager } from "../timer"; @@ -55,7 +55,7 @@ export class AI { static validKeys: (keyof AI)[] = ['context', 'tool', 'memory', 'imageManager', 'setting']; id: string; context: Context; - tool: ToolManager; + tool: ToolService; memory: MemoryManager; imageManager: ImageManager; setting: Setting; @@ -75,7 +75,7 @@ export class AI { constructor() { this.id = ''; this.context = new Context(); - this.tool = new ToolManager(); + this.tool = new ToolService(); this.memory = new MemoryManager(); this.imageManager = new ImageManager(); this.setting = new Setting(); @@ -190,7 +190,7 @@ export class AI { await this.context.addMessage(ctx, msg, this, match[0], [], "assistant", ''); try { - await ToolManager.handlePromptToolCall(ctx, msg, this, match[1]); + await ToolService.handlePromptToolCall(ctx, msg, this, match[1]); await this.chat(ctx, msg, '函数回调触发'); } catch (e) { logger.error(`在handlePromptToolCall中出错:`, e.message); @@ -206,7 +206,7 @@ export class AI { this.context.addToolCallsMessage(tool_calls); try { - tool_choice = await ToolManager.handleToolCalls(ctx, msg, this, tool_calls); + tool_choice = await ToolService.handleToolCalls(ctx, msg, this, tool_calls); await this.chat(ctx, msg, '函数回调触发', tool_choice); } catch (e) { logger.error(`在handleToolCalls中出错:`, e.message); @@ -298,7 +298,7 @@ export class AI { await this.context.addMessage(ctx, msg, this, match[0], [], "assistant", ''); try { - await ToolManager.handlePromptToolCall(ctx, msg, this, match[1]); + await ToolService.handlePromptToolCall(ctx, msg, this, match[1]); } catch (e) { logger.error(`在handlePromptToolCall中出错:`, e.message); return; @@ -443,7 +443,7 @@ export class AIManager { return context; } if (key === "tool") { - const tm = revive(ToolManager, value); + const tm = revive(ToolService, value); tm.reviveToolStauts(); return tm; } diff --git a/src/agent/agents/compress_agent.ts b/src/agent/agents/compress_agent.ts index 8d0c210..d47277a 100644 --- a/src/agent/agents/compress_agent.ts +++ b/src/agent/agents/compress_agent.ts @@ -1,10 +1,8 @@ import { AgentManager } from "../agent"; -export function initCompressAgent() { - const agent = AgentManager.agentMap["compress_agent"]; - agent.name = "compress_agent"; - agent.description = "压缩智能体"; - agent.instruction = "你是一个压缩智能体,你可以压缩文本。"; - AgentManager.agentMap[agent.name] = agent; - AgentManager.saveAgent(agent); -} \ No newline at end of file +const compressAgent = AgentManager.getAgent("compress_agent"); +compressAgent.name = "compress_agent"; +compressAgent.description = "压缩智能体"; +compressAgent.instruction = "你是一个压缩智能体,你可以压缩文本。"; +AgentManager.saveAgent(compressAgent); +export { compressAgent }; diff --git a/src/agent/agents/root_agent.ts b/src/agent/agents/root_agent.ts index 91b13a9..efd4a92 100644 --- a/src/agent/agents/root_agent.ts +++ b/src/agent/agents/root_agent.ts @@ -1,10 +1,8 @@ import { AgentManager } from "../agent"; -export function initRootAgent() { - const agent = AgentManager.agentMap["root_agent"]; - agent.name = "root_agent"; - agent.description = "根智能体"; - agent.instruction = "你是一个根智能体,你可以调用其他智能体。"; - AgentManager.agentMap[agent.name] = agent; - AgentManager.saveAgent(agent); -} \ No newline at end of file +const rootAgent = AgentManager.getAgent("root_agent"); +rootAgent.name = "root_agent"; +rootAgent.description = "根智能体"; +rootAgent.instruction = "你是一个根智能体,你可以调用其他智能体。"; +AgentManager.saveAgent(rootAgent); +export { rootAgent }; \ No newline at end of file diff --git a/src/agent/agents/samples.ts b/src/agent/agents/samples.ts index 22147b1..32eb356 100644 --- a/src/agent/agents/samples.ts +++ b/src/agent/agents/samples.ts @@ -1,10 +1,8 @@ import { AgentManager } from "../agent"; -export function initSampleAgent() { - const agent = AgentManager.agentMap["sample_agent"]; - agent.name = "sample_agent"; - agent.description = "示例智能体"; - agent.instruction = "你是一个示例智能体。"; - AgentManager.agentMap[agent.name] = agent; - AgentManager.saveAgent(agent); -} \ No newline at end of file +const sampleAgent = AgentManager.getAgent("sample_agent"); +sampleAgent.name = "sample_agent"; +sampleAgent.description = "示例智能体"; +sampleAgent.instruction = "你是一个示例智能体。"; +AgentManager.saveAgent(sampleAgent); +export { sampleAgent }; diff --git a/src/agent/model.ts b/src/agent/model.ts index 536e1cb..bb9ddb7 100644 --- a/src/agent/model.ts +++ b/src/agent/model.ts @@ -1,6 +1,6 @@ import { Config } from "../config/config"; import { logger } from "../logger"; -import { ToolCall } from "../tool/tool"; +import { ToolCall } from "../tool/types"; import { withTimeout } from "../utils/utils"; import { Agent } from "./agent"; import { UsageManager } from "./usage"; @@ -59,11 +59,11 @@ export class ChatModel extends BaseModel { } async callChat(agent: Agent, sessionId: string): Promise<{ content: string, tool_calls: ToolCall[] }> { - const { timeout } = Config.request; + const { TIMEOUT } = Config.base; try { const time = Date.now(); - const data = await withTimeout(() => fetchData(this.url, this.apiKey, this.buildChatBody(agent, sessionId)), timeout); + const data = await withTimeout(() => fetchData(this.url, this.apiKey, this.buildChatBody(agent, sessionId)), TIMEOUT); if (data.choices && data.choices.length > 0) { UsageManager.updateUsage(data.model, data.usage); @@ -107,7 +107,7 @@ export class ImageModel extends BaseModel { } async callITT(src: string, prompt = ''): Promise { - const { timeout } = Config.request; + const { TIMEOUT } = Config.base; try { const time = Date.now(); @@ -122,7 +122,7 @@ export class ImageModel extends BaseModel { "text": prompt }] }] - })), timeout); + })), TIMEOUT); if (data.choices && data.choices.length > 0) { UsageManager.updateUsage(data.model, data.usage); @@ -143,11 +143,11 @@ export class ImageModel extends BaseModel { } async callChat(agent: Agent, sessionId: string): Promise<{ content: string, tool_calls: ToolCall[] }> { - const { timeout } = Config.request; + const { TIMEOUT } = Config.base; try { const time = Date.now(); - const data = await withTimeout(() => fetchData(this.url, this.apiKey, this.buildChatBody(agent, sessionId)), timeout); + const data = await withTimeout(() => fetchData(this.url, this.apiKey, this.buildChatBody(agent, sessionId)), TIMEOUT); if (data.choices && data.choices.length > 0) { UsageManager.updateUsage(data.model, data.usage); @@ -191,7 +191,7 @@ export class EmbeddingModel extends BaseModel { return []; } - const { timeout } = Config.request; + const { TIMEOUT } = Config.base; if (EmbeddingModel.vectorCache.text === text && EmbeddingModel.vectorCache.vector.length === this.body.dimensions) { const v = EmbeddingModel.vectorCache.vector; @@ -203,7 +203,7 @@ export class EmbeddingModel extends BaseModel { const data = await withTimeout(() => fetchData(this.url, this.apiKey, this.buildBody({ input: text - })), timeout); + })), TIMEOUT); if (data.data && data.data.length > 0) { UsageManager.updateUsage(data.model, data.usage); diff --git a/src/cmd/sub_cmd/image.ts b/src/cmd/sub_cmd/image.ts index 57a72c6..b985b81 100644 --- a/src/cmd/sub_cmd/image.ts +++ b/src/cmd/sub_cmd/image.ts @@ -1,5 +1,5 @@ import { AIManager } from "../../AI/AI"; -import { ImageManager } from "../../image/image"; +import { ImageService } from "../../image/image"; import { aliasToCmd } from "../../utils/utils"; import { transformArrayToContent, transformTextToArray } from "../../utils/string"; import { I, M, U } from "../privilege"; @@ -41,7 +41,7 @@ export function registerCmdImage() { return ret; } case 'local': { - seal.replyToSender(ctx, msg, ImageManager.getLocalImageListText(page) || '暂无本地图片'); + seal.replyToSender(ctx, msg, ImageService.getLocalImageListText(page) || '暂无本地图片'); return ret; } default: { diff --git a/src/cmd/sub_cmd/tool.ts b/src/cmd/sub_cmd/tool.ts index 68e526d..4459467 100644 --- a/src/cmd/sub_cmd/tool.ts +++ b/src/cmd/sub_cmd/tool.ts @@ -1,7 +1,7 @@ import { AIManager } from "../../AI/AI"; import { Config } from "../../config/config"; import { logger } from "../../logger"; -import { ToolManager } from "../../tool/tool"; +import { ToolService } from "../../tool/tool"; import { aliasToCmd } from "../../utils/utils"; import { I, M, U } from "../privilege"; import { SubCmd, SubCmdContext } from "../root_cmd"; @@ -72,12 +72,12 @@ export function registerCmdTool() { return ret; } - if (!ToolManager.toolMap.hasOwnProperty(val3)) { + if (!ToolService.toolMap.hasOwnProperty(val3)) { seal.replyToSender(ctx, msg, '没有这个工具函数'); return ret; } - const tool = ToolManager.toolMap[val3]; + const tool = ToolService.toolMap[val3]; const s = `${tool.toolInfo.function.name} 描述:${tool.toolInfo.function.description} @@ -95,12 +95,12 @@ export function registerCmdTool() { seal.replyToSender(ctx, msg, `调用函数缺少工具函数名`); return ret; } - if (!ToolManager.toolMap.hasOwnProperty(val3)) { + if (!ToolService.toolMap.hasOwnProperty(val3)) { seal.replyToSender(ctx, msg, `调用函数失败:未注册的函数:${val3}`); return ret; } - const tool = ToolManager.toolMap[val3]; - if (tool.ExtCmdInfo.extName !== '' && ToolManager.cmdArgs == null) { + const tool = ToolService.toolMap[val3]; + if (tool.ExtCmdInfo.extName !== '' && ToolService.cmdArgs == null) { seal.replyToSender(ctx, msg, `暂时无法调用函数,请先使用 .r 指令`); return ret; } diff --git a/src/image/image.ts b/src/image/image.ts index e86592d..08f4fec 100644 --- a/src/image/image.ts +++ b/src/image/image.ts @@ -115,7 +115,7 @@ export class Image { logger.error("在imageUrlToBase64中请求出错:", error); } - ImageManager.saveImage(this); + ImageService.saveImage(this); } async imageToText(prompt = '') { @@ -141,7 +141,7 @@ export class Image { } } -export class ImageManager { +export class ImageService { static imageMap: { [key: string]: Image } = {}; static generateImageId(): string { diff --git a/src/index.ts b/src/index.ts index 14533ff..fe16c36 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ import { AIManager } from "./AI/AI"; -import { ToolManager } from "./tool/tool"; +import { ToolService } from "./tool/tool"; import { Config } from "./config/config"; import { triggerConditionMap } from "./tool/tool_trigger"; import { logger } from "./logger"; @@ -15,7 +15,7 @@ import { registerCmd } from "./cmd/root_cmd"; function main() { Config.registerConfig(); checkUpdate(); - ToolManager.registerTool(); + ToolService.registerTool(); TimerManager.init(); knowledgeService.init(); @@ -126,8 +126,8 @@ function main() { //接受的指令 ext.onCommandReceived = (ctx, msg, cmdArgs) => { try { - if (ToolManager.cmdArgs === null) { - ToolManager.cmdArgs = cmdArgs; + if (ToolService.cmdArgs === null) { + ToolService.cmdArgs = cmdArgs; } const { allcmd } = Config.received; diff --git a/src/session/context.ts b/src/session/context.ts index 6a822f3..beedecd 100644 --- a/src/session/context.ts +++ b/src/session/context.ts @@ -1,39 +1,11 @@ -import { ToolCall } from "../tool/tool"; import { Config } from "../config/config"; -import { Image, ImageManager } from "../image/image"; +import { Image, ImageService } from "../image/image"; import { getCtxAndMsg } from "../utils/seal"; import { levenshteinDistance } from "../utils/string"; import { logger } from "../logger"; import { netExists, getFriendList, getGroupList, getGroupMemberInfo, getGroupMemberList, getStrangerInfo } from "../utils/ob11"; import { TypeDescriptor } from "../utils/utils"; - -export interface BaseMessageItem { - time: number; // 秒 - text: string; -} - -export interface UserMessageItem extends BaseMessageItem { - userId: string; - messageId: string; -} - -export interface AssistantMessageItem extends BaseMessageItem { - messageId: string; -} - -export interface SystemUserMessageItem extends BaseMessageItem { - tip: string; -} - -export interface ToolCallsMessageItem extends BaseMessageItem { - tool_calls: ToolCall[]; -} - -export interface ToolCallbackMessageItem extends BaseMessageItem { - tool_call_id: string; -} - -export type MessageItem = UserMessageItem | AssistantMessageItem | SystemUserMessageItem | ToolCallsMessageItem | ToolCallbackMessageItem; +import { MessageItem } from "./types"; export class Context { static validKeysMap: { [key in keyof Context]?: TypeDescriptor } = { diff --git a/src/session/memory.ts b/src/session/memory.ts index b0d10e8..cfc19c0 100644 --- a/src/session/memory.ts +++ b/src/session/memory.ts @@ -4,7 +4,7 @@ import { cosineSimilarity, generateId, getCommonGroup, getCommonKeyword, getComm import { logger } from "../logger"; import { fetchData, getEmbedding } from "../agent/service"; import { buildContent, getRoleSetting, parseBody } from "../utils/message"; -import { ToolManager } from "../tool/tool"; +import { ToolService } from "../tool/tool"; import { fmtDate } from "../utils/string"; import { Image } from "../image/image"; @@ -582,7 +582,7 @@ export class SessionMemoryService extends MemoryService { this.limitShortMemory(); memoryData.memories.forEach(m => { - ToolManager.toolMap["add_memory"].solve(ctx, msg, ai, m); + ToolService.toolMap["add_memory"].solve(ctx, msg, ai, m); }); } } catch (e) { diff --git a/src/session/types.ts b/src/session/types.ts new file mode 100644 index 0000000..a6367da --- /dev/null +++ b/src/session/types.ts @@ -0,0 +1,29 @@ +import { ToolCall } from "../tool/types"; + +export interface BaseMessageItem { + time: number; // 秒 + text: string; +} + +export interface UserMessageItem extends BaseMessageItem { + userId: string; + messageId: string; +} + +export interface AssistantMessageItem extends BaseMessageItem { + messageId: string; +} + +export interface SystemUserMessageItem extends BaseMessageItem { + tip: string; +} + +export interface ToolCallsMessageItem extends BaseMessageItem { + tool_calls: ToolCall[]; +} + +export interface ToolCallbackMessageItem extends BaseMessageItem { + tool_call_id: string; +} + +export type MessageItem = UserMessageItem | AssistantMessageItem | SystemUserMessageItem | ToolCallsMessageItem | ToolCallbackMessageItem; \ No newline at end of file diff --git a/src/tool/tool.ts b/src/tool/tool.ts index c5f3c67..c676fd5 100644 --- a/src/tool/tool.ts +++ b/src/tool/tool.ts @@ -1,132 +1,19 @@ import { Config } from "../config/config" -import { registerAttr } from "./tool_attr" -import { registerBan } from "./tool_ban" -import { registerDeck } from "./tool_deck" -import { registerImage } from "./tool_image" -import { registerJrrp } from "./tool_jrrp" -import { registerMemory } from "./tool_memory" -import { registerModu } from "./tool_modu" -import { registerRename } from "./tool_rename" -import { registerRollCheck } from "./tool_roll_check" -import { registerTime } from "./tool_time" -import { registerRecord } from "./tool_voice" -import { registerWeb } from "./tool_web" -import { registerGroupSign } from "./tool_group_sign" -import { registerGetPersonInfo } from "./tool_person_info" -import { registerMessage } from "./tool_message" -import { registerEssenceMsg } from "./tool_essence_msg" -import { registerContext } from "./tool_context" -import { registerQQList } from "./tool_qq_list" -import { registerSetTrigger } from "./tool_trigger" -import { registerMusicPlay } from "./tool_music" -import { registerMeme } from "./tool_meme" -import { registerRender } from "./tool_render" import { logger } from "../logger" -import { Image } from "../image/image"; import { fixJsonString } from "../utils/string"; import { Agent } from "../agent/agent" +import { ExtCmdInfo, ToolInfo } from "./types"; -export interface ToolInfoString { - type: "string"; - description?: string; - enum?: string[]; - minLength?: number; - maxLength?: number; - pattern?: string; - format?: "date-time" | "email" | "uri" | "uuid" | "hostname" | "ipv4" | "ipv6"; -} - -export interface ToolInfoNumber { - type: "number"; - description?: string; - minimum?: number; - maximum?: number; - exclusiveMinimum?: number; - exclusiveMaximum?: number; - multipleOf?: number; -} - -export interface ToolInfoInteger { - type: "integer"; - description?: string; - minimum?: number; - maximum?: number; - exclusiveMinimum?: number; - exclusiveMaximum?: number; - multipleOf?: number; -} - -export interface ToolInfoBoolean { - type: "boolean"; - description?: string; -} - -export interface ToolInfoNull { - type: "null"; - description?: string; -} - -export interface ToolInfoArray { - type: "array"; - description?: string; - items: ToolInfoItem; - minItems?: number; - maxItems?: number; - uniqueItems?: boolean; -} - -export interface ToolInfoObject { - type: "object"; - description?: string; - properties?: { - [key: string]: ToolInfoItem; - }; - required?: (keyof ToolInfoObject["properties"])[]; - additionalProperties?: boolean | ToolInfoItem; - minProperties?: number; - maxProperties?: number; -} - -export type ToolInfoItem = - | ToolInfoString - | ToolInfoNumber - | ToolInfoInteger - | ToolInfoBoolean - | ToolInfoNull - | ToolInfoArray - | ToolInfoObject; - -export interface ToolInfo { - type: "function", - function: { - name: string, - description: string, - parameters: ToolInfoObject - } -} - -export interface ToolCall { - index: number, - id: string, - type: "function", - function: { - name: string, - arguments: string - } -} - -export interface ExtCmdInfo { - extName: string, // 使用的扩展名称 - cmd: string, // 指令名称 - staticArgs: string[] // 参数 +const toolMap = { + } export class Tool { toolInfo: ToolInfo; ExtCmdInfo: ExtCmdInfo; // 海豹指令信息 sessionType: 'any' | 'user' | 'group'; // 可使用函数的会话类型 - callBack: boolean; // 是否回调函数 - solve: (ctx: seal.MsgContext, msg: seal.Message, agent: Agent, args: { [key: string]: any }) => Promise<{ content: string, images: Image[] }>; + callBack: boolean; // 是否回调智能体 + solve: (ctx: seal.MsgContext, msg: seal.Message, agent: Agent, args: { [key: string]: any }) => Promise; constructor(info: ToolInfo) { this.toolInfo = info; @@ -135,16 +22,15 @@ export class Tool { cmd: '', staticArgs: [] } - this.sessionType = "all" - this.tool_choice = 'auto'; - this.solve = async (_, __, ___, ____) => ({ content: "函数未实现", images: [] }); + this.sessionType = "any"; + this.callBack = true; + this.solve = async (_, __, ___, ____) => "函数未实现"; - ToolManager.toolMap[info.function.name] = this; + ToolService.toolMap[info.function.name] = this; } } -export class ToolManager { - static validKeys: (keyof ToolManager)[] = ['toolStatus']; +export class ToolService { static cmdArgs: seal.CmdArgs = null; static toolMap: { [key: string]: Tool } = {}; toolStatus: { [key: string]: boolean }; @@ -160,7 +46,7 @@ export class ToolManager { constructor() { const { toolsNotAllow, toolsDefaultClosed } = Config.tool; - this.toolStatus = Object.keys(ToolManager.toolMap).reduce((acc, key) => { + this.toolStatus = Object.keys(ToolService.toolMap).reduce((acc, key) => { acc[key] = !toolsNotAllow.includes(key) && !toolsDefaultClosed.includes(key); return acc; }, {}); @@ -490,7 +376,7 @@ export class ToolManager { reviveToolStauts() { const { toolsNotAllow, toolsDefaultClosed } = Config.tool; const toolStatus: { [key: string]: boolean } = {}; - for (const k in ToolManager.toolMap) { + for (const k in ToolService.toolMap) { if (!this.toolStatus.hasOwnProperty(k)) { toolStatus[k] = !toolsNotAllow.includes(k) && !toolsDefaultClosed.includes(k); } else if (toolsNotAllow.includes(k)) { @@ -510,11 +396,11 @@ export class ToolManager { const tools = Object.keys(this.toolStatus) .map(key => { if (this.toolStatus[key]) { - if (!ToolManager.toolMap.hasOwnProperty(key)) { + if (!ToolService.toolMap.hasOwnProperty(key)) { logger.error(`在getToolsInfo中找不到工具:${key}`); return null; } - const tool = ToolManager.toolMap[key]; + const tool = ToolService.toolMap[key]; if (tool.sessionType !== "all" && tool.sessionType !== type) { return null; } diff --git a/src/tool/tools/sample.ts b/src/tool/tools/sample.ts index 1863d3c..87596f4 100644 --- a/src/tool/tools/sample.ts +++ b/src/tool/tools/sample.ts @@ -1,26 +1,26 @@ -import { Tool } from "./tool"; +import { Tool } from "../tool"; -export function registerSample() { - const tool = new Tool({ - type: "function", - function: { - name: "sample", - description: `示例工具`, - parameters: { - type: "object", - properties: { - arg: { - type: 'string', - description: '参数' - } - }, - required: ["arg"] - } +const toolSample = new Tool({ + type: "function", + function: { + name: "sample", + description: `示例工具`, + parameters: { + type: "object", + properties: { + arg: { + type: 'string', + description: '参数' + } + }, + required: ["arg"] } - }); - tool.solve = async (ctx, msg, ai, args) => { - const { arg } = args; - arg; ctx; msg; ai; - return { content: "调用成功", images: [] }; } -} \ No newline at end of file +}); +toolSample.solve = async (ctx, msg, agent, args) => { + const { arg } = args; + arg; ctx; msg; agent; + return "调用示例函数成功"; +} + +export { toolSample }; \ No newline at end of file diff --git a/src/tool/types.ts b/src/tool/types.ts new file mode 100644 index 0000000..9fa1212 --- /dev/null +++ b/src/tool/types.ts @@ -0,0 +1,94 @@ +export interface ToolInfoString { + type: "string"; + description?: string; + enum?: string[]; + minLength?: number; + maxLength?: number; + pattern?: string; + format?: "date-time" | "email" | "uri" | "uuid" | "hostname" | "ipv4" | "ipv6"; +} + +export interface ToolInfoNumber { + type: "number"; + description?: string; + minimum?: number; + maximum?: number; + exclusiveMinimum?: number; + exclusiveMaximum?: number; + multipleOf?: number; +} + +export interface ToolInfoInteger { + type: "integer"; + description?: string; + minimum?: number; + maximum?: number; + exclusiveMinimum?: number; + exclusiveMaximum?: number; + multipleOf?: number; +} + +export interface ToolInfoBoolean { + type: "boolean"; + description?: string; +} + +export interface ToolInfoNull { + type: "null"; + description?: string; +} + +export interface ToolInfoArray { + type: "array"; + description?: string; + items: ToolInfoItem; + minItems?: number; + maxItems?: number; + uniqueItems?: boolean; +} + +export interface ToolInfoObject { + type: "object"; + description?: string; + properties?: { + [key: string]: ToolInfoItem; + }; + required?: (keyof ToolInfoObject["properties"])[]; + additionalProperties?: boolean | ToolInfoItem; + minProperties?: number; + maxProperties?: number; +} + +export type ToolInfoItem = + | ToolInfoString + | ToolInfoNumber + | ToolInfoInteger + | ToolInfoBoolean + | ToolInfoNull + | ToolInfoArray + | ToolInfoObject; + +export interface ToolInfo { + type: "function", + function: { + name: string, + description: string, + parameters: ToolInfoObject + } +} + +export interface ToolCall { + index: number, + id: string, + type: "function", + function: { + name: string, + arguments: string + } +} + +export interface ExtCmdInfo { + extName: string, // 使用的扩展名称 + cmd: string, // 指令名称 + staticArgs: string[] // 参数 +} \ No newline at end of file From a2f4f3e7c8d767c26419e86f388b1d4acd8bd905 Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Mon, 9 Mar 2026 00:46:49 +0800 Subject: [PATCH 19/33] =?UTF-8?q?=E7=BB=A7=E7=BB=AD=E9=87=8D=E6=9E=84tool?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/session/session.ts | 34 ++++++++++-- src/tool/tool.ts | 107 ++++++++---------------------------- src/tool/tools/jrrp.ts | 38 +++++++++++++ src/tool/tools/sample.ts | 6 +- src/tool/tools/tool_jrrp.ts | 40 -------------- src/tool/types.ts | 7 +++ 6 files changed, 101 insertions(+), 131 deletions(-) create mode 100644 src/tool/tools/jrrp.ts delete mode 100644 src/tool/tools/tool_jrrp.ts diff --git a/src/session/session.ts b/src/session/session.ts index 60f95a7..97e3800 100644 --- a/src/session/session.ts +++ b/src/session/session.ts @@ -1,6 +1,7 @@ import { Config } from "../config/config"; import { logger } from "../logger"; -import { ToolCall } from "../tool/tool"; +import { ToolCall, toolMap, ToolName, ToolState } from "../tool/tool"; +import { ToolListen } from "../tool/types"; import { revive, TypeDescriptor } from "../utils/utils"; import { Context } from "./context"; import { MemoryService } from "./memory"; @@ -23,7 +24,11 @@ export class Session { state: 'any', context: Context, memory: MemoryService, - // tool: ToolState; wip + tool: { + object: { + state: { objectValue: 'boolean' } + } + }, ignoredUserIdList: { array: 'string' }, } isPrivate: boolean; @@ -31,7 +36,11 @@ export class Session { state: State; context: Context; memory: MemoryService; - // tool: ToolState; wip + tool: { + state: ToolState, + callCount: number, // 单次触发调用函数计数 + listen: ToolListen // 监听调用函数发送的内容 + } ignoredUserIdList: string[]; constructor() { @@ -40,7 +49,24 @@ export class Session { this.state = {}; this.context = new Context(); this.memory = new MemoryService(); - // this.tool = new ToolState(); + this.tool = { + state: Object.keys(toolMap).reduce((acc, key) => { + acc[key as ToolName] = false; + return acc; + }, {} as ToolState), + callCount: 0, + listen: { + timeoutId: null, + resolve: null, + reject: null, + cleanup: () => { + if (this.tool.listen.timeoutId) clearTimeout(this.tool.listen.timeoutId); + this.tool.listen.timeoutId = null; + this.tool.listen.resolve = null; + this.tool.listen.reject = null; + } + } + } this.ignoredUserIdList = []; } diff --git a/src/tool/tool.ts b/src/tool/tool.ts index c676fd5..571efd1 100644 --- a/src/tool/tool.ts +++ b/src/tool/tool.ts @@ -2,12 +2,16 @@ import { Config } from "../config/config" import { logger } from "../logger" import { fixJsonString } from "../utils/string"; import { Agent } from "../agent/agent" -import { ExtCmdInfo, ToolInfo } from "./types"; +import { ExtCmdInfo, ToolInfo, ToolListen } from "./types"; +import { toolJrrp } from "./tools/jrrp"; -const toolMap = { - +export const toolMap = { + jrrp: toolJrrp, } +export type ToolName = keyof typeof toolMap; +export type ToolState = { [key in ToolName]: boolean }; + export class Tool { toolInfo: ToolInfo; ExtCmdInfo: ExtCmdInfo; // 海豹指令信息 @@ -32,76 +36,14 @@ export class Tool { export class ToolService { static cmdArgs: seal.CmdArgs = null; - static toolMap: { [key: string]: Tool } = {}; - toolStatus: { [key: string]: boolean }; - toolCallCount: number; // 一次性调用函数计数 - - // 监听调用函数发送的内容 - listen: { - timeoutId: number, - resolve: (content: string) => void, - reject: (err: Error) => void, - cleanup: () => void - } - - constructor() { - const { toolsNotAllow, toolsDefaultClosed } = Config.tool; - this.toolStatus = Object.keys(ToolService.toolMap).reduce((acc, key) => { - acc[key] = !toolsNotAllow.includes(key) && !toolsDefaultClosed.includes(key); - return acc; - }, {}); - this.toolCallCount = 0; - - this.listen = { - timeoutId: null, - resolve: null, - reject: null, - cleanup: () => { - if (this.listen.timeoutId) { - clearTimeout(this.listen.timeoutId); - } - - this.listen.timeoutId = null; - this.listen.resolve = null; - this.listen.reject = null; - } - }; - } - - static registerTool() { - registerMemory(); - registerDeck(); - registerJrrp(); - registerModu(); - registerRollCheck(); - registerRename(); - registerAttr(); - registerBan(); - registerRecord(); - registerTime(); - registerWeb(); - registerImage(); - registerGroupSign(); - registerGetPersonInfo(); - registerMessage(); - registerEssenceMsg(); - registerContext(); - registerQQList(); - registerSetTrigger(); - registerMusicPlay(); - registerMeme(); - registerRender(); - } /** - * 利用预存的指令信息和额外输入的参数构建一个cmdArgs, 并调用solve函数 - * @param cmdArgs - * @param args + * 利用预存的指令信息和额外输入的参数构建一个cmdArgs并调用solve函数,监听消息并返回结果 */ - static async extensionSolve(ctx: seal.MsgContext, msg: seal.Message, ai: AI, cmdInfo: ExtCmdInfo, args: string[], kwargs: seal.Kwarg[], at: seal.AtInfo[]): Promise<[string, boolean]> { + static async extensionSolve(ctx: seal.MsgContext, msg: seal.Message, listen: ToolListen, eci: ExtCmdInfo, args: string[], kwargs: seal.Kwarg[], at: seal.AtInfo[]): Promise<[string, boolean]> { const cmdArgs = this.cmdArgs; - cmdArgs.command = cmdInfo.cmd; - cmdArgs.args = cmdInfo.staticArgs.concat(args); + cmdArgs.command = eci.cmd; + cmdArgs.args = eci.staticArgs.concat(args); cmdArgs.kwargs = kwargs; cmdArgs.at = at; cmdArgs.rawArgs = `${cmdArgs.args.join(' ')} ${kwargs.map(item => `--${item.name}${item.valueExists ? `=${item.value}` : ``}`).join(' ')}`; @@ -111,38 +53,35 @@ export class ToolService { cmdArgs.specialExecuteTimes = 0; cmdArgs.rawText = `.${cmdArgs.command} ${cmdArgs.rawArgs} ${at.map(item => `[CQ:at,qq=${item.userId.replace(/^.+:/, '')}]`).join(' ')}`; - const ext = seal.ext.find(cmdInfo.extName); - if (!ext.cmdMap.hasOwnProperty(cmdInfo.cmd)) { - logger.warning(`扩展${cmdInfo.extName}中未找到指令:${cmdInfo.cmd}`); + const ext = seal.ext.find(eci.extName); + if (!ext.cmdMap.hasOwnProperty(eci.cmd)) { + logger.warning(`扩展${eci.extName}中未找到指令:${eci.cmd}`); return ['', false]; } - ai.tool.listen.reject?.(new Error('中断当前监听')); + listen.reject?.(new Error('中断当前监听')); return new Promise(( resolve: (result: [string, boolean]) => void, reject: (err: Error) => void ) => { - ai.tool.listen.timeoutId = setTimeout(() => { + listen.timeoutId = setTimeout(() => { reject(new Error('监听消息超时')); - ai.tool.listen.cleanup(); + listen.cleanup(); }, 10 * 1000); - - ai.tool.listen.resolve = (content: string) => { + listen.resolve = (content: string) => { resolve([content, true]); - ai.tool.listen.cleanup(); + listen.cleanup(); }; - - ai.tool.listen.reject = (err: Error) => { + listen.reject = (err: Error) => { reject(err); - ai.tool.listen.cleanup(); + listen.cleanup(); }; - try { - ext.cmdMap[cmdInfo.cmd].solve(ctx, msg, cmdArgs); + ext.cmdMap[eci.cmd].solve(ctx, msg, cmdArgs); } catch (err) { reject(new Error(`solve中发生错误:${err.message}`)); - ai.tool.listen.cleanup(); + listen.cleanup(); } }).catch((err) => { logger.error(`在extensionSolve中: 调用函数失败:${err.message}`); diff --git a/src/tool/tools/jrrp.ts b/src/tool/tools/jrrp.ts new file mode 100644 index 0000000..ed283e4 --- /dev/null +++ b/src/tool/tools/jrrp.ts @@ -0,0 +1,38 @@ +import { Tool } from "../tool"; + +const tool = new Tool({ + type: "function", + function: { + name: "jrrp", + description: `查看指定用户的今日人品`, + parameters: { + type: "object", + properties: { + name: { + type: 'string', + description: '用户名称或纯数字QQ号' + } + }, + required: ["name"] + } + } +}); +tool.ExtCmdInfo = { + extName: 'fun', + cmd: 'jrrp', + staticArgs: [] +} +tool.solve = async (ctx, msg, agent, args) => { + const { name } = args; + + const ui = await ai.context.findUserInfo(ctx, name); + if (ui === null) return { content: `未找到<${name}>`, images: [] }; + + ({ ctx, msg } = getCtxAndMsg(ctx.endPoint.userId, ui.id, ctx.group.groupId)); + const [s, success] = await ToolManager.extensionSolve(ctx, msg, ai, tool.ExtCmdInfo, [], [], []); + if (!success) return { content: '今日人品查询失败', images: [] }; + + return { content: s, images: [] }; +} + +export { tool as toolJrrp } \ No newline at end of file diff --git a/src/tool/tools/sample.ts b/src/tool/tools/sample.ts index 87596f4..1d38ab1 100644 --- a/src/tool/tools/sample.ts +++ b/src/tool/tools/sample.ts @@ -1,6 +1,6 @@ import { Tool } from "../tool"; -const toolSample = new Tool({ +const tool = new Tool({ type: "function", function: { name: "sample", @@ -17,10 +17,10 @@ const toolSample = new Tool({ } } }); -toolSample.solve = async (ctx, msg, agent, args) => { +tool.solve = async (ctx, msg, agent, args) => { const { arg } = args; arg; ctx; msg; agent; return "调用示例函数成功"; } -export { toolSample }; \ No newline at end of file +export { tool as toolSample }; \ No newline at end of file diff --git a/src/tool/tools/tool_jrrp.ts b/src/tool/tools/tool_jrrp.ts deleted file mode 100644 index 99fe04e..0000000 --- a/src/tool/tools/tool_jrrp.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { ConfigManager } from "../config/configManager"; -import { getCtxAndMsg } from "../utils/utils_seal"; -import { Tool, ToolManager } from "./tool"; - -export function registerJrrp() { - const tool = new Tool({ - type: "function", - function: { - name: "jrrp", - description: `查看指定用户的今日人品`, - parameters: { - type: "object", - properties: { - name: { - type: 'string', - description: '用户名称' + (ConfigManager.message.showNumber ? '或纯数字QQ号' : '') - } - }, - required: ["name"] - } - } - }); - tool.ExtCmdInfo = { - extName: 'fun', - cmd: 'jrrp', - staticArgs: [] - } - tool.solve = async (ctx, msg, ai, args) => { - const { name } = args; - - const ui = await ai.context.findUserInfo(ctx, name); - if (ui === null) return { content: `未找到<${name}>`, images: [] }; - - ({ ctx, msg } = getCtxAndMsg(ctx.endPoint.userId, ui.id, ctx.group.groupId)); - const [s, success] = await ToolManager.extensionSolve(ctx, msg, ai, tool.ExtCmdInfo, [], [], []); - if (!success) return { content: '今日人品查询失败', images: [] }; - - return { content: s, images: [] }; - } -} \ No newline at end of file diff --git a/src/tool/types.ts b/src/tool/types.ts index 9fa1212..4a4e987 100644 --- a/src/tool/types.ts +++ b/src/tool/types.ts @@ -91,4 +91,11 @@ export interface ExtCmdInfo { extName: string, // 使用的扩展名称 cmd: string, // 指令名称 staticArgs: string[] // 参数 +} + +export interface ToolListen { + timeoutId: number, + resolve: (content: string) => void, + reject: (err: Error) => void, + cleanup: () => void } \ No newline at end of file From a03ef19b4410c05b38cf7896bdfe13dcb9dc68f1 Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Mon, 9 Mar 2026 14:56:57 +0800 Subject: [PATCH 20/33] sdfjkl --- src/agent/model.ts | 77 ++++++------- src/agent/types.ts | 11 ++ src/config/config.ts | 10 +- src/config/configs/backend.ts | 2 +- src/config/configs/base.ts | 2 +- src/config/configs/model.ts | 186 +++++-------------------------- src/config/configs/received.ts | 2 +- src/config/configs/sample.ts | 2 +- src/config/configs/trigger.ts | 2 +- src/config/static_config.ts | 198 ++++++--------------------------- src/image/image.ts | 20 ++-- src/note.txt | 4 +- src/session/group.ts | 10 +- src/session/user.ts | 10 +- 14 files changed, 144 insertions(+), 392 deletions(-) create mode 100644 src/agent/types.ts diff --git a/src/agent/model.ts b/src/agent/model.ts index bb9ddb7..26d5ca5 100644 --- a/src/agent/model.ts +++ b/src/agent/model.ts @@ -1,32 +1,21 @@ import { Config } from "../config/config"; +import { DEFAULT_CHAT_MODEL_BODY, DEFAULT_EMBEDDING_MODEL_BODY, DEFAULT_IMAGE_MODEL_BODY } from "../config/static_config"; import { logger } from "../logger"; import { ToolCall } from "../tool/types"; import { withTimeout } from "../utils/utils"; import { Agent } from "./agent"; +import { ChatModelUse, EmbeddingModelUse, ImageModelUse, ModelBody } from "./types"; import { UsageManager } from "./usage"; -export type ModelUse = 'any' | 'chat' | 'image-understanding' | 'text-embedding' - -export interface ModelBody { - max_tokens?: number, - stop?: string[] | null, - stream?: boolean, - temperature?: number, - top_p?: number, - [key: string]: any -} - export class BaseModel { name: string; - use: ModelUse[]; provider: string; baseUrl: string; apiKey: string; body: ModelBody; - constructor(name: string, use: ModelUse[], provider: string, base_url: string, api_key: string, body: ModelBody) { + constructor(name: string, provider: string, base_url: string, api_key: string, body: ModelBody) { this.name = name; - this.use = use; this.provider = provider; this.baseUrl = base_url; this.apiKey = api_key; @@ -43,27 +32,26 @@ export class BaseModel { } export class ChatModel extends BaseModel { - constructor(name: string, use: ModelUse[], provider: string, base_url: string, api_key: string, body: ModelBody) { - super(name, use, provider, base_url, api_key, body); + use: ChatModelUse[]; + constructor(use: ChatModelUse[], name: string, provider: string, base_url: string, api_key: string, body: ModelBody) { + super(name, provider, base_url, api_key, body); + this.use = use; } get url() { return `${this.baseUrl}/chat/completions`; } - buildChatBody(agent: Agent, sessionId: string) { - return this.buildBody({ - messages: agent.sessionService.getSession(sessionId).getMessages(), - tools: agent.getRequestTools() - }); - } - async callChat(agent: Agent, sessionId: string): Promise<{ content: string, tool_calls: ToolCall[] }> { const { TIMEOUT } = Config.base; try { const time = Date.now(); - const data = await withTimeout(() => fetchData(this.url, this.apiKey, this.buildChatBody(agent, sessionId)), TIMEOUT); + const data = await withTimeout(() => fetchData(this.url, this.apiKey, this.buildBody({ + ...DEFAULT_CHAT_MODEL_BODY, + messages: agent.sessionService.getSession(sessionId).getMessages(), + tools: agent.getRequestTools() + })), TIMEOUT); if (data.choices && data.choices.length > 0) { UsageManager.updateUsage(data.model, data.usage); @@ -91,27 +79,23 @@ export class ChatModel extends BaseModel { } export class ImageModel extends BaseModel { - constructor(name: string, use: ModelUse[], provider: string, base_url: string, api_key: string, body: ModelBody) { - super(name, use, provider, base_url, api_key, body); + use: ImageModelUse[]; + constructor(use: ImageModelUse[], name: string, provider: string, base_url: string, api_key: string, body: ModelBody) { + super(name, provider, base_url, api_key, body); + this.use = use; } get url() { return `${this.baseUrl}/chat/completions`; } - buildChatBody(agent: Agent, sessionId: string) { - return this.buildBody({ - messages: agent.sessionService.getSession(sessionId).getImageMessages(), - tools: agent.getRequestTools() - }); - } - async callITT(src: string, prompt = ''): Promise { const { TIMEOUT } = Config.base; try { const time = Date.now(); const data = await withTimeout(() => fetchData(this.url, this.apiKey, this.buildBody({ + ...DEFAULT_IMAGE_MODEL_BODY, messages: [{ role: "user", content: [{ @@ -147,7 +131,11 @@ export class ImageModel extends BaseModel { try { const time = Date.now(); - const data = await withTimeout(() => fetchData(this.url, this.apiKey, this.buildChatBody(agent, sessionId)), TIMEOUT); + const data = await withTimeout(() => fetchData(this.url, this.apiKey, this.buildBody({ + ...DEFAULT_IMAGE_MODEL_BODY, + messages: agent.sessionService.getSession(sessionId).getImageMessages(), + tools: agent.getRequestTools() + })), TIMEOUT); if (data.choices && data.choices.length > 0) { UsageManager.updateUsage(data.model, data.usage); @@ -177,8 +165,10 @@ export class ImageModel extends BaseModel { export class EmbeddingModel extends BaseModel { static vectorCache: { text: string, vector: number[] } = { text: '', vector: [] }; - constructor(name: string, use: ModelUse[], provider: string, base_url: string, api_key: string, body) { - super(name, use, provider, base_url, api_key, body); + use: EmbeddingModelUse[]; + constructor(use: EmbeddingModelUse[], name: string, provider: string, base_url: string, api_key: string, body: ModelBody) { + super(name, provider, base_url, api_key, body); + this.use = use; } get url() { @@ -202,6 +192,7 @@ export class EmbeddingModel extends BaseModel { const time = Date.now(); const data = await withTimeout(() => fetchData(this.url, this.apiKey, this.buildBody({ + ...DEFAULT_EMBEDDING_MODEL_BODY, input: text })), TIMEOUT); @@ -233,7 +224,7 @@ export class ModelManager { static imageModels: ImageModel[] = []; static embeddingModels: EmbeddingModel[] = []; - static getChatModel(use: ModelUse): ChatModel | ImageModel | null { + static getChatModel(use: ChatModelUse): ChatModel | ImageModel | null { const chatModelList = ModelManager.chatModels.filter(model => model.use.includes(use)); if (chatModelList.length > 0) { const randomIndex = Math.floor(Math.random() * chatModelList.length); @@ -244,12 +235,12 @@ export class ModelManager { const randomIndex = Math.floor(Math.random() * ImageModelList.length); return ImageModelList[randomIndex]; } - const chatModelAnyList = ModelManager.chatModels.filter(model => model.use.includes('any')); + const chatModelAnyList = ModelManager.chatModels.filter(model => model.use.length === 0); if (chatModelAnyList.length > 0) { const randomIndex = Math.floor(Math.random() * chatModelAnyList.length); return chatModelAnyList[randomIndex]; } - const ImageModelAnyList = ModelManager.imageModels.filter(model => model.use.includes('any')); + const ImageModelAnyList = ModelManager.imageModels.filter(model => model.use.length === 0); if (ImageModelAnyList.length > 0) { const randomIndex = Math.floor(Math.random() * ImageModelAnyList.length); return ImageModelAnyList[randomIndex]; @@ -257,13 +248,13 @@ export class ModelManager { return null; } - static getImageModel(use: ModelUse): ImageModel | null { + static getImageModel(use: ImageModelUse): ImageModel | null { const ImageModelList = ModelManager.imageModels.filter(model => model.use.includes(use)); if (ImageModelList.length > 0) { const randomIndex = Math.floor(Math.random() * ImageModelList.length); return ImageModelList[randomIndex]; } - const ImageModelAnyList = ModelManager.imageModels.filter(model => model.use.includes('any')); + const ImageModelAnyList = ModelManager.imageModels.filter(model => model.use.length === 0); if (ImageModelAnyList.length > 0) { const randomIndex = Math.floor(Math.random() * ImageModelAnyList.length); return ImageModelAnyList[randomIndex]; @@ -271,13 +262,13 @@ export class ModelManager { return null; } - static getEmbeddingModel(use: ModelUse): EmbeddingModel | null { + static getEmbeddingModel(use: EmbeddingModelUse): EmbeddingModel | null { const EmbeddingModelList = ModelManager.embeddingModels.filter(model => model.use.includes(use)); if (EmbeddingModelList.length > 0) { const randomIndex = Math.floor(Math.random() * EmbeddingModelList.length); return EmbeddingModelList[randomIndex]; } - const EmbeddingModelAnyList = ModelManager.embeddingModels.filter(model => model.use.includes('any')); + const EmbeddingModelAnyList = ModelManager.embeddingModels.filter(model => model.use.length === 0); if (EmbeddingModelAnyList.length > 0) { const randomIndex = Math.floor(Math.random() * EmbeddingModelAnyList.length); return EmbeddingModelAnyList[randomIndex]; diff --git a/src/agent/types.ts b/src/agent/types.ts new file mode 100644 index 0000000..5249728 --- /dev/null +++ b/src/agent/types.ts @@ -0,0 +1,11 @@ +export type ChatModelUse = 'chat' | 'compression'; +export type ImageModelUse = 'image-understanding' | ChatModelUse; +export type EmbeddingModelUse = 'text-embedding'; +export interface ModelBody { + max_tokens?: number, + stop?: string[] | null, + stream?: boolean, + temperature?: number, + top_p?: number, + [key: string]: any +} \ No newline at end of file diff --git a/src/config/config.ts b/src/config/config.ts index 9cf40b8..5a8b552 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -1,11 +1,11 @@ import Handlebars from "handlebars"; import { logger } from "../logger"; import { AUTHOR, CONFIG_CACHE_TTL, NAME, VERSION } from "./static_config"; -import { BaseConfig } from "./configs/base"; -import { ModelConfig } from "./configs/model"; -import { BackendConfig } from "./configs/backend"; -import { ReceivedConfig } from "./configs/received"; -import { TriggerConfig } from "./configs/trigger"; +import BaseConfig from "./configs/base"; +import ModelConfig from "./configs/model"; +import BackendConfig from "./configs/backend"; +import ReceivedConfig from "./configs/received"; +import TriggerConfig from "./configs/trigger"; const configMap = { base: BaseConfig, diff --git a/src/config/configs/backend.ts b/src/config/configs/backend.ts index 6c59d78..fe31a2d 100644 --- a/src/config/configs/backend.ts +++ b/src/config/configs/backend.ts @@ -1,6 +1,6 @@ import { Config } from "../config"; -export class BackendConfig { +export default class BackendConfig { static ext: seal.ExtInfo; static register() { diff --git a/src/config/configs/base.ts b/src/config/configs/base.ts index b836dba..089ef44 100644 --- a/src/config/configs/base.ts +++ b/src/config/configs/base.ts @@ -1,6 +1,6 @@ import { Config } from "../config"; -export class BaseConfig { +export default class BaseConfig { static ext: seal.ExtInfo; static register() { diff --git a/src/config/configs/model.ts b/src/config/configs/model.ts index c28715c..d6e71a8 100644 --- a/src/config/configs/model.ts +++ b/src/config/configs/model.ts @@ -1,196 +1,66 @@ import { ChatModel, EmbeddingModel, ImageModel } from "../../agent/model"; +import { ModelBody } from "../../agent/types"; import { logger } from "../../logger"; import { Config } from "../config"; -import { CHAT_MODEL_MAP, EMBEDDING_MODEL_MAP, IMAGE_MODEL_MAP } from "../static_config"; +import { CHAT_MODEL_TO_PROVIDER, EMBEDDING_MODEL_TO_PROVIDER, IMAGE_MODEL_TO_PROVIDER, PROVIDER_MAP } from "../static_config"; -export class ModelConfig { +export default class ModelConfig { static ext: seal.ExtInfo; static register() { ModelConfig.ext = Config.getExt('aiplugin4:模型'); - seal.ext.registerOptionConfig(ModelConfig.ext, "快速选择对话模型", "", getChatModelOptions(), ''); - seal.ext.registerStringConfig(ModelConfig.ext, "快速填入对话模型api key", "", ''); - seal.ext.registerOptionConfig(ModelConfig.ext, "快速选择图片模型", "", getImageModelOptions(), ''); - seal.ext.registerStringConfig(ModelConfig.ext, "快速填入图片模型api key", "", ''); - seal.ext.registerOptionConfig(ModelConfig.ext, "快速选择嵌入模型", "", getEmbeddingModelOptions(), ''); - seal.ext.registerStringConfig(ModelConfig.ext, "快速填入嵌入模型api key", "", ''); seal.ext.registerTemplateConfig(ModelConfig.ext, "对话模型", [`{ - "name": "deepseek-chat", "use": ["chat"], + "name": "deepseek-chat", "api_key": "sk-xxxx", - "body": ${JSON.stringify(DEFAULT_CHAT_MODEL_BODY)} + "body": { + "temperature": 1, + "top_p": 1 + } }`], ''); seal.ext.registerTemplateConfig(ModelConfig.ext, "图片模型", [`{ + "use": ["image-understanding"], "name": "glm-4v", - "use": ["any"], - "api_key": "sk-xxxx", - "base_url": "https://open.bigmodel.cn/api/paas/v4", - "body": ${JSON.stringify(DEFAULT_IMAGE_MODEL_BODY)} + "api_key": "sk-xxxx" }`], ''); seal.ext.registerTemplateConfig(ModelConfig.ext, "嵌入模型", [`{ + "use": ["text-embedding"], "name": "text-embedding-v4", - "use": ["any"], - "api_key": "sk-xxxx", - "body": ${JSON.stringify(DEFAULT_EMBEDDING_MODEL_BODY)} + "api_key": "sk-xxxx" }`], ''); } static get() { return { - CHAT_MODELS: [getFastChatModel(), ...getChatModelsConfig()].filter(model => model !== null), - IMAGE_MODELS: [getFastImageModel(), ...getImageModelsConfig()].filter(model => model !== null), - EMBEDDING_MODELS: [getFastEmbeddingModel(), ...getEmbeddingModelsConfig()].filter(model => model !== null), + CHAT_MODELS: getModelsConfig("对话模型", CHAT_MODEL_TO_PROVIDER, ChatModel), + IMAGE_MODELS: getModelsConfig("图片模型", IMAGE_MODEL_TO_PROVIDER, ImageModel), + EMBEDDING_MODELS: getModelsConfig("嵌入模型", EMBEDDING_MODEL_TO_PROVIDER, EmbeddingModel), } } } -const DEFAULT_CHAT_MODEL_BODY = { - "max_tokens": 4096, - "stop": null, - "stream": false, - "temperature": 1, - "top_p": 1 -} -const DEFAULT_IMAGE_MODEL_BODY = { - "max_tokens": 4096, - "stop": null, - "stream": false -} -const DEFAULT_EMBEDDING_MODEL_BODY = { - "encoding_format": "float" -} - -function getChatModelOptions() { - const op: string[] = []; - Object.keys(CHAT_MODEL_MAP).forEach(provider => { - CHAT_MODEL_MAP[provider].model.forEach(model => { - op.push(`${provider}/${model}`); - }); - }); - return op; -} - -function getImageModelOptions() { - const op: string[] = []; - Object.keys(IMAGE_MODEL_MAP).forEach(provider => { - IMAGE_MODEL_MAP[provider].model.forEach(model => { - op.push(`${provider}/${model}`); - }); - }); - return op; -} - -function getEmbeddingModelOptions() { - const op: string[] = []; - Object.keys(EMBEDDING_MODEL_MAP).forEach(provider => { - EMBEDDING_MODEL_MAP[provider].model.forEach(model => { - op.push(`${provider}/${model}`); - }); - }); - return op; -} - -function getFastChatModel(): ChatModel | null { - const model = seal.ext.getOptionConfig(ModelConfig.ext, "快速选择对话模型"); - const apiKey = seal.ext.getStringConfig(ModelConfig.ext, "快速填入对话模型api key"); - const [provider, name] = model.split('/'); - const baseUrl = CHAT_MODEL_MAP[provider].baseUrl; - return new ChatModel(name, ["any"], provider, baseUrl, apiKey, DEFAULT_CHAT_MODEL_BODY); -} - -function getFastImageModel(): ImageModel | null { - const model = seal.ext.getOptionConfig(ModelConfig.ext, "快速选择图片模型"); - const apiKey = seal.ext.getStringConfig(ModelConfig.ext, "快速填入图片模型api key"); - const [provider, name] = model.split('/'); - const baseUrl = IMAGE_MODEL_MAP[provider].baseUrl; - return new ImageModel(name, ["any"], provider, baseUrl, apiKey, DEFAULT_IMAGE_MODEL_BODY); -} - -function getFastEmbeddingModel(): EmbeddingModel | null { - const model = seal.ext.getOptionConfig(ModelConfig.ext, "快速选择嵌入模型"); - const apiKey = seal.ext.getStringConfig(ModelConfig.ext, "快速填入嵌入模型api key"); - const [provider, name] = model.split('/'); - const baseUrl = EMBEDDING_MODEL_MAP[provider].baseUrl; - return new EmbeddingModel(name, ["any"], provider, baseUrl, apiKey, DEFAULT_EMBEDDING_MODEL_BODY); -} - -function getChatModelsConfig(): ChatModel[] { - return seal.ext.getTemplateConfig(ModelConfig.ext, "对话模型").map(x => { - try { - const data = JSON.parse(x); - if (!data.hasOwnProperty('name')) throw new Error('缺失模型名称'); - if (!data.hasOwnProperty('api_key')) throw new Error('缺失模型API密钥'); - if (!data.hasOwnProperty('body')) data.body = DEFAULT_CHAT_MODEL_BODY; - if (!data.hasOwnProperty('use')) data.use = ["any"]; - if (!data.hasOwnProperty('provider')) data.provider = ""; - if (!data.hasOwnProperty('base_url')) { - for (const provider in CHAT_MODEL_MAP) { - if (CHAT_MODEL_MAP[provider].model.includes(data.name)) { - data.base_url = CHAT_MODEL_MAP[provider].baseUrl; - break; - } - } - if (!data.hasOwnProperty('base_url')) throw new Error('缺失模型基础URL'); - } - - return new ChatModel(data.name, data.use, data.provider, data.base_url, data.api_key, data.body); - } catch (e) { - logger.error(`对话模型解析错误,内容:${x},错误信息:${e.message}`); - return null; - } - }).filter(x => x !== null); -} - -function getImageModelsConfig(): ImageModel[] { - return seal.ext.getTemplateConfig(ModelConfig.ext, "图片模型").map(x => { +function getModelsConfig( + key: string, + m2p: { [model: string]: string }, + modelConstructor: new (use: T['use'], name: string, provider: string, base_url: string, api_key: string, body: ModelBody) => T +): T[] { + return seal.ext.getTemplateConfig(ModelConfig.ext, key).map(x => { try { const data = JSON.parse(x); if (!data.hasOwnProperty('name')) throw new Error('缺失模型名称'); if (!data.hasOwnProperty('api_key')) throw new Error('缺失模型API密钥'); - if (!data.hasOwnProperty('body')) data.body = DEFAULT_IMAGE_MODEL_BODY; - if (!data.hasOwnProperty('use')) data.use = ["any"]; - if (!data.hasOwnProperty('provider')) data.provider = ""; + if (!data.hasOwnProperty('use')) data.use = []; + if (!data.hasOwnProperty('body')) data.body = {}; + if (!data.hasOwnProperty('provider')) data.provider = m2p?.[data.name] || ""; if (!data.hasOwnProperty('base_url')) { - for (const provider in IMAGE_MODEL_MAP) { - if (IMAGE_MODEL_MAP[provider].model.includes(data.name)) { - data.base_url = IMAGE_MODEL_MAP[provider].baseUrl; - break; - } - } + if (!data.hasOwnProperty('provider')) throw new Error('缺失模型基础URL 且 缺失模型供应商'); + data.base_url = PROVIDER_MAP?.[data.provider] || ""; if (!data.hasOwnProperty('base_url')) throw new Error('缺失模型基础URL'); } - - return new ImageModel(data.name, data.use, data.provider, data.base_url, data.api_key, data.body); - } catch (e) { - logger.error(`图片模型解析错误,内容:${x},错误信息:${e.message}`); - return null; - } - }).filter(x => x !== null); -} - -function getEmbeddingModelsConfig(): EmbeddingModel[] { - return seal.ext.getTemplateConfig(ModelConfig.ext, "嵌入模型").map(x => { - try { - const data = JSON.parse(x); - if (!data.hasOwnProperty('name')) throw new Error('缺失模型名称'); - if (!data.hasOwnProperty('api_key')) throw new Error('缺失模型API密钥'); - if (!data.hasOwnProperty('body')) data.body = DEFAULT_EMBEDDING_MODEL_BODY; - if (!data.hasOwnProperty('use')) data.use = ["any"]; - if (!data.hasOwnProperty('provider')) data.provider = ""; - if (!data.hasOwnProperty('base_url')) { - for (const provider in EMBEDDING_MODEL_MAP) { - if (EMBEDDING_MODEL_MAP[provider].model.includes(data.name)) { - data.base_url = EMBEDDING_MODEL_MAP[provider].baseUrl; - break; - } - } - if (!data.hasOwnProperty('base_url')) throw new Error('缺失模型基础URL'); - } - - return new EmbeddingModel(data.name, data.use, data.provider, data.base_url, data.api_key, data.body); + return new modelConstructor(data.use, data.name, data.provider, data.base_url, data.api_key, data.body); } catch (e) { - logger.error(`嵌入模型解析错误,内容:${x},错误信息:${e.message}`); + logger.error(`${key}解析错误,内容:${x},错误信息:${e.message}`); return null; } }).filter(x => x !== null); diff --git a/src/config/configs/received.ts b/src/config/configs/received.ts index 615551d..90cc6a3 100644 --- a/src/config/configs/received.ts +++ b/src/config/configs/received.ts @@ -1,6 +1,6 @@ import { Config, getRegexConfig } from "../config"; -export class ReceivedConfig { +export default class ReceivedConfig { static ext: seal.ExtInfo; static register() { diff --git a/src/config/configs/sample.ts b/src/config/configs/sample.ts index 7d33a27..ac9cbd3 100644 --- a/src/config/configs/sample.ts +++ b/src/config/configs/sample.ts @@ -1,6 +1,6 @@ import { Config } from "../config"; -export class SampleConfig { +export default class SampleConfig { static ext: seal.ExtInfo; static register() { diff --git a/src/config/configs/trigger.ts b/src/config/configs/trigger.ts index 307a2b0..8ad1e64 100644 --- a/src/config/configs/trigger.ts +++ b/src/config/configs/trigger.ts @@ -1,6 +1,6 @@ import { Config, getRegexConfig } from "../config"; -export class TriggerConfig { +export default class TriggerConfig { static ext: seal.ExtInfo; static register() { diff --git a/src/config/static_config.ts b/src/config/static_config.ts index 9466317..aa739ec 100644 --- a/src/config/static_config.ts +++ b/src/config/static_config.ts @@ -370,169 +370,45 @@ export const FACE_MAP = { "432": "灵蛇献瑞" } -export interface ModelInfo { - provider: string; - model: string[]; - baseUrl: string; +export const PROVIDER_MAP = { + "deepseek": "https://api.deepseek.com/v1", + "zhipu": "https://open.bigmodel.cn/api/paas/v4", + "alibaba": "https://dashscope.aliyuncs.com/compatible-mode/v1" + } -export const CHAT_MODEL_MAP: { [key: string]: ModelInfo } = { - // 海外厂商 - "openai": { - provider: "openai", - model: ["gpt-4o", "gpt-4-turbo", "gpt-4", "gpt-3.5-turbo", "o1-mini", "o1-preview"], - baseUrl: "https://api.openai.com/v1" - }, - "anthropic": { - provider: "anthropic", - model: ["claude-3-5-sonnet-20241022", "claude-3-opus-20240229", "claude-3-sonnet-20240229", "claude-3-haiku-20240307"], - baseUrl: "https://api.anthropic.com/v1" - }, - "google": { - provider: "google", - model: ["gemini-2.5-pro-exp-03-25", "gemini-2.0-flash-exp", "gemini-1.5-pro", "gemini-1.5-flash"], - baseUrl: "https://generativelanguage.googleapis.com/v1beta" // 或使用 Vertex AI 的端点 - }, - "meta": { // 通过特定服务商调用,如Replicate, Together AI,或自托管 - provider: "meta", - model: ["llama-3.1-405b-instruct", "llama-3.1-70b-instruct", "llama-3.1-8b-instruct"], - baseUrl: "https://api.together.xyz/v1" // 示例:使用 Together AI 作为代理 - }, - "mistralai": { - provider: "mistralai", - model: ["mistral-large-latest", "mistral-small-latest", "codestral-latest"], - baseUrl: "https://api.mistral.ai/v1" - }, - "cohere": { - provider: "cohere", - model: ["command-r-plus", "command-r", "command-light"], - baseUrl: "https://api.cohere.ai/v1" - }, - "xai": { - provider: "xai", - model: ["grok-2", "grok-2-mini"], - baseUrl: "https://api.x.ai/v1" // 示例地址,实际需确认 - }, - "deepseek": { // 深度求索,以推理能力见长 [citation:8] - provider: "deepseek", - model: ["deepseek-chat", "deepseek-reasoner"], // 对应 V3 和 R1 系列 - baseUrl: "https://api.deepseek.com/v1" - }, - // 国内厂商 - "alibaba": { - provider: "alibaba", - model: ["qwen-max", "qwen-plus", "qwen-turbo", "qwen2.5-72b-instruct"], - baseUrl: "https://dashscope.aliyuncs.com/compatible-mode/v1" // 通义千问 DashScope 兼容OpenAI的地址 - }, - "baidu": { - provider: "baidu", - model: ["ernie-4.0-turbo-8k", "ernie-3.5-8k", "ernie-lite-8k"], - baseUrl: "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop" // 文心一言 API 地址 - }, - "tencent": { - provider: "tencent", - model: ["hunyuan-pro", "hunyuan-standard", "hunyuan-lite"], - baseUrl: "https://api.hunyuan.cloud.tencent.com/v1" // 示例地址,实际需确认 - }, - "zhipu": { // 智谱AI [citation:8] - provider: "zhipu", - model: ["glm-4-plus", "glm-4-0520", "glm-4-air", "glm-3-turbo"], - baseUrl: "https://open.bigmodel.cn/api/paas/v4" // 智谱AI API 地址 - }, - "minimax": { - provider: "minimax", - model: ["abab6.5s-chat", "abab5.5s-chat"], - baseUrl: "https://api.minimax.chat/v1" // 示例地址 - }, - "moonshot": { // 月之暗面 Kimi [citation:8] - provider: "moonshot", - model: ["moonshot-v1-128k", "moonshot-v1-32k", "moonshot-v1-8k"], - baseUrl: "https://api.moonshot.cn/v1" - } +export const CHAT_MODEL_MAP = { + "deepseek": ["deepseek-chat", "deepseek-reasoner"] }; - -export const IMAGE_MODEL_MAP: { [key: string]: ModelInfo } = { - "openai": { - provider: "openai", - model: ["dall-e-3", "dall-e-2", "gpt-image-1.5"], // GPT Image 1.5 是2025年底的新模型 [citation:2] - baseUrl: "https://api.openai.com/v1" - }, - "google": { - provider: "google", - model: ["imagen-3.0-generate-001", "imagen-3.0-fast-001"], // Imagen 3 系列 [citation:2][citation:10] - baseUrl: "https://us-central1-aiplatform.googleapis.com/v1" // Vertex AI 端点 - }, - "stabilityai": { - provider: "stabilityai", - model: ["stable-diffusion-3-5-large", "stable-diffusion-3-5-large-turbo", "stable-diffusion-3-medium"], - baseUrl: "https://api.stability.ai/v2beta" // Stability AI 官方API - }, - "black-forest-labs": { // 黑森林实验室,由前 Stability AI 成员创建,Flux 模型表现优异 [citation:2] - provider: "black-forest-labs", - model: ["flux-1.1-pro", "flux-1-pro", "flux-1-dev"], - baseUrl: "https://api.bfl.ml/v1" // Black Forest Labs 官方API - }, - "ideogram": { - provider: "ideogram", - model: ["ideogram-v2", "ideogram-v2-turbo"], - baseUrl: "https://api.ideogram.ai/v1" - }, - "midjourney": { // Midjourney 通常通过 Discord 调用,或通过第三方API [citation:5][citation:10] - provider: "midjourney", - model: ["midjourney-v7", "midjourney-v6"], - baseUrl: "https://api.midjourney.com/v1" // 官方API,可能需要申请 - }, - "bytedance": { // 字节跳动 [citation:2] - provider: "bytedance", - model: ["seedream-4.5", "seedream-3.0"], - baseUrl: "https://api.bytedance.com/v1" // 示例地址 - }, - "tencent": { - provider: "tencent", - model: ["hunyuan-image-3.0"], - baseUrl: "https://api.hunyuan.cloud.tencent.com/v1" // 示例地址 [citation:2] - } +export const CHAT_MODEL_TO_PROVIDER = Object.entries(CHAT_MODEL_MAP).reduce((acc, [provider, models]) => { + models.forEach(model => acc[model] = provider); + return acc; +}, {} as { [model: string]: string }); +export const IMAGE_MODEL_MAP = { + "zhipu": ["glm-4v-plus-0111", "glm-4v"] +}; +export const IMAGE_MODEL_TO_PROVIDER = Object.entries(IMAGE_MODEL_MAP).reduce((acc, [provider, models]) => { + models.forEach(model => acc[model] = provider); + return acc; +}, {} as { [model: string]: string }); +export const EMBEDDING_MODEL_MAP = { + "alibaba": ["text-embedding-v4", "text-embedding-v3"] }; +export const EMBEDDING_MODEL_TO_PROVIDER = Object.entries(EMBEDDING_MODEL_MAP).reduce((acc, [provider, models]) => { + models.forEach(model => acc[model] = provider); + return acc; +}, {} as { [model: string]: string }); -export const EMBEDDING_MODEL_MAP: { [key: string]: ModelInfo } = { - "openai": { - provider: "openai", - model: ["text-embedding-3-large", "text-embedding-3-small", "text-embedding-ada-002"], - baseUrl: "https://api.openai.com/v1" - }, - "google": { - provider: "google", - model: ["text-embedding-004", "text-multilingual-embedding-002"], - baseUrl: "https://generativelanguage.googleapis.com/v1beta" // 或 Vertex AI - }, - "cohere": { - provider: "cohere", - model: ["embed-english-v3.0", "embed-multilingual-v3.0"], - baseUrl: "https://api.cohere.ai/v1" - }, - "alibaba": { - provider: "alibaba", - model: ["text-embedding-v4", "text-embedding-v3"], - baseUrl: "https://dashscope.aliyuncs.com/compatible-mode/v1" // DashScope 兼容地址 - }, - "baidu": { - provider: "baidu", - model: ["embedding-v1"], - baseUrl: "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop" // 文心 Embedding API - }, - "zhipu": { - provider: "zhipu", - model: ["embedding-3", "embedding-2"], - baseUrl: "https://open.bigmodel.cn/api/paas/v4" // 智谱AI API - }, - "siliconflow": { // SiliconFlow 提供多种开源嵌入模型的托管服务 [citation:6] - provider: "siliconflow", - model: ["BAAI/bge-large-zh-v1.5", "BAAI/bge-large-en-v1.5", "Pro/BAAI/bge-m3"], - baseUrl: "https://api.siliconflow.cn/v1" - }, - "huggingface": { // Hugging Face 的 Inference API 可以调用多种嵌入模型 [citation:6] - provider: "huggingface", - model: ["sentence-transformers/all-MiniLM-L6-v2", "intfloat/multilingual-e5-large-instruct"], - baseUrl: "https://api-inference.huggingface.co/models/" - } -}; \ No newline at end of file +export const DEFAULT_CHAT_MODEL_BODY = { + "max_tokens": 4096, + "stop": null, + "stream": false +} +export const DEFAULT_IMAGE_MODEL_BODY = { + "max_tokens": 4096, + "stop": null, + "stream": false +} +export const DEFAULT_EMBEDDING_MODEL_BODY = { + "encoding_format": "float" +} \ No newline at end of file diff --git a/src/image/image.ts b/src/image/image.ts index 08f4fec..e0684b1 100644 --- a/src/image/image.ts +++ b/src/image/image.ts @@ -5,7 +5,7 @@ import { MessageSegment, parseSpecialTokens } from "../utils/string"; import { getSessionId } from "../utils/seal"; import { ModelManager } from "../agent/model"; -export class Image { +export default class Image { static validKeysMap: { [key in keyof Image]?: TypeDescriptor } = { imageId: 'string', sourceSessionId: 'string', @@ -115,11 +115,11 @@ export class Image { logger.error("在imageUrlToBase64中请求出错:", error); } - ImageService.saveImage(this); + Image.save(this); } async imageToText(prompt = '') { - const { defaultPrompt, urlToBase64, maxChars } = Config.image; + const { defaultPrompt, urlToBase64, maxChars } = Config.image; // wip if (urlToBase64 == '总是' && this.type === 'url') await this.urlToBase64(); @@ -139,14 +139,13 @@ export class Image { if (!this.description) logger.error(`图片${this.imageId}识别失败`); } -} -export class ImageService { + static imageMap: { [key: string]: Image } = {}; static generateImageId(): string { let id = generateId(), a = 0; - while (this.getImage(id)) { + while (this.get(id)) { id = generateId(); a++; if (a > 1000) { @@ -175,7 +174,7 @@ export class ImageService { return img; } - static getImage(imageId: string): Image | null { + static get(imageId: string): Image | null { if (!this.imageMap.hasOwnProperty(imageId)) { let img = new Image(); try { @@ -191,8 +190,7 @@ export class ImageService { } return this.imageMap[imageId]; } - - static saveImage(img: Image) { + static save(img: Image) { Config.ext.storageSet(`image_${img.imageId}`, JSON.stringify(img)); } @@ -211,7 +209,7 @@ export class ImageService { } static get LocalImageList() { - const { localImagePathMap } = Config.image; + const { localImagePathMap } = Config.image; // wip return Object.keys(localImagePathMap).map(id => this.createLocalImage(id, localImagePathMap[id])); } @@ -263,7 +261,7 @@ ${img.CQCode}`; switch (seg.type) { case 'img': { const id = seg.content; - const image = this.getImage(id); + const image = this.get(id); if (image) { if (image.type === 'url') await image.urlToBase64(); diff --git a/src/note.txt b/src/note.txt index e0fcd2b..bc6ffec 100644 --- a/src/note.txt +++ b/src/note.txt @@ -74,4 +74,6 @@ ai读取所有插件的指令并调用的功能 提供api给其他插件,实现kp agent -kwarg,p,存在页码和概率撞车的情况 \ No newline at end of file +kwarg,p,存在页码和概率撞车的情况 + +把manager合并入实例类里面,减少命名冗余 \ No newline at end of file diff --git a/src/session/group.ts b/src/session/group.ts index 5e4b474..e369246 100644 --- a/src/session/group.ts +++ b/src/session/group.ts @@ -4,7 +4,7 @@ import { revive, TypeDescriptor } from "../utils/utils"; import { MemoryService } from "./memory"; import { State } from "./session"; -export class Group { +export default class Group { static validKeysMap: { [key in keyof Group]?: TypeDescriptor } = { groupId: 'string', groupName: 'string', @@ -26,12 +26,11 @@ export class Group { state: State; //储存状态信息 memory: MemoryService; -} -export class GroupManager { + static groupMap: { [key: string]: Group }; - static getGroup(groupId: string): Group { + static get(groupId: string): Group { if (!this.groupMap.hasOwnProperty(groupId)) { let group = new Group(); try { @@ -45,4 +44,7 @@ export class GroupManager { } return this.groupMap[groupId]; } + static save(group: Group) { + Config.ext.storageSet(`group_${group.groupId}`, JSON.stringify(group)); + } } diff --git a/src/session/user.ts b/src/session/user.ts index 1874569..99f82a4 100644 --- a/src/session/user.ts +++ b/src/session/user.ts @@ -4,7 +4,7 @@ import { revive, TypeDescriptor } from "../utils/utils"; import { MemoryService } from "./memory"; import { State } from "./session"; -export class User { +export default class User { static validKeysMap: { [key in keyof User]?: TypeDescriptor } = { userId: 'string', userName: 'string', @@ -20,12 +20,11 @@ export class User { state: State; //储存状态信息 memory: MemoryService; -} -export class UserManager { + static userMap: { [key: string]: User }; - static getUser(userId: string): User { + static get(userId: string): User { if (!this.userMap.hasOwnProperty(userId)) { let user = new User(); try { @@ -39,4 +38,7 @@ export class UserManager { } return this.userMap[userId]; } + static save(user: User) { + Config.ext.storageSet(`user_${user.userId}`, JSON.stringify(user)); + } } \ No newline at end of file From f8df3144172680f387dcbd5e029cacad16655e4e Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Mon, 9 Mar 2026 19:58:32 +0800 Subject: [PATCH 21/33] # - # --- src/agent/agent.ts | 8 +++--- src/config/config.ts | 2 ++ src/config/configs/config_image.ts | 42 ------------------------------ src/config/configs/image.ts | 25 ++++++++++++++++++ src/config/configs/received.ts | 2 ++ src/image/image.ts | 24 ++++++++--------- src/note.txt | 6 ++++- src/session/session.ts | 4 +++ src/tool/tool.ts | 27 +++++-------------- src/tool/types.ts | 4 +++ 10 files changed, 63 insertions(+), 81 deletions(-) delete mode 100644 src/config/configs/config_image.ts create mode 100644 src/config/configs/image.ts diff --git a/src/agent/agent.ts b/src/agent/agent.ts index 9a901b6..5e5fd8f 100644 --- a/src/agent/agent.ts +++ b/src/agent/agent.ts @@ -34,12 +34,10 @@ export class Agent { async chat() { } -} -export class AgentManager { static agentMap: { [key: string]: Agent } = {}; - static getAgent(name: string): Agent { + static get(name: string): Agent { if (!this.agentMap.hasOwnProperty(name)) { let agent = new Agent(); try { @@ -54,11 +52,11 @@ export class AgentManager { return this.agentMap[name]; } - static saveAgent(agent: Agent) { + static save(agent: Agent) { Config.ext.storageSet(`agent_${agent.name}`, JSON.stringify(agent)); } - static initAgent() { + static init() { } } \ No newline at end of file diff --git a/src/config/config.ts b/src/config/config.ts index 5a8b552..4e92ac1 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -6,6 +6,7 @@ import ModelConfig from "./configs/model"; import BackendConfig from "./configs/backend"; import ReceivedConfig from "./configs/received"; import TriggerConfig from "./configs/trigger"; +import ImageConfig from "./configs/image"; const configMap = { base: BaseConfig, @@ -13,6 +14,7 @@ const configMap = { backend: BackendConfig, received: ReceivedConfig, trigger: TriggerConfig, + image: ImageConfig, } as const; type ConfigMap = typeof configMap; diff --git a/src/config/configs/config_image.ts b/src/config/configs/config_image.ts deleted file mode 100644 index 8e33b6a..0000000 --- a/src/config/configs/config_image.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Config } from "../config"; - -export class ImageConfig { - static ext: seal.ExtInfo; - - static register() { - ImageConfig.ext = Config.getExt('aiplugin4_5:图片'); - - seal.ext.registerTemplateConfig(ImageConfig.ext, "本地图片路径", ['data/images/sealdice.png'], "如不需要可以不填写,修改完需要重载js"); - seal.ext.registerBoolConfig(ImageConfig.ext, "是否接收图片", true, ""); - seal.ext.registerStringConfig(ImageConfig.ext, "图片识别需要满足的条件", '0', "使用豹语表达式,例如:$t群号_RAW=='2001'。若要开启所有图片自动识别转文字,请填写'1'"); - seal.ext.registerIntConfig(ImageConfig.ext, "发送图片的概率/%", 0, "在回复后发送本地图片或偷取图片的概率"); - seal.ext.registerStringConfig(ImageConfig.ext, "图片大模型URL", "https://open.bigmodel.cn/api/paas/v4/chat/completions"); - seal.ext.registerStringConfig(ImageConfig.ext, "图片API key", "yours"); - seal.ext.registerTemplateConfig(ImageConfig.ext, "图片body", [ - `"model":"glm-4v"`, - `"max_tokens":128`, - `"stop":null`, - `"stream":false`, - ], "messages不存在时,将会自动替换") - seal.ext.registerStringConfig(ImageConfig.ext, "图片识别默认prompt", "请帮我用简短的语言概括这张图片的特征,包括图片类型、场景、主题、主体等信息,如果有文字,请全部输出", ""); - seal.ext.registerOptionConfig(ImageConfig.ext, "识别图片时将url转换为base64", "永不", ["永不", "自动", "总是"], "解决大模型无法正常获取QQ图床图片的问题"); - seal.ext.registerIntConfig(ImageConfig.ext, "图片最大回复字符数", 500); - seal.ext.registerIntConfig(ImageConfig.ext, "偷取图片存储上限", 50, "每个群聊或私聊单独储存"); - } - - static get() { - return { - localImagePathMap: ConfigManager.getPathMapConfig(ImageConfig.ext, "本地图片路径"), - receiveImage: seal.ext.getBoolConfig(ImageConfig.ext, "是否接收图片"), - condition: seal.ext.getStringConfig(ImageConfig.ext, "图片识别需要满足的条件"), - p: seal.ext.getIntConfig(ImageConfig.ext, "发送图片的概率/%"), - url: seal.ext.getStringConfig(ImageConfig.ext, "图片大模型URL"), - apiKey: seal.ext.getStringConfig(ImageConfig.ext, "图片API key"), - bodyTemplate: seal.ext.getTemplateConfig(ImageConfig.ext, "图片body"), - defaultPrompt: seal.ext.getStringConfig(ImageConfig.ext, "图片识别默认prompt"), - urlToBase64: seal.ext.getOptionConfig(ImageConfig.ext, "识别图片时将url转换为base64"), - maxChars: seal.ext.getIntConfig(ImageConfig.ext, "图片最大回复字符数"), - maxStolenImageNum: seal.ext.getIntConfig(ImageConfig.ext, "偷取图片存储上限") - } - } -} \ No newline at end of file diff --git a/src/config/configs/image.ts b/src/config/configs/image.ts new file mode 100644 index 0000000..a3bc0ea --- /dev/null +++ b/src/config/configs/image.ts @@ -0,0 +1,25 @@ +import { Config, getPathMapConfig } from "../config"; + +export default class ImageConfig { + static ext: seal.ExtInfo; + + static register() { + ImageConfig.ext = Config.getExt('aiplugin4_5:图片'); + + seal.ext.registerTemplateConfig(ImageConfig.ext, "本地图片路径", ['data/images/sealdice.png'], "如不需要可以不填写,修改完需要重载js"); + seal.ext.registerStringConfig(ImageConfig.ext, "图片全局识别豹语条件", '0', "使用豹语表达式,例如:$t群号_RAW=='2001'。若要开启所有图片自动识别转文字,请填写'1'"); + seal.ext.registerStringConfig(ImageConfig.ext, "图片识别默认prompt", "请帮我用简短的语言概括这张图片的特征,包括图片类型、场景、主题、主体等信息,如果有文字,请全部输出", ""); + seal.ext.registerOptionConfig(ImageConfig.ext, "识别图片时将url转换为base64", "永不", ["永不", "自动", "总是"], "解决大模型无法正常获取QQ图床图片的问题"); + seal.ext.registerIntConfig(ImageConfig.ext, "图片转文字最大字符数", 500); + } + + static get() { + return { + LOCAL_IMAGE_PATH_MAP: getPathMapConfig(ImageConfig.ext, "本地图片路径"), + IMAGE_CONDITION: seal.ext.getStringConfig(ImageConfig.ext, "图片全局识别豹语条件"), + IMAGE_DEFAULT_PROMPT: seal.ext.getStringConfig(ImageConfig.ext, "图片识别默认prompt"), + URL_TO_BASE64: seal.ext.getOptionConfig(ImageConfig.ext, "识别图片时将url转换为base64"), + MAX_CHARS: seal.ext.getIntConfig(ImageConfig.ext, "图片转文字最大字符数") + } + } +} \ No newline at end of file diff --git a/src/config/configs/received.ts b/src/config/configs/received.ts index 90cc6a3..34df114 100644 --- a/src/config/configs/received.ts +++ b/src/config/configs/received.ts @@ -6,6 +6,7 @@ export default class ReceivedConfig { static register() { ReceivedConfig.ext = Config.getExt('aiplugin4:消息接收'); + seal.ext.registerBoolConfig(ReceivedConfig.ext, "接收图片", true, ""); seal.ext.registerBoolConfig(ReceivedConfig.ext, "接收指令消息", false, ""); seal.ext.registerBoolConfig(ReceivedConfig.ext, "接收骰子发送的消息", false, ""); seal.ext.registerBoolConfig(ReceivedConfig.ext, "忽略私聊消息", false, ""); @@ -17,6 +18,7 @@ export default class ReceivedConfig { static get() { return { + RECEIVE_IMAGE: seal.ext.getBoolConfig(ReceivedConfig.ext, "接收图片"), RECEIVE_CMD: seal.ext.getBoolConfig(ReceivedConfig.ext, "接收指令消息"), RECEIVE_MSG_BY_BOT: seal.ext.getBoolConfig(ReceivedConfig.ext, "接收骰子发送的消息"), IGNORE_PRIVATE: seal.ext.getBoolConfig(ReceivedConfig.ext, "忽略私聊消息"), diff --git a/src/image/image.ts b/src/image/image.ts index e0684b1..4d9f111 100644 --- a/src/image/image.ts +++ b/src/image/image.ts @@ -119,9 +119,9 @@ export default class Image { } async imageToText(prompt = '') { - const { defaultPrompt, urlToBase64, maxChars } = Config.image; // wip + const { IMAGE_DEFAULT_PROMPT, URL_TO_BASE64, MAX_CHARS } = Config.image; - if (urlToBase64 == '总是' && this.type === 'url') await this.urlToBase64(); + if (URL_TO_BASE64 == '总是' && this.type === 'url') await this.urlToBase64(); const model = ModelManager.getImageModel('image-understanding'); if (!model) { @@ -129,12 +129,12 @@ export default class Image { return; } - this.description = (await model.callITT(this.src, prompt ? prompt : defaultPrompt)).slice(0, maxChars); + this.description = (await model.callITT(this.src, prompt ? prompt : IMAGE_DEFAULT_PROMPT)).slice(0, MAX_CHARS); - if (!this.description && urlToBase64 === '自动' && this.type === 'url') { + if (!this.description && URL_TO_BASE64 === '自动' && this.type === 'url') { logger.info(`图片${this.imageId}第一次识别失败,自动尝试使用转换为base64`); await this.urlToBase64(); - this.description = (await model.callITT(this.src, prompt ? prompt : defaultPrompt)).slice(0, maxChars); + this.description = (await model.callITT(this.src, prompt ? prompt : IMAGE_DEFAULT_PROMPT)).slice(0, MAX_CHARS); } if (!this.description) logger.error(`图片${this.imageId}识别失败`); @@ -209,8 +209,8 @@ export default class Image { } static get LocalImageList() { - const { localImagePathMap } = Config.image; // wip - return Object.keys(localImagePathMap).map(id => this.createLocalImage(id, localImagePathMap[id])); + const { LOCAL_IMAGE_PATH_MAP } = Config.image; + return Object.keys(LOCAL_IMAGE_PATH_MAP).map(id => this.createLocalImage(id, LOCAL_IMAGE_PATH_MAP[id])); } static getLocalImageListText(p: number = 1): string { @@ -225,14 +225,14 @@ ${img.CQCode}`; } /** - * 提取并替换CQ码中的图片 + * 提取并替换CQ码中的图片 wip * @param ctx * @param message * @returns */ static async handleImageMessageSegment(ctx: seal.MsgContext, seg: MessageSegment): Promise<{ content: string, images: Image[] }> { - const { receiveImage } = Config.image; - if (!receiveImage || seg.type !== 'image') return { content: '', images: [] }; + const { RECEIVE_IMAGE } = Config.received; + if (!RECEIVE_IMAGE || seg.type !== 'image') return { content: '', images: [] }; let content = ''; const images: Image[] = []; @@ -241,8 +241,8 @@ ${img.CQCode}`; if (!file) return { content: '', images: [] }; const image = this.createUrlImage(getSessionId(ctx), file); - const { condition } = Config.image; - const fmtCondition = parseInt(seal.format(ctx, `{${condition}}`)); + const { IMAGE_CONDITION } = Config.image; + const fmtCondition = parseInt(seal.format(ctx, `{${IMAGE_CONDITION}}`)); if (fmtCondition === 1) await image.imageToText(); content += image.description ? `<|img:${image.imageId}:${image.description}|>` : `<|img:${image.imageId}|>`; diff --git a/src/note.txt b/src/note.txt index bc6ffec..00427eb 100644 --- a/src/note.txt +++ b/src/note.txt @@ -76,4 +76,8 @@ ai读取所有插件的指令并调用的功能 kwarg,p,存在页码和概率撞车的情况 -把manager合并入实例类里面,减少命名冗余 \ No newline at end of file +把manager合并入实例类里面,减少命名冗余 + +https://api-docs.deepseek.com/zh-cn/guides/thinking_mode#%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8 + +https://api-docs.deepseek.com/zh-cn/guides/tool_calls#%E6%80%9D%E8%80%83%E6%A8%A1%E5%BC%8F \ No newline at end of file diff --git a/src/session/session.ts b/src/session/session.ts index 97e3800..4ef6c72 100644 --- a/src/session/session.ts +++ b/src/session/session.ts @@ -79,6 +79,10 @@ export class Session { getImageMessages(): RequestMessage[] { return []; } + + getToolState(): ToolState {// 刷新工具状态 + return this.tool.state; + } } export class SessionService { diff --git a/src/tool/tool.ts b/src/tool/tool.ts index 571efd1..4658282 100644 --- a/src/tool/tool.ts +++ b/src/tool/tool.ts @@ -2,7 +2,7 @@ import { Config } from "../config/config" import { logger } from "../logger" import { fixJsonString } from "../utils/string"; import { Agent } from "../agent/agent" -import { ExtCmdInfo, ToolInfo, ToolListen } from "./types"; +import { ExtCmdInfo, ToolCall, ToolCallResult, ToolInfo, ToolListen } from "./types"; import { toolJrrp } from "./tools/jrrp"; export const toolMap = { @@ -30,11 +30,9 @@ export class Tool { this.callBack = true; this.solve = async (_, __, ___, ____) => "函数未实现"; - ToolService.toolMap[info.function.name] = this; + toolMap[info.function.name] = this; } -} -export class ToolService { static cmdArgs: seal.CmdArgs = null; /** @@ -90,14 +88,9 @@ export class ToolService { } /** - * 调用函数并返回tool_choice - * @param ctx - * @param msg - * @param ai - * @param tool_calls - * @returns tool_choice + * 调用多个函数 */ - static async handleToolCalls(ctx: seal.MsgContext, msg: seal.Message, ai: AI, tool_calls: ToolCall[]): Promise { + static async handleToolCalls(ctx: seal.MsgContext, msg: seal.Message, agent: Agent, tool_calls: ToolCall[]): Promise { const { maxCallCount } = Config.tool; if (tool_calls.length !== 0) { @@ -141,15 +134,7 @@ export class ToolService { return tool_choice; } - static async handleToolCall(ctx: seal.MsgContext, msg: seal.Message, ai: AI, tool_call: { - index: number, - id: string, - type: "function", - function: { - name: string, - arguments: string - } - }): Promise { + static async handleToolCall(ctx: seal.MsgContext, msg: seal.Message, agent: Agent, tool_call: ToolCall): Promise { const name = tool_call.function.name; if (Config.tool.toolsNotAllow.includes(name)) { logger.warning(`调用函数失败:禁止调用的函数:${name}`); @@ -219,7 +204,7 @@ export class ToolService { } } - static async handlePromptToolCall(ctx: seal.MsgContext, msg: seal.Message, ai: AI, tool_call_str: string): Promise { + static async handlePromptToolCalls(ctx: seal.MsgContext, msg: seal.Message, agent: Agent, tool_call_str: string): Promise { const { maxCallCount } = Config.tool; ai.tool.toolCallCount++; diff --git a/src/tool/types.ts b/src/tool/types.ts index 4a4e987..7178f28 100644 --- a/src/tool/types.ts +++ b/src/tool/types.ts @@ -86,6 +86,10 @@ export interface ToolCall { arguments: string } } +export interface ToolCallResult { + tool_call_id: string, + content: string +} export interface ExtCmdInfo { extName: string, // 使用的扩展名称 From 1b969841bb702bf18c7a5fb47d7da24f643fb94c Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Mon, 9 Mar 2026 21:09:50 +0800 Subject: [PATCH 22/33] =?UTF-8?q?=E7=BB=A7=E7=BB=AD=E9=87=8D=E6=9E=84tool?= =?UTF-8?q?=E2=80=A6=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config/config.ts | 2 + .../configs/{config_tool.ts => tool.ts} | 42 ++-- src/tool/tool.ts | 237 +++++------------- src/tool/tools/jrrp.ts | 15 +- src/tool/tools/sample.ts | 4 +- 5 files changed, 94 insertions(+), 206 deletions(-) rename src/config/configs/{config_tool.ts => tool.ts} (53%) diff --git a/src/config/config.ts b/src/config/config.ts index 4e92ac1..d3621c9 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -7,6 +7,7 @@ import BackendConfig from "./configs/backend"; import ReceivedConfig from "./configs/received"; import TriggerConfig from "./configs/trigger"; import ImageConfig from "./configs/image"; +import ToolConfig from "./configs/tool"; const configMap = { base: BaseConfig, @@ -15,6 +16,7 @@ const configMap = { received: ReceivedConfig, trigger: TriggerConfig, image: ImageConfig, + tool: ToolConfig, } as const; type ConfigMap = typeof configMap; diff --git a/src/config/configs/config_tool.ts b/src/config/configs/tool.ts similarity index 53% rename from src/config/configs/config_tool.ts rename to src/config/configs/tool.ts index 971dac8..2c7d5a5 100644 --- a/src/config/configs/config_tool.ts +++ b/src/config/configs/tool.ts @@ -1,13 +1,13 @@ -import { Config } from "../config"; +import { Config, getHandlebarsTemplateConfig, getPathMapConfig } from "../config"; -export class ToolConfig { +export default class ToolConfig { static ext: seal.ExtInfo; static register() { - ToolConfig.ext = ConfigManager.getExt('aiplugin4_2:函数调用'); + ToolConfig.ext = Config.getExt('aiplugin4_2:函数调用'); - seal.ext.registerBoolConfig(ToolConfig.ext, "是否开启调用函数功能", true, ""); - seal.ext.registerBoolConfig(ToolConfig.ext, "是否切换为提示词工程", false, "API在不支持function calling功能的时候开启"); + seal.ext.registerBoolConfig(ToolConfig.ext, "开启调用函数功能", true, ""); + seal.ext.registerBoolConfig(ToolConfig.ext, "切换为提示词工程", false, "API在不支持function calling功能的时候开启"); seal.ext.registerTemplateConfig(ToolConfig.ext, "工具函数prompt模板", [ `{{序号}}. 名称:{{{函数名称}}} - 描述:{{{函数描述}}} @@ -15,17 +15,9 @@ export class ToolConfig { - 必需参数:{{{必需参数}}}` ], "提示词工程中每个函数的prompt"); seal.ext.registerIntConfig(ToolConfig.ext, "允许连续调用函数次数", 5, "单次对话中允许连续调用函数的次数"); - seal.ext.registerTemplateConfig(ToolConfig.ext, "不允许调用的函数", [ - 'ban', - 'whole_ban', - 'get_ban_list' - ], "修改后保存并重载js"); - seal.ext.registerTemplateConfig(ToolConfig.ext, "默认关闭的函数", [ - 'rename', - 'record', - 'text_to_image' - ], ""); - seal.ext.registerTemplateConfig(ToolConfig.ext, "提供给AI的牌堆名称", ["克苏鲁神话"], "没有的话建议把draw_deck这个函数加入不允许调用"); + seal.ext.registerTemplateConfig(ToolConfig.ext, "禁止调用的函数", [''], "修改后保存并重载js"); + seal.ext.registerTemplateConfig(ToolConfig.ext, "默认关闭的函数", [''], ""); + seal.ext.registerTemplateConfig(ToolConfig.ext, "提供给AI的牌堆名称", [''], "没有的话建议把draw_deck这个函数加入不允许调用"); seal.ext.registerOptionConfig(ToolConfig.ext, "ai语音使用的音色", '傲娇少女', [ "小新", "猴哥", @@ -56,15 +48,15 @@ export class ToolConfig { static get() { return { - isTool: seal.ext.getBoolConfig(ToolConfig.ext, "是否开启调用函数功能"), - usePromptEngineering: seal.ext.getBoolConfig(ToolConfig.ext, "是否切换为提示词工程"), - toolsPromptTemplate: ConfigManager.getHandlebarsTemplateConfig(ToolConfig.ext, "工具函数prompt模板"), - maxCallCount: seal.ext.getIntConfig(ToolConfig.ext, "允许连续调用函数次数"), - toolsNotAllow: seal.ext.getTemplateConfig(ToolConfig.ext, "不允许调用的函数"), - toolsDefaultClosed: seal.ext.getTemplateConfig(ToolConfig.ext, "默认关闭的函数"), - decks: seal.ext.getTemplateConfig(ToolConfig.ext, "提供给AI的牌堆名称"), - character: seal.ext.getOptionConfig(ToolConfig.ext, "ai语音使用的音色"), - recordPathMap: ConfigManager.getPathMapConfig(ToolConfig.ext, "本地语音路径"), + STATUS: seal.ext.getBoolConfig(ToolConfig.ext, "开启调用函数功能"), + PROMPT_ENGINEERING: seal.ext.getBoolConfig(ToolConfig.ext, "切换为提示词工程"), + TOOLS_PROMPT_TEMPLATE: getHandlebarsTemplateConfig(ToolConfig.ext, "工具函数prompt模板"), + MAX_CALL_COUNT: seal.ext.getIntConfig(ToolConfig.ext, "允许连续调用函数次数"), + BLOCKED: seal.ext.getTemplateConfig(ToolConfig.ext, "禁止调用的函数"), + DEFAULT_CLOSED: seal.ext.getTemplateConfig(ToolConfig.ext, "默认关闭的函数"), + DECKS: seal.ext.getTemplateConfig(ToolConfig.ext, "提供给AI的牌堆名称"), + CHARACTER: seal.ext.getOptionConfig(ToolConfig.ext, "ai语音使用的音色"), + RECORD_PATH_MAP: getPathMapConfig(ToolConfig.ext, "本地语音路径") } } } \ No newline at end of file diff --git a/src/tool/tool.ts b/src/tool/tool.ts index 4658282..7e9382a 100644 --- a/src/tool/tool.ts +++ b/src/tool/tool.ts @@ -4,8 +4,9 @@ import { fixJsonString } from "../utils/string"; import { Agent } from "../agent/agent" import { ExtCmdInfo, ToolCall, ToolCallResult, ToolInfo, ToolListen } from "./types"; import { toolJrrp } from "./tools/jrrp"; +import { Session } from "../session/session"; -export const toolMap = { +export const toolMap: { [key: string]: Tool } = { jrrp: toolJrrp, } @@ -17,7 +18,7 @@ export class Tool { ExtCmdInfo: ExtCmdInfo; // 海豹指令信息 sessionType: 'any' | 'user' | 'group'; // 可使用函数的会话类型 callBack: boolean; // 是否回调智能体 - solve: (ctx: seal.MsgContext, msg: seal.Message, agent: Agent, args: { [key: string]: any }) => Promise; + solve: (ctx: seal.MsgContext, msg: seal.Message, session: Session, args: { [key: string]: any }) => Promise; constructor(info: ToolInfo) { this.toolInfo = info; @@ -90,74 +91,52 @@ export class Tool { /** * 调用多个函数 */ - static async handleToolCalls(ctx: seal.MsgContext, msg: seal.Message, agent: Agent, tool_calls: ToolCall[]): Promise { - const { maxCallCount } = Config.tool; + static async handleToolCalls(ctx: seal.MsgContext, msg: seal.Message, session: Session, tool_calls: ToolCall[]): Promise<{ result: ToolCallResult[], callBack: boolean }> { + const { MAX_CALL_COUNT } = Config.tool; - if (tool_calls.length !== 0) { - logger.info(`调用函数:`, tool_calls.map((item, i) => { - return `(${i}) ${item.function.name}:${item.function.arguments}`; - }).join('\n')); - } - - if (tool_calls.length + ai.tool.toolCallCount > maxCallCount) { - logger.warning('一次性调用超过上限,将进行截断操作……'); - tool_calls.splice(Math.max(0, maxCallCount - ai.tool.toolCallCount)); - } + const ret = { result: [], callBack: true }; - ai.tool.toolCallCount += tool_calls.length; - if (ai.tool.toolCallCount === maxCallCount) { - logger.warning('连续调用函数次数达到上限'); - } else if (ai.tool.toolCallCount === maxCallCount + tool_calls.length) { - logger.warning('连续调用函数次数超过上限'); - for (let i = 0; i < tool_calls.length; i++) { - const tool_call = tool_calls[i]; - await ai.context.addToolMessage(tool_call.id, `连续调用函数次数超过上限`, []); - ai.tool.toolCallCount++; - } - return "none"; - } else if (ai.tool.toolCallCount > maxCallCount + tool_calls.length) { - throw new Error('连续调用函数次数超过上限,已终止对话'); - } - - let tool_choice = 'none'; for (let i = 0; i < tool_calls.length; i++) { const tool_call = tool_calls[i]; - const tool_choice2 = await this.handleToolCall(ctx, msg, ai, tool_call); - - if (tool_choice2 === 'required') { - tool_choice = 'required'; - } else if (tool_choice === 'none' && tool_choice2 === 'auto') { - tool_choice = 'auto'; + if (session.tool.callCount > MAX_CALL_COUNT) { + logger.warning('工具调用超过上限'); + ret.result.push({ + tool_call_id: tool_call.id, + content: '工具调用超过上限' + }); + ret.callBack = false; + continue; } + const { result, callBack } = await this.handleToolCall(ctx, msg, session, tool_call); + ret.result.push(result); + ret.callBack = ret.callBack && callBack; + session.tool.callCount++; } - return tool_choice; + return ret; } - static async handleToolCall(ctx: seal.MsgContext, msg: seal.Message, agent: Agent, tool_call: ToolCall): Promise { + static async handleToolCall(ctx: seal.MsgContext, msg: seal.Message, session: Session, tool_call: ToolCall): Promise<{ result: ToolCallResult, callBack: boolean }> { const name = tool_call.function.name; - if (Config.tool.toolsNotAllow.includes(name)) { - logger.warning(`调用函数失败:禁止调用的函数:${name}`); - await ai.context.addToolMessage(tool_call.id, `调用函数失败:禁止调用的函数:${name}`, []); - return "none"; - } - if (!this.toolMap.hasOwnProperty(name)) { + if (!toolMap.hasOwnProperty(name)) { logger.warning(`调用函数失败:未注册的函数:${name}`); - await ai.context.addToolMessage(tool_call.id, `调用函数失败:未注册的函数:${name}`, []); - return "none"; + return { result: { tool_call_id: tool_call.id, content: `调用函数失败:未注册的函数:${name}` }, callBack: true }; + } + if (session.getToolState()?.[name]) { + logger.warning(`调用函数失败:未经许可的函数:${name}`); + return { result: { tool_call_id: tool_call.id, content: `调用函数失败:未经许可的函数:${name}` }, callBack: true }; } - - const tool = this.toolMap[name]; - if (tool.ExtCmdInfo.extName !== '' && this.cmdArgs == null) { + const tool = toolMap[name]; + if (tool.ExtCmdInfo.extName !== '' && this.cmdArgs === null) { logger.warning(`暂时无法调用函数,请先使用 .r 指令`); - await ai.context.addToolMessage(tool_call.id, `暂时无法调用函数,请先提示用户使用 .r 指令`, []); - return "none"; + return { result: { tool_call_id: tool_call.id, content: `暂时无法调用函数,请先提示用户使用 .r 指令` }, callBack: true }; } - if (tool.sessionType !== "all" && tool.sessionType !== msg.messageType) { - logger.warning(`调用函数失败:函数${name}可使用的场景类型为${tool.sessionType},当前场景类型为${msg.messageType}`); - await ai.context.addToolMessage(tool_call.id, `调用函数失败:函数${name}可使用的场景类型为${tool.sessionType},当前场景类型为${msg.messageType}`, []); - return "none"; + + const msgType = msg.messageType === 'private' ? 'user' : 'group'; + if (tool.sessionType !== "any" && tool.sessionType !== msgType) { + logger.warning(`调用函数失败:函数${name}可使用的场景类型为${tool.sessionType},当前场景类型为${msgType}`); + return { result: { tool_call_id: tool_call.id, content: `调用函数失败:函数${name}可使用的场景类型为${tool.sessionType},当前场景类型为${msgType}` }, callBack: true }; } let args = null; @@ -167,15 +146,13 @@ export class Tool { const fixedStr = fixJsonString(tool_call.function.arguments); if (fixedStr === '') { logger.error(`调用函数 (${name}:${tool_call.function.arguments}) 失败:${e.message}`); - await ai.context.addToolMessage(tool_call.id, `调用函数 (${name}:${tool_call.function.arguments}) 失败:${e.message}`, []); - return "none"; + return { result: { tool_call_id: tool_call.id, content: `调用函数 (${name}:${tool_call.function.arguments}) 失败:${e.message}` }, callBack: true }; } try { args = JSON.parse(fixedStr); } catch (e) { logger.error(`调用函数 (${name}:${tool_call.function.arguments}) 失败:${e.message}`); - await ai.context.addToolMessage(tool_call.id, `调用函数 (${name}:${tool_call.function.arguments}) 失败:${e.message}`, []); - return "none"; + return { result: { tool_call_id: tool_call.id, content: `调用函数 (${name}:${tool_call.function.arguments}) 失败:${e.message}` }, callBack: true }; } } @@ -183,136 +160,52 @@ export class Tool { try { if (args !== null && typeof args !== 'object') { logger.warning(`调用函数失败:arguement不是一个object`); - await ai.context.addToolMessage(tool_call.id, `调用函数失败:arguement不是一个object`, []); - return "auto"; + return { result: { tool_call_id: tool_call.id, content: `调用函数失败:arguement不是一个object` }, callBack: true }; } for (const key of tool.toolInfo.function.parameters.required) { if (!args.hasOwnProperty(key)) { logger.warning(`调用函数失败:缺少必需参数 ${key}`); - await ai.context.addToolMessage(tool_call.id, `调用函数失败:缺少必需参数 ${key}`, []); - return "auto"; + return { result: { tool_call_id: tool_call.id, content: `调用函数失败:缺少必需参数 ${key}` }, callBack: true }; } } - const { content, images } = await tool.solve(ctx, msg, ai, args); - await ai.context.addToolMessage(tool_call.id, content, images); - return tool.tool_choice; + const content = await tool.solve(ctx, msg, session, args); + return { result: { tool_call_id: tool_call.id, content }, callBack: true }; } catch (e) { logger.error(`调用函数 (${name}:${tool_call.function.arguments}) 失败:${e.message}`); - await ai.context.addToolMessage(tool_call.id, `调用函数 (${name}:${tool_call.function.arguments}) 失败:${e.message}`, []); - return "none"; + return { result: { tool_call_id: tool_call.id, content: `调用函数 (${name}:${tool_call.function.arguments}) 失败:${e.message}` }, callBack: true }; } } - static async handlePromptToolCalls(ctx: seal.MsgContext, msg: seal.Message, agent: Agent, tool_call_str: string): Promise { - const { maxCallCount } = Config.tool; - - ai.tool.toolCallCount++; - if (ai.tool.toolCallCount === maxCallCount) { - logger.warning('连续调用函数次数达到上限'); - } else if (ai.tool.toolCallCount === maxCallCount + 1) { - logger.warning('连续调用函数次数超过上限'); - await ai.context.addSystemUserMessage('调用函数返回', `连续调用函数次数超过上限`, []); - return; - } else if (ai.tool.toolCallCount > maxCallCount + 1) { - throw new Error('连续调用函数次数超过上限,已终止对话'); - } - - let tool_call: { - name: string, - arguments: { - [key: string]: any - } - } = null; - - try { - tool_call = JSON.parse(tool_call_str); - } catch (e) { - const fixedStr = fixJsonString(tool_call_str); - if (fixedStr === '') { - logger.error('解析tool_call时出现错误:', e); - await ai.context.addSystemUserMessage('调用函数返回', `解析tool_call时出现错误:${e.message}`, []); - return; - } - try { - tool_call = JSON.parse(fixedStr); - } catch (e) { - logger.error('解析tool_call时出现错误:', e); - await ai.context.addSystemUserMessage('调用函数返回', `解析tool_call时出现错误:${e.message}`, []); - return; - } - } - - if (!tool_call.hasOwnProperty('name') || !tool_call.hasOwnProperty('arguments')) { - logger.warning(`调用函数失败:缺少name或arguments`); - await ai.context.addSystemUserMessage('调用函数返回', `调用函数失败:缺少name或arguments`, []); - return; - } - - const name = tool_call.name; - if (Config.tool.toolsNotAllow.includes(name)) { - logger.warning(`调用函数失败:禁止调用的函数:${name}`); - await ai.context.addSystemUserMessage('调用函数返回', `调用函数失败:禁止调用的函数:${name}`, []); - return; - } - if (!this.toolMap.hasOwnProperty(name)) { - logger.warning(`调用函数失败:未注册的函数:${name}`); - await ai.context.addSystemUserMessage('调用函数返回', `调用函数失败:未注册的函数:${name}`, []); - return; - } - - - const tool = this.toolMap[name]; - if (tool.ExtCmdInfo.extName !== '' && this.cmdArgs == null) { - logger.warning(`暂时无法调用函数,请先使用 .r 指令`); - await ai.context.addSystemUserMessage('调用函数返回', `暂时无法调用函数,请先提示用户使用 .r 指令`, []); - return; - } - if (tool.sessionType !== "all" && tool.sessionType !== msg.messageType) { - logger.warning(`调用函数失败:函数${name}可使用的场景类型为${tool.sessionType},当前场景类型为${msg.messageType}`); - await ai.context.addSystemUserMessage('调用函数返回', `调用函数失败:函数${name}可使用的场景类型为${tool.sessionType},当前场景类型为${msg.messageType}`, []); - return; - } - + static async handlePromptToolCalls(ctx: seal.MsgContext, msg: seal.Message, session: Session, toolCallStr: string): Promise<{ result: ToolCallResult[], callBack: boolean }> { try { - const args = tool_call.arguments; - if (args !== null && typeof args !== 'object') { - logger.warning(`调用函数失败:arguement不是一个object`); - await ai.context.addSystemUserMessage('调用函数返回', `调用函数失败:arguement不是一个object`, []); - return; - } - for (const key of tool.toolInfo.function.parameters.required) { - if (!args.hasOwnProperty(key)) { - logger.warning(`调用函数失败:缺少必需参数 ${key}`); - await ai.context.addSystemUserMessage('调用函数返回', `调用函数失败:缺少必需参数 ${key}`, []); - return; - } + const data = JSON.parse(toolCallStr); + if (!Array.isArray(data)) { + logger.warning(`解析函数调用失败:tool_calls不是一个数组`); + return { result: [{ tool_call_id: '', content: `解析函数调用失败:tool_calls不是一个数组` }], callBack: true }; } - - const { content, images } = await tool.solve(ctx, msg, ai, args); - await ai.context.addSystemUserMessage('调用函数返回', content, images); + const tool_calls = data.map((item, index) => { + if (!item.hasOwnProperty('name') || !item.hasOwnProperty('arguments')) throw new Error(`缺少name或arguments属性`); + if (typeof item.name !== 'string' || typeof item.arguments !== 'string') throw new Error(`name或arguments不是字符串`); + return { + index: index, + id: index.toString(), + type: "function" as const, + function: { + name: item.name, + arguments: item.arguments + } + }; + }); + return await this.handleToolCalls(ctx, msg, session, tool_calls); } catch (e) { - logger.error(`调用函数 (${name}:${JSON.stringify(tool_call.arguments, null, 2)}) 失败:${e.message}`); - await ai.context.addSystemUserMessage('调用函数返回', `调用函数 (${name}:${JSON.stringify(tool_call.arguments, null, 2)}) 失败:${e.message}`, []); - } - } - - reviveToolStauts() { - const { toolsNotAllow, toolsDefaultClosed } = Config.tool; - const toolStatus: { [key: string]: boolean } = {}; - for (const k in ToolService.toolMap) { - if (!this.toolStatus.hasOwnProperty(k)) { - toolStatus[k] = !toolsNotAllow.includes(k) && !toolsDefaultClosed.includes(k); - } else if (toolsNotAllow.includes(k)) { - toolStatus[k] = false; - } else { - toolStatus[k] = this.toolStatus[k]; - } + logger.error(`解析函数调用失败:${e.message}`); + return { result: [{ tool_call_id: '', content: `解析函数调用失败:${e.message}` }], callBack: true }; } - this.toolStatus = toolStatus; } - getToolsInfo(type: string): ToolInfo[] { + // 生成prompt的逻辑 wip + static getToolsInfo(type: string): ToolInfo[] { if (type !== "private" && type !== "group") { type = "all"; } @@ -342,7 +235,7 @@ export class Tool { } } - getToolsPrompt(ctx: seal.MsgContext): string { + static getToolsPrompt(ctx: seal.MsgContext): string { const { toolsPromptTemplate } = Config.tool; const tools = this.getToolsInfo(ctx.isPrivate ? 'private' : 'group'); diff --git a/src/tool/tools/jrrp.ts b/src/tool/tools/jrrp.ts index ed283e4..993e5da 100644 --- a/src/tool/tools/jrrp.ts +++ b/src/tool/tools/jrrp.ts @@ -1,3 +1,4 @@ +import { getCtxAndMsg } from "../../utils/seal"; import { Tool } from "../tool"; const tool = new Tool({ @@ -22,17 +23,17 @@ tool.ExtCmdInfo = { cmd: 'jrrp', staticArgs: [] } -tool.solve = async (ctx, msg, agent, args) => { +tool.solve = async (ctx, msg, session, args) => { const { name } = args; - const ui = await ai.context.findUserInfo(ctx, name); - if (ui === null) return { content: `未找到<${name}>`, images: [] }; + const uid = await session.findUserId(ctx, name); + if (uid === '') return `未找到<${name}>`; - ({ ctx, msg } = getCtxAndMsg(ctx.endPoint.userId, ui.id, ctx.group.groupId)); - const [s, success] = await ToolManager.extensionSolve(ctx, msg, ai, tool.ExtCmdInfo, [], [], []); - if (!success) return { content: '今日人品查询失败', images: [] }; + ({ ctx, msg } = getCtxAndMsg(ctx.endPoint.userId, uid, ctx.group.groupId)); + const [s, success] = await Tool.extensionSolve(ctx, msg, session.tool.listen, tool.ExtCmdInfo, [], [], []); + if (!success) return '今日人品查询失败'; - return { content: s, images: [] }; + return s; } export { tool as toolJrrp } \ No newline at end of file diff --git a/src/tool/tools/sample.ts b/src/tool/tools/sample.ts index 1d38ab1..cfcd7bc 100644 --- a/src/tool/tools/sample.ts +++ b/src/tool/tools/sample.ts @@ -17,9 +17,9 @@ const tool = new Tool({ } } }); -tool.solve = async (ctx, msg, agent, args) => { +tool.solve = async (ctx, msg, session, args) => { const { arg } = args; - arg; ctx; msg; agent; + arg; ctx; msg; session; return "调用示例函数成功"; } From 58aeb9544c9edaf7b097e6a872f2ddbfd177f991 Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Mon, 9 Mar 2026 21:23:20 +0800 Subject: [PATCH 23/33] =?UTF-8?q?=E5=85=B6=E5=AE=9E=E6=9C=89=E7=9A=84?= =?UTF-8?q?=E5=9C=B0=E6=96=B9=E6=88=91=E8=BF=98=E6=B2=A1=E6=83=B3=E5=A5=BD?= =?UTF-8?q?=E6=80=8E=E4=B9=88=E5=86=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/agent/agent.ts | 10 +++++++--- src/agent/agents/compress_agent.ts | 7 ++++--- src/agent/agents/root_agent.ts | 7 ++++--- src/agent/agents/samples.ts | 7 ++++--- src/agent/model.ts | 20 +++++++++----------- 5 files changed, 28 insertions(+), 23 deletions(-) diff --git a/src/agent/agent.ts b/src/agent/agent.ts index 5e5fd8f..9816f09 100644 --- a/src/agent/agent.ts +++ b/src/agent/agent.ts @@ -2,8 +2,10 @@ import { Config } from "../config/config"; import { logger } from "../logger"; import { SessionService } from "../session/session"; import { revive, TypeDescriptor } from "../utils/utils"; +import Model from "./model"; +import { ChatModelUse, ModelUse } from "./types"; -export class Agent { +export default class Agent { static validKeysMap: { [key in keyof Agent]?: TypeDescriptor } = { sessionService: SessionService, tools: { array: 'string' }, @@ -13,6 +15,7 @@ export class Agent { name: string; description: string; instruction: string | ((sessionService: SessionService) => string); + use: ChatModelUse; sessionService: SessionService; tools: string[]; @@ -22,6 +25,7 @@ export class Agent { this.name = ""; this.description = ""; this.instruction = ""; + this.use = "chat"; this.sessionService = new SessionService(); this.tools = []; this.subAgents = []; @@ -31,8 +35,8 @@ export class Agent { getRequestTools() { } - async chat() { - + async chat(prompt: string): Promise { + const model = Model.getChatModel(this.use); } static agentMap: { [key: string]: Agent } = {}; diff --git a/src/agent/agents/compress_agent.ts b/src/agent/agents/compress_agent.ts index d47277a..4e65be2 100644 --- a/src/agent/agents/compress_agent.ts +++ b/src/agent/agents/compress_agent.ts @@ -1,8 +1,9 @@ -import { AgentManager } from "../agent"; +import Agent from "../agent"; -const compressAgent = AgentManager.getAgent("compress_agent"); +const compressAgent = Agent.get("compress_agent"); compressAgent.name = "compress_agent"; compressAgent.description = "压缩智能体"; compressAgent.instruction = "你是一个压缩智能体,你可以压缩文本。"; -AgentManager.saveAgent(compressAgent); +compressAgent.use = "compression"; +Agent.save(compressAgent); export { compressAgent }; diff --git a/src/agent/agents/root_agent.ts b/src/agent/agents/root_agent.ts index efd4a92..2c47d8d 100644 --- a/src/agent/agents/root_agent.ts +++ b/src/agent/agents/root_agent.ts @@ -1,8 +1,9 @@ -import { AgentManager } from "../agent"; +import Agent from "../agent"; -const rootAgent = AgentManager.getAgent("root_agent"); +const rootAgent = Agent.get("root_agent"); rootAgent.name = "root_agent"; rootAgent.description = "根智能体"; rootAgent.instruction = "你是一个根智能体,你可以调用其他智能体。"; -AgentManager.saveAgent(rootAgent); +rootAgent.use = "chat"; +Agent.save(rootAgent); export { rootAgent }; \ No newline at end of file diff --git a/src/agent/agents/samples.ts b/src/agent/agents/samples.ts index 32eb356..1aacff3 100644 --- a/src/agent/agents/samples.ts +++ b/src/agent/agents/samples.ts @@ -1,8 +1,9 @@ -import { AgentManager } from "../agent"; +import Agent from "../agent"; -const sampleAgent = AgentManager.getAgent("sample_agent"); +const sampleAgent = Agent.get("sample_agent"); sampleAgent.name = "sample_agent"; sampleAgent.description = "示例智能体"; sampleAgent.instruction = "你是一个示例智能体。"; -AgentManager.saveAgent(sampleAgent); +sampleAgent.use = "chat"; +Agent.save(sampleAgent); export { sampleAgent }; diff --git a/src/agent/model.ts b/src/agent/model.ts index 26d5ca5..1c61037 100644 --- a/src/agent/model.ts +++ b/src/agent/model.ts @@ -217,30 +217,28 @@ export class EmbeddingModel extends BaseModel { } } -export type Model = ChatModel | ImageModel | EmbeddingModel; - -export class ModelManager { +export default class Model { static chatModels: ChatModel[] = []; static imageModels: ImageModel[] = []; static embeddingModels: EmbeddingModel[] = []; static getChatModel(use: ChatModelUse): ChatModel | ImageModel | null { - const chatModelList = ModelManager.chatModels.filter(model => model.use.includes(use)); + const chatModelList = Model.chatModels.filter(model => model.use.includes(use)); if (chatModelList.length > 0) { const randomIndex = Math.floor(Math.random() * chatModelList.length); return chatModelList[randomIndex]; } - const ImageModelList = ModelManager.imageModels.filter(model => model.use.includes(use)); + const ImageModelList = Model.imageModels.filter(model => model.use.includes(use)); if (ImageModelList.length > 0) { const randomIndex = Math.floor(Math.random() * ImageModelList.length); return ImageModelList[randomIndex]; } - const chatModelAnyList = ModelManager.chatModels.filter(model => model.use.length === 0); + const chatModelAnyList = Model.chatModels.filter(model => model.use.length === 0); if (chatModelAnyList.length > 0) { const randomIndex = Math.floor(Math.random() * chatModelAnyList.length); return chatModelAnyList[randomIndex]; } - const ImageModelAnyList = ModelManager.imageModels.filter(model => model.use.length === 0); + const ImageModelAnyList = Model.imageModels.filter(model => model.use.length === 0); if (ImageModelAnyList.length > 0) { const randomIndex = Math.floor(Math.random() * ImageModelAnyList.length); return ImageModelAnyList[randomIndex]; @@ -249,12 +247,12 @@ export class ModelManager { } static getImageModel(use: ImageModelUse): ImageModel | null { - const ImageModelList = ModelManager.imageModels.filter(model => model.use.includes(use)); + const ImageModelList = Model.imageModels.filter(model => model.use.includes(use)); if (ImageModelList.length > 0) { const randomIndex = Math.floor(Math.random() * ImageModelList.length); return ImageModelList[randomIndex]; } - const ImageModelAnyList = ModelManager.imageModels.filter(model => model.use.length === 0); + const ImageModelAnyList = Model.imageModels.filter(model => model.use.length === 0); if (ImageModelAnyList.length > 0) { const randomIndex = Math.floor(Math.random() * ImageModelAnyList.length); return ImageModelAnyList[randomIndex]; @@ -263,12 +261,12 @@ export class ModelManager { } static getEmbeddingModel(use: EmbeddingModelUse): EmbeddingModel | null { - const EmbeddingModelList = ModelManager.embeddingModels.filter(model => model.use.includes(use)); + const EmbeddingModelList = Model.embeddingModels.filter(model => model.use.includes(use)); if (EmbeddingModelList.length > 0) { const randomIndex = Math.floor(Math.random() * EmbeddingModelList.length); return EmbeddingModelList[randomIndex]; } - const EmbeddingModelAnyList = ModelManager.embeddingModels.filter(model => model.use.length === 0); + const EmbeddingModelAnyList = Model.embeddingModels.filter(model => model.use.length === 0); if (EmbeddingModelAnyList.length > 0) { const randomIndex = Math.floor(Math.random() * EmbeddingModelAnyList.length); return EmbeddingModelAnyList[randomIndex]; From 33132407a0074431c9cea9d554eaf5a3a666a349 Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Tue, 10 Mar 2026 03:06:27 +0800 Subject: [PATCH 24/33] =?UTF-8?q?=E5=AE=8C=E6=88=90tool=E5=9F=BA=E7=A1=80?= =?UTF-8?q?=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/agent/agent.ts | 3 +- src/session/session.ts | 23 +++------- src/session/types.ts | 15 ++++++- src/tool/tool.ts | 98 ++++++++++++++++++------------------------ src/tool/types.ts | 40 ++++++++--------- 5 files changed, 85 insertions(+), 94 deletions(-) diff --git a/src/agent/agent.ts b/src/agent/agent.ts index 9816f09..ac1774c 100644 --- a/src/agent/agent.ts +++ b/src/agent/agent.ts @@ -1,6 +1,7 @@ import { Config } from "../config/config"; import { logger } from "../logger"; import { SessionService } from "../session/session"; +import { ToolName } from "../tool/tool"; import { revive, TypeDescriptor } from "../utils/utils"; import Model from "./model"; import { ChatModelUse, ModelUse } from "./types"; @@ -18,7 +19,7 @@ export default class Agent { use: ChatModelUse; sessionService: SessionService; - tools: string[]; + tools: ToolName[]; subAgents: string[]; constructor() { diff --git a/src/session/session.ts b/src/session/session.ts index 4ef6c72..1001c26 100644 --- a/src/session/session.ts +++ b/src/session/session.ts @@ -1,26 +1,16 @@ import { Config } from "../config/config"; import { logger } from "../logger"; -import { ToolCall, toolMap, ToolName, ToolState } from "../tool/tool"; +import { toolMap, ToolName, ToolState } from "../tool/tool"; import { ToolListen } from "../tool/types"; import { revive, TypeDescriptor } from "../utils/utils"; import { Context } from "./context"; import { MemoryService } from "./memory"; - -export class State { - [key: string]: any; -} - -export interface RequestMessage { - role: 'user' | 'assistant' | 'system' | 'tool'; - content?: string; - tool_calls?: ToolCall[]; - tool_call_id?: string; -} +import { RequestMessage, SessionType, State } from "./types"; export class Session { static validKeysMap: { [key in keyof Session]?: TypeDescriptor } = { - isPrivate: 'boolean', sessionId: 'string', + sessionType: 'string', state: 'any', context: Context, memory: MemoryService, @@ -31,8 +21,8 @@ export class Session { }, ignoredUserIdList: { array: 'string' }, } - isPrivate: boolean; sessionId: string; + sessionType: SessionType; state: State; context: Context; memory: MemoryService; @@ -44,8 +34,8 @@ export class Session { ignoredUserIdList: string[]; constructor() { - this.isPrivate = false; this.sessionId = ''; + this.sessionType = 'group'; this.state = {}; this.context = new Context(); this.memory = new MemoryService(); @@ -80,7 +70,7 @@ export class Session { return []; } - getToolState(): ToolState {// 刷新工具状态 + get toolState(): ToolState {// 刷新工具状态 wip return this.tool.state; } } @@ -105,6 +95,7 @@ export class SessionService { logger.error(`加载会话${sessionId}失败: ${error}`); } session.sessionId = sessionId; + if (sessionId.startsWith('QQ:')) session.sessionType = 'user'; this.sessionMap[sessionId] = session; } return this.sessionMap[sessionId]; diff --git a/src/session/types.ts b/src/session/types.ts index a6367da..df45e85 100644 --- a/src/session/types.ts +++ b/src/session/types.ts @@ -26,4 +26,17 @@ export interface ToolCallbackMessageItem extends BaseMessageItem { tool_call_id: string; } -export type MessageItem = UserMessageItem | AssistantMessageItem | SystemUserMessageItem | ToolCallsMessageItem | ToolCallbackMessageItem; \ No newline at end of file +export type MessageItem = UserMessageItem | AssistantMessageItem | SystemUserMessageItem | ToolCallsMessageItem | ToolCallbackMessageItem; + +export interface State { + [key: string]: any; +} + +export interface RequestMessage { + role: 'user' | 'assistant' | 'system' | 'tool'; + content?: string; + tool_calls?: ToolCall[]; + tool_call_id?: string; +} + +export type SessionType = 'user' | 'group'; \ No newline at end of file diff --git a/src/tool/tool.ts b/src/tool/tool.ts index 7e9382a..bd584d7 100644 --- a/src/tool/tool.ts +++ b/src/tool/tool.ts @@ -1,12 +1,13 @@ import { Config } from "../config/config" import { logger } from "../logger" import { fixJsonString } from "../utils/string"; -import { Agent } from "../agent/agent" +import Agent from "../agent/agent" import { ExtCmdInfo, ToolCall, ToolCallResult, ToolInfo, ToolListen } from "./types"; import { toolJrrp } from "./tools/jrrp"; import { Session } from "../session/session"; +import { SessionType } from "../session/types"; -export const toolMap: { [key: string]: Tool } = { +export const toolMap = { jrrp: toolJrrp, } @@ -16,7 +17,7 @@ export type ToolState = { [key in ToolName]: boolean }; export class Tool { toolInfo: ToolInfo; ExtCmdInfo: ExtCmdInfo; // 海豹指令信息 - sessionType: 'any' | 'user' | 'group'; // 可使用函数的会话类型 + sessionType: 'any' | SessionType; // 可使用函数的会话类型 callBack: boolean; // 是否回调智能体 solve: (ctx: seal.MsgContext, msg: seal.Message, session: Session, args: { [key: string]: any }) => Promise; @@ -88,41 +89,13 @@ export class Tool { }); } - /** - * 调用多个函数 - */ - static async handleToolCalls(ctx: seal.MsgContext, msg: seal.Message, session: Session, tool_calls: ToolCall[]): Promise<{ result: ToolCallResult[], callBack: boolean }> { - const { MAX_CALL_COUNT } = Config.tool; - - const ret = { result: [], callBack: true }; - - for (let i = 0; i < tool_calls.length; i++) { - const tool_call = tool_calls[i]; - if (session.tool.callCount > MAX_CALL_COUNT) { - logger.warning('工具调用超过上限'); - ret.result.push({ - tool_call_id: tool_call.id, - content: '工具调用超过上限' - }); - ret.callBack = false; - continue; - } - const { result, callBack } = await this.handleToolCall(ctx, msg, session, tool_call); - ret.result.push(result); - ret.callBack = ret.callBack && callBack; - session.tool.callCount++; - } - - return ret; - } - static async handleToolCall(ctx: seal.MsgContext, msg: seal.Message, session: Session, tool_call: ToolCall): Promise<{ result: ToolCallResult, callBack: boolean }> { const name = tool_call.function.name; if (!toolMap.hasOwnProperty(name)) { logger.warning(`调用函数失败:未注册的函数:${name}`); return { result: { tool_call_id: tool_call.id, content: `调用函数失败:未注册的函数:${name}` }, callBack: true }; } - if (session.getToolState()?.[name]) { + if (session.toolState?.[name]) { logger.warning(`调用函数失败:未经许可的函数:${name}`); return { result: { tool_call_id: tool_call.id, content: `调用函数失败:未经许可的函数:${name}` }, callBack: true }; } @@ -176,7 +149,30 @@ export class Tool { return { result: { tool_call_id: tool_call.id, content: `调用函数 (${name}:${tool_call.function.arguments}) 失败:${e.message}` }, callBack: true }; } } + static async handleToolCalls(ctx: seal.MsgContext, msg: seal.Message, session: Session, tool_calls: ToolCall[]): Promise<{ result: ToolCallResult[], callBack: boolean }> { + const { MAX_CALL_COUNT } = Config.tool; + + const ret = { result: [], callBack: true }; + + for (let i = 0; i < tool_calls.length; i++) { + const tool_call = tool_calls[i]; + if (session.tool.callCount > MAX_CALL_COUNT) { + logger.warning('工具调用超过上限'); + ret.result.push({ + tool_call_id: tool_call.id, + content: '工具调用超过上限' + }); + ret.callBack = false; + continue; + } + const { result, callBack } = await this.handleToolCall(ctx, msg, session, tool_call); + ret.result.push(result); + ret.callBack = ret.callBack && callBack; + session.tool.callCount++; + } + return ret; + } static async handlePromptToolCalls(ctx: seal.MsgContext, msg: seal.Message, session: Session, toolCallStr: string): Promise<{ result: ToolCallResult[], callBack: boolean }> { try { const data = JSON.parse(toolCallStr); @@ -204,23 +200,18 @@ export class Tool { } } - // 生成prompt的逻辑 wip - static getToolsInfo(type: string): ToolInfo[] { - if (type !== "private" && type !== "group") { - type = "all"; - } - - const tools = Object.keys(this.toolStatus) + static getToolsInfo(session: Session): ToolInfo[] | null { + const toolState = session.toolState; + const sessionType = session.sessionType; + const tools = Object.keys(toolState) .map(key => { - if (this.toolStatus[key]) { - if (!ToolService.toolMap.hasOwnProperty(key)) { - logger.error(`在getToolsInfo中找不到工具:${key}`); - return null; - } - const tool = ToolService.toolMap[key]; - if (tool.sessionType !== "all" && tool.sessionType !== type) { + if (toolState[key]) { + if (!toolMap.hasOwnProperty(key)) { + logger.warning(`在getToolsInfo中找不到工具:${key}`); return null; } + const tool: Tool = toolMap[key]; + if (tool.sessionType !== "any" && tool.sessionType !== sessionType) return null; return tool.toolInfo; } else { return null; @@ -228,20 +219,15 @@ export class Tool { }) .filter(item => item !== null); - if (tools.length === 0) { - return null; - } else { - return tools; - } + return tools.length > 0 ? tools : null; } + static getToolsInfoPrompt(session: Session): string { + const { TOOLS_PROMPT_TEMPLATE } = Config.tool; - static getToolsPrompt(ctx: seal.MsgContext): string { - const { toolsPromptTemplate } = Config.tool; - - const tools = this.getToolsInfo(ctx.isPrivate ? 'private' : 'group'); + const tools = this.getToolsInfo(session); if (tools && tools.length > 0) { return tools.map((item, index) => { - return toolsPromptTemplate({ + return TOOLS_PROMPT_TEMPLATE({ "序号": index + 1, "函数名称": item.function.name, "函数描述": item.function.description, diff --git a/src/tool/types.ts b/src/tool/types.ts index 7178f28..aefe580 100644 --- a/src/tool/types.ts +++ b/src/tool/types.ts @@ -1,10 +1,22 @@ +export interface ToolInfoObject { + type: "object"; + description?: string; + properties?: { + [key: string]: ToolInfoItem; + }; + required?: (keyof ToolInfoObject["properties"])[]; + additionalProperties?: boolean | ToolInfoItem; + minProperties?: number; + maxProperties?: number; +} + export interface ToolInfoString { type: "string"; description?: string; enum?: string[]; minLength?: number; maxLength?: number; - pattern?: string; + pattern?: string; // 正则表达式 format?: "date-time" | "email" | "uri" | "uuid" | "hostname" | "ipv4" | "ipv6"; } @@ -28,16 +40,6 @@ export interface ToolInfoInteger { multipleOf?: number; } -export interface ToolInfoBoolean { - type: "boolean"; - description?: string; -} - -export interface ToolInfoNull { - type: "null"; - description?: string; -} - export interface ToolInfoArray { type: "array"; description?: string; @@ -47,16 +49,14 @@ export interface ToolInfoArray { uniqueItems?: boolean; } -export interface ToolInfoObject { - type: "object"; +export interface ToolInfoBoolean { + type: "boolean"; + description?: string; +} + +export interface ToolInfoNull { + type: "null"; description?: string; - properties?: { - [key: string]: ToolInfoItem; - }; - required?: (keyof ToolInfoObject["properties"])[]; - additionalProperties?: boolean | ToolInfoItem; - minProperties?: number; - maxProperties?: number; } export type ToolInfoItem = From 473aac953936d885f7a741e043e6feaedb4e66cb Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Tue, 10 Mar 2026 20:08:52 +0800 Subject: [PATCH 25/33] =?UTF-8?q?=E5=AE=8C=E5=96=84=E4=B8=80=E4=B8=8Btool?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/agent/agent.ts | 1 + src/session/session.ts | 18 +++++++++++++++++- src/tool/tool.ts | 3 +-- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/agent/agent.ts b/src/agent/agent.ts index ac1774c..7205e29 100644 --- a/src/agent/agent.ts +++ b/src/agent/agent.ts @@ -28,6 +28,7 @@ export default class Agent { this.instruction = ""; this.use = "chat"; this.sessionService = new SessionService(); + this.sessionService.agentName = this.name; this.tools = []; this.subAgents = []; } diff --git a/src/session/session.ts b/src/session/session.ts index 1001c26..199a5c0 100644 --- a/src/session/session.ts +++ b/src/session/session.ts @@ -1,3 +1,4 @@ +import Agent from "../agent/agent"; import { Config } from "../config/config"; import { logger } from "../logger"; import { toolMap, ToolName, ToolState } from "../tool/tool"; @@ -11,6 +12,7 @@ export class Session { static validKeysMap: { [key in keyof Session]?: TypeDescriptor } = { sessionId: 'string', sessionType: 'string', + agentName: 'string', state: 'any', context: Context, memory: MemoryService, @@ -23,6 +25,7 @@ export class Session { } sessionId: string; sessionType: SessionType; + agentName: string; state: State; context: Context; memory: MemoryService; @@ -36,6 +39,7 @@ export class Session { constructor() { this.sessionId = ''; this.sessionType = 'group'; + this.agentName = ''; this.state = {}; this.context = new Context(); this.memory = new MemoryService(); @@ -70,18 +74,29 @@ export class Session { return []; } - get toolState(): ToolState {// 刷新工具状态 wip + get toolState(): ToolState { + const { BLOCKED, DEFAULT_CLOSED } = Config.tool; + const tools = Agent.get(this.agentName).tools; + const state: ToolState = {}; + tools.forEach(tool => { + if (BLOCKED.includes(tool)) return; + if (!this.state.hasOwnProperty(tool)) this.state[tool] = !DEFAULT_CLOSED.includes(tool); + state[tool] = this.state[tool]; + }) return this.tool.state; } } export class SessionService { static validKeysMap: { [key in keyof SessionService]?: TypeDescriptor } = { + agentName: 'string', sessionMap: { objectValue: Session }, } + agentName: string; sessionMap: { [key: string]: Session }; constructor() { + this.agentName = ''; this.sessionMap = {}; } @@ -96,6 +111,7 @@ export class SessionService { } session.sessionId = sessionId; if (sessionId.startsWith('QQ:')) session.sessionType = 'user'; + session.agentName = this.agentName; this.sessionMap[sessionId] = session; } return this.sessionMap[sessionId]; diff --git a/src/tool/tool.ts b/src/tool/tool.ts index bd584d7..344f4c1 100644 --- a/src/tool/tool.ts +++ b/src/tool/tool.ts @@ -1,7 +1,6 @@ import { Config } from "../config/config" import { logger } from "../logger" import { fixJsonString } from "../utils/string"; -import Agent from "../agent/agent" import { ExtCmdInfo, ToolCall, ToolCallResult, ToolInfo, ToolListen } from "./types"; import { toolJrrp } from "./tools/jrrp"; import { Session } from "../session/session"; @@ -12,7 +11,7 @@ export const toolMap = { } export type ToolName = keyof typeof toolMap; -export type ToolState = { [key in ToolName]: boolean }; +export type ToolState = { [key in ToolName]?: boolean }; export class Tool { toolInfo: ToolInfo; From 0d48db972f2cba931234f5f8b0c2e333c366e256 Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Wed, 11 Mar 2026 00:22:39 +0800 Subject: [PATCH 26/33] =?UTF-8?q?=E7=A8=8D=E5=BE=AE=E5=AF=B9memory?= =?UTF-8?q?=E8=BF=9B=E8=A1=8C=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/session/memory.ts | 207 +++++++++--------- src/session/session.ts | 3 - src/session/types.ts | 11 +- src/tool/tool.ts | 4 +- src/tool/tools/builtin_cmd.ts/init.ts | 5 + src/tool/tools/{ => builtin_cmd.ts}/jrrp.ts | 4 +- .../tools/{ => builtin_cmd.ts}/tool_attr.ts | 0 .../tools/{ => builtin_cmd.ts}/tool_modu.ts | 0 .../{ => builtin_cmd.ts}/tool_roll_check.ts | 0 src/tool/tools/{ => image.ts}/tool_image.ts | 0 src/tool/tools/{ => image.ts}/tool_meme.ts | 0 src/tool/tools/{ => image.ts}/tool_render.ts | 0 src/tool/tools/{ => ob11.ts}/tool_ban.ts | 0 .../tools/{ => ob11.ts}/tool_essence_msg.ts | 0 .../tools/{ => ob11.ts}/tool_group_sign.ts | 0 .../tools/{ => ob11.ts}/tool_person_info.ts | 0 src/tool/tools/{ => ob11.ts}/tool_qq_list.ts | 0 src/tool/tools/{ => ob11.ts}/tool_rename.ts | 0 src/tool/tools/{ => seal.ts}/tool_deck.ts | 0 src/utils/utils.ts | 16 +- 20 files changed, 123 insertions(+), 127 deletions(-) create mode 100644 src/tool/tools/builtin_cmd.ts/init.ts rename src/tool/tools/{ => builtin_cmd.ts}/jrrp.ts (91%) rename src/tool/tools/{ => builtin_cmd.ts}/tool_attr.ts (100%) rename src/tool/tools/{ => builtin_cmd.ts}/tool_modu.ts (100%) rename src/tool/tools/{ => builtin_cmd.ts}/tool_roll_check.ts (100%) rename src/tool/tools/{ => image.ts}/tool_image.ts (100%) rename src/tool/tools/{ => image.ts}/tool_meme.ts (100%) rename src/tool/tools/{ => image.ts}/tool_render.ts (100%) rename src/tool/tools/{ => ob11.ts}/tool_ban.ts (100%) rename src/tool/tools/{ => ob11.ts}/tool_essence_msg.ts (100%) rename src/tool/tools/{ => ob11.ts}/tool_group_sign.ts (100%) rename src/tool/tools/{ => ob11.ts}/tool_person_info.ts (100%) rename src/tool/tools/{ => ob11.ts}/tool_qq_list.ts (100%) rename src/tool/tools/{ => ob11.ts}/tool_rename.ts (100%) rename src/tool/tools/{ => seal.ts}/tool_deck.ts (100%) diff --git a/src/session/memory.ts b/src/session/memory.ts index cfc19c0..0cc93f0 100644 --- a/src/session/memory.ts +++ b/src/session/memory.ts @@ -1,6 +1,6 @@ import { Config } from "../config/config"; import { Context } from "./context"; -import { cosineSimilarity, generateId, getCommonGroup, getCommonKeyword, getCommonUser, revive, TypeDescriptor } from "../utils/utils"; +import { cosineSimilarity, generateId, getCommonGroup, getCommonKeyword, getCommonItem, revive, TypeDescriptor } from "../utils/utils"; import { logger } from "../logger"; import { fetchData, getEmbedding } from "../agent/service"; import { buildContent, getRoleSetting, parseBody } from "../utils/message"; @@ -8,71 +8,60 @@ import { ToolService } from "../tool/tool"; import { fmtDate } from "../utils/string"; import { Image } from "../image/image"; -export interface searchOptions { - topK: number; - userIdList: string[]; - groupIdList: string[]; - keywords: string[]; - includeImages: boolean; - method: 'weight' | 'similarity' | 'score' | 'early' | 'late' | 'recent'; -} - export class MemoryItem { static validKeysMap: { [key in keyof MemoryItem]?: TypeDescriptor } = { 'id': 'string', - 'sourceSessionId': 'string', - 'createTime': 'number', - 'lastMentionTime': 'number', - 'weight': 'number', + 'sessionId': 'string', + 'visibility': 'string', + 'createAt': 'number', + 'lastAccessedAt': 'number', + 'accessCount': 'number', + 'importance': 'number', 'content': 'string', 'vector': { array: 'number' }, - 'keywords': { array: 'string' }, - 'userIdList': { array: 'string' }, - 'groupIdList': { array: 'string' }, - 'imageIdList': { array: 'string' } + 'tags': { array: 'string' }, + 'relatedMemories': { array: 'string' }, + 'users': { array: 'string' }, + 'groups': { array: 'string' } }; + // 核心字段 id: string; // 记忆ID - sourceSessionId: string; // 记忆来源会话ID - createTime: number; // 秒级时间戳 - lastMentionTime: number; // 最后被提及时间,秒级时间戳 - weight: number; // 记忆权重,0-10 + sessionId: string; // 记忆来源会话ID + visibility: 'public' | 'private'; // 记忆可见性 + + // 淘汰策略相关 + createAt: number; // 创建时间 TTL + lastAccessedAt: number; // 最后访问时间 LRU + accessCount: number; // 访问次数 LFU + importance: number; // 重要性0-1 + // 内容 content: string; // 记忆内容 vector: number[]; // 记忆向量 - keywords: string[]; // 记忆关键词列表 - userIdList: string[]; // 记忆相关用户ID列表 - groupIdList: string[]; // 记忆相关群组ID列表 - imageIdList: string[]; // 记忆相关图片ID列表 + tags: string[]; // 记忆标签列表 + relatedMemories: string[]; // 相关记忆ID列表 + users: string[]; // 记忆相关用户ID列表 + groups: string[]; // 记忆相关群组ID列表 constructor() { this.id = ''; - this.sourceSessionId = ''; - this.createTime = 0; - this.lastMentionTime = 0; - this.weight = 0; + this.sessionId = ''; + this.visibility = 'public'; + this.createAt = 0; + this.lastAccessedAt = 0; + this.accessCount = 0; + this.importance = 0; this.content = ''; this.vector = []; - this.keywords = []; - this.userIdList = []; - this.groupIdList = []; - this.imageIdList = []; + this.tags = []; + this.relatedMemories = []; + this.users = []; + this.groups = []; } get copy(): MemoryItem { - const m = new MemoryItem(); - m.id = this.id; - m.sourceSessionId = this.sourceSessionId; - m.createTime = this.createTime; - m.lastMentionTime = this.lastMentionTime; - m.weight = this.weight; - m.content = this.content; - m.vector = [...this.vector]; - m.keywords = [...this.keywords]; - m.userIdList = [...this.userIdList]; - m.groupIdList = [...this.groupIdList]; - m.imageIdList = [...this.imageIdList]; - return m; + return revive(MemoryItem, JSON.parse(JSON.stringify(this))); } /** @@ -81,55 +70,61 @@ export class MemoryItem { */ get decay() { const now = Math.floor(Date.now() / 1000); - const ageInDays = (now - this.createTime) / (24 * 60 * 60); - const activityInHours = (now - this.lastMentionTime) / (60 * 60); - // 基础新鲜度: exp(-ageInDays / 7) - const ageDecay = Math.exp(-ageInDays / 7); - // 活跃度: exp(-activityInHours / 4) - const activityDecay = Math.exp(-activityInHours / 4); - // 衰减因子,取年龄衰减和活跃度衰减的较大值 - return Math.max(ageDecay, activityDecay); + // 年龄(天) + const age = (now - this.createAt) / (24 * 60 * 60); + // 活跃时间(小时) + const activity = (now - this.lastAccessedAt) / (60 * 60); + // 年龄衰减: 半衰期7天 + const ageDecay = Math.exp(-age / 7 * Math.LN2); + // 活跃衰减: 半衰期4小时 + const activityDecay = Math.exp(-activity / 4 * Math.LN2); + // 衰减因子 + return ageDecay * 0.7 + activityDecay * 0.3; // 一拍脑门决定的加权 + } + + get accessScore() { + // 饱和函数,访问次数归一化 + const accessNorm = 1 - 1 / (this.accessCount + 1); + return accessNorm * this.decay; } /** * 计算记忆与查询的相似度分数 * @param v 查询向量 - * @param ul 查询用户列表 - * @param gl 查询群组列表 - * @param kws 查询关键词列表 + * @param u 查询用户列表 + * @param g 查询群组列表 + * @param t 查询标签列表 * @returns 相似度分数(0-1) */ - calculateSimilarity(v: number[], ul: string[], gl: string[], kws: string[]): number { + calculateSimilarity(v: number[], u: string[], g: string[], t: string[]): number { // 总权重 0-1 - const totalWeight = (v.length ? 0.4 : 0) + (ul.length ? 0.2 : 0) + (gl.length ? 0.2 : 0) + (kws.length ? 0.2 : 0); - if (totalWeight === 0) return 0; + const tw = (v.length ? 0.4 : 0) + (u.length ? 0.2 : 0) + (g.length ? 0.2 : 0) + (t.length ? 0.2 : 0); + if (tw === 0) return 0; // 向量相似度分数(如果提供了向量v) 0-1 - const vectorSimilarity = (v && v.length > 0 && this.vector && this.vector.length > 0) ? (cosineSimilarity(v, this.vector) + 1) / 2 : 0; + const vs = (v && v.length > 0 && this.vector && this.vector.length > 0) ? (cosineSimilarity(v, this.vector) + 1) / 2 : 0; // 用户相似度分数 0-1 - const commonUser = getCommonUser(this.userIdList, ul); - const userSimilarity = (ul && ul.length > 0) ? commonUser.length / ul.length : 0; + const us = u.length ? getCommonItem(this.users, u).length / new Set([...this.users, ...u]).size : 0; // 群组相似度分数 0-1 - const commonGroup = getCommonGroup(this.groupIdList, gl); - const groupSimilarity = (gl && gl.length > 0) ? commonGroup.length / gl.length : 0; - // 关键词匹配分数 0-1 - const commonKeyword = getCommonKeyword(this.keywords, kws); - const keywordSimilarity = (kws && kws.length > 0) ? commonKeyword.length / kws.length : 0; + const gs = g.length ? getCommonItem(this.groups, g).length / new Set([...this.groups, ...g]).size : 0; + // 标签匹配分数 0-1 + const ts = t.length ? getCommonItem(this.tags, t).length / new Set([...this.tags, ...t]).size : 0; // 综合相似度分数 0-1 - const avgSimilarity = vectorSimilarity * 0.4 + userSimilarity * 0.2 + groupSimilarity * 0.2 + keywordSimilarity * 0.2; + const avs = vs * 0.4 + us * 0.2 + gs * 0.2 + ts * 0.2; // 相似度增强因子 0-1 - return avgSimilarity / totalWeight; + return avs / tw; } /** * 计算记忆的最终分数 * @param v 查询向量 - * @param ul 查询用户列表 - * @param gl 查询群组列表 - * @param kws 查询关键词列表 + * @param u 查询用户列表 + * @param g 查询群组列表 + * @param t 查询标签列表 * @returns 相似度分数(0-1) */ - calculateScore(v: number[], ul: string[], gl: string[], kws: string[]): number { - return this.weight * 0.03 + this.calculateSimilarity(v, ul, gl, kws) * 0.7; + calculateScore(v: number[], u: string[], g: string[], t: string[]): number { + const similarity = this.calculateSimilarity(v, u, g, t); + return this.importance * 0.2 + this.accessScore * 0.2 + similarity * 0.6; } async updateVector() { @@ -170,16 +165,16 @@ export class MemoryService { get keywords() { const keywords = new Set(); - this.memoryList.forEach(m => m.keywords.forEach(kw => keywords.add(kw))); + this.memoryList.forEach(m => m.tags.forEach(kw => keywords.add(kw))); return Array.from(keywords); } async addMemory(sid: string, ul: string[], gl: string[], kws: string[], il: string[], content: string) { for (const id of this.memoryIdList) { const m = this.memoryMap[id]; - if (content === m.content && sid === m.sourceSessionId && getCommonUser(ul, m.userIdList).length > 0 && getCommonGroup(gl, m.groupIdList).length > 0) { - m.keywords = Array.from(new Set([...m.keywords, ...kws])); - logger.info(`记忆已存在,id:${id},合并关键词:${m.keywords.join(',')}`); + if (content === m.content && sid === m.sessionId && getCommonItem(ul, m.users).length > 0 && getCommonGroup(gl, m.groups).length > 0) { + m.tags = Array.from(new Set([...m.tags, ...kws])); + logger.info(`记忆已存在,id:${id},合并关键词:${m.tags.join(',')}`); return; } } @@ -205,14 +200,14 @@ export class MemoryService { const now = Math.floor(Date.now() / 1000); const m = new MemoryItem(); m.id = id; - m.sourceSessionId = sid; - m.createTime = now; - m.lastMentionTime = now; + m.sessionId = sid; + m.createAt = now; + m.lastAccessedAt = now; m.weight = 5; m.content = content; - m.keywords = kws; - m.userIdList = ul; - m.groupIdList = gl; + m.tags = kws; + m.users = ul; + m.groups = gl; m.imageIdList = il; await m.updateVector(); @@ -227,7 +222,7 @@ export class MemoryService { if (kws.length > 0) { for (const id in this.memoryMap) { - if (kws.some(kw => this.memoryMap[id].keywords.includes(kw))) { + if (kws.some(kw => this.memoryMap[id].tags.includes(kw))) { delete this.memoryMap[id]; } } @@ -257,12 +252,12 @@ export class MemoryService { topK: 10, userIdList: [], groupIdList: [], - keywords: [], + tags: [], includeImages: false, method: 'score' }) { if (!this.memoryList.length) return []; - const { userIdList: ul, groupIdList: gl, keywords: kws, includeImages, method } = options; + const { userIdList: ul, groupIdList: gl, tags: kws, includeImages, method } = options; const { isMemoryVector, embeddingDimension } = Config.memory; let qv: number[] = []; @@ -284,7 +279,7 @@ export class MemoryService { .map(m => { if (includeImages && m.imageIdList.length === 0) return null; const mc = m.copy; - if (mc.keywords.some(kw => query.includes(kw))) mc.weight += 10; //提权 + if (mc.tags.some(kw => query.includes(kw))) mc.weight += 10; //提权 return mc; }) .filter(m => m) @@ -293,9 +288,9 @@ export class MemoryService { case 'weight': return b.weight - a.weight; case 'similarity': return b.calculateSimilarity(qv, ul, gl, kws) - a.calculateSimilarity(qv, ul, gl, kws); case 'score': return b.calculateScore(qv, ul, gl, kws) - a.calculateScore(qv, ul, gl, kws); - case 'early': return a.createTime - b.createTime; - case 'late': return b.createTime - a.createTime; - case 'recent': return b.lastMentionTime - a.lastMentionTime; + case 'early': return a.createAt - b.createAt; + case 'late': return b.createAt - a.createAt; + case 'recent': return b.lastAccessedAt - a.lastAccessedAt; } }) .slice(0, options.topK || 10); @@ -308,9 +303,9 @@ export class MemoryService { for (const id in this.memoryMap) { const m = this.memoryMap[id]; - if (m.keywords.some(kw => s.includes(kw))) { + if (m.tags.some(kw => s.includes(kw))) { m.weight = Math.max(10, m.weight + increase); - m.lastMentionTime = now; + m.lastAccessedAt = now; } else { m.weight = Math.min(0, m.weight - decrease); } @@ -335,7 +330,7 @@ export class MemoryService { topK: memoryShowNumber, userIdList: uid ? [uid] : [], groupIdList: gid ? [gid] : [], - keywords: [], + tags: [], includeImages: false, method: 'score' }); @@ -345,7 +340,7 @@ export class MemoryService { if (this.memoryList.length === 0) return ''; if (p > Math.ceil(this.memoryList.length / 5)) p = Math.ceil(this.memoryList.length / 5); const latestMemoryList = this.memoryList - .sort((a, b) => b.createTime - a.createTime) + .sort((a, b) => b.createAt - a.createAt) .slice((p - 1) * 5, p * 5); return this.buildMemory(sid, latestMemoryList) + `\n当前页码: ${p}/${Math.ceil(this.memoryList.length / 5)}`; } @@ -365,7 +360,7 @@ export class MemoryService { return memorySingleShowTemplate({ "序号": i + 1, "记忆ID": m.id, - "记忆时间": fmtDate(m.createTime), + "记忆时间": fmtDate(m.createAt), "个人记忆": si.isPrivate, "私聊": m.sessionInfo.isPrivate, "展示号码": showNumber, @@ -373,7 +368,7 @@ export class MemoryService { "群聊号码": m.sessionInfo.id, "相关用户": m.userList.map(u => u.name + (showNumber ? `(${u.id.replace(/^.+:/, '')})` : '')).join(';'), "相关群聊": m.groupList.map(g => g.name + (showNumber ? `(${g.id.replace(/^.+:/, '')})` : '')).join(';'), - "关键词": m.keywords.join(';'), + "关键词": m.tags.join(';'), "记忆内容": m.content }); }).join('\n'); @@ -657,7 +652,7 @@ export class KnowledgeService extends MemoryService { break; } case '关键词': { - m.keywords = value.split(/[,,]/).map(kw => kw.trim()).filter(kw => kw); + m.tags = value.split(/[,,]/).map(kw => kw.trim()).filter(kw => kw); break; } case '图片': { @@ -693,13 +688,13 @@ export class KnowledgeService extends MemoryService { const m2 = this.memoryMap[m.id]; m.vector = m2.vector; if (m2.content !== m.content) await m.updateVector(); - m.createTime = m2.createTime; - m.lastMentionTime = m2.lastMentionTime; + m.createAt = m2.createAt; + m.lastAccessedAt = m2.lastAccessedAt; m.weight = m2.weight; } else { await m.updateVector(); - m.createTime = now; - m.lastMentionTime = now; + m.createAt = now; + m.lastAccessedAt = now; m.weight = 5; } })) @@ -725,7 +720,7 @@ export class KnowledgeService extends MemoryService { "记忆ID": m.id, "用户列表": m.userList.map(u => u.name + (showNumber ? `(${u.id.replace(/^.+:/, '')})` : '')).join(';'), "群聊列表": m.groupList.map(g => g.name + (showNumber ? `(${g.id.replace(/^.+:/, '')})` : '')).join(';'), - "关键词": m.keywords.join(';'), + "关键词": m.tags.join(';'), "记忆内容": m.content }); }).join('\n'); @@ -744,7 +739,7 @@ export class KnowledgeService extends MemoryService { topK: knowledgeMemoryShowNumber, userIdList: ui ? [ui] : [], groupIdList: gi ? [gi] : [], - keywords: [], + tags: [], includeImages: false, method: 'score' }); diff --git a/src/session/session.ts b/src/session/session.ts index 199a5c0..25bec0c 100644 --- a/src/session/session.ts +++ b/src/session/session.ts @@ -15,7 +15,6 @@ export class Session { agentName: 'string', state: 'any', context: Context, - memory: MemoryService, tool: { object: { state: { objectValue: 'boolean' } @@ -28,7 +27,6 @@ export class Session { agentName: string; state: State; context: Context; - memory: MemoryService; tool: { state: ToolState, callCount: number, // 单次触发调用函数计数 @@ -42,7 +40,6 @@ export class Session { this.agentName = ''; this.state = {}; this.context = new Context(); - this.memory = new MemoryService(); this.tool = { state: Object.keys(toolMap).reduce((acc, key) => { acc[key as ToolName] = false; diff --git a/src/session/types.ts b/src/session/types.ts index df45e85..1756cb7 100644 --- a/src/session/types.ts +++ b/src/session/types.ts @@ -39,4 +39,13 @@ export interface RequestMessage { tool_call_id?: string; } -export type SessionType = 'user' | 'group'; \ No newline at end of file +export type SessionType = 'user' | 'group'; + +export interface searchOptions { + topK: number; + userIdList: string[]; + groupIdList: string[]; + tags: string[]; + includeImages: boolean; + method: 'weight' | 'similarity' | 'score' | 'early' | 'late' | 'recent'; +} \ No newline at end of file diff --git a/src/tool/tool.ts b/src/tool/tool.ts index 344f4c1..0f99740 100644 --- a/src/tool/tool.ts +++ b/src/tool/tool.ts @@ -2,12 +2,12 @@ import { Config } from "../config/config" import { logger } from "../logger" import { fixJsonString } from "../utils/string"; import { ExtCmdInfo, ToolCall, ToolCallResult, ToolInfo, ToolListen } from "./types"; -import { toolJrrp } from "./tools/jrrp"; import { Session } from "../session/session"; import { SessionType } from "../session/types"; +import { builtinCmdToolMap } from "./tools/builtin_cmd.ts/init"; export const toolMap = { - jrrp: toolJrrp, + ...builtinCmdToolMap, } export type ToolName = keyof typeof toolMap; diff --git a/src/tool/tools/builtin_cmd.ts/init.ts b/src/tool/tools/builtin_cmd.ts/init.ts new file mode 100644 index 0000000..44e760d --- /dev/null +++ b/src/tool/tools/builtin_cmd.ts/init.ts @@ -0,0 +1,5 @@ +import { toolJrrp } from "./jrrp"; + +export default { + jrrp: toolJrrp, +} \ No newline at end of file diff --git a/src/tool/tools/jrrp.ts b/src/tool/tools/builtin_cmd.ts/jrrp.ts similarity index 91% rename from src/tool/tools/jrrp.ts rename to src/tool/tools/builtin_cmd.ts/jrrp.ts index 993e5da..b6dbe79 100644 --- a/src/tool/tools/jrrp.ts +++ b/src/tool/tools/builtin_cmd.ts/jrrp.ts @@ -1,5 +1,5 @@ -import { getCtxAndMsg } from "../../utils/seal"; -import { Tool } from "../tool"; +import { getCtxAndMsg } from "../../../utils/seal"; +import { Tool } from "../../tool"; const tool = new Tool({ type: "function", diff --git a/src/tool/tools/tool_attr.ts b/src/tool/tools/builtin_cmd.ts/tool_attr.ts similarity index 100% rename from src/tool/tools/tool_attr.ts rename to src/tool/tools/builtin_cmd.ts/tool_attr.ts diff --git a/src/tool/tools/tool_modu.ts b/src/tool/tools/builtin_cmd.ts/tool_modu.ts similarity index 100% rename from src/tool/tools/tool_modu.ts rename to src/tool/tools/builtin_cmd.ts/tool_modu.ts diff --git a/src/tool/tools/tool_roll_check.ts b/src/tool/tools/builtin_cmd.ts/tool_roll_check.ts similarity index 100% rename from src/tool/tools/tool_roll_check.ts rename to src/tool/tools/builtin_cmd.ts/tool_roll_check.ts diff --git a/src/tool/tools/tool_image.ts b/src/tool/tools/image.ts/tool_image.ts similarity index 100% rename from src/tool/tools/tool_image.ts rename to src/tool/tools/image.ts/tool_image.ts diff --git a/src/tool/tools/tool_meme.ts b/src/tool/tools/image.ts/tool_meme.ts similarity index 100% rename from src/tool/tools/tool_meme.ts rename to src/tool/tools/image.ts/tool_meme.ts diff --git a/src/tool/tools/tool_render.ts b/src/tool/tools/image.ts/tool_render.ts similarity index 100% rename from src/tool/tools/tool_render.ts rename to src/tool/tools/image.ts/tool_render.ts diff --git a/src/tool/tools/tool_ban.ts b/src/tool/tools/ob11.ts/tool_ban.ts similarity index 100% rename from src/tool/tools/tool_ban.ts rename to src/tool/tools/ob11.ts/tool_ban.ts diff --git a/src/tool/tools/tool_essence_msg.ts b/src/tool/tools/ob11.ts/tool_essence_msg.ts similarity index 100% rename from src/tool/tools/tool_essence_msg.ts rename to src/tool/tools/ob11.ts/tool_essence_msg.ts diff --git a/src/tool/tools/tool_group_sign.ts b/src/tool/tools/ob11.ts/tool_group_sign.ts similarity index 100% rename from src/tool/tools/tool_group_sign.ts rename to src/tool/tools/ob11.ts/tool_group_sign.ts diff --git a/src/tool/tools/tool_person_info.ts b/src/tool/tools/ob11.ts/tool_person_info.ts similarity index 100% rename from src/tool/tools/tool_person_info.ts rename to src/tool/tools/ob11.ts/tool_person_info.ts diff --git a/src/tool/tools/tool_qq_list.ts b/src/tool/tools/ob11.ts/tool_qq_list.ts similarity index 100% rename from src/tool/tools/tool_qq_list.ts rename to src/tool/tools/ob11.ts/tool_qq_list.ts diff --git a/src/tool/tools/tool_rename.ts b/src/tool/tools/ob11.ts/tool_rename.ts similarity index 100% rename from src/tool/tools/tool_rename.ts rename to src/tool/tools/ob11.ts/tool_rename.ts diff --git a/src/tool/tools/tool_deck.ts b/src/tool/tools/seal.ts/tool_deck.ts similarity index 100% rename from src/tool/tools/tool_deck.ts rename to src/tool/tools/seal.ts/tool_deck.ts diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 3546839..ab7ab7e 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -179,18 +179,8 @@ export function cosineSimilarity(a: number[], b: number[]): number { return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB)); } -export function getCommonUser(a: string[], b: string[]): string[] { +export function getCommonItem(a: string[], b: string[]): string[] { if (a.length === 0 || b.length === 0) return []; - const aid = new Set(a); - return b.filter(u => aid.has(u)); -} -export function getCommonGroup(a: string[], b: string[]): string[] { - if (a.length === 0 || b.length === 0) return []; - const aid = new Set(a); - return b.filter(g => aid.has(g)); -} -export function getCommonKeyword(a: string[], b: string[]): string[] { - if (a.length === 0 || b.length === 0) return []; - const aid = new Set(a); - return b.filter(k => aid.has(k)); + const aset = new Set(a); + return b.filter(u => aset.has(u)); } \ No newline at end of file From bd81a91317df64e8b24caa69e2a1196aa67e6b99 Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Thu, 12 Mar 2026 00:30:01 +0800 Subject: [PATCH 27/33] tool&memo --- package.json | 1 + src/config/configs/config_memory.ts | 34 ++++++++++++++++------------- src/config/configs/tool.ts | 33 +++++++++++++++++++++++----- src/index.ts | 4 ++++ src/note.txt | 4 +++- src/session/group.ts | 8 ++++--- src/tool/tool.ts | 17 +++++---------- 7 files changed, 66 insertions(+), 35 deletions(-) diff --git a/package.json b/package.json index fa3b89e..838ade4 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "dependencies": { "@types/handlebars": "^4.0.40", "handlebars": "^4.7.8", + "js-toml": "^1.0.3", "lodash-es": "^4.17.21" }, "devDependencies": { diff --git a/src/config/configs/config_memory.ts b/src/config/configs/config_memory.ts index 0f7b028..a16af32 100644 --- a/src/config/configs/config_memory.ts +++ b/src/config/configs/config_memory.ts @@ -1,25 +1,29 @@ import { Config } from "../config"; +import { load } from 'js-toml' -export class MemoryConfig { +export default class MemoryConfig { static ext: seal.ExtInfo; static register() { - MemoryConfig.ext = ConfigManager.getExt('aiplugin4_7:记忆'); + MemoryConfig.ext = Config.getExt('aiplugin4_7:记忆'); seal.ext.registerIntConfig(MemoryConfig.ext, "知识库记忆展示数量", 10, ""); - seal.ext.registerTemplateConfig(MemoryConfig.ext, "知识库记忆", [ - ``, - `ID:测试 -用户:用户1:114514,用户2:1919810 -群聊:群聊1:114514,群聊2:1919810 -关键词:关键词1,关键词2 -图片:本地图片1的名字,本地图片2的名字 -内容:这是内容 -内容放在最后,可以换行 ---- -ID:上面是分割符 -内容:用于多个知识词条的分割` - ], "与角色设定一一对应"); + seal.ext.registerTemplateConfig(MemoryConfig.ext, "知识库记忆", [`# 采用toml进行格式化 +roles = ["正确"] # 当数组为空或不存在时,默认对所有角色生效 + +[测试] +content = """ +这是内容 +可以换行 +""" +importance = 0.9 # 记忆重要性,0-1之间的浮点数,默认0.5 +tags = ["标签1", "标签2"] # 标签列表 +relatedMemories = ["测试2"] # 相关记忆ID列表 +users = ["114514", "1919810"] # 相关用户ID列表 +groups = ["114514", "1919810"] # 相关群组ID列表 + +[测试2] +content = "单行形式,只有content字段是必须的"`], ""); seal.ext.registerTemplateConfig(MemoryConfig.ext, "单条知识库记忆展示模板", [ ` {{{序号}}}. 记忆ID:{{{记忆ID}}} 相关用户:{{{用户列表}}} diff --git a/src/config/configs/tool.ts b/src/config/configs/tool.ts index 2c7d5a5..0d3f793 100644 --- a/src/config/configs/tool.ts +++ b/src/config/configs/tool.ts @@ -9,11 +9,34 @@ export default class ToolConfig { seal.ext.registerBoolConfig(ToolConfig.ext, "开启调用函数功能", true, ""); seal.ext.registerBoolConfig(ToolConfig.ext, "切换为提示词工程", false, "API在不支持function calling功能的时候开启"); seal.ext.registerTemplateConfig(ToolConfig.ext, "工具函数prompt模板", [ - `{{序号}}. 名称:{{{函数名称}}} - - 描述:{{{函数描述}}} - - 参数信息:{{{参数信息}}} - - 必需参数:{{{必需参数}}}` - ], "提示词工程中每个函数的prompt"); + `{{#if PROMPT_ENGINEERING}} + +## 调用函数 +当需要调用函数功能时,请严格使用以下JSON格式,示例: + + +[ +{ + "name": "函数名", + "arguments": "{\\"参数1\\": \\"值1\\",\\"参数2\\": \\"值2\\"}" +} +] + + +要使用成对的标签:\`\`在前面,\`\`在后面包裹调用工具的数组。 +可调用多个函数,每个调用需包含name字段和arguments字段,且arguments字段必须是JSON字符串。 + +可用函数列表: +{{#each tools}} +{{index @index}}. 名称:{{{name}}} + - 描述:{{{description}}} + - 参数信息:{{{json_stringify parameters.properties}}} + - 必需参数:{{{string_array parameters.required}}} +{{else}} + 暂无可用函数。 +{{/each}} +{{/if}}` + ], ""); seal.ext.registerIntConfig(ToolConfig.ext, "允许连续调用函数次数", 5, "单次对话中允许连续调用函数的次数"); seal.ext.registerTemplateConfig(ToolConfig.ext, "禁止调用的函数", [''], "修改后保存并重载js"); seal.ext.registerTemplateConfig(ToolConfig.ext, "默认关闭的函数", [''], ""); diff --git a/src/index.ts b/src/index.ts index fe16c36..3901763 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,6 +13,10 @@ import { CQ_TYPES_ALLOW } from "./config/static_config"; import { registerCmd } from "./cmd/root_cmd"; function main() { + Handlebars.registerHelper('index', (index: number) => index + 1); + Handlebars.registerHelper('json_stringify', (obj: any) => JSON.stringify(obj, null, 2)); + Handlebars.registerHelper('string_array', (arr: string[]) => arr.join(', ')); + Config.registerConfig(); checkUpdate(); ToolService.registerTool(); diff --git a/src/note.txt b/src/note.txt index 00427eb..a3cbecb 100644 --- a/src/note.txt +++ b/src/note.txt @@ -80,4 +80,6 @@ kwarg,p,存在页码和概率撞车的情况 https://api-docs.deepseek.com/zh-cn/guides/thinking_mode#%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8 -https://api-docs.deepseek.com/zh-cn/guides/tool_calls#%E6%80%9D%E8%80%83%E6%A8%A1%E5%BC%8F \ No newline at end of file +https://api-docs.deepseek.com/zh-cn/guides/tool_calls#%E6%80%9D%E8%80%83%E6%A8%A1%E5%BC%8F + +检查调用并修复的agent \ No newline at end of file diff --git a/src/session/group.ts b/src/session/group.ts index e369246..64d61e4 100644 --- a/src/session/group.ts +++ b/src/session/group.ts @@ -8,6 +8,7 @@ export default class Group { static validKeysMap: { [key in keyof Group]?: TypeDescriptor } = { groupId: 'string', groupName: 'string', + role: 'string', owner: 'string', adminList: { array: 'string' }, memberList: { array: 'string' }, @@ -18,9 +19,10 @@ export default class Group { } groupId: string; groupName: string; - owner: string; - adminList: string[]; - memberList: string[]; + role: 'owner' | 'admin' | 'member'; // 自己的角色 + owner: string; // 群主id + adminList: string[]; // 管理员id列表 + memberList: string[]; // 普通成员id列表 description: string; // 自定义描述 impression: string; // ai可修改的印象 diff --git a/src/tool/tool.ts b/src/tool/tool.ts index 0f99740..d284235 100644 --- a/src/tool/tool.ts +++ b/src/tool/tool.ts @@ -4,7 +4,7 @@ import { fixJsonString } from "../utils/string"; import { ExtCmdInfo, ToolCall, ToolCallResult, ToolInfo, ToolListen } from "./types"; import { Session } from "../session/session"; import { SessionType } from "../session/types"; -import { builtinCmdToolMap } from "./tools/builtin_cmd.ts/init"; +import builtinCmdToolMap from "./tools/builtin_cmd.ts/init"; export const toolMap = { ...builtinCmdToolMap, @@ -221,19 +221,14 @@ export class Tool { return tools.length > 0 ? tools : null; } static getToolsInfoPrompt(session: Session): string { - const { TOOLS_PROMPT_TEMPLATE } = Config.tool; + const { PROMPT_ENGINEERING, TOOLS_PROMPT_TEMPLATE } = Config.tool; const tools = this.getToolsInfo(session); if (tools && tools.length > 0) { - return tools.map((item, index) => { - return TOOLS_PROMPT_TEMPLATE({ - "序号": index + 1, - "函数名称": item.function.name, - "函数描述": item.function.description, - "参数信息": JSON.stringify(item.function.parameters.properties, null, 2), - "必需参数": item.function.parameters.required.join('\n') - }); - }).join('\n'); + return TOOLS_PROMPT_TEMPLATE({ + "PROMPT_ENGINEERING": PROMPT_ENGINEERING, + "tools": tools + }); } return ''; From caf087300557a894ecd94d6d8a4b4742e66baea3 Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Thu, 12 Mar 2026 02:44:35 +0800 Subject: [PATCH 28/33] =?UTF-8?q?=E9=87=8D=E6=9E=84=E4=BA=86memo=E7=9A=84c?= =?UTF-8?q?onfig?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config/config.ts | 2 + src/config/configs/config_memory.ts | 187 -------------------- src/config/configs/image.ts | 2 +- src/config/configs/memory.ts | 261 ++++++++++++++++++++++++++++ src/config/configs/tool.ts | 12 +- src/index.ts | 1 - src/note.txt | 6 +- src/session/memory.ts | 34 ++-- 8 files changed, 289 insertions(+), 216 deletions(-) delete mode 100644 src/config/configs/config_memory.ts create mode 100644 src/config/configs/memory.ts diff --git a/src/config/config.ts b/src/config/config.ts index d3621c9..90e21c7 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -8,6 +8,7 @@ import ReceivedConfig from "./configs/received"; import TriggerConfig from "./configs/trigger"; import ImageConfig from "./configs/image"; import ToolConfig from "./configs/tool"; +import MemoryConfig from "./configs/memory"; const configMap = { base: BaseConfig, @@ -17,6 +18,7 @@ const configMap = { trigger: TriggerConfig, image: ImageConfig, tool: ToolConfig, + memory: MemoryConfig, } as const; type ConfigMap = typeof configMap; diff --git a/src/config/configs/config_memory.ts b/src/config/configs/config_memory.ts deleted file mode 100644 index a16af32..0000000 --- a/src/config/configs/config_memory.ts +++ /dev/null @@ -1,187 +0,0 @@ -import { Config } from "../config"; -import { load } from 'js-toml' - -export default class MemoryConfig { - static ext: seal.ExtInfo; - - static register() { - MemoryConfig.ext = Config.getExt('aiplugin4_7:记忆'); - - seal.ext.registerIntConfig(MemoryConfig.ext, "知识库记忆展示数量", 10, ""); - seal.ext.registerTemplateConfig(MemoryConfig.ext, "知识库记忆", [`# 采用toml进行格式化 -roles = ["正确"] # 当数组为空或不存在时,默认对所有角色生效 - -[测试] -content = """ -这是内容 -可以换行 -""" -importance = 0.9 # 记忆重要性,0-1之间的浮点数,默认0.5 -tags = ["标签1", "标签2"] # 标签列表 -relatedMemories = ["测试2"] # 相关记忆ID列表 -users = ["114514", "1919810"] # 相关用户ID列表 -groups = ["114514", "1919810"] # 相关群组ID列表 - -[测试2] -content = "单行形式,只有content字段是必须的"`], ""); - seal.ext.registerTemplateConfig(MemoryConfig.ext, "单条知识库记忆展示模板", [ - ` {{{序号}}}. 记忆ID:{{{记忆ID}}} - 相关用户:{{{用户列表}}} - 相关群聊:{{{群聊列表}}} - 关键词:{{{关键词}}} - 内容:{{{记忆内容}}}` - ], ""); - seal.ext.registerBoolConfig(MemoryConfig.ext, "是否启用长期记忆", true, ""); - seal.ext.registerIntConfig(MemoryConfig.ext, "长期记忆上限", 50, ""); - seal.ext.registerIntConfig(MemoryConfig.ext, "长期记忆展示数量", 5, ""); - seal.ext.registerBoolConfig(MemoryConfig.ext, "长期记忆是否启用向量", false, ""); - seal.ext.registerIntConfig(MemoryConfig.ext, "向量维度", 1024, ""); - seal.ext.registerStringConfig(MemoryConfig.ext, "嵌入url地址", "https://dashscope.aliyuncs.com/compatible-mode/v1/embeddings", ''); - seal.ext.registerStringConfig(MemoryConfig.ext, "嵌入API Key", "你的API Key", ''); - seal.ext.registerTemplateConfig(MemoryConfig.ext, "嵌入body", [ - `"model":"text-embedding-v4"`, - `"encoding_format":"float"` - ], "input, dimensions不存在时,将会自动替换。具体参数请参考你所使用模型的接口文档"); - seal.ext.registerTemplateConfig(MemoryConfig.ext, "长期记忆展示模板", [ - `{{#if 私聊}} -### 关于用户<{{{用户名称}}}>{{#if 展示号码}}({{{用户号码}}}){{/if}}: -{{else}} -### 关于群聊<{{{群聊名称}}}>{{#if 展示号码}}({{{群聊号码}}}){{/if}}: -{{/if}} - - 设定:{{{设定}}} - - 记忆: -{{{记忆列表}}}` - ], ""); - seal.ext.registerTemplateConfig(MemoryConfig.ext, "单条长期记忆展示模板", [ - ` {{{序号}}}. 记忆ID:{{{记忆ID}}} - 时间:{{{记忆时间}}} -{{#if 个人记忆}} - 来源:{{#if 私聊}}私聊{{else}}群聊<{{{群聊名称}}}>{{#if 展示号码}}({{{群聊号码}}}){{/if}}{{/if}} -{{/if}} - 相关用户:{{{用户列表}}} - 相关群聊:{{{群聊列表}}} - 关键词:{{{关键词}}} - 内容:{{{记忆内容}}}` - ], ""); - seal.ext.registerBoolConfig(MemoryConfig.ext, "是否启用短期记忆", true, ""); - seal.ext.registerIntConfig(MemoryConfig.ext, "短期记忆上限", 10, ""); - seal.ext.registerIntConfig(MemoryConfig.ext, "短期记忆总结轮数", 10, ""); - seal.ext.registerStringConfig(MemoryConfig.ext, "记忆总结 url地址", "", '为空时,默认使用对话接口'); - seal.ext.registerStringConfig(MemoryConfig.ext, "记忆总结 API Key", "你的API Key", '若使用对话接口无需填写'); - seal.ext.registerTemplateConfig(MemoryConfig.ext, "记忆总结 body", [ - `"model":"deepseek-chat"`, - `"max_tokens":1024`, - `"response_format": { "type": "json_object" }`, - `"stop":null`, - `"stream":false` - ], "messages不存在时,将会自动替换"); - seal.ext.registerTemplateConfig(MemoryConfig.ext, "记忆总结prompt模板", [ - `你现在扮演的角色如下: -## 扮演详情 -{{{角色设定}}} - -## 聊天相关 - - 当前平台:{{{平台}}} -{{#if 私聊}} - - 当前私聊:<{{{用户名称}}}>{{#if 展示号码}}({{{用户号码}}}){{/if}} -{{else}} - - 当前群聊:<{{{群聊名称}}}>{{#if 展示号码}}({{{群聊号码}}}){{/if}} - - <|at:xxx|>表示@某个群成员 - - <|poke:xxx|>表示戳一戳某个群成员 - - <|face:xxx|>表示使用某个表情,xxx为表情名称,注意与img表情包区分 -{{/if}} -{{#if 添加前缀}} - - <|from:xxx|>表示消息来源,不要在生成的回复中使用 -{{/if}} -{{#if 展示消息ID}} - - <|msg_id:xxx|>表示消息ID,仅用于调用函数时使用,不要在生成的回复中提及或使用 - - <|quote:xxx|>表示引用消息,xxx为对应的消息ID -{{/if}} -{{#if 展示时间}} - - <|time:xxxx-xx-xx xx:xx:xx|>表示消息发送时间,不要在生成的回复中提及或使用 -{{/if}} - - \\f用于分割多条消息 - -请根据你的设定,对以下对话内容进行总结: -{{{对话内容}}} - -返回格式为JSON,格式类型如下: -{ - "content": { - type: 'string', - description: '总结后的对话摘要,请根据人物、行为、场景,以所扮演角色的口吻进行简短描述,只保留核心内容' - }, - "memories": { - type: 'array', - description: '记忆数组。单条记忆应只有一个话题或事件。若对话内容对记忆有重要影响时返回,否则返回空数组', - items: { - type: 'object', - description: '记忆对象', - properties: { - "memory_type": { - type: "string", - description: "记忆类型,个人或群聊。", - enum: ["private", "group"] - }, - "name": { - type: 'string', - description: '用户名称或群聊名称{{#if 展示号码}}或纯数字QQ号、群号{{/if}},实际使用时与记忆类型对应' - }, - "text": { - type: 'string', - description: '记忆内容,尽量简短,无需附带时间与来源' - }, - "keywords": { - type: 'array', - description: '相关用户名称列表', - items: { - type: 'string' - } - }, - "userList": { - type: 'array', - description: '相关用户名称列表', - items: { - type: 'string' - } - }, - "groupList": { - type: 'array', - description: '相关群聊名称列表', - items: { - type: 'string' - } - } - }, - "required": ['memory_type', 'name', 'text'] - } - } -}` - ], ""); - } - - static get() { - return { - knowledgeMemoryShowNumber: seal.ext.getIntConfig(MemoryConfig.ext, "知识库记忆展示数量"), - knowledgeMemoryStringList: seal.ext.getTemplateConfig(MemoryConfig.ext, "知识库记忆"), - knowledgeMemorySingleShowTemplate: ConfigManager.getHandlebarsTemplateConfig(MemoryConfig.ext, "单条知识库记忆展示模板"), - isMemory: seal.ext.getBoolConfig(MemoryConfig.ext, "是否启用长期记忆"), - memoryLimit: seal.ext.getIntConfig(MemoryConfig.ext, "长期记忆上限"), - memoryShowNumber: seal.ext.getIntConfig(MemoryConfig.ext, "长期记忆展示数量"), - isMemoryVector: seal.ext.getBoolConfig(MemoryConfig.ext, "长期记忆是否启用向量"), - embeddingDimension: seal.ext.getIntConfig(MemoryConfig.ext, "向量维度"), - embeddingUrl: seal.ext.getStringConfig(MemoryConfig.ext, "嵌入url地址"), - embeddingApiKey: seal.ext.getStringConfig(MemoryConfig.ext, "嵌入API Key"), - embeddingBodyTemplate: seal.ext.getTemplateConfig(MemoryConfig.ext, "嵌入body"), - memoryShowTemplate: ConfigManager.getHandlebarsTemplateConfig(MemoryConfig.ext, "长期记忆展示模板"), - memorySingleShowTemplate: ConfigManager.getHandlebarsTemplateConfig(MemoryConfig.ext, "单条长期记忆展示模板"), - isShortMemory: seal.ext.getBoolConfig(MemoryConfig.ext, "是否启用短期记忆"), - shortMemoryLimit: seal.ext.getIntConfig(MemoryConfig.ext, "短期记忆上限"), - shortMemorySummaryRound: seal.ext.getIntConfig(MemoryConfig.ext, "短期记忆总结轮数"), - memoryUrl: seal.ext.getStringConfig(MemoryConfig.ext, "记忆总结 url地址"), - memoryApiKey: seal.ext.getStringConfig(MemoryConfig.ext, "记忆总结 API Key"), - memoryBodyTemplate: seal.ext.getTemplateConfig(MemoryConfig.ext, "记忆总结 body"), - memoryPromptTemplate: ConfigManager.getHandlebarsTemplateConfig(MemoryConfig.ext, "记忆总结prompt模板") - } - } -} \ No newline at end of file diff --git a/src/config/configs/image.ts b/src/config/configs/image.ts index a3bc0ea..e850c66 100644 --- a/src/config/configs/image.ts +++ b/src/config/configs/image.ts @@ -4,7 +4,7 @@ export default class ImageConfig { static ext: seal.ExtInfo; static register() { - ImageConfig.ext = Config.getExt('aiplugin4_5:图片'); + ImageConfig.ext = Config.getExt('aiplugin4:图片'); seal.ext.registerTemplateConfig(ImageConfig.ext, "本地图片路径", ['data/images/sealdice.png'], "如不需要可以不填写,修改完需要重载js"); seal.ext.registerStringConfig(ImageConfig.ext, "图片全局识别豹语条件", '0', "使用豹语表达式,例如:$t群号_RAW=='2001'。若要开启所有图片自动识别转文字,请填写'1'"); diff --git a/src/config/configs/memory.ts b/src/config/configs/memory.ts new file mode 100644 index 0000000..86407df --- /dev/null +++ b/src/config/configs/memory.ts @@ -0,0 +1,261 @@ +import { MemoryItem } from "../../session/memory"; +import { revive, TypeDescriptor } from "../../utils/utils"; +import { Config, getHandlebarsTemplateConfig } from "../config"; +import { load } from 'js-toml' + +export default class MemoryConfig { + static ext: seal.ExtInfo; + + static register() { + MemoryConfig.ext = Config.getExt('aiplugin4:记忆'); + + seal.ext.registerIntConfig(MemoryConfig.ext, "向量维度", 1024, ""); + seal.ext.registerBoolConfig(MemoryConfig.ext, "启用知识库记忆", false, ""); + seal.ext.registerIntConfig(MemoryConfig.ext, "知识库记忆展示数量", 10, ""); + seal.ext.registerBoolConfig(MemoryConfig.ext, "启用长期记忆", true, ""); + seal.ext.registerIntConfig(MemoryConfig.ext, "长期记忆上限", 50, ""); + seal.ext.registerIntConfig(MemoryConfig.ext, "长期记忆展示数量", 5, ""); + seal.ext.registerBoolConfig(MemoryConfig.ext, "启用总结记忆", true, ""); + seal.ext.registerIntConfig(MemoryConfig.ext, "总结记忆上限", 10, ""); + seal.ext.registerIntConfig(MemoryConfig.ext, "总结记忆间隔轮数", 10, ""); + seal.ext.registerIntConfig(MemoryConfig.ext, "总结记忆参与轮数", 10, ""); + seal.ext.registerTemplateConfig(MemoryConfig.ext, "知识库记忆", [ + `# 采用toml进行格式化 +roles = ["正确"] # 当数组为空或不存在时,默认对所有角色生效 + +[knowleges.测试] +content = """ +这是内容 +可以换行 +""" +importance = 0.9 # 记忆重要性,0-1之间的浮点数,默认0.5 +tags = ["标签1", "标签2"] # 标签列表 +relatedMemories = ["测试2"] # 相关记忆ID列表 +users = ["114514", "1919810"] # 相关用户ID列表 +groups = ["114514", "1919810"] # 相关群组ID列表 + +[knowleges.测试2] +content = "单行形式,只有content字段是必须的"` + ], ""); + seal.ext.registerTemplateConfig(MemoryConfig.ext, "知识库记忆展示模板", [ + `{{#if KNOWLEGE}} + +## 知识库 + {{#each knowleges}} +{{index @index}}. ID:{{id}} + 重要性:{{importance}} + {{#each tags}}{{#if @first}}标签:{{/if}}{{{this}}}{{#unless @last}}, {{/unless}}{{/each}} + {{#each relatedMemories}}{{#if @first}}相关记忆:{{/if}}{{{this}}}{{#unless @last}}, {{/unless}}{{/each}} + {{#each users}}{{#if @first}}相关用户:{{/if}}{{{this}}}{{#unless @last}}, {{/unless}}{{/each}} + {{#each groups}}{{#if @first}}相关群组:{{/if}}{{{this}}}{{#unless @last}}, {{/unless}}{{/each}} + 内容:{{{content}}} + {{else}} +知识库为空 + {{/each}} +{{/if}}` + ], ""); + seal.ext.registerTemplateConfig(MemoryConfig.ext, "长期记忆展示模板", [ + `{{#if MEMORY}} + +## 长期记忆 + {{#each sources}} +来源:{{{source}}} + {{#each memories}} +{{index @index}}. ID:{{id}} + 重要性:{{importance}} + 创建时间:{{{createAt}}} + {{#each tags}}{{#if @first}}标签:{{/if}}{{{this}}}{{#unless @last}}, {{/unless}}{{/each}} + {{#each relatedMemories}}{{#if @first}}相关记忆:{{/if}}{{{this}}}{{#unless @last}}, {{/unless}}{{/each}} + {{#each users}}{{#if @first}}相关用户:{{/if}}{{{this}}}{{#unless @last}}, {{/unless}}{{/each}} + {{#each groups}}{{#if @first}}相关群组:{{/if}}{{{this}}}{{#unless @last}}, {{/unless}}{{/each}} + 内容:{{{content}}} + {{else}} +暂无记忆 + {{/each}} +{{#unless @last}}---{{/unless}} + {{else}} +长期记忆为空 + {{/each}} +{{/if}}` + ], ""); + seal.ext.registerTemplateConfig(MemoryConfig.ext, "总结记忆展示模板", [ + `{{#if SUMMARY}} + +## 总结记忆 + {{#each summaries}} +{{index @index}}. {{{this}}} + {{else}} +总结记忆为空 + {{/each}} +{{/if}}` + ], ""); + seal.ext.registerTemplateConfig(MemoryConfig.ext, "记忆总结prompt模板", [ // wip + `你现在扮演的角色如下: +## 扮演详情 +{{{角色设定}}} + +## 聊天相关 + - 当前平台:{{{平台}}} +{{#if 私聊}} + - 当前私聊:<{{{用户名称}}}>{{#if 展示号码}}({{{用户号码}}}){{/if}} +{{else}} + - 当前群聊:<{{{群聊名称}}}>{{#if 展示号码}}({{{群聊号码}}}){{/if}} + - <|at:xxx|>表示@某个群成员 + - <|poke:xxx|>表示戳一戳某个群成员 + - <|face:xxx|>表示使用某个表情,xxx为表情名称,注意与img表情包区分 +{{/if}} +{{#if 添加前缀}} + - <|from:xxx|>表示消息来源,不要在生成的回复中使用 +{{/if}} +{{#if 展示消息ID}} + - <|msg_id:xxx|>表示消息ID,仅用于调用函数时使用,不要在生成的回复中提及或使用 + - <|quote:xxx|>表示引用消息,xxx为对应的消息ID +{{/if}} +{{#if 展示时间}} + - <|time:xxxx-xx-xx xx:xx:xx|>表示消息发送时间,不要在生成的回复中提及或使用 +{{/if}} + - \\f用于分割多条消息 + +请根据你的设定,对以下对话内容进行总结: +{{{对话内容}}} + +返回格式为JSON,格式类型如下: +{ + "content": { + type: 'string', + description: '总结后的对话摘要,请根据人物、行为、场景,以所扮演角色的口吻进行简短描述,只保留核心内容' + }, + "memories": { + type: 'array', + description: '记忆数组。单条记忆应只有一个话题或事件。若对话内容对记忆有重要影响时返回,否则返回空数组', + items: { + type: 'object', + description: '记忆对象', + properties: { + "memory_type": { + type: "string", + description: "记忆类型,个人或群聊。", + enum: ["private", "group"] + }, + "name": { + type: 'string', + description: '用户名称或群聊名称{{#if 展示号码}}或纯数字QQ号、群号{{/if}},实际使用时与记忆类型对应' + }, + "text": { + type: 'string', + description: '记忆内容,尽量简短,无需附带时间与来源' + }, + "keywords": { + type: 'array', + description: '相关用户名称列表', + items: { + type: 'string' + } + }, + "userList": { + type: 'array', + description: '相关用户名称列表', + items: { + type: 'string' + } + }, + "groupList": { + type: 'array', + description: '相关群聊名称列表', + items: { + type: 'string' + } + } + }, + "required": ['memory_type', 'name', 'text'] + } + } +}` + ], ""); + } + + static get() { + return { + DIMENSION: seal.ext.getIntConfig(MemoryConfig.ext, "向量维度"), + KNOWLEGE: seal.ext.getBoolConfig(MemoryConfig.ext, "启用知识库记忆"), + KNOWLEGE_SHOW_NUMBER: seal.ext.getIntConfig(MemoryConfig.ext, "知识库记忆展示数量"), + MEMORY: seal.ext.getBoolConfig(MemoryConfig.ext, "启用长期记忆"), + MEMORY_LIMIT: seal.ext.getIntConfig(MemoryConfig.ext, "长期记忆上限"), + MEMORY_SHOW_NUMBER: seal.ext.getIntConfig(MemoryConfig.ext, "长期记忆展示数量"), + SUMMARY: seal.ext.getBoolConfig(MemoryConfig.ext, "启用总结记忆"), + SUMMARY_LIMIT: seal.ext.getIntConfig(MemoryConfig.ext, "总结记忆上限"), + SUMMARY_INTERVAL: seal.ext.getIntConfig(MemoryConfig.ext, "总结记忆间隔轮数"), + SUMMARY_SIZE: seal.ext.getIntConfig(MemoryConfig.ext, "总结记忆参与轮数"), + KNOWLEGE_MAPS: getKnowledgeMapsConfig(), + KNOWLEGE_TEMPLATE: getHandlebarsTemplateConfig(MemoryConfig.ext, "知识库记忆展示模板"), + MEMORY_TEMPLATE: getHandlebarsTemplateConfig(MemoryConfig.ext, "长期记忆展示模板"), + SUMMARY_TEMPLATE: getHandlebarsTemplateConfig(MemoryConfig.ext, "总结记忆展示模板"), + SUMMARY_PROMPT_TEMPLATE: getHandlebarsTemplateConfig(MemoryConfig.ext, "记忆总结prompt模板") + } + } +} + +class KnowlegeConfigItem { + static validKeysMap: { [key in keyof KnowlegeConfigItem]?: TypeDescriptor } = { + roles: { array: 'string' }, + knowleges: { + objectValue: { + object: { + content: 'string', + importance: 'number', + tags: { array: 'string' }, + relatedMemories: { array: 'string' }, + users: { array: 'string' }, + groups: { array: 'string' } + } + } + } + } + roles: string[]; + knowleges: { + [id: string]: { + content: string, + importance: number, + tags: string[], + relatedMemories: string[] + users: string[], + groups: string[] + } + } + + constructor() { + this.roles = []; + this.knowleges = {}; + } +} + +interface KnowlegeMap { + [id: string]: MemoryItem +} + +function getKnowledgeMapsConfig(): { [role: string]: KnowlegeMap } { + const tomlArray = seal.ext.getTemplateConfig(MemoryConfig.ext, "知识库记忆"); + const knowlegeConfigArray = tomlArray.map((tomlString) => revive(KnowlegeConfigItem, load(tomlString))); + const knowlegeMaps: { [role: string]: KnowlegeMap } = {}; + for (const kc of knowlegeConfigArray) { + const knowlegeMap: KnowlegeMap = {}; + for (const id in kc.knowleges) { + const k = kc.knowleges[id]; + const m = new MemoryItem(); + m.id = id; + m.importance = k.importance; + m.content = k.content; + m.tags = k.tags; + m.relatedMemories = k.relatedMemories; + m.users = k.users.map(u => String(u)); + m.groups = k.groups.map(g => String(g)); + knowlegeMap[id] = m; + } + if (kc.roles.length === 0) kc.roles.push('*'); + for (const role of kc.roles) { + if (!knowlegeMaps.hasOwnProperty(role)) knowlegeMaps[role] = {}; + knowlegeMaps[role] = { ...knowlegeMaps[role], ...knowlegeMap }; + } + } + return knowlegeMaps; +} \ No newline at end of file diff --git a/src/config/configs/tool.ts b/src/config/configs/tool.ts index 0d3f793..d953833 100644 --- a/src/config/configs/tool.ts +++ b/src/config/configs/tool.ts @@ -4,7 +4,7 @@ export default class ToolConfig { static ext: seal.ExtInfo; static register() { - ToolConfig.ext = Config.getExt('aiplugin4_2:函数调用'); + ToolConfig.ext = Config.getExt('aiplugin4:工具'); seal.ext.registerBoolConfig(ToolConfig.ext, "开启调用函数功能", true, ""); seal.ext.registerBoolConfig(ToolConfig.ext, "切换为提示词工程", false, "API在不支持function calling功能的时候开启"); @@ -27,14 +27,14 @@ export default class ToolConfig { 可调用多个函数,每个调用需包含name字段和arguments字段,且arguments字段必须是JSON字符串。 可用函数列表: -{{#each tools}} + {{#each tools}} {{index @index}}. 名称:{{{name}}} - 描述:{{{description}}} - 参数信息:{{{json_stringify parameters.properties}}} - - 必需参数:{{{string_array parameters.required}}} -{{else}} - 暂无可用函数。 -{{/each}} + - 必需参数:{{#each parameters.required}}{{{this}}}{{#unless @last}}, {{/unless}}{{/each}} + {{else}} +暂无可用函数。 + {{/each}} {{/if}}` ], ""); seal.ext.registerIntConfig(ToolConfig.ext, "允许连续调用函数次数", 5, "单次对话中允许连续调用函数的次数"); diff --git a/src/index.ts b/src/index.ts index 3901763..211d7ba 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,7 +15,6 @@ import { registerCmd } from "./cmd/root_cmd"; function main() { Handlebars.registerHelper('index', (index: number) => index + 1); Handlebars.registerHelper('json_stringify', (obj: any) => JSON.stringify(obj, null, 2)); - Handlebars.registerHelper('string_array', (arr: string[]) => arr.join(', ')); Config.registerConfig(); checkUpdate(); diff --git a/src/note.txt b/src/note.txt index a3cbecb..ca1dc9e 100644 --- a/src/note.txt +++ b/src/note.txt @@ -82,4 +82,8 @@ https://api-docs.deepseek.com/zh-cn/guides/thinking_mode#%E5%B7%A5%E5%85%B7%E8%B https://api-docs.deepseek.com/zh-cn/guides/tool_calls#%E6%80%9D%E8%80%83%E6%A8%A1%E5%BC%8F -检查调用并修复的agent \ No newline at end of file +检查调用并修复的agent + +更新文本向量是不是要用一起吧标签,user,group之类的信息塞进去啊 + +知识库记忆加进去的时候别忘了更新向量 \ No newline at end of file diff --git a/src/session/memory.ts b/src/session/memory.ts index 0cc93f0..64360d2 100644 --- a/src/session/memory.ts +++ b/src/session/memory.ts @@ -1,12 +1,13 @@ import { Config } from "../config/config"; import { Context } from "./context"; -import { cosineSimilarity, generateId, getCommonGroup, getCommonKeyword, getCommonItem, revive, TypeDescriptor } from "../utils/utils"; +import { cosineSimilarity, generateId, getCommonItem, revive, TypeDescriptor } from "../utils/utils"; import { logger } from "../logger"; import { fetchData, getEmbedding } from "../agent/service"; import { buildContent, getRoleSetting, parseBody } from "../utils/message"; -import { ToolService } from "../tool/tool"; +import { Tool } from "../tool/tool"; import { fmtDate } from "../utils/string"; -import { Image } from "../image/image"; +import Image from "../image/image"; +import Model from "../agent/model"; export class MemoryItem { static validKeysMap: { [key in keyof MemoryItem]?: TypeDescriptor } = { @@ -75,9 +76,9 @@ export class MemoryItem { // 活跃时间(小时) const activity = (now - this.lastAccessedAt) / (60 * 60); // 年龄衰减: 半衰期7天 - const ageDecay = Math.exp(-age / 7 * Math.LN2); + const ageDecay = this.createAt === 0 ? 1 : Math.exp(-age / 7 * Math.LN2); // 活跃衰减: 半衰期4小时 - const activityDecay = Math.exp(-activity / 4 * Math.LN2); + const activityDecay = this.lastAccessedAt === 0 ? 1 : Math.exp(-activity / 4 * Math.LN2); // 衰减因子 return ageDecay * 0.7 + activityDecay * 0.3; // 一拍脑门决定的加权 } @@ -128,20 +129,13 @@ export class MemoryItem { } async updateVector() { - const { isMemoryVector, embeddingDimension } = Config.memory; - if (isMemoryVector) { - logger.info(`更新记忆向量: ${this.id}`); - const vector = await getEmbedding(this.content); - if (!vector.length) { - logger.error('返回向量为空'); - return; - } - if (vector.length !== embeddingDimension) { - logger.error(`向量维度不匹配。期望: ${embeddingDimension}, 实际: ${vector.length}`); - return; - } - this.vector = vector; - } + const { DIMENSION } = Config.memory; + logger.info(`更新记忆向量: ${this.id}`); + const model = Model.getEmbeddingModel('text-embedding'); + const vector = await model.callEmbedding(this.content); + if (!vector.length) return logger.error('返回向量为空'); + if (vector.length !== DIMENSION) return logger.error(`向量维度不匹配。期望: ${DIMENSION}, 实际: ${vector.length}`); + this.vector = vector; } } @@ -577,7 +571,7 @@ export class SessionMemoryService extends MemoryService { this.limitShortMemory(); memoryData.memories.forEach(m => { - ToolService.toolMap["add_memory"].solve(ctx, msg, ai, m); + Tool.toolMap["add_memory"].solve(ctx, msg, ai, m); }); } } catch (e) { From 4093c3e65227135e91f912f8ff479785ea1c6341 Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Thu, 12 Mar 2026 16:06:29 +0800 Subject: [PATCH 29/33] =?UTF-8?q?=E6=9E=84=E4=B8=8D=E5=8A=A8=E4=BA=86?= =?UTF-8?q?=E2=80=A6=E2=80=A6=E4=B8=8B=E4=B8=80=E6=AD=A5=E6=98=AF=E9=87=8D?= =?UTF-8?q?=E6=9E=84memory?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config/configs/memory.ts | 30 +- src/image/image.ts | 4 +- src/index.ts | 2 +- src/memory/image.ts | 5 + src/memory/knowlege.ts | 161 +++++++++++ src/{session => memory}/memory.ts | 466 ++++++------------------------ src/memory/session.ts | 133 +++++++++ src/session/group.ts | 15 +- src/session/session.ts | 30 +- src/session/types.ts | 9 +- src/session/user.ts | 14 +- src/utils/message.ts | 2 +- src/utils/utils.ts | 35 +-- 13 files changed, 458 insertions(+), 448 deletions(-) create mode 100644 src/memory/image.ts create mode 100644 src/memory/knowlege.ts rename src/{session => memory}/memory.ts (50%) create mode 100644 src/memory/session.ts diff --git a/src/config/configs/memory.ts b/src/config/configs/memory.ts index 86407df..6319206 100644 --- a/src/config/configs/memory.ts +++ b/src/config/configs/memory.ts @@ -1,4 +1,4 @@ -import { MemoryItem } from "../../session/memory"; +import { MemoryItem } from "../../memory/memory"; import { revive, TypeDescriptor } from "../../utils/utils"; import { Config, getHandlebarsTemplateConfig } from "../config"; import { load } from 'js-toml' @@ -186,7 +186,7 @@ content = "单行形式,只有content字段是必须的"` SUMMARY_LIMIT: seal.ext.getIntConfig(MemoryConfig.ext, "总结记忆上限"), SUMMARY_INTERVAL: seal.ext.getIntConfig(MemoryConfig.ext, "总结记忆间隔轮数"), SUMMARY_SIZE: seal.ext.getIntConfig(MemoryConfig.ext, "总结记忆参与轮数"), - KNOWLEGE_MAPS: getKnowledgeMapsConfig(), + KNOWLEGE_MEMORIES_MAP: getKnowledgeMemoriesMapConfig(), KNOWLEGE_TEMPLATE: getHandlebarsTemplateConfig(MemoryConfig.ext, "知识库记忆展示模板"), MEMORY_TEMPLATE: getHandlebarsTemplateConfig(MemoryConfig.ext, "长期记忆展示模板"), SUMMARY_TEMPLATE: getHandlebarsTemplateConfig(MemoryConfig.ext, "总结记忆展示模板"), @@ -229,16 +229,11 @@ class KnowlegeConfigItem { } } -interface KnowlegeMap { - [id: string]: MemoryItem -} - -function getKnowledgeMapsConfig(): { [role: string]: KnowlegeMap } { - const tomlArray = seal.ext.getTemplateConfig(MemoryConfig.ext, "知识库记忆"); - const knowlegeConfigArray = tomlArray.map((tomlString) => revive(KnowlegeConfigItem, load(tomlString))); - const knowlegeMaps: { [role: string]: KnowlegeMap } = {}; - for (const kc of knowlegeConfigArray) { - const knowlegeMap: KnowlegeMap = {}; +function getKnowledgeMemoriesMapConfig(): { [role: string]: MemoryItem[] } { + const knowlegeMaps: { [role: string]: { [id: string]: MemoryItem } } = {}; + seal.ext.getTemplateConfig(MemoryConfig.ext, "知识库记忆").forEach(tomlString => { + const kc = revive(KnowlegeConfigItem, load(tomlString)); + const mmap: { [id: string]: MemoryItem } = {}; for (const id in kc.knowleges) { const k = kc.knowleges[id]; const m = new MemoryItem(); @@ -249,13 +244,16 @@ function getKnowledgeMapsConfig(): { [role: string]: KnowlegeMap } { m.relatedMemories = k.relatedMemories; m.users = k.users.map(u => String(u)); m.groups = k.groups.map(g => String(g)); - knowlegeMap[id] = m; + mmap[id] = m; } if (kc.roles.length === 0) kc.roles.push('*'); for (const role of kc.roles) { if (!knowlegeMaps.hasOwnProperty(role)) knowlegeMaps[role] = {}; - knowlegeMaps[role] = { ...knowlegeMaps[role], ...knowlegeMap }; + knowlegeMaps[role] = { ...knowlegeMaps[role], ...mmap }; } - } - return knowlegeMaps; + }); + + const knowlegeMemoriesMap: { [role: string]: MemoryItem[] } = {}; + for (const role of Object.keys(knowlegeMaps)) knowlegeMemoriesMap[role] = Object.values(knowlegeMaps[role]); + return knowlegeMemoriesMap; } \ No newline at end of file diff --git a/src/image/image.ts b/src/image/image.ts index 4d9f111..5a9491f 100644 --- a/src/image/image.ts +++ b/src/image/image.ts @@ -149,8 +149,8 @@ export default class Image { id = generateId(); a++; if (a > 1000) { - logger.error(`生成记忆id失败,已尝试1000次,放弃`); - throw new Error(`生成记忆id失败,已尝试1000次,放弃`); + logger.error(`生成图片id失败,已尝试1000次,放弃`); + throw new Error(`生成图片id失败,已尝试1000次,放弃`); } } return id; diff --git a/src/index.ts b/src/index.ts index 211d7ba..aa69305 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,7 +8,7 @@ import { checkUpdate } from "./utils/update"; import { TimerManager } from "./timer"; import { createMsg } from "./utils/seal"; import { PrivilegeManager } from "./cmd/privilege"; -import { knowledgeService } from "./session/memory"; +import { knowledgeService } from "./memory/memory"; import { CQ_TYPES_ALLOW } from "./config/static_config"; import { registerCmd } from "./cmd/root_cmd"; diff --git a/src/memory/image.ts b/src/memory/image.ts new file mode 100644 index 0000000..0391c7c --- /dev/null +++ b/src/memory/image.ts @@ -0,0 +1,5 @@ +export default class ImageMemoryService extends MemoryService { + constructor() { + super(); + } +} \ No newline at end of file diff --git a/src/memory/knowlege.ts b/src/memory/knowlege.ts new file mode 100644 index 0000000..2ec75d1 --- /dev/null +++ b/src/memory/knowlege.ts @@ -0,0 +1,161 @@ +export default class KnowledgeService extends MemoryService { + constructor() { + super(); + } + + init() { + const data = JSON.parse(Config.ext.storageGet('knowledge') || '{}'); + const ms = revive(MemoryService, data); + this.memoryMap = ms.memoryMap; + } + + save() { + Config.ext.storageSet('knowledge', JSON.stringify(this.memoryMap)); + } + + // wip 和配置一起改 + async updateKnowledgeMemory(roleIndex: number) { + const { knowledgeMemoryStringList } = Config.memory; + if (roleIndex < 0 || roleIndex >= knowledgeMemoryStringList.length) return; + const s = knowledgeMemoryStringList[roleIndex]; + if (!s) return; + + const memoryMap: { [id: string]: MemoryItem } = {} + const segs = s.split(/\n-{3,}\n/); + for (const seg of segs) { + if (!seg.trim()) continue; + + const lines = seg.split('\n'); + if (lines.length === 0) continue; + + const m = new MemoryItem(); + for (let i = 0; i < lines.length; i++) { + const match = lines[i].match(/^\s*?(ID|用户|群聊|关键词|图片|内容)\s*?[::](.*)/); + if (!match) { + continue; + } + const type = match[1]; + const value = match[2].trim(); + switch (type) { + case 'ID': { + m.id = value; + break; + } + case '用户': { + m.userList = value.split(/[,,]/).map(s => { + const segs = s.split(/[::]/).map(s => s.trim()).filter(s => s); + if (segs.length < 2) return null; + const name = value.replace(/[::].*$/, '').trim(); + const id = segs[segs.length - 1]; + if (!name || !id) return null; + return { isPrivate: true, id, name }; + }).filter(ui => ui) as UserInfo[]; + break; + } + case '群聊': { + m.groupList = value.split(/[,,]/).map(s => { + const segs = s.split(/[::]/).map(s => s.trim()).filter(s => s); + if (segs.length < 2) return null; + const name = value.replace(/[::].*$/, '').trim(); + const id = segs[segs.length - 1]; + if (!name || !id) return null; + return { isPrivate: false, id, name }; + }).filter(ui => ui) as GroupInfo[]; + break; + } + case '关键词': { + m.tags = value.split(/[,,]/).map(kw => kw.trim()).filter(kw => kw); + break; + } + case '图片': { + const { localImagePathMap } = Config.image; + + m.images = value.split(/[,,]/).map(id => id.trim()).map(id => { + if (localImagePathMap.hasOwnProperty(id)) { + const image = new Image(); + image.file = localImagePathMap[id]; + return image; + } + logger.error(`图片${id}不存在`); + return null; + }).filter(img => img); + break; + } + case '内容': { + m.content = lines.slice(i).join('\n').trim().replace(/^内容[::]/, ''); + break; + } + default: continue; + } + } + + if (!m.id && !m.content) continue; + + memoryMap[m.id] = m; + } + + const now = Math.floor(Date.now() / 1000); + await Promise.all(Object.values(memoryMap).map(async m => { + if (this.memoryMap.hasOwnProperty(m.id)) { + const m2 = this.memoryMap[m.id]; + m.vector = m2.vector; + if (m2.content !== m.content) await m.updateVector(); + m.createAt = m2.createAt; + m.lastAccessedAt = m2.lastAccessedAt; + m.weight = m2.weight; + } else { + await m.updateVector(); + m.createAt = now; + m.lastAccessedAt = now; + m.weight = 5; + } + })) + + this.memoryMap = memoryMap; + this.save(); + } + + // wip + buildKnowledgeMemory(memoryList: MemoryItem[]) { + const { showNumber } = Config.message; + const { knowledgeMemorySingleShowTemplate } = Config.memory; + if (memoryList.length === 0) return ''; + + let prompt = ''; + if (memoryList.length === 0) { + prompt = '无'; + } else { + prompt = memoryList + .map((m, i) => { + return knowledgeMemorySingleShowTemplate({ + "序号": i + 1, + "记忆ID": m.id, + "用户列表": m.userList.map(u => u.name + (showNumber ? `(${u.id.replace(/^.+:/, '')})` : '')).join(';'), + "群聊列表": m.groupList.map(g => g.name + (showNumber ? `(${g.id.replace(/^.+:/, '')})` : '')).join(';'), + "关键词": m.tags.join(';'), + "记忆内容": m.content + }); + }).join('\n'); + } + + return prompt; + } + + // wip + async buildKnowledgeMemoryPrompt(roleIndex: number, text: string, ui: UserInfo, gi: GroupInfo): Promise { + await this.updateKnowledgeMemory(roleIndex); + if (this.memoryIds.length === 0) return ''; + + const { knowledgeMemoryShowNumber } = Config.memory; + const memoryList = await this.search(text, { + topK: knowledgeMemoryShowNumber, + userIdList: ui ? [ui] : [], + groupIdList: gi ? [gi] : [], + tags: [], + includeImages: false, + method: 'score' + }); + + return this.buildKnowledgeMemory(memoryList); + } +} \ No newline at end of file diff --git a/src/session/memory.ts b/src/memory/memory.ts similarity index 50% rename from src/session/memory.ts rename to src/memory/memory.ts index 64360d2..228bddd 100644 --- a/src/session/memory.ts +++ b/src/memory/memory.ts @@ -1,5 +1,5 @@ import { Config } from "../config/config"; -import { Context } from "./context"; +import { Context } from "../session/context"; import { cosineSimilarity, generateId, getCommonItem, revive, TypeDescriptor } from "../utils/utils"; import { logger } from "../logger"; import { fetchData, getEmbedding } from "../agent/service"; @@ -128,6 +128,18 @@ export class MemoryItem { return this.importance * 0.2 + this.accessScore * 0.2 + similarity * 0.6; } + compareWith(m: MemoryItem): boolean { + return this.content === m.content && this.sessionId === m.sessionId; + } + + merge(m: MemoryItem) { + this.importance = m.importance; + this.tags = Array.from(new Set([...this.tags, ...m.tags])); + this.relatedMemories = Array.from(new Set([...this.relatedMemories, ...m.relatedMemories])); + this.users = Array.from(new Set([...this.users, ...m.users])); + this.groups = Array.from(new Set([...this.groups, ...m.groups])); + } + async updateVector() { const { DIMENSION } = Config.memory; logger.info(`更新记忆向量: ${this.id}`); @@ -139,7 +151,7 @@ export class MemoryItem { } } -export class MemoryService { +export default class MemoryService { static validKeysMap: { [key in keyof MemoryService]?: TypeDescriptor } = { memoryMap: { array: MemoryItem } }; @@ -149,93 +161,105 @@ export class MemoryService { this.memoryMap = {}; } - get memoryIdList() { + get memoryIds() { return Object.keys(this.memoryMap); } - - get memoryList() { + get memories() { return Object.values(this.memoryMap); } - - get keywords() { - const keywords = new Set(); - this.memoryList.forEach(m => m.tags.forEach(kw => keywords.add(kw))); - return Array.from(keywords); + get tags() { + const tags = new Set(); + this.memories.forEach(m => m.tags.forEach(t => tags.add(t))); + return Array.from(tags); } - async addMemory(sid: string, ul: string[], gl: string[], kws: string[], il: string[], content: string) { - for (const id of this.memoryIdList) { - const m = this.memoryMap[id]; - if (content === m.content && sid === m.sessionId && getCommonItem(ul, m.users).length > 0 && getCommonGroup(gl, m.groups).length > 0) { - m.tags = Array.from(new Set([...m.tags, ...kws])); - logger.info(`记忆已存在,id:${id},合并关键词:${m.tags.join(',')}`); - return; - } - } - - // 添加文本内插入的图片 - const imgIdSet = new Set(il); - (await ImageService.extractExistingImagesToSave(content)).forEach(img => { - if (imgIdSet.has(img.id)) return; - imgIdSet.add(img.id); - il.push(img.id); - }); - + generateMemoryId(): string { let id = generateId(), a = 0; while (this.memoryMap.hasOwnProperty(id)) { id = generateId(); a++; if (a > 1000) { logger.error(`生成记忆id失败,已尝试1000次,放弃`); - return; + throw new Error(`生成记忆id失败,已尝试1000次,放弃`); } } + return id; + } + async addMemories(memories: MemoryItem[]) { const now = Math.floor(Date.now() / 1000); - const m = new MemoryItem(); - m.id = id; - m.sessionId = sid; - m.createAt = now; - m.lastAccessedAt = now; - m.weight = 5; - m.content = content; - m.tags = kws; - m.users = ul; - m.groups = gl; - m.imageIdList = il; - - await m.updateVector(); - this.limitMemory(); - this.memoryMap[id] = m; - } + const memoriesToAdd: MemoryItem[] = []; + for (const m of memories) { + for (const om of this.memories) { + if (om.compareWith(m)) { + logger.info(`记忆已存在,id:${om.id},进行合并`); + om.merge(m); + om.accessCount++; + om.lastAccessedAt = now; + continue; + } + } + memoriesToAdd.push(m); + } - deleteMemory(ml: string[] = [], kws: string[] = []) { - if (ml.length === 0 && kws.length === 0) return; + if (memoriesToAdd.length === 0) return; - ml.forEach(id => delete this.memoryMap?.[id]) + await Promise.all(memoriesToAdd.map(async m => await m.updateVector())); + this.limitMemory(memoriesToAdd.length); + memoriesToAdd.forEach(m => this.memoryMap[m.id] = m); + } - if (kws.length > 0) { - for (const id in this.memoryMap) { - if (kws.some(kw => this.memoryMap[id].tags.includes(kw))) { - delete this.memoryMap[id]; + /** + * 删除记忆,删除完全符合条件的记忆 + * @param ids 记忆id列表 + * @param tags 标签列表 + * @param relatedMemories 相关记忆id列表 + * @param users 用户id列表 + * @param groups 群组id列表 + * @returns + */ + deleteMemories(ids: string[] = [], tags: string[] = [], relatedMemories: string[] = [], users: string[] = [], groups: string[] = []) { + if (ids.length === 0 && tags.length === 0 && relatedMemories.length === 0 && users.length === 0 && groups.length === 0) return; + + if (ids.length > 0) { + ids.forEach(id => { + if (this.memoryMap.hasOwnProperty(id)) { + const m = this.memoryMap[id]; + if ( + tags.every(t => m.tags.includes(t)) && + relatedMemories.every(r => m.relatedMemories.includes(r)) && + users.every(u => m.users.includes(u)) && + groups.every(g => m.groups.includes(g)) + ) delete this.memoryMap[id]; } + }) + } else { + for (const id in this.memoryMap) { + const m = this.memoryMap[id]; + if ( + tags.every(t => m.tags.includes(t)) && + relatedMemories.every(r => m.relatedMemories.includes(r)) && + users.every(u => m.users.includes(u)) && + groups.every(g => m.groups.includes(g)) + ) delete this.memoryMap[id]; } } } - limitMemory() { - const { memoryLimit } = Config.memory; - const limit = memoryLimit > 0 ? memoryLimit - 1 : 0; // 预留1个位置用于存储最新记忆 - if (this.memoryList.length <= limit) return; - this.memoryList.map((m) => { - return { - id: m.id, - score: m.decay * m.weight - } - }) + limitMemory(vacancy: number) { + const { MEMORY_LIMIT } = Config.memory; + const limit = MEMORY_LIMIT > vacancy ? MEMORY_LIMIT - vacancy : 0; // 预留空位用于存储最新记忆 + if (this.memories.length <= limit) return; + this.memories + .map((m) => { + return { + id: m.id, + score: m.calculateScore([], [], [], []) + } + }) .sort((a, b) => b.score - a.score) // 从大到小排序 .slice(limit) - .forEach(item => delete this.memoryMap?.[item.id]); + .forEach(m => delete this.memoryMap?.[m.id]); } clearMemory() { @@ -250,7 +274,7 @@ export class MemoryService { includeImages: false, method: 'score' }) { - if (!this.memoryList.length) return []; + if (!this.memories.length) return []; const { userIdList: ul, groupIdList: gl, tags: kws, includeImages, method } = options; const { isMemoryVector, embeddingDimension } = Config.memory; @@ -261,7 +285,7 @@ export class MemoryService { logger.error('查询向量为空'); return []; } - await Promise.all(this.memoryList.map(async m => { + await Promise.all(this.memories.map(async m => { if (m.vector.length !== embeddingDimension) { logger.info(`记忆向量维度不匹配,重新获取向量: ${m.id}`); await m.updateVector(); @@ -269,7 +293,7 @@ export class MemoryService { })) } - return this.memoryList + return this.memories .map(m => { if (includeImages && m.imageIdList.length === 0) return null; const mc = m.copy; @@ -331,12 +355,12 @@ export class MemoryService { } getLatestMemoryListText(sid: string, p: number = 1): string { - if (this.memoryList.length === 0) return ''; - if (p > Math.ceil(this.memoryList.length / 5)) p = Math.ceil(this.memoryList.length / 5); - const latestMemoryList = this.memoryList + if (this.memories.length === 0) return ''; + if (p > Math.ceil(this.memories.length / 5)) p = Math.ceil(this.memories.length / 5); + const latestMemoryList = this.memories .sort((a, b) => b.createAt - a.createAt) .slice((p - 1) * 5, p * 5); - return this.buildMemory(sid, latestMemoryList) + `\n当前页码: ${p}/${Math.ceil(this.memoryList.length / 5)}`; + return this.buildMemory(sid, latestMemoryList) + `\n当前页码: ${p}/${Math.ceil(this.memories.length / 5)}`; } // wip 和默认配置一起改 @@ -424,7 +448,7 @@ export class MemoryService { } includedImage(id: string): boolean { - for (const m of this.memoryList) { + for (const m of this.memories) { const image = m.imageIdList.find(i => i === id); if (image) { m.weight += 0.2; @@ -435,7 +459,7 @@ export class MemoryService { } findMemoryByImageIdPrefix(id: string): MemoryItem | null { - for (const m of this.memoryList) { + for (const m of this.memories) { const image = m.imageIdList.find(img => img.replace(/_\d+$/, "") === id); if (image) { m.weight += 0.2; @@ -446,304 +470,6 @@ export class MemoryService { } } -export class SessionMemoryService extends MemoryService { - static validKeysMap: { [key in keyof SessionMemoryService]?: TypeDescriptor } = { - memoryMap: { array: MemoryItem }, - summaryStatus: 'boolean', - summaryList: { array: 'string' } - }; - summaryStatus: boolean; - summaryList: string[]; - - constructor() { - super(); - this.summaryStatus = false; - this.summaryList = []; - } - - limitSummary() { - const { SummaryLimit } = Config.memory; - if (this.summaryList.length > SummaryLimit) { - this.summaryList.splice(0, this.summaryList.length - SummaryLimit); - } - } - - clearSummary() { - this.summaryList = []; - } - - // wip - async updateSummary(ctx: seal.MsgContext, msg: seal.Message, ai: AI) { - if (!this.summaryStatus) return; - - const { url: chatUrl, apiKey: chatApiKey } = Config.request; - const { isPrefix, showNumber, showMsgId, showTime } = Config.message; - const { shortMemorySummaryRound, memoryUrl, memoryApiKey, memoryBodyTemplate, memoryPromptTemplate } = Config.memory; - - const { roleSetting } = getRoleSetting(ctx); - - const messages = ai.context.messages; - let sumMessages = messages.slice(); - let round = 0; - for (let i = 0; i < messages.length; i++) { - if (messages[i].role === 'user' && !messages[i].name.startsWith('_')) { - round++; - } - if (round > shortMemorySummaryRound) { - sumMessages = messages.slice(0, i); // 只保留最近的shortMemorySummaryRound轮对话 - break; - } - } - - if (sumMessages.length === 0) { - return; - } - - let url = chatUrl; - let apiKey = chatApiKey; - if (memoryUrl.trim()) { - url = memoryUrl; - apiKey = memoryApiKey; - } - - try { - const prompt = memoryPromptTemplate({ - "角色设定": roleSetting, - "平台": ctx.endPoint.platform, - "私聊": ctx.isPrivate, - "展示号码": showNumber, - "用户名称": ctx.player.name, - "用户号码": ctx.player.userId.replace(/^.+:/, ''), - "群聊名称": ctx.group.groupName, - "群聊号码": ctx.group.groupId.replace(/^.+:/, ''), - "添加前缀": isPrefix, - "展示消息ID": showMsgId, - "展示时间": showTime, - "对话内容": isPrefix ? sumMessages.map(message => { - if (message.role === 'assistant' && message?.tool_calls && message?.tool_calls.length > 0) { - return `\n[function_call]: ${message.tool_calls.map((tool_call, index) => `${index + 1}. ${JSON.stringify(tool_call.function, null, 2)}`).join('\n')}`; - } - - return `[${message.role}]: ${buildContent(message)}`; - }).join('\n') : JSON.stringify(sumMessages) - }) - - logger.info(`记忆总结prompt:\n`, prompt); - - const messages = [ - { - role: "system", - content: prompt - } - ] - const bodyObject = parseBody(memoryBodyTemplate, messages, [], "none"); - - const time = Date.now(); - const data = await fetchData(url, apiKey, bodyObject); - - if (data.choices && data.choices.length > 0) { - AIManager.updateUsage(data.model, data.usage); - - const message = data.choices[0].message; - const finish_reason = data.choices[0].finish_reason; - - if (message.hasOwnProperty('reasoning_content')) { - logger.info(`思维链内容:`, message.reasoning_content); - } - - const reply = message.content || ''; - logger.info(`响应内容:`, reply, '\nlatency:', Date.now() - time, 'ms', '\nfinish_reason:', finish_reason); - - const memoryData = JSON.parse(reply) as { - content: string, - memories: { - memory_type: 'private' | 'group', - name: string, - text: string, - keywords?: string[], - userList?: string[], - groupList?: string[], - }[] - }; - - - this.shortMemoryList.push(memoryData.content); - this.limitShortMemory(); - - memoryData.memories.forEach(m => { - Tool.toolMap["add_memory"].solve(ctx, msg, ai, m); - }); - } - } catch (e) { - logger.error(`更新短期记忆失败: ${e.message}`); - } - } -} - -export class KnowledgeService extends MemoryService { - constructor() { - super(); - } - - init() { - const data = JSON.parse(Config.ext.storageGet('knowledge') || '{}'); - const ms = revive(MemoryService, data); - this.memoryMap = ms.memoryMap; - } - - save() { - Config.ext.storageSet('knowledge', JSON.stringify(this.memoryMap)); - } - - // wip 和配置一起改 - async updateKnowledgeMemory(roleIndex: number) { - const { knowledgeMemoryStringList } = Config.memory; - if (roleIndex < 0 || roleIndex >= knowledgeMemoryStringList.length) return; - const s = knowledgeMemoryStringList[roleIndex]; - if (!s) return; - - const memoryMap: { [id: string]: MemoryItem } = {} - const segs = s.split(/\n-{3,}\n/); - for (const seg of segs) { - if (!seg.trim()) continue; - - const lines = seg.split('\n'); - if (lines.length === 0) continue; - - const m = new MemoryItem(); - for (let i = 0; i < lines.length; i++) { - const match = lines[i].match(/^\s*?(ID|用户|群聊|关键词|图片|内容)\s*?[::](.*)/); - if (!match) { - continue; - } - const type = match[1]; - const value = match[2].trim(); - switch (type) { - case 'ID': { - m.id = value; - break; - } - case '用户': { - m.userList = value.split(/[,,]/).map(s => { - const segs = s.split(/[::]/).map(s => s.trim()).filter(s => s); - if (segs.length < 2) return null; - const name = value.replace(/[::].*$/, '').trim(); - const id = segs[segs.length - 1]; - if (!name || !id) return null; - return { isPrivate: true, id, name }; - }).filter(ui => ui) as UserInfo[]; - break; - } - case '群聊': { - m.groupList = value.split(/[,,]/).map(s => { - const segs = s.split(/[::]/).map(s => s.trim()).filter(s => s); - if (segs.length < 2) return null; - const name = value.replace(/[::].*$/, '').trim(); - const id = segs[segs.length - 1]; - if (!name || !id) return null; - return { isPrivate: false, id, name }; - }).filter(ui => ui) as GroupInfo[]; - break; - } - case '关键词': { - m.tags = value.split(/[,,]/).map(kw => kw.trim()).filter(kw => kw); - break; - } - case '图片': { - const { localImagePathMap } = Config.image; - - m.images = value.split(/[,,]/).map(id => id.trim()).map(id => { - if (localImagePathMap.hasOwnProperty(id)) { - const image = new Image(); - image.file = localImagePathMap[id]; - return image; - } - logger.error(`图片${id}不存在`); - return null; - }).filter(img => img); - break; - } - case '内容': { - m.content = lines.slice(i).join('\n').trim().replace(/^内容[::]/, ''); - break; - } - default: continue; - } - } - - if (!m.id && !m.content) continue; - - memoryMap[m.id] = m; - } - - const now = Math.floor(Date.now() / 1000); - await Promise.all(Object.values(memoryMap).map(async m => { - if (this.memoryMap.hasOwnProperty(m.id)) { - const m2 = this.memoryMap[m.id]; - m.vector = m2.vector; - if (m2.content !== m.content) await m.updateVector(); - m.createAt = m2.createAt; - m.lastAccessedAt = m2.lastAccessedAt; - m.weight = m2.weight; - } else { - await m.updateVector(); - m.createAt = now; - m.lastAccessedAt = now; - m.weight = 5; - } - })) - - this.memoryMap = memoryMap; - this.save(); - } - - // wip - buildKnowledgeMemory(memoryList: MemoryItem[]) { - const { showNumber } = Config.message; - const { knowledgeMemorySingleShowTemplate } = Config.memory; - if (memoryList.length === 0) return ''; - - let prompt = ''; - if (memoryList.length === 0) { - prompt = '无'; - } else { - prompt = memoryList - .map((m, i) => { - return knowledgeMemorySingleShowTemplate({ - "序号": i + 1, - "记忆ID": m.id, - "用户列表": m.userList.map(u => u.name + (showNumber ? `(${u.id.replace(/^.+:/, '')})` : '')).join(';'), - "群聊列表": m.groupList.map(g => g.name + (showNumber ? `(${g.id.replace(/^.+:/, '')})` : '')).join(';'), - "关键词": m.tags.join(';'), - "记忆内容": m.content - }); - }).join('\n'); - } - - return prompt; - } - - // wip - async buildKnowledgeMemoryPrompt(roleIndex: number, text: string, ui: UserInfo, gi: GroupInfo): Promise { - await this.updateKnowledgeMemory(roleIndex); - if (this.memoryIdList.length === 0) return ''; - - const { knowledgeMemoryShowNumber } = Config.memory; - const memoryList = await this.search(text, { - topK: knowledgeMemoryShowNumber, - userIdList: ui ? [ui] : [], - groupIdList: gi ? [gi] : [], - tags: [], - includeImages: false, - method: 'score' - }); - - return this.buildKnowledgeMemory(memoryList); - } -} - -export const knowledgeService = new KnowledgeService(); - // 可以通过维护一组索引来优化搜索性能。 // 好麻烦,不想弄 // 目前数量级应该没什么优化的需求 \ No newline at end of file diff --git a/src/memory/session.ts b/src/memory/session.ts new file mode 100644 index 0000000..8a608d6 --- /dev/null +++ b/src/memory/session.ts @@ -0,0 +1,133 @@ +export default class SessionMemoryService extends MemoryService { + static validKeysMap: { [key in keyof SessionMemoryService]?: TypeDescriptor } = { + memoryMap: { array: MemoryItem }, + summaryStatus: 'boolean', + summaryList: { array: 'string' } + }; + summaryStatus: boolean; + summaryList: string[]; + + constructor() { + super(); + this.summaryStatus = false; + this.summaryList = []; + } + + limitSummary() { + const { SummaryLimit } = Config.memory; + if (this.summaryList.length > SummaryLimit) { + this.summaryList.splice(0, this.summaryList.length - SummaryLimit); + } + } + + clearSummary() { + this.summaryList = []; + } + + // wip + async updateSummary(ctx: seal.MsgContext, msg: seal.Message, ai: AI) { + if (!this.summaryStatus) return; + + const { url: chatUrl, apiKey: chatApiKey } = Config.request; + const { isPrefix, showNumber, showMsgId, showTime } = Config.message; + const { shortMemorySummaryRound, memoryUrl, memoryApiKey, memoryBodyTemplate, memoryPromptTemplate } = Config.memory; + + const { roleSetting } = getRoleSetting(ctx); + + const messages = ai.context.messages; + let sumMessages = messages.slice(); + let round = 0; + for (let i = 0; i < messages.length; i++) { + if (messages[i].role === 'user' && !messages[i].name.startsWith('_')) { + round++; + } + if (round > shortMemorySummaryRound) { + sumMessages = messages.slice(0, i); // 只保留最近的shortMemorySummaryRound轮对话 + break; + } + } + + if (sumMessages.length === 0) { + return; + } + + let url = chatUrl; + let apiKey = chatApiKey; + if (memoryUrl.trim()) { + url = memoryUrl; + apiKey = memoryApiKey; + } + + try { + const prompt = memoryPromptTemplate({ + "角色设定": roleSetting, + "平台": ctx.endPoint.platform, + "私聊": ctx.isPrivate, + "展示号码": showNumber, + "用户名称": ctx.player.name, + "用户号码": ctx.player.userId.replace(/^.+:/, ''), + "群聊名称": ctx.group.groupName, + "群聊号码": ctx.group.groupId.replace(/^.+:/, ''), + "添加前缀": isPrefix, + "展示消息ID": showMsgId, + "展示时间": showTime, + "对话内容": isPrefix ? sumMessages.map(message => { + if (message.role === 'assistant' && message?.tool_calls && message?.tool_calls.length > 0) { + return `\n[function_call]: ${message.tool_calls.map((tool_call, index) => `${index + 1}. ${JSON.stringify(tool_call.function, null, 2)}`).join('\n')}`; + } + + return `[${message.role}]: ${buildContent(message)}`; + }).join('\n') : JSON.stringify(sumMessages) + }) + + logger.info(`记忆总结prompt:\n`, prompt); + + const messages = [ + { + role: "system", + content: prompt + } + ] + const bodyObject = parseBody(memoryBodyTemplate, messages, [], "none"); + + const time = Date.now(); + const data = await fetchData(url, apiKey, bodyObject); + + if (data.choices && data.choices.length > 0) { + AIManager.updateUsage(data.model, data.usage); + + const message = data.choices[0].message; + const finish_reason = data.choices[0].finish_reason; + + if (message.hasOwnProperty('reasoning_content')) { + logger.info(`思维链内容:`, message.reasoning_content); + } + + const reply = message.content || ''; + logger.info(`响应内容:`, reply, '\nlatency:', Date.now() - time, 'ms', '\nfinish_reason:', finish_reason); + + const memoryData = JSON.parse(reply) as { + content: string, + memories: { + memory_type: 'private' | 'group', + name: string, + text: string, + keywords?: string[], + userList?: string[], + groupList?: string[], + }[] + }; + + + this.shortMemoryList.push(memoryData.content); + this.limitShortMemory(); + + memoryData.memories.forEach(m => { + Tool.toolMap["add_memory"].solve(ctx, msg, ai, m); + }); + } + } catch (e) { + logger.error(`更新短期记忆失败: ${e.message}`); + } + } +} \ No newline at end of file diff --git a/src/session/group.ts b/src/session/group.ts index 64d61e4..65154fa 100644 --- a/src/session/group.ts +++ b/src/session/group.ts @@ -1,9 +1,6 @@ import { Config } from "../config/config"; import { logger } from "../logger"; import { revive, TypeDescriptor } from "../utils/utils"; -import { MemoryService } from "./memory"; -import { State } from "./session"; - export default class Group { static validKeysMap: { [key in keyof Group]?: TypeDescriptor } = { groupId: 'string', @@ -11,11 +8,7 @@ export default class Group { role: 'string', owner: 'string', adminList: { array: 'string' }, - memberList: { array: 'string' }, - description: 'string', - impression: 'string', - state: 'any', - memory: MemoryService + memberList: { array: 'string' } } groupId: string; groupName: string; @@ -23,13 +16,7 @@ export default class Group { owner: string; // 群主id adminList: string[]; // 管理员id列表 memberList: string[]; // 普通成员id列表 - description: string; // 自定义描述 - impression: string; // ai可修改的印象 - - state: State; //储存状态信息 - memory: MemoryService; - static groupMap: { [key: string]: Group }; static get(groupId: string): Group { diff --git a/src/session/session.ts b/src/session/session.ts index 25bec0c..d4c0f61 100644 --- a/src/session/session.ts +++ b/src/session/session.ts @@ -5,28 +5,37 @@ import { toolMap, ToolName, ToolState } from "../tool/tool"; import { ToolListen } from "../tool/types"; import { revive, TypeDescriptor } from "../utils/utils"; import { Context } from "./context"; -import { MemoryService } from "./memory"; +import { MemoryService } from "../memory/memory"; import { RequestMessage, SessionType, State } from "./types"; export class Session { static validKeysMap: { [key in keyof Session]?: TypeDescriptor } = { + agentName: 'string', sessionId: 'string', sessionType: 'string', - agentName: 'string', - state: 'any', + state: { + object: { + description: 'string', + impression: 'string', + }, + objectValue: 'any' + }, context: Context, + memory: MemoryService, tool: { object: { state: { objectValue: 'boolean' } - } + }, + objectValue: 'default' }, ignoredUserIdList: { array: 'string' }, } + agentName: string; sessionId: string; sessionType: SessionType; - agentName: string; state: State; context: Context; + memory: MemoryService; tool: { state: ToolState, callCount: number, // 单次触发调用函数计数 @@ -35,10 +44,13 @@ export class Session { ignoredUserIdList: string[]; constructor() { + this.agentName = ''; this.sessionId = ''; this.sessionType = 'group'; - this.agentName = ''; - this.state = {}; + this.state = { + description: '', + impression: '', + }; this.context = new Context(); this.tool = { state: Object.keys(toolMap).reduce((acc, key) => { @@ -87,9 +99,11 @@ export class Session { export class SessionService { static validKeysMap: { [key in keyof SessionService]?: TypeDescriptor } = { agentName: 'string', - sessionMap: { objectValue: Session }, + memory: MemoryService, + sessionMap: { objectValue: Session } } agentName: string; + memory: MemoryService; // 全局记忆服务 sessionMap: { [key: string]: Session }; constructor() { diff --git a/src/session/types.ts b/src/session/types.ts index 1756cb7..f63b90e 100644 --- a/src/session/types.ts +++ b/src/session/types.ts @@ -29,6 +29,8 @@ export interface ToolCallbackMessageItem extends BaseMessageItem { export type MessageItem = UserMessageItem | AssistantMessageItem | SystemUserMessageItem | ToolCallsMessageItem | ToolCallbackMessageItem; export interface State { + description: string; // 自定义描述 + impression: string; // ai可修改的印象 [key: string]: any; } @@ -43,9 +45,10 @@ export type SessionType = 'user' | 'group'; export interface searchOptions { topK: number; - userIdList: string[]; - groupIdList: string[]; tags: string[]; + relatedMemories: string[]; + users: string[]; + groups: string[]; includeImages: boolean; - method: 'weight' | 'similarity' | 'score' | 'early' | 'late' | 'recent'; + method: 'importance' | 'similarity' | 'score' | 'early' | 'late' | 'recent'; } \ No newline at end of file diff --git a/src/session/user.ts b/src/session/user.ts index 99f82a4..ee586b6 100644 --- a/src/session/user.ts +++ b/src/session/user.ts @@ -1,26 +1,14 @@ import { Config } from "../config/config"; import { logger } from "../logger"; import { revive, TypeDescriptor } from "../utils/utils"; -import { MemoryService } from "./memory"; -import { State } from "./session"; export default class User { static validKeysMap: { [key in keyof User]?: TypeDescriptor } = { userId: 'string', - userName: 'string', - description: 'string', - impression: 'string', - state: 'any', - memory: MemoryService + userName: 'string' } userId: string; userName: string; - description: string; - impression: string; // ai可修改的印象 - - state: State; //储存状态信息 - memory: MemoryService; - static userMap: { [key: string]: User }; diff --git a/src/utils/message.ts b/src/utils/message.ts index 3a77468..c4aaa41 100644 --- a/src/utils/message.ts +++ b/src/utils/message.ts @@ -3,7 +3,7 @@ import { Message } from "../session/context"; import { Config } from "../config/config"; import { ToolInfo } from "../tool/tool"; import { fmtDate } from "./string"; -import { knowledgeService } from "../session/memory"; +import { knowledgeService } from "../memory/memory"; export async function buildSystemMessage(ctx: seal.MsgContext, ai: AI): Promise { const { systemMessageTemplate, isPrefix, showNumber, showMsgId, showTime } = Config.message; diff --git a/src/utils/utils.ts b/src/utils/utils.ts index ab7ab7e..d63d013 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -79,14 +79,17 @@ export function withTimeout(asyncFunc: () => Promise, timeoutMs: number): } export type TypeDescriptor = + 'default' | 'string' | 'number' | 'boolean' | 'any' | TypeDescriptor[] // 元组元素类型 | { array: TypeDescriptor } // 数组元素类型 - | { object: { [key: string]: TypeDescriptor } } // 对象键值对类型 - | { objectValue: TypeDescriptor } // 对象值类型 + | { + object?: { [key: string]: TypeDescriptor }, + objectValue?: TypeDescriptor + } // 对象键值对类型,对象值类型 | RevivableConstructor; // 嵌套类 interface RevivableConstructor { @@ -108,31 +111,23 @@ export function revive(constructor: RevivableConstructor, value: any): T { if (typeof value === 'number') return value; } else if (descriptor === 'boolean') { if (typeof value === 'boolean') return value; - } else if (descriptor === 'any') { - return value; } else if (Array.isArray(descriptor)) { - if (Array.isArray(value)) return descriptor.map((d: any, index: number) => { - if (index < value.length) return reviveItem(d, defaultValue?.[index], value[index]); - return defaultValue?.[index]; - }); + if (Array.isArray(value)) return descriptor.map((d: any, i: number) => reviveItem(d, defaultValue?.[i], value?.[i])); } else if (typeof descriptor === 'object' && 'array' in descriptor) { - if (Array.isArray(value)) return value.map((item: any) => reviveItem(descriptor.array, defaultValue?.[0], item)); - } else if (typeof descriptor === 'object' && 'object' in descriptor) { - if (typeof value === 'object' && value !== null) return Object.keys(descriptor.object).reduce((obj: any, k: string) => { - if (value.hasOwnProperty(k)) obj[k] = reviveItem(descriptor.object[k], defaultValue?.[k], value[k]); - else obj[k] = defaultValue?.[k]; - return obj; - }, {}); - } else if (typeof descriptor === 'object' && 'objectValue' in descriptor) { - if (typeof value === 'object' && value !== null) return Object.keys(value).reduce((obj: any, k: string) => { - obj[k] = reviveItem(descriptor.objectValue, defaultValue?.[k], value[k]); - return obj; - }, {}); + if (Array.isArray(value)) return value.map((v: any, i: number) => reviveItem(descriptor.array, defaultValue?.[i], v)); + } else if (typeof descriptor === 'object' && ('object' in descriptor || 'objectValue' in descriptor)) { + const ov: any = {}, o: any = {}; + if (typeof value === 'object' && value !== null) { + if ('objectValue' in descriptor) Object.keys(value).forEach(k => ov[k] = reviveItem(descriptor.objectValue, defaultValue?.[k], value?.[k])); + if ('object' in descriptor) Object.keys(descriptor.object).forEach(k => o[k] = reviveItem(descriptor.object[k], defaultValue?.[k], value?.[k])); + return { ...o, ...ov }; + } } else if (typeof descriptor === 'function') { return revive(descriptor, value); } else { return value; } + return defaultValue; } const obj: any = new constructor(); From 8f779908961db51373db89ff5e01c29407348d66 Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Thu, 12 Mar 2026 22:32:50 +0800 Subject: [PATCH 30/33] =?UTF-8?q?=E7=BB=A7=E7=BB=AD=E9=87=8D=E6=9E=84memo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config/configs/memory.ts | 44 +++++++-------- src/memory/knowlege.ts | 2 + src/memory/memory.ts | 104 ++++++++++++++++++----------------- src/memory/types.ts | 8 +++ src/session/context.ts | 2 +- src/session/session.ts | 6 ++ src/session/types.ts | 12 +--- 7 files changed, 95 insertions(+), 83 deletions(-) create mode 100644 src/memory/types.ts diff --git a/src/config/configs/memory.ts b/src/config/configs/memory.ts index 6319206..2ca125c 100644 --- a/src/config/configs/memory.ts +++ b/src/config/configs/memory.ts @@ -23,7 +23,7 @@ export default class MemoryConfig { `# 采用toml进行格式化 roles = ["正确"] # 当数组为空或不存在时,默认对所有角色生效 -[knowleges.测试] +[knowledges.测试] content = """ 这是内容 可以换行 @@ -34,14 +34,14 @@ relatedMemories = ["测试2"] # 相关记忆ID列表 users = ["114514", "1919810"] # 相关用户ID列表 groups = ["114514", "1919810"] # 相关群组ID列表 -[knowleges.测试2] +[knowledges.测试2] content = "单行形式,只有content字段是必须的"` ], ""); seal.ext.registerTemplateConfig(MemoryConfig.ext, "知识库记忆展示模板", [ - `{{#if KNOWLEGE}} + `{{#if KNOWLEDGE}} ## 知识库 - {{#each knowleges}} + {{#each knowledges}} {{index @index}}. ID:{{id}} 重要性:{{importance}} {{#each tags}}{{#if @first}}标签:{{/if}}{{{this}}}{{#unless @last}}, {{/unless}}{{/each}} @@ -177,8 +177,8 @@ content = "单行形式,只有content字段是必须的"` static get() { return { DIMENSION: seal.ext.getIntConfig(MemoryConfig.ext, "向量维度"), - KNOWLEGE: seal.ext.getBoolConfig(MemoryConfig.ext, "启用知识库记忆"), - KNOWLEGE_SHOW_NUMBER: seal.ext.getIntConfig(MemoryConfig.ext, "知识库记忆展示数量"), + KNOWLEDGE: seal.ext.getBoolConfig(MemoryConfig.ext, "启用知识库记忆"), + KNOWLEDGE_SHOW_NUMBER: seal.ext.getIntConfig(MemoryConfig.ext, "知识库记忆展示数量"), MEMORY: seal.ext.getBoolConfig(MemoryConfig.ext, "启用长期记忆"), MEMORY_LIMIT: seal.ext.getIntConfig(MemoryConfig.ext, "长期记忆上限"), MEMORY_SHOW_NUMBER: seal.ext.getIntConfig(MemoryConfig.ext, "长期记忆展示数量"), @@ -186,8 +186,8 @@ content = "单行形式,只有content字段是必须的"` SUMMARY_LIMIT: seal.ext.getIntConfig(MemoryConfig.ext, "总结记忆上限"), SUMMARY_INTERVAL: seal.ext.getIntConfig(MemoryConfig.ext, "总结记忆间隔轮数"), SUMMARY_SIZE: seal.ext.getIntConfig(MemoryConfig.ext, "总结记忆参与轮数"), - KNOWLEGE_MEMORIES_MAP: getKnowledgeMemoriesMapConfig(), - KNOWLEGE_TEMPLATE: getHandlebarsTemplateConfig(MemoryConfig.ext, "知识库记忆展示模板"), + KNOWLEDGE_MEMORIES_MAP: getKnowledgeMemoriesMapConfig(), + KNOWLEDGE_TEMPLATE: getHandlebarsTemplateConfig(MemoryConfig.ext, "知识库记忆展示模板"), MEMORY_TEMPLATE: getHandlebarsTemplateConfig(MemoryConfig.ext, "长期记忆展示模板"), SUMMARY_TEMPLATE: getHandlebarsTemplateConfig(MemoryConfig.ext, "总结记忆展示模板"), SUMMARY_PROMPT_TEMPLATE: getHandlebarsTemplateConfig(MemoryConfig.ext, "记忆总结prompt模板") @@ -195,10 +195,10 @@ content = "单行形式,只有content字段是必须的"` } } -class KnowlegeConfigItem { - static validKeysMap: { [key in keyof KnowlegeConfigItem]?: TypeDescriptor } = { +class KnowledgeConfigItem { + static validKeysMap: { [key in keyof KnowledgeConfigItem]?: TypeDescriptor } = { roles: { array: 'string' }, - knowleges: { + knowledges: { objectValue: { object: { content: 'string', @@ -212,7 +212,7 @@ class KnowlegeConfigItem { } } roles: string[]; - knowleges: { + knowledges: { [id: string]: { content: string, importance: number, @@ -225,17 +225,17 @@ class KnowlegeConfigItem { constructor() { this.roles = []; - this.knowleges = {}; + this.knowledges = {}; } } function getKnowledgeMemoriesMapConfig(): { [role: string]: MemoryItem[] } { - const knowlegeMaps: { [role: string]: { [id: string]: MemoryItem } } = {}; + const knowledgeMaps: { [role: string]: { [id: string]: MemoryItem } } = {}; seal.ext.getTemplateConfig(MemoryConfig.ext, "知识库记忆").forEach(tomlString => { - const kc = revive(KnowlegeConfigItem, load(tomlString)); + const kc = revive(KnowledgeConfigItem, load(tomlString)); const mmap: { [id: string]: MemoryItem } = {}; - for (const id in kc.knowleges) { - const k = kc.knowleges[id]; + for (const id in kc.knowledges) { + const k = kc.knowledges[id]; const m = new MemoryItem(); m.id = id; m.importance = k.importance; @@ -248,12 +248,12 @@ function getKnowledgeMemoriesMapConfig(): { [role: string]: MemoryItem[] } { } if (kc.roles.length === 0) kc.roles.push('*'); for (const role of kc.roles) { - if (!knowlegeMaps.hasOwnProperty(role)) knowlegeMaps[role] = {}; - knowlegeMaps[role] = { ...knowlegeMaps[role], ...mmap }; + if (!knowledgeMaps.hasOwnProperty(role)) knowledgeMaps[role] = {}; + knowledgeMaps[role] = { ...knowledgeMaps[role], ...mmap }; } }); - const knowlegeMemoriesMap: { [role: string]: MemoryItem[] } = {}; - for (const role of Object.keys(knowlegeMaps)) knowlegeMemoriesMap[role] = Object.values(knowlegeMaps[role]); - return knowlegeMemoriesMap; + const knowledgeMemoriesMap: { [role: string]: MemoryItem[] } = {}; + for (const role of Object.keys(knowledgeMaps)) knowledgeMemoriesMap[role] = Object.values(knowledgeMaps[role]); + return knowledgeMemoriesMap; } \ No newline at end of file diff --git a/src/memory/knowlege.ts b/src/memory/knowlege.ts index 2ec75d1..f20f78f 100644 --- a/src/memory/knowlege.ts +++ b/src/memory/knowlege.ts @@ -1,3 +1,5 @@ +import MemoryService from "./memory"; + export default class KnowledgeService extends MemoryService { constructor() { super(); diff --git a/src/memory/memory.ts b/src/memory/memory.ts index 228bddd..7bb2247 100644 --- a/src/memory/memory.ts +++ b/src/memory/memory.ts @@ -8,6 +8,9 @@ import { Tool } from "../tool/tool"; import { fmtDate } from "../utils/string"; import Image from "../image/image"; import Model from "../agent/model"; +import { searchOptions } from "./types"; +import Agent from "../agent/agent"; +import { Session } from "../session/session"; export class MemoryItem { static validKeysMap: { [key in keyof MemoryItem]?: TypeDescriptor } = { @@ -92,12 +95,12 @@ export class MemoryItem { /** * 计算记忆与查询的相似度分数 * @param v 查询向量 + * @param t 查询标签列表 * @param u 查询用户列表 * @param g 查询群组列表 - * @param t 查询标签列表 * @returns 相似度分数(0-1) */ - calculateSimilarity(v: number[], u: string[], g: string[], t: string[]): number { + calculateSimilarity(v: number[], t: string[], u: string[], g: string[]): number { // 总权重 0-1 const tw = (v.length ? 0.4 : 0) + (u.length ? 0.2 : 0) + (g.length ? 0.2 : 0) + (t.length ? 0.2 : 0); if (tw === 0) return 0; @@ -118,13 +121,13 @@ export class MemoryItem { /** * 计算记忆的最终分数 * @param v 查询向量 + * @param t 查询标签列表 * @param u 查询用户列表 * @param g 查询群组列表 - * @param t 查询标签列表 * @returns 相似度分数(0-1) */ - calculateScore(v: number[], u: string[], g: string[], t: string[]): number { - const similarity = this.calculateSimilarity(v, u, g, t); + calculateScore(v: number[], t: string[], u: string[], g: string[]): number { + const similarity = this.calculateSimilarity(v, t, u, g); return this.importance * 0.2 + this.accessScore * 0.2 + similarity * 0.6; } @@ -268,25 +271,30 @@ export default class MemoryService { async search(query: string, options: searchOptions = { topK: 10, - userIdList: [], - groupIdList: [], tags: [], - includeImages: false, + relatedMemories: [], + users: [], + groups: [], method: 'score' }) { if (!this.memories.length) return []; - const { userIdList: ul, groupIdList: gl, tags: kws, includeImages, method } = options; + const { topK = 10, tags = [], relatedMemories = [], users = [], groups = [], method = 'score' } = options; - const { isMemoryVector, embeddingDimension } = Config.memory; - let qv: number[] = []; - if (isMemoryVector && query) { - qv = await getEmbedding(query); - if (!qv.length) { + const { DIMENSION } = Config.memory; + let v: number[] = []; + if (DIMENSION > 0 && query) { + const model = Model.getEmbeddingModel('text-embedding'); + v = await model.callEmbedding(query); + if (!v.length) { logger.error('查询向量为空'); return []; } + if (v.length !== DIMENSION) { + logger.error(`查询向量维度不匹配。期望: ${DIMENSION}, 实际: ${v.length}`); + return []; + } await Promise.all(this.memories.map(async m => { - if (m.vector.length !== embeddingDimension) { + if (m.vector.length !== DIMENSION) { logger.info(`记忆向量维度不匹配,重新获取向量: ${m.id}`); await m.updateVector(); } @@ -295,61 +303,59 @@ export default class MemoryService { return this.memories .map(m => { - if (includeImages && m.imageIdList.length === 0) return null; - const mc = m.copy; - if (mc.tags.some(kw => query.includes(kw))) mc.weight += 10; //提权 - return mc; + if (relatedMemories.length > 0 && relatedMemories.some(r => m.id === r || m.relatedMemories.includes(r))) return m; + return null; }) - .filter(m => m) + .filter(m => m !== null) .sort((a, b) => { switch (method) { - case 'weight': return b.weight - a.weight; - case 'similarity': return b.calculateSimilarity(qv, ul, gl, kws) - a.calculateSimilarity(qv, ul, gl, kws); - case 'score': return b.calculateScore(qv, ul, gl, kws) - a.calculateScore(qv, ul, gl, kws); + case 'importance': return b.importance - a.importance; + case 'similarity': return b.calculateSimilarity(v, tags, users, groups) - a.calculateSimilarity(v, tags, users, groups); + case 'score': return b.calculateScore(v, tags, users, groups) - a.calculateScore(v, tags, users, groups); case 'early': return a.createAt - b.createAt; case 'late': return b.createAt - a.createAt; case 'recent': return b.lastAccessedAt - a.lastAccessedAt; } }) - .slice(0, options.topK || 10); + .slice(0, topK); } - updateMemoryWeight(s: string, role: 'user' | 'assistant') { - const increase = role === 'user' ? 1 : 0.1; - const decrease = role === 'user' ? 0.1 : 0; + async accessMemory(s: string) { const now = Math.floor(Date.now() / 1000); - - for (const id in this.memoryMap) { - const m = this.memoryMap[id]; - if (m.tags.some(kw => s.includes(kw))) { - m.weight = Math.max(10, m.weight + increase); - m.lastAccessedAt = now; - } else { - m.weight = Math.min(0, m.weight - decrease); - } - } + (await this.search(s, { + topK: 5, + tags: [], + relatedMemories: [], + users: [], + groups: [], + method: 'similarity' + })).forEach(m => { + m.lastAccessedAt = now; + m.accessCount++; + }) } - // wip - updateRelatedMemoryWeight(ctx: seal.MsgContext, context: Context, s: string, role: 'user' | 'assistant') { + static async accessRelatedMemory(agent: Agent, session: Session, s: string, role: 'user' | 'assistant') { + const task: Promise[] = []; // bot记忆权重更新 - AIManager.getAI(ctx.endPoint.userId).memory.updateMemoryWeight(s, role); + task.push(agent.sessionService.memory.accessMemory(s)); // 知识库记忆权重更新 - knowledgeService.updateMemoryWeight(s, role); + task.push(agent.sessionService.knowledge.accessMemory(s)); // 会话自身记忆权重更新 - this.updateMemoryWeight(s, role); + task.push(session.memory.accessMemory(s)); // 群内用户的记忆权重更新 - if (!ctx.isPrivate) context.userInfoList.forEach(ui => AIManager.getAI(ui.id).memory.updateMemoryWeight(s, role)); + if (session.sessionType === 'group') task.push(...session.context.users.map(u => agent.sessionService.getSession(u).memory.accessMemory(s))); + await Promise.all(task); } - async getTopScoreMemoryList(text: string = '', uid: string = '', gid: string = '') { - const { memoryShowNumber } = Config.memory; + async getTopScoreMemories(text: string = '', uid: string = '', gid: string = '') { + const { MEMORY_SHOW_NUMBER } = Config.memory; return await this.search(text, { - topK: memoryShowNumber, - userIdList: uid ? [uid] : [], - groupIdList: gid ? [gid] : [], + topK: MEMORY_SHOW_NUMBER, tags: [], - includeImages: false, + relatedMemories: [], + users: uid ? [uid] : [], + groups: gid ? [gid] : [], method: 'score' }); } diff --git a/src/memory/types.ts b/src/memory/types.ts new file mode 100644 index 0000000..1c8e821 --- /dev/null +++ b/src/memory/types.ts @@ -0,0 +1,8 @@ +export interface searchOptions { + topK: number; + tags: string[]; + relatedMemories: string[]; + users: string[]; + groups: string[]; + method: 'importance' | 'similarity' | 'score' | 'early' | 'late' | 'recent'; +} \ No newline at end of file diff --git a/src/session/context.ts b/src/session/context.ts index beedecd..d1c8961 100644 --- a/src/session/context.ts +++ b/src/session/context.ts @@ -339,7 +339,7 @@ export class Context { return null; } - get userInfoList(): UserInfo[] { + get users(): UserInfo[] { const userMap: { [key: string]: UserInfo } = {}; this.messages.forEach(message => { if (message.role === 'user' && message.name && message.uid && !message.name.startsWith('_')) { diff --git a/src/session/session.ts b/src/session/session.ts index d4c0f61..55f828d 100644 --- a/src/session/session.ts +++ b/src/session/session.ts @@ -7,6 +7,7 @@ import { revive, TypeDescriptor } from "../utils/utils"; import { Context } from "./context"; import { MemoryService } from "../memory/memory"; import { RequestMessage, SessionType, State } from "./types"; +import KnowledgeService from "../memory/knowlege"; export class Session { static validKeysMap: { [key in keyof Session]?: TypeDescriptor } = { @@ -127,4 +128,9 @@ export class SessionService { } return this.sessionMap[sessionId]; } + + get knowledge(): KnowledgeService { + if (!knowledgeServiceMap.hasOwnProperty(this.agentName)) return knowledgeServiceMap['*']; + return knowledgeServiceMap[this.agentName]; + } } \ No newline at end of file diff --git a/src/session/types.ts b/src/session/types.ts index f63b90e..94be47d 100644 --- a/src/session/types.ts +++ b/src/session/types.ts @@ -41,14 +41,4 @@ export interface RequestMessage { tool_call_id?: string; } -export type SessionType = 'user' | 'group'; - -export interface searchOptions { - topK: number; - tags: string[]; - relatedMemories: string[]; - users: string[]; - groups: string[]; - includeImages: boolean; - method: 'importance' | 'similarity' | 'score' | 'early' | 'late' | 'recent'; -} \ No newline at end of file +export type SessionType = 'user' | 'group'; \ No newline at end of file From 01798f94d9d9105cbb62bbd551a871fb89b1f5e8 Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Thu, 12 Mar 2026 23:56:03 +0800 Subject: [PATCH 31/33] =?UTF-8?q?=E5=9F=BA=E7=A1=80memory=E9=87=8D?= =?UTF-8?q?=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/memory/memory.ts | 314 ++++--------------- src/memory/memory_item.ts | 146 +++++++++ src/memory/{session.ts => session_memory.ts} | 36 ++- src/memory/types.ts | 6 + src/session/context.ts | 2 +- 5 files changed, 226 insertions(+), 278 deletions(-) create mode 100644 src/memory/memory_item.ts rename src/memory/{session.ts => session_memory.ts} (87%) diff --git a/src/memory/memory.ts b/src/memory/memory.ts index 7bb2247..3c90f91 100644 --- a/src/memory/memory.ts +++ b/src/memory/memory.ts @@ -1,158 +1,11 @@ import { Config } from "../config/config"; -import { Context } from "../session/context"; -import { cosineSimilarity, generateId, getCommonItem, revive, TypeDescriptor } from "../utils/utils"; +import { generateId, TypeDescriptor } from "../utils/utils"; import { logger } from "../logger"; -import { fetchData, getEmbedding } from "../agent/service"; -import { buildContent, getRoleSetting, parseBody } from "../utils/message"; -import { Tool } from "../tool/tool"; -import { fmtDate } from "../utils/string"; -import Image from "../image/image"; import Model from "../agent/model"; -import { searchOptions } from "./types"; +import { MemorySource, searchOptions } from "./types"; import Agent from "../agent/agent"; import { Session } from "../session/session"; - -export class MemoryItem { - static validKeysMap: { [key in keyof MemoryItem]?: TypeDescriptor } = { - 'id': 'string', - 'sessionId': 'string', - 'visibility': 'string', - 'createAt': 'number', - 'lastAccessedAt': 'number', - 'accessCount': 'number', - 'importance': 'number', - 'content': 'string', - 'vector': { array: 'number' }, - 'tags': { array: 'string' }, - 'relatedMemories': { array: 'string' }, - 'users': { array: 'string' }, - 'groups': { array: 'string' } - }; - - // 核心字段 - id: string; // 记忆ID - sessionId: string; // 记忆来源会话ID - visibility: 'public' | 'private'; // 记忆可见性 - - // 淘汰策略相关 - createAt: number; // 创建时间 TTL - lastAccessedAt: number; // 最后访问时间 LRU - accessCount: number; // 访问次数 LFU - importance: number; // 重要性0-1 - - // 内容 - content: string; // 记忆内容 - vector: number[]; // 记忆向量 - tags: string[]; // 记忆标签列表 - relatedMemories: string[]; // 相关记忆ID列表 - users: string[]; // 记忆相关用户ID列表 - groups: string[]; // 记忆相关群组ID列表 - - constructor() { - this.id = ''; - this.sessionId = ''; - this.visibility = 'public'; - this.createAt = 0; - this.lastAccessedAt = 0; - this.accessCount = 0; - this.importance = 0; - this.content = ''; - this.vector = []; - this.tags = []; - this.relatedMemories = []; - this.users = []; - this.groups = []; - } - - get copy(): MemoryItem { - return revive(MemoryItem, JSON.parse(JSON.stringify(this))); - } - - /** - * 计算记忆的新鲜度衰减因子,越大表示越新鲜 - * @returns 衰减因子(1→0) - */ - get decay() { - const now = Math.floor(Date.now() / 1000); - // 年龄(天) - const age = (now - this.createAt) / (24 * 60 * 60); - // 活跃时间(小时) - const activity = (now - this.lastAccessedAt) / (60 * 60); - // 年龄衰减: 半衰期7天 - const ageDecay = this.createAt === 0 ? 1 : Math.exp(-age / 7 * Math.LN2); - // 活跃衰减: 半衰期4小时 - const activityDecay = this.lastAccessedAt === 0 ? 1 : Math.exp(-activity / 4 * Math.LN2); - // 衰减因子 - return ageDecay * 0.7 + activityDecay * 0.3; // 一拍脑门决定的加权 - } - - get accessScore() { - // 饱和函数,访问次数归一化 - const accessNorm = 1 - 1 / (this.accessCount + 1); - return accessNorm * this.decay; - } - - /** - * 计算记忆与查询的相似度分数 - * @param v 查询向量 - * @param t 查询标签列表 - * @param u 查询用户列表 - * @param g 查询群组列表 - * @returns 相似度分数(0-1) - */ - calculateSimilarity(v: number[], t: string[], u: string[], g: string[]): number { - // 总权重 0-1 - const tw = (v.length ? 0.4 : 0) + (u.length ? 0.2 : 0) + (g.length ? 0.2 : 0) + (t.length ? 0.2 : 0); - if (tw === 0) return 0; - // 向量相似度分数(如果提供了向量v) 0-1 - const vs = (v && v.length > 0 && this.vector && this.vector.length > 0) ? (cosineSimilarity(v, this.vector) + 1) / 2 : 0; - // 用户相似度分数 0-1 - const us = u.length ? getCommonItem(this.users, u).length / new Set([...this.users, ...u]).size : 0; - // 群组相似度分数 0-1 - const gs = g.length ? getCommonItem(this.groups, g).length / new Set([...this.groups, ...g]).size : 0; - // 标签匹配分数 0-1 - const ts = t.length ? getCommonItem(this.tags, t).length / new Set([...this.tags, ...t]).size : 0; - // 综合相似度分数 0-1 - const avs = vs * 0.4 + us * 0.2 + gs * 0.2 + ts * 0.2; - // 相似度增强因子 0-1 - return avs / tw; - } - - /** - * 计算记忆的最终分数 - * @param v 查询向量 - * @param t 查询标签列表 - * @param u 查询用户列表 - * @param g 查询群组列表 - * @returns 相似度分数(0-1) - */ - calculateScore(v: number[], t: string[], u: string[], g: string[]): number { - const similarity = this.calculateSimilarity(v, t, u, g); - return this.importance * 0.2 + this.accessScore * 0.2 + similarity * 0.6; - } - - compareWith(m: MemoryItem): boolean { - return this.content === m.content && this.sessionId === m.sessionId; - } - - merge(m: MemoryItem) { - this.importance = m.importance; - this.tags = Array.from(new Set([...this.tags, ...m.tags])); - this.relatedMemories = Array.from(new Set([...this.relatedMemories, ...m.relatedMemories])); - this.users = Array.from(new Set([...this.users, ...m.users])); - this.groups = Array.from(new Set([...this.groups, ...m.groups])); - } - - async updateVector() { - const { DIMENSION } = Config.memory; - logger.info(`更新记忆向量: ${this.id}`); - const model = Model.getEmbeddingModel('text-embedding'); - const vector = await model.callEmbedding(this.content); - if (!vector.length) return logger.error('返回向量为空'); - if (vector.length !== DIMENSION) return logger.error(`向量维度不匹配。期望: ${DIMENSION}, 实际: ${vector.length}`); - this.vector = vector; - } -} +import { MemoryItem } from "./memory_item"; export default class MemoryService { static validKeysMap: { [key in keyof MemoryService]?: TypeDescriptor } = { @@ -208,7 +61,7 @@ export default class MemoryService { if (memoriesToAdd.length === 0) return; await Promise.all(memoriesToAdd.map(async m => await m.updateVector())); - this.limitMemory(memoriesToAdd.length); + this.limitMemories(memoriesToAdd.length); memoriesToAdd.forEach(m => this.memoryMap[m.id] = m); } @@ -249,7 +102,7 @@ export default class MemoryService { } } - limitMemory(vacancy: number) { + limitMemories(vacancy: number) { const { MEMORY_LIMIT } = Config.memory; const limit = MEMORY_LIMIT > vacancy ? MEMORY_LIMIT - vacancy : 0; // 预留空位用于存储最新记忆 if (this.memories.length <= limit) return; @@ -265,7 +118,7 @@ export default class MemoryService { .forEach(m => delete this.memoryMap?.[m.id]); } - clearMemory() { + clearMemories() { this.memoryMap = {}; } @@ -320,7 +173,7 @@ export default class MemoryService { .slice(0, topK); } - async accessMemory(s: string) { + async accessMemories(s: string) { const now = Math.floor(Date.now() / 1000); (await this.search(s, { topK: 5, @@ -335,12 +188,13 @@ export default class MemoryService { }) } - static async accessRelatedMemory(agent: Agent, session: Session, s: string, role: 'user' | 'assistant') { + static async accessRelatedMemories(session: Session, s: string) { + const agent = Agent.get(session.agentName); const task: Promise[] = []; // bot记忆权重更新 task.push(agent.sessionService.memory.accessMemory(s)); // 知识库记忆权重更新 - task.push(agent.sessionService.knowledge.accessMemory(s)); + task.push(agent.sessionService.knowledge.accessMemories(s)); // 会话自身记忆权重更新 task.push(session.memory.accessMemory(s)); // 群内用户的记忆权重更新 @@ -348,131 +202,69 @@ export default class MemoryService { await Promise.all(task); } - async getTopScoreMemories(text: string = '', uid: string = '', gid: string = '') { + async getTopScoreMemories(text: string = '', users: string[] = [], groups: string[] = []) { const { MEMORY_SHOW_NUMBER } = Config.memory; return await this.search(text, { topK: MEMORY_SHOW_NUMBER, tags: [], relatedMemories: [], - users: uid ? [uid] : [], - groups: gid ? [gid] : [], + users, + groups, method: 'score' }); } - - getLatestMemoryListText(sid: string, p: number = 1): string { - if (this.memories.length === 0) return ''; + getLatestMemories(p: number = 1): MemoryItem[] { + if (this.memories.length === 0) return []; if (p > Math.ceil(this.memories.length / 5)) p = Math.ceil(this.memories.length / 5); - const latestMemoryList = this.memories + return this.memories .sort((a, b) => b.createAt - a.createAt) .slice((p - 1) * 5, p * 5); - return this.buildMemory(sid, latestMemoryList) + `\n当前页码: ${p}/${Math.ceil(this.memories.length / 5)}`; } - // wip 和默认配置一起改 - buildMemory(sid: string, ml: MemoryItem[]): string { - if (ml.length === 0) return ''; - const { showNumber } = Config.message; - const { memoryShowTemplate, memorySingleShowTemplate } = Config.memory; - - let memoryContent = ''; - if (ml.length === 0) { - memoryContent = '无'; - } else { - memoryContent = ml - .map((m, i) => { - return memorySingleShowTemplate({ - "序号": i + 1, - "记忆ID": m.id, - "记忆时间": fmtDate(m.createAt), - "个人记忆": si.isPrivate, - "私聊": m.sessionInfo.isPrivate, - "展示号码": showNumber, - "群聊名称": m.sessionInfo.name, - "群聊号码": m.sessionInfo.id, - "相关用户": m.userList.map(u => u.name + (showNumber ? `(${u.id.replace(/^.+:/, '')})` : '')).join(';'), - "相关群聊": m.groupList.map(g => g.name + (showNumber ? `(${g.id.replace(/^.+:/, '')})` : '')).join(';'), - "关键词": m.tags.join(';'), - "记忆内容": m.content - }); - }).join('\n'); - } - - return memoryShowTemplate({ - "私聊": si.isPrivate, - "展示号码": showNumber, - "用户名称": si.name, - "用户号码": si.id.replace(/^.+:/, ''), - "群聊名称": si.name, - "群聊号码": si.id.replace(/^.+:/, ''), - "设定": this.persona, - "记忆列表": memoryContent - }) + '\n'; + buildMemoriesPrompt(sources: MemorySource[]): string { + if (sources.length === 0) return ''; + const { MEMORY, MEMORY_TEMPLATE } = Config.memory; + return MEMORY_TEMPLATE({ + "MEMORY": MEMORY, + "sources": sources + }); } - // wip - async buildMemoryPrompt(ctx: seal.MsgContext, context: Context, text: string, ui: UserInfo, gi: GroupInfo): Promise { - const ai = AIManager.getAI(ctx.endPoint.userId); - let s = ai.memory.buildMemory({ - isPrivate: true, - id: ctx.endPoint.userId, - name: seal.formatTmpl(ctx, "核心:骰子名字") - }, await ai.memory.getTopScoreMemoryList(text, ui, gi)); - - if (ctx.isPrivate) { - return this.buildMemory({ - isPrivate: true, - id: ctx.player.userId, - name: ctx.player.name - }, await ai.memory.getTopScoreMemoryList(text, ui, gi)); - } else { - // 群聊记忆 - s += this.buildMemory({ - isPrivate: false, - id: ctx.group.groupId, - name: ctx.group.groupName - }, await ai.memory.getTopScoreMemoryList(text, ui, gi)); - - // 群内用户的个人记忆 - const set = new Set(); - for (const ui of context.userInfoList) { - const name = ui.name; - const uid = ui.id; - if (set.has(uid)) continue; - set.add(uid); - - const ai = AIManager.getAI(uid); - s += ai.memory.buildMemory({ - isPrivate: true, - id: uid, - name: name - }, await ai.memory.getTopScoreMemoryList(text, ui, gi)); - } - - return s; - } + buildLatestMemoriesText(p: number = 1): string { + const sources = [{ + source: '最新记忆', + memories: this.getLatestMemories(p) + }] + return this.buildMemoriesPrompt(sources) + `\n当前页码: ${p}/${Math.ceil(this.memories.length / 5)}`; } - includedImage(id: string): boolean { - for (const m of this.memories) { - const image = m.imageIdList.find(i => i === id); - if (image) { - m.weight += 0.2; - return true; + async buildMemoryPrompt(session: Session, text: string): Promise { + // 获取users、groups + const users = session.sessionType === 'group' ? session.context.users : [session.sessionId]; + const groups = session.sessionType === 'group' ? [session.sessionId] : []; + const agent = Agent.get(session.agentName); + const sources: MemorySource[] = []; + // bot记忆 + sources.push({ + source: '核心记忆', + memories: await agent.sessionService.memory.getTopScoreMemories(text, users, groups) + }) + // 会话记忆 + sources.push({ + source: '会话记忆', + memories: await session.memory.getTopScoreMemories(text, users, groups) + }) + // 群内用户的记忆 + if (session.sessionType === 'group') { + for (const u of session.context.users) { + sources.push({ + source: `用户${u}记忆`, + memories: await agent.sessionService.getSession(u).memory.getTopScoreMemories(text, users, groups) + }) } } - return false; - } - findMemoryByImageIdPrefix(id: string): MemoryItem | null { - for (const m of this.memories) { - const image = m.imageIdList.find(img => img.replace(/_\d+$/, "") === id); - if (image) { - m.weight += 0.2; - return m; - } - } - return null; + return this.buildMemoriesPrompt(sources); } } diff --git a/src/memory/memory_item.ts b/src/memory/memory_item.ts new file mode 100644 index 0000000..9a46fac --- /dev/null +++ b/src/memory/memory_item.ts @@ -0,0 +1,146 @@ +import Model from "../agent/model"; +import { Config } from "../config/config"; +import { logger } from "../logger"; +import { cosineSimilarity, getCommonItem, revive, TypeDescriptor } from "../utils/utils"; + +export class MemoryItem { + static validKeysMap: { [key in keyof MemoryItem]?: TypeDescriptor } = { + 'id': 'string', + 'sessionId': 'string', + 'visibility': 'string', + 'createAt': 'number', + 'lastAccessedAt': 'number', + 'accessCount': 'number', + 'importance': 'number', + 'content': 'string', + 'vector': { array: 'number' }, + 'tags': { array: 'string' }, + 'relatedMemories': { array: 'string' }, + 'users': { array: 'string' }, + 'groups': { array: 'string' } + }; + + // 核心字段 + id: string; // 记忆ID + sessionId: string; // 记忆来源会话ID + visibility: 'public' | 'private'; // 记忆可见性 + + // 淘汰策略相关 + createAt: number; // 创建时间 TTL + lastAccessedAt: number; // 最后访问时间 LRU + accessCount: number; // 访问次数 LFU + importance: number; // 重要性0-1 + + // 内容 + content: string; // 记忆内容 + vector: number[]; // 记忆向量 + tags: string[]; // 记忆标签列表 + relatedMemories: string[]; // 相关记忆ID列表 + users: string[]; // 记忆相关用户ID列表 + groups: string[]; // 记忆相关群组ID列表 + + constructor() { + this.id = ''; + this.sessionId = ''; + this.visibility = 'public'; + this.createAt = 0; + this.lastAccessedAt = 0; + this.accessCount = 0; + this.importance = 0; + this.content = ''; + this.vector = []; + this.tags = []; + this.relatedMemories = []; + this.users = []; + this.groups = []; + } + + get copy(): MemoryItem { + return revive(MemoryItem, JSON.parse(JSON.stringify(this))); + } + + /** + * 计算记忆的新鲜度衰减因子,越大表示越新鲜 + * @returns 衰减因子(1→0) + */ + get decay() { + const now = Math.floor(Date.now() / 1000); + // 年龄(天) + const age = (now - this.createAt) / (24 * 60 * 60); + // 活跃时间(小时) + const activity = (now - this.lastAccessedAt) / (60 * 60); + // 年龄衰减: 半衰期7天 + const ageDecay = this.createAt === 0 ? 1 : Math.exp(-age / 7 * Math.LN2); + // 活跃衰减: 半衰期4小时 + const activityDecay = this.lastAccessedAt === 0 ? 1 : Math.exp(-activity / 4 * Math.LN2); + // 衰减因子 + return ageDecay * 0.7 + activityDecay * 0.3; // 一拍脑门决定的加权 + } + + get accessScore() { + // 饱和函数,访问次数归一化 + const accessNorm = 1 - 1 / (this.accessCount + 1); + return accessNorm * this.decay; + } + + /** + * 计算记忆与查询的相似度分数 + * @param v 查询向量 + * @param t 查询标签列表 + * @param u 查询用户列表 + * @param g 查询群组列表 + * @returns 相似度分数(0-1) + */ + calculateSimilarity(v: number[], t: string[], u: string[], g: string[]): number { + // 总权重 0-1 + const tw = (v.length ? 0.4 : 0) + (u.length ? 0.2 : 0) + (g.length ? 0.2 : 0) + (t.length ? 0.2 : 0); + if (tw === 0) return 0; + // 向量相似度分数(如果提供了向量v) 0-1 + const vs = (v && v.length > 0 && this.vector && this.vector.length > 0) ? (cosineSimilarity(v, this.vector) + 1) / 2 : 0; + // 用户相似度分数 0-1 + const us = u.length ? getCommonItem(this.users, u).length / new Set([...this.users, ...u]).size : 0; + // 群组相似度分数 0-1 + const gs = g.length ? getCommonItem(this.groups, g).length / new Set([...this.groups, ...g]).size : 0; + // 标签匹配分数 0-1 + const ts = t.length ? getCommonItem(this.tags, t).length / new Set([...this.tags, ...t]).size : 0; + // 综合相似度分数 0-1 + const avs = vs * 0.4 + us * 0.2 + gs * 0.2 + ts * 0.2; + // 相似度增强因子 0-1 + return avs / tw; + } + + /** + * 计算记忆的最终分数 + * @param v 查询向量 + * @param t 查询标签列表 + * @param u 查询用户列表 + * @param g 查询群组列表 + * @returns 相似度分数(0-1) + */ + calculateScore(v: number[], t: string[], u: string[], g: string[]): number { + const similarity = this.calculateSimilarity(v, t, u, g); + return this.importance * 0.2 + this.accessScore * 0.2 + similarity * 0.6; + } + + compareWith(m: MemoryItem): boolean { + return this.content === m.content && this.sessionId === m.sessionId; + } + + merge(m: MemoryItem) { + this.importance = m.importance; + this.tags = Array.from(new Set([...this.tags, ...m.tags])); + this.relatedMemories = Array.from(new Set([...this.relatedMemories, ...m.relatedMemories])); + this.users = Array.from(new Set([...this.users, ...m.users])); + this.groups = Array.from(new Set([...this.groups, ...m.groups])); + } + + async updateVector() { + const { DIMENSION } = Config.memory; + logger.info(`更新记忆向量: ${this.id}`); + const model = Model.getEmbeddingModel('text-embedding'); + const vector = await model.callEmbedding(this.content); + if (!vector.length) return logger.error('返回向量为空'); + if (vector.length !== DIMENSION) return logger.error(`向量维度不匹配。期望: ${DIMENSION}, 实际: ${vector.length}`); + this.vector = vector; + } +} \ No newline at end of file diff --git a/src/memory/session.ts b/src/memory/session_memory.ts similarity index 87% rename from src/memory/session.ts rename to src/memory/session_memory.ts index 8a608d6..965b312 100644 --- a/src/memory/session.ts +++ b/src/memory/session_memory.ts @@ -1,31 +1,26 @@ +import { Config } from "../config/config"; +import { logger } from "../logger"; +import { TypeDescriptor } from "../utils/utils"; +import MemoryService from "./memory"; +import { MemoryItem } from "./memory_item"; + export default class SessionMemoryService extends MemoryService { static validKeysMap: { [key in keyof SessionMemoryService]?: TypeDescriptor } = { memoryMap: { array: MemoryItem }, summaryStatus: 'boolean', - summaryList: { array: 'string' } + summaries: { array: 'string' } }; summaryStatus: boolean; - summaryList: string[]; + summaries: string[]; constructor() { super(); this.summaryStatus = false; - this.summaryList = []; - } - - limitSummary() { - const { SummaryLimit } = Config.memory; - if (this.summaryList.length > SummaryLimit) { - this.summaryList.splice(0, this.summaryList.length - SummaryLimit); - } - } - - clearSummary() { - this.summaryList = []; + this.summaries = []; } - // wip - async updateSummary(ctx: seal.MsgContext, msg: seal.Message, ai: AI) { + // wip 使用总结智能体 + async summarize(session: Session) { if (!this.summaryStatus) return; const { url: chatUrl, apiKey: chatApiKey } = Config.request; @@ -130,4 +125,13 @@ export default class SessionMemoryService extends MemoryService { logger.error(`更新短期记忆失败: ${e.message}`); } } + + limitSummaries() { + const { SUMMARY_LIMIT } = Config.memory; + if (this.summaries.length > SUMMARY_LIMIT) this.summaries.splice(0, this.summaries.length - SUMMARY_LIMIT); + } + + clearSummaries() { + this.summaries = []; + } } \ No newline at end of file diff --git a/src/memory/types.ts b/src/memory/types.ts index 1c8e821..faae796 100644 --- a/src/memory/types.ts +++ b/src/memory/types.ts @@ -1,3 +1,5 @@ +import { MemoryItem } from "./memory"; + export interface searchOptions { topK: number; tags: string[]; @@ -5,4 +7,8 @@ export interface searchOptions { users: string[]; groups: string[]; method: 'importance' | 'similarity' | 'score' | 'early' | 'late' | 'recent'; +} +export interface MemorySource { + source: string; + memories: MemoryItem[]; } \ No newline at end of file diff --git a/src/session/context.ts b/src/session/context.ts index d1c8961..5836286 100644 --- a/src/session/context.ts +++ b/src/session/context.ts @@ -339,7 +339,7 @@ export class Context { return null; } - get users(): UserInfo[] { + get users(): string[] { const userMap: { [key: string]: UserInfo } = {}; this.messages.forEach(message => { if (message.role === 'user' && message.name && message.uid && !message.name.startsWith('_')) { From ab27b2d49bbe977142eda6d955ce46964d5ef4ef Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Sat, 14 Mar 2026 02:36:22 +0800 Subject: [PATCH 32/33] =?UTF-8?q?=E8=B6=8A=E5=86=99=E8=B6=8A=E6=99=95?= =?UTF-8?q?=E5=85=A8=E6=98=AF=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/agent/agent.ts | 4 +- src/agent/model.ts | 319 ---------------------------------- src/agent/stream.ts | 2 +- src/cmd/sub_cmd/image.ts | 2 +- src/config/config.ts | 12 +- src/config/configs/memory.ts | 6 +- src/config/configs/model.ts | 4 +- src/{image => }/image.ts | 10 +- src/index.ts | 3 +- src/logger.ts | 37 ++-- src/memory/image.ts | 5 - src/memory/knowledge.ts | 85 +++++++++ src/memory/knowlege.ts | 163 ----------------- src/memory/memory.ts | 52 ++---- src/memory/memory_item.ts | 14 +- src/memory/session_memory.ts | 57 +++++- src/memory/types.ts | 2 +- src/model/chat.ts | 58 +++++++ src/model/embedding.ts | 63 +++++++ src/model/image.ts | 96 ++++++++++ src/model/model.ts | 86 +++++++++ src/{agent => model}/types.ts | 2 +- src/session/context.ts | 2 +- src/session/session.ts | 5 +- src/utils/string.ts | 2 +- src/utils/web.ts | 31 ++++ 26 files changed, 536 insertions(+), 586 deletions(-) delete mode 100644 src/agent/model.ts rename src/{image => }/image.ts (97%) delete mode 100644 src/memory/image.ts create mode 100644 src/memory/knowledge.ts delete mode 100644 src/memory/knowlege.ts create mode 100644 src/model/chat.ts create mode 100644 src/model/embedding.ts create mode 100644 src/model/image.ts create mode 100644 src/model/model.ts rename src/{agent => model}/types.ts (80%) create mode 100644 src/utils/web.ts diff --git a/src/agent/agent.ts b/src/agent/agent.ts index 7205e29..eb72fad 100644 --- a/src/agent/agent.ts +++ b/src/agent/agent.ts @@ -1,10 +1,10 @@ -import { Config } from "../config/config"; +import Config from "../config/config"; import { logger } from "../logger"; import { SessionService } from "../session/session"; import { ToolName } from "../tool/tool"; import { revive, TypeDescriptor } from "../utils/utils"; import Model from "./model"; -import { ChatModelUse, ModelUse } from "./types"; +import { ChatModelUse, ModelUse } from "../model/types"; export default class Agent { static validKeysMap: { [key in keyof Agent]?: TypeDescriptor } = { diff --git a/src/agent/model.ts b/src/agent/model.ts deleted file mode 100644 index 1c61037..0000000 --- a/src/agent/model.ts +++ /dev/null @@ -1,319 +0,0 @@ -import { Config } from "../config/config"; -import { DEFAULT_CHAT_MODEL_BODY, DEFAULT_EMBEDDING_MODEL_BODY, DEFAULT_IMAGE_MODEL_BODY } from "../config/static_config"; -import { logger } from "../logger"; -import { ToolCall } from "../tool/types"; -import { withTimeout } from "../utils/utils"; -import { Agent } from "./agent"; -import { ChatModelUse, EmbeddingModelUse, ImageModelUse, ModelBody } from "./types"; -import { UsageManager } from "./usage"; - -export class BaseModel { - name: string; - provider: string; - baseUrl: string; - apiKey: string; - body: ModelBody; - - constructor(name: string, provider: string, base_url: string, api_key: string, body: ModelBody) { - this.name = name; - this.provider = provider; - this.baseUrl = base_url; - this.apiKey = api_key; - this.body = body; - } - - buildBody(args: { [key: string]: any }) { - const body = JSON.parse(JSON.stringify(this.body)); - for (const key in args) { - if (!args.hasOwnProperty(key)) body[key] = args[key]; - } - return body; - } -} - -export class ChatModel extends BaseModel { - use: ChatModelUse[]; - constructor(use: ChatModelUse[], name: string, provider: string, base_url: string, api_key: string, body: ModelBody) { - super(name, provider, base_url, api_key, body); - this.use = use; - } - - get url() { - return `${this.baseUrl}/chat/completions`; - } - - async callChat(agent: Agent, sessionId: string): Promise<{ content: string, tool_calls: ToolCall[] }> { - const { TIMEOUT } = Config.base; - try { - const time = Date.now(); - - const data = await withTimeout(() => fetchData(this.url, this.apiKey, this.buildBody({ - ...DEFAULT_CHAT_MODEL_BODY, - messages: agent.sessionService.getSession(sessionId).getMessages(), - tools: agent.getRequestTools() - })), TIMEOUT); - - if (data.choices && data.choices.length > 0) { - UsageManager.updateUsage(data.model, data.usage); - - const message = data.choices[0].message; - const finish_reason = data.choices[0].finish_reason; - - if (message.hasOwnProperty('reasoning_content')) { - logger.info(`思维链内容:`, message.reasoning_content); - } - - const content = message.content || ''; - - logger.info(`响应内容:`, content, '\nlatency:', Date.now() - time, 'ms', '\nfinish_reason:', finish_reason); - - return { content, tool_calls: message.tool_calls || [] }; - } else { - throw new Error(`服务器响应中没有choices或choices为空\n响应体:${JSON.stringify(data, null, 2)}`); - } - } catch (e) { - logger.error(`在调用模型${this.name}中出错:`, e.message); - return { content: '', tool_calls: [] }; - } - } -} - -export class ImageModel extends BaseModel { - use: ImageModelUse[]; - constructor(use: ImageModelUse[], name: string, provider: string, base_url: string, api_key: string, body: ModelBody) { - super(name, provider, base_url, api_key, body); - this.use = use; - } - - get url() { - return `${this.baseUrl}/chat/completions`; - } - - async callITT(src: string, prompt = ''): Promise { - const { TIMEOUT } = Config.base; - try { - const time = Date.now(); - - const data = await withTimeout(() => fetchData(this.url, this.apiKey, this.buildBody({ - ...DEFAULT_IMAGE_MODEL_BODY, - messages: [{ - role: "user", - content: [{ - "type": "image_url", - "image_url": { "url": src } - }, { - "type": "text", - "text": prompt - }] - }] - })), TIMEOUT); - - if (data.choices && data.choices.length > 0) { - UsageManager.updateUsage(data.model, data.usage); - - const message = data.choices[0].message; - const content = message.content || ''; - - logger.info(`响应内容:`, content, '\nlatency', Date.now() - time, 'ms'); - - return content; - } else { - throw new Error(`服务器响应中没有choices或choices为空\n响应体:${JSON.stringify(data, null, 2)}`); - } - } catch (e) { - logger.error(`在调用模型${this.name}中出错:`, e.message); - return ''; - } - } - - async callChat(agent: Agent, sessionId: string): Promise<{ content: string, tool_calls: ToolCall[] }> { - const { TIMEOUT } = Config.base; - try { - const time = Date.now(); - - const data = await withTimeout(() => fetchData(this.url, this.apiKey, this.buildBody({ - ...DEFAULT_IMAGE_MODEL_BODY, - messages: agent.sessionService.getSession(sessionId).getImageMessages(), - tools: agent.getRequestTools() - })), TIMEOUT); - - if (data.choices && data.choices.length > 0) { - UsageManager.updateUsage(data.model, data.usage); - - const message = data.choices[0].message; - const finish_reason = data.choices[0].finish_reason; - - if (message.hasOwnProperty('reasoning_content')) { - logger.info(`思维链内容:`, message.reasoning_content); - } - - const content = message.content || ''; - - logger.info(`响应内容:`, content, '\nlatency:', Date.now() - time, 'ms', '\nfinish_reason:', finish_reason); - - return { content, tool_calls: message.tool_calls || [] }; - } else { - throw new Error(`服务器响应中没有choices或choices为空\n响应体:${JSON.stringify(data, null, 2)}`); - } - } catch (e) { - logger.error(`在调用模型${this.name}中出错:`, e.message); - return { content: '', tool_calls: [] }; - } - } -} - -export class EmbeddingModel extends BaseModel { - static vectorCache: { text: string, vector: number[] } = { text: '', vector: [] }; - - use: EmbeddingModelUse[]; - constructor(use: EmbeddingModelUse[], name: string, provider: string, base_url: string, api_key: string, body: ModelBody) { - super(name, provider, base_url, api_key, body); - this.use = use; - } - - get url() { - return `${this.baseUrl}/embeddings`; - } - - async callEmbedding(text: string): Promise { - if (!text) { - logger.warning(`getEmbedding: 文本为空`); - return []; - } - - const { TIMEOUT } = Config.base; - - if (EmbeddingModel.vectorCache.text === text && EmbeddingModel.vectorCache.vector.length === this.body.dimensions) { - const v = EmbeddingModel.vectorCache.vector; - return v; - } - - try { - const time = Date.now(); - - const data = await withTimeout(() => fetchData(this.url, this.apiKey, this.buildBody({ - ...DEFAULT_EMBEDDING_MODEL_BODY, - input: text - })), TIMEOUT); - - if (data.data && data.data.length > 0) { - UsageManager.updateUsage(data.model, data.usage); - - const embedding = data.data[0].embedding; - - logger.info(`文本:`, text, `\n响应embedding长度:`, embedding.length, '\nlatency:', Date.now() - time, 'ms'); - EmbeddingModel.vectorCache.text = text; - EmbeddingModel.vectorCache.vector = embedding; - - return embedding; - } else { - throw new Error(`服务器响应中没有data或data为空\n响应体:${JSON.stringify(data, null, 2)}`); - } - } catch (e) { - logger.error(`在调用模型${this.name}中出错:`, e.message); - return []; - } - - } -} - -export default class Model { - static chatModels: ChatModel[] = []; - static imageModels: ImageModel[] = []; - static embeddingModels: EmbeddingModel[] = []; - - static getChatModel(use: ChatModelUse): ChatModel | ImageModel | null { - const chatModelList = Model.chatModels.filter(model => model.use.includes(use)); - if (chatModelList.length > 0) { - const randomIndex = Math.floor(Math.random() * chatModelList.length); - return chatModelList[randomIndex]; - } - const ImageModelList = Model.imageModels.filter(model => model.use.includes(use)); - if (ImageModelList.length > 0) { - const randomIndex = Math.floor(Math.random() * ImageModelList.length); - return ImageModelList[randomIndex]; - } - const chatModelAnyList = Model.chatModels.filter(model => model.use.length === 0); - if (chatModelAnyList.length > 0) { - const randomIndex = Math.floor(Math.random() * chatModelAnyList.length); - return chatModelAnyList[randomIndex]; - } - const ImageModelAnyList = Model.imageModels.filter(model => model.use.length === 0); - if (ImageModelAnyList.length > 0) { - const randomIndex = Math.floor(Math.random() * ImageModelAnyList.length); - return ImageModelAnyList[randomIndex]; - } - return null; - } - - static getImageModel(use: ImageModelUse): ImageModel | null { - const ImageModelList = Model.imageModels.filter(model => model.use.includes(use)); - if (ImageModelList.length > 0) { - const randomIndex = Math.floor(Math.random() * ImageModelList.length); - return ImageModelList[randomIndex]; - } - const ImageModelAnyList = Model.imageModels.filter(model => model.use.length === 0); - if (ImageModelAnyList.length > 0) { - const randomIndex = Math.floor(Math.random() * ImageModelAnyList.length); - return ImageModelAnyList[randomIndex]; - } - return null; - } - - static getEmbeddingModel(use: EmbeddingModelUse): EmbeddingModel | null { - const EmbeddingModelList = Model.embeddingModels.filter(model => model.use.includes(use)); - if (EmbeddingModelList.length > 0) { - const randomIndex = Math.floor(Math.random() * EmbeddingModelList.length); - return EmbeddingModelList[randomIndex]; - } - const EmbeddingModelAnyList = Model.embeddingModels.filter(model => model.use.length === 0); - if (EmbeddingModelAnyList.length > 0) { - const randomIndex = Math.floor(Math.random() * EmbeddingModelAnyList.length); - return EmbeddingModelAnyList[randomIndex]; - } - return null; - } -} - -export async function fetchData(url: string, apiKey: string, body: any): Promise { - // 打印请求发送前的上下文 - if (body.hasOwnProperty('messages')) { - const s = JSON.stringify(body.messages, (key, value) => { - if (key === "" && Array.isArray(value)) { - return value.filter(item => item.role !== "system"); - } - return value; - }); - logger.info(`请求发送前的上下文:\n`, s); - } - - const response = await fetch(url, { - method: 'POST', - headers: { - "Authorization": `Bearer ${apiKey}`, - "Content-Type": "application/json", - "Accept": "application/json" - }, - body: JSON.stringify(body) - }); - - // logger.info("响应体", JSON.stringify(response, null, 2)); - - const text = await response.text(); - if (!response.ok) { - throw new Error(`请求失败! 状态码: ${response.status}\n响应体:${text}`); - } - if (!text) { - throw new Error("响应体为空"); - } - - try { - const data = JSON.parse(text); - if (data.error) { - throw new Error(`请求失败! 错误信息: ${data.error.message}`); - } - return data; - } catch (e) { - throw new Error(`解析响应体时出错:${e.message}\n响应体:${text}`); - } -} \ No newline at end of file diff --git a/src/agent/stream.ts b/src/agent/stream.ts index 5b9afe4..68b4152 100644 --- a/src/agent/stream.ts +++ b/src/agent/stream.ts @@ -1,4 +1,4 @@ -import { Config } from "../config/config"; +import Config from "../config/config"; import { logger } from "../logger"; import { withTimeout } from "../utils/utils"; import { Agent } from "./agent"; diff --git a/src/cmd/sub_cmd/image.ts b/src/cmd/sub_cmd/image.ts index b985b81..d4d83f3 100644 --- a/src/cmd/sub_cmd/image.ts +++ b/src/cmd/sub_cmd/image.ts @@ -1,5 +1,5 @@ import { AIManager } from "../../AI/AI"; -import { ImageService } from "../../image/image"; +import { ImageService } from "../../image"; import { aliasToCmd } from "../../utils/utils"; import { transformArrayToContent, transformTextToArray } from "../../utils/string"; import { I, M, U } from "../privilege"; diff --git a/src/config/config.ts b/src/config/config.ts index 90e21c7..52bc6e8 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -31,12 +31,12 @@ interface ConfigCache { data: ConfigProp } -class Config { +class _Config { static ext: seal.ExtInfo; static cache: { [K in ConfigKey]?: ConfigCache } = {} static registerConfig() { - this.ext = Config.getExt(NAME); + this.ext = this.getExt(NAME); for (const k of Object.keys(configMap) as ConfigKey[]) { configMap[k].register(); Object.defineProperty(this, k, { @@ -59,8 +59,8 @@ class Config { } static getExt(name: string): seal.ExtInfo { - if (name == NAME && Config.ext) { - return Config.ext; + if (name == NAME && this.ext) { + return this.ext; } let ext = seal.ext.find(name); @@ -73,8 +73,8 @@ class Config { } } -const _Config = Config as typeof Config & ConfigProps; -export { _Config as Config }; +const Config = _Config as typeof _Config & ConfigProps; +export default Config; export function getRegexConfig(ext: seal.ExtInfo, key: string): RegExp { const patterns = seal.ext.getTemplateConfig(ext, key).filter(x => x); diff --git a/src/config/configs/memory.ts b/src/config/configs/memory.ts index 2ca125c..8ae7b92 100644 --- a/src/config/configs/memory.ts +++ b/src/config/configs/memory.ts @@ -1,6 +1,6 @@ -import { MemoryItem } from "../../memory/memory"; +import MemoryItem from "../../memory/memory_item"; import { revive, TypeDescriptor } from "../../utils/utils"; -import { Config, getHandlebarsTemplateConfig } from "../config"; +import Config, { getHandlebarsTemplateConfig } from "../config"; import { load } from 'js-toml' export default class MemoryConfig { @@ -63,7 +63,7 @@ content = "单行形式,只有content字段是必须的"` {{#each memories}} {{index @index}}. ID:{{id}} 重要性:{{importance}} - 创建时间:{{{createAt}}} + 创建时间:{{{time createAt}}} {{#each tags}}{{#if @first}}标签:{{/if}}{{{this}}}{{#unless @last}}, {{/unless}}{{/each}} {{#each relatedMemories}}{{#if @first}}相关记忆:{{/if}}{{{this}}}{{#unless @last}}, {{/unless}}{{/each}} {{#each users}}{{#if @first}}相关用户:{{/if}}{{{this}}}{{#unless @last}}, {{/unless}}{{/each}} diff --git a/src/config/configs/model.ts b/src/config/configs/model.ts index d6e71a8..0e22dc7 100644 --- a/src/config/configs/model.ts +++ b/src/config/configs/model.ts @@ -1,7 +1,7 @@ import { ChatModel, EmbeddingModel, ImageModel } from "../../agent/model"; -import { ModelBody } from "../../agent/types"; +import { ModelBody } from "../../model/types"; import { logger } from "../../logger"; -import { Config } from "../config"; +import Config from "../config"; import { CHAT_MODEL_TO_PROVIDER, EMBEDDING_MODEL_TO_PROVIDER, IMAGE_MODEL_TO_PROVIDER, PROVIDER_MAP } from "../static_config"; export default class ModelConfig { diff --git a/src/image/image.ts b/src/image.ts similarity index 97% rename from src/image/image.ts rename to src/image.ts index 5a9491f..9f070e2 100644 --- a/src/image/image.ts +++ b/src/image.ts @@ -1,8 +1,8 @@ -import { Config } from "../config/config"; -import { generateId, revive, TypeDescriptor } from "../utils/utils"; -import { logger } from "../logger"; -import { MessageSegment, parseSpecialTokens } from "../utils/string"; -import { getSessionId } from "../utils/seal"; +import { Config } from "./config/config"; +import { generateId, revive, TypeDescriptor } from "./utils/utils"; +import { logger } from "./logger"; +import { MessageSegment, parseSpecialTokens } from "./utils/string"; +import { getSessionId } from "./utils/seal"; import { ModelManager } from "../agent/model"; export default class Image { diff --git a/src/index.ts b/src/index.ts index aa69305..3064b66 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,7 +3,7 @@ import { ToolService } from "./tool/tool"; import { Config } from "./config/config"; import { triggerConditionMap } from "./tool/tool_trigger"; import { logger } from "./logger"; -import { transformTextToArray } from "./utils/string"; +import { fmtDate, transformTextToArray } from "./utils/string"; import { checkUpdate } from "./utils/update"; import { TimerManager } from "./timer"; import { createMsg } from "./utils/seal"; @@ -15,6 +15,7 @@ import { registerCmd } from "./cmd/root_cmd"; function main() { Handlebars.registerHelper('index', (index: number) => index + 1); Handlebars.registerHelper('json_stringify', (obj: any) => JSON.stringify(obj, null, 2)); + Handlebars.registerHelper('time', (t: number) => fmtDate(t)); Config.registerConfig(); checkUpdate(); diff --git a/src/logger.ts b/src/logger.ts index 10861ef..a1f21ce 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -1,13 +1,8 @@ import { NAME } from "./config/static_config"; -import { Config } from "./config/config"; +import Config from "./config/config"; -class Logger { - name: string; - constructor(name: string) { - this.name = name; - } - - handleLog(...data: any[]): string { +export default class Logger { + static handleLog(...data: any[]): string { const { LOG_LEVEL: logLevel } = Config.base; if (logLevel === "永不") { return ''; @@ -25,39 +20,45 @@ class Logger { } } - info(...data: any[]) { + static info(...data: any[]) { const s = this.handleLog(...data); if (!s) { return; } - console.log(`【${this.name}】: ${s}`); + console.log(`【${NAME}】: ${s}`); } - warning(...data: any[]) { + static warning(...data: any[]) { const s = this.handleLog(...data); if (!s) { return; } - console.warn(`【${this.name}】: ${s}`); + console.warn(`【${NAME}】: ${s}`); } - error(...data: any[]) { + static error(...data: any[]) { const s = this.handleLog(...data); if (!s) { return; } - console.error(`【${this.name}】: ${s}`); + console.error(`【${NAME}】: ${s}`); } - debug(...data: any[]) { + static debug(...data: any[]) { const { LOG_LEVEL: logLevel } = Config.base; if (logLevel !== "调试") return; const s = this.handleLog(...data); if (!s) { return; } - console.info(`【${this.name}】: ${s}`); + console.info(`【${NAME}】: ${s}`); } -} -export const logger = new Logger(NAME); \ No newline at end of file + static logMessages(body: any) { + if (body.hasOwnProperty('messages')) { + const messages = body.messages.filter(item => item.role !== "system"); + if (messages.length === 0) return; + this.info(`请求发送前的上下文:\n`, JSON.stringify(messages)); + } + } +} \ No newline at end of file diff --git a/src/memory/image.ts b/src/memory/image.ts deleted file mode 100644 index 0391c7c..0000000 --- a/src/memory/image.ts +++ /dev/null @@ -1,5 +0,0 @@ -export default class ImageMemoryService extends MemoryService { - constructor() { - super(); - } -} \ No newline at end of file diff --git a/src/memory/knowledge.ts b/src/memory/knowledge.ts new file mode 100644 index 0000000..05c9046 --- /dev/null +++ b/src/memory/knowledge.ts @@ -0,0 +1,85 @@ +import Logger from "../logger"; +import Config from "../config/config"; +import { revive, TypeDescriptor } from "../utils/utils"; +import MemoryService from "./memory"; +import MemoryItem from "./memory_item"; +import Agent from "../agent/agent"; + +export default class KnowledgeService extends MemoryService { + static validKeysMap: { [key in keyof KnowledgeService]?: TypeDescriptor } = { + memoryMap: { array: MemoryItem }, + role: 'string' + }; + role: string; + + constructor() { + super(); + this.role = '*'; + } + + async initKnowledges() { + const { KNOWLEDGE_MEMORIES_MAP } = Config.memory; + const knowledges = KNOWLEDGE_MEMORIES_MAP[this.role] || []; + await Promise.all(knowledges.map(async m => m.updateVector())); + this.memoryMap = knowledges.reduce((map, m) => { + if (this.memoryMap.hasOwnProperty(m.id)) { + m.lastAccessedAt = Math.max(m.lastAccessedAt, this.memoryMap[m.id].lastAccessedAt); + m.accessCount = Math.max(m.accessCount, this.memoryMap[m.id].accessCount); + } + map[m.id] = m; + return map; + }, {} as { [id: string]: MemoryItem }); + KnowledgeService.save(this); + } + + async accessMemories(s: string) { + const now = Math.floor(Date.now() / 1000); + (await this.search(s, { + topK: 5, + tags: [], + relatedMemories: [], + users: [], + groups: [], + method: 'similarity' + })).forEach(m => { + m.lastAccessedAt = now; + m.accessCount++; + }) + KnowledgeService.save(this); + } + + buildKnowledgePrompt(sessionId: string, text: string): string { + if (this.memories.length === 0) return ''; + const agent = Agent.get(this.role); + const session = agent.sessionService.getSession(sessionId); + const users = session.sessionType === 'group' ? session.context.users : [session.sessionId]; + const groups = session.sessionType === 'group' ? [session.sessionId] : []; + const { KNOWLEDGE, KNOWLEDGE_TEMPLATE } = Config.memory; + return KNOWLEDGE_TEMPLATE({ + "KNOWLEDGE": KNOWLEDGE, + "memories": this.getTopScoreMemories(text, users, groups) + }); + } + + static knowledgeServiceMap: { [role: string]: KnowledgeService } = {}; + + static async get(role: string) { + if (!this.knowledgeServiceMap.hasOwnProperty(role)) { + let knowledgeService = new KnowledgeService(); + try { + const data = JSON.parse(Config.ext.storageGet(`knowledge_${role}`) || '{}'); + knowledgeService = revive(KnowledgeService, data); + } catch (error) { + Logger.error(`加载知识库${role}失败: ${error}`); + } + knowledgeService.role = role; + await knowledgeService.initKnowledges(); + this.knowledgeServiceMap[role] = knowledgeService; + } + return this.knowledgeServiceMap[role]; + } + + static save(knowledgeService: KnowledgeService) { + Config.ext.storageSet(`knowledge_${knowledgeService.role}`, JSON.stringify(knowledgeService)); + } +} \ No newline at end of file diff --git a/src/memory/knowlege.ts b/src/memory/knowlege.ts deleted file mode 100644 index f20f78f..0000000 --- a/src/memory/knowlege.ts +++ /dev/null @@ -1,163 +0,0 @@ -import MemoryService from "./memory"; - -export default class KnowledgeService extends MemoryService { - constructor() { - super(); - } - - init() { - const data = JSON.parse(Config.ext.storageGet('knowledge') || '{}'); - const ms = revive(MemoryService, data); - this.memoryMap = ms.memoryMap; - } - - save() { - Config.ext.storageSet('knowledge', JSON.stringify(this.memoryMap)); - } - - // wip 和配置一起改 - async updateKnowledgeMemory(roleIndex: number) { - const { knowledgeMemoryStringList } = Config.memory; - if (roleIndex < 0 || roleIndex >= knowledgeMemoryStringList.length) return; - const s = knowledgeMemoryStringList[roleIndex]; - if (!s) return; - - const memoryMap: { [id: string]: MemoryItem } = {} - const segs = s.split(/\n-{3,}\n/); - for (const seg of segs) { - if (!seg.trim()) continue; - - const lines = seg.split('\n'); - if (lines.length === 0) continue; - - const m = new MemoryItem(); - for (let i = 0; i < lines.length; i++) { - const match = lines[i].match(/^\s*?(ID|用户|群聊|关键词|图片|内容)\s*?[::](.*)/); - if (!match) { - continue; - } - const type = match[1]; - const value = match[2].trim(); - switch (type) { - case 'ID': { - m.id = value; - break; - } - case '用户': { - m.userList = value.split(/[,,]/).map(s => { - const segs = s.split(/[::]/).map(s => s.trim()).filter(s => s); - if (segs.length < 2) return null; - const name = value.replace(/[::].*$/, '').trim(); - const id = segs[segs.length - 1]; - if (!name || !id) return null; - return { isPrivate: true, id, name }; - }).filter(ui => ui) as UserInfo[]; - break; - } - case '群聊': { - m.groupList = value.split(/[,,]/).map(s => { - const segs = s.split(/[::]/).map(s => s.trim()).filter(s => s); - if (segs.length < 2) return null; - const name = value.replace(/[::].*$/, '').trim(); - const id = segs[segs.length - 1]; - if (!name || !id) return null; - return { isPrivate: false, id, name }; - }).filter(ui => ui) as GroupInfo[]; - break; - } - case '关键词': { - m.tags = value.split(/[,,]/).map(kw => kw.trim()).filter(kw => kw); - break; - } - case '图片': { - const { localImagePathMap } = Config.image; - - m.images = value.split(/[,,]/).map(id => id.trim()).map(id => { - if (localImagePathMap.hasOwnProperty(id)) { - const image = new Image(); - image.file = localImagePathMap[id]; - return image; - } - logger.error(`图片${id}不存在`); - return null; - }).filter(img => img); - break; - } - case '内容': { - m.content = lines.slice(i).join('\n').trim().replace(/^内容[::]/, ''); - break; - } - default: continue; - } - } - - if (!m.id && !m.content) continue; - - memoryMap[m.id] = m; - } - - const now = Math.floor(Date.now() / 1000); - await Promise.all(Object.values(memoryMap).map(async m => { - if (this.memoryMap.hasOwnProperty(m.id)) { - const m2 = this.memoryMap[m.id]; - m.vector = m2.vector; - if (m2.content !== m.content) await m.updateVector(); - m.createAt = m2.createAt; - m.lastAccessedAt = m2.lastAccessedAt; - m.weight = m2.weight; - } else { - await m.updateVector(); - m.createAt = now; - m.lastAccessedAt = now; - m.weight = 5; - } - })) - - this.memoryMap = memoryMap; - this.save(); - } - - // wip - buildKnowledgeMemory(memoryList: MemoryItem[]) { - const { showNumber } = Config.message; - const { knowledgeMemorySingleShowTemplate } = Config.memory; - if (memoryList.length === 0) return ''; - - let prompt = ''; - if (memoryList.length === 0) { - prompt = '无'; - } else { - prompt = memoryList - .map((m, i) => { - return knowledgeMemorySingleShowTemplate({ - "序号": i + 1, - "记忆ID": m.id, - "用户列表": m.userList.map(u => u.name + (showNumber ? `(${u.id.replace(/^.+:/, '')})` : '')).join(';'), - "群聊列表": m.groupList.map(g => g.name + (showNumber ? `(${g.id.replace(/^.+:/, '')})` : '')).join(';'), - "关键词": m.tags.join(';'), - "记忆内容": m.content - }); - }).join('\n'); - } - - return prompt; - } - - // wip - async buildKnowledgeMemoryPrompt(roleIndex: number, text: string, ui: UserInfo, gi: GroupInfo): Promise { - await this.updateKnowledgeMemory(roleIndex); - if (this.memoryIds.length === 0) return ''; - - const { knowledgeMemoryShowNumber } = Config.memory; - const memoryList = await this.search(text, { - topK: knowledgeMemoryShowNumber, - userIdList: ui ? [ui] : [], - groupIdList: gi ? [gi] : [], - tags: [], - includeImages: false, - method: 'score' - }); - - return this.buildKnowledgeMemory(memoryList); - } -} \ No newline at end of file diff --git a/src/memory/memory.ts b/src/memory/memory.ts index 3c90f91..2491551 100644 --- a/src/memory/memory.ts +++ b/src/memory/memory.ts @@ -1,11 +1,11 @@ -import { Config } from "../config/config"; +import Config from "../config/config"; import { generateId, TypeDescriptor } from "../utils/utils"; -import { logger } from "../logger"; -import Model from "../agent/model"; +import Logger from "../logger"; +import Model from "../model/model"; import { MemorySource, searchOptions } from "./types"; import Agent from "../agent/agent"; import { Session } from "../session/session"; -import { MemoryItem } from "./memory_item"; +import MemoryItem from "./memory_item"; export default class MemoryService { static validKeysMap: { [key in keyof MemoryService]?: TypeDescriptor } = { @@ -35,7 +35,7 @@ export default class MemoryService { id = generateId(); a++; if (a > 1000) { - logger.error(`生成记忆id失败,已尝试1000次,放弃`); + Logger.error(`生成记忆id失败,已尝试1000次,放弃`); throw new Error(`生成记忆id失败,已尝试1000次,放弃`); } } @@ -48,7 +48,7 @@ export default class MemoryService { for (const m of memories) { for (const om of this.memories) { if (om.compareWith(m)) { - logger.info(`记忆已存在,id:${om.id},进行合并`); + Logger.info(`记忆已存在,id:${om.id},进行合并`); om.merge(m); om.accessCount++; om.lastAccessedAt = now; @@ -139,16 +139,16 @@ export default class MemoryService { const model = Model.getEmbeddingModel('text-embedding'); v = await model.callEmbedding(query); if (!v.length) { - logger.error('查询向量为空'); + Logger.error('查询向量为空'); return []; } if (v.length !== DIMENSION) { - logger.error(`查询向量维度不匹配。期望: ${DIMENSION}, 实际: ${v.length}`); + Logger.error(`查询向量维度不匹配。期望: ${DIMENSION}, 实际: ${v.length}`); return []; } await Promise.all(this.memories.map(async m => { if (m.vector.length !== DIMENSION) { - logger.info(`记忆向量维度不匹配,重新获取向量: ${m.id}`); + Logger.info(`记忆向量维度不匹配,重新获取向量: ${m.id}`); await m.updateVector(); } })) @@ -196,9 +196,9 @@ export default class MemoryService { // 知识库记忆权重更新 task.push(agent.sessionService.knowledge.accessMemories(s)); // 会话自身记忆权重更新 - task.push(session.memory.accessMemory(s)); + task.push(session.memory.accessMemories(s)); // 群内用户的记忆权重更新 - if (session.sessionType === 'group') task.push(...session.context.users.map(u => agent.sessionService.getSession(u).memory.accessMemory(s))); + if (session.sessionType === 'group') task.push(...session.context.users.map(u => agent.sessionService.getSession(u).memory.accessMemories(s))); await Promise.all(task); } @@ -213,6 +213,7 @@ export default class MemoryService { method: 'score' }); } + getLatestMemories(p: number = 1): MemoryItem[] { if (this.memories.length === 0) return []; if (p > Math.ceil(this.memories.length / 5)) p = Math.ceil(this.memories.length / 5); @@ -237,35 +238,6 @@ export default class MemoryService { }] return this.buildMemoriesPrompt(sources) + `\n当前页码: ${p}/${Math.ceil(this.memories.length / 5)}`; } - - async buildMemoryPrompt(session: Session, text: string): Promise { - // 获取users、groups - const users = session.sessionType === 'group' ? session.context.users : [session.sessionId]; - const groups = session.sessionType === 'group' ? [session.sessionId] : []; - const agent = Agent.get(session.agentName); - const sources: MemorySource[] = []; - // bot记忆 - sources.push({ - source: '核心记忆', - memories: await agent.sessionService.memory.getTopScoreMemories(text, users, groups) - }) - // 会话记忆 - sources.push({ - source: '会话记忆', - memories: await session.memory.getTopScoreMemories(text, users, groups) - }) - // 群内用户的记忆 - if (session.sessionType === 'group') { - for (const u of session.context.users) { - sources.push({ - source: `用户${u}记忆`, - memories: await agent.sessionService.getSession(u).memory.getTopScoreMemories(text, users, groups) - }) - } - } - - return this.buildMemoriesPrompt(sources); - } } // 可以通过维护一组索引来优化搜索性能。 diff --git a/src/memory/memory_item.ts b/src/memory/memory_item.ts index 9a46fac..85f3cc7 100644 --- a/src/memory/memory_item.ts +++ b/src/memory/memory_item.ts @@ -1,9 +1,9 @@ -import Model from "../agent/model"; -import { Config } from "../config/config"; -import { logger } from "../logger"; +import Model from "../model/model"; +import Config from "../config/config"; +import Logger from "../logger"; import { cosineSimilarity, getCommonItem, revive, TypeDescriptor } from "../utils/utils"; -export class MemoryItem { +export default class MemoryItem { static validKeysMap: { [key in keyof MemoryItem]?: TypeDescriptor } = { 'id': 'string', 'sessionId': 'string', @@ -136,11 +136,11 @@ export class MemoryItem { async updateVector() { const { DIMENSION } = Config.memory; - logger.info(`更新记忆向量: ${this.id}`); + Logger.info(`更新记忆向量: ${this.id}`); const model = Model.getEmbeddingModel('text-embedding'); const vector = await model.callEmbedding(this.content); - if (!vector.length) return logger.error('返回向量为空'); - if (vector.length !== DIMENSION) return logger.error(`向量维度不匹配。期望: ${DIMENSION}, 实际: ${vector.length}`); + if (!vector.length) return Logger.error('返回向量为空'); + if (vector.length !== DIMENSION) return Logger.error(`向量维度不匹配。期望: ${DIMENSION}, 实际: ${vector.length}`); this.vector = vector; } } \ No newline at end of file diff --git a/src/memory/session_memory.ts b/src/memory/session_memory.ts index 965b312..a63624b 100644 --- a/src/memory/session_memory.ts +++ b/src/memory/session_memory.ts @@ -1,24 +1,58 @@ -import { Config } from "../config/config"; -import { logger } from "../logger"; +import Agent from "../agent/agent"; +import Config from "../config/config"; +import Logger from "../logger"; import { TypeDescriptor } from "../utils/utils"; import MemoryService from "./memory"; -import { MemoryItem } from "./memory_item"; +import MemoryItem from "./memory_item"; +import { MemorySource } from "./types"; export default class SessionMemoryService extends MemoryService { static validKeysMap: { [key in keyof SessionMemoryService]?: TypeDescriptor } = { memoryMap: { array: MemoryItem }, + sessionId: 'string', summaryStatus: 'boolean', summaries: { array: 'string' } }; + sessionId: string; summaryStatus: boolean; summaries: string[]; constructor() { super(); + this.sessionId = ''; this.summaryStatus = false; this.summaries = []; } + async buildMemoryPrompt(agent: Agent, text: string): Promise { + // 获取users、groups + const session = agent.sessionService.getSession(this.sessionId); + const users = session.sessionType === 'group' ? session.context.users : [session.sessionId]; + const groups = session.sessionType === 'group' ? [session.sessionId] : []; + const sources: MemorySource[] = []; + // bot记忆 + sources.push({ + source: '核心记忆', + memories: await agent.sessionService.memory.getTopScoreMemories(text, users, groups) + }) + // 会话记忆 + sources.push({ + source: '会话记忆', + memories: await session.memory.getTopScoreMemories(text, users, groups) + }) + // 群内用户的记忆 + if (session.sessionType === 'group') { + for (const u of session.context.users) { + sources.push({ + source: `用户${u}记忆`, + memories: await agent.sessionService.getSession(u).memory.getTopScoreMemories(text, users, groups) + }) + } + } + + return this.buildMemoriesPrompt(sources); + } + // wip 使用总结智能体 async summarize(session: Session) { if (!this.summaryStatus) return; @@ -75,7 +109,7 @@ export default class SessionMemoryService extends MemoryService { }).join('\n') : JSON.stringify(sumMessages) }) - logger.info(`记忆总结prompt:\n`, prompt); + Logger.info(`记忆总结prompt:\n`, prompt); const messages = [ { @@ -95,11 +129,11 @@ export default class SessionMemoryService extends MemoryService { const finish_reason = data.choices[0].finish_reason; if (message.hasOwnProperty('reasoning_content')) { - logger.info(`思维链内容:`, message.reasoning_content); + Logger.info(`思维链内容:`, message.reasoning_content); } const reply = message.content || ''; - logger.info(`响应内容:`, reply, '\nlatency:', Date.now() - time, 'ms', '\nfinish_reason:', finish_reason); + Logger.info(`响应内容:`, reply, '\nlatency:', Date.now() - time, 'ms', '\nfinish_reason:', finish_reason); const memoryData = JSON.parse(reply) as { content: string, @@ -122,7 +156,7 @@ export default class SessionMemoryService extends MemoryService { }); } } catch (e) { - logger.error(`更新短期记忆失败: ${e.message}`); + Logger.error(`更新短期记忆失败: ${e.message}`); } } @@ -134,4 +168,13 @@ export default class SessionMemoryService extends MemoryService { clearSummaries() { this.summaries = []; } + + buildSummaryPrompt(): string { + if (this.summaries.length === 0) return ''; + const { SUMMARY, SUMMARY_TEMPLATE } = Config.memory; + return SUMMARY_TEMPLATE({ + "SUMMARY": SUMMARY, + "summaries": this.summaries + }); + } } \ No newline at end of file diff --git a/src/memory/types.ts b/src/memory/types.ts index faae796..f0b5265 100644 --- a/src/memory/types.ts +++ b/src/memory/types.ts @@ -1,4 +1,4 @@ -import { MemoryItem } from "./memory"; +import { MemoryItem } from "./memory_item"; export interface searchOptions { topK: number; diff --git a/src/model/chat.ts b/src/model/chat.ts new file mode 100644 index 0000000..fa76ea8 --- /dev/null +++ b/src/model/chat.ts @@ -0,0 +1,58 @@ +import Agent from "../agent/agent"; +import { UsageManager } from "../agent/usage"; +import { Config } from "../config/config"; +import { DEFAULT_CHAT_MODEL_BODY } from "../config/static_config"; +import { logger } from "../logger"; +import { ToolCall } from "../tool/types"; +import { withTimeout } from "../utils/utils"; +import { fetchData } from "../utils/web"; +import { BaseModel } from "./model"; +import { ChatModelUse, ModelBody } from "./types"; + +export default class ChatModel extends BaseModel { + use: ChatModelUse[]; + constructor(use: ChatModelUse[], name: string, provider: string, base_url: string, api_key: string, body: ModelBody) { + super(name, provider, base_url, api_key, body); + this.use = use; + } + + get url() { + return `${this.baseUrl}/chat/completions`; + } + + async callChat(agent: Agent, sessionId: string): Promise<{ content: string, tool_calls: ToolCall[] }> { + const { TIMEOUT } = Config.base; + try { + const body = this.buildBody({ + ...DEFAULT_CHAT_MODEL_BODY, + messages: agent.sessionService.getSession(sessionId).getMessages(), + tools: agent.getRequestTools() + }); + logger.logMessages(body) + + const time = Date.now(); + const data = await withTimeout(() => fetchData(this.url, this.apiKey, body), TIMEOUT); + if (data.choices && data.choices.length > 0) { + UsageManager.updateUsage(data.model, data.usage); + + const message = data.choices[0].message; + const finish_reason = data.choices[0].finish_reason; + + if (message.hasOwnProperty('reasoning_content')) { + logger.info(`思维链内容:`, message.reasoning_content); + } + + const content = message.content || ''; + + logger.info(`响应内容:`, content, '\nlatency:', Date.now() - time, 'ms', '\nfinish_reason:', finish_reason); + + return { content, tool_calls: message.tool_calls || [] }; + } else { + throw new Error(`服务器响应中没有choices或choices为空\n响应体:${JSON.stringify(data, null, 2)}`); + } + } catch (e) { + logger.error(`在调用模型${this.name}中出错:`, e.message); + return { content: '', tool_calls: [] }; + } + } +} \ No newline at end of file diff --git a/src/model/embedding.ts b/src/model/embedding.ts new file mode 100644 index 0000000..713d37c --- /dev/null +++ b/src/model/embedding.ts @@ -0,0 +1,63 @@ +import { UsageManager } from "../agent/usage"; +import { Config } from "../config/config"; +import { DEFAULT_EMBEDDING_MODEL_BODY } from "../config/static_config"; +import { logger } from "../logger"; +import { withTimeout } from "../utils/utils"; +import { fetchData } from "../utils/web"; +import { BaseModel } from "./model"; +import { EmbeddingModelUse, ModelBody } from "./types"; + +export default class EmbeddingModel extends BaseModel { + static vectorCache: { text: string, vector: number[] } = { text: '', vector: [] }; + + use: EmbeddingModelUse[]; + constructor(use: EmbeddingModelUse[], name: string, provider: string, base_url: string, api_key: string, body: ModelBody) { + super(name, provider, base_url, api_key, body); + this.use = use; + } + + get url() { + return `${this.baseUrl}/embeddings`; + } + + async callEmbedding(text: string): Promise { + if (!text) { + logger.warning(`getEmbedding: 文本为空`); + return []; + } + + const { TIMEOUT } = Config.base; + + if (EmbeddingModel.vectorCache.text === text && EmbeddingModel.vectorCache.vector.length === this.body.dimensions) { + const v = EmbeddingModel.vectorCache.vector; + return v; + } + + try { + const body = this.buildBody({ + ...DEFAULT_EMBEDDING_MODEL_BODY, + input: text + }); + + const time = Date.now(); + const data = await withTimeout(() => fetchData(this.url, this.apiKey, body), TIMEOUT); + if (data.data && data.data.length > 0) { + UsageManager.updateUsage(data.model, data.usage); + + const embedding = data.data[0].embedding; + + logger.info(`文本:`, text, `\n响应embedding长度:`, embedding.length, '\nlatency:', Date.now() - time, 'ms'); + EmbeddingModel.vectorCache.text = text; + EmbeddingModel.vectorCache.vector = embedding; + + return embedding; + } else { + throw new Error(`服务器响应中没有data或data为空\n响应体:${JSON.stringify(data, null, 2)}`); + } + } catch (e) { + logger.error(`在调用模型${this.name}中出错:`, e.message); + return []; + } + + } +} \ No newline at end of file diff --git a/src/model/image.ts b/src/model/image.ts new file mode 100644 index 0000000..72e8531 --- /dev/null +++ b/src/model/image.ts @@ -0,0 +1,96 @@ +import Agent from "../agent/agent"; +import { UsageManager } from "../agent/usage"; +import Config from "../config/config"; +import { DEFAULT_IMAGE_MODEL_BODY } from "../config/static_config"; +import Logger from "../logger"; +import { ToolCall } from "../tool/types"; +import { withTimeout } from "../utils/utils"; +import { fetchData } from "../utils/web"; +import { BaseModel } from "./model"; +import { ImageModelUse, ModelBody } from "./types"; + +export default class ImageModel extends BaseModel { + use: ImageModelUse[]; + constructor(use: ImageModelUse[], name: string, provider: string, base_url: string, api_key: string, body: ModelBody) { + super(name, provider, base_url, api_key, body); + this.use = use; + } + + get url() { + return `${this.baseUrl}/chat/completions`; + } + + async callITT(src: string, prompt = ''): Promise { + const { TIMEOUT } = Config.base; + try { + const body = this.buildBody({ + ...DEFAULT_IMAGE_MODEL_BODY, + messages: [{ + role: "user", + content: [{ + "type": "image_url", + "image_url": { "url": src } + }, { + "type": "text", + "text": prompt + }] + }] + }); + Logger.logMessages(body); + + const time = Date.now(); + const data = await withTimeout(() => fetchData(this.url, this.apiKey, body), TIMEOUT); + if (data.choices && data.choices.length > 0) { + UsageManager.updateUsage(data.model, data.usage); + + const message = data.choices[0].message; + const content = message.content || ''; + + Logger.info(`响应内容:`, content, '\nlatency', Date.now() - time, 'ms'); + + return content; + } else { + throw new Error(`服务器响应中没有choices或choices为空\n响应体:${JSON.stringify(data, null, 2)}`); + } + } catch (e) { + Logger.error(`在调用模型${this.name}中出错:`, e.message); + return ''; + } + } + + async callChat(agent: Agent, sessionId: string): Promise<{ content: string, tool_calls: ToolCall[] }> { + const { TIMEOUT } = Config.base; + try { + const body = this.buildBody({ + ...DEFAULT_IMAGE_MODEL_BODY, + messages: agent.sessionService.getSession(sessionId).getImageMessages(), + tools: agent.getRequestTools() + }); + Logger.logMessages(body); + + const time = Date.now(); + const data = await withTimeout(() => fetchData(this.url, this.apiKey, body), TIMEOUT); + if (data.choices && data.choices.length > 0) { + UsageManager.updateUsage(data.model, data.usage); + + const message = data.choices[0].message; + const finish_reason = data.choices[0].finish_reason; + + if (message.hasOwnProperty('reasoning_content')) { + Logger.info(`思维链内容:`, message.reasoning_content); + } + + const content = message.content || ''; + + Logger.info(`响应内容:`, content, '\nlatency:', Date.now() - time, 'ms', '\nfinish_reason:', finish_reason); + + return { content, tool_calls: message.tool_calls || [] }; + } else { + throw new Error(`服务器响应中没有choices或choices为空\n响应体:${JSON.stringify(data, null, 2)}`); + } + } catch (e) { + Logger.error(`在调用模型${this.name}中出错:`, e.message); + return { content: '', tool_calls: [] }; + } + } +} \ No newline at end of file diff --git a/src/model/model.ts b/src/model/model.ts new file mode 100644 index 0000000..5e47c47 --- /dev/null +++ b/src/model/model.ts @@ -0,0 +1,86 @@ +import ChatModel from "./chat"; +import EmbeddingModel from "./embedding"; +import ImageModel from "./image"; +import { ChatModelUse, EmbeddingModelUse, ImageModelUse, ModelBody } from "./types"; + +export class BaseModel { + name: string; + provider: string; + baseUrl: string; + apiKey: string; + body: ModelBody; + + constructor(name: string, provider: string, base_url: string, api_key: string, body: ModelBody) { + this.name = name; + this.provider = provider; + this.baseUrl = base_url; + this.apiKey = api_key; + this.body = body; + } + + buildBody(args: { [key: string]: any }) { + const body = JSON.parse(JSON.stringify(this.body)); + for (const key in args) { + if (!args.hasOwnProperty(key)) body[key] = args[key]; + } + return body; + } +} + +export default class Model { + static chatModels: ChatModel[] = []; + static imageModels: ImageModel[] = []; + static embeddingModels: EmbeddingModel[] = []; + + static getChatModel(use: ChatModelUse): ChatModel | ImageModel | null { + const chatModelList = Model.chatModels.filter(model => model.use.includes(use)); + if (chatModelList.length > 0) { + const randomIndex = Math.floor(Math.random() * chatModelList.length); + return chatModelList[randomIndex]; + } + const ImageModelList = Model.imageModels.filter(model => model.use.includes(use)); + if (ImageModelList.length > 0) { + const randomIndex = Math.floor(Math.random() * ImageModelList.length); + return ImageModelList[randomIndex]; + } + const chatModelAnyList = Model.chatModels.filter(model => model.use.length === 0); + if (chatModelAnyList.length > 0) { + const randomIndex = Math.floor(Math.random() * chatModelAnyList.length); + return chatModelAnyList[randomIndex]; + } + const ImageModelAnyList = Model.imageModels.filter(model => model.use.length === 0); + if (ImageModelAnyList.length > 0) { + const randomIndex = Math.floor(Math.random() * ImageModelAnyList.length); + return ImageModelAnyList[randomIndex]; + } + return null; + } + + static getImageModel(use: ImageModelUse): ImageModel | null { + const ImageModelList = Model.imageModels.filter(model => model.use.includes(use)); + if (ImageModelList.length > 0) { + const randomIndex = Math.floor(Math.random() * ImageModelList.length); + return ImageModelList[randomIndex]; + } + const ImageModelAnyList = Model.imageModels.filter(model => model.use.length === 0); + if (ImageModelAnyList.length > 0) { + const randomIndex = Math.floor(Math.random() * ImageModelAnyList.length); + return ImageModelAnyList[randomIndex]; + } + return null; + } + + static getEmbeddingModel(use: EmbeddingModelUse): EmbeddingModel | null { + const EmbeddingModelList = Model.embeddingModels.filter(model => model.use.includes(use)); + if (EmbeddingModelList.length > 0) { + const randomIndex = Math.floor(Math.random() * EmbeddingModelList.length); + return EmbeddingModelList[randomIndex]; + } + const EmbeddingModelAnyList = Model.embeddingModels.filter(model => model.use.length === 0); + if (EmbeddingModelAnyList.length > 0) { + const randomIndex = Math.floor(Math.random() * EmbeddingModelAnyList.length); + return EmbeddingModelAnyList[randomIndex]; + } + return null; + } +} \ No newline at end of file diff --git a/src/agent/types.ts b/src/model/types.ts similarity index 80% rename from src/agent/types.ts rename to src/model/types.ts index 5249728..32505c3 100644 --- a/src/agent/types.ts +++ b/src/model/types.ts @@ -1,4 +1,4 @@ -export type ChatModelUse = 'chat' | 'compression'; +export type ChatModelUse = 'chat' | 'compression' | 'summarization'; export type ImageModelUse = 'image-understanding' | ChatModelUse; export type EmbeddingModelUse = 'text-embedding'; export interface ModelBody { diff --git a/src/session/context.ts b/src/session/context.ts index 5836286..1cf1d6d 100644 --- a/src/session/context.ts +++ b/src/session/context.ts @@ -1,5 +1,5 @@ import { Config } from "../config/config"; -import { Image, ImageService } from "../image/image"; +import { Image, ImageService } from "../image"; import { getCtxAndMsg } from "../utils/seal"; import { levenshteinDistance } from "../utils/string"; import { logger } from "../logger"; diff --git a/src/session/session.ts b/src/session/session.ts index 55f828d..f2ad847 100644 --- a/src/session/session.ts +++ b/src/session/session.ts @@ -8,6 +8,7 @@ import { Context } from "./context"; import { MemoryService } from "../memory/memory"; import { RequestMessage, SessionType, State } from "./types"; import KnowledgeService from "../memory/knowlege"; +import SessionMemoryService from "../memory/session_memory"; export class Session { static validKeysMap: { [key in keyof Session]?: TypeDescriptor } = { @@ -22,7 +23,7 @@ export class Session { objectValue: 'any' }, context: Context, - memory: MemoryService, + memory: SessionMemoryService, tool: { object: { state: { objectValue: 'boolean' } @@ -36,7 +37,7 @@ export class Session { sessionType: SessionType; state: State; context: Context; - memory: MemoryService; + memory: SessionMemoryService; tool: { state: ToolState, callCount: number, // 单次触发调用函数计数 diff --git a/src/utils/string.ts b/src/utils/string.ts index aaa3b69..868ce00 100644 --- a/src/utils/string.ts +++ b/src/utils/string.ts @@ -1,5 +1,5 @@ import { Context } from "../session/context"; -import { Image } from "../image/image"; +import { Image } from "../image"; import { logger } from "../logger"; import { Config } from "../config/config"; import { transformMsgId, transformMsgIdBack } from "./utils"; diff --git a/src/utils/web.ts b/src/utils/web.ts new file mode 100644 index 0000000..c9bd55d --- /dev/null +++ b/src/utils/web.ts @@ -0,0 +1,31 @@ +export async function fetchData(url: string, apiKey: string, body: any): Promise { + const response = await fetch(url, { + method: 'POST', + headers: { + "Authorization": `Bearer ${apiKey}`, + "Content-Type": "application/json", + "Accept": "application/json" + }, + body: JSON.stringify(body) + }); + + // logger.info("响应体", JSON.stringify(response, null, 2)); + + const text = await response.text(); + if (!response.ok) { + throw new Error(`请求失败! 状态码: ${response.status}\n响应体:${text}`); + } + if (!text) { + throw new Error("响应体为空"); + } + + try { + const data = JSON.parse(text); + if (data.error) { + throw new Error(`请求失败! 错误信息: ${data.error.message}`); + } + return data; + } catch (e) { + throw new Error(`解析响应体时出错:${e.message}\n响应体:${text}`); + } +} \ No newline at end of file From ac203a719ae6a0a83f0b6dd70bde5c978498b18f Mon Sep 17 00:00:00 2001 From: error2913 <2913949387@qq.com> Date: Sat, 14 Mar 2026 02:40:52 +0800 Subject: [PATCH 33/33] =?UTF-8?q?=E7=BA=A2=E7=BA=A2=E7=81=AB=E7=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/agent/agents/compress_agent.ts | 2 +- src/agent/agents/root_agent.ts | 9 --------- src/agent/agents/samples.ts | 2 +- src/agent/agents/summarize_agent.ts | 9 +++++++++ src/{agent => }/usage.ts | 0 5 files changed, 11 insertions(+), 11 deletions(-) delete mode 100644 src/agent/agents/root_agent.ts create mode 100644 src/agent/agents/summarize_agent.ts rename src/{agent => }/usage.ts (100%) diff --git a/src/agent/agents/compress_agent.ts b/src/agent/agents/compress_agent.ts index 4e65be2..8366413 100644 --- a/src/agent/agents/compress_agent.ts +++ b/src/agent/agents/compress_agent.ts @@ -6,4 +6,4 @@ compressAgent.description = "压缩智能体"; compressAgent.instruction = "你是一个压缩智能体,你可以压缩文本。"; compressAgent.use = "compression"; Agent.save(compressAgent); -export { compressAgent }; +export default compressAgent; diff --git a/src/agent/agents/root_agent.ts b/src/agent/agents/root_agent.ts deleted file mode 100644 index 2c47d8d..0000000 --- a/src/agent/agents/root_agent.ts +++ /dev/null @@ -1,9 +0,0 @@ -import Agent from "../agent"; - -const rootAgent = Agent.get("root_agent"); -rootAgent.name = "root_agent"; -rootAgent.description = "根智能体"; -rootAgent.instruction = "你是一个根智能体,你可以调用其他智能体。"; -rootAgent.use = "chat"; -Agent.save(rootAgent); -export { rootAgent }; \ No newline at end of file diff --git a/src/agent/agents/samples.ts b/src/agent/agents/samples.ts index 1aacff3..c4b5acb 100644 --- a/src/agent/agents/samples.ts +++ b/src/agent/agents/samples.ts @@ -6,4 +6,4 @@ sampleAgent.description = "示例智能体"; sampleAgent.instruction = "你是一个示例智能体。"; sampleAgent.use = "chat"; Agent.save(sampleAgent); -export { sampleAgent }; +export default sampleAgent; diff --git a/src/agent/agents/summarize_agent.ts b/src/agent/agents/summarize_agent.ts new file mode 100644 index 0000000..b9eedb6 --- /dev/null +++ b/src/agent/agents/summarize_agent.ts @@ -0,0 +1,9 @@ +import Agent from "../agent"; + +const summarizeAgent = Agent.get("summarize_agent"); +summarizeAgent.name = "summarize_agent"; +summarizeAgent.description = "摘要智能体"; +summarizeAgent.instruction = "你是一个摘要智能体,你可以摘要文本。"; +summarizeAgent.use = "summarization"; +Agent.save(summarizeAgent); +export default summarizeAgent; diff --git a/src/agent/usage.ts b/src/usage.ts similarity index 100% rename from src/agent/usage.ts rename to src/usage.ts