diff --git a/AGENT_TEAMS.md b/AGENT_TEAMS.md index 2829bce9a355c4cfb1ee76f36b42541f170a9fdc..00b043efdbe658c5eb4270c5ddde95f84903b8c2 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 051eedf77730e7c616103db63db3e4c758a1d7af..bb45218d57be987c79b377bee8b66c188a479720 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; @@ -182,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()); } @@ -208,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() @@ -244,11 +245,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"); + } } @@ -283,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); @@ -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, @@ -314,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/AgentProperties.java b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/AgentProperties.java index 743f178b404dc2c01ceb94dade103f240119eaf5..0f8f007c96b5c473f2f8e90ae0172e2379c56366 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; @@ -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/memory/ActionRecord.java b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/memory/ActionRecord.java deleted file mode 100644 index efd2759eba56d1a41cc5f224e9deb476f756bae3..0000000000000000000000000000000000000000 --- 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 b351a57d013c8793ddc6cfdf96e4a5189325c1f0..3f0896f78253fb184ec7b78090929937dbf54602 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("所有记忆已清空"); } - // ========== 便捷方法(简化键值对接口) ========== /** * 存储短期记忆(便捷方法) @@ -565,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; + } + /** * 存储知识记忆(便捷方法) * @@ -732,7 +741,7 @@ public class SharedMemoryManager { LOG.warn("紧急清理完成,清理了 {} 条短期记忆", cleared); // 建议垃圾回收 - System.gc(); +// System.gc(); } /** @@ -828,42 +837,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 9b82740d605219b6dc236d228e866a8e587ea89e..846a30bde97eb8d90186c712a16b6b96f2dc5f5c 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/consolidator/MemoryConsolidator.java b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/memory/consolidator/MemoryConsolidator.java index 19318825f88b2b3a226681cdcf6c8b2fefd91555..adde04c55c6533e944e98e15331f04ed5ef2ed23 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/memory/smart/IntelligentMemoryManager.java b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/memory/smart/IntelligentMemoryManager.java index cf251dd177b76b129fd1f9cdf64c671880a6ce6d..026000a7b1ba698715c5fa0ea7faaf13ba3eac9d 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 4a5b96b9d6ba8130145cc37fda88a2f650b5f9d7..68351b229d6d85d6f6c0812d532a93c0422b170e 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,19 @@ 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; 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 +38,10 @@ import reactor.core.publisher.Flux; /** * 抽象子代理实现 * + * 增强功能: + * - 元数据验证 + * - 元数据继承 + * * @author bai * @since 3.9.5 */ @@ -42,14 +51,32 @@ 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; protected SubAgentMetadata metadata; 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.defaultInterceptorAdd(mainAgent.getSummarizationInterceptor()); + // 应用元数据中的属性配置到builder + applyMetadataToBuilder(builder, metadata); + // 应用自定义配置 + customize(builder); + cachedAgent = builder.build(); } @Override @@ -63,37 +90,31 @@ 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() + .description(getDefaultDescription()) // 设置描述 + .build(); } - public final void setSystemPrompt(String systemPrompt) { - this.systemPrompt = systemPrompt; - } /** - * 获取内置系描述(由子类实现) + * 获取内置描述(由子类实现) */ protected abstract String getDefaultDescription(); @@ -102,14 +123,52 @@ 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; + } + 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()); + LOG.debug("maxStepsAutoExtensible: {}", metadata.getMaxStepsAutoExtensible()); + } else { + // 默认启用步数自动扩展 + builder.maxStepsExtensible(true); + LOG.debug("使用默认 maxStepsAutoExtensible: true"); + } + } + - private volatile ReActAgent cachedAgent; protected void refresh() { Utils.locker().lock(); @@ -120,37 +179,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 +196,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/AgentStreamOutput.java b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/AgentStreamOutput.java new file mode 100644 index 0000000000000000000000000000000000000000..285a0e03bc41608d862d1cee8741140c83a5b364 --- /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/BashSubagent.java b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/BashSubagent.java index f7cea3c1f1444dce0d097eaaadfda165e1f94ff0..3062a0c309690a53e64bae2564e09ae05ad55d8e 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,7 @@ public class BashSubagent extends AbsSubagent { super(mainAgent); } + /** * 初始化 Bash 代理 */ @@ -40,31 +42,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 105e5f3501577ddac3179e65611da9723a2af549..d61eecbbc0d2b54a67e2e553662e5dd106d92cb1 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,7 @@ public class ExploreSubagent extends AbsSubagent { super(mainAgent); } + @Override protected void customize(ReActAgent.Builder builder) { // 添加技能(仅终端和专家技能,不添加代码搜索) @@ -49,43 +50,14 @@ 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 4c90894ead8b4b9de0914c22d73e8c21581488f0..46e9d1617f6b067b4d539db6100d9abcee156b4b 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; @@ -34,60 +33,29 @@ 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); - } + String instruction; - public GeneralPurposeSubagent(AgentKernel mainAgent, String subagentType) { + public GeneralPurposeSubagent(AgentKernel mainAgent) { super(mainAgent); - - if (Assert.isEmpty(subagentType)) { - this.subagentType = "general-purpose"; - } else { - this.subagentType = subagentType; - } - - // 初始化默认 metadata - this.metadata = createDefaultMetadata(); } - /** - * 创建默认的 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; + public GeneralPurposeSubagent(AgentKernel mainAgent, SubAgentMetadata metadata) { + super(mainAgent, metadata); } - /** - * 设置 metadata(用于从文件加载) - */ - @Override - public void setMetadata(SubAgentMetadata metadata) { - this.metadata = metadata; + public GeneralPurposeSubagent(AgentKernel mainAgent, SubAgentMetadata metadata, String instruction) { + super(mainAgent, metadata); + this.instruction = instruction; } - /** - * 获取 metadata(返回维护的字段) - */ @Override - public SubAgentMetadata getMetadata() { - return metadata; + protected void applyMetadataToBuilder(ReActAgent.Builder builder, SubAgentMetadata metadata) { + super.applyMetadataToBuilder(builder, metadata); + builder.instruction(this.instruction); } + @Override protected void customize(ReActAgent.Builder builder) { // 添加所有核心技能 @@ -100,23 +68,8 @@ public class GeneralPurposeSubagent extends AbsSubagent { builder.defaultToolAdd(WebsearchTool.getInstance()); builder.defaultToolAdd(CodeSearchTool.getInstance()); - builder.defaultInterceptorAdd(mainAgent.getSummarizationInterceptor()); - - // 如果主 CodeAgent 有代码搜索能力,也可以添加 - // 这里可以根据需要动态添加工具 - - // 设置最大步数(通用任务可能需要更多步数) - builder.maxSteps(25); - builder.maxStepsExtensible(true); - - // 设置会话窗口大小 - builder.sessionWindowSize(5); } - @Override - public String getType() { - return this.subagentType; - } @Override protected String getDefaultDescription() { 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 37c61ad0474a8b26096e510fee418d345c4d4a1e..99a8be1762181fa405ef7965c69ae26f90abbf2e 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,7 @@ public class PlanSubagent extends AbsSubagent { super(mainAgent); } + @Override protected void customize(ReActAgent.Builder builder) { // 计划代理主要依赖推理能力,不需要太多工具 @@ -48,39 +53,11 @@ 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 384fdc3c50d816bb84f1c41c00e8c90981c480b2..16da0e7a0e2cb0930060e8ef8fcfc68e7d0588de 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; @@ -156,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*"))); @@ -224,6 +263,7 @@ public class SubAgentMetadata { /** * 提示词和元数据的组合 */ + @Getter public static class PromptWithMetadata { private final SubAgentMetadata metadata; private final String prompt; @@ -233,13 +273,6 @@ public class SubAgentMetadata { this.prompt = prompt; } - public SubAgentMetadata getMetadata() { - return metadata; - } - - public String getPrompt() { - return prompt; - } } /** @@ -278,11 +311,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"); @@ -321,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/Subagent.java b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/subagent/Subagent.java index 5919196dc51125aa0a44c346c7be7686258652d4..d114df2f2e4b79904e05d9deaebbd2adc7c4ba14 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 cbfe39c159bf03b8955620863863a8a25f5fdba3..92ded64254a9a4373ae2f2770bc7f8da4d135403 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,16 +163,10 @@ public class SubagentManager { } AbsSubagent subagent = (AbsSubagent) subagentMap.computeIfAbsent(subagentType, - k -> new GeneralPurposeSubagent(mainAgent, k)); + k -> new GeneralPurposeSubagent(mainAgent, parsed.getMetadata(), parsed.getPrompt())); // 设置解析后的属性 subagent.setDescription(parsed.getMetadata().getDescription()); - subagent.setSystemPrompt(parsed.getPrompt()); - - // 设置完整的 metadata(包括 teamName 等字段) - subagent.setMetadata(parsed.getMetadata()); - - subagent.refresh(); 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 d97063ebf8519e204242b824f49918ac5e278e3a..47b159c130f991481694ec62ea2aab17265b40d5 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,34 +15,32 @@ */ 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.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.time.Duration; +import java.util.List; 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,12 +56,7 @@ 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 AgentKernel agentKernel; private final SubagentManager manager; // 并发控制:使用 Semaphore 限制同时发起的子代理请求数(从配置读取) @@ -72,78 +65,78 @@ 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 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("### 核心规则(强制执行)\n\n"); + sb.append("#### 禁止行为\n"); sb.append("1. **禁止模拟工作**:严禁不断更新状态而无实际产出\n"); - sb.append("2. **必须有实际产出**:代码任务必须生成文件,使用 ls/read 验证\n\n"); + sb.append("2. **禁止自己代替子代理完成任务**:必须调用真实的子代理\n\n"); - sb.append("#### ✅ 必须行为\n"); - sb.append("1. **强制使用 task() 工具**:所有实际工作必须通过 task() 完成\n\n"); + sb.append("#### 必须行为\n"); + sb.append("1. **强制使用 task() 工具**:所有实际工作必须通过 task() 完成\n"); + sb.append("2. **必须有实际产出**:代码任务必须生成文件,使用 ls/read 验证\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"); sb.append("### 调用约定\n"); sb.append("- **上下文对齐**: 子代理看不见当前历史,必须在 prompt 中传入必要的上下文\n"); - sb.append("- **示例**: `task(subagentType=\"explore\", prompt=\"分析项目架构\")`\n"); + sb.append("- **示例**: `task(name=\"explore\", prompt=\"分析项目架构\")`\n"); return sb.toString(); } @ToolMapping(name = "task", - description = "【强制使用】派生并分派任务给专项子代理。所有实际开发工作必须使用此工具委派给子代理完成。") + description = "派生并分派任务给专项子代理。所有实际开发工作必须使用此工具委派给子代理完成。") public String 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, String __cwd, String __sessionId ) { - AgentSession __parentSession = mainAgent.getSession(__sessionId); + AgentSession __parentSession = agentKernel.getSession(__sessionId); ReActTrace __parentTrace = ReActTrace.getCurrent(__parentSession.getSnapshot()); try { - Subagent agent = manager.getAgent(subagentType); + Subagent agent = manager.getAgent(name); if (agent == null) { - return "ERROR: 未知的子代理类型 '" + subagentType + "'。"; + return "ERROR: 未知的子代理类型 '" + name + "'。"; } String finalSessionId = Assert.isEmpty(taskId) - ? "subagent_" + subagentType + "_" + System.currentTimeMillis() + ? "subagent_" + name : taskId; - LOG.info("分派任务 -> 类型: {}, 会话: {}, 描述: {}", subagentType, finalSessionId, description); + LOG.info("分派任务 -> 类型: {}, 会话: {}, 描述: {}", name, finalSessionId, description); - // ==================== 并发控制 ==================== - if (!acquirePermit(subagentType)) { + if (!acquirePermit(name)) { return "ERROR: 等待子代理执行许可超时。当前可能有太多子代理在执行。"; } @@ -157,28 +150,28 @@ public class TaskSkill extends AbsSkill { __parentTrace.getMetrics().addMetrics(response.getMetrics()); } else { // 流式模式 - result = executeStream(agent, __cwd, finalSessionId, Prompt.of(prompt), - __parentTrace, subagentType); + result = AgentStreamOutput.executeStream(agent, __cwd, finalSessionId, Prompt.of(prompt), + __parentTrace, name); } LOG.info("子代理任务完成: {}", finalSessionId); return String.format( "task_id: %s\n" + - "subagentType: %s\n" + - "\n" + - "\n" + - "%s\n" + - "", - finalSessionId, subagentType, result != null ? result : "(无输出)" + "name: %s\n" + + "\n" + + "\n" + + "%s\n" + + "", + finalSessionId, name, result != null ? result : "(无输出)" ); } finally { - releasePermit(subagentType); + releasePermit(name); } } catch (Throwable e) { - LOG.error("子代理执行崩溃: type={}, error={}", subagentType, e.getMessage(), e); + LOG.error("子代理执行崩溃: type={}, error={}", name, e.getMessage(), e); return "ERROR: 子代理执行失败: " + e.getMessage(); } } @@ -190,7 +183,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); @@ -203,7 +196,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) { @@ -228,85 +221,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 = "动态创建一个新的子代理。") @@ -324,7 +238,6 @@ public class TaskSkill extends AbsSkill { ) { try { SubAgentMetadata metadata = new SubAgentMetadata(); - metadata.setCode(code); metadata.setName(name); metadata.setDescription(description); metadata.setEnabled(true); @@ -361,9 +274,8 @@ public class TaskSkill extends AbsSkill { LOG.info("Agent 定义已保存到: {}", agentFile); } - AbsSubagent newAgent = new GeneralPurposeSubagent(mainAgent, code); + GeneralPurposeSubagent newAgent = new GeneralPurposeSubagent(agentKernel, metadata, agentDefinition); newAgent.setDescription(description); - newAgent.setSystemPrompt(agentDefinition); newAgent.refresh(); manager.addSubagent(newAgent); @@ -371,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 3288f9a9273a8f9ee38db223a7aba7d8f3079774..c78848fcfa6d976a4ffb6b445d3b0d5e8b6a7053 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,31 +15,40 @@ */ 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; 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.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; +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.concurrent.TimeUnit; import java.util.stream.Collectors; /** @@ -75,12 +84,7 @@ 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); + this.intelligentMemoryManager = new IntelligentMemoryManager(Paths.get(mainAgent.getWorkDir(), AgentKernel.SOLONCODE_MEMORY)); } else { this.intelligentMemoryManager = null; } @@ -89,54 +93,63 @@ 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("### ⚠️ 核心规则(强制执行)\n\n"); - sb.append("#### 🚫 禁止行为(绝对不可违反)\n"); - sb.append("1. **禁止模拟工作**:\n"); - sb.append(" - 严禁不断更新 `update_working_memory`、`step`、`currentAgent` 而无实际产出\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"); + 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("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(" - 不断更新工作记忆但无子代理实际执行是**严重违规**\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、solon-code-guide\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(" - 超过 5,000 tokens 无产出时必须改变策略\n"); - sb.append(" - 禁止无限循环调用工具\n\n"); - - sb.append("### 工作流程\n" + - ". 分析任务:识别所需的专业领域。\n" + - ". 组建团队:自动激活相关领域的专家 Agent。\n" + - ". 引导讨论:\n" + - " - 让专家轮流发表观点。\n" + - " - 鼓励专家互相质疑(例如:安全专家挑战开发专家的架构)。\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"); + 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"); @@ -152,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"); @@ -167,25 +186,23 @@ 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"); @@ -193,7 +210,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"); @@ -203,7 +219,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"); @@ -229,39 +244,40 @@ 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("```"); + 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("```"); - sb.append("result1 = task(subagent_type='plan', prompt='设计...')\n"); + sb.append("```\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("```"); - sb.append("result1 = task(subagent_type='explore', prompt='分析项目')\n"); + sb.append("```\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"); 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"); @@ -285,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"); @@ -313,7 +359,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"); @@ -343,106 +392,92 @@ 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, + @Param(name = "taskId", required = false, description = "可选。若要继续之前的任务会话,请传入对应的 task_id") String taskId, String __cwd, String __sessionId ) { - try { - if (mainAgent.isRunning()) { - return "[WARN] 团队任务正在执行中,请等待当前任务完成。"; - } + // 检查是否已有任务在执行 + if (mainAgent.isRunning()) { + return Mono.just("[WARN] 团队任务正在执行中,请等待当前任务完成。\n"); + } - LOG.info("启动团队协作任务: {}", prompt); + AgentSession __parentSession = kernel.getSession(__sessionId); + ReActTrace __parentTrace = ReActTrace.getCurrent(__parentSession.getSnapshot()); - // 使用流式执行(实时输出) - StringBuilder streamingOutput = new StringBuilder(); + String finalSessionId = Assert.isEmpty(taskId) + ? "team_task_" + __sessionId + : taskId; - 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(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] 团队任务执行被中断"; - } catch (Exception e) { - LOG.error("团队任务执行失败", e); - return "[ERROR] 团队任务执行失败: " + e.getMessage(); - } - - // 获取任务统计 - SharedTaskList.TaskStatistics stats = mainAgent.getTaskList().getStatistics(); + LOG.info("启动团队协作任务: {}", prompt); - 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...(内容过长,已截断)"; - } - result.append(agentResponse); - } else { - result.append("(流式输出已完成,请查看上方控制台输出)"); - } + // 🔑 关键:使用 Mono.create 避免阻塞主 Flux + return Mono.create(emitter -> { + // 在单独的线程中执行 MainAgent + StringBuilder finalResult = new StringBuilder(); - return result.toString(); + 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()); + } - } catch (Throwable e) { - LOG.error("团队任务执行失败", e); - return "[ERROR] 团队任务执行失败: " + e.getMessage(); - } + } catch (Exception e) { + LOG.error("团队任务执行失败", e); + emitter.error(new RuntimeException("团队任务执行失败: " + e.getMessage(), e)); + } + }); } /** * 查看团队任务状态 */ @ToolMapping(name = "team_status", - description = "查看当前团队任务状态,包括任务列表、进度统计等") + description = "查看团队任务状态。返回任务统计和当前任务列表。") public String teamStatus() { try { SharedTaskList taskList = mainAgent.getTaskList(); @@ -514,7 +549,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 +613,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,39 +696,42 @@ 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) { + @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 分析 - org.noear.solon.ai.chat.prompt.Prompt llmPrompt = - org.noear.solon.ai.chat.prompt.Prompt.of(prompt); - - // 通过 kernel 获取 ChatModel - if (kernel == null) { - return "[WARN] Kernel 未初始化"; - } - - org.noear.solon.ai.chat.ChatModel chatModel = kernel.getChatModel(); - if (chatModel == null) { - return "[WARN] ChatModel 未初始化"; + 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); } } @@ -722,25 +836,13 @@ 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 = taskNode.toBean(TeamTask.class); if (task != null) { tasks.add(task); } @@ -752,93 +854,6 @@ public class AgentTeamsSkill extends AbsSkill { return tasks; } - /** - * 解析单个任务 JSON - */ - private TeamTask parseSingleTaskJson(String json, int index) { - try { - String title = extractJsonValue(json, "title"); - String description = extractJsonValue(json, "description"); - String typeStr = extractJsonValue(json, "type"); - String priorityStr = extractJsonValue(json, "priority"); - - if (title == null || title.isEmpty()) { - title = "任务 " + (index + 1); - } - if (description == null || description.isEmpty()) { - description = title; - } - - TeamTask.TaskType type = TeamTask.TaskType.DEVELOPMENT; - if (typeStr != null) { - try { - type = TeamTask.TaskType.valueOf(typeStr.toUpperCase()); - } catch (IllegalArgumentException e) { - // 使用默认类型 - } - } - - int priority = 7; - if (priorityStr != null) { - try { - priority = Integer.parseInt(priorityStr.trim()); - priority = Math.max(1, Math.min(10, priority)); - } catch (NumberFormatException e) { - // 使用默认优先级 - } - } - - String taskId = "task-" + System.currentTimeMillis() + "-" + index; - return TeamTask.builder() - .id(taskId) - .title(cleanJsonString(title)) - .description(cleanJsonString(description)) - .type(type) - .priority(priority) - .dependencies(new ArrayList<>()) - .build(); - - } catch (Exception e) { - LOG.warn("解析单个任务失败: {}, error: {}", json, e.getMessage()); - return null; - } - } - - /** - * 从 JSON 提取字段值 - */ - private String extractJsonValue(String json, String key) { - String searchKey = "\"" + key + "\""; - int keyIndex = json.indexOf(searchKey); - if (keyIndex < 0) { - searchKey = "'" + key + "'"; - keyIndex = json.indexOf(searchKey); - } - if (keyIndex < 0) return null; - - int colonIndex = json.indexOf(":", keyIndex); - if (colonIndex < 0) return null; - - int valueStart = json.indexOf("\"", colonIndex); - if (valueStart < 0) return null; - - valueStart++; - int valueEnd = json.indexOf("\"", valueStart); - if (valueEnd < 0) return null; - - return json.substring(valueStart, valueEnd); - } - - /** - * 清理 JSON 字符串 - */ - private String cleanJsonString(String str) { - if (str == null) return ""; - return str.replace("\\\"", "\"") - .replace("\\n", "\n") - .replace("\\t", "\t") - .trim(); - } /** * 获取状态图标 @@ -854,6 +869,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 +907,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,28 +954,16 @@ 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); - metadata.setName(role); + metadata.setName(name); metadata.setDescription(description); 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 +982,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 +1018,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 +1040,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,11 +1069,11 @@ 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"); - 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(); @@ -1134,19 +1142,18 @@ public class AgentTeamsSkill extends AbsSkill { } // 表格格式的成员列表 - result.append("| 名称 | 角色 | 描述 | 团队 | 状态 | 模型 |\n"); + result.append("| 名称 | 角色 | 描述 | 团队 | 模型 |\n"); 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() : "-"; - 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()) { @@ -1161,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"); @@ -1223,28 +1230,17 @@ 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 +1260,6 @@ public class AgentTeamsSkill extends AbsSkill { return text.substring(0, maxLength - 3) + "..."; } - // ==================== 记忆管理工具(智能接口)==================== /** * 工具 1:智能存储记忆 @@ -1350,46 +1345,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(); + } } /** @@ -1547,7 +1560,6 @@ public class AgentTeamsSkill extends AbsSkill { } } - // ==================== 任务管理工具 ==================== /** * 认领任务 @@ -1587,7 +1599,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) { @@ -1603,6 +1615,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(); } @@ -1735,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(); + } + } + /** * 获取任务详情 */ @@ -1970,22 +2109,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(); - } - } - - // ==================== 代理间通信工具 ==================== /** * 发送消息给其他代理 @@ -2057,19 +2180,18 @@ 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())); } } 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(); @@ -2260,7 +2382,181 @@ 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()); } + + + /** + * 执行团队任务(同步) + * + * 根据已保存的团队配置,动态构建 TeamAgent 并执行任务 + * + * @param teamName 团队名称 + * @param task 任务描述 + * @return 同步执行结果(String),包含完整的团队协作输出 + */ + @ToolMapping(name = "run_team_task", + description = "简易版本,使用指定的团队执行任务。根据团队配置自动协调多个代理协作完成任务。返回 String 同步结果。") + public String runTeamTask( + @Param(name = "teamName", description = "团队名称(使用 teammates 查看)") String teamName, + @Param(name = "task", description = "任务描述") String task) { + + try { + String description = ""; + String protocol = "sequential"; + List validAgents = new ArrayList<>(); + + // 1. 尝试从配置文件读取 + String teamsDir = mainAgent.getWorkDir() + File.separator + ".soloncode" + File.separator + "agentsTeams"; + File configFile = new File(teamsDir + File.separator + teamName + ".md"); + + if (configFile.exists()) { + // 从配置文件解析 + String content = new String(Files.readAllBytes(configFile.toPath()), StandardCharsets.UTF_8); + 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); + } + } + } + } + + // 验证代理是否可用 + 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 中注册。"; + } + + 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); + } else { + teamBuilder.protocol(TeamProtocols.SEQUENTIAL); + } + + // 7. 配置运行参数 + teamBuilder.maxTurns(15); // 最多协作 15 轮 + + // 8. 构建并执行 + TeamAgent teamAgent = teamBuilder.build(); + + LOG.info("启动团队任务: team={}, members={}, protocol={}", + teamName, validAgents.size(), protocol); + + // 9. 同步执行任务(收集所有输出) + StringBuilder result = new StringBuilder(); + + // 添加头部信息 + result.append(String.format( + "[OK] 团队任务执行\n\n" + + "**团队**: %s\n" + + "**协议**: %s\n" + + "**参与成员**: %d\n" + + "\n--- 执行结果 ---\n\n", + teamName, protocol, validAgents.size())); + + // 执行并收集流式输出 + 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); + result.append("\n[ERROR] 团队任务执行异常: ").append(e.getMessage()); + } + + 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/AgentTeamsTools.java b/solonbot-sdk/src/main/java/org/noear/solon/bot/core/teams/AgentTeamsTools.java index 8b77bb87df426c0b62bd31f894a1607c57011610..a66194ed7199e722196da91065e789f576ee9ffd 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 a776f003bfc5197cd9452b6232e010de28ead4d5..768398a113adb3d854239b5b9fd6a79229ee060d 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 @@ -21,12 +21,13 @@ 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; -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; @@ -39,12 +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.noear.solon.bot.core.subagent.TaskSkill; 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; @@ -97,6 +100,10 @@ public class MainAgent { // 性能优化:使用 CountDownLatch 替代轮询 private volatile CountDownLatch taskCompletionLatch; + private static final long SUBAGENT_STREAM_TIMEOUT_MS = 180_000; + + + public MainAgent(SubAgentMetadata config, AgentSessionProvider sessionProvider, SharedMemoryManager sharedMemoryManager, @@ -141,55 +148,142 @@ 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" + + "#### 模式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" + + "**多轮讨论(如果需要专家互相质疑、补充)**:\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" + + "- 需要任务之间的依赖和协调\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" + + "**子代理调用**(模式1使用):\n" + + "- `task(name, prompt)` - 直接调用特定子代理执行任务\n" + + "- `teammate_quick(name, role)` - 快速创建团队成员\n" + + "- `teammates()` - 列出所有团队成员\n\n" + + "**任务管理**(模式2使用):\n" + + "- `task_add(title)` - 快速添加任务\n" + + "- `tasks_add(titles)` - 批量添加任务\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\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" + + "- 记忆重要决策,方便后续查阅"; + } - 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 +293,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 +338,7 @@ public class MainAgent { } try { - // 1. 发布主代理任务开始事件 + // 2. 发布主代理任务开始事件 publishEvent(AgentEventType.MAIN_TASK_STARTED, prompt.getUserContent(), null); // 2. 执行主代理内部的协调逻辑(流式输出) @@ -234,7 +354,17 @@ public class MainAgent { .stream(); // 7. 在流完成后等待所有子任务完成 - Flux resultStream = responseStream + // 等待所有子任务完成 + // 汇总结果 + // 更新主任务状态 + // 发布主代理任务完成事件 + // 停止目标守护 + // 更新主任务状态为失败 + // 出错时也要停止目标守护 + // 更新主任务状态为取消 + // 取消时也要停止目标守护 + + return responseStream .doOnComplete(() -> { try { // 等待所有子任务完成 @@ -243,6 +373,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); @@ -251,6 +391,9 @@ public class MainAgent { } catch (Exception e) { LOG.error("MainAgent 后处理失败", e); } finally { + // 设置运行状态为 false + running.set(false); + // 停止目标守护 try { this.stopGoalGuarding(); @@ -262,6 +405,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 +427,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 +448,6 @@ public class MainAgent { } }); - return resultStream; - } finally { running.set(false); // 确保目标守护被停止(即使发生异常) @@ -308,7 +471,7 @@ public class MainAgent { payload.put("tasks", tasks); AgentMessage> message = AgentMessage.>of(payload) - .from(config.getCode()) + .from(config.getName()) .to("*") .type("task_notification") .build(); @@ -581,7 +744,7 @@ public class MainAgent { private void registerMessageHandler() { if (messageChannel != null) { messageHandlerId = messageChannel.registerHandler( - config.getCode(), + config.getName(), this::handleMessage ); LOG.info("MainAgent 消息处理器已注册"); @@ -641,7 +804,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(); @@ -651,133 +814,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"; - } /** * 检查是否正在运行 @@ -805,7 +841,7 @@ public class MainAgent { // 注销消息处理器 if (messageChannel != null && messageHandlerId != null) { - messageChannel.unregisterHandler(config.getCode(), messageHandlerId); + messageChannel.unregisterHandler(config.getName(), messageHandlerId); LOG.info("MainAgent 消息处理器已注销"); } } @@ -832,6 +868,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); + } + } + /** * 获取当前目标 * 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 a761d682b3c8826f5044cabf81d866e06da8f96f..c3ee03de069ec2789249121ab2d25b928f8c567f 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; @@ -88,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; } @@ -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); } @@ -239,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);