From 1b7ef87338c038df38885be4baa8dcd9de796155 Mon Sep 17 00:00:00 2001 From: bai <1145000687@qq.com> Date: Fri, 13 Mar 2026 18:19:23 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feat(teams):=20=E9=87=8D=E6=9E=84Agent=20Te?= =?UTF-8?q?ams=E6=9E=B6=E6=9E=84=E5=B9=B6=E5=A2=9E=E5=BC=BA=E5=8D=8F?= =?UTF-8?q?=E4=BD=9C=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将MainAgent更名为Team Lead,强化协调决策角色 - 新增简化工具task_add和tasks_add,减少80%参数配置 - 实现teammate_quick快速创建功能,优化团队成员管理 - 集成SharedMemoryManager支持团队成员记忆共享 - 更新文档说明新的工作流程和架构设计 - 优化team_task工具描述,明确适用场景和使用指引 - 增加自动记忆管理工具集成,提升协作效率 --- AGENT_TEAMS.md | 590 ++++++++++-------- .../org/noear/solon/bot/core/AgentKernel.java | 25 +- .../noear/solon/bot/core/AgentProperties.java | 8 +- .../solon/bot/core/subagent/TaskSkill.java | 3 +- .../solon/bot/core/teams/AgentTeamsSkill.java | 245 +++++--- .../noear/solon/bot/core/teams/MainAgent.java | 182 ++++-- .../bot/core/teams/SubAgentAgentBuilder.java | 22 +- 7 files changed, 644 insertions(+), 431 deletions(-) diff --git a/AGENT_TEAMS.md b/AGENT_TEAMS.md index 2829bce..00b043e 100644 --- a/AGENT_TEAMS.md +++ b/AGENT_TEAMS.md @@ -2,337 +2,397 @@ ## 概述 -Agent Teams 是一种多代理协作模式,通过 **MainAgent(协调器)** 和 **SubAgents(执行者)** 的分工协作,实现复杂任务的自动化分解和执行。 +Agent Teams 是一种多代理协作模式,通过 **Team Lead(团队领导)** 协调 **SubAgents(执行者)** 的分工协作,实现复杂任务的自动化分解和执行。 --- ## 架构设计 +### 整体架构 + ``` -┌─────────────────────────────────────────────────────────────────┐ -│ 用户请求 │ -│ "实现用户登录功能" │ -└─────────────────────────────────────────────────────────────────┘ +┌─────────────────────────────────────────────────────────┐ +│ 主 ReActAgent │ +│ (用户交互入口) │ +│ │ +│ ┌─────────────────────────────────────────────────┐ │ +│ │ Team Lead 系统提示词 │ │ +│ │ - 任务分解 │ │ +│ │ - 团队协作 │ │ +│ │ - 结果汇总 │ │ +│ └─────────────────────────────────────────────────┘ │ +│ │ +│ 技能集: │ +│ ├── AgentTeamsSkill (团队协作工具) │ +│ ├── TaskSkill (子代理调用) │ +│ └── ... (其他技能) │ +└─────────────────────────────────────────────────────────┘ │ + │ 调用 team_task() ▼ -┌─────────────────────────────────────────────────────────────────┐ -│ MainAgent (协调器) │ -│ - 分析任务需求 │ -│ - 创建子任务 │ -│ - 协调 SubAgents │ -│ - 汇总执行结果 │ -└─────────────────────────────────────────────────────────────────┘ - │ - ┌───────────────┼───────────────┐ - │ │ │ - ▼ ▼ ▼ -┌──────────────┐ ┌──────────────┐ ┌──────────────┐ -│ ExploreSub │ │ PlanSub │ │ BashSub │ -│ 探索代码库 │ │ 设计方案 │ │ 执行命令 │ -└──────────────┘ └──────────────┘ └──────────────┘ - │ │ │ - └───────────────┴───────────────┘ +┌─────────────────────────────────────────────────────────┐ +│ MainAgent │ +│ (团队协调器) │ +│ │ +│ 基础设施: │ +│ ├── SharedTaskList (任务池) │ +│ ├── SharedMemoryManager (记忆管理) │ +│ ├── EventBus (事件总线) │ +│ └── MessageChannel (消息通道) │ +└─────────────────────────────────────────────────────────┘ │ + │ 调用子代理 ▼ - ┌─────────────────────┐ - │ SharedTaskList │ - │ (共享任务队列) │ - └─────────────────────┘ +┌─────────────────────────────────────────────────────────┐ +│ SubAgent │ +│ (执行者) │ +│ ├── ExploreSubagent (代码探索) │ +│ ├── PlanSubagent (方案设计) │ +│ ├── BashSubagent (命令执行) │ +│ ├── GeneralPurposeSubagent (通用任务) │ +│ └── ... (自定义 teammates) │ +└─────────────────────────────────────────────────────────┘ +``` + +### 工作流程 + +``` +用户请求 → Team Lead 分析 → 创建主任务 → 分解子任务 + ↓ + ┌───────────────┴───────────────┐ + │ │ + ▼ ▼ + 简单任务 复杂任务 + │ │ + ▼ ▼ + 直接回答 team_task() + ↓ + ┌────────────────────────────────┐ + │ SubAgent 协作执行 │ + │ - explore: 探索代码 │ + │ - plan: 设计方案 │ + │ - bash: 执行命令 │ + │ - general-purpose: 通用任务 │ + └────────────────────────────────┘ + ↓ + ┌────────────────────────────────┐ + │ 汇总结果并返回 │ + └────────────────────────────────┘ ``` --- ## 核心组件 -### 1. MainAgent(主代理) +### 1. Team Lead(团队领导) -**职责**: 任务协调和决策 +**角色**: 协调决策者 -**功能**: -- 接收用户请求,分析任务复杂度 -- 将复杂任务分解为多个子任务 -- 协调 SubAgents 认领和执行任务 -- 汇总结果并生成最终回复 +**核心职责**: +- 分析任务复杂度,决定执行方式 +- 简单任务直接回答 +- 复杂任务启动团队协作 +- 协调 SubAgents 执行 +- 汇总结果 -### 2. SubAgents(子代理) +### 2. MainAgent(协调器) -**内置类型**: +**职责**: 提供协作基础设施 -| 子代理 | 代码 | 专长 | 最大步数 | -|--------|------|------|----------| -| 探索代理 | `explore` | 快速定位文件、理解代码结构 | 15 | -| 规划代理 | `plan` | 任务分解、实现方案设计 | 20 | -| 命令代理 | `bash` | Git 操作、构建、测试 | 10 | -| 通用代理 | `general-purpose` | 复杂多步骤任务 | 25 | +**功能**: +- SharedTaskList: 任务池管理 +- SharedMemoryManager: 记忆共享 +- EventBus: 事件通知 +- MessageChannel: 消息传递 -### 3. SharedTaskList(共享任务队列) +### 3. SubAgents(执行者) -**功能**: -- 存储所有团队任务 -- 支持任务依赖关系 -- 自动分配可认领任务 -- 追踪任务状态和进度 +**内置类型**: + +| 子代理 | 类型 | 专长 | 场景 | +|--------|------|------|------| +| 探索代理 | `explore` | 快速定位文件、理解代码结构 | 代码探索 | +| 规划代理 | `plan` | 任务分解、实现方案设计 | 方案设计 | +| 命令代理 | `bash` | Git 操作、构建、测试 | 命令执行 | +| 通用代理 | `general-purpose` | 复杂多步骤任务 | 通用场景 | --- -## Teammate 管理(团队成员) +## 团队成员管理 + +### 快速创建(推荐) + +```bash +# 只需 name + role,其他智能默认 +teammate_quick(name="security-expert", role="安全专家") + +# 输出: +# [OK] 团队成员创建成功 +# +# ## 成员信息 +# +# | 属性 | 值 | +# |------|------| +# | **名称** | `security-expert` | +# | **角色** | 安全专家 | +# | **描述** | 安全专家 | +# | **所属团队** | security-team (智能生成) | +# | **状态** | 🟢 已激活 | +``` -类似 Claude Code 的 `/teammate` 功能,支持动态创建和管理团队成员。 -### 创建团队成员 +### 完整自定义创建 -```java -// 通过 AgentTeamsSkill 创建新成员 +```bash +# 需要完整自定义时使用 teammate( - name="security-expert", - role="安全专家", - description="专注于安全审计、漏洞检测和合规性检查", - expertise="security,auth,encryption", - model="gpt-4" + name="my-expert", + role="专家", + description="详细描述", + expertise="skill1,skill2", + tools="read,write,edit", + skills="skill1,skill2" ) ``` -**输出格式**(表格): -``` -✅ 团队成员创建成功 +### 查看团队成员 -## 成员信息 +```bash +# 列出所有成员 +teammates() -| 属性 | 值 | -|------|------| -| **名称** | `security-expert` | -| **角色** | 安全专家 | -| **描述** | 专注于安全审计、漏洞检测和合规性检查 | -| **专业领域** | security,auth,encryption | -| **模型** | gpt-4 | -| **文件** | `.soloncode/agents/security-expert.md` | -| **状态** | 🟢 已激活 | +# 按团队筛选 +teammates(teamName="security-team") ``` -### 查看所有成员 +### 移除成员 -```java -// 列出所有团队成员(表格格式) -teammates() +```bash +remove_teammate("security-expert") ``` -**输出格式**: -``` -## 团队成员 +--- -| 名称 | 角色 | 描述 | 状态 | 模型 | -|------|------|------|------|------| -| `explore` | Explore | 代码探索专家 | 🟢 活跃 | 默认 | -| `plan` | Plan | 方案设计专家 | 🟢 活跃 | 默认 | -| `security-expert` | 安全专家 | 安全审计和漏洞检测 | 🟢 活跃 | gpt-4 | +## 任务管理 -**总计**: 3 位活跃成员 -``` +### 快速添加任务(推荐) -### 移除团队成员 +```bash +# 只需标题 +task_add(title="实现登录功能") -```java -// 禁用指定成员 -remove_teammate("security-expert") +# 带描述 +task_add(title="实现登录", description="包括用户认证和权限验证") ``` ---- +### 批量添加任务 + +```bash +# 逗号分隔 +tasks_add(titles="任务1,任务2,任务3") +``` -## 简单代码实现 +### 完整任务配置 -### 基础初始化 +```bash +# 需要完整配置时使用 +create_task( + title="实现登录", + description="用户认证和权限验证", + type="DEVELOPMENT", + priority=8, + dependencies="task1,task2" +) +``` -```java -// 1. 创建 SubAgentAgentBuilder -SubAgentAgentBuilder builder = SubAgentAgentBuilder.of(chatModel) - .workDir("./work") - .sessionProvider(sessionProvider) - .poolManager(poolManager) - .addAllFrom(subagentManager); +### 查看任务状态 -// 2. 构建 MainAgent -MainAgent mainAgent = builder.build(); +```bash +# 查看团队任务状态 +team_status() -// 3. 执行团队协作任务 -AgentResponse response = mainAgent.execute( - Prompt.of("实现用户登录功能,包括探索代码、设计方案、开发实现、编写测试") -); +# 查看任务统计 +get_task_statistics() -// 4. 获取任务统计 -SharedTaskList.TaskStatistics stats = mainAgent.getTaskList().getStatistics(); -System.out.println("总任务: " + stats.totalTasks); -System.out.println("已完成: " + stats.completedTasks); +# 查看所有任务 +list_all_tasks() ``` -### 创建和管理任务 - -```java -// 获取共享任务列表 -SharedTaskList taskList = mainAgent.getTaskList(); +--- -// 创建新任务 -TeamTask exploreTask = new TeamTask(); -exploreTask.setTitle("探索现有认证代码"); -exploreTask.setDescription("分析项目中的认证相关代码结构"); -exploreTask.setType(TeamTask.TaskType.EXPLORATION); -exploreTask.setPriority(8); +## 记忆管理 -// 添加到任务队列 -CompletableFuture future = taskList.addTask(exploreTask); -TeamTask addedTask = future.join(); +### 存储记忆 -// 创建带依赖的任务 -TeamTask implTask = new TeamTask(); -implTask.setTitle("实现登录功能"); -implTask.setDependencies(Arrays.asList(addedTask.getId())); // 依赖 exploreTask +```bash +# 自动分类存储 +memory_store(content="用户登录使用 JWT Token") -taskList.addTask(implTask); +# 带键名 +memory_store(key="auth-method", content="JWT") ``` -### SubAgent 认领任务 +### 检索记忆 -```java -// SubAgent 主动认领任务 -Subagent exploreAgent = subagentManager.getAgent("explore"); +```bash +# 按关键词检索 +memory_recall(query="登录") -// 查看可认领任务 -List claimableTasks = taskList.getClaimableTasks(); - -// 认领并执行 -for (TeamTask task : claimableTasks) { - if (task.getType() == TeamTask.TaskType.EXPLORATION) { - exploreAgent.claimAndExecute(task); - break; - } -} +# 限制返回数量 +memory_recall(query="认证", limit=5) ``` -### 监听任务事件 - -```java -// 订阅任务事件 -EventBus eventBus = mainAgent.getEventBus(); - -// 监听任务创建 -eventBus.subscribe(AgentEventType.TASK_CREATED, event -> { - TeamTask task = (TeamTask) event.getData(); - System.out.println("新任务创建: " + task.getTitle()); -}); - -// 监听任务完成 -eventBus.subscribe(AgentEventType.TASK_COMPLETED, event -> { - TeamTask task = (TeamTask) event.getData(); - System.out.println("任务完成: " + task.getTitle()); -}); - -// 监听任务失败 -eventBus.subscribe(AgentEventType.TASK_FAILED, event -> { - TeamTask task = (TeamTask) event.getData(); - System.out.println("任务失败: " + task.getErrorMessage()); -}); +### 查看统计 + +```bash +memory_stats() ``` --- -## 工作流程 - -``` -1. 用户请求 - ↓ -2. MainAgent 分析任务 - ↓ -3. 创建主任务并添加到 SharedTaskList - ↓ -4. MainAgent 分解任务为子任务 - ↓ -5. 子任务添加到 SharedTaskList(带依赖关系) - ↓ -6. SubAgents 扫描可认领任务 - ↓ -7. SubAgent 认领并执行任务 - ↓ -8. 更新任务状态,触发事件 - ↓ -9. 重复 6-8 直到所有任务完成 - ↓ -10. MainAgent 汇总结果并返回 +## 启动团队协作 + +### team_task 工具 + +```bash +# 启动团队协作任务 +team_task(prompt="实现用户登录功能") + +# 输出: +# [OK] 团队任务执行完成 +# +# **任务统计**: +# - 总任务数: 4 +# - 已完成: 4 +# - 失败: 0 +# - 进行中: 0 +# - 待认领: 0 +# +# **主 Agent 回复**: +# ... ``` +**何时使用**: +- ✅ 多步骤复杂任务 +- ✅ 需要多种技能组合 +- ✅ 需要多个专业领域协作 +- ❌ 简单问答(直接回答即可) + --- -## 典型使用场景 +## 核心工具速查 -### 场景 1: 功能开发 +### 团队协作 -``` -用户: "实现用户登录功能" +| 工具 | 参数 | 说明 | +|------|------|------| +| `team_task(prompt)` | prompt | 启动团队协作 | +| `team_status()` | - | 查看任务状态 | -MainAgent: - ├─ 创建任务: "实现用户登录" - ├─ 分解: - │ ├─ Task 1: 探索现有认证代码 (explore) - │ ├─ Task 2: 设计登录方案 (plan) - │ ├─ Task 3: 实现登录逻辑 (general-purpose) - │ └─ Task 4: 编写测试 (general-purpose) - └─ 协调 SubAgents 执行 -``` +### 成员管理 -### 场景 2: 代码重构 +| 工具 | 参数 | 说明 | +|------|------|------| +| `teammate_quick(name, role)` | 2个 | 快速创建 ⭐ | +| `teammate_template(template)` | 1个 | 模板创建 ⭐ | +| `teammate_templates()` | 0个 | 查看所有模板 | +| `teammates(teamName?)` | 0-1个 | 列出成员 | +| `remove_teammate(name)` | 1个 | 移除成员 | -``` -用户: "重构 UserService 类,使其符合 SOLID 原则" - -MainAgent: - ├─ 创建任务: "重构 UserService" - ├─ 分解: - │ ├─ Task 1: 分析 UserService 依赖 (explore) - │ ├─ Task 2: 设计重构方案 (plan) - │ ├─ Task 3: 执行重构 (general-purpose) - │ └─ Task 4: 运行测试验证 (bash) - └─ 协调 SubAgents 执行 -``` +### 任务管理 -### 场景 3: 问题诊断 +| 工具 | 参数 | 说明 | +|------|------|------| +| `task_add(title, desc?)` | 1-2个 | 快速添加 ⭐ | +| `tasks_add(titles)` | 1个 | 批量添加 ⭐ | +| `create_task(...)` | 5个 | 完整配置 | +| `list_all_tasks()` | - | 查看所有任务 | -``` -用户: "登录时偶尔超时,帮我排查问题" - -MainAgent: - ├─ 创建任务: "诊断登录超时问题" - ├─ 分解: - │ ├─ Task 1: 搜索日志文件 (bash) - │ ├─ Task 2: 分析认证代码 (explore) - │ ├─ Task 3: 检查数据库连接 (bash) - │ └─ Task 4: 生成诊断报告 (general-purpose) - └─ 协调 SubAgents 执行 -``` +### 记忆管理 + +| 工具 | 参数 | 说明 | +|------|------|------| +| `memory_store(content, key?)` | 1-2个 | 存储记忆 | +| `memory_recall(query?, limit?)` | 0-2个 | 检索记忆 | +| `memory_stats()` | - | 记忆统计 | + +### 子代理调用 + +| 工具 | 参数 | 说明 | +|------|------|------| +| `task(type, prompt)` | 2个 | 调用子代理 | --- ## 配置示例 -**config.yml**: ```yaml solon: code: cli: workDir: ./work + + # 启用 Teams 模式 + agentTeamEnabled: true + + # Teams 配置 + teams: + taskExecutorThreads: 20 + eventExecutorThreads: 1 + maxCompletedTasks: 100 + maxDependencyDepth: 100 + chatModel: apiUrl: https://api.openai.com/v1 apiKey: ${OPENAI_API_KEY} model: gpt-4 - defaultOptions: - temperature: 0.7 - - # 启用子代理系统 - subAgentEnabled: true - - # 子代理模型配置(可选) - subAgentModels: - explore: gpt-4o-mini - plan: gpt-4 - bash: gpt-4o-mini - general-purpose: gpt-4 +``` + +--- + +## 典型使用场景 + +### 场景 1: 功能开发 + +``` +用户: "实现用户登录功能" + +Team Lead 分析: 多步骤任务 → 启动 team_task() + +执行流程: + ├─ Task 1: explore - 探索现有认证代码 + ├─ Task 2: plan - 设计登录方案 + ├─ Task 3: general-purpose - 实现登录逻辑 + └─ Task 4: bash - 运行测试验证 + +汇总: 返回完整的实现方案和代码 +``` + +### 场景 2: 安全审计 + +``` +用户: "检查代码中的安全问题" + +Team Lead: + ├─ 创建 security-expert: teammate_template(template="security") + ├─ Task 1: analyze_tasks - 分解审计任务 + ├─ Task 2: security-expert - 执行安全审计 + └─ Task 3: general-purpose - 生成审计报告 +``` + +### 场景 3: 性能优化 + +``` +用户: "优化 API 响应时间" + +Team Lead: + ├─ 创建团队成员: + │ ├─ performance-eng (teammate_template) + │ └─ database-expert (teammate_template) + ├─ 分析: explore - 查找 API 代码 + ├─ 分析: database-expert - 分析 SQL 查询 + ├─ 优化: general-purpose - 实施优化 + └─ 验证: bash - 性能测试 ``` --- @@ -341,41 +401,43 @@ solon: | 特性 | 说明 | |------|------| -| **任务分解** | MainAgent 自动将复杂任务分解为可执行的子任务 | -| **依赖管理** | 支持任务间依赖关系,确保执行顺序正确 | -| **并行执行** | 多个 SubAgent 可并行执行独立任务 | -| **状态跟踪** | 实时追踪任务状态、进度和结果 | -| **事件驱动** | 基于 EventBus 的异步事件通知 | -| **容错机制** | 任务失败自动重试或标记错误 | +| **智能决策** | Team Lead 自动判断任务复杂度 | +| **简化工具** | 80% 参数减少,降低 token 消耗 | +| **共享记忆** | 团队成员共享上下文信息 | +| **任务追踪** | 自动创建主任务,状态实时更新 | +| **事件驱动** | 异步事件通知,松耦合通信 | --- ## 总结 -Agent Teams 模式通过 **MainAgent 协调 + SubAgents 执行** 的分工协作,实现了复杂任务的自动化处理: +Agent Teams 模式通过 **Team Lead 协调 + SubAgents 执行** 的协作模式,实现复杂任务的高效处理: -1. **MainAgent** 负责任务分解和协调决策 -2. **SubAgents** 专注于特定领域的任务执行 -3. **SharedTaskList** 提供统一的任务管理 -4. **EventBus** 实现松耦合的事件通信 -5. **Teammate 管理** 支持动态创建和管理团队成员(类似 Claude Code) +1. **Team Lead** - 智能分析,决策执行方式 +2. **MainAgent** - 提供协作基础设施 +3. **SubAgents** - 专注领域任务执行 +4. **简化工具** - 降低使用复杂度 -### 核心工具 +### 工具选择指南 -| 工具 | 功能 | 输出格式 | -|------|------|----------| -| `team_task()` | 启动团队协作任务 | 统计 + 结果 | -| `team_status()` | 查看任务状态 | 表格 + 统计 | -| `create_task()` | 创建新任务 | 任务信息 | -| `teammate()` | 创建团队成员 | 表格 | -| `teammates()` | 列出所有成员 | 表格 | -| `remove_teammate()` | 移除团队成员 | 状态信息 | -| `subagent()` | 调用子代理 | task_id + 结果 | +``` +简单任务 + └─ 直接回答,无需调用工具 -这种模式特别适合需要多个步骤、涉及多个文件或需要专业领域知识的复杂任务。 +创建成员 + ├─ 快速创建 → teammate_quick(name, role) + ├─ 模板创建 → teammate_template(template) + └─ 完整配置 → teammate(...) + +创建任务 + ├─ 快速添加 → task_add(title) + ├─ 批量添加 → tasks_add(titles) + └─ 完整配置 → create_task(...) + +复杂任务 + └─ team_task(prompt) → 启动团队协作 +``` --- -**作者**: bai -**日期**: 2026-03-09 -**版本**: 1.0 + 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 051eedf..2f787fb 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 @@ -244,11 +244,32 @@ public class AgentKernel { agentBuilder.defaultSkillAdd(restApis); } + // Agent Teams 模式:追加 Team Lead 指令到系统提示词 + if (properties.isAgentTeamEnabled() && mainAgent != null) { + String teamLeadInstruction = mainAgent.getTeamLeadInstruction(); + if (Assert.isNotEmpty(teamLeadInstruction)) { + // 获取当前的 systemPrompt 并追加指令 + agentBuilder.systemPrompt(SystemPrompt.builder() + .instruction(trace -> { + String basePrompt = agentsMd != null ? agentsMd : ""; + return basePrompt + "\n\n" + teamLeadInstruction; + }) + .build()); + LOG.info("Team Lead 指令已追加到系统提示词"); + } + } + if (configurator != null) { configurator.accept(agentBuilder); } reActAgent = agentBuilder.build(); + + // Agent Teams 模式:将主 ReActAgent 设置到 MainAgent + if (properties.isAgentTeamEnabled() && mainAgent != null) { + mainAgent.setSharedAgent(reActAgent, chatModel); + LOG.info("MainAgent 已设置共享 ReActAgent"); + } } @@ -303,10 +324,6 @@ public class AgentKernel { ); LOG.debug("MainAgent 已创建"); - // 5.1 初始化 MainAgent(需要传入 ChatModel) - this.mainAgent.initialize(chatModel); - LOG.debug("MainAgent 已初始化"); - // 6. 创建 AgentTeamsSkill 并注册到主 Agent AgentTeamsSkill agentTeamsSkill = new AgentTeamsSkill( mainAgent, 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 743f178..c400b0e 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 @@ -225,7 +225,7 @@ public class AgentProperties implements Serializable { * 默认:1(串行执行,避免触发速率限制) * 设置为 2-3 可以适当提高性能,但可能触发速率限制 */ - public int maxConcurrent = 1; + public int maxConcurrent = 2; /** * 子代理调用间隔(毫秒) @@ -241,12 +241,6 @@ public class AgentProperties implements Serializable { */ public long acquireTimeoutMs = 60000L; - /** - * 触发429错误后的等待时间(毫秒) - * 默认:5000ms(5秒) - * 收到速率限制错误后,等待的时间 - */ - public long rateLimitBackoffMs = 5000L; } /** 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 d97063e..b9d15f8 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 @@ -137,12 +137,11 @@ public class TaskSkill extends AbsSkill { } String finalSessionId = Assert.isEmpty(taskId) - ? "subagent_" + subagentType + "_" + System.currentTimeMillis() + ? "subagent_" + subagentType : taskId; LOG.info("分派任务 -> 类型: {}, 会话: {}, 描述: {}", subagentType, finalSessionId, description); - // ==================== 并发控制 ==================== if (!acquirePermit(subagentType)) { return "ERROR: 等待子代理执行许可超时。当前可能有太多子代理在执行。"; } 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 3288f9a..c4ab2ee 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 @@ -15,7 +15,9 @@ */ package org.noear.solon.bot.core.teams; +import lombok.Getter; import org.noear.solon.ai.agent.AgentChunk; +import org.noear.solon.ai.chat.ChatModel; import org.noear.solon.ai.chat.prompt.Prompt; import org.noear.solon.ai.chat.skill.AbsSkill; import org.noear.solon.ai.annotation.ToolMapping; @@ -37,6 +39,7 @@ import java.io.File; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.time.Duration; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeoutException; @@ -343,7 +346,7 @@ public class AgentTeamsSkill extends AbsSkill { private static final long TEAM_TASK_TIMEOUT_MS = 300_000; // 5分钟超时 @ToolMapping(name = "team_task", - description = "启动团队协作任务。MainAgent 会自动分解任务并协调多个 SubAgent 协作完成。适用于复杂、多步骤的任务。") + description = "启动团队协作任务。MainAgent 会自动分解任务并协调多个 SubAgent 协作完成。适用于复杂、多步骤的任务。注意:简单任务请直接回答,无需调用此工具。") public String teamTask( @Param(name = "prompt", description = "任务描述,清晰说明目标和要求") String prompt, String __cwd, @@ -365,7 +368,7 @@ public class AgentTeamsSkill extends AbsSkill { mainAgent.executeStream(Prompt.of(prompt), __cwd); // 收集流式输出(带超时) - List chunks = responseStream + List chunks = responseStream .doOnNext(chunk -> { String content = chunk.getContent(); if (content != null && !content.isEmpty()) { @@ -379,25 +382,12 @@ public class AgentTeamsSkill extends AbsSkill { LOG.error("团队任务流式执行出错", error); }) .collectList() - .block(java.time.Duration.ofMillis(TEAM_TASK_TIMEOUT_MS)); - - LOG.debug("流式响应收集完成,收到 {} 个 chunk", chunks.size()); - - } catch (TimeoutException e) { - LOG.error("团队任务执行超时({}ms)", TEAM_TASK_TIMEOUT_MS); - return "[ERROR] 团队任务执行超时(超过 " + (TEAM_TASK_TIMEOUT_MS / 1000) + " 秒)。\n\n" + - "可能原因:\n" + - "1. LLM API 响应慢或无响应\n" + - "2. 任务过于复杂,子代理调用链过长\n" + - "3. 网络连接问题\n\n" + - "建议:\n" + - "- 简化任务描述,分解为更小的子任务\n" + - "- 检查网络连接和 API 配置\n" + - "- 使用 `task(subagent_type='bash', ...)` 直接调用子代理"; - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - LOG.error("团队任务执行被中断"); - return "[ERROR] 团队任务执行被中断"; + .block(Duration.ofMillis(TEAM_TASK_TIMEOUT_MS)); + + if (chunks != null) { + LOG.debug("流式响应收集完成,收到 {} 个 chunk", chunks.size()); + } + } catch (Exception e) { LOG.error("团队任务执行失败", e); return "[ERROR] 团队任务执行失败: " + e.getMessage(); @@ -442,7 +432,7 @@ public class AgentTeamsSkill extends AbsSkill { * 查看团队任务状态 */ @ToolMapping(name = "team_status", - description = "查看当前团队任务状态,包括任务列表、进度统计等") + description = "查看团队任务状态。返回任务统计和当前任务列表。") public String teamStatus() { try { SharedTaskList taskList = mainAgent.getTaskList(); @@ -514,7 +504,7 @@ public class AgentTeamsSkill extends AbsSkill { * 创建新任务 */ @ToolMapping(name = "create_task", - description = "创建新的团队任务。可以设置依赖关系、优先级等。") + description = "创建任务(完整版)。支持设置依赖、优先级等。简单场景请使用 task_add() 或 tasks_add()。") public String createTask( @Param(name = "title", description = "任务标题") String title, @Param(name = "description", required = false, description = "任务描述") String description, @@ -578,6 +568,82 @@ public class AgentTeamsSkill extends AbsSkill { } } + + /** + * 快速添加任务(简化版) + * + * 只需要标题,其他参数使用智能默认值。 + * 适合快速创建任务的场景。 + */ + @ToolMapping(name = "task_add", + description = "快速添加任务。只需提供标题,其他参数使用智能默认值。适合快速创建任务。") + public String taskAdd( + @Param(name = "title", description = "任务标题") String title, + @Param(name = "description", required = false, description = "任务描述(可选)") String description + ) { + // 使用默认值调用完整版 createTask + return createTask(title, description, null, null, null); + } + + /** + * 快速添加多个任务(简化版) + * + * 接受标题列表,批量创建任务。 + */ + @ToolMapping(name = "tasks_add", + description = "批量添加任务。接受标题列表,一次性创建多个任务。") + public String tasksAdd( + @Param(name = "titles", description = "任务标题列表,逗号分隔(如:任务1,任务2,任务3)") String titles + ) { + try { + if (mainAgent == null) { + return "[WARN] MainAgent 未初始化"; + } + + SharedTaskList taskList = mainAgent.getTaskList(); + String[] titleArray = titles.split(",\\s*"); + List tasks = new ArrayList<>(); + + for (String title : titleArray) { + if (!title.isEmpty()) { + TeamTask task = new TeamTask(); + task.setTitle(title.trim()); + task.setDescription(""); + task.setType(TeamTask.TaskType.DEVELOPMENT); + task.setPriority(5); + task.setDependencies(new ArrayList<>()); + tasks.add(task); + } + } + + 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 < Math.min(added.size(), 10); i++) { + TeamTask task = added.get(i); + sb.append(String.format("%d. `%s` (ID: %s)\n", + i + 1, task.getTitle(), task.getId())); + } + + if (added.size() > 10) { + sb.append(String.format("... 还有 %d 个任务\n", added.size() - 10)); + } + + return sb.toString(); + + } catch (Exception e) { + LOG.error("批量创建任务失败", e); + return "[ERROR] 批量创建失败: " + e.getMessage(); + } + } /** * 分析任务(使用 LLM 分解任务) * @@ -585,7 +651,7 @@ public class AgentTeamsSkill extends AbsSkill { * Agent 可以根据建议逐个调用 create_task 创建任务 */ @ToolMapping(name = "analyze_tasks", - description = "分析用户请求,将其分解为多个子任务。返回任务分解建议(JSON格式),Agent可以根据建议使用create_task创建任务。") + description = "针对团队协作任务,分析用户请求,将其分解为多个子任务。返回任务分解建议(JSON格式),Agent可以根据建议使用create_task创建任务。") public String analyzeTasks( @Param(name = "request", description = "用户请求或任务描述") String request) { try { @@ -597,15 +663,14 @@ public class AgentTeamsSkill extends AbsSkill { String prompt = buildTaskAnalysisPrompt(request); // 调用 LLM 分析 - org.noear.solon.ai.chat.prompt.Prompt llmPrompt = - org.noear.solon.ai.chat.prompt.Prompt.of(prompt); + Prompt llmPrompt = Prompt.of(prompt); // 通过 kernel 获取 ChatModel if (kernel == null) { return "[WARN] Kernel 未初始化"; } - org.noear.solon.ai.chat.ChatModel chatModel = kernel.getChatModel(); + ChatModel chatModel = kernel.getChatModel(); if (chatModel == null) { return "[WARN] ChatModel 未初始化"; } @@ -854,6 +919,34 @@ public class AgentTeamsSkill extends AbsSkill { } } + /** + * 快速创建团队成员(简化版) + * + * 只需提供 name 和 role,其他参数使用智能默认值。 + * 适合快速创建团队成员的场景。 + */ + @ToolMapping(name = "teammate_quick", + description = "快速创建团队成员。只需 name + role,其他全部智能默认(自动开启记忆管理、智能团队名生成)。适合快速创建场景。") + public String createTeammateQuick( + @Param(name = "name", description = "团队成员唯一标识(如:security-expert)") String name, + @Param(name = "role", description = "角色描述(如:安全专家)") String role, + @Param(name = "description", required = false, description = "详细职责描述(可选,默认使用角色描述)") String description, + String __cwd + ) { + // 使用默认值调用完整版 createTeammate + return createTeammate( + name, // name + role, // role + description != null ? description : role, // description + null, // teamName - 智能生成 + null, // systemPrompt - 自动生成 + null, // model - 使用默认 + null, // tools - 使用默认记忆工具 + null, // disallowedTools - 空 + null, // skills - 空 + __cwd + ); + } /** * 创建团队成员 * @@ -864,21 +957,17 @@ public class AgentTeamsSkill extends AbsSkill { * 或者 {roleName}.md(如果未指定 teamName) */ @ToolMapping(name = "teammate", - description = "创建新的团队成员。可以定义角色、职责、技能集,并立即激活。支持联网搜索相关资料。") + description = "创建团队成员(完整版)。支持完整自定义配置。快速创建请使用 teammate_quick() 或 teammate_template()。") public String createTeammate( @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)。如果不指定,将根据角色和描述智能生成语义化名称(如: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 { @@ -915,12 +1004,6 @@ public class AgentTeamsSkill extends AbsSkill { LOG.warn("警告:已存在 role='{}' 的子代理,新成员可能会覆盖它。建议使用唯一的 role 名称。", role); } - // 如果需要联网搜索上下文 - if (searchContext != null && searchContext && kernel != null) { - LOG.info("为 teammate {} 搜索相关上下文...", name); - LOG.info("联网搜索功能需要进一步集成 WebSearch 工具"); - } - // 构建子代理元数据 SubAgentMetadata metadata = new SubAgentMetadata(); metadata.setCode(name); @@ -929,14 +1012,9 @@ public class AgentTeamsSkill extends AbsSkill { metadata.setEnabled(true); metadata.setTeamName(teamName); - // 设置专业领域 - if (expertise != null && !expertise.isEmpty()) { - metadata.setSkills(Arrays.asList(expertise.split(",\\s*"))); - } - // 设置启用的工具 if (tools != null && !tools.isEmpty()) { - metadata.setTools(Arrays.asList(tools.split(",\\s*"))); + metadata.getTools().addAll(Arrays.asList(tools.split(",\\s*"))); } // 设置禁用的工具 @@ -955,41 +1033,27 @@ public class AgentTeamsSkill extends AbsSkill { 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 记忆管理工具"); - } - } + // 自动添加记忆管理相关工具 + 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()) { - finalPrompt = generateDefaultSystemPrompt(name, role, description, expertise); + finalPrompt = generateDefaultSystemPrompt(name, role, description); } // 生成完整的代理定义 @@ -1005,14 +1069,12 @@ public class AgentTeamsSkill extends AbsSkill { LOG.info("Agent 定义已保存到: {}", agentFile); // 重新扫描目录以加载新创建的 agent(使用 putIfAbsent 避免重复) - if (manager != null) { - try { - Path parentDir = agentFile.getParent(); - manager.agentPool(parentDir, false); - LOG.info("已重新扫描目录并加载新团队成员: {}", name); - } catch (Throwable ex) { - LOG.warn("重新扫描目录失败(文件已保存): {}", ex.getMessage()); - } + try { + Path parentDir = agentFile.getParent(); + manager.agentPool(parentDir, false); + LOG.info("已重新扫描目录并加载新团队成员: {}", name); + } catch (Throwable ex) { + LOG.warn("重新扫描目录失败(文件已保存): {}", ex.getMessage()); } // 返回结果(使用表格格式) @@ -1029,9 +1091,6 @@ public class AgentTeamsSkill extends AbsSkill { result.append(String.format("| **所属团队** | %s |\n", teamName)); result.append(String.format("| **文件路径** | `.soloncode/agentsTeams/%s/%s.md` |\n", teamName, name)); - if (expertise != null && !expertise.isEmpty()) { - result.append(String.format("| **专业领域** | %s |\n", expertise)); - } if (model != null && !model.isEmpty()) { result.append(String.format("| **模型** | %s |\n", model)); @@ -1061,7 +1120,7 @@ public class AgentTeamsSkill extends AbsSkill { String.join(", ", metadata.getMcpServers()))); } - result.append(String.format("| **状态** | 🟢 已激活 |\n")); + result.append("| **状态** | 🟢 已激活 |\n"); result.append("\n**使用方法**:\n"); result.append("```bash\n"); @@ -1134,7 +1193,7 @@ public class AgentTeamsSkill extends AbsSkill { } // 表格格式的成员列表 - result.append("| 名称 | 角色 | 描述 | 团队 | 状态 | 模型 |\n"); + result.append("| 名称 | 角色 | 描述 | 团队 | 模型 |\n"); result.append("|------|------|------|------|------|------|\n"); for (Subagent agent : agents) { @@ -1142,11 +1201,10 @@ public class AgentTeamsSkill extends AbsSkill { String role = agent.getClass().getSimpleName().replace("Subagent", ""); String desc = truncate(agent.getDescription(), 30); String team = agent.getMetadata().hasTeamName() ? agent.getMetadata().getTeamName() : "-"; - String status = "🟢 活跃"; String model = agent.getMetadata().getModel() != null ? agent.getMetadata().getModel() : "默认"; - result.append(String.format("| %s | %s | %s | %s | %s | %s |\n", - name, role, desc, team, status, model)); + result.append(String.format("| %s | %s | %s | %s | %s |\n", + name, role, desc, team, model)); } if (teamName != null && !teamName.isEmpty()) { @@ -1223,28 +1281,19 @@ public class AgentTeamsSkill extends AbsSkill { /** * 生成默认系统提示词 */ - private String generateDefaultSystemPrompt(String name, String role, String description, String expertise) { + private String generateDefaultSystemPrompt(String name, String role, String description) { StringBuilder prompt = new StringBuilder(); prompt.append(String.format("# %s\n\n", role)); prompt.append(String.format("你是 %s,专门负责 %s。\n\n", role, description)); prompt.append("## 工作原则\n\n"); prompt.append("1. **专业专注**: 始终在你的专业领域内提供建议和解决方案\n"); - prompt.append("2. **质量优先**: 注重代码质量和最佳实践\n"); + prompt.append("2. **平衡优先**: 同时看效率比\n"); prompt.append("3. **协作配合**: 与其他团队成员保持良好沟通\n"); - prompt.append("4. **持续学习**: 不断更新知识,掌握最新技术趋势\n\n"); - if (expertise != null && !expertise.isEmpty()) { - prompt.append("## 专业领域\n\n"); - String[] areas = expertise.split(",\\s*"); - for (String area : areas) { - prompt.append(String.format("- %s\n", area)); - } - prompt.append("\n"); - } + prompt.append("## 沟通风格\n\n"); prompt.append("- 使用清晰、简洁的语言\n"); - prompt.append("- 提供具体的代码示例\n"); prompt.append("- 解释技术决策的理由\n"); prompt.append("- 在不确定时主动寻求帮助\n"); @@ -1264,7 +1313,6 @@ public class AgentTeamsSkill extends AbsSkill { return text.substring(0, maxLength - 3) + "..."; } - // ==================== 记忆管理工具(智能接口)==================== /** * 工具 1:智能存储记忆 @@ -1547,7 +1595,6 @@ public class AgentTeamsSkill extends AbsSkill { } } - // ==================== 任务管理工具 ==================== /** * 认领任务 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 a776f00..ce8df1e 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 @@ -141,55 +141,67 @@ public class MainAgent { } /** - * 初始化主代理 + * 获取 Team Lead(团队协调器)指令 + * + * 此指令会被追加到系统提示词中,让 Agent 了解自己作为 Team Lead 的角色和职责。 + * + * @return Team Lead 角色指令 */ - public synchronized void initialize(ChatModel chatModel) { - if (agent == null) { - ReActAgent.Builder builder = ReActAgent.of(chatModel); - - // 设置系统提示词 - builder.systemPrompt(SystemPrompt.builder() - .instruction(getSystemPrompt()) - .build()); - - // 添加技能 - CliSkillProvider skillProvider = new CliSkillProvider(workDir); - if (poolManager != null) { - poolManager.getPoolMap().forEach((alias, path) -> { - skillProvider.skillPool(alias, path); - }); - } - - // 基础技能 - builder.defaultSkillAdd(skillProvider.getTerminalSkill()); - builder.defaultSkillAdd(skillProvider.getExpertSkill()); - - // Agent Teams 工具集(记忆、事件、消息) - AgentTeamsTools teamsTools = new AgentTeamsTools( - sharedMemoryManager, - eventBus - ); - builder.defaultSkillAdd(teamsTools); - - // 子代理调用工具(如果有 kernel 和 subagentManager) - if (kernel != null && subagentManager != null) { - TaskSkill taskSkill = new TaskSkill(kernel, subagentManager); - builder.defaultSkillAdd(taskSkill); - LOG.debug("MainAgent: TaskSkill 已添加"); - } else { - LOG.debug("MainAgent: 无 kernel 或 subagentManager,跳过 TaskSkill"); - } - - // 设置较大的步数(主代理需要协调多个任务) - builder.maxSteps(50); - builder.sessionWindowSize(10); - - this.chatModel = chatModel; // 保存 ChatModel 引用 - this.agent = builder.build(); - this.session = sessionProvider.getSession("main_agent"); + public String getTeamLeadInstruction() { + return "## Team Lead(团队协调器)角色\n\n" + + "你现在启用了 **Agent Teams 模式**,你是团队的 **Team Lead(团队领导)**。\n\n" + + "### 核心职责\n\n" + + "1. **任务分解** - 将复杂任务分解为可执行的子任务\n" + + "2. **团队协作** - 协调多个专业子代理(teammates)协作完成任务\n" + + "3. **结果汇总** - 收集各子代理的结果,形成最终答案\n" + + "4. **质量控制** - 确保子任务完成的质量和一致性\n\n" + + "### 可用工具\n\n" + + "**团队协作**:\n" + + "- `team_task(prompt)` - 启动团队协作任务(适用于复杂多步骤任务)\n" + + "- `team_status()` - 查看团队任务状态\n" + + "- `teammate_quick(name, role)` - 快速创建团队成员\n" + + "- `teammate_template(template)` - 使用预设模板创建成员\n" + + "- `teammates()` - 列出所有团队成员\n\n" + + "**任务管理**:\n" + + "- `task_add(title)` - 快速添加任务\n" + + "- `tasks_add(titles)` - 批量添加任务\n" + + "- `create_task(title, ...)` - 创建任务(完整配置)\n" + + "- `list_all_tasks()` - 查看所有任务\n\n" + + "**记忆管理**:\n" + + "- `memory_store(content)` - 存储记忆(自动分类)\n" + + "- `memory_recall(query)` - 检索记忆\n" + + "- `memory_stats()` - 查看记忆统计\n\n" + + "**子代理调用**:\n" + + "- `task(subagent_type, prompt)` - 直接调用特定子代理\n\n" + + "### 工作流程\n\n" + + "1. 分析用户请求,判断是否需要团队协作\n" + + "2. 如需协作,调用 `team_task()` 或先创建子任务\n" + + "3. 通过 `task()` 调用专业子代理执行子任务\n" + + "4. 汇总结果,给出最终答案\n\n" + + "### 重要提示\n\n" + + "- **简单任务直接回答**,无需调用 `team_task()`\n" + + "- **复杂任务**(多步骤、需要多种技能)才启用团队协作\n" + + "- 使用 `teammate_quick()` 或 `teammate_template()` 快速创建团队成员\n" + + "- 利用 `memory_store()` 记录重要决策和结果"; + } - LOG.info("MainAgent '{}' 初始化完成", config.getCode()); + /** + * 设置共享的 ReActAgent + * + * MainAgent 不再创建自己的 ReActAgent,而是使用 AgentKernel 创建的主 ReActAgent。 + * 这样可以避免两层推理,简化架构。 + * + * @param agent 共享的 ReActAgent + * @param chatModel ChatModel(用于任务分析等功能) + */ + public synchronized void setSharedAgent(ReActAgent agent, ChatModel chatModel) { + if (this.agent != null) { + LOG.warn("MainAgent 已有共享 Agent,将被覆盖"); } + this.agent = agent; + this.chatModel = chatModel; + this.session = sessionProvider.getSession("main_agent"); + LOG.info("MainAgent 已设置共享 ReActAgent"); } /** @@ -199,14 +211,40 @@ public class MainAgent { * @param __cwd 工作目录 * @return 响应流 */ - public Flux executeStream(Prompt prompt, String __cwd) throws Throwable { + public Flux executeStream(Prompt prompt, String __cwd) { if (agent == null) { throw new IllegalStateException("MainAgent 尚未初始化"); } running.set(true); - // 0. 启动目标守护(防止在多轮循环中偏离目标) + // 0. 自动创建主任务到 SharedTaskList + TeamTask mainTask = null; + try { + String userContent = prompt.getUserContent(); + // 生成简短标题(取前50个字符) + String title = userContent.length() > 50 + ? userContent.substring(0, 50) + "..." + : userContent; + + mainTask = new TeamTask(); + mainTask.setTitle(title); + mainTask.setDescription(userContent); + mainTask.setType(TeamTask.TaskType.DEVELOPMENT); + mainTask.setPriority(10); // 主任务高优先级 + mainTask.setStatus(TeamTask.Status.IN_PROGRESS); + + CompletableFuture future = taskList.addTask(mainTask); + mainTask = future.join(); + LOG.info("主任务已自动创建: taskId={}, title={}", mainTask.getId(), mainTask.getTitle()); + + } catch (Exception e) { + LOG.warn("创建主任务失败(继续执行): {}", e.getMessage()); + } + + final TeamTask finalMainTask = mainTask; + + // 1. 启动目标守护(防止在多轮循环中偏离目标) String goalId = null; try { if (kernel != null) { @@ -218,7 +256,7 @@ public class MainAgent { } try { - // 1. 发布主代理任务开始事件 + // 2. 发布主代理任务开始事件 publishEvent(AgentEventType.MAIN_TASK_STARTED, prompt.getUserContent(), null); // 2. 执行主代理内部的协调逻辑(流式输出) @@ -234,7 +272,17 @@ public class MainAgent { .stream(); // 7. 在流完成后等待所有子任务完成 - Flux resultStream = responseStream + // 等待所有子任务完成 + // 汇总结果 + // 更新主任务状态 + // 发布主代理任务完成事件 + // 停止目标守护 + // 更新主任务状态为失败 + // 出错时也要停止目标守护 + // 更新主任务状态为取消 + // 取消时也要停止目标守护 + + return responseStream .doOnComplete(() -> { try { // 等待所有子任务完成 @@ -243,6 +291,16 @@ public class MainAgent { // 汇总结果 String summary = summarizeResults(); + // 更新主任务状态 + if (finalMainTask != null) { + try { + taskList.completeTask(finalMainTask.getId(), summary); + LOG.info("主任务已完成: taskId={}", finalMainTask.getId()); + } catch (Exception e) { + LOG.warn("更新主任务状态失败: {}", e.getMessage()); + } + } + // 发布主代理任务完成事件 publishEvent(AgentEventType.MAIN_TASK_COMPLETED, summary, null); @@ -262,6 +320,17 @@ public class MainAgent { }) .doOnError(error -> { LOG.error("MainAgent 流式执行出错", error); + + // 更新主任务状态为失败 + if (finalMainTask != null) { + try { + taskList.failTask(finalMainTask.getId(), error.getMessage()); + LOG.info("主任务已标记为失败: taskId={}", finalMainTask.getId()); + } catch (Exception e) { + LOG.warn("更新主任务失败状态失败: {}", e.getMessage()); + } + } + running.set(false); // 出错时也要停止目标守护 try { @@ -273,6 +342,17 @@ public class MainAgent { }) .doOnCancel(() -> { LOG.warn("MainAgent 流式执行被取消"); + + // 更新主任务状态为取消 + if (finalMainTask != null) { + try { + taskList.removeTask(finalMainTask.getId()); + LOG.info("主任务已取消: taskId={}", finalMainTask.getId()); + } catch (Exception e) { + LOG.warn("更新主任务取消状态失败: {}", e.getMessage()); + } + } + running.set(false); // 取消时也要停止目标守护 try { @@ -283,8 +363,6 @@ public class MainAgent { } }); - return resultStream; - } finally { running.set(false); // 确保目标守护被停止(即使发生异常) diff --git a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/teams/SubAgentAgentBuilder.java b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/teams/SubAgentAgentBuilder.java index a761d68..28d16b9 100644 --- a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/teams/SubAgentAgentBuilder.java +++ b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/teams/SubAgentAgentBuilder.java @@ -18,9 +18,11 @@ package org.noear.solon.bot.core.teams; import lombok.Builder; import lombok.Getter; import org.noear.solon.ai.agent.AgentSessionProvider; +import org.noear.solon.ai.agent.react.ReActAgent; import org.noear.solon.ai.chat.ChatModel; import org.noear.solon.bot.core.AgentKernel; import org.noear.solon.bot.core.PoolManager; +import org.noear.solon.bot.core.SystemPrompt; import org.noear.solon.bot.core.event.EventBus; import org.noear.solon.bot.core.message.MessageChannel; import org.noear.solon.bot.core.memory.SharedMemoryManager; @@ -172,12 +174,26 @@ public class SubAgentAgentBuilder { poolManager ); - // 4. 初始化主代理 + // 4. 创建 ReActAgent 并设置到 MainAgent try { - mainAgent.initialize(chatModel); + // 创建一个简单的 ReActAgent 用于 MainAgent + ReActAgent.Builder agentBuilder = ReActAgent.of(chatModel); + + // 使用 MainAgent 的 Team Lead 指令 + String teamLeadInstruction = mainAgent.getTeamLeadInstruction(); + agentBuilder.systemPrompt(SystemPrompt.builder() + .instruction(teamLeadInstruction) + .build()); + agentBuilder.maxSteps(50); + + ReActAgent reactAgent = agentBuilder.build(); + + // 设置到 MainAgent + mainAgent.setSharedAgent(reactAgent, chatModel); + LOG.info("Agent 团队构建成功!主代理: {}", config.getName()); } catch (Exception e) { - LOG.error("初始化 MainAgent 失败", e); + LOG.error("设置 MainAgent 共享 Agent 失败", e); throw new RuntimeException("构建 Agent 团队失败", e); } -- Gitee From 5069e87f12de5971cca338f81d5675e287c89f0d Mon Sep 17 00:00:00 2001 From: bai <1145000687@qq.com> Date: Mon, 16 Mar 2026 18:44:17 +0800 Subject: [PATCH 2/5] =?UTF-8?q?feat(subagent):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E5=AD=90=E4=BB=A3=E7=90=86=E5=AE=9E=E7=8E=B0=E5=A2=9E=E5=BC=BA?= =?UTF-8?q?=E5=85=83=E6=95=B0=E6=8D=AE=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 引入SubAgentMetadata统一管理子代理配置 - 添加元数据验证和继承功能 - 实现ReActAgent缓存机制提升性能 - 增加团队协作工具集成支持 - 优化JSON解析使用ONode替代手工解析 - 添加团队任务执行异步支持 - 统一代理名称获取方式为name()方法 - 增强AgentTeamsTools单例管理模式 --- .../org/noear/solon/bot/core/AgentKernel.java | 5 +- .../solon/bot/core/subagent/AbsSubagent.java | 137 +++--- .../solon/bot/core/subagent/BashSubagent.java | 41 +- .../bot/core/subagent/ExploreSubagent.java | 43 +- .../core/subagent/GeneralPurposeSubagent.java | 63 +-- .../solon/bot/core/subagent/PlanSubagent.java | 45 +- .../bot/core/subagent/SubAgentMetadata.java | 58 ++- .../solon/bot/core/subagent/Subagent.java | 21 +- .../bot/core/subagent/SubagentManager.java | 18 +- .../solon/bot/core/subagent/TaskSkill.java | 176 ++------ .../solon/bot/core/teams/AgentTeamsSkill.java | 424 ++++++++++++------ .../solon/bot/core/teams/AgentTeamsTools.java | 14 +- .../noear/solon/bot/core/teams/MainAgent.java | 14 +- 13 files changed, 566 insertions(+), 493 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 2f787fb..eea4cb5 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 @@ -23,6 +23,7 @@ import org.noear.solon.bot.core.config.ApiServerParameters; import org.noear.solon.bot.core.subagent.SubagentManager; import org.noear.solon.bot.core.subagent.TaskSkill; import org.noear.solon.bot.core.teams.AgentTeamsSkill; +import org.noear.solon.bot.core.teams.AgentTeamsTools; import org.noear.solon.bot.core.teams.MainAgent; import org.noear.solon.bot.core.teams.SharedTaskList; import org.noear.solon.bot.core.tool.ApplyPatchTool; @@ -304,8 +305,7 @@ public class AgentKernel { // 5. 创建 MainAgent 配置 SubAgentMetadata mainAgentConfig = new SubAgentMetadata(); - mainAgentConfig.setCode("main-agent"); - mainAgentConfig.setName("主代理"); + mainAgentConfig.setName("main-agent"); mainAgentConfig.setDescription("Agent Teams 协调器,负责任务分解和团队协作"); mainAgentConfig.setEnabled(true); @@ -331,6 +331,7 @@ public class AgentKernel { subagentManager ); agentBuilder.defaultSkillAdd(agentTeamsSkill); + agentBuilder.defaultSkillAdd(new AgentTeamsTools(memoryManager, mainAgent.getEventBus())); LOG.info("AgentTeamsSkill 已注册"); diff --git a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/AbsSubagent.java b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/AbsSubagent.java index 4a5b96b..7ac8441 100644 --- a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/AbsSubagent.java +++ b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/AbsSubagent.java @@ -17,14 +17,17 @@ package org.noear.solon.bot.core.subagent; import lombok.Getter; import lombok.Setter; + import org.noear.solon.Utils; import org.noear.solon.ai.agent.AgentChunk; import org.noear.solon.ai.agent.AgentResponse; import org.noear.solon.ai.agent.AgentSession; -import org.noear.solon.ai.agent.react.ReActAgent; +import org.noear.solon.ai.agent.react.*; import org.noear.solon.ai.chat.prompt.Prompt; import org.noear.solon.bot.core.AgentKernel; +import org.noear.solon.bot.core.teams.AgentTeamsTools; import org.noear.solon.core.util.Assert; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.publisher.Flux; @@ -33,6 +36,10 @@ import reactor.core.publisher.Flux; /** * 抽象子代理实现 * + * 增强功能: + * - 元数据验证 + * - 元数据继承 + * * @author bai * @since 3.9.5 */ @@ -42,6 +49,7 @@ public abstract class AbsSubagent implements Subagent { private static final Logger LOG = LoggerFactory.getLogger(AbsSubagent.class); protected final AgentKernel mainAgent; + private volatile ReActAgent cachedAgent; protected String description; protected String systemPrompt; @@ -49,7 +57,26 @@ public abstract class AbsSubagent implements Subagent { public AbsSubagent(AgentKernel mainAgent) { + this(mainAgent, null); + } + + public AbsSubagent(AgentKernel mainAgent, SubAgentMetadata metadata) { this.mainAgent = mainAgent; + // 初始化默认元数据 + this.metadata = metadata == null ? createDefaultMetadata() : metadata; + + ReActAgent.Builder builder = ReActAgent.of(mainAgent.getChatModel()); + if (mainAgent.getProperties().isTeamsEnabled()){ + builder.defaultToolAdd(AgentTeamsTools.getInstance()); + } + builder.instruction(getDefaultSystemPrompt()); + + // 应用元数据中的属性配置到builder + applyMetadataToBuilder(builder, metadata); + + // 应用自定义配置 + customize(builder); + cachedAgent = builder.build(); } @Override @@ -63,37 +90,32 @@ public abstract class AbsSubagent implements Subagent { @Override - public String getType() { - // 从类名推断类型 - // 例如:ExploreSubagent -> explore - // BashSubagent -> bash - String simpleName = this.getClass().getSimpleName(); - if (simpleName.endsWith("Subagent") || simpleName.endsWith("SubAgent")) { - String type = simpleName.substring(0, simpleName.lastIndexOf("Subagent") != -1 ? - simpleName.lastIndexOf("Subagent") : simpleName.lastIndexOf("SubAgent")); - return type.toLowerCase(); - } - return simpleName.toLowerCase(); + public SubAgentMetadata getMetadata() { + return metadata; } - public final void setDescription(String description) { - this.description = description; + public String name(){ + return cachedAgent.name(); } - public final String getSystemPrompt() { - if (Assert.isEmpty(systemPrompt)) { - return getDefaultSystemPrompt(); - } else { - return systemPrompt; - } + /** + * 创建默认元数据(由子类重写) + * + * 此方法在构造函数开始时调用,在 cachedAgent 构建之前。 + * 元数据的名称会被用于配置 cachedAgent。 + * + * @return 默认元数据 + */ + protected SubAgentMetadata createDefaultMetadata() { + return SubAgentMetadata.builder() + .name(name()) // 设置代理名称 + .description(getDefaultDescription()) // 设置描述 + .build(); } - public final void setSystemPrompt(String systemPrompt) { - this.systemPrompt = systemPrompt; - } /** - * 获取内置系描述(由子类实现) + * 获取内置描述(由子类实现) */ protected abstract String getDefaultDescription(); @@ -102,14 +124,46 @@ public abstract class AbsSubagent implements Subagent { */ protected abstract String getDefaultSystemPrompt(); - /** - * 定制 + * 定制(由子类实现) */ protected abstract void customize(ReActAgent.Builder builder); + /** + * 应用元数据属性到 Builder + * + * 将 metadata 中的配置属性应用到 ReActAgent.Builder, + * 包括最大步数、工具列表、技能列表等。 + * + * @param builder ReActAgent 构建器 + * @param metadata 元数据配置 + */ + protected void applyMetadataToBuilder(ReActAgent.Builder builder, SubAgentMetadata metadata) { + if (metadata == null) { + return; + } + + // 应用最大步数 + if (metadata.getMaxSteps() != null && metadata.getMaxSteps() > 0) { + builder.maxSteps(metadata.getMaxSteps()); + } + + // 应用最大步数自动扩展 + if (metadata.getMaxStepsAutoExtensible() != null) { + builder.maxStepsExtensible(metadata.getMaxStepsAutoExtensible()); + } + + // 应用最大轮次 + if (metadata.hasMaxTurns()) { + LOG.debug("元数据指定了最大轮次: {}", metadata.getMaxTurns()); + // 可以作为最大步数的参考 + if (metadata.getMaxSteps() == null) { + builder.maxSteps(metadata.getMaxTurns()); + } + } + } + - private volatile ReActAgent cachedAgent; protected void refresh() { Utils.locker().lock(); @@ -120,37 +174,12 @@ public abstract class AbsSubagent implements Subagent { } } - /** - * 初始化代理 - */ - protected ReActAgent getOrBuildAgent() { - if (cachedAgent == null) { - Utils.locker().lock(); - try { - if (cachedAgent == null) { - ReActAgent.Builder builder = ReActAgent.of(mainAgent.getChatModel()); - - builder.name(getType()) - .systemPrompt(t -> getSystemPrompt()); - - // 应用自定义配置 - customize(builder); - - cachedAgent = builder.build(); - } - } finally { - Utils.locker().unlock(); - } - } - - return cachedAgent; - } @Override public AgentResponse call(String __cwd, String sessionId, Prompt prompt) throws Throwable { AgentSession session = mainAgent.getSession(sessionId); - return getOrBuildAgent().prompt(prompt) + return cachedAgent.prompt(prompt) .session(session) .options(o -> { o.toolContextPut("__cwd", __cwd); @@ -162,7 +191,7 @@ public abstract class AbsSubagent implements Subagent { public Flux stream(String __cwd, String sessionId, Prompt prompt) { AgentSession session = mainAgent.getSession(sessionId); - return getOrBuildAgent().prompt(prompt) + return cachedAgent.prompt(prompt) .session(session) .options(o -> { o.toolContextPut("__cwd", __cwd); diff --git a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/BashSubagent.java b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/BashSubagent.java index f7cea3c..9c491ca 100644 --- a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/BashSubagent.java +++ b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/BashSubagent.java @@ -16,6 +16,7 @@ package org.noear.solon.bot.core.subagent; import org.noear.solon.ai.agent.react.ReActAgent; +import org.noear.solon.ai.agent.react.ReActAgentConfig; import org.noear.solon.bot.core.AgentKernel; import java.util.Arrays; @@ -32,6 +33,21 @@ public class BashSubagent extends AbsSubagent { super(mainAgent); } + /** + * 创建默认元数据 + * + * Bash 代理主要用于执行命令,步数根据任务复杂度变化 + */ + @Override + protected SubAgentMetadata createDefaultMetadata() { + return SubAgentMetadata.builder() + .name("bash") + .description(getDefaultDescription()) + .maxSteps(20) + .maxStepsAutoExtensible(true) + .build(); + } + /** * 初始化 Bash 代理 */ @@ -40,31 +56,6 @@ public class BashSubagent extends AbsSubagent { // 只添加终端技能的(bash 工具) builder.defaultToolAdd(mainAgent.getCliSkills().getTerminalSkill() .getToolAry("ls", "read", "bash")); - - // 设置最大步数 - builder.maxSteps(10); - - // 设置会话窗口大小 - builder.sessionWindowSize(3); - } - - @Override - public String getType() { - return "bash"; - } - - @Override - public SubAgentMetadata getMetadata() { - SubAgentMetadata metadata = new SubAgentMetadata(); - metadata.setCode("bash"); - metadata.setName("Bash 命令子代理"); - metadata.setDescription(getDefaultDescription()); - metadata.setEnabled(true); - metadata.setMaxTurns(10); - // Bash 代理的工具:基本文件操作、bash 命令 - metadata.setTools(Arrays.asList("ls", "read", "bash")); - // Bash 代理没有额外的技能 - return metadata; } @Override diff --git a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/ExploreSubagent.java b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/ExploreSubagent.java index 105e5f3..0c9fffd 100644 --- a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/ExploreSubagent.java +++ b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/ExploreSubagent.java @@ -41,6 +41,21 @@ public class ExploreSubagent extends AbsSubagent { super(mainAgent); } + /** + * 创建默认元数据 + * + * 探索代理需要适度的步数,并启用自动扩展 + */ + @Override + protected SubAgentMetadata createDefaultMetadata() { + return SubAgentMetadata.builder() + .name("explore") + .description(getDefaultDescription()) + .maxSteps(15) + .maxStepsAutoExtensible(true) + .build(); + } + @Override protected void customize(ReActAgent.Builder builder) { // 添加技能(仅终端和专家技能,不添加代码搜索) @@ -49,43 +64,15 @@ public class ExploreSubagent extends AbsSubagent { builder.defaultSkillAdd(mainAgent.getCliSkills().getExpertSkill()); - builder.defaultSkillAdd(LuceneSkill.getInstance()); - // 添加网络工具 builder.defaultToolAdd(WebfetchTool.getInstance()); builder.defaultToolAdd(WebsearchTool.getInstance()); builder.defaultToolAdd(CodeSearchTool.getInstance()); - builder.defaultInterceptorAdd(mainAgent.getSummarizationInterceptor()); - // 设置最大步数(探索任务通常需要较少步数) - builder.maxSteps(15); - builder.maxStepsExtensible(true); - - // 设置会话窗口大小 - builder.sessionWindowSize(5); - } - - @Override - public String getType() { - return "explore"; } - @Override - public SubAgentMetadata getMetadata() { - SubAgentMetadata metadata = new SubAgentMetadata(); - metadata.setCode("explore"); - metadata.setName("探索子代理"); - metadata.setDescription(getDefaultDescription()); - metadata.setEnabled(true); - metadata.setMaxTurns(15); - // 探索代理的工具:只读文件操作 - metadata.setTools(Arrays.asList("ls", "read", "grep", "glob", "codesearch")); - // 探索代理的技能:专家技能、代码搜索 - metadata.setSkills(Arrays.asList("expert", "lucene")); - return metadata; - } @Override protected String getDefaultDescription() { diff --git a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/GeneralPurposeSubagent.java b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/GeneralPurposeSubagent.java index 4c90894..45c1ac1 100644 --- a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/GeneralPurposeSubagent.java +++ b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/GeneralPurposeSubagent.java @@ -34,42 +34,28 @@ import java.util.Arrays; * @since 3.9.5 */ public class GeneralPurposeSubagent extends AbsSubagent { - private final String subagentType; - // 维护 metadata 字段 public GeneralPurposeSubagent(AgentKernel mainAgent) { - this(mainAgent, null); - } - - public GeneralPurposeSubagent(AgentKernel mainAgent, String subagentType) { super(mainAgent); + } - if (Assert.isEmpty(subagentType)) { - this.subagentType = "general-purpose"; - } else { - this.subagentType = subagentType; - } - - // 初始化默认 metadata - this.metadata = createDefaultMetadata(); + public GeneralPurposeSubagent(AgentKernel mainAgent, SubAgentMetadata metadata) { + super(mainAgent, metadata); } /** - * 创建默认的 metadata + * 创建默认元数据 + * + * 通用代理需要更多的步数和自动扩展能力 */ - private SubAgentMetadata createDefaultMetadata() { - SubAgentMetadata metadata = new SubAgentMetadata(); - metadata.setCode(this.subagentType); - metadata.setName("通用子代理"); - metadata.setDescription(getDefaultDescription()); - metadata.setEnabled(true); - metadata.setMaxTurns(25); - // 通用代理的工具:包含所有核心工具 - metadata.setTools(Arrays.asList("ls", "read", "write", "edit", "grep", "glob", "bash", - "websearch", "webfetch", "codesearch")); - // 通用代理的技能:包含所有核心技能 - metadata.setSkills(Arrays.asList("terminal", "expert", "lucene", "todo", "code")); - return metadata; + @Override + protected SubAgentMetadata createDefaultMetadata() { + return SubAgentMetadata.builder() + .name("general-purpose") + .description(getDefaultDescription()) + .maxSteps(25) + .maxStepsAutoExtensible(true) + .build(); } /** @@ -80,13 +66,6 @@ public class GeneralPurposeSubagent extends AbsSubagent { this.metadata = metadata; } - /** - * 获取 metadata(返回维护的字段) - */ - @Override - public SubAgentMetadata getMetadata() { - return metadata; - } @Override protected void customize(ReActAgent.Builder builder) { @@ -102,22 +81,14 @@ public class GeneralPurposeSubagent extends AbsSubagent { builder.defaultInterceptorAdd(mainAgent.getSummarizationInterceptor()); - // 如果主 CodeAgent 有代码搜索能力,也可以添加 - // 这里可以根据需要动态添加工具 - - // 设置最大步数(通用任务可能需要更多步数) - builder.maxSteps(25); - builder.maxStepsExtensible(true); - // 设置会话窗口大小 builder.sessionWindowSize(5); - } - @Override - public String getType() { - return this.subagentType; + // 注意:maxSteps 和 maxStepsExtensible 现在通过元数据配置 + // 在 applyMetadataToBuilder() 中应用 } + @Override protected String getDefaultDescription() { return "通用子代理,擅长研究复杂问题、执行多步骤任务"; diff --git a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/PlanSubagent.java b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/PlanSubagent.java index 37c61ad..41ff23d 100644 --- a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/PlanSubagent.java +++ b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/PlanSubagent.java @@ -15,14 +15,18 @@ */ package org.noear.solon.bot.core.subagent; +import org.noear.solon.ai.agent.AgentSession; import org.noear.solon.ai.agent.react.ReActAgent; import org.noear.solon.ai.agent.react.intercept.SummarizationInterceptor; import org.noear.solon.ai.agent.react.intercept.summarize.HierarchicalSummarizationStrategy; +import org.noear.solon.ai.chat.message.AssistantMessage; +import org.noear.solon.ai.chat.prompt.Prompt; import org.noear.solon.bot.core.AgentKernel; import org.noear.solon.bot.core.LuceneSkill; import org.noear.solon.bot.core.tool.CodeSearchTool; import org.noear.solon.bot.core.tool.WebfetchTool; import org.noear.solon.bot.core.tool.WebsearchTool; +import org.noear.solon.lang.Nullable; import java.util.Arrays; @@ -38,6 +42,21 @@ public class PlanSubagent extends AbsSubagent { super(mainAgent); } + /** + * 创建默认元数据 + * + * 计划代理主要用于规划,不需要太多步数 + */ + @Override + protected SubAgentMetadata createDefaultMetadata() { + return SubAgentMetadata.builder() + .name("plan") + .description(getDefaultDescription()) + .maxSteps(20) + .maxStepsAutoExtensible(true) + .build(); + } + @Override protected void customize(ReActAgent.Builder builder) { // 计划代理主要依赖推理能力,不需要太多工具 @@ -48,39 +67,13 @@ public class PlanSubagent extends AbsSubagent { builder.defaultSkillAdd(mainAgent.getCliSkills().getExpertSkill()); builder.defaultSkillAdd(LuceneSkill.getInstance()); - builder.defaultToolAdd(WebsearchTool.getInstance()); builder.defaultToolAdd(WebfetchTool.getInstance()); builder.defaultToolAdd(CodeSearchTool.getInstance()); builder.defaultInterceptorAdd(mainAgent.getSummarizationInterceptor()); - // 设置最大步数(计划任务通常需要较少步数) - builder.maxSteps(20); - builder.maxStepsExtensible(true); - - // 设置会话窗口大小 - builder.sessionWindowSize(5); - } - @Override - public String getType() { - return "plan"; - } - - @Override - public SubAgentMetadata getMetadata() { - SubAgentMetadata metadata = new SubAgentMetadata(); - metadata.setCode("plan"); - metadata.setName("计划子代理"); - metadata.setDescription(getDefaultDescription()); - metadata.setEnabled(true); - metadata.setMaxTurns(20); - // 计划代理的工具:只读文件操作、网络搜索 - metadata.setTools(Arrays.asList("ls", "read", "grep", "glob", "websearch", "webfetch", "codesearch")); - // 计划代理的技能:专家技能、代码搜索 - metadata.setSkills(Arrays.asList("expert", "lucene")); - return metadata; } @Override diff --git a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/SubAgentMetadata.java b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/SubAgentMetadata.java index 384fdc3..bba31d6 100644 --- a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/SubAgentMetadata.java +++ b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/SubAgentMetadata.java @@ -15,7 +15,10 @@ */ package org.noear.solon.bot.core.subagent; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; import java.util.ArrayList; @@ -28,51 +31,80 @@ import java.util.List; * 从系统提示词头部的 YAML 配置中解析出来的元数据 * 兼容 Claude Code Agent Skills 规范 * + * 增强功能: + * - 元数据验证 + * - 元数据继承和合并 + * - Builder 模式支持(使用 Lombok) + * * @author bai * @since 3.9.5 */ @Getter @Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor public class SubAgentMetadata { - // 代理标识 - private String code; + + @Builder.Default private boolean enabled = true; // 必需字段 private String name; private String description; + // 模型配置 + private String model; + + // 最大步数(新增) + private Integer maxSteps; + + // 最大步数自动扩展(新增) + private Boolean maxStepsAutoExtensible; + + // 工具配置 + @Builder.Default private List tools = new ArrayList<>(); + + @Builder.Default private List disallowedTools = new ArrayList<>(); - // 模型配置 - private String model; // 权限配置 - private String permissionMode; // default, acceptEdits, dontAsk, bypassPermissions, plan + private String permissionMode; + // 执行限制 private Integer maxTurns; + // Skills 配置 + @Builder.Default private List skills = new ArrayList<>(); + // MCP Servers 配置 + @Builder.Default private List mcpServers = new ArrayList<>(); + // Hooks 配置(暂不解析,保留字段) private Object hooks; + // 记忆配置 private String memory; // user, project, local + // 后台任务 private Boolean background; + // 隔离配置 private String isolation; // worktree + // 团队配置 private String teamName; // 所属团队名称(用于团队成员) @@ -123,9 +155,6 @@ public class SubAgentMetadata { String value = line.substring(colonIndex + 1).trim(); switch (key) { - case "code": - metadata.code = value; - break; case "name": metadata.name = value; break; @@ -224,6 +253,7 @@ public class SubAgentMetadata { /** * 提示词和元数据的组合 */ + @Getter public static class PromptWithMetadata { private final SubAgentMetadata metadata; private final String prompt; @@ -233,13 +263,6 @@ public class SubAgentMetadata { this.prompt = prompt; } - public SubAgentMetadata getMetadata() { - return metadata; - } - - public String getPrompt() { - return prompt; - } } /** @@ -278,11 +301,6 @@ public class SubAgentMetadata { StringBuilder yaml = new StringBuilder(); yaml.append("---\n"); - // 代理标识 - if (code != null && !code.isEmpty()) { - yaml.append("code: ").append(code).append("\n"); - } - // 必需字段 if (name != null && !name.isEmpty()) { yaml.append("name: ").append(name).append("\n"); diff --git a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/Subagent.java b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/Subagent.java index 5919196..d114df2 100644 --- a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/Subagent.java +++ b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/Subagent.java @@ -23,7 +23,8 @@ import reactor.core.publisher.Flux; /** * 子代理接口 * - * 定义专门的任务执行代理接口,支持同步和流式执行。 + * 定义专门的任务执行代理接口,支持同步和流式执行, + * 提供增强的元数据支持。 * * @author bai * @since 3.9.5 @@ -32,24 +33,23 @@ public interface Subagent { /** * 获取类型 */ - String getType(); + String name(); /** - * 获取描述 + * 获取代理描述 */ String getDescription(); /** - * 获取配置 - * - * @return 子代理配置 - * 获取系统提示词 + * 获取元数据 */ - String getSystemPrompt(); + SubAgentMetadata getMetadata(); /** * 执行任务(同步) * + * @param __cwd 工作目录 + * @param sessionId 会话ID * @param prompt 任务提示 * @return 执行结果 * @throws Throwable 执行异常 @@ -59,12 +59,11 @@ public interface Subagent { /** * 执行任务(流式) * + * @param __cwd 工作目录 + * @param sessionId 会话ID * @param prompt 任务提示 * @return 流式结果 */ Flux stream(String __cwd, String sessionId, Prompt prompt); - - - SubAgentMetadata getMetadata(); } \ No newline at end of file diff --git a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/SubagentManager.java b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/SubagentManager.java index cbfe39c..1667e2a 100644 --- a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/SubagentManager.java +++ b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/SubagentManager.java @@ -15,19 +15,14 @@ */ package org.noear.solon.bot.core.subagent; -import org.noear.snack4.ONode; import org.noear.solon.bot.core.AgentKernel; -import org.noear.solon.core.util.Assert; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.yaml.snakeyaml.Yaml; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; @@ -50,17 +45,15 @@ public class SubagentManager { public SubagentManager(AgentKernel mainAgent) { this.mainAgent = mainAgent; - // 添加预置的智能体类型 + // 添加预置的智能体类型(保持向后兼容) addSubagent(new ExploreSubagent(mainAgent)); addSubagent(new PlanSubagent(mainAgent)); addSubagent(new GeneralPurposeSubagent(mainAgent)); - addSubagent(new BashSubagent(mainAgent)); } - public void addSubagent(Subagent subagent) { - subagentMap.putIfAbsent(subagent.getType(), subagent); + subagentMap.putIfAbsent(subagent.name(), subagent); } /** @@ -170,17 +163,12 @@ public class SubagentManager { } AbsSubagent subagent = (AbsSubagent) subagentMap.computeIfAbsent(subagentType, - k -> new GeneralPurposeSubagent(mainAgent, k)); + k -> new GeneralPurposeSubagent(mainAgent, parsed.getMetadata())); // 设置解析后的属性 subagent.setDescription(parsed.getMetadata().getDescription()); subagent.setSystemPrompt(parsed.getPrompt()); - // 设置完整的 metadata(包括 teamName 等字段) - subagent.setMetadata(parsed.getMetadata()); - - subagent.refresh(); - LOG.debug("加载子代理: {} 从 {}", subagentType, file); } catch (IOException e) { LOG.error("读取代理文件失败: {}", file, e); 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 b9d15f8..7d62bf8 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 @@ -17,32 +17,25 @@ package org.noear.solon.bot.core.subagent; import org.noear.solon.ai.agent.AgentResponse; import org.noear.solon.ai.agent.AgentSession; -import org.noear.solon.ai.agent.react.ReActChunk; import org.noear.solon.ai.agent.react.ReActTrace; -import org.noear.solon.ai.agent.react.task.ActionChunk; -import org.noear.solon.ai.agent.react.task.ReasonChunk; import org.noear.solon.ai.annotation.ToolMapping; -import org.noear.solon.ai.chat.ChatSession; import org.noear.solon.ai.chat.prompt.Prompt; import org.noear.solon.ai.chat.skill.AbsSkill; -import org.noear.solon.ai.chat.tool.AbsTool; import org.noear.solon.annotation.Param; 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 reactor.core.scheduler.Schedulers; import java.io.BufferedWriter; -import java.io.FileOutputStream; import java.io.OutputStreamWriter; 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; @@ -58,11 +51,6 @@ import java.util.concurrent.TimeUnit; 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; @@ -95,18 +83,18 @@ public class TaskSkill extends AbsSkill { StringBuilder sb = new StringBuilder(); sb.append("处理复杂的、多步骤的任务,必须委派子代理(Subagent)执行\n\n"); - sb.append("### ⚠️ 核心规则(强制执行)\n\n"); - sb.append("#### 🚫 禁止行为\n"); + sb.append("### 核心规则(强制执行)\n\n"); + sb.append("#### 禁止行为\n"); sb.append("1. **禁止模拟工作**:严禁不断更新状态而无实际产出\n"); sb.append("2. **必须有实际产出**:代码任务必须生成文件,使用 ls/read 验证\n\n"); - sb.append("#### ✅ 必须行为\n"); + sb.append("#### 必须行为\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())); + sb.append(String.format(" - \"%s\": %s\n", agent.name(), agent.getDescription())); } sb.append("\n\n"); @@ -118,8 +106,8 @@ public class TaskSkill extends AbsSkill { } @ToolMapping(name = "task", - description = "【强制使用】派生并分派任务给专项子代理。所有实际开发工作必须使用此工具委派给子代理完成。") - public String task( + description = "【强制使用】派生并分派任务给专项子代理。所有实际开发工作必须使用此工具委派给子代理完成。返回 Mono 用于异步处理。") + public Mono task( @Param(name = "subagentType", description = "子代理类型") String subagentType, @Param(name = "prompt", description = "具体指令。必须包含任务目标、关键类名或必要的背景上下文。") String prompt, @Param(name = "description", required = false, description = "简短的任务描述") String description, @@ -130,35 +118,37 @@ public class TaskSkill extends AbsSkill { AgentSession __parentSession = mainAgent.getSession(__sessionId); ReActTrace __parentTrace = ReActTrace.getCurrent(__parentSession.getSnapshot()); - try { - Subagent agent = manager.getAgent(subagentType); - if (agent == null) { - return "ERROR: 未知的子代理类型 '" + subagentType + "'。"; - } + // 检查子代理是否存在 + Subagent agent = manager.getAgent(subagentType); + if (agent == null) { + return Mono.just("ERROR: 未知的子代理类型 '" + subagentType + "'。"); + } - String finalSessionId = Assert.isEmpty(taskId) - ? "subagent_" + subagentType - : taskId; + String finalSessionId = Assert.isEmpty(taskId) + ? "subagent_" + subagentType + : taskId; - LOG.info("分派任务 -> 类型: {}, 会话: {}, 描述: {}", subagentType, finalSessionId, description); + LOG.info("分派任务 -> 类型: {}, 会话: {}, 描述: {}", subagentType, finalSessionId, description); - if (!acquirePermit(subagentType)) { - return "ERROR: 等待子代理执行许可超时。当前可能有太多子代理在执行。"; + // 使用 Mono.fromCallable 包装阻塞操作 + return Mono.fromCallable(() -> { + // 获取执行许可 + boolean acquired; + try { + acquired = acquirePermit(subagentType); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException("获取执行许可被中断", e); } - String result = null; + if (!acquired) { + throw new RuntimeException("等待子代理执行许可超时。当前可能有太多子代理在执行。"); + } 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); - } + AgentResponse response = agent.call(__cwd, finalSessionId, Prompt.of(prompt)); + String result = response.getContent(); + __parentTrace.getMetrics().addMetrics(response.getMetrics()); LOG.info("子代理任务完成: {}", finalSessionId); @@ -171,15 +161,25 @@ public class TaskSkill extends AbsSkill { "", finalSessionId, subagentType, result != null ? result : "(无输出)" ); - + } catch (Throwable e) { + LOG.error("子代理调用异常: type={}, error={}", subagentType, e.getMessage(), e); + throw new RuntimeException("子代理调用失败: " + e.getMessage(), e); } finally { releasePermit(subagentType); } - - } catch (Throwable e) { - LOG.error("子代理执行崩溃: type={}, error={}", subagentType, e.getMessage(), e); - return "ERROR: 子代理执行失败: " + e.getMessage(); - } + }) + .subscribeOn(Schedulers.boundedElastic()) // 在弹性线程池中执行,避免阻塞 + .doOnSubscribe(subscription -> LOG.debug("开始异步执行子代理任务: type={}, sessionId={}", subagentType, finalSessionId)) + .doOnError(error -> LOG.error("子代理执行失败: type={}, error={}", subagentType, error.getMessage(), error)) + .onErrorResume(throwable -> { + // 捕获所有异常并返回错误消息 + if (throwable instanceof RuntimeException && + throwable.getMessage().contains("等待子代理执行许可超时")) { + return Mono.just("ERROR: " + throwable.getMessage()); + } + LOG.error("子代理执行崩溃: type={}, error={}", subagentType, throwable.getMessage(), throwable); + return Mono.just("ERROR: 子代理执行失败: " + throwable.getMessage()); + }); } /** @@ -227,85 +227,6 @@ public class TaskSkill extends AbsSkill { 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 = "动态创建一个新的子代理。") @@ -323,7 +244,6 @@ public class TaskSkill extends AbsSkill { ) { try { SubAgentMetadata metadata = new SubAgentMetadata(); - metadata.setCode(code); metadata.setName(name); metadata.setDescription(description); metadata.setEnabled(true); @@ -360,7 +280,7 @@ public class TaskSkill extends AbsSkill { LOG.info("Agent 定义已保存到: {}", agentFile); } - AbsSubagent newAgent = new GeneralPurposeSubagent(mainAgent, code); + AbsSubagent newAgent = new GeneralPurposeSubagent(mainAgent, metadata); newAgent.setDescription(description); newAgent.setSystemPrompt(agentDefinition); newAgent.refresh(); 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 c4ab2ee..73fb750 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 @@ -15,34 +15,36 @@ */ package org.noear.solon.bot.core.teams; -import lombok.Getter; +import org.noear.snack4.ONode; import org.noear.solon.ai.agent.AgentChunk; +import org.noear.solon.ai.agent.Agent; +import org.noear.solon.ai.agent.team.TeamAgent; +import org.noear.solon.ai.agent.team.TeamProtocols; import org.noear.solon.ai.chat.ChatModel; import org.noear.solon.ai.chat.prompt.Prompt; import org.noear.solon.ai.chat.skill.AbsSkill; import org.noear.solon.ai.annotation.ToolMapping; import org.noear.solon.annotation.Param; import org.noear.solon.bot.core.AgentKernel; -import org.noear.solon.bot.core.memory.KnowledgeMemory; -import org.noear.solon.bot.core.memory.LongTermMemory; -import org.noear.solon.bot.core.memory.Memory; -import org.noear.solon.bot.core.memory.ShortTermMemory; import org.noear.solon.bot.core.memory.smart.IntelligentMemoryManager; +import org.noear.solon.bot.core.subagent.AbsSubagent; import org.noear.solon.bot.core.subagent.SubAgentMetadata; import org.noear.solon.bot.core.subagent.Subagent; import org.noear.solon.bot.core.subagent.SubagentManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Schedulers; import java.io.File; +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.*; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeoutException; import java.util.stream.Collectors; /** @@ -78,9 +80,6 @@ public class AgentTeamsSkill extends AbsSkill { // 初始化智能记忆管理器 if (mainAgent != null && mainAgent.getSharedMemoryManager() != null) { - // 使用与 SharedMemoryManager 相同的路径 - // 注意:IntelligentMemoryManager 内部会创建 SharedMemoryManager,会再次拼接路径 - // 所以这里传入 workDir 即可 String workDir = System.getProperty("user.dir") + File.separator + "work"; this.intelligentMemoryManager = new IntelligentMemoryManager(workDir); LOG.info("初始化智能记忆管理器: workDir={}", workDir); @@ -98,12 +97,12 @@ public class AgentTeamsSkill extends AbsSkill { @Override public String getInstruction(Prompt prompt) { StringBuilder sb = new StringBuilder(); + sb.append("## Agent Teams 协调能力\n\n"); sb.append("你是一个团队协调器,可以启动和管理多代理协作任务。\n\n"); - // ========== 新增:禁止模拟工作规则 ========== - sb.append("### ⚠️ 核心规则(强制执行)\n\n"); - sb.append("#### 🚫 禁止行为(绝对不可违反)\n"); + sb.append("### 核心规则(强制执行)\n\n"); + sb.append("#### 禁止行为(绝对不可违反)\n"); sb.append("1. **禁止模拟工作**:\n"); sb.append(" - 严禁不断更新 `update_working_memory`、`step`、`currentAgent` 而无实际产出\n"); sb.append(" - 不得使用 `memory_store` 存储虚假的\"已完成\"状态\n"); @@ -117,7 +116,7 @@ public class AgentTeamsSkill extends AbsSkill { sb.append("3. **禁止循环操作**:\n"); sb.append(" - 不得重复调用相同工具而不产生新进展\n"); sb.append(" - 检测到循环时必须立即停止并使用 `task()` 工具\n\n"); - sb.append("#### ✅ 必须行为(强制执行)\n"); + sb.append("#### 必须行为(强制执行)\n"); sb.append("1. **必须使用 task 工具**:\n"); sb.append(" - 所有实际工作必须通过 `task(subagent_type, prompt)` 委派给子代理\n"); sb.append(" - 可用类型:explore、plan、bash、general-purpose、solon-code-guide\n"); @@ -131,15 +130,17 @@ public class AgentTeamsSkill extends AbsSkill { sb.append(" - 超过 5,000 tokens 无产出时必须改变策略\n"); sb.append(" - 禁止无限循环调用工具\n\n"); - sb.append("### 工作流程\n" + - ". 分析任务:识别所需的专业领域。\n" + - ". 组建团队:自动激活相关领域的专家 Agent。\n" + - ". 引导讨论:\n" + - " - 让专家轮流发表观点。\n" + - " - 鼓励专家互相质疑(例如:安全专家挑战开发专家的架构)。\n" + - " - 记录争议点并寻求共识。\n" + - ". 生成报告:汇总讨论结果,去除冗余对话,只保留高质量的最终结论。"); - + // ========== 工作流程 ========== + sb.append("### 工作流程\n"); + sb.append("1. 分析任务:识别所需的专业领域。\n"); + sb.append("2. 组建团队:自动激活相关领域的专家 Agent。\n"); + sb.append("3. 引导讨论:\n"); + sb.append(" - 让专家轮流发表观点。\n"); + sb.append(" - 鼓励专家互相质疑(例如:安全专家挑战开发专家的架构)。\n"); + sb.append(" - 记录争议点并寻求共识。\n"); + sb.append("4. 生成报告:汇总讨论结果,去除冗余对话,只保留高质量的最终结论。\n\n"); + + // ========== 核心能力 ========== sb.append("### 核心能力\n"); sb.append("1. **团队协作任务**: 使用 `team_task()` 启动多代理协作\n"); sb.append("2. **任务管理**: \n"); @@ -170,25 +171,24 @@ public class AgentTeamsSkill extends AbsSkill { for (Subagent agent : manager.getAgents()) { if (agent.getMetadata().hasTeamName()) { sb.append(String.format(" - `%s`: %s (团队: %s)\n", - agent.getType(), + agent.name(), agent.getDescription(), agent.getMetadata().getTeamName())); } } sb.append("\n\n"); + // ========== 团队成员管理 ========== sb.append("### 团队成员管理\n"); sb.append("1. **创建成员**: 使用 `teammate()` 创建新的团队成员\n"); 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"); @@ -196,7 +196,6 @@ public class AgentTeamsSkill extends AbsSkill { 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"); @@ -206,7 +205,6 @@ public class AgentTeamsSkill extends AbsSkill { 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"); @@ -234,7 +232,7 @@ public class AgentTeamsSkill extends AbsSkill { sb.append("### 任务链协调:如何将结果传递给下一个 subagent\n\n"); sb.append("**⚠️ 重要:完成任务链协调的三种方法**\n\n"); sb.append("**方法 1:在 prompt 中传递上下文(推荐)**\n"); - sb.append("```"); + sb.append("```\n"); sb.append("# 第一步:plan 完成设计\n"); sb.append("result1 = task(subagent_type='plan', prompt='设计用户登录模块')\n"); sb.append("\n"); @@ -245,14 +243,14 @@ public class AgentTeamsSkill extends AbsSkill { sb.append(")\n"); sb.append("```\n\n"); sb.append("**方法 2:使用共享记忆**\n"); - sb.append("```"); + sb.append("```\n"); sb.append("result1 = task(subagent_type='plan', prompt='设计...')\n"); sb.append("memory_store(content=result1, key='design')\n"); sb.append("design = memory_recall(query='设计', limit=1)\n"); sb.append("task(subagent_type='bash', prompt='' + design + '创建代码')\n"); sb.append("```\n\n"); sb.append("**方法 3:使用 taskId 续接会话**\n"); - sb.append("```"); + sb.append("```\n"); sb.append("result1 = task(subagent_type='explore', prompt='分析项目')\n"); sb.append("# 返回 task_id: explore_12345\n"); sb.append("result2 = task(\n"); @@ -263,8 +261,9 @@ public class AgentTeamsSkill extends AbsSkill { sb.append("```\n\n"); sb.append("**详细指南**:参考项目文档 `docs/TASK_CHAIN_GUIDE.md`\n\n"); + // ========== 使用场景 ========== sb.append("### 使用场景\n"); - sb.append("```"); + sb.append("```\n"); sb.append("# 场景1: 创建团队成员(指定团队)\n"); sb.append("teammate(\n"); sb.append(" name=\"security-expert\",\n"); @@ -316,7 +315,10 @@ public class AgentTeamsSkill extends AbsSkill { sb.append(" message=\"需要设计一个用户认证模块,请提供实现方案\"\n"); sb.append(")\n\n"); sb.append("# 查看可用代理列表\n"); - sb.append("list_agents()\n\n"); + sb.append("list_agents()\n"); + sb.append("```\n\n"); + + // ========== 记忆管理说明 ========== sb.append("### 记忆管理说明\n"); sb.append("**智能记忆系统**(推荐使用):\n"); sb.append("- **memory_store(content, key?)**: 自动分类存储\n"); @@ -787,25 +789,15 @@ public class AgentTeamsSkill extends AbsSkill { */ 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); + ONode root = ONode.deserialize(json); + + // 检查是否为数组 + if (root.isArray()) { + for (int i = 0; i < root.size(); i++) { + ONode taskNode = root.get(i); + TeamTask task = parseTaskNode(taskNode, i); if (task != null) { tasks.add(task); } @@ -814,18 +806,19 @@ public class AgentTeamsSkill extends AbsSkill { } catch (Exception e) { LOG.warn("解析任务 JSON 失败: {}", e.getMessage()); } + return tasks; } /** - * 解析单个任务 JSON + * 从 ONode 解析单个任务 */ - private TeamTask parseSingleTaskJson(String json, int index) { + private TeamTask parseTaskNode(ONode node, int index) { try { - String title = extractJsonValue(json, "title"); - String description = extractJsonValue(json, "description"); - String typeStr = extractJsonValue(json, "type"); - String priorityStr = extractJsonValue(json, "priority"); + String title = node.get("title").getString(); + String description = node.get("description").getString(); + String typeStr = node.get("type").getString(); + int priority = node.get("priority").getInt(); if (title == null || title.isEmpty()) { title = "任务 " + (index + 1); @@ -835,7 +828,7 @@ public class AgentTeamsSkill extends AbsSkill { } TeamTask.TaskType type = TeamTask.TaskType.DEVELOPMENT; - if (typeStr != null) { + if (typeStr != null && !typeStr.isEmpty()) { try { type = TeamTask.TaskType.valueOf(typeStr.toUpperCase()); } catch (IllegalArgumentException e) { @@ -843,68 +836,25 @@ public class AgentTeamsSkill extends AbsSkill { } } - int priority = 7; - if (priorityStr != null) { - try { - priority = Integer.parseInt(priorityStr.trim()); - priority = Math.max(1, Math.min(10, priority)); - } catch (NumberFormatException e) { - // 使用默认优先级 - } + if (priority < 1 || priority > 10) { + priority = 7; } String taskId = "task-" + System.currentTimeMillis() + "-" + index; return TeamTask.builder() .id(taskId) - .title(cleanJsonString(title)) - .description(cleanJsonString(description)) + .title(title) + .description(description) .type(type) .priority(priority) .dependencies(new ArrayList<>()) .build(); - } catch (Exception e) { - LOG.warn("解析单个任务失败: {}, error: {}", json, e.getMessage()); + LOG.warn("解析任务节点失败: {}, error: {}", node, 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(); - } - /** * 获取状态图标 */ @@ -1006,7 +956,6 @@ public class AgentTeamsSkill extends AbsSkill { // 构建子代理元数据 SubAgentMetadata metadata = new SubAgentMetadata(); - metadata.setCode(name); metadata.setName(role); metadata.setDescription(description); metadata.setEnabled(true); @@ -1197,7 +1146,7 @@ public class AgentTeamsSkill extends AbsSkill { result.append("|------|------|------|------|------|------|\n"); for (Subagent agent : agents) { - String name = String.format("`%s`", agent.getType()); + String name = String.format("`%s`", agent.name()); String role = agent.getClass().getSimpleName().replace("Subagent", ""); String desc = truncate(agent.getDescription(), 30); String team = agent.getMetadata().hasTeamName() ? agent.getMetadata().getTeamName() : "-"; @@ -2017,22 +1966,6 @@ public class AgentTeamsSkill extends AbsSkill { } } - /** - * 获取记忆内容(辅助方法) - */ - private String getMemoryContent(Memory memory) { - if (memory instanceof ShortTermMemory) { - return ((ShortTermMemory) memory).getContext(); - } else if (memory instanceof LongTermMemory) { - return ((LongTermMemory) memory).getSummary(); - } else if (memory instanceof KnowledgeMemory) { - return ((KnowledgeMemory) memory).getContent(); - } else { - return memory.getId(); - } - } - - // ==================== 代理间通信工具 ==================== /** * 发送消息给其他代理 @@ -2104,13 +2037,12 @@ public class AgentTeamsSkill extends AbsSkill { sb.append("**团队成员**:\n"); // 从 SubagentManager 获取自定义团队成员 for (Subagent agent : manager.getAgents()) { - if (!agent.getType().equals("explore") - && !agent.getType().equals("plan") - && !agent.getType().equals("bash") - && !agent.getType().equals("general-purpose") - && !agent.getType().equals("solon-code-guide")) { + if (!agent.name().equals("explore") + && !agent.name().equals("plan") + && !agent.name().equals("bash") + && !agent.name().equals("general-purpose")) { sb.append(String.format("- `%s`: %s\n", - agent.getType(), agent.getDescription())); + agent.name(), agent.getDescription())); } } @@ -2307,7 +2239,241 @@ public class AgentTeamsSkill extends AbsSkill { return manager.getAgents().stream() .filter(agent -> agent.getMetadata().hasTeamName() && agent.getMetadata().getTeamName().equals(teamName)) - .map(agent -> agent.getMetadata().getCode()) + .map(agent -> agent.getMetadata().getName()) .collect(Collectors.toList()); } + + + /** + * 列出所有团队配置 + * + * @return 团队列表 + */ + @ToolMapping(name = "list_teams", + description = "列出所有已配置的团队。显示团队名称、描述、成员数量等信息。") + public String listTeams() { + try { + StringBuilder sb = new StringBuilder(); + sb.append("## 团队配置列表\n\n"); + + String teamsDir = mainAgent.getWorkDir() + File.separator + ".soloncode" + File.separator + "teams"; + File dir = new File(teamsDir); + + if (!dir.exists()) { + return "[INFO] 暂无团队配置。请使用 `create_team_config` 创建团队。\n\n" + + "提示:可用的代理包括:explore, plan, bash, general-purpose"; + } + + File[] files = dir.listFiles((d, name) -> name.endsWith(".md")); + if (files == null || files.length == 0) { + return "[INFO] 暂无团队配置。请使用 `create_team_config` 创建团队。\n\n" + + "提示:可用的代理包括:explore, plan, bash, general-purpose"; + } + + for (File file : files) { + String teamName = file.getName().replace(".md", ""); + + // 读取配置文件 + String content = new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8); + + // 解析基本信息 + String description = ""; + String protocol = "sequential"; + List members = new ArrayList<>(); + + String[] lines = content.split("\n"); + boolean inFrontMatter = false; + for (String line : lines) { + if (line.trim().equals("---")) { + inFrontMatter = !inFrontMatter; + continue; + } + + if (inFrontMatter) { + if (line.startsWith("description:")) { + description = line.substring(12).trim(); + } else if (line.startsWith("protocol:")) { + protocol = line.substring(9).trim(); + } else if (line.startsWith("agents:")) { + // 解析成员列表在后面的部分 + } + } else { + if (line.startsWith("- **")) { + String member = line.substring(4).replace("**", "").trim(); + if (!member.isEmpty()) { + members.add(member); + } + } + } + } + + sb.append("### ").append(teamName).append("\n\n"); + sb.append("**描述**: ").append(description).append("\n"); + sb.append("**协议**: ").append(protocol).append("\n"); + sb.append("**成员** (").append(members.size()).append("): "); + sb.append(String.join(", ", members)).append("\n"); + sb.append("**配置文件**: ").append(file.getAbsolutePath()).append("\n\n"); + } + + if (sb.length() == 23) { // 只有标题 + sb.append("暂无团队配置\n"); + } + + sb.append("\n**使用说明**:\n"); + sb.append("- 使用 `create_team_config` 创建新团队\n"); + sb.append("- 团队配置保存在: ").append(teamsDir).append("\n"); + sb.append("- 支持的协议: sequential(顺序执行), hierarchical(层级调度)"); + + return sb.toString(); + + } catch (Exception e) { + LOG.error("列出团队失败", e); + return "[ERROR] 列出团队失败: " + e.getMessage(); + } + } + + /** + * 执行团队任务(异步) + * + * 根据已保存的团队配置,动态构建 TeamAgent 并执行任务 + * + * @param teamName 团队名称 + * @param task 任务描述 + * @return 异步执行结果(Mono) + */ + @ToolMapping(name = "run_team_task", + description = "使用指定的团队执行任务。根据团队配置自动协调多个代理协作完成任务。返回 Mono 用于异步处理。") + public Mono runTeamTask( + @Param(name = "teamName", description = "团队名称(使用 list_teams 查看)") String teamName, + @Param(name = "task", description = "任务描述") String task) { + + return Mono.fromCallable(() -> { + try { + // 1. 读取团队配置文件 + String teamsDir = mainAgent.getWorkDir() + File.separator + ".soloncode" + File.separator + "teams"; + File configFile = new File(teamsDir + File.separator + teamName + ".md"); + + if (!configFile.exists()) { + return "[ERROR] 团队配置不存在: " + teamName + "\n\n" + + "请使用 `list_teams` 查看可用的团队,或使用 `create_team_config` 创建新团队。"; + } + + // 2. 解析配置 + String content = new String(Files.readAllBytes(configFile.toPath()), StandardCharsets.UTF_8); + + String description = ""; + String protocol = "sequential"; + List agentNames = new ArrayList<>(); + + String[] lines = content.split("\n"); + boolean inFrontMatter = false; + for (String line : lines) { + if (line.trim().equals("---")) { + inFrontMatter = !inFrontMatter; + continue; + } + + if (inFrontMatter) { + if (line.startsWith("description:")) { + description = line.substring(12).trim(); + } else if (line.startsWith("protocol:")) { + protocol = line.substring(9).trim(); + } + } else { + if (line.startsWith("- **")) { + String agentName = line.substring(4).replace("**", "").trim(); + if (!agentName.isEmpty()) { + agentNames.add(agentName); + } + } + } + } + + // 3. 验证代理是否可用 + List validAgents = new ArrayList<>(); + List missingAgents = new ArrayList<>(); + + for (String agentName : agentNames) { + Subagent agent = manager.getAgent(agentName); + if (agent != null) { + validAgents.add(agent); + } else { + missingAgents.add(agentName); + } + } + + if (validAgents.isEmpty()) { + return "[ERROR] 团队中没有可用的代理\n\n" + + "缺少的代理: " + String.join(", ", missingAgents) + "\n" + + "请确保这些代理已在 SubagentManager 中注册。"; + } + + // 4. 构建 TeamAgent + ChatModel chatModel = mainAgent.getChatModel(); + + TeamAgent.Builder teamBuilder = TeamAgent.of(chatModel) + .name(teamName) + .role(description.isEmpty() ? teamName : description) + .instruction("团队协作完成用户指定的任务。根据任务特点和各成员的能力,合理分配和协调工作。"); + + // 5. 添加成员(从 Subagent 中提取底层的 ReActAgent) + for (Subagent agent : validAgents) { + if (agent instanceof AbsSubagent) { + AbsSubagent absSubagent = (AbsSubagent) agent; + teamBuilder.agentAdd(absSubagent.getCachedAgent()); + } else { + LOG.warn("代理 {} 不是 AbsSubagent 类型,跳过", agent.name()); + } + } + + // 6. 配置协议 + if ("hierarchical".equalsIgnoreCase(protocol)) { + teamBuilder.protocol(TeamProtocols.HIERARCHICAL); + } else { + teamBuilder.protocol(TeamProtocols.SEQUENTIAL); + } + + // 7. 配置运行参数 + teamBuilder.maxTurns(15); // 最多协作 15 轮 + + // 8. 构建并执行 + TeamAgent teamAgent = teamBuilder.build(); + + LOG.info("启动团队任务: team={}, members={}, protocol={}", + teamName, validAgents.size(), protocol); + + // 执行任务 + String result; + try { + result = teamAgent.prompt(task) + .call() + .getContent(); + } catch (Throwable ex) { + LOG.error("团队任务执行异常", ex); + return "[ERROR] 团队任务执行异常: " + ex.getMessage(); + } + + // 9. 返回结果 + StringBuilder sb = new StringBuilder(); + sb.append("[OK] 团队任务执行完成\n\n"); + sb.append("**团队**: ").append(teamName).append("\n"); + sb.append("**协议**: ").append(protocol).append("\n"); + sb.append("**参与成员**: ").append(validAgents.size()).append("\n"); + sb.append("\n--- 执行结果 ---\n\n"); + sb.append(result); + + return sb.toString(); + + } catch (Exception e) { + LOG.error("执行团队任务失败: team={}, task={}", teamName, task, e); + return "[ERROR] 执行团队任务失败: " + e.getMessage() + "\n\n" + + "团队: " + teamName + "\n" + + "任务: " + task; + } + }) + .subscribeOn(Schedulers.boundedElastic()) // 在弹性线程池中执行,避免阻塞 + .doOnSubscribe(subscription -> LOG.info("开始异步执行团队任务: team={}, task={}", teamName, task)) + .doOnError(error -> LOG.error("团队任务执行失败: team={}, task={}", teamName, task, error)) + .doOnSuccess(result -> LOG.info("团队任务执行成功: team={}", teamName)); + } } diff --git a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/teams/AgentTeamsTools.java b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/teams/AgentTeamsTools.java index 8b77bb8..a66194e 100644 --- a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/teams/AgentTeamsTools.java +++ b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/teams/AgentTeamsTools.java @@ -44,6 +44,8 @@ public class AgentTeamsTools extends AbsSkill { private static final Logger LOG = LoggerFactory.getLogger(AgentTeamsTools.class); + private static AgentTeamsTools instance = null; + private final SharedMemoryManager memoryManager; private final EventBus eventBus; @@ -51,6 +53,16 @@ public class AgentTeamsTools extends AbsSkill { EventBus eventBus) { this.memoryManager = memoryManager; this.eventBus = eventBus; + if (instance == null){ + instance = this; + } + } + + public static AgentTeamsTools getInstance(){ + if (instance == null){ + throw new RuntimeException("AgentTeamsTools is not initialized"); + } + return instance; } @Override @@ -63,7 +75,7 @@ public class AgentTeamsTools extends AbsSkill { StringBuilder sb = new StringBuilder(); sb.append("## Agent Teams 内部工具集(底层API)\n\n"); sb.append("这是 MainAgent 内部使用的底层工具集,提供记忆管理和事件发布功能。\n\n"); - sb.append("### ⚠️ 使用建议\n\n"); + sb.append("### 使用建议\n\n"); sb.append("**大多数情况下,推荐使用 AgentTeamsSkill 中的智能记忆工具**:\n"); sb.append("- `memory_store()`: 自动分类存储(系统自动判断存储类型和重要性)\n"); sb.append("- `memory_recall()`: 智能检索(按相关性排序,自动合并重复内容)\n"); 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 ce8df1e..2438108 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 @@ -17,16 +17,13 @@ package org.noear.solon.bot.core.teams; import lombok.Getter; import org.noear.solon.ai.agent.AgentChunk; -import org.noear.solon.ai.agent.AgentResponse; import org.noear.solon.ai.agent.AgentSession; import org.noear.solon.ai.agent.AgentSessionProvider; import org.noear.solon.ai.agent.react.ReActAgent; import org.noear.solon.ai.chat.ChatModel; import org.noear.solon.ai.chat.prompt.Prompt; import org.noear.solon.bot.core.AgentKernel; -import org.noear.solon.bot.core.CliSkillProvider; import org.noear.solon.bot.core.PoolManager; -import org.noear.solon.bot.core.SystemPrompt; import org.noear.solon.bot.core.event.AgentEvent; import org.noear.solon.bot.core.event.AgentEventType; import org.noear.solon.bot.core.event.EventBus; @@ -40,7 +37,6 @@ import org.noear.solon.bot.core.message.MessageAck; import org.noear.solon.bot.core.message.MessageChannel; import org.noear.solon.bot.core.subagent.SubAgentMetadata; import org.noear.solon.bot.core.subagent.SubagentManager; -import org.noear.solon.bot.core.subagent.TaskSkill; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.publisher.Flux; @@ -97,6 +93,8 @@ public class MainAgent { // 性能优化:使用 CountDownLatch 替代轮询 private volatile CountDownLatch taskCompletionLatch; + + public MainAgent(SubAgentMetadata config, AgentSessionProvider sessionProvider, SharedMemoryManager sharedMemoryManager, @@ -386,7 +384,7 @@ public class MainAgent { payload.put("tasks", tasks); AgentMessage> message = AgentMessage.>of(payload) - .from(config.getCode()) + .from(config.getName()) .to("*") .type("task_notification") .build(); @@ -659,7 +657,7 @@ public class MainAgent { private void registerMessageHandler() { if (messageChannel != null) { messageHandlerId = messageChannel.registerHandler( - config.getCode(), + config.getName(), this::handleMessage ); LOG.info("MainAgent 消息处理器已注册"); @@ -719,7 +717,7 @@ public class MainAgent { private void publishEvent(AgentEventType eventType, Object payload, String taskId) { if (eventBus != null) { EventMetadata metadata = EventMetadata.builder() - .sourceAgent(config.getCode()) + .sourceAgent(config.getName()) .taskId(taskId) .priority(5) .build(); @@ -883,7 +881,7 @@ public class MainAgent { // 注销消息处理器 if (messageChannel != null && messageHandlerId != null) { - messageChannel.unregisterHandler(config.getCode(), messageHandlerId); + messageChannel.unregisterHandler(config.getName(), messageHandlerId); LOG.info("MainAgent 消息处理器已注销"); } } -- Gitee From 7ab85446aa367974df01e71141995a038809d0d6 Mon Sep 17 00:00:00 2001 From: bai <1145000687@qq.com> Date: Mon, 16 Mar 2026 22:56:44 +0800 Subject: [PATCH 3/5] =?UTF-8?q?refactor(agents):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E6=99=BA=E8=83=BD=E4=BD=93=E5=9B=A2=E9=98=9F=E5=8D=8F=E4=BD=9C?= =?UTF-8?q?=E6=9E=B6=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除 AbsSubagent 中的 systemPrompt 字段和 createDefaultMetadata 方法中的 name 配置 - 将 SummarizationInterceptor 的初始化从 AgentKernel 后移到构建流程中 - 更新 AgentTeamsSkill 中的智能记忆管理器路径设置方式 - 将 teamTask 方法改为异步 Mono 返回并优化错误处理机制 - 修复 SubAgentMetadata 中的 name 属性设置问题 - 简化 MainAgent 角色指令,优化协作模式说明 - 改进团队任务执行逻辑,支持从配置文件或动态获取团队成员 - 优化 FileMemoryStore 中的文件路径处理和目录验证逻辑 - 重构 GeneralPurposeSubagent 的构造函数和指令应用方式 - 更新 IntelligentMemoryManager 的工作目录参数类型为 Path - 修复 PlanSubagent 和 ExploreSubagent 中的元数据创建方法移除问题 --- .../org/noear/solon/bot/core/AgentKernel.java | 24 +- .../bot/core/memory/SharedMemoryManager.java | 70 ++-- .../memory/bank/store/FileMemoryStore.java | 29 +- .../smart/IntelligentMemoryManager.java | 7 +- .../solon/bot/core/subagent/AbsSubagent.java | 9 +- .../solon/bot/core/subagent/BashSubagent.java | 14 - .../bot/core/subagent/ExploreSubagent.java | 15 - .../core/subagent/GeneralPurposeSubagent.java | 34 +- .../solon/bot/core/subagent/PlanSubagent.java | 16 - .../bot/core/subagent/SubagentManager.java | 3 +- .../solon/bot/core/subagent/TaskSkill.java | 109 ++--- .../solon/bot/core/teams/AgentTeamsSkill.java | 386 ++++++++---------- .../noear/solon/bot/core/teams/MainAgent.java | 103 ++++- .../bot/core/teams/SubAgentAgentBuilder.java | 5 +- 14 files changed, 411 insertions(+), 413 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 eea4cb5..bb45218 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 @@ -183,6 +183,18 @@ public class AgentKernel { agentBuilder.defaultSkillAdd(codeSkill); agentBuilder.defaultSkillAdd(luceneSkill); + //上下文摘要 + SummarizationStrategy strategy = new CompositeSummarizationStrategy() + .addStrategy(new KeyInfoExtractionStrategy(chatModel)) // 提取干货(去水) + .addStrategy(new HierarchicalSummarizationStrategy(chatModel)); // 滚动更新摘要 + + summarizationInterceptor = new SummarizationInterceptor( + properties.getSummaryWindowSize(), + properties.getSummaryWindowToken(), + strategy); + + agentBuilder.defaultInterceptorAdd(summarizationInterceptor); + if(properties.isBrowserEnabled() && ClassUtil.hasClass(()-> Playwright.class)) { agentBuilder.defaultSkillAdd(new BrowserSkill()); } @@ -209,18 +221,6 @@ public class AgentKernel { initAgentTeams(properties, agentBuilder); } - //上下文摘要 - SummarizationStrategy strategy = new CompositeSummarizationStrategy() - .addStrategy(new KeyInfoExtractionStrategy(chatModel)) // 提取干货(去水) - .addStrategy(new HierarchicalSummarizationStrategy(chatModel)); // 滚动更新摘要 - - summarizationInterceptor = new SummarizationInterceptor( - properties.getSummaryWindowSize(), - properties.getSummaryWindowToken(), - strategy); - - agentBuilder.defaultInterceptorAdd(summarizationInterceptor); - // HITL 交互干预(优先使用实例字段,否则使用配置) if (properties.isHitlEnabled()) { agentBuilder.defaultInterceptorAdd(new HITLInterceptor() diff --git a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/memory/SharedMemoryManager.java b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/memory/SharedMemoryManager.java index b351a57..2e52a33 100644 --- a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/memory/SharedMemoryManager.java +++ b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/memory/SharedMemoryManager.java @@ -828,42 +828,54 @@ public class SharedMemoryManager { LOG.info("从存储加载了 {} 条观察", observations.size()); // 将 Observation 转换并缓存到对应的 Memory 类型 - for (Observation obs : observations) { - // 根据类型转换 - Memory memory = null; - if (obs.getType() == Observation.ObservationType.GENERAL) { - ShortTermMemory stm = new ShortTermMemory(); - stm.setId(obs.getId()); - stm.setContext(obs.getContent()); - stm.setTimestamp(obs.getTimestamp()); - memory = stm; - } else if (obs.getType() == Observation.ObservationType.TASK_RESULT) { - LongTermMemory ltm = new LongTermMemory(); - ltm.setId(obs.getId()); - ltm.setSummary(obs.getContent()); - ltm.setTimestamp(obs.getTimestamp()); - ltm.setImportance((float) obs.getImportance()); - ltm.setTags(new ArrayList<>()); - memory = ltm; - } else if (obs.getType() == Observation.ObservationType.ARCHITECTURE) { - KnowledgeMemory km = new KnowledgeMemory(); - km.setId(obs.getId()); - km.setContent(obs.getContent()); - km.setTimestamp(obs.getTimestamp()); - km.setKeywords(new ArrayList<>()); - memory = km; - } + int loadedCount = 0; + int skippedCount = 0; - if (memory != null && !memory.isExpired()) { - store(memory); + for (Observation obs : observations) { + try { + // 根据类型转换 + Memory memory = null; + if (obs.getType() == Observation.ObservationType.GENERAL) { + ShortTermMemory stm = new ShortTermMemory(); + stm.setId(obs.getId()); + stm.setContext(obs.getContent()); + stm.setTimestamp(obs.getTimestamp()); + memory = stm; + } else if (obs.getType() == Observation.ObservationType.TASK_RESULT) { + LongTermMemory ltm = new LongTermMemory(); + ltm.setId(obs.getId()); + ltm.setSummary(obs.getContent()); + ltm.setTimestamp(obs.getTimestamp()); + ltm.setImportance((float) obs.getImportance()); + ltm.setTags(new ArrayList<>()); + memory = ltm; + } else if (obs.getType() == Observation.ObservationType.ARCHITECTURE) { + KnowledgeMemory km = new KnowledgeMemory(); + km.setId(obs.getId()); + km.setContent(obs.getContent()); + km.setTimestamp(obs.getTimestamp()); + km.setKeywords(new ArrayList<>()); + memory = km; + } + + if (memory != null && !memory.isExpired()) { + store(memory); + loadedCount++; + } else { + skippedCount++; + } + } catch (Exception e) { + LOG.warn("加载单个观察失败: id={}, error={}", obs.getId(), e.getMessage()); + skippedCount++; } } Map stats = memoryBank.getStats(); - LOG.info("共享记忆加载完成: {}", stats); + LOG.info("共享记忆加载完成: loaded={}, skipped={}, stats={}", loadedCount, skippedCount, stats); } catch (Exception e) { - LOG.warn("共享记忆初始化失败: error={}", e.getMessage()); + LOG.warn("共享记忆初始化失败: error={}", e.getMessage(), e); + // 不抛出异常,允许系统继续运行 } } diff --git a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/memory/bank/store/FileMemoryStore.java b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/memory/bank/store/FileMemoryStore.java index 9b82740..846a30b 100644 --- a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/memory/bank/store/FileMemoryStore.java +++ b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/memory/bank/store/FileMemoryStore.java @@ -51,9 +51,24 @@ public class FileMemoryStore implements MemoryStore { this.storePath = workDir; // 确保目录存在 - File dir = new File(storePath); - if (!dir.exists()) { - dir.mkdirs(); + try { + File dir = new File(storePath); + if (!dir.exists()) { + boolean created = dir.mkdirs(); + if (created) { + LOG.info("创建记忆存储目录: {}", storePath); + } + } + + // 验证目录是否可写 + if (!dir.isDirectory()) { + LOG.error("路径不是目录: {}", storePath); + } else if (!dir.canWrite()) { + LOG.warn("目录不可写: {}", storePath); + } + + } catch (Exception e) { + LOG.error("初始化文件存储失败: path={}, error={}", storePath, e.getMessage(), e); } LOG.info("MemoryBank 文件存储初始化完成: path={}", storePath); @@ -96,8 +111,8 @@ public class FileMemoryStore implements MemoryStore { private void storeWithAtomicWrite(Observation observation) throws Exception { String fileName = observation.getId() + ".json"; - String tempFilePath = storePath + fileName + TEMP_FILE_SUFFIX; - String targetFilePath = storePath + fileName; + String tempFilePath = storePath + File.separator + fileName + TEMP_FILE_SUFFIX; + String targetFilePath = storePath + File.separator + fileName; // 序列化为 JSON String json = ONode.serialize(observation); @@ -117,7 +132,7 @@ public class FileMemoryStore implements MemoryStore { @Override public Observation load(String id) { try { - String filePath = storePath + id + ".json"; + String filePath = storePath + File.separator + id + ".json"; File file = new File(filePath); if (!file.exists()) { @@ -136,7 +151,7 @@ public class FileMemoryStore implements MemoryStore { @Override public void delete(String id) { try { - String filePath = storePath + id + ".json"; + String filePath = storePath + File.separator + id + ".json"; File file = new File(filePath); if (file.exists()) { diff --git a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/memory/smart/IntelligentMemoryManager.java b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/memory/smart/IntelligentMemoryManager.java index cf251dd..026000a 100644 --- a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/memory/smart/IntelligentMemoryManager.java +++ b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/memory/smart/IntelligentMemoryManager.java @@ -27,6 +27,7 @@ import org.noear.solon.bot.core.memory.scorer.EnhancedImportanceScorer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; import java.util.concurrent.Executors; @@ -77,7 +78,7 @@ public class IntelligentMemoryManager { * * @param workDir 工作目录 */ - public IntelligentMemoryManager(String workDir) { + public IntelligentMemoryManager(Path workDir) { this(workDir, true, 0.85, 300_000L); // 默认5分钟合并一次 } @@ -89,13 +90,13 @@ public class IntelligentMemoryManager { * @param consolidationThreshold 合并阈值(0-1) * @param consolidationInterval 合并间隔(毫秒) */ - public IntelligentMemoryManager(String workDir, + public IntelligentMemoryManager(Path workDir, boolean autoConsolidate, double consolidationThreshold, long consolidationInterval) { this.autoClassifier = new MemoryAutoClassifier(); this.importanceScorer = new EnhancedImportanceScorer(); - this.delegate = new SharedMemoryManager(Paths.get(workDir)); + this.delegate = new SharedMemoryManager(workDir); this.autoConsolidate = autoConsolidate; this.consolidationThreshold = consolidationThreshold; this.consolidationInterval = consolidationInterval; diff --git a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/AbsSubagent.java b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/AbsSubagent.java index 7ac8441..da2bd41 100644 --- a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/AbsSubagent.java +++ b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/AbsSubagent.java @@ -18,6 +18,8 @@ package org.noear.solon.bot.core.subagent; import lombok.Getter; import lombok.Setter; +import org.apache.commons.lang3.StringUtils; +import org.jsoup.internal.StringUtil; import org.noear.solon.Utils; import org.noear.solon.ai.agent.AgentChunk; import org.noear.solon.ai.agent.AgentResponse; @@ -52,7 +54,6 @@ public abstract class AbsSubagent implements Subagent { private volatile ReActAgent cachedAgent; protected String description; - protected String systemPrompt; protected SubAgentMetadata metadata; @@ -70,10 +71,9 @@ public abstract class AbsSubagent implements Subagent { builder.defaultToolAdd(AgentTeamsTools.getInstance()); } builder.instruction(getDefaultSystemPrompt()); - + builder.defaultInterceptorAdd(mainAgent.getSummarizationInterceptor()); // 应用元数据中的属性配置到builder applyMetadataToBuilder(builder, metadata); - // 应用自定义配置 customize(builder); cachedAgent = builder.build(); @@ -108,7 +108,6 @@ public abstract class AbsSubagent implements Subagent { */ protected SubAgentMetadata createDefaultMetadata() { return SubAgentMetadata.builder() - .name(name()) // 设置代理名称 .description(getDefaultDescription()) // 设置描述 .build(); } @@ -142,7 +141,7 @@ public abstract class AbsSubagent implements Subagent { if (metadata == null) { return; } - + builder.name(metadata.getName()); // 应用最大步数 if (metadata.getMaxSteps() != null && metadata.getMaxSteps() > 0) { builder.maxSteps(metadata.getMaxSteps()); diff --git a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/BashSubagent.java b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/BashSubagent.java index 9c491ca..3062a0c 100644 --- a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/BashSubagent.java +++ b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/BashSubagent.java @@ -33,20 +33,6 @@ public class BashSubagent extends AbsSubagent { super(mainAgent); } - /** - * 创建默认元数据 - * - * Bash 代理主要用于执行命令,步数根据任务复杂度变化 - */ - @Override - protected SubAgentMetadata createDefaultMetadata() { - return SubAgentMetadata.builder() - .name("bash") - .description(getDefaultDescription()) - .maxSteps(20) - .maxStepsAutoExtensible(true) - .build(); - } /** * 初始化 Bash 代理 diff --git a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/ExploreSubagent.java b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/ExploreSubagent.java index 0c9fffd..d61eecb 100644 --- a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/ExploreSubagent.java +++ b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/ExploreSubagent.java @@ -41,20 +41,6 @@ public class ExploreSubagent extends AbsSubagent { super(mainAgent); } - /** - * 创建默认元数据 - * - * 探索代理需要适度的步数,并启用自动扩展 - */ - @Override - protected SubAgentMetadata createDefaultMetadata() { - return SubAgentMetadata.builder() - .name("explore") - .description(getDefaultDescription()) - .maxSteps(15) - .maxStepsAutoExtensible(true) - .build(); - } @Override protected void customize(ReActAgent.Builder builder) { @@ -69,7 +55,6 @@ public class ExploreSubagent extends AbsSubagent { builder.defaultToolAdd(WebfetchTool.getInstance()); builder.defaultToolAdd(WebsearchTool.getInstance()); builder.defaultToolAdd(CodeSearchTool.getInstance()); - builder.defaultInterceptorAdd(mainAgent.getSummarizationInterceptor()); } diff --git a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/GeneralPurposeSubagent.java b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/GeneralPurposeSubagent.java index 45c1ac1..46e9d16 100644 --- a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/GeneralPurposeSubagent.java +++ b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/GeneralPurposeSubagent.java @@ -23,7 +23,6 @@ import org.noear.solon.bot.core.LuceneSkill; import org.noear.solon.bot.core.tool.CodeSearchTool; import org.noear.solon.bot.core.tool.WebfetchTool; import org.noear.solon.bot.core.tool.WebsearchTool; -import org.noear.solon.core.util.Assert; import java.util.Arrays; @@ -35,6 +34,8 @@ import java.util.Arrays; */ public class GeneralPurposeSubagent extends AbsSubagent { + String instruction; + public GeneralPurposeSubagent(AgentKernel mainAgent) { super(mainAgent); } @@ -43,27 +44,15 @@ public class GeneralPurposeSubagent extends AbsSubagent { super(mainAgent, metadata); } - /** - * 创建默认元数据 - * - * 通用代理需要更多的步数和自动扩展能力 - */ - @Override - protected SubAgentMetadata createDefaultMetadata() { - return SubAgentMetadata.builder() - .name("general-purpose") - .description(getDefaultDescription()) - .maxSteps(25) - .maxStepsAutoExtensible(true) - .build(); + public GeneralPurposeSubagent(AgentKernel mainAgent, SubAgentMetadata metadata, String instruction) { + super(mainAgent, metadata); + this.instruction = instruction; } - /** - * 设置 metadata(用于从文件加载) - */ @Override - public void setMetadata(SubAgentMetadata metadata) { - this.metadata = metadata; + protected void applyMetadataToBuilder(ReActAgent.Builder builder, SubAgentMetadata metadata) { + super.applyMetadataToBuilder(builder, metadata); + builder.instruction(this.instruction); } @@ -79,13 +68,6 @@ public class GeneralPurposeSubagent extends AbsSubagent { builder.defaultToolAdd(WebsearchTool.getInstance()); builder.defaultToolAdd(CodeSearchTool.getInstance()); - builder.defaultInterceptorAdd(mainAgent.getSummarizationInterceptor()); - - // 设置会话窗口大小 - builder.sessionWindowSize(5); - - // 注意:maxSteps 和 maxStepsExtensible 现在通过元数据配置 - // 在 applyMetadataToBuilder() 中应用 } diff --git a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/PlanSubagent.java b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/PlanSubagent.java index 41ff23d..99a8be1 100644 --- a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/PlanSubagent.java +++ b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/PlanSubagent.java @@ -42,20 +42,6 @@ public class PlanSubagent extends AbsSubagent { super(mainAgent); } - /** - * 创建默认元数据 - * - * 计划代理主要用于规划,不需要太多步数 - */ - @Override - protected SubAgentMetadata createDefaultMetadata() { - return SubAgentMetadata.builder() - .name("plan") - .description(getDefaultDescription()) - .maxSteps(20) - .maxStepsAutoExtensible(true) - .build(); - } @Override protected void customize(ReActAgent.Builder builder) { @@ -71,8 +57,6 @@ public class PlanSubagent extends AbsSubagent { builder.defaultToolAdd(WebfetchTool.getInstance()); builder.defaultToolAdd(CodeSearchTool.getInstance()); - builder.defaultInterceptorAdd(mainAgent.getSummarizationInterceptor()); - } diff --git a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/SubagentManager.java b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/SubagentManager.java index 1667e2a..92ded64 100644 --- a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/SubagentManager.java +++ b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/SubagentManager.java @@ -163,11 +163,10 @@ public class SubagentManager { } AbsSubagent subagent = (AbsSubagent) subagentMap.computeIfAbsent(subagentType, - k -> new GeneralPurposeSubagent(mainAgent, parsed.getMetadata())); + k -> new GeneralPurposeSubagent(mainAgent, parsed.getMetadata(), parsed.getPrompt())); // 设置解析后的属性 subagent.setDescription(parsed.getMetadata().getDescription()); - subagent.setSystemPrompt(parsed.getPrompt()); LOG.debug("加载子代理: {} 从 {}", subagentType, file); } catch (IOException e) { 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 7d62bf8..0c8e74b 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 @@ -15,7 +15,7 @@ */ package org.noear.solon.bot.core.subagent; -import org.noear.solon.ai.agent.AgentResponse; +import org.noear.solon.ai.agent.AgentChunk; import org.noear.solon.ai.agent.AgentSession; import org.noear.solon.ai.agent.react.ReActTrace; import org.noear.solon.ai.annotation.ToolMapping; @@ -30,6 +30,8 @@ import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; import java.io.BufferedWriter; +import java.time.Duration; +import java.util.List; import java.io.OutputStreamWriter; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -106,9 +108,9 @@ public class TaskSkill extends AbsSkill { } @ToolMapping(name = "task", - description = "【强制使用】派生并分派任务给专项子代理。所有实际开发工作必须使用此工具委派给子代理完成。返回 Mono 用于异步处理。") + description = "【强制使用】派生并分派任务给专项子代理。所有实际开发工作必须使用此工具委派给子代理完成。返回 Mono 异步结果。") public Mono task( - @Param(name = "subagentType", description = "子代理类型") String subagentType, + @Param(name = "name", description = "子代理名称") String name, @Param(name = "prompt", description = "具体指令。必须包含任务目标、关键类名或必要的背景上下文。") String prompt, @Param(name = "description", required = false, description = "简短的任务描述") String description, @Param(name = "taskId", required = false, description = "可选。若要继续之前的任务会话,请传入对应的 task_id") String taskId, @@ -119,66 +121,74 @@ public class TaskSkill extends AbsSkill { ReActTrace __parentTrace = ReActTrace.getCurrent(__parentSession.getSnapshot()); // 检查子代理是否存在 - Subagent agent = manager.getAgent(subagentType); + Subagent agent = manager.getAgent(name); if (agent == null) { - return Mono.just("ERROR: 未知的子代理类型 '" + subagentType + "'。"); + return Mono.just("ERROR: 未知的子代理类型 '" + name + "'。"); } String finalSessionId = Assert.isEmpty(taskId) - ? "subagent_" + subagentType + ? "subagent_" + name : taskId; - LOG.info("分派任务 -> 类型: {}, 会话: {}, 描述: {}", subagentType, finalSessionId, description); + LOG.info("分派任务 -> 类型: {}, 会话: {}, 描述: {}", name, finalSessionId, description); - // 使用 Mono.fromCallable 包装阻塞操作 + // 获取执行许可(同步) + boolean acquired; + try { + acquired = acquirePermit(name); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return Mono.just("ERROR: 获取执行许可被中断"); + } + + if (!acquired) { + return Mono.just("ERROR: 等待子代理执行许可超时。当前可能有太多子代理在执行。"); + } + + // 异步执行并收集结果 return Mono.fromCallable(() -> { - // 获取执行许可 - boolean acquired; - try { - acquired = acquirePermit(subagentType); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new RuntimeException("获取执行许可被中断", e); - } + StringBuilder result = new StringBuilder(); - if (!acquired) { - throw new RuntimeException("等待子代理执行许可超时。当前可能有太多子代理在执行。"); - } + // 添加任务头部信息 + result.append(String.format( + "task_id: %s\n" + + "subagentType: %s\n" + + "\n" + + "\n", + finalSessionId, name)); try { - AgentResponse response = agent.call(__cwd, finalSessionId, Prompt.of(prompt)); - String result = response.getContent(); - __parentTrace.getMetrics().addMetrics(response.getMetrics()); - - LOG.info("子代理任务完成: {}", finalSessionId); - - return String.format( - "task_id: %s\n" + - "subagentType: %s\n" + - "\n" + - "\n" + - "%s\n" + - "", - finalSessionId, subagentType, result != null ? result : "(无输出)" - ); - } catch (Throwable e) { - LOG.error("子代理调用异常: type={}, error={}", subagentType, e.getMessage(), e); - throw new RuntimeException("子代理调用失败: " + e.getMessage(), e); + // 调用子代理并收集流式输出 + List chunks = agent.stream(__cwd, finalSessionId, Prompt.of(prompt)) + .doOnNext(chunk -> { + String content = chunk.getContent(); + if (content != null && !content.isEmpty()) { + result.append(content); + } + }) + .collectList() + .block(Duration.ofMillis(300000)); // 5分钟超时 + + if (chunks != null) { + LOG.info("子代理任务完成: type={}, chunks={}", name, chunks.size()); + } + + } catch (Exception e) { + LOG.error("子代理执行失败: type={}, error={}", name, e.getMessage(), e); + result.append("\nERROR: 子代理执行失败: ").append(e.getMessage()); } finally { - releasePermit(subagentType); + result.append("\n"); + releasePermit(name); } + + return result.toString(); }) - .subscribeOn(Schedulers.boundedElastic()) // 在弹性线程池中执行,避免阻塞 - .doOnSubscribe(subscription -> LOG.debug("开始异步执行子代理任务: type={}, sessionId={}", subagentType, finalSessionId)) - .doOnError(error -> LOG.error("子代理执行失败: type={}, error={}", subagentType, error.getMessage(), error)) + .subscribeOn(Schedulers.boundedElastic()) // 在弹性线程池中执行 + .doOnSubscribe(subscription -> LOG.info("开始异步执行子代理任务: type={}", name)) + .doOnError(error -> LOG.error("子代理任务执行失败: type={}, error={}", name, error.getMessage(), error)) .onErrorResume(throwable -> { - // 捕获所有异常并返回错误消息 - if (throwable instanceof RuntimeException && - throwable.getMessage().contains("等待子代理执行许可超时")) { - return Mono.just("ERROR: " + throwable.getMessage()); - } - LOG.error("子代理执行崩溃: type={}, error={}", subagentType, throwable.getMessage(), throwable); - return Mono.just("ERROR: 子代理执行失败: " + throwable.getMessage()); + releasePermit(name); + return Mono.just("ERROR: 子代理执行失败: " + throwable.getMessage() + "\n"); }); } @@ -280,9 +290,8 @@ public class TaskSkill extends AbsSkill { LOG.info("Agent 定义已保存到: {}", agentFile); } - AbsSubagent newAgent = new GeneralPurposeSubagent(mainAgent, metadata); + GeneralPurposeSubagent newAgent = new GeneralPurposeSubagent(mainAgent, metadata, agentDefinition); newAgent.setDescription(description); - newAgent.setSystemPrompt(agentDefinition); newAgent.refresh(); manager.addSubagent(newAgent); 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 73fb750..d678005 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 @@ -80,9 +80,7 @@ public class AgentTeamsSkill extends AbsSkill { // 初始化智能记忆管理器 if (mainAgent != null && mainAgent.getSharedMemoryManager() != null) { - String workDir = System.getProperty("user.dir") + File.separator + "work"; - this.intelligentMemoryManager = new IntelligentMemoryManager(workDir); - LOG.info("初始化智能记忆管理器: workDir={}", workDir); + this.intelligentMemoryManager = new IntelligentMemoryManager(Paths.get(mainAgent.getWorkDir(), AgentKernel.SOLONCODE_MEMORY)); } else { this.intelligentMemoryManager = null; } @@ -348,19 +346,21 @@ public class AgentTeamsSkill extends AbsSkill { private static final long TEAM_TASK_TIMEOUT_MS = 300_000; // 5分钟超时 @ToolMapping(name = "team_task", - description = "启动团队协作任务。MainAgent 会自动分解任务并协调多个 SubAgent 协作完成。适用于复杂、多步骤的任务。注意:简单任务请直接回答,无需调用此工具。") - public String teamTask( + description = "启动团队协作任务。MainAgent 会自动分解任务并协调多个 SubAgent 协作完成。适用于复杂、多步骤的任务。注意:简单任务请直接回答,无需调用此工具。返回 Mono 用于异步处理。") + public Mono teamTask( @Param(name = "prompt", description = "任务描述,清晰说明目标和要求") String prompt, String __cwd, String __sessionId ) { - try { - if (mainAgent.isRunning()) { - return "[WARN] 团队任务正在执行中,请等待当前任务完成。"; - } + // 检查是否已有任务在执行 + if (mainAgent.isRunning()) { + return Mono.just("[WARN] 团队任务正在执行中,请等待当前任务完成。"); + } - LOG.info("启动团队协作任务: {}", prompt); + LOG.info("启动团队协作任务: {}", prompt); + // 使用流式执行并转换为 Mono + return Mono.fromCallable(() -> { // 使用流式执行(实时输出) StringBuilder streamingOutput = new StringBuilder(); @@ -392,7 +392,7 @@ public class AgentTeamsSkill extends AbsSkill { } catch (Exception e) { LOG.error("团队任务执行失败", e); - return "[ERROR] 团队任务执行失败: " + e.getMessage(); + throw new RuntimeException("团队任务执行失败: " + e.getMessage(), e); } // 获取任务统计 @@ -423,11 +423,14 @@ public class AgentTeamsSkill extends AbsSkill { } return result.toString(); - - } catch (Throwable e) { - LOG.error("团队任务执行失败", e); - return "[ERROR] 团队任务执行失败: " + e.getMessage(); - } + }) + .subscribeOn(Schedulers.boundedElastic()) // 在弹性线程池中执行 + .doOnSubscribe(subscription -> LOG.info("开始异步执行团队任务: {}", prompt)) + .doOnError(error -> LOG.error("团队任务执行失败: prompt={}", prompt, error)) + .onErrorResume(throwable -> { + LOG.error("团队任务执行失败: prompt={}", prompt, throwable); + return Mono.just("[ERROR] 团队任务执行失败: " + throwable.getMessage()); + }); } /** @@ -956,7 +959,7 @@ public class AgentTeamsSkill extends AbsSkill { // 构建子代理元数据 SubAgentMetadata metadata = new SubAgentMetadata(); - metadata.setName(role); + metadata.setName(name); metadata.setDescription(description); metadata.setEnabled(true); metadata.setTeamName(teamName); @@ -1239,8 +1242,6 @@ public class AgentTeamsSkill extends AbsSkill { prompt.append("2. **平衡优先**: 同时看效率比\n"); prompt.append("3. **协作配合**: 与其他团队成员保持良好沟通\n"); - - prompt.append("## 沟通风格\n\n"); prompt.append("- 使用清晰、简洁的语言\n"); prompt.append("- 解释技术决策的理由\n"); @@ -1347,46 +1348,64 @@ public class AgentTeamsSkill extends AbsSkill { return "[WARN] 智能记忆管理器未初始化"; } - Map stats = intelligentMemoryManager.getStats(); + try { + Map stats = intelligentMemoryManager.getStats(); - // 格式化输出 - StringBuilder sb = new StringBuilder(); - sb.append("[STATS] 记忆系统统计:\n\n"); + // 格式化输出 + StringBuilder sb = new StringBuilder(); + sb.append("[STATS] 记忆系统统计:\n\n"); - // 各层级记忆数量 - sb.append("工作记忆: ").append(stats.getOrDefault("workingCount", 0)).append(" 条\n"); - sb.append("短期记忆: ").append(stats.getOrDefault("shortTermCount", 0)).append(" 条\n"); - sb.append("长期记忆: ").append(stats.getOrDefault("longTermCount", 0)).append(" 条\n"); - sb.append("知识记忆: ").append(stats.getOrDefault("knowledgeCount", 0)).append(" 条\n"); + // 各层级记忆数量 + sb.append("工作记忆: ").append(stats.getOrDefault("workingCount", 0)).append(" 条\n"); + sb.append("短期记忆: ").append(stats.getOrDefault("shortTermCount", 0)).append(" 条\n"); + sb.append("长期记忆: ").append(stats.getOrDefault("longTermCount", 0)).append(" 条\n"); + sb.append("知识记忆: ").append(stats.getOrDefault("knowledgeCount", 0)).append(" 条\n"); - sb.append("\n"); + sb.append("\n"); - // 智能层状态 - sb.append("智能层: ").append(stats.getOrDefault("intelligentLayer", "未启用")).append("\n"); - sb.append("自动合并: ").append(stats.getOrDefault("autoConsolidate", false)).append("\n"); + // 智能层状态 + sb.append("智能层: ").append(stats.getOrDefault("intelligentLayer", "未启用")).append("\n"); + sb.append("自动合并: ").append(stats.getOrDefault("autoConsolidate", false)).append("\n"); - if (stats.containsKey("consolidationThreshold")) { - sb.append("合并阈值: ").append(stats.get("consolidationThreshold")).append("\n"); - } - if (stats.containsKey("consolidationInterval")) { - sb.append("合并间隔: ").append(stats.get("consolidationInterval")).append("ms\n"); - } + if (stats.containsKey("consolidationThreshold")) { + sb.append("合并阈值: ").append(stats.get("consolidationThreshold")).append("\n"); + } + if (stats.containsKey("consolidationInterval")) { + sb.append("合并间隔: ").append(stats.get("consolidationInterval")).append("ms\n"); + } - sb.append("\n"); + sb.append("\n"); - // 内存使用情况 - if (stats.containsKey("memoryUsed")) { - long used = (long) stats.get("memoryUsed"); - long max = (long) stats.getOrDefault("memoryMax", 100 * 1024 * 1024); - double ratio = (double) used / max * 100; + // 内存使用情况 + if (stats.containsKey("memoryUsed")) { + Object usedObj = stats.get("memoryUsed"); + Object maxObj = stats.getOrDefault("memoryMax", "100MB"); - sb.append(String.format("内存使用: %.2fMB / %.2fMB (%.1f%%)\n", - used / (1024.0 * 1024), - max / (1024.0 * 1024), - ratio)); - } + // 处理可能的字符串或数字类型 + long used = 0; + long max = 100 * 1024 * 1024; - return sb.toString(); + if (usedObj instanceof Number) { + used = ((Number) usedObj).longValue(); + } + if (maxObj instanceof Number) { + max = ((Number) maxObj).longValue(); + } + + double ratio = (double) used / max * 100; + + sb.append(String.format("内存使用: %.2fMB / %.2fMB (%.1f%%)\n", + used / (1024.0 * 1024), + max / (1024.0 * 1024), + ratio)); + } + + return sb.toString(); + + } catch (Exception e) { + LOG.error("获取记忆统计失败", e); + return "[ERROR] 获取记忆统计失败: " + e.getMessage(); + } } /** @@ -1583,7 +1602,7 @@ public class AgentTeamsSkill extends AbsSkill { * 完成任务 */ @ToolMapping(name = "complete_task", - description = "标记任务为已完成(将任务状态改为COMPLETED)") + description = "标记任务为已完成(将任务状态改为COMPLETED)。如果任务是PENDING状态,会自动先认领再完成。") public String completeTask( @Param(name = "taskId", description = "任务ID") String taskId, @Param(name = "result", description = "任务执行结果(可选)") String result) { @@ -1599,6 +1618,13 @@ public class AgentTeamsSkill extends AbsSkill { return "[ERROR] 任务不存在: " + taskId; } + // 智能处理:如果任务是 PENDING,自动先认领 + if (task.getStatus() == TeamTask.Status.PENDING) { + LOG.info("任务处于PENDING状态,自动认领: taskId={}", taskId); + taskList.claimTask(taskId, "auto-complete"); + } + + // 检查任务状态是否可以完成 if (task.getStatus() != TeamTask.Status.IN_PROGRESS) { return "[WARN] 任务状态不是进行中: " + task.getStatus(); } @@ -2244,125 +2270,33 @@ public class AgentTeamsSkill extends AbsSkill { } - /** - * 列出所有团队配置 - * - * @return 团队列表 - */ - @ToolMapping(name = "list_teams", - description = "列出所有已配置的团队。显示团队名称、描述、成员数量等信息。") - public String listTeams() { - try { - StringBuilder sb = new StringBuilder(); - sb.append("## 团队配置列表\n\n"); - - String teamsDir = mainAgent.getWorkDir() + File.separator + ".soloncode" + File.separator + "teams"; - File dir = new File(teamsDir); - - if (!dir.exists()) { - return "[INFO] 暂无团队配置。请使用 `create_team_config` 创建团队。\n\n" + - "提示:可用的代理包括:explore, plan, bash, general-purpose"; - } - - File[] files = dir.listFiles((d, name) -> name.endsWith(".md")); - if (files == null || files.length == 0) { - return "[INFO] 暂无团队配置。请使用 `create_team_config` 创建团队。\n\n" + - "提示:可用的代理包括:explore, plan, bash, general-purpose"; - } - - for (File file : files) { - String teamName = file.getName().replace(".md", ""); - - // 读取配置文件 - String content = new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8); - - // 解析基本信息 - String description = ""; - String protocol = "sequential"; - List members = new ArrayList<>(); - - String[] lines = content.split("\n"); - boolean inFrontMatter = false; - for (String line : lines) { - if (line.trim().equals("---")) { - inFrontMatter = !inFrontMatter; - continue; - } - - if (inFrontMatter) { - if (line.startsWith("description:")) { - description = line.substring(12).trim(); - } else if (line.startsWith("protocol:")) { - protocol = line.substring(9).trim(); - } else if (line.startsWith("agents:")) { - // 解析成员列表在后面的部分 - } - } else { - if (line.startsWith("- **")) { - String member = line.substring(4).replace("**", "").trim(); - if (!member.isEmpty()) { - members.add(member); - } - } - } - } - - sb.append("### ").append(teamName).append("\n\n"); - sb.append("**描述**: ").append(description).append("\n"); - sb.append("**协议**: ").append(protocol).append("\n"); - sb.append("**成员** (").append(members.size()).append("): "); - sb.append(String.join(", ", members)).append("\n"); - sb.append("**配置文件**: ").append(file.getAbsolutePath()).append("\n\n"); - } - - if (sb.length() == 23) { // 只有标题 - sb.append("暂无团队配置\n"); - } - - sb.append("\n**使用说明**:\n"); - sb.append("- 使用 `create_team_config` 创建新团队\n"); - sb.append("- 团队配置保存在: ").append(teamsDir).append("\n"); - sb.append("- 支持的协议: sequential(顺序执行), hierarchical(层级调度)"); - - return sb.toString(); - - } catch (Exception e) { - LOG.error("列出团队失败", e); - return "[ERROR] 列出团队失败: " + e.getMessage(); - } - } - /** - * 执行团队任务(异步) + * 执行团队任务(同步) * * 根据已保存的团队配置,动态构建 TeamAgent 并执行任务 * * @param teamName 团队名称 * @param task 任务描述 - * @return 异步执行结果(Mono) + * @return 同步执行结果(String),包含完整的团队协作输出 */ @ToolMapping(name = "run_team_task", - description = "使用指定的团队执行任务。根据团队配置自动协调多个代理协作完成任务。返回 Mono 用于异步处理。") - public Mono runTeamTask( - @Param(name = "teamName", description = "团队名称(使用 list_teams 查看)") String teamName, + description = "简易版本,使用指定的团队执行任务。根据团队配置自动协调多个代理协作完成任务。返回 String 同步结果。") + public String runTeamTask( + @Param(name = "teamName", description = "团队名称(使用 teammates 查看)") String teamName, @Param(name = "task", description = "任务描述") String task) { - return Mono.fromCallable(() -> { - try { - // 1. 读取团队配置文件 - String teamsDir = mainAgent.getWorkDir() + File.separator + ".soloncode" + File.separator + "teams"; - File configFile = new File(teamsDir + File.separator + teamName + ".md"); + try { + String description = ""; + String protocol = "sequential"; + List validAgents = new ArrayList<>(); - if (!configFile.exists()) { - return "[ERROR] 团队配置不存在: " + teamName + "\n\n" + - "请使用 `list_teams` 查看可用的团队,或使用 `create_team_config` 创建新团队。"; - } + // 1. 尝试从配置文件读取 + String teamsDir = mainAgent.getWorkDir() + File.separator + ".soloncode" + File.separator + "agentsTeams"; + File configFile = new File(teamsDir + File.separator + teamName + ".md"); - // 2. 解析配置 + if (configFile.exists()) { + // 从配置文件解析 String content = new String(Files.readAllBytes(configFile.toPath()), StandardCharsets.UTF_8); - - String description = ""; - String protocol = "sequential"; List agentNames = new ArrayList<>(); String[] lines = content.split("\n"); @@ -2389,10 +2323,8 @@ public class AgentTeamsSkill extends AbsSkill { } } - // 3. 验证代理是否可用 - List validAgents = new ArrayList<>(); + // 验证代理是否可用 List missingAgents = new ArrayList<>(); - for (String agentName : agentNames) { Subagent agent = manager.getAgent(agentName); if (agent != null) { @@ -2408,72 +2340,106 @@ public class AgentTeamsSkill extends AbsSkill { "请确保这些代理已在 SubagentManager 中注册。"; } - // 4. 构建 TeamAgent - ChatModel chatModel = mainAgent.getChatModel(); + LOG.info("从配置文件加载团队: team={}, members={}", teamName, validAgents.size()); + } else { + // 2. 配置文件不存在,动态从 SubagentManager 获取该团队的成员 + LOG.info("配置文件不存在,尝试从 SubagentManager 动态获取团队成员: team={}", teamName); + + validAgents = manager.getAgents().stream() + .filter(agent -> agent.getMetadata().hasTeamName() && + teamName.equals(agent.getMetadata().getTeamName())) + .collect(java.util.stream.Collectors.toList()); + + if (validAgents.isEmpty()) { + return "[ERROR] 团队 '" + teamName + "' 中没有可用的代理\n\n" + + "提示:\n" + + "- 使用 `teammates()` 查看可用的团队成员和团队\n" + + "- 使用 `teammate()` 创建新的团队成员(可指定 teamName)\n" + + "- 确保创建成员时指定了正确的团队名称"; + } + + // 动态生成团队描述 + description = "团队 " + teamName + ",由 " + validAgents.size() + " 位成员组成"; + protocol = "sequential"; // 默认使用顺序执行 + + LOG.info("动态加载团队成员: team={}, members={}", teamName, validAgents.size()); + } + + // 4. 构建 TeamAgent + ChatModel chatModel = mainAgent.getChatModel(); TeamAgent.Builder teamBuilder = TeamAgent.of(chatModel) .name(teamName) .role(description.isEmpty() ? teamName : description) .instruction("团队协作完成用户指定的任务。根据任务特点和各成员的能力,合理分配和协调工作。"); - // 5. 添加成员(从 Subagent 中提取底层的 ReActAgent) - for (Subagent agent : validAgents) { - if (agent instanceof AbsSubagent) { - AbsSubagent absSubagent = (AbsSubagent) agent; - teamBuilder.agentAdd(absSubagent.getCachedAgent()); - } else { - LOG.warn("代理 {} 不是 AbsSubagent 类型,跳过", agent.name()); - } - } - - // 6. 配置协议 - if ("hierarchical".equalsIgnoreCase(protocol)) { - teamBuilder.protocol(TeamProtocols.HIERARCHICAL); + // 5. 添加成员(从 Subagent 中提取底层的 ReActAgent) + for (Subagent agent : validAgents) { + if (agent instanceof AbsSubagent) { + AbsSubagent absSubagent = (AbsSubagent) agent; + teamBuilder.agentAdd(absSubagent.getCachedAgent()); } else { - teamBuilder.protocol(TeamProtocols.SEQUENTIAL); + LOG.warn("代理 {} 不是 AbsSubagent 类型,跳过", agent.name()); } + } - // 7. 配置运行参数 - teamBuilder.maxTurns(15); // 最多协作 15 轮 + // 6. 配置协议 + if ("hierarchical".equalsIgnoreCase(protocol)) { + teamBuilder.protocol(TeamProtocols.HIERARCHICAL); + } else { + teamBuilder.protocol(TeamProtocols.SEQUENTIAL); + } - // 8. 构建并执行 - TeamAgent teamAgent = teamBuilder.build(); + // 7. 配置运行参数 + teamBuilder.maxTurns(15); // 最多协作 15 轮 - LOG.info("启动团队任务: team={}, members={}, protocol={}", - teamName, validAgents.size(), protocol); + // 8. 构建并执行 + TeamAgent teamAgent = teamBuilder.build(); - // 执行任务 - String result; - try { - result = teamAgent.prompt(task) - .call() - .getContent(); - } catch (Throwable ex) { - LOG.error("团队任务执行异常", ex); - return "[ERROR] 团队任务执行异常: " + ex.getMessage(); - } + LOG.info("启动团队任务: team={}, members={}, protocol={}", + teamName, validAgents.size(), protocol); + + // 9. 同步执行任务(收集所有输出) + StringBuilder result = new StringBuilder(); - // 9. 返回结果 - StringBuilder sb = new StringBuilder(); - sb.append("[OK] 团队任务执行完成\n\n"); - sb.append("**团队**: ").append(teamName).append("\n"); - sb.append("**协议**: ").append(protocol).append("\n"); - sb.append("**参与成员**: ").append(validAgents.size()).append("\n"); - sb.append("\n--- 执行结果 ---\n\n"); - sb.append(result); + // 添加头部信息 + result.append(String.format( + "[OK] 团队任务执行\n\n" + + "**团队**: %s\n" + + "**协议**: %s\n" + + "**参与成员**: %d\n" + + "\n--- 执行结果 ---\n\n", + teamName, protocol, validAgents.size())); - return sb.toString(); + // 执行并收集流式输出 + try { + List chunks = teamAgent.prompt(task) + .stream() + .doOnNext(chunk -> { + String content = chunk.getContent(); + if (content != null && !content.isEmpty()) { + result.append(content); + } + }) + .collectList() + .block(Duration.ofMillis(300000)); // 5分钟超时 + + if (chunks != null) { + LOG.info("团队任务执行完成: team={}, chunks={}", teamName, chunks.size()); + } } catch (Exception e) { - LOG.error("执行团队任务失败: team={}, task={}", teamName, task, e); - return "[ERROR] 执行团队任务失败: " + e.getMessage() + "\n\n" + - "团队: " + teamName + "\n" + - "任务: " + task; + LOG.error("团队任务执行异常: team={}, task={}", teamName, task, e); + result.append("\n[ERROR] 团队任务执行异常: ").append(e.getMessage()); } - }) - .subscribeOn(Schedulers.boundedElastic()) // 在弹性线程池中执行,避免阻塞 - .doOnSubscribe(subscription -> LOG.info("开始异步执行团队任务: team={}, task={}", teamName, task)) - .doOnError(error -> LOG.error("团队任务执行失败: team={}, task={}", teamName, task, error)) - .doOnSuccess(result -> LOG.info("团队任务执行成功: team={}", teamName)); + + return result.toString(); + + } catch (Exception e) { + LOG.error("执行团队任务失败: team={}, task={}", teamName, task, e); + return "[ERROR] 执行团队任务失败: " + e.getMessage() + "\n\n" + + "团队: " + teamName + "\n" + + "任务: " + task; + } } } 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 2438108..8373944 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 @@ -149,38 +149,96 @@ public class MainAgent { return "## Team Lead(团队协调器)角色\n\n" + "你现在启用了 **Agent Teams 模式**,你是团队的 **Team Lead(团队领导)**。\n\n" + "### 核心职责\n\n" + - "1. **任务分解** - 将复杂任务分解为可执行的子任务\n" + + "1. **任务分析** - 判断任务类型,选择合适的协作模式\n" + "2. **团队协作** - 协调多个专业子代理(teammates)协作完成任务\n" + "3. **结果汇总** - 收集各子代理的结果,形成最终答案\n" + "4. **质量控制** - 确保子任务完成的质量和一致性\n\n" + + "### 两种协作模式(必须正确选择)\n\n" + + "#### 模式1:简单多专家协作(直接调用 task())\n\n" + + "**适用场景**:\n" + + "- 用户明确要求多个专家从不同角度分析\n" + + "- 任务相对简单,不需要复杂的工作流\n" + + "- 可以并行调用多个专家,然后汇总结果\n\n" + + "**关键词**:\"专家\"、\"分析\"、\"建议\"、\"从.*角度\"、\"多.*位.*专家\"\n\n" + + "**执行流程**:\n" + + "1. 直接调用多个 `task(name=\"专家名\", prompt=\"具体问题\")`\n" + + "2. 等待所有专家返回结果\n" + + "3. MainAgent 汇总各专家意见,给出总结\n\n" + + "**示例**:\n" + + "```\n" + + "用户:找100以内的完全平方数,请三位专家(代数、几何、数论)分析\n\n" + + "执行:\n" + + "1. task(name=\"代数专家\", prompt=\"从代数角度分析100以内的完全平方数\")\n" + + "2. task(name=\"几何专家\", prompt=\"从几何角度分析100以内的完全平方数\")\n" + + "3. task(name=\"数论专家\", prompt=\"从数论角度分析100以内的完全平方数\")\n" + + "4. 汇总三位专家的分析结果\n" + + "```\n\n" + + "#### 模式2:复杂任务编排(任务列表 + 智能路由)\n\n" + + "**适用场景**:\n" + + "- 多步骤、复杂的工作流\n" + + "- 需要任务之间的依赖和协调\n" + + "- 需要团队成员通过任务列表自主协作\n\n" + + "**关键词**:\"实现\"、\"开发\"、\"设计\"、\"重构\"、\"优化\"等多步骤工程任务\n\n" + + "**执行流程**:\n" + + "1. 使用 `task_add()` 或 `create_task()` 创建任务列表\n" + + "2. 团队成员通过 `list_all_tasks()` 或 `get_claimable_tasks()` 查看任务\n" + + "3. 团队成员认领任务并执行(可选:使用 `claim_task()` 显式认领)\n" + + "4. 团队成员完成任务(`complete_task()` 会自动处理认领)\n" + + "5. MainAgent 使用 `team_status()` 跟踪进度并汇总最终结果\n\n" + + "**简化用法**:\n" + + "- 创建任务后,可以直接调用 `complete_task(taskId, result)`\n" + + "- `complete_task()` 会自动处理 PENDING → IN_PROGRESS → COMPLETED 的状态流转\n" + + "- 无需手动调用 `claim_task()`(除非需要明确指派给特定成员)\n\n" + + "**示例**:\n" + + "```\n" + + "用户:实现一个用户登录功能\n\n" + + "执行:\n" + + "1. task_add(\"设计登录API接口\")\n" + + "2. task_add(\"实现用户认证逻辑\")\n" + + "3. task_add(\"编写单元测试\")\n" + + "4. task_add(\"更新文档\")\n" + + "5. team_status() 查看进度\n" + + "6. 等待团队成员认领并完成任务\n" + + "```\n\n" + "### 可用工具\n\n" + - "**团队协作**:\n" + - "- `team_task(prompt)` - 启动团队协作任务(适用于复杂多步骤任务)\n" + - "- `team_status()` - 查看团队任务状态\n" + + "**子代理调用**(模式1使用):\n" + + "- `task(name, prompt)` - 直接调用特定子代理执行任务\n" + "- `teammate_quick(name, role)` - 快速创建团队成员\n" + - "- `teammate_template(template)` - 使用预设模板创建成员\n" + "- `teammates()` - 列出所有团队成员\n\n" + - "**任务管理**:\n" + + "**任务管理**(模式2使用):\n" + "- `task_add(title)` - 快速添加任务\n" + "- `tasks_add(titles)` - 批量添加任务\n" + - "- `create_task(title, ...)` - 创建任务(完整配置)\n" + - "- `list_all_tasks()` - 查看所有任务\n\n" + - "**记忆管理**:\n" + + "- `create_task(title, description, ...)` - 创建任务(完整配置)\n" + + "- `list_all_tasks()` - 查看所有任务\n" + + "- `team_status()` - 查看团队任务状态\n\n" + + "**记忆管理**:\n" + "- `memory_store(content)` - 存储记忆(自动分类)\n" + "- `memory_recall(query)` - 检索记忆\n" + "- `memory_stats()` - 查看记忆统计\n\n" + - "**子代理调用**:\n" + - "- `task(subagent_type, prompt)` - 直接调用特定子代理\n\n" + - "### 工作流程\n\n" + - "1. 分析用户请求,判断是否需要团队协作\n" + - "2. 如需协作,调用 `team_task()` 或先创建子任务\n" + - "3. 通过 `task()` 调用专业子代理执行子任务\n" + - "4. 汇总结果,给出最终答案\n\n" + - "### 重要提示\n\n" + - "- **简单任务直接回答**,无需调用 `team_task()`\n" + - "- **复杂任务**(多步骤、需要多种技能)才启用团队协作\n" + - "- 使用 `teammate_quick()` 或 `teammate_template()` 快速创建团队成员\n" + - "- 利用 `memory_store()` 记录重要决策和结果"; + "### 强制规则(必须遵守)\n\n" + + "**禁止行为**:\n" + + "- ❌ 禁止\"模拟\"或\"编造\"专家的回答\n" + + "- ❌ 禁止自己代替专家发言\n" + + "- ❌ 禁止说\"专家讨论完毕\"但实际没有调用 `task()`\n" + + "- ❌ 禁止简单任务也使用任务编排(过度设计)\n" + + "- ❌ 禁止复杂任务直接调用 `task()`(缺乏协调)\n\n" + + "**必须行为**:\n" + + "- ✅ 简单多专家任务:必须使用 `task()` 多次调用,然后汇总\n" + + "- ✅ 复杂多步骤任务:必须使用任务编排(task_add + team_status)\n" + + "- ✅ 每个专家角色对应一次独立的 `task()` 调用\n" + + "- ✅ 使用 `teammate_quick()` 快速创建需要的团队成员\n\n" + + "### 决策流程\n\n" + + "1. 用户请求包含\"专家\"、\"分析\"等关键词?\n" + + " → 是:使用模式1(直接调用 task())\n" + + " → 否:继续判断\n" + + "2. 任务是否为多步骤、需要依赖协调?\n" + + " → 是:使用模式2(任务编排)\n" + + " → 否:直接回答\n\n" + + "### 工作原则\n\n" + + "- 简单问题直接回答,无需协作\n" + + "- 多专家分析用 `task()`,快速并行\n" + + "- 复杂工程用任务编排,智能路由\n" + + "- 记忆重要决策,方便后续查阅"; } /** @@ -307,6 +365,9 @@ public class MainAgent { } catch (Exception e) { LOG.error("MainAgent 后处理失败", e); } finally { + // 设置运行状态为 false + running.set(false); + // 停止目标守护 try { this.stopGoalGuarding(); diff --git a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/teams/SubAgentAgentBuilder.java b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/teams/SubAgentAgentBuilder.java index 28d16b9..c3ee03d 100644 --- a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/teams/SubAgentAgentBuilder.java +++ b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/teams/SubAgentAgentBuilder.java @@ -90,7 +90,7 @@ public class SubAgentAgentBuilder { public SubAgentAgentBuilder addAgent(Subagent subAgent) { if (subAgent != null) { this.subAgents.add(subAgent); - LOG.debug("添加团队成员: {}", subAgent.getType()); + LOG.debug("添加团队成员: {}", subAgent.name()); } return this; } @@ -255,8 +255,7 @@ public class SubAgentAgentBuilder { */ private SubAgentMetadata createDefaultMainAgentConfig() { SubAgentMetadata config = new SubAgentMetadata(); - config.setCode("main-agent"); - config.setName("主代理"); + config.setName("main-agent"); config.setDescription("Agent 团队协调器,负责任务分发和结果汇总"); config.setEnabled(true); -- Gitee From f8246cbb85340b7d1f49c234e4a335bbd0034605 Mon Sep 17 00:00:00 2001 From: bai <1145000687@qq.com> Date: Tue, 17 Mar 2026 17:44:51 +0800 Subject: [PATCH 4/5] =?UTF-8?q?refactor(bot):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E5=AD=90=E4=BB=A3=E7=90=86=E5=92=8C=E5=9B=A2=E9=98=9F=E5=8D=8F?= =?UTF-8?q?=E4=BD=9C=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 优化子代理最大步数配置逻辑,支持maxSteps > maxTurns > 默认值优先级 - 移除ActionRecord类及其相关实现 - 更新AgentTeamsSkill中的团队任务执行逻辑,改进流式处理机制 - 重构MainAgent的流式执行方法,增强错误处理和超时管理 - 修改任务分析和分解功能,优化JSON解析逻辑 - 调整团队协作规则描述,更新可用子代理类型和token使用建议 - 优化内存整合器中的ID日志输出,防止过长ID影响日志可读性 --- .../solon/bot/core/memory/ActionRecord.java | 328 ------------------ .../bot/core/memory/SharedMemoryManager.java | 11 +- .../consolidator/MemoryConsolidator.java | 8 +- .../solon/bot/core/subagent/AbsSubagent.java | 28 +- .../bot/core/subagent/AgentStreamOutput.java | 102 ++++++ .../bot/core/subagent/SubAgentMetadata.java | 20 ++ .../solon/bot/core/subagent/TaskSkill.java | 124 +++---- .../solon/bot/core/teams/AgentTeamsSkill.java | 249 ++++++------- .../noear/solon/bot/core/teams/MainAgent.java | 238 ++++++------- 9 files changed, 407 insertions(+), 701 deletions(-) delete mode 100644 solonbot-sdk/src/main/java/org/noear/solon/bot/core/memory/ActionRecord.java create mode 100644 solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/AgentStreamOutput.java diff --git a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/memory/ActionRecord.java b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/memory/ActionRecord.java deleted file mode 100644 index efd2759..0000000 --- a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/memory/ActionRecord.java +++ /dev/null @@ -1,328 +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.memory; - -import lombok.Getter; -import lombok.Setter; - -import java.util.HashMap; -import java.util.Map; - -/** - * 动作执行记录(通用类) - * - * 记录各种动作的执行过程和结果,用于调试和审计。 - * 支持多种动作类型: - * - Tool: 内置工具(bash, grep, read, write 等) - * - Skill: 技能脚本(Python, Shell, JavaScript 等) - * - MCP: Model Context Protocol 服务器工具 - * - Custom: 自定义动作类型 - * - * @author bai - * @since 3.9.5 - */ -@Getter -@Setter -public class ActionRecord { - // ========== 类型常量 ========== - - /** 内置工具类型 */ - public static final String TYPE_TOOL = "tool"; - - /** 技能类型 */ - public static final String TYPE_SKILL = "skill"; - - /** MCP 服务器工具类型 */ - public static final String TYPE_MCP = "mcp"; - - /** 自定义动作类型 */ - public static final String TYPE_CUSTOM = "custom"; - - // ========== 字段 ========== - - private String actionName; // 动作名称(通用字段) - private String actionDescription; // 动作描述 - private String actionType; // 动作类型:tool/skill/mcp/custom 等 - private Map inputs; // 输入参数 - private Object output; // 输出结果 - private long startTime; // 开始时间 - private long endTime; // 结束时间 - private long duration; // 执行耗时(毫秒) - private boolean success; // 是否成功 - private String errorMessage; // 错误信息 - private String agentId; // 执行的 Agent ID - - // Skill 特有字段(可选) - private String skillPool; // Skill 池(如 @soloncode_skills) - private String skillFile; // Skill 文件路径(如果是文件 skill) - - // MCP 特有字段(可选) - private String mcpServer; // MCP 服务器名称 - private String mcpMethod; // MCP 方法名称 - private String mcpResource; // MCP 资源标识 - - /** - * 构造函数(Tool) - * - * @param actionName 动作名称 - * @param agentId 执行的 Agent ID - */ - public ActionRecord(String actionName, String agentId) { - this(actionName, agentId, TYPE_TOOL); - } - - /** - * 构造函数(Skill - 向后兼容) - * - * @param skillName Skill 名称 - * @param agentId 执行的 Agent ID - * @param asSkill 标记为 Skill 类型 - */ - public ActionRecord(String skillName, String agentId, boolean asSkill) { - this(skillName, agentId, asSkill ? TYPE_SKILL : TYPE_TOOL); - } - - /** - * 完整构造函数 - * - * @param name 动作名称 - * @param agentId 执行的 Agent ID - * @param actionType 动作类型 - */ - public ActionRecord(String name, String agentId, String actionType) { - this.actionName = name; - this.agentId = agentId; - this.actionType = actionType != null ? actionType : TYPE_TOOL; - this.inputs = new HashMap<>(); - this.startTime = System.currentTimeMillis(); - this.success = false; - } - - // ========== 静态工厂方法 ========== - - /** - * 创建 Tool 记录 - * - * @param toolName 工具名称 - * @param agentId 执行的 Agent ID - * @return ActionRecord 实例 - */ - public static ActionRecord forTool(String toolName, String agentId) { - return new ActionRecord(toolName, agentId, TYPE_TOOL); - } - - /** - * 创建 Skill 记录 - * - * @param skillName Skill 名称 - * @param agentId 执行的 Agent ID - * @return ActionRecord 实例 - */ - public static ActionRecord forSkill(String skillName, String agentId) { - return new ActionRecord(skillName, agentId, TYPE_SKILL); - } - - /** - * 创建 MCP 工具记录 - * - * @param toolName MCP 工具名称 - * @param mcpServer MCP 服务器名称 - * @param agentId 执行的 Agent ID - * @return ActionRecord 实例 - */ - public static ActionRecord forMcp(String toolName, String mcpServer, String agentId) { - ActionRecord record = new ActionRecord(toolName, agentId, TYPE_MCP); - record.setMcpServer(mcpServer); - return record; - } - - /** - * 创建自定义动作记录 - * - * @param actionName 动作名称 - * @param customType 自定义类型 - * @param agentId 执行的 Agent ID - * @return ActionRecord 实例 - */ - public static ActionRecord forCustom(String actionName, String customType, String agentId) { - return new ActionRecord(actionName, agentId, customType); - } - - /** - * 标记成功 - * - * @param output 输出结果 - */ - public void success(Object output) { - this.output = output; - this.success = true; - this.endTime = System.currentTimeMillis(); - this.duration = this.endTime - this.startTime; - } - - /** - * 标记失败 - * - * @param errorMessage 错误信息 - */ - public void failure(String errorMessage) { - this.errorMessage = errorMessage; - this.success = false; - this.endTime = System.currentTimeMillis(); - this.duration = this.endTime - this.startTime; - } - - /** - * 添加输入参数 - * - * @param key 参数名 - * @param value 参数值 - */ - public ActionRecord addInput(String key, Object value) { - this.inputs.put(key, value); - return this; - } - - /** - * 设置输入参数 - * - * @param inputs 输入参数 Map - */ - public void setInputs(Map inputs) { - this.inputs = inputs != null ? inputs : new HashMap<>(); - } - - // ========== 向后兼容方法 ========== - - /** - * 获取 Tool 名称(别名) - * 向后兼容 ToolRecord.getToolName() - */ - public String getToolName() { - return actionName; - } - - /** - * 设置 Tool 名称(别名) - * 向后兼容 ToolRecord.setToolName() - */ - public void setToolName(String toolName) { - this.actionName = toolName; - } - - /** - * 获取 Skill 名称(别名) - * 向后兼容 SkillRecord.getSkillName() - */ - public String getSkillName() { - return actionName; - } - - /** - * 设置 Skill 名称(别名) - * 向后兼容 SkillRecord.setSkillName() - */ - public void setSkillName(String skillName) { - this.actionName = skillName; - } - - // ========== 类型判断方法 ========== - - /** - * 判断是否为 Tool 类型 - */ - public boolean isTool() { - return TYPE_TOOL.equals(actionType); - } - - /** - * 判断是否为 Skill 类型 - */ - public boolean isSkill() { - return TYPE_SKILL.equals(actionType); - } - - /** - * 判断是否为 MCP 类型 - */ - public boolean isMcp() { - return TYPE_MCP.equals(actionType); - } - - /** - * 判断是否为自定义类型 - */ - public boolean isCustom() { - return TYPE_CUSTOM.equals(actionType); - } - - /** - * 判断是否为指定类型 - * - * @param type 类型字符串 - * @return 是否匹配 - */ - public boolean isType(String type) { - return (type != null) && type.equals(actionType); - } - - // ========== toString ========== - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("ActionRecord{"); - sb.append("type='").append(actionType).append("'"); - sb.append(", name='").append(actionName).append("'"); - sb.append(", success=").append(success); - sb.append(", duration=").append(duration); - sb.append(", inputs=").append(inputs.size()); - - if (output != null) { - sb.append(", hasOutput=true"); - } - - if (errorMessage != null) { - sb.append(", error='").append(errorMessage).append("'"); - } - - // Skill 特有字段 - if (isSkill()) { - if (skillPool != null) { - sb.append(", pool='").append(skillPool).append("'"); - } - if (skillFile != null) { - sb.append(", file='").append(skillFile).append("'"); - } - } - - // MCP 特有字段 - if (isMcp()) { - if (mcpServer != null) { - sb.append(", mcpServer='").append(mcpServer).append("'"); - } - if (mcpMethod != null) { - sb.append(", mcpMethod='").append(mcpMethod).append("'"); - } - if (mcpResource != null) { - sb.append(", mcpResource='").append(mcpResource).append("'"); - } - } - - sb.append("}"); - return sb.toString(); - } -} diff --git a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/memory/SharedMemoryManager.java b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/memory/SharedMemoryManager.java index 2e52a33..49dd8f9 100644 --- a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/memory/SharedMemoryManager.java +++ b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/memory/SharedMemoryManager.java @@ -67,7 +67,7 @@ public class SharedMemoryManager { private final int maxLongTermCount; // 性能监控:内存使用上限 - private static final long MAX_MEMORY_USAGE = 100 * 1024 * 1024; // 100MB + private static final long MAX_MEMORY_USAGE = 300 * 1024 * 1024; // 100MB private static final double MEMORY_WARNING_THRESHOLD = 0.8; // 80% 警告阈值 /** @@ -374,7 +374,6 @@ public class SharedMemoryManager { return usedMemory > MAX_MEMORY_USAGE; } - // ========== 工作记忆方法 ========== /** * 创建短期记忆(使用配置的TTL) @@ -500,11 +499,6 @@ public class SharedMemoryManager { if (memory != null) { memory.complete(); LOG.debug("工作记忆已完成: taskId={}, step={}", taskId, memory.getStep()); - - // 可以选择转移到短期记忆 - // ShortTermMemory stm = convertToShortTerm(memory); - // store(stm); - workingCache.remove(taskId); } } @@ -530,7 +524,6 @@ public class SharedMemoryManager { LOG.info("所有记忆已清空"); } - // ========== 便捷方法(简化键值对接口) ========== /** * 存储短期记忆(便捷方法) @@ -732,7 +725,7 @@ public class SharedMemoryManager { LOG.warn("紧急清理完成,清理了 {} 条短期记忆", cleared); // 建议垃圾回收 - System.gc(); +// System.gc(); } /** diff --git a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/memory/consolidator/MemoryConsolidator.java b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/memory/consolidator/MemoryConsolidator.java index 1931882..adde04c 100644 --- a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/memory/consolidator/MemoryConsolidator.java +++ b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/memory/consolidator/MemoryConsolidator.java @@ -157,9 +157,13 @@ public class MemoryConsolidator { // 4. 综合相似度 double similarity = jaccard * 0.8 + typeBonus * 0.1 + lenSimilarity * 0.1; + // 安全地截取 ID 前 8 个字符用于日志 + String idA = a.getId() != null ? (a.getId().length() > 8 ? a.getId().substring(0, 8) : a.getId()) : "null"; + String idB = b.getId() != null ? (b.getId().length() > 8 ? b.getId().substring(0, 8) : b.getId()) : "null"; + LOG.trace("相似度计算: {} <-> {} = {}", - a.getId().substring(0, 8), - b.getId().substring(0, 8), + idA, + idB, similarity); return similarity; diff --git a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/AbsSubagent.java b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/AbsSubagent.java index da2bd41..68351b2 100644 --- a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/AbsSubagent.java +++ b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/AbsSubagent.java @@ -142,23 +142,29 @@ public abstract class AbsSubagent implements Subagent { return; } builder.name(metadata.getName()); - // 应用最大步数 + + // 应用最大步数(优先级:maxSteps > maxTurns > 默认值) if (metadata.getMaxSteps() != null && metadata.getMaxSteps() > 0) { builder.maxSteps(metadata.getMaxSteps()); + LOG.debug("使用 maxSteps 配置: {}", metadata.getMaxSteps()); + } else if (metadata.hasMaxTurns()) { + // maxTurns 作为备选 + builder.maxSteps(metadata.getMaxTurns()); + LOG.debug("使用 maxTurns 作为 maxSteps: {}", metadata.getMaxTurns()); + } else { + // 使用默认步数 30(与主 Agent 保持一致) + builder.maxSteps(30); + LOG.debug("使用默认 maxSteps: 30"); } - // 应用最大步数自动扩展 + // 应用最大步数自动扩展(默认启用) if (metadata.getMaxStepsAutoExtensible() != null) { builder.maxStepsExtensible(metadata.getMaxStepsAutoExtensible()); - } - - // 应用最大轮次 - if (metadata.hasMaxTurns()) { - LOG.debug("元数据指定了最大轮次: {}", metadata.getMaxTurns()); - // 可以作为最大步数的参考 - if (metadata.getMaxSteps() == null) { - builder.maxSteps(metadata.getMaxTurns()); - } + LOG.debug("maxStepsAutoExtensible: {}", metadata.getMaxStepsAutoExtensible()); + } else { + // 默认启用步数自动扩展 + builder.maxStepsExtensible(true); + LOG.debug("使用默认 maxStepsAutoExtensible: true"); } } diff --git a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/AgentStreamOutput.java b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/AgentStreamOutput.java new file mode 100644 index 0000000..285a0e0 --- /dev/null +++ b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/AgentStreamOutput.java @@ -0,0 +1,102 @@ +package org.noear.solon.bot.core.subagent; + +import org.noear.solon.ai.agent.react.ReActTrace; +import org.noear.solon.ai.agent.react.task.ActionChunk; +import org.noear.solon.ai.agent.react.task.ReasonChunk; +import org.noear.solon.ai.chat.prompt.Prompt; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import reactor.core.publisher.Mono; + +import java.time.Duration; + +public class AgentStreamOutput { + + private static final Logger LOG = LoggerFactory.getLogger(AgentStreamOutput.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; + + /** + * 执行流式子代理调用 + */ + public static String executeStream(Subagent agent, String __cwd, String sessionId, + Prompt prompt, ReActTrace __parentTrace, String name) { + try { + String promptStr = prompt.toString(); + LOG.info("[子代理] 启动异步流式执行: type={}, sessionId={}, promptLength={}", + name, 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("[子代理] 流订阅成功: name={}, sessionId={}", name, sessionId); + }) + .doOnNext(chunk -> { + long now = System.currentTimeMillis(); + if (chunkCount[0] == 0) { + firstChunkTime[0] = now; + long firstChunkDelay = now - lastChunkTime[0]; + LOG.info("[子代理] 收到首个chunk: name={}, delay={}ms, chunkType={}", + name, firstChunkDelay, chunk.getClass().getSimpleName()); + } + lastChunkTime[0] = now; + chunkCount[0]++; + + LOG.debug("[子代理] 收到chunk: type={}, chunkType={}, total={}", + name, 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", + name, sessionId, chunkCount[0], totalDuration); + }) + .doOnError(e -> { + LOG.error("[子代理] 流错误: type={}, sessionId={}, error={}, chunksReceived={}", + name, 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={}", + name, 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("[子代理] 执行超时: name={}, sessionId={}", name, sessionId); + return "ERROR: 子代理执行超时。\n\n" + + "可能原因:\n" + + "1. LLM API 响应过慢或无响应\n" + + "2. 子代理执行的任务过于复杂\n" + + "3. 网络连接问题\n\n" + + "建议:\n" + + "- 简化任务描述\n" + + "- 检查网络连接\n" + + "- 查看子代理日志了解详情"; + } + throw new RuntimeException(e); + } + } + +} diff --git a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/SubAgentMetadata.java b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/SubAgentMetadata.java index bba31d6..16da0e7 100644 --- a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/SubAgentMetadata.java +++ b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/SubAgentMetadata.java @@ -185,6 +185,16 @@ public class SubAgentMetadata { // 忽略无效值 } break; + case "maxSteps": + try { + metadata.maxSteps = Integer.parseInt(value); + } catch (NumberFormatException e) { + // 忽略无效值 + } + break; + case "maxStepsAutoExtensible": + metadata.maxStepsAutoExtensible = Boolean.parseBoolean(value); + break; case "skills": // Skills 列表,逗号分隔 metadata.skills = new ArrayList<>(Arrays.asList(value.split(",\\s*"))); @@ -339,6 +349,16 @@ public class SubAgentMetadata { yaml.append("maxTurns: ").append(maxTurns).append("\n"); } + // 最大步数配置 + if (maxSteps != null && maxSteps > 0) { + yaml.append("maxSteps: ").append(maxSteps).append("\n"); + } + + // 最大步数自动扩展 + if (maxStepsAutoExtensible != null && maxStepsAutoExtensible) { + yaml.append("maxStepsAutoExtensible: ").append(maxStepsAutoExtensible).append("\n"); + } + // Skills 配置 if (hasSkills()) { yaml.append("skills: ").append(String.join(", ", skills)).append("\n"); 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 0c8e74b..a21319d 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 @@ -16,8 +16,11 @@ package org.noear.solon.bot.core.subagent; import org.noear.solon.ai.agent.AgentChunk; +import org.noear.solon.ai.agent.AgentResponse; import org.noear.solon.ai.agent.AgentSession; import org.noear.solon.ai.agent.react.ReActTrace; +import org.noear.solon.ai.agent.react.task.ActionChunk; +import org.noear.solon.ai.agent.react.task.ReasonChunk; import org.noear.solon.ai.annotation.ToolMapping; import org.noear.solon.ai.chat.prompt.Prompt; import org.noear.solon.ai.chat.skill.AbsSkill; @@ -53,7 +56,7 @@ import java.util.concurrent.TimeUnit; public class TaskSkill extends AbsSkill { private static final Logger LOG = LoggerFactory.getLogger(TaskSkill.class); - private final AgentKernel mainAgent; + private final AgentKernel agentKernel; private final SubagentManager manager; // 并发控制:使用 Semaphore 限制同时发起的子代理请求数(从配置读取) @@ -62,17 +65,17 @@ public class TaskSkill extends AbsSkill { // 记录每个子代理类型的调用时间,用于精细控制 private static final ConcurrentHashMap lastCallTimeByType = new ConcurrentHashMap<>(); - public TaskSkill(AgentKernel mainAgent, SubagentManager manager) { - this.mainAgent = mainAgent; + public TaskSkill(AgentKernel agentKernel, SubagentManager manager) { + this.agentKernel = agentKernel; this.manager = manager; // 从配置读取并发控制参数 - int maxConcurrent = mainAgent.getProperties().subagentConcurrency.maxConcurrent; + int maxConcurrent = agentKernel.getProperties().subagentConcurrency.maxConcurrent; this.concurrencySemaphore = new Semaphore(maxConcurrent); LOG.info("TaskSkill 初始化: maxConcurrent={}, callIntervalMs={}ms", maxConcurrent, - mainAgent.getProperties().subagentConcurrency.callIntervalMs); + agentKernel.getProperties().subagentConcurrency.callIntervalMs); } @Override @@ -90,9 +93,6 @@ public class TaskSkill extends AbsSkill { sb.append("1. **禁止模拟工作**:严禁不断更新状态而无实际产出\n"); sb.append("2. **必须有实际产出**:代码任务必须生成文件,使用 ls/read 验证\n\n"); - sb.append("#### 必须行为\n"); - sb.append("1. **强制使用 task() 工具**:所有实际工作必须通过 task() 完成\n\n"); - sb.append("### 可用的子代理注册表\n"); sb.append("\n"); for (Subagent agent : manager.getAgents()) { @@ -108,8 +108,8 @@ public class TaskSkill extends AbsSkill { } @ToolMapping(name = "task", - description = "【强制使用】派生并分派任务给专项子代理。所有实际开发工作必须使用此工具委派给子代理完成。返回 Mono 异步结果。") - public Mono task( + description = "派生并分派任务给专项子代理。所有实际开发工作必须使用此工具委派给子代理完成。") + public String task( @Param(name = "name", description = "子代理名称") String name, @Param(name = "prompt", description = "具体指令。必须包含任务目标、关键类名或必要的背景上下文。") String prompt, @Param(name = "description", required = false, description = "简短的任务描述") String description, @@ -117,79 +117,59 @@ public class TaskSkill extends AbsSkill { String __cwd, String __sessionId ) { - AgentSession __parentSession = mainAgent.getSession(__sessionId); + AgentSession __parentSession = agentKernel.getSession(__sessionId); ReActTrace __parentTrace = ReActTrace.getCurrent(__parentSession.getSnapshot()); - // 检查子代理是否存在 - Subagent agent = manager.getAgent(name); - if (agent == null) { - return Mono.just("ERROR: 未知的子代理类型 '" + name + "'。"); - } - - String finalSessionId = Assert.isEmpty(taskId) - ? "subagent_" + name - : taskId; - - LOG.info("分派任务 -> 类型: {}, 会话: {}, 描述: {}", name, finalSessionId, description); - - // 获取执行许可(同步) - boolean acquired; try { - acquired = acquirePermit(name); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - return Mono.just("ERROR: 获取执行许可被中断"); - } + Subagent agent = manager.getAgent(name); + if (agent == null) { + return "ERROR: 未知的子代理类型 '" + name + "'。"; + } - if (!acquired) { - return Mono.just("ERROR: 等待子代理执行许可超时。当前可能有太多子代理在执行。"); - } + String finalSessionId = Assert.isEmpty(taskId) + ? "subagent_" + name + : taskId; + + LOG.info("分派任务 -> 类型: {}, 会话: {}, 描述: {}", name, finalSessionId, description); - // 异步执行并收集结果 - return Mono.fromCallable(() -> { - StringBuilder result = new StringBuilder(); + if (!acquirePermit(name)) { + return "ERROR: 等待子代理执行许可超时。当前可能有太多子代理在执行。"; + } - // 添加任务头部信息 - result.append(String.format( - "task_id: %s\n" + - "subagentType: %s\n" + - "\n" + - "\n", - finalSessionId, name)); + String result = null; try { - // 调用子代理并收集流式输出 - List chunks = agent.stream(__cwd, finalSessionId, Prompt.of(prompt)) - .doOnNext(chunk -> { - String content = chunk.getContent(); - if (content != null && !content.isEmpty()) { - result.append(content); - } - }) - .collectList() - .block(Duration.ofMillis(300000)); // 5分钟超时 - - if (chunks != null) { - LOG.info("子代理任务完成: type={}, chunks={}", name, chunks.size()); + if (__parentTrace.getOptions().getStreamSink() == null) { + // 同步模式 + AgentResponse response = agent.call(__cwd, finalSessionId, Prompt.of(prompt)); + result = response.getContent(); + __parentTrace.getMetrics().addMetrics(response.getMetrics()); + } else { + // 流式模式 + result = AgentStreamOutput.executeStream(agent, __cwd, finalSessionId, Prompt.of(prompt), + __parentTrace, name); } - } catch (Exception e) { - LOG.error("子代理执行失败: type={}, error={}", name, e.getMessage(), e); - result.append("\nERROR: 子代理执行失败: ").append(e.getMessage()); + LOG.info("子代理任务完成: {}", finalSessionId); + + return String.format( + "task_id: %s\n" + + "name: %s\n" + + "\n" + + "\n" + + "%s\n" + + "", + finalSessionId, name, result != null ? result : "(无输出)" + ); + } finally { - result.append("\n"); releasePermit(name); } - return result.toString(); - }) - .subscribeOn(Schedulers.boundedElastic()) // 在弹性线程池中执行 - .doOnSubscribe(subscription -> LOG.info("开始异步执行子代理任务: type={}", name)) - .doOnError(error -> LOG.error("子代理任务执行失败: type={}, error={}", name, error.getMessage(), error)) - .onErrorResume(throwable -> { - releasePermit(name); - return Mono.just("ERROR: 子代理执行失败: " + throwable.getMessage() + "\n"); - }); + } catch (Throwable e) { + LOG.error("子代理执行崩溃: type={}, error={}", name, e.getMessage(), e); + return "ERROR: 子代理执行失败: " + e.getMessage(); + } } /** @@ -199,7 +179,7 @@ public class TaskSkill extends AbsSkill { long waitStart = System.currentTimeMillis(); // 1. 等待并发许可(从配置读取超时时间) - long acquireTimeoutMs = mainAgent.getProperties().subagentConcurrency.acquireTimeoutMs; + long acquireTimeoutMs = agentKernel.getProperties().subagentConcurrency.acquireTimeoutMs; boolean acquired = concurrencySemaphore.tryAcquire(acquireTimeoutMs, TimeUnit.MILLISECONDS); if (!acquired) { LOG.warn("[并发控制] 等待执行许可超时: type={}, timeout={}ms", subagentType, acquireTimeoutMs); @@ -212,7 +192,7 @@ public class TaskSkill extends AbsSkill { } // 2. 控制调用间隔(避免连续调用触发速率限制) - long callIntervalMs = mainAgent.getProperties().subagentConcurrency.callIntervalMs; + long callIntervalMs = agentKernel.getProperties().subagentConcurrency.callIntervalMs; Long lastCall = lastCallTimeByType.get(subagentType); long now = System.currentTimeMillis(); if (lastCall != null) { @@ -290,7 +270,7 @@ public class TaskSkill extends AbsSkill { LOG.info("Agent 定义已保存到: {}", agentFile); } - GeneralPurposeSubagent newAgent = new GeneralPurposeSubagent(mainAgent, metadata, agentDefinition); + GeneralPurposeSubagent newAgent = new GeneralPurposeSubagent(agentKernel, metadata, agentDefinition); newAgent.setDescription(description); newAgent.refresh(); manager.addSubagent(newAgent); 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 d678005..56c11d8 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 @@ -18,6 +18,11 @@ package org.noear.solon.bot.core.teams; import org.noear.snack4.ONode; import org.noear.solon.ai.agent.AgentChunk; import org.noear.solon.ai.agent.Agent; +import org.noear.solon.ai.agent.AgentResponse; +import org.noear.solon.ai.agent.AgentSession; +import org.noear.solon.ai.agent.react.ReActTrace; +import org.noear.solon.ai.agent.react.task.ActionChunk; +import org.noear.solon.ai.agent.react.task.ReasonChunk; import org.noear.solon.ai.agent.team.TeamAgent; import org.noear.solon.ai.agent.team.TeamProtocols; import org.noear.solon.ai.chat.ChatModel; @@ -27,10 +32,8 @@ import org.noear.solon.ai.annotation.ToolMapping; import org.noear.solon.annotation.Param; import org.noear.solon.bot.core.AgentKernel; import org.noear.solon.bot.core.memory.smart.IntelligentMemoryManager; -import org.noear.solon.bot.core.subagent.AbsSubagent; -import org.noear.solon.bot.core.subagent.SubAgentMetadata; -import org.noear.solon.bot.core.subagent.Subagent; -import org.noear.solon.bot.core.subagent.SubagentManager; +import org.noear.solon.bot.core.subagent.*; +import org.noear.solon.core.util.Assert; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.publisher.Flux; @@ -45,6 +48,7 @@ import java.nio.file.Paths; import java.time.Duration; import java.util.*; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; /** @@ -102,29 +106,20 @@ public class AgentTeamsSkill extends AbsSkill { sb.append("### 核心规则(强制执行)\n\n"); sb.append("#### 禁止行为(绝对不可违反)\n"); sb.append("1. **禁止模拟工作**:\n"); - sb.append(" - 严禁不断更新 `update_working_memory`、`step`、`currentAgent` 而无实际产出\n"); + sb.append(" - 严禁不断更新 `update_working_memory`、`step`、`currentAgent`\n"); sb.append(" - 不得使用 `memory_store` 存储虚假的\"已完成\"状态\n"); sb.append(" - 不得声称\"需求分析已完成\"、\"代码已编写\"等虚假结论\n"); sb.append(" - 不断更新状态而无实际文件产出是**严重违规**\n\n"); - sb.append("2. **必须有实际产出**:\n"); - sb.append(" - 代码任务必须生成 `.java`、`.py` 等文件\n"); - sb.append(" - 文档任务必须生成 `.md`、`.txt` 等文件\n"); - sb.append(" - 使用 `ls`、`read` 工具验证文件已真实创建\n"); - sb.append(" - 确认文件内容符合要求后才可宣称任务完成\n\n"); - sb.append("3. **禁止循环操作**:\n"); + sb.append("2. **禁止循环操作**:\n"); sb.append(" - 不得重复调用相同工具而不产生新进展\n"); sb.append(" - 检测到循环时必须立即停止并使用 `task()` 工具\n\n"); sb.append("#### 必须行为(强制执行)\n"); sb.append("1. **必须使用 task 工具**:\n"); sb.append(" - 所有实际工作必须通过 `task(subagent_type, prompt)` 委派给子代理\n"); - sb.append(" - 可用类型:explore、plan、bash、general-purpose、solon-code-guide\n"); + sb.append(" - 可用类型:explore、plan、bash、general-purpose\n"); sb.append(" - 例如:`task(subagent_type='bash', prompt='创建文件并编写代码')`\n\n"); - sb.append("2. **必须验证产出**:\n"); - sb.append(" - 使用 `ls(path='.')` 列出创建的文件\n"); - sb.append(" - 使用 `read(file_path='xxx')` 验证文件内容\n"); - sb.append(" - 只有确认真实产出后才可完成\n\n"); - sb.append("3. **token 使用警告**:\n"); - sb.append(" - 每个任务建议不超过 10,000 tokens\n"); + sb.append("2. **token 使用警告**:\n"); + sb.append(" - 每个任务建议不超过 20,000 tokens\n"); sb.append(" - 超过 5,000 tokens 无产出时必须改变策略\n"); sb.append(" - 禁止无限循环调用工具\n\n"); @@ -184,7 +179,6 @@ public class AgentTeamsSkill extends AbsSkill { 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"); @@ -228,7 +222,7 @@ public class AgentTeamsSkill extends AbsSkill { sb.append("```\n\n"); sb.append("### 任务链协调:如何将结果传递给下一个 subagent\n\n"); - sb.append("**⚠️ 重要:完成任务链协调的三种方法**\n\n"); + sb.append("** 重要:完成任务链协调的三种方法**\n\n"); sb.append("**方法 1:在 prompt 中传递上下文(推荐)**\n"); sb.append("```\n"); sb.append("# 第一步:plan 完成设计\n"); @@ -346,90 +340,84 @@ public class AgentTeamsSkill extends AbsSkill { private static final long TEAM_TASK_TIMEOUT_MS = 300_000; // 5分钟超时 @ToolMapping(name = "team_task", - description = "启动团队协作任务。MainAgent 会自动分解任务并协调多个 SubAgent 协作完成。适用于复杂、多步骤的任务。注意:简单任务请直接回答,无需调用此工具。返回 Mono 用于异步处理。") + description = "启动团队协作任务。MainAgent 会自动分解任务并协调多个 SubAgent 协作完成。适用于复杂、多步骤的任务。注意:简单任务请直接回答,无需调用此工具。返回 Mono。") public Mono teamTask( @Param(name = "prompt", description = "任务描述,清晰说明目标和要求") String prompt, + @Param(name = "taskId", required = false, description = "可选。若要继续之前的任务会话,请传入对应的 task_id") String taskId, String __cwd, String __sessionId ) { // 检查是否已有任务在执行 if (mainAgent.isRunning()) { - return Mono.just("[WARN] 团队任务正在执行中,请等待当前任务完成。"); + return Mono.just("[WARN] 团队任务正在执行中,请等待当前任务完成。\n"); } - LOG.info("启动团队协作任务: {}", prompt); - - // 使用流式执行并转换为 Mono - return Mono.fromCallable(() -> { - // 使用流式执行(实时输出) - StringBuilder streamingOutput = new StringBuilder(); + AgentSession __parentSession = kernel.getSession(__sessionId); + ReActTrace __parentTrace = ReActTrace.getCurrent(__parentSession.getSnapshot()); - try { - // 获取流式响应(传递 __cwd 以支持文件操作) - Flux responseStream = - mainAgent.executeStream(Prompt.of(prompt), __cwd); - - // 收集流式输出(带超时) - List chunks = responseStream - .doOnNext(chunk -> { - String content = chunk.getContent(); - if (content != null && !content.isEmpty()) { - streamingOutput.append(content); - } - }) - .doOnComplete(() -> { - LOG.info("\n[OK] 团队任务流式执行完成"); - }) - .doOnError(error -> { - LOG.error("团队任务流式执行出错", error); - }) - .collectList() - .block(Duration.ofMillis(TEAM_TASK_TIMEOUT_MS)); + String finalSessionId = Assert.isEmpty(taskId) + ? "team_task_" + __sessionId + : taskId; - if (chunks != null) { - LOG.debug("流式响应收集完成,收到 {} 个 chunk", chunks.size()); - } + LOG.info("启动团队协作任务: {}", prompt); - } catch (Exception e) { - LOG.error("团队任务执行失败", e); - throw new RuntimeException("团队任务执行失败: " + e.getMessage(), e); - } + // 🔑 关键:使用 Mono.create 避免阻塞主 Flux + return Mono.create(emitter -> { + // 在单独的线程中执行 MainAgent + StringBuilder finalResult = new StringBuilder(); - // 获取任务统计 - SharedTaskList.TaskStatistics stats = mainAgent.getTaskList().getStatistics(); + try { + // 获取流式响应 + List chunks = mainAgent.executeStream(Prompt.of(prompt), __cwd) + .doOnNext(chunk -> { + String content = chunk.getContent(); + if (content != null && !content.isEmpty()) { + + if (chunk instanceof ActionChunk) { + __parentTrace.getOptions().getStreamSink().next(chunk); + } else if (chunk instanceof ReasonChunk) { + __parentTrace.getOptions().getStreamSink().next(chunk); + } + + if (chunk != null && chunk.hasContent()) { + // 实时追加到结果 + finalResult.append(content); + } + } + }) + .doOnComplete(() -> { + LOG.info("团队任务流式执行完成"); + + // 在完成后输出统计信息 + try { + SharedTaskList.TaskStatistics stats = mainAgent.getTaskList().getStatistics(); + String statsMsg = String.format( + "\n\n[STATS] 总=%d, 完成=%d, 失败=%d, 进行中=%d, 待认领=%d\n", + stats.totalTasks, stats.completedTasks, stats.failedTasks, + stats.inProgressTasks, stats.pendingTasks); + finalResult.append(statsMsg); + } catch (Exception e) { + LOG.warn("获取任务统计失败: {}", e.getMessage()); + } + + // 完成 Mono + emitter.success(finalResult.toString()); + }) + .doOnError(error -> { + LOG.error("团队任务流式执行出错: {}", error.getMessage(), error); + emitter.error(error); + }) + .collectList() + .block(Duration.ofMillis(TEAM_TASK_TIMEOUT_MS)); + + if (chunks != null) { + LOG.debug("流式响应收集完成,收到 {} 个 chunk", chunks.size()); + } - StringBuilder result = new StringBuilder(); - result.append("[OK] 团队任务执行完成\n\n"); - result.append("**任务统计**:\n"); - result.append(String.format("- 总任务数: %d\n", stats.totalTasks)); - result.append(String.format("- 已完成: %d\n", stats.completedTasks)); - result.append(String.format("- 失败: %d\n", stats.failedTasks)); - result.append(String.format("- 进行中: %d\n", stats.inProgressTasks)); - result.append(String.format("- 待认领: %d\n\n", stats.pendingTasks)); - - // 主 Agent 的回复(流式输出已实时打印) - result.append("**主 Agent 回复**:\n"); - - // 如果流式输出有内容,添加到结果中 - String agentResponse = streamingOutput.toString(); - if (!agentResponse.isEmpty()) { - // 限制显示长度,避免过长 - if (agentResponse.length() > 2000) { - agentResponse = agentResponse.substring(0, 2000) + "\n...(内容过长,已截断)"; + } catch (Exception e) { + LOG.error("团队任务执行失败", e); + emitter.error(new RuntimeException("团队任务执行失败: " + e.getMessage(), e)); } - result.append(agentResponse); - } else { - result.append("(流式输出已完成,请查看上方控制台输出)"); - } - - return result.toString(); - }) - .subscribeOn(Schedulers.boundedElastic()) // 在弹性线程池中执行 - .doOnSubscribe(subscription -> LOG.info("开始异步执行团队任务: {}", prompt)) - .doOnError(error -> LOG.error("团队任务执行失败: prompt={}", prompt, error)) - .onErrorResume(throwable -> { - LOG.error("团队任务执行失败: prompt={}", prompt, throwable); - return Mono.just("[ERROR] 团队任务执行失败: " + throwable.getMessage()); }); } @@ -658,36 +646,40 @@ public class AgentTeamsSkill extends AbsSkill { @ToolMapping(name = "analyze_tasks", description = "针对团队协作任务,分析用户请求,将其分解为多个子任务。返回任务分解建议(JSON格式),Agent可以根据建议使用create_task创建任务。") public String analyzeTasks( - @Param(name = "request", description = "用户请求或任务描述") String request) { + @Param(name = "request", description = "用户请求或任务描述") String request, + String __cwd, + String __sessionId) { + + AgentSession __parentSession = kernel.getSession(__sessionId); + ReActTrace __parentTrace = ReActTrace.getCurrent(__parentSession.getSnapshot()); try { if (mainAgent == null) { return "[WARN] MainAgent 未初始化"; } + String result = null; // 构建 LLM 分析提示词 String prompt = buildTaskAnalysisPrompt(request); - // 调用 LLM 分析 - Prompt llmPrompt = Prompt.of(prompt); - - // 通过 kernel 获取 ChatModel - if (kernel == null) { - return "[WARN] Kernel 未初始化"; - } - - ChatModel chatModel = kernel.getChatModel(); - if (chatModel == null) { - return "[WARN] ChatModel 未初始化"; + if (__parentTrace.getOptions().getStreamSink() == null) { + // 同步模式 + AgentResponse response = mainAgent.call(__cwd, __sessionId, Prompt.of(prompt)); + result = response.getContent(); + __parentTrace.getMetrics().addMetrics(response.getMetrics()); + } else { + // 流式模式 + result = mainAgent.executeStream(__cwd, __sessionId, Prompt.of(prompt), + __parentTrace, "analyze_tasks"); } - String response = chatModel.prompt(llmPrompt).call().getContent(); - // 解析并格式化响应 - return formatAnalysisResponse(response); + return formatAnalysisResponse(result); } catch (Exception e) { LOG.error("任务分析失败", e); return "[ERROR] 任务分析失败: " + e.getMessage(); + } catch (Throwable e) { + throw new RuntimeException(e); } } @@ -792,15 +784,13 @@ public class AgentTeamsSkill extends AbsSkill { */ private List parseTasksJson(String json) { List tasks = new ArrayList<>(); - try { ONode root = ONode.deserialize(json); - // 检查是否为数组 if (root.isArray()) { for (int i = 0; i < root.size(); i++) { ONode taskNode = root.get(i); - TeamTask task = parseTaskNode(taskNode, i); + TeamTask task = taskNode.toBean(TeamTask.class); if (task != null) { tasks.add(task); } @@ -809,54 +799,9 @@ public class AgentTeamsSkill extends AbsSkill { } catch (Exception e) { LOG.warn("解析任务 JSON 失败: {}", e.getMessage()); } - return tasks; } - /** - * 从 ONode 解析单个任务 - */ - private TeamTask parseTaskNode(ONode node, int index) { - try { - String title = node.get("title").getString(); - String description = node.get("description").getString(); - String typeStr = node.get("type").getString(); - int priority = node.get("priority").getInt(); - - if (title == null || title.isEmpty()) { - title = "任务 " + (index + 1); - } - if (description == null || description.isEmpty()) { - description = title; - } - - TeamTask.TaskType type = TeamTask.TaskType.DEVELOPMENT; - if (typeStr != null && !typeStr.isEmpty()) { - try { - type = TeamTask.TaskType.valueOf(typeStr.toUpperCase()); - } catch (IllegalArgumentException e) { - // 使用默认类型 - } - } - - if (priority < 1 || priority > 10) { - priority = 7; - } - - String taskId = "task-" + System.currentTimeMillis() + "-" + index; - return TeamTask.builder() - .id(taskId) - .title(title) - .description(description) - .type(type) - .priority(priority) - .dependencies(new ArrayList<>()) - .build(); - } catch (Exception e) { - LOG.warn("解析任务节点失败: {}, error: {}", node, e.getMessage()); - return null; - } - } /** * 获取状态图标 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 8373944..fbd977a 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 @@ -17,9 +17,13 @@ package org.noear.solon.bot.core.teams; import lombok.Getter; import org.noear.solon.ai.agent.AgentChunk; +import org.noear.solon.ai.agent.AgentResponse; import org.noear.solon.ai.agent.AgentSession; import org.noear.solon.ai.agent.AgentSessionProvider; import org.noear.solon.ai.agent.react.ReActAgent; +import org.noear.solon.ai.agent.react.ReActTrace; +import org.noear.solon.ai.agent.react.task.ActionChunk; +import org.noear.solon.ai.agent.react.task.ReasonChunk; import org.noear.solon.ai.chat.ChatModel; import org.noear.solon.ai.chat.prompt.Prompt; import org.noear.solon.bot.core.AgentKernel; @@ -36,11 +40,14 @@ import org.noear.solon.bot.core.message.AgentMessage; import org.noear.solon.bot.core.message.MessageAck; import org.noear.solon.bot.core.message.MessageChannel; import org.noear.solon.bot.core.subagent.SubAgentMetadata; +import org.noear.solon.bot.core.subagent.Subagent; import org.noear.solon.bot.core.subagent.SubagentManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import java.time.Duration; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; @@ -93,6 +100,8 @@ public class MainAgent { // 性能优化:使用 CountDownLatch 替代轮询 private volatile CountDownLatch taskCompletionLatch; + private static final long SUBAGENT_STREAM_TIMEOUT_MS = 180_000; + public MainAgent(SubAgentMetadata config, @@ -788,133 +797,6 @@ public class MainAgent { } } - /** - * 获取系统提示词 - */ - private String getSystemPrompt() { - return "## 主代理(Team Lead)- 强制执行模式\n\n" + - "你是 Agent Teams 的团队领导,负责协调多个子代理协作完成任务。\n" + - "\n" + - "### ⚠️ 核心规则(违反即失败)\n" + - "\n" + - "#### 🚫 禁止行为(绝对不可违反)\n" + - "1. **禁止模拟工作**:\n" + - " - 严禁使用 `update_working_memory`、`memory_store` 等工具声称工作已完成\n" + - " - 不断更新 step、currentAgent 字段而不实际工作是**严重违规**\n" + - " - 不得在记忆中存储虚假的\"已完成\"状态\n\n" + - "2. **禁止虚假产出**:\n" + - " - 不得声称\"需求分析已完成\"、\"代码已编写\"等虚假结论\n" + - " - 没有实际文件产出前,不得宣称任务完成\n" + - " - 记忆存储只能存储真实已完成的工作结果\n\n" + - "3. **禁止循环操作**:\n" + - " - 不得重复调用相同的工具而不产生新进展\n" + - " - 不得无限更新状态而无实际工作\n" + - " - 检测到循环时必须立即停止并改变策略\n\n" + - "#### ✅ 必须行为(必须执行)\n" + - "1. **必须使用 subagent 工具**:\n" + - " - 所有实际工作必须通过 `subagent(type, prompt)` 工具委派给专门的子代理\n" + - " - 可用的子代理类型:explore、plan、bash、general-purpose、solon-code-guide\n" + - " - 例如:`subagent(type='bash', prompt='创建项目目录并初始化')`\n\n" + - "2. **必须有实际产出**:\n" + - " - **代码任务**必须生成 `.java`、`.py` 等代码文件\n" + - " - **文档任务**必须生成 `.md`、`.txt` 等文档文件\n" + - " - **测试任务**必须有测试报告或测试结果文件\n" + - " - **架构任务**必须有架构图或设计文档\n\n" + - "3. **必须验证产出**:\n" + - " - 使用 `read` 或 `ls` 工具验证文件是否真实创建\n" + - " - 确认文件内容符合要求后才可宣称任务完成\n\n" + - "\n" + - "### 工作流程(强制执行)\n" + - "\n" + - "#### 步骤 1:任务分析(使用 subagent)\n" + - "```\n" + - "subagent(\n" + - " type='plan',\n" + - " prompt='分析任务需求:[用户任务],提供详细的实现方案'\n" + - ")\n" + - "```\n" + - "\n" + - "#### 步骤 2:执行工作(使用 subagent)\n" + - "```\n" + - "# 开发任务\n" + - "subagent(\n" + - " type='bash',\n" + - " prompt='创建文件 [文件名],编写代码实现:[具体需求]'\n" + - ")\n" + - "\n" + - "# 测试任务\n" + - "subagent(\n" + - " type='bash',\n" + - " prompt='编写测试用例并运行测试,生成测试报告'\n" + - ")\n" + - "```\n" + - "\n" + - "#### 步骤 3:验证产出(使用 ls/read)\n" + - "```\n" + - "ls(path='.') # 列出文件\n" + - "read(file_path='xxx.java') # 验证文件内容\n" + - "```\n" + - "\n" + - "#### 步骤 4:总结结果(仅在真实完成后)\n" + - "```\n" + - "# 只有在确认文件真实创建后才可总结\n" + - "Final Answer: [ANSWER]\n" + - "已完成以下工作:\n" + - "1. 创建文件:file1.java, file2.py\n" + - "2. 文件内容:[简要描述]\n" + - "3. 验证结果:所有文件已通过测试\n" + - "```\n" + - "\n" + - "### ⚠️ 常见错误(必须避免)\n" + - "\n" + - "❌ **错误示例**:\n" + - "```\n" + - "# 错误1:虚假更新状态\n" + - "update_working_memory(field='step', value='1')\n" + - "update_working_memory(field='step', value='2')\n" + - "memory_store(content='需求分析已完成') # 虚假!\n" + - "\n" + - "# 错误2:声称完成但无产出\n" + - "Final Answer: [ANSWER]\n" + - "团队协作完成! # 但没有创建任何文件\n" + - "```\n" + - "\n" + - "✅ **正确示例**:\n" + - "```\n" + - "# 正确:实际调用子代理\n" + - "subagent(type='bash', prompt='创建 UserController.java')\n" + - "# 等待结果...\n" + - "ls(path='src/main/java') # 验证文件已创建\n" + - "read(file_path='src/main/java/UserController.java') # 验证内容\n" + - "Final Answer: [ANSWER]\n" + - "已创建 UserController.java,包含用户增删改查功能\n" + - "```\n" + - "\n" + - "### 🎯 成功标准\n" + - "\n" + - "任务被认为完成,当且仅当:\n" + - "1. **有实际文件产出**:代码、文档、测试报告等\n" + - "2. **文件内容已验证**:使用 read 工具确认内容正确\n" + - "3. **通过必要测试**:代码可编译、可运行\n" + - "4. **无虚假声明**:所有声称的完成都是真实的\n" + - "\n" + - "### 📊 token 使用警告\n" + - "\n" + - "- 每个任务建议不超过 10,000 tokens\n" + - "- 超过 5,000 tokens 时必须检查是否有实际产出\n" + - "- 超过 10,000 tokens 无产出时立即终止任务\n" + - "- 禁止循环调用工具而不产生进展\n" + - "\n" + - "### 🚀 立即开始\n" + - "\n" + - "接到任务后,必须:\n" + - "1. 先使用 `subagent(type='plan', ...)` 分析需求\n" + - "2. 再使用 `subagent(type='bash', ...)` 执行工作\n" + - "3. 使用 `ls`、`read` 验证产出\n" + - "4. 确认真实完成后才给出 Final Answer\n" + - "\n" + - "**记住:禁止模拟,必须实际产出!**\n"; - } /** * 检查是否正在运行 @@ -969,6 +851,108 @@ public class MainAgent { } } + public AgentResponse call(String __cwd, String sessionId, Prompt prompt) throws Throwable { + AgentSession session = kernel.getSession(sessionId); + + return agent.prompt(prompt) + .session(session) + .options(o -> { + o.toolContextPut("__cwd", __cwd); + }) + .call(); + } + + public Flux stream(String __cwd, String sessionId, Prompt prompt) { + AgentSession session = kernel.getSession(sessionId); + + return agent.prompt(prompt) + .session(session) + .options(o -> { + o.toolContextPut("__cwd", __cwd); + }) + .stream(); + } + + /** + * 执行流式子代理调用 + */ + public String executeStream( String __cwd, String sessionId, + Prompt prompt, ReActTrace __parentTrace, String name) { + try { + String promptStr = prompt.toString(); + LOG.info("[mainAgent] 启动异步流式执行: type={}, sessionId={}, promptLength={}", + name, 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 = this.stream(__cwd, sessionId, prompt) + .doOnSubscribe(s -> { + LOG.info("[mainAgent] 流订阅成功: name={}, sessionId={}", name, sessionId); + }) + .doOnNext(chunk -> { + long now = System.currentTimeMillis(); + if (chunkCount[0] == 0) { + firstChunkTime[0] = now; + long firstChunkDelay = now - lastChunkTime[0]; + LOG.info("[mainAgent] 收到首个chunk: name={}, delay={}ms, chunkType={}", + name, firstChunkDelay, chunk.getClass().getSimpleName()); + } + lastChunkTime[0] = now; + chunkCount[0]++; + + LOG.debug("[mainAgent] 收到chunk: type={}, chunkType={}, total={}", + name, 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("[mainAgent] 流完成: type={}, sessionId={}, totalChunks={}, totalDuration={}ms", + name, sessionId, chunkCount[0], totalDuration); + }) + .doOnError(e -> { + LOG.error("[mainAgent] 流错误: type={}, sessionId={}, error={}, chunksReceived={}", + name, 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("[mainAgent] 执行成功: type={}, sessionId={}, chunks={}, resultLength={}", + name, 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("[mainAgent] 执行超时: name={}, sessionId={}", name, sessionId); + return "ERROR: mainAgent执行超时。\n\n" + + "可能原因:\n" + + "1. LLM API 响应过慢或无响应\n" + + "2. mainAgent执行的任务过于复杂\n" + + "3. 网络连接问题\n\n" + + "建议:\n" + + "- 简化任务描述\n" + + "- 检查网络连接\n" + + "- 查看mainAgent日志了解详情"; + } + throw new RuntimeException(e); + } + } + /** * 获取当前目标 * -- Gitee From 5616cce2a3866d0d796367819e5878e10380203e Mon Sep 17 00:00:00 2001 From: bai <1145000687@qq.com> Date: Tue, 17 Mar 2026 18:44:59 +0800 Subject: [PATCH 5/5] =?UTF-8?q?feat(bot):=20=E4=BC=98=E5=8C=96AgentTeams?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F=E7=9A=84=E5=A4=9A=E4=B8=93=E5=AE=B6=E5=8D=8F?= =?UTF-8?q?=E4=BD=9C=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将maxSteps默认值从30调整为10,优化性能 - 重构AgentTeams技能描述和指令,明确团队协作模式 - 添加多专家讨论机制,支持简单讨论和复杂讨论板模式 - 新增create_discussion_board、append_discussion_board、get_discussion_board工具 - 修复task调用参数,统一使用name字段替代subagent_type - 完善SubAgent模式描述和调用规范 - 优化SharedMemoryManager的长期记忆检索功能 - 更新文档示例和使用场景说明 --- .../noear/solon/bot/core/AgentProperties.java | 2 +- .../bot/core/memory/SharedMemoryManager.java | 16 ++ .../solon/bot/core/subagent/TaskSkill.java | 12 +- .../solon/bot/core/teams/AgentTeamsSkill.java | 232 +++++++++++++++--- .../noear/solon/bot/core/teams/MainAgent.java | 17 ++ 5 files changed, 244 insertions(+), 35 deletions(-) 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 c400b0e..0f8f007 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 @@ -35,7 +35,7 @@ import java.util.Map; public class AgentProperties implements Serializable { private String workDir = "./work/"; - private int maxSteps = 30; + private int maxSteps = 10; private boolean maxStepsAutoExtensible = false; private int sessionWindowSize = 10; diff --git a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/memory/SharedMemoryManager.java b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/memory/SharedMemoryManager.java index 49dd8f9..3f0896f 100644 --- a/solonbot-sdk/src/main/java/org/noear/solon/bot/core/memory/SharedMemoryManager.java +++ b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/memory/SharedMemoryManager.java @@ -558,6 +558,22 @@ public class SharedMemoryManager { store(memory); } + /** + * 检索长期记忆(便捷方法) + * + * @param key 键 + * @return 值,不存在返回 null + */ + public String recallLongTerm(String key) { + Memory ltm = longTermCache.get(key); + if (ltm != null && !ltm.isExpired()) { + if (ltm instanceof LongTermMemory) { + return ((LongTermMemory) ltm).getSummary(); + } + } + return null; + } + /** * 存储知识记忆(便捷方法) * 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 a21319d..47b159c 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 @@ -80,17 +80,21 @@ public class TaskSkill extends AbsSkill { @Override public String description() { - return "战略任务调度与子代理委派专家"; + return "SubAgent 模式工具:委派任务给专门的子代理(explore、plan、bash等)"; } @Override public String getInstruction(Prompt prompt) { StringBuilder sb = new StringBuilder(); - sb.append("处理复杂的、多步骤的任务,必须委派子代理(Subagent)执行\n\n"); + sb.append("## SubAgent 模式工具\n\n"); sb.append("### 核心规则(强制执行)\n\n"); sb.append("#### 禁止行为\n"); sb.append("1. **禁止模拟工作**:严禁不断更新状态而无实际产出\n"); + sb.append("2. **禁止自己代替子代理完成任务**:必须调用真实的子代理\n\n"); + + sb.append("#### 必须行为\n"); + sb.append("1. **强制使用 task() 工具**:所有实际工作必须通过 task() 完成\n"); sb.append("2. **必须有实际产出**:代码任务必须生成文件,使用 ls/read 验证\n\n"); sb.append("### 可用的子代理注册表\n"); @@ -102,7 +106,7 @@ public class TaskSkill extends AbsSkill { sb.append("### 调用约定\n"); sb.append("- **上下文对齐**: 子代理看不见当前历史,必须在 prompt 中传入必要的上下文\n"); - sb.append("- **示例**: `task(subagentType=\"explore\", prompt=\"分析项目架构\")`\n"); + sb.append("- **示例**: `task(name=\"explore\", prompt=\"分析项目架构\")`\n"); return sb.toString(); } @@ -279,7 +283,7 @@ public class TaskSkill extends AbsSkill { String.format("**代码**: %s\n", code) + String.format("**名称**: %s\n", name) + String.format("**描述**: %s\n", description) + - String.format("\n现在可以使用 `task(subagentType=\"%s\", prompt=\"...\")` 来调用。", code); + String.format("\n现在可以使用 `task(name=\"%s\", prompt=\"...\")` 来调用。", code); } catch (Throwable e) { LOG.error("创建子代理失败: code={}, error={}", code, e.getMessage(), e); 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 56c11d8..c78848f 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 @@ -93,35 +93,51 @@ public class AgentTeamsSkill extends AbsSkill { @Override public String description() { - return "Agent Teams 协调专家:支持团队协作任务、任务管理、子代理调用"; + return "AgentTeams 模式协调器:MainAgent团队协作、任务列表管理、子代理协调"; } @Override public String getInstruction(Prompt prompt) { StringBuilder sb = new StringBuilder(); - sb.append("## Agent Teams 协调能力\n\n"); - sb.append("你是一个团队协调器,可以启动和管理多代理协作任务。\n\n"); + sb.append("## AgentTeams 模式(团队协作模式)\n\n"); + sb.append("### 角色定位\n"); + sb.append("你是 **MainAgent(团队领导)**,负责协调多个子代理协作完成复杂任务。\n"); + sb.append("与 SubAgent 模式不同,你需要管理任务列表、协调工作流、汇总团队成果。\n\n"); - sb.append("### 核心规则(强制执行)\n\n"); + sb.append("### 核心规则(团队协作专用)\n\n"); sb.append("#### 禁止行为(绝对不可违反)\n"); - sb.append("1. **禁止模拟工作**:\n"); - sb.append(" - 严禁不断更新 `update_working_memory`、`step`、`currentAgent`\n"); + sb.append("1. **禁止虚假团队协作**:\n"); + sb.append(" - 严禁模拟多个\"专家\"讨论但实际不调用 task() 工具\n"); + sb.append(" - 不得声称\"专家A认为...、专家B建议...\"但未真实调用\n"); sb.append(" - 不得使用 `memory_store` 存储虚假的\"已完成\"状态\n"); - sb.append(" - 不得声称\"需求分析已完成\"、\"代码已编写\"等虚假结论\n"); - sb.append(" - 不断更新状态而无实际文件产出是**严重违规**\n\n"); + sb.append(" - 不断更新工作记忆但无子代理实际执行是**严重违规**\n\n"); sb.append("2. **禁止循环操作**:\n"); sb.append(" - 不得重复调用相同工具而不产生新进展\n"); - sb.append(" - 检测到循环时必须立即停止并使用 `task()` 工具\n\n"); - sb.append("#### 必须行为(强制执行)\n"); - sb.append("1. **必须使用 task 工具**:\n"); - sb.append(" - 所有实际工作必须通过 `task(subagent_type, prompt)` 委派给子代理\n"); - sb.append(" - 可用类型:explore、plan、bash、general-purpose\n"); - sb.append(" - 例如:`task(subagent_type='bash', prompt='创建文件并编写代码')`\n\n"); - sb.append("2. **token 使用警告**:\n"); - sb.append(" - 每个任务建议不超过 20,000 tokens\n"); - sb.append(" - 超过 5,000 tokens 无产出时必须改变策略\n"); - sb.append(" - 禁止无限循环调用工具\n\n"); + sb.append(" - 检测到循环时必须立即停止并使用 `task()` 或 `complete_task()`\n\n"); + sb.append("#### 必须行为(团队协作流程)\n"); + sb.append("1. **任务分解**:使用 `analyze_tasks()` 或 `create_tasks()` 创建任务列表\n"); + sb.append("2. **委派执行**:使用 `task()` 或 `complete_task()` 让子代理实际执行\n"); + sb.append("3. **跟踪进度**:使用 `team_status()` 查看任务状态\n"); + sb.append("4. **汇总成果**:收集各子代理结果,形成最终答案\n\n"); + + sb.append("### 模式对比:AgentTeams vs SubAgent\n\n"); + sb.append("| 特性 | **AgentTeams 模式**(当前) | **SubAgent 模式** |\n"); + sb.append("|------|---------------------------|------------------|\n"); + sb.append("| **协调器** | MainAgent(团队领导) | 主Agent直接调用 |\n"); + sb.append("| **任务管理** | 使用任务列表(`create_task`/`complete_task`) | 无任务列表 |\n"); + sb.append("| **协作方式** | 多子代理通过任务列表协作 | 单次直接调用 |\n"); + sb.append("| **适用场景** | 复杂、多步骤、需协调的工程任务 | 单一、独立的专业任务 |\n"); + sb.append("| **工具数量** | 20+(团队协作、记忆管理等) | 1(只有task) |\n\n"); + sb.append("**何时使用 AgentTeams 模式**:\n"); + sb.append("- ✅ 需要**任务分解和跟踪**(有明确步骤)\n"); + sb.append("- ✅ 需要**多个子代理协作**(2个以上专业领域)\n"); + sb.append("- ✅ 需要**中间结果共享**(子代理间传递数据)\n"); + sb.append("- ✅ 需要**进度监控**(查看哪些任务完成/进行中)\n\n"); + sb.append("**何时使用 SubAgent 模式**:\n"); + sb.append("- ✅ 简单任务(单次调用即可完成)\n"); + sb.append("- ✅ 探索性任务(分析项目、查找代码)\n"); + sb.append("- ✅ 不需要任务协调(独立执行)\n\n"); // ========== 工作流程 ========== sb.append("### 工作流程\n"); @@ -149,14 +165,20 @@ public class AgentTeamsSkill extends AbsSkill { sb.append(" - 自动评分:多维度评估记忆重要性(0-10分)\n"); sb.append(" - 智能检索:`memory_recall()` 按相关性排序\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("7. **多专家讨论**: \n"); + sb.append(" - **简单讨论**(3位以内专家):使用多轮 `task()` 调用,传递前一位专家的观点\n"); + sb.append(" - **复杂讨论**(4位以上专家):使用讨论板 `create_discussion_board()`\n"); + sb.append(" - `create_discussion_board()` 创建讨论板\n"); + sb.append(" - `append_discussion_board()` 追加专家观点\n"); + sb.append(" - `get_discussion_board()` 获取完整讨论\n"); + sb.append("8. **代理间通信**: 使用 `send_message()` 发送消息,`list_agents()` 查看可用代理\n"); + sb.append("9. **团队命名**: 使用 `suggest_team_name()` 获取命名建议\n\n"); sb.append("### 强制委派准则\n"); sb.append("- **项目认知**: 探索项目、分析架构 → 委派给子代理\n"); sb.append("- **复杂变更**: 跨文件修复、重构 → 委派给子代理\n"); sb.append("- **决策量化**: 超过 3 次工具调用 → 改用子代理\n"); - sb.append("- **所有开发任务**: 必须使用 `task(subagent_type='bash', ...)` 实际创建文件\n\n"); + sb.append("- **所有开发任务**: 必须使用 `task(name='bash', ...)` 实际创建文件\n\n"); sb.append("### 可用的子代理\n"); sb.append("\n"); @@ -226,27 +248,27 @@ public class AgentTeamsSkill extends AbsSkill { sb.append("**方法 1:在 prompt 中传递上下文(推荐)**\n"); sb.append("```\n"); sb.append("# 第一步:plan 完成设计\n"); - sb.append("result1 = task(subagent_type='plan', prompt='设计用户登录模块')\n"); + sb.append("result1 = task(name='plan', prompt='设计用户登录模块')\n"); sb.append("\n"); sb.append("# 第二步:传递给 bash\n"); sb.append("task(\n"); - sb.append(" subagent_type='bash',\n"); + sb.append(" name='bash',\n"); sb.append(" prompt='基于以下设计:' + result1 + '创建代码'\n"); sb.append(")\n"); sb.append("```\n\n"); sb.append("**方法 2:使用共享记忆**\n"); sb.append("```\n"); - sb.append("result1 = task(subagent_type='plan', prompt='设计...')\n"); + sb.append("result1 = task(name='plan', prompt='设计...')\n"); sb.append("memory_store(content=result1, key='design')\n"); sb.append("design = memory_recall(query='设计', limit=1)\n"); - sb.append("task(subagent_type='bash', prompt='' + design + '创建代码')\n"); + sb.append("task(name='bash', prompt='' + design + '创建代码')\n"); sb.append("```\n\n"); sb.append("**方法 3:使用 taskId 续接会话**\n"); sb.append("```\n"); - sb.append("result1 = task(subagent_type='explore', prompt='分析项目')\n"); + sb.append("result1 = task(name='explore', prompt='分析项目')\n"); sb.append("# 返回 task_id: explore_12345\n"); sb.append("result2 = task(\n"); - sb.append(" subagent_type='explore',\n"); + sb.append(" name='explore',\n"); sb.append(" prompt='深入分析 Controller 层',\n"); sb.append(" taskId='explore_12345' # 续接之前的会话\n"); sb.append(")\n"); @@ -279,6 +301,36 @@ public class AgentTeamsSkill extends AbsSkill { sb.append("team_task(\"实现用户登录功能\")\n\n"); sb.append("# 场景5: 查看任务状态\n"); sb.append("team_status()\n\n"); + sb.append("# 场景6: 多专家讨论(简单模式 - 多轮调用)\n"); + sb.append("# 适用于:3位以内专家,需要互相质疑、补充\n"); + sb.append("result1 = task(name=\"security\", prompt=\"评审登录设计\")\n"); + sb.append("result2 = task(name=\"performance\", prompt=\"安全专家建议:\" + result1 + \"\\n请从性能角度回应\")\n"); + sb.append("result3 = task(name=\"arch\", prompt=\"前两位专家讨论如下,请给出折中方案:\" + result1 + result2)\n"); + sb.append("# 汇总三位专家的讨论结果\n\n"); + sb.append("# 场景7: 多专家讨论(复杂模式 - 讨论板)\n"); + sb.append("# 适用于:4位以上专家,或需要长期记录的讨论\n"); + sb.append("# 步骤1: 创建讨论板\n"); + sb.append("board = create_discussion_board(topic=\"评审登录API设计\")\n"); + sb.append("# 返回: boardId = \"discussion-1234567890\"\n\n"); + sb.append("# 步骤2: 让第一位专家发言\n"); + sb.append("result1 = task(\n"); + sb.append(" name=\"security\",\n"); + sb.append(" prompt=\"阅读讨论板:\" + get_discussion_board(boardId) + \"\\n发表你的观点\"\n"); + sb.append(")\n"); + sb.append("append_discussion_board(boardId, \"security\", result1)\n\n"); + sb.append("# 步骤3: 让第二位专家回应\n"); + sb.append("result2 = task(\n"); + sb.append(" name=\"performance\",\n"); + sb.append(" prompt=\"阅读讨论板:\" + get_discussion_board(boardId) + \"\\n请回应或质疑专家A\"\n"); + sb.append(")\n"); + sb.append("append_discussion_board(boardId, \"performance\", result2)\n\n"); + sb.append("# 步骤4: 让更多专家参与...\n"); + sb.append("# 每位专家都能看到完整的讨论记录\n\n"); + sb.append("# 步骤5: 获取完整讨论\n"); + sb.append("full_discussion = get_discussion_board(boardId)\n"); + sb.append("# 汇总所有专家的观点\n\n"); + sb.append("# 场景8: 查看任务状态\n"); + sb.append("team_status()\n\n"); sb.append("# 场景6: 智能记忆存储(推荐使用)\n"); sb.append("# 自动分类存储(系统自动判断存储类型和周期)\n"); sb.append("memory_store(content=\"采用三层架构:Controller-Service-Repository\", key=\"架构决策\")\n"); @@ -1021,7 +1073,7 @@ public class AgentTeamsSkill extends AbsSkill { result.append("\n**使用方法**:\n"); result.append("```bash\n"); - result.append(String.format("task(subagent_type=\"%s\", prompt=\"你的任务描述\")\n", name)); + result.append(String.format("task(name=\"%s\", prompt=\"你的任务描述\")\n", name)); result.append("```\n"); return result.toString(); @@ -1116,7 +1168,7 @@ public class AgentTeamsSkill extends AbsSkill { result.append("# 创建新成员\n"); result.append("teammate(name=\"expert\", role=\"专家\", description=\"...\")\n\n"); result.append("# 调用成员\n"); - result.append("task(subagent_type=\"explore\", prompt=\"任务描述\")\n\n"); + result.append("task(name=\"explore\", prompt=\"任务描述\")\n\n"); result.append("# 查看任务状态\n"); result.append("team_status()\n"); result.append("```\n"); @@ -1702,6 +1754,126 @@ public class AgentTeamsSkill extends AbsSkill { } } + /** + * 创建讨论板(用于复杂的多专家讨论) + * + * 使用 SharedMemory 创建一个讨论板,专家可以查看并追加内容 + */ + @ToolMapping(name = "create_discussion_board", + description = "创建一个讨论板,用于记录多个专家之间的讨论过程。返回讨论板ID。") + public String createDiscussionBoard( + @Param(name = "topic", description = "讨论主题") String topic, + @Param(name = "participants", description = "参与者列表(可选)") String participants) { + try { + if (mainAgent == null) { + return "[WARN] MainAgent 未初始化"; + } + + String boardId = "discussion-" + System.currentTimeMillis(); + StringBuilder boardContent = new StringBuilder(); + + boardContent.append("# 讨论主题: ").append(topic).append("\n\n"); + boardContent.append("## 参与者\n"); + if (participants != null && !participants.isEmpty()) { + boardContent.append(participants).append("\n"); + } else { + boardContent.append("(待补充)\n"); + } + boardContent.append("\n"); + boardContent.append("## 讨论记录\n\n"); + + // 存储到长期记忆(7天) + mainAgent.getSharedMemoryManager().putLongTerm( + boardId, + boardContent.toString(), + 604800 + ); + + LOG.info("讨论板已创建: boardId={}, topic={}", boardId, topic); + + return "[OK] 讨论板已创建\n\n" + + String.format("**讨论板ID**: %s\n", boardId) + + String.format("**主题**: %s\n", topic) + + "\n使用方法:\n" + + "1. 使用 `get_discussion_board(boardId)` 查看当前内容\n" + + "2. 调用专家时传入讨论板内容\n" + + "3. 使用 `append_discussion_board(boardId, content)` 追加专家观点\n"; + + } catch (Exception e) { + LOG.error("创建讨论板失败", e); + return "[ERROR] 创建失败: " + e.getMessage(); + } + } + + /** + * 追加讨论内容 + */ + @ToolMapping(name = "append_discussion_board", + description = "向讨论板追加专家的观点或发言。") + public String appendDiscussionBoard( + @Param(name = "boardId", description = "讨论板ID") String boardId, + @Param(name = "speaker", description = "发言者名称") String speaker, + @Param(name = "content", description = "发言内容") String content) { + try { + if (mainAgent == null) { + return "[WARN] MainAgent 未初始化"; + } + + // 读取现有内容 + String existingContent = mainAgent.getSharedMemoryManager().recallLongTerm(boardId); + if (existingContent == null || existingContent.isEmpty()) { + return "[ERROR] 讨论板不存在或已过期: " + boardId; + } + + // 追加新内容 + StringBuilder newContent = new StringBuilder(existingContent); + newContent.append(String.format("### [%s] %s\n", + speaker, + new java.util.Date().toString())); + newContent.append(content).append("\n\n"); + + // 更新存储 + mainAgent.getSharedMemoryManager().putLongTerm(boardId, newContent.toString(), 604800); + + LOG.info("讨论板已更新: boardId={}, speaker={}", boardId, speaker); + + return "[OK] 发言已追加到讨论板\n\n" + + String.format("**讨论板**: %s\n", boardId) + + String.format("**发言者**: %s\n", speaker) + + String.format("**内容长度**: %d 字符\n", content.length()); + + } catch (Exception e) { + LOG.error("追加讨论内容失败", e); + return "[ERROR] 追加失败: " + e.getMessage(); + } + } + + /** + * 获取讨论板内容 + */ + @ToolMapping(name = "get_discussion_board", + description = "获取讨论板的完整内容(包括所有发言)。") + public String getDiscussionBoard( + @Param(name = "boardId", description = "讨论板ID") String boardId) { + try { + if (mainAgent == null) { + return "[WARN] MainAgent 未初始化"; + } + + String content = mainAgent.getSharedMemoryManager().recallLongTerm(boardId); + + if (content == null || content.isEmpty()) { + return "[ERROR] 讨论板不存在或已过期: " + boardId; + } + + return "[OK] 讨论板内容\n\n" + content; + + } catch (Exception e) { + LOG.error("获取讨论板失败", e); + return "[ERROR] 获取失败: " + e.getMessage(); + } + } + /** * 获取任务详情 */ @@ -2019,7 +2191,7 @@ public class AgentTeamsSkill extends AbsSkill { sb.append("\n**提示**:\n"); sb.append("- 使用 `send_message(targetAgent, message)` 向指定代理发送消息\n"); - sb.append("- 使用 `task(subagent_type, prompt)` 调用代理并获取响应\n"); + sb.append("- 使用 `task(name, prompt)` 调用代理并获取响应\n"); sb.append("- 使用 `teammates()` 查看所有团队成员\n"); return sb.toString(); 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 fbd977a..768398a 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 @@ -182,6 +182,23 @@ public class MainAgent { "3. task(name=\"数论专家\", prompt=\"从数论角度分析100以内的完全平方数\")\n" + "4. 汇总三位专家的分析结果\n" + "```\n\n" + + "**多轮讨论(如果需要专家互相质疑、补充)**:\n" + + "```\n" + + "# 第1轮:专家A首发\n" + + "result1 = task(name=\"专家A\", prompt=\"分析问题\")\n\n" + + "# 第2轮:专家B回应专家A\n" + + "result2 = task(\n" + + " name=\"专家B\",\n" + + " prompt=\"专家A的观点:\" + result1 + \"\\n请从你的角度回应或质疑\"\n" + + ")\n\n" + + "# 第3轮:专家C综合前两位\n" + + "result3 = task(\n" + + " name=\"专家C\",\n" + + " prompt=\"专家A:\" + result1 + \"\\n专家B:\" + result2 + \"\\n请给出综合建议\"\n" + + ")\n\n" + + "# 汇总讨论结果\n" + + "final = \"经过讨论,三位专家达成共识...\"\n" + + "```\n\n" + "#### 模式2:复杂任务编排(任务列表 + 智能路由)\n\n" + "**适用场景**:\n" + "- 多步骤、复杂的工作流\n" + -- Gitee