7.6 KiB
| title | created | author | labels | ||||
|---|---|---|---|---|---|---|---|
| Workflow Execution Results Structure - Redundancy & Complexity | 2026-01-10 | copilot |
|
问题报告:工作流执行结果的数据结构冗余与复杂性
摘要:
- 后端在生成并返回工作流执行报告时保留了多套数据(
node_infos、node_results、并在 API 层做额外合并与序列化),导致返回体中字段重复、语义混淆、序列化逻辑分散,增加前端解析复杂度与潜在性能问题。
1) 核心矛盾
- 后端为了兼容历史实现同时保留新的执行上下文,当前返回给前端的执行报告既包含
node_infos又包含node_results(并在 API 层再次合并/序列化),导致字段冗余、语义重复、序列化逻辑被多处重复实现。
2) 关键代码片段
- 执行器在
_get_execution_report中返回:
"node_infos": { node_id: info.to_dict() for node_id, info in self.context.node_infos.items() },
"node_results": self.context.node_results
- API 层(
endpoints_graph.execute)对 report 做两种兼容解析并合并后序列化:
if 'node_infos' in report or 'node_results' in report:
node_infos = report.get('node_infos', {}) or {}
node_results = report.get('node_results', {}) or {}
elif isinstance(report, dict):
node_infos = report
node_results = {}
for nid in all_node_ids:
info = node_infos.get(nid)
outputs = node_results.get(nid)
ser_outputs = serialize(outputs) # polars.DataFrame -> preview
results_out[nid] = {
'status': _get_status(info),
'outputs': ser_outputs,
'error': _get_error(info)
}
3) 技术限制与风险
- 冗余存储:
NodeExecutionInfo.result、context.node_results[node_id]、以及 API 层results_out三处可含相同数据。 - 性能与带宽:DataFrame/大对象可能被多次序列化,产生额外 CPU 与内存消耗。
- 语义不一致:前端需同时解析多套字段,提升错误率。
4) 可决策方案
方案 A(推荐)—— 单一标准响应(canonical schema)
- 描述:强制执行一个统一的响应结构:顶层
nodes映射,每个 node 包含status,duration,cache_hit,error,outputs,meta。 - 优点:清晰、便于文档化与版本化、降低重复序列化。
- 缺点:需要一次性后端变更与前端适配窗口。
方案 B(渐进兼容)—— 分层输出(summary/results)
- 描述:API 层导出
summary(轻量)与results(可选详细),通过参数控制是否返回详细 outputs(如?full=true)。 - 优点:低破坏、可渐进迁移。
- 缺点:仍需前端适配
full策略。
方案 C(重构)—— 大数据 artifact 存储与引用
- 描述:将完整的大对象(DataFrame)写入临时 artifact 存储,响应仅包含
preview与artifact_id。 - 优点:解决大 payload 问题,支持按需拉取。
- 缺点:实现复杂(存储、TTL、权限)。
5) 立刻可执行的短期修复
- 把 DataFrame 序列化抽成单一函数并确保只执行一次。
- 在响应中避免重复包含相同数据(检测并合并重复字段)。
- 添加
api_schema_version字段并在返回 header 中标注,便于兼容管理。
6) 建议与决策请求给 Gemini
- 是否接受 breaking-change(方案 A)并安排前端迁移窗口?
- 是否采用渐进方案(方案 B)为过渡,并设定过渡期(例如 2 个版本)?
- 是否在短期内实现 artifact 存储(方案 C 的关键部分)?
7) 建议的 canonical schema(示例)
{
"status": "success",
"execution_id": "uuid",
"execution_time": 0.123,
"nodes": {
"node1": {
"status": "success",
"duration": 0.032,
"cache_hit": false,
"error": null,
"outputs": {
"out": {
"__type": "DataFrame",
"columns": ["a","b"],
"preview": [{"a":1,"b":2}],
"rows": 1234
}
},
"meta": { "class_name": "SomeNode", "node_type": "normal" }
}
}
}
8) 后续动作(会议后)
- 如果选 A:制定迁移计划(API version、前端适配、回退策略)。
- 如果选 B:实现
summary/results与?full控制,并优化序列化集中点。 - 如果选 C:实现 artifact 存储与权限管理。
title: "Refactor: Unified Execution Protocol & Compatibility Cleanup" created: 2026-01-10 author: gemini labels: [architecture, breaking-change, cleanup, protocol]
🧠 《Copilot 开发指导手册》:执行引擎标准化重构
1. 功能摘要
废除 node_infos 与 node_results 的冗余双轨数据流。强制推行以节点为中心的 Canonical Schema 作为唯一真相来源,彻底清理开发期遗留的兼容性逻辑(如旧版 Input/Output 节点分支),确保执行引擎职责单一化。
2. 核心矛盾
后端为了兼容历史实现,当前返回的报告中字段重复且语义混淆。API 层(endpoints_graph.execute)被迫进行繁琐的合并与二次序列化,这增加了前端解析难度并导致大型对象(如 DataFrame)的性能损耗。
3. 逻辑契约 (Canonical Schema)
WorkflowExecutor._get_execution_report 必须输出如下扁平化结构:
{ "success": "boolean", "execution_id": "string (uuid)", "total_duration": "float", "nodes": { "{node_id}": { "status": "success | error | pending", "metrics": { "duration": "float", "cache_hit": "boolean" }, "outputs": { "{port_name}": { "__type": "DataFrame | any", "preview": "array", "shape": "tuple" } }, "error": "string | null", "meta": { "class_name": "string", "node_type": "string" } } } }
4. 实现提示 (Implementation Hints)
- 彻底移除兼容性方法:删除已在
workflow_executor.py头部声明弃用的_execute_input_node和_execute_output_node内部方法。 - 清理 API 层冗余:重构
endpoints_graph.py,直接透传执行器结果,禁止使用if 'node_infos' in report等逻辑做降级解析。 - 数据精炼原则:在执行器内部实现
_smart_serialize,针对 Polars DataFrame 等对象强制执行采样预览(如.head(5)),贯彻“采样预览,全量生产”的核心原则。
5. 核心函数重构示例 (WorkflowExecutor)
def _get_execution_report(self) -> Dict: """ [utrace 架构决策]:废除双轨制,执行数据精炼采样。 """ if not self.context: return {"success": False, "error": "Context not initialized"}
nodes_map = {}
for nid, info in self.context.node_infos.items():
# 合并执行上下文与结果
raw_res = self.context.node_results.get(nid, {})
raw_outputs = raw_res.get("outputs", {})
nodes_map[nid] = {
"status": info.status.value,
"metrics": {
"duration": round(info.duration, 4),
"cache_hit": info.cache_hit
},
"outputs": self._smart_serialize(raw_outputs),
"error": info.error,
"meta": {
"class_name": self.graph.nodes[nid].get("class_name"),
"node_type": info.node_type
}
}
return {
"success": all(n["status"] == "success" for n in nodes_map.values()),
"execution_id": self.context.execution_id,
"nodes": nodes_map
}
utrace 架构审计批注: TraceStudio 坚持“逻辑即文档”。本次重构是消除系统熵增的关键一步。所有不符合此 Schema 的前端调用均视为待修复 Bug,而非后端需要兼容的对象。