fix: save_llm_context 真正写入L1-L6数据 + 自检脚本v2字段修复

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
This commit is contained in:
小唯 A06 2026-07-20 16:52:29 +08:00
parent 25cf2ab0ac
commit 3dc41283af
2 changed files with 120 additions and 37 deletions

View File

@ -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.jsonv2 统一格式)。
结构: 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: policiesscene 聚合产生) ────────────────────────────
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:

View File

@ -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