分析日期:2026-05-18
代码版本:zulong_beta4
核心文件:zulong/memory/memory_graph.py(3564行)
底层存储:zulong/memory/memory_graph.py
# 三层存储架构
self._graph = nx.DiGraph() # NetworkX有向图
self._nodes: Dict[str, GraphNode] = {} # 节点字典
self._embeddings: Dict[str, np.ndarray] = {} # 512维向量嵌入11种节点类型:
- TASK、DIALOGUE、KNOWLEDGE、SESSION、EPISODE、FILE、CONCEPT、PERSON、DOCUMENT、CODE_SYMBOL、MODULE
7种边类型:
- HIERARCHY(父子)、DEPENDENCY(依赖)、REFERENCE(引用)、TEMPORAL(时序)、SEMANTIC(语义)、CAUSAL(因果)、ASSOCIATION(赫布关联)
Temperature(温度):
HOT: < 1小时 # 最近访问
WARM: 1-24小时 # 中等未激活
COLD: > 24小时 # 长期未激活Importance(重要度):
TRIVIAL: 6h半衰期 # 无意义闲聊
NORMAL: 24h半衰期 # 普通对话
IDENTITY: 720h半衰期 # 身份信息(30天)
FACT: 360h半衰期 # 客观事实(15天)
IMPORTANT: 168h半衰期 # 承诺/指令(7天)
MUST_REMEMBER: 永不衰减 # 用户显式要求def compute_activations(seed_node_ids, max_depth=3, decay=0.5, min_activation=0.01):
"""
核心算法:从种子节点出发,沿边传播激活值
公式:propagated = activation × edge_weight × decay
每跳衰减,低于阈值停止传播
"""
# 初始化种子节点激活值 = 1.0
# BFS遍历所有出边和入边(视为无向传播)
# 记录共激活边(用于赫布学习)
# 返回 Dict[node_id, activation_score]动态参数调整:
# 大窗口(>64K) + 低占用(<0.5) → max_depth=5
# 小窗口(<32K) 或 高占用(>0.8) → max_depth=2边权增强公式:
new_weight = old_weight + eta × (1 - old_weight) # eta=0.1
# 渐近趋向1.0,永不超限ASSOCIATION边自动创建:
# 共激活计数 >= 3 → 自动创建ASSOCIATION边
# 限制:每节点ASSOCIATION出度上限10,总对数上限5000衰减公式:
decayed = weight × exp(-elapsed_hours × ln(2) / half_life)
# half_life根据重要度动态选择
# must_remember节点 → 永不衰减(protected=True)修剪策略:
prune_threshold = 0.05 # 直接移除
review_threshold = 0.15 # 候选LLM审查并行检索架构:
async def retrieve_context(query_text, top_k=10, hot_window_minutes=30):
# 路径A:热路径BFS遍历(<50ms)
# 路径B:冷路径FAISS向量检索(<200ms)
# asyncio.gather并行执行
# 结果合并后按分数排序热路径实现:
# 时间窗口过滤 + 语义相似度 + BFS邻域扩展
# 重要度提升:identity/fact +0.1, important +0.15, must_remember +0.2
# 会话内提升:+0.3冷路径实现:
# FAISS向量检索 + BM25关键词检索并行
# 融合权重:0.7 × 向量 + 0.3 × 关键词
# BFS下钻获取命中摘要节点的子节点详情位置:memory_graph.py:500-503
class MemoryGraph:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance风险:
- 多线程/多进程环境下,单例状态可能被意外修改
- 持久化路径在首次创建后无法更改
- 测试环境下难以隔离,可能导致测试污染
- 长期运行可能导致内存泄漏(节点只增不减)
影响等级:高风险
位置:memory_graph.py:1244-1292
while queue:
node_id, depth, act = queue.popleft()
# 遍历所有出边
for _, neighbor, data in self._graph.out_edges(node_id, data=True):
# ... 传播逻辑
# 遍历所有入边(视为无向传播)
for predecessor, _, data in self._graph.in_edges(node_id, data=True):
# ... 传播逻辑风险:
- 时间复杂度:O(E × D),E为边数,D为深度。当图规模达到10万节点时,max_depth=6可能遍历数百万条边
- 内存占用:
activations字典存储所有激活节点,可能占用大量内存 - 阻塞风险:同步BFS可能阻塞主线程,导致响应延迟
- 无超时保护:BFS循环没有超时机制,可能陷入死循环
影响等级:高风险
位置:memory_graph.py:1446-1494
def _update_coactivation_counter(activated_edges):
# 每次BFS后更新共激活计数
for src, tgt in activated_edges:
pair = (a, b) if a < b else (b, a)
self._coactivation_counter[pair] += 1
if self._coactivation_counter[pair] >= 3:
# 创建ASSOCIATION边
self.add_edge(a, b, EdgeType.ASSOCIATION, weight=0.3)风险:
- 边数爆炸:虽然限制了每节点出度上限10,但全局边数仍可能快速增长
- 共激活计数器溢出:
_coactivation_counter字典虽然设置了5000上限,但可能导致有效配对被误删 - 语义噪声:频繁激活的节点可能创建大量弱关联边,降低检索质量
- 性能退化:ASSOCIATION边增加了图的密度,导致BFS遍历开销增大
影响等级:高风险
位置:memory_graph.py:1537
decayed = data["weight"] * math.exp(-elapsed_hours * ln2 / half_life)风险:
- 浮点精度累积误差:多次衰减后,权重可能因精度丢失而失效
- 时间戳漂移:
last_activated依赖系统时钟,时间调整可能导致异常衰减 - 半衰期选择刚性:仅根据重要度选择半衰期,未考虑节点类型、访问频率等上下文
- must_remember永不衰减:可能积累大量永不衰减的边,占用存储空间
影响等级:中风险
位置:memory_graph.py:2184-2191
loop = asyncio.get_event_loop()
hot_task = loop.run_in_executor(None, self._retrieve_hot, ...)
cold_task = loop.run_in_executor(None, self._retrieve_cold, ...)
hot_results, cold_results = await asyncio.gather(hot_task, cold_task)风险:
- 数据竞争:热路径和冷路径同时读取
_nodes字典,若并发写入可能导致数据不一致 - 事件循环依赖:
asyncio.get_event_loop()在无事件循环时会报错 - 线程池竞争:
run_in_executor使用默认线程池,多任务并发可能导致资源竞争 - 异常未隔离:一条路径异常会中断整个检索流程
影响等级:中风险
位置:memory_graph.py(推测,未直接看到但文档提到)
风险:
- 崩溃一致性:写入临时文件时崩溃可能导致数据损坏
- 备份策略缺失:虽然有原子写入,但缺少多版本备份机制
- 增量持久化缺失:每次全量保存,大图可能导致I/O瓶颈
- 并发写入风险:虽然有
_save_lock,但进程间并发无法防护
影响等级:中风险
位置:memory_graph.py:195-300
class SummarySidecarIndex:
def __init__(self, dimension=512):
self._store = FAISSVectorStore(dimension=512, index_type="Flat")
self._node_to_faiss: Dict[str, str] = {}
self._text_index: Dict[str, str] = {} # 摘要文本缓存风险:
- 内存占用:FAISS Flat索引需要将所有向量加载到内存,10万节点 × 512维 × 4字节 ≈ 200MB
- 索引更新开销:每次添加摘要需要重新计算向量,批量更新效率低
- 延迟初始化失败:
_ensure_init()失败后无法恢复,导致冷路径检索失效 - 向量维度刚性:512维固定,无法适应不同Embedding模型
影响等级:中风险
位置:memory_graph.py:543
self._data_lock = threading.RLock() # 可重入锁风险:
- 锁粒度过粗:单个RLock保护所有数据结构,可能成为性能瓶颈
- 读写不分离:检索和写入共享同一锁,读多写少场景下并发度低
- 死锁风险:RLock虽然可重入,但多锁嵌套(
_data_lock+_candidates_lock)可能导致死锁 - GIL依赖:Python GIL提供了额外保护,但掩盖了并发设计问题
影响等级:低风险
位置:memory_graph.py:1562-1586
# 移除孤立节点
if self._graph.degree(node_id) == 0:
if (now - node.last_accessed) > orphan_age:
nodes_to_remove.append(node_id)风险:
- 误删风险:节点暂时孤立后被删除,可能导致有价值信息丢失
- 恢复困难:删除后无法恢复(除非有备份)
- 重要度判断误差:重要度标签依赖外部标注,误判可能导致关键记忆丢失
- 语义孤立误判:
cleanup_orphan_nodes()可能误删仅有结构性边的节点
影响等级:低风险
位置:memory_graph.py:2236-2290
def _compute_semantic_similarity(query_text, node):
# 优先使用embedding余弦相似度
cosine_sim = np.dot(query_vec, node_vec) / (norm(q) * norm(n))
# 回退到词汇重叠
word_overlap = len(query_words & node_words) / len(query_words)风险:
- Embedding缺失:新节点可能未生成embedding,导致回退到低效词汇匹配
- 归一化开销:每次计算都需要归一化,可预先归一化存储
- 批量计算未优化:遍历所有节点计算相似度,未利用FAISS加速
- 词汇匹配局限:中文分词依赖空格,未使用专业分词工具
影响等级:低风险
# 当前:全局单例
mg = get_memory_graph()
# 改进:依赖注入
class InferenceEngine:
def __init__(self, memory_graph: MemoryGraph):
self.memory = memory_graph
# 测试环境可注入Mock
class TestMemoryGraph:
def retrieve_context(self, query):
return [{"node_id": "test", "score": 1.0}]收益:解决全局状态污染、提升可测试性、支持多实例场景
# 当前:内存图(NetworkX)
self._graph = nx.DiGraph()
# 改进:Neo4j / SQLite-vec
from neo4j import GraphDatabase
class MemoryGraph:
def __init__(self, uri="bolt://localhost:7687"):
self.driver = GraphDatabase.driver(uri)
def compute_activations(self, seeds, max_depth=3):
# Cypher查询实现BFS
query = """
MATCH path = (seed)-[*1..3]-(related)
WHERE seed.node_id IN $seeds
RETURN related.node_id, sum(1.0 / length(path)) as activation
"""
return self.driver.session().run(query, seeds=seeds).data()收益:
- 支持持久化、索引、查询优化
- 解决内存瓶颈(支持百万级节点)
- 提供事务、并发控制
class MemoryGraph:
def __init__(self):
self._nodes_cache = LRUCache(maxsize=10000) # 读缓存
self._write_buffer = [] # 写缓冲
def retrieve_context(self, query):
# 读操作走缓存
cached = self._nodes_cache.get(query)
if cached:
return cached
result = self._retrieve_from_db(query)
self._nodes_cache.set(query, result)
return result
def add_node(self, node):
# 写操作批量提交
self._write_buffer.append(node)
if len(self._write_buffer) >= 100:
self._flush_to_db()收益:
- 降低读操作延迟
- 减少锁竞争
- 批量写入提升吞吐
async def compute_activations_async(
self, seeds, max_depth=3, timeout=2.0
):
"""异步BFS,带超时保护"""
activations = {}
queue = asyncio.Queue()
for seed in seeds:
await queue.put((seed, 0, 1.0))
start_time = time.time()
while not queue.empty():
# 超时检查
if time.time() - start_time > timeout:
logger.warning("BFS超时,返回已计算结果")
break
node_id, depth, act = await queue.get()
# 异步遍历边
edges = await self._get_edges_async(node_id)
for neighbor, weight in edges:
propagated = act * weight * self.decay
if propagated >= self.min_activation:
activations[neighbor] = max(
activations.get(neighbor, 0), propagated
)
await queue.put((neighbor, depth+1, propagated))
return activations收益:
- 非阻塞BFS,不占用主线程
- 超时保护防止无限循环
- 可中断的异步任务
def hebbian_strengthen(self):
"""赫布学习 + 语义验证"""
for src, tgt in self._last_activated_edges:
# 新增:语义相似度验证
semantic_sim = self._compute_semantic_similarity(
self._nodes[src], self._nodes[tgt]
)
# 低语义相似度的边不增强
if semantic_sim < 0.3:
logger.debug(f"跳过低语义相似边: {src}-{tgt}, sim={semantic_sim}")
continue
# 原有增强逻辑
old_w = self._graph.edges[src, tgt]["weight"]
new_w = old_w + self.eta * (1.0 - old_w)
# 新增:权重上限保护
self._graph.edges[src, tgt]["weight"] = min(new_w, 0.95)收益:
- 避免无意义关联边膨胀
- 提升图质量
- 防止边权过度集中
def decay_and_prune(self):
"""精细衰减 + 多因子融合"""
for src, tgt, data in self._graph.edges(data=True):
# 新增:访问频率衰减减缓
access_freq = (
self._nodes[src].access_count +
self._nodes[tgt].access_count
) / 2.0
freq_factor = 1.0 / (1.0 + math.log(1 + access_freq))
# 新增:节点类型因子
type_factor = self._get_type_decay_factor(
self._nodes[src].node_type
)
# 综合半衰期
base_half_life = _IMPORTANCE_HALF_LIFE[higher_imp]
effective_half_life = base_half_life * freq_factor * type_factor
# 衰减计算
decayed = data["weight"] * math.exp(
-elapsed_hours * ln2 / effective_half_life
)收益:
- 访问频繁的节点衰减更慢
- 不同节点类型可定制衰减策略
- 更符合人类记忆规律
class MemoryGraph:
def save(self):
"""多版本备份"""
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
# 备份链:graph.json → graph.backup.1 → graph.backup.2
for i in range(3, 0, -1):
old_backup = f"graph.backup.{i}"
new_backup = f"graph.backup.{i+1}"
if os.path.exists(old_backup):
shutil.move(old_backup, new_backup)
# 当前版本备份
shutil.copy("graph.json", "graph.backup.1")
# 时间戳备份
shutil.copy("graph.json", f"graph.{timestamp}.json")
# 原子写入当前版本
self._atomic_write("graph.json", data)
def restore(self, version="latest"):
"""版本恢复"""
if version == "latest":
path = "graph.json"
else:
path = f"graph.{version}.json"
if not os.path.exists(path):
raise FileNotFoundError(f"备份版本不存在: {path}")
self._load(path)收益:
- 防止数据丢失
- 支持版本回滚
- 灾难恢复能力
class MemoryGraphMetrics:
"""记忆系统监控指标"""
def __init__(self):
self.metrics = {
"bfs_avg_time": [],
"retrieval_latency": [],
"node_count": [],
"edge_count": [],
"memory_mb": [],
}
def record_bfs(self, duration_ms, nodes_activated):
self.metrics["bfs_avg_time"].append(duration_ms)
# 告警:BFS超时
if duration_ms > 1000:
self._alert("BFS超时", f"耗时{duration_ms}ms, 激活{nodes_activated}节点")
def check_health(self):
"""健康检查"""
issues = []
# 检查1:节点数过载
if len(self._nodes) > 100000:
issues.append("节点数超过10万,建议归档冷数据")
# 检查2:ASSOCIATION边过多
assoc_count = sum(
1 for _, _, d in self._graph.edges(data=True)
if d.get("edge_type") == "association"
)
if assoc_count / len(self._nodes) > 5:
issues.append("ASSOCIATION边密度过高,建议清理")
# 检查3:FAISS索引异常
if not self._summary_index._initialized:
issues.append("FAISS索引未初始化,冷路径检索失效")
return {"healthy": len(issues) == 0, "issues": issues}收益:
- 实时监控性能
- 异常及时发现
- 容量预警
from redis import Redis
from redlock import RedLock
class DistributedMemoryGraph(MemoryGraph):
"""分布式记忆图"""
def __init__(self, redis_url="redis://localhost:6379"):
super().__init__()
self.redis = Redis.from_url(redis_url)
self.lock = RedLock("memory_graph", [redis_url], ttl=10000)
def retrieve_context(self, query):
"""分布式检索"""
# 获取分布式读锁
with self.lock.acquire():
result = super().retrieve_context(query)
# 缓存到Redis(供其他实例共享)
self.redis.setex(
f"memory:query:{hash(query)}",
ttl=300,
value=json.dumps(result)
)
return result收益:
- 多实例部署支持
- 防止数据竞争
- 查询结果共享缓存
class OptimizedSummaryIndex:
"""优化的FAISS索引"""
def __init__(self):
# 使用IVF索引替代Flat,支持大规模数据
quantizer = faiss.IndexFlatL2(512)
self._store = faiss.IndexIVFFlat(
quantizer, 512, nlist=100 # 100个聚类中心
)
# 预训练聚类中心
if not self._store.is_trained:
self._train_cluster_centers()
def search(self, query_vec, top_k=10):
"""优化检索"""
# 设置nprobe,平衡精度与速度
self._store.nprobe = 10 # 搜索10个聚类
distances, indices = self._store.search(
query_vec.reshape(1, -1), top_k
)
return [
(self._faiss_to_node[idx], 1.0 / (1.0 + dist))
for dist, idx in zip(distances[0], indices[0])
]收益:
- 检索速度提升10倍+
- 内存占用降低50%
- 支持百万级向量
| 风险 | 影响等级 | 改进难度 | 优先级 | 建议编号 |
|---|---|---|---|---|
| 单例全局状态污染 | 高 | 低 | P0 | 建议1 |
| BFS性能瓶颈 | 高 | 中 | P0 | 建议4 |
| 赫布学习边膨胀 | 高 | 低 | P0 | 建议5 |
| 衰减精度丢失 | 中 | 低 | P1 | 建议6 |
| 双路径检索竞态 | 中 | 中 | P1 | 建议9 |
| 持久化原子性 | 中 | 中 | P1 | 建议7 |
| FAISS内存占用 | 中 | 高 | P2 | 建议10 |
| 并发锁竞争 | 低 | 中 | P3 | 建议2 |
| 记忆遗忘丢失 | 低 | 低 | P3 | 建议7 |
| 相似度计算效率 | 低 | 低 | P3 | 建议10 |
祖龙系统记忆板块是一个设计精良但工程实现存在隐患的复杂系统:
- ✅ 异构图结构灵活支持多种记忆类型
- ✅ BFS扩散激活实现跨类型上下文发现
- ✅ 赫布学习自动建立节点关联
- ✅ 艾宾浩斯衰减模拟人类遗忘曲线
- ✅ 双路径检索平衡速度与召回率
- ✅ 三维标签系统精细控制记忆生命周期
- ❌ 单例模式导致全局状态污染
- ❌ BFS算法在图规模增长后性能急剧退化
- ❌ 赫布学习可能导致边数爆炸
- ❌ 缺乏分布式支持,无法横向扩展
- ❌ 持久化与容灾机制不完善
- 短期(1-2周):实现BFS异步化与超时保护、赫布学习语义验证、多版本备份
- 中期(1个月):引入图数据库替代NetworkX、实现监控告警系统
- 长期(3个月):重构为分布式架构、引入向量数据库优化检索
建议按P0→P1→P2→P3优先级逐步改进,确保系统稳定性与可扩展性。
| 功能 | 文件位置 | 行号 |
|---|---|---|
| 单例初始化 | memory_graph.py | 500-583 |
| BFS扩散激活 | memory_graph.py | 1205-1318 |
| 动态BFS参数 | memory_graph.py | 1320-1373 |
| 赫布学习 | memory_graph.py | 1415-1445 |
| 共激活计数 | memory_graph.py | 1446-1494 |
| 艾宾浩斯衰减 | memory_graph.py | 1500-1594 |
| 孤立节点清理 | memory_graph.py | 1596-1686 |
| 双路径检索 | memory_graph.py | 2158-2214 |
| 热路径实现 | memory_graph.py | 2292-2482 |
| 冷路径实现 | memory_graph.py | 2482-2550 |
| 语义相似度 | memory_graph.py | 2236-2290 |
| FAISS摘要索引 | memory_graph.py | 195-300 |
| 节点CRUD | memory_graph.py | 607-699 |
| 边操作 | memory_graph.py | 700-900 |
报告生成时间:2026-05-18
分析工具:华为云码道(CodeArts)代码智能体
模型:Glm-5-internal