From ce92697181bd4e0cf54833e8bbdfbc45739a7445 Mon Sep 17 00:00:00 2001 From: JOJO <1498581755@qq.com> Date: Tue, 14 Jul 2026 22:11:48 +0800 Subject: [PATCH] feat(multi_agent): add formatters and structured rendering for multi-agent tools --- AGENTS.md | 39 ++- modules/sub_agent/toolkit.py | 16 + static/src/components/chat/MinimalBlocks.vue | 16 +- static/src/components/chat/StackedBlocks.vue | 66 +--- .../components/chat/actions/ToolAction.vue | 282 ++++++++++++++++-- .../components/chat/actions/toolRenderers.ts | 267 ++++++++++++++++- utils/tool_result_formatter/agent_context.py | 92 ++++++ utils/tool_result_formatter/dispatch.py | 14 + 8 files changed, 695 insertions(+), 97 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 832bbf4..61fa4e0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -387,7 +387,40 @@ AI 执行以下流程时,每一步都要向用户说明在做什么: - 子智能体进度弹窗:输出与工具按真实时间线混排,默认 3 行,过长用省略号或内部滚动;颜色走 `--text-primary` 等语义 token。 - 全局工具规范统一适用于多智能体权限:UI 不能引入 `compact` 以外的 fallback 样式习惯复制到多智能体渲染。 -### 11.6 调试机制 +### 11.6 工具结果格式化与前端渲染位置 + +新增多智能体工具时,必须同步补齐「后端结果格式化」和「前端结构化渲染」,否则前端会 fallback 到原始 JSON 或空白。 + +**后端 formatter(主智能体工具)** +- 实现位置:`utils/tool_result_formatter/agent_context.py` +- 注册位置:`utils/tool_result_formatter/dispatch.py` 的 `TOOL_FORMATTERS` +- 处理入口:所有主智能体工具执行结果,最终由 `format_tool_result_for_context()` 转换为自然语言摘要,写入对话历史。 + +**后端 formatter(子智能体通信工具)** +- 实现位置:`modules/sub_agent/toolkit.py` 的 `_format_tool_result()` +- 覆盖工具:`ask_master` / `ask_other_agent` / `answer_other_agent` / `list_active_sub_agents` +- 处理入口:子智能体 tool call 结果回填到子对话上下文前。 + +**前端 renderer(主路径)** +- 实现位置:`static/src/components/chat/actions/toolRenderers.ts` 的 `renderEnhancedToolResult()` +- 调用方: + - `static/src/components/chat/MinimalBlocks.vue` + - `static/src/components/chat/StackedBlocks.vue` +- 注意:这两个视图已移除 `enhanced_tool_display` 开关判断,**所有工具块都强制走结构化渲染**,不再显示原始 JSON。 + +**前端 renderer(备用路径)** +- 实现位置:`static/src/components/chat/actions/ToolAction.vue` 的 `renderToolResult()` +- 调用方:`static/src/components/chat/ChatArea.vue` +- 同样已移除原始 JSON fallback,仅作为 ChatArea 独立工具块渲染的备用。 + +**新增工具 checklist** +1. `modules/multi_agent/tools.py` 定义工具签名。 +2. `core/main_terminal_parts/tools_execution.py` 实现工具 handler 并返回标准 dict(`success` + 业务字段 + 可选 `error`)。 +3. `utils/tool_result_formatter/agent_context.py` 新增 `_format_()`,`dispatch.py` 注册。 +4. `static/src/components/chat/actions/toolRenderers.ts` 新增对应 `render()` 并在 `renderEnhancedToolResult()` 中分发。 +5. 如果是子智能体通信工具,同步在 `modules/sub_agent/toolkit.py` 的 `_format_tool_result()` 中补 formatter。 + +### 11.7 调试机制 - 调试日志统一走 `modules/multi_agent/debug_logger.py` 的 `ma_debug()` 函数,写入 `~/.astrion/astrion/host/logs/multi_agent_loop.log`。 - 关键状态转换点必须打 `ma_debug`,便于复现 bug: @@ -398,7 +431,7 @@ AI 执行以下流程时,每一步都要向用户说明在做什么: - `create_chat_task` 异常 / `provide_answer` 跨循环回写 - `runtime_injected` 字段标记(metadata 里能区分多智能体 inline / idle / ask插入路径) -### 11.7 已知坑 +### 11.8 已知坑 - 多智能体模式下根本不 `emit` `sub_agent_waiting` 事件,避免前端进入「等待后台子智能体」的输入区阻塞态。 - `_announced_sub_agent_tasks` / `notified` 等标记仅适用于传统后台子智能体任务,多智能体任务不走这条通知路径。 @@ -406,7 +439,7 @@ AI 执行以下流程时,每一步都要向用户说明在做什么: - 传统后台通知池 `_collect_pending_completion_notices` 在 `task.get("multi_agent_mode")` 为真时跳过该 task;多智能体派出走独立的 `poll_multi_agent_notifications` 路径,不当混用。 - `_has_pending_completion_work` 主动排除 `multi_agent_mode=True` 的任务;两者永远独立。 -### 11.8 对话切换与状态保留 +### 11.9 对话切换与状态保留 - 切换会话不清理 `_running_tasks` 和 `_sub_agent_instances`。`SubAgentManager` 的全局 tasks 字典按 `task_id` 保持,多智能体状态由 `conversation_id` 在 `get_multi_agent_state` 中查。 - 子智能体对话存在 `~/.astrion/astrion/host/host/data/sub_agents/`。重启后走 `manager.restore_sub_agent` 恢复实例引用。 diff --git a/modules/sub_agent/toolkit.py b/modules/sub_agent/toolkit.py index a9d160f..70f6cbb 100644 --- a/modules/sub_agent/toolkit.py +++ b/modules/sub_agent/toolkit.py @@ -373,6 +373,22 @@ def _format_tool_result(name: str, raw: Any) -> str: if raw.get("path"): return f"已保存: {raw.get('path')}" return raw.get("message") or "网页已保存" + # 多智能体通信工具 + if name == "ask_master": + if raw.get("success"): + return f"Team Leader 的回答:\n{raw.get('answer') or ''}" + return raw.get("error") or "向 Team Leader 提问失败" + if name == "ask_other_agent": + if raw.get("success"): + return f"子智能体的回答:\n{raw.get('answer') or ''}" + return raw.get("error") or "向其他子智能体提问失败" + if name == "answer_other_agent": + if raw.get("success"): + return "回复已发送。" + return raw.get("error") or "回复失败" + if name == "list_active_sub_agents": + from utils.tool_result_formatter.agent_context import _format_active_sub_agents_list + return _format_active_sub_agents_list(raw.get("agents") or []) return json.dumps(raw, ensure_ascii=False) diff --git a/static/src/components/chat/MinimalBlocks.vue b/static/src/components/chat/MinimalBlocks.vue index 7bf9665..d6cd22b 100644 --- a/static/src/components/chat/MinimalBlocks.vue +++ b/static/src/components/chat/MinimalBlocks.vue @@ -939,15 +939,13 @@ const getToolIntent = (action: Action) => { }; const renderToolResult = (action: Action) => { - if (personalizationStore.form.enhanced_tool_display) { - const rendered = renderEnhancedToolResult( - action, - props.formatSearchTopic || (() => ''), - props.formatSearchTime || (() => ''), - props.formatSearchDomains || (() => '') - ); - if (rendered) return rendered; - } + const rendered = renderEnhancedToolResult( + action, + props.formatSearchTopic || (() => ''), + props.formatSearchTime || (() => ''), + props.formatSearchDomains || (() => '') + ); + if (rendered) return rendered; const result = action.tool?.result; if (!result) return '
执行中...
'; diff --git a/static/src/components/chat/StackedBlocks.vue b/static/src/components/chat/StackedBlocks.vue index 84d1cd2..9ada265 100644 --- a/static/src/components/chat/StackedBlocks.vue +++ b/static/src/components/chat/StackedBlocks.vue @@ -1,7 +1,11 @@