上一篇:浏览器联调台:编辑图 JSON、一点运行、看 events
相关:02 最小工作流 API
示例仓库:flow-forge
本篇讲:工作流里新增第四种节点 Code------在图里写一小段 Python 逻辑加工变量,比 Template 的「填空成句」更灵活。
先说结论
前三篇已经能:存图 → 跑图 → 在浏览器里看 events。但 Template 节点只能做字符串插值 (把 {name} 换成输入值),做不了「转大写、算长度、拼列表」这类小逻辑。
本篇补的是 可编程变换:
| 对比 | Template 节点 | Code 节点 |
|---|---|---|
| 能做什么 | 固定句式 + 占位符填空 | 短 Python 片段加工变量 |
| 配置字段 | data.template |
data.code |
| 输出约定 | 写入 text |
必须 赋值 result = ...;若是字符串,也会写入 text 方便接 End |
| 典型路径 | start → template → end | start → code → end,或与 template 串联 |
一句话:Code 节点让工作流里能写「几行小逻辑」,而不是只能拼句子;本仓是学习向受控执行,不是生产级沙箱。
HTTP 接口形状不变 :仍是 create → run → events。变的是图里多了一种 data.type: "code"。
1. 为什么需要 Code 节点?
想象你要把用户输入的名字转成大写,或根据两个数字算和。Template 只能写 Hello, {name}!,没法写 name.upper() 这种运算。
对照 Dify 一类产品,Code 节点就是专门干这个的:在图上挂一小段代码,读当前运行里的变量,算完写回,交给下游。
本仓 change code-node 在现有 Runner 上扩展,不新加接口、不改 Web 联调流程------你在联调页把图 JSON 换成含 code 的版本即可。
| 术语 | 是什么 | 本仓怎么用 |
|---|---|---|
| Code 节点 | 执行用户提供的短代码 | data.type: "code" + data.code |
| 变量表 | 这次运行里「名字 → 值」的对照 | start 的 inputs 进来;每个节点可读可写 |
result |
Code 节点必须赋值的输出名 | 例如 result = name.upper() |
| 受控执行 | 只允许有限能力,禁 import / 读文件等 | 学习向;文档会写明不是真沙箱 |
2. 功能一:图里怎么配 Code 节点
在 nodes 里增加一类,和 start / template / end 并列:
json
{
"id": "code_1",
"data": {
"type": "code",
"code": "result = name.upper()"
}
}
含义(不必先会 Python 语法):
| 片段 | 在干什么 |
|---|---|
"type": "code" |
告诉 Runner:这一步要跑代码 |
"code": "..." |
用户写的短脚本 |
name |
来自运行 inputs 的变量名(本例输入 "name": "forge") |
name.upper() |
把字符串转成大写(.upper() 是字符串自带的方法) |
result = ... |
固定约定 :必须把最终结果赋给 result,Runner 才认 |
最小整图(start → code → end):
json
{
"nodes": [
{ "id": "start_1", "data": { "type": "start" } },
{
"id": "code_1",
"data": { "type": "code", "code": "result = name.upper()" }
},
{ "id": "end_1", "data": { "type": "end" } }
],
"edges": [
{ "id": "e1", "source": "start_1", "target": "code_1" },
{ "id": "e2", "source": "code_1", "target": "end_1" }
]
}
创建图时还会校验:
data.code不能为空- 源码不超过 4KB
- 静态拒绝
import、open(、__import__等危险模式
非法图在创建阶段就会被拒绝,和 Template 缺字段一样。
3. 功能二:跑起来会发生什么
inputs 示例:{"name": "forge"}。
Runner 走到 Code 节点时(白话顺序):
- 把当前变量表(含
name)交给执行器 - 在受控环境 里跑
data.code里的脚本 - 检查是否出现了
result - 把
result写回变量表;若是字符串,同时写入text,End 节点能直接输出 - 照常写
node_started/node_succeeded事件
成功时 outputs 大致是:
json
{
"text": "FORGE",
"result": "FORGE"
}
失败时(语法错、没写 result、用了禁止的 import 等):Run 为 failed,事件里有 node_failed,error 字段可读------和 Template 缺变量失败同一套观测方式。
和 Template 怎么选?
| 场景 | 更合适的节点 |
|---|---|
| 固定问候语 + 填名字 | Template |
| 大小写、简单运算、列表拼接 | Code |
| 两者都要 | start → template → code → end(先拼句,再加工) |
4. 功能三:安全边界(学习向,不是真沙箱)
Code 节点会执行用户写的 Python 片段,因此必须有硬边界。本仓当前做法:
| 限制 | 目的 |
|---|---|
禁用 import / open / exec / eval 等模式 |
避免读文件、拉库、嵌套执行 |
只开放少量内置函数(如 len、str、int) |
允许常见小运算,不开放整台 Python |
| 源码长度上限 4KB | 避免超长脚本拖垮请求 |
在受控命名空间里 exec |
学习演示够用;不能当生产隔离方案 |
仓库 code.py 顶部注释和 api/README.md 都写明:这是学习向受控命名空间,不是 Docker 级沙箱。
你若对照 Dify 生产环境,应假设那边还有更强隔离;本系列先学会「节点类型怎么扩展、Runner 怎么调度、失败怎么落事件」。
5. 你怎么跟跑?
方式 A:浏览器联调页(推荐)
- 按第三篇起好
api+web - 打开 http://localhost:3000
- 把左侧图 JSON 换成上面的 start → code → end(或从
web/lib/sample.ts里的SAMPLE_CODE_GRAPH复制) - 右侧 inputs 填
{"name": "forge"},点「运行」 - 看
status: succeeded与outputs里的FORGE
联调页默认仍预填 Template 示例;换 Code 图只需改文本框,不必改前端代码。
方式 B:curl
api/README.md 有完整的 Code 节点 curl 示例:创建含 code 的图 → POST .../runs → 查 events。
自动化
bash
cd api
uv run pytest
新增用例会覆盖:合法 code 图、缺 code 拒绝、禁止 import、运行成功/语法失败等。
6. 仓库里改动了哪几块?(地图,不是语法课)
| 位置 | 本篇新增职责 |
|---|---|
core/workflow/graph.py |
允许 data.type: "code",校验 data.code |
core/workflow/nodes/code.py |
受控执行 + result 约定 |
core/workflow/runner.py |
调度循环里多一个 code 分支 |
web/lib/sample.ts |
可选示例图 SAMPLE_CODE_GRAPH |
api/README.md |
Code 示例与安全说明 |
读代码建议顺序:
- 先看 README / 联调页跑通(本节第 5 节)
- 再看
graph.py:多了一种节点类型意味着什么 - 最后看
code.py:「受控」具体禁了什么
Runner 其它节点(start / template / end)逻辑与第二篇相同;Code 只是在循环里多插一个分支------这是以后加 LLM 节点等同理可扩的模式。
本篇顺带认识的 Python(仍只钉词)
| 词 / 写法 | 先怎么记 | 和 Code 节点的关系 |
|---|---|---|
result = ... |
给名为 result 的格子赋值 | 输出约定,缺了就算失败 |
name.upper() |
对字符串 name 调「转大写」 |
示例图里的典型一行 |
exec(...) |
把字符串当代码跑(危险,故本仓受限使用) | 实现细节在 code.py,你知道「会被关进受控环境」即可 |
import |
引入外部库 | 本仓 Code 节点禁止,创建图时就会拦 |
和前作怎么接
| 篇 | 补哪一段 |
|---|---|
| 02 最小工作流 API | Template 与 Runner 主路径 |
| 03 浏览器联调台 | 页面触发 create → run → events |
| 本篇 | 第四种节点 Code:可编程变换 + 受控边界 |
下一篇更可能落在 LLM 节点、并行边或其它产品能力;本切片停在「一种新节点类型如何接入现有 Runner」。
你可以从这里带走什么?
- Template 管填空,Code 管小逻辑------节点类型是按能力切的,不是越堆越多 UI。
- Code 节点的契约很简单:读变量 → 跑
data.code→ 必须写出result。 - 加新节点类型时,HTTP 往往不用改;改的是图校验 + Runner 分支 + 文档示例。
- 任何「用户可写代码」的能力都要写清安全边界;学习向受控 exec ≠ 生产沙箱。
- 联调页仍够用:换 JSON 就能试 Code,说明前后端契约稳定。
仓库与相关文档
- GitHub :https://github.com/jimchou-h/flow-forge
- Code 节点说明与 curl :api/README.md
- 受控执行 :core/workflow/nodes/code.py
- 图校验扩展 :core/workflow/graph.py
- Runner 调度 :core/workflow/runner.py
- Web 示例图 :web/lib/sample.ts
欢迎 Star、Issue 和 PR。
_本文基于 Flow Forge code-node:覆盖 Code 节点配置、受控执行与 result 约定。