feat(aibox): 调度器任务间上下文隔离

- 每个定时任务创建独立DeepSeekChat实例,拥有独立messages/工具注册表/消息锁
- 任务与WebSocket主对话互不干扰,消除并发冲突与上下文污染根因
- 共享技能管理器懒加载一次供所有任务复用,避免重复扫描技能
- DeepSeekChat支持传入skill_manager复用,任务实例history_manager=None不污染历史
This commit is contained in:
qing committed 2026-08-04 09:23:51 +08:00
1 parent fa9679b7a6
commit 69a8eee192
1 file changed
+45 -22
+45 -22
View File
@@ -2835,8 +2835,37 @@ class TaskScheduler:
self.execution_history = [] self.execution_history = []
self.max_history = 100 self.max_history = 100
# 共享技能管理器(懒加载一次,供所有任务实例复用,避免重复扫描技能)
self._shared_skill_manager = None
self.logger.info(i18n.get('scheduler_started')) self.logger.info(i18n.get('scheduler_started'))
def _get_shared_skill_manager(self):
"""获取共享技能管理器(懒加载,全局只创建一次)"""
if self._shared_skill_manager is None:
self._shared_skill_manager = SkillManager(self.logger)
return self._shared_skill_manager
def _create_task_ai(self):
"""为单个定时任务创建独立的AI实例,实现任务间上下文隔离。
每个任务拥有独立的 messages / 工具注册表 / 消息锁,互不干扰,
也不影响 WebSocket 主对话。共享只读资源(记忆库、备忘录、提示词、
技能管理器)复用,避免重复加载开销。
"""
return DeepSeekChat(
api_key=None,
max_history=constants.DEFAULT_MAX_HISTORY,
debug_level=self.logger.level,
exit_callback=None, # 任务执行不触发全局对话结束
history_manager=None, # 不加载历史对话上下文,任务隔离
memory_db=self.ai_manager.memory_db if self.ai_manager else None,
memo_db=self.ai_manager.memo_db if self.ai_manager else None,
command_executor=self.ai_manager.command_executor if self.ai_manager else None,
prompt_manager=self.ai_manager.prompt_manager if self.ai_manager else None,
skill_manager=self._get_shared_skill_manager()
)
def set_websocket_handler(self, handler): def set_websocket_handler(self, handler):
"""设置WebSocket处理器,用于广播任务执行状态""" """设置WebSocket处理器,用于广播任务执行状态"""
self.ws_handler = handler self.ws_handler = handler
@@ -2994,8 +3023,8 @@ class TaskScheduler:
result = "任务已触发AI对话" result = "任务已触发AI对话"
# 如果有AI管理器,触发AI对话 # 为当前任务创建独立的AI实例,实现任务间上下文隔离(不干扰主对话,也不受其他任务影响)
if self.ai_manager and self.ai_manager.ai: ai = self._create_task_ai()
try: try:
# 获取当前事件循环 # 获取当前事件循环
loop = asyncio.get_running_loop() loop = asyncio.get_running_loop()
@@ -3027,29 +3056,13 @@ class TaskScheduler:
loop loop
) )
# 修改:确保AI处于新对话状态,不加载历史上下文
if self.ai_manager.ai:
ai = self.ai_manager.ai
# 先取消任何正在进行中的对话(防止WebSocket线程与调度器线程并发修改messages)
if ai.is_busy():
self.logger.info("AI正在处理中,先取消当前对话...")
ai.cancel()
# 短暂等待让正在进行的请求退出并释放锁(使用asyncio.sleep避免阻塞事件循环)
for _ in range(20):
await asyncio.sleep(0.1)
if not ai.is_busy():
break
# 重置AI对话状态,但保留系统提示词(在锁保护下执行)
ai.reset_conversation()
self.logger.info("AI对话状态已重置,准备执行任务")
# 创建一个 Future 来等待 AI 完成 # 创建一个 Future 来等待 AI 完成
ai_future = loop.create_future() ai_future = loop.create_future()
def run_ai(): def run_ai():
try: try:
# 在单独的线程中运行 AI # 在单独的线程中运行 AI(独立实例,天然隔离,无并发冲突)
response = self.ai_manager.ai.think_and_respond(user_input, output_callback) response = ai.think_and_respond(user_input, output_callback)
# 将结果设置到 Future 中 # 将结果设置到 Future 中
loop.call_soon_threadsafe(ai_future.set_result, response) loop.call_soon_threadsafe(ai_future.set_result, response)
except Exception as e: except Exception as e:
@@ -3089,6 +3102,13 @@ class TaskScheduler:
}) })
except: except:
pass pass
finally:
# 任务执行完毕,释放独立实例占用的资源
try:
if ai and hasattr(ai, 'cancel'):
ai.cancel()
except Exception:
pass
# 获取任务的最新状态(可选,用于日志) # 获取任务的最新状态(可选,用于日志)
updated_memo = memo_db.get_memo(memo['id']) updated_memo = memo_db.get_memo(memo['id'])
@@ -6686,7 +6706,7 @@ class DeepSeekChat:
system_prompt: str = None, debug_level: int = None, exit_callback: Callable = None, system_prompt: str = None, debug_level: int = None, exit_callback: Callable = None,
history_manager: ConversationHistory = None, memory_db: MemoryDatabase = None, history_manager: ConversationHistory = None, memory_db: MemoryDatabase = None,
memo_db: MemoDatabase = None, command_executor: CommandExecutor = None, memo_db: MemoDatabase = None, command_executor: CommandExecutor = None,
prompt_manager: PromptManager = None): prompt_manager: PromptManager = None, skill_manager: SkillManager = None):
self.config = ConfigManager() self.config = ConfigManager()
self.logger = Logger("DeepSeek", debug_level if debug_level is not None else Logger("").level) self.logger = Logger("DeepSeek", debug_level if debug_level is not None else Logger("").level)
self.debug_level = self.logger.level self.debug_level = self.logger.level
@@ -6701,6 +6721,8 @@ class DeepSeekChat:
self.history_loaded = False self.history_loaded = False
self.conversation_active = True self.conversation_active = True
self.messages = [] self.messages = []
# 共享技能管理器:传入时复用(避免每个任务重复扫描技能),否则按需创建
self._shared_skill_manager = skill_manager
# 添加对话压缩器 # 添加对话压缩器
self.compressor = ConversationCompressor(self, self.logger) self.compressor = ConversationCompressor(self, self.logger)
@@ -6826,7 +6848,8 @@ class DeepSeekChat:
self.tool_registry.register_many(builtin_tools, is_builtin=True) self.tool_registry.register_many(builtin_tools, is_builtin=True)
# 创建技能管理器:启动时对每个技能执行 --help,收集第一行简介 # 创建技能管理器:启动时对每个技能执行 --help,收集第一行简介
skill_manager = SkillManager(self.logger) # 若传入共享 skill_manager 则复用,避免每个任务/实例重复扫描技能
skill_manager = self._shared_skill_manager or SkillManager(self.logger)
self.tool_registry.skill_manager = skill_manager self.tool_registry.skill_manager = skill_manager
# 注册扩展技能搜索工具(单一工具,替代逐个注册 ExtensionTool) # 注册扩展技能搜索工具(单一工具,替代逐个注册 ExtensionTool)