fix(personalization): 修复子智能体最大执行轮次保存丢失,多智能体豁免该上限

- sanitize_personalization_payload 白名单注册 sub_agent_max_turns /
  sub_agent_compress_threshold_tokens(此前保存时被静默丢弃,刷新回默认)
- PUT /api/multiagent/settings 恢复默认由 pop() 改为写显式 null,
  绕开 save 的 fallback=existing 复活旧值问题
- task.py:multi_agent_mode 任务一律不设轮次上限(含历史任务恢复),
  该设置仅对传统后台子智能体生效
- 个人中心文案标明适用范围
This commit is contained in:
JOJO 2026-08-12 19:14:22 +08:00
parent ff927d6739
commit 34bb5d61cc
5 changed files with 61 additions and 15 deletions

View File

@ -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 避免触发后台完成通知轮询,保持主对话输入区可用。

View File

@ -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未设置下游默认 500 → 无上限;正整数 → 该值。"""
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 = []

View File

@ -79,11 +79,15 @@ 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,仅传统模式生效
# 未设置 → 默认 500或负数→ None 表示无上限;正整数 → 该值
# 多智能体模式的子智能体是长期协作成员,一律无上限,不受该设置约束
if multi_agent_mode:
self.max_turns: Optional[int] = None
else:
_raw_max_turns = task_record.get("max_turns")
if _raw_max_turns is None:
self.max_turns: Optional[int] = 50
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

View File

@ -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写入显式 nullsanitize 会保留 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)

View File

@ -1594,7 +1594,7 @@
<span class="settings-row-copy">
<span class="settings-row-title">最大执行轮次</span>
<span class="settings-row-desc">
子智能体单次任务的最大执行轮次一轮 = 一次模型调用留空默认 50 0 表示无上限慎用失控任务会持续消耗 API 额度
传统后台子智能体单次任务的最大执行轮次一轮 = 一次模型调用留空默认 50 0 表示无上限慎用失控任务会持续消耗 API 额度多智能体模式的团队成员是长期协作角色不受此限制
</span>
</span>
<div style="display: flex; gap: 6px; align-items: center">
@ -2580,7 +2580,7 @@ watch(
const subAgentRoles = ref<any[]>([]);
const subAgentRolesLoading = ref(false);
const subAgentCompressThreshold = ref(150000);
/** 子智能体最大执行轮次null/'' = 默认 500 = 无上限;正整数 = 该值 */
/** 子智能体最大执行轮次(仅传统后台子智能体;多智能体成员不受限)null/'' = 默认 500 = 无上限;正整数 = 该值 */
const subAgentMaxTurns = ref<number | null>(null);
const subAgentSettingsSaving = ref(false);
const subAgentModels = ref<any[]>([]);