diff --git a/src/AI/AI.ts b/src/AI/AI.ts index 454b6c8..8666701 100644 --- a/src/AI/AI.ts +++ b/src/AI/AI.ts @@ -10,6 +10,20 @@ import { logger } from "../logger"; import { checkRepeat, handleReply, MessageSegment, transformTextToArray } from "../utils/utils_string"; import { TimerManager } from "../timer"; +export interface GroupInfo { + isPrivate: false; + id: string; + name: string; +} + +export interface UserInfo { + isPrivate: true; + id: string; + name: string; +} + +export type SessionInfo = GroupInfo | UserInfo; + export class Setting { static validKeys: (keyof Setting)[] = ['priv', 'standby', 'counter', 'timer', 'prob', 'activeTimeInfo']; priv: number; @@ -147,7 +161,7 @@ export class AI { const MaxRetry = 3; for (let retry = 1; retry <= MaxRetry; retry++) { // 处理messages - const messages = handleMessages(ctx, this); + const messages = await handleMessages(ctx, this); //获取处理后的回复 const raw_reply = await sendChatRequest(ctx, msg, this, messages, "auto"); @@ -195,7 +209,7 @@ export class AI { await this.stopCurrentChatStream(); - const messages = handleMessages(ctx, this); + const messages = await handleMessages(ctx, this); const id = await startStream(messages); if (id === '') { return; @@ -337,7 +351,7 @@ export class AI { } // 若不在活动时间范围内,返回-1 - getCurSegIndex(): number { + get curActiveTimeSegIndex(): number { const now = new Date(); const cur = now.getHours() * 60 + now.getMinutes(); const { start, end, segs } = this.setting.activeTimeInfo; @@ -378,7 +392,7 @@ export class AI { if (segs !== 0 && (start !== 0 || end !== 0)) { const timers = TimerManager.getTimers(this.id, '', ['activeTime']); if (timers.length === 0) { - const curSegIndex = this.getCurSegIndex(); + const curSegIndex = this.curActiveTimeSegIndex; const nextTimePoint = this.getNextTimePoint(curSegIndex); if (nextTimePoint !== -1) { TimerManager.addActiveTimeTimer(ctx, msg, this, nextTimePoint); @@ -390,17 +404,25 @@ export class AI { } } +export interface UsageInfo { + prompt_tokens: number, + completion_tokens: number +} + export class AIManager { - static version = "1.0.1"; static cache: { [key: string]: AI } = {}; - static usageMap: { - [key: string]: { // 模型名 - [key: number]: { // 年月日 - prompt_tokens: number, - completion_tokens: number + static usageMapCache: { [model: string]: { [time: number]: UsageInfo } } = null; + + static get usageMap(): { [model: string]: { [time: number]: UsageInfo } } { + if (!this.usageMapCache) { + try { + this.usageMapCache = JSON.parse(ConfigManager.ext.storageGet('usageMap') || '{}'); + } catch (error) { + logger.error(`从数据库中获取usageMap失败:`, error); } } - } = {}; + return this.usageMapCache; + } static clearCache() { this.cache = {}; @@ -458,7 +480,7 @@ export class AIManager { } static clearUsageMap() { - this.usageMap = {}; + this.usageMapCache = {}; } static clearExpiredUsage(model: string) { @@ -504,17 +526,8 @@ export class AIManager { } } - static getUsageMap() { - try { - const usage = JSON.parse(ConfigManager.ext.storageGet('usageMap') || '{}'); - this.usageMap = usage; - } catch (error) { - logger.error(`从数据库中获取usageMap失败:`, error); - } - } - static saveUsageMap() { - ConfigManager.ext.storageSet('usageMap', JSON.stringify(this.usageMap)); + ConfigManager.ext.storageSet('usageMap', JSON.stringify(this.usageMapCache)); } static updateUsage(model: string, usage: { diff --git a/src/AI/context.ts b/src/AI/context.ts index 6b3b368..2c94f53 100644 --- a/src/AI/context.ts +++ b/src/AI/context.ts @@ -3,16 +3,11 @@ import { ConfigManager } from "../config/config"; import { Image } from "./image"; import { createCtx, createMsg } from "../utils/utils_seal"; import { levenshteinDistance, MessageSegment } from "../utils/utils_string"; -import { AI, AIManager } from "./AI"; +import { AI, AIManager, UserInfo } from "./AI"; import { logger } from "../logger"; import { transformMsgId } from "../utils/utils"; import { getGroupMemberInfo, getStrangerInfo } from "../utils/utils_ob11"; -export interface UserNameInfo { // 用于上下文名字修改相关操作 - uid: string; - name: string; -} - export interface MessageInfo { msgId: string; time: number; // 秒 @@ -86,7 +81,7 @@ export class Context { } async addMessage(ctx: seal.MsgContext, msg: seal.Message, ai: AI, messageArray: MessageSegment[], images: Image[], role: 'user' | 'assistant', msgId: string = '') { - const { showNumber, showMsgId, maxRounds } = ConfigManager.message; + const { showNumber, showMsgId } = ConfigManager.message; const { isShortMemory, shortMemorySummaryRound } = ConfigManager.memory; const messages = this.messages; @@ -129,11 +124,11 @@ export class Context { return; } + const now = Math.floor(Date.now() / 1000); const uid = role == 'user' ? ctx.player.userId : ctx.endPoint.userId; - // 自动更新上下文里的名字 - const exists = messages.some(message => message.uid === uid); - if (!exists) { + // 自动更新上下文里的名字,发言时间一小时内不更新 + if (!messages.some(message => message.uid === uid && message.msgArray.some(msgInfo => msgInfo.time >= now - 3600))) { await this.updateName(ctx.endPoint.userId, ctx.group.groupId, uid); } @@ -167,7 +162,7 @@ export class Context { messages[length - 1].images.push(...images); messages[length - 1].msgArray.push({ msgId: msgId, - time: Math.floor(Date.now() / 1000), + time: now, content: s }); } else { @@ -178,7 +173,7 @@ export class Context { images: images, msgArray: [{ msgId: msgId, - time: Math.floor(Date.now() / 1000), + time: now, content: s }] }; @@ -197,10 +192,10 @@ export class Context { } //更新记忆权重 - ai.memory.updateMemoryWeight(ctx, ai.context, s, role); + ai.memory.updateRelatedMemoryWeight(ctx, ai.context, s, role); //删除多余的上下文 - this.limitMessages(maxRounds); + this.limitMessages(); } async addToolCallsMessage(tool_calls: ToolCall[]) { @@ -216,6 +211,7 @@ export class Context { } async addToolMessage(tool_call_id: string, s: string, images: Image[]) { + const now = Math.floor(Date.now() / 1000); const message: Message = { role: 'tool', tool_call_id: tool_call_id, @@ -224,7 +220,7 @@ export class Context { images: images, msgArray: [{ msgId: '', - time: Math.floor(Date.now() / 1000), + time: now, content: s }] }; @@ -240,6 +236,7 @@ export class Context { } async addSystemUserMessage(name: string, s: string, images: Image[]) { + const now = Math.floor(Date.now() / 1000); const message: Message = { role: 'user', uid: '', @@ -247,14 +244,15 @@ export class Context { images: images, msgArray: [{ msgId: '', - time: Math.floor(Date.now() / 1000), + time: now, content: s }] }; this.messages.push(message); } - limitMessages(maxRounds: number) { + limitMessages() { + const { maxRounds } = ConfigManager.message; const messages = this.messages; let round = 0; for (let i = messages.length - 1; i >= 0; i--) { @@ -382,17 +380,14 @@ export class Context { continue; } - const ai = AIManager.getAI(uid); - const memoryList = Object.values(ai.memory.memoryMap); - - for (const m of memoryList) { - if (m.group.groupName === groupName) { - return m.group.groupId; + for (const m of AIManager.getAI(uid).memory.memoryList) { + if (m.sessionInfo.isPrivate && m.sessionInfo.name === groupName) { + return m.sessionInfo.id; } - if (m.group.groupName.length > 4) { - const distance = levenshteinDistance(groupName, m.group.groupName); + if (m.sessionInfo.isPrivate && m.sessionInfo.name.length > 4) { + const distance = levenshteinDistance(groupName, m.sessionInfo.name); if (distance <= 2) { - return m.group.groupId; + return m.sessionInfo.id; } } } @@ -423,13 +418,14 @@ export class Context { return null; } - getUserNameInfo(): UserNameInfo[] { - const userMap: { [key: string]: UserNameInfo } = {}; + get userInfoList(): UserInfo[] { + const userMap: { [key: string]: UserInfo } = {}; this.messages.forEach(message => { if (message.role === 'user' && message.name && message.uid && !message.name.startsWith('_')) { userMap[message.uid] = { - name: message.name, - uid: message.uid, + isPrivate: true, + id: message.uid, + name: message.name }; } }); @@ -530,15 +526,10 @@ export class Context { const { localImagePaths } = ConfigManager.image; const localImages: { [key: string]: string } = localImagePaths.reduce((acc: { [key: string]: string }, path: string) => { - if (path.trim() === '') { - return acc; - } + if (path.trim() === '') return acc; try { const name = path.split('/').pop().replace(/\.[^/.]+$/, ''); - if (!name) { - throw new Error(`本地图片路径格式错误:${path}`); - } - + if (!name) throw new Error(`本地图片路径格式错误:${path}`); acc[name] = path; } catch (e) { logger.error(e); diff --git a/src/AI/image.ts b/src/AI/image.ts index 31a1ab4..1e72526 100644 --- a/src/AI/image.ts +++ b/src/AI/image.ts @@ -82,15 +82,10 @@ export class ImageManager { drawLocalImageFile(): string { const { localImagePaths } = ConfigManager.image; const localImages: { [key: string]: string } = localImagePaths.reduce((acc: { [key: string]: string }, path: string) => { - if (path.trim() === '') { - return acc; - } + if (path.trim() === '') return acc; try { const name = path.split('/').pop().replace(/\.[^/.]+$/, ''); - if (!name) { - throw new Error(`本地图片路径格式错误:${path}`); - } - + if (!name) throw new Error(`本地图片路径格式错误:${path}`); acc[name] = path; } catch (e) { logger.error(e); @@ -99,17 +94,13 @@ export class ImageManager { }, {}); const keys = Object.keys(localImages); - if (keys.length == 0) { - return ''; - } + if (keys.length == 0) return ''; const index = Math.floor(Math.random() * keys.length); return localImages[keys[index]]; } async drawStolenImageFile(): Promise { - if (this.stolenImages.length === 0) { - return ''; - } + if (this.stolenImages.length === 0) return ''; const index = Math.floor(Math.random() * this.stolenImages.length); const image = this.stolenImages.splice(index, 1)[0]; @@ -133,15 +124,10 @@ export class ImageManager { async drawImageFile(): Promise { const { localImagePaths } = ConfigManager.image; const localImages: { [key: string]: string } = localImagePaths.reduce((acc: { [key: string]: string }, path: string) => { - if (path.trim() === '') { - return acc; - } + if (path.trim() === '') return acc; try { const name = path.split('/').pop().replace(/\.[^/.]+$/, ''); - if (!name) { - throw new Error(`本地图片路径格式错误:${path}`); - } - + if (!name) throw new Error(`本地图片路径格式错误:${path}`); acc[name] = path; } catch (e) { logger.error(e); @@ -150,19 +136,13 @@ export class ImageManager { }, {}); const values = Object.values(localImages); - if (this.stolenImages.length == 0 && values.length == 0 && this.savedImages.length == 0) { - return ''; - } + if (this.stolenImages.length == 0 && values.length == 0 && this.savedImages.length == 0) return ''; const index = Math.floor(Math.random() * (values.length + this.stolenImages.length + this.savedImages.length)); - if (index < values.length) { - return values[index]; - } else if (index < values.length + this.stolenImages.length) { - return await this.drawStolenImageFile(); - } else { - return this.drawSavedImageFile(); - } + if (index < values.length) return values[index]; + else if (index < values.length + this.stolenImages.length) return await this.drawStolenImageFile(); + else return this.drawSavedImageFile(); } /** @@ -320,4 +300,35 @@ export class ImageManager { return { base64: '', format: '' }; } } + + static async extractExistingImages(ai: AI, s: string): Promise { + const images = []; + const match = s.match(/[<<][\|│|]img:.+?(?:[\|│|][>>]|[\|│|>>])/g); + if (match) { + for (let i = 0; i < match.length; i++) { + const id = match[i].match(/[<<][\|│|]img:(.+?)(?:[\|│|][>>]|[\|│|>>])/)[1]; + const image = ai.context.findImage(id, ai); + + if (image) { + if (!image.isUrl) { + if (image.base64) { + image.weight += 1; + } + images.push(image); + } else { + const { base64 } = await ImageManager.imageUrlToBase64(image.file); + if (!base64) { + logger.error(`图片${id}转换为base64失败`); + continue; + } + + image.isUrl = false; + image.base64 = base64; + images.push(image); + } + } + } + } + return images; + } } \ No newline at end of file diff --git a/src/AI/memory.ts b/src/AI/memory.ts index 036f18d..9b86fdd 100644 --- a/src/AI/memory.ts +++ b/src/AI/memory.ts @@ -1,69 +1,139 @@ import Handlebars from "handlebars"; import { ConfigManager } from "../config/config"; -import { AI, AIManager } from "./AI"; +import { AI, AIManager, GroupInfo, SessionInfo, UserInfo } from "./AI"; import { Context } from "./context"; -import { generateId, revive } from "../utils/utils"; +import { cosineSimilarity, generateId, getCommonGroup, getCommonKeyword, getCommonUser, revive } from "../utils/utils"; import { logger } from "../logger"; -import { fetchData } from "../service"; +import { fetchData, getEmbedding } from "../service"; import { buildContent, parseBody } from "../utils/utils_message"; import { ToolManager } from "../tool/tool"; import { fmtDate } from "../utils/utils_string"; import { Image, ImageManager } from "./image"; +export interface searchOptions { + topK: number; + userList: UserInfo[]; + groupList: GroupInfo[]; + keywords: string[]; + includeImages: boolean; + method: 'weight' | 'similarity' | 'score'; +} + export class Memory { - static validKeys: (keyof Memory)[] = ['id', 'isPrivate', 'player', 'group', 'createTime', 'lastMentionTime', 'keywords', 'weight', 'content', 'images']; + static validKeys: (keyof Memory)[] = ['id', 'vector', 'text', 'sessionInfo', 'userList', 'groupList', 'createTime', 'lastMentionTime', 'keywords', 'weight', 'images']; id: string; // 记忆ID - isPrivate: boolean; - player: { - userId: string; - name: string; - } - group: { - groupId: string; - groupName: string; - } + vector: number[]; // 记忆向量 + text: string; // 记忆内容 + sessionInfo: SessionInfo; + userList: UserInfo[]; + groupList: GroupInfo[]; createTime: number; // 秒级时间戳 lastMentionTime: number; keywords: string[]; weight: number; // 记忆权重,0-10 - content: string; images: Image[]; constructor() { this.id = ''; - this.isPrivate = true; - this.player = { - userId: '', - name: '' - }; - this.group = { - groupId: '', - groupName: '' + this.vector = []; + this.text = ''; + this.sessionInfo = { + id: '', + isPrivate: false, + name: '', }; + this.userList = []; + this.groupList = []; this.createTime = 0; this.lastMentionTime = 0; this.keywords = []; this.weight = 0; - this.content = ''; this.images = []; } - calcFgtWeight(now: number) { - const d = 24 * 60 * 60; - // 基础新鲜度衰减(按天计算) - const ageDecay = Math.log10((now - this.createTime) / d + 1); - // 活跃度衰减因子(最近接触按小时衰减) - const activityDecay = Math.max(1, (now - this.lastMentionTime) / 3600); - // 权重转换(0-10 → 1.0-3.0 指数曲线) - const importance = Math.pow(1.1161, this.weight); - return (ageDecay * activityDecay) / importance; + get copy(): Memory { + const m = new Memory(); + 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.createTime = this.createTime; + m.lastMentionTime = this.lastMentionTime; + m.keywords = [...this.keywords]; + m.weight = this.weight; + m.images = [...this.images]; + return m; + } + + /** + * 计算记忆的新鲜度衰减因子,越大表示越新鲜 + * @returns 衰减因子(1→0) + */ + 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); + } + + /** + * 计算记忆与查询的相似度分数 + * @param v 查询向量 + * @param ul 查询用户列表 + * @param gl 查询群组列表 + * @param kws 查询关键词列表 + * @returns 相似度分数(1-2) + */ + calculateSimilarity(v: number[], ul: UserInfo[], gl: GroupInfo[], 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; + // 群组相似度分数 0-1 + const commonGroup = getCommonGroup(this.groupList, gl); + const groupSimilarity = (gl && gl.length > 0) ? commonGroup.length / (this.groupList.length + gl.length - commonGroup.length) : 0; + // 关键词匹配分数 0-1 + const commonKeyword = getCommonKeyword(this.keywords, kws); + const keywordSimilarity = (kws && kws.length > 0) ? commonKeyword.length / kws.length : 0; + // 综合相似度分数 0-1 + const avgSimilarity = vectorSimilarity * 0.4 + userSimilarity * 0.2 + groupSimilarity * 0.2 + keywordSimilarity * 0.2; + // 相似度增强因子 1-2 + return 1 + avgSimilarity / totalWeight; + } + + async updateVector() { + const { isMemoryVector, embeddingDimension } = ConfigManager.memory; + if (isMemoryVector) { + logger.info(`更新记忆向量: ${this.id}`); + const vector = await getEmbedding(this.text); + if (!vector.length) { + logger.error('返回向量为空'); + return; + } + if (vector.length !== embeddingDimension) { + logger.error(`向量维度不匹配。期望: ${embeddingDimension}, 实际: ${vector.length}`); + return; + } + this.vector = vector; + } } } export class MemoryManager { static validKeys: (keyof MemoryManager)[] = ['persona', 'memoryMap', 'useShortMemory', 'shortMemoryList']; persona: string; - memoryMap: { [key: string]: Memory }; // key: 记忆ID + memoryMap: { [id: string]: Memory }; useShortMemory: boolean; shortMemoryList: string[]; @@ -77,10 +147,27 @@ export class MemoryManager { reviveMemoryMap() { for (const id in this.memoryMap) { this.memoryMap[id] = revive(Memory, this.memoryMap[id]); + if (!this.memoryMap[id].text) { + delete this.memoryMap[id]; + } } } - async addMemory(ctx: seal.MsgContext, ai: AI, kws: string[], content: string) { + get memoryIds() { + return Object.keys(this.memoryMap); + } + + get memoryList() { + return Object.values(this.memoryMap); + } + + get keywords() { + const keywords = new Set(); + this.memoryList.forEach(m => m.keywords.forEach(kw => keywords.add(kw))); + return Array.from(keywords); + } + + async addMemory(ctx: seal.MsgContext, ai: AI, ul: UserInfo[], gl: GroupInfo[], kws: string[], text: string) { let id = generateId(), a = 0; while (this.memoryMap.hasOwnProperty(id)) { id = generateId(); @@ -91,9 +178,9 @@ export class MemoryManager { } } - for (const id of Object.keys(this.memoryMap)) { + for (const id of this.memoryIds) { const m = this.memoryMap[id]; - if (content === m.content && ((!m.isPrivate && ctx.group.groupId === m.group.groupId) || m.isPrivate)) { + if (text === m.text && 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; @@ -103,99 +190,55 @@ export class MemoryManager { const now = Math.floor(Date.now() / 1000); const m = new Memory(); m.id = id; - m.isPrivate = ctx.isPrivate; - m.player = { - userId: ctx.player.userId, - name: ctx.player.name - }; - m.group = { - groupId: ctx.group.groupId, - groupName: ctx.group.groupName + m.text = 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.createTime = now; m.lastMentionTime = now; - m.keywords = kws || []; - m.weight = 0; - m.content = content || ''; - - const images = []; - const match = content.match(/[<<][\|│|]img:.+?(?:[\|│|][>>]|[\|│|>>])/g); - if (match) { - for (let i = 0; i < match.length; i++) { - const id = match[i].match(/[<<][\|│|]img:(.+?)(?:[\|│|][>>]|[\|│|>>])/)[1]; - const image = ai.context.findImage(id, ai); - - if (image) { - if (!image.isUrl) { - if (image.base64) { - image.weight += 1; - } - images.push(image); - } else { - const { base64 } = await ImageManager.imageUrlToBase64(image.file); - if (!base64) { - logger.error(`图片${id}转换为base64失败`); - continue; - } - - image.isUrl = false; - image.base64 = base64; - images.push(image); - } - } - } - } - m.images = images || []; - - this.memoryMap[id] = m; - + m.keywords = kws; + m.weight = 5; + m.images = await ImageManager.extractExistingImages(ai, text); + await m.updateVector(); this.limitMemory(); + this.memoryMap[id] = m; } - delMemory(idList: string[] = [], kws: string[] = []) { - if (idList.length === 0 && kws.length === 0) { - return; - } + deleteMemory(ids: string[] = [], kws: string[] = []) { + if (ids.length === 0 && kws.length === 0) return; - idList.forEach(id => { - delete this.memoryMap?.[id]; - }) + ids.forEach(id => delete this.memoryMap?.[id]) if (kws.length > 0) { for (const id in this.memoryMap) { - const m = this.memoryMap[id]; - if (kws.some(kw => m.keywords.includes(kw))) { + if (kws.some(kw => this.memoryMap[id].keywords.includes(kw))) { delete this.memoryMap[id]; } } } } - clearMemory() { - this.memoryMap = {}; - } - - clearShortMemory() { - this.shortMemoryList = []; - } - limitMemory() { const { memoryLimit } = ConfigManager.memory; - const now = Math.floor(Date.now() / 1000); - const memoryList = Object.values(this.memoryMap); - - const forgetIdList = memoryList - .map((item) => { - return { - id: item.id, - fgtWeight: item.calcFgtWeight(now) - } - }) - .sort((a, b) => b.fgtWeight - a.fgtWeight) - .slice(0, memoryList.length - memoryLimit) - .map(item => item.id); + 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 + } + }) + .sort((a, b) => b.score - a.score) // 从大到小排序 + .slice(limit) + .forEach(item => delete this.memoryMap?.[item.id]); + } - this.delMemory(forgetIdList); + clearMemory() { + this.memoryMap = {}; } limitShortMemory() { @@ -205,6 +248,10 @@ export class MemoryManager { } } + clearShortMemory() { + this.shortMemoryList = []; + } + async updateShortMemory(ctx: seal.MsgContext, msg: seal.Message, ai: AI) { if (!this.useShortMemory) { return; @@ -304,8 +351,10 @@ export class MemoryManager { memories: { memory_type: 'private' | 'group', name: string, - keywords: string[], - content: string + text: string, + keywords?: string[], + userList?: string[], + groupList?: string[], }[] }; @@ -322,7 +371,56 @@ export class MemoryManager { } } - updateSingleMemoryWeight(s: string, role: 'user' | 'assistant') { + async search(query: string, options: searchOptions = { + topK: 10, + userList: [], + groupList: [], + keywords: [], + includeImages: false, + method: 'score' + }) { + if (!this.memoryList.length) return []; + const { userList: ul, groupList: gl, keywords: kws, includeImages, method } = options; + + const { isMemoryVector, embeddingDimension } = ConfigManager.memory; + let qv: number[] = []; + if (isMemoryVector && query) { + qv = await getEmbedding(query); + if (!qv.length) { + logger.error('查询向量为空'); + return []; + } + await Promise.all(this.memoryList.map(async m => { + if (m.vector.length !== embeddingDimension) { + logger.info(`记忆向量维度不匹配,重新获取向量: ${m.id}`); + await m.updateVector(); + } + })) + } + + return this.memoryList + .map(m => { + if (includeImages && m.images.length === 0) return null; + const mc = m.copy; + if (mc.keywords.some(kw => query.includes(kw))) mc.weight += 10; //提权 + return mc; + }) + .filter(m => m) + .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': { + const aScore = a.weight * a.calculateSimilarity(qv, ul, gl, kws); + const bScore = b.weight * b.calculateSimilarity(qv, ul, gl, kws); + return bScore - aScore; + } + } + }) + .slice(0, options.topK || 10); + } + + updateMemoryWeight(s: string, role: 'user' | 'assistant') { const increase = role === 'user' ? 1 : 0.1; const decrease = role === 'user' ? 0.1 : 0; const now = Math.floor(Date.now() / 1000); @@ -338,68 +436,53 @@ export class MemoryManager { } } - updateMemoryWeight(ctx: seal.MsgContext, context: Context, s: string, role: 'user' | 'assistant') { - const ai = AIManager.getAI(ctx.endPoint.userId); - ai.memory.updateSingleMemoryWeight(s, role); - this.updateSingleMemoryWeight(s, role); - - if (!ctx.isPrivate) { - // 群内用户的记忆权重更新 - const arr = []; - for (const message of context.messages) { - const uid = message.uid; - if (arr.includes(uid) || message.role !== 'user') { - continue; - } - - const name = message.name; - if (name.startsWith('_')) { - continue; - } - - const ai = AIManager.getAI(uid); - ai.memory.updateSingleMemoryWeight(s, role); + 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); + // 会话自身记忆权重更新 + this.updateMemoryWeight(s, role); + // 群内用户的记忆权重更新 + if (!ctx.isPrivate) context.userInfoList.forEach(ui => AIManager.getAI(ui.id).memory.updateMemoryWeight(s, role)); + } - arr.push(uid); - } - } + async getTopMemoryList(lastMsg: string) { + const { memoryShowNumber } = ConfigManager.memory; + return await this.search(lastMsg, { + topK: memoryShowNumber, + userList: [], + groupList: [], + keywords: [], + includeImages: false, + method: 'score' + }); } - buildMemory(isPrivate: boolean, un: string, uid: string, gn: string, gid: string, lastMsg: string = ''): string { + buildMemory(sessionInfo: SessionInfo, memoryList: Memory[]): string { + if (this.persona === '无' && memoryList.length === 0) return ''; const { showNumber } = ConfigManager.message; - const { memoryShowNumber, memoryShowTemplate, memorySingleShowTemplate } = ConfigManager.memory; - const memoryList = Object.values(this.memoryMap); - - if (memoryList.length === 0 && this.persona === '无') { - return ''; - } + const { memoryShowTemplate, memorySingleShowTemplate } = ConfigManager.memory; let memoryContent = ''; if (memoryList.length === 0) { - memoryContent += '无'; + memoryContent = '无'; } else { - memoryContent += memoryList - .map(item => { - const mi: Memory = JSON.parse(JSON.stringify(item)); - if (item.keywords.some(kw => lastMsg.includes(kw))) { - mi.weight += 10; - } - return mi; - }) - .sort((a, b) => b.weight - a.weight) - .slice(0, memoryShowNumber) - .map((item, i) => { + memoryContent = memoryList + .map((m, i) => { const data = { "序号": i + 1, - "记忆ID": item.id, - "记忆时间": fmtDate(item.createTime), - "个人记忆": uid, //有uid代表这是个人记忆 - "私聊": item.isPrivate, + "记忆ID": m.id, + "记忆时间": fmtDate(m.createTime), + "个人记忆": sessionInfo.isPrivate, + "私聊": m.sessionInfo.isPrivate, "展示号码": showNumber, - "群聊名称": item.group.groupName, - "群聊号码": item.group.groupId.replace(/^.+:/, ''), - "关键词": item.keywords.join(';'), - "记忆内容": item.content + "群聊名称": 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.keywords.join(';'), + "记忆内容": m.text } const template = Handlebars.compile(memorySingleShowTemplate[0]); @@ -408,12 +491,12 @@ export class MemoryManager { } const data = { - "私聊": isPrivate, + "私聊": sessionInfo.isPrivate, "展示号码": showNumber, - "用户名称": un, - "用户号码": uid.replace(/^.+:/, ''), - "群聊名称": gn, - "群聊号码": gid.replace(/^.+:/, ''), + "用户名称": sessionInfo.name, + "用户号码": sessionInfo.id.replace(/^.+:/, ''), + "群聊名称": sessionInfo.name, + "群聊号码": sessionInfo.id.replace(/^.+:/, ''), "设定": this.persona, "记忆列表": memoryContent } @@ -422,32 +505,42 @@ export class MemoryManager { return template(data) + '\n'; } - buildMemoryPrompt(ctx: seal.MsgContext, context: Context): string { - const userMessages = context.messages.filter(msg => msg.role === 'user' && !msg.name.startsWith('_')); - const lastMsg = userMessages.length > 0 ? userMessages[userMessages.length - 1].msgArray.map(m => m.content).join('') : ''; - + async buildMemoryPrompt(ctx: seal.MsgContext, context: Context, lastMsg: string): Promise { const ai = AIManager.getAI(ctx.endPoint.userId); - let s = ai.memory.buildMemory(true, seal.formatTmpl(ctx, "核心:骰子名字"), ctx.endPoint.userId, '', '', lastMsg); + let s = ai.memory.buildMemory({ + isPrivate: true, + id: ctx.endPoint.userId, + name: seal.formatTmpl(ctx, "核心:骰子名字") + }, await ai.memory.getTopMemoryList(lastMsg)); if (ctx.isPrivate) { - return this.buildMemory(true, ctx.player.name, ctx.player.userId, '', ''); + return this.buildMemory({ + isPrivate: true, + id: ctx.player.userId, + name: ctx.player.name + }, await ai.memory.getTopMemoryList(lastMsg)); } else { // 群聊记忆 - s += this.buildMemory(false, '', '', ctx.group.groupName, ctx.group.groupId); + s += this.buildMemory({ + isPrivate: false, + id: ctx.group.groupId, + name: ctx.group.groupName + }, await ai.memory.getTopMemoryList(lastMsg)); // 群内用户的个人记忆 - const arr = []; - for (const message of userMessages) { - const name = message.name; - const uid = message.uid; - if (arr.includes(uid)) { - continue; - } + 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(true, name, uid, '', ''); - - arr.push(uid); + s += ai.memory.buildMemory({ + isPrivate: true, + id: uid, + name: name + }, await ai.memory.getTopMemoryList(lastMsg)); } return s; @@ -455,12 +548,186 @@ export class MemoryManager { } findImage(id: string): Image | null { - for (const m of Object.values(this.memoryMap)) { + for (const m of this.memoryList) { const image = m.images.find(item => item.id === id); - if (image) { - return image; - } + if (image) return image; } return null; } -} \ No newline at end of file +} + +export class KnowledgeMemoryManager extends MemoryManager { + constructor() { + super(); + } + + init() { + this.memoryMap = JSON.parse(ConfigManager.ext.storageGet('knowledgeMemoryMap') || '{}'); + this.reviveMemoryMap(); + } + + save() { + ConfigManager.ext.storageSet('knowledgeMemoryMap', JSON.stringify(this.memoryMap)); + } + + async updateKnowledgeMemory(index: number) { + const { knowledgeMemoryStringList } = ConfigManager.memory; + if (index < 0 || index >= knowledgeMemoryStringList.length) return; + const s = knowledgeMemoryStringList[index]; + if (!s) return; + + const memoryMap: { [id: string]: Memory } = {} + 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 Memory(); + 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.keywords = value.split(/[,,]/).map(kw => kw.trim()).filter(kw => kw); + break; + } + case '图片': { + const { localImagePaths } = ConfigManager.image; + const localImages: { [key: string]: string } = localImagePaths.reduce((acc: { [key: string]: string }, path: string) => { + if (path.trim() === '') { + return acc; + } + try { + const name = path.split('/').pop().replace(/\.[^/.]+$/, ''); + if (!name) throw new Error(`本地图片路径格式错误:${path}`); + acc[name] = path; + } catch (e) { + logger.error(e); + } + return acc; + }, {}); + + m.images = value.split(/[,,]/).map(id => id.trim()).map(id => { + if (localImages.hasOwnProperty(id)) return new Image(localImages[id]); + logger.error(`图片${id}不存在`); + return null; + }).filter(img => img); + break; + } + case '内容': { + m.text = lines.slice(i).join('\n').trim().replace(/^内容[::]/, ''); + break; + } + default: continue; + } + } + + if (!m.id && !m.text) 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.text !== m.text) await m.updateVector(); + m.createTime = m2.createTime; + m.lastMentionTime = m2.lastMentionTime; + m.weight = m2.weight; + } else { + await m.updateVector(); + m.createTime = now; + m.lastMentionTime = now; + m.weight = 5; + } + })) + + this.memoryMap = memoryMap; + this.save(); + } + + buildKnowledgeMemory(memoryList: Memory[]) { + const { showNumber } = ConfigManager.message; + const { knowledgeMemorySingleShowTemplate } = ConfigManager.memory; + if (memoryList.length === 0) return ''; + + let prompt = ''; + if (memoryList.length === 0) { + prompt = '无'; + } else { + prompt = memoryList + .map((m, i) => { + const data = { + "序号": 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.keywords.join(';'), + "记忆内容": m.text + } + + const template = Handlebars.compile(knowledgeMemorySingleShowTemplate[0]); + return template(data); + }).join('\n'); + } + + return prompt; + } + + async buildKnowledgeMemoryPrompt(index: number, lastMsg: string): Promise { + await this.updateKnowledgeMemory(index); + if (this.memoryIds.length === 0) return ''; + + const { knowledgeMemoryShowNumber } = ConfigManager.memory; + const memoryList = await this.search(lastMsg, { + topK: knowledgeMemoryShowNumber, + userList: [], + groupList: [], + keywords: [], + includeImages: false, + method: 'score' + }); + + return this.buildKnowledgeMemory(memoryList); + } +} + +export const knowledgeMM = new KnowledgeMemoryManager(); + +// 可以通过维护一组索引来优化搜索性能。 +// 好麻烦,不想弄 +// 目前数量级应该没什么优化的需求 \ No newline at end of file diff --git a/src/config/config_memory.ts b/src/config/config_memory.ts index 1a39587..9bc595b 100644 --- a/src/config/config_memory.ts +++ b/src/config/config_memory.ts @@ -6,9 +6,38 @@ export class MemoryConfig { static register() { MemoryConfig.ext = ConfigManager.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, "单条知识库记忆展示模板", [ + ` {{{序号}}}. 记忆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}}: @@ -25,6 +54,8 @@ export class MemoryConfig { {{#if 个人记忆}} 来源:{{#if 私聊}}私聊{{else}}群聊<{{{群聊名称}}}>{{#if 展示号码}}({{{群聊号码}}}){{/if}}{{/if}} {{/if}} + 相关用户:{{{用户列表}}} + 相关群聊:{{{群聊列表}}} 关键词:{{{关键词}}} 内容:{{{记忆内容}}}` ], ""); @@ -91,18 +122,33 @@ export class MemoryConfig { type: 'string', description: '用户名称或群聊名称{{#if 展示号码}}或纯数字QQ号、群号{{/if}},实际使用时与记忆类型对应' }, + "text": { + type: 'string', + description: '记忆内容,尽量简短,无需附带时间与来源' + }, "keywords": { type: 'array', - description: '记忆关键词', + description: '相关用户名称列表', items: { type: 'string' } }, - "content": { - type: 'string', - description: '记忆内容,尽量简短,无需附带时间与来源' + "userList": { + type: 'array', + description: '相关用户名称列表', + items: { + type: 'string' + } + }, + "groupList": { + type: 'array', + description: '相关群聊名称列表', + items: { + type: 'string' + } } - } + }, + "required": ['memory_type', 'name', 'text'] } } }` @@ -111,9 +157,17 @@ export class MemoryConfig { static get() { return { + knowledgeMemoryShowNumber: seal.ext.getIntConfig(MemoryConfig.ext, "知识库记忆展示数量"), + knowledgeMemoryStringList: seal.ext.getTemplateConfig(MemoryConfig.ext, "知识库记忆"), + knowledgeMemorySingleShowTemplate: seal.ext.getTemplateConfig(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: seal.ext.getTemplateConfig(MemoryConfig.ext, "长期记忆展示模板"), memorySingleShowTemplate: seal.ext.getTemplateConfig(MemoryConfig.ext, "单条长期记忆展示模板"), isShortMemory: seal.ext.getBoolConfig(MemoryConfig.ext, "是否启用短期记忆"), diff --git a/src/config/config_message.ts b/src/config/config_message.ts index 590450d..3223158 100644 --- a/src/config/config_message.ts +++ b/src/config/config_message.ts @@ -55,6 +55,11 @@ export class MessageConfig { {{#if 可发送图片不为空}} - 可使用<|img:图片名称|>发送表情包,表情名称有:{{{可发送图片列表}}} {{/if}} +{{#if 知识库}} + +## 知识库 +{{{知识库}}} +{{/if}} {{#if 开启长期记忆}} ## 记忆 diff --git a/src/index.ts b/src/index.ts index f715696..2db3256 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,14 +12,15 @@ import { TimerManager } from "./timer"; import { createMsg } from "./utils/utils_seal"; import { PrivilegeManager } from "./privilege"; import { aliasToCmd } from "./utils/utils"; +import { knowledgeMM } from "./AI/memory"; function main() { ConfigManager.registerConfig(); checkUpdate(); - AIManager.getUsageMap(); ToolManager.registerTool(); TimerManager.init(); PrivilegeManager.reviveCmdPriv(); + knowledgeMM.init(); const ext = ConfigManager.ext; @@ -189,9 +190,10 @@ ${HELPMAP["权限限制"]}`); } } case 'prompt': { - const systemMessage = buildSystemMessage(ctx, ai); - logger.info(`system prompt:\n`, systemMessage.msgArray[0].content); - seal.replyToSender(ctx, msg, systemMessage.msgArray[0].content); + buildSystemMessage(ctx, ai).then(systemMessage => { + logger.info(`system prompt:\n`, systemMessage.msgArray[0].content); + seal.replyToSender(ctx, msg, systemMessage.msgArray[0].content); + }); return ret; } case 'status': { @@ -214,7 +216,7 @@ ${HELPMAP["权限限制"]}`); switch (aliasToCmd(val2)) { case 'status': { seal.replyToSender(ctx, msg, `自动修改上下文里的名字状态:${ai.context.autoNameMod} -上下文里的名字有:\n${ai.context.getUserNameInfo().map(uni => `${uni.name}(${uni.uid})`).join('\n')}`); +上下文里的名字有:\n${ai.context.userInfoList.map(ui => `${ui.name}(${ui.id})`).join('\n')}`); return ret; } case 'set': { @@ -225,9 +227,9 @@ ${HELPMAP["权限限制"]}`); 【.ai ctxn set [nick/card]】设置上下文里的名字为昵称/群名片`); return ret; } - const promises = ai.context.getUserNameInfo().map(uni => ai.context.setName(epId, gid, uni.uid, mod)); + const promises = ai.context.userInfoList.map(ui => ai.context.setName(epId, gid, ui.id, mod)); Promise.all(promises).then(() => { - seal.replyToSender(ctx, msg, `设置完成,上下文里的名字有:\n${ai.context.getUserNameInfo().map(uni => `${uni.name}(${uni.uid})`).join('\n')}`); + seal.replyToSender(ctx, msg, `设置完成,上下文里的名字有:\n${ai.context.userInfoList.map(uni => `${uni.name}(${uni.id})`).join('\n')}`); }); return ret; } @@ -403,7 +405,7 @@ ${HELPMAP["权限限制"]}`); text += `\n活跃时间段:${Math.floor(start / 60).toString().padStart(2, '0')}:${(start % 60).toString().padStart(2, '0')}至${Math.floor(end / 60).toString().padStart(2, '0')}:${(end % 60).toString().padStart(2, '0')}`; text += `\n活跃次数:${segs}`; - const curSegIndex = ai.getCurSegIndex(); + const curSegIndex = ai.curActiveTimeSegIndex; const nextTimePoint = ai.getNextTimePoint(curSegIndex); if (nextTimePoint !== -1) { TimerManager.addActiveTimeTimer(ctx, msg, ai, nextTimePoint); @@ -580,18 +582,11 @@ ${HELPMAP["权限限制"]}`); if (cmdArgs.at.length > 0 && (cmdArgs.at.length !== 1 || cmdArgs.at[0].userId !== epId)) { ai3 = ai2; } - const { isMemory, isShortMemory } = ConfigManager.memory; - - const keywords = new Set(); - for (const key in ai3.memory.memoryMap) { - ai3.memory.memoryMap[key].keywords.forEach(kw => keywords.add(kw)); - } - seal.replyToSender(ctx, msg, `${ai3.id} 长期记忆开启状态: ${isMemory ? '是' : '否'} -长期记忆条数: ${Object.keys(ai3.memory.memoryMap).length} -关键词库: ${Array.from(keywords).join('、') || '无'} +长期记忆条数: ${ai3.memory.memoryIds.length} +关键词库: ${ai3.memory.keywords.join('、') || '无'} 短期记忆开启状态: ${(isShortMemory && ai3.memory.useShortMemory) ? '是' : '否'} 短期记忆条数: ${ai3.memory.shortMemoryList.length}`); return ret; @@ -631,15 +626,29 @@ ${HELPMAP["权限限制"]}`); seal.replyToSender(ctx, msg, '参数缺失,【.ai memo p del --关键词1 --关键词2】删除个人记忆'); return ret; } - ai2.memory.delMemory(idList, kw); - const s = ai2.memory.buildMemory(true, mctx.player.name, mctx.player.userId, '', ''); - seal.replyToSender(ctx, msg, s || '无'); - AIManager.saveAI(muid); + ai2.memory.deleteMemory(idList, kw); + ai2.memory.getTopMemoryList('').then(memoryList => { + const s = ai2.memory.buildMemory({ + isPrivate: true, + id: mctx.player.userId, + name: mctx.player.name + }, memoryList); + seal.replyToSender(ctx, msg, s || '无'); + AIManager.saveAI(muid); + } + ); return ret; } case 'show': { - const s = ai2.memory.buildMemory(true, mctx.player.name, mctx.player.userId, '', ''); - seal.replyToSender(ctx, msg, s || '无'); + ai2.memory.getTopMemoryList('').then(memoryList => { + const s = ai2.memory.buildMemory({ + isPrivate: true, + id: mctx.player.userId, + name: mctx.player.name + }, memoryList); + seal.replyToSender(ctx, msg, s || '无'); + } + ); return ret; } case 'clear': { @@ -699,15 +708,29 @@ ${HELPMAP["权限限制"]}`); seal.replyToSender(ctx, msg, '参数缺失,【.ai memo g del 】删除群聊记忆'); return ret; } - ai.memory.delMemory(idList, kw); - const s = ai.memory.buildMemory(false, '', '', ctx.group.groupName, ctx.group.groupId); - seal.replyToSender(ctx, msg, s || '无'); - AIManager.saveAI(id); + ai.memory.deleteMemory(idList, kw); + ai.memory.getTopMemoryList('').then(memoryList => { + const s = ai.memory.buildMemory({ + isPrivate: false, + id: ctx.group.groupId, + name: ctx.group.groupName + }, memoryList); + seal.replyToSender(ctx, msg, s || '无'); + AIManager.saveAI(id); + } + ); return ret; } case 'show': { - const s = ai.memory.buildMemory(false, '', '', ctx.group.groupName, ctx.group.groupId); - seal.replyToSender(ctx, msg, s || '无'); + ai.memory.getTopMemoryList('').then(memoryList => { + const s = ai.memory.buildMemory({ + isPrivate: false, + id: ctx.group.groupId, + name: ctx.group.groupName + }, memoryList); + seal.replyToSender(ctx, msg, s || '无'); + } + ); return ret; } case 'clear': { diff --git a/src/service.ts b/src/service.ts index ca5264d..15410ea 100644 --- a/src/service.ts +++ b/src/service.ts @@ -1,7 +1,7 @@ import { AI, AIManager } from "./AI/AI"; import { ToolCall, ToolManager } from "./tool/tool"; import { ConfigManager } from "./config/config"; -import { handleMessages, parseBody } from "./utils/utils_message"; +import { handleMessages, parseBody, parseEmbeddingBody } from "./utils/utils_message"; import { ImageManager } from "./AI/image"; import { logger } from "./logger"; import { withTimeout } from "./utils/utils"; @@ -51,7 +51,7 @@ export async function sendChatRequest(ctx: seal.MsgContext, msg: seal.Message, a return ''; } - const messages = handleMessages(ctx, ai); + const messages = await handleMessages(ctx, ai); return await sendChatRequest(ctx, msg, ai, messages, tool_choice); } } else { @@ -68,7 +68,7 @@ export async function sendChatRequest(ctx: seal.MsgContext, msg: seal.Message, a return ''; } - const messages = handleMessages(ctx, ai); + const messages = await handleMessages(ctx, ai); return await sendChatRequest(ctx, msg, ai, messages, tool_choice); } } @@ -140,15 +140,58 @@ export async function sendITTRequest(messages: { } } +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 { // 打印请求发送前的上下文 - 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); + 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', diff --git a/src/timer.ts b/src/timer.ts index a3a7e30..b0fefba 100644 --- a/src/timer.ts +++ b/src/timer.ts @@ -264,7 +264,7 @@ export class TimerManager { const ctx = createCtx(epId, msg); const ai = AIManager.getAI(id); - const curSegIndex = ai.getCurSegIndex(); + const curSegIndex = ai.curActiveTimeSegIndex; const nextTimePoint = ai.getNextTimePoint(curSegIndex); if (curSegIndex === -1) { logger.error(`${id} 不在活跃时间内,触发了 activeTime 定时器,真奇怪\ncurSegIndex:${curSegIndex},setTime:${set},nextTimePoint:${fmtDate(nextTimePoint)}`); diff --git a/src/tool/tool.ts b/src/tool/tool.ts index 971ae9d..94ec042 100644 --- a/src/tool/tool.ts +++ b/src/tool/tool.ts @@ -437,13 +437,17 @@ export class ToolManager { reviveToolStauts() { const { toolsNotAllow, toolsDefaultClosed } = ConfigManager.tool; + const toolStatus: {[key: string]: boolean} = {}; for (const k in ToolManager.toolMap) { if (!this.toolStatus.hasOwnProperty(k)) { - this.toolStatus[k] = !toolsNotAllow.includes(k) && !toolsDefaultClosed.includes(k); + toolStatus[k] = !toolsNotAllow.includes(k) && !toolsDefaultClosed.includes(k); } else if (toolsNotAllow.includes(k)) { - this.toolStatus[k] = false; + toolStatus[k] = false; + } else { + toolStatus[k] = this.toolStatus[k]; } } + this.toolStatus = toolStatus; } getToolsInfo(type: string): ToolInfo[] { diff --git a/src/tool/tool_memory.ts b/src/tool/tool_memory.ts index 06607f4..15e2c4d 100644 --- a/src/tool/tool_memory.ts +++ b/src/tool/tool_memory.ts @@ -1,7 +1,8 @@ -import { AIManager } from "../AI/AI"; +import { AIManager, GroupInfo, SessionInfo, UserInfo } from "../AI/AI"; import { ConfigManager } from "../config/config"; import { createMsg, createCtx } from "../utils/utils_seal"; import { Tool } from "./tool"; +import { knowledgeMM, searchOptions as SearchOptions } from "../AI/memory"; export function registerMemory() { const toolAdd = new Tool({ @@ -19,26 +20,40 @@ export function registerMemory() { }, name: { type: 'string', - description: '用户名称或群聊名称' + (ConfigManager.message.showNumber ? '或纯数字QQ号、群号' : '') + ',实际使用时与记忆类型对应' + description: '目标用户名称或群聊名称' + (ConfigManager.message.showNumber ? '或纯数字QQ号、群号' : '') + ',实际使用时与记忆类型对应' + }, + text: { + type: 'string', + description: '记忆内容,尽量简短,无需附带时间与来源' }, keywords: { type: 'array', - description: '记忆关键词', + description: '相关用户名称列表', items: { type: 'string' } }, - content: { - type: 'string', - description: '记忆内容,尽量简短,无需附带时间与来源' + userList: { + type: 'array', + description: '相关用户名称列表', + items: { + type: 'string' + } + }, + groupList: { + type: 'array', + description: '相关群聊名称列表', + items: { + type: 'string' + } } }, - required: ['memory_type', 'name', 'keywords', 'content'] + required: ['memory_type', 'name', 'text'] } } }); toolAdd.solve = async (ctx, msg, ai, args) => { - const { memory_type, name, keywords, content } = args; + const { memory_type, name, text, keywords = [], userList = [], groupList = [] } = args; if (memory_type === "private") { const uid = await ai.context.findUserId(ctx, name, true); @@ -64,8 +79,31 @@ export function registerMemory() { return { content: `未知的记忆类型<${memory_type}>`, images: [] }; } + const uiList: UserInfo[] = []; + for (const n of userList) { + const uid = await ai.context.findUserId(ctx, n, true); + if (uid !== null) { + uiList.push({ + isPrivate: true, + id: uid, + name: n + }); + } + } + const giList: GroupInfo[] = []; + for (const n of groupList) { + const gid = await ai.context.findGroupId(ctx, n); + if (gid !== null) { + giList.push({ + isPrivate: false, + id: gid, + name: n + }); + } + } + //记忆相关处理 - await ai.memory.addMemory(ctx, ai, Array.isArray(keywords) ? keywords : [], content); + await ai.memory.addMemory(ctx, ai, uiList, giList, Array.isArray(keywords) ? keywords : [], text); AIManager.saveAI(ai.id); return { content: `添加记忆成功`, images: [] }; @@ -88,9 +126,9 @@ export function registerMemory() { type: 'string', description: '用户名称或群聊名称' + (ConfigManager.message.showNumber ? '或纯数字QQ号、群号' : '') + ',实际使用时与记忆类型对应' }, - index_list: { + id_list: { type: 'array', - description: '记忆序号列表,可为空', + description: '记忆ID列表,可为空', items: { type: 'integer' } @@ -103,12 +141,12 @@ export function registerMemory() { } } }, - required: ['memory_type', 'name', 'index_list', 'keywords'] + required: ['memory_type', 'name', 'id_list', 'keywords'] } } }); toolDel.solve = async (ctx, msg, ai, args) => { - const { memory_type, name, index_list, keywords } = args; + const { memory_type, name, id_list, keywords } = args; if (memory_type === "private") { const uid = await ai.context.findUserId(ctx, name, true); @@ -135,17 +173,198 @@ export function registerMemory() { } //记忆相关处理 - ai.memory.delMemory(index_list, keywords); + ai.memory.deleteMemory(id_list, keywords); AIManager.saveAI(ai.id); return { content: `删除记忆成功`, images: [] }; } - const toolShow = new Tool({ + const toolSearch = new Tool({ + type: 'function', + function: { + name: 'search_memory', + description: '搜索个人记忆或群聊记忆', + parameters: { + type: 'object', + properties: { + memory_type: { + type: "string", + description: "记忆类型,个人或群聊或知识库,选择知识库时不用填写name", + enum: ["private", "group", "knowledge"] + }, + name: { + type: 'string', + description: '用户名称或群聊名称' + (ConfigManager.message.showNumber ? '或纯数字QQ号、群号' : '') + ',实际使用时与记忆类型对应' + }, + query: { + type: 'string', + description: '搜索查询,为空时返回权重靠前的记忆' + }, + topK: { + type: 'number', + description: '返回记忆条数,默认5条' + }, + keywords: { + type: 'array', + description: '相关用户名称列表', + items: { + type: 'string' + } + }, + userList: { + type: 'array', + description: '相关用户名称列表', + items: { + type: 'string' + } + }, + groupList: { + type: 'array', + description: '相关群聊名称列表', + items: { + type: 'string' + } + }, + includeImages: { + type: 'boolean', + description: '是否包含图片' + }, + method: { + type: 'string', + description: '搜索方法,默认similarity', + enum: ['weight', 'similarity', 'score'] + } + }, + required: ['memory_type'] + } + } + }); + toolSearch.solve = async (ctx, msg, ai, args) => { + const { memory_type, name = '', query = '', topK = 5, keywords = [], userList = [], groupList = [], includeImages = false, method = 'similarity' } = args; + + let si: SessionInfo = { + isPrivate: false, + id: '', + name: '' + }; + if (memory_type === "private") { + const uid = await ai.context.findUserId(ctx, name, true); + if (uid === null) { + return { content: `未找到<${name}>`, images: [] }; + } + + msg = createMsg('private', uid, ''); + ctx = createCtx(ctx.endPoint.userId, msg); + + ai = AIManager.getAI(uid); + si = { + isPrivate: true, + id: uid, + name: name + } + } else if (memory_type === "group") { + const gid = await ai.context.findGroupId(ctx, name); + if (gid === null) { + return { content: `未找到<${name}>`, images: [] }; + } + + msg = createMsg('group', ctx.player.userId, gid); + ctx = createCtx(ctx.endPoint.userId, msg); + + ai = AIManager.getAI(gid); + si = { + isPrivate: false, + id: gid, + name: name + } + } else if (memory_type === "knowledge") { + const giList: GroupInfo[] = []; + for (const n of groupList) { + const gid = await ai.context.findGroupId(ctx, n); + if (gid !== null) { + giList.push({ + isPrivate: false, + id: gid, + name: n + }); + } + } + + const options: SearchOptions = { + topK: topK, + keywords: keywords, + userList: userList, + groupList: groupList, + includeImages: includeImages, + method: method + } + + const { roleSettingNames, roleSettingTemplate } = ConfigManager.message; + const [roleName, exists] = seal.vars.strGet(ctx, "$gSYSPROMPT"); + let roleIndex = 0; + if (exists && roleName !== '' && roleSettingNames.includes(roleName)) { + roleIndex = roleSettingNames.indexOf(roleName); + if (roleIndex < 0 || roleIndex >= roleSettingTemplate.length) roleIndex = 0; + } else { + const [roleIndex2, exists2] = seal.vars.intGet(ctx, "$gSYSPROMPT"); + if (exists2 && roleIndex2 >= 0 && roleIndex2 < roleSettingTemplate.length) roleIndex = roleIndex2; + } + await knowledgeMM.updateKnowledgeMemory(roleIndex); + if (knowledgeMM.memoryIds.length === 0) return { content: `暂无记忆`, images: [] }; + + const memoryList = await knowledgeMM.search(query, options); + const images = Array.from(new Set([].concat(...memoryList.map(m => m.images)))); + + return { content: knowledgeMM.buildKnowledgeMemory(memoryList) || '暂无记忆', images: images }; + } else { + return { content: `未知的记忆类型<${memory_type}>`, images: [] }; + } + + if (ai.memory.memoryIds.length === 0) return { content: `暂无记忆`, images: [] }; + + const uiList: UserInfo[] = []; + for (const n of userList) { + const uid = await ai.context.findUserId(ctx, n, true); + if (uid !== null) { + uiList.push({ + isPrivate: true, + id: uid, + name: n + }); + } + } + const giList: GroupInfo[] = []; + for (const n of groupList) { + const gid = await ai.context.findGroupId(ctx, n); + if (gid !== null) { + giList.push({ + isPrivate: false, + id: gid, + name: n + }); + } + } + + const options: SearchOptions = { + topK: topK, + keywords: keywords, + userList: userList, + groupList: groupList, + includeImages: includeImages, + method: method + } + + const memoryList = await ai.memory.search(query, options); + const images = Array.from(new Set([].concat(...memoryList.map(m => m.images)))); + + return { content: ai.memory.buildMemory(si, memoryList) || '暂无记忆', images: images }; + } + + const toolClear = new Tool({ type: 'function', function: { - name: 'show_memory', - description: '查看个人记忆或群聊记忆', + name: 'clear_memory', + description: '清除个人记忆或群聊记忆', parameters: { type: 'object', properties: { @@ -163,7 +382,7 @@ export function registerMemory() { } } }); - toolShow.solve = async (ctx, msg, ai, args) => { + toolClear.solve = async (ctx, msg, ai, args) => { const { memory_type, name } = args; if (memory_type === "private") { @@ -171,31 +390,28 @@ export function registerMemory() { if (uid === null) { return { content: `未找到<${name}>`, images: [] }; } - if (uid === ctx.player.userId) { - return { content: `查看该用户记忆无需调用函数`, images: [] }; - } msg = createMsg('private', uid, ''); ctx = createCtx(ctx.endPoint.userId, msg); ai = AIManager.getAI(uid); - return { content: ai.memory.buildMemory(true, ctx.player.name, ctx.player.userId, '', ''), images: [] }; } else if (memory_type === "group") { const gid = await ai.context.findGroupId(ctx, name); if (gid === null) { return { content: `未找到<${name}>`, images: [] }; } - if (gid === ctx.group.groupId) { - return { content: `查看当前群聊记忆无需调用函数`, images: [] }; - } msg = createMsg('group', ctx.player.userId, gid); ctx = createCtx(ctx.endPoint.userId, msg); ai = AIManager.getAI(gid); - return { content: ai.memory.buildMemory(false, '', '', ctx.group.groupName, ctx.group.groupId), images: [] }; } else { return { content: `未知的记忆类型<${memory_type}>`, images: [] }; } + + + ai.memory.clearMemory(); + AIManager.saveAI(ai.id); + return { content: `清除记忆成功`, images: [] }; } } \ No newline at end of file diff --git a/src/update.ts b/src/update.ts index 56fc109..e94d0e5 100644 --- a/src/update.ts +++ b/src/update.ts @@ -4,7 +4,9 @@ export const updateInfo = { - 修复获取好友、群聊等列表时的bug - 修复了调用函数时,无需cmdArgs的函数也会报错的问题 - 新增了修改上下文里的名字相关功能 -- 活跃时间添加上一条消息时间提示`, +- 活跃时间添加上一条消息时间提示 +- 新增向量记忆 +- 新增知识库`, "4.11.2": `- 增加修复json解析错误的功能`, "4.11.1": `- 修复了戳戳、权限检查、权限设置、帮助文本等相关问题`, "4.11.0": `- 新增请求超时相关 diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 5472d66..6555a5f 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -1,4 +1,4 @@ -import { AI } from "../AI/AI"; +import { AI, GroupInfo, UserInfo } from "../AI/AI"; import { logger } from "../logger"; import { ConfigManager } from "../config/config"; import { transformTextToArray } from "./utils_string"; @@ -160,4 +160,41 @@ export function aliasToCmd(val: string) { "nick": "nickname" } return aliasMap[val] || val; +} + +// 计算余弦相似度 +export function cosineSimilarity(a: number[], b: number[]): number { + if (a.length !== b.length) { + logger.error(`cosineSimilarity: 向量维度必须相同,a: ${a.length}, b: ${b.length}`); + return 0; + } + + let dotProduct = 0; + let normA = 0; + let normB = 0; + + for (let i = 0; i < a.length; i++) { + dotProduct += a[i] * b[i]; + normA += a[i] * a[i]; + normB += b[i] * b[i]; + } + + if (normA === 0 || normB === 0) return 0; + return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB)); +} + +export function getCommonUser(a: UserInfo[], b: UserInfo[]): UserInfo[] { + 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)); +} +export function getCommonGroup(a: GroupInfo[], b: GroupInfo[]): GroupInfo[] { + 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)); +} +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)); } \ No newline at end of file diff --git a/src/utils/utils_message.ts b/src/utils/utils_message.ts index 2aba4b9..9f782fa 100644 --- a/src/utils/utils_message.ts +++ b/src/utils/utils_message.ts @@ -5,23 +5,21 @@ import { logger } from "../logger"; import { ConfigManager } from "../config/config"; import { ToolInfo } from "../tool/tool"; import { fmtDate } from "./utils_string"; +import { knowledgeMM } from "../AI/memory"; -export function buildSystemMessage(ctx: seal.MsgContext, ai: AI): Message { +export async function buildSystemMessage(ctx: seal.MsgContext, ai: AI): Promise { const { roleSettingNames, roleSettingTemplate, systemMessageTemplate, isPrefix, showNumber, showMsgId, showTime } = ConfigManager.message; const { isTool, usePromptEngineering } = ConfigManager.tool; const { localImagePaths, receiveImage, condition } = ConfigManager.image; const { isMemory, isShortMemory } = ConfigManager.memory; + + // 可发送的图片提示 const sandableImagesPrompt: string = localImagePaths .map(path => { - if (path.trim() === '') { - return null; - } + if (path.trim() === '') return null; try { const name = path.split('/').pop().replace(/\.[^/.]+$/, ''); - if (!name) { - throw new Error(`本地图片路径格式错误:${path}`); - } - + if (!name) throw new Error(`本地图片路径格式错误:${path}`); return name; } catch (e) { logger.error(e); @@ -33,37 +31,30 @@ export function buildSystemMessage(ctx: seal.MsgContext, ai: AI): Message { .map((prompt, index) => `${index + 1}. ${prompt}`) .join('\n'); + + // 角色设定 const [roleName, exists] = seal.vars.strGet(ctx, "$gSYSPROMPT"); let roleIndex = 0; if (exists && roleName !== '' && roleSettingNames.includes(roleName)) { roleIndex = roleSettingNames.indexOf(roleName); - if (roleIndex < 0 || roleIndex >= roleSettingTemplate.length) { - roleIndex = 0; - } + if (roleIndex < 0 || roleIndex >= roleSettingTemplate.length) roleIndex = 0; } else { const [roleIndex2, exists2] = seal.vars.intGet(ctx, "$gSYSPROMPT"); - if (exists2 && roleIndex2 >= 0 && roleIndex2 < roleSettingTemplate.length) { - roleIndex = roleIndex2; - } + if (exists2 && roleIndex2 >= 0 && roleIndex2 < roleSettingTemplate.length) roleIndex = roleIndex2; } - // 记忆 - let memoryPrompt = ''; - if (isMemory) { - memoryPrompt = ai.memory.buildMemoryPrompt(ctx, ai.context); - } + // 获取lastMsg + const userMessages = ai.context.messages.filter(msg => msg.role === 'user' && !msg.name.startsWith('_')); + const lastMsg = userMessages.length > 0 ? userMessages[userMessages.length - 1].msgArray.map(m => m.content).join('') : ''; + // 知识库 + const knowledgePrompt = await knowledgeMM.buildKnowledgeMemoryPrompt(roleIndex, lastMsg); + // 记忆 + const memoryPrompt = isMemory ? await ai.memory.buildMemoryPrompt(ctx, ai.context, lastMsg) : ''; // 短期记忆 - let shortMemoryPrompt = ''; - if (isShortMemory && ai.memory.useShortMemory) { - shortMemoryPrompt = ai.memory.shortMemoryList.map((item, index) => `${index + 1}. ${item}`).join('\n'); - } - + const shortMemoryPrompt = isShortMemory && ai.memory.useShortMemory ? ai.memory.shortMemoryList.map((item, index) => `${index + 1}. ${item}`).join('\n') : ''; // 调用函数 - let toolsPrompt = ''; - if (isTool && usePromptEngineering) { - toolsPrompt = ai.tool.getToolsPrompt(ctx); - } + const toolsPrompt = isTool && usePromptEngineering ? ai.tool.getToolsPrompt(ctx) : ''; const data = { "角色设定": roleSettingTemplate[roleIndex], @@ -81,6 +72,7 @@ export function buildSystemMessage(ctx: seal.MsgContext, ai: AI): Message { "图片条件不为零": condition !== '0', "可发送图片不为空": sandableImagesPrompt, "可发送图片列表": sandableImagesPrompt, + "知识库": knowledgePrompt, "开启长期记忆": isMemory && memoryPrompt, "记忆信息": memoryPrompt, "开启短期记忆": isShortMemory && ai.memory.useShortMemory && shortMemoryPrompt, @@ -176,10 +168,10 @@ function buildContextMessages(systemMessage: Message, messages: Message[]): Mess return contextMessages; } -export function handleMessages(ctx: seal.MsgContext, ai: AI) { +export async function handleMessages(ctx: seal.MsgContext, ai: AI) { const { isMerge } = ConfigManager.message; - const systemMessage = buildSystemMessage(ctx, ai); + const systemMessage = await buildSystemMessage(ctx, ai); const samplesMessages = buildSamplesMessages(ctx); const contextMessages = buildContextMessages(systemMessage, ai.context.messages); @@ -282,6 +274,34 @@ export function parseBody(template: string[], messages: any[], tools: ToolInfo[] return bodyObject; } +export function parseEmbeddingBody(template: string[], input: string, dimensions: number) { + const bodyObject: any = {}; + + for (let i = 0; i < template.length; i++) { + const s = template[i]; + if (s.trim() === '') { + continue; + } + + try { + const obj = JSON.parse(`{${s}}`); + const key = Object.keys(obj)[0]; + bodyObject[key] = obj[key]; + } catch (err) { + throw new Error(`解析body的【${s}】时出现错误:${err}`); + } + } + + if (!bodyObject.hasOwnProperty('input')) { + bodyObject.input = input; + } + if (!bodyObject.hasOwnProperty('dimensions')) { + bodyObject.dimensions = dimensions; + } + + return bodyObject; +} + export function buildContent(message: Message): string { const { isPrefix, showNumber, showMsgId, showTime } = ConfigManager.message; const prefix = (isPrefix && message.name) ? (