265 lines
12 KiB
Markdown
265 lines
12 KiB
Markdown
# 多智能体圆桌 · 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/` |
|