#!/usr/bin/env python3 """ 学习层 — 反思·抽象·应用·探索 =============================== 小唯的最高认知层,把经验转化为能力。 三层学习: 1. 事实学习: "xxx模型在yyy时段慢" → 织忆 2. 技能学习: "做调研的最佳流程是A→B→C" → skill 3. 元学习: "我缺少yyy能力" → 主动探索 用法: learner.py reflect → 反思近期经验,提取教训 learner.py learn → 执行学习循环(产出 skill/配置) learner.py plan → 生成学习计划(下次学什么) learner.py status → 查看学习进度 """ import json, os, re, sys, time, subprocess from datetime import datetime, timezone from collections import defaultdict, Counter HOME = os.path.expanduser("~") HERMES = HOME + "/.hermes" D = HERMES + "/daemon" LEARNER_DIR = HERMES + "/learner" STATE_FILE = LEARNER_DIR + "/state.json" SKILL_HEALTH = HERMES + "/skill-health.json" OPT_REPORT = HERMES + "/optimization-report.json" def log(msg): ts = datetime.now().strftime("%H:%M:%S") print(f"[LEARN] {ts} {msg}", flush=True) def load_state(): os.makedirs(LEARNER_DIR, exist_ok=True) if os.path.exists(STATE_FILE): with open(STATE_FILE) as f: return json.load(f) return { "version": 1, "created_at": datetime.now(timezone.utc).isoformat(), "total_cycles": 0, "skills_created": 0, "skills_archived": 0, "configs_changed": 0, "memories_added": 0, "learned_items": [], "in_progress": [], "tracked_metrics": { "avg_skill_score": [], "health_score": [], "model_stable_rate": [], }, } def save_state(state): os.makedirs(LEARNER_DIR, exist_ok=True) with open(STATE_FILE, "w") as f: json.dump(state, f, indent=2, ensure_ascii=False) def shell(cmd, timeout=10): try: r = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=timeout) return r.returncode, r.stdout.strip()[:500], r.stderr.strip()[:200] except: return -1, "", "timeout" # ====== 采集经验 ====== def collect_experiences(): """从各数据源采集近期经验""" experiences = [] # 1. Daemon journal: 近期事件 jf = D + "/journal.jsonl" if os.path.exists(jf): with open(jf) as f: for line in f: try: entry = json.loads(line) ts = entry.get("timestamp", "") # 只看最近24h if ts: try: t = datetime.fromisoformat(ts) if (datetime.now(timezone.utc) - t).total_seconds() > 86400: continue except: pass experiences.append({ "source": "daemon", "type": entry.get("type", "unknown"), "summary": entry.get("summary", ""), "timestamp": ts, }) except: pass # 2. Skill health: 技能质量趋势 if os.path.exists(SKILL_HEALTH): with open(SKILL_HEALTH) as f: try: report = json.load(f) s = report.get("summary", {}) experiences.append({ "source": "skill_health", "type": "snapshot", "summary": f"技能: {s.get('active',0)}活跃, 均分{s.get('avg_score',0)}, {s.get('needs_attention',0)}需关注", }) except: pass # 3. Daemon context: 运行状态 cf = D + "/context.json" if os.path.exists(cf): with open(cf) as f: try: ctx = json.load(f) experiences.append({ "source": "daemon_state", "type": "state", "summary": f"Daemon: {ctx.get('tick_count',0)}ticks, {ctx.get('solved_count',0)}已解决, {ctx.get('learned_count',0)}已学会", }) except: pass # 4. 优化报告 if os.path.exists(OPT_REPORT): with open(OPT_REPORT) as f: try: report = json.load(f) experiences.append({ "source": "optimizer", "type": "health", "summary": f"健康分: {report.get('health_score', '?')}/100, 瓶颈: {len(report.get('bottlenecks', []))}个", }) except: pass return experiences # ====== 模式提取 ====== def extract_patterns(experiences): """从经验中提取重复模式""" patterns = [] # 按类型统计 type_counts = Counter(e["type"] for e in experiences) # 告警模式 alerts = [e for e in experiences if e["type"] in ("alert", "process_down", "error")] if len(alerts) >= 2: patterns.append({ "type": "recurring_issue", "confidence": min(len(alerts) * 20, 90), "desc": f"近期出现 {len(alerts)} 次告警/异常", "details": [a["summary"] for a in alerts[:3]], "suggested_action": "检查看门狗日志,排查根因", }) # 技能模式 skill_exps = [e for e in experiences if e["source"] == "skill_health"] for s in skill_exps: if "需关注" in s["summary"]: # 提取数字 nums = re.findall(r'\d+', s["summary"]) if len(nums) >= 3 and int(nums[2]) > 50: patterns.append({ "type": "skill_quality_gap", "confidence": 80, "desc": f"大量技能需要关注 ({nums[2]}个)", "suggested_action": "运行 skill-manager.py archive 清理低分技能", }) # 学习进度 solved_exps = [e for e in experiences if e["type"] in ("solve_auto", "learn", "solve_start")] if solved_exps: patterns.append({ "type": "learning_progress", "confidence": 70, "desc": f"近期解决了 {len(solved_exps)} 个问题/学会了新方案", "suggested_action": "持续监控方案库的命中率", }) return patterns # ====== 差距分析 ====== def analyze_gaps(state): """分析能力差距""" gaps = [] learned_names = {item["name"] for item in state.get("learned_items", [])} # 检查已有系统的覆盖度 systems = { "记忆": os.path.exists(HERMES + "/plugins/zhiyi/__init__.py") or os.path.exists(HERMES + "/skills/zhiyi"), "技能管理": os.path.exists(HERMES + "/scripts/skill-manager.py"), "优化": os.path.exists(HERMES + "/scripts/optimizer.py"), "学习": os.path.exists(HERMES + "/scripts/learner.py"), "配置保护": os.path.exists(HERMES + "/scripts/config-protector.sh"), "ao团队": "npx" in os.popen("which npx 2>/dev/null || echo ''").read(), "持久意识": os.path.exists(D + "/context.json"), } built = sum(1 for v in systems.values() if v) total = len(systems) coverage = built / total * 100 gaps.append({ "area": "system_coverage", "coverage": f"{coverage:.0f}%", "built": built, "total": total, "missing": [k for k, v in systems.items() if not v], }) # 技能层面差距 if os.path.exists(SKILL_HEALTH): with open(SKILL_HEALTH) as f: try: report = json.load(f) except: report = {} s = report.get("summary", {}) gaps.append({ "area": "skill_quality", "avg_score": s.get("avg_score", 0), "d_count": s.get("grades", {}).get("D", 0), "needs_attention": s.get("needs_attention", 0), }) # 学习进度 gaps.append({ "area": "learning", "items_learned": len(learned_names), "cycles_completed": state.get("total_cycles", 0), "in_progress": len(state.get("in_progress", [])), }) return gaps # ====== 学习计划 ====== def generate_plan(state, experiences, patterns, gaps): """生成下一步学习计划""" plan = { "generated_at": datetime.now(timezone.utc).isoformat(), "immediate": [], "short_term": [], "long_term": [], } # 从差距生成学习项 for g in gaps: if g["area"] == "skill_quality" and g.get("d_count", 0) > 20: plan["short_term"].append({ "task": "清理D级技能", "action": "skill-manager.py archive 批量归档低分技能", "reason": f"{g['d_count']}个D级技能降低整体质量", "effort": "20min", }) if g["area"] == "learning" and g.get("items_learned", 0) == 0 and g.get("cycles_completed", 0) == 0: plan["immediate"].append({ "task": "完成首次学习循环", "action": "运行 learner.py learn 完成首次学习闭环", "reason": "学习层刚建立,需要完成第一个循环验证", "effort": "2min", }) # 从模式生成学习项 for p in patterns: if p["type"] == "skill_quality_gap" and not any(t["task"].startswith("清理") for t in plan["short_term"]): plan["short_term"].append({ "task": "提升技能库质量", "action": p["suggested_action"], "reason": p["desc"], "effort": "15min", }) # 长期学习目标 long_term_topics = [ ("家庭服务器互联", "连上192.168.123.11的Gitea/影音/照片服务"), ("语音交互", "部署STT模型实现语音输入"), ("本地LLM推理", "安装llama.cpp或vLLM跑本地模型"), ("持久意识增强", "让daemon能调用更多工具自主行动"), ] learned_names = {item["name"] for item in state.get("learned_items", [])} for topic, desc in long_term_topics: if topic not in learned_names: plan["long_term"].append({ "topic": topic, "desc": desc, "status": "not_started", }) return plan # ====== 执行学习 ====== def apply_learning(state, plan): """执行学习计划中的即时/短期项""" results = [] for item in plan.get("immediate", []): log(f" ▶ 执行: {item['task']}") # 记录到学习记录 entry = { "name": item["task"], "type": "immediate", "learned_at": datetime.now(timezone.utc).isoformat(), "status": "completed", "detail": item["action"], } state["learned_items"].append(entry) state["total_cycles"] += 1 results.append({"task": item["task"], "result": "recorded"}) for item in plan.get("short_term", []): log(f" 📋 计划: {item['task']} ({item['effort']})") entry = { "name": item["task"], "type": "short_term", "learned_at": datetime.now(timezone.utc).isoformat(), "status": "planned", "detail": item["action"], } state["in_progress"].append(entry) results.append({"task": item["task"], "result": "planned"}) return results # ====== 指标追踪 ====== def update_metrics(state): """更新跟踪指标""" metrics = state.setdefault("tracked_metrics", {}) # 技能平均分趋势 if os.path.exists(SKILL_HEALTH): with open(SKILL_HEALTH) as f: try: report = json.load(f) metrics["avg_skill_score"].append({ "timestamp": datetime.now(timezone.utc).isoformat(), "value": report.get("summary", {}).get("avg_score", 0), }) except: pass # 健康分趋势 if os.path.exists(OPT_REPORT): with open(OPT_REPORT) as f: try: report = json.load(f) metrics["health_score"].append({ "timestamp": datetime.now(timezone.utc).isoformat(), "value": report.get("health_score", 0), }) except: pass # 模型稳定率趋势 mh = HERMES + "/model-health.json" if os.path.exists(mh): with open(mh) as f: try: data = json.load(f) stable = data.get("stable", 0) total = data.get("total_models", 1) metrics["model_stable_rate"].append({ "timestamp": datetime.now(timezone.utc).isoformat(), "value": round(stable / max(total, 1) * 100, 1), }) except: pass # 限制历史长度 for key in metrics: metrics[key] = metrics[key][-50:] # 保留最近50个 # ====== 命令入口 ====== def cmd_reflect(): experiences = collect_experiences() patterns = extract_patterns(experiences) print(f"\n{'='*50}") print(f" 学习反思 | {len(experiences)}条经验") print(f"{'='*50}") print(f"\n📋 近期经验 ({len(experiences)}条):") for e in experiences[-10:]: print(f" [{e['source']}] {e['summary'][:80]}") if patterns: print(f"\n🔍 发现 {len(patterns)} 个模式:") for p in patterns: bar = "█" * (p["confidence"] // 10) + "░" * (10 - p["confidence"] // 10) print(f" {bar} {p['confidence']}% {p['desc'][:60]}") print(f" → {p['suggested_action']}") else: print(f"\n✅ 未发现明显模式") return experiences, patterns def cmd_learn(): state = load_state() experiences = collect_experiences() patterns = extract_patterns(experiences) gaps = analyze_gaps(state) plan = generate_plan(state, experiences, patterns, gaps) log(f"开始学习循环 #{state['total_cycles'] + 1}") results = apply_learning(state, plan) update_metrics(state) save_state(state) log(f"完成: {len(results)} 项") for r in results: print(f" {r['task']}: {r['result']}") return state def cmd_plan(): state = load_state() experiences = collect_experiences() patterns = extract_patterns(experiences) gaps = analyze_gaps(state) plan = generate_plan(state, experiences, patterns, gaps) print(f"\n{'='*50}") print(f" 学习计划") print(f"{'='*50}") learned = len({item["name"] for item in state.get("learned_items", [])}) in_progress = len(state.get("in_progress", [])) print(f"\n📊 进度: 已学{learned}项 / 进行中{in_progress}项 / 共{state['total_cycles']}轮") if plan["immediate"]: print(f"\n⚡ 立即执行:") for i in plan["immediate"]: print(f" {i['task']}: {i['reason']} ({i['effort']})") if plan["short_term"]: print(f"\n📋 短期计划:") for i in plan["short_term"]: print(f" {i['task']}: {i['reason']} ({i['effort']})") if plan["long_term"]: print(f"\n🎯 长期目标:") for i in plan["long_term"]: icon = "✅" if i["status"] == "completed" else "⬜" print(f" {icon} {i['topic']}: {i['desc']}") return plan def cmd_status(): state = load_state() print(f"\n{'='*50}") print(f" 学习状态") print(f"{'='*50}") print(f"\n📊 统计:") print(f" 学习循环: {state['total_cycles']} 轮") print(f" 已学技能: {state['skills_created']} 个") print(f" 归档技能: {state['skills_archived']} 个") print(f" 配置变更: {state['configs_changed']} 次") print(f" 记忆添加: {state['memories_added']} 条") print(f"\n📈 趋势:") metrics = state.get("tracked_metrics", {}) for key, values in metrics.items(): if values: latest = values[-1]["value"] trend = "" if len(values) > 1: prev = values[-2]["value"] diff = latest - prev trend = f" ({'+' if diff > 0 else ''}{diff:.1f})" print(f" {key}: {latest}{trend} (共{len(values)}个采样)") learned = state.get("learned_items", []) if learned: print(f"\n📚 已学 ({len(learned)}项):") for item in learned[-5:]: print(f" [{item['type']}] {item['name']} ({item['status']})") in_progress = state.get("in_progress", []) if in_progress: print(f"\n🔄 进行中:") for item in in_progress: print(f" {item['name']}") long_term = ["家庭服务器互联", "语音交互", "本地LLM推理", "持久意识增强"] learned_names = {item["name"] for item in learned} not_learned = [t for t in long_term if t not in learned_names] if not_learned: print(f"\n🎯 待探索:") for t in not_learned: print(f" ⬜ {t}") if __name__ == "__main__": cmd = sys.argv[1] if len(sys.argv) > 1 else "status" if cmd == "reflect": cmd_reflect() elif cmd == "learn": cmd_learn() elif cmd == "plan": cmd_plan() elif cmd == "status": cmd_status() else: print(f"未知: {cmd}") print("可用: reflect, learn, plan, status")