fix(memory): add coalesce defaults for activation_value and related node properties

- Add coalesce fallbacks for importance_score, activation_value, and
access_count in statement, entity, weak entity, strong entity, and
memory summary MERGE queries to prevent null values on new nodes
- Set activation_value default to coalesce(importance_score, 0.5) for
consistency with the forgetting/activation scoring logic
- Suppress Neo4j UNRECOGNIZED property key notifications in driver init
since missing keys like last_access_time and activation_value are
expected for newly created nodes
This commit is contained in:
lanceyq
2026-04-17 13:54:12 +08:00
parent 643f69bb90
commit 34387e1f23
2 changed files with 17 additions and 3 deletions

View File

@@ -42,6 +42,9 @@ SET s += {
last_access_time: statement.last_access_time, last_access_time: statement.last_access_time,
access_count: statement.access_count access_count: statement.access_count
} }
SET s.importance_score = coalesce(s.importance_score, 0.5),
s.activation_value = coalesce(s.activation_value, s.importance_score, 0.5),
s.access_count = coalesce(s.access_count, 0)
RETURN s.id AS uuid RETURN s.id AS uuid
""" """
@@ -120,7 +123,7 @@ SET e.name = CASE WHEN entity.name IS NOT NULL AND entity.name <> '' THEN entity
END END
END, END,
e.importance_score = CASE WHEN entity.importance_score IS NOT NULL THEN entity.importance_score ELSE coalesce(e.importance_score, 0.5) END, e.importance_score = CASE WHEN entity.importance_score IS NOT NULL THEN entity.importance_score ELSE coalesce(e.importance_score, 0.5) END,
e.activation_value = CASE WHEN entity.activation_value IS NOT NULL THEN entity.activation_value ELSE e.activation_value END, e.activation_value = CASE WHEN entity.activation_value IS NOT NULL THEN entity.activation_value ELSE coalesce(e.activation_value, e.importance_score, 0.5) END,
e.access_history = CASE WHEN entity.access_history IS NOT NULL THEN entity.access_history ELSE coalesce(e.access_history, []) END, e.access_history = CASE WHEN entity.access_history IS NOT NULL THEN entity.access_history ELSE coalesce(e.access_history, []) END,
e.last_access_time = CASE WHEN entity.last_access_time IS NOT NULL THEN entity.last_access_time ELSE e.last_access_time END, e.last_access_time = CASE WHEN entity.last_access_time IS NOT NULL THEN entity.last_access_time ELSE e.last_access_time END,
e.access_count = CASE WHEN entity.access_count IS NOT NULL THEN entity.access_count ELSE coalesce(e.access_count, 0) END, e.access_count = CASE WHEN entity.access_count IS NOT NULL THEN entity.access_count ELSE coalesce(e.access_count, 0) END,
@@ -165,6 +168,7 @@ SET e += {
} }
// Independent weak flag仅标记弱关系不再维护 relations 聚合字段 // Independent weak flag仅标记弱关系不再维护 relations 聚合字段
SET e.is_weak = true SET e.is_weak = true
SET e.activation_value = coalesce(e.activation_value, 0.5)
RETURN e.id AS id RETURN e.id AS id
""" """
@@ -175,10 +179,12 @@ MERGE (s:ExtractedEntity {id: item.source_id, run_id: item.run_id})
SET s += {name: item.subject, end_user_id: item.end_user_id, run_id: item.run_id} SET s += {name: item.subject, end_user_id: item.end_user_id, run_id: item.run_id}
// Independent strong flag // Independent strong flag
SET s.is_strong = true SET s.is_strong = true
SET s.activation_value = coalesce(s.activation_value, 0.5)
MERGE (o:ExtractedEntity {id: item.target_id, run_id: item.run_id}) MERGE (o:ExtractedEntity {id: item.target_id, run_id: item.run_id})
SET o += {name: item.object, end_user_id: item.end_user_id, run_id: item.run_id} SET o += {name: item.object, end_user_id: item.end_user_id, run_id: item.run_id}
// Independent strong flag // Independent strong flag
SET o.is_strong = true SET o.is_strong = true
SET o.activation_value = coalesce(o.activation_value, 0.5)
""" """
@@ -739,7 +745,7 @@ SET m += {
summary_embedding: summary.summary_embedding, summary_embedding: summary.summary_embedding,
config_id: summary.config_id, config_id: summary.config_id,
importance_score: CASE WHEN summary.importance_score IS NOT NULL THEN summary.importance_score ELSE coalesce(m.importance_score, 0.5) END, importance_score: CASE WHEN summary.importance_score IS NOT NULL THEN summary.importance_score ELSE coalesce(m.importance_score, 0.5) END,
activation_value: CASE WHEN summary.activation_value IS NOT NULL THEN summary.activation_value ELSE m.activation_value END, activation_value: CASE WHEN summary.activation_value IS NOT NULL THEN summary.activation_value ELSE coalesce(m.activation_value, m.importance_score, 0.5) END,
access_history: CASE WHEN summary.access_history IS NOT NULL THEN summary.access_history ELSE coalesce(m.access_history, []) END, access_history: CASE WHEN summary.access_history IS NOT NULL THEN summary.access_history ELSE coalesce(m.access_history, []) END,
last_access_time: CASE WHEN summary.last_access_time IS NOT NULL THEN summary.last_access_time ELSE m.last_access_time END, last_access_time: CASE WHEN summary.last_access_time IS NOT NULL THEN summary.last_access_time ELSE m.last_access_time END,
access_count: CASE WHEN summary.access_count IS NOT NULL THEN summary.access_count ELSE coalesce(m.access_count, 0) END access_count: CASE WHEN summary.access_count IS NOT NULL THEN summary.access_count ELSE coalesce(m.access_count, 0) END

View File

@@ -9,12 +9,15 @@ Classes:
""" """
from typing import Any, List, Dict from typing import Any, List, Dict
import logging
from neo4j import AsyncGraphDatabase, basic_auth from neo4j import AsyncGraphDatabase, basic_auth
from neo4j.time import DateTime as Neo4jDateTime, Date as Neo4jDate, Time as Neo4jTime, Duration as Neo4jDuration from neo4j.time import DateTime as Neo4jDateTime, Date as Neo4jDate, Time as Neo4jTime, Duration as Neo4jDuration
from app.core.config import settings from app.core.config import settings
logger = logging.getLogger(__name__)
def _convert_neo4j_types(value: Any) -> Any: def _convert_neo4j_types(value: Any) -> Any:
"""递归将 neo4j 原生时间类型转为 Python 原生类型 / ISO 字符串,确保可被 json.dumps 序列化。""" """递归将 neo4j 原生时间类型转为 Python 原生类型 / ISO 字符串,确保可被 json.dumps 序列化。"""
@@ -67,7 +70,12 @@ class Neo4jConnector:
) )
self.driver = AsyncGraphDatabase.driver( self.driver = AsyncGraphDatabase.driver(
uri, uri,
auth=basic_auth(username, password) auth=basic_auth(username, password),
# 抑制属性键不存在的 UNRECOGNIZED 分类通知警告(如 01N52
# last_access_time 等属性在节点被检索命中后才写入,
# activation_value 在新节点创建后可能尚未被计算,
# 全新数据库或清空数据后这些属性键不存在是正常业务行为
notifications_disabled_classifications=["UNRECOGNIZED"],
) )
async def close(self): async def close(self):