diff --git a/astrbot/core/db/vec_db/base.py b/astrbot/core/db/vec_db/base.py index 6c5133df..732de243 100644 --- a/astrbot/core/db/vec_db/base.py +++ b/astrbot/core/db/vec_db/base.py @@ -9,7 +9,7 @@ class Result: id: str doc_id: str text: str - metadata: dict + metadata: str created_at: int updated_at: int diff --git a/astrbot/core/memory/memory_manager.py b/astrbot/core/memory/memory_manager.py index d45c1823..b286725a 100644 --- a/astrbot/core/memory/memory_manager.py +++ b/astrbot/core/memory/memory_manager.py @@ -1,3 +1,4 @@ +import json import uuid from datetime import datetime, timezone from pathlib import Path @@ -114,7 +115,7 @@ class MemoryManager: # Get all candidate memories from database candidate_memories: list[tuple[str, MemoryChunk]] = [] for candidate in merge_candidates: - mem_id = candidate.data["metadata"]["mem_id"] + mem_id = json.loads(candidate.data["metadata"])["mem_id"] memory = await self.mem_db.get_memory_by_id(mem_id) if memory: candidate_memories.append((mem_id, memory)) @@ -184,7 +185,7 @@ class MemoryManager: # Step 4: Apply Hebbian learning to similar memories hebb_mem_ids = [ - r.data["metadata"]["mem_id"] + json.loads(r.data["metadata"])["mem_id"] for r in similar_results if r.similarity >= HEBB_THRESHOLD ] diff --git a/astrbot/dashboard/routes/__init__.py b/astrbot/dashboard/routes/__init__.py index b7997cf8..2a22d8b5 100644 --- a/astrbot/dashboard/routes/__init__.py +++ b/astrbot/dashboard/routes/__init__.py @@ -5,6 +5,7 @@ from .conversation import ConversationRoute from .file import FileRoute from .knowledge_base import KnowledgeBaseRoute from .log import LogRoute +from .memory import MemoryRoute from .persona import PersonaRoute from .plugin import PluginRoute from .session_management import SessionManagementRoute @@ -21,6 +22,7 @@ __all__ = [ "FileRoute", "KnowledgeBaseRoute", "LogRoute", + "MemoryRoute", "PersonaRoute", "PluginRoute", "SessionManagementRoute", diff --git a/astrbot/dashboard/routes/memory.py b/astrbot/dashboard/routes/memory.py new file mode 100644 index 00000000..534e7b1f --- /dev/null +++ b/astrbot/dashboard/routes/memory.py @@ -0,0 +1,174 @@ +"""Memory management API routes""" + +from quart import jsonify, request + +from astrbot.core import logger +from astrbot.core.core_lifecycle import AstrBotCoreLifecycle +from astrbot.core.db import BaseDatabase + +from .route import Response, Route, RouteContext + + +class MemoryRoute(Route): + """Memory management routes""" + + def __init__( + self, + context: RouteContext, + db: BaseDatabase, + core_lifecycle: AstrBotCoreLifecycle, + ): + super().__init__(context) + self.db = db + self.core_lifecycle = core_lifecycle + self.memory_manager = core_lifecycle.memory_manager + self.provider_manager = core_lifecycle.provider_manager + self.routes = [ + ("/memory/status", ("GET", self.get_status)), + ("/memory/initialize", ("POST", self.initialize)), + ("/memory/update_merge_llm", ("POST", self.update_merge_llm)), + ] + self.register_routes() + + async def get_status(self): + """Get memory system status""" + try: + is_initialized = self.memory_manager._initialized + + status_data = { + "initialized": is_initialized, + "embedding_provider_id": None, + "merge_llm_provider_id": None, + } + + if is_initialized: + # Get embedding provider info + if self.memory_manager.embedding_provider: + status_data["embedding_provider_id"] = ( + self.memory_manager.embedding_provider.provider_config["id"] + ) + # Get merge LLM provider info + if self.memory_manager.merge_llm_provider: + status_data["merge_llm_provider_id"] = ( + self.memory_manager.merge_llm_provider.provider_config["id"] + ) + + return jsonify(Response().ok(status_data).__dict__) + except Exception as e: + logger.error(f"Failed to get memory status: {e}") + return jsonify(Response().error(str(e)).__dict__) + + async def initialize(self): + """Initialize memory system with embedding and merge LLM providers""" + try: + data = await request.get_json() + embedding_provider_id = data.get("embedding_provider_id") + merge_llm_provider_id = data.get("merge_llm_provider_id") + + if not embedding_provider_id or not merge_llm_provider_id: + return jsonify( + Response() + .error( + "embedding_provider_id and merge_llm_provider_id are required" + ) + .__dict__, + ) + + # Check if already initialized + if self.memory_manager._initialized: + return jsonify( + Response() + .error( + "Memory system already initialized. Embedding provider cannot be changed.", + ) + .__dict__, + ) + + # Get providers + embedding_provider = await self.provider_manager.get_provider_by_id( + embedding_provider_id, + ) + merge_llm_provider = await self.provider_manager.get_provider_by_id( + merge_llm_provider_id, + ) + + if not embedding_provider: + return jsonify( + Response() + .error(f"Embedding provider {embedding_provider_id} not found") + .__dict__, + ) + + if not merge_llm_provider: + return jsonify( + Response() + .error(f"Merge LLM provider {merge_llm_provider_id} not found") + .__dict__, + ) + + # Initialize memory manager + await self.memory_manager.initialize( + embedding_provider=embedding_provider, + merge_llm_provider=merge_llm_provider, + ) + + logger.info( + f"Memory system initialized with embedding: {embedding_provider_id}, " + f"merge LLM: {merge_llm_provider_id}", + ) + + return jsonify( + Response() + .ok({"message": "Memory system initialized successfully"}) + .__dict__, + ) + + except Exception as e: + logger.error(f"Failed to initialize memory system: {e}") + return jsonify(Response().error(str(e)).__dict__) + + async def update_merge_llm(self): + """Update merge LLM provider (only allowed after initialization)""" + try: + data = await request.get_json() + merge_llm_provider_id = data.get("merge_llm_provider_id") + + if not merge_llm_provider_id: + return jsonify( + Response().error("merge_llm_provider_id is required").__dict__, + ) + + # Check if initialized + if not self.memory_manager._initialized: + return jsonify( + Response() + .error("Memory system not initialized. Please initialize first.") + .__dict__, + ) + + # Get new merge LLM provider + merge_llm_provider = await self.provider_manager.get_provider_by_id( + merge_llm_provider_id, + ) + + if not merge_llm_provider: + return jsonify( + Response() + .error(f"Merge LLM provider {merge_llm_provider_id} not found") + .__dict__, + ) + + # Update merge LLM provider + self.memory_manager.merge_llm_provider = merge_llm_provider + + logger.info(f"Updated merge LLM provider to: {merge_llm_provider_id}") + + return jsonify( + Response() + .ok({"message": "Merge LLM provider updated successfully"}) + .__dict__, + ) + + except Exception as e: + logger.error(f"Failed to update merge LLM provider: {e}") + return jsonify(Response().error(str(e)).__dict__) diff --git a/astrbot/dashboard/server.py b/astrbot/dashboard/server.py index 84976f2b..504a4afe 100644 --- a/astrbot/dashboard/server.py +++ b/astrbot/dashboard/server.py @@ -79,6 +79,7 @@ class AstrBotDashboard: self.persona_route = PersonaRoute(self.context, db, core_lifecycle) self.t2i_route = T2iRoute(self.context, core_lifecycle) self.kb_route = KnowledgeBaseRoute(self.context, core_lifecycle) + self.memory_route = MemoryRoute(self.context, db, core_lifecycle) self.app.add_url_rule( "/api/plug/", diff --git a/dashboard/src/i18n/locales/en-US/core/navigation.json b/dashboard/src/i18n/locales/en-US/core/navigation.json index 9351d1da..fbe81ab9 100644 --- a/dashboard/src/i18n/locales/en-US/core/navigation.json +++ b/dashboard/src/i18n/locales/en-US/core/navigation.json @@ -12,6 +12,7 @@ "console": "Console", "alkaid": "Alkaid Lab", "knowledgeBase": "Knowledge Base", + "memory": "Long-term Memory", "about": "About", "settings": "Settings", "documentation": "Documentation", diff --git a/dashboard/src/i18n/locales/zh-CN/core/navigation.json b/dashboard/src/i18n/locales/zh-CN/core/navigation.json index 61f69d9c..db89cd22 100644 --- a/dashboard/src/i18n/locales/zh-CN/core/navigation.json +++ b/dashboard/src/i18n/locales/zh-CN/core/navigation.json @@ -12,6 +12,7 @@ "console": "控制台", "alkaid": "Alkaid", "knowledgeBase": "知识库", + "memory": "长期记忆", "about": "关于", "settings": "设置", "documentation": "官方文档", diff --git a/dashboard/src/layouts/full/vertical-sidebar/sidebarItem.ts b/dashboard/src/layouts/full/vertical-sidebar/sidebarItem.ts index 3203985c..0c241546 100644 --- a/dashboard/src/layouts/full/vertical-sidebar/sidebarItem.ts +++ b/dashboard/src/layouts/full/vertical-sidebar/sidebarItem.ts @@ -48,6 +48,11 @@ const sidebarItem: menu[] = [ icon: 'mdi-book-open-variant', to: '/knowledge-base', }, + { + title: 'core.navigation.memory', + icon: 'mdi-brain', + to: '/memory', + }, { title: 'core.navigation.chat', icon: 'mdi-chat', diff --git a/dashboard/src/router/MainRoutes.ts b/dashboard/src/router/MainRoutes.ts index 276d3744..f85bdc29 100644 --- a/dashboard/src/router/MainRoutes.ts +++ b/dashboard/src/router/MainRoutes.ts @@ -90,6 +90,11 @@ const MainRoutes = { } ] }, + { + name: 'Memory', + path: '/memory', + component: () => import('@/views/MemoryPage.vue') + }, // 旧版本的知识库路由 { diff --git a/dashboard/src/views/MemoryPage.vue b/dashboard/src/views/MemoryPage.vue new file mode 100644 index 00000000..b0a8d315 --- /dev/null +++ b/dashboard/src/views/MemoryPage.vue @@ -0,0 +1,358 @@ + + + + +