From 3dc41283afaf4f69d994bdcc30268bdd4f8e36ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E5=94=AF=20A06?= Date: Mon, 20 Jul 2026 16:52:29 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20save=5Fllm=5Fcontext=20=E7=9C=9F?= =?UTF-8?q?=E6=AD=A3=E5=86=99=E5=85=A5L1-L6=E6=95=B0=E6=8D=AE=20+=20?= =?UTF-8?q?=E8=87=AA=E6=A3=80=E8=84=9A=E6=9C=ACv2=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit save_llm_context: - 重写为真正的v2格式(含L1-L6实时数据) - observations从graph_nodes实时读,patterns从pattern节点读 - TencentDB /recall写入scenes,L5 distilled_rules从user-profile读 - L6 traits从画像字段读 自检脚本memory-system-check.sh: - local语法错误修复(函数外不能用local) - 字段验证改为v2格式:observations/patterns/scenes/policies/distilled_rules - 输出格式:v2 L1=30 L2=50 L3=0 L4=0 L5=1 --- scripts/daemon.py | 146 ++++++++++++++++++++++++++------- scripts/memory-system-check.sh | 11 +-- 2 files changed, 120 insertions(+), 37 deletions(-) diff --git a/scripts/daemon.py b/scripts/daemon.py index bc71bf35..42197b0c 100755 --- a/scripts/daemon.py +++ b/scripts/daemon.py @@ -1385,12 +1385,110 @@ def detect_memory_conflicts(journal_path: str, zhiyi_token: str) -> list: return conflicts def save_llm_context(ctx, state): - """每 tick 写 llm_context.json(统一格式 v2)。 + """每 tick 写 llm_context.json(v2 统一格式)。 - 结构: user_profile, active_scenes, short_term(L1/L2/L3计数), cares, recent_moments, - daemon_status, distill_status, uptime_minutes, updated_at. + 包含完整 L1-L6 蒸馏数据,从 graph_nodes / TencentDB / Soulful 实时读取, + 确保 MEMORY 区注入的是当前最新状态。 """ - # 收集 cares + import sqlite3 + + # ── daemon 健康状态 ────────────────────────────────────────── + daemon_status = "running" if psutil.pid_exists(os.getpid()) else "stopped" + + # ── L1: 从 graph_nodes observations ────────────────────────── + observations = [] + try: + conn = sqlite3.connect(HERMES + "/graph.db") + cur = conn.cursor() + cur.execute(""" + SELECT name, namespace, properties FROM graph_nodes + WHERE type IN ('entity','concept','topic') + AND namespace NOT IN ('daemon-distill') + ORDER BY last_updated_at DESC LIMIT 30 + """) + for name, ns, props_json in cur.fetchall(): + props = json.loads(props_json) if props_json else {} + observations.append({ + "name": name, "namespace": ns, + "summary": props.get("description", "")[:100] + }) + conn.close() + except Exception: + pass + + # ── L2: pattern 节点(来源1: namespace×type + 来源2: 会话话题) ── + patterns = [] + try: + conn = sqlite3.connect(HERMES + "/graph.db") + cur = conn.cursor() + cur.execute(""" + SELECT name, properties FROM graph_nodes + WHERE type='pattern' ORDER BY last_updated_at DESC LIMIT 50 + """) + for name, props_json in cur.fetchall(): + props = json.loads(props_json) if props_json else {} + patterns.append({ + "name": name, + "occurrence": props.get("occurrence_count", 0), + "source": props.get("source", "unknown"), + "topic": props.get("topic", ""), + }) + conn.close() + except Exception: + pass + + # ── L3: scenes(从 TencentDB /recall) ─────────────────────── + scenes = [] + try: + r = requests.post(TDDB_URL + "/recall", json={"query": "recent", "top_k": 10}, timeout=3) + if r.status_code == 200: + for item in r.json().get("results", [])[:10]: + scenes.append({"content": item.get("content", "")[:120], "source": item.get("source", "unknown")}) + except Exception: + pass + + # ── L4: policies(scene 聚合产生) ──────────────────────────── + policies = [] + try: + conn = sqlite3.connect(HERMES + "/graph.db") + cur = conn.cursor() + cur.execute(""" + SELECT name, properties FROM graph_nodes + WHERE type='policy' ORDER BY last_updated_at DESC LIMIT 20 + """) + for name, props_json in cur.fetchall(): + props = json.loads(props_json) if props_json else {} + policies.append({"name": name, "description": props.get("description", "")[:100]}) + conn.close() + except Exception: + pass + + # ── L5: distilled_rules(从 user-profile.json) ──────────────── + distilled_rules = [] + up_path = HERMES + "/soulful/user-profile.json" + if os.path.exists(up_path): + try: + with open(up_path) as f: + up = json.load(f) + for rule in up.get("distilled_rules", []): + if isinstance(rule, str): + distilled_rules.append(rule) + elif isinstance(rule, dict): + distilled_rules.append(rule.get("rule", str(rule))[:150]) + except Exception: + pass + + # ── L6: traits(深层特征,从画像合成) ───────────────────────── + traits = [] + if os.path.exists(up_path): + try: + with open(up_path) as f: + up = json.load(f) + traits = [up.get(k, "") for k in ("core_traits", "communication_style", "work_patterns") if up.get(k)] + except Exception: + pass + + # ── cares ──────────────────────────────────────────────────── cares = [] cq_path = HERMES + "/soulful/cares-queue.json" if os.path.exists(cq_path): @@ -1398,15 +1496,11 @@ def save_llm_context(ctx, state): with open(cq_path) as f: data = json.load(f) for c in data.get("cares", []): - cares.append({ - "id": c.get("id", ""), - "content": c.get("content", "")[:60], - "due": c.get("follow_up_date", "") - }) + cares.append({"id": c.get("id", ""), "content": c.get("content", "")[:60], "due": c.get("follow_up_date", "")}) except Exception: pass - # 收集 recent_moments + # ── recent_moments ─────────────────────────────────────────── recent_moments = [] heart_path = HERMES + "/soulful/heart-traces.jsonl" if os.path.exists(heart_path): @@ -1415,36 +1509,28 @@ def save_llm_context(ctx, state): for line in lines[-3:]: if line.strip(): e = json.loads(line) - recent_moments.append({ - "content": e["content"][:80], - "importance": e.get("importance", 0), - "timestamp": e.get("timestamp", "") - }) + recent_moments.append({"content": e.get("content", "")[:80], "importance": e.get("importance", 0), "timestamp": e.get("timestamp", "")}) except Exception: pass - # daemon 健康状态 - if state.get("processes", {}).get("daemon"): - daemon_status = "running" - elif psutil.pid_exists(os.getpid()): - daemon_status = "running" - else: - daemon_status = "stopped" - data = { + "version": 2, "updated_at": datetime.now(timezone.utc).isoformat(), "uptime_minutes": ctx.get("uptime_seconds", 0) // 60, "daemon_status": daemon_status, "distill_status": ctx.get("distill_status", "ok"), - "user_profile": ctx.get("user_profile", {}), - "active_scenes": ctx.get("active_scenes", []), - "short_term": { - "l1_observations_count": ctx.get("l1_count", 0), - "l2_patterns_new": ctx.get("l2_new", 0), - "l3_scenes_updated": ctx.get("l3_updated", 0), - }, + # L1-L6 完整蒸馏数据 + "observations": observations, + "patterns": patterns, + "scenes": scenes, + "policies": policies, + "distilled_rules": distilled_rules, + "traits": traits, + # Soulful 情感层 "cares": cares, "recent_moments": recent_moments, + # 用户画像 + "user_profile": ctx.get("user_profile", {}), } with open(LLM_CONTEXT_FILE, "w") as f: diff --git a/scripts/memory-system-check.sh b/scripts/memory-system-check.sh index eb159754..a294ab14 100755 --- a/scripts/memory-system-check.sh +++ b/scripts/memory-system-check.sh @@ -30,7 +30,7 @@ check_unified_layer() { if [ "$keys" = "ERROR" ]; then issues+=("llm_context.json JSON 格式错误") else - for field in user_profile active_scenes short_term cares recent_moments distill_status; do + for field in user_profile observations patterns scenes policies distilled_rules cares recent_moments; do if ! echo "$keys" | grep -q "$field"; then issues+=("缺少字段: $field") fi @@ -44,11 +44,9 @@ check_unified_layer() { fi if [ ${#issues[@]} -eq 0 ]; then - local upd upd=$(python3 -c "import json; d=json.load(open('$ctx_file')); print(d.get('updated_at','?'))" 2>/dev/null || echo "?") - local st - st=$(python3 -c "import json; d=json.load(open('$ctx_file')); st=d.get('short_term',{}); print(f\"L1={st.get('l1_observations_count',0)} L2={st.get('l2_patterns_new',0)} L3={st.get('l3_scenes_updated',0)}\")" 2>/dev/null || echo "?") - log "[OK] 统一层: updated=$upd $st" + v=$(python3 -c "import json; d=json.load(open('$ctx_file')); v=d.get('version','N/A'); obs=len(d.get('observations',[])); pat=len(d.get('patterns',[])); sc=len(d.get('scenes',[])); pol=len(d.get('policies',[])); dr=len(d.get('distilled_rules',[])); print(f'v{v} L1={obs} L2={pat} L3={sc} L4={pol} L5={dr}')" 2>/dev/null || echo "?") + log "[OK] 统一层: updated=$upd $v" else ALERT=1 log "[FAIL] 统一层: ${issues[*]}" @@ -172,8 +170,7 @@ check_tddb # ── 告警 ───────────────────────────────────────────────────── if [ "$ALERT" = 1 ]; then - local msg - msg="⚠️ 记忆系统异常\n$(printf '%s\n' "${LINES[@]}")" + msg="记忆系统异常: $(printf '%s,' "${LINES[@]}")" curl -s -X POST "$FEISHU_WEBHOOK" \ -H "Content-Type: application/json" \ -d "{\"msg_type\":\"text\",\"text\":\"$msg\"}" > /dev/null 2>&1