agent-cli
This commit is contained in:
1 parent
e0d652d211
commit
0690285147
4 files changed
+192
-51
No files matched your search
@@ -36,21 +36,24 @@ func NewAgentCLIHandler(db *gorm.DB) *AgentCLIHandler { return &AgentCLIHandler{
|
||||
|
||||
// wsEvent WebSocket 事件信封(后端→前端) // Logan.Li
|
||||
type wsEvent struct {
|
||||
Type string `json:"type"`
|
||||
Round int `json:"round,omitempty"`
|
||||
SessionID uint `json:"session_id,omitempty"`
|
||||
Thought string `json:"thought,omitempty"`
|
||||
ToolName string `json:"tool_name,omitempty"`
|
||||
ToolArgs map[string]interface{} `json:"tool_args,omitempty"`
|
||||
Risk string `json:"risk,omitempty"`
|
||||
Output string `json:"output,omitempty"`
|
||||
Success bool `json:"success,omitempty"`
|
||||
Duration string `json:"duration,omitempty"`
|
||||
Tokens int `json:"tokens,omitempty"`
|
||||
Answer string `json:"answer,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
ConfirmID string `json:"confirm_id,omitempty"`
|
||||
Command string `json:"command,omitempty"`
|
||||
Type string `json:"type"`
|
||||
Phase string `json:"phase,omitempty"`
|
||||
Summary string `json:"summary,omitempty"`
|
||||
Round int `json:"round,omitempty"`
|
||||
SessionID uint `json:"session_id,omitempty"`
|
||||
Thought string `json:"thought,omitempty"`
|
||||
ToolName string `json:"tool_name,omitempty"`
|
||||
ToolArgs map[string]interface{} `json:"tool_args,omitempty"`
|
||||
Risk string `json:"risk,omitempty"`
|
||||
Output string `json:"output,omitempty"`
|
||||
Success bool `json:"success,omitempty"`
|
||||
Duration string `json:"duration,omitempty"`
|
||||
OutputSize int `json:"output_size,omitempty"`
|
||||
Tokens int `json:"tokens,omitempty"`
|
||||
Answer string `json:"answer,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
ConfirmID string `json:"confirm_id,omitempty"`
|
||||
Command string `json:"command,omitempty"`
|
||||
}
|
||||
|
||||
// wsControl 前端→后端控制消息 // Logan.Li
|
||||
@@ -456,9 +459,10 @@ func (ac *agentConn) onEvent(e core.Event) {
|
||||
}
|
||||
|
||||
ev := wsEvent{
|
||||
Type: string(e.Type), Round: e.Round, Thought: e.Thought,
|
||||
Type: string(e.Type), Phase: e.Phase, Summary: e.Summary,
|
||||
Round: e.Round, Thought: e.Thought,
|
||||
ToolName: e.ToolName, ToolArgs: e.ToolArgs, Risk: e.Risk,
|
||||
Output: e.Output, Success: e.Success, Duration: e.Duration,
|
||||
Output: e.Output, Success: e.Success, Duration: e.Duration, OutputSize: e.OutputSize,
|
||||
Tokens: e.Tokens, Answer: e.Answer, Message: e.Message,
|
||||
}
|
||||
if ac.session != nil {
|
||||
@@ -472,6 +476,9 @@ func (ac *agentConn) onEvent(e core.Event) {
|
||||
}
|
||||
sid := ac.session.ID
|
||||
switch e.Type {
|
||||
case core.EventStatus:
|
||||
// status 事件不改 DB schema:duration 字段在 type=status 时复用为 phase。
|
||||
ac.persistMsg(sid, e.Round, "status", "system", e.Summary, e.ToolName, "", "", e.Risk, e.Phase != core.PhaseError && e.Phase != core.PhaseBlocked, e.Phase, e.Tokens)
|
||||
case core.EventThought:
|
||||
ac.persistMsg(sid, e.Round, "thought", "assistant", e.Thought, "", "", "", "", false, "", 0)
|
||||
case core.EventToolCall:
|
||||
@@ -503,7 +510,7 @@ func (ac *agentConn) confirmFn(toolName string, args map[string]interface{}, ris
|
||||
if cmd == "" && (toolName == "kubernetes_action" || toolName == "k8s_action") {
|
||||
cmd = fmt.Sprintf("%v %v", args["action"], args["name"])
|
||||
}
|
||||
ac.write(wsEvent{Type: "confirm", ConfirmID: confirmID, ToolName: toolName, Command: cmd, Risk: risk.Display(), ToolArgs: args})
|
||||
ac.write(wsEvent{Type: "confirm", Phase: core.PhaseApproval, Summary: "等待用户确认", ConfirmID: confirmID, ToolName: toolName, Command: cmd, Risk: risk.Display(), ToolArgs: args})
|
||||
|
||||
ch := make(chan bool, 1)
|
||||
ac.cfMu.Lock()
|
||||
|
||||
@@ -23,29 +23,49 @@ import (
|
||||
type EventType string
|
||||
|
||||
const (
|
||||
EventStatus EventType = "status" // 生命周期状态(accepted/context/thinking/...)
|
||||
EventThought EventType = "thought" // AI 思考/推理文本(流式结束后的完整内容)
|
||||
EventThoughtDelta EventType = "thought_delta" // AI 思考流式增量 token
|
||||
EventToolCall EventType = "tool_call" // 即将执行的工具(名/参数/风险)
|
||||
EventToolResult EventType = "tool_result" // 工具执行结果(输出/成败/耗时)
|
||||
EventProgress EventType = "progress" // 轮次进度 + token
|
||||
EventDone EventType = "done" // 任务完成(最终答案)
|
||||
EventError EventType = "error" // 错误(含达到最大轮次)
|
||||
EventToolResult EventType = "tool_result" // 工具执行结果(输出/成败/耗时)
|
||||
EventProgress EventType = "progress" // 轮次进度 + token
|
||||
EventDone EventType = "done" // 任务完成(最终答案)
|
||||
EventError EventType = "error" // 错误(含达到最大轮次)
|
||||
)
|
||||
|
||||
const (
|
||||
PhaseAccepted = "accepted"
|
||||
PhaseContext = "context"
|
||||
PhaseThinking = "thinking"
|
||||
PhasePlanning = "planning"
|
||||
PhaseSafety = "safety_check"
|
||||
PhaseApproval = "approval_required"
|
||||
PhaseToolRunning = "tool_running"
|
||||
PhaseObserving = "observing"
|
||||
PhaseValidating = "validating"
|
||||
PhaseSummarizing = "summarizing"
|
||||
PhaseDone = "done"
|
||||
PhaseBlocked = "blocked"
|
||||
PhaseError = "error"
|
||||
)
|
||||
|
||||
// Event 引擎产生的事件(controller 序列化后通过 WebSocket 推送) // Logan.Li
|
||||
type Event struct {
|
||||
Type EventType `json:"type"`
|
||||
Round int `json:"round,omitempty"`
|
||||
Thought string `json:"thought,omitempty"`
|
||||
ToolName string `json:"tool_name,omitempty"`
|
||||
ToolArgs map[string]interface{} `json:"tool_args,omitempty"`
|
||||
Risk string `json:"risk,omitempty"`
|
||||
Output string `json:"output,omitempty"`
|
||||
Success bool `json:"success,omitempty"`
|
||||
Duration string `json:"duration,omitempty"`
|
||||
Tokens int `json:"tokens,omitempty"`
|
||||
Answer string `json:"answer,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Type EventType `json:"type"`
|
||||
Phase string `json:"phase,omitempty"`
|
||||
Summary string `json:"summary,omitempty"`
|
||||
Round int `json:"round,omitempty"`
|
||||
Thought string `json:"thought,omitempty"`
|
||||
ToolName string `json:"tool_name,omitempty"`
|
||||
ToolArgs map[string]interface{} `json:"tool_args,omitempty"`
|
||||
Risk string `json:"risk,omitempty"`
|
||||
Output string `json:"output,omitempty"`
|
||||
Success bool `json:"success,omitempty"`
|
||||
Duration string `json:"duration,omitempty"`
|
||||
OutputSize int `json:"output_size,omitempty"`
|
||||
Tokens int `json:"tokens,omitempty"`
|
||||
Answer string `json:"answer,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
// EventCallback 事件回调(controller 注入) // Logan.Li
|
||||
@@ -127,6 +147,7 @@ func (a *Agent) emit(e Event) {
|
||||
func (a *Agent) Run(ctx context.Context, query, serverInfo string, prior []llm.Message, priorTokens int) (*RunResult, error) {
|
||||
a.totalTokens = priorTokens
|
||||
start := time.Now()
|
||||
a.emit(Event{Type: EventStatus, Phase: PhaseAccepted, Summary: "任务已接收"})
|
||||
|
||||
// Evolver PREDICT:获取历史经验注入 system prompt
|
||||
evolveCtx := ""
|
||||
@@ -137,6 +158,7 @@ func (a *Agent) Run(ctx context.Context, query, serverInfo string, prior []llm.M
|
||||
// 工具描述
|
||||
toolDescs := a.buildToolDescriptions()
|
||||
systemPrompt := prompt.BuildSystemPrompt(a.config.Mode, serverInfo, toolDescs, evolveCtx)
|
||||
a.emit(Event{Type: EventStatus, Phase: PhaseContext, Summary: fmt.Sprintf("mode=%s tools=%d prior_messages=%d", a.config.Mode, len(a.registry.List()), len(prior))})
|
||||
|
||||
messages := []llm.Message{{Role: "system", Content: systemPrompt}}
|
||||
if len(prior) > 0 {
|
||||
@@ -168,6 +190,7 @@ func (a *Agent) Run(ctx context.Context, query, serverInfo string, prior []llm.M
|
||||
|
||||
// ReAct 须用 Complete:Stream 无法组装 tool_calls/tool_use(GLM/Claude 等会只回文字不执行) // Logan.Li
|
||||
round := a.roundNum(iter)
|
||||
a.emit(Event{Type: EventStatus, Phase: PhaseThinking, Round: round, Summary: "LLM 推理中"})
|
||||
resp, err := a.llm.Complete(ctx, &llm.CompletionRequest{
|
||||
Messages: trimmed,
|
||||
Tools: llmTools,
|
||||
@@ -175,7 +198,7 @@ func (a *Agent) Run(ctx context.Context, query, serverInfo string, prior []llm.M
|
||||
MaxTokens: a.config.MaxTokens,
|
||||
})
|
||||
if err != nil {
|
||||
a.emit(Event{Type: EventError, Message: "LLM 调用失败: " + err.Error()})
|
||||
a.emit(Event{Type: EventError, Phase: PhaseError, Message: "LLM 调用失败: " + err.Error()})
|
||||
return nil, fmt.Errorf("LLM调用失败: %w", err)
|
||||
}
|
||||
|
||||
@@ -208,7 +231,8 @@ func (a *Agent) Run(ctx context.Context, query, serverInfo string, prior []llm.M
|
||||
answer = "任务已完成。"
|
||||
}
|
||||
}
|
||||
a.emit(Event{Type: EventDone, Answer: answer, Tokens: a.totalTokens})
|
||||
a.emit(Event{Type: EventStatus, Phase: PhaseSummarizing, Round: round, Summary: "汇总最终答案"})
|
||||
a.emit(Event{Type: EventDone, Phase: PhaseDone, Answer: answer, Tokens: a.totalTokens})
|
||||
a.triggerEvolve(ctx, query, serverInfo, answer, toolRecords, true, start)
|
||||
return &RunResult{
|
||||
Success: true, Output: answer,
|
||||
@@ -218,7 +242,7 @@ func (a *Agent) Run(ctx context.Context, query, serverInfo string, prior []llm.M
|
||||
|
||||
// 中间轮思考(后续还有 tool_calls,与终局 done 分开,避免重复输出) // Logan.Li
|
||||
if thought := strings.TrimSpace(resp.Message.Content); thought != "" {
|
||||
a.emit(Event{Type: EventThought, Round: round, Thought: thought})
|
||||
a.emit(Event{Type: EventThought, Phase: PhasePlanning, Round: round, Thought: thought})
|
||||
}
|
||||
|
||||
// 处理工具调用
|
||||
@@ -231,7 +255,7 @@ func (a *Agent) Run(ctx context.Context, query, serverInfo string, prior []llm.M
|
||||
|
||||
// 达到最大轮次
|
||||
msg := fmt.Sprintf("达到最大迭代次数(%d),任务未能完成。可能任务过于复杂或需要更多步骤。", a.config.MaxIterations)
|
||||
a.emit(Event{Type: EventError, Message: msg, Tokens: a.totalTokens})
|
||||
a.emit(Event{Type: EventError, Phase: PhaseError, Message: msg, Tokens: a.totalTokens})
|
||||
a.triggerEvolve(ctx, query, serverInfo, "", toolRecords, false, start)
|
||||
return &RunResult{
|
||||
Success: false, History: a.extractHistory(messages), TotalTokens: a.totalTokens,
|
||||
@@ -270,11 +294,12 @@ func (a *Agent) processToolCalls(ctx context.Context, calls []llm.ToolCall, hist
|
||||
}
|
||||
|
||||
risk := a.toolRisk(tool, args)
|
||||
a.emit(Event{Type: EventToolCall, Round: round, ToolName: tc.Function.Name, ToolArgs: args, Risk: risk.Display()})
|
||||
a.emit(Event{Type: EventStatus, Phase: PhaseSafety, Round: round, ToolName: tc.Function.Name, ToolArgs: args, Risk: risk.Display(), Summary: "安全检查"})
|
||||
|
||||
// 安全检查(高危触发 confirmFn,由 controller 异步确认)
|
||||
approved, err := a.safetyCtl.Check(tool, args)
|
||||
if err != nil {
|
||||
a.emit(Event{Type: EventStatus, Phase: PhaseError, Round: round, ToolName: tc.Function.Name, ToolArgs: args, Risk: risk.Display(), Message: err.Error()})
|
||||
obs = append(obs, llm.Message{Role: "tool", ToolCallID: tc.ID, Name: tc.Function.Name,
|
||||
Content: "安全检查失败: " + err.Error()})
|
||||
continue
|
||||
@@ -282,12 +307,14 @@ func (a *Agent) processToolCalls(ctx context.Context, calls []llm.ToolCall, hist
|
||||
if !approved {
|
||||
obs = append(obs, llm.Message{Role: "tool", ToolCallID: tc.ID, Name: tc.Function.Name,
|
||||
Content: "操作已被用户拒绝执行"})
|
||||
a.emit(Event{Type: EventStatus, Phase: PhaseBlocked, Round: round, ToolName: tc.Function.Name, ToolArgs: args, Risk: risk.Display(), Summary: "用户拒绝执行"})
|
||||
a.emit(Event{Type: EventToolResult, Round: round, ToolName: tc.Function.Name,
|
||||
Output: "(用户拒绝执行)", Success: false})
|
||||
Phase: PhaseObserving, Output: "(用户拒绝执行)", Success: false, OutputSize: len("(用户拒绝执行)")})
|
||||
continue
|
||||
}
|
||||
|
||||
// 执行
|
||||
a.emit(Event{Type: EventToolCall, Phase: PhaseToolRunning, Round: round, ToolName: tc.Function.Name, ToolArgs: args, Risk: risk.Display(), Summary: "工具执行中"})
|
||||
toolStart := time.Now()
|
||||
toolCtx, cancel := context.WithTimeout(ctx, a.config.ToolTimeout)
|
||||
result, execErr := tool.Execute(toolCtx, args)
|
||||
@@ -317,8 +344,8 @@ func (a *Agent) processToolCalls(ctx context.Context, calls []llm.ToolCall, hist
|
||||
fmt.Sprintf("\n[输出已截断,原始 %d 字符]", len(output))
|
||||
}
|
||||
|
||||
a.emit(Event{Type: EventToolResult, Round: round, ToolName: tc.Function.Name,
|
||||
Output: output, Success: success, Duration: dur.Truncate(time.Millisecond).String()})
|
||||
a.emit(Event{Type: EventToolResult, Phase: PhaseObserving, Round: round, ToolName: tc.Function.Name,
|
||||
Output: output, Success: success, Duration: dur.Truncate(time.Millisecond).String(), OutputSize: len(output)})
|
||||
|
||||
records = append(records, evolver.ToolCallRecord{
|
||||
ToolName: tc.Function.Name, Args: args, Output: output,
|
||||
|
||||
Reference in new issue
Block a user