diff --git a/go/internal/governance/graph_sqlite.go b/go/internal/governance/graph_sqlite.go index 765b7e1..3d6954c 100644 --- a/go/internal/governance/graph_sqlite.go +++ b/go/internal/governance/graph_sqlite.go @@ -20,6 +20,7 @@ import ( "strings" "sync" "unicode" + "unicode/utf8" "unsafe" "github.com/xiaoxue/memoryweave/internal/models" @@ -441,11 +442,30 @@ type PathResult struct { Score float64 } +// deriveNamespaceForGraph 将 namespace 转为图谱中的实际格式 +// hermes → hermes-main, shared → shared, default → default +func deriveNamespaceForGraph(ns string) string { + if ns == "" { + return "" + } + // already full form + if strings.HasSuffix(ns, "-main") || ns == "shared" || ns == "default" { + return ns + } + // bare name → full form (hermes → hermes-main) + return ns + "-main" +} + func buildNamespaceClause(namespace string) string { if namespace == "" { return "1=1" } - return fmt.Sprintf("(e.namespace = '%s' OR e.namespace = 'default')", escape(namespace)) + // 确保用图谱中的实际格式 + derived := deriveNamespaceForGraph(namespace) + if derived == "shared" { + return "(e.namespace = 'shared')" + } + return fmt.Sprintf("(e.namespace = '%s' OR e.namespace = 'default')", escape(derived)) } // sortResultsByScore 简单选择排序 @@ -567,30 +587,32 @@ func (gs *SQLiteGraphStore) ExpandFromResults(results []models.RecallResult, nam } seen[r.ID] = true - // 从内容中提取可能作为实体的关键词 + fmt.Printf("[E1] content=%q\n", r.Content) entities := extractPotentialEntities(r.Content) + fmt.Printf("[E1] entities=%v, namespace=%q\n", entities, namespace) for _, entity := range entities { + fmt.Printf("[E1] trying entity=%q\n", entity) if seenEntities[entity] { continue } seenEntities[entity] = true + // SQLite 图谱节点 ID 格式: n_{entity_name},需 normalizeEntityID 转换 nodeID := normalizeEntityID(entity) + fmt.Printf("[E1] Navigate(nodeID=%q, ns=%q)\n", nodeID, namespace) paths, _ := gs.Navigate(nodeID, maxHops, namespace) + fmt.Printf("[E1] Navigate returned %d paths for entity=%q\n", len(paths), entity) for _, p := range paths { - // Navigate 返回的是单条边 (source/target/relation/weight) - // 有两种情况: - // 1. source == entity(正向边):target 是下游邻居 - // 2. target == entity(反向边):source 是上游邻居 + // Navigate 返回字段: from, to, relation, weight, hop var neighbor, rel string - src, _ := p["source"].(string) - tgt, _ := p["target"].(string) + from, _ := p["from"].(string) + to, _ := p["to"].(string) relVal, _ := p["relation"].(string) - if src == entity && tgt != "" { - neighbor = tgt + if from == entity && to != "" { + neighbor = to rel = relVal - } else if tgt == entity && src != "" { - neighbor = src + } else if to == entity && from != "" { + neighbor = from rel = "↩ " + relVal } if neighbor == "" { @@ -625,8 +647,8 @@ func extractPotentialEntities(text string) []string { i++ } chinese := string(runes[start:i]) - // 不等式:2 <= len(chinese) <= 8 - if len(chinese) >= 2 && len(chinese) <= 8 && !seen[chinese] { + // 不等式:2 <= len(chinese) <= 8(字符数,非字节数) + if utf8.RuneCountInString(chinese) >= 2 && utf8.RuneCountInString(chinese) <= 8 && !seen[chinese] { seen[chinese] = true entities = append(entities, chinese) }