Flow JSON 契约与持久化协议
学习目标
- 理解 Flow JSON 的结构和每个字段的契约含义
- 掌握 Flow 的序列化(
dump)和反序列化(load)流程 - 了解加载时迁移策略:新字段自动兼容,缺失字段回退到默认值
- 学会修改 Flow JSON 时不破坏已有保存的 Flow
前置知识
- 组件系统设计 — 组件的契约冻结清单
- JSON Schema 基础
项目实践
1. Flow JSON 结构
Flow JSON 是 Langflow 中最重要的用户契约,由 Graph.dump() 方法生成:
{ "data": { "nodes": [ { "id": "ChatInput-abc123", "type": "genericNode", "data": { "type": "ChatInput", "node": { "template": { "input_value": { "type": "str", "required": false, "placeholder": "", "list": false, "show": true, "multiline": true, "value": "你好" } }, "display_name": "Chat Input", "description": "Receive chat message input.", "icon": "message-square", "edited": true } }, "position": { "x": 100, "y": 200 } } ], "edges": [ { "source": "ChatInput-abc123", "target": "ChatOpenAI-def456", "sourceHandle": { "id": "ChatInput-abc123", "name": "message", "output_types": ["Message"] }, "targetHandle": { "id": "ChatOpenAI-def456", "fieldName": "input_value", "type": "str", "inputTypes": ["Message"] } } ] }, "is_component": false, "name": "My Flow", "description": "..."}关键字段契约:
| 路径 | 契约类型 | 规则 |
|---|---|---|
nodes[*].data.type | 组件类型标识 | 必须匹配注册的组件 name,否则加载失败 |
nodes[*].data.node.template[*] | 输入字段 | 键名匹配组件的 Input.name |
nodes[*].data.node.template[*].value | 输入值 | 用户配置的实际值,加载时直接回填 |
edges[*].sourceHandle.name | 输出端口名 | 匹配组件的 Output.name |
edges[*].targetHandle.fieldName | 输入端口名 | 匹配组件的 Input.name |
edges[*].targetHandle.inputTypes | 类型兼容检查 | 加载时验证输出类型是否与输入类型兼容 |
2. 序列化流程
3. 加载时迁移策略
Flow JSON 加载时的迁移策略基于宽容设计:
关键设计:
- 新增字段:必须是 optional,带默认值 → 旧 Flow 加载时自动使用默认值
- 删除字段:旧 Flow 中的值被忽略 → 不会崩溃,但用户配置丢失
- 重命名字段:旧 Flow 中的键名在新组件中不存在 → 连线失效,这是回归
- 类型收紧:旧 Flow 中传递的值类型不兼容新组件 → 加载失败,这是回归
4. Starter Projects 作为规范示例
src/backend/base/langflow/initial_setup/starter_projects/ 中的 JSON 文件是 Flow JSON 的规范示例:
- 每个 Starter Project 引用真实组件的
name和type - 组件变更时,Starter Projects 必须更新并重新验证
- CI 会在组件变更后自动检查 Starter Projects 是否仍可加载
为什么 Starter Projects 是契约的一部分:
- 它们是用户首次接触 Langflow 时看到的模板
- 它们验证了组件的向后兼容性:如果组件变更导致 Starter Project 无法加载,说明契约被破坏
- 文档和教程中的截图和深链接指向这些文件
问题与规避
| 陷阱 | 现象 | 对策 |
|---|---|---|
| 组件重命名后 Starter Project 无法加载 | 启动时报 “Component X not found” | 使用 legacy=True + replacement 保持旧名称可解析 |
| 新字段无默认值 | 旧 Flow 加载时新字段为 undefined | 所有新字段必须有合理的默认值 |
| 导出时凭证泄露 | Flow JSON 中包含明文 API Key | 导出时自动将凭证替换为 Variable 引用 |
| 边的类型不兼容 | 加载后连线显示为红色错误状态 | 加载时验证边的类型兼容性,不兼容时标记警告但不崩溃 |
设计取舍
宽容加载 vs 严格校验:
| 维度 | 宽容加载 | 严格校验 |
|---|---|---|
| 用户体验 | 旧 Flow 始终可加载 | 不兼容时明确报错 |
| 隐性错误 | 用户可能不知道某些配置已失效 | 错误立即可见 |
| 迁移动力 | 无强制力,用户可能长期运行在降级状态 | 强迫用户更新 Flow |
| 生产安全 | 降级运行可能无感知 | 崩溃可能导致服务中断 |
Langflow 选择宽容加载策略,核心原因是 Flow 是生产中的用户产物。宽容加载保证了即使组件发生变更,已有的 Flow 也不会突然停止工作。缺失字段回退到默认值,多余字段被忽略,只有在组件类型完全不存在时才报错。
参考来源
- 源码:
src/lfx/src/lfx/graph/graph/base.py—dump()/dumps()方法 - 契约文档:
docs/agents/CONTRACTS.md— 契约 #7(Flow JSON schema) - Starter Projects:
src/backend/base/langflow/initial_setup/starter_projects/