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:
1 parent
bcd63e4fde
commit
6db0cf017e
7 files changed
+1009
-939
No files matched your search
+2
-939
@@ -38,397 +38,8 @@ import requests
|
||||
# 大模型客户端抽象层(支持多厂商切换)
|
||||
from llm_client import create_llm_client
|
||||
|
||||
# ==================== 语言检测和多语言支持 ====================
|
||||
|
||||
|
||||
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()
|
||||
|
||||
# 基础设施(多语言 / 配置 / 常量 / 日志)已抽离至 common 模块
|
||||
from common import i18n, I18n, ConfigManager, Constants, constants, Logger
|
||||
|
||||
# ==================== 提示词管理器 ====================
|
||||
|
||||
@@ -1160,557 +771,9 @@ update_soul content="我是...(新的自我定义)"
|
||||
return self._prompts_dir
|
||||
|
||||
|
||||
# ==================== 配置管理 ====================
|
||||
|
||||
|
||||
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 # 只显示是否存在
|
||||
}
|
||||
# ==================== 常量定义 ====================
|
||||
|
||||
|
||||
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()
|
||||
|
||||
|
||||
# ==================== 日志系统 ====================
|
||||
|
||||
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)
|
||||
|
||||
class ConversationCompressor:
|
||||
"""对话压缩管理器 - 优先压缩较早的消息,将tool调用信息一并压缩"""
|
||||
|
||||
@@ -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"]
|
||||
@@ -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 # 只显示是否存在
|
||||
}
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# backend
|
||||
websockets
|
||||
requests
|
||||
PySocks
|
||||
|
||||
# frontend
|
||||
websockets>=11.0
|
||||
|
||||
Reference in new issue
Block a user