Files
voidea/app/agents/meta_agent.py
T

177 lines
5.6 KiB
Python

"""MetaAgent — Orchestrator agent for VoIdea.
Configurable from admin panel:
- cron_schedule: str (default "0 9 * * 1,4")
- weights: dict[agent_name, float]
- sources_count: int (default 10)
- auto_create: bool (default True)
Runs a subset of agents based on weights and collects reports.
"""
import json
from datetime import datetime, timezone
from typing import Any
from app.agents.base import AgentResult, AgentStatus, AgentTrigger, BaseAgent
from app.agents.registry import registry
DEFAULT_CONFIG: dict[str, Any] = {
"cron_schedule": "0 9 * * 1,4",
"weights": {},
"sources_count": 10,
"auto_create": True,
}
class MetaAgent(BaseAgent):
"""Orchestrator agent that coordinates other agents."""
name = "meta_agent"
version = "1.0.0"
description = "Orchestrates other agents based on configurable schedule and weights"
triggers = [
AgentTrigger.MANUAL,
AgentTrigger.CRON,
AgentTrigger.API,
]
def __init__(self, config: dict[str, Any] | None = None):
super().__init__()
self._config = {**DEFAULT_CONFIG, **(config or {})}
@property
def config(self) -> dict[str, Any]:
return self._config
@config.setter
def config(self, value: dict[str, Any]) -> None:
self._config = {**DEFAULT_CONFIG, **value}
async def run(self, context: dict[str, Any] | None = None) -> AgentResult:
"""Execute meta-agent orchestration.
Context can contain:
- action: str (orchestrate | report | status) — default "orchestrate"
- target_agents: list[str] — subset of agents to run (default: all)
- sources_count: int — override config
"""
await self.set_running("orchestration")
try:
action = (context or {}).get("action", "orchestrate")
if action == "status":
return await self._get_status()
elif action == "report":
return await self._generate_report()
else:
return await self._orchestrate(context or {})
except Exception as e:
await self.set_error(str(e))
return AgentResult(
success=False,
message=f"MetaAgent failed: {str(e)}",
errors=[str(e)],
)
async def health_check(self) -> bool:
return True
async def _orchestrate(self, context: dict[str, Any]) -> AgentResult:
"""Run target agents and collect results."""
target_agents: list[str] | None = context.get("target_agents")
sources_count = context.get("sources_count", self._config["sources_count"])
weights = self._config.get("weights", {})
all_agents = registry.list_agents()
if not all_agents:
await self.set_idle()
return AgentResult(
success=True,
message="No agents registered",
data={"orchestrated": 0},
)
candidates = []
for a in all_agents:
name = a["name"]
if name == self.name:
continue
weight = weights.get(name, 1.0)
if weight <= 0:
continue
candidates.append((name, weight))
if not candidates:
candidates = [(a["name"], 1.0) for a in all_agents if a["name"] != self.name]
if target_agents:
candidates = [c for c in candidates if c[0] in target_agents]
max_sources = min(sources_count, len(candidates))
selected = candidates[:max_sources]
results: dict[str, Any] = {}
for name, _weight in selected:
try:
result = await registry.run_agent(name, {"trigger": "meta_agent"})
results[name] = {
"success": result.success,
"message": result.message,
"duration_ms": result.duration_ms,
}
except Exception as e:
results[name] = {
"success": False,
"message": str(e),
}
success_count = sum(1 for r in results.values() if r["success"])
total_duration = sum(r.get("duration_ms", 0) for r in results.values())
await self.set_idle()
return AgentResult(
success=True,
message=f"Orchestrated {len(results)} agents ({success_count} ok)",
data={
"orchestrated": len(results),
"success_count": success_count,
"agents": results,
"total_duration_ms": total_duration,
"config": {
"cron_schedule": self._config["cron_schedule"],
"sources_count": sources_count,
"auto_create": self._config["auto_create"],
},
},
)
async def _generate_report(self) -> AgentResult:
"""Generate summary report of all agent statuses."""
all_agents = registry.list_agents()
metrics = registry.get_metrics_all()
return AgentResult(
success=True,
message=f"Report: {len(all_agents)} agents",
data={
"agents": all_agents,
"metrics": metrics,
"generated_at": datetime.now(timezone.utc).isoformat(),
},
)
async def _get_status(self) -> AgentResult:
return AgentResult(
success=True,
message="Meta-agent operational",
data={
"status": self.status.value,
"config": self._config,
"last_run": self._last_run.isoformat() if self._last_run else None,
},
)