177 lines
5.6 KiB
Python
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,
|
|
},
|
|
)
|