212 lines
7.0 KiB
Python
212 lines
7.0 KiB
Python
#!/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() |