#!/usr/bin/env python3 """图谱优化:节点聚类 + 关系增强 优化内容: 1. 节点聚类:按共现关系聚成概念组(使用连接数 + 共现关系) 2. 关系增强:给边加权重,识别核心关系 3. 生成增强版 graph_data.json(带聚类标签) """ import sqlite3 import json from collections import Counter, defaultdict from pathlib import Path DB = Path.home() / '.memory-fabric/zhiyi/graph.db' OUT = Path("/home/muc/projects/zhiyi/docs/graph_data.json") # ── 中文词组合并规则(简单 n-gram 合并)───────────────────────────── # 常见的 2-4 字组合词(织忆系统内常见概念) COMPOUND_WORDS = { "织忆", "牧尘", "小唯", "小雪", "Hermes", "OpenClaw", "MemoryWeave", "MemoryFabric", "FAISS", "Redis", "FastAPI", "SBert", "bge-m3", "ComfyUI", "Docker", "Python", "Rust", "LLM", "RAG", "CRDT", "向量数据库", "语义搜索", "知识图谱", "模力方舟", "Gitea", "GitHub", "Jina", "HuggingFace", "deepseek", "qwen", "llama", "RTX", "3050", "GPU", "CPU", "API", "SDK", "JSONL", "memory-fabric", "agent", "agent-id", } def merge_adjacent_words(text: str) -> list[str]: """把相邻的单字词合并成常见组合词""" words = [] i = 0 while i < len(text): matched = False # 优先匹配 4 字词 for size in [4, 3, 2]: if i + size <= len(text): chunk = text[i:i+size] if chunk in COMPOUND_WORDS: words.append(chunk) i += size matched = True break if not matched: i += 1 return words # ── 读取图谱数据 ────────────────────────────────────────────── conn = sqlite3.connect(str(DB)) cur = conn.cursor() # 构建 entity→id 映射 cur.execute("SELECT id, entity, label, properties, episode_id FROM graph_nodes") nodes_raw = {} entity_to_id = {} for row in cur.fetchall(): node_id, entity, label, props, ep_id = row props = json.loads(props) if props and props != '{}' else {} nodes_raw[entity] = { "id": entity, "uuid": node_id, "name": entity, "label": label, "episode_id": ep_id, "connections": 0, **props } entity_to_id[entity] = entity # 边 cur.execute("SELECT source, target, relation, properties FROM graph_edges") edges_raw = [] for row in cur.fetchall(): src, tgt, rel, props = row props = json.loads(props) if props and props != '{}' else {} edges_raw.append({"source": src, "target": tgt, "relation": rel, **props}) conn.close() # ── 统计连接数 ──────────────────────────────────────────────── cnt = Counter() for e in edges_raw: cnt[e['source']] += 1 cnt[e['target']] += 1 # 给节点加连接数 for entity in nodes_raw: nodes_raw[entity]["connections"] = cnt.get(entity, 0) # ── 节点分类:区分「碎片词」vs「概念词」──────────────────────────── # 碎片词:单字或 2 字无意义词(常见工具检测) STOP_NODES = { '用了', '使用', '做了', '进行', '开始', '完成', '结束', '通过', '根据', '按照', '因为', '所以', '如果', '但是', '之后', '之前', '时候', '之后', '现在', '今天', '昨天', '那个', '这个', '什么', '怎么', '为什么', '如何', '牧尘用', '牧尘用', '牧尘今天', '牧尘的', '牧尘在', 'ip', 'url', 'uri', 'id', 'id', # 常见后缀 '0', '1', '2', '3', '4', '5', '00001', '00002', # 数字噪声 } def is_meaningful(entity: str) -> bool: """判断节点是否有意义(不是碎片)""" if len(entity) <= 1: return False if entity in STOP_NODES: return False if entity.endswith(('用', '了', '在', '的', '和', '或', '与')): return False if all(c.isdigit() or c in '.-_' for c in entity): return False if len(entity) == 2 and entity[0] == entity[1]: # AA型重复 return False return True # ── 聚类:高频共现节点自动组成概念簇 ───────────────────────────── # 思路:找一个节点的所有邻居,如果邻居互相也相连,视为「概念簇」 concept_clusters = [] processed = set() # 按连接数排序,先处理大节点 sorted_nodes = sorted(nodes_raw.values(), key=lambda x: -x['connections']) for node in sorted_nodes: if node['name'] in processed or not is_meaningful(node['name']): continue if node['connections'] < 2: continue # 收集邻居 neighbors = set() for e in edges_raw: if e['source'] == node['name'] and is_meaningful(e['target']): neighbors.add(e['target']) if e['target'] == node['name'] and is_meaningful(e['source']): neighbors.add(e['source']) # 如果有 2+ 有意义邻居,构成簇 good_neighbors = [n for n in neighbors if n in nodes_raw and is_meaningful(n)] if len(good_neighbors) >= 2: cluster = [node['name']] + good_neighbors[:5] # 最多 6 个 cluster = [n for n in cluster if is_meaningful(n)] if len(cluster) >= 2: concept_clusters.append(sorted(set(cluster), key=lambda x: -nodes_raw[x]['connections'])) for n in cluster: processed.add(n) # ── 生成可视化数据 ─────────────────────────────────────────── # 只保留有意义节点,过滤碎片 meaningful_nodes = {e: n for e, n in nodes_raw.items() if is_meaningful(e)} # 采样:TOP 120 高连接节点 + 1跳邻居 top_nodes = sorted(meaningful_nodes.values(), key=lambda x: -x['connections'])[:120] top_ids = set(n['name'] for n in top_nodes) for e in edges_raw: if e['source'] in top_ids and e['target'] in meaningful_nodes: top_ids.add(e['target']) if e['target'] in top_ids and e['source'] in meaningful_nodes: top_ids.add(e['source']) # 最终节点列表 final_nodes = [] for name in top_ids: if name not in meaningful_nodes: continue n = nodes_raw[name].copy() # 找簇标签 cluster_label = None for cluster in concept_clusters: if name in cluster: cluster_label = cluster[0] # 簇内最高连接节点作为代表 break n['cluster'] = cluster_label n['group'] = 'concept' if n['connections'] >= 5 else 'term' final_nodes.append(n) # 最终边 final_links = [e for e in edges_raw if e['source'] in top_ids and e['target'] in top_ids and is_meaningful(e['source']) and is_meaningful(e['target'])] # 连接数更新 cnt2 = Counter() for l in final_links: cnt2[l['source']] += 1 cnt2[l['target']] += 1 for n in final_nodes: n['connections'] = cnt2.get(n['name'], 0) result = {"nodes": final_nodes, "links": final_links} OUT.write_text(json.dumps(result, ensure_ascii=False)) print(f"✅ 增强图谱: {len(final_nodes)} 节点, {len(final_links)} 边") print(f" 聚类簇: {len(concept_clusters)} 个") print(f" 碎片过滤: {len(nodes_raw)} → {len(meaningful_nodes)}") print(f" 高连接TOP10:") for n in sorted(final_nodes, key=lambda x: -x['connections'])[:10]: print(f" {n['name']}({n['connections']})", end=" ") print() print(f" 簇示例:") for c in concept_clusters[:5]: print(f" {c}")