文档修改
This commit is contained in:
1 parent
dffb8a955b
commit
8c55d4b55d
20 files changed
+1315
-5421
No files matched your search
@@ -1,14 +1,47 @@
|
||||
# 多智能体圆桌研讨 · 后端开发文档
|
||||
|
||||
本文档面向需要维护或扩展"多智能体圆桌研讨"功能后端的开发者。前端 API 速查请看 `multi-agent-api.md`,此文档关注**实现细节、设计取舍、性能特征**。
|
||||
本文档面向需要维护或扩展「多智能体圆桌研讨」功能后端的开发者。前端 API 速查见 `multi-agent-api.md`、`multi-agent-step1-api.md`、`multi-agent-step3-api.md`;推荐弹窗见 `recommend-agents-progress.md`;前端实现见 `multi-agent-frontend-dev.md`。本文关注**实现细节、协议帧、设计取舍**。
|
||||
|
||||
源码内另有中文模块级注释(`app/gateway/routers/intent.py`、`recommend.py`、`multi_agent.py` 及 `clarification_utils.py`),与本文互为补充。
|
||||
|
||||
---
|
||||
|
||||
## 1. 功能定位
|
||||
## 1. 功能定位与三步数据流
|
||||
|
||||
把"任务目标 + 用户约束"丢给一个**协调智能体**,由它通过 `agent_orchestration` 技能调度 6 个固定职能的子智能体(情报收集 / 环境评估 / 方案设计 / 风险审查 / 执行规划 / 总结陈述)协同产出方案,直到协调智能体认为信息足够、停止派活并输出最终结论为止。
|
||||
对应前端页面:`/page/roundtable/planning`(`RoundtablePlanningPage.tsx`)的三个步骤。
|
||||
|
||||
对应前端页面:`/page/roundtable/planning` 的 **Step 2 多智能体研讨**。
|
||||
```
|
||||
Step 1 任务意图理解
|
||||
POST /api/intent/init → 建 thread,绑定内置 roundtable-intent
|
||||
POST /api/intent/stream (多轮) → 澄清对话,终态 [INTENT_READY] + summary
|
||||
|
||||
↓ intentReady { objective, constraints, assumptions }
|
||||
|
||||
推荐弹窗(Step 1 → Step 2 之间,可选)
|
||||
POST /api/recommend/stream → 从候选池选 2–6 个席位,[RECOMMEND_READY] + picks
|
||||
|
||||
↓ selectedAgents[]
|
||||
|
||||
Step 2 多智能体圆桌研讨
|
||||
POST /api/multi-agent/init (SSE) → 协调智能体 + 各席位 thread(席位列表由前端传入)
|
||||
POST /api/multi-agent/run/stream → leader 派活 / special 执行 / 澄清
|
||||
|
||||
↓ lastLeaderContent、各席位交付、hasConsensus
|
||||
|
||||
Step 3 最终方案展示
|
||||
GET /api/threads/{tid}/artifacts → 列出 outputs/ 产出文件
|
||||
GET /api/threads/{tid}/artifacts/{path} → 预览或下载
|
||||
(总控结论、席位文本主要来自 Step 2 前端内存,一般不再调 run/stream)
|
||||
```
|
||||
|
||||
| 步骤 | 前端 `currentStep` | 专属 Router | 内置 Agent |
|
||||
|------|-------------------|-------------|------------|
|
||||
| 1 | 1 | `intent.py` | `roundtable-intent`(固定 id,复用) |
|
||||
| 1→2 | 推荐弹窗 | `recommend.py` | `roundtable-recommender`(每请求新建 thread) |
|
||||
| 2 | 2 | `multi_agent.py` | 协调智能体(每次新建)+ 用户选定的 `roundtable-*` 席位 |
|
||||
| 3 | 3 | `artifacts.py`(通用) | 无新 agent |
|
||||
|
||||
所有接口需 `Authorization: Bearer <JWT>`。Loopback 调用统一 `trust_env=False`,避免本机 `HTTP_PROXY` 把 `127.0.0.1` 指到不可达代理(症状:单次请求卡住 40s+)。
|
||||
|
||||
---
|
||||
|
||||
@@ -17,13 +50,16 @@
|
||||
```
|
||||
offline-backend-20260512/backend/
|
||||
├── app/gateway/
|
||||
│ ├── app.py # 注册 multi_agent.router
|
||||
│ ├── app.py # include_router: multi_agent, intent, recommend
|
||||
│ └── routers/
|
||||
│ └── multi_agent.py # 本功能唯一新增的 router
|
||||
│ ├── multi_agent.py # Step 2:init(SSE) + run/stream
|
||||
│ ├── intent.py # Step 1:init + stream
|
||||
│ ├── recommend.py # Step 1→2:推荐 stream
|
||||
│ └── artifacts.py # Step 3:列出/下载 thread 产出
|
||||
└── .deer-flow/agents/
|
||||
├── roundtable-intelligence/ # 6 个内置子智能体目录
|
||||
│ ├── config.yaml # ├── id / name / description
|
||||
│ └── SOUL.md # └── 角色 prompt
|
||||
├── roundtable-intent/ # Step 1 任务理解(SOUL 含完整工作流,可不挂 Skill)
|
||||
├── roundtable-recommender/ # 推荐智能体
|
||||
├── roundtable-intelligence/ # 默认可选席位(6 个,前端可替换)
|
||||
├── roundtable-environment/
|
||||
├── roundtable-solution-design/
|
||||
├── roundtable-risk-review/
|
||||
@@ -31,329 +67,371 @@ offline-backend-20260512/backend/
|
||||
└── roundtable-summary/
|
||||
```
|
||||
|
||||
**零代码改动构造内置 agent**:6 个目录通过既有的 `_sync_legacy_agents()`(`app/gateway/routers/agents.py`)在下次任意 `/api/agents` 请求时自动 upsert 进 `agents` 表,`user_id IS NULL` 标记为内置。
|
||||
**内置 agent 注册**:各 `roundtable-*` 目录通过 `_sync_legacy_agents()`(`app/gateway/routers/agents.py`)在下次 `/api/agents` 请求时 upsert 进 `agents` 表,`user_id IS NULL` 表示内置。
|
||||
|
||||
**共享工具逻辑**:`packages/harness/deerflow/tools/builtins/clarification_utils.py` 中的 `resolve_allow_multiple()` 被 `intent.py`、`multi_agent.py` 与 `ClarificationMiddleware` 共用,决定选项卡片默认单选还是多选。
|
||||
|
||||
---
|
||||
|
||||
## 3. 接口总览
|
||||
|
||||
路由前缀 `/api/multi-agent`,所有接口需要 `Authorization: Bearer <JWT>`。
|
||||
| 方法 | 路径 | 步骤 | 用途 |
|
||||
|------|------|------|------|
|
||||
| POST | `/api/intent/init` | 1 | 创建意图澄清 thread |
|
||||
| POST | `/api/intent/stream` | 1 | SSE,一轮用户对话 |
|
||||
| POST | `/api/recommend/stream` | 1→2 | SSE,一次性推荐席位 |
|
||||
| POST | `/api/multi-agent/init` | 2 | SSE,并行拉起席位 + 协调智能体 |
|
||||
| POST | `/api/multi-agent/run/stream` | 2 | SSE,leader 或 special 一轮 |
|
||||
| GET | `/api/threads/{thread_id}/artifacts` | 3 | 列出该 thread 的 outputs 文件 |
|
||||
| GET | `/api/threads/{thread_id}/artifacts/{path}` | 3 | 预览/下载单个产出 |
|
||||
|
||||
| 方法 | 路径 | 用途 |
|
||||
|---|---|---|
|
||||
| POST | `/api/multi-agent/init` | 一次性创建协调智能体 + 6 个子席位线程,返回 `thread_ids` 映射 |
|
||||
| POST | `/api/multi-agent/run/stream` | SSE 流式驱动一次 leader 或 special 调用 |
|
||||
|
||||
字段细节见 `multi-agent-api.md`,这里不重复。
|
||||
字段级请求/响应见各 step 的 API 文档;下文只写**后端实现语义**。
|
||||
|
||||
---
|
||||
|
||||
## 4. 内部实现详解
|
||||
## 4. Step 1:任务理解(`intent.py`)
|
||||
|
||||
### 4.1 `/init` 内部流程
|
||||
### 4.1 定位
|
||||
|
||||
```
|
||||
[阶段1] 并行准备 6 个子智能体席位 (asyncio.gather)
|
||||
├─ GET /api/agents/{id} × 6 → 取 name + description
|
||||
└─ POST /api/threads × 6 → 建新 thread
|
||||
轻量包装 Lead Agent 运行时,固定跑内置 `roundtable-intent`。不做 broadcast、不派活;每轮用户消息对应一次 `/stream`。
|
||||
|
||||
[阶段2] 创建协调智能体
|
||||
├─ POST /api/threads → 协调智能体的 thread
|
||||
└─ POST /api/agents (with model + skills=[agent_orchestration] + soul)
|
||||
└─ soul 里包含可调度的 6 个 agent_name + description 列表
|
||||
### 4.2 `POST /api/intent/init`
|
||||
|
||||
1. `_create_thread()` → LangGraph thread
|
||||
2. 返回固定 `agent_id: "roundtable-intent"`(**不**每次新建 agent)
|
||||
|
||||
与 Step 2 `/init` 的区别:无协调智能体、无 `agent_names` 列表。
|
||||
|
||||
### 4.3 `POST /api/intent/stream`
|
||||
|
||||
**上游**:`_run_payload(agent_name=roundtable-intent, excluded_tools=[...])`,硬排除 `agent_orchestration`、`write_file`、`web_search` 等,**仅保留** `ask_clarification` 等澄清能力,防止模型写方案文档。
|
||||
|
||||
**流式**:透传 LangGraph SSE → 解析 `_parse_last_messages(captured)`。
|
||||
|
||||
**终态帧判定顺序(重要)**:
|
||||
|
||||
1. **先**扫描全部 AI 消息中的 `[INTENT_READY]\n\`\`\`json {...}\`\`\``(正则 `_INTENT_READY_PATTERN`)
|
||||
- 命中 → `status: "done"`,`summary` 为解析后的 JSON,`content` 为含标记的整段文本
|
||||
- **优先于** `ask_clarification`,避免「已汇总仍被当成待澄清」
|
||||
|
||||
2. 否则查最后一则带 `tool_calls` 的 AIMessage 是否含 `ask_clarification`
|
||||
- 命中 → `status: "clarification"`,附带结构化字段供前端选项卡片:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "clarification",
|
||||
"content": "<引导语/前言>",
|
||||
"question": "...",
|
||||
"clarification_type": "missing_info",
|
||||
"clarification_context": "...",
|
||||
"options": ["..."],
|
||||
"allow_custom": true,
|
||||
"allow_multiple": false,
|
||||
"summary": null
|
||||
}
|
||||
```
|
||||
|
||||
**协调智能体 SOUL 是动态拼出来的**(`multi_agent.py:216-224`):
|
||||
`allow_multiple` 由 `resolve_allow_multiple(cargs)` 决定:**默认单选**(点击即发送);仅模型显式传 `allow_multiple: true` 时为多选。
|
||||
|
||||
```python
|
||||
soul = (
|
||||
"#核心定位:\n"
|
||||
"用途:根据用户的任务目标,调用其他智能体干活,只负责协调汇总,...\n"
|
||||
"#调度方法:\n"
|
||||
"通过使用agent_orchestration技能调用其他智能体干活,...\n"
|
||||
"#可调度智能体:\n"
|
||||
f"{descriptions}" # ← 6 个子席位的 name+description
|
||||
)
|
||||
3. 否则 → `status: "asking"`(继续对话,无结构化澄清)
|
||||
|
||||
**`summary` 形状**(`done` 时):
|
||||
|
||||
```json
|
||||
{
|
||||
"objective": "一句话目标",
|
||||
"constraints": ["硬约束 1", "..."],
|
||||
"assumptions": ["可选假设"]
|
||||
}
|
||||
```
|
||||
|
||||
由于 `main_agent_name` 由前端每次新建(`roundtable-coordinator-{timestamp36}`),所以协调智能体本身是一次性的、用完即弃。
|
||||
### 4.4 内置 Agent:`roundtable-intent`
|
||||
|
||||
### 4.2 `/run/stream` 内部流程
|
||||
- 配置:`.deer-flow/agents/roundtable-intent/config.yaml` + `SOUL.md`
|
||||
- SOUL 内写清:工作流、`ask_clarification` 用法、`[INTENT_READY]` 格式、禁止派活/写文件
|
||||
- **可不配置 `skills`**;若配置 Skill,SOUL 要求不得偏离澄清与结束协议
|
||||
|
||||
接收 `agent_type` 后走两条分支:
|
||||
---
|
||||
|
||||
#### `agent_type = "leader"` (协调智能体)
|
||||
## 5. 推荐智能体(`recommend.py`,Step 1 → 2)
|
||||
|
||||
### 5.1 定位
|
||||
|
||||
无状态单次调用:每次请求内部 `_create_thread()`,不暴露 `/init`。前端传入 Step 1 的 `intent` + 过滤后的 `candidates`(排除 `roundtable-intent`、`roundtable-recommender`、历史 `roundtable-coordinator-*`)。
|
||||
|
||||
### 5.2 `POST /api/recommend/stream`
|
||||
|
||||
**入参校验**:
|
||||
|
||||
- `intent.objective` 必填
|
||||
- `candidates` 非空,且至少一个有效 `agent_id`
|
||||
|
||||
**Prompt 拼装**(`_build_prompt`):把 objective/constraints/assumptions 与候选池格式化为一条 user message。
|
||||
|
||||
**上游**:`roundtable-recommender`,`excluded_tools` 含 `ask_clarification`、`agent_orchestration` 等(纯判断,不调工具)。
|
||||
|
||||
**终态**:
|
||||
|
||||
- 解析 `[RECOMMEND_READY]\n\`\`\`json {"picks":[{"agent_id","reason"},...]}\`\`\``
|
||||
- `_validate_picks()`:只保留候选池内的 id、去重、数量 clamp 到 2–6
|
||||
- 有效 picks ≥ 2 → `status: "done"`,`picks` 有值
|
||||
- 否则 → `status: "asking"`(前端回退默认席位列表)
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "done",
|
||||
"content": "<自然语言推荐理由>",
|
||||
"picks": [{"agent_id": "roundtable-risk-review", "reason": "..."}]
|
||||
}
|
||||
```
|
||||
|
||||
**前端流式展示**:自然语言理由与 JSON 由 `[RECOMMEND_READY]` 分界;`TitleMiddleware` 产生的标题增量会在客户端过滤,避免污染推荐理由区。
|
||||
|
||||
---
|
||||
|
||||
## 6. Step 2:多智能体研讨(`multi_agent.py`)
|
||||
|
||||
### 6.1 `/init` 内部流程(SSE,非一次性 JSON)
|
||||
|
||||
当前实现为 **流式 init**,便于前端按席位点亮 UI:
|
||||
|
||||
```
|
||||
1. broadcast「总控收到用户消息」到所有非自己线程 (asyncio.gather)
|
||||
2. 拼 RunCreateRequest,转发到 POST /api/threads/{coordinator}/runs/stream
|
||||
asyncio.as_completed(各席位 _prepare_sub_agent)
|
||||
每完成一个 → data: {"event":"seat_ready","agent_name","thread_id","display_name","description"}
|
||||
|
||||
协调智能体:
|
||||
POST /api/threads → coord_thread
|
||||
POST /api/agents(skills=[agent_orchestration],SOUL 动态拼入可调度列表)
|
||||
→ data: {"event":"coordinator_ready","main_agent_name","thread_id"}
|
||||
|
||||
→ data: {"event":"init_done","main_agent_name","thread_ids":{...}}
|
||||
失败 → data: {"event":"error","detail":"..."}
|
||||
```
|
||||
|
||||
**席位列表**:由前端 `agent_names` 传入(推荐弹窗确认后的 2–6 个 id),不再写死 6 个。
|
||||
|
||||
**协调智能体 SOUL**(动态拼接)包含:只负责协调汇总、通过 `agent_orchestration` 派活、`agent_name` 必须用英文 id(不要用中文显示名)。
|
||||
|
||||
### 6.2 `/run/stream` — `agent_type = "leader"`
|
||||
|
||||
```
|
||||
1. (可选)pre_broadcast:总控收到用户消息 → 广播到非自己的 thread
|
||||
2. POST /api/threads/{coordinator}/runs/stream
|
||||
- excluded_tools: ["web_search"]
|
||||
- context.model_name = 用户传的 model
|
||||
3. 流式透传 LangGraph 事件给前端
|
||||
4. 流结束后从最后一帧 values 取最末一条 message
|
||||
5. 解析 message.tool_calls (agent_orchestration 技能调用):
|
||||
- 用 _extract_name_task() 提取 [agent_name, task_text]
|
||||
- 对每个 [agent_name, task]:
|
||||
broadcast「总控给 X 派活,内容为: ...」到所有非派活双方
|
||||
6. 末尾追加状态帧:
|
||||
{
|
||||
"status": [["agent_name", "task"], ...], // 空数组 = 共识达成
|
||||
"content": "<leader 最终文本>",
|
||||
"agent_name": "<coordinator>"
|
||||
}
|
||||
3. 透传 LangGraph 事件
|
||||
4. 解析 messages → tool_calls
|
||||
|
||||
分支 A — ask_clarification:
|
||||
status: "clarification"
|
||||
+ question / options / allow_multiple / clarification_type / clarification_context
|
||||
(空 dispatched 不等于共识,前端展示澄清卡片并暂停自动派活循环)
|
||||
|
||||
分支 B — 有 agent_orchestration 派活:
|
||||
并发 _broadcast 派活消息到相关 thread
|
||||
status: [["sub_agent_id", "task_text"], ...]
|
||||
|
||||
分支 C — 无 tool_calls:
|
||||
status: [] // 共识达成,content 为总控最终文本
|
||||
```
|
||||
|
||||
#### `agent_type = "special"` (子智能体)
|
||||
### 6.3 `/run/stream` — `agent_type = "special"`
|
||||
|
||||
```
|
||||
1. 拼 RunCreateRequest,转发到 POST /api/threads/{sub_agent}/runs/stream
|
||||
- excluded_tools: ["web_search","ask_clarification","present_files","view_image"]
|
||||
2. 流式透传 LangGraph 事件
|
||||
3. 流结束后从最后一帧 values 取交付内容 (content)
|
||||
4. broadcast「子智能体 X 完成 Y 工作,交付内容为: ...」到所有非自己线程
|
||||
5. 末尾追加状态帧:
|
||||
{
|
||||
"status": "子智能体 X 完成 Y 工作",
|
||||
"content": "<交付内容>",
|
||||
"agent_name": "<sub agent>"
|
||||
}
|
||||
1. POST /api/threads/{sub}/runs/stream
|
||||
- excluded_tools: web_search, ask_clarification, present_files, view_image, agent_orchestration
|
||||
2. 透传 + 解析最后 AI 文本
|
||||
3. post_broadcast:子智能体完成交付 → 广播到非自己的 thread
|
||||
4. status: "<完成提示字符串>" 或业务自定义;content 为交付正文;agent_name 为子 agent id
|
||||
```
|
||||
|
||||
### 4.3 _broadcast 机制
|
||||
### 6.4 `_broadcast` 机制
|
||||
|
||||
```python
|
||||
async def _broadcast(client, base, headers, message, agent_threads, skip):
|
||||
targets = [tid for name, tid in agent_threads.items() if name not in skip]
|
||||
await asyncio.gather(*(_append_thread_message(...) for tid in targets))
|
||||
# 对每个 target: GET state → append human message → POST state
|
||||
```
|
||||
|
||||
`_append_thread_message` 对每个目标 thread:
|
||||
目的:各 thread 对话历史里保留「谁派活、谁交付」的群聊上下文。`skip` 通常含发起方与接收方,避免回声。
|
||||
|
||||
```
|
||||
GET /api/threads/{id}/state # 拿当前 messages 列表
|
||||
state.values.messages.append({"type":"human","content":...})
|
||||
POST /api/threads/{id}/state # 写回完整 state
|
||||
```
|
||||
### 6.5 终态帧协议(leader / special / clarification)
|
||||
|
||||
这是**为了让每个子线程的对话历史里都能看到全局上下文**(其他席位说了什么、协调智能体怎么派活的)。子智能体下次被调用时,模型自然能基于这份"群消息回放"做判断。
|
||||
前端 `streamMultiAgent` 用 `isFinalStatusFrame()` 识别:**同时存在 `status` 与 `content`**。
|
||||
|
||||
**关键不变量**:`skip` 列表通常包含发起方和接收方,避免发起方收到回声、接收方收到重复消息。
|
||||
| `status` 类型 | 含义 |
|
||||
|---------------|------|
|
||||
| `[["agent","task"],...]` | 派活列表;`[]` = 共识 |
|
||||
| `"clarification"` | 总控追问,需用户回复后再 leader |
|
||||
| `"子智能体 X 完成..."` | special 完成(字符串) |
|
||||
| `"error"` | 异常 |
|
||||
|
||||
与 Step 1 的 `clarification` 帧字段对齐(`question`、`options`、`allow_multiple` 等),便于复用选项卡片组件。
|
||||
|
||||
---
|
||||
|
||||
## 5. 6 个内置 agent SOUL 设计要点
|
||||
## 7. Step 3:产出文件(`artifacts.py`,通用 Thread API)
|
||||
|
||||
每个 `roundtable-*` 的 SOUL.md 遵循统一结构:
|
||||
Step 3 **没有** 单独的 `multi_agent` 路由;使用既有 artifacts 能力。
|
||||
|
||||
```
|
||||
# {席位名}
|
||||
### 7.1 `GET /api/threads/{thread_id}/artifacts`
|
||||
|
||||
## 角色定位
|
||||
你是"多智能体圆桌研讨"会议的 **{席位}** 席位。会议由"总控协调智能体"统一调度,
|
||||
你只在被派到任务时发言,不需要主动推动议程。
|
||||
|
||||
## 核心职责
|
||||
- ...具体职责...
|
||||
|
||||
## 输出风格
|
||||
- 每次发言以 {emoji} 开头
|
||||
- 第一句直接给结论
|
||||
- 不超过 4 句
|
||||
|
||||
## 边界
|
||||
- 只产出 {本席位输出}
|
||||
- 不替其他席位做 {其他席位输出}
|
||||
- 任务结束后停下,等待总控派下一个任务
|
||||
|
||||
## 示例输出
|
||||
> ...
|
||||
```
|
||||
|
||||
| 席位 | agent_id | emoji | 核心职责 |
|
||||
|---|---|---|---|
|
||||
| 情报收集 | `roundtable-intelligence` | 📢 | 同类案例 + 历史风险预警 |
|
||||
| 环境评估 | `roundtable-environment` | 🌍 | 硬件容量 + 量化承载力 |
|
||||
| 方案设计 | `roundtable-solution-design` | 📐 | 主备方案 + 关键路径 |
|
||||
| 风险审查 | `roundtable-risk-review` | ⚠️ | 合规红线 + 收敛建议 |
|
||||
| 执行规划 | `roundtable-execution-plan` | 📅 | 里程碑 + 资源排布 |
|
||||
| 总结陈述 | `roundtable-summary` | 🎤 | 归纳 + 冲突标记 + 盖印 |
|
||||
|
||||
**为什么不让子智能体自己调度**:所有 SOUL 都明文写了"只在被点到时发言,由总控协调智能体派活"。子智能体不装 `agent_orchestration` 技能。只有协调智能体装。这是单调度源,避免多智能体相互抢调度权造成的死循环。
|
||||
|
||||
---
|
||||
|
||||
## 6. 关键设计决策
|
||||
|
||||
### 6.1 为什么用 HTTP loopback 而不是内部函数调用
|
||||
|
||||
`multi_agent.py` 调用 `/api/threads`、`/api/agents`、`/api/threads/.../runs/stream` 都是用 `httpx` 走 `http://localhost:8001/...` 自身循环回来,而不是直接 `from app.gateway.services import ...`。
|
||||
|
||||
**好处**:
|
||||
- 自动复用 Gateway 的鉴权 + CSRF + permission 中间件,不用再手写
|
||||
- 复用既有路由的请求验证(Pydantic)和错误响应
|
||||
- `Authorization` 头透传,被调端自然知道是哪个用户在调
|
||||
|
||||
**代价**: 每次调用都有一次额外的 TCP+JWT 解析开销(~10-50ms)。但因为 init 已经做了并行优化(A)、broadcast 已经省掉(C),目前总体耗时是可接受的。
|
||||
|
||||
如未来需要进一步优化,可改为直接调用 `app/gateway/services.py` 里的内部函数,绕过 HTTP 一层。
|
||||
|
||||
### 6.2 为什么协调智能体每次新建
|
||||
|
||||
前端在每次进入 step 2 时生成 `roundtable-coordinator-{Date.now().toString(36)}`,作为 `main_agent_name`。
|
||||
|
||||
**好处**:
|
||||
- 不同会话/不同用户/同一用户不同任务之间完全隔离
|
||||
- 协调智能体的对话历史就是本次研讨的全部上下文,不会被上次任务污染
|
||||
- SOUL 不会被"上次的可调度列表"覆盖(虽然现在 6 个席位是固定的)
|
||||
|
||||
**代价**: 每次都要在 `agents` 表插一行,`.deer-flow/agents/<id>/` 下生成新目录。长期会累积大量协调智能体目录,**未来需要清理脚本**(标记 description 以 "你是总控控制协调智能体" 开头的 agent 自动 GC)。
|
||||
|
||||
### 6.3 子智能体席位为什么固定 6 个
|
||||
|
||||
- 圆桌的 6 个角色定位是产品级强约束(情报/环境/方案/风险/执行/总结)
|
||||
- 写在后端 SOUL.md 一处维护,跟其他内置 agent 同等地位
|
||||
- 前端只需要把 6 个 hyphen-case 名作为 `agent_names` 传过来即可
|
||||
|
||||
如果后续要支持"用户自选席位",在前端加 mapping 面板让用户从 `GET /api/agents` 里挑,不动后端。
|
||||
|
||||
### 6.4 为什么 SSE 末尾还要追加一个 `{status:...}` 帧
|
||||
|
||||
LangGraph 原生 SSE 里没有"调度结果"这种语义,前端只能从最后一帧 values 里去翻 tool_calls。如果由前端解析,前后端协议会绑死 LangGraph 内部数据形状,迁移成本高。所以后端在流末尾追加一个**协议层的总结帧**:
|
||||
- 鉴权:`threads:read`,owner 校验
|
||||
- 扫描:`users/{user_id}/threads/{thread_id}/user-data/outputs/`(`collect_outputs_listing`)
|
||||
- 目录不存在 → `files: []`(非 404)
|
||||
|
||||
```json
|
||||
{
|
||||
"status": [["agent_name", "task"], ...] | "<完成提示>",
|
||||
"content": "<最终 AI 文本>",
|
||||
"agent_name": "<本轮 agent>"
|
||||
"thread_id": "...",
|
||||
"files": [
|
||||
{
|
||||
"path": "mnt/user-data/outputs/report.md",
|
||||
"name": "report.md",
|
||||
"size_bytes": 12480,
|
||||
"mime_type": "text/markdown",
|
||||
"modified_at": "2026-05-23T14:32:30.123Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
前端 `streamMultiAgent` helper 通过 `isFinalStatusFrame()` 识别该帧(必须同时有 `status` + `content` 键),与其他 LangGraph 帧区分。
|
||||
前端 `listSessionArtifacts(threadId)` 封装此接口;Step 3 可对协调 thread 与各席位 thread 分别拉取,合并为下载清单。
|
||||
|
||||
### 7.2 `GET /api/threads/{thread_id}/artifacts/{path}`
|
||||
|
||||
- 虚拟路径前缀 `mnt/user-data/outputs/...`
|
||||
- 路径穿越防护;`download=true` 强制附件下载
|
||||
- HTML/XHTML/SVG 等 active content 始终按下载处理
|
||||
|
||||
**总控结论、各席位最终段落**:主要来自 Step 2 已缓存的 `lastLeaderContent`、`step2RoundtableDialogues`;无需为 Step 3 新增聚合接口(可选优化见 `multi-agent-step3-api.md` 中的 `sessions/summary` 草案)。
|
||||
|
||||
---
|
||||
|
||||
## 7. 性能特征
|
||||
## 8. 内置席位 SOUL 设计要点
|
||||
|
||||
### 7.1 已应用的优化
|
||||
每个 `roundtable-*`(除 intent / recommender)遵循统一结构:角色定位、核心职责、输出风格(emoji + 简短结论)、边界(只产出本席位、等总控派活)。
|
||||
|
||||
| ID | 优化 | 收益 |
|
||||
|---|---|---|
|
||||
| **A** | `/init` 阶段 1 并行化(`asyncio.gather`) | 阶段 1 从 2-4s 降到 0.3-0.7s |
|
||||
| **C** | 删除 `/init` 末尾的 intro 广播(SOUL 已含定位) | 阶段 3 整段消失,省 1-2s |
|
||||
| 席位 | agent_id | emoji | 核心职责 |
|
||||
|------|----------|-------|----------|
|
||||
| 情报收集 | `roundtable-intelligence` | 📢 | 同类案例 + 历史风险 |
|
||||
| 环境评估 | `roundtable-environment` | 🌍 | 硬件容量 + 承载力 |
|
||||
| 方案设计 | `roundtable-solution-design` | 📐 | 主备方案 + 关键路径 |
|
||||
| 风险审查 | `roundtable-risk-review` | ⚠️ | 合规红线 + 收敛建议 |
|
||||
| 执行规划 | `roundtable-execution-plan` | 📅 | 里程碑 + 资源 |
|
||||
| 总结陈述 | `roundtable-summary` | 🎤 | 归纳 + 冲突标记 |
|
||||
|
||||
应用前 `/init` 耗时 **3-8s**,应用后 **0.5-1.2s**。
|
||||
|
||||
### 7.2 仍然存在的开销点
|
||||
|
||||
| 调用 | 单次耗时 | 主因 |
|
||||
|---|---|---|
|
||||
| `GET /api/agents/{id}` | 50-300ms | 每次触发 `_sync_legacy_agents()` 扫整个 `.deer-flow/agents/` 目录(目前 30+ 项) |
|
||||
| `POST /api/threads` | 100-200ms | LangGraph thread 表插入 + 默认 state 初始化 |
|
||||
| `POST /api/agents` | 100-300ms | 文件系统写入 SOUL.md + config.yaml + agents 表 |
|
||||
| `GET/POST /state` | 50-150ms × 2 | checkpointer 读写 |
|
||||
| `POST /runs/stream` 启动 | 50-100ms | RunManager + StreamBridge 初始化 |
|
||||
| LLM 推理 | **数百毫秒~数十秒** | 取决于 model + 上下文长度 |
|
||||
|
||||
**LLM 推理才是绝对大头**,基础设施开销在十几个百分点以内。
|
||||
|
||||
### 7.3 未来可探索
|
||||
|
||||
- **B**(已识别未做): broadcast 跳过 GET-state,直接 POST 新建态。但 POST 需要构造完整的 state shape,改造成本不小。
|
||||
- **D**(已识别未做): loopback HTTP → 内部函数调用。代码改动大,但能省掉 ~20-30 次 HTTP 解析开销。
|
||||
子智能体**不**安装 `agent_orchestration`;仅协调智能体安装,保证单调度源。
|
||||
|
||||
---
|
||||
|
||||
## 8. 依赖与扩展点
|
||||
## 9. 关键设计决策
|
||||
|
||||
### 8.1 `agent_orchestration` 技能
|
||||
### 9.1 HTTP loopback
|
||||
|
||||
协调智能体强依赖此技能,SOUL 里硬编码:
|
||||
三个 router 均通过 `httpx` 调本机 Gateway(`127.0.0.1`),复用鉴权与 LangGraph 路由,代价是额外 HTTP 开销;init 已用 `asyncio.as_completed` 并行化席位准备。
|
||||
|
||||
```
|
||||
通过使用agent_orchestration技能调用其他智能体干活,使用技能时传入agent_name和需要做的活
|
||||
```
|
||||
### 9.2 协调智能体每次新建
|
||||
|
||||
技能的实际派活产物是一个形如 `python3 scripts/agent_orchestration.py "agent_name" "task"` 的 shell 调用文本,后端用正则提取:
|
||||
`main_agent_name` 形如 `roundtable-coordinator-{timestamp36}`,会话隔离;长期会累积 agent 目录,需后续 GC 策略。
|
||||
|
||||
```python
|
||||
_TOOL_CALL_PATTERN = re.compile(r'\.py\s+"([^"]*)"\s+"([^"]*)"')
|
||||
```
|
||||
### 9.3 澄清协议统一
|
||||
|
||||
**若该技能被删除/改名**,leader 流程会拿不到 `tool_calls`,直接走"无派活=共识达成"分支。注意 `skills/` 下必须保留此技能。
|
||||
Step 1 与 Step 2 leader 均通过 `ask_clarification` + 终态 `clarification` 帧驱动选项卡片;`[INTENT_READY]` / 派活列表 / `[]` 各自表示阶段结束语义,互不混用。
|
||||
|
||||
### 8.2 模型选择
|
||||
### 9.4 选项单选默认
|
||||
|
||||
前端通过 `GET /api/models` 拿到列表,在 `model` 字段传 `Model.name` (与 `config.yaml.models[].name` 对应)。
|
||||
`resolve_allow_multiple()`:**未传则 false**(单选、点击发送);仅显式 `allow_multiple: true` 时多选。降低模型把 `missing_info` 全标成多选导致 UX 混乱的概率。
|
||||
|
||||
- `/init` 的 `model` 字段 → 协调智能体的默认模型 (写进 agents 配置)
|
||||
- `/run/stream` 的 `model` 字段 → 该次具体运行用什么模型 (写进 `context.model_name`)
|
||||
### 9.5 SSE 末尾状态帧
|
||||
|
||||
前端允许"逐席位覆盖模型",由前端在 special 调用时按 `agentConfigs[seat].model` 传入。后端不感知"席位"概念,只看到 `model` 字段。
|
||||
|
||||
### 8.3 自定义新席位
|
||||
|
||||
如果要加第 7 个席位(比如"成本测算"):
|
||||
|
||||
1. `.deer-flow/agents/roundtable-cost/` 加 `config.yaml` + `SOUL.md`
|
||||
2. 前端 `RoundtablePlanningPage.tsx` 的 `ROUNDTABLE_ROLE_TEMPLATES` + `AGENT_NAME_TO_ROLE` 加映射
|
||||
3. 圆桌可视化(圆圈环形图)加一个气泡 + 头像
|
||||
|
||||
**后端代码完全不需要改**。
|
||||
LangGraph 原生流不含「派活结果 / 意图 summary / 推荐 picks」;各 router 在流末追加 JSON `data:` 帧,前端与 LangGraph 帧区分解析。
|
||||
|
||||
---
|
||||
|
||||
## 9. 故障排查
|
||||
## 10. 性能特征
|
||||
|
||||
| 阶段 | 已做优化 | 典型耗时 |
|
||||
|------|----------|----------|
|
||||
| Step 2 `/init` | 席位 `as_completed` + 去掉 intro 广播 | 约 0.5–1.2s(视席位数) |
|
||||
| LLM `/run/stream` | — | 数百 ms~数十秒(主因) |
|
||||
| Step 1/推荐 | 单 thread,无 broadcast | 与模型相关 |
|
||||
|
||||
仍存在的开销:`GET /api/agents/{id}` 触发 legacy sync 扫目录、`GET/POST /state` 广播等。未来可考虑 loopback 改内部函数调用(见原 B/D 优化项)。
|
||||
|
||||
---
|
||||
|
||||
## 11. 依赖与扩展点
|
||||
|
||||
### 11.1 `agent_orchestration` 技能
|
||||
|
||||
协调智能体派活依赖此技能;正则 `_TOOL_CALL_PATTERN` 从 tool 输出提取 `agent_name` 与 task。技能缺失时 leader 易变成「无 tool_calls → 空 status → 前端误判共识」。
|
||||
|
||||
### 11.2 模型
|
||||
|
||||
`model` 写入 run 的 `context.model_name`;前端可按席位覆盖 special 调用的模型。
|
||||
|
||||
### 11.3 新增席位
|
||||
|
||||
1. `.deer-flow/agents/roundtable-xxx/` 增加 `config.yaml` + `SOUL.md`
|
||||
2. 前端候选池 / 推荐逻辑纳入该 id
|
||||
3. Step 2 `agent_names` 传入即可 — **multi_agent 路由无需改**
|
||||
|
||||
### 11.4 新增 Step 1 工具
|
||||
|
||||
若需新工具,须同步修改 `intent.py` 的 `excluded_tools` 白名单逻辑(当前为黑名单,仅 `ask_clarification` 实际可用)。
|
||||
|
||||
---
|
||||
|
||||
## 12. 故障排查
|
||||
|
||||
| 现象 | 可能原因 | 排查 |
|
||||
|---|---|---|
|
||||
| `/init` 500 "create coordinator agent failed" | `agent_orchestration` 技能不存在 / config.yaml 写入失败 | 看后端日志 `multi_agent` logger;检查 `skills/` 目录 |
|
||||
| `/run/stream` 502 "upstream returned HTTP 4xx" | JWT 过期 / 转发到上游时鉴权挂了 | 前端检查 Authorization 头是否带上 |
|
||||
| leader 永远 `status: []` 立即结束 | 协调智能体没产生 `tool_calls`,直接给文本回答 | 协调智能体 SOUL 不够强、或模型不支持工具调用,换 model |
|
||||
| special 内容为空 | 子智能体被 `excluded_tools` 限制到无可用工具 / 模型无文本输出 | 看上游 `runs/stream` 的 values 帧 messages.last.content |
|
||||
| 进入 step 2 卡住转圈 | `/init` 超时或失败 | F12 Network 看 `/api/multi-agent/init` 状态码与响应体 |
|
||||
|------|----------|------|
|
||||
| Step 1 一直有澄清、无法进入 Step 2 | 未产出 `[INTENT_READY]` 或终态被 `clarification` 覆盖 | 看 SOUL、最后一帧 `status`;确认 intent 先扫 READY |
|
||||
| 选项全是多选 | 旧前端默认 `allow_multiple !== false` | 确认 `allow_multiple` 字段与 `resolve_allow_multiple` |
|
||||
| Step 2 init SSE `error` | 协调 agent 创建失败 / agent_orchestration 缺失 | Network + 后端 `multi_agent` 日志 |
|
||||
| leader 立即 `status: []` | 未调用 orchestration 技能 | 换模型或加强 SOUL |
|
||||
| 推荐 picks 为空 | 未输出 `[RECOMMEND_READY]` 或 picks 不在候选池 | 看 recommend 终态帧 |
|
||||
| Step 3 无文件 | 子 agent 未写 outputs | `GET .../artifacts` 对子 thread_id 逐个查 |
|
||||
|
||||
### 服务端日志关键字
|
||||
### 日志关键字
|
||||
|
||||
```bash
|
||||
# multi_agent 路由的日志
|
||||
grep "multi_agent" .runtime/logs/*.log
|
||||
|
||||
# 派活解析失败
|
||||
grep "intent stream" .runtime/logs/*.log
|
||||
grep "multi_agent\|multi-agent" .runtime/logs/*.log
|
||||
grep "recommend stream" .runtime/logs/*.log
|
||||
grep "dispatch failed" .runtime/logs/*.log
|
||||
|
||||
# 广播失败(通常无害)
|
||||
grep "update state .* failed" .runtime/logs/*.log
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. 测试入口
|
||||
|
||||
后端目前没有针对本功能的单测。建议补:
|
||||
## 13. 测试建议
|
||||
|
||||
```
|
||||
backend/tests/test_multi_agent.py
|
||||
- test_init_creates_threads_for_all_subagents
|
||||
- test_init_creates_coordinator_with_orchestration_skill
|
||||
- test_init_returns_400_when_agent_names_empty
|
||||
- test_stream_leader_emits_status_frame_with_content
|
||||
- test_stream_special_emits_status_frame_with_content
|
||||
- test_extract_name_task_parses_orchestration_skill_output
|
||||
- test_broadcast_skips_listed_agents
|
||||
backend/tests/test_intent.py # init、INTENT_READY、clarification 优先级
|
||||
backend/tests/test_recommend.py # picks 校验、候选池过滤
|
||||
backend/tests/test_multi_agent.py # init SSE 事件、leader/special 终态、clarification
|
||||
```
|
||||
|
||||
可用 `httpx.MockTransport` 拦截 loopback 请求,不依赖真实 Gateway。
|
||||
可用 `httpx.MockTransport` 模拟 loopback,不依赖真实 Gateway。
|
||||
|
||||
---
|
||||
|
||||
## 11. 路径速查
|
||||
## 14. 路径速查
|
||||
|
||||
| 资源 | 路径 |
|
||||
|---|---|
|
||||
| 路由实现 | `offline-backend-20260512/backend/app/gateway/routers/multi_agent.py` |
|
||||
| 路由注册 | `offline-backend-20260512/backend/app/gateway/app.py`(`include_router(multi_agent.router)`) |
|
||||
| 6 个内置 agent | `offline-backend-20260512/backend/.deer-flow/agents/roundtable-*/` |
|
||||
| `agent_orchestration` 技能定义 | `skills/public/agent_orchestration/`(若存在) |
|
||||
| 前端 API helper | `frontend-web/src/roundtable-planning/api/multi-agent.ts` |
|
||||
|------|------|
|
||||
| Step 1 路由 | `offline-backend-20260512/backend/app/gateway/routers/intent.py` |
|
||||
| 推荐路由 | `offline-backend-20260512/backend/app/gateway/routers/recommend.py` |
|
||||
| Step 2 路由 | `offline-backend-20260512/backend/app/gateway/routers/multi_agent.py` |
|
||||
| Step 3 列出/下载 | `offline-backend-20260512/backend/app/gateway/routers/artifacts.py` |
|
||||
| 路由注册 | `offline-backend-20260512/backend/app/gateway/app.py` |
|
||||
| 澄清单选/多选 | `packages/harness/deerflow/tools/builtins/clarification_utils.py` |
|
||||
| 任务理解 Agent | `.deer-flow/agents/roundtable-intent/` |
|
||||
| 推荐 Agent | `.deer-flow/agents/roundtable-recommender/` |
|
||||
| 圆桌席位 | `.deer-flow/agents/roundtable-*/` |
|
||||
| 前端 Step 1 API | `frontend-web/src/roundtable-planning/api/intent.ts` |
|
||||
| 前端推荐 API | `frontend-web/src/roundtable-planning/api/recommend.ts` |
|
||||
| 前端 Step 2 API | `frontend-web/src/roundtable-planning/api/multi-agent.ts` |
|
||||
| 前端页面 | `frontend-web/src/roundtable-planning/pages/RoundtablePlanningPage.tsx` |
|
||||
| 前端 API 速查 | `frontend-web/docs/multi-agent-api.md` |
|
||||
| API 速查 | `frontend-web/docs/multi-agent-api.md` |
|
||||
| Step 1 API 说明 | `frontend-web/docs/multi-agent-step1-api.md` |
|
||||
| Step 3 API 说明 | `frontend-web/docs/multi-agent-step3-api.md` |
|
||||
Reference in new issue
Block a user