xiaowei-system/scripts/update_profile.py

212 lines
7.0 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
"""
update_profile.py — 从 journal 增量更新用户画像
=========================================
用法:
python3 update_profile.py # 增量更新
python3 update_profile.py --full # 全量重新分析
python3 update_profile.py --dry # 只输出,不写入
依赖soulful_core.py同目录
"""
import json, os, re, sys
from datetime import datetime, timezone
from collections import Counter
HERMES = os.path.expanduser("~/.hermes")
D = HERMES + "/soulful"
JOURNAL_FILE = HERMES + "/daemon/journal.jsonl"
# 情绪词库
EMOTION_WORDS = {
"positive": ["开心", "高兴", "顺利", "", "完美", "感谢", "不错", "很好", "解决了", "成功了", "搞定", "舒服"],
"negative": ["", "", "压力", "焦虑", "沮丧", "失望", "崩溃", "", "头疼", "", "搞不定", "失败", "错误"],
"focus": ["专注", "", "", "deadline", "紧急", "重要"],
}
# 目标关键词
GOAL_KEYWORDS = ["研究", "", "", "修复", "完成", "测试", "上线", "调研", "计划", "推进", "同步"]
# 沟通风格判断(基于句长)
def detect_communication_style(summaries: list) -> str:
if not summaries:
return "简洁"
total_len = sum(len(s) for s in summaries)
avg_len = total_len / len(summaries)
if avg_len < 30:
return "简洁直接"
elif avg_len < 60:
return "简洁"
else:
return "详细"
def load_journal(n: int = 100) -> list:
"""加载最近 N 条 journal 条目"""
if not os.path.exists(JOURNAL_FILE):
return []
with open(JOURNAL_FILE) as f:
lines = f.readlines()
entries = [json.loads(l) for l in lines[-n:] if l.strip()]
return entries[::-1] # 倒序,最新的在前
def extract_goals(entries: list) -> list:
"""从 journal 中提取当前目标(通过关键词+主题判断)"""
goals = []
recent_topics = []
for e in entries:
summary = e.get("summary", "")
event_type = e.get("type", "")
# 任务完成类型的事件通常是目标
if event_type in ("solve", "learn", "skill", "config"):
# 提取关键动作
if "织忆" in summary:
goals.append("织忆系统优化")
if "ColaOS" in summary or "page-agent" in summary:
goals.append("ColaOS研究")
if "skill" in summary.lower():
goals.append("技能管理")
if "daemon" in summary.lower():
goals.append("Daemon自动化")
recent_topics.append(summary[:50])
# 去重保留顺序
seen = set()
unique_goals = []
for g in goals:
if g not in seen:
seen.add(g)
unique_goals.append(g)
return unique_goals[:5]
def extract_emotional_state(entries: list) -> tuple:
"""从 journal 中判断情绪状态"""
pos_count = 0
neg_count = 0
notes = []
for e in entries[-20:]: # 只看最近20条
summary = e.get("summary", "")
for w in EMOTION_WORDS["positive"]:
if w in summary:
pos_count += 1
for w in EMOTION_WORDS["negative"]:
if w in summary:
neg_count += 1
notes.append(summary[:60])
if neg_count > pos_count * 2:
state = "tired"
elif pos_count > neg_count:
state = "good"
else:
state = "neutral"
return state, notes[-3:]
def detect_events_for_hearttraces(entries: list) -> list:
"""从 journal 中检测值得写入心迹的事件"""
events = []
for e in entries:
summary = e.get("summary", "")
# 检测重要共同经历
if "织忆" in summary and "修好" in summary:
events.append({
"content": "我们一起修好了织忆失忆问题2026-06-25",
"tags": ["成长", "织忆", "共同经历"],
"importance": 5,
"type": "moment"
})
if "ColaOS" in summary and "研究" in summary:
events.append({
"content": "牧尘开始研究 ColaOS寻找织忆 Soulful 改造方向",
"tags": ["ColaOS", "研究"],
"importance": 4,
"type": "moment"
})
# 检测情绪表达
for w in EMOTION_WORDS["negative"]:
if w in summary:
events.append({
"content": f"牧尘表达了{w}{summary[:40]}",
"tags": ["情绪", w],
"importance": 3,
"type": "signal"
})
break
return events
def main():
dry = "--dry" in sys.argv
full = "--full" in sys.argv
print(f"[update_profile] {'[DRY]' if dry else ''} 开始更新用户画像...")
# 加载 journal
entries = load_journal(200 if full else 100)
print(f" 读取 journal: {len(entries)}")
if not entries:
print(" 无 journal 数据,退出")
return
# 提取各项
summaries = [e.get("summary", "") for e in entries]
style = detect_communication_style(summaries)
goals = extract_goals(entries)
emotion, emotion_notes = extract_emotional_state(entries)
print(f" 沟通风格: {style}")
print(f" 当前目标: {goals}")
print(f" 情绪状态: {emotion}")
if dry:
print(" [DRY] 不写入,仅输出")
return
# 更新画像
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from soulful_core import UserProfile, HeartTraces
up = UserProfile()
# 更新沟通风格
if not up.get().get("communication_style") or full:
up.update({"communication_style": style})
# 更新目标(合并去重)
if goals:
existing_goals = up.get().get("current_goals", [])
merged = list(dict.fromkeys(existing_goals + goals))[:10]
up.update({"current_goals": merged})
# 更新情绪(只有更严重才降级,不轻易升级)
current = up.get().get("emotional_state", {}).get("current", "unknown")
if emotion == "tired" or current in ("tired",):
up.set_emotional_state(emotion, "; ".join(emotion_notes[:2]) if emotion_notes else "")
print(f" ✓ 画像已更新")
# 检测心迹事件
ht = HeartTraces()
current_count = ht.count()
events = detect_events_for_hearttraces(entries)
# 写入新心迹(只写入之前没有的)
for ev in events:
# 简单去重:检查最近 10 条是否有相同内容
recent = ht.recent(n=10)
duplicate = any(ev["content"][:30] in r.get("content", "") for r in recent)
if not duplicate:
ht.record(ev["content"], ev["tags"], ev["importance"], trace_type=ev["type"])
print(f" ✦ 心迹: {ev['content'][:50]}")
new_count = ht.count()
if new_count > current_count:
print(f" ✓ 心迹新增 {new_count - current_count}")
else:
print(f" ✓ 无新增心迹({new_count} 条)")
if __name__ == "__main__":
main()