feat(multi_agent): 提取prompt、恢复任务、优化侧边栏与sleep工具
- 将多智能体/子智能体prompt从代码提取到prompts/multi_agent/和prompts/sub_agent/ - 多智能体模式添加可用的子智能体动态prompt并冻结 - 重启后自动从conversation.json恢复多智能体idle任务 - 修复新对话侧边栏显示其他对话子智能体的问题 - 简化子智能体弹窗输出样式 - sleep工具在多智能体模式下移除wait_sub_agent_ids参数
This commit is contained in:
parent
de5160ead0
commit
fe6fba3958
@ -48,6 +48,7 @@ from modules.terminal_manager import TerminalManager
|
|||||||
from modules.todo_manager import TodoManager
|
from modules.todo_manager import TodoManager
|
||||||
from modules.sub_agent import SubAgentManager
|
from modules.sub_agent import SubAgentManager
|
||||||
from modules.webpage_extractor import extract_webpage_content, tavily_extract
|
from modules.webpage_extractor import extract_webpage_content, tavily_extract
|
||||||
|
from modules.multi_agent.prompts import build_available_agents_prompt
|
||||||
from modules.ocr_client import OCRClient
|
from modules.ocr_client import OCRClient
|
||||||
from modules.easter_egg_manager import EasterEggManager
|
from modules.easter_egg_manager import EasterEggManager
|
||||||
from modules.personalization_manager import (
|
from modules.personalization_manager import (
|
||||||
@ -288,6 +289,17 @@ class MessagesMixin:
|
|||||||
messages.append({"role": "system", "content": multi_agent_prompt})
|
messages.append({"role": "system", "content": multi_agent_prompt})
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.warning(f"[messages] 注入多智能体 prompt 失败: {exc}")
|
logger.warning(f"[messages] 注入多智能体 prompt 失败: {exc}")
|
||||||
|
|
||||||
|
# 可用的子智能体角色(动态 prompt,第一个用户消息后冻结)
|
||||||
|
try:
|
||||||
|
available_agents_prompt = self._get_or_init_frozen_prompt(
|
||||||
|
"frozen_available_agents_prompt",
|
||||||
|
lambda: build_available_agents_prompt() or "",
|
||||||
|
)
|
||||||
|
if available_agents_prompt:
|
||||||
|
messages.append({"role": "system", "content": available_agents_prompt})
|
||||||
|
except Exception as exc:
|
||||||
|
logger.warning(f"[messages] 注入可用子智能体 prompt 失败: {exc}")
|
||||||
if disabled_notice:
|
if disabled_notice:
|
||||||
messages.append({
|
messages.append({
|
||||||
"role": "system",
|
"role": "system",
|
||||||
|
|||||||
@ -90,38 +90,74 @@ DISABLE_LENGTH_CHECK = True
|
|||||||
|
|
||||||
|
|
||||||
class ToolsDefinitionCoreToolsMixin:
|
class ToolsDefinitionCoreToolsMixin:
|
||||||
|
|
||||||
|
def _build_sleep_tool_definition(self) -> Dict:
|
||||||
|
"""根据运行模式构建 sleep 工具定义。
|
||||||
|
|
||||||
|
多智能体模式下子智能体不会调用 finish_task 结束,因此不提供
|
||||||
|
wait_sub_agent_ids 参数;常规模式保留完整功能。
|
||||||
|
"""
|
||||||
|
is_multi_agent = getattr(self, "multi_agent_mode", False)
|
||||||
|
if is_multi_agent:
|
||||||
|
return {
|
||||||
|
"type": "function",
|
||||||
|
"function": {
|
||||||
|
"name": "sleep",
|
||||||
|
"description": "等待工具。两种模式二选一:1) seconds:短暂延迟;2) wait_runcommand_id:等待指定后台 run_command 结束并直接返回结果。若同时提供多个等待参数会报错。",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": self._inject_intent({
|
||||||
|
"seconds": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "等待的秒数,可以是小数(如0.2秒)。建议范围:0.1-10秒"
|
||||||
|
},
|
||||||
|
"wait_runcommand_id": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "等待指定后台 run_command 的 command_id 结束后返回。"
|
||||||
|
},
|
||||||
|
"reason": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "等待的原因说明(可选)"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
"required": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
"type": "function",
|
||||||
|
"function": {
|
||||||
|
"name": "sleep",
|
||||||
|
"description": "等待工具。三种模式三选一:1) seconds:短暂延迟;2) wait_sub_agent_ids:等待指定子智能体全部结束并直接返回结果;3) wait_runcommand_id:等待指定后台 run_command 结束并直接返回结果。若同时提供多个等待参数会报错。",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": self._inject_intent({
|
||||||
|
"seconds": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "等待的秒数,可以是小数(如0.2秒)。建议范围:0.1-10秒"
|
||||||
|
},
|
||||||
|
"wait_sub_agent_ids": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {"type": "integer"},
|
||||||
|
"description": "等待这些子智能体全部结束后返回(可提供一个或多个编号)。"
|
||||||
|
},
|
||||||
|
"wait_runcommand_id": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "等待指定后台 run_command 的 command_id 结束后返回。"
|
||||||
|
},
|
||||||
|
"reason": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "等待的原因说明(可选)"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
"required": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def _build_core_tools(self) -> List[Dict]:
|
def _build_core_tools(self) -> List[Dict]:
|
||||||
return [
|
return [
|
||||||
{
|
self._build_sleep_tool_definition(),
|
||||||
"type": "function",
|
|
||||||
"function": {
|
|
||||||
"name": "sleep",
|
|
||||||
"description": "等待工具。三种模式三选一:1) seconds:短暂延迟;2) wait_sub_agent_ids:等待指定子智能体全部结束并直接返回结果;3) wait_runcommand_id:等待指定后台 run_command 结束并直接返回结果。若同时提供多个等待参数会报错。",
|
|
||||||
"parameters": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": self._inject_intent({
|
|
||||||
"seconds": {
|
|
||||||
"type": "number",
|
|
||||||
"description": "等待的秒数,可以是小数(如0.2秒)。建议范围:0.1-10秒"
|
|
||||||
},
|
|
||||||
"wait_sub_agent_ids": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {"type": "integer"},
|
|
||||||
"description": "等待这些子智能体全部结束后返回(可提供一个或多个编号)。"
|
|
||||||
},
|
|
||||||
"wait_runcommand_id": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "等待指定后台 run_command 的 command_id 结束后返回。"
|
|
||||||
},
|
|
||||||
"reason": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "等待的原因说明(可选)"
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
"required": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
{
|
||||||
"type": "function",
|
"type": "function",
|
||||||
|
|||||||
@ -1099,7 +1099,12 @@ class MainTerminalToolsExecutionMixin:
|
|||||||
"error": "sleep 的等待参数互斥:seconds / wait_sub_agent_ids / wait_runcommand_id 只能提供一个"
|
"error": "sleep 的等待参数互斥:seconds / wait_sub_agent_ids / wait_runcommand_id 只能提供一个"
|
||||||
}
|
}
|
||||||
elif wait_sub_agent_ids:
|
elif wait_sub_agent_ids:
|
||||||
if not isinstance(wait_sub_agent_ids, list) or not wait_sub_agent_ids:
|
if getattr(self, "multi_agent_mode", False):
|
||||||
|
result = {
|
||||||
|
"success": False,
|
||||||
|
"error": "多智能体模式下 sleep 工具不支持 wait_sub_agent_ids。请通过 list_active_sub_agents / get_sub_agent_status 查看状态,或直接向子智能体发送消息。"
|
||||||
|
}
|
||||||
|
elif not isinstance(wait_sub_agent_ids, list) or not wait_sub_agent_ids:
|
||||||
result = {"success": False, "error": "wait_sub_agent_ids 必须是非空数组"}
|
result = {"success": False, "error": "wait_sub_agent_ids 必须是非空数组"}
|
||||||
else:
|
else:
|
||||||
normalized_ids = []
|
normalized_ids = []
|
||||||
@ -1129,27 +1134,6 @@ class MainTerminalToolsExecutionMixin:
|
|||||||
task_ids.append(task.get("task_id"))
|
task_ids.append(task.get("task_id"))
|
||||||
if missing:
|
if missing:
|
||||||
result = {"success": False, "error": f"未找到对应子智能体: {missing}"}
|
result = {"success": False, "error": f"未找到对应子智能体: {missing}"}
|
||||||
elif getattr(self, "multi_agent_mode", False):
|
|
||||||
# 多智能体模式:子智能体通过自然输出结束,不调用 finish_task。
|
|
||||||
# sleep 不再阻塞等待,而是直接返回各实例最近一次输出内容。
|
|
||||||
conv_id = self.context_manager.current_conversation_id
|
|
||||||
ma_state = manager.get_multi_agent_state(conv_id)
|
|
||||||
outputs = []
|
|
||||||
for aid in normalized_ids:
|
|
||||||
inst = ma_state.get_instance(aid) if ma_state else None
|
|
||||||
outputs.append({
|
|
||||||
"agent_id": aid,
|
|
||||||
"display_name": inst.display_name if inst else f"Agent_{aid}",
|
|
||||||
"status": inst.status if inst else "unknown",
|
|
||||||
"last_output": inst.last_output if inst else "",
|
|
||||||
})
|
|
||||||
result = {
|
|
||||||
"success": True,
|
|
||||||
"mode": "wait_sub_agent_ids",
|
|
||||||
"agent_ids": normalized_ids,
|
|
||||||
"outputs": outputs,
|
|
||||||
"message": f"已获取 {len(normalized_ids)} 个子智能体最近一次输出"
|
|
||||||
}
|
|
||||||
else:
|
else:
|
||||||
wait_results = []
|
wait_results = []
|
||||||
waited_task_ids = []
|
waited_task_ids = []
|
||||||
|
|||||||
@ -1,134 +1,55 @@
|
|||||||
"""多智能体模式的系统提示词。"""
|
"""多智能体模式的系统提示词。
|
||||||
|
|
||||||
|
所有 prompt 正文均从 prompts/multi_agent/ 下的文本文件加载,避免在代码中硬编码。
|
||||||
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Optional
|
from pathlib import Path
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
try:
|
||||||
|
from config import PROMPTS_DIR
|
||||||
|
except ImportError:
|
||||||
|
import sys
|
||||||
|
|
||||||
|
project_root = Path(__file__).resolve().parents[2]
|
||||||
|
if str(project_root) not in sys.path:
|
||||||
|
sys.path.insert(0, str(project_root))
|
||||||
|
from config import PROMPTS_DIR
|
||||||
|
|
||||||
|
|
||||||
MULTI_AGENT_MASTER_PROMPT_BODY = """# 多智能体模式
|
_MULTI_AGENT_PROMPTS_DIR = Path(PROMPTS_DIR) / "multi_agent"
|
||||||
|
_TEMPLATE_CACHE: dict[str, str] = {}
|
||||||
你是 **Team Leader**(团队领导者),负责协调多个子智能体分工协作完成用户的复杂任务。
|
|
||||||
|
|
||||||
## 工作原则
|
|
||||||
|
|
||||||
- **主动分工**:除非任务极其简单或明确不需要子智能体,否则主动把任务拆解并指派给合适的角色。
|
|
||||||
- **明确指令**:用 `send_message_to_sub_agent` 发任务时,写清楚任务目标、范围、产出要求。
|
|
||||||
- **及时回答**:当子智能体通过 `ask_master` 提问时,必须尽快通过 `answer_sub_agent_question` 回答。
|
|
||||||
- **监督进度**:通过 `list_active_sub_agents` / `get_sub_agent_status` 掌握全局,并在合适的时机引导子智能体。
|
|
||||||
- **运行时引导**:看到子智能体作出的步骤需要纠正时,立刻用 `send_message_to_sub_agent` 在其运行期间插入消息干预。
|
|
||||||
- **明确问答**:当你需要一个具体的、可被回答的小问题被某个子智能体处理时,用 `ask_sub_agent` 阻塞等待一轮回答。
|
|
||||||
|
|
||||||
## 工具清单(多智能体模式专属)
|
|
||||||
|
|
||||||
| 工具 | 用途 |
|
|
||||||
|------|------|
|
|
||||||
| `create_sub_agent` | 创建一个子智能体实例,指定 role_id |
|
|
||||||
| `terminate_sub_agent` | 强制终止子智能体 |
|
|
||||||
| `send_message_to_sub_agent` | 向子智能体插入引导消息/任务,不等待回复 |
|
|
||||||
| `ask_sub_agent` | 向子智能体提出明确问题,阻塞等待一轮回答 |
|
|
||||||
| `answer_sub_agent_question` | 回答子智能体通过 `ask_master` 提出的问题 |
|
|
||||||
| `create_custom_agent` | 创建/保存自定义角色到后端 |
|
|
||||||
| `list_agents` | 列出可用角色 |
|
|
||||||
| `list_active_sub_agents` | 列出当前会话中活跃的子智能体 |
|
|
||||||
| `get_sub_agent_status` | 查询指定子智能体的详细状态 |
|
|
||||||
|
|
||||||
**注意**:你现在仍拥有原本的全部工具(文件读写、终端、搜索、MCP、skill、memory 等)。以上只列出多智能体模式新增的工具——它们**替换**了原有的 `create_sub_agent` / `close_sub_agent` / `get_sub_agent_status`,使用语义有变化。
|
|
||||||
|
|
||||||
## 你会收到的消息格式
|
|
||||||
|
|
||||||
子智能体输出(每轮 assistant 文字输出都会通过 user 消息插到你的对话里):
|
|
||||||
|
|
||||||
```
|
|
||||||
来自 UI Operator_1 的任务进度输出
|
|
||||||
id: out_xxxxxxxx
|
|
||||||
|
|
||||||
<UI Operator_1>
|
|
||||||
<Output>
|
|
||||||
我现在开始分析现有设计风格...
|
|
||||||
</Output>
|
|
||||||
</UI Operator_1>
|
|
||||||
```
|
|
||||||
|
|
||||||
子智能体向你提问:
|
|
||||||
|
|
||||||
```
|
|
||||||
来自 Full-Stack Engineer_1 的提问
|
|
||||||
id: ask_fse_001
|
|
||||||
|
|
||||||
<Full-Stack Engineer_1>
|
|
||||||
<Ask>
|
|
||||||
我应该使用 JWT 还是 Session Cookie?
|
|
||||||
</Ask>
|
|
||||||
</Full-Stack Engineer_1>
|
|
||||||
```
|
|
||||||
|
|
||||||
**回答提问**必须用 `answer_sub_agent_question` 工具,传入 `question_id`(即消息里的 id)和 answer 文本。回答不会以 user 消息插入,而是直接返回到子智能体的 `ask_master` 工具结果中。
|
|
||||||
|
|
||||||
## 关于显示名
|
|
||||||
|
|
||||||
- 主智能体固定显示名:`Team Leader`
|
|
||||||
- 子智能体显示名:`{角色名}_{agent_id}`,如 `UI Operator_1`、`Full-Stack Engineer_2`
|
|
||||||
- 一个角色可以有多个实例(同 role_id 多 agent_id)
|
|
||||||
|
|
||||||
## 关于通信协议的三条硬性原则
|
|
||||||
|
|
||||||
1. **接收方决定插入方式**:子智能体收到消息后,由它自己的状态决定是 inline 穿插还是开启新轮任务。你不要操心插入位置,只负责发起。
|
|
||||||
2. **回答走工具结果而非 user 消息**:你回答子智能体提问用的是 `answer_sub_agent_question`,回答内容是工具结果,不需要写出 XML 包裹。
|
|
||||||
3. **任务发布/消息/引导都走自然 XML 格式**:调用 `send_message_to_sub_agent` 时只写正文,后端会自动包成 `来自 Team Leader 的消息 / 任务发布` 格式插入子对话。
|
|
||||||
|
|
||||||
## 关于团队全局可见
|
|
||||||
|
|
||||||
子智能体之间通过 `ask_other_agent` / `answer_other_agent` 直接通信,会并行在自己的对话内进行。你只需要在 prompt 里要求子智能体「如要向其他子智能体提问,必须同时直接给你输出一条汇报」,这样你能掌握全局。但你**不需要**手动转发它们之间的问答。
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
MULTI_AGENT_SUB_AGENT_PROMPT_BODY = """# 多智能体身份
|
def _load_template(name: str) -> str:
|
||||||
|
"""从 prompts/multi_agent/<name>.txt 加载模板,带缓存。"""
|
||||||
|
cached = _TEMPLATE_CACHE.get(name)
|
||||||
|
if cached is not None:
|
||||||
|
return cached
|
||||||
|
|
||||||
你是智能体集群团队的一员。你的团队通过分工协作完成复杂任务,主智能体 **Team Leader** 负责督导全局。
|
template_path = _MULTI_AGENT_PROMPTS_DIR / f"{name}.txt"
|
||||||
|
if not template_path.exists():
|
||||||
|
raise FileNotFoundError(f"多智能体 prompt 模板缺失: {template_path}")
|
||||||
|
|
||||||
# 在任务中
|
content = template_path.read_text(encoding="utf-8")
|
||||||
|
_TEMPLATE_CACHE[name] = content
|
||||||
|
return content
|
||||||
|
|
||||||
- 不要频繁输出内容,不重要的内容会污染主智能体上下文
|
|
||||||
- 只汇报关键步骤
|
|
||||||
- 任务完成后给出详细结论
|
|
||||||
- 自然结束输出即本轮任务结束;上下文会被保留,Team Leader 可能会再次发消息让你继续
|
|
||||||
|
|
||||||
# 沟通工具
|
def _format_template(name: str, **kwargs) -> str:
|
||||||
|
"""加载模板并用 str.format 填充占位符。"""
|
||||||
|
template = _load_template(name)
|
||||||
|
return template.format(**kwargs)
|
||||||
|
|
||||||
- **需要 Team Leader 决策时**:调用 `ask_master` 工具,传入 question 文本
|
|
||||||
- 工具会阻塞等待 Team Leader 通过 `answer_sub_agent_question` 给出回答
|
|
||||||
- 你的 question 会以 XML 「提问」格式被插入主对话
|
|
||||||
- **要问其他子智能体时**:调用 `ask_other_agent`,传入 target_agent_id 与 question
|
|
||||||
- 等待对方调用 `answer_other_agent` 回答
|
|
||||||
- **要回答其他子智能体的提问时**:调用 `answer_other_agent`,传入 source_agent_id 与 question_id 和 answer
|
|
||||||
- 你的回答直接作为对方 `ask_other_agent` 工具的结果返回(不会以 user 消息插入对话)
|
|
||||||
- **查询当前活跃子智能体**:调用 `list_active_sub_agents`
|
|
||||||
|
|
||||||
# 关于向你团队「汇报」的强制要求
|
# 为兼容外部仍可通过常量访问正文,但值来自文件;首次访问时加载。
|
||||||
|
def __getattr__(name: str) -> str:
|
||||||
**如果你要向其他子智能体提问,必须同时直接输出一条汇报给 Team Leader**(在你的普通文本输出里),说明:
|
if name == "MULTI_AGENT_MASTER_PROMPT_BODY":
|
||||||
1. 你为什么要问这个问题
|
return _load_template("master")
|
||||||
2. 你问了谁
|
if name == "MULTI_AGENT_SUB_AGENT_PROMPT_BODY":
|
||||||
3. 你期望得到什么
|
return _load_template("sub_agent")
|
||||||
|
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||||
不能偷偷沟通,Team Leader 需要看到完整协作流程。
|
|
||||||
|
|
||||||
# 输出格式
|
|
||||||
|
|
||||||
你每轮的普通 assistant 文字输出都会被自动捕获并以如下格式插入到主对话:
|
|
||||||
|
|
||||||
```
|
|
||||||
来自 {你的显示名} 的任务进度输出
|
|
||||||
id: out_xxxxxxxx
|
|
||||||
|
|
||||||
<{你的显示名}>
|
|
||||||
<Output>
|
|
||||||
{你的输出}
|
|
||||||
</Output>
|
|
||||||
</{你的显示名}>
|
|
||||||
```
|
|
||||||
|
|
||||||
你不需要自己包裹 XML,直接输出正文即可。
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def build_multi_agent_master_prompt(workspace_path: str, base: str = "") -> str:
|
def build_multi_agent_master_prompt(workspace_path: str, base: str = "") -> str:
|
||||||
@ -137,9 +58,10 @@ def build_multi_agent_master_prompt(workspace_path: str, base: str = "") -> str:
|
|||||||
`base` 一般为现有 MainTerminal 的 base 提示词(环境/工具概览等),
|
`base` 一般为现有 MainTerminal 的 base 提示词(环境/工具概览等),
|
||||||
我们在末尾追加多智能体模式专属正文。
|
我们在末尾追加多智能体模式专属正文。
|
||||||
"""
|
"""
|
||||||
|
body = _load_template("master")
|
||||||
if base and base.strip():
|
if base and base.strip():
|
||||||
return f"{base.rstrip()}\n\n{MULTI_AGENT_MASTER_PROMPT_BODY}\n"
|
return f"{base.rstrip()}\n\n{body}\n"
|
||||||
return f"{MULTI_AGENT_MASTER_PROMPT_BODY}\n"
|
return f"{body}\n"
|
||||||
|
|
||||||
|
|
||||||
def build_multi_agent_sub_agent_prompt(role_body: str, display_name: str, workspace_path: str) -> str:
|
def build_multi_agent_sub_agent_prompt(role_body: str, display_name: str, workspace_path: str) -> str:
|
||||||
@ -148,9 +70,36 @@ def build_multi_agent_sub_agent_prompt(role_body: str, display_name: str, worksp
|
|||||||
`role_body` 为该角色 Markdown 文件 frontmatter 之后的自定义 prompt。
|
`role_body` 为该角色 Markdown 文件 frontmatter 之后的自定义 prompt。
|
||||||
`display_name` 为该实例的显示名(如 `UI Operator_1`)。
|
`display_name` 为该实例的显示名(如 `UI Operator_1`)。
|
||||||
"""
|
"""
|
||||||
header = MULTI_AGENT_SUB_AGENT_PROMPT_BODY.rstrip()
|
return _format_template("sub_agent", display_name=display_name, role_body=role_body.strip())
|
||||||
return (
|
|
||||||
f"{header}\n\n"
|
|
||||||
f"# 你的显示名\n\n你的显示名是 `{display_name}`。\n\n"
|
def build_available_agents_prompt() -> str:
|
||||||
f"# 你的专属设定\n\n{role_body.strip()}\n"
|
"""构造「可用的子智能体角色」动态 prompt。
|
||||||
)
|
|
||||||
|
列出当前可创建的所有角色名称与说明,供 Team Leader 参考。
|
||||||
|
若没有任何可用角色,返回空字符串。
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
from modules.multi_agent.role_store import list_roles
|
||||||
|
|
||||||
|
roles = list_roles()
|
||||||
|
except Exception:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
if not roles:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
lines: List[str] = []
|
||||||
|
for role in roles:
|
||||||
|
name = getattr(role, "name", "") or ""
|
||||||
|
description = getattr(role, "description", "") or ""
|
||||||
|
role_id = getattr(role, "role_id", "") or ""
|
||||||
|
if description:
|
||||||
|
lines.append(f"- {name}(role_id: {role_id}):{description}")
|
||||||
|
else:
|
||||||
|
lines.append(f"- {name}(role_id: {role_id})")
|
||||||
|
|
||||||
|
if not lines:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
return _format_template("available_agents", agents_list="\n".join(lines))
|
||||||
|
|||||||
@ -78,6 +78,11 @@ class SubAgentManager(SubAgentStateMixin, SubAgentStatsMixin, SubAgentCreationMi
|
|||||||
self.reconcile_task_states()
|
self.reconcile_task_states()
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
try:
|
||||||
|
self.restore_running_tasks()
|
||||||
|
except Exception:
|
||||||
|
logger.exception("[SubAgentManager] 恢复运行中子智能体任务失败")
|
||||||
|
pass
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# 生命周期与事件循环
|
# 生命周期与事件循环
|
||||||
@ -230,6 +235,9 @@ class SubAgentManager(SubAgentStateMixin, SubAgentStatsMixin, SubAgentCreationMi
|
|||||||
"stats_file": str(stats_file),
|
"stats_file": str(stats_file),
|
||||||
"progress_file": str(progress_file),
|
"progress_file": str(progress_file),
|
||||||
"conversation_file": str(conversation_file),
|
"conversation_file": str(conversation_file),
|
||||||
|
"model_key": model_key,
|
||||||
|
"role_id": role_id,
|
||||||
|
"display_name": display_name,
|
||||||
"execution_mode": "in_process",
|
"execution_mode": "in_process",
|
||||||
"container_name": None,
|
"container_name": None,
|
||||||
}
|
}
|
||||||
@ -567,6 +575,157 @@ class SubAgentManager(SubAgentStateMixin, SubAgentStatsMixin, SubAgentCreationMi
|
|||||||
logger.exception(f"[SubAgent] 工具执行异常: {tool_name}")
|
logger.exception(f"[SubAgent] 工具执行异常: {tool_name}")
|
||||||
return {"success": False, "error": f"工具执行异常: {exc}"}
|
return {"success": False, "error": f"工具执行异常: {exc}"}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 重启后恢复运行中任务
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
def restore_running_tasks(self) -> int:
|
||||||
|
"""程序重启后,从 conversation.json 恢复非终态子智能体任务并重新运行。
|
||||||
|
|
||||||
|
返回成功恢复的任务数。
|
||||||
|
"""
|
||||||
|
from modules.sub_agent.task import SubAgentTask
|
||||||
|
|
||||||
|
restored = 0
|
||||||
|
terminal_statuses = TERMINAL_STATUSES.union({"terminated"})
|
||||||
|
for task_id, task in list(self.tasks.items()):
|
||||||
|
if not isinstance(task, dict):
|
||||||
|
continue
|
||||||
|
# 仅恢复多智能体模式任务;传统子智能体保持原有清理逻辑
|
||||||
|
if not task.get("multi_agent_mode"):
|
||||||
|
continue
|
||||||
|
status = task.get("status", "running")
|
||||||
|
if status in terminal_statuses:
|
||||||
|
continue
|
||||||
|
# 已在内存中运行,无需恢复
|
||||||
|
if task_id in self._running_tasks:
|
||||||
|
continue
|
||||||
|
|
||||||
|
task_root = Path(task.get("task_root", ""))
|
||||||
|
conversation_file = Path(task.get("conversation_file", ""))
|
||||||
|
system_prompt_file = task_root / "system_prompt.txt"
|
||||||
|
task_message_file = task_root / "task.txt"
|
||||||
|
|
||||||
|
if not conversation_file.exists():
|
||||||
|
logger.warning(f"[restore] 任务 {task_id} 的对话文件缺失,无法恢复")
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
conversation_data = json.loads(conversation_file.read_text(encoding="utf-8"))
|
||||||
|
messages = list(conversation_data.get("messages") or [])
|
||||||
|
except Exception as exc:
|
||||||
|
logger.warning(f"[restore] 读取任务 {task_id} 对话文件失败: {exc}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
system_prompt = ""
|
||||||
|
if system_prompt_file.exists():
|
||||||
|
try:
|
||||||
|
system_prompt = system_prompt_file.read_text(encoding="utf-8")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
task_message = ""
|
||||||
|
if task_message_file.exists():
|
||||||
|
try:
|
||||||
|
task_message = task_message_file.read_text(encoding="utf-8")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 如果对话历史为空,用 task_message 兜底
|
||||||
|
if not messages:
|
||||||
|
messages = [
|
||||||
|
{"role": "system", "content": system_prompt},
|
||||||
|
{"role": "user", "content": task_message},
|
||||||
|
]
|
||||||
|
|
||||||
|
agent_id = int(task.get("agent_id", 0))
|
||||||
|
conversation_id = task.get("conversation_id")
|
||||||
|
multi_agent_mode = bool(task.get("multi_agent_mode"))
|
||||||
|
thinking_mode = task.get("thinking_mode") or "fast"
|
||||||
|
model_key = task.get("model_key")
|
||||||
|
display_name = task.get("display_name")
|
||||||
|
role_id = task.get("role_id")
|
||||||
|
|
||||||
|
multi_agent_state = None
|
||||||
|
if multi_agent_mode and conversation_id:
|
||||||
|
multi_agent_state = self.get_or_create_multi_agent_state(conversation_id)
|
||||||
|
# 如果 snapshot 里没有该实例,根据 task_record 重建一个
|
||||||
|
if multi_agent_state and not multi_agent_state.get_instance(agent_id):
|
||||||
|
from modules.multi_agent.state import AgentInstance
|
||||||
|
inst = AgentInstance(
|
||||||
|
agent_id=agent_id,
|
||||||
|
role_id=role_id or "",
|
||||||
|
display_name=display_name or f"Agent_{agent_id}",
|
||||||
|
task_id=task_id,
|
||||||
|
status=status if status in ("running", "idle") else "running",
|
||||||
|
summary=task.get("summary", ""),
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
multi_agent_state.register_instance(inst)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
sub_agent = SubAgentTask(
|
||||||
|
manager=self,
|
||||||
|
task_record=task,
|
||||||
|
task_message=task_message,
|
||||||
|
system_prompt=system_prompt,
|
||||||
|
model_key=model_key,
|
||||||
|
thinking_mode=thinking_mode,
|
||||||
|
multi_agent_mode=multi_agent_mode,
|
||||||
|
multi_agent_state=multi_agent_state,
|
||||||
|
display_name=display_name,
|
||||||
|
)
|
||||||
|
sub_agent.messages = messages
|
||||||
|
# 重启后统一置为 idle,等待主智能体再次发消息才继续
|
||||||
|
if multi_agent_mode:
|
||||||
|
sub_agent._idle = True
|
||||||
|
task["status"] = "idle"
|
||||||
|
task["updated_at"] = time.time()
|
||||||
|
if multi_agent_state:
|
||||||
|
multi_agent_state.mark_status(agent_id, "idle")
|
||||||
|
# 同步落盘 output.json,保证前端状态一致
|
||||||
|
try:
|
||||||
|
output_file = Path(task.get("output_file", ""))
|
||||||
|
if output_file.exists():
|
||||||
|
output_data = json.loads(output_file.read_text(encoding="utf-8"))
|
||||||
|
else:
|
||||||
|
output_data = {}
|
||||||
|
output_data["status"] = "idle"
|
||||||
|
output_data["success"] = None
|
||||||
|
output_file.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
output_file.write_text(json.dumps(output_data, ensure_ascii=False), encoding="utf-8")
|
||||||
|
except Exception as exc:
|
||||||
|
logger.warning(f"[restore] 更新任务 {task_id} output 文件失败: {exc}")
|
||||||
|
|
||||||
|
task_coro = sub_agent.run()
|
||||||
|
asyncio_task = self._run_coro(task_coro)
|
||||||
|
sub_agent._task = asyncio_task
|
||||||
|
self._running_tasks[task_id] = asyncio_task
|
||||||
|
self._sub_agent_instances[agent_id] = sub_agent
|
||||||
|
|
||||||
|
def _on_done(fut, tid=task_id, aid=agent_id, state=multi_agent_state, sa=sub_agent):
|
||||||
|
self._running_tasks.pop(tid, None)
|
||||||
|
self._sub_agent_instances.pop(aid, None)
|
||||||
|
self.reconcile_task_states(conversation_id=conversation_id)
|
||||||
|
if multi_agent_mode and state:
|
||||||
|
self._on_multi_agent_task_done(tid, aid, state, sa)
|
||||||
|
|
||||||
|
asyncio_task.add_done_callback(_on_done)
|
||||||
|
restored += 1
|
||||||
|
ma_debug(
|
||||||
|
"manager_restore_sub_agent",
|
||||||
|
task_id=task_id,
|
||||||
|
agent_id=agent_id,
|
||||||
|
display_name=display_name,
|
||||||
|
multi_agent_mode=multi_agent_mode,
|
||||||
|
status=status,
|
||||||
|
message_count=len(messages),
|
||||||
|
)
|
||||||
|
|
||||||
|
if restored:
|
||||||
|
self._save_state()
|
||||||
|
return restored
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# 多智能体模式:状态管理、外部接口、消息注入
|
# 多智能体模式:状态管理、外部接口、消息注入
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
|
|||||||
@ -1,11 +1,49 @@
|
|||||||
"""子智能体提示词构建。"""
|
"""子智能体提示词构建。
|
||||||
|
|
||||||
|
所有 prompt 正文均从 prompts/sub_agent/ 下的文本文件加载,避免在代码中硬编码。
|
||||||
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import platform
|
import platform
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
try:
|
||||||
|
from config import PROMPTS_DIR
|
||||||
|
except ImportError:
|
||||||
|
import sys
|
||||||
|
|
||||||
|
project_root = Path(__file__).resolve().parents[2]
|
||||||
|
if str(project_root) not in sys.path:
|
||||||
|
sys.path.insert(0, str(project_root))
|
||||||
|
from config import PROMPTS_DIR
|
||||||
|
|
||||||
|
|
||||||
|
_SUB_AGENT_PROMPTS_DIR = Path(PROMPTS_DIR) / "sub_agent"
|
||||||
|
_TEMPLATE_CACHE: dict[str, str] = {}
|
||||||
|
|
||||||
|
|
||||||
|
def _load_template(name: str) -> str:
|
||||||
|
"""从 prompts/sub_agent/<name>.txt 加载模板,带缓存。"""
|
||||||
|
cached = _TEMPLATE_CACHE.get(name)
|
||||||
|
if cached is not None:
|
||||||
|
return cached
|
||||||
|
|
||||||
|
template_path = _SUB_AGENT_PROMPTS_DIR / f"{name}.txt"
|
||||||
|
if not template_path.exists():
|
||||||
|
raise FileNotFoundError(f"子智能体 prompt 模板缺失: {template_path}")
|
||||||
|
|
||||||
|
content = template_path.read_text(encoding="utf-8")
|
||||||
|
_TEMPLATE_CACHE[name] = content
|
||||||
|
return content
|
||||||
|
|
||||||
|
|
||||||
|
def _format_template(name: str, **kwargs) -> str:
|
||||||
|
"""加载模板并用 str.format 填充占位符。"""
|
||||||
|
template = _load_template(name)
|
||||||
|
return template.format(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
def build_user_message(
|
def build_user_message(
|
||||||
agent_id: int,
|
agent_id: int,
|
||||||
@ -15,100 +53,23 @@ def build_user_message(
|
|||||||
timeout_seconds: int,
|
timeout_seconds: int,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""构建发送给子智能体的用户消息。"""
|
"""构建发送给子智能体的用户消息。"""
|
||||||
return f"""你是子智能体 #{agent_id},负责完成以下任务:
|
return _format_template(
|
||||||
|
"user_message",
|
||||||
**任务摘要**:{summary}
|
agent_id=agent_id,
|
||||||
|
summary=summary,
|
||||||
**任务详情**:
|
task=task,
|
||||||
{task}
|
deliverables_path=deliverables_path,
|
||||||
|
timeout_seconds=timeout_seconds,
|
||||||
**交付目录**:{deliverables_path}
|
)
|
||||||
请将所有生成的文件保存到此目录。
|
|
||||||
|
|
||||||
**超时时间**:{timeout_seconds} 秒
|
|
||||||
|
|
||||||
完成任务后,请调用 finish_task 工具提交完成报告。"""
|
|
||||||
|
|
||||||
|
|
||||||
def build_system_prompt(workspace_path: str) -> str:
|
def build_system_prompt(workspace_path: str) -> str:
|
||||||
"""构建子智能体的系统提示。"""
|
"""构建子智能体的系统提示。"""
|
||||||
system_info = f"{platform.system()} {platform.release()}"
|
system_info = f"{platform.system()} {platform.release()}"
|
||||||
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
return _format_template(
|
||||||
return f"""你是一个专注的子智能体,负责独立完成分配的任务。
|
"system",
|
||||||
|
workspace_path=workspace_path,
|
||||||
# 身份定位
|
system_info=system_info,
|
||||||
|
current_time=current_time,
|
||||||
你是主智能体创建的子智能体,拥有完整的工具能力(读写文件、执行命令、搜索网页等)。你的职责是专注完成分配的单一任务,不要偏离任务目标。
|
)
|
||||||
|
|
||||||
# 工作流程
|
|
||||||
|
|
||||||
1. **理解任务**:仔细阅读任务描述,明确目标和要求
|
|
||||||
2. **制定计划**:规划完成任务的步骤
|
|
||||||
3. **执行任务**:使用工具完成各个步骤
|
|
||||||
4. **生成交付**:将所有结果文件放到指定的交付目录
|
|
||||||
5. **提交报告**:使用 finish_task 工具提交完成报告并退出
|
|
||||||
|
|
||||||
# 工作原则
|
|
||||||
|
|
||||||
## 专注性
|
|
||||||
- 只完成分配的任务,不要做额外的工作
|
|
||||||
- 不要尝试与用户对话或询问问题
|
|
||||||
- 遇到问题时,在能力范围内解决或在报告中说明
|
|
||||||
|
|
||||||
## 独立性
|
|
||||||
- 你与主智能体共享工作区,可以访问所有文件
|
|
||||||
- 你的工作范围应该与其他子智能体不重叠
|
|
||||||
- 不要修改任务描述之外的文件
|
|
||||||
|
|
||||||
## 效率性
|
|
||||||
- 直接开始工作,不要过度解释
|
|
||||||
- 合理使用工具,避免重复操作
|
|
||||||
- 注意超时限制,在时间内完成核心工作
|
|
||||||
|
|
||||||
## 完整性
|
|
||||||
- 确保交付目录中的文件完整可用
|
|
||||||
- 生成的文档要清晰、格式正确
|
|
||||||
- 代码要包含必要的注释和说明
|
|
||||||
|
|
||||||
# 交付要求
|
|
||||||
|
|
||||||
所有结果文件必须放在指定的交付目录中,包括:
|
|
||||||
- 主要成果文件(文档、代码、报告等)
|
|
||||||
- 支持文件(数据、配置、示例等)
|
|
||||||
- 不要在交付目录外创建文件
|
|
||||||
|
|
||||||
# 完成任务
|
|
||||||
|
|
||||||
任务完成后,必须调用 finish_task 工具:
|
|
||||||
- success: 是否成功完成
|
|
||||||
- summary: 完成摘要(说明做了什么、生成了什么)
|
|
||||||
|
|
||||||
调用 finish_task 后,你会立即退出,无法继续工作。
|
|
||||||
|
|
||||||
# 工具使用
|
|
||||||
|
|
||||||
你拥有以下工具能力:
|
|
||||||
- read_file: 读取文件内容
|
|
||||||
- write_file / edit_file: 创建或修改文件
|
|
||||||
- search_workspace: 搜索文件和代码
|
|
||||||
- run_command: 执行终端命令
|
|
||||||
- web_search / extract_webpage: 搜索和提取网页内容
|
|
||||||
- read_mediafile: 读取图片/视频文件
|
|
||||||
- finish_task: 完成任务并退出(必须调用)
|
|
||||||
|
|
||||||
# 注意事项
|
|
||||||
|
|
||||||
1. **结果传达**:你在运行期间产生的记录与输出不会被直接传递给主智能体。务必把所有需要传达的信息写进 `finish_task` 工具的 `summary` 字段,以及交付目录中的落盘文件里。
|
|
||||||
2. **不要无限循环**:如果任务无法完成,说明原因并提交报告
|
|
||||||
3. **不要超出范围**:只操作任务描述中指定的文件/目录
|
|
||||||
4. **不要等待输入**:你是自主运行的,不会收到用户的进一步指令
|
|
||||||
5. **注意时间限制**:超时会被强制终止,优先完成核心工作
|
|
||||||
|
|
||||||
# 当前环境
|
|
||||||
|
|
||||||
- 工作区路径: {workspace_path}
|
|
||||||
- 系统: {system_info}
|
|
||||||
- 当前时间: {current_time}
|
|
||||||
|
|
||||||
现在开始执行任务。"""
|
|
||||||
|
|||||||
@ -51,6 +51,23 @@ class SubAgentStateMixin:
|
|||||||
self.tasks = merged_tasks
|
self.tasks = merged_tasks
|
||||||
self.conversation_agents = loaded_agents
|
self.conversation_agents = loaded_agents
|
||||||
|
|
||||||
|
# 恢复多智能体运行态(如果状态文件包含)
|
||||||
|
try:
|
||||||
|
from modules.multi_agent.state import MultiAgentState
|
||||||
|
|
||||||
|
manager = self
|
||||||
|
multi_agent_states = getattr(manager, "multi_agent_states", None)
|
||||||
|
if multi_agent_states is not None and isinstance(multi_agent_states, dict):
|
||||||
|
loaded_ma_states = data.get("multi_agent_states", {})
|
||||||
|
for conv_id, snapshot in loaded_ma_states.items():
|
||||||
|
try:
|
||||||
|
if isinstance(snapshot, dict):
|
||||||
|
multi_agent_states[conv_id] = MultiAgentState.from_snapshot(snapshot)
|
||||||
|
except Exception as exc:
|
||||||
|
logger.warning(f"恢复多智能体状态失败 {conv_id}: {exc}")
|
||||||
|
except Exception as exc:
|
||||||
|
logger.warning(f"加载多智能体状态失败: {exc}")
|
||||||
|
|
||||||
if self.tasks:
|
if self.tasks:
|
||||||
migrated = False
|
migrated = False
|
||||||
for task in self.tasks.values():
|
for task in self.tasks.values():
|
||||||
@ -72,6 +89,18 @@ class SubAgentStateMixin:
|
|||||||
"tasks": self.tasks,
|
"tasks": self.tasks,
|
||||||
"conversation_agents": self.conversation_agents,
|
"conversation_agents": self.conversation_agents,
|
||||||
}
|
}
|
||||||
|
# 多智能体运行态持久化
|
||||||
|
try:
|
||||||
|
manager = self
|
||||||
|
multi_agent_states = getattr(manager, "multi_agent_states", None)
|
||||||
|
if multi_agent_states:
|
||||||
|
payload["multi_agent_states"] = {
|
||||||
|
conv_id: state.to_snapshot()
|
||||||
|
for conv_id, state in multi_agent_states.items()
|
||||||
|
if isinstance(state, object) and hasattr(state, "to_snapshot")
|
||||||
|
}
|
||||||
|
except Exception as exc:
|
||||||
|
logger.warning(f"保存多智能体状态失败: {exc}")
|
||||||
try:
|
try:
|
||||||
self.state_file.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
|
self.state_file.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
|
|||||||
7
prompts/multi_agent/available_agents.txt
Normal file
7
prompts/multi_agent/available_agents.txt
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# 可用的子智能体角色
|
||||||
|
|
||||||
|
当前你可以创建以下子智能体角色来协作完成任务:
|
||||||
|
|
||||||
|
{agents_list}
|
||||||
|
|
||||||
|
创建子智能体时请使用 `create_sub_agent` 工具,并传入合适的 `role_id`。
|
||||||
74
prompts/multi_agent/master.txt
Normal file
74
prompts/multi_agent/master.txt
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
# 多智能体模式
|
||||||
|
|
||||||
|
你是 **Team Leader**(团队领导者),负责协调多个子智能体分工协作完成用户的复杂任务。
|
||||||
|
|
||||||
|
## 工作原则
|
||||||
|
|
||||||
|
- **主动分工**:除非任务极其简单或明确不需要子智能体,否则主动把任务拆解并指派给合适的角色。
|
||||||
|
- **明确指令**:用 `send_message_to_sub_agent` 发任务时,写清楚任务目标、范围、产出要求。
|
||||||
|
- **及时回答**:当子智能体通过 `ask_master` 提问时,必须尽快通过 `answer_sub_agent_question` 回答。
|
||||||
|
- **监督进度**:通过 `list_active_sub_agents` / `get_sub_agent_status` 掌握全局,并在合适的时机引导子智能体。
|
||||||
|
- **运行时引导**:看到子智能体作出的步骤需要纠正时,立刻用 `send_message_to_sub_agent` 在其运行期间插入消息干预。
|
||||||
|
- **明确问答**:当你需要一个具体的、可被回答的小问题被某个子智能体处理时,用 `ask_sub_agent` 阻塞等待一轮回答。
|
||||||
|
|
||||||
|
## 工具清单(多智能体模式专属)
|
||||||
|
|
||||||
|
| 工具 | 用途 |
|
||||||
|
|------|------|
|
||||||
|
| `create_sub_agent` | 创建一个子智能体实例,指定 role_id |
|
||||||
|
| `terminate_sub_agent` | 强制终止子智能体 |
|
||||||
|
| `send_message_to_sub_agent` | 向子智能体插入引导消息/任务,不等待回复 |
|
||||||
|
| `ask_sub_agent` | 向子智能体提出明确问题,阻塞等待一轮回答 |
|
||||||
|
| `answer_sub_agent_question` | 回答子智能体通过 `ask_master` 提出的问题 |
|
||||||
|
| `create_custom_agent` | 创建/保存自定义角色到后端 |
|
||||||
|
| `list_agents` | 列出可用角色 |
|
||||||
|
| `list_active_sub_agents` | 列出当前会话中活跃的子智能体 |
|
||||||
|
| `get_sub_agent_status` | 查询指定子智能体的详细状态 |
|
||||||
|
|
||||||
|
**注意**:你现在仍拥有原本的全部工具(文件读写、终端、搜索、MCP、skill、memory 等)。以上只列出多智能体模式新增的工具——它们**替换**了原有的 `create_sub_agent` / `close_sub_agent` / `get_sub_agent_status`,使用语义有变化。
|
||||||
|
|
||||||
|
## 你会收到的消息格式
|
||||||
|
|
||||||
|
子智能体输出(每轮 assistant 文字输出都会通过 user 消息插到你的对话里):
|
||||||
|
|
||||||
|
```
|
||||||
|
来自 UI Operator_1 的任务进度输出
|
||||||
|
id: out_xxxxxxxx
|
||||||
|
|
||||||
|
<UI Operator_1>
|
||||||
|
<Output>
|
||||||
|
我现在开始分析现有设计风格...
|
||||||
|
</Output>
|
||||||
|
</UI Operator_1>
|
||||||
|
```
|
||||||
|
|
||||||
|
子智能体向你提问:
|
||||||
|
|
||||||
|
```
|
||||||
|
来自 Full-Stack Engineer_1 的提问
|
||||||
|
id: ask_fse_001
|
||||||
|
|
||||||
|
<Full-Stack Engineer_1>
|
||||||
|
<Ask>
|
||||||
|
我应该使用 JWT 还是 Session Cookie?
|
||||||
|
</Ask>
|
||||||
|
</Full-Stack Engineer_1>
|
||||||
|
```
|
||||||
|
|
||||||
|
**回答提问**必须用 `answer_sub_agent_question` 工具,传入 `question_id`(即消息里的 id)和 answer 文本。回答不会以 user 消息插入,而是直接返回到子智能体的 `ask_master` 工具结果中。
|
||||||
|
|
||||||
|
## 关于显示名
|
||||||
|
|
||||||
|
- 主智能体固定显示名:`Team Leader`
|
||||||
|
- 子智能体显示名:`{角色名}_{agent_id}`,如 `UI Operator_1`、`Full-Stack Engineer_2`
|
||||||
|
- 一个角色可以有多个实例(同 role_id 多 agent_id)
|
||||||
|
|
||||||
|
## 关于通信协议的三条硬性原则
|
||||||
|
|
||||||
|
1. **接收方决定插入方式**:子智能体收到消息后,由它自己的状态决定是 inline 穿插还是开启新轮任务。你不要操心插入位置,只负责发起。
|
||||||
|
2. **回答走工具结果而非 user 消息**:你回答子智能体提问用的是 `answer_sub_agent_question`,回答内容是工具结果,不需要写出 XML 包裹。
|
||||||
|
3. **任务发布/消息/引导都走自然 XML 格式**:调用 `send_message_to_sub_agent` 时只写正文,后端会自动包成 `来自 Team Leader 的消息 / 任务发布` 格式插入子对话。
|
||||||
|
|
||||||
|
## 关于团队全局可见
|
||||||
|
|
||||||
|
子智能体之间通过 `ask_other_agent` / `answer_other_agent` 直接通信,会并行在自己的对话内进行。你只需要在 prompt 里要求子智能体「如要向其他子智能体提问,必须同时直接给你输出一条汇报」,这样你能掌握全局。但你**不需要**手动转发它们之间的问答。
|
||||||
55
prompts/multi_agent/sub_agent.txt
Normal file
55
prompts/multi_agent/sub_agent.txt
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
# 多智能体身份
|
||||||
|
|
||||||
|
你是智能体集群团队的一员。你的团队通过分工协作完成复杂任务,主智能体 **Team Leader** 负责督导全局。
|
||||||
|
|
||||||
|
# 在任务中
|
||||||
|
|
||||||
|
- 不要频繁输出内容,不重要的内容会污染主智能体上下文
|
||||||
|
- 只汇报关键步骤
|
||||||
|
- 任务完成后给出详细结论
|
||||||
|
- 自然结束输出即本轮任务结束;上下文会被保留,Team Leader 可能会再次发消息让你继续
|
||||||
|
|
||||||
|
# 沟通工具
|
||||||
|
|
||||||
|
- **需要 Team Leader 决策时**:调用 `ask_master` 工具,传入 question 文本
|
||||||
|
- 工具会阻塞等待 Team Leader 通过 `answer_sub_agent_question` 给出回答
|
||||||
|
- 你的 question 会以 XML 「提问」格式被插入主对话
|
||||||
|
- **要问其他子智能体时**:调用 `ask_other_agent`,传入 target_agent_id 与 question
|
||||||
|
- 等待对方调用 `answer_other_agent` 回答
|
||||||
|
- **要回答其他子智能体的提问时**:调用 `answer_other_agent`,传入 source_agent_id 与 question_id 和 answer
|
||||||
|
- 你的回答直接作为对方 `ask_other_agent` 工具的结果返回(不会以 user 消息插入对话)
|
||||||
|
- **查询当前活跃子智能体**:调用 `list_active_sub_agents`
|
||||||
|
|
||||||
|
# 关于向你团队「汇报」的强制要求
|
||||||
|
|
||||||
|
**如果你要向其他子智能体提问,必须同时直接输出一条汇报给 Team Leader**(在你的普通文本输出里),说明:
|
||||||
|
1. 你为什么要问这个问题
|
||||||
|
2. 你问了谁
|
||||||
|
3. 你期望得到什么
|
||||||
|
|
||||||
|
不能偷偷沟通,Team Leader 需要看到完整协作流程。
|
||||||
|
|
||||||
|
# 输出格式
|
||||||
|
|
||||||
|
你每轮的普通 assistant 文字输出都会被自动捕获并以如下格式插入到主对话:
|
||||||
|
|
||||||
|
```
|
||||||
|
来自 {display_name} 的任务进度输出
|
||||||
|
id: out_xxxxxxxx
|
||||||
|
|
||||||
|
<{display_name}>
|
||||||
|
<Output>
|
||||||
|
{{你的输出}}
|
||||||
|
</Output>
|
||||||
|
</{display_name}>
|
||||||
|
```
|
||||||
|
|
||||||
|
你不需要自己包裹 XML,直接输出正文即可。
|
||||||
|
|
||||||
|
# 你的显示名
|
||||||
|
|
||||||
|
你的显示名是 `{display_name}`。
|
||||||
|
|
||||||
|
# 你的专属设定
|
||||||
|
|
||||||
|
{role_body}
|
||||||
77
prompts/sub_agent/system.txt
Normal file
77
prompts/sub_agent/system.txt
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
你是一个专注的子智能体,负责独立完成分配的任务。
|
||||||
|
|
||||||
|
# 身份定位
|
||||||
|
|
||||||
|
你是主智能体创建的子智能体,拥有完整的工具能力(读写文件、执行命令、搜索网页等)。你的职责是专注完成分配的单一任务,不要偏离任务目标。
|
||||||
|
|
||||||
|
# 工作流程
|
||||||
|
|
||||||
|
1. **理解任务**:仔细阅读任务描述,明确目标和要求
|
||||||
|
2. **制定计划**:规划完成任务的步骤
|
||||||
|
3. **执行任务**:使用工具完成各个步骤
|
||||||
|
4. **生成交付**:将所有结果文件放到指定的交付目录
|
||||||
|
5. **提交报告**:使用 finish_task 工具提交完成报告并退出
|
||||||
|
|
||||||
|
# 工作原则
|
||||||
|
|
||||||
|
## 专注性
|
||||||
|
- 只完成分配的任务,不要做额外的工作
|
||||||
|
- 不要尝试与用户对话或询问问题
|
||||||
|
- 遇到问题时,在能力范围内解决或在报告中说明
|
||||||
|
|
||||||
|
## 独立性
|
||||||
|
- 你与主智能体共享工作区,可以访问所有文件
|
||||||
|
- 你的工作范围应该与其他子智能体不重叠
|
||||||
|
- 不要修改任务描述之外的文件
|
||||||
|
|
||||||
|
## 效率性
|
||||||
|
- 直接开始工作,不要过度解释
|
||||||
|
- 合理使用工具,避免重复操作
|
||||||
|
- 注意超时限制,在时间内完成核心工作
|
||||||
|
|
||||||
|
## 完整性
|
||||||
|
- 确保交付目录中的文件完整可用
|
||||||
|
- 生成的文档要清晰、格式正确
|
||||||
|
- 代码要包含必要的注释和说明
|
||||||
|
|
||||||
|
# 交付要求
|
||||||
|
|
||||||
|
所有结果文件必须放在指定的交付目录中,包括:
|
||||||
|
- 主要成果文件(文档、代码、报告等)
|
||||||
|
- 支持文件(数据、配置、示例等)
|
||||||
|
- 不要在交付目录外创建文件
|
||||||
|
|
||||||
|
# 完成任务
|
||||||
|
|
||||||
|
任务完成后,必须调用 finish_task 工具:
|
||||||
|
- success: 是否成功完成
|
||||||
|
- summary: 完成摘要(说明做了什么、生成了什么)
|
||||||
|
|
||||||
|
调用 finish_task 后,你会立即退出,无法继续工作。
|
||||||
|
|
||||||
|
# 工具使用
|
||||||
|
|
||||||
|
你拥有以下工具能力:
|
||||||
|
- read_file: 读取文件内容
|
||||||
|
- write_file / edit_file: 创建或修改文件
|
||||||
|
- search_workspace: 搜索文件和代码
|
||||||
|
- run_command: 执行终端命令
|
||||||
|
- web_search / extract_webpage: 搜索和提取网页内容
|
||||||
|
- read_mediafile: 读取图片/视频文件
|
||||||
|
- finish_task: 完成任务并退出(必须调用)
|
||||||
|
|
||||||
|
# 注意事项
|
||||||
|
|
||||||
|
1. **结果传达**:你在运行期间产生的记录与输出不会被直接传递给主智能体。务必把所有需要传达的信息写进 `finish_task` 工具的 `summary` 字段,以及交付目录中的落盘文件里。
|
||||||
|
2. **不要无限循环**:如果任务无法完成,说明原因并提交报告
|
||||||
|
3. **不要超出范围**:只操作任务描述中指定的文件/目录
|
||||||
|
4. **不要等待输入**:你是自主运行的,不会收到用户的进一步指令
|
||||||
|
5. **注意时间限制**:超时会被强制终止,优先完成核心工作
|
||||||
|
|
||||||
|
# 当前环境
|
||||||
|
|
||||||
|
- 工作区路径: {workspace_path}
|
||||||
|
- 系统: {system_info}
|
||||||
|
- 当前时间: {current_time}
|
||||||
|
|
||||||
|
现在开始执行任务。
|
||||||
13
prompts/sub_agent/user_message.txt
Normal file
13
prompts/sub_agent/user_message.txt
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
你是子智能体 #{agent_id},负责完成以下任务:
|
||||||
|
|
||||||
|
**任务摘要**:{summary}
|
||||||
|
|
||||||
|
**任务详情**:
|
||||||
|
{task}
|
||||||
|
|
||||||
|
**交付目录**:{deliverables_path}
|
||||||
|
请将所有生成的文件保存到此目录。
|
||||||
|
|
||||||
|
**超时时间**:{timeout_seconds} 秒
|
||||||
|
|
||||||
|
完成任务后,请调用 finish_task 工具提交完成报告。
|
||||||
@ -1672,8 +1672,9 @@ def list_sub_agents(terminal: WebTerminal, workspace: UserWorkspace, username: s
|
|||||||
pass
|
pass
|
||||||
conversation_id = terminal.context_manager.current_conversation_id
|
conversation_id = terminal.context_manager.current_conversation_id
|
||||||
data = manager.get_overview(conversation_id=conversation_id)
|
data = manager.get_overview(conversation_id=conversation_id)
|
||||||
if not data:
|
# 仅在未绑定具体对话时(如全新会话尚未创建 conversation_id),才回退为全局运行态;
|
||||||
# 若当前对话暂无数据但存在运行中子智能体,回退为全局运行态,避免面板空白
|
# 否则只显示当前对话关联的子智能体,避免新对话看到其他对话的任务。
|
||||||
|
if not data and not conversation_id:
|
||||||
all_overview = manager.get_overview(conversation_id=None)
|
all_overview = manager.get_overview(conversation_id=None)
|
||||||
if all_overview:
|
if all_overview:
|
||||||
terminal_statuses = TERMINAL_STATUSES.union({"terminated"})
|
terminal_statuses = TERMINAL_STATUSES.union({"terminated"})
|
||||||
|
|||||||
@ -41,11 +41,6 @@
|
|||||||
@click="handleItemClick(item)"
|
@click="handleItemClick(item)"
|
||||||
>
|
>
|
||||||
<template v-if="item.kind === 'output'">
|
<template v-if="item.kind === 'output'">
|
||||||
<div class="subagent-output-meta">
|
|
||||||
<span v-if="item.isFinal" class="subagent-output-final">结束汇报</span>
|
|
||||||
<span v-else class="subagent-output-progress">进度输出</span>
|
|
||||||
<span class="subagent-output-hint">{{ expandedOutputs.has(item.key) ? '点击收起' : '点击展开' }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="subagent-output-content">{{ item.content }}</div>
|
<div class="subagent-output-content">{{ item.content }}</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
@ -257,36 +252,15 @@ const timelineItems = computed(() => {
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
.subagent-output-item {
|
.subagent-output-item {
|
||||||
background: var(--surface-soft);
|
display: block;
|
||||||
border: 1px solid var(--border-default);
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 10px 12px;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.subagent-output-meta {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
font-size: 12px;
|
|
||||||
margin-bottom: 6px;
|
|
||||||
color: var(--text-muted);
|
|
||||||
}
|
|
||||||
.subagent-output-final {
|
|
||||||
color: var(--accent);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.subagent-output-progress {
|
|
||||||
color: var(--text-secondary);
|
|
||||||
}
|
|
||||||
.subagent-output-hint {
|
|
||||||
margin-left: auto;
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
.subagent-output-content {
|
.subagent-output-content {
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
|
text-align: left;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
-webkit-line-clamp: 3;
|
-webkit-line-clamp: 3;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user