diff --git a/packages/core/src/bot.ts b/packages/core/src/bot.ts index 8df44de..2a15f80 100644 --- a/packages/core/src/bot.ts +++ b/packages/core/src/bot.ts @@ -52,12 +52,7 @@ export interface FailedResponse { type Response = SuccessResponse | SkipResponse | FailedResponse; export class Bot { - private memorySize: number; - - private summarySize: number; // 上下文达到多少时进行总结 private contextSize: number = 20; // 以对话形式给出的上下文长度 - private retainedContextSize: number; // 进行总结时保留的上下文长度,用于保持记忆连贯性 - private maxSelfMemoryCharacters: 5000; // private minTriggerCount: number; private maxTriggerCount: number; @@ -69,10 +64,8 @@ export class Bot { private template: Template; private extensions: { [key: string]: Extension & Function } = {}; private toolsSchema: ToolSchema[] = []; - private lastModified: Date = new Date(); private emojiManager: EmojiManager; - private embedder: EmbeddingBase; readonly verifier: ResponseVerifier; readonly imageViewer: ImageViewer; @@ -89,7 +82,6 @@ export class Bot { ); if (config.Embedding.Enabled) { this.emojiManager = new EmojiManager(config.Embedding); - this.embedder = getEmbedding(config.Embedding); }; if (config.Verifier.Enabled) this.verifier = new ResponseVerifier(config); @@ -103,11 +95,6 @@ export class Bot { } } - updateConfig(config: Config) { - this.config = config; - this.adapterSwitcher.updateConfig(config.API.APIList, config.Parameters); - } - setSystemPrompt(content: string) { this.prompt = content; } diff --git a/packages/core/src/extensions/ext_memory.ts b/packages/core/src/extensions/ext_memory.ts index d815aa9..f892231 100644 --- a/packages/core/src/extensions/ext_memory.ts +++ b/packages/core/src/extensions/ext_memory.ts @@ -5,9 +5,11 @@ import { Description, Extension, Name, Param } from "./base"; @Name("addCoreMemory") @Description("Append to the contents of core memory.") @Param("content", SchemaNode.String("Content to write to the memory. All unicode (including emojis) are supported.")) +@Param("topic", SchemaNode.String("The topic of the memory.", "")) +@Param("keywords", SchemaNode.Array("The keywords of the memory.", "")) export class AddCoreMemory extends Extension { - async apply(content: string) { - return await this.ctx.memory.addCoreMemory(content); + async apply(content: string, topic?: string, keywords?: string[]) { + return await this.ctx.memory.addCoreMemory(content, topic, keywords); } } diff --git a/packages/memory/src/config.ts b/packages/memory/src/config.ts index 3d61a32..57b9dc5 100644 --- a/packages/memory/src/config.ts +++ b/packages/memory/src/config.ts @@ -28,8 +28,7 @@ export const EmbeddingConfig: Schema = Schema.intersect([ .default(300) .experimental() .description("文本分词长度"), - - }), + }).description("Embedding API 配置"), Schema.union([ Schema.object({ APIType: Schema.const("OpenAI"), @@ -58,12 +57,3 @@ export const EmbeddingConfig: Schema = Schema.intersect([ }), ]) ]) - - -export interface Config { - embedding: EmbeddingConfig; -} - -export const Config: Schema = Schema.object({ - embedding: EmbeddingConfig, -}); diff --git a/packages/memory/src/index.ts b/packages/memory/src/index.ts index 99f1c57..8a48b08 100644 --- a/packages/memory/src/index.ts +++ b/packages/memory/src/index.ts @@ -1,11 +1,9 @@ import { Context, Schema, Service } from "koishi"; -import { ChatMessage } from "koishi-plugin-yesimbot"; -import { EmbeddingBase, calculateCosineSimilarity } from "koishi-plugin-yesimbot/embeddings"; -import { getEmbedding } from "koishi-plugin-yesimbot/utils"; +import { ChatMessage, EmbeddingBase, calculateCosineSimilarity, getEmbedding } from "koishi-plugin-yesimbot"; import { EmbeddingConfig } from "./config"; import { MemoryItem, MemoryType } from "./model"; -import { MemoryMetadata, MemoryVectorStore } from "./vectorStore"; import { MEMORY_PROMPT } from "./prompt"; +import { MemoryMetadata, MemoryVectorStore } from "./vectorStore"; declare module "koishi" { interface Context { @@ -17,8 +15,6 @@ export const inject = { required: ["yesimbot", "database"], }; -export { Config } from "./config"; - class Memory extends Service { private vectorStore: MemoryVectorStore; private embedder: EmbeddingBase; @@ -51,13 +47,29 @@ class Memory extends Service { this.vectorStore.clear(); } - async addCoreMemory(content: string) { + async modifyMemoryById(memoryId: string, content: string, type?: MemoryType, topic?: string, keywords?: string[]) { + const memory = this.vectorStore.get(memoryId); + if (memory) { + const embedding = (content == memory.content) ? memory.embedding : await this.embedder.embed(content); + const metadata: MemoryMetadata = { + content, + type: type || memory.type, + topic: topic || memory.topic, + keywords: keywords || memory.keywords, + createdAt: memory.createdAt, + updatedAt: new Date(), + }; + this.vectorStore.update(memoryId, embedding, metadata); + } + } + + async addCoreMemory(content: string, topic?: string, keywords?: string[]) { const embedding = await this.embedder.embed(content); const metadata: MemoryMetadata = { content, - topic: "核心记忆", - keywords: [], type: MemoryType.Core, + topic, + keywords, createdAt: new Date(), updatedAt: new Date(), }; @@ -124,14 +136,14 @@ class Memory extends Service { }) } - async searchArchivalMemory(query: string, options: { type?: MemoryType, topic?: string, keywords?: string[]; limit?: number }): Promise { + async searchArchivalMemory(query: string, type?: MemoryType, topic?: string, keywords?: string[], limit?: number): Promise { const contextEmbedding = await this.embedder.embed(query); // 1. 主题与关键词过滤 let filteredMemory = this.vectorStore.filter(item => { - const topicMatch = options.topic ? item.topic === options.topic : true; - const keywordMatch = options.keywords - ? options.keywords.some(keyword => item.keywords.includes(keyword)) + const topicMatch = topic ? item.topic === topic : true; + const keywordMatch = keywords + ? keywords.some(keyword => item.keywords.includes(keyword)) : true; return topicMatch && keywordMatch; }); @@ -145,29 +157,28 @@ class Memory extends Service { // 3. 排序并限制结果数 const sortedMemory = scoredMemory .sort((a, b) => b.similarity - a.similarity) // 按相似度降序排序 - .slice(0, options.limit || 5); // 限制返回结果数 + .slice(0, limit || 5); // 限制返回结果数 return sortedMemory.map(item => item.content); } - /** - * Searches for conversation messages from a specific user that are semantically similar to a given query. + * 搜索与给定查询语义相似且来自特定用户的对话消息。 * - * This method performs the following steps: - * 1. Embeds the query into a vector representation using the configured embedder. - * 2. Retrieves the most recent chat messages sent by the specified user from the database. - * 3. Computes the cosine similarity between the query embedding and the embedding of each chat message. - * 4. Sorts the messages by similarity in descending order and limits the results to the specified count. - * 5. Re-sorts the limited results by the original send time in ascending order to maintain chronological order. - * 6. Returns the content of the most relevant messages. + * 该方法执行以下步骤: + * 1. 使用配置的嵌入器将查询嵌入为向量表示。 + * 2. 从数据库中检索指定用户发送的最新聊天消息。 + * 3. 计算查询嵌入与每条聊天消息嵌入之间的余弦相似度。 + * 4. 按相似度降序排序消息,并将结果限制为指定的数量。 + * 5. 将限制后的结果按原始发送时间升序重新排序,以保持时间顺序。 + * 6. 返回最相关消息的内容。 * - * @param query - The search query string to find similar messages. - * @param userId - The ID of the user whose messages are to be searched. - * @param count - The maximum number of messages to return. Defaults to 10. - * @returns A promise that resolves to an array of message contents sorted by relevance and chronological order. + * @param query - 用于查找相似消息的搜索查询字符串。 + * @param userId - 要搜索消息的用户ID。 + * @param count - 要返回的最大消息数量。默认为10。 + * @returns 一个Promise,解析为按相关性和时间顺序排序的消息内容数组。 */ - async searchConversation(query: string, userId: string, count: number = 10): Promise{ + async searchConversation(query: string, userId: string, count: number = 10): Promise { let embedding = await this.embedder.embed(query); let chatMessages = await this.ctx.database @@ -199,9 +210,17 @@ class Memory extends Service { namespace Memory { export interface Config { + memorySize: number; // 记忆容量 + summarySize: number; // 上下文达到多少时进行总结 + retainedContextSize: number; // 进行总结时保留的上下文长度,用于保持记忆连贯性 + maxCoreMemoryCharacters: number; // 最大记忆字符数 embedding: EmbeddingConfig; } export const Config: Schema = Schema.object({ + memorySize: Schema.number().default(1000), + summarySize: Schema.number().default(100), + retainedContextSize: Schema.number().default(10), + maxCoreMemoryCharacters: Schema.number().default(5000), embedding: EmbeddingConfig, }); } diff --git a/packages/memory/src/vectorStore.ts b/packages/memory/src/vectorStore.ts index 8563adf..0c3575c 100644 --- a/packages/memory/src/vectorStore.ts +++ b/packages/memory/src/vectorStore.ts @@ -4,7 +4,7 @@ import { Context } from "koishi"; import { defineAccessor } from "@satorijs/core"; import { CacheManager } from "koishi-plugin-yesimbot"; -import { calculateCosineSimilarity } from "koishi-plugin-yesimbot/embeddings"; +import { calculateCosineSimilarity } from "koishi-plugin-yesimbot"; import { MemoryItem, MemoryType } from "./model"; export interface MemoryMetadata { @@ -17,13 +17,6 @@ export interface MemoryMetadata { updatedAt: Date; } - -export interface MemoryVectorStore { - get(id: string): MemoryItem; - delete(id: string): boolean; - clear(): void; -} - export class MemoryVectorStore { readonly store: CacheManager; @@ -32,6 +25,14 @@ export class MemoryVectorStore { this.store = new CacheManager(vectorsFilePath, true); } + delete(id: string): boolean { + return this.store.delete(id); + } + + clear(): void { + this.store.clear(); + } + get(id: string): MemoryItem | undefined { return this.store.get(id); } @@ -136,10 +137,6 @@ export class MemoryVectorStore { } } -defineAccessor(MemoryVectorStore.prototype, "get", ["store", "get"]); -defineAccessor(MemoryVectorStore.prototype, "clear", ["store", "clear"]); -defineAccessor(MemoryVectorStore.prototype, "delete", ["store", "delete"]); - /** * 获取向量的模 * @param vector diff --git a/packages/webui/client/components/MemoryChart.vue b/packages/webui/client/components/MemoryChart.vue deleted file mode 100644 index 3fb6e4e..0000000 --- a/packages/webui/client/components/MemoryChart.vue +++ /dev/null @@ -1,50 +0,0 @@ - - - diff --git a/packages/webui/client/components/MemoryDialog.vue b/packages/webui/client/components/MemoryDialog.vue new file mode 100644 index 0000000..3ffe100 --- /dev/null +++ b/packages/webui/client/components/MemoryDialog.vue @@ -0,0 +1,255 @@ + + + + + diff --git a/packages/webui/client/components/MemoryForm.vue b/packages/webui/client/components/MemoryForm.vue deleted file mode 100644 index 160c419..0000000 --- a/packages/webui/client/components/MemoryForm.vue +++ /dev/null @@ -1,23 +0,0 @@ - - - diff --git a/packages/webui/client/components/MemoryList.vue b/packages/webui/client/components/MemoryList.vue index 9148fe8..6394141 100644 --- a/packages/webui/client/components/MemoryList.vue +++ b/packages/webui/client/components/MemoryList.vue @@ -1,42 +1,103 @@ - - loadMemories(); + diff --git a/packages/webui/client/components/MemorySearch.vue b/packages/webui/client/components/MemorySearch.vue new file mode 100644 index 0000000..4b3cabb --- /dev/null +++ b/packages/webui/client/components/MemorySearch.vue @@ -0,0 +1,204 @@ + + + + + diff --git a/packages/webui/client/components/MemoryStats.vue b/packages/webui/client/components/MemoryStats.vue new file mode 100644 index 0000000..e47c3f3 --- /dev/null +++ b/packages/webui/client/components/MemoryStats.vue @@ -0,0 +1,105 @@ + + + + + diff --git a/packages/webui/client/components/MemoryTable.vue b/packages/webui/client/components/MemoryTable.vue deleted file mode 100644 index 6c41dbc..0000000 --- a/packages/webui/client/components/MemoryTable.vue +++ /dev/null @@ -1,41 +0,0 @@ - - - diff --git a/packages/webui/client/index.ts b/packages/webui/client/index.ts index bca7580..0a6c536 100644 --- a/packages/webui/client/index.ts +++ b/packages/webui/client/index.ts @@ -1,12 +1,10 @@ import { Context } from "@koishijs/client"; -import Page from "./page.vue"; -import StatisticsPage from "./pages/StatisticsPage.vue"; -import EditPage from "./pages/EditPage.vue"; +import View from "./views/MemoryView.vue"; export default (ctx: Context) => { ctx.page({ name: "记忆管理", path: "/memory", - component: Page, + component: View, }); }; diff --git a/packages/webui/client/model.ts b/packages/webui/client/model.ts new file mode 100644 index 0000000..a287842 --- /dev/null +++ b/packages/webui/client/model.ts @@ -0,0 +1,6 @@ +export enum MemoryType { + Core = "核心记忆", + User = "用户记忆", + Group = "群成员记忆", + Knowledge = "通用知识", +} diff --git a/packages/webui/client/page.vue b/packages/webui/client/page.vue deleted file mode 100644 index ddeb505..0000000 --- a/packages/webui/client/page.vue +++ /dev/null @@ -1,402 +0,0 @@ - - - - - diff --git a/packages/webui/client/pages/EditPage.vue b/packages/webui/client/pages/EditPage.vue deleted file mode 100644 index 9fbdfd5..0000000 --- a/packages/webui/client/pages/EditPage.vue +++ /dev/null @@ -1,72 +0,0 @@ - - - diff --git a/packages/webui/client/pages/StatisticsPage.vue b/packages/webui/client/pages/StatisticsPage.vue deleted file mode 100644 index b93ec91..0000000 --- a/packages/webui/client/pages/StatisticsPage.vue +++ /dev/null @@ -1,48 +0,0 @@ - - - diff --git a/packages/webui/client/views/MemoryView.vue b/packages/webui/client/views/MemoryView.vue new file mode 100644 index 0000000..b402e1b --- /dev/null +++ b/packages/webui/client/views/MemoryView.vue @@ -0,0 +1,185 @@ + + + + + diff --git a/packages/webui/src/index.ts b/packages/webui/src/index.ts index bea0dae..7a19e3a 100644 --- a/packages/webui/src/index.ts +++ b/packages/webui/src/index.ts @@ -1,8 +1,8 @@ -// @ts-nocheck -import { resolve } from "path"; +import { } from "@koishijs/plugin-console"; import { Context } from "koishi"; -import {} from "@koishijs/plugin-console"; -import { Metadata } from "koishi-plugin-yesimbot-memory"; +import { MemoryItem, MemoryType } from "koishi-plugin-yesimbot-memory"; +import { resolve } from "path"; + import { Config } from "./config"; export const name = "yesimbot-webui"; @@ -15,10 +15,18 @@ export { Config } from "./config"; declare module "@koishijs/plugin-console" { interface Events { - "memory/getAll": () => Metadata[]; - "memory/addText": (content: string, tags: string[]) => Promise; - "memory/delete": (id: string) => void; - "memory/update": (id: string, data: any) => void; + "memory/get": (memoryId: string) => MemoryItem; + "memory/getAll": () => MemoryItem[]; + "memory/delete": (memoryId: string) => boolean; + "memory/clear": () => void; + "memory/modifyMemoryById": (memoryId: string, content: string, type?: MemoryType, topic?: string, keywords?: string[]) => Promise; + "memory/addCoreMemory": (content: string, topic?: string, keywords?: string[]) => Promise; + "memory/modifyCoreMemory": (oldContent: string, newContent: string) => Promise; + "memory/addUserMemory": (userId: string, content: string) => Promise; + "memory/modifyUserMemory": (userId: string, oldContent: string, newContent: string) => Promise; + "memory/addArchivalMemory": (content: string, type: MemoryType, topic: string, kerwords: string[]) => Promise; + "memory/searchArchivalMemory": (query: string, type: MemoryType, topic: string, kerwords: string[], count?: number) => Promise; + "memory/searchConversation": (query: string, userId: string, count?: number) => Promise; } } @@ -30,25 +38,58 @@ export function apply(ctx: Context, config: Config) { }); }); - // 提供后端 API - ctx.console.addListener("memory/getAll", () => { - ctx.logger.info("memory/getAll"); - return ctx.memory.getAll(); + ctx.console.addListener("memory/get", (memoryId) => { + ctx.logger.info("memory/get", memoryId); + return ctx.memory.get(memoryId); }); - ctx.console.addListener("memory/addText", (content: string, tags: string[]) => { - ctx.logger.info("memory/addText"); - return ctx.memory.addText(content); - } - ); + ctx.console.addListener('memory/getAll', () => { + ctx.logger.info("memory/getAll"); + return ctx.memory.getAll() + }) - ctx.console.addListener("memory/delete", (id: string) => { - ctx.logger.info("memory/delete"); - return ctx.memory.delete(id); + ctx.console.addListener("memory/delete", (memoryId) => { + ctx.logger.info("memory/delete", memoryId); + return ctx.memory.delete(memoryId); }); - ctx.console.addListener("memory/update", (id: string, data: any) => { - ctx.logger.info("memory/update"); - return ctx.memory.update(id, data); + ctx.console.addListener("memory/clear", () => { + ctx.logger.info("memory/clear"); + return ctx.memory.clear(); }); + + ctx.console.addListener("memory/modifyMemoryById", async (memoryId, content, type, topic, keywords)=>{ + ctx.logger.info("memory/modifyMemoryById", memoryId, content, type, topic, keywords) + return await ctx.memory.modifyMemoryById(memoryId, content, type, topic, keywords) + }) + + ctx.console.addListener('memory/addCoreMemory', async (content, topic, kerwords) => { + ctx.logger.info("memory/addCoreMemory", content, topic, kerwords) + return await ctx.memory.addCoreMemory(content, topic, kerwords) + }) + + ctx.console.addListener('memory/modifyCoreMemory', async (oldContent, newContent) => { + ctx.logger.info("memory/modifyCoreMemory", oldContent, newContent) + return await ctx.memory.modifyCoreMemory(oldContent, newContent) + }) + + ctx.console.addListener("memory/addUserMemory", async (userId, content) => { + ctx.logger.info("memory/addUserMemory", userId, content) + return await ctx.memory.addUserMemory(userId, content) + }) + + ctx.console.addListener("memory/modifyUserMemory", async (userId, oldContent, newContent) => { + ctx.logger.info("memory/modifyUserMemory", userId, oldContent, newContent) + return await ctx.memory.modifyUserMemory(userId, oldContent, newContent) + }) + + ctx.console.addListener("memory/addArchivalMemory", async (content, type, topic, kerwords) => { + ctx.logger.info("memory/addArchivalMemory", content) + return await ctx.memory.addArchivalMemory(content, type, topic, kerwords) + }) + + ctx.console.addListener("memory/searchArchivalMemory", async (query, type, topic, kerwords, count) => { + ctx.logger.info("memory/searchArchivalMemory", query, type, topic, kerwords, count) + return await ctx.memory.searchArchivalMemory(query, type, topic, kerwords, count) + }) }