#!/usr/bin/env python3 """ ml/task_graph.py — Dask DAG-basert agent-koordinering for OPAX. Konsept (fra ML-dokumentet): Dask bygger en task graph (DAG) av operasjoner og avhengigheter. Lazy evaluation: beregninger utføres kun når .compute() kalles. Agent-funksjoner wrappes som @dask.delayed og kjøres parallelt. PS-analogi: OPAX orchestrator = DAG scheduler (server-node) Individuelle agenter = Dask-delayed tasks (worker-noder) """ import time from typing import Any, Callable, Dict, List, Optional import dask @dask.delayed def run_agent_task( agent_fn: Callable, payload: Dict[str, Any], agent_id: Optional[str] = None, ) -> Dict[str, Any]: """ Wrapper: kjører én agent som en Dask-delayed task. Returnerer resultat med metadata for telemetri og state store. """ _id = agent_id or getattr(agent_fn, "__name__", "unknown") start = time.monotonic() try: result = agent_fn(payload) success = True error = None except Exception as exc: # noqa: BLE001 result = None success = False error = str(exc) duration = round(time.monotonic() - start, 3) return { "agent": _id, "result": result, "success": success, "error": error, "duration_s": duration, "timestamp": time.time(), } def build_agent_dag( agent_fns: List[Callable], payload: Dict[str, Any], agent_ids: Optional[List[str]] = None, ) -> List: """ Bygger en liste av Dask-delayed tasks (én per agent). Agenter uten avhengigheter kjøres parallelt av scheduleren. Args: agent_fns: Liste av callable agentfunksjoner. payload: Felles input-dict til alle agenter. agent_ids: Valgfrie ID-er som matcher agent_fns (for logging). Returns: Liste av Dask-delayed objects — klar for execute_dag(). """ ids = agent_ids or [getattr(fn, "__name__", f"agent_{i}") for i, fn in enumerate(agent_fns)] return [run_agent_task(fn, payload, aid) for fn, aid in zip(agent_fns, ids)] def execute_dag( tasks: List, scheduler: str = "synchronous", ) -> List[Dict[str, Any]]: """ Kjører den ferdigbygde DAGen. Scheduler-valg: 'synchronous' — enkelt-tråd, ingen overhead (dev / unit-test) 'threads' — lokal multi-tråd, GIL-vennlig for IO-bound agenter 'processes' — multi-prosess, CPU-bound arbeid 'distributed' — Dask cluster (full skala, krever dask[distributed]) Returns: Liste av resultat-dicts fra run_agent_task. """ results = dask.compute(*tasks, scheduler=scheduler) return list(results)