fix(aibox): 补全 common/logger 缺失的 logging 导入并固化 common 包抽取

- common/logger.py 漏导 logging / RotatingFileHandler,导致 main() 在
  Logger.init_file_logging() 处抛 NameError;现已补回(抽取自 aibox.py 时
  未携带该 import)
- 固化 aibox 模块化重构成果:common/ 包(i18n/config/constants/logger
  + __init__ 重导出)已落地,aibox.py 删除内联 I18n 改从 common 导入
- requirements.txt 新增 PySocks 依赖
This commit is contained in:
qing committed 2026-08-12 09:41:33 +08:00
1 parent bcd63e4fde
commit 6db0cf017e
7 files changed
+1009 -939

No files matched your search

+21
View File
@@ -0,0 +1,21 @@
"""
common - aibox 底层基础设施公共模块(包)
抽离自 aibox.py 的零依赖基础层,供 aibox.py 及后续 tools.py 等模块导入。
当前包含:
- i18n.py : I18n 类 + i18n 实例(多语言)
- config.py : ConfigManager(配置管理)
- constants.py : Constants 类 + constants 实例(系统常量)
- logger.py : Logger(统一日志)
形成单向依赖:common <- tools <- aibox。
采用"重导出"策略:外部统一用 `from common import i18n, I18n, ...`,
调用点无需关心具体子模块所在文件。
"""
from .i18n import i18n, I18n
from .config import ConfigManager
from .constants import Constants, constants
from .logger import Logger
__all__ = ["i18n", "I18n", "ConfigManager", "Constants", "constants", "Logger"]
+250
View File
@@ -0,0 +1,250 @@
#!/usr/bin/env python3
"""
common.config - aibox 配置管理器
抽离自 aibox.py 的 ConfigManager。仅依赖 common.i18n(在运行期方法内部使用),
不反向依赖 aibox 任何符号,可安全独立导入。
"""
import os
import json
from typing import Dict, Optional
from .i18n import i18n
class ConfigManager:
"""配置管理器 - 单例模式,负责加载和保存配置"""
_instance = None
_config = None
_config_path = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self):
if self._config is None:
self._load_config()
def _get_default_config_path(self) -> str:
env_config = os.environ.get("AI_CONFIG_PATH")
if env_config:
return env_config
return os.path.expanduser("~/.aibox/config.json")
def _get_default_config(self) -> Dict:
return {
# 系统参数
"system": {
"save_dir": "~/.aibox", # 储存目录
"datetime_format": "%Y-%m-%d %H:%M:%S", # 时间格式
"date_format": "%Y%m%d_%H%M%S", # 另一个时间格式
"debug_level": 1, # 调试等级
"load_history_by_default": True, # 默认加载历史记录
"log_file": "~/.aibox/ai.log", # 日志文件
"log_max_size": 10485760, # 日志最大大小
"log_backup_count": 5, # 日志备份数量
"skills_dir": "~/.aibox/skills", # 技能目录
"prompts_dir": "~/.aibox/prompts", # 提示词目录
"workspace_dir": "~/.aibox/workspace", # 工作空间目录
"websocket_host": "localhost", # websocket监听地址
"websocket_port": 8765, # websocket监听端口
},
# 调度程序
"scheduler": {
"enabled": True, # 开启调度
"check_interval": 1.0, # 检查间隔
"max_tasks_per_run": 10, # 每次运行最大任务量
"broadcast_enabled": True # 开启广播
},
# 大模型调用接口
"api": {
"deepseek_api_url": "https://api.deepseek.com/chat/completions", # deepseek API URL
"deepseek_model": "deepseek-v4-flash", # 模型名字
"reasoning_enabled": False, # 开启思维链
"api_key_env": "DEEPSEEK_API_KEY", # API-KEY所在的环境变量
"timeout": 120, # 调用大模型请求超时时间
"temperature": 0.7 # 大模型对话温度(类似思维发散程度)
},
# 大模型上下文
"context": {
"max_history_messages": 500, # 最大历史消息数量
"max_context_messages": 120, # 最大内容消息数量(?)
"max_tokens": 8192, # 最大输出token量
"max_history_conversations": 5, # 最大历史对话
"preserve_system_prompts": True, # 开启前置系统提示词
# 对话压缩配置
"compress_enabled": True, # 开启压缩
"compress_message_threshold": 200, # 压缩消息条数阈值
"compress_token_threshold": 120000, # 压缩消息词元阈值
"compress_ratio": 0.2, # 压缩率
"compress_keep_recent": 16 # 压缩保留最近的几条
},
# 定时任务参数
"memo": {
"default_reminder_minutes": 60,
"max_memos_per_check": 10, # 每次检查定时任务数量
"allow_user_tasks": True # 允许用户任务
},
# 文件
"files": {
"max_file_size": 10485710 # 最大文件大小
},
# 指令
"commands": {
"enabled": True # 开启指令
},
# 网页
"web": {
"timeout": 10, # 超时时间
"delay": 0, # 延时
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"respect_robots": False, #
"max_download_size": 10485760 # 最大下载大小
},
# 记忆
"memory": {
"db_name": "memories.db", # 数据库名
"max_search_results": 100, # 最大搜索结果
"default_importance": 2 # ?
},
# 对话
"conversation": {
"current_file": "current_conversation.json", # 当前对话文件
"history_file": "conversation_history.json", # 历史对话文件
"archive_dir": "archive" # ?
},
# 工具
"tools": {
"max_result_length": 10000, # 结果最大长度
"save_result_to_file": False, # 保存结果到文件
"tool_results_dir": "tool_results" # 工具结果存储目录
},
# 压缩器
"compressor": {
"min_messages_to_compress": 5, # 最小压缩数量
"weight_decay_factor": 0.01, # ?
"max_input_chars": 6000, # 最大输入字符
"llm_max_tokens": 1000 # 大模型最大输出词元
}
}
def _load_config(self):
config_path = self._get_default_config_path()
self._config_path = config_path
if os.path.exists(config_path):
try:
with open(config_path, 'r', encoding='utf-8') as f:
user_config = json.load(f)
default_config = self._get_default_config()
self._config = self._merge_configs(default_config, user_config)
print(i18n.get('config_loaded', config_path))
except Exception as e:
print(f"⚠️ {i18n.get('error_prefix')}{e},使用默认配置")
self._config = self._get_default_config()
else:
print(i18n.get('config_not_found', config_path))
self._config = self._get_default_config()
self._save_default_config(config_path)
def _merge_configs(self, default: Dict, user: Dict) -> Dict:
result = default.copy()
for key, value in user.items():
if key in result and isinstance(result[key], dict) and isinstance(value, dict):
result[key] = self._merge_configs(result[key], value)
else:
result[key] = value
return result
def _save_default_config(self, config_path: str):
try:
os.makedirs(os.path.dirname(config_path), exist_ok=True)
with open(config_path, 'w', encoding='utf-8') as f:
json.dump(self._get_default_config(), f, ensure_ascii=False, indent=2)
print(i18n.get('config_created', config_path))
except:
pass
def get(self, key_path: str, default=None):
keys = key_path.split('.')
value = self._config
try:
for key in keys:
value = value[key]
return value
except (KeyError, TypeError):
return default
def reload(self):
self._load_config()
def get_proxy(self) -> Optional[Dict[str, str]]:
"""获取当前代理配置(返回 requests 可用的 proxies 字典)"""
proxy_conf = self.get("proxy.socks5")
if not proxy_conf or not proxy_conf.get("enabled", False):
return None
host = proxy_conf.get("host", "127.0.0.1")
port = proxy_conf.get("port", 1080)
username = proxy_conf.get("username")
password = proxy_conf.get("password")
if username and password:
proxy_url = f"socks5://{username}:{password}@{host}:{port}"
else:
proxy_url = f"socks5://{host}:{port}"
return {"http": proxy_url, "https": proxy_url}
def set_proxy(self, host: str, port: int, username: str = None, password: str = None, enabled: bool = True, save: bool = True):
"""动态设置代理配置(内存中更新,并可选择写入配置文件)"""
# 确保 proxy 节点存在
if "proxy" not in self._config:
self._config["proxy"] = {}
self._config["proxy"]["socks5"] = {
"enabled": enabled,
"host": host,
"port": port,
"username": username,
"password": password
}
if save:
self._save_config()
return True
def _save_config(self):
"""将当前配置保存到配置文件(覆盖)"""
config_path = self._config_path
if config_path:
try:
os.makedirs(os.path.dirname(config_path), exist_ok=True)
with open(config_path, 'w', encoding='utf-8') as f:
json.dump(self._config, f, ensure_ascii=False, indent=2)
print(f"✅ 配置已保存到 {config_path}")
except Exception as e:
print(f"⚠️ 保存配置失败: {e}")
def get_proxy_info(self) -> Dict:
"""获取代理配置的详细信息(用于显示)"""
proxy_conf = self.get("proxy.socks5", {})
return {
"enabled": proxy_conf.get("enabled", False),
"host": proxy_conf.get("host", ""),
"port": proxy_conf.get("port", 0),
"username": proxy_conf.get("username"),
"password": proxy_conf.get("password") is not None # 只显示是否存在
}
+241
View File
@@ -0,0 +1,241 @@
#!/usr/bin/env python3
"""
common.constants - aibox 系统常量
抽离自 aibox.py 的 Constants 类与 constants 实例。仅依赖 common.config
(类体内 _config = ConfigManager(),运行期实例化时读取 i18n),不反向依赖
aibox 任何符号,可安全独立导入。
"""
import os
import json
from .config import ConfigManager
# ==================== 常量定义 ====================
class Constants:
"""系统常量 - 从配置管理器获取"""
_config = ConfigManager()
@classmethod
def _get_config(cls):
return cls._config
@property
def DATETIME_FORMAT(cls):
return cls._get_config().get("system.datetime_format", "%Y-%m-%d %H:%M:%S")
@property
def DATE_FORMAT(cls):
return cls._get_config().get("system.date_format", "%Y%m%d_%H%M%S")
@property
def DEEPSEEK_API_URL(cls):
return cls._get_config().get("api.deepseek_api_url")
@property
def DEEPSEEK_MODEL(cls):
return cls._get_config().get("api.deepseek_model", "deepseek-v4-flash")
@property
def REASONING_ENABLED(cls):
return cls._get_config().get("api.reasoning_enabled", False)
@property
def DEFAULT_TIMEOUT(cls):
return cls._get_config().get("api.timeout", 30)
@property
def DEFAULT_MAX_HISTORY(cls):
return cls._get_config().get("context.max_history_messages", 500)
@property
def DEFAULT_TEMPERATURE(cls):
return cls._get_config().get("api.temperature", 0.7)
@property
def DEFAULT_MAX_TOKENS(cls):
return cls._get_config().get("context.max_tokens", 8000)
@property
def MAX_FILE_SIZE(cls):
return cls._get_config().get("files.max_file_size", 10485710)
@property
def SAVE_DIR(cls):
return os.path.expanduser(cls._get_config().get("system.save_dir", "~/.aibox"))
@property
def SKILLS_DIR(cls):
return os.path.expanduser(cls._get_config().get("system.skills_dir", "~/.aibox/skills"))
@property
def WORKSPACE_DIR(cls):
return os.path.expanduser(cls._get_config().get("system.workspace_dir", "~/.aibox/workspace"))
@property
def PROMPTS_DIR(cls):
return os.path.expanduser(cls._get_config().get("system.prompts_dir", "~/.aibox/prompts"))
@property
def MEMORY_DB(cls):
return cls._get_config().get("memory.db_name", "memories.db")
@property
def CURRENT_CONVERSATION_FILE(cls):
return cls._get_config().get("conversation.current_file", "current_conversation.json")
@property
def HISTORY_FILE(cls):
return cls._get_config().get("conversation.history_file", "conversation_history.json")
@property
def ARCHIVE_DIR(cls):
return cls._get_config().get("conversation.archive_dir", "archive")
@property
def USER_PREFIX(cls):
return "[用户]"
@property
def SYSTEM_PREFIX(cls):
return "[系统]"
@property
def ASSISTANT_PREFIX(cls):
return "[助手]"
@property
def MAX_HISTORY_CONTEXT(cls):
return cls._get_config().get("context.max_context_messages", 50)
@property
def MAX_HISTORY_CONVERSATIONS(cls):
return cls._get_config().get("context.max_history_conversations", 5)
@property
def PRESERVE_SYSTEM_PROMPTS(cls):
return cls._get_config().get("context.preserve_system_prompts", True)
@property
def WEB_TIMEOUT(cls):
return cls._get_config().get("web.timeout", 10)
@property
def WEB_DELAY(cls):
return cls._get_config().get("web.delay", 0)
@property
def WEB_USER_AGENT(cls):
return cls._get_config().get("web.user_agent",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
@property
def WEB_MAX_DOWNLOAD_SIZE(cls):
return cls._get_config().get("web.max_download_size", 104857600)
@property
def MEMORY_MAX_SEARCH_RESULTS(cls):
return cls._get_config().get("memory.max_search_results", 100)
@property
def MEMORY_DEFAULT_IMPORTANCE(cls):
return cls._get_config().get("memory.default_importance", 2)
@property
def MEMO_DEFAULT_REMINDER_MINUTES(cls):
return cls._get_config().get("memo.default_reminder_minutes", 60)
@property
def MEMO_MAX_PER_CHECK(cls):
return cls._get_config().get("memo.max_memos_per_check", 10)
@property
def LOG_FILE(cls):
return os.path.expanduser(cls._get_config().get("system.log_file", "~/.aibox/ai.log"))
@property
def LOG_MAX_SIZE(cls):
return cls._get_config().get("system.log_max_size", 10485760)
@property
def LOG_BACKUP_COUNT(cls):
return cls._get_config().get("system.log_backup_count", 5)
@property
def COMMANDS_ENABLED(cls):
return cls._get_config().get("commands.enabled", True)
@property
def WEBSOCKET_HOST(cls):
return cls._get_config().get("system.websocket_host", "localhost")
@property
def WEBSOCKET_PORT(cls):
return cls._get_config().get("system.websocket_port", 8765)
@property
def SCHEDULER_ENABLED(cls):
return cls._get_config().get("scheduler.enabled", True)
@property
def SCHEDULER_CHECK_INTERVAL(cls):
return cls._get_config().get("scheduler.check_interval", 1.0)
@property
def SCHEDULER_MAX_TASKS(cls):
return cls._get_config().get("scheduler.max_tasks_per_run", 10)
@property
def SCHEDULER_BROADCAST(cls):
return cls._get_config().get("scheduler.broadcast_enabled", True)
@property
def MEMO_ALLOW_USER_TASKS(cls):
return cls._get_config().get("memo.allow_user_tasks", True)
@property
def COMPRESS_ENABLED(cls):
return cls._get_config().get("context.compress_enabled", True)
@property
def COMPRESS_MESSAGE_THRESHOLD(cls):
return cls._get_config().get("context.compress_message_threshold", 100)
@property
def COMPRESS_TOKEN_THRESHOLD(cls):
return cls._get_config().get("context.compress_token_threshold", 4000)
@property
def COMPRESS_RATIO(cls):
return cls._get_config().get("context.compress_ratio", 0.4)
@property
def COMPRESS_KEEP_RECENT(cls):
return cls._get_config().get("context.compress_keep_recent", 20)
@property
def TOOL_MAX_RESULT_LENGTH(cls):
return cls._get_config().get("tools.max_result_length", 100000) # 0.1MB 字符限制
@property
def COMPRESSOR_MIN_MESSAGES(cls):
return cls._get_config().get("compressor.min_messages_to_compress", 5)
@property
def COMPRESSOR_WEIGHT_DECAY_FACTOR(cls):
return cls._get_config().get("compressor.weight_decay_factor", 0.01)
@property
def COMPRESSOR_MAX_INPUT_CHARS(cls):
return cls._get_config().get("compressor.max_input_chars", 6000)
@property
def COMPRESSOR_LLM_MAX_TOKENS(cls):
return cls._get_config().get("compressor.llm_max_tokens", 1000)
# 创建常量实例
constants = Constants()
+397
View File
@@ -0,0 +1,397 @@
"""
i18n - 多语言支持子模块
仅依赖标准库 (os / sys / locale),是 common 包的最底层,不依赖包内其他模块。
"""
import os
import sys
import locale
class I18n:
"""多语言支持类 - 单例模式"""
_instance = None
_lang = None
_strings = {}
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self):
if not self._strings:
self._detect_language()
self._load_strings()
def _detect_language(self):
# 首先尝试从环境变量获取(Linux/Mac)
lang = os.environ.get('LANG') or os.environ.get('LC_ALL')
if lang:
if lang.startswith('en_'):
self._lang = 'en'
else:
self._lang = 'zh'
else:
# Windows: 使用 locale.getlocale()(无需 setlocale)
try:
sys_locale = locale.getlocale()[0]
if sys_locale and sys_locale.startswith('en_'):
self._lang = 'en'
else:
self._lang = 'zh'
except:
self._lang = 'zh'
# 强制覆盖
force_lang = os.environ.get("AI_LANGUAGE")
if force_lang in ['en', 'zh']:
self._lang = force_lang
def _load_strings(self):
"""加载多语言字符串"""
self._strings = {
'zh': {
# 欢迎信息
'welcome_title': "🤖 单AI智能体系统 - WebSocket服务器已启动",
'data_dir': "📁 数据存储目录: {}",
'skills_dir': "📁 技能目录: {}",
'prompts_dir': "📁 提示词目录: {}",
'config_file': "📋 配置文件: {}",
'memo_stats': "📋 任务统计:",
'total_memos': " 📝 总任务: {} 个",
'pending_memos': " ⏰ 待执行: {} 个",
'overdue_memos': " ⚠️ 已过期: {} 个",
'completed_memos': " ✅ 已完成: {} 个",
'loaded_skills': "🔧 已加载扩展技能 ({} 个):",
'skill_item': " • {}",
'more_skills': " ... 还有 {} 个",
'loaded_prompts': "📝 已加载提示词 ({} 个):",
'prompt_item': " • {}",
'websocket_started': "🌐 WebSocket服务器已启动在 ws://{}:{}",
'client_connected': "🔌 客户端已连接: {}",
'client_disconnected': "🔌 客户端断开连接: {}",
# 任务调度器相关
'scheduler_started': "⏰ 任务调度器已启动",
'scheduler_stopped': "⏰ 任务调度器已停止",
'task_added': "📋 已添加任务: {} (ID: {})",
'task_removed': "📋 已移除任务: {}",
'task_executing': "⚙️ 正在执行任务: {} (ID: {})",
'task_executed': "✅ 任务执行成功: {}",
'task_failed': "❌ 任务执行失败: {} - {}",
'task_stats': "📊 任务统计: 总数={}, 待执行={}, 已过期={}, 已完成={}",
'check_memos_task': "检查过期任务",
'broadcast_tasks': "📢 广播: {} 个任务已到期",
'next_execution': "⏱️ 下次执行: {}",
'task_completed': "✅ 任务已完成: {}",
# 命令处理
'goodbye': "👋 再见!",
'new_conversation': "🔄 开始新对话(历史已保存)",
'new_conversation_reset': "🔄 开始新对话(历史已保存,对话记忆已重置)",
'saved_conversations': "📚 已保存的对话:",
'conv_status_current': "当前",
'conv_status_archived': "归档",
'recent_memories': "📚 最近的记忆:",
'pending_memos_title': "📋 待执行的任务:",
'memo_overdue': "⚠️",
'memo_pending': "⏰",
'search_results': "🔍 搜索 '{}' 找到 {} 条记忆:",
'current_config': "⚙️ 当前配置:",
'config_debug': " 调试级别: {}",
'config_data_dir': " 数据目录: {}",
'config_skills_dir': " 技能目录: {}",
'config_prompts_dir': " 提示词目录: {}",
'config_commands': " 命令执行: {}",
'config_history': " 加载历史: {}",
'config_executor': " 命令执行器: {}",
'config_scheduler': " 任务调度器: {}",
'enabled': "启用",
'disabled': "禁用",
'config_reloaded': "✅ 配置已重新加载",
'prompts_reloaded': "✅ 提示词已重新加载 ({} 个)",
'checking_memos': "🔍 检查任务...",
'overdue_found': "发现 {} 个到期任务:",
'no_overdue': "没有到期的任务",
'memo_tool_usage': "📝 任务工具使用:",
'memo_usage_in_dialogue': " 在对话中可以直接使用 memo 工具:",
'memo_add_example': " - memo add title=\"任务标题\" content=\"任务内容\" reminder_time=\"+1h\" repeat_type=\"none\"",
'memo_list_example': " - memo list",
'memo_complete_example': " - memo complete memo_id=1",
'memory_habit': "🧠 记忆习惯:",
'memory_habit_desc': " AI会自动检查相关记忆,保持对话连贯性",
'memory_save_desc': " 对话结束时,AI会自动保存重要记忆",
'command_stream': "⚙️ 命令执行实时输出:",
'command_stream_desc': " run_command 工具会自动实时显示命令输出",
'command_stream_disable': " 可以通过 stream=false 参数关闭实时输出",
'skills_extension': "🔧 扩展技能:",
'skills_desc': " 将Python脚本放入 ~/.aibox/skills/ 目录",
'skills_interface_desc': " 可执行文件需支持 --help 选项,第一行为简介,其余为完整使用说明",
'skills_reloaded': "✅ 技能已重新加载 ({} 个)",
'skills_reload_failed': "❌ 技能重载失败: {}",
'skills_reload_success': "✅ 技能热重载成功,当前共有 {} 个扩展技能",
# 对话相关
'input_prompt': "💬 请输入问题: ",
'thinking': "🤔 思考中... [{}]",
'assistant_prefix': " 💬 助手: ",
'calling_tools': "\n 🔧 需要调用工具...",
'analyzing_tool_results': "\n 🤔 分析工具结果...",
'conversation_start': "🎭 对话开始",
'conversation_ended': "✅ AI保存记忆并结束了对话",
'conversation_stats': "\n📊 对话结束,总轮数: {}",
'user_interrupted': "\n\n👋 用户中断",
'error_prefix': "❌ 错误: ",
# 工具相关
'command_output_title': "\n" + "=" * 50 + "\n📟 命令实时输出:\n" + "=" * 50,
'command_output_end': "\n" + "=" * 50,
# 备忘录检查
'memo_check_title': "⏰ 检查到期任务...",
'memo_check_found': "发现 {} 个到期任务:",
'memo_check_none': "没有到期的任务",
'memo_list_title': "📋 待执行任务 (共 {} 个):",
'memo_added': "✅ 任务已添加,ID: {}",
'memo_add_failed': "❌ 添加失败: {}",
'memo_completed': "✅ 任务 #{} 已完成",
'memo_complete_failed': "❌ 无法完成任务 #{}",
# 即时任务相关
'immediate_memo_added': "⚡ 已添加为即时任务并立即检查",
'immediate_memo_executing': "⚡ 正在执行即时任务: {}",
# 新对话
'new_dialogue_start': "🎭 开始新对话: {}",
# 记忆相关
'memory_check': "习惯性检查相关记忆...",
'memory_found': "找到 {} 条相关记忆",
# 配置文件
'config_loaded': "📋 已加载配置文件: {}",
'config_not_found': "📋 配置文件不存在: {},使用默认配置",
'config_created': "📋 已创建默认配置文件: {}",
# WebSocket消息类型
'ws_connected': "✅ 已连接到AI服务器",
'ws_disconnected': "❌ 与服务器断开连接",
'ws_error': "⚠️ WebSocket错误: {}",
'ws_unknown_command': "❌ 未知命令: {}",
'ws_command_processed': "✅ 命令已处理",
# 消息类型
'msg_type_chunk': "chunk",
'msg_type_complete': "complete",
'msg_type_error': "error",
'msg_type_command_result': "command_result",
'msg_type_connection_ack': "connection_ack",
'msg_type_notification': "notification",
'msg_type_task_status': "task_status",
'msg_type_task_executing': "task_executing",
'msg_type_task_complete': "task_complete",
# 新增提示词
'memory_search_prompt': "【重要习惯】在每次回答用户问题前,请先使用 memory search 工具搜索记忆库中与当前话题相关的记忆,确保回答的连贯性和准确性。",
'identity_updated': "✅ 用户身份已更新,ID: {}",
'status_updated': "✅ 智能体状态已更新",
# 对话结束相关
'dialogue_ended': "🔚 对话已结束,请开始新对话",
'dialogue_force_reset': "🔄 对话状态已重置",
'dialogue_reset_complete': "✅ 对话状态已完全重置,系统提示词已重新加载",
# 对话压缩相关(静默执行,不输出信息)
'conversation_compress': "📦 对话内容压缩",
'conversation_compressed': "✅ 对话已压缩: 从 {} 条消息压缩为 {} 条",
'compress_threshold_reached': "⚠️ 对话已达到压缩阈值 ({} 条消息),正在压缩...",
'compress_token_threshold_reached': "⚠️ 对话token数已达到 {},正在压缩...",
'compress_failed': "❌ 对话压缩失败: {}",
'compressed_summary': "【对话摘要】{}",
'last_n_messages': "【最近消息】{}"
},
'en': {
# Welcome messages
'welcome_title': "🤖 Single AI Agent System - WebSocket Server Started",
'data_dir': "📁 Data directory: {}",
'skills_dir': "📁 Skills directory: {}",
'prompts_dir': "📁 Prompts directory: {}",
'config_file': "📋 Configuration file: {}",
'memo_stats': "📋 Task statistics:",
'total_memos': " 📝 Total tasks: {}",
'pending_memos': " ⏰ Pending: {}",
'overdue_memos': " ⚠️ Overdue: {}",
'completed_memos': " ✅ Completed: {}",
'loaded_skills': "🔧 Loaded extension skills ({}):",
'skill_item': " • {}",
'more_skills': " ... {} more",
'loaded_prompts': "📝 Loaded prompts ({}):",
'prompt_item': " • {}",
'websocket_started': "🌐 WebSocket server started at ws://{}:{}",
'client_connected': "🔌 Client connected: {}",
'client_disconnected': "🔌 Client disconnected: {}",
# Scheduler related
'scheduler_started': "⏰ Task scheduler started",
'scheduler_stopped': "⏰ Task scheduler stopped",
'task_added': "📋 Task added: {} (ID: {})",
'task_removed': "📋 Task removed: {}",
'task_executing': "⚙️ Executing task: {} (ID: {})",
'task_executed': "✅ Task executed successfully: {}",
'task_failed': "❌ Task execution failed: {} - {}",
'task_stats': "📊 Task stats: total={}, pending={}, overdue={}, completed={}",
'check_memos_task': "Check overdue tasks",
'broadcast_tasks': "📢 Broadcast: {} tasks overdue",
'next_execution': "⏱️ Next execution: {}",
'task_completed': "✅ Task completed: {}",
# Command processing
'goodbye': "👋 Goodbye!",
'new_conversation': "🔄 Starting new conversation (history saved)",
'new_conversation_reset': "🔄 Starting new conversation (history saved, conversation memory reset)",
'saved_conversations': "📚 Saved conversations:",
'conv_status_current': "current",
'conv_status_archived': "archived",
'recent_memories': "📚 Recent memories:",
'pending_memos_title': "📋 Pending tasks:",
'memo_overdue': "⚠️",
'memo_pending': "⏰",
'search_results': "🔍 Searching '{}' found {} memories:",
'current_config': "⚙️ Current configuration:",
'config_debug': " Debug level: {}",
'config_data_dir': " Data directory: {}",
'config_skills_dir': " Skills directory: {}",
'config_prompts_dir': " Prompts directory: {}",
'config_commands': " Command execution: {}",
'config_history': " Load history: {}",
'config_executor': " Command executor: {}",
'config_scheduler': " Task scheduler: {}",
'enabled': "enabled",
'disabled': "disabled",
'config_reloaded': "✅ Configuration reloaded",
'prompts_reloaded': "✅ Prompts reloaded ({})",
'checking_memos': "🔍 Checking tasks...",
'overdue_found': "Found {} overdue tasks:",
'no_overdue': "No overdue tasks",
'memo_tool_usage': "📝 Task tool usage:",
'memo_usage_in_dialogue': " You can use memo tool directly in dialogue:",
'memo_add_example': " - memo add title=\"Task\" content=\"Content\" reminder_time=\"+1h\" repeat_type=\"none\"",
'memo_list_example': " - memo list",
'memo_complete_example': " - memo complete memo_id=1",
'memory_habit': "🧠 Memory habits:",
'memory_habit_desc': " AI automatically checks relevant memories for conversation continuity",
'memory_save_desc': " AI automatically saves important memories when conversation ends",
'command_stream': "⚙️ Command execution real-time output:",
'command_stream_desc': " run_command tool automatically displays command output in real-time",
'command_stream_disable': " You can disable real-time output with stream=false parameter",
'skills_extension': "🔧 Extension skills:",
'skills_desc': " Put Python scripts in ~/.aibox/skills/ directory",
'skills_interface_desc': " Executables must support --help; first line is the summary, rest is the full manual",
'skills_reloaded': "✅ Skills reloaded ({})",
'skills_reload_failed': "❌ Skills reload failed: {}",
'skills_reload_success': "✅ Skills hot-reloaded successfully, now {} extension skills available",
# Dialogue related
'input_prompt': "💬 Please enter your question: ",
'thinking': "🤔 Thinking... [{}]",
'assistant_prefix': " 💬 Assistant: ",
'calling_tools': "\n 🔧 Need to call tools...",
'analyzing_tool_results': "\n 🤔 Analyzing tool results...",
'conversation_start': "🎭 Conversation started",
'conversation_ended': "✅ AI saved memory and ended conversation",
'conversation_stats': "\n📊 Conversation ended, total turns: {}",
'user_interrupted': "\n\n👋 User interrupted",
'error_prefix': "❌ Error: ",
# Tool related
'command_output_title': "\n" + "=" * 50 + "\n📟 Command real-time output:\n" + "=" * 50,
'command_output_end': "\n" + "=" * 50,
# Memo check
'memo_check_title': "⏰ Checking overdue tasks...",
'memo_check_found': "Found {} overdue tasks:",
'memo_check_none': "No overdue tasks",
'memo_list_title': "📋 Pending tasks (total {}):",
'memo_added': "✅ Task added, ID: {}",
'memo_add_failed': "❌ Failed to add: {}",
'memo_completed': "✅ Task #{} completed",
'memo_complete_failed': "❌ Cannot complete task #{}",
# Immediate task related
'immediate_memo_added': "⚡ Added as immediate task and checked now",
'immediate_memo_executing': "⚡ Executing immediate task: {}",
# New dialogue
'new_dialogue_start': "🎭 Starting new dialogue: {}",
# Memory related
'memory_check': "Habitually checking relevant memories...",
'memory_found': "Found {} relevant memories",
# Configuration file
'config_loaded': "📋 Configuration file loaded: {}",
'config_not_found': "📋 Configuration file not found: {}, using default configuration",
'config_created': "📋 Default configuration file created: {}",
# WebSocket message types
'ws_connected': "✅ Connected to AI server",
'ws_disconnected': "❌ Disconnected from server",
'ws_error': "⚠️ WebSocket error: {}",
'ws_unknown_command': "❌ Unknown command: {}",
'ws_command_processed': "✅ Command processed",
# Message types
'msg_type_chunk': "chunk",
'msg_type_complete': "complete",
'msg_type_error': "error",
'msg_type_command_result': "command_result",
'msg_type_connection_ack': "connection_ack",
'msg_type_notification': "notification",
'msg_type_task_status': "task_status",
'msg_type_task_executing': "task_executing",
'msg_type_task_complete': "task_complete",
# New prompts
'memory_search_prompt': "【Important Habit】Before answering each user question, please use the memory search tool to search the memory database for information related to the current topic, ensuring coherence and accuracy of responses.",
'identity_updated': "✅ User identity updated, ID: {}",
'status_updated': "✅ Agent status updated",
# Dialogue end related
'dialogue_ended': "🔚 Dialogue ended, please start a new one",
'dialogue_force_reset': "🔄 Dialogue state reset",
'dialogue_reset_complete': "✅ Dialogue state fully reset, system prompts reloaded",
# Conversation compression related (silent execution)
'conversation_compress': "📦 Conversation compression",
'conversation_compressed': "✅ Conversation compressed: from {} messages to {}",
'compress_threshold_reached': "⚠️ Conversation reached compression threshold ({} messages), compressing...",
'compress_token_threshold_reached': "⚠️ Conversation token count reached {}, compressing...",
'compress_failed': "❌ Conversation compression failed: {}",
'compressed_summary': "【Conversation Summary】{}",
'last_n_messages': "【Recent Messages】{}"
}
}
def get(self, key: str, *args) -> str:
"""获取指定语言的字符串,支持格式化参数"""
if key in self._strings[self._lang]:
text = self._strings[self._lang][key]
if args:
return text.format(*args)
return text
if key in self._strings['zh']:
text = self._strings['zh'][key]
if args:
return text.format(*args)
return text
return key
@property
def lang(self) -> str:
return self._lang
# 创建全局多语言实例
i18n = I18n()
+97
View File
@@ -0,0 +1,97 @@
#!/usr/bin/env python3
"""
common.logger - aibox 统一日志系统
抽离自 aibox.py 的 Logger 类。依赖 common.constants(日志文件路径与大小限制)
与 common.config,不反向依赖 aibox 任何符号,可安全独立导入。
"""
import os
import logging
from logging.handlers import RotatingFileHandler
from datetime import datetime
from .constants import constants
from .config import ConfigManager
# ==================== 日志系统 ====================
class Logger:
"""统一日志系统 - 支持文件和终端输出"""
LEVELS = {
"DEBUG": 3,
"INFO": 2,
"WARNING": 1,
"ERROR": 0
}
_file_handler = None
_log_initialized = False
@classmethod
def init_file_logging(cls, log_file: str = None, max_size: int = None, backup_count: int = None):
"""初始化文件日志"""
if cls._log_initialized:
return
if log_file is None:
log_file = constants.LOG_FILE
if max_size is None:
max_size = constants.LOG_MAX_SIZE
if backup_count is None:
backup_count = constants.LOG_BACKUP_COUNT
log_dir = os.path.dirname(log_file)
if log_dir:
os.makedirs(log_dir, exist_ok=True)
cls._file_handler = RotatingFileHandler(
log_file,
maxBytes=max_size,
backupCount=backup_count,
encoding='utf-8'
)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
cls._file_handler.setFormatter(formatter)
cls._log_initialized = True
def __init__(self, name: str = "System", level: int = None):
self.name = name
config = ConfigManager()
if level is None:
level = config.get("system.debug_level", 1)
self.level = level
self.py_logger = logging.getLogger(f"AI.{name}")
self.py_logger.setLevel(logging.DEBUG)
if Logger._file_handler and not self.py_logger.handlers:
self.py_logger.addHandler(Logger._file_handler)
def debug(self, msg: str):
if self.level >= 3:
self._log("🐛", "DEBUG", msg)
self.py_logger.debug(msg)
def info(self, msg: str):
if self.level >= 2:
self._log("📋", "INFO", msg)
self.py_logger.info(msg)
def warning(self, msg: str):
if self.level >= 1:
self._log("⚠️", "WARNING", msg)
self.py_logger.warning(msg)
def error(self, msg: str):
if self.level >= 0:
self._log("❌", "ERROR", msg)
self.py_logger.error(msg)
def _log(self, emoji: str, level: str, msg: str):
"""输出到终端"""
timestamp = datetime.now().strftime("%H:%M:%S")
print(f"{emoji} [{timestamp}] [{self.name}] {msg}", flush=True)