diff --git a/core/main_terminal_parts/tools_execution.py b/core/main_terminal_parts/tools_execution.py index f95f8f0d..bd822f1f 100644 --- a/core/main_terminal_parts/tools_execution.py +++ b/core/main_terminal_parts/tools_execution.py @@ -1955,12 +1955,16 @@ class MainTerminalToolsExecutionMixin: task_message = build_master_dispatch_text(arguments.get("task", "")) summary_text = (arguments.get("summary") or f"{role.name}作业")[:80] thinking_mode = arguments.get("thinking_mode") or role.thinking_mode or "fast" - # 读取子智能体压缩阈值配置 + # 读取子智能体压缩阈值与最大轮次配置 _compress_threshold = 150_000 + _max_turns = None try: from modules.personalization_manager import load_personalization_config _prefs = load_personalization_config(data_dir) or {} _compress_threshold = int(_prefs.get("sub_agent_compress_threshold_tokens", 150_000)) + _raw_max_turns = _prefs.get("sub_agent_max_turns") + if _raw_max_turns is not None: + _max_turns = int(_raw_max_turns) except Exception: pass # 走原行 发事件创建(避免后期重建提供重复工能重费,直接使用 multi_agent_mode=True 调用) @@ -1978,6 +1982,7 @@ class MainTerminalToolsExecutionMixin: system_prompt=system_prompt, task_message=task_message, compress_threshold_tokens=_compress_threshold, + max_turns=_max_turns, ) # 在多智能体模式下,子智能体是团队协作成员,不是传统后台任务。 # run_in_background=False 避免触发后台完成通知轮询,保持主对话输入区可用。 @@ -1985,6 +1990,16 @@ class MainTerminalToolsExecutionMixin: logger.exception("[multi_agent] create_sub_agent failed") result = {"success": False, "error": str(exc)} else: + # 读取子智能体最大轮次配置(None=默认50、0=无上限、N=N轮) + _max_turns = None + try: + from modules.personalization_manager import load_personalization_config + _prefs = load_personalization_config(str(getattr(self, "data_dir", ""))) or {} + _raw_max_turns = _prefs.get("sub_agent_max_turns") + if _raw_max_turns is not None: + _max_turns = int(_raw_max_turns) + except Exception: + pass result = self.sub_agent_manager.create_sub_agent( agent_id=arguments.get("agent_id"), summary=arguments.get("summary", ""), @@ -1993,7 +2008,8 @@ class MainTerminalToolsExecutionMixin: run_in_background=arguments.get("run_in_background", False), timeout_seconds=arguments.get("timeout_seconds"), thinking_mode=arguments.get("thinking_mode"), - conversation_id=self.context_manager.current_conversation_id + conversation_id=self.context_manager.current_conversation_id, + max_turns=_max_turns, ) # 如果不是后台运行,阻塞等待完成 diff --git a/modules/sub_agent/manager.py b/modules/sub_agent/manager.py index 3bfe4f26..4baa0f63 100644 --- a/modules/sub_agent/manager.py +++ b/modules/sub_agent/manager.py @@ -239,6 +239,7 @@ class SubAgentManager(SubAgentStateMixin, SubAgentStatsMixin, SubAgentCreationMi system_prompt: Optional[str] = None, task_message: Optional[str] = None, compress_threshold_tokens: Optional[int] = None, + max_turns: Optional[int] = None, ) -> Dict: """创建子智能体任务并启动协程。 @@ -329,6 +330,7 @@ class SubAgentManager(SubAgentStateMixin, SubAgentStatsMixin, SubAgentCreationMi "display_name": display_name, "execution_mode": "in_process", "compress_threshold_tokens": compress_threshold_tokens, + "max_turns": max_turns, "container_name": None, } # 多智能体模式:为该会话创建或复用 MultiAgentState diff --git a/modules/sub_agent/task.py b/modules/sub_agent/task.py index 5ddc82a4..53d254bf 100644 --- a/modules/sub_agent/task.py +++ b/modules/sub_agent/task.py @@ -79,6 +79,14 @@ class SubAgentTask: # 上下文压缩配置 # 默认阈值 150k tokens,可由外部覆盖(如个人空间子智能体管理配置) self.compress_threshold_tokens: int = int(task_record.get("compress_threshold_tokens") or 150_000) + # 最大执行轮次(对应个人空间子智能体设置项 sub_agent_max_turns): + # 未设置 → 默认 50;0(或负数)→ None 表示无上限;正整数 → 该值 + _raw_max_turns = task_record.get("max_turns") + if _raw_max_turns is None: + self.max_turns: Optional[int] = 50 + else: + _max_turns_int = int(_raw_max_turns) + self.max_turns = _max_turns_int if _max_turns_int > 0 else None self.current_context_tokens: int = 0 self._compress_round: int = 0 @@ -191,7 +199,7 @@ class SubAgentTask: tools.append(FINISH_TOOL) start_time = time.time() - max_turns = 50 + max_turns = self.max_turns # None 表示无上限(个人空间可配置,默认 50) turn = 0 while not self._cancelled: @@ -262,7 +270,7 @@ class SubAgentTask: continue turn += 1 - if turn > max_turns: + if max_turns is not None and turn > max_turns: await self._write_failure("任务执行超过最大轮次限制", max_turns_exceeded=True) return diff --git a/server/multi_agent.py b/server/multi_agent.py index 0eb1dd94..954b671d 100644 --- a/server/multi_agent.py +++ b/server/multi_agent.py @@ -205,14 +205,17 @@ def get_multi_agent_settings_api(): _, workspace = get_user_resources(username) if not workspace: return jsonify({"success": False, "error": "工作区未就绪"}), 503 - # 从个人化配置中读取子智能体压缩阈值 + # 从个人化配置中读取子智能体设置 from modules.personalization_manager import load_personalization_config prefs = load_personalization_config(workspace.data_dir) or {} compress_threshold = prefs.get("sub_agent_compress_threshold_tokens", 150000) + # 最大执行轮次:None(未设置)表示默认 50;0 表示无上限;正整数为该值 + max_turns = prefs.get("sub_agent_max_turns") return jsonify({ "success": True, "settings": { "sub_agent_compress_threshold_tokens": compress_threshold, + "sub_agent_max_turns": max_turns, }, }) except Exception as exc: @@ -232,15 +235,33 @@ def update_multi_agent_settings_api(): return jsonify({"success": False, "error": "工作区未就绪"}), 503 data = request.get_json() or {} settings = data.get("settings") or {} + from modules.personalization_manager import load_personalization_config, save_personalization_config + prefs = load_personalization_config(workspace.data_dir) or {} + dirty = False # 更新子智能体压缩阈值 threshold = settings.get("sub_agent_compress_threshold_tokens") if threshold is not None: threshold = int(threshold) if threshold < 10000: return jsonify({"success": False, "error": "压缩阈值不能小于 10000"}), 400 - from modules.personalization_manager import load_personalization_config, save_personalization_config - prefs = load_personalization_config(workspace.data_dir) or {} prefs["sub_agent_compress_threshold_tokens"] = threshold + dirty = True + # 更新子智能体最大轮次(键存在才处理:None → 删除键恢复默认 50;0 → 无上限;正整数 → 该值) + if "sub_agent_max_turns" in settings: + max_turns_raw = settings.get("sub_agent_max_turns") + if max_turns_raw is None: + if prefs.pop("sub_agent_max_turns", None) is not None: + dirty = True + else: + try: + max_turns = int(max_turns_raw) + except (TypeError, ValueError): + return jsonify({"success": False, "error": "最大轮次必须是整数"}), 400 + if max_turns < 0: + return jsonify({"success": False, "error": "最大轮次不能为负数(0 表示无上限)"}), 400 + prefs["sub_agent_max_turns"] = max_turns + dirty = True + if dirty: save_personalization_config(workspace.data_dir, prefs) return jsonify({"success": True}) except Exception as exc: diff --git a/static/src/components/personalization/PersonalizationDrawer.vue b/static/src/components/personalization/PersonalizationDrawer.vue index f67a9e8c..36432bac 100644 --- a/static/src/components/personalization/PersonalizationDrawer.vue +++ b/static/src/components/personalization/PersonalizationDrawer.vue @@ -1772,6 +1772,35 @@ + +