stage simple webui
This commit is contained in:
@@ -9,7 +9,7 @@ class Result:
|
||||
id: str
|
||||
doc_id: str
|
||||
text: str
|
||||
metadata: dict
|
||||
metadata: str
|
||||
created_at: int
|
||||
updated_at: int
|
||||
|
||||
|
||||
@@ -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
|
||||
]
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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__)
|
||||
@@ -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/<path:subpath>",
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"console": "Console",
|
||||
"alkaid": "Alkaid Lab",
|
||||
"knowledgeBase": "Knowledge Base",
|
||||
"memory": "Long-term Memory",
|
||||
"about": "About",
|
||||
"settings": "Settings",
|
||||
"documentation": "Documentation",
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"console": "控制台",
|
||||
"alkaid": "Alkaid",
|
||||
"knowledgeBase": "知识库",
|
||||
"memory": "长期记忆",
|
||||
"about": "关于",
|
||||
"settings": "设置",
|
||||
"documentation": "官方文档",
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -90,6 +90,11 @@ const MainRoutes = {
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Memory',
|
||||
path: '/memory',
|
||||
component: () => import('@/views/MemoryPage.vue')
|
||||
},
|
||||
|
||||
// 旧版本的知识库路由
|
||||
{
|
||||
|
||||
@@ -0,0 +1,358 @@
|
||||
<template>
|
||||
<div class="memory-page">
|
||||
<v-container fluid class="pa-0">
|
||||
<!-- 页面标题 -->
|
||||
<v-row class="d-flex justify-space-between align-center px-4 py-3 pb-8">
|
||||
<div>
|
||||
<h1 class="text-h1 font-weight-bold mb-2">
|
||||
<v-icon color="black" class="me-2">mdi-brain</v-icon>{{ t('core.navigation.memory') }}
|
||||
</h1>
|
||||
<p class="text-subtitle-1 text-medium-emphasis mb-4">
|
||||
管理长期记忆系统的配置
|
||||
</p>
|
||||
</div>
|
||||
</v-row>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<v-row v-if="loading">
|
||||
<v-col cols="12">
|
||||
<v-card>
|
||||
<v-card-text class="text-center">
|
||||
<v-progress-circular indeterminate color="primary"></v-progress-circular>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- 主内容 -->
|
||||
<v-row v-else>
|
||||
<v-col cols="12" md="8" lg="6">
|
||||
<v-card rounded="lg">
|
||||
<v-card-title class="d-flex align-center">
|
||||
<v-icon class="mr-2">mdi-cog</v-icon>
|
||||
记忆系统配置
|
||||
</v-card-title>
|
||||
<v-divider></v-divider>
|
||||
|
||||
<v-card-text>
|
||||
<!-- 状态显示 -->
|
||||
<v-alert
|
||||
:type="memoryStatus.initialized ? 'success' : 'info'"
|
||||
variant="tonal"
|
||||
class="mb-4"
|
||||
>
|
||||
<div class="d-flex align-center">
|
||||
<v-icon class="mr-2">
|
||||
{{ memoryStatus.initialized ? 'mdi-check-circle' : 'mdi-information' }}
|
||||
</v-icon>
|
||||
<div>
|
||||
<strong>状态:</strong>
|
||||
{{ memoryStatus.initialized ? '已初始化' : '未初始化' }}
|
||||
</div>
|
||||
</div>
|
||||
</v-alert>
|
||||
|
||||
<!-- 未初始化时显示初始化表单 -->
|
||||
<div v-if="!memoryStatus.initialized">
|
||||
<v-form @submit.prevent="initializeMemory">
|
||||
<v-select
|
||||
v-model="selectedEmbeddingProvider"
|
||||
:items="embeddingProviders"
|
||||
item-title="text"
|
||||
item-value="value"
|
||||
label="Embedding 模型 *"
|
||||
hint="用于生成向量表示,初始化后不可更改"
|
||||
persistent-hint
|
||||
class="mb-4"
|
||||
required
|
||||
:disabled="initializing"
|
||||
></v-select>
|
||||
|
||||
<v-select
|
||||
v-model="selectedMergeLLM"
|
||||
:items="llmProviders"
|
||||
item-title="text"
|
||||
item-value="value"
|
||||
label="合并 LLM *"
|
||||
hint="用于合并相似记忆,可在初始化后更改"
|
||||
persistent-hint
|
||||
class="mb-4"
|
||||
required
|
||||
:disabled="initializing"
|
||||
></v-select>
|
||||
|
||||
<v-btn
|
||||
type="submit"
|
||||
color="primary"
|
||||
:loading="initializing"
|
||||
:disabled="!selectedEmbeddingProvider || !selectedMergeLLM"
|
||||
block
|
||||
size="large"
|
||||
>
|
||||
初始化记忆系统
|
||||
</v-btn>
|
||||
</v-form>
|
||||
</div>
|
||||
|
||||
<!-- 已初始化时显示配置信息 -->
|
||||
<div v-else>
|
||||
<v-list>
|
||||
<v-list-item>
|
||||
<template v-slot:prepend>
|
||||
<v-icon>mdi-vector-triangle</v-icon>
|
||||
</template>
|
||||
<v-list-item-title>Embedding 模型</v-list-item-title>
|
||||
<v-list-item-subtitle>
|
||||
{{ getProviderName(memoryStatus.embedding_provider_id) }}
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
|
||||
<v-divider class="my-2"></v-divider>
|
||||
|
||||
<v-list-item>
|
||||
<template v-slot:prepend>
|
||||
<v-icon>mdi-robot</v-icon>
|
||||
</template>
|
||||
<v-list-item-title>合并 LLM</v-list-item-title>
|
||||
<v-list-item-subtitle>
|
||||
{{ getProviderName(memoryStatus.merge_llm_provider_id) }}
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
|
||||
<v-divider class="my-4"></v-divider>
|
||||
|
||||
<v-form @submit.prevent="updateMergeLLM">
|
||||
<v-select
|
||||
v-model="newMergeLLM"
|
||||
:items="llmProviders"
|
||||
item-title="text"
|
||||
item-value="value"
|
||||
label="更新合并 LLM"
|
||||
hint="可以更换用于合并记忆的 LLM"
|
||||
persistent-hint
|
||||
class="mb-4"
|
||||
:disabled="updating"
|
||||
></v-select>
|
||||
|
||||
<v-btn
|
||||
type="submit"
|
||||
color="primary"
|
||||
:loading="updating"
|
||||
:disabled="!newMergeLLM || newMergeLLM === memoryStatus.merge_llm_provider_id"
|
||||
block
|
||||
variant="tonal"
|
||||
>
|
||||
更新合并 LLM
|
||||
</v-btn>
|
||||
</v-form>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<!-- 说明卡片 -->
|
||||
<v-col cols="12" md="4" lg="6">
|
||||
<v-card rounded="lg">
|
||||
<v-card-title class="d-flex align-center">
|
||||
<v-icon class="mr-2">mdi-information</v-icon>
|
||||
说明
|
||||
</v-card-title>
|
||||
<v-divider></v-divider>
|
||||
<v-card-text>
|
||||
<v-list density="compact">
|
||||
<v-list-item>
|
||||
<v-list-item-title class="text-wrap">
|
||||
<strong>Embedding 模型:</strong>用于将文本转换为向量,支持语义相似度搜索。
|
||||
<v-chip size="x-small" color="warning" class="ml-2">不可更改</v-chip>
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-list-item-title class="text-wrap">
|
||||
<strong>合并 LLM:</strong>当检测到相似记忆时,使用此模型合并为一条记忆。
|
||||
<v-chip size="x-small" color="success" class="ml-2">可更改</v-chip>
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-list-item-title class="text-wrap">
|
||||
<strong>注意:</strong>Embedding 模型一旦选择后无法更改,请谨慎选择。
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
|
||||
<!-- 提示框 -->
|
||||
<v-snackbar v-model="snackbar.show" :color="snackbar.color" :timeout="3000">
|
||||
{{ snackbar.message }}
|
||||
</v-snackbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import axios from 'axios';
|
||||
import { useI18n } from '@/i18n/composables';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
interface MemoryStatus {
|
||||
initialized: boolean;
|
||||
embedding_provider_id: string | null;
|
||||
merge_llm_provider_id: string | null;
|
||||
}
|
||||
|
||||
interface Provider {
|
||||
value: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
const loading = ref(true);
|
||||
const initializing = ref(false);
|
||||
const updating = ref(false);
|
||||
|
||||
const memoryStatus = ref<MemoryStatus>({
|
||||
initialized: false,
|
||||
embedding_provider_id: null,
|
||||
merge_llm_provider_id: null,
|
||||
});
|
||||
|
||||
const embeddingProviders = ref<Provider[]>([]);
|
||||
const llmProviders = ref<Provider[]>([]);
|
||||
|
||||
const selectedEmbeddingProvider = ref<string>('');
|
||||
const selectedMergeLLM = ref<string>('');
|
||||
const newMergeLLM = ref<string>('');
|
||||
|
||||
const snackbar = ref({
|
||||
show: false,
|
||||
message: '',
|
||||
color: 'success',
|
||||
});
|
||||
|
||||
const showMessage = (message: string, color: string = 'success') => {
|
||||
snackbar.value.message = message;
|
||||
snackbar.value.color = color;
|
||||
snackbar.value.show = true;
|
||||
};
|
||||
|
||||
const getProviderName = (providerId: string | null): string => {
|
||||
if (!providerId) return '未设置';
|
||||
const embedding = embeddingProviders.value.find(p => p.value === providerId);
|
||||
const llm = llmProviders.value.find(p => p.value === providerId);
|
||||
return embedding?.text || llm?.text || providerId;
|
||||
};
|
||||
|
||||
const loadProviders = async () => {
|
||||
try {
|
||||
// Load embedding providers
|
||||
const embeddingResponse = await axios.get('/api/config/provider/list', {
|
||||
params: { provider_type: 'embedding' }
|
||||
});
|
||||
if (embeddingResponse.data.status === 'ok') {
|
||||
embeddingProviders.value = (embeddingResponse.data.data || []).map((p: any) => ({
|
||||
value: p.id,
|
||||
text: `${p.embedding_model} (${p.id})`,
|
||||
}));
|
||||
}
|
||||
|
||||
// Load LLM providers
|
||||
const llmResponse = await axios.get('/api/config/provider/list', {
|
||||
params: { provider_type: 'chat_completion' }
|
||||
});
|
||||
if (llmResponse.data.status === 'ok') {
|
||||
llmProviders.value = (llmResponse.data.data || []).map((p: any) => ({
|
||||
value: p.id,
|
||||
text: `${p?.model_config?.model} (${p.id})`,
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load providers:', error);
|
||||
showMessage('加载提供商列表失败', 'error');
|
||||
}
|
||||
};
|
||||
|
||||
const loadStatus = async () => {
|
||||
try {
|
||||
const response = await axios.get('/api/memory/status');
|
||||
if (response.data.status === 'ok') {
|
||||
memoryStatus.value = response.data.data;
|
||||
if (memoryStatus.value.merge_llm_provider_id) {
|
||||
newMergeLLM.value = memoryStatus.value.merge_llm_provider_id;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load memory status:', error);
|
||||
showMessage('加载记忆系统状态失败', 'error');
|
||||
}
|
||||
};
|
||||
|
||||
const initializeMemory = async () => {
|
||||
if (!selectedEmbeddingProvider.value || !selectedMergeLLM.value) {
|
||||
showMessage('请选择 Embedding 模型和合并 LLM', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
initializing.value = true;
|
||||
try {
|
||||
const response = await axios.post('/api/memory/initialize', {
|
||||
embedding_provider_id: selectedEmbeddingProvider.value,
|
||||
merge_llm_provider_id: selectedMergeLLM.value,
|
||||
});
|
||||
|
||||
if (response.data.status === 'ok') {
|
||||
showMessage('记忆系统初始化成功', 'success');
|
||||
await loadStatus();
|
||||
} else {
|
||||
showMessage(response.data.message || '初始化失败', 'error');
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('Failed to initialize memory:', error);
|
||||
showMessage(error.response?.data?.message || '初始化失败', 'error');
|
||||
} finally {
|
||||
initializing.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const updateMergeLLM = async () => {
|
||||
if (!newMergeLLM.value) {
|
||||
showMessage('请选择新的合并 LLM', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
updating.value = true;
|
||||
try {
|
||||
const response = await axios.post('/api/memory/update_merge_llm', {
|
||||
merge_llm_provider_id: newMergeLLM.value,
|
||||
});
|
||||
|
||||
if (response.data.status === 'ok') {
|
||||
showMessage('合并 LLM 更新成功', 'success');
|
||||
await loadStatus();
|
||||
} else {
|
||||
showMessage(response.data.message || '更新失败', 'error');
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('Failed to update merge LLM:', error);
|
||||
showMessage(error.response?.data?.message || '更新失败', 'error');
|
||||
} finally {
|
||||
updating.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
loading.value = true;
|
||||
await Promise.all([loadProviders(), loadStatus()]);
|
||||
loading.value = false;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.memory-page {
|
||||
min-height: 100vh;
|
||||
padding: 8px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user