Add files via upload

This commit is contained in:
公明
2026-04-19 03:01:30 +08:00
committed by GitHub
parent ef3de9e950
commit b8a0f40017
11 changed files with 352 additions and 145 deletions
+39 -10
View File
@@ -177,6 +177,8 @@ type ChatRequest struct {
Role string `json:"role,omitempty"` // 角色名称
Attachments []ChatAttachment `json:"attachments,omitempty"`
WebShellConnectionID string `json:"webshellConnectionId,omitempty"` // WebShell 管理 - AI 助手:当前选中的连接 ID,仅使用 webshell_* 工具
// Orchestration 仅对 /api/multi-agent、/api/multi-agent/streamdeep | plan_execute | supervisor;空则等同 deep。机器人/批量等无请求体时由服务端默认 deep。
Orchestration string `json:"orchestration,omitempty"`
}
const (
@@ -673,6 +675,7 @@ func (h *AgentHandler) ProcessMessageForRobot(ctx context.Context, conversationI
roleTools,
progressCallback,
h.agentsMarkdownDir,
"deep",
)
if errMA != nil {
errMsg := "执行失败: " + errMA.Error()
@@ -1616,17 +1619,34 @@ type BatchTaskRequest struct {
Title string `json:"title"` // 任务标题(可选)
Tasks []string `json:"tasks" binding:"required"` // 任务列表,每行一个任务
Role string `json:"role,omitempty"` // 角色名称(可选,空字符串表示默认角色)
AgentMode string `json:"agentMode,omitempty"` // single | multi
AgentMode string `json:"agentMode,omitempty"` // single | deep | plan_execute | supervisor(旧版 multi 视为 deep
ScheduleMode string `json:"scheduleMode,omitempty"` // manual | cron
CronExpr string `json:"cronExpr,omitempty"` // scheduleMode=cron 时必填
ExecuteNow bool `json:"executeNow,omitempty"` // 创建后是否立即执行(默认 false)
}
func normalizeBatchQueueAgentMode(mode string) string {
if strings.TrimSpace(mode) == "multi" {
return "multi"
m := strings.TrimSpace(strings.ToLower(mode))
if m == "multi" {
return "deep"
}
return "single"
if m == "" || m == "single" {
return "single"
}
switch config.NormalizeMultiAgentOrchestration(m) {
case "plan_execute":
return "plan_execute"
case "supervisor":
return "supervisor"
default:
return "deep"
}
}
// batchQueueWantsEino 队列是否配置为走 Eino 多代理(不含「空 agentMode + 仅 BatchUseMultiAgent」这种运行期推断)。
func batchQueueWantsEino(agentMode string) bool {
m := strings.TrimSpace(strings.ToLower(agentMode))
return m == "multi" || m == "deep" || m == "plan_execute" || m == "supervisor"
}
func normalizeBatchQueueScheduleMode(mode string) string {
@@ -2093,9 +2113,9 @@ func (h *AgentHandler) startBatchQueueExecution(queueID string, scheduled bool)
return true, fmt.Errorf("队列状态不允许启动")
}
if queue != nil && queue.AgentMode == "multi" && (h.config == nil || !h.config.MultiAgent.Enabled) {
if queue != nil && batchQueueWantsEino(queue.AgentMode) && (h.config == nil || !h.config.MultiAgent.Enabled) {
h.unmarkBatchQueueRunning(queueID)
err := fmt.Errorf("当前队列配置为多代理,但系统未启用多代理")
err := fmt.Errorf("当前队列配置为 Eino 多代理,但系统未启用多代理")
if scheduled {
h.batchTaskManager.SetLastScheduleError(queueID, err.Error())
}
@@ -2252,17 +2272,26 @@ func (h *AgentHandler) executeBatchQueue(queueID string) {
// 使用队列配置的角色工具列表(如果为空,表示使用所有工具)
// 注意:skills不会硬编码注入,但会在系统提示词中提示AI这个角色推荐使用哪些skills
useBatchMulti := false
if queue.AgentMode == "multi" {
useBatchMulti = h.config != nil && h.config.MultiAgent.Enabled
batchOrch := "deep"
am := strings.TrimSpace(strings.ToLower(queue.AgentMode))
if am == "multi" {
am = "deep"
}
if batchQueueWantsEino(queue.AgentMode) && h.config != nil && h.config.MultiAgent.Enabled {
useBatchMulti = true
batchOrch = config.NormalizeMultiAgentOrchestration(am)
} else if queue.AgentMode == "" {
// 兼容历史数据:未配置队列代理模式时,沿用旧的系统级开关
useBatchMulti = h.config != nil && h.config.MultiAgent.Enabled && h.config.MultiAgent.BatchUseMultiAgent
if h.config != nil && h.config.MultiAgent.Enabled && h.config.MultiAgent.BatchUseMultiAgent {
useBatchMulti = true
batchOrch = "deep"
}
}
var result *agent.AgentLoopResult
var resultMA *multiagent.RunResult
var runErr error
if useBatchMulti {
resultMA, runErr = multiagent.RunDeepAgent(ctx, h.config, &h.config.MultiAgent, h.agent, h.logger, conversationID, finalMessage, []agent.ChatMessage{}, roleTools, progressCallback, h.agentsMarkdownDir)
resultMA, runErr = multiagent.RunDeepAgent(ctx, h.config, &h.config.MultiAgent, h.agent, h.logger, conversationID, finalMessage, []agent.ChatMessage{}, roleTools, progressCallback, h.agentsMarkdownDir, batchOrch)
} else {
result, runErr = h.agent.AgentLoopWithProgress(ctx, finalMessage, []agent.ChatMessage{}, conversationID, progressCallback, roleTools, roleSkills)
}
+1 -1
View File
@@ -57,7 +57,7 @@ type BatchTaskQueue struct {
ID string `json:"id"`
Title string `json:"title,omitempty"`
Role string `json:"role,omitempty"` // 角色名称(空字符串表示默认角色)
AgentMode string `json:"agentMode"` // single | multi
AgentMode string `json:"agentMode"` // single | deep | plan_execute | supervisor
ScheduleMode string `json:"scheduleMode"` // manual | cron
CronExpr string `json:"cronExpr,omitempty"`
NextRunAt *time.Time `json:"nextRunAt,omitempty"`
+12 -5
View File
@@ -266,11 +266,13 @@ func (h *ConfigHandler) GetConfig(c *gin.Context) {
subAgentCount = len(agents.MergeYAMLAndMarkdown(h.config.MultiAgent.SubAgents, load.SubAgents))
}
multiPub := config.MultiAgentPublic{
Enabled: h.config.MultiAgent.Enabled,
DefaultMode: h.config.MultiAgent.DefaultMode,
RobotUseMultiAgent: h.config.MultiAgent.RobotUseMultiAgent,
BatchUseMultiAgent: h.config.MultiAgent.BatchUseMultiAgent,
SubAgentCount: subAgentCount,
Enabled: h.config.MultiAgent.Enabled,
DefaultMode: h.config.MultiAgent.DefaultMode,
RobotUseMultiAgent: h.config.MultiAgent.RobotUseMultiAgent,
BatchUseMultiAgent: h.config.MultiAgent.BatchUseMultiAgent,
SubAgentCount: subAgentCount,
Orchestration: config.NormalizeMultiAgentOrchestration(h.config.MultiAgent.Orchestration),
PlanExecuteLoopMaxIterations: h.config.MultiAgent.PlanExecuteLoopMaxIterations,
}
if strings.TrimSpace(multiPub.DefaultMode) == "" {
multiPub.DefaultMode = "single"
@@ -664,11 +666,15 @@ func (h *ConfigHandler) UpdateConfig(c *gin.Context) {
}
h.config.MultiAgent.RobotUseMultiAgent = req.MultiAgent.RobotUseMultiAgent
h.config.MultiAgent.BatchUseMultiAgent = req.MultiAgent.BatchUseMultiAgent
if req.MultiAgent.PlanExecuteLoopMaxIterations != nil {
h.config.MultiAgent.PlanExecuteLoopMaxIterations = *req.MultiAgent.PlanExecuteLoopMaxIterations
}
h.logger.Info("更新多代理配置",
zap.Bool("enabled", h.config.MultiAgent.Enabled),
zap.String("default_mode", h.config.MultiAgent.DefaultMode),
zap.Bool("robot_use_multi_agent", h.config.MultiAgent.RobotUseMultiAgent),
zap.Bool("batch_use_multi_agent", h.config.MultiAgent.BatchUseMultiAgent),
zap.Int("plan_execute_loop_max_iterations", h.config.MultiAgent.PlanExecuteLoopMaxIterations),
)
}
@@ -1341,6 +1347,7 @@ func updateMultiAgentConfig(doc *yaml.Node, cfg config.MultiAgentConfig) {
setStringInMap(maNode, "default_mode", cfg.DefaultMode)
setBoolInMap(maNode, "robot_use_multi_agent", cfg.RobotUseMultiAgent)
setBoolInMap(maNode, "batch_use_multi_agent", cfg.BatchUseMultiAgent)
setIntInMap(maNode, "plan_execute_loop_max_iterations", cfg.PlanExecuteLoopMaxIterations)
}
func ensureMap(parent *yaml.Node, path ...string) *yaml.Node {
+28 -10
View File
@@ -38,19 +38,32 @@ func (h *MarkdownAgentsHandler) safeJoin(filename string) (string, error) {
return filepath.Join(h.dir, clean), nil
}
// existingOtherOrchestrator 若目录中已有别的主代理文件,返回其文件名;writingBasename 为当前正在写入的文件名时视为同一文件不冲突。
// existingOtherOrchestrator 若目录中已有同槽位的其他主代理文件,返回其文件名;writingBasename 为当前正在写入的文件名时不冲突。
func existingOtherOrchestrator(dir, writingBasename string) (other string, err error) {
load, err := agents.LoadMarkdownAgentsDir(dir)
if err != nil {
return "", err
}
if load.Orchestrator == nil {
return "", nil
wb := filepath.Base(strings.TrimSpace(writingBasename))
switch agents.OrchestratorMarkdownKind(wb) {
case "plan_execute":
if load.OrchestratorPlanExecute != nil && !strings.EqualFold(load.OrchestratorPlanExecute.Filename, wb) {
return load.OrchestratorPlanExecute.Filename, nil
}
case "supervisor":
if load.OrchestratorSupervisor != nil && !strings.EqualFold(load.OrchestratorSupervisor.Filename, wb) {
return load.OrchestratorSupervisor.Filename, nil
}
case "deep":
if load.Orchestrator != nil && !strings.EqualFold(load.Orchestrator.Filename, wb) {
return load.Orchestrator.Filename, nil
}
default:
if load.Orchestrator != nil && !strings.EqualFold(load.Orchestrator.Filename, wb) {
return load.Orchestrator.Filename, nil
}
}
if strings.EqualFold(load.Orchestrator.Filename, writingBasename) {
return "", nil
}
return load.Orchestrator.Filename, nil
return "", nil
}
// ListMarkdownAgents GET /api/multi-agent/markdown-agents
@@ -101,7 +114,7 @@ func (h *MarkdownAgentsHandler) GetMarkdownAgent(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
isOrch := agents.IsOrchestratorMarkdown(filename, agents.FrontMatter{Kind: sub.Kind})
isOrch := agents.IsOrchestratorLikeMarkdown(filename, sub.Kind)
c.JSON(http.StatusOK, gin.H{
"filename": filename,
"raw": string(b),
@@ -172,7 +185,10 @@ func (h *MarkdownAgentsHandler) CreateMarkdownAgent(c *gin.Context) {
MaxIterations: body.MaxIterations,
Kind: strings.TrimSpace(body.Kind),
}
if strings.EqualFold(filepath.Base(path), agents.OrchestratorMarkdownFilename) && sub.Kind == "" {
base := filepath.Base(path)
if (strings.EqualFold(base, agents.OrchestratorMarkdownFilename) ||
strings.EqualFold(base, agents.OrchestratorPlanExecuteMarkdownFilename) ||
strings.EqualFold(base, agents.OrchestratorSupervisorMarkdownFilename)) && sub.Kind == "" {
sub.Kind = "orchestrator"
}
if sub.ID == "" {
@@ -237,7 +253,9 @@ func (h *MarkdownAgentsHandler) UpdateMarkdownAgent(c *gin.Context) {
MaxIterations: body.MaxIterations,
Kind: strings.TrimSpace(body.Kind),
}
if strings.EqualFold(filename, agents.OrchestratorMarkdownFilename) && sub.Kind == "" {
if (strings.EqualFold(filename, agents.OrchestratorMarkdownFilename) ||
strings.EqualFold(filename, agents.OrchestratorPlanExecuteMarkdownFilename) ||
strings.EqualFold(filename, agents.OrchestratorSupervisorMarkdownFilename)) && sub.Kind == "" {
sub.Kind = "orchestrator"
}
if sub.Name == "" {
+9 -2
View File
@@ -10,6 +10,7 @@ import (
"sync"
"time"
"cyberstrike-ai/internal/config"
"cyberstrike-ai/internal/multiagent"
"github.com/gin-gonic/gin"
@@ -139,7 +140,7 @@ func (h *AgentHandler) MultiAgentLoopStream(c *gin.Context) {
taskStatus := "completed"
defer h.tasks.FinishTask(conversationID, taskStatus)
sendEvent("progress", "正在启动 Eino DeepAgent...", map[string]interface{}{
sendEvent("progress", "正在启动 Eino 多代理...", map[string]interface{}{
"conversationId": conversationID,
})
@@ -159,6 +160,7 @@ func (h *AgentHandler) MultiAgentLoopStream(c *gin.Context) {
prep.RoleTools,
progressCallback,
h.agentsMarkdownDir,
strings.TrimSpace(req.Orchestration),
)
if runErr != nil {
@@ -215,11 +217,15 @@ func (h *AgentHandler) MultiAgentLoopStream(c *gin.Context) {
}
}
effectiveOrch := config.NormalizeMultiAgentOrchestration(h.config.MultiAgent.Orchestration)
if o := strings.TrimSpace(req.Orchestration); o != "" {
effectiveOrch = config.NormalizeMultiAgentOrchestration(o)
}
sendEvent("response", result.Response, map[string]interface{}{
"mcpExecutionIds": result.MCPExecutionIDs,
"conversationId": conversationID,
"messageId": assistantMessageID,
"agentMode": "eino_deep",
"agentMode": "eino_" + effectiveOrch,
})
sendEvent("done", "", map[string]interface{}{"conversationId": conversationID})
}
@@ -258,6 +264,7 @@ func (h *AgentHandler) MultiAgentLoop(c *gin.Context) {
prep.RoleTools,
nil,
h.agentsMarkdownDir,
strings.TrimSpace(req.Orchestration),
)
if runErr != nil {
h.logger.Error("Eino DeepAgent 执行失败", zap.Error(runErr))
+16 -6
View File
@@ -405,8 +405,8 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
},
"agentMode": map[string]interface{}{
"type": "string",
"description": "代理模式single | multi",
"enum": []string{"single", "multi"},
"description": "代理模式singleReAct| deep | plan_execute | supervisorEino);旧值 multi 按 deep",
"enum": []string{"single", "deep", "plan_execute", "supervisor", "multi"},
},
"scheduleMode": map[string]interface{}{
"type": "string",
@@ -1502,8 +1502,8 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
"/api/multi-agent": map[string]interface{}{
"post": map[string]interface{}{
"tags": []string{"对话交互"},
"summary": "发送消息并获取 AI 回复(Eino DeepAgent,非流式)",
"description": "与 `POST /api/agent-loop` 请求体相同,但由 **CloudWeGo Eino DeepAgent** 执行多代理编排。**前提**`multi_agent.enabled: true`(可在设置页或 `config.yaml` 开启);未启用时返回 404 JSON。请求体支持 `webshellConnectionId`(与单代理 WebShell 助手一致)。",
"summary": "发送消息并获取 AI 回复(Eino 多代理,非流式)",
"description": "与 `POST /api/agent-loop` 请求体相同,但由 **CloudWeGo Eino** 多代理执行。编排由请求体 `orchestration``deep` | `plan_execute` | `supervisor`)指定,缺省为 `deep`。**前提**`multi_agent.enabled: true`;未启用时返回 404 JSON。支持 `webshellConnectionId`。",
"operationId": "sendMessageMultiAgent",
"requestBody": map[string]interface{}{
"required": true,
@@ -1528,6 +1528,11 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
"type": "string",
"description": "WebShell 连接 ID(可选,与 agent-loop 行为一致)",
},
"orchestration": map[string]interface{}{
"type": "string",
"description": "Eino 预置编排:deep | plan_execute | supervisor;缺省 deep",
"enum": []string{"deep", "plan_execute", "supervisor"},
},
},
"required": []string{"message"},
},
@@ -1548,8 +1553,8 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
"/api/multi-agent/stream": map[string]interface{}{
"post": map[string]interface{}{
"tags": []string{"对话交互"},
"summary": "发送消息并获取 AI 回复(Eino DeepAgentSSE",
"description": "与 `POST /api/agent-loop/stream` 类似,事件类型兼容;由 Eino DeepAgent 执行。**前提**`multi_agent.enabled: true`路由常注册,未启用时仍返回 200 SSE,流内首条为 `type: error` 后接 `done`。支持 `webshellConnectionId`。",
"summary": "发送消息并获取 AI 回复(Eino 多代理SSE",
"description": "与 `POST /api/agent-loop/stream` 类似;由 Eino 多代理执行。`orchestration` 指定 deep / plan_execute / supervisor,缺省 deep。**前提**`multi_agent.enabled: true`未启用时 SSE 内首条为 `type: error` 后接 `done`。支持 `webshellConnectionId`。",
"operationId": "sendMessageMultiAgentStream",
"requestBody": map[string]interface{}{
"required": true,
@@ -1562,6 +1567,11 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
"conversationId": map[string]interface{}{"type": "string"},
"role": map[string]interface{}{"type": "string"},
"webshellConnectionId": map[string]interface{}{"type": "string"},
"orchestration": map[string]interface{}{
"type": "string",
"description": "deep | plan_execute | supervisor;缺省 deep",
"enum": []string{"deep", "plan_execute", "supervisor"},
},
},
"required": []string{"message"},
},