refactor: 将 A_Memorix 重构为主线长期记忆子系统并重建管理界面
- 将 A_Memorix 从旧 submodule / 插件形态迁入主线源码,主体落到 src/A_memorix - 调整主程序接入方式,使 A_Memorix 作为源码内长期记忆子系统运行 - 回收父项目插件体系中针对 A_Memorix 的特判,减少对 plugin 通用层的侵入 - 将长期记忆配置、运行时、自检、导入、调优等能力收口到 memory 路由与主线服务层 - 重做长期记忆控制台与图谱页面,按 MaiBot 现有 dashboard 风格接入 - 补充实体关系图与证据视图双视图能力,支持查看节点、关系、段落及其证据链路 - 新增长期记忆配置编辑器与 memory-api,支持主线内配置管理 - 补齐删除管理能力:删除预览、混合删除、来源批量删除、删除操作恢复 - 优化删除预览与删除操作详情的前端展示,支持分页、检索,并以实体名/关系内容/段落摘要替代单纯 hash 展示 - 修复图谱与控制台相关前端问题,包括证据视图切换、查询触发时机、删除弹层空值保护等 - 新增或更新 A_Memorix 相关测试、WebUI 路由测试、前端 vitest 测试与辅助验证脚本 - 移除旧 plugins/A_memorix、.gitmodules 及相关历史维护文档
This commit is contained in:
@@ -6,14 +6,16 @@ import uuid
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional
|
||||
|
||||
from fastapi import APIRouter, Body, Depends, File, Form, Query, UploadFile
|
||||
import tomlkit
|
||||
from fastapi import APIRouter, Body, Depends, File, Form, HTTPException, Query, UploadFile
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from src.A_memorix.host_service import a_memorix_host_service
|
||||
from src.services.memory_service import MemorySearchResult, memory_service
|
||||
from src.webui.dependencies import require_auth
|
||||
|
||||
|
||||
router = APIRouter(prefix="/api/webui/memory", tags=["memory"], dependencies=[Depends(require_auth)])
|
||||
router = APIRouter(prefix="/memory", tags=["memory"], dependencies=[Depends(require_auth)])
|
||||
compat_router = APIRouter(prefix="/api", tags=["memory-compat"], dependencies=[Depends(require_auth)])
|
||||
STAGING_ROOT = Path(__file__).resolve().parents[3] / "data" / "memory_upload_staging"
|
||||
|
||||
@@ -82,6 +84,14 @@ class AutoSaveRequest(BaseModel):
|
||||
enabled: bool
|
||||
|
||||
|
||||
class MemoryConfigUpdateRequest(BaseModel):
|
||||
config: dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class MemoryRawConfigUpdateRequest(BaseModel):
|
||||
config: str = ""
|
||||
|
||||
|
||||
class TuningApplyProfileRequest(BaseModel):
|
||||
profile: dict[str, Any] = Field(default_factory=dict)
|
||||
reason: str = "manual"
|
||||
@@ -158,6 +168,44 @@ async def _graph_get(limit: int) -> dict:
|
||||
return await memory_service.graph_admin(action="get_graph", limit=limit)
|
||||
|
||||
|
||||
async def _graph_get_node_detail(
|
||||
node_id: str,
|
||||
*,
|
||||
relation_limit: int,
|
||||
paragraph_limit: int,
|
||||
evidence_node_limit: int,
|
||||
) -> dict:
|
||||
payload = await memory_service.graph_admin(
|
||||
action="node_detail",
|
||||
node_id=node_id,
|
||||
relation_limit=relation_limit,
|
||||
paragraph_limit=paragraph_limit,
|
||||
evidence_node_limit=evidence_node_limit,
|
||||
)
|
||||
if not bool(payload.get("success", False)):
|
||||
raise HTTPException(status_code=404, detail=str(payload.get("error", "未找到节点详情")))
|
||||
return payload
|
||||
|
||||
|
||||
async def _graph_get_edge_detail(
|
||||
source: str,
|
||||
target: str,
|
||||
*,
|
||||
paragraph_limit: int,
|
||||
evidence_node_limit: int,
|
||||
) -> dict:
|
||||
payload = await memory_service.graph_admin(
|
||||
action="edge_detail",
|
||||
source=source,
|
||||
target=target,
|
||||
paragraph_limit=paragraph_limit,
|
||||
evidence_node_limit=evidence_node_limit,
|
||||
)
|
||||
if not bool(payload.get("success", False)):
|
||||
raise HTTPException(status_code=404, detail=str(payload.get("error", "未找到边详情")))
|
||||
return payload
|
||||
|
||||
|
||||
async def _graph_create_node(payload: NodeRequest) -> dict:
|
||||
return await memory_service.graph_admin(action="create_node", name=payload.name)
|
||||
|
||||
@@ -325,6 +373,42 @@ async def _runtime_auto_save(enabled: bool | None = None) -> dict:
|
||||
return await memory_service.runtime_admin(action="set_auto_save", enabled=enabled)
|
||||
|
||||
|
||||
async def _memory_config_schema() -> dict:
|
||||
return {
|
||||
"success": True,
|
||||
"schema": a_memorix_host_service.get_config_schema(),
|
||||
"path": str(a_memorix_host_service.get_config_path()),
|
||||
}
|
||||
|
||||
|
||||
async def _memory_config_get() -> dict:
|
||||
return {
|
||||
"success": True,
|
||||
"config": a_memorix_host_service.get_config(),
|
||||
"path": str(a_memorix_host_service.get_config_path()),
|
||||
}
|
||||
|
||||
|
||||
async def _memory_config_get_raw() -> dict:
|
||||
return {
|
||||
"success": True,
|
||||
"config": a_memorix_host_service.get_raw_config(),
|
||||
"path": str(a_memorix_host_service.get_config_path()),
|
||||
}
|
||||
|
||||
|
||||
async def _memory_config_update(payload: MemoryConfigUpdateRequest) -> dict:
|
||||
return await a_memorix_host_service.update_config(payload.config)
|
||||
|
||||
|
||||
async def _memory_config_update_raw(payload: MemoryRawConfigUpdateRequest) -> dict:
|
||||
try:
|
||||
tomlkit.loads(payload.config)
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=400, detail=f"TOML 格式错误: {exc}") from exc
|
||||
return await a_memorix_host_service.update_raw_config(payload.config)
|
||||
|
||||
|
||||
async def _maintenance_recycle_bin(limit: int) -> dict:
|
||||
return await memory_service.get_recycle_bin(limit=limit)
|
||||
|
||||
@@ -565,6 +649,36 @@ async def get_memory_graph(limit: int = Query(200, ge=1, le=5000)):
|
||||
return await _graph_get(limit)
|
||||
|
||||
|
||||
@router.get("/graph/node-detail")
|
||||
async def get_memory_graph_node_detail(
|
||||
node_id: str = Query(..., min_length=1),
|
||||
relation_limit: int = Query(20, ge=1, le=100),
|
||||
paragraph_limit: int = Query(20, ge=1, le=100),
|
||||
evidence_node_limit: int = Query(80, ge=12, le=200),
|
||||
):
|
||||
return await _graph_get_node_detail(
|
||||
node_id,
|
||||
relation_limit=relation_limit,
|
||||
paragraph_limit=paragraph_limit,
|
||||
evidence_node_limit=evidence_node_limit,
|
||||
)
|
||||
|
||||
|
||||
@router.get("/graph/edge-detail")
|
||||
async def get_memory_graph_edge_detail(
|
||||
source: str = Query(..., min_length=1),
|
||||
target: str = Query(..., min_length=1),
|
||||
paragraph_limit: int = Query(20, ge=1, le=100),
|
||||
evidence_node_limit: int = Query(80, ge=12, le=200),
|
||||
):
|
||||
return await _graph_get_edge_detail(
|
||||
source,
|
||||
target,
|
||||
paragraph_limit=paragraph_limit,
|
||||
evidence_node_limit=evidence_node_limit,
|
||||
)
|
||||
|
||||
|
||||
@router.post("/graph/node")
|
||||
async def create_memory_node(payload: NodeRequest):
|
||||
return await _graph_create_node(payload)
|
||||
@@ -703,6 +817,31 @@ async def save_memory_runtime():
|
||||
return await _runtime_save()
|
||||
|
||||
|
||||
@router.get("/config/schema")
|
||||
async def get_memory_config_schema():
|
||||
return await _memory_config_schema()
|
||||
|
||||
|
||||
@router.get("/config")
|
||||
async def get_memory_config():
|
||||
return await _memory_config_get()
|
||||
|
||||
|
||||
@router.put("/config")
|
||||
async def update_memory_config(payload: MemoryConfigUpdateRequest):
|
||||
return await _memory_config_update(payload)
|
||||
|
||||
|
||||
@router.get("/config/raw")
|
||||
async def get_memory_config_raw():
|
||||
return await _memory_config_get_raw()
|
||||
|
||||
|
||||
@router.put("/config/raw")
|
||||
async def update_memory_config_raw(payload: MemoryRawConfigUpdateRequest):
|
||||
return await _memory_config_update_raw(payload)
|
||||
|
||||
|
||||
@router.get("/runtime/config")
|
||||
async def get_memory_runtime_config():
|
||||
return await _runtime_config()
|
||||
|
||||
Reference in New Issue
Block a user