feat(backend): Agent 系统和 API 增强
- task_delegator: 完善团队任务分发逻辑 - runtime API: 增强运行时管理功能 - skills_manager: 技能管理改进 - tool_guard: 工具调用守卫优化 - evo_agent: 核心 Agent 改进 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,9 @@ from agentscope.message import Msg
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Default timeout for subagent execution (seconds)
|
||||
DEFAULT_EXECUTION_TIMEOUT = 120.0
|
||||
|
||||
|
||||
# Type alias for subagent specification
|
||||
SubagentSpec = Dict[str, Any]
|
||||
@@ -56,19 +59,26 @@ class TaskDelegator:
|
||||
}
|
||||
"""
|
||||
|
||||
def __init__(self, messenger: Any, registry: Any):
|
||||
def __init__(self, agent: Any):
|
||||
"""Initialize TaskDelegator.
|
||||
|
||||
Args:
|
||||
messenger: AgentMessenger for communication
|
||||
registry: AgentRegistry for agent lookup
|
||||
agent: Parent EvoAgent instance for accessing model, formatter, workspace
|
||||
"""
|
||||
self._messenger = messenger
|
||||
self._registry = registry
|
||||
self._agent = agent
|
||||
# Get messenger from parent agent if available
|
||||
self._messenger = getattr(agent, "messenger", None)
|
||||
self._registry = getattr(agent, "_registry", None)
|
||||
self._subagents: Dict[str, Any] = {}
|
||||
self._dynamic_subagents: Dict[str, SubagentSpec] = {}
|
||||
self._tasks: Dict[str, asyncio.Task] = {}
|
||||
|
||||
# Extract model and formatter from parent agent
|
||||
self._model = getattr(agent, "model", None)
|
||||
self._formatter = getattr(agent, "formatter", None)
|
||||
self._workspace_dir = getattr(agent, "workspace_dir", None)
|
||||
self._config_name = getattr(agent, "config_name", None)
|
||||
|
||||
async def delegate(
|
||||
self,
|
||||
agent_id: str,
|
||||
@@ -187,7 +197,7 @@ class TaskDelegator:
|
||||
"""Get copy of active tasks dict."""
|
||||
return dict(self._tasks)
|
||||
|
||||
def delegate_task(
|
||||
async def delegate_task(
|
||||
self,
|
||||
task_type: str,
|
||||
task_data: Dict[str, Any],
|
||||
@@ -239,8 +249,8 @@ class TaskDelegator:
|
||||
else:
|
||||
effective_target = "default"
|
||||
|
||||
# Execute the task
|
||||
task_result = self._execute_task(
|
||||
# Execute the task (async)
|
||||
task_result = await self._execute_task(
|
||||
task_type=task_type,
|
||||
task_data=task_data,
|
||||
target_agent=effective_target,
|
||||
@@ -263,13 +273,13 @@ class TaskDelegator:
|
||||
"error": str(e),
|
||||
}
|
||||
|
||||
def _execute_task(
|
||||
async def _execute_task(
|
||||
self,
|
||||
task_type: str,
|
||||
task_data: Dict[str, Any],
|
||||
target_agent: str,
|
||||
) -> Any:
|
||||
"""Execute the delegated task.
|
||||
) -> Dict[str, Any]:
|
||||
"""Execute the delegated task with a real subagent.
|
||||
|
||||
Args:
|
||||
task_type: Type of task
|
||||
@@ -277,48 +287,315 @@ class TaskDelegator:
|
||||
target_agent: Target agent identifier
|
||||
|
||||
Returns:
|
||||
Task execution result
|
||||
Task execution result with success/failure info
|
||||
"""
|
||||
task_content = task_data.get("task", task_data.get("prompt", ""))
|
||||
timeout = task_data.get("timeout", DEFAULT_EXECUTION_TIMEOUT)
|
||||
|
||||
# Check if we have a dynamic subagent spec for this target
|
||||
agent_spec = self._dynamic_subagents.get(target_agent)
|
||||
|
||||
if agent_spec:
|
||||
logger.info(
|
||||
"Executing task '%s' with dynamic subagent '%s' (prompt: %s)",
|
||||
"Executing task '%s' with dynamic subagent '%s'",
|
||||
task_type,
|
||||
target_agent,
|
||||
agent_spec.get("prompt", "")[:50],
|
||||
)
|
||||
# In a full implementation, this would create and run an actual agent
|
||||
# For now, return a structured result indicating the task was received
|
||||
return await self._create_and_run_subagent(
|
||||
agent_name=target_agent,
|
||||
agent_spec=agent_spec,
|
||||
task_content=task_content,
|
||||
task_type=task_type,
|
||||
timeout=timeout,
|
||||
)
|
||||
|
||||
# Fallback: try to use parent agent's model to process the task directly
|
||||
logger.info(
|
||||
"Executing task '%s' with parent agent '%s' (no dynamic subagent)",
|
||||
task_type,
|
||||
target_agent,
|
||||
)
|
||||
return await self._run_with_parent_agent(
|
||||
task_content=task_content,
|
||||
task_type=task_type,
|
||||
timeout=timeout,
|
||||
)
|
||||
|
||||
async def _create_and_run_subagent(
|
||||
self,
|
||||
agent_name: str,
|
||||
agent_spec: SubagentSpec,
|
||||
task_content: str,
|
||||
task_type: str,
|
||||
timeout: float,
|
||||
) -> Dict[str, Any]:
|
||||
"""Create and run a dynamic subagent.
|
||||
|
||||
Args:
|
||||
agent_name: Name identifier for the subagent
|
||||
agent_spec: Subagent specification (description, prompt, tools, model)
|
||||
task_content: Task prompt to send to the subagent
|
||||
task_type: Type of task
|
||||
timeout: Execution timeout in seconds
|
||||
|
||||
Returns:
|
||||
Dict with execution results
|
||||
"""
|
||||
subagent_id = f"subagent_{agent_name}_{uuid.uuid4().hex[:8]}"
|
||||
|
||||
try:
|
||||
# Create subagent instance
|
||||
subagent = await self._create_subagent(
|
||||
subagent_id=subagent_id,
|
||||
agent_spec=agent_spec,
|
||||
)
|
||||
|
||||
if subagent is None:
|
||||
return {
|
||||
"task_type": task_type,
|
||||
"task": task_content,
|
||||
"subagent": agent_name,
|
||||
"status": "failed",
|
||||
"error": "Failed to create subagent",
|
||||
"message": f"Could not instantiate subagent '{agent_name}'",
|
||||
}
|
||||
|
||||
# Store for potential cleanup
|
||||
self._subagents[subagent_id] = subagent
|
||||
|
||||
# Execute with timeout
|
||||
result = await asyncio.wait_for(
|
||||
self._run_subagent(subagent, task_content),
|
||||
timeout=timeout,
|
||||
)
|
||||
|
||||
# Extract response content
|
||||
response_content = ""
|
||||
if isinstance(result, Msg):
|
||||
response_content = result.content
|
||||
elif hasattr(result, "content"):
|
||||
response_content = str(result.content)
|
||||
elif isinstance(result, dict):
|
||||
response_content = result.get("content", str(result))
|
||||
else:
|
||||
response_content = str(result)
|
||||
|
||||
logger.info(
|
||||
"Subagent '%s' completed task '%s' successfully",
|
||||
agent_name,
|
||||
task_type,
|
||||
)
|
||||
|
||||
return {
|
||||
"task_type": task_type,
|
||||
"task": task_content,
|
||||
"subagent": {
|
||||
"name": target_agent,
|
||||
"name": agent_name,
|
||||
"id": subagent_id,
|
||||
"description": agent_spec.get("description", ""),
|
||||
"prompt": agent_spec.get("prompt", ""),
|
||||
"tools": agent_spec.get("tools", []),
|
||||
},
|
||||
"status": "completed",
|
||||
"message": f"Task '{task_type}' executed with dynamic subagent '{target_agent}'",
|
||||
"response": response_content,
|
||||
"message": f"Task '{task_type}' executed with subagent '{agent_name}'",
|
||||
}
|
||||
|
||||
# Fallback: execute with default behavior
|
||||
logger.info(
|
||||
"Executing task '%s' with default agent '%s'",
|
||||
task_type,
|
||||
target_agent,
|
||||
except asyncio.TimeoutError:
|
||||
logger.warning(
|
||||
"Subagent '%s' timed out after %.1f seconds for task '%s'",
|
||||
agent_name,
|
||||
timeout,
|
||||
task_type,
|
||||
)
|
||||
# Cancel the task if still running
|
||||
if subagent_id in self._subagents:
|
||||
self._subagents.pop(subagent_id, None)
|
||||
return {
|
||||
"task_type": task_type,
|
||||
"task": task_content,
|
||||
"subagent": agent_name,
|
||||
"status": "timeout",
|
||||
"error": f"Execution timed out after {timeout} seconds",
|
||||
"message": f"Task '{task_type}' timed out for subagent '{agent_name}'",
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
"Subagent '%s' failed for task '%s': %s",
|
||||
agent_name,
|
||||
task_type,
|
||||
e,
|
||||
exc_info=True,
|
||||
)
|
||||
# Cleanup on failure
|
||||
if subagent_id in self._subagents:
|
||||
self._subagents.pop(subagent_id, None)
|
||||
return {
|
||||
"task_type": task_type,
|
||||
"task": task_content,
|
||||
"subagent": agent_name,
|
||||
"status": "error",
|
||||
"error": str(e),
|
||||
"message": f"Task '{task_type}' failed for subagent '{agent_name}': {e}",
|
||||
}
|
||||
|
||||
async def _create_subagent(
|
||||
self,
|
||||
subagent_id: str,
|
||||
agent_spec: SubagentSpec,
|
||||
) -> Optional[Any]:
|
||||
"""Create a subagent instance.
|
||||
|
||||
Uses the parent agent's model/formatter to create a lightweight
|
||||
subagent for task execution.
|
||||
|
||||
Args:
|
||||
subagent_id: Unique identifier for the subagent
|
||||
agent_spec: Subagent specification
|
||||
|
||||
Returns:
|
||||
Subagent instance or None if creation fails
|
||||
"""
|
||||
try:
|
||||
# Import here to avoid circular imports
|
||||
from agentscope.memory import InMemoryMemory
|
||||
|
||||
# Get model and formatter from parent
|
||||
model = self._model
|
||||
formatter = self._formatter
|
||||
|
||||
if model is None:
|
||||
logger.error("Cannot create subagent: parent agent has no model")
|
||||
return None
|
||||
|
||||
# Build system prompt from agent spec
|
||||
description = agent_spec.get("description", "")
|
||||
prompt_template = agent_spec.get("prompt", "")
|
||||
system_prompt = f"""You are {description}
|
||||
|
||||
{prompt_template}
|
||||
|
||||
Your task is to complete the user's request below.
|
||||
"""
|
||||
|
||||
# Create a minimal ReActAgent as the subagent
|
||||
from agentscope.agent import ReActAgent
|
||||
|
||||
subagent = ReActAgent(
|
||||
name=subagent_id,
|
||||
model=model,
|
||||
sys_prompt=system_prompt,
|
||||
toolkit=None, # Could load tools from agent_spec.get("tools", [])
|
||||
memory=InMemoryMemory(),
|
||||
formatter=formatter,
|
||||
max_iters=agent_spec.get("max_iters", 5),
|
||||
)
|
||||
|
||||
logger.debug("Created subagent: %s", subagent_id)
|
||||
return subagent
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
"Failed to create subagent '%s': %s",
|
||||
subagent_id,
|
||||
e,
|
||||
exc_info=True,
|
||||
)
|
||||
return None
|
||||
|
||||
async def _run_subagent(
|
||||
self,
|
||||
subagent: Any,
|
||||
task_content: str,
|
||||
) -> Any:
|
||||
"""Run a subagent with the given task.
|
||||
|
||||
Args:
|
||||
subagent: Subagent instance
|
||||
task_content: Task prompt
|
||||
|
||||
Returns:
|
||||
Agent response (Msg or similar)
|
||||
"""
|
||||
from agentscope.message import Msg
|
||||
|
||||
# Create message for the subagent
|
||||
task_msg = Msg(
|
||||
name="user",
|
||||
content=task_content,
|
||||
role="user",
|
||||
)
|
||||
return {
|
||||
"task_type": task_type,
|
||||
"task": task_content,
|
||||
"target_agent": target_agent,
|
||||
"status": "completed",
|
||||
"message": f"Task '{task_type}' executed with agent '{target_agent}'",
|
||||
}
|
||||
|
||||
# Execute the agent
|
||||
response = await subagent.reply(task_msg)
|
||||
return response
|
||||
|
||||
async def _run_with_parent_agent(
|
||||
self,
|
||||
task_content: str,
|
||||
task_type: str,
|
||||
timeout: float,
|
||||
) -> Dict[str, Any]:
|
||||
"""Run task using the parent agent directly.
|
||||
|
||||
Used when no dynamic subagent is defined.
|
||||
|
||||
Args:
|
||||
task_content: Task prompt
|
||||
task_type: Type of task
|
||||
timeout: Execution timeout
|
||||
|
||||
Returns:
|
||||
Dict with execution results
|
||||
"""
|
||||
try:
|
||||
result = await asyncio.wait_for(
|
||||
self._agent.reply(Msg(
|
||||
name="user",
|
||||
content=task_content,
|
||||
role="user",
|
||||
)),
|
||||
timeout=timeout,
|
||||
)
|
||||
|
||||
response_content = ""
|
||||
if isinstance(result, Msg):
|
||||
response_content = result.content
|
||||
elif hasattr(result, "content"):
|
||||
response_content = str(result.content)
|
||||
else:
|
||||
response_content = str(result)
|
||||
|
||||
return {
|
||||
"task_type": task_type,
|
||||
"task": task_content,
|
||||
"status": "completed",
|
||||
"response": response_content,
|
||||
"message": f"Task '{task_type}' executed with parent agent",
|
||||
}
|
||||
|
||||
except asyncio.TimeoutError:
|
||||
return {
|
||||
"task_type": task_type,
|
||||
"task": task_content,
|
||||
"status": "timeout",
|
||||
"error": f"Execution timed out after {timeout} seconds",
|
||||
"message": f"Task '{task_type}' timed out",
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
"Parent agent failed for task '%s': %s",
|
||||
task_type,
|
||||
e,
|
||||
exc_info=True,
|
||||
)
|
||||
return {
|
||||
"task_type": task_type,
|
||||
"task": task_content,
|
||||
"status": "error",
|
||||
"error": str(e),
|
||||
"message": f"Task '{task_type}' failed: {e}",
|
||||
}
|
||||
|
||||
def get_dynamic_subagent(self, name: str) -> Optional[SubagentSpec]:
|
||||
"""Get a dynamically defined subagent specification.
|
||||
|
||||
Reference in New Issue
Block a user