266 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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()