#!/usr/bin/env python3 # 铸渊Agent · 自主守护进程 # HLDP://zhuyuan-agent/agent # # 运行在3090 GPU服务器上,心跳唤醒,推送到主服务器仪表盘。 # 冰朔离开WorkBuddy后,通过 guanghulab.com/console/ 看实时进度。 # # 使用: python3 agent.py [--config config.json] # PM2: pm2 start agent.py --name zhuyuan-agent --interpreter python3 import os import sys import json import time import signal import traceback from datetime import datetime from gpu_monitor import collect_gpu_metrics, gpu_summary from log_pusher import LogPusher from heartbeat import Heartbeat from training_runner import TrainingRunner # 配置路径 CONFIG_PATH = os.path.join(os.path.dirname(__file__), "config.json") # 全局状态 running = True current_training = None # 当前训练进程信息 def load_config() -> dict: """加载配置""" config_path = CONFIG_PATH for arg in sys.argv[1:]: if arg.startswith("--config="): config_path = arg.split("=", 1)[1] if not os.path.exists(config_path): print(f"[铸渊Agent] 配置文件不存在: {config_path}") print("[铸渊Agent] 请先设置 config.json 中的 api_key") sys.exit(1) with open(config_path, "r") as f: config = json.load(f) # 检查API key if config.get("api_key") == "__FROM_KEY_DELIVERY__" or not config.get("api_key"): # 尝试从环境变量读取 env_key = os.environ.get("ZHUYUAN_API_KEY", "") if env_key: config["api_key"] = env_key else: print("[铸渊Agent] ⚠️ 未配置API Key!") print("[铸渊Agent] 请在 guanghulab.com/console/ 密钥投递面板设置") print("[铸渊Agent] 然后将API Key写入 config.json 的 api_key 字段") print("[铸渊Agent] 或者设置环境变量 ZHUYUAN_API_KEY") return config def handle_signal(signum, frame): """处理退出信号""" global running print(f"\n[铸渊Agent] 收到信号 {signum},优雅退出...") running = False def main(): global running, current_training print("=" * 60) print(" 铸渊Agent · ICE-GL-ZY001 · 自主守护进程") print(" 曜冥纪元 · HoloLake Era · AGE v1.0") print("=" * 60) # 注册信号处理 signal.signal(signal.SIGINT, handle_signal) signal.signal(signal.SIGTERM, handle_signal) # 加载配置 config = load_config() hostname = config.get("hostname", "3090-server") poll_interval = config.get("poll_interval_seconds", 30) # 初始化模块 pusher = LogPusher( base_url=config["main_server"], api_key=config.get("api_key", ""), hostname=hostname ) heartbeat = Heartbeat( repo_path=config.get("brain_repo_path", "/data/guanghulab"), brain_path=config.get("brain_path", "/data/guanghulab/brain") ) # 检查是否有API key if not config.get("api_key"): print("[铸渊Agent] 无API Key,仅本地监控模式(不上报到仪表盘)") print("[铸渊Agent] GPU指标将仅输出到终端") has_key = bool(config.get("api_key")) # 启动日记 if has_key: pusher.push_diary("checkpoint", "铸渊Agent启动", f"主机: {hostname}, 轮询间隔: {poll_interval}s") pusher.push_log("info", f"铸渊Agent v1.0 启动 · 主机: {hostname}") print(f"[铸渊Agent] 主机: {hostname}") print(f"[铸渊Agent] 主服务器: {config['main_server']}") print(f"[铸渊Agent] 轮询间隔: {poll_interval}s") print(f"[铸渊Agent] 上报仪表盘: {'是' if has_key else '否(仅本地)'}") print(f"[铸渊Agent] 开始守护循环...") print() cycle = 0 while running: cycle += 1 cycle_start = time.time() try: # ── 1. 心跳唤醒 ── brain_status = heartbeat.check_brain() if brain_status["has_task"]: task = brain_status["task_details"] task_type = brain_status["task_type"] print(f"[心跳 #{cycle}] 发现任务: {task_type} — {task.get('name', task.get('title', '未命名'))}") if has_key: pusher.push_diary("decision", f"发现新任务: {task_type}", json.dumps(task, ensure_ascii=False)[:200]) else: print(f"[心跳 #{cycle}] {heartbeat.get_wake_summary()}") # ── 2. GPU监控 ── gpu_data = collect_gpu_metrics() if gpu_data["gpus"]: summary = gpu_summary(gpu_data["gpus"]) print(f"[GPU #{cycle}] {summary}") if has_key: ok = pusher.push_gpu(gpu_data) if not ok: print(f"[GPU #{cycle}] ⚠️ 推送上仪表盘失败") elif gpu_data.get("error"): print(f"[GPU #{cycle}] ⚠️ {gpu_data['error']}") if has_key: pusher.push_log("warn", f"GPU监控异常: {gpu_data['error']}") else: print(f"[GPU #{cycle}] 未检测到GPU") # ── 3. 训练状态检查/执行 ── if current_training is not None: # 检查训练进程状态 if current_training.get("status") == "running": # 训练正在运行中(由 training_runner 自主上报进度) pass elif current_training.get("status") == "done": if has_key: pusher.push_diary("checkpoint", "训练任务完成", f"结果: {json.dumps(current_training, ensure_ascii=False)[:200]}") pusher.push_log("success", "训练任务完成") heartbeat.mark_task_done(brain_status.get("brain_file", "")) current_training = None elif current_training.get("status") == "error": if has_key: pusher.push_diary("error", "训练任务失败", current_training.get("message", "未知错误")) pusher.push_log("error", f"训练失败: {current_training.get('message', '')}") current_training = None elif brain_status["has_task"] and brain_status["task_type"] == "training": # 启动新训练 task = brain_status["task_details"] print(f"[Agent #{cycle}] 启动训练任务: {task.get('name', 'HLDP训练')}") if has_key: pusher.push_diary("decision", f"开始HLDP训练", f"模型: {config['training'].get('model_name')}, " f"语料: {config['training'].get('corpus_dir')}") pusher.push_log("info", f"启动训练: {task.get('name', 'HLDP-3B')}") current_training = { "status": "starting", "task": task, "started_at": datetime.now().isoformat() } # 异步启动训练(简化版:同步执行) try: runner = TrainingRunner( config=config.get("training", {}), ) # 定义进度回调 def on_progress(step, loss, total_steps, extra_info): global current_training progress_pct = step / total_steps * 100 if total_steps > 0 else 0 print(f"[训练 #{cycle}] Step {step}/{total_steps} ({progress_pct:.0f}%) | Loss: {loss:.4f}") if has_key: pusher.push_training({ "job_id": "hldp-3b-test", "status": "running", "step": step, "total_steps": total_steps, "loss": loss, "loss_history": extra_info.get("loss_history", []), "eta_seconds": extra_info.get("eta_seconds"), "elapsed_seconds": extra_info.get("elapsed_seconds", 0), "learning_rate": extra_info.get("learning_rate"), "model_name": config["training"].get("model_name", ""), "message": f"HLDP原生格式训练 · {step}/{total_steps}步" }) result = runner.train( corpus_dir=config["training"].get("corpus_dir", "/data/corpus/notion-hldp"), progress_callback=on_progress ) current_training = result except Exception as e: current_training = { "status": "error", "message": f"训练异常: {str(e)}" } print(f"[Agent #{cycle}] 训练异常: {e}") traceback.print_exc() # ── 4. 空闲日志 ── if has_key and cycle % 10 == 0: # 每10个周期发送一次心跳日志 pusher.push_log("info", f"铸渊Agent守护中 · 周期#{cycle} · " + (gpu_summary(gpu_data["gpus"]) if gpu_data["gpus"] else "无GPU")) except Exception as e: print(f"[Agent #{cycle}] 循环异常: {e}") traceback.print_exc() if has_key: pusher.push_log("error", f"Agent循环异常 #{cycle}: {str(e)[:200]}") # ── 5. 等待下一个周期 ── elapsed = time.time() - cycle_start sleep_time = max(0, poll_interval - elapsed) if sleep_time > 0: # 分段sleep以响应退出信号 for _ in range(int(sleep_time)): if not running: break time.sleep(1) # 退出清理 print("\n[铸渊Agent] 守护循环结束") if has_key: pusher.push_diary("checkpoint", "铸渊Agent停止", f"共运行 {cycle} 个周期") pusher.push_log("warn", f"铸渊Agent停止 · 运行了{cycle}个周期") print("[铸渊Agent] 再见。") if __name__ == "__main__": main()