Files
Enterprise-Knowledge-Base/internal/agent/tools/query_knowledge_graph.go
T

424 lines
13 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.
package tools
import (
"context"
"fmt"
"sort"
"sync"
"github.com/Tencent/WeKnora/internal/types"
"github.com/Tencent/WeKnora/internal/types/interfaces"
)
// QueryKnowledgeGraphTool queries the knowledge graph for entities and relationships
type QueryKnowledgeGraphTool struct {
BaseTool
knowledgeService interfaces.KnowledgeBaseService
}
// NewQueryKnowledgeGraphTool creates a new query knowledge graph tool
func NewQueryKnowledgeGraphTool(knowledgeService interfaces.KnowledgeBaseService) *QueryKnowledgeGraphTool {
description := `查询知识图谱,探索实体关系和知识网络。
## 何时使用
**适用场景**:
- ✅ 需要了解实体之间的关系(如"Docker和Kubernetes的关系")
- ✅ 探索知识网络和概念关联
- ✅ 查找特定实体的相关信息
- ✅ 理解技术架构和系统关系
**不适用**:
- ❌ 普通文本搜索(用 knowledge_search 更合适)
- ❌ 知识库未配置图谱抽取
- ❌ 需要精确的文档内容(用 knowledge_search)
## 参数说明
**knowledge_base_ids** (required): 要查询的知识库ID数组(1-10个)
- 只有配置了图谱抽取的知识库才会有效
- 支持批量并发查询多个知识库
- 示例: ["kb_tech", "kb_arch"]
**query** (required): 查询内容
- 可以是实体名称(如"Docker")
- 可以是关系查询(如"容器编排")
- 可以是概念搜索(如"微服务架构")
## 图谱配置
知识图谱需要在知识库中预先配置:
- **实体类型**(Nodes):如"技术"、"工具"、"概念"
- **关系类型**(Relations):如"依赖"、"使用"、"包含"
如果知识库未配置图谱,工具会提示并返回普通搜索结果。
## 配合使用
1. **关系探索**: query_knowledge_graph → get_chunk_detail(查看详细内容)
2. **网络分析**: query_knowledge_graph → get_related_chunks(扩展上下文)
3. **主题研究**: knowledge_search → query_knowledge_graph(深入实体关系)
## 当前状态
⚠️ **注意**: 完整的图数据库集成正在开发中。当前版本:
- ✓ 支持图谱配置查询
- ✓ 返回图谱相关的文档片段
- ✓ 显示实体和关系配置信息
- ⏳ 完整的图查询语言(Cypher)支持开发中
- ⏳ 可视化图数据结构开发中
## Tips
- 结果会标注图谱配置状态
- 返回的Data字段包含结构化图信息供前端展示
- 跨知识库结果自动去重
- 按相关度排序`
return &QueryKnowledgeGraphTool{
BaseTool: NewBaseTool("query_knowledge_graph", description),
knowledgeService: knowledgeService,
}
}
// Parameters returns the JSON schema for the tool's parameters
func (t *QueryKnowledgeGraphTool) Parameters() map[string]interface{} {
return map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"knowledge_base_ids": map[string]interface{}{
"type": "array",
"description": "Array of knowledge base IDs to query",
"items": map[string]interface{}{
"type": "string",
},
"minItems": 1,
"maxItems": 10,
},
"query": map[string]interface{}{
"type": "string",
"description": "查询内容(实体名称或查询文本)",
},
},
"required": []string{"knowledge_base_ids", "query"},
}
}
// Execute queries the knowledge graph with concurrent KB processing
func (t *QueryKnowledgeGraphTool) Execute(ctx context.Context, args map[string]interface{}) (*types.ToolResult, error) {
// Extract knowledge_base_ids array
kbIDsRaw, ok := args["knowledge_base_ids"].([]interface{})
if !ok || len(kbIDsRaw) == 0 {
return &types.ToolResult{
Success: false,
Error: "knowledge_base_ids is required and must be a non-empty array",
}, fmt.Errorf("knowledge_base_ids is required")
}
// Convert to string slice
var kbIDs []string
for _, id := range kbIDsRaw {
if idStr, ok := id.(string); ok && idStr != "" {
kbIDs = append(kbIDs, idStr)
}
}
if len(kbIDs) == 0 {
return &types.ToolResult{
Success: false,
Error: "knowledge_base_ids must contain at least one valid KB ID",
}, fmt.Errorf("no valid KB IDs provided")
}
query, ok := args["query"].(string)
if !ok || query == "" {
return &types.ToolResult{
Success: false,
Error: "query is required",
}, fmt.Errorf("invalid query")
}
// Concurrently query all knowledge bases
type graphQueryResult struct {
kbID string
kb *types.KnowledgeBase
results []*types.SearchResult
err error
}
var wg sync.WaitGroup
var mu sync.Mutex
kbResults := make(map[string]*graphQueryResult)
searchParams := types.SearchParams{
QueryText: query,
MatchCount: 10,
}
for _, kbID := range kbIDs {
wg.Add(1)
go func(id string) {
defer wg.Done()
// Get knowledge base to check graph configuration
kb, err := t.knowledgeService.GetKnowledgeBaseByID(ctx, id)
if err != nil {
mu.Lock()
kbResults[id] = &graphQueryResult{kbID: id, err: fmt.Errorf("获取知识库失败: %v", err)}
mu.Unlock()
return
}
// Check if graph extraction is enabled
if kb.ExtractConfig == nil || (len(kb.ExtractConfig.Nodes) == 0 && len(kb.ExtractConfig.Relations) == 0) {
mu.Lock()
kbResults[id] = &graphQueryResult{kbID: id, err: fmt.Errorf("未配置知识图谱抽取")}
mu.Unlock()
return
}
// Query graph
results, err := t.knowledgeService.HybridSearch(ctx, id, searchParams)
if err != nil {
mu.Lock()
kbResults[id] = &graphQueryResult{kbID: id, kb: kb, err: fmt.Errorf("查询失败: %v", err)}
mu.Unlock()
return
}
mu.Lock()
kbResults[id] = &graphQueryResult{kbID: id, kb: kb, results: results}
mu.Unlock()
}(kbID)
}
wg.Wait()
// Collect and deduplicate results
seenChunks := make(map[string]*types.SearchResult)
var errors []string
graphConfigs := make(map[string]map[string]interface{})
kbCounts := make(map[string]int)
for _, kbID := range kbIDs {
result := kbResults[kbID]
if result.err != nil {
errors = append(errors, fmt.Sprintf("KB %s: %v", kbID, result.err))
continue
}
if result.kb != nil && result.kb.ExtractConfig != nil {
graphConfigs[kbID] = map[string]interface{}{
"nodes": result.kb.ExtractConfig.Nodes,
"relations": result.kb.ExtractConfig.Relations,
}
}
kbCounts[kbID] = len(result.results)
for _, r := range result.results {
if _, seen := seenChunks[r.ID]; !seen {
seenChunks[r.ID] = r
}
}
}
// Convert map to slice and sort by score
allResults := make([]*types.SearchResult, 0, len(seenChunks))
for _, result := range seenChunks {
allResults = append(allResults, result)
}
sort.Slice(allResults, func(i, j int) bool {
return allResults[i].Score > allResults[j].Score
})
if len(allResults) == 0 {
return &types.ToolResult{
Success: true,
Output: "未找到相关的图谱信息。",
Data: map[string]interface{}{
"knowledge_base_ids": kbIDs,
"query": query,
"results": []interface{}{},
"graph_configs": graphConfigs,
"errors": errors,
},
}, nil
}
// Format output with enhanced graph information
output := "=== 知识图谱查询 ===\n\n"
output += fmt.Sprintf("📊 查询: %s\n", query)
output += fmt.Sprintf("🎯 目标知识库: %v\n", kbIDs)
output += fmt.Sprintf("✓ 找到 %d 条相关结果(已去重)\n\n", len(allResults))
if len(errors) > 0 {
output += "=== ⚠️ 部分失败 ===\n"
for _, errMsg := range errors {
output += fmt.Sprintf(" - %s\n", errMsg)
}
output += "\n"
}
// Display graph configuration status
hasGraphConfig := false
output += "=== 📈 图谱配置状态 ===\n\n"
for kbID, config := range graphConfigs {
hasGraphConfig = true
output += fmt.Sprintf("知识库【%s】:\n", kbID)
nodes, _ := config["nodes"].([]interface{})
relations, _ := config["relations"].([]interface{})
if len(nodes) > 0 {
output += fmt.Sprintf(" ✓ 实体类型 (%d): ", len(nodes))
nodeNames := make([]string, 0, len(nodes))
for _, n := range nodes {
if nodeMap, ok := n.(map[string]interface{}); ok {
if name, ok := nodeMap["name"].(string); ok {
nodeNames = append(nodeNames, name)
}
}
}
output += fmt.Sprintf("%v\n", nodeNames)
} else {
output += " ⚠️ 未配置实体类型\n"
}
if len(relations) > 0 {
output += fmt.Sprintf(" ✓ 关系类型 (%d): ", len(relations))
relNames := make([]string, 0, len(relations))
for _, r := range relations {
if relMap, ok := r.(map[string]interface{}); ok {
if name, ok := relMap["name"].(string); ok {
relNames = append(relNames, name)
}
}
}
output += fmt.Sprintf("%v\n", relNames)
} else {
output += " ⚠️ 未配置关系类型\n"
}
output += "\n"
}
if !hasGraphConfig {
output += "⚠️ 所查询的知识库均未配置图谱抽取\n"
output += "💡 提示: 需要在知识库设置中配置实体和关系类型\n\n"
}
// Display result counts by KB
if len(kbCounts) > 0 {
output += "=== 📚 知识库覆盖 ===\n"
for kbID, count := range kbCounts {
output += fmt.Sprintf(" - %s: %d 条结果\n", kbID, count)
}
output += "\n"
}
// Display search results
output += "=== 🔍 查询结果 ===\n\n"
if !hasGraphConfig {
output += "💡 当前返回相关文档片段(知识库未配置图谱)\n\n"
} else {
output += "💡 基于图谱配置的相关内容检索\n\n"
}
formattedResults := make([]map[string]interface{}, 0, len(allResults))
currentKB := ""
for i, result := range allResults {
// Group by knowledge base
if result.KnowledgeID != currentKB {
currentKB = result.KnowledgeID
if i > 0 {
output += "\n"
}
output += fmt.Sprintf("【来源文档: %s】\n\n", result.KnowledgeTitle)
}
relevanceLevel := GetRelevanceLevel(result.Score)
output += fmt.Sprintf("结果 #%d:\n", i+1)
output += fmt.Sprintf(" 📍 相关度: %.2f (%s)\n", result.Score, relevanceLevel)
output += fmt.Sprintf(" 🔗 匹配方式: %s\n", FormatMatchType(result.MatchType))
output += fmt.Sprintf(" 📄 内容: %s\n", result.Content)
output += fmt.Sprintf(" 🆔 chunk_id: %s\n\n", result.ID)
formattedResults = append(formattedResults, map[string]interface{}{
"result_index": i + 1,
"chunk_id": result.ID,
"content": result.Content,
"score": result.Score,
"relevance_level": relevanceLevel,
"knowledge_id": result.KnowledgeID,
"knowledge_title": result.KnowledgeTitle,
"match_type": FormatMatchType(result.MatchType),
})
}
output += "=== 💡 使用提示 ===\n"
output += "- ✓ 结果已跨知识库去重并按相关度排序\n"
output += "- ✓ 使用 get_chunk_detail 获取完整内容\n"
output += "- ✓ 使用 get_related_chunks 探索上下文\n"
if !hasGraphConfig {
output += "- ⚠️ 配置图谱抽取以获得更精准的实体关系结果\n"
}
output += "- ⏳ 完整的图查询语言(Cypher)支持开发中\n"
// Build structured graph data for frontend visualization
graphData := buildGraphVisualizationData(allResults, graphConfigs)
return &types.ToolResult{
Success: true,
Output: output,
Data: map[string]interface{}{
"knowledge_base_ids": kbIDs,
"query": query,
"results": formattedResults,
"count": len(allResults),
"kb_counts": kbCounts,
"graph_configs": graphConfigs,
"graph_data": graphData,
"has_graph_config": hasGraphConfig,
"errors": errors,
"display_type": "graph_query_results",
},
}, nil
}
// buildGraphVisualizationData builds structured data for graph visualization
func buildGraphVisualizationData(results []*types.SearchResult, graphConfigs map[string]map[string]interface{}) map[string]interface{} {
// Build a simple graph structure for frontend visualization
nodes := make([]map[string]interface{}, 0)
edges := make([]map[string]interface{}, 0)
// Create nodes from results
seenEntities := make(map[string]bool)
for i, result := range results {
if !seenEntities[result.ID] {
nodes = append(nodes, map[string]interface{}{
"id": result.ID,
"label": fmt.Sprintf("Chunk %d", i+1),
"content": result.Content,
"kb_id": result.KnowledgeID,
"kb_title": result.KnowledgeTitle,
"score": result.Score,
"type": "chunk",
})
seenEntities[result.ID] = true
}
}
// TODO: Extract actual entities and relations when graph extraction is fully implemented
// For now, create placeholder structure
return map[string]interface{}{
"nodes": nodes,
"edges": edges,
"total_nodes": len(nodes),
"total_edges": len(edges),
"visualization_note": "完整的图可视化功能开发中,当前显示文档节点",
}
}