// engine.go - 自进化引擎(Evolver)核心 // Logan.Li // 保留 opsxcli 13 步进化循环的思想,精简为核心步骤: // OBSERVE → CLASSIFY → LEARN → STORE → MEMORIZE → PREDICT(注入下次 Prompt)。 // 异步执行、失败优雅降级(LLM 不可用退化为规则生成),绝不影响主 ReAct 循环。 package evolver import ( "context" "fmt" "strings" "time" "ezops/pkg/agentcli/llm" ) // ToolCallRecord 工具调用记录(供 Evolver 观察学习) // Logan.Li type ToolCallRecord struct { ToolName string `json:"tool_name"` Args map[string]interface{} `json:"args"` Output string `json:"output"` Duration time.Duration `json:"duration"` Success bool `json:"success"` RiskLevel int `json:"risk_level"` } // TaskExecution 一次任务的完整执行记录 // Logan.Li type TaskExecution struct { Query string `json:"query"` ToolCalls []ToolCallRecord `json:"tool_calls"` TotalSteps int `json:"total_steps"` TotalTokens int `json:"total_tokens"` Duration time.Duration `json:"duration"` Success bool `json:"success"` FinalAnswer string `json:"final_answer"` Timestamp time.Time `json:"timestamp"` ServerHost string `json:"server_host"` // 用于环境记忆 } // EvolveResult 进化结果(会话级回灌 Prompt) // Logan.Li type EvolveResult struct { TaskType string `json:"task_type"` LearnedHint string `json:"learned_hint"` ToolSequence []string `json:"tool_sequence"` ExperienceAdded bool `json:"experience_added"` SkillAdded bool `json:"skill_added"` Consolidated bool `json:"consolidated"` } // EvolverEngine 自进化引擎 // Logan.Li type EvolverEngine struct { mem *Memories llm llm.Client // 可选,用于提炼经验/事实 memDir string } // NewEvolverEngine 创建进化引擎(llm 可为 nil,退化为规则学习) // Logan.Li func NewEvolverEngine(memDir string, client llm.Client) (*EvolverEngine, error) { if memDir == "" { memDir = "data/agentcli" } if err := ensureDir(memDir); err != nil { return nil, fmt.Errorf("初始化记忆目录失败: %w", err) } return &EvolverEngine{ mem: NewMemories(memDir), llm: client, memDir: memDir, }, nil } // Evolve 执行一次进化(观察任务 → 学习经验 → 存储) // Logan.Li // 设计为幂等、可降级:任何子步骤失败都不返回 error 给调用方(仅影响学习质量)。 func (e *EvolverEngine) Evolve(ctx context.Context, exec *TaskExecution) *EvolveResult { if exec == nil { return nil } result := &EvolveResult{} // Step 1: OBSERVE —— 提取工具序列 for _, tc := range exec.ToolCalls { result.ToolSequence = append(result.ToolSequence, tc.ToolName) } // Step 2: CLASSIFY —— 任务类型分类(关键词规则,无需 LLM) result.TaskType = classifyTask(exec.Query, exec.ToolCalls) // Step 3: LEARN —— 生成经验 hint(优先 LLM,降级规则) result.LearnedHint = e.learnHint(ctx, exec, result.TaskType) // Step 4: STORE —— 存入经验记忆 + 环境记忆(主机) exp := ExperienceEntry{ TaskType: result.TaskType, Query: truncate(exec.Query, 200), Hint: result.LearnedHint, ToolSequence: result.ToolSequence, Success: exec.Success, Timestamp: exec.Timestamp.Unix(), } if err := e.mem.AddExperience(exp); err == nil { result.ExperienceAdded = true } if exec.ServerHost != "" { _ = e.mem.AddHost(exec.ServerHost) // 失败忽略 } // Step 5: MEMORIZE —— LLM 提炼持久事实(可选,失败降级) if e.llm != nil && exec.Success && exec.FinalAnswer != "" { go e.memorizeFact(ctx, exec) // 异步,不阻塞进化主流程 } // Step 6: PROCEDURAL —— 成功任务沉淀程序记忆(工具命令序列) // Logan.Li if exec.Success { if steps := extractSSHSteps(exec.ToolCalls); len(steps) >= 2 { skill := SkillEntry{ Name: result.TaskType + "标准流程", When: result.TaskType, Steps: steps, Timestamp: time.Now().Unix(), } if err := e.mem.AddSkill(skill); err == nil { result.SkillAdded = true } } } return result } // learnHint 生成经验提示(LLM 优先,降级规则) // Logan.Li func (e *EvolverEngine) learnHint(ctx context.Context, exec *TaskExecution, taskType string) string { tools := strings.Join(uniqueStrings(resultToolNames(exec.ToolCalls)), " → ") // 尝试用 LLM 提炼 if e.llm != nil { if hint := e.llmLearnHint(ctx, exec, taskType, tools, exec.Success); hint != "" { return hint } } // 降级:规则生成 if exec.Success { return fmt.Sprintf("完成「%s」类任务时,工具序列 %s 最终成功,可作为参考。", taskType, tools) } return fmt.Sprintf("尝试「%s」类任务时,工具序列 %s 未成功,下次可换用不同命令或思路。", taskType, tools) } // llmLearnHint 调用 LLM 提炼一句经验 // Logan.Li func (e *EvolverEngine) llmLearnHint(ctx context.Context, exec *TaskExecution, taskType, tools string, success bool) string { result := "失败" if success { result = "成功" } prompt := fmt.Sprintf( "你是运维经验提炼器。根据以下任务执行记录,用一句中文(不超过80字)提炼经验提示,"+ "说明下次做同类任务的关键要点或推荐工具顺序。只返回经验文本,不要多余解释。\n"+ "任务: %s\n工具序列: %s\n结果: %s", truncate(exec.Query, 150), tools, result) resp, err := e.llm.Complete(ctx, &llm.CompletionRequest{ Messages: []llm.Message{{Role: "user", Content: prompt}}, }) if err != nil || resp == nil { return "" } return strings.TrimSpace(truncate(resp.Message.Content, 120)) } // memorizeFact 异步提取持久事实存入事实记忆 // Logan.Li func (e *EvolverEngine) memorizeFact(ctx context.Context, exec *TaskExecution) { defer func() { _ = recover() }() prompt := fmt.Sprintf( "从以下运维任务记录中,提炼 1 条值得长期记住的事实(如某主机的固定配置、已知问题、约定)。"+ "如果没有明显可沉淀的事实,回复「无」。只返回事实本身,一句话。\n任务: %s\n结论: %s", truncate(exec.Query, 150), truncate(exec.FinalAnswer, 300)) ctx2, cancel := context.WithTimeout(ctx, 20*time.Second) defer cancel() resp, err := e.llm.Complete(ctx2, &llm.CompletionRequest{ Messages: []llm.Message{{Role: "user", Content: prompt}}, }) if err != nil || resp == nil { return } fact := strings.TrimSpace(resp.Message.Content) if fact == "" || strings.HasPrefix(fact, "无") { return } _ = e.mem.AddFact(FactEntry{Fact: truncate(fact, 150), Source: truncate(exec.Query, 80)}) } // GetContextForPrompt 为下一次任务构建记忆上下文(注入 system prompt) // Logan.Li func (e *EvolverEngine) GetContextForPrompt(query string) string { taskType := classifyTask(query, nil) var sb strings.Builder if exps := e.mem.FindExperiences(taskType, 3); len(exps) > 0 { sb.WriteString(fmt.Sprintf(" 【%s 类历史经验】\n", taskType)) for _, exp := range exps { mark := "成功" if !exp.Success { mark = "未成功" } sb.WriteString(fmt.Sprintf(" - (%s) %s\n", mark, exp.Hint)) } } if facts := e.mem.RelevantFacts(query, 3); len(facts) > 0 { sb.WriteString(" 【已知事实】\n") for _, f := range facts { sb.WriteString(fmt.Sprintf(" - %s\n", f.Fact)) } } if hosts := e.mem.Hosts(); len(hosts) > 0 { sb.WriteString(fmt.Sprintf(" 【已知主机】%s\n", strings.Join(hosts, ", "))) } if skills := e.mem.FindSkills(taskType, 2); len(skills) > 0 { sb.WriteString(" 【推荐流程】\n") for _, sk := range skills { sb.WriteString(fmt.Sprintf(" - %s: %s\n", sk.Name, strings.Join(sk.Steps, " → "))) } } return sb.String() } // GetStats 进化统计(供审计/展示) // Logan.Li func (e *EvolverEngine) GetStats() map[string]interface{} { return map[string]interface{}{ "enabled": true, "memory": e.mem.CountStats(), } } // ---- 任务分类(关键词规则) ---- // Logan.Li // classifyTask 按 query + 工具调用关键词分类任务类型 // Logan.Li func classifyTask(query string, calls []ToolCallRecord) string { text := strings.ToLower(query) for _, c := range calls { if cmd, ok := c.Args["command"].(string); ok { text += " " + strings.ToLower(cmd) } } rules := []struct { typ string kws []string }{ {"磁盘空间", []string{"disk", "空间", "df", "du", "inode", "磁盘", "满了"}}, {"网络", []string{"network", "网络", "ping", "netstat", "ss ", "端口", "连接", "tcp", "dns"}}, {"服务管理", []string{"service", "服务", "systemctl", "nginx", "mysql", "redis", "restart", "启动", "停止"}}, {"软件安装", []string{"install", "安装", "yum", "apt", "docker", "部署", "deploy", "pip", "npm"}}, {"进程排查", []string{"process", "进程", "ps ", "kill", "cpu", "top", "卡死", "hang"}}, {"内存排查", []string{"memory", "内存", "mem", "free", "oom", "swap"}}, {"日志排查", []string{"log", "日志", "journalctl", "tail", "grep"}}, {"权限问题", []string{"permission", "权限", "chmod", "chown", "denied", "拒绝", "forbidden"}}, {"性能调优", []string{"performance", "性能", "负载", "load", "慢", "slow", "latency", "延迟"}}, {"配置管理", []string{"config", "配置", "conf", "vim", "sed"}}, } for _, r := range rules { for _, kw := range r.kws { if strings.Contains(text, kw) { return r.typ } } } return "通用运维" } // ---- 辅助 ---- // Logan.Li func truncate(s string, n int) string { r := []rune(s) if len(r) <= n { return s } return string(r[:n]) + "..." } func resultToolNames(calls []ToolCallRecord) []string { out := make([]string, 0, len(calls)) for _, c := range calls { out = append(out, c.ToolName) } return out } // extractSSHSteps 从工具调用记录提取 ssh_execute 命令序列 // Logan.Li func extractSSHSteps(calls []ToolCallRecord) []string { out := make([]string, 0, len(calls)) for _, c := range calls { if c.ToolName != "ssh_execute" { continue } if cmd, ok := c.Args["command"].(string); ok && strings.TrimSpace(cmd) != "" { out = append(out, strings.TrimSpace(cmd)) } } return out } func uniqueStrings(in []string) []string { seen := make(map[string]bool) out := make([]string, 0, len(in)) for _, s := range in { if !seen[s] { seen[s] = true out = append(out, s) } } return out }