// Package evolver 实现 Agent CLI 的自进化引擎(Evolver)。 // Logan.Li // 设计参考 opsxcli internal/agent/evolver,保留四层记忆 + 进化循环思想,精简适配为后端库: // - 记忆持久化用文件(data/agentcli/*.jsonl),与 opsxcli 原生一致,零 DB 依赖。 // - 进化循环异步执行,失败优雅降级,绝不影响主 ReAct 循环。 // - LLM 可用时用于提炼经验/事实;不可用时退化为规则生成。 package evolver import ( "encoding/json" "os" "path/filepath" "strings" "sync" "time" ) // ---- 基础文件存储 ---- // Logan.Li // ensureDir 确保目录存在 // Logan.Li func ensureDir(dir string) error { return os.MkdirAll(dir, 0o755) } // readJSONL 读取 JSONL 文件为切片 // Logan.Li func readJSONL[T any](path string) []T { raw, err := os.ReadFile(path) if err != nil || len(raw) == 0 { return nil } var out []T for _, line := range strings.Split(strings.TrimSpace(string(raw)), "\n") { line = strings.TrimSpace(line) if line == "" { continue } var item T if json.Unmarshal([]byte(line), &item) == nil { out = append(out, item) } } return out } // appendJSONL 追加一行 JSON // Logan.Li func appendJSONL[T any](path string, item T) error { b, err := json.Marshal(item) if err != nil { return err } b = append(b, '\n') f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644) if err != nil { return err } defer f.Close() _, err = f.Write(b) return err } // readJSON 读取整个 JSON 文件(失败返回默认值) // Logan.Li func readJSON[T any](path string, def T) T { raw, err := os.ReadFile(path) if err != nil { return def } var v T if json.Unmarshal(raw, &v) != nil { return def } return v } // writeJSON 写入整个 JSON 文件 // Logan.Li func writeJSON[T any](path string, v T) error { b, err := json.MarshalIndent(v, "", " ") if err != nil { return err } return os.WriteFile(path, b, 0o644) } // ---- 四层记忆 ---- // Logan.Li // ExperienceEntry 经验记忆条目(最核心,直接注入下次 Prompt) // Logan.Li type ExperienceEntry struct { TaskType string `json:"task_type"` Query string `json:"query"` Hint string `json:"hint"` // 经验提示 ToolSequence []string `json:"tool_sequence"` // 推荐工具序列 Success bool `json:"success"` Timestamp int64 `json:"timestamp"` } // FactEntry 事实记忆条目(持久事实) // Logan.Li type FactEntry struct { Fact string `json:"fact"` Source string `json:"source"` Timestamp int64 `json:"timestamp"` } // SkillEntry 程序记忆条目(运维技能/流程) // Logan.Li type SkillEntry struct { Name string `json:"name"` When string `json:"when"` Steps []string `json:"steps"` Timestamp int64 `json:"timestamp"` } // Memories 四层记忆集合(线程安全) // Logan.Li type Memories struct { dir string mu sync.Mutex expPath string // experience.jsonl facPath string // factual.jsonl sklPath string // procedural.jsonl envPath string // environment.json } // NewMemories 创建四层记忆(dir 为 data/agentcli) // Logan.Li func NewMemories(dir string) *Memories { return &Memories{ dir: dir, expPath: filepath.Join(dir, "experience.jsonl"), facPath: filepath.Join(dir, "factual.jsonl"), sklPath: filepath.Join(dir, "procedural.jsonl"), envPath: filepath.Join(dir, "environment.json"), } } // AddExperience 追加经验 // Logan.Li func (m *Memories) AddExperience(e ExperienceEntry) error { m.mu.Lock() defer m.mu.Unlock() if e.Timestamp == 0 { e.Timestamp = time.Now().Unix() } return appendJSONL(m.expPath, e) } // FindExperiences 按 task_type 查找最近的经验(最多 n 条,优先成功) // Logan.Li func (m *Memories) FindExperiences(taskType string, n int) []ExperienceEntry { all := readJSONL[ExperienceEntry](m.expPath) var matched []ExperienceEntry for _, e := range all { if taskType == "" || e.TaskType == taskType { matched = append(matched, e) } } // 优先成功经验,按时间倒序取前 n successFirst := make([]ExperienceEntry, 0, len(matched)) others := make([]ExperienceEntry, 0, len(matched)) for _, e := range matched { if e.Success { successFirst = append(successFirst, e) } else { others = append(others, e) } } // 简单逆序(文件本身就是时间顺序,取末尾最新) pick := func(s []ExperienceEntry) []ExperienceEntry { if len(s) <= n { return s } return s[len(s)-n:] } out := append(pick(successFirst), pick(others)...) if len(out) > n { out = out[len(out)-n:] } return out } // AddFact 追加事实 // Logan.Li func (m *Memories) AddFact(f FactEntry) error { m.mu.Lock() defer m.mu.Unlock() if f.Timestamp == 0 { f.Timestamp = time.Now().Unix() } return appendJSONL(m.facPath, f) } // RelevantFacts 返回与 query 关键词相关的事实(最多 n 条) // Logan.Li func (m *Memories) RelevantFacts(query string, n int) []FactEntry { all := readJSONL[FactEntry](m.facPath) if len(all) == 0 { return nil } kw := strings.ToLower(query) var out []FactEntry for _, f := range all { if strings.Contains(strings.ToLower(f.Fact), kw) || strings.Contains(kw, strings.ToLower(f.Fact)) { out = append(out, f) } } if len(out) > n { out = out[len(out)-n:] } return out } // AddSkill 追加技能 // Logan.Li func (m *Memories) AddSkill(s SkillEntry) error { m.mu.Lock() defer m.mu.Unlock() if s.Timestamp == 0 { s.Timestamp = time.Now().Unix() } return appendJSONL(m.sklPath, s) } // KnownHosts 已知主机(环境记忆) // Logan.Li type KnownHosts struct { Hosts []string `json:"hosts"` } // AddHost 记录已知主机(去重) // Logan.Li func (m *Memories) AddHost(host string) error { if host == "" { return nil } m.mu.Lock() defer m.mu.Unlock() env := readJSON(m.envPath, KnownHosts{}) for _, h := range env.Hosts { if h == host { return nil } } env.Hosts = append(env.Hosts, host) return writeJSON(m.envPath, env) } // Hosts 返回已知主机列表 // Logan.Li func (m *Memories) Hosts() []string { return readJSON(m.envPath, KnownHosts{}).Hosts } // CountStats 统计各层记忆条目数 // Logan.Li func (m *Memories) CountStats() map[string]int { return map[string]int{ "experience": len(readJSONL[ExperienceEntry](m.expPath)), "factual": len(readJSONL[FactEntry](m.facPath)), "skill": len(readJSONL[SkillEntry](m.sklPath)), "hosts": len(readJSON(m.envPath, KnownHosts{}).Hosts), } }