""" AI RUNNER ÚNICO — executa receitas YAML de QUALQUER família de IA e produz saída padronizada: output/ai_runs/_/{resultados.json, ANALISE_IA.md, ledger.sha256} python3 scripts/ai_runner.py --receita data/receitas_ia/llm_cego.yaml python3 scripts/ai_runner.py --receita ... --dry # mostra passos sem rodar Princípios: zero-mock (passos reais, saída hashada) · hardware no meta · falha de passo aborta a receita e registra no JSON. """ import argparse import datetime import hashlib import json import os import platform import subprocess import sys from pathlib import Path ROOT = Path(__file__).resolve().parent.parent SAIDA_RAIZ = ROOT / "output" / "ai_runs" def sha256_file(p): h = hashlib.sha256() with open(p, "rb") as f: for chunk in iter(lambda: f.read(1 << 20), b""): h.update(chunk) return h.hexdigest() def deep_get(d, caminho): cur = d for parte in caminho.split("."): if not isinstance(cur, dict): return None cur = cur.get(parte) return cur def main(): ap = argparse.ArgumentParser() ap.add_argument("--receita", required=True) ap.add_argument("--hardware", default=None, help="cpu_local | gpu_vast (default: detecta pelo local do runner)") ap.add_argument("--dry", action="store_true") args = ap.parse_args() try: import yaml except ImportError: sys.exit("[ERRO] pip install pyyaml") rec = yaml.safe_load(Path(args.receita).read_text(encoding="utf-8")) stamp = datetime.datetime.now(datetime.timezone.utc).strftime("%Y%m%d_%H%M%S") hardware = args.hardware or ("gpu_vast" if os.environ.get("VAST_INSTANCE") else "cpu_local") run_dir = SAIDA_RAIZ / f"{stamp}_{rec['nome']}_{hardware}" run_dir.mkdir(parents=True, exist_ok=True) print("=" * 76) print(f"🧪 RECEITA {rec['nome']} ({rec['familia']}) · hardware={hardware}") print(f"Artefatos: {run_dir}") print("=" * 76) resultados = { "receita": rec["nome"], "familia": rec["familia"], "versao": str(rec.get("versao", "1")), "hardware": hardware, "host_fingerprint": platform.node(), "timestamp_utc": stamp, "passos": {}, "erro": None, } for passo in rec.get("passos", []): pid = passo["id"] cmd = " ".join(passo["cmd"].split()) print(f"\n──[{pid}] {passo.get('desc','')}\n $ {cmd}") if args.dry: continue proc = subprocess.run(cmd, shell=True, cwd=ROOT, capture_output=True, text=True, timeout=60 * 60 * 12) (run_dir / f"{pid}.log").write_text(proc.stdout[-20000:] + "\n|ERR|\n" + proc.stderr[-5000:]) if proc.returncode != 0: resultados["erro"] = f"passo {pid} falhou (rc={proc.returncode})" print(f" ✗ ABORTADO — ver {run_dir}/{pid}.log") break saidas = {} for glob_s in passo.get("saida_glob", []): import glob as _g hits = sorted(_g.glob(str(ROOT / glob_s))) if hits: mais_novo = max(hits, key=os.path.getmtime) alvo = run_dir / Path(mais_novo).name alvo.write_text(Path(mais_novo).read_text(encoding="utf-8")) saidas[str(glob_s)] = {"arquivo": Path(mais_novo).name, "sha256": sha256_file(mais_novo)[:16]} try: saidas["dados"] = json.loads(alvo.read_text(encoding="utf-8")) except Exception: pass resultados["passos"][pid] = {"rc": 0, "saidas": saidas} print(f" ✓ ok → coletado(s): {list(saidas) or 'nenhum'}") # métricas-chave extraídas dos dados coletados metricas = {} for m in rec.get("metricas_chave", []): for pid, info in resultados["passos"].items(): valor = deep_get(info.get("saidas", {}).get("dados", {}), m) if valor is not None: metricas[f"{pid}.{m}"] = valor resultados["metricas"] = metricas verdicto_tpl = rec.get("veredito_auto") resultados["veredito_auto"] = verdicto_tpl if not args.dry else "(dry-run)" (run_dir / "resultados.json").write_text( json.dumps(resultados, indent=2, ensure_ascii=False), encoding="utf-8") linhas_hash = [f"{sha256_file(run_dir / f)} {f}" for f in ["resultados.json"] + sorted( p.name for p in run_dir.glob("*.log"))] chain = hashlib.sha256("\n".join(linhas_hash).encode()).hexdigest() (run_dir / "ledger.sha256").write_text("\n".join(linhas_hash) + f"\n{chain} CHAIN_SHA256\n") analise = [f"# ANALISE_IA — {rec['nome']} ({hardware}) `{stamp}`\n", f"- Receita v{resultados['versao']} · chain `{chain[:16]}`", f"- Métricas-chave: ```json\n{json.dumps(metricas, indent=2)}```", f"- Erro: {resultados['erro'] or 'nenhum'}\n", "| Passo | rc | Saídas hashadas |\n|---|---|---|"] for pid, info in resultados["passos"].items(): analise.append(f"| {pid} | {info['rc']} | " + "; ".join(f"{k}:{v['sha256'][:8]}" for k, v in info["saidas"].items() if k != "dados") + " |") (run_dir / "ANALISE_IA.md").write_text("\n".join(analise), encoding="utf-8") print("\n" + "=" * 76) print(f"📊 MÉTRICAS: {json.dumps(metricas, ensure_ascii=False)}") print(f"🔗 CHAIN: {chain[:16]}… · ANALISE_IA.md pronto em {run_dir}") print("=" * 76) if __name__ == "__main__": main()