diff --git a/core/main_terminal_parts/tools_execution.py b/core/main_terminal_parts/tools_execution.py index 68a13c95..8f5dfe43 100644 --- a/core/main_terminal_parts/tools_execution.py +++ b/core/main_terminal_parts/tools_execution.py @@ -2032,16 +2032,13 @@ 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" - # 读取子智能体压缩阈值与最大轮次配置 + # 读取子智能体压缩阈值配置(多智能体成员长期存在,不设轮次上限, + # sub_agent_max_turns 仅对传统后台子智能体生效,这里不读取) _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 调用) @@ -2059,7 +2056,7 @@ class MainTerminalToolsExecutionMixin: system_prompt=system_prompt, task_message=task_message, compress_threshold_tokens=_compress_threshold, - max_turns=_max_turns, + max_turns=None, # 多智能体成员不设轮次上限(task.py 对 multi_agent_mode 亦强制豁免) ) # 在多智能体模式下,子智能体是团队协作成员,不是传统后台任务。 # run_in_background=False 避免触发后台完成通知轮询,保持主对话输入区可用。 diff --git a/modules/personalization_manager.py b/modules/personalization_manager.py index 6de1bebf..65b3833a 100644 --- a/modules/personalization_manager.py +++ b/modules/personalization_manager.py @@ -119,6 +119,9 @@ DEFAULT_PERSONALIZATION_CONFIG: Dict[str, Any] = { "goal_end_conditions": ["max_turns"], # 结束方式,可多选:max_turns / max_tokens "goal_max_turns": GOAL_MAX_TURNS_DEFAULT, # 最多自动续命轮数 "goal_max_tokens": None, # 累计(输入+输出)token 上限;None 表示不启用 + # 传统模式子智能体设置(个人空间-子智能体管理;与多智能体无关) + "sub_agent_compress_threshold_tokens": 150000, # 子智能体上下文压缩阈值,最小 10000 + "sub_agent_max_turns": None, # 子智能体最大执行轮次:None-默认 50 / 0-无上限 / 正整数-该值 } __all__ = [ @@ -630,9 +633,48 @@ def sanitize_personalization_payload( base.get("goal_max_tokens"), min_value=GOAL_MAX_TOKENS_MIN, max_value=GOAL_MAX_TOKENS_MAX ) + # 传统模式子智能体:上下文压缩阈值(最小 10000,与 PUT /api/multiagent/settings 校验一致) + if "sub_agent_compress_threshold_tokens" in data: + base["sub_agent_compress_threshold_tokens"] = ( + _sanitize_optional_int( + data.get("sub_agent_compress_threshold_tokens"), + min_value=10000, + max_value=10_000_000, + ) + or 150000 + ) + else: + base["sub_agent_compress_threshold_tokens"] = ( + _sanitize_optional_int( + base.get("sub_agent_compress_threshold_tokens"), + min_value=10000, + max_value=10_000_000, + ) + or 150000 + ) + + # 传统模式子智能体:最大执行轮次(None-默认 50 / 0-无上限 / 正整数-该值) + if "sub_agent_max_turns" in data: + base["sub_agent_max_turns"] = _sanitize_sub_agent_max_turns(data.get("sub_agent_max_turns")) + else: + base["sub_agent_max_turns"] = _sanitize_sub_agent_max_turns(base.get("sub_agent_max_turns")) + return base +def _sanitize_sub_agent_max_turns(value: Any) -> Optional[int]: + """清洗子智能体最大轮次:None/''/非法值/负数 → None(未设置,下游默认 50);0 → 无上限;正整数 → 该值。""" + if value is None or value == "" or isinstance(value, bool): + return None + try: + parsed = int(value) + except (TypeError, ValueError): + return None + if parsed < 0: + return None + return min(parsed, 100_000) # 防御性上限;需要更大时用 0 表示无上限 + + def _sanitize_goal_end_conditions(value: Any) -> list: """清洗目标模式结束方式列表,保证至少包含 max_turns。""" cleaned: list = [] diff --git a/modules/sub_agent/task.py b/modules/sub_agent/task.py index 53d254bf..d6a6bf85 100644 --- a/modules/sub_agent/task.py +++ b/modules/sub_agent/task.py @@ -79,14 +79,18 @@ class SubAgentTask: # 上下文压缩配置 # 默认阈值 150k tokens,可由外部覆盖(如个人空间子智能体管理配置) self.compress_threshold_tokens: int = int(task_record.get("compress_threshold_tokens") or 150_000) - # 最大执行轮次(对应个人空间子智能体设置项 sub_agent_max_turns): + # 最大执行轮次(对应个人空间子智能体设置项 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 + # 多智能体模式的子智能体是长期协作成员,一律无上限,不受该设置约束 + if multi_agent_mode: + self.max_turns: Optional[int] = None else: - _max_turns_int = int(_raw_max_turns) - self.max_turns = _max_turns_int if _max_turns_int > 0 else None + _raw_max_turns = task_record.get("max_turns") + if _raw_max_turns is None: + self.max_turns = 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 diff --git a/server/multi_agent.py b/server/multi_agent.py index 954b671d..03d74475 100644 --- a/server/multi_agent.py +++ b/server/multi_agent.py @@ -250,8 +250,11 @@ def update_multi_agent_settings_api(): 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: + # 置空恢复默认 50:写入显式 null(sanitize 会保留 None),不能 pop—— + # save_personalization_config 的 fallback=existing 会把已存在的旧值带回来 + if prefs.get("sub_agent_max_turns") is not None: dirty = True + prefs["sub_agent_max_turns"] = None else: try: max_turns = int(max_turns_raw) diff --git a/static/src/components/personalization/PersonalizationDrawer.vue b/static/src/components/personalization/PersonalizationDrawer.vue index 94c1938a..7acf5296 100644 --- a/static/src/components/personalization/PersonalizationDrawer.vue +++ b/static/src/components/personalization/PersonalizationDrawer.vue @@ -1594,7 +1594,7 @@ 最大执行轮次 - 子智能体单次任务的最大执行轮次(一轮 = 一次模型调用)。留空默认 50 轮;填 0 表示无上限(慎用,失控任务会持续消耗 API 额度) + 传统后台子智能体单次任务的最大执行轮次(一轮 = 一次模型调用)。留空默认 50 轮;填 0 表示无上限(慎用,失控任务会持续消耗 API 额度)。多智能体模式的团队成员是长期协作角色,不受此限制