fix:移动A_memorix配置项到botconfig

This commit is contained in:
DawnARC
2026-05-03 20:15:30 +08:00
parent 9090b72746
commit 87716d18b7
6 changed files with 510 additions and 77 deletions

View File

@@ -1,11 +1,10 @@
"""SDK runtime exports for A_Memorix.""" """SDK runtime exports for A_Memorix."""
from .search_runtime_initializer import ( from __future__ import annotations
SearchRuntimeBundle,
SearchRuntimeInitializer, from typing import Any
build_search_runtime,
) from .search_runtime_initializer import SearchRuntimeBundle, SearchRuntimeInitializer, build_search_runtime
from .sdk_memory_kernel import KernelSearchRequest, SDKMemoryKernel
__all__ = [ __all__ = [
"SearchRuntimeBundle", "SearchRuntimeBundle",
@@ -14,3 +13,14 @@ __all__ = [
"KernelSearchRequest", "KernelSearchRequest",
"SDKMemoryKernel", "SDKMemoryKernel",
] ]
def __getattr__(name: str) -> Any:
if name in {"KernelSearchRequest", "SDKMemoryKernel"}:
from .sdk_memory_kernel import KernelSearchRequest, SDKMemoryKernel
return {
"KernelSearchRequest": KernelSearchRequest,
"SDKMemoryKernel": SDKMemoryKernel,
}[name]
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

View File

@@ -4,20 +4,35 @@ import asyncio
import json import json
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
from typing import Any, Dict, Optional from typing import TYPE_CHECKING, Any, Dict, Optional, Sequence
import tomlkit import tomlkit
from src.common.logger import get_logger from src.common.logger import get_logger
from src.webui.utils.toml_utils import save_toml_with_format from src.config.official_configs import AMemorixConfig
from src.webui.utils.toml_utils import _update_toml_doc
from .core.runtime.sdk_memory_kernel import KernelSearchRequest, SDKMemoryKernel from .paths import repo_root, schema_path
from .paths import config_path, repo_root, schema_path
from .runtime_registry import set_runtime_kernel from .runtime_registry import set_runtime_kernel
if TYPE_CHECKING:
from .core.runtime.sdk_memory_kernel import SDKMemoryKernel
logger = get_logger("a_memorix.host_service") logger = get_logger("a_memorix.host_service")
def _get_config_manager():
from src.config.config import config_manager
return config_manager
def _get_bot_config_path() -> Path:
from src.config.config import BOT_CONFIG_PATH
return BOT_CONFIG_PATH
def _to_builtin_data(obj: Any) -> Any: def _to_builtin_data(obj: Any) -> Any:
if hasattr(obj, "unwrap"): if hasattr(obj, "unwrap"):
try: try:
@@ -46,6 +61,7 @@ class AMemorixHostService:
self._lock = asyncio.Lock() self._lock = asyncio.Lock()
self._kernel: Optional[SDKMemoryKernel] = None self._kernel: Optional[SDKMemoryKernel] = None
self._config_cache: Dict[str, Any] | None = None self._config_cache: Dict[str, Any] | None = None
self._reload_callback_registered = False
async def start(self) -> None: async def start(self) -> None:
if not self.is_enabled(): if not self.is_enabled():
@@ -69,7 +85,7 @@ class AMemorixHostService:
logger.info("A_Memorix 配置为未启用,运行时保持关闭") logger.info("A_Memorix 配置为未启用,运行时保持关闭")
def get_config_path(self) -> Path: def get_config_path(self) -> Path:
return config_path() return _get_bot_config_path()
def get_schema_path(self) -> Path: def get_schema_path(self) -> Path:
return schema_path() return schema_path()
@@ -106,53 +122,17 @@ class AMemorixHostService:
return bool(plugin_config.get("enabled", True)) return bool(plugin_config.get("enabled", True))
def _build_default_config(self) -> Dict[str, Any]: def _build_default_config(self) -> Dict[str, Any]:
schema = self.get_config_schema() return self._config_model_to_runtime_dict(AMemorixConfig())
sections = schema.get("sections") if isinstance(schema, dict) else None
if not isinstance(sections, dict):
return {}
defaults: Dict[str, Any] = {}
for section_name, section_payload in sections.items():
if not isinstance(section_payload, dict):
continue
fields = section_payload.get("fields")
if not isinstance(fields, dict):
continue
section_parts = [part for part in str(section_name or "").split(".") if part]
if not section_parts:
continue
section_target: Dict[str, Any] = defaults
for part in section_parts:
nested = section_target.get(part)
if not isinstance(nested, dict):
nested = {}
section_target[part] = nested
section_target = nested
for field_name, field_payload in fields.items():
if not isinstance(field_payload, dict) or "default" not in field_payload:
continue
section_target[str(field_name)] = _to_builtin_data(field_payload.get("default"))
return defaults
def get_raw_config_with_meta(self) -> Dict[str, Any]: def get_raw_config_with_meta(self) -> Dict[str, Any]:
path = self.get_config_path() config = self.get_config()
if path.exists():
return {
"config": path.read_text(encoding="utf-8"),
"exists": True,
"using_default": False,
}
default_config = self._build_default_config() default_config = self._build_default_config()
default_raw = tomlkit.dumps(default_config) if default_config else "" raw_doc = tomlkit.document()
raw_doc.add("a_memorix", config)
return { return {
"config": default_raw, "config": tomlkit.dumps(raw_doc),
"exists": False, "exists": self.get_config_path().exists(),
"using_default": True, "using_default": config == default_config,
} }
def get_raw_config(self) -> str: def get_raw_config(self) -> str:
@@ -160,12 +140,10 @@ class AMemorixHostService:
return str(payload.get("config", "") or "") return str(payload.get("config", "") or "")
async def update_raw_config(self, raw_config: str) -> Dict[str, Any]: async def update_raw_config(self, raw_config: str) -> Dict[str, Any]:
tomlkit.loads(raw_config) loaded = tomlkit.loads(raw_config)
path = self.get_config_path() raw_payload = _to_builtin_data(loaded) if isinstance(loaded, dict) else {}
path.parent.mkdir(parents=True, exist_ok=True) config_payload = raw_payload.get("a_memorix") if isinstance(raw_payload.get("a_memorix"), dict) else raw_payload
backup_path = _backup_config_file(path) path, backup_path = await self._write_config_to_bot_config(config_payload)
path.write_text(raw_config, encoding="utf-8")
await self.reload()
return { return {
"success": True, "success": True,
"message": "配置已保存", "message": "配置已保存",
@@ -174,11 +152,7 @@ class AMemorixHostService:
} }
async def update_config(self, config: Dict[str, Any]) -> Dict[str, Any]: async def update_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
path = self.get_config_path() path, backup_path = await self._write_config_to_bot_config(config)
path.parent.mkdir(parents=True, exist_ok=True)
backup_path = _backup_config_file(path)
save_toml_with_format(config, str(path), preserve_comments=True)
await self.reload()
return { return {
"success": True, "success": True,
"message": "配置已保存", "message": "配置已保存",
@@ -194,6 +168,8 @@ class AMemorixHostService:
kernel = await self._ensure_kernel() kernel = await self._ensure_kernel()
if component_name == "search_memory": if component_name == "search_memory":
from .core.runtime.sdk_memory_kernel import KernelSearchRequest
return await kernel.search_memory( return await kernel.search_memory(
KernelSearchRequest( KernelSearchRequest(
query=str(payload.get("query", "") or ""), query=str(payload.get("query", "") or ""),
@@ -297,6 +273,8 @@ class AMemorixHostService:
async def _ensure_kernel(self) -> SDKMemoryKernel: async def _ensure_kernel(self) -> SDKMemoryKernel:
async with self._lock: async with self._lock:
if self._kernel is None: if self._kernel is None:
from .core.runtime.sdk_memory_kernel import SDKMemoryKernel
config = self._read_config() config = self._read_config()
if not self._is_enabled_config(config): if not self._is_enabled_config(config):
raise RuntimeError("A_Memorix 未启用") raise RuntimeError("A_Memorix 未启用")
@@ -314,24 +292,72 @@ class AMemorixHostService:
if self._config_cache is not None: if self._config_cache is not None:
return dict(self._config_cache) return dict(self._config_cache)
path = self.get_config_path()
if not path.exists():
defaults = self._build_default_config()
self._config_cache = defaults
return dict(defaults)
try: try:
with path.open("r", encoding="utf-8") as handle: config_model = _get_config_manager().get_global_config().a_memorix
loaded = tomlkit.load(handle)
except Exception as exc: except Exception as exc:
logger.warning("读取 A_Memorix 配置失败 %s: %s", path, exc) logger.warning("读取 A_Memorix 配置失败,使用默认值: %s", exc)
defaults = self._build_default_config() defaults = self._build_default_config()
self._config_cache = defaults self._config_cache = defaults
return dict(defaults) return dict(defaults)
self._config_cache = _to_builtin_data(loaded) if isinstance(loaded, dict) else {} self._config_cache = self._config_model_to_runtime_dict(config_model)
return dict(self._config_cache) return dict(self._config_cache)
@staticmethod
def _config_model_to_runtime_dict(config_model: AMemorixConfig) -> Dict[str, Any]:
payload = config_model.model_dump(mode="json")
web_config = payload.get("web")
if isinstance(web_config, dict) and "import_config" in web_config:
web_config["import"] = web_config.pop("import_config")
return _to_builtin_data(payload) if isinstance(payload, dict) else {}
@staticmethod
def _runtime_dict_to_bot_config_dict(config: Dict[str, Any]) -> Dict[str, Any]:
payload = _to_builtin_data(config)
if not isinstance(payload, dict):
return {}
web_config = payload.get("web")
if isinstance(web_config, dict) and "import_config" in web_config and "import" not in web_config:
web_config["import"] = web_config.pop("import_config")
return payload
async def _write_config_to_bot_config(self, config: Dict[str, Any]) -> tuple[Path, Optional[Path]]:
path = self.get_config_path()
path.parent.mkdir(parents=True, exist_ok=True)
backup_path = _backup_config_file(path)
if path.exists():
with path.open("r", encoding="utf-8") as handle:
doc = tomlkit.load(handle)
else:
doc = tomlkit.document()
bot_config_payload = self._runtime_dict_to_bot_config_dict(config)
current = doc.get("a_memorix")
if isinstance(current, dict):
_update_toml_doc(current, bot_config_payload)
else:
doc["a_memorix"] = bot_config_payload
with path.open("w", encoding="utf-8") as handle:
tomlkit.dump(doc, handle)
await _get_config_manager().reload_config(changed_scopes=("bot",))
if not self._reload_callback_registered:
await self.reload()
return path, backup_path
def register_config_reload_callback(self) -> None:
if self._reload_callback_registered:
return
_get_config_manager().register_reload_callback(self.on_config_reload)
self._reload_callback_registered = True
async def on_config_reload(self, changed_scopes: Sequence[str] | None = None) -> None:
normalized = {str(scope or "").strip().lower() for scope in (changed_scopes or [])}
if normalized and "bot" not in normalized:
return
await self.reload()
@staticmethod @staticmethod
def _disabled_response(component_name: str) -> Dict[str, Any]: def _disabled_response(component_name: str) -> Dict[str, Any]:
reason = "a_memorix_disabled" reason = "a_memorix_disabled"

View File

@@ -16,6 +16,7 @@ from .file_watcher import FileChange, FileWatcher
from .legacy_migration import migrate_legacy_bind_env_to_bot_config_dict, try_migrate_legacy_bot_config_dict from .legacy_migration import migrate_legacy_bind_env_to_bot_config_dict, try_migrate_legacy_bot_config_dict
from .model_configs import APIProvider, ModelInfo, ModelTaskConfig from .model_configs import APIProvider, ModelInfo, ModelTaskConfig
from .official_configs import ( from .official_configs import (
AMemorixConfig,
BotConfig, BotConfig,
ChatConfig, ChatConfig,
ChineseTypoConfig, ChineseTypoConfig,
@@ -55,8 +56,9 @@ CONFIG_DIR: Path = PROJECT_ROOT / "config"
BOT_CONFIG_PATH: Path = (CONFIG_DIR / "bot_config.toml").resolve().absolute() BOT_CONFIG_PATH: Path = (CONFIG_DIR / "bot_config.toml").resolve().absolute()
MODEL_CONFIG_PATH: Path = (CONFIG_DIR / "model_config.toml").resolve().absolute() MODEL_CONFIG_PATH: Path = (CONFIG_DIR / "model_config.toml").resolve().absolute()
LEGACY_ENV_PATH: Path = (PROJECT_ROOT / ".env").resolve().absolute() LEGACY_ENV_PATH: Path = (PROJECT_ROOT / ".env").resolve().absolute()
A_MEMORIX_LEGACY_CONFIG_PATH: Path = (CONFIG_DIR / "a_memorix.toml").resolve().absolute()
MMC_VERSION: str = "1.0.0" MMC_VERSION: str = "1.0.0"
CONFIG_VERSION: str = "8.9.20" CONFIG_VERSION: str = "8.9.21"
MODEL_CONFIG_VERSION: str = "1.14.6" MODEL_CONFIG_VERSION: str = "1.14.6"
logger = get_logger("config") logger = get_logger("config")
@@ -86,6 +88,9 @@ class Config(ConfigBase):
memory: MemoryConfig = Field(default_factory=MemoryConfig) memory: MemoryConfig = Field(default_factory=MemoryConfig)
"""记忆配置类""" """记忆配置类"""
a_memorix: AMemorixConfig = Field(default_factory=AMemorixConfig)
"""A_Memorix 长期记忆子系统配置"""
message_receive: MessageReceiveConfig = Field(default_factory=MessageReceiveConfig) message_receive: MessageReceiveConfig = Field(default_factory=MessageReceiveConfig)
"""消息接收配置类""" """消息接收配置类"""
@@ -176,6 +181,45 @@ class ModelConfig(ConfigBase):
return super().model_post_init(context) return super().model_post_init(context)
def _normalize_a_memorix_legacy_config(config_data: dict[str, Any]) -> dict[str, Any]:
normalized = copy.deepcopy(config_data)
web_config = normalized.get("web")
if isinstance(web_config, dict) and "import" in web_config and "import_config" not in web_config:
web_config["import_config"] = web_config.pop("import")
return normalized
def _migrate_legacy_a_memorix_config(config_data: dict[str, Any]) -> tuple[dict[str, Any], bool]:
if isinstance(config_data.get("a_memorix"), dict):
return config_data, False
if not A_MEMORIX_LEGACY_CONFIG_PATH.exists():
return config_data, False
try:
with A_MEMORIX_LEGACY_CONFIG_PATH.open("r", encoding="utf-8") as handle:
legacy_data = tomlkit.load(handle).unwrap()
except Exception as exc:
logger.warning(f"读取旧版 A_Memorix 配置失败,已使用主配置默认值: {A_MEMORIX_LEGACY_CONFIG_PATH},原因: {exc}")
return config_data, False
if not isinstance(legacy_data, dict):
logger.warning(f"旧版 A_Memorix 配置内容无效,已使用主配置默认值: {A_MEMORIX_LEGACY_CONFIG_PATH}")
return config_data, False
migrated_data = copy.deepcopy(config_data)
migrated_data["a_memorix"] = _normalize_a_memorix_legacy_config(legacy_data)
logger.warning(f"检测到旧版 A_Memorix 配置,已迁移到 bot_config.toml 的 [a_memorix]: {A_MEMORIX_LEGACY_CONFIG_PATH}")
return migrated_data, True
def _normalize_loaded_bot_config_dict(config_data: dict[str, Any]) -> dict[str, Any]:
normalized = copy.deepcopy(config_data)
a_memorix_config = normalized.get("a_memorix")
if isinstance(a_memorix_config, dict):
normalized["a_memorix"] = _normalize_a_memorix_legacy_config(a_memorix_config)
return normalized
class ConfigManager: class ConfigManager:
"""总配置管理类""" """总配置管理类"""
@@ -498,6 +542,7 @@ def load_config_from_file(
raise TypeError(t("config.invalid_inner_version")) raise TypeError(t("config.invalid_inner_version"))
old_ver: str = inner_version old_ver: str = inner_version
env_migration_applied: bool = False env_migration_applied: bool = False
a_memorix_migration_applied: bool = False
config_data.remove("inner") # 移除 inner 部分,避免干扰后续处理 config_data.remove("inner") # 移除 inner 部分,避免干扰后续处理
config_data = config_data.unwrap() # 转换为普通字典,方便后续处理 config_data = config_data.unwrap() # 转换为普通字典,方便后续处理
if config_path.name == "bot_config.toml" and config_class.__name__ == "Config": if config_path.name == "bot_config.toml" and config_class.__name__ == "Config":
@@ -510,6 +555,8 @@ def load_config_from_file(
if legacy_migration.migrated: if legacy_migration.migrated:
logger.warning(t("config.legacy_migrated", reason=legacy_migration.reason)) logger.warning(t("config.legacy_migrated", reason=legacy_migration.reason))
config_data = legacy_migration.data config_data = legacy_migration.data
config_data, a_memorix_migration_applied = _migrate_legacy_a_memorix_config(config_data)
config_data = _normalize_loaded_bot_config_dict(config_data)
# 保留一份“干净”的原始数据副本,避免第一次 from_dict 过程中对 dict 的就地修改 # 保留一份“干净”的原始数据副本,避免第一次 from_dict 过程中对 dict 的就地修改
original_data: dict[str, Any] = copy.deepcopy(config_data) original_data: dict[str, Any] = copy.deepcopy(config_data)
try: try:
@@ -529,7 +576,7 @@ def load_config_from_file(
raise e raise e
else: else:
raise e raise e
if compare_versions(old_ver, new_ver) or env_migration_applied: if compare_versions(old_ver, new_ver) or env_migration_applied or a_memorix_migration_applied:
output_config_changes(attribute_data, logger, old_ver, new_ver, config_path.name) output_config_changes(attribute_data, logger, old_ver, new_ver, config_path.name)
write_config_to_file(target_config, config_path, new_ver, override_repr) write_config_to_file(target_config, config_path, new_ver, override_repr)
if env_migration_applied: if env_migration_applied:
@@ -578,6 +625,14 @@ def write_config_to_file(
else: else:
raise TypeError(t("config.write_unsupported_type")) raise TypeError(t("config.write_unsupported_type"))
if isinstance(config, Config):
try:
a_memorix_web = full_config_data["a_memorix"]["web"]
if "import_config" in a_memorix_web and "import" not in a_memorix_web:
a_memorix_web["import"] = a_memorix_web.pop("import_config")
except Exception:
logger.debug("A_Memorix 配置写出时转换 web.import_config 失败", exc_info=True)
# 备份旧文件 # 备份旧文件
if config_path.exists(): if config_path.exists():
backup_root = config_path.parent / "old" backup_root = config_path.parent / "old"

View File

@@ -649,6 +649,345 @@ class MemoryConfig(ConfigBase):
return super().model_post_init(context) return super().model_post_init(context)
class AMemorixPluginConfig(ConfigBase):
"""A_Memorix 子系统状态"""
enabled: bool = Field(default=False)
"""是否启用 A_Memorix"""
class AMemorixStorageConfig(ConfigBase):
"""A_Memorix 存储位置"""
data_dir: str = Field(default="data/a-memorix")
"""数据目录"""
class AMemorixEmbeddingFallbackConfig(ConfigBase):
"""A_Memorix Embedding 回退"""
enabled: bool = Field(default=True)
"""是否启用回退机制"""
probe_interval_seconds: int = Field(default=180, ge=10)
"""探测间隔秒数"""
allow_metadata_only_write: bool = Field(default=True)
"""是否允许仅写入元数据"""
class AMemorixParagraphVectorBackfillConfig(ConfigBase):
"""A_Memorix 段落向量回填"""
enabled: bool = Field(default=True)
"""是否启用回填任务"""
interval_seconds: int = Field(default=60, ge=5)
"""回填轮询间隔"""
batch_size: int = Field(default=64, ge=1)
"""单批回填数量"""
max_retry: int = Field(default=5, ge=0)
"""最大重试次数"""
class AMemorixEmbeddingConfig(ConfigBase):
"""A_Memorix Embedding 配置"""
model_name: str = Field(default="auto")
"""Embedding 模型选择"""
dimension: int = Field(default=1024, ge=1)
"""向量维度"""
batch_size: int = Field(default=32, ge=1)
"""单批请求大小"""
max_concurrent: int = Field(default=5, ge=1)
"""最大并发数"""
enable_cache: bool = Field(default=False)
"""是否启用缓存"""
quantization_type: Literal["int8"] = Field(default="int8")
"""量化方式,当前 vNext 仅支持 int8(SQ8)"""
fallback: AMemorixEmbeddingFallbackConfig = Field(default_factory=AMemorixEmbeddingFallbackConfig)
"""Embedding 回退配置"""
paragraph_vector_backfill: AMemorixParagraphVectorBackfillConfig = Field(
default_factory=AMemorixParagraphVectorBackfillConfig
)
"""段落向量回填配置"""
class AMemorixSparseRetrievalConfig(ConfigBase):
"""A_Memorix 稀疏检索配置"""
enabled: bool = Field(default=True)
"""是否启用稀疏检索"""
backend: Literal["fts5"] = Field(default="fts5")
"""稀疏检索后端"""
mode: Literal["auto", "fallback_only", "hybrid"] = Field(default="auto")
"""稀疏检索模式"""
tokenizer_mode: Literal["jieba", "mixed", "char_2gram"] = Field(default="jieba")
"""分词模式"""
candidate_k: int = Field(default=80, ge=1)
"""段落候选数"""
relation_candidate_k: int = Field(default=60, ge=1)
"""关系候选数"""
class AMemorixRetrievalConfig(ConfigBase):
"""A_Memorix 检索配置"""
top_k_paragraphs: int = Field(default=20, ge=1)
"""段落候选数"""
top_k_relations: int = Field(default=10, ge=1)
"""关系候选数"""
top_k_final: int = Field(default=10, ge=1)
"""最终返回条数"""
alpha: float = Field(default=0.5, ge=0.0, le=1.0)
"""关系融合权重"""
enable_ppr: bool = Field(default=True)
"""是否启用 PPR"""
ppr_alpha: float = Field(default=0.85, ge=0.0, le=1.0)
"""PPR alpha"""
ppr_timeout_seconds: float = Field(default=1.5, ge=0.1)
"""PPR 超时秒数"""
ppr_concurrency_limit: int = Field(default=4, ge=1)
"""PPR 并发限制"""
enable_parallel: bool = Field(default=True)
"""是否启用并行检索"""
sparse: AMemorixSparseRetrievalConfig = Field(default_factory=AMemorixSparseRetrievalConfig)
"""稀疏检索配置"""
class AMemorixThresholdConfig(ConfigBase):
"""A_Memorix 阈值过滤配置"""
min_threshold: float = Field(default=0.3, ge=0.0, le=1.0)
"""最小阈值"""
max_threshold: float = Field(default=0.95, ge=0.0, le=1.0)
"""最大阈值"""
percentile: int = Field(default=75, ge=0, le=100)
"""动态阈值百分位"""
min_results: int = Field(default=3, ge=1)
"""最小保留条数"""
enable_auto_adjust: bool = Field(default=True)
"""是否启用自动阈值调整"""
class AMemorixFilterConfig(ConfigBase):
"""A_Memorix 聊天过滤配置"""
enabled: bool = Field(default=True)
"""是否启用聊天过滤"""
mode: Literal["blacklist", "whitelist"] = Field(default="blacklist")
"""过滤模式"""
chats: list[str] = Field(default_factory=lambda: [])
"""聊天流列表"""
class AMemorixEpisodeConfig(ConfigBase):
"""A_Memorix Episode 配置"""
enabled: bool = Field(default=True)
"""是否启用 Episode"""
generation_enabled: bool = Field(default=True)
"""是否启用自动生成"""
pending_batch_size: int = Field(default=20, ge=1)
"""待处理批大小"""
pending_max_retry: int = Field(default=3, ge=0)
"""待处理最大重试次数"""
max_paragraphs_per_call: int = Field(default=20, ge=1)
"""单次最大段落数"""
max_chars_per_call: int = Field(default=6000, ge=100)
"""单次最大字符数"""
source_time_window_hours: float = Field(default=24.0, ge=0.0)
"""时间窗口小时数"""
segmentation_model: str = Field(default="auto")
"""分段模型选择"""
class AMemorixPersonProfileConfig(ConfigBase):
"""A_Memorix 人物画像配置"""
enabled: bool = Field(default=True)
"""是否启用画像"""
refresh_interval_minutes: int = Field(default=30, ge=1)
"""刷新间隔分钟数"""
active_window_hours: float = Field(default=72.0, ge=1.0)
"""活跃窗口小时数"""
max_refresh_per_cycle: int = Field(default=50, ge=1)
"""单轮最大刷新数"""
top_k_evidence: int = Field(default=12, ge=1)
"""证据条数"""
class AMemorixMemoryEvolutionConfig(ConfigBase):
"""A_Memorix 记忆演化配置"""
enabled: bool = Field(default=True)
"""是否启用记忆演化"""
half_life_hours: float = Field(default=24.0, ge=0.1)
"""半衰期小时数"""
prune_threshold: float = Field(default=0.1, ge=0.0, le=1.0)
"""裁剪阈值"""
freeze_duration_hours: float = Field(default=24.0, ge=0.0)
"""冻结时长小时数"""
class AMemorixAdvancedConfig(ConfigBase):
"""A_Memorix 高级运行时配置"""
enable_auto_save: bool = Field(default=True)
"""是否启用自动保存"""
auto_save_interval_minutes: int = Field(default=5, ge=1)
"""自动保存间隔"""
debug: bool = Field(default=False)
"""是否启用调试"""
class AMemorixWebImportConfig(ConfigBase):
"""A_Memorix 导入中心配置"""
enabled: bool = Field(default=True)
"""是否启用导入中心"""
max_queue_size: int = Field(default=20, ge=1)
"""最大队列长度"""
max_files_per_task: int = Field(default=200, ge=1)
"""单任务最大文件数"""
max_file_size_mb: int = Field(default=20, ge=1)
"""单文件大小上限 MB"""
max_paste_chars: int = Field(default=200000, ge=100)
"""粘贴字符数上限"""
default_file_concurrency: int = Field(default=2, ge=1)
"""默认文件并发"""
default_chunk_concurrency: int = Field(default=4, ge=1)
"""默认分块并发"""
class AMemorixWebTuningConfig(ConfigBase):
"""A_Memorix 调优中心配置"""
enabled: bool = Field(default=True)
"""是否启用调优中心"""
max_queue_size: int = Field(default=8, ge=1)
"""最大队列长度"""
poll_interval_ms: int = Field(default=1200, ge=200)
"""轮询间隔毫秒数"""
default_intensity: Literal["quick", "standard", "deep"] = Field(default="standard")
"""默认调优强度"""
default_objective: Literal["precision_priority", "balanced", "recall_priority"] = Field(
default="precision_priority"
)
"""默认调优目标"""
default_top_k_eval: int = Field(default=20, ge=1)
"""默认评估 Top-K"""
default_sample_size: int = Field(default=24, ge=1)
"""默认样本数"""
class AMemorixWebConfig(ConfigBase):
"""A_Memorix Web 运维配置"""
import_config: AMemorixWebImportConfig = Field(default_factory=AMemorixWebImportConfig)
"""导入中心配置"""
tuning: AMemorixWebTuningConfig = Field(default_factory=AMemorixWebTuningConfig)
"""调优中心配置"""
class AMemorixConfig(ConfigBase):
"""A_Memorix 长期记忆子系统配置"""
__ui_label__ = "长期记忆"
__ui_icon__ = "brain"
plugin: AMemorixPluginConfig = Field(default_factory=AMemorixPluginConfig)
"""子系统状态"""
storage: AMemorixStorageConfig = Field(default_factory=AMemorixStorageConfig)
"""存储位置"""
embedding: AMemorixEmbeddingConfig = Field(default_factory=AMemorixEmbeddingConfig)
"""Embedding 配置"""
retrieval: AMemorixRetrievalConfig = Field(default_factory=AMemorixRetrievalConfig)
"""检索配置"""
threshold: AMemorixThresholdConfig = Field(default_factory=AMemorixThresholdConfig)
"""阈值过滤配置"""
filter: AMemorixFilterConfig = Field(default_factory=AMemorixFilterConfig)
"""聊天过滤配置"""
episode: AMemorixEpisodeConfig = Field(default_factory=AMemorixEpisodeConfig)
"""Episode 配置"""
person_profile: AMemorixPersonProfileConfig = Field(default_factory=AMemorixPersonProfileConfig)
"""人物画像配置"""
memory: AMemorixMemoryEvolutionConfig = Field(default_factory=AMemorixMemoryEvolutionConfig)
"""记忆演化配置"""
advanced: AMemorixAdvancedConfig = Field(default_factory=AMemorixAdvancedConfig)
"""高级运行时配置"""
web: AMemorixWebConfig = Field(default_factory=AMemorixWebConfig)
"""Web 运维配置"""
class LearningItem(ConfigBase): class LearningItem(ConfigBase):
platform: str = Field( platform: str = Field(
default="", default="",

View File

@@ -80,6 +80,7 @@ class MainSystem:
init_start_time = time.time() init_start_time = time.time()
await config_manager.start_file_watcher() await config_manager.start_file_watcher()
a_memorix_host_service.register_config_reload_callback()
# 添加在线时间统计任务 # 添加在线时间统计任务
await async_task_manager.add_task(OnlineTimeRecordTask()) await async_task_manager.add_task(OnlineTimeRecordTask())

View File

@@ -19,6 +19,7 @@ from src.config.model_configs import (
ModelTaskConfig, ModelTaskConfig,
) )
from src.config.official_configs import ( from src.config.official_configs import (
AMemorixConfig,
BotConfig, BotConfig,
ChatConfig, ChatConfig,
ChineseTypoConfig, ChineseTypoConfig,
@@ -128,6 +129,7 @@ async def get_config_section_schema(section_name: str):
"telemetry": TelemetryConfig, "telemetry": TelemetryConfig,
"maim_message": MaimMessageConfig, "maim_message": MaimMessageConfig,
"memory": MemoryConfig, "memory": MemoryConfig,
"a_memorix": AMemorixConfig,
"debug": DebugConfig, "debug": DebugConfig,
"voice": VoiceConfig, "voice": VoiceConfig,
"model_task_config": ModelTaskConfig, "model_task_config": ModelTaskConfig,