Files
deerflow-code/frontend-web/docs/multi-agent-step3-api.md
T
2026-05-23 23:05:01 +08:00

265 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 多智能体圆桌 · Step 3 · 接口与对接说明
> Step 3(`/page/roundtable/planning` 的"最终方案展示"页)对应 `frontend-web/src/roundtable-planning/components/HighFidelityReport.tsx`。当前页面是产品早期的"展厅效果",绝大多数内容是硬编码 mock。本文档梳理**真正需要的数据**、**用哪些接口**、**前后端怎么对接**。
---
## 1. 改造方向(在做什么,不做什么)
### ✅ 保留并对接真实数据
| 区块 | 现状 | 改造后 |
|---|---|---|
| 标题栏 + 生成时间 | 硬编码"2026-05-21 11:01:12" | 用本地 `Date.now()` 或 init/最后一次 leader 完成时刻 |
| "已达成共识"徽章 | 静态显示 | step2 自然收敛(`leaderRes.dispatched.length === 0`)后才显示 |
| **总控最终结论** | 完全是 mock 文本 | 取最后一次 `leaderRes.content`(已经在 step2 拿到了) |
| **各席位最终交付(折叠卡片)** | 当前 step3 没渲染 | 取每个 sub 线程的最后一条 AIMessage 文本 |
| **生成的报告文件清单** | 5 张假"档案" | 真实列出 `outputs/` 目录里的所有产出(markdown / pdf / csv 等) |
| **文件下载** | 假 toast | 用现有 `GET /api/threads/{tid}/artifacts/{path}` |
| "重新起草新规划" | 调用 `setCurrentStep(1)` 重置 | **保留**,含义就是回到 step1 重新开始 |
### ❌ 不做(直接删 / 不接口)
| 区块 | 原因 |
|---|---|
| 4 张 metric 卡片(设计全要素通过率 99.4% / Tier III+ / 终审共识度 42 轮研商 / 90.3分)| 全是按 `budgetLimit` 编出来的"展厅指标",没有真实数据源 |
| 三阶段推进路线图(0-24h / 24-60h / 60-72h 三张卡)| 假设业务必然是 72 小时交付,写死场景,不通用 |
| "采纳理由与最终裁定判词"段 | 整段都是模板话术,没接口能算出"90.3 分总评" |
| "12 轮极限对抗研商" 数据 | 当前没记录"轮次"语义,临时就用 leader 实际轮数代替即可,不值得为它出接口 |
| "签发起草成果并投产运行" 按钮 | 无对应业务动作(不是要部署、不是要审批),删掉 |
| "执行摘要" 段写死的 3 个 bullet | 模板话术,跟实际任务无关 |
最终 step3 页面只保留四块:**头部状态 + 总控结论 + 席位交付列表 + 文件下载清单**,再加底部"重新起草"。
---
## 2. 数据来源全景
```
┌───────────────────────────────────────────────────────────┐
│ step2 全程结束时,前端手里已经有的本地状态 │
├───────────────────────────────────────────────────────────┤
│ coordinatorName: "roundtable-coordinator-xxx" │
│ threadIds: { [agent_name]: thread_id, ... } │ ← /init 返回
│ step2RoundtableDialogues: [...所有气泡, 含每个 sub 最终 │
│ 交付的 content...] │
│ leaderRes.content (最后一次 leader 的 final text) │
└───────────────────────────────────────────────────────────┘
后端权威数据(需要时从后端拉):
- 每个 sub 线程的完整 state → GET /api/threads/{tid}/state
- 单个 outputs 文件内容/下载 → GET /api/threads/{tid}/artifacts/{path}
- outputs 目录文件清单 ❗暂无 → 新增接口(见 §3)
```
**关键判断**:席位最终交付文本 + 总控结论文本 **已经在 step2 内存里**,无需重新调接口。文件清单是**目前唯一缺失**的数据源。
---
## 3. 接口需求
### 3.1 新增(**必需**):列出某 thread 的产出文件
```
GET /api/multi-agent/sessions/artifacts?thread_id=<sub_thread_id>
```
或者更通用、放进 artifacts 路由下:
```
GET /api/threads/{thread_id}/artifacts
```
**为什么需要**:现有 artifacts endpoint 只能"按已知路径下载文件",没有"列出目录"功能。Step 3 需要知道 sub-agent 写到 `/mnt/user-data/outputs/` 下哪些文件,才能在前端列出可下载清单。
**响应**:
```json
{
"thread_id": "<sub_thread_id>",
"files": [
{
"path": "mnt/user-data/outputs/方案设计报告_生产系统安全改造技术方案.md",
"name": "方案设计报告_生产系统安全改造技术方案.md",
"size_bytes": 12480,
"mime_type": "text/markdown",
"modified_at": "2026-05-23T14:32:30.123Z"
},
...
]
}
```
**实现建议**:
- 列 `backend/.deer-flow/users/{user_id}/threads/{thread_id}/user-data/outputs/` 下的文件
- 鉴权复用 `@require_permission("threads", "read", owner_check=True)`
- 路径要做 traversal 保护(虚拟路径仅限 `mnt/user-data/outputs/`)
### 3.2 新增(**可选,体感优化**):一次性聚合 step3 数据
```
POST /api/multi-agent/sessions/summary
Body: {
"main_agent_name": "roundtable-coordinator-xxx",
"thread_ids": { "<agent_name>": "<thread_id>", ... }
}
```
**为什么可选**:不加这个接口的话,前端需要为每个席位发一次 `GET /state` + 一次 `GET /artifacts`(共 7-13 次 round-trip)。加了这个接口,**一次返回所有数据**。
**响应**:
```json
{
"main_agent_name": "roundtable-coordinator-xxx",
"coordinator": {
"thread_id": "...",
"final_text": "...", // 最后一条 AIMessage content
"dispatch_rounds": 3 // leader 派活的轮数(可选展示)
},
"seats": [
{
"agent_name": "roundtable-intelligence",
"thread_id": "...",
"display_name": "情报收集",
"final_text": "...", // 最后一条 AIMessage content
"files": [ // 该席位写到 outputs/ 的文件
{ "path": "...", "name": "...", "size_bytes": ..., "mime_type": "..." }
]
},
...
]
}
```
**取舍**:
- 优点:1 次请求 vs 13 次;体验更顺
- 缺点:等于把 3.1 + N 次 state 读放到一个 endpoint,实现稍复杂
- 推荐:**先做 3.1,3.2 暂缓**;前端用现有 endpoint 串起来够用
### 3.3 复用(**无需改动**)
| Endpoint | 用途 |
|---|---|
| `GET /api/threads/{tid}/state` | 取席位 / 总控线程的完整状态,提取最后一条 AIMessage content |
| `GET /api/threads/{tid}/artifacts/{path}` | 点击文件名预览或下载(已有 `?download=true` 支持) |
> Note: 这两个 endpoint 已经在 `app.gateway.routers.threads` / `app.gateway.routers.artifacts` 里实现,前端 `fetch-client` 也有现成调用方式。
---
## 4. 前后端对接流程
### 4.1 进入 step3 时
```ts
// 触发条件:runOrchestration 自然结束(leaderRes.dispatched.length === 0
// 且无 clarification),用户点"达成共识生成全案成果"按钮 / 自动跳转。
// 前端手里已有:threadIds, coordinatorName, 最后的 leaderRes
// 1. 总控结论文本 — 已在内存,直接用
const consensusText = lastLeaderRes.content;
// 2. 各席位交付 — 已在 step2RoundtableDialogues 里,过滤"子智能体交付"的卡片
const seatDeliveries = step2RoundtableDialogues
.filter(d => d.confidence === "子智能体交付")
.map(d => ({ sender: d.sender, content: d.content }));
// 3. 文件清单 — 调新接口
const filesPerSeat = await Promise.all(
Object.entries(threadIds)
.filter(([name]) => name !== coordinatorName)
.map(async ([name, tid]) => ({
agentName: name,
files: (await fetch(`/api/threads/${tid}/artifacts`).then(r => r.json())).files,
})),
);
```
### 4.2 用户点击文件时
```ts
// 在线预览(mime 是 markdown/text/pdf 自动 inline)
window.open(`/api/threads/${threadId}/artifacts/${encodeURIComponent(filePath)}`);
// 强制下载
window.open(`/api/threads/${threadId}/artifacts/${encodeURIComponent(filePath)}?download=true`);
```
### 4.3 "重新起草新规划" 按钮
```ts
// 跟现在一样,纯前端:清掉 dialogues + 跳 step1
setCurrentStep(1);
setInteractiveMessages(...); // 已有逻辑
setStep2RoundtableDialogues([]);
// 不需要调任何接口
```
---
## 5. 改造前端的具体文件清单
| 文件 | 改动 |
|---|---|
| `frontend-web/src/roundtable-planning/components/HighFidelityReport.tsx` | **大改**。删除四张 metric 卡 / 三阶段路线图 / 论证要件 / 裁定判词;新增结论卡 + 席位折叠列表 + 文件清单 |
| `frontend-web/src/roundtable-planning/api/multi-agent.ts` | 新增 `listSessionArtifacts(threadId)` helper(封装 §3.1 接口) |
| `frontend-web/src/roundtable-planning/pages/RoundtablePlanningPage.tsx` | 把 `threadIds` / `coordinatorName` / `lastLeaderContent` 传进 `HighFidelityReport` 的 props(目前没传) |
---
## 6. 改造后端的具体文件清单
| 文件 | 改动 |
|---|---|
| `offline-backend-20260512/backend/app/gateway/routers/artifacts.py` | 新增 `GET /api/threads/{tid}/artifacts`(不带 path,列目录) |
| `offline-backend-20260512/backend/CLAUDE.md` | 在 artifacts 路由表里增加新条目说明 |
| `offline-backend-20260512/backend/tests/test_artifacts_list.py` | 列目录新接口的单测(覆盖:空目录返回 [];traversal 路径返回 403;不存在 thread 返回 404)|
---
## 7. 实施优先级
| 优先级 | 项 | 备注 |
|---|---|---|
| P0 | 新增 `GET /api/threads/{tid}/artifacts` 列文件接口 | 必需,没它前端无法列文件 |
| P0 | step3 大改:删 mock 区块、加结论/席位/文件三块 | 主要价值改造 |
| P1 | 前端 helper `listSessionArtifacts` | 封装 fetch + 类型 |
| P2 | 单测 `test_artifacts_list.py` | 上线后跟进 |
| P3 | `POST /api/multi-agent/sessions/summary` 聚合接口 | 13 次→1 次,体感优化,**非必需** |
---
## 8. 显式不做的事 ❌
1. **不做"PDF 导出"**:后端没有现成 PDF 渲染管线,要做就是单独的项目。当前页面那个 `导出方案 PDF` 按钮直接删掉,或者保留按钮但点击只触发 `window.print()`(让浏览器自己生成 PDF)。
2. **不做"安全分享"**:项目里已有 `/api/thread-shares`(per-thread 分享)和 `/api/public/shares`(公开页面),但那是针对单个 thread 的,不是多 thread 会话的。要做"多 thread 整合分享"是新功能,远超 step3 的范围。
3. **不做"指标雷达图"**(什么 90.3 分总评):没有真实数据源,强行加只会变成下一个 mock。
4. **不做"签发投产"**:纯展示按钮,不接业务系统,删。
5. **不做"轮次/对抗次数"显示**:除非前端记一下 `dispatch_count`,否则后端没现成统计。如果真要展示,用现成的"派活了几次"代替"对抗 N 轮"。
---
## 9. 验收清单
step3 改造完成后应该能:
- [ ] 看到当前 session 的总控最终结论,markdown 渲染
- [ ] 看到 6 个席位各自的最终交付内容(默认折叠,点击展开)
- [ ] 看到所有 sub-agent 写到 outputs 的文件列表,含文件名、大小、类型 icon
- [ ] 点击文件名能在线预览(markdown / pdf / 图片)
- [ ] 点击下载按钮能下载(带正确文件名)
- [ ] 点"重新起草新规划"能回到 step1 且 step2 内存清空
- [ ] 页面不再包含任何来自 `budgetLimit` 的"假指标"
---
## 10. 路径速查
| 资源 | 位置 |
|---|---|
| Step 3 组件 | `frontend-web/src/roundtable-planning/components/HighFidelityReport.tsx` |
| 父页面 (state 持有者) | `frontend-web/src/roundtable-planning/pages/RoundtablePlanningPage.tsx` |
| 前端 API helper | `frontend-web/src/roundtable-planning/api/multi-agent.ts` |
| 后端 artifacts 路由 | `offline-backend-20260512/backend/app/gateway/routers/artifacts.py` |
| 后端 multi-agent 路由 | `offline-backend-20260512/backend/app/gateway/routers/multi_agent.py` |
| 物理 outputs 目录 | `backend/.deer-flow/users/{user_id}/threads/{thread_id}/user-data/outputs/` |