自验证机制:consolidate 聚类下限 + timestamp 前置检查 + 启动数据目录检查 + 修复图谱扩展字段名

1. consolidation_pipe.go:
   - 前置:抽样检查记忆时间戳(< 2024-01-01 视为可疑 epoch-0)
   - 后置:clusters <= 1 时记录 ERROR + patterns 标记异常
2. server.go: runStartupChecks() 启动时检测废弃路径 + socket 可达性
3. graph_expander.go: Navigate 返回字段从 "target"/"source" 修正为 "to"/"from"
This commit is contained in:
xiaowei 2026-06-03 12:44:31 +08:00
parent 612d915eec
commit 3d054e7590
3 changed files with 108 additions and 4 deletions

View File

@ -50,7 +50,14 @@ func (cp *ConsolidationPipeline) Run() (*ConsolidationReport, error) {
// mode: "cluster_only" | "full" | "prune_only"
// full 模式触发 LLM 质量回溯 + 衰减校准(仅低频调度使用)
func (cp *ConsolidationPipeline) RunWithMode(mode string) (*ConsolidationReport, error) {
// ─── 尝试 Rust sidecar ──────────────────────────────
// ─── 前置检查timestamp 合理性(防止 epoch-0 数据污染聚类结果)────────
if mode == "full" || mode == "cluster_only" {
if suspicious, total := cp.checkTimestampSanity(); suspicious > 0 {
log.Printf("[WARN] timestamp 检查: %d/%d 条记忆时间戳可疑(可能是 epoch-0结果仅供参考", suspicious, total)
}
}
// ─── 尝试 Rust sidecar ───────────────────────────────────────
var report *ConsolidationReport
if cp.dataDir != "" && cp.sqlitePath != "" {
// 用指定模式调用 Rust sidecar
@ -64,6 +71,13 @@ func (cp *ConsolidationPipeline) RunWithMode(mode string) (*ConsolidationReport,
Patterns: []string{fmt.Sprintf("decay_rates=%v", rustReport.DecayRates)},
GraphPruned: 0,
}
// ─── 后置检查:聚类数量下限 ─────────────────────────────
if rustReport.Clusters <= 1 {
log.Printf("[ERROR] consolidate 生成 clusters=%d疑似 epoch-0 数据或聚类失效),结果可能无效", rustReport.Clusters)
report.Patterns = append(report.Patterns, fmt.Sprintf("⚠️ clusters=%d 可能异常", rustReport.Clusters))
}
if rustReport.Quality != nil {
report.Patterns = append(report.Patterns,
fmt.Sprintf("quality_score=%.2f low_info=%d hallucinations=%d",
@ -270,6 +284,56 @@ func (cp *ConsolidationPipeline) updateGraph() (int, error) {
return before - after, nil
}
// checkTimestampSanity 抽样检查记忆时间戳合理性5% sampling不阻塞
// 返回 (可疑数量, 抽样总数)。epoch-0< 2024-01-01视为可疑。
func (cp *ConsolidationPipeline) checkTimestampSanity() (suspicious, total int) {
const epochThreshold int64 = 1704067200 // 2024-01-01 00:00:00 UTC
memories, err := cp.ldb.GetCandidatesForForgetting()
if err != nil || len(memories) == 0 {
return 0, 0
}
// 全量不超过 100 条
sampleSize := len(memories)
if sampleSize > 100 {
sampleSize = 100
}
step := len(memories) / sampleSize
if step < 1 {
step = 1
}
for i := 0; i < len(memories); i += step {
total++
ts := memoryInt64Val(memories[i], "created_at")
if ts > 0 && ts < epochThreshold {
suspicious++
}
}
return suspicious, total
}
// memoryInt64Val 安全取 int64处理 string/int/float
func memoryInt64Val(m map[string]interface{}, key string) int64 {
v, ok := m[key]
if !ok {
return 0
}
switch val := v.(type) {
case int64:
return val
case float64:
return int64(val)
case int:
return int64(val)
case string:
t, _ := time.Parse(time.RFC3339, val)
return t.Unix()
}
return 0
}
// ─── 报告 ─────────────────────────────────────────────
type ConsolidationReport struct {

View File

@ -87,6 +87,10 @@ func NewServer() http.Handler {
ldb = storage.NewMemLanceClient(emb)
log.Printf("[zhiyid] 存储后端: 内存(零依赖)")
}
// 启动时数据目录一致性检查(防止路径混乱导致读取废弃数据)
runStartupChecks(backend)
// G7.2: 注册 LDB getter供 skill crystallize 使用)
routes.RegisterLDBGetter(func() storage.LanceDB { return ldb })
@ -1109,3 +1113,39 @@ func AgentTypeDecayOrDefault(agentType string) float64 {
}
return 0.015
}
// runStartupChecks 启动时数据目录一致性检查
// 检测废弃路径(如 /home/muc/data并警告防止数据源混乱
func runStartupChecks(backend string) {
canonicalDataDir := "/var/lib/memoryweave"
deprecatedPaths := []string{
"/home/muc/data",
"/home/muc/.local/share/memoryweave",
}
for _, dep := range deprecatedPaths {
if _, err := os.Stat(dep); err == nil {
log.Printf("[WARN] 检测到废弃数据目录 %s仍有数据残留当前 canonical: %s", dep, canonicalDataDir)
}
}
// LanceDB 后端:检查 Rust sidecar socket 是否可达
if backend == "lancedb" {
sockPath := os.Getenv("LANCEDB_SOCKET")
if sockPath == "" {
sockPath = "/tmp/zhiyi-ipc.sock"
}
if _, err := os.Stat(sockPath); os.IsNotExist(err) {
log.Printf("[WARN] LanceDB socket 不存在 (%s)Rust sidecar 可能未运行", sockPath)
} else {
log.Printf("[startup] LanceDB socket 就绪: %s", sockPath)
}
}
// canonical 目录存在性检查
if _, err := os.Stat(canonicalDataDir); os.IsNotExist(err) {
log.Printf("[WARN] canonical 数据目录不存在: %s首次部署", canonicalDataDir)
} else {
log.Printf("[startup] 数据目录正常: %s", canonicalDataDir)
}
}

View File

@ -26,10 +26,10 @@ func (g *InMemoryGraph) ExpandFromResults(results []models.RecallResult, namespa
continue
}
for _, p := range paths {
target, _ := p["target"].(string)
source, _ := p["source"].(string)
to, _ := p["to"].(string)
from, _ := p["from"].(string)
for _, id := range []string{target, source} {
for _, id := range []string{to, from} {
if id != "" && !seen[id] {
seen[id] = true
expanded = append(expanded, models.RecallResult{