From d217e76a6f7ca8b99c964291d853f6d5ed7bc047 Mon Sep 17 00:00:00 2001 From: bai <1145000687@qq.com> Date: Thu, 12 Mar 2026 15:03:41 +0800 Subject: [PATCH 1/3] =?UTF-8?q?refactor(teams):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E5=9B=A2=E9=98=9F=E5=8D=8F=E4=BD=9C=E5=8A=9F=E8=83=BD=E5=B9=B6?= =?UTF-8?q?=E7=A7=BB=E9=99=A4=E7=8B=AC=E7=AB=8B=E7=9A=84=E5=91=BD=E5=90=8D?= =?UTF-8?q?=E5=BB=BA=E8=AE=AE=E5=B7=A5=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 从 AgentKernel 中移除 TeamNameSuggestionTool 的默认注册 - 将 TeamNameSuggestionTool 类合并到 AgentTeamsSkill 中作为内部功能 - 移除 AgentTeamsTools 内部工具集的单独实例化 - 更新文档说明,添加团队成员配置选项的详细说明 - 扩展 teammate 工具参数,支持 tools、disallowedTools、skills、mcpServers 等配置 - 添加 suggest_team_name 工具用于团队命名建议和现有团队分析 - 改进团队创建场景说明,支持智能团队名生成 --- .../org/noear/solon/bot/core/AgentKernel.java | 3 - .../solon/bot/core/teams/AgentTeamsSkill.java | 315 +++++++++++++++++- .../core/teams/TeamNameSuggestionTool.java | 202 ----------- 3 files changed, 297 insertions(+), 223 deletions(-) delete mode 100644 solonbot-sdk/src/main/java/org/noear/solon/bot/core/teams/TeamNameSuggestionTool.java diff --git a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/AgentKernel.java b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/AgentKernel.java index 4ca855e..cf56269 100644 --- a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/AgentKernel.java +++ b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/AgentKernel.java @@ -15,7 +15,6 @@ import org.noear.solon.ai.agent.react.intercept.summarize.*; import org.noear.solon.ai.chat.ChatModel; import org.noear.solon.ai.chat.prompt.Prompt; import org.noear.solon.bot.core.event.EventBus; -import org.noear.solon.bot.core.goalker.GoalKeeperIntegration; import org.noear.solon.bot.core.memory.SharedMemoryManager; import org.noear.solon.bot.core.message.MessageChannel; import org.noear.solon.bot.core.subagent.SubAgentMetadata; @@ -26,7 +25,6 @@ import org.noear.solon.bot.core.subagent.TaskSkill; import org.noear.solon.bot.core.teams.AgentTeamsSkill; import org.noear.solon.bot.core.teams.MainAgent; import org.noear.solon.bot.core.teams.SharedTaskList; -import org.noear.solon.bot.core.teams.TeamNameSuggestionTool; import org.noear.solon.bot.core.tool.ApplyPatchTool; import org.noear.solon.bot.core.tool.CodeSearchTool; import org.noear.solon.bot.core.tool.WebfetchTool; @@ -317,7 +315,6 @@ public class AgentKernel { subagentManager ); agentBuilder.defaultSkillAdd(agentTeamsSkill); - agentBuilder.defaultToolAdd(new TeamNameSuggestionTool(subagentManager)); LOG.info("AgentTeamsSkill 已注册"); diff --git a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/teams/AgentTeamsSkill.java b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/teams/AgentTeamsSkill.java index 73a913d..e5b474c 100644 --- a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/teams/AgentTeamsSkill.java +++ b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/teams/AgentTeamsSkill.java @@ -40,6 +40,7 @@ import java.nio.file.Paths; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeoutException; +import java.util.stream.Collectors; /** * Agent Teams 技能 @@ -62,7 +63,6 @@ public class AgentTeamsSkill extends AbsSkill { private final MainAgent mainAgent; private final AgentKernel kernel; private final SubagentManager manager; - private final AgentTeamsTools agentTeamsTools; // 内部工具集 private final IntelligentMemoryManager intelligentMemoryManager; /** @@ -73,16 +73,6 @@ public class AgentTeamsSkill extends AbsSkill { this.kernel = kernel; this.manager = manager; - // 初始化内部工具集 - if (mainAgent != null) { - this.agentTeamsTools = new AgentTeamsTools( - mainAgent.getSharedMemoryManager(), - mainAgent.getEventBus() - ); - } else { - this.agentTeamsTools = null; - } - // 初始化智能记忆管理器 if (mainAgent != null && mainAgent.getSharedMemoryManager() != null) { // 使用与 SharedMemoryManager 相同的路径 @@ -155,14 +145,19 @@ public class AgentTeamsSkill extends AbsSkill { sb.append("### 核心能力\n"); sb.append("1. **团队协作任务**: 使用 `team_task()` 启动多代理协作\n"); - sb.append("2. **任务管理**: 查看、创建团队任务\n"); + sb.append("2. **任务管理**: \n"); + sb.append(" - `create_task()` 创建任务,`team_status()` 查看状态\n"); + sb.append(" - `claim_task()` 认领,`complete_task()` 完成,`fail_task()` 失败\n"); + sb.append(" - `list_all_tasks()` 列出所有,`get_claimable_tasks()` 获取可认领\n"); sb.append("3. **子代理调用**: 使用 `task()` 委派专门任务(支持会话续接)【强制使用】\n"); - sb.append("4. **动态代理**: 使用 `create_agent()` 创建新的子代理定义\n"); + sb.append("4. **团队成员管理**: 使用 `teammate()` 创建,`teammates()` 列出,`remove_teammate()` 移除\n"); sb.append("5. **智能记忆管理**: 使用 `memory_store()` 自动分类存储(推荐)\n"); sb.append(" - 自动分类:决策→永久、任务→7天、临时→10分钟\n"); sb.append(" - 自动评分:多维度评估记忆重要性(0-10分)\n"); sb.append(" - 智能检索:`memory_recall()` 按相关性排序\n"); - sb.append("6. **代理间通信**: 使用 `send_message()` 向其他代理发送消息\n\n"); + sb.append("6. **工作记忆**: `get_working_memory()` 查看,`update_working_memory()` 更新\n"); + sb.append("7. **代理间通信**: 使用 `send_message()` 发送消息,`list_agents()` 查看可用代理\n"); + sb.append("8. **团队命名**: 使用 `suggest_team_name()` 获取命名建议\n\n"); sb.append("### 强制委派准则\n"); sb.append("- **项目认知**: 探索项目、分析架构 → 委派给子代理\n"); @@ -188,6 +183,55 @@ public class AgentTeamsSkill extends AbsSkill { sb.append("2. **列出成员**: 使用 `teammates()` 查看所有团队成员(表格格式)\n"); sb.append("3. **移除成员**: 使用 `remove_teammate()` 移除团队成员\n\n"); + sb.append("### 团队成员配置选项\n\n"); + sb.append("**基本配置**:\n"); + sb.append("- `name`: 成员唯一标识(如:security-expert)\n"); + sb.append("- `role`: 角色描述(如:安全专家)\n"); + sb.append("- `description`: 详细职责描述\n"); + sb.append("- `teamName`: 团队名称(可选,自动生成)\n\n"); + + sb.append("**技能与工具配置**:\n"); + sb.append("- `expertise`: 专业领域(如:security,auth,encryption)\n"); + sb.append("- `skills`: 启用的技能列表(如:expert,terminal,lucene)\n"); + sb.append("- `tools`: 启用的工具列表(如:read,write,edit,browser)\n"); + sb.append("- `disallowedTools`: 禁用的工具列表\n"); + sb.append("- `mcpServers`: 启用的 MCP 服务器\n"); + sb.append("- `includeAgentTeamsTools`: 引入记忆管理工具(默认true)\n\n"); + + sb.append("**可用工具参考**:\n"); + sb.append("- **文件操作**: `read`, `write`, `edit`, `ls`, `find`(由子代理提供)\n"); + sb.append("- **浏览器**: `browser_screenshot`, `browser_interact`, `browser_navigate`(由子代理提供)\n"); + sb.append("- **记忆管理**: `memory_store`, `memory_recall`, `memory_stats`\n"); + sb.append("- **工作记忆**: `get_working_memory`, `update_working_memory`\n"); + sb.append("- **任务管理**: `create_task`, `team_status`, `claim_task`, `complete_task`, `list_all_tasks`\n"); + sb.append("- **团队协作**: `task`(子代理调用), `teammate`, `teammates`, `remove_teammate`\n"); + sb.append("- **代理通信**: `send_message`, `list_agents`, `get_message_stats`\n"); + sb.append("- **团队命名**: `suggest_team_name`\n\n"); + + sb.append("**使用示例**:\n"); + sb.append("```bash\n"); + sb.append("# 创建带记忆管理工具的成员\n"); + sb.append("teammate(\n"); + sb.append(" name=\"analyst\",\n"); + sb.append(" role=\"数据分析师\",\n"); + sb.append(" description=\"负责数据分析和报告生成\",\n"); + sb.append(" skills=\"expert,lucene\",\n"); + sb.append(" tools=\"read,write,ls\"\n"); + sb.append(")\n\n"); + sb.append("# 创建带浏览器工具的成员\n"); + sb.append("teammate(\n"); + sb.append(" name=\"browser-bot\",\n"); + sb.append(" role=\"浏览器自动化专家\",\n"); + sb.append(" tools=\"browser_screenshot,browser_interact,browser_navigate\"\n"); + sb.append(")\n\n"); + sb.append("# 创建禁用某些工具的成员\n"); + sb.append("teammate(\n"); + sb.append(" name=\"read-only-expert\",\n"); + sb.append(" role=\"只读代码审查专家\",\n"); + sb.append(" disallowedTools=\"write,edit,bash\"\n"); + sb.append(")\n"); + sb.append("```\n\n"); + sb.append("### 任务链协调:如何将结果传递给下一个 subagent\n\n"); sb.append("**⚠️ 重要:完成任务链协调的三种方法**\n\n"); sb.append("**方法 1:在 prompt 中传递上下文(推荐)**\n"); @@ -230,14 +274,14 @@ public class AgentTeamsSkill extends AbsSkill { sb.append(" teamName=\"myteam\" # 指定团队名称\n"); sb.append(")\n"); sb.append("# 生成文件: .soloncode/agentsTeams/myteam/security-expert.md\n\n"); - sb.append("# 场景2: 创建成员(不指定团队,自动生成)\n"); + sb.append("# 场景2: 创建成员(不指定团队,智能生成)\n"); sb.append("teammate(\n"); sb.append(" name=\"db-optimizer\",\n"); sb.append(" role=\"db-optimizer\",\n"); sb.append(" description=\"SQL 查询优化\"\n"); sb.append(")\n"); - sb.append("# 自动生成团队名: team-1736640123456\n"); - sb.append("# 生成文件: .soloncode/agentsTeams/team-1736640123456/db-optimizer.md\n"); + sb.append("# 智能生成团队名: database-team (根据关键词自动识别)\n"); + sb.append("# 生成文件: .soloncode/agentsTeams/database-team/db-optimizer.md\n"); sb.append("# 团队名会保存到内存,后续创建会自动使用同一团队\n\n"); sb.append("# 场景3: 查看所有成员\n"); sb.append("teammates()\n\n"); @@ -570,11 +614,16 @@ public class AgentTeamsSkill extends AbsSkill { @Param(name = "name", description = "团队成员唯一标识(如:security-expert)") String name, @Param(name = "role", description = "角色描述(如:安全专家)") String role, @Param(name = "description", description = "详细职责描述") String description, - @Param(name = "teamName", required = false, description = "团队名称(如:myteam)。如果不指定,将自动生成(格式:team-{timestamp})") String teamName, + @Param(name = "teamName", required = false, description = "团队名称(如:myteam)。如果不指定,将根据角色和描述智能生成语义化名称(如:database-team、security-squad)") String teamName, @Param(name = "systemPrompt", required = false, description = "系统提示词,定义行为模式") String systemPrompt, @Param(name = "expertise", required = false, description = "专业领域,逗号分隔(如:security,auth,encryption)") String expertise, @Param(name = "model", required = false, description = "使用的模型(如:默认)") String model, @Param(name = "searchContext", required = false, description = "是否联网搜索相关上下文(默认false)") Boolean searchContext, + @Param(name = "tools", required = false, description = "启用的工具列表,逗号分隔(如:read,write,edit,browser)") String tools, + @Param(name = "disallowedTools", required = false, description = "禁用的工具列表,逗号分隔") String disallowedTools, + @Param(name = "skills", required = false, description = "启用的技能列表,逗号分隔(如:expert,terminal,lucene)") String skills, + @Param(name = "mcpServers", required = false, description = "启用的 MCP 服务器,逗号分隔") String mcpServers, + @Param(name = "includeAgentTeamsTools", required = false, description = "是否引入 AgentTeamsTool 的记忆管理工具(memory_store, memory_recall等),默认true") Boolean includeAgentTeamsTools, String __cwd ) { try { @@ -630,11 +679,58 @@ public class AgentTeamsSkill extends AbsSkill { metadata.setSkills(Arrays.asList(expertise.split(",\\s*"))); } + // 设置启用的工具 + if (tools != null && !tools.isEmpty()) { + metadata.setTools(Arrays.asList(tools.split(",\\s*"))); + } + + // 设置禁用的工具 + if (disallowedTools != null && !disallowedTools.isEmpty()) { + metadata.setDisallowedTools(Arrays.asList(disallowedTools.split(",\\s*"))); + } + + // 设置启用的技能 + if (skills != null && !skills.isEmpty()) { + // 合并 expertise 定义的技能和 skills 参数定义的技能 + List allSkills = new ArrayList<>(); + if (metadata.getSkills() != null) { + allSkills.addAll(metadata.getSkills()); + } + allSkills.addAll(Arrays.asList(skills.split(",\\s*"))); + metadata.setSkills(allSkills); + } + + // 设置 MCP 服务器 + if (mcpServers != null && !mcpServers.isEmpty()) { + metadata.setMcpServers(Arrays.asList(mcpServers.split(",\\s*"))); + } + // 设置模型 if (model != null && !model.isEmpty()) { metadata.setModel(model); } + // 是否引入 AgentTeamsTool 的记忆管理工具 + boolean includeTools = (includeAgentTeamsTools != null && includeAgentTeamsTools) || + (includeAgentTeamsTools == null); // 默认启用 + + if (includeTools) { + // 如果用户没有明确指定 tools,则自动添加记忆管理工具 + if ((tools == null || tools.isEmpty()) && + (metadata.getTools() == null || metadata.getTools().isEmpty())) { + // 自动添加记忆管理相关工具 + List defaultTools = Arrays.asList( + "memory_store", + "memory_recall", + "memory_stats", + "working_memory_set", + "working_memory_get" + ); + metadata.setTools(defaultTools); + LOG.info("已自动引入 AgentTeamsTool 记忆管理工具"); + } + } + // 生成系统提示词(如果没有提供) String finalPrompt = systemPrompt; if (finalPrompt == null || finalPrompt.isEmpty()) { @@ -686,6 +782,30 @@ public class AgentTeamsSkill extends AbsSkill { result.append(String.format("| **模型** | %s |\n", model)); } + // 显示启用的技能 + if (metadata.getSkills() != null && !metadata.getSkills().isEmpty()) { + result.append(String.format("| **技能** | %s |\n", + String.join(", ", metadata.getSkills()))); + } + + // 显示启用的工具 + if (metadata.getTools() != null && !metadata.getTools().isEmpty()) { + result.append(String.format("| **工具** | %s |\n", + String.join(", ", metadata.getTools()))); + } + + // 显示禁用的工具 + if (metadata.getDisallowedTools() != null && !metadata.getDisallowedTools().isEmpty()) { + result.append(String.format("| **禁用工具** | %s |\n", + String.join(", ", metadata.getDisallowedTools()))); + } + + // 显示 MCP 服务器 + if (metadata.getMcpServers() != null && !metadata.getMcpServers().isEmpty()) { + result.append(String.format("| **MCP服务** | %s |\n", + String.join(", ", metadata.getMcpServers()))); + } + result.append(String.format("| **状态** | 🟢 已激活 |\n")); result.append("\n**使用方法**:\n"); @@ -1647,4 +1767,163 @@ public class AgentTeamsSkill extends AbsSkill { return "[ERROR] 获取失败: " + e.getMessage(); } } + + /** + * 为团队提供命名建议 + */ + @ToolMapping(name = "suggest_team_name", + description = "为现有团队或即将创建的团队提供更好的命名建议。分析团队成员的角色和职责,生成语义化的团队名。") + public String suggestTeamName( + @Param(name = "oldTeamName", required = false, + description = "现有团队名(可选)。如果提供,将分析该团队的成员并给出改进建议") String oldTeamName, + @Param(name = "role", required = false, + description = "主要角色(可选)。如:security-expert") String role, + @Param(name = "description", required = false, + description = "团队描述(可选)。如:专注于系统安全") String description + ) { + StringBuilder result = new StringBuilder(); + + if (oldTeamName != null && !oldTeamName.isEmpty()) { + // 分析现有团队 + result.append(analyzeExistingTeam(oldTeamName)); + } else { + // 为新团队生成建议 + result.append(generateSuggestions(role, description)); + } + + return result.toString(); + } + + /** + * 分析现有团队并提供建议 + */ + private String analyzeExistingTeam(String oldTeamName) { + StringBuilder sb = new StringBuilder(); + + sb.append("## 团队名称分析\n\n"); + + // 检查是否是旧的时间戳格式(如 team-1736640123456) + // 注意:新版本已改用智能生成,此处仅用于迁移旧数据 + if (oldTeamName.matches("^team-\\d+$")) { + sb.append("⚠️ **当前团队名**: `").append(oldTeamName).append("`\n\n"); + sb.append("**问题**: 检测到旧版本时间戳格式,已不够语义化。\n"); + sb.append("**说明**: 新版本已改用智能语义化生成(如 database-team、security-squad)。\n\n"); + + // 获取该团队的成员 + List memberNames = getTeamMembers(oldTeamName); + if (!memberNames.isEmpty()) { + sb.append("**当前成员**:\n"); + for (String member : memberNames) { + sb.append(" - ").append(member).append("\n"); + } + sb.append("\n"); + + // 生成建议 + String suggestedName = TeamNameGenerator.suggestBetterName(oldTeamName, memberNames); + if (suggestedName != null) { + sb.append("✅ **建议团队名**: `").append(suggestedName).append("`\n\n"); + sb.append("**建议描述**: ") + .append(TeamNameGenerator.getTeamDescription(suggestedName)) + .append("\n\n"); + } + } + + sb.append("**如何重命名**:\n"); + sb.append("使用 `create_team` 工具创建新团队,并指定语义化的 teamName。\n"); + } else { + sb.append("✅ **当前团队名**: `").append(oldTeamName).append("`\n\n"); + + // 检查团队名是否有效 + if (!TeamNameGenerator.isValidTeamName(oldTeamName)) { + sb.append("⚠️ **问题**: 团队名格式不符合规范(只允许小写字母、数字和连字符)\n\n"); + String normalized = TeamNameGenerator.normalizeTeamName(oldTeamName); + sb.append("✅ **规范化建议**: `").append(normalized).append("`\n\n"); + } else { + sb.append("✅ 团队名格式正确!\n\n"); + + // 提取领域 + String domain = TeamNameGenerator.extractDomainFromTeamName(oldTeamName); + if (domain != null) { + sb.append("**识别的领域**: ").append(domain).append("\n\n"); + sb.append("**团队描述**: ") + .append(TeamNameGenerator.getTeamDescription(oldTeamName)) + .append("\n\n"); + } + } + } + + return sb.toString(); + } + + /** + * 为新团队生成命名建议 + */ + private String generateSuggestions(String role, String description) { + StringBuilder sb = new StringBuilder(); + + sb.append("## 团队命名建议\n\n"); + + if (role == null && description == null) { + sb.append("请提供角色或描述信息,以便生成更准确的团队名建议。\n\n"); + sb.append("**示例**:\n"); + sb.append("```\n"); + sb.append("suggest_team_name(role=\"security-expert\", description=\"专注于安全审计\")\n"); + sb.append("```\n\n"); + + sb.append("**常见领域示例**:\n"); + sb.append("- `security-squad` - 安全专家团队\n"); + sb.append("- `database-team` - 数据库专家团队\n"); + sb.append("- `frontend-experts` - 前端开发团队\n"); + sb.append("- `backend-force` - 后端开发团队\n"); + sb.append("- `devops-alliance` - 运维自动化团队\n"); + sb.append("- `testing-guild` - 质量保证团队\n"); + sb.append("- `architecture-lab` - 架构设计团队\n"); + sb.append("- `ai-collective` - 人工智能团队\n"); + + return sb.toString(); + } + + // 生成建议 + String teamName = TeamNameGenerator.generateTeamName( + role != null ? role : "expert", + description != null ? description : "", + null + ); + + sb.append("**基于输入生成的建议**:\n\n"); + sb.append("```\n"); + sb.append("团队名: ").append(teamName).append("\n"); + sb.append("描述: ").append(TeamNameGenerator.getTeamDescription(teamName)).append("\n"); + sb.append("```\n\n"); + + // 生成多个备选方案 + sb.append("**其他备选方案**:\n\n"); + String taskGoal = description != null ? description : role; + for (int i = 0; i < 3; i++) { + String alternative = TeamNameGenerator.generateTeamName( + role + "-" + i, + description, + taskGoal + ); + sb.append((i + 1)).append(". `").append(alternative).append("` - ") + .append(TeamNameGenerator.getTeamDescription(alternative)) + .append("\n"); + } + + sb.append("\n**使用方法**:\n"); + sb.append("在创建团队成员时使用 `teamName=\"").append(teamName).append("\"` 参数。\n"); + + return sb.toString(); + } + + /** + * 获取团队成员 + */ + private List getTeamMembers(String teamName) { + return manager.getAgents().stream() + .filter(agent -> agent.getMetadata().hasTeamName() && + agent.getMetadata().getTeamName().equals(teamName)) + .map(agent -> agent.getMetadata().getCode()) + .collect(Collectors.toList()); + } } diff --git a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/teams/TeamNameSuggestionTool.java b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/teams/TeamNameSuggestionTool.java deleted file mode 100644 index 522b59b..0000000 --- a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/teams/TeamNameSuggestionTool.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright 2017-2026 noear.org and authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.noear.solon.bot.core.teams; - -import org.noear.solon.ai.annotation.ToolMapping; -import org.noear.solon.annotation.Param; -import org.noear.solon.bot.core.subagent.SubAgentMetadata; -import org.noear.solon.bot.core.subagent.SubagentManager; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -/** - * 团队命名建议工具 - * - * 为现有团队提供更好的命名建议 - * - * @author bai - * @since 3.9.5 - */ -public class TeamNameSuggestionTool { - private static final Logger LOG = LoggerFactory.getLogger(TeamNameSuggestionTool.class); - - private final SubagentManager manager; - - public TeamNameSuggestionTool(SubagentManager manager) { - this.manager = manager; - } - - /** - * 为团队提供命名建议 - */ - @ToolMapping(name = "suggest_team_name", - description = "为现有团队或即将创建的团队提供更好的命名建议。分析团队成员的角色和职责,生成语义化的团队名。") - public String suggestTeamName( - @Param(name = "oldTeamName", required = false, - description = "现有团队名(可选)。如果提供,将分析该团队的成员并给出改进建议") String oldTeamName, - @Param(name = "role", required = false, - description = "主要角色(可选)。如:security-expert") String role, - @Param(name = "description", required = false, - description = "团队描述(可选)。如:专注于系统安全") String description - ) { - StringBuilder result = new StringBuilder(); - - if (oldTeamName != null && !oldTeamName.isEmpty()) { - // 分析现有团队 - result.append(analyzeExistingTeam(oldTeamName)); - } else { - // 为新团队生成建议 - result.append(generateSuggestions(role, description)); - } - - return result.toString(); - } - - /** - * 分析现有团队并提供建议 - */ - private String analyzeExistingTeam(String oldTeamName) { - StringBuilder sb = new StringBuilder(); - - sb.append("## 团队名称分析\n\n"); - - // 检查是否是时间戳格式(如 team-1736640123456) - if (oldTeamName.matches("^team-\\d+$")) { - sb.append("⚠️ **当前团队名**: `").append(oldTeamName).append("`\n\n"); - sb.append("**问题**: 这是时间戳格式的团队名,不够语义化。\n\n"); - - // 获取该团队的成员 - List memberNames = getTeamMembers(oldTeamName); - if (!memberNames.isEmpty()) { - sb.append("**当前成员**:\n"); - for (String member : memberNames) { - sb.append(" - ").append(member).append("\n"); - } - sb.append("\n"); - - // 生成建议 - String suggestedName = TeamNameGenerator.suggestBetterName(oldTeamName, memberNames); - if (suggestedName != null) { - sb.append("✅ **建议团队名**: `").append(suggestedName).append("`\n\n"); - sb.append("**建议描述**: ") - .append(TeamNameGenerator.getTeamDescription(suggestedName)) - .append("\n\n"); - } - } - - sb.append("**如何重命名**:\n"); - sb.append("使用 `create_team` 工具创建新团队,并指定语义化的 teamName。\n"); - } else { - sb.append("✅ **当前团队名**: `").append(oldTeamName).append("`\n\n"); - - // 检查团队名是否有效 - if (!TeamNameGenerator.isValidTeamName(oldTeamName)) { - sb.append("⚠️ **问题**: 团队名格式不符合规范(只允许小写字母、数字和连字符)\n\n"); - String normalized = TeamNameGenerator.normalizeTeamName(oldTeamName); - sb.append("✅ **规范化建议**: `").append(normalized).append("`\n\n"); - } else { - sb.append("✅ 团队名格式正确!\n\n"); - - // 提取领域 - String domain = TeamNameGenerator.extractDomainFromTeamName(oldTeamName); - if (domain != null) { - sb.append("**识别的领域**: ").append(domain).append("\n\n"); - sb.append("**团队描述**: ") - .append(TeamNameGenerator.getTeamDescription(oldTeamName)) - .append("\n\n"); - } - } - } - - return sb.toString(); - } - - /** - * 为新团队生成命名建议 - */ - private String generateSuggestions(String role, String description) { - StringBuilder sb = new StringBuilder(); - - sb.append("## 团队命名建议\n\n"); - - if (role == null && description == null) { - sb.append("请提供角色或描述信息,以便生成更准确的团队名建议。\n\n"); - sb.append("**示例**:\n"); - sb.append("```\n"); - sb.append("suggest_team_name(role=\"security-expert\", description=\"专注于安全审计\")\n"); - sb.append("```\n\n"); - - sb.append("**常见领域示例**:\n"); - sb.append("- `security-squad` - 安全专家团队\n"); - sb.append("- `database-team` - 数据库专家团队\n"); - sb.append("- `frontend-experts` - 前端开发团队\n"); - sb.append("- `backend-force` - 后端开发团队\n"); - sb.append("- `devops-alliance` - 运维自动化团队\n"); - sb.append("- `testing-guild` - 质量保证团队\n"); - sb.append("- `architecture-lab` - 架构设计团队\n"); - sb.append("- `ai-collective` - 人工智能团队\n"); - - return sb.toString(); - } - - // 生成建议 - String teamName = TeamNameGenerator.generateTeamName( - role != null ? role : "expert", - description != null ? description : "", - null - ); - - sb.append("**基于输入生成的建议**:\n\n"); - sb.append("```\n"); - sb.append("团队名: ").append(teamName).append("\n"); - sb.append("描述: ").append(TeamNameGenerator.getTeamDescription(teamName)).append("\n"); - sb.append("```\n\n"); - - // 生成多个备选方案 - sb.append("**其他备选方案**:\n\n"); - String taskGoal = description != null ? description : role; - for (int i = 0; i < 3; i++) { - String alternative = TeamNameGenerator.generateTeamName( - role + "-" + i, - description, - taskGoal - ); - sb.append((i + 1)).append(". `").append(alternative).append("` - ") - .append(TeamNameGenerator.getTeamDescription(alternative)) - .append("\n"); - } - - sb.append("\n**使用方法**:\n"); - sb.append("在创建团队成员时使用 `teamName=\"").append(teamName).append("\"` 参数。\n"); - - return sb.toString(); - } - - /** - * 获取团队成员 - */ - private List getTeamMembers(String teamName) { - return manager.getAgents().stream() - .filter(agent -> agent.getMetadata().hasTeamName() && - agent.getMetadata().getTeamName().equals(teamName)) - .map(agent -> agent.getMetadata().getCode()) - .collect(Collectors.toList()); - } -} -- Gitee From 77f78ea9c9adae7207fc0d3c229a2b3918ac99eb Mon Sep 17 00:00:00 2001 From: bai <1145000687@qq.com> Date: Thu, 12 Mar 2026 15:39:16 +0800 Subject: [PATCH 2/3] =?UTF-8?q?config:=20=E5=88=A0=E9=99=A4=20CLI=20?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除整个 config.yml 配置文件 - 包含日志、工作目录、ReAct 步数等所有配置项 - 删除沙盒模式、人工审核、Web API 等功能开关配置 - 清理子代理、共享内存、事件总线等高级功能配置 - 移除聊天模型 API 地址、密钥、模型名称等 LLM 配置 - 删除技能池、MCP 服务器等扩展功能配置 --- soloncode-cli/src/main/resources/config.yml | 88 --------------------- 1 file changed, 88 deletions(-) delete mode 100644 soloncode-cli/src/main/resources/config.yml diff --git a/soloncode-cli/src/main/resources/config.yml b/soloncode-cli/src/main/resources/config.yml deleted file mode 100644 index fbb25b1..0000000 --- a/soloncode-cli/src/main/resources/config.yml +++ /dev/null @@ -1,88 +0,0 @@ -solon.logging.appender: - console: - enable: false # 是否启用控制台日志(启用后,会对 cli console 有干扰) - file: - level: INFO - -solon.code.cli: - workDir: "work" # 工作区目录 - maxSteps: 30 # ReAct 最大循环步数 - maxStepsAutoExtensible: true #最大步数自动续航(由 LLM 反思控制) - sessionWindowSize: 10 # 会话历史窗口大小(即,新指令时使用几条历史消息) - - hitlEnabled: true # 是否启用人工审核危险操作,和 maxSteps 动态继步 - - sandboxMode: true # 消盒模式,启用时禁止访问绝对路径 - thinkPrinted: true # 内心思考,是否打印 - - cliEnabled: true # 是否启用控制台 - cliPrintSimplified: true - - webEnabled: false # 是否启用 WebApi - webEndpoint: "/cli" - - acpEnabled: false # 是否启用 Acp 协议(一些支持 Acp 的 IDE 可以连接) - acpTransport: "websocket" # "stdio" or "websocket" - acpEndpoint: "/acp" - - subAgentEnabled: true - - # SubAgent 模型配置(可选) - # 如果不配置,将使用 chatModel.model 作为默认值 - # subAgentModels: - # explore: "glm-4-flash" # 探索代理使用快速模型 - # plan: "glm-4.7" # 计划代理使用推理模型 - # bash: "glm-4-flash" # Bash代理使用快速模型 - # general-purpose: "glm-4.7" # 通用代理使用推理模型 - # solon-code-guide: "glm-4.7" # Solon指南代理使用推理模型 - - # Agent Teams 增强功能配置 - # 共享记忆功能:允许子代理之间共享信息和知识 - sharedMemoryEnabled: true - sharedMemory: - shortTermTtl: 3600000 # 短期记忆TTL(毫秒,默认1小时) - longTermTtl: 604800000 # 长期记忆TTL(毫秒,默认7天) - cleanupInterval: 300000 # 清理间隔(毫秒,默认5分钟) - persistOnWrite: true # 写入时立即持久化 - maxShortTermCount: 1000 # 短期记忆最大数量 - maxLongTermCount: 500 # 长期记忆最大数量 - - # 事件总线功能:基于事件的异步通信 - eventBusEnabled: true - eventBus: - asyncThreads: 4 # 异步处理线程数(默认CPU核心数) - maxHistorySize: 1000 # 事件历史最大数量 - defaultPriority: 5 # 默认优先级(0-10) - timeoutSeconds: 30 # 处理超时时间(秒) - - # 消息通道功能:代理之间的直接消息传递 - messageChannelEnabled: true - messageChannel: - threads: 4 # 处理线程数 - defaultTtl: 60000 # 默认消息TTL(毫秒,默认60秒) - maxQueueSize: 1000 # 每个代理的最大队列长度 - persistMessages: true # 是否持久化消息 - - chatModel: # llm 配置(参考 solon ai chatmodel 配置) - apiUrl: "https://open.bigmodel.cn/api/paas/v4/chat/completions" - apiKey: "1c6ec604ad0644e09384103437643a7c.Mu9Mof2ge3KWbkGN" - model: "glm-4.7" - userAgent: "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; SolonCode/1.0; +https://solon.noear.org/)" - # defaultOptions: - # enable_thinking: false #不同模型可能不同 - # temperature: 0.2 - # top_p: 0.5 - # frequency_penalty: 0.5 - # presence_penalty: 0.4 - skillPools: # 技能池(可以多个) - "@shared": "/path/to/shared-skills" -# mcpServers: -# gitee: -# url: "http://..." -# headers: { API_KEY: "xxx" } -# memory: -# command: "npx" -# args: [ "-y", "@modelcontextprotocol/server-memory" ] - -# 1. chatModel 按需配置,不熟的看下 solon ai 官网资料 -# 2. SolonCodeCLI 兼容 Claude Code Agent Skills \ No newline at end of file -- Gitee From c8783248cbc4ff213b5c4cac41feb188dd7a24f5 Mon Sep 17 00:00:00 2001 From: bai <1145000687@qq.com> Date: Thu, 12 Mar 2026 18:51:12 +0800 Subject: [PATCH 3/3] =?UTF-8?q?feat(core):=20=E9=87=8D=E6=9E=84=20Agent=20?= =?UTF-8?q?Teams=20=E6=A8=A1=E5=BC=8F=E9=85=8D=E7=BD=AE=E5=92=8C=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1=E7=AE=A1=E7=90=86=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除 getSummarizationInterceptor 方法和无用注释 - 添加 TeamsConfig 和 SubagentConcurrencyConfig 配置类,支持线程池、并发控制等配置 - 修改 EventBus、SharedTaskList、MessageChannel 初始化以使用配置参数 - 新增 analyze_tasks 工具用于任务分解分析和 create_tasks 批量创建任务 - 添加 remove_task 和 release_task 工具用于任务管理 - 实现子代理并发控制机制,限制同时发起的子代理请求数量 - 优化任务依赖检测和循环依赖处理逻辑 - 更新 AgentTeamsSkill 描述文档,同步新的任务管理功能 - 重构 SharedTaskList 构造函数,支持配置化初始化参数 --- .../org/noear/solon/bot/core/AgentKernel.java | 25 +- .../noear/solon/bot/core/AgentProperties.java | 71 +++- .../bot/core/message/MessageChannel.java | 2 +- .../solon/bot/core/subagent/TaskSkill.java | 330 ++++++++++------ .../solon/bot/core/teams/AgentTeamsSkill.java | 373 +++++++++++++++++- .../noear/solon/bot/core/teams/MainAgent.java | 191 +-------- .../solon/bot/core/teams/SharedTaskList.java | 91 ++--- 7 files changed, 692 insertions(+), 391 deletions(-) diff --git a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/AgentKernel.java b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/AgentKernel.java index cf56269..051eedf 100644 --- a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/AgentKernel.java +++ b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/AgentKernel.java @@ -115,10 +115,6 @@ public class AgentKernel { return properties; } - public SummarizationInterceptor getSummarizationInterceptor() { - return summarizationInterceptor; - } - public AgentKernel(ChatModel chatModel, AgentProperties properties, AgentSessionProvider sessionProvider, Consumer configurator) { this.chatModel = chatModel; this.properties = properties; @@ -146,7 +142,6 @@ public class AgentKernel { throw new RuntimeException("Mcp servers load failure", e); } - //----------- final ReActAgent.Builder agentBuilder = ReActAgent.of(chatModel).name("main"); final String agentsMd = getAgentsMd(); @@ -264,12 +259,14 @@ public class AgentKernel { try { LOG.info("正在初始化 Agent Teams 模式..."); - // 1. 创建 EventBus(事件总线) - this.eventBus = new EventBus(); - LOG.debug("EventBus 已创建"); + // 1. 创建 EventBus(事件总线)- 使用配置 + int eventThreads = properties.getEventBus().asyncThreads; + int eventHistorySize = properties.getEventBus().maxHistorySize; + this.eventBus = new EventBus(eventThreads, eventHistorySize); + LOG.debug("EventBus 已创建 (线程数: {}, 历史大小: {})", eventThreads, eventHistorySize); - // 2. 创建 SharedTaskList(共享任务列表) - this.taskList = new SharedTaskList(eventBus); + // 2. 创建 SharedTaskList(共享任务列表)- 使用配置 + this.taskList = new SharedTaskList(eventBus, properties.getTeams()); LOG.debug("SharedTaskList 已创建"); // 3. 创建 SharedMemoryManager(共享内存管理器) @@ -277,10 +274,12 @@ public class AgentKernel { this.memoryManager = new SharedMemoryManager(memoryPath); LOG.debug("SharedMemoryManager 已创建,路径: {}", memoryPath); - // 4. 创建 MessageChannel(消息通道) + // 4. 创建 MessageChannel(消息通道)- 使用配置 Path messagePath = Paths.get(properties.getWorkDir(), SOLONCODE_MEMORY); - this.messageChannel = new MessageChannel(messagePath.toString()); - LOG.debug("MessageChannel 已创建,路径: {}", messagePath); + int messageThreads = properties.getMessageChannel().threads != null ? + properties.getMessageChannel().threads : 4; + this.messageChannel = new MessageChannel(messagePath.toString(), messageThreads); + LOG.debug("MessageChannel 已创建,路径: {}, 线程数: {}", messagePath, messageThreads); // 5. 创建 MainAgent 配置 SubAgentMetadata mainAgentConfig = new SubAgentMetadata(); diff --git a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/AgentProperties.java b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/AgentProperties.java index 20c51cf..743f178 100644 --- a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/AgentProperties.java +++ b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/AgentProperties.java @@ -83,6 +83,7 @@ public class AgentProperties implements Serializable { * Agent Teams 模式配置 */ public boolean teamsEnabled = false; + public TeamsConfig teams = new TeamsConfig(); public Map mcpServers; public ChatConfig chatModel; @@ -142,7 +143,7 @@ public class AgentProperties implements Serializable { /** * 异步处理线程数(默认CPU核心数) */ - public Integer asyncThreads; + public int asyncThreads = 4; /** * 事件历史最大数量 @@ -184,4 +185,72 @@ public class AgentProperties implements Serializable { */ public boolean persistMessages = true; } + + /** + * Agent Teams 模式配置类 + */ + public static class TeamsConfig { + /** + * 任务操作线程池大小 + * 默认:CPU核心数 * 2,最小10 + */ + public Integer taskExecutorThreads; + + /** + * 事件发布线程池大小 + * 默认:1(单线程) + */ + public Integer eventExecutorThreads; + + /** + * 最大保留已完成任务数 + * 默认:100 + */ + public int maxCompletedTasks = 100; + + /** + * 最大依赖深度限制 + * 默认:100 + */ + public int maxDependencyDepth = 100; + } + + /** + * 子代理并发控制配置类 + * 用于控制同时发起的子代理请求数量,避免触发API速率限制 + */ + public static class SubagentConcurrencyConfig { + /** + * 最大并发子代理数 + * 默认:1(串行执行,避免触发速率限制) + * 设置为 2-3 可以适当提高性能,但可能触发速率限制 + */ + public int maxConcurrent = 1; + + /** + * 子代理调用间隔(毫秒) + * 默认:1000ms(1秒) + * 在串行模式下,每次调用后等待的时间 + */ + public long callIntervalMs = 1000L; + + /** + * 获取执行许可的超时时间(毫秒) + * 默认:60000ms(60秒) + * 当达到并发限制时,等待获取许可的最长时间 + */ + public long acquireTimeoutMs = 60000L; + + /** + * 触发429错误后的等待时间(毫秒) + * 默认:5000ms(5秒) + * 收到速率限制错误后,等待的时间 + */ + public long rateLimitBackoffMs = 5000L; + } + + /** + * 子代理并发控制配置 + */ + public SubagentConcurrencyConfig subagentConcurrency = new SubagentConcurrencyConfig(); } diff --git a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/message/MessageChannel.java b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/message/MessageChannel.java index bc3f0df..a717154 100644 --- a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/message/MessageChannel.java +++ b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/message/MessageChannel.java @@ -410,7 +410,7 @@ public class MessageChannel { } catch (Exception e) { LOG.warn("消息持久化失败: messageId={}, error={}", message.getId(), e.getMessage()); } - }); + }, executor); } /** diff --git a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/TaskSkill.java b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/TaskSkill.java index 82fd498..d97063e 100644 --- a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/TaskSkill.java +++ b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/TaskSkill.java @@ -31,6 +31,7 @@ import org.noear.solon.bot.core.AgentKernel; import org.noear.solon.core.util.Assert; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import reactor.core.publisher.Mono; import java.io.BufferedWriter; import java.io.FileOutputStream; @@ -39,8 +40,12 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.time.Duration; import java.util.Arrays; import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; /** * 子代理技能 @@ -53,14 +58,32 @@ import java.util.List; public class TaskSkill extends AbsSkill { private static final Logger LOG = LoggerFactory.getLogger(TaskSkill.class); + // 子代理执行超时配置(单位:毫秒) + private static final long SUBAGENT_SYNC_TIMEOUT_MS = 120_000; + private static final long SUBAGENT_STREAM_TIMEOUT_MS = 180_000; + private static final long FIRST_CHUNK_TIMEOUT_MS = 45_000; + private final AgentKernel mainAgent; private final SubagentManager manager; + // 并发控制:使用 Semaphore 限制同时发起的子代理请求数(从配置读取) + private final Semaphore concurrencySemaphore; + + // 记录每个子代理类型的调用时间,用于精细控制 + private static final ConcurrentHashMap lastCallTimeByType = new ConcurrentHashMap<>(); + public TaskSkill(AgentKernel mainAgent, SubagentManager manager) { this.mainAgent = mainAgent; this.manager = manager; - } + // 从配置读取并发控制参数 + int maxConcurrent = mainAgent.getProperties().subagentConcurrency.maxConcurrent; + this.concurrencySemaphore = new Semaphore(maxConcurrent); + + LOG.info("TaskSkill 初始化: maxConcurrent={}, callIntervalMs={}ms", + maxConcurrent, + mainAgent.getProperties().subagentConcurrency.callIntervalMs); + } @Override public String description() { @@ -72,32 +95,15 @@ public class TaskSkill extends AbsSkill { StringBuilder sb = new StringBuilder(); sb.append("处理复杂的、多步骤的任务,必须委派子代理(Subagent)执行\n\n"); - sb.append("可用的代理类型及其拥有的工具:\n"); - // ========== 新增:禁止模拟工作规则 ========== sb.append("### ⚠️ 核心规则(强制执行)\n\n"); sb.append("#### 🚫 禁止行为\n"); - sb.append("1. **禁止模拟工作**:\n"); - sb.append(" - 严禁不断更新状态而无实际产出\n"); - sb.append(" - 不得声称任务已完成但没有文件生成\n"); - sb.append(" - 使用 task() 工具后,子代理必须实际创建文件或执行命令\n\n"); - sb.append("2. **必须有实际产出**:\n"); - sb.append(" - 代码任务:必须生成 .java、.py 等代码文件\n"); - sb.append(" - 文档任务:必须生成 .md、.txt 等文档文件\n"); - sb.append(" - 测试任务:必须有测试结果或报告\n"); - sb.append(" - 使用 ls、read 工具验证文件已真实创建\n\n"); + sb.append("1. **禁止模拟工作**:严禁不断更新状态而无实际产出\n"); + sb.append("2. **必须有实际产出**:代码任务必须生成文件,使用 ls/read 验证\n\n"); + sb.append("#### ✅ 必须行为\n"); - sb.append("1. **强制使用 task() 工具**:\n"); - sb.append(" - 所有实际工作必须通过 task(subagent_type=..., prompt=...) 完成\n"); - sb.append(" - 不得自己在主对话中重复尝试\n\n"); - - sb.append("### 强制委派准则\n"); - sb.append("- **项目认知**: 凡是涉及\"探索项目\"、\"分析架构\"、\"查找核心入口\"等需要阅读多个文件或理解代码库的任务,应委派给子代理。\n"); - sb.append("- **复杂变更**: 涉及跨文件的代码修复、重构或需要运行测试验证的任务,应委派给子代理。\n"); - sb.append("- **决策量化**: 预感需要连续调用超过 3 次原子工具(如 grep, read_file)时,应改用子代理以节省主对话上下文。\n"); - sb.append("- **所有开发任务**: 必须使用 task() 工具委派,禁止在主对话中模拟执行\n\n"); - - sb.append("### 可用的子代理注册表 (Capabilities Registry)\n"); - sb.append("请根据任务语义匹配最合适的 `subagent_type`:\n"); + sb.append("1. **强制使用 task() 工具**:所有实际工作必须通过 task() 完成\n\n"); + + sb.append("### 可用的子代理注册表\n"); sb.append("\n"); for (Subagent agent : manager.getAgents()) { sb.append(String.format(" - \"%s\": %s\n", agent.getType(), agent.getDescription())); @@ -105,20 +111,18 @@ public class TaskSkill extends AbsSkill { sb.append("\n\n"); sb.append("### 调用约定\n"); - sb.append("- **上下文对齐**: 子代理看不见当前历史。必须在 `prompt` 中通过 标签传入必要的类名、报错或路径。\n"); - sb.append("- **示例**: `task(subagent_type=\"explore\", prompt=\"分析 demo-web 核心架构\", description=\"架构探索\")`\n"); - sb.append("- **动态创建 Agent**: 可以使用 `create_agent` 工具动态创建新的子代理,自定义其行为和技能。\n"); + sb.append("- **上下文对齐**: 子代理看不见当前历史,必须在 prompt 中传入必要的上下文\n"); + sb.append("- **示例**: `task(subagentType=\"explore\", prompt=\"分析项目架构\")`\n"); return sb.toString(); } - @ToolMapping(name = "task", - description = "【强制使用】派生并分派任务给专项子代理。所有实际开发工作(代码编写、文件创建、测试执行等)必须使用此工具委派给子代理完成,禁止在主对话中模拟执行或虚假声称完成。子代理会实际创建文件并返回真实结果。") + description = "【强制使用】派生并分派任务给专项子代理。所有实际开发工作必须使用此工具委派给子代理完成。") public String task( - @Param(name = "subagent_type", description = "子代理类型") String subagent_type, - @Param(name = "prompt",description = "具体指令。必须包含任务目标、关键类名或必要的背景上下文。") String prompt, - @Param(name = "description", required = false, description = "简短的任务描述(3-5个词),给用户看") String description, + @Param(name = "subagentType", description = "子代理类型") String subagentType, + @Param(name = "prompt", description = "具体指令。必须包含任务目标、关键类名或必要的背景上下文。") String prompt, + @Param(name = "description", required = false, description = "简短的任务描述") String description, @Param(name = "taskId", required = false, description = "可选。若要继续之前的任务会话,请传入对应的 task_id") String taskId, String __cwd, String __sessionId @@ -127,96 +131,204 @@ public class TaskSkill extends AbsSkill { ReActTrace __parentTrace = ReActTrace.getCurrent(__parentSession.getSnapshot()); try { - Subagent agent = manager.getAgent(subagent_type); - + Subagent agent = manager.getAgent(subagentType); if (agent == null) { - return "ERROR: 未知的子代理类型 '" + subagent_type + "'。请从 列表中选择。"; + return "ERROR: 未知的子代理类型 '" + subagentType + "'。"; } - // 1. 会话标识处理:如果有 task_id 则沿用,否则生成基于类型的标识 String finalSessionId = Assert.isEmpty(taskId) - ? "subagent_" + subagent_type + "_" + System.currentTimeMillis() + ? "subagent_" + subagentType + "_" + System.currentTimeMillis() : taskId; - LOG.info("分派任务 -> 类型: {}, 会话: {}, 描述: {}", subagent_type, finalSessionId, description); + LOG.info("分派任务 -> 类型: {}, 会话: {}, 描述: {}", subagentType, finalSessionId, description); - // 2. 这里的 AgentSession 逻辑应与 AgentKernel 深度绑定 - // 注意:execute 内部应能识别 sessionId 并从 sessionManager 恢复上下文 + // ==================== 并发控制 ==================== + if (!acquirePermit(subagentType)) { + return "ERROR: 等待子代理执行许可超时。当前可能有太多子代理在执行。"; + } String result = null; - if (__parentTrace.getOptions().getStreamSink() == null) { - //没有流信息(同步) - AgentResponse response = agent.call(__cwd, finalSessionId, Prompt.of(prompt)); - result = response.getContent(); - - //累计 tokens 计数 - __parentTrace.getMetrics().addMetrics(response.getMetrics()); - } else { - ReActChunk chunk1 = (ReActChunk) agent.stream(__cwd, finalSessionId, Prompt.of(prompt)) - .doOnNext(chunk -> { - if(chunk instanceof ActionChunk) { - __parentTrace.getOptions().getStreamSink().next(chunk); - } else if(chunk instanceof ReasonChunk){ - __parentTrace.getOptions().getStreamSink().next(chunk); - } - }) - .blockLast(); - - result = chunk1.getContent(); - //累计 tokens 计数 - __parentTrace.getMetrics().addMetrics(chunk1.getResponse().getMetrics()); - } + try { + if (__parentTrace.getOptions().getStreamSink() == null) { + // 同步模式 + AgentResponse response = agent.call(__cwd, finalSessionId, Prompt.of(prompt)); + result = response.getContent(); + __parentTrace.getMetrics().addMetrics(response.getMetrics()); + } else { + // 流式模式 + result = executeStream(agent, __cwd, finalSessionId, Prompt.of(prompt), + __parentTrace, subagentType); + } + LOG.info("子代理任务完成: {}", finalSessionId); - LOG.info("子代理任务完成: {}", finalSessionId); + return String.format( + "task_id: %s\n" + + "subagentType: %s\n" + + "\n" + + "\n" + + "%s\n" + + "", + finalSessionId, subagentType, result != null ? result : "(无输出)" + ); - // 3. 结构化输出,方便主代理识别结果并知道如何继续 - return String.format( - "task_id: %s\n" + - "subagent_type: %s\n" + - "\n" + - "\n" + - "%s\n" + - "", - finalSessionId, subagent_type, result - ); + } finally { + releasePermit(subagentType); + } } catch (Throwable e) { - LOG.error("子代理执行崩溃: type={}, error={}", subagent_type, e.getMessage(), e); + LOG.error("子代理执行崩溃: type={}, error={}", subagentType, e.getMessage(), e); return "ERROR: 子代理执行失败: " + e.getMessage(); } } /** - * 动态创建子代理工具 - * - * 允许主 Agent 动态创建新的子代理,自定义其行为和技能。 - * 创建的 agent 定义会保存到文件,并立即注册到 SubagentManager 中。 + * 获取执行许可(带限流控制) + */ + private boolean acquirePermit(String subagentType) throws InterruptedException { + long waitStart = System.currentTimeMillis(); + + // 1. 等待并发许可(从配置读取超时时间) + long acquireTimeoutMs = mainAgent.getProperties().subagentConcurrency.acquireTimeoutMs; + boolean acquired = concurrencySemaphore.tryAcquire(acquireTimeoutMs, TimeUnit.MILLISECONDS); + if (!acquired) { + LOG.warn("[并发控制] 等待执行许可超时: type={}, timeout={}ms", subagentType, acquireTimeoutMs); + return false; + } + + long waitTime = System.currentTimeMillis() - waitStart; + if (waitTime > 100) { + LOG.info("[并发控制] 等待执行许可: type={}, wait={}ms", subagentType, waitTime); + } + + // 2. 控制调用间隔(避免连续调用触发速率限制) + long callIntervalMs = mainAgent.getProperties().subagentConcurrency.callIntervalMs; + Long lastCall = lastCallTimeByType.get(subagentType); + long now = System.currentTimeMillis(); + if (lastCall != null) { + long elapsed = now - lastCall; + if (elapsed < callIntervalMs) { + long sleepTime = callIntervalMs - elapsed; + LOG.debug("[并发控制] 调用间隔等待: type={}, sleep={}ms", subagentType, sleepTime); + Thread.sleep(sleepTime); + } + } + lastCallTimeByType.put(subagentType, System.currentTimeMillis()); + + return true; + } + + /** + * 释放执行许可 */ + private void releasePermit(String subagentType) { + concurrencySemaphore.release(); + LOG.debug("[并发控制] 释放执行许可: type={}, available={}", + subagentType, concurrencySemaphore.availablePermits()); + } + + /** + * 执行流式子代理调用 + */ + private String executeStream(Subagent agent, String __cwd, String sessionId, + Prompt prompt, ReActTrace __parentTrace, String subagentType) { + try { + String promptStr = prompt.toString(); + LOG.info("[子代理] 启动异步流式执行: type={}, sessionId={}, promptLength={}", + subagentType, sessionId, promptStr != null ? promptStr.length() : 0); + + final long[] firstChunkTime = {0}; + final long[] lastChunkTime = {System.currentTimeMillis()}; + final int[] chunkCount = {0}; + final StringBuilder contentBuilder = new StringBuilder(); + + String result = agent.stream(__cwd, sessionId, prompt) + .doOnSubscribe(s -> { + LOG.info("[子代理] 流订阅成功: type={}, sessionId={}", subagentType, sessionId); + }) + .doOnNext(chunk -> { + long now = System.currentTimeMillis(); + if (chunkCount[0] == 0) { + firstChunkTime[0] = now; + long firstChunkDelay = now - lastChunkTime[0]; + LOG.info("[子代理] 收到首个chunk: type={}, delay={}ms, chunkType={}", + subagentType, firstChunkDelay, chunk.getClass().getSimpleName()); + } + lastChunkTime[0] = now; + chunkCount[0]++; + + LOG.debug("[子代理] 收到chunk: type={}, chunkType={}, total={}", + subagentType, chunk.getClass().getSimpleName(), chunkCount[0]); + + if (chunk instanceof ActionChunk) { + __parentTrace.getOptions().getStreamSink().next(chunk); + } else if (chunk instanceof ReasonChunk) { + __parentTrace.getOptions().getStreamSink().next(chunk); + } + + if (chunk != null && chunk.hasContent()) { + contentBuilder.append(chunk.getContent()); + } + }) + .doOnComplete(() -> { + long totalDuration = System.currentTimeMillis() - firstChunkTime[0]; + LOG.info("[子代理] 流完成: type={}, sessionId={}, totalChunks={}, totalDuration={}ms", + subagentType, sessionId, chunkCount[0], totalDuration); + }) + .doOnError(e -> { + LOG.error("[子代理] 流错误: type={}, sessionId={}, error={}, chunksReceived={}", + subagentType, sessionId, e.getMessage(), chunkCount[0]); + }) + .subscribeOn(reactor.core.scheduler.Schedulers.boundedElastic()) + .then(Mono.fromCallable(() -> contentBuilder.toString())) + .block(Duration.ofMillis(SUBAGENT_STREAM_TIMEOUT_MS)); + + LOG.info("[子代理] 执行成功: type={}, sessionId={}, chunks={}, resultLength={}", + subagentType, sessionId, chunkCount[0], + result != null ? result.length() : 0); + + return result; + + } catch (Exception e) { + String errorMsg = e.getMessage(); + if (errorMsg != null && errorMsg.contains("Timeout")) { + LOG.error("[子代理] 执行超时: type={}, sessionId={}", subagentType, sessionId); + return "ERROR: 子代理执行超时。\n\n" + + "可能原因:\n" + + "1. LLM API 响应过慢或无响应\n" + + "2. 子代理执行的任务过于复杂\n" + + "3. 网络连接问题\n\n" + + "建议:\n" + + "- 简化任务描述\n" + + "- 检查网络连接\n" + + "- 查看子代理日志了解详情"; + } + throw new RuntimeException(e); + } + } + @ToolMapping(name = "create_agent", - description = "动态创建一个新的子代理。创建的子代理将保存到 .soloncode/agents/ 目录并立即可用。") + description = "动态创建一个新的子代理。") public String createAgent( - @Param(name = "code", description = "子代理的唯一标识码(如 'my-custom-agent')") String code, + @Param(name = "code", description = "子代理的唯一标识码") String code, @Param(name = "name", description = "子代理的显示名称") String name, @Param(name = "description", description = "子代理的功能描述") String description, - @Param(name = "systemPrompt", description = "子代理的系统提示词(行为指令)") String systemPrompt, - @Param(name = "model", required = false, description = "可选。指定使用的模型(如 'gpt-4')") String model, - @Param(name = "tools", required = false, description = "可选。允许使用的工具列表,逗号分隔(如 'read,write,grep')") String tools, - @Param(name = "skills", required = false, description = "可选。启用的技能列表,逗号分隔") String skills, - @Param(name = "maxTurns", required = false, description = "可选。最大对话轮数限制") Integer maxTurns, - @Param(name = "saveToFile", required = false, description = "可选。是否保存到文件(默认 true)") Boolean saveToFile, + @Param(name = "systemPrompt", description = "子代理的系统提示词") String systemPrompt, + @Param(name = "model", required = false) String model, + @Param(name = "tools", required = false) String tools, + @Param(name = "skills", required = false) String skills, + @Param(name = "maxTurns", required = false) Integer maxTurns, + @Param(name = "saveToFile", required = false) Boolean saveToFile, String __cwd ) { try { - // 1. 构建 SubAgentMetadata SubAgentMetadata metadata = new SubAgentMetadata(); metadata.setCode(code); metadata.setName(name); metadata.setDescription(description); metadata.setEnabled(true); - // 可选参数 if (model != null && !model.isEmpty()) { metadata.setModel(model); } @@ -230,27 +342,15 @@ public class TaskSkill extends AbsSkill { metadata.setMaxTurns(maxTurns); } - // 2. 生成完整的 agent 定义(YAML frontmatter + system prompt) String agentDefinition = metadata.toYamlFrontmatterWithPrompt(systemPrompt); - // 3. 保存到文件(默认保存) boolean shouldSave = saveToFile == null || saveToFile; - String filePath = null; - if (shouldSave) { Path agentsDir = Paths.get(__cwd, ".soloncode", "agents"); - - // 确保目录存在 if (!Files.exists(agentsDir)) { Files.createDirectories(agentsDir); - LOG.info("创建 agents 目录: {}", agentsDir); } - - // 使用 code 作为文件名 - String fileName = code + ".md"; - Path agentFile = agentsDir.resolve(fileName); - filePath = agentFile.toString(); - + Path agentFile = agentsDir.resolve(code + ".md"); try (BufferedWriter writer = new BufferedWriter( new OutputStreamWriter( @@ -258,38 +358,24 @@ public class TaskSkill extends AbsSkill { StandardCharsets.UTF_8))) { writer.write(agentDefinition); } - LOG.info("Agent 定义已保存到: {}", filePath); + LOG.info("Agent 定义已保存到: {}", agentFile); } - // 4. 动态创建并注册新的子代理 AbsSubagent newAgent = new GeneralPurposeSubagent(mainAgent, code); newAgent.setDescription(description); newAgent.setSystemPrompt(agentDefinition); newAgent.refresh(); - - // 注册到 manager manager.addSubagent(newAgent); - LOG.info("动态创建子代理成功: code={}, name={}", code, name); - - // 5. 返回结果 - StringBuilder result = new StringBuilder(); - result.append("[OK] 子代理创建成功!\n\n"); - result.append(String.format("**代码**: %s\n", code)); - result.append(String.format("**名称**: %s\n", name)); - result.append(String.format("**描述**: %s\n", description)); - - if (filePath != null) { - result.append(String.format("**文件**: %s\n", filePath)); - } - - result.append(String.format("\n现在可以使用 `task(subagent_type=\"%s\", prompt=\"...\")` 来调用这个子代理。\n", code)); - - return result.toString(); + return "[OK] 子代理创建成功!\n\n" + + String.format("**代码**: %s\n", code) + + String.format("**名称**: %s\n", name) + + String.format("**描述**: %s\n", description) + + String.format("\n现在可以使用 `task(subagentType=\"%s\", prompt=\"...\")` 来调用。", code); } catch (Throwable e) { LOG.error("创建子代理失败: code={}, error={}", code, e.getMessage(), e); return "ERROR: 创建子代理失败: " + e.getMessage(); } } -} \ No newline at end of file +} diff --git a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/teams/AgentTeamsSkill.java b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/teams/AgentTeamsSkill.java index e5b474c..3288f9a 100644 --- a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/teams/AgentTeamsSkill.java +++ b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/teams/AgentTeamsSkill.java @@ -86,12 +86,6 @@ public class AgentTeamsSkill extends AbsSkill { } } - /** - * 简化构造函数(兼容性) - */ - public AgentTeamsSkill(MainAgent mainAgent, SubagentManager manager) { - this(mainAgent, null, manager); - } @Override public String description() { @@ -146,8 +140,10 @@ public class AgentTeamsSkill extends AbsSkill { sb.append("### 核心能力\n"); sb.append("1. **团队协作任务**: 使用 `team_task()` 启动多代理协作\n"); sb.append("2. **任务管理**: \n"); - sb.append(" - `create_task()` 创建任务,`team_status()` 查看状态\n"); - sb.append(" - `claim_task()` 认领,`complete_task()` 完成,`fail_task()` 失败\n"); + sb.append(" - `analyze_tasks()` 分析任务(返回分解建议,JSON格式)\n"); + sb.append(" - `create_task()` 单个创建,`create_tasks()` 批量创建,`remove_task()` 删除\n"); + sb.append(" - `team_status()` 查看状态,`claim_task()` 认领,`release_task()` 释放\n"); + sb.append(" - `complete_task()` 完成,`fail_task()` 失败\n"); sb.append(" - `list_all_tasks()` 列出所有,`get_claimable_tasks()` 获取可认领\n"); sb.append("3. **子代理调用**: 使用 `task()` 委派专门任务(支持会话续接)【强制使用】\n"); sb.append("4. **团队成员管理**: 使用 `teammate()` 创建,`teammates()` 列出,`remove_teammate()` 移除\n"); @@ -203,7 +199,7 @@ public class AgentTeamsSkill extends AbsSkill { sb.append("- **浏览器**: `browser_screenshot`, `browser_interact`, `browser_navigate`(由子代理提供)\n"); sb.append("- **记忆管理**: `memory_store`, `memory_recall`, `memory_stats`\n"); sb.append("- **工作记忆**: `get_working_memory`, `update_working_memory`\n"); - sb.append("- **任务管理**: `create_task`, `team_status`, `claim_task`, `complete_task`, `list_all_tasks`\n"); + sb.append("- **任务管理**: `analyze_tasks`, `create_task`, `create_tasks`, `remove_task`, `team_status`, `claim_task`, `release_task`, `complete_task`, `fail_task`, `list_all_tasks`\n"); sb.append("- **团队协作**: `task`(子代理调用), `teammate`, `teammates`, `remove_teammate`\n"); sb.append("- **代理通信**: `send_message`, `list_agents`, `get_message_stats`\n"); sb.append("- **团队命名**: `suggest_team_name`\n\n"); @@ -373,9 +369,6 @@ public class AgentTeamsSkill extends AbsSkill { .doOnNext(chunk -> { String content = chunk.getContent(); if (content != null && !content.isEmpty()) { - // 实时输出到控制台 - System.out.print(content); - System.out.flush(); streamingOutput.append(content); } }) @@ -585,6 +578,268 @@ public class AgentTeamsSkill extends AbsSkill { } } + /** + * 分析任务(使用 LLM 分解任务) + * + * 将用户请求分解为多个子任务,返回 JSON 格式的任务建议 + * Agent 可以根据建议逐个调用 create_task 创建任务 + */ + @ToolMapping(name = "analyze_tasks", + description = "分析用户请求,将其分解为多个子任务。返回任务分解建议(JSON格式),Agent可以根据建议使用create_task创建任务。") + public String analyzeTasks( + @Param(name = "request", description = "用户请求或任务描述") String request) { + try { + if (mainAgent == null) { + return "[WARN] MainAgent 未初始化"; + } + + // 构建 LLM 分析提示词 + String prompt = buildTaskAnalysisPrompt(request); + + // 调用 LLM 分析 + org.noear.solon.ai.chat.prompt.Prompt llmPrompt = + org.noear.solon.ai.chat.prompt.Prompt.of(prompt); + + // 通过 kernel 获取 ChatModel + if (kernel == null) { + return "[WARN] Kernel 未初始化"; + } + + org.noear.solon.ai.chat.ChatModel chatModel = kernel.getChatModel(); + if (chatModel == null) { + return "[WARN] ChatModel 未初始化"; + } + + String response = chatModel.prompt(llmPrompt).call().getContent(); + + // 解析并格式化响应 + return formatAnalysisResponse(response); + + } catch (Exception e) { + LOG.error("任务分析失败", e); + return "[ERROR] 任务分析失败: " + e.getMessage(); + } + } + + /** + * 构建任务分析提示词 + */ + private String buildTaskAnalysisPrompt(String request) { + StringBuilder sb = new StringBuilder(); + sb.append("# 任务分解分析\n\n"); + sb.append("你是一个任务规划专家。请分析用户请求,将其分解为具体的子任务。\n\n"); + sb.append("## 用户请求\n").append(request).append("\n\n"); + sb.append("## 输出要求\n"); + sb.append("请以 JSON 格式返回任务列表,格式如下:\n"); + sb.append("```json\n"); + sb.append("[\n"); + sb.append(" {\n"); + sb.append(" \"title\": \"任务标题\",\n"); + sb.append(" \"description\": \"任务详细描述\",\n"); + sb.append(" \"type\": \"DEVELOPMENT|EXPLORATION|TESTING|ANALYSIS|DOCUMENTATION\",\n"); + sb.append(" \"priority\": 1-10\n"); + sb.append(" }\n"); + sb.append("]\n"); + sb.append("```\n\n"); + sb.append("## 任务类型\n"); + sb.append("- DEVELOPMENT: 代码开发\n"); + sb.append("- EXPLORATION: 代码探索\n"); + sb.append("- TESTING: 测试相关\n"); + sb.append("- ANALYSIS: 分析诊断\n"); + sb.append("- DOCUMENTATION: 文档编写\n\n"); + sb.append("只返回 JSON,不要其他解释。"); + return sb.toString(); + } + + /** + * 格式化分析响应 + */ + private String formatAnalysisResponse(String response) { + StringBuilder sb = new StringBuilder(); + sb.append("[OK] 任务分析完成\n\n"); + sb.append("**任务分解建议**(JSON格式):\n\n"); + sb.append("```json\n"); + sb.append(response); + sb.append("\n```\n\n"); + sb.append("**使用方法**:\n"); + sb.append("根据上述建议,使用 create_task() 逐个创建任务。\n"); + sb.append("例如:\n"); + sb.append("```bash\n"); + sb.append("create_task(\n"); + sb.append(" title=\"探索代码库\",\n"); + sb.append(" description=\"分析项目结构\",\n"); + sb.append(" type=\"EXPLORATION\",\n"); + sb.append(" priority=8\n"); + sb.append(")\n"); + sb.append("```\n"); + return sb.toString(); + } + + /** + * 批量创建任务 + * + * 根据 JSON 格式的任务列表批量创建任务 + */ + @ToolMapping(name = "create_tasks", + description = "批量创建任务。接受 JSON 格式的任务列表,一次性创建多个任务。可以与 analyze_tasks 配合使用。") + public String createTasks( + @Param(name = "tasksJson", description = "JSON格式的任务列表,例如:[{\"title\":\"任务1\",\"description\":\"描述\",\"type\":\"DEVELOPMENT\",\"priority\":8}]") String tasksJson) { + try { + if (mainAgent == null) { + return "[WARN] MainAgent 未初始化"; + } + + SharedTaskList taskList = mainAgent.getTaskList(); + List tasks = parseTasksJson(tasksJson); + + if (tasks.isEmpty()) { + return "[WARN] 没有有效的任务可创建"; + } + + // 批量添加任务 + List added = taskList.addTasks(tasks).join(); + + StringBuilder sb = new StringBuilder(); + sb.append("[OK] 批量创建任务完成\n\n"); + sb.append("**成功创建**: ").append(added.size()).append(" 个任务\n\n"); + + for (int i = 0; i < added.size(); i++) { + TeamTask task = added.get(i); + sb.append(String.format("%d. `%s` (ID: %s, 类型: %s, 优先级: %d)\n", + i + 1, task.getTitle(), task.getId(), task.getType(), task.getPriority())); + } + + return sb.toString(); + + } catch (Exception e) { + LOG.error("批量创建任务失败", e); + return "[ERROR] 批量创建失败: " + e.getMessage(); + } + } + + /** + * 解析 JSON 格式的任务列表 + */ + private List parseTasksJson(String json) { + List tasks = new ArrayList<>(); + // 简化实现:提取 JSON 数组中的任务对象 + // 实际项目建议使用 Jackson/Gson + + try { + // 提取数组内容 + int start = json.indexOf("["); + int end = json.lastIndexOf("]"); + if (start >= 0 && end > start) { + String content = json.substring(start + 1, end).trim(); + if (content.isEmpty()) return tasks; + + // 简单解析(假设格式正确) + String[] objects = content.split("\\},\\s*\\{"); + for (int i = 0; i < objects.length; i++) { + String obj = objects[i]; + if (!obj.startsWith("{")) obj = "{" + obj; + if (!obj.endsWith("}")) obj = obj + "}"; + + TeamTask task = parseSingleTaskJson(obj, i); + if (task != null) { + tasks.add(task); + } + } + } + } catch (Exception e) { + LOG.warn("解析任务 JSON 失败: {}", e.getMessage()); + } + return tasks; + } + + /** + * 解析单个任务 JSON + */ + private TeamTask parseSingleTaskJson(String json, int index) { + try { + String title = extractJsonValue(json, "title"); + String description = extractJsonValue(json, "description"); + String typeStr = extractJsonValue(json, "type"); + String priorityStr = extractJsonValue(json, "priority"); + + if (title == null || title.isEmpty()) { + title = "任务 " + (index + 1); + } + if (description == null || description.isEmpty()) { + description = title; + } + + TeamTask.TaskType type = TeamTask.TaskType.DEVELOPMENT; + if (typeStr != null) { + try { + type = TeamTask.TaskType.valueOf(typeStr.toUpperCase()); + } catch (IllegalArgumentException e) { + // 使用默认类型 + } + } + + int priority = 7; + if (priorityStr != null) { + try { + priority = Integer.parseInt(priorityStr.trim()); + priority = Math.max(1, Math.min(10, priority)); + } catch (NumberFormatException e) { + // 使用默认优先级 + } + } + + String taskId = "task-" + System.currentTimeMillis() + "-" + index; + return TeamTask.builder() + .id(taskId) + .title(cleanJsonString(title)) + .description(cleanJsonString(description)) + .type(type) + .priority(priority) + .dependencies(new ArrayList<>()) + .build(); + + } catch (Exception e) { + LOG.warn("解析单个任务失败: {}, error: {}", json, e.getMessage()); + return null; + } + } + + /** + * 从 JSON 提取字段值 + */ + private String extractJsonValue(String json, String key) { + String searchKey = "\"" + key + "\""; + int keyIndex = json.indexOf(searchKey); + if (keyIndex < 0) { + searchKey = "'" + key + "'"; + keyIndex = json.indexOf(searchKey); + } + if (keyIndex < 0) return null; + + int colonIndex = json.indexOf(":", keyIndex); + if (colonIndex < 0) return null; + + int valueStart = json.indexOf("\"", colonIndex); + if (valueStart < 0) return null; + + valueStart++; + int valueEnd = json.indexOf("\"", valueStart); + if (valueEnd < 0) return null; + + return json.substring(valueStart, valueEnd); + } + + /** + * 清理 JSON 字符串 + */ + private String cleanJsonString(String str) { + if (str == null) return ""; + return str.replace("\\\"", "\"") + .replace("\\n", "\n") + .replace("\\t", "\t") + .trim(); + } + /** * 获取状态图标 */ @@ -1398,6 +1653,88 @@ public class AgentTeamsSkill extends AbsSkill { } } + /** + * 删除任务 + */ + @ToolMapping(name = "remove_task", + description = "删除指定的任务。注意:只能删除状态为 PENDING 的任务,已认领的任务需要先释放才能删除。") + public String removeTask( + @Param(name = "taskId", description = "任务ID") String taskId) { + try { + if (mainAgent == null) { + return "[WARN] MainAgent 未初始化"; + } + + SharedTaskList taskList = mainAgent.getTaskList(); + TeamTask task = taskList.getTask(taskId); + + if (task == null) { + return "[ERROR] 任务不存在: " + taskId; + } + + // 检查任务状态 + if (task.getStatus() == TeamTask.Status.IN_PROGRESS) { + return "[WARN] 任务正在执行中,无法删除。请先使用 release_task 释放任务。"; + } + + boolean removed = taskList.removeTask(taskId); + + if (removed) { + LOG.info("任务已删除: taskId={}", taskId); + return "[OK] 任务已删除: " + taskId; + } else { + return "[ERROR] 删除任务失败: " + taskId; + } + } catch (Exception e) { + LOG.error("删除任务失败", e); + return "[ERROR] 删除失败: " + e.getMessage(); + } + } + + /** + * 释放任务 + */ + @ToolMapping(name = "release_task", + description = "释放已认领的任务(将任务状态重新改为PENDING,其他代理可以认领)。用于取消当前认领或让其他代理接管。") + public String releaseTask( + @Param(name = "taskId", description = "任务ID") String taskId, + @Param(name = "agentName", description = "当前认领该任务的代理名称") String agentName) { + try { + if (mainAgent == null) { + return "[WARN] MainAgent 未初始化"; + } + + SharedTaskList taskList = mainAgent.getTaskList(); + TeamTask task = taskList.getTask(taskId); + + if (task == null) { + return "[ERROR] 任务不存在: " + taskId; + } + + // 检查任务状态 + if (task.getStatus() != TeamTask.Status.IN_PROGRESS) { + return "[WARN] 任务状态不是进行中,无法释放: " + task.getStatus(); + } + + // 检查认领者 + if (!agentName.equals(task.getClaimedBy())) { + return "[WARN] 不是任务的认领者。当前认领者: " + task.getClaimedBy(); + } + + boolean released = taskList.releaseTask(taskId, agentName); + + if (released) { + LOG.info("任务已释放: taskId={}, agent={}", taskId, agentName); + return "[OK] 任务已释放: " + taskId + ",其他代理现在可以认领"; + } else { + return "[ERROR] 释放任务失败: " + taskId; + } + } catch (Exception e) { + LOG.error("释放任务失败", e); + return "[ERROR] 释放失败: " + e.getMessage(); + } + } + /** * 获取任务详情 */ @@ -1805,7 +2142,7 @@ public class AgentTeamsSkill extends AbsSkill { // 检查是否是旧的时间戳格式(如 team-1736640123456) // 注意:新版本已改用智能生成,此处仅用于迁移旧数据 if (oldTeamName.matches("^team-\\d+$")) { - sb.append("⚠️ **当前团队名**: `").append(oldTeamName).append("`\n\n"); + sb.append("**当前团队名**: `").append(oldTeamName).append("`\n\n"); sb.append("**问题**: 检测到旧版本时间戳格式,已不够语义化。\n"); sb.append("**说明**: 新版本已改用智能语义化生成(如 database-team、security-squad)。\n\n"); @@ -1821,7 +2158,7 @@ public class AgentTeamsSkill extends AbsSkill { // 生成建议 String suggestedName = TeamNameGenerator.suggestBetterName(oldTeamName, memberNames); if (suggestedName != null) { - sb.append("✅ **建议团队名**: `").append(suggestedName).append("`\n\n"); + sb.append("**建议团队名**: `").append(suggestedName).append("`\n\n"); sb.append("**建议描述**: ") .append(TeamNameGenerator.getTeamDescription(suggestedName)) .append("\n\n"); @@ -1831,15 +2168,15 @@ public class AgentTeamsSkill extends AbsSkill { sb.append("**如何重命名**:\n"); sb.append("使用 `create_team` 工具创建新团队,并指定语义化的 teamName。\n"); } else { - sb.append("✅ **当前团队名**: `").append(oldTeamName).append("`\n\n"); + sb.append("**当前团队名**: `").append(oldTeamName).append("`\n\n"); // 检查团队名是否有效 if (!TeamNameGenerator.isValidTeamName(oldTeamName)) { - sb.append("⚠️ **问题**: 团队名格式不符合规范(只允许小写字母、数字和连字符)\n\n"); + sb.append("**问题**: 团队名格式不符合规范(只允许小写字母、数字和连字符)\n\n"); String normalized = TeamNameGenerator.normalizeTeamName(oldTeamName); - sb.append("✅ **规范化建议**: `").append(normalized).append("`\n\n"); + sb.append("**规范化建议**: `").append(normalized).append("`\n\n"); } else { - sb.append("✅ 团队名格式正确!\n\n"); + sb.append("团队名格式正确!\n\n"); // 提取领域 String domain = TeamNameGenerator.extractDomainFromTeamName(oldTeamName); diff --git a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/teams/MainAgent.java b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/teams/MainAgent.java index a1b945a..a776f00 100644 --- a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/teams/MainAgent.java +++ b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/teams/MainAgent.java @@ -85,6 +85,7 @@ public class MainAgent { private final SubagentManager subagentManager; private ReActAgent agent; + private ChatModel chatModel; // 保存 ChatModel 引用用于任务分析 private AgentSession session; private final AtomicBoolean running = new AtomicBoolean(false); @@ -96,10 +97,6 @@ public class MainAgent { // 性能优化:使用 CountDownLatch 替代轮询 private volatile CountDownLatch taskCompletionLatch; - // 超时配置(单位:毫秒) - private static final long EXECUTION_TIMEOUT_MS = 300_000; // 5分钟超时 - private static final long LLM_CALL_TIMEOUT_MS = 120_000; // LLM调用超时2分钟 - public MainAgent(SubAgentMetadata config, AgentSessionProvider sessionProvider, SharedMemoryManager sharedMemoryManager, @@ -187,6 +184,7 @@ public class MainAgent { builder.maxSteps(50); builder.sessionWindowSize(10); + this.chatModel = chatModel; // 保存 ChatModel 引用 this.agent = builder.build(); this.session = sessionProvider.getSession("main_agent"); @@ -223,45 +221,8 @@ public class MainAgent { // 1. 发布主代理任务开始事件 publishEvent(AgentEventType.MAIN_TASK_STARTED, prompt.getUserContent(), null); - // 2. 分析任务并创建子任务 - List subTasks = analyzeAndCreateTasks(prompt); - - // 3. 将任务添加到共享任务列表(带错误处理和验证) - if (!subTasks.isEmpty()) { - try { - // 打印依赖图 - LOG.debug("任务依赖关系图:\n{}", taskList.getDependencyGraph()); - - // 检测循环依赖 - List cyclicTasks = taskList.detectCyclicDependencies(); - if (!cyclicTasks.isEmpty()) { - LOG.error("检测到循环依赖,任务将被拒绝:"); - for (TeamTask task : cyclicTasks) { - LOG.error(" - {}", task.getTitle()); - } - throw new IllegalStateException("存在循环依赖,无法添加任务"); - } - - List added = taskList.addTasks(subTasks).join(); - LOG.info("主代理已添加 {} 个子任务到共享任务列表", added.size()); - - // 4. 广播任务可用通知 - broadcastTaskNotification(added); - - // 5. 记录可认领任务数 - List claimableTasks = taskList.getClaimableTasks(); - LOG.info("当前可认领任务数: {} / {}", claimableTasks.size(), added.size()); - - } catch (IllegalStateException e) { - LOG.error("添加任务失败: {}", e.getMessage()); - // 继续执行主代理自身任务 - } catch (Exception e) { - LOG.error("添加任务时发生异常: {}", e.getMessage(), e); - // 继续执行主代理自身任务 - } - } - - // 6. 执行主代理内部的协调逻辑(流式输出) + // 2. 执行主代理内部的协调逻辑(流式输出) + // 注意:任务分解现在由 Agent 通过工具自主决定 reactor.core.publisher.Flux responseStream = agent.prompt(prompt) .session(session) .options(o -> { @@ -273,7 +234,7 @@ public class MainAgent { .stream(); // 7. 在流完成后等待所有子任务完成 - reactor.core.publisher.Flux resultStream = responseStream + Flux resultStream = responseStream .doOnComplete(() -> { try { // 等待所有子任务完成 @@ -336,148 +297,6 @@ public class MainAgent { } } - /** - * 分析任务并创建子任务 - */ - private List analyzeAndCreateTasks(Prompt prompt) { - List tasks = new ArrayList<>(); - String userPrompt = prompt.getUserContent().toLowerCase(); - - // 从共享内存中读取相关信息(如果有历史任务结果) - StringBuilder contextBuilder = new StringBuilder(); - if (sharedMemoryManager != null) { - try { - // 查询相关的历史任务结果 - List recentMemories = - sharedMemoryManager.search("task-result", 10); - - if (!recentMemories.isEmpty()) { - contextBuilder.append("\n\n# 历史任务上下文\n\n"); - for (org.noear.solon.bot.core.memory.Memory memory : recentMemories) { - if (memory instanceof ShortTermMemory) { - ShortTermMemory stm = (ShortTermMemory) memory; - String taskTitle = (String) stm.getMetadata("taskTitle"); - contextBuilder.append(String.format("- **%s**: %s\n", - taskTitle != null ? taskTitle : "Unknown", - stm.getContext())); - } - } - LOG.debug("从共享内存加载了 {} 条历史任务记录", recentMemories.size()); - } - } catch (Exception e) { - LOG.warn("从共享内存读取历史任务失败", e); - } - } - - // 根据提示内容智能创建任务 - // 这里是简化版本,实际可以使用 LLM 来分析任务 - // 可以将 contextBuilder 中的内容添加到任务的描述中 - - if (userPrompt.contains("探索") || userPrompt.contains("explore") || userPrompt.contains("分析")) { - // 创建探索任务链:探索 -> 搜索 - String exploreId = "task-explore-" + System.currentTimeMillis(); - String searchId = "task-search-" + System.currentTimeMillis(); - - TeamTask exploreTask = TeamTask.builder() - .id(exploreId) - .title("探索代码库") - .description("探索和分析代码库结构") - .type(TeamTask.TaskType.EXPLORATION) - .priority(8) - .build(); - - TeamTask searchTask = TeamTask.builder() - .id(searchId) - .title("搜索关键文件") - .description("搜索与任务相关的关键文件") - .type(TeamTask.TaskType.ANALYSIS) - .priority(7) - .dependencies(Collections.singletonList(exploreId)) - .build(); - - tasks.add(exploreTask); - tasks.add(searchTask); - } - - if (userPrompt.contains("实现") || userPrompt.contains("开发") || userPrompt.contains("implement")) { - // 创建开发任务链:计划 -> 实现 -> 测试 - String planId = "task-plan-" + System.currentTimeMillis(); - String implId = "task-impl-" + System.currentTimeMillis(); - String testId = "task-test-" + System.currentTimeMillis(); - - TeamTask planTask = TeamTask.builder() - .id(planId) - .title("制定实现计划") - .description("制定详细的实现计划") - .type(TeamTask.TaskType.DEVELOPMENT) - .priority(9) - .build(); - - TeamTask implTask = TeamTask.builder() - .id(implId) - .title("实现功能") - .description("根据计划实现功能") - .type(TeamTask.TaskType.DEVELOPMENT) - .priority(8) - .dependencies(Collections.singletonList(planId)) - .build(); - - TeamTask testTask = TeamTask.builder() - .id(testId) - .title("编写测试") - .description("为实现的代码编写测试") - .type(TeamTask.TaskType.TESTING) - .priority(6) - .dependencies(Collections.singletonList(implId)) - .build(); - - tasks.add(planTask); - tasks.add(implTask); - tasks.add(testTask); - } - - if (userPrompt.contains("文档") || userPrompt.contains("document")) { - // 创建文档任务 - tasks.add(TeamTask.builder() - .id("task-doc-" + System.currentTimeMillis()) - .title("生成文档") - .description("生成项目文档") - .type(TeamTask.TaskType.DOCUMENTATION) - .priority(5) - .build()); - } - - // 默认任务(如果没有匹配到特定类型) - if (tasks.isEmpty()) { - tasks.add(TeamTask.builder() - .id("task-default-" + System.currentTimeMillis()) - .title("处理用户请求") - .description(prompt.getUserContent()) - .type(TeamTask.TaskType.DEVELOPMENT) - .priority(7) - .build()); - } - - // 验证任务依赖关系 - logTaskDependencies(tasks); - - return tasks; - } - - /** - * 记录任务依赖关系(用于调试) - */ - private void logTaskDependencies(List tasks) { - if (LOG.isDebugEnabled()) { - LOG.debug("创建任务依赖关系:"); - for (TeamTask task : tasks) { - if (task.getDependencies() != null && !task.getDependencies().isEmpty()) { - LOG.debug(" {} 依赖: {}", task.getTitle(), task.getDependencies()); - } - } - } - } - /** * 广播任务可用通知 */ diff --git a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/teams/SharedTaskList.java b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/teams/SharedTaskList.java index 5543a85..4c13df4 100644 --- a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/teams/SharedTaskList.java +++ b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/teams/SharedTaskList.java @@ -15,6 +15,7 @@ */ package org.noear.solon.bot.core.teams; +import org.noear.solon.bot.core.AgentProperties; import org.noear.solon.bot.core.event.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -65,24 +66,49 @@ public class SharedTaskList { private final ExecutorService eventExecutor; // 事件发布线程池 // 性能优化:最大依赖深度限制 - private static final int MAX_DEPENDENCY_DEPTH = 100; + private int maxDependencyDepth = 100; /** - * 构造函数 + * 构造函数(使用默认配置) * * @param eventBus 事件总线 */ public SharedTaskList(EventBus eventBus) { - this(eventBus, 100); + this(eventBus, null, null, 100); } /** - * 完整构造函数 + * 完整构造函数(直接指定参数) * * @param eventBus 事件总线 * @param maxCompletedTasks 最大保留已完成任务数 + * @deprecated 使用配置构造函数 {@link #SharedTaskList(EventBus, AgentProperties.TeamsConfig)} */ public SharedTaskList(EventBus eventBus, int maxCompletedTasks) { + this(eventBus, null, null, maxCompletedTasks); + } + + /** + * 配置构造函数 + * + * @param eventBus 事件总线 + * @param config Teams 配置 + */ + public SharedTaskList(EventBus eventBus, AgentProperties.TeamsConfig config) { + this(eventBus, + config != null ? config.taskExecutorThreads : null, + config != null ? config.eventExecutorThreads : null, + config != null ? config.maxCompletedTasks : 100); + + if (config != null && config.maxDependencyDepth > 0) { + this.maxDependencyDepth = config.maxDependencyDepth; + } + } + + /** + * 内部构造函数 + */ + private SharedTaskList(EventBus eventBus, Integer taskThreads, Integer eventThreads, int maxCompletedTasks) { this.tasks = new ConcurrentHashMap<>(); this.pendingTasks = new ConcurrentHashMap<>(); this.agentTasks = new ConcurrentHashMap<>(); @@ -93,12 +119,16 @@ public class SharedTaskList { this.maxCompletedTasks = maxCompletedTasks; this.completedTaskQueue = new LinkedList<>(); - // 创建专用线程池 - int poolSize = Math.max(10, Runtime.getRuntime().availableProcessors() * 2); - this.taskExecutor = Executors.newFixedThreadPool(poolSize, new NamedThreadFactory("SharedTaskList")); - this.eventExecutor = Executors.newSingleThreadExecutor(new NamedThreadFactory("SharedTaskList-Event")); + // 创建专用线程池(使用配置或默认值) + int taskPoolSize = taskThreads != null ? taskThreads : + Math.max(10, Runtime.getRuntime().availableProcessors() * 2); + int eventPoolSize = eventThreads != null ? eventThreads : 1; + + this.taskExecutor = Executors.newFixedThreadPool(taskPoolSize, new NamedThreadFactory("SharedTaskList")); + this.eventExecutor = Executors.newFixedThreadPool(eventPoolSize, new NamedThreadFactory("SharedTaskList-Event")); - LOG.info("SharedTaskList 初始化完成 (线程池大小: {}, 使用细粒度锁)", poolSize); + LOG.info("SharedTaskList 初始化完成 (任务线程池: {}, 事件线程池: {}, 使用细粒度锁)", + taskPoolSize, eventPoolSize); } /** @@ -165,7 +195,7 @@ public class SharedTaskList { } // 检测循环依赖(带深度限制) - if (hasCyclicDependency(task, tasks::get, MAX_DEPENDENCY_DEPTH)) { + if (hasCyclicDependency(task, tasks::get, maxDependencyDepth)) { throw new IllegalArgumentException("检测到循环依赖或依赖深度超过限制: " + task.getTitle()); } @@ -272,9 +302,8 @@ public class SharedTaskList { } // 第五阶段:触发事件(释放锁后触发) - List finalAdded = added; CompletableFuture.runAsync(() -> { - for (TeamTask task : finalAdded) { + for (TeamTask task : added) { publishTaskEvent(AgentEventType.TASK_CREATED, task, null); } }, eventExecutor); @@ -402,44 +431,6 @@ public class SharedTaskList { }, taskExecutor); } - /** - * 智能认领(自动选择最佳任务) - * - * @param agentId Agent ID - * @return 认领的任务,无任务可认领返回 null - */ - public CompletableFuture smartClaim(String agentId) { - return CompletableFuture.supplyAsync(() -> { - // 获取可认领的任务(不加锁,使用 ConcurrentHashMap) - List claimable = getClaimableTasks(); - - if (claimable.isEmpty()) { - return null; - } - - // 按优先级排序(高优先级优先) - claimable.sort((a, b) -> Integer.compare(b.getPriority(), a.getPriority())); - - // 修复:尝试认领任务,失败则尝试下一个(避免竞争导致全部失败) - for (TeamTask task : claimable) { - try { - Boolean claimed = claimTask(task.getId(), agentId).join(); - if (claimed) { - LOG.debug("Agent {} 成功认领任务: {}", agentId, task.getTitle()); - return task; - } - // 任务被其他代理认领,尝试下一个 - LOG.debug("任务 {} 已被其他代理认领,尝试下一个", task.getTitle()); - } catch (Exception e) { - LOG.warn("认领任务 {} 时发生异常,尝试下一个", task.getTitle(), e); - } - } - - // 所有任务都认领失败 - LOG.debug("Agent {} 未能认领任何任务", agentId); - return null; - }, taskExecutor); - } /** * 释放任务 -- Gitee