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:
parent
25cf2ab0ac
commit
3dc41283af
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue