memoryweave/go/internal/governance/graph_expander.go

191 lines
4.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 织忆 MemoryWeave — 图谱扩展器(供 Recall 管线调用)
package governance
import (
"fmt"
"strings"
"github.com/xiaoxue/memoryweave/internal/models"
)
// ExpandFromResults 从 recall 结果出发,双向 BFS 扩展图谱邻接节点
// 实现 GraphStore 接口
func (g *InMemoryGraph) ExpandFromResults(results []models.RecallResult, namespace string, maxHops int) []models.RecallResult {
var expanded []models.RecallResult
seen := make(map[string]bool)
// 收集已有结果 ID
for _, r := range results {
seen[r.ID] = true
}
// 从每个结果出发扩展
for _, r := range results {
paths, err := g.Navigate(r.Category, maxHops, namespace, nil)
if err != nil {
continue
}
for _, p := range paths {
to, _ := p["to"].(string)
from, _ := p["from"].(string)
for _, id := range []string{to, from} {
if id != "" && !seen[id] {
seen[id] = true
expanded = append(expanded, models.RecallResult{
ID: id,
Category: "graph_expanded",
Score: 0.5, // 图谱扩展降权
})
}
}
}
}
return expanded
}
// ExpandWithSummary BFS 扩展 + 生成汇总语句 — E1 图谱导航增强
func (g *InMemoryGraph) ExpandWithSummary(results []models.RecallResult, namespace string, maxHops int) models.GraphBFSResult {
if maxHops <= 0 {
maxHops = 2
}
seenEntities := make(map[string]bool)
var relations []models.ExpandedRelation
// 从 recall 结果提取实体
for _, r := range results {
entities := extractPotentialEntitiesFromContent(r.Content)
for _, entity := range entities {
if seenEntities[entity] {
continue
}
seenEntities[entity] = true
nodeID := normalizeEntityID(entity)
paths, _ := g.Navigate(nodeID, maxHops, namespace, nil)
for _, p := range paths {
from, _ := p["source"].(string)
to, _ := p["target"].(string)
rel, _ := p["relation"].(string)
weight, _ := p["weight"].(float64)
hop, _ := p["hop"].(int)
fromName := strings.TrimPrefix(from, "n_")
toName := strings.TrimPrefix(to, "n_")
rel = strings.TrimSpace(rel)
if rel == "" {
rel = "RELATED_TO"
}
relations = append(relations, models.ExpandedRelation{
From: fromName,
To: toName,
Relation: rel,
Hops: hop,
Weight: weight,
Score: r.Score * weight,
})
}
}
}
summary := buildBFSSummary(relations)
return models.GraphBFSResult{
ExpandedRelations: relations,
Summary: summary,
}
}
// extractPotentialEntitiesFromContent 从文本提取实体InMemoryGraph 用)
func extractPotentialEntitiesFromContent(text string) []string {
var entities []string
seen := make(map[string]bool)
runes := []rune(text)
for i := 0; i < len(runes); {
r := runes[i]
// 中文字符
if r >= 0x4E00 && r <= 0x9FFF {
start := i
i++
for i < len(runes) && runes[i] >= 0x4E00 && runes[i] <= 0x9FFF {
i++
}
chinese := string(runes[start:i])
if len(chinese) >= 2 && len(chinese) <= 8 && !seen[chinese] {
seen[chinese] = true
entities = append(entities, chinese)
}
continue
}
// 英文/其他
start := i
for i < len(runes) {
r2 := runes[i]
if r2 >= 0x4E00 && r2 <= 0x9FFF {
break
}
i++
}
if i-start < 2 {
continue
}
w := string(runes[start:i])
w = strings.Trim(w, ",.;:!?,。;:!?、\"'()[]【】")
if len(w) < 2 {
continue
}
first := []rune(w)
if len(first) > 0 && first[0] >= 'A' && first[0] <= 'Z' {
lower := strings.ToLower(w)
if !seen[lower] {
seen[lower] = true
entities = append(entities, w)
}
}
}
return entities
}
// buildBFSSummary 从扩展关系列表生成一句话汇总
func buildBFSSummary(relations []models.ExpandedRelation) string {
if len(relations) == 0 {
return "未发现图谱关联"
}
if len(relations) == 1 {
r := relations[0]
return fmt.Sprintf("%s --[%s]--> %s%d跳权重%.2f", r.From, r.Relation, r.To, r.Hops, r.Weight)
}
relCounts := make(map[string]int)
var totalWeight float64
maxHops := 0
for _, r := range relations {
relCounts[r.Relation]++
totalWeight += r.Weight
if r.Hops > maxHops {
maxHops = r.Hops
}
}
var topRel string
topCount := 0
for rel, cnt := range relCounts {
if cnt > topCount {
topCount = cnt
topRel = rel
}
}
avgWeight := totalWeight / float64(len(relations))
uniqueEntities := make(map[string]bool)
for _, r := range relations {
uniqueEntities[r.From] = true
uniqueEntities[r.To] = true
}
return fmt.Sprintf("发现 %d 条关联(跨越 %d 个实体,最深 %d 跳),关系以 [%s] 为主(%d 条),平均权重 %.2f",
len(relations), len(uniqueEntities), maxHops, topRel, topCount, avgWeight)
}