22 KiB
Workflow Studio:Coze 独立前端对接 DeerFlow 后端适配开发文档
状态:待实施
日期:2026-08-29
前端范围:F:/react01/code-stutsiu/frontend/apps/coze-studio/src/workflow-standalone/
后端范围:offline-backend-20260512/backend/
关联文档:
- docs/WORKFLOW_STUDIO_BACKEND_DEV_ZH.md:工作流运行时总体设计与已有能力。
- docs/WORKFLOW_STUDIO_BACKEND_REMEDIATION_ZH.md:安全、并发、租约与事件持久化整改。
本文只处理“当前独立 Coze 页面与真实 DeerFlow 后端之间”的适配,不重新设计工作流运行内核。
1. 目标与结论
当前独立页的交互、三栏布局、资源卡、运行历史、SSE reducer 与 mock 演示已可用,但其保存、校验、发布和部分运行事件仍使用 mock 协议。真实 DeerFlow 后端采用 WorkflowGraph v1.0 和持久化运行时,两者不能直接互换。
本次适配目标:
- Coze 画布能够保存、重新加载、校验、发布,并由 DeerFlow 运行。
- 画布上的 Agent、Skill、SQL、HTTP、Code、Input、Output 节点都有真实、可审计的执行配置。
- 左侧对话运行和右侧执行过程能从同一条 DeerFlow SSE 事件流稳定恢复工具步骤、人工介入与文件制品。
- 不把 Coze 的内部数值节点类型、任意画布 JSON、凭据明文或完整工具参数泄漏到运行时协议中。
结论:不能仅在现有 POST /api/workflows/{id}/runs 中增加几个兼容字段。必须先建立“画布表示”和“执行图表示”的明确边界;否则保存成功的流程仍无法发布或运行。
2. 当前差异清单
| 编号 | 当前独立页行为 | DeerFlow 当前行为 | 影响 | 所属 |
|---|---|---|---|---|
| A01 | 保存 body 为 schemaJson,读取 workflowId / draftRevision / schemaJson | 草稿接口要求 graph,读取返回 id / draft_graph / draft_revision | 自动保存、冲突恢复、发布均无法真实联调 | 前端主改,后端提供工作室 DTO |
| A02 | 校验期待 valid 字段 | 后端返回 ok 字段 | 前端始终判定校验不通过 | 前后端契约统一 |
| A03 | Coze JSON 被原样持久化 | 后端运行时只接受 WorkflowGraph v1.0 | Coze 数值节点类型不可执行 | 前端编译,后端持久化与验证 |
| A04 | 拖入资源只预填节点标题 | Agent / Skill 执行器要求 agentId、skillNames、promptTemplate 等 | 资源卡看起来可用,真实运行必失败 | 前端表单 + 后端发布校验 |
| A05 | 工具事件读取 name、status | 后端发送 toolName、ok | 工具步骤丢失,失败状态错误 | 后端规范化,前端兼容旧字段 |
| A06 | 人工介入读取顶层 prompt / formSchema | 后端放在 pendingInput 内 | 介入卡没有问题文本和表单 | 后端事件扁平化,前端兼容 |
| A07 | 文件预览请求单个 artifact 内容接口 | 后端只提供制品列表 | 文件卡可见但无法打开或下载 | 后端新增制品内容接口 |
| A08 | 资源 hook 请求 credentials 目录 | 后端没有该路由 | 整个资源加载 Promise.all 可能显示失败 | 后端补只读引用目录,或前端暂停请求 |
| A09 | UI 可选“当前草稿试运行”和 mock 场景 | 后端忽略 target / scenario,只运行发布版本 | 用户以为执行草稿,实际执行旧版本 | 前端先隐藏;后端后续可实现草稿快照 |
| A10 | 连续输入会创建新的 run | 后端无 conversation / turn 模型 | 不是 ChatDev 式多轮会话 | 二期可选能力 |
3. 必须冻结的总体设计
3.1 两种表示,两个职责
同一工作流必须保留两份有明确用途的数据,不能让任何一份冒充另一份。
| 数据 | 责任方 | 用途 | 是否可直接执行 |
|---|---|---|---|
| canvasSchemaJson | Coze 前端产生;后端仅作为受限 JSON 文档保存 | 画布恢复、节点位置、Coze 表单内部字段、编辑体验 | 否 |
| executionGraph | 前端适配器编译;后端以 WorkflowGraph 校验并版本化 | 发布校验、权限检查、调度、恢复、SSE、审计 | 是 |
约束:
- DeerFlow harness 只接收 executionGraph,绝不解析 Coze StandardNodeType 或 Coze 内部服务字段。
- canvasSchemaJson 不进入 SSE,不进入运行快照,不作为表达式或节点配置来源。
- 发布版本至少冻结 executionGraph;建议同时保存其来源 canvasSchemaJson 的哈希,便于审计与排错。
- Coze 到 WorkflowGraph 的逐节点转换放在前端 adapters/coze-canvas-adapter.ts 或其同级编译器中。后端不耦合 Coze 的数值节点类型。
- 后端应拒绝“只有 canvasSchemaJson、没有合法 executionGraph”的发布请求,不能静默将其当作空图。
3.2 推荐的工作室适配层
保持现有 /api/workflows 的标准运行时 API 语义,不在其核心请求中混入 Coze 专属字段。新增一层仅面向独立页的 studio facade:
GET /api/workflows/{workflow_id}/studio-document
PUT /api/workflows/{workflow_id}/studio-draft
POST /api/workflows/{workflow_id}/studio-validate
POST /api/workflows/{workflow_id}/studio-publish
现有以下端点继续作为运行时和其他客户端的标准接口:
POST /api/workflows/{workflow_id}/runs
GET /api/workflows/{workflow_id}/runs
GET /api/workflows/runs/{run_id}/events
GET /api/workflows/runs/{run_id}/stream
这样既避免污染通用 API,也让前端能获得它需要的 camelCase DTO。
4. Studio facade API 契约
4.1 获取文档
请求:
GET /api/workflows/{workflow_id}/studio-document
响应:
{
"workflowId": "wf_001",
"name": "周报生成",
"draftRevision": 12,
"publishedVersionId": "ver_009",
"canvasSchemaJson": "{...}",
"executionGraph": { "schemaVersion": "1.0", "id": "wf_001", "nodes": [], "edges": [] },
"updatedAt": "2026-08-29T10:00:00+08:00"
}
要求:
- 从现有 definition 的 id、draft_revision、draft_graph、published_version_id 等字段适配生成。
- canvasSchemaJson 为空时返回空画布 JSON,而不是 null。
- 权限沿用现有 owner/admin 规则。
- 不返回 DSN、HTTP 请求头、resume token、完整 agent 配置或任何私密字段。
4.2 保存草稿
请求:
PUT /api/workflows/{workflow_id}/studio-draft
{
"expectedRevision": 12,
"canvasSchemaJson": "{...}",
"executionGraph": {
"schemaVersion": "1.0",
"id": "wf_001",
"nodes": [],
"edges": []
}
}
响应:
{ "revision": 13, "updatedAt": "2026-08-29T10:01:00+08:00" }
规则:
-
先做 WorkflowGraph 结构解析;无法解析返回 400,错误码 WORKFLOW_SCHEMA_INVALID。
-
画布 JSON 只做大小、JSON 格式、深度和危险字段控制,不做执行解释。
-
canvasSchemaJson 与 executionGraph 必须在同一次 CAS 更新中落库,不能出现画布和执行图不同 revision。
-
revision 冲突返回 409:
{ "code": "WORKFLOW_DRAFT_CONFLICT", "message": "草稿版本冲突,请刷新后重试或另存副本", "currentRevision": 13 } -
不建议让旧 PUT /draft 同时接受 schemaJson 与 graph。若为灰度必须暂时兼容,兼容逻辑只能位于 facade 路由,且必须在一个发布周期后删除。
4.3 校验与发布
校验请求:
POST /api/workflows/{workflow_id}/studio-validate
{ "executionGraph": { ... } }
校验响应:
{
"valid": false,
"issues": [
{
"code": "WORKFLOW_RESOURCE_MISSING",
"nodeId": "agent_writer",
"field": "config.agentId",
"message": "未选择可访问的智能体"
}
],
"graphHash": null
}
发布请求:
POST /api/workflows/{workflow_id}/studio-publish
{ "expectedRevision": 13, "changeNote": "配置报告智能体" }
发布响应:
{ "versionId": "ver_010", "publishedAt": "2026-08-29T10:02:00+08:00" }
发布必须复用标准发布版本表和不可变 graph 快照。facade 只负责 DTO 命名、画布关联和调用标准发布服务,不能复制一套版本逻辑。
5. 后端实施项
5.1 持久化 canvas schema 与草稿原子更新
现有 workflow_definitions 只有 draft_graph_json。新增独立字段,例如 draft_canvas_schema_json;不要把 Coze 画布塞进 draft_graph_json。
需要修改:
| 路径 | 改动 |
|---|---|
| packages/harness/deerflow/persistence/workflows/model.py | WorkflowDefinitionRow 增加 draft_canvas_schema_json;如需审计可增加 published_canvas_schema_hash |
| packages/harness/deerflow/persistence/migrations/versions/ | 新增 Alembic migration,存量行默认 {} |
| packages/harness/deerflow/persistence/workflows/base.py | save_studio_draft 或扩展 save_draft 的原子接口,输入两份文档和 expected_revision |
| packages/harness/deerflow/persistence/workflows/sql.py | 单条 CAS UPDATE 同时更新两份文档、revision、updated_at |
| packages/harness/deerflow/persistence/workflows/memory.py | 同步实现,保证测试环境语义一致 |
禁止:
- 先保存 canvas、后保存 graph 的两次写入。
- 在 SQL store 之外用“先读 revision、再无条件写”的方式实现 CAS。
- 把 canvasSchemaJson 输出到运行事件或 run context。
5.2 Studio facade 路由与 DTO
新增 app/gateway/routers/workflow_studio.py,负责:
- studio-document、studio-draft、studio-validate、studio-publish 四个端点。
- camelCase DTO 与现有 persistence snake_case 字段之间的映射。
- 统一错误结构和 owner/admin 鉴权。
- 调用通用 WorkflowGraph 结构校验与 app 层资源校验。
在 app/gateway/app.py 或当前工作流 router 注册位置挂载该 router。不要让 packages/harness/deerflow 导入 app.gateway。
前端移交项:
- api/workflows.ts 改为调用 studio facade。
- coze-canvas-adapter.ts 产出 executionGraph,不再只做 JSON.stringify。
- 画布新增或修改节点时,同时更新可编译的节点配置。
5.3 发布前资源和权限校验
现有 validator 支持 resource_checker 钩子,但通用 harness 不应直接导入应用层的 agent、skill、data-source store。应在 app 层构造 checker 后传入。
在 studio-validate、studio-publish 和标准 publish 入口都执行:
| 节点 | 必检字段 | 后端校验 |
|---|---|---|
| agent | agentId、promptTemplate、responseSchema | 智能体存在、调用者可见、提示词非空、输出 schema 合法 |
| skill | mode、agentId、skillNames、promptTemplate | 技能存在且启用;agent_skill 必须属于智能体白名单;callable_skill 未开放时明确拒绝 |
| sql_read | dataSourceId、queryTemplate、parameters | 数据源可见、只读、表白名单、SQL 解析与 maxRows 上限 |
| http | dataSourceId 或 credentialRef、method、path、headers 映射 | 资源可见、方法白名单、URL/内网策略、凭据引用可用 |
| code | language、source、timeoutSeconds、output mapping | 沙箱已启用、语言白名单、超时和输出目录限制 |
| output | schema、field mapping | 输出字段可解析,输出 schema 可校验 |
| subworkflow | workflowId、versionId | 子工作流已发布、调用者有权限、深度限制 |
校验必须返回 nodeId 与字段路径,便于前端定位 Coze 节点表单。运行时仍保留二次校验,防止资源被禁用或权限在发布后撤销。
5.4 资源目录与凭据引用
保留现有:
- app/gateway/routers/workflow_resources.py:Agent、Skill、数据源目录。
- app/gateway/routers/workflow_data_sources.py:SQL/HTTP 数据源创建、加密与只读策略。
补充:
GET /api/workflows/resources/credentials
响应只允许:
{
"credentials": [
{
"credentialId": "http_source_001",
"name": "CRM 接口密钥",
"type": "http_headers",
"sourceKind": "http"
}
]
}
绝不返回 headers、token、DSN、加密密文或可逆提示。若一期 UI 没有凭据选择器,前端必须停止请求该端点,不能让一个 404 使整个资源面板显示“加载失败”。
5.5 运行目标、输入 schema 与草稿试运行
一期决策:
- POST /runs 只接受发布版本或明确的 versionId。
- 前端隐藏“当前草稿(试运行快照)”和 mock scenario 选择器。
- 后端若收到 target=draft,返回 422 WORKFLOW_DRAFT_RUN_UNSUPPORTED,禁止静默回退到发布版本。
二期若需要草稿试运行,新增明确的 test-run API,而不是篡改正式 runs:
POST /api/workflows/{workflow_id}/test-runs
流程为:CAS 读取草稿 → 生成不可变 test snapshot → 记录 snapshot id → 运行 → 保留期清理。运行记录必须注明 test=true,不能被误认为发布版本运行。
运行输入:
- studio-document 响应必须带 inputSchema。
- 前端根据 inputSchema 渲染输入,而非永远发送 input.query。
- POST /runs 继续使用后端 JSON Schema 验证;错误返回 field/path/expected,前端在表单附近展示。
5.6 SSE 事件契约收敛
后端事件是前端执行过程唯一事实来源。所有事件须先持久化再广播,seq 单调递增且可 REST 重放。
规范化以下 payload:
node.tool.started
{
"name": "web_search",
"toolCallId": "call_001",
"title": "联网检索",
"args": { "path": "/mnt/user-data/outputs/report.md" }
}
node.tool.finished
{
"name": "web_search",
"toolCallId": "call_001",
"status": "succeeded",
"summary": "获得 12 条结果"
}
run.awaiting_input
{
"resumeToken": "one-time-token",
"nodeId": "review",
"prompt": "请确认是否发布",
"formSchema": { "type": "object", "properties": {} },
"actions": ["approve", "reject"]
}
artifact.created
{
"artifactId": "artifact_001",
"name": "weekly-report.docx",
"kind": "file",
"mimeType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"sizeBytes": 1234
}
规则:
- 工具字段统一为 name;不要只发 toolName。
- 失败工具必须明确 status=failed 与脱敏 error。
- pendingInput 可以继续存储在数据库,但对外事件须扁平化为 prompt、formSchema、actions。
- agent 节点继续发送安全展示元数据 agent、parallelGroupId、roundIndex;不发送系统提示词、技能白名单、凭据和完整配置。
- 不将每个模型 token 当作 aria/live 或持久化事件;继续使用节流后的 node.output.delta。
- 事件类型表若仍不含 node.tool.input_delta,应从前端 mock 中删除该依赖,或后端正式实现该事件;不能只有 mock 有。
涉及路径:
| 路径 | 改动 |
|---|---|
| packages/harness/deerflow/workflows/nodes/agents.py | on_tool 输出 name、status、最小安全 args |
| app/gateway/workflow_executor.py | run.awaiting_input 对外 payload 扁平化 |
| packages/harness/deerflow/workflows/events.py | 冻结事件类型与 payload schema 样例 |
| app/gateway/routers/workflow_runs.py | REST 重放与 SSE 输出保持同一 envelope |
| packages/harness/deerflow/workflows/runtime/engine.py | 保持 node.started 的 agent / parallelGroupId / roundIndex 安全投影 |
5.7 制品内容、预览和下载
新增:
GET /api/workflows/runs/{run_id}/artifacts/{artifact_id}
GET /api/workflows/runs/{run_id}/artifacts/{artifact_id}/download
内容接口返回受限预览:
{
"artifactId": "artifact_001",
"name": "weekly-report.md",
"kind": "markdown",
"mimeType": "text/markdown",
"text": "# 周报",
"sizeBytes": 2048
}
策略:
- 每次读取先校验 run owner/admin,再校验 artifact 属于该 run。
- 文本、JSON、表格只返回大小受限预览;大文件返回 truncated=true。
- 二进制文件不塞入 JSON;download 接口以附件或受控 inline 响应流返回。
- 路径只能从已登记的 artifact id 解析,禁止接收任意 sandbox 路径。
- 制品存储生命周期沿用现有 sandbox/artifact 机制;工作流表只存引用与元数据。
涉及路径:
- app/gateway/routers/workflow_runs.py:新增两个路由。
- packages/harness/deerflow/persistence/workflow_runs/*:按 run_id + artifact_id 查询的 store 方法。
- app/gateway/routers/artifacts.py:仅可参考现有 thread artifact 的鉴权和文件响应,不可直接跨 thread 复用。
5.8 取消状态和事件游标
统一运行状态:
| 后端状态 | 前端显示 | 说明 |
|---|---|---|
| queued | 排队中 | 已入队未领取 |
| running | 运行中 | 正在执行 |
| awaiting_input | 等待人工介入 | 暂停并持有一次性 token |
| cancel_requested | 取消中 | 等待运行器确认,非终态 |
| completed / failed / cancelled | 终态 | SSE 停止重连 |
后端工作:
- _public_run 输出 lastSeq 时映射 persistence 的 next_event_seq,不能读取不存在的 last_seq。
- 前端 RunStatus 类型和 RunStatusBadge 增加 cancel_requested;后端不应把它伪装为 cancelled。
- list events 空页必须返回请求 cursor,防止前端游标回退。
5.9 连续对话运行(二期,可选)
当前“对话运行”是单个 run 的视图投影:每次发送创建一个新 run。若业务要 ChatDev 式连续问答,需要独立设计,不能复用 run_id 作为会话 id。
建议新增:
workflow_conversations
workflow_conversation_turns
workflow_runs.conversation_id (nullable)
接口:
POST /api/workflows/{workflow_id}/conversations
GET /api/workflows/{workflow_id}/conversations
POST /api/workflows/conversations/{conversation_id}/turns
每个 turn 创建一个 run,并记录输入、run id、可展示摘要和制品引用。默认不把完整模型上下文拼接给每个节点;由 graph 明确配置哪些字段、哪些历史摘要可见。
6. 推荐实施顺序
阶段 A:真实保存与发布闭环(阻断项)
- 增加 canvas schema 持久化迁移和 store 原子接口。
- 实现 studio facade 四个端点。
- 前端实现 Coze JSON 到 WorkflowGraph 编译,并改接 facade。
- 完成输入/输出、代码、Agent、Skill、SQL、HTTP 的最小节点配置表单。
- 加入发布前 app 层资源检查。
验收:编辑一个“Input → SQL → Agent → Output”流程,刷新后画布与配置不丢失,发布后执行的是同一版本的标准图。
阶段 B:真实执行过程闭环
- 统一工具、人工介入、制品的 SSE payload。
- 实现制品内容与下载接口。
- 修复 lastSeq、cancel_requested。
- 增加跨 REST 重放、SSE 断线重连、人工介入恢复的契约测试。
验收:报告工作流在 iframe 刷新后能恢复步骤、工具摘要、文件卡和人工介入状态。
阶段 C:草稿试运行与连续会话
- 先由产品确认是否真的需要草稿试运行;需要时实现独立 test-run snapshot。
- 再由业务确认是否需要跨 run 的上下文记忆;需要时实现 conversation/turn。
在阶段 C 前,前端必须隐藏对应入口,禁止用“看似可点、实际忽略”的体验替代能力。
7. 测试与验收
7.1 后端新增测试
建议新增:
| 文件 | 重点场景 |
|---|---|
| tests/test_workflow_studio_facade.py | studio DTO、camelCase、CAS、错误结构、owner/admin 鉴权 |
| tests/test_workflow_studio_graph_contract.py | Coze 编译产物可被 WorkflowGraph 接受;非法 graph 被拒绝 |
| tests/test_workflow_resource_validation.py | Agent/Skill/SQL/HTTP/Code/Output 配置与权限校验 |
| tests/test_workflow_event_contract.py | tool、awaiting_input、artifact、parallel agent payload 与 REST/SSE 一致 |
| tests/test_workflow_artifact_content.py | 文本预览、二进制下载、越权、路径穿越、超大文件 |
| tests/test_workflow_runs.py | target=draft 显式拒绝、cancel_requested、lastSeq |
保留并扩展:
- tests/test_workflow_engine.py:并行、循环、节点 started 元数据。
- tests/test_workflow_runs.py:运行、取消、恢复、SSE、重放。
- tests/test_workflow_data_sources.py:SQL 只读、HTTP allowlist、秘密不回显。
7.2 前后端契约 fixture
在 backend/tests/fixtures/workflow_studio/ 固化 JSON fixture,并让前端测试复制或通过生成脚本消费:
- studio-document。
- 保存成功与 409 冲突。
- 资源缺失校验。
- Agent 工具 started/finished。
- human_input 暂停与恢复。
- markdown/json/table/docx 制品。
- 并行三智能体与取消中状态。
禁止前后端各自手写近似 mock 事件。fixture 变更必须同时更新双方测试。
7.3 发布准入
- Coze 画布和 executionGraph 采用两份数据、一次 CAS 保存。
- 真实后端不再依赖 schemaJson、valid 等 mock 专用字段。
- 所有可拖入资源都能在 graph 中找到真实绑定字段。
- 发布前可定位到节点和字段的资源/权限错误。
- 工具、人工介入、制品在 SSE 与 REST replay 中字段一致。
- 制品内容和下载经过 run 级鉴权,不能按任意路径读取。
- target=draft 不会被静默执行为发布版本。
- typecheck、后端 pytest、ruff 与至少一条 iframe E2E 全部通过。
8. 明确不做的事项
- 不把整个 Coze Go 后端或 ChatDev 后端迁入 DeerFlow。
- 不在 DeerFlow harness 中导入 app.gateway 或 Coze 前端类型。
- 不为了兼容 mock 而让标准 WorkflowGraph API 永久接受任意 schemaJson。
- 不向浏览器返回数据库 DSN、HTTP headers、token、resume token、沙箱真实文件路径或模型隐藏思维链。
- 不在未实现草稿快照、连续会话前保留误导性的 UI 入口。