| New file |
| | |
| | | package graph |
| | | |
| | | import ( |
| | | "github.com/aisim/kb-cli/internal/vault" |
| | | ) |
| | | |
| | | // BuildGraph 从文件元数据构建知识图谱 |
| | | func BuildGraph(files []*vault.FileMeta) *Graph { |
| | | g := &Graph{} |
| | | |
| | | // 标签/实体 -> 虚拟节点 ID 映射 |
| | | labelToID := make(map[string]int64) |
| | | nextVirtualID := int64(1000000) // 虚拟节点从 1000000 开始 |
| | | |
| | | getOrCreateVirtualNode := func(label string) int64 { |
| | | if id, ok := labelToID[label]; ok { |
| | | return id |
| | | } |
| | | id := nextVirtualID |
| | | nextVirtualID++ |
| | | labelToID[label] = id |
| | | return id |
| | | } |
| | | |
| | | for i, f := range files { |
| | | node := &Node{ |
| | | ID: int64(i + 1), |
| | | Path: f.Path, |
| | | Title: f.Title, |
| | | Section: f.Section, |
| | | Tags: f.Tags, |
| | | Entities: f.Entities, |
| | | Wikilinks: f.Wikilinks, |
| | | Content: f.Content, |
| | | } |
| | | g.Nodes = append(g.Nodes, node) |
| | | |
| | | // 创建 tag 边 |
| | | for _, tag := range f.Tags { |
| | | virtualID := getOrCreateVirtualNode("tag:" + tag) |
| | | g.Edges = append(g.Edges, &Edge{ |
| | | FromNode: node.ID, |
| | | ToNode: virtualID, |
| | | Relation: "tag", |
| | | Label: tag, |
| | | }) |
| | | } |
| | | |
| | | // 创建 entity 边 |
| | | for _, entity := range f.Entities { |
| | | virtualID := getOrCreateVirtualNode("entity:" + entity) |
| | | g.Edges = append(g.Edges, &Edge{ |
| | | FromNode: node.ID, |
| | | ToNode: virtualID, |
| | | Relation: "entity", |
| | | Label: entity, |
| | | }) |
| | | } |
| | | } |
| | | |
| | | // 创建 wikilink 边(文件间链接) |
| | | pathToID := make(map[string]int64) |
| | | for _, n := range g.Nodes { |
| | | pathToID[n.Path] = n.ID |
| | | } |
| | | |
| | | for _, n := range g.Nodes { |
| | | for _, link := range n.Wikilinks { |
| | | // 尝试匹配目标文件(模糊匹配:链接文本可能只是标题的一部分) |
| | | for _, target := range g.Nodes { |
| | | if matchesWikilink(target, link) { |
| | | g.Edges = append(g.Edges, &Edge{ |
| | | FromNode: n.ID, |
| | | ToNode: target.ID, |
| | | Relation: "wikilink", |
| | | Label: link, |
| | | }) |
| | | break |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | return g |
| | | } |
| | | |
| | | // matchesWikilink 检查文件是否匹配 wikilink |
| | | func matchesWikilink(node *Node, link string) bool { |
| | | // 精确匹配标题 |
| | | if node.Title == link { |
| | | return true |
| | | } |
| | | // 匹配文件名(不含扩展名和编号前缀) |
| | | // 例如:[[充装规格配置]] 匹配 "知识/002-充装规格配置.md" |
| | | return false // 简化版,后续可扩展 |
| | | } |
| New file |
| | |
| | | package graph |
| | | |
| | | import ( |
| | | "testing" |
| | | |
| | | "github.com/aisim/kb-cli/internal/vault" |
| | | ) |
| | | |
| | | func TestBuildGraph(t *testing.T) { |
| | | files := []*vault.FileMeta{ |
| | | { |
| | | Path: "FAQ/充装类/001-test.md", |
| | | Title: "测试FAQ", |
| | | Section: "FAQ", |
| | | Tags: []string{"充装", "智能枪"}, |
| | | Entities: []string{"YSP-35.5"}, |
| | | Wikilinks: []string{}, |
| | | Content: "测试内容", |
| | | }, |
| | | { |
| | | Path: "知识/002-配置.md", |
| | | Title: "充装规格配置", |
| | | Section: "知识", |
| | | Tags: []string{"充装"}, |
| | | Entities: []string{}, |
| | | Wikilinks: []string{}, |
| | | Content: "配置内容", |
| | | }, |
| | | } |
| | | |
| | | g := BuildGraph(files) |
| | | |
| | | if len(g.Nodes) != 2 { |
| | | t.Errorf("node count = %d, want 2", len(g.Nodes)) |
| | | } |
| | | |
| | | // 应该有 3 条边:2条 tag + 1条 entity |
| | | tagEdges := 0 |
| | | entityEdges := 0 |
| | | for _, e := range g.Edges { |
| | | if e.Relation == "tag" { |
| | | tagEdges++ |
| | | } |
| | | if e.Relation == "entity" { |
| | | entityEdges++ |
| | | } |
| | | } |
| | | if tagEdges != 3 { // 充装 + 智能枪 + 充装(第二个文件) |
| | | t.Errorf("tag edges = %d, want 3", tagEdges) |
| | | } |
| | | if entityEdges != 1 { |
| | | t.Errorf("entity edges = %d, want 1", entityEdges) |
| | | } |
| | | } |
| New file |
| | |
| | | package graph |
| | | |
| | | // Node 图节点(对应一个 markdown 文件) |
| | | type Node struct { |
| | | ID int64 `json:"id"` |
| | | Path string `json:"path"` |
| | | Title string `json:"title"` |
| | | Section string `json:"section"` |
| | | Tags []string `json:"tags"` |
| | | Entities []string `json:"entities"` |
| | | Wikilinks []string `json:"wikilinks"` |
| | | Content string `json:"content"` |
| | | } |
| | | |
| | | // Edge 图边(实体关系) |
| | | type Edge struct { |
| | | FromNode int64 `json:"from_node"` |
| | | ToNode int64 `json:"to_node"` // 对于 tag/entity 边,ToNode 可以是虚拟节点 ID |
| | | Relation string `json:"relation"` // "tag" | "entity" | "wikilink" |
| | | Label string `json:"label"` // 具体值 |
| | | } |
| | | |
| | | // Graph 知识图谱 |
| | | type Graph struct { |
| | | Nodes []*Node |
| | | Edges []*Edge |
| | | } |