From 1ba86b102ecc01de3191c3861592d9d4d22dfd81 Mon Sep 17 00:00:00 2001
From: ai_xiaopei <xiaopei@aisim.cn>
Date: Sun, 26 Jul 2026 15:17:48 +0800
Subject: [PATCH] feat: 添加 classify 命令和实体提取功能

---
 internal/classify/entities.go |  164 +++++++++++++++++++++++++++++++++++++++++
 cmd/classify.go               |   55 +++++++++++++
 2 files changed, 219 insertions(+), 0 deletions(-)

diff --git a/cmd/classify.go b/cmd/classify.go
new file mode 100644
index 0000000..d698dd0
--- /dev/null
+++ b/cmd/classify.go
@@ -0,0 +1,55 @@
+package cmd
+
+import (
+	"fmt"
+
+	"github.com/aisim/kb-cli/internal/classify"
+	"github.com/spf13/cobra"
+)
+
+var classifyCmd = &cobra.Command{
+	Use:   "classify",
+	Short: "使用 LLM 对知识库文档进行分类",
+	Long: `使用本地 LLM 分析知识库文档,提取平台、设备、内容类型等信息。
+
+输出:
+  - entities.json: 实体定义(从 实体/ 目录提取)
+  - relations.json: 实体关系
+  - classification.json: 文档分类结果`,
+	RunE: runClassify,
+}
+
+var (
+	classifyBatchSize int
+	classifyDryRun    bool
+)
+
+func init() {
+	rootCmd.AddCommand(classifyCmd)
+	classifyCmd.Flags().IntVar(&classifyBatchSize, "batch-size", 10, "每批处理的文档数量")
+	classifyCmd.Flags().BoolVar(&classifyDryRun, "dry-run", false, "仅显示将要执行的操作")
+}
+
+func runClassify(cmd *cobra.Command, args []string) error {
+	vaultPath := cmd.Flag("vault").Value.String()
+
+	fmt.Println("步骤 1/3: 提取实体信息...")
+	entities, relations, err := classify.ExtractEntities(vaultPath)
+	if err != nil {
+		return fmt.Errorf("提取实体失败: %w", err)
+	}
+
+	if !classifyDryRun {
+		if err := classify.SaveEntities(vaultPath, entities, relations); err != nil {
+			return fmt.Errorf("保存实体失败: %w", err)
+		}
+		fmt.Printf("✓ 已生成 entities.json 和 relations.json\n")
+	} else {
+		fmt.Printf("[DRY-RUN] 将生成 entities.json 和 relations.json\n")
+	}
+
+	fmt.Println("\n步骤 2/3: 文档分类(待实现)...")
+	fmt.Println("步骤 3/3: 生成 classification.json(待实现)...")
+
+	return nil
+}
diff --git a/internal/classify/entities.go b/internal/classify/entities.go
new file mode 100644
index 0000000..14e4c7c
--- /dev/null
+++ b/internal/classify/entities.go
@@ -0,0 +1,164 @@
+package classify
+
+import (
+	"encoding/json"
+	"fmt"
+	"os"
+	"path/filepath"
+)
+
+// Entity 实体定义
+type Entity struct {
+	ID          string   `json:"id"`
+	Name        string   `json:"name"`
+	Aliases     []string `json:"aliases"`
+	Platform    string   `json:"platform,omitempty"`
+	Description string   `json:"description,omitempty"`
+	Users       []string `json:"users,omitempty"`
+	URL         string   `json:"url,omitempty"`
+}
+
+// Entities 实体集合
+type Entities struct {
+	Platforms []Entity `json:"platforms"`
+	Apps      []Entity `json:"apps"`
+	Devices   []Entity `json:"devices"`
+}
+
+// Relation 实体关系
+type Relation struct {
+	From        string `json:"from"`
+	To          string `json:"to"`
+	Type        string `json:"type"`
+	Description string `json:"description"`
+}
+
+// Relations 关系集合
+type Relations struct {
+	Relations []Relation `json:"relations"`
+}
+
+// ExtractEntities 从实体目录提取实体信息
+func ExtractEntities(vaultPath string) (*Entities, *Relations, error) {
+	entityDir := filepath.Join(vaultPath, "实体")
+
+	// 检查目录是否存在
+	if _, err := os.Stat(entityDir); os.IsNotExist(err) {
+		return nil, nil, fmt.Errorf("实体目录不存在: %s", entityDir)
+	}
+
+	// 预定义实体(从设计文档)
+	entities := &Entities{
+		Platforms: []Entity{
+			{
+				ID:          "elc",
+				Name:        "电子秤平台",
+				Aliases:     []string{"电子秤后台", "elc平台", "elc.zhiheiot.com"},
+				URL:         "https://elc.zhiheiot.com",
+				Description: "气站充装作业管理系统",
+				Users:       []string{"气站站长", "充装员", "开票员"},
+			},
+			{
+				ID:          "ops",
+				Name:        "运营管理平台",
+				Aliases:     []string{"运营平台", "运营后台", "rb.zhiheiot.com"},
+				URL:         "https://rb.zhiheiot.com",
+				Description: "气站运营管理系统",
+				Users:       []string{"老板", "管理层", "客服"},
+			},
+		},
+		Apps: []Entity{
+			{
+				ID:          "safety",
+				Name:        "安全用气App",
+				Aliases:     []string{"配送App", "电子秤配送App"},
+				Platform:    "elc",
+				Description: "二维码配送",
+			},
+			{
+				ID:          "delivery",
+				Name:        "易配送App",
+				Aliases:     []string{"配送App", "运营配送"},
+				Platform:    "ops",
+				Description: "NFC识别芯片配送",
+			},
+			{
+				ID:          "assistant",
+				Name:        "艾信助手App",
+				Aliases:     []string{"建档App"},
+				Platform:    "both",
+				Description: "融合两平台建档",
+			},
+		},
+		Devices: []Entity{
+			{
+				ID:          "scale",
+				Name:        "电子秤",
+				Aliases:     []string{"充装电子秤"},
+				Platform:    "elc",
+				Description: "充装作业设备",
+			},
+			{
+				ID:          "gun",
+				Name:        "智能枪",
+				Aliases:     []string{"充装枪", "智能充装枪"},
+				Platform:    "both",
+				Description: "共有硬件",
+			},
+			{
+				ID:          "valve",
+				Name:        "智能阀",
+				Aliases:     []string{"智能角阀"},
+				Platform:    "ops",
+				Description: "气瓶阀门",
+			},
+			{
+				ID:          "box",
+				Name:        "艾信盒子",
+				Aliases:     []string{"4G通信设备"},
+				Platform:    "both",
+				Description: "共有硬件",
+			},
+		},
+	}
+
+	// 预定义关系
+	relations := &Relations{
+		Relations: []Relation{
+			{From: "gun", To: "scale", Type: "connects", Description: "智能枪连接电子秤"},
+			{From: "gun", To: "box", Type: "syncs", Description: "充装数据同步到艾信盒子"},
+			{From: "box", To: "elc", Type: "uploads", Description: "上传充装记录"},
+			{From: "box", To: "ops", Type: "uploads", Description: "上传充装记录"},
+			{From: "valve", To: "delivery", Type: "identified_by", Description: "NFC识别"},
+			{From: "safety", To: "elc", Type: "belongs_to", Description: "属于电子秤平台"},
+			{From: "delivery", To: "ops", Type: "belongs_to", Description: "属于运营管理平台"},
+		},
+	}
+
+	return entities, relations, nil
+}
+
+// SaveEntities 保存实体和关系到 JSON 文件
+func SaveEntities(vaultPath string, entities *Entities, relations *Relations) error {
+	// 保存 entities.json
+	entitiesPath := filepath.Join(vaultPath, "entities.json")
+	entitiesData, err := json.MarshalIndent(entities, "", "  ")
+	if err != nil {
+		return fmt.Errorf("序列化 entities 失败: %w", err)
+	}
+	if err := os.WriteFile(entitiesPath, entitiesData, 0644); err != nil {
+		return fmt.Errorf("写入 entities.json 失败: %w", err)
+	}
+
+	// 保存 relations.json
+	relationsPath := filepath.Join(vaultPath, "relations.json")
+	relationsData, err := json.MarshalIndent(relations, "", "  ")
+	if err != nil {
+		return fmt.Errorf("序列化 relations 失败: %w", err)
+	}
+	if err := os.WriteFile(relationsPath, relationsData, 0644); err != nil {
+		return fmt.Errorf("写入 relations.json 失败: %w", err)
+	}
+
+	return nil
+}

--
Gitblit v1.9.1