agent-Specialization/modules/sub_agent/creation.py
JOJO 16aaea6b1e refactor(multi-agent): 子智能体统一显示名寻址并增强可靠性
- 全局 agent_id 转为内部实现细节:由 next_free_agent_id 自动分配对话级
  最小空闲正整数,不再接受模型指定、不出现在工具参数/结果文案中
- 角色内编号改为 peek + commit 两步:创建成功才消耗编号,失败不跳号
- 所有寻址类工具改用显示名:send_message/stop/terminate 传 display_name,
  get_sub_agent_status 传 display_names,sleep 的 wait_sub_agent_output,
  子侧 ask_other_agent 传 target_display_name,answer_other_agent 去掉
  source_agent_id
- _run_loop 对 _call_model 增加重试:最多 5 次、间隔 10s、仅零接收时重试,
  输出中断直接失败;多智能体模式下 5 次全失败转 idle 并向 Team Leader 报错
- 新增工具「正在调用」进度事件(calling),与 running/completed 共用
  tool_call id,前端支持同 id 原地更新
2026-08-27 09:21:30 +08:00

127 lines
5.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""子智能体创建、查询与会话槽位管理工具。"""
from __future__ import annotations
import time
import uuid
from pathlib import Path
from typing import Any, Dict, List, Optional, Set
from config import SUB_AGENT_DEFAULT_TIMEOUT, SUB_AGENT_MAX_ACTIVE
class SubAgentCreationMixin:
"""提供子智能体创建参数校验、任务ID生成、交付目录解析与槽位管理能力。"""
tasks: Dict[str, Dict[str, Any]]
conversation_agents: Dict[str, List[int]]
project_path: Path
def _select_task(
self,
task_id: Optional[str],
agent_id: Optional[int],
*,
include_idle: bool = False,
) -> Optional[Dict]:
self.reconcile_task_states()
if task_id:
return self.tasks.get(task_id)
if agent_id is None:
return None
# 多智能体模式下子智能体常驻 idle等待唤醒terminate/stop 需要能选中它们
allowed = {"pending", "running"}
if include_idle:
allowed = allowed | {"idle"}
candidates = [
task for task in self.tasks.values()
if task.get("agent_id") == agent_id and task.get("status") in allowed
]
if candidates:
candidates.sort(key=lambda item: item.get("created_at", 0), reverse=True)
return candidates[0]
return None
def _active_task_count(self, conversation_id: Optional[str] = None) -> int:
self.reconcile_task_states(conversation_id=conversation_id)
active = [t for t in self.tasks.values() if t.get("status") in {"pending", "running"}]
if conversation_id:
active = [t for t in active if t.get("conversation_id") == conversation_id]
return len(active)
def _ensure_agent_slot_available(self, conversation_id: str, agent_id: int) -> bool:
used = self.conversation_agents.setdefault(conversation_id, [])
return agent_id not in used
def next_free_agent_id(self, conversation_id: Optional[str], extra_used: Optional[Set[int]] = None) -> int:
"""分配对话级全局最小空闲 agent_id。
agent_id 是对话全局唯一的内部编号(不对外暴露:模型与用户只看到
角色内编号显示名)。多智能体模式下创建实例时由系统自动分配,
不接受调用方指定。
"""
used: Set[int] = set()
if conversation_id:
for aid in self.conversation_agents.get(conversation_id, []) or []:
try:
used.add(int(aid))
except (TypeError, ValueError):
continue
for task in self.tasks.values():
if conversation_id and task.get("conversation_id") != conversation_id:
continue
try:
aid = task.get("agent_id")
if aid is not None:
used.add(int(aid))
except (TypeError, ValueError):
continue
if extra_used:
used.update(extra_used)
n = 1
while n in used:
n += 1
return n
def _mark_agent_id_used(self, conversation_id: str, agent_id: int):
used = self.conversation_agents.setdefault(conversation_id, [])
if agent_id not in used:
used.append(agent_id)
def _validate_create_params(self, agent_id: Optional[int], summary: str, task: str, target_dir: Optional[str], *, multi_agent_mode: bool = False) -> Optional[str]:
if agent_id is None:
return "子智能体代号不能为空"
try:
agent_id = int(agent_id)
except ValueError:
return "子智能体代号必须是整数"
if agent_id <= 0:
return "子智能体代号必须为正整数"
if not summary or not summary.strip():
return "任务摘要不能为空"
if not task or not task.strip():
return "任务详情不能为空"
# 多智能体模式不需要交付目录
if not multi_agent_mode and target_dir is None:
return "指定文件夹不能为空"
return None
def _generate_task_id(self, agent_id: int) -> str:
suffix = uuid.uuid4().hex[:6]
return f"sub_{agent_id}_{int(time.time())}_{suffix}"
def _resolve_deliverables_dir(self, relative_dir: Optional[str], *, multi_agent_mode: bool = False) -> Path:
relative_dir = (relative_dir or "").strip()
# 多智能体模式:没有交付目录概念,直接使用项目根目录
if multi_agent_mode and not relative_dir:
return self.project_path.resolve()
if not relative_dir:
raise ValueError("交付目录不能为空,必须指定")
deliverables_path = (self.project_path / relative_dir).resolve()
if not str(deliverables_path).startswith(str(self.project_path)):
raise ValueError("交付目录必须位于项目目录内")
if deliverables_path.exists():
raise ValueError("交付目录必须为不存在的新目录")
deliverables_path.mkdir(parents=True, exist_ok=True)
return deliverables_path