6612 字
约 22 分钟
3
工作流引擎里的图是怎么构建的?节点之间的参数又是怎么传递的?

工作流引擎里的图是怎么构建的?节点之间的参数又是怎么传递的?

在做工作流引擎、智能体编排、AI Agent Pipeline 的时候,经常会遇到两个非常核心的问题:

第一,节点之间的图是怎么构建出来的?

第二,A 节点执行完之后,B 节点是怎么拿到 A 节点输出结果的?

这两个问题看起来简单,但其实正好戳中了工作流引擎的核心。

因为一个工作流不是简单地“按顺序执行几个方法”,而是要把多个节点组织成一张图,然后让数据沿着这张图流动起来。

我们可以把它想象成一条生产线。

每个节点都是一个加工站点。

边就是站点之间的传送带。

State 就是整个生产线上的“流转单”,上面记录着当前输入、历史输出、当前节点等执行过程中的上下文信息。

这篇文章就围绕两个点展开:

  1. 图是怎么构建的;
  2. 参数是怎么从一个节点传到另一个节点的。

一、先理解:工作流本质上是一张图

在工作流系统里,用户通常会配置一批节点和一批边。

比如有这样一个流程:

用户输入
   ↓
意图识别节点
   ↓
资料检索节点
   ↓
大模型总结节点
   ↓
最终输出

从程序角度看,这其实就是一张有向图:

START
  ↓
node_intent
  ↓
node_search
  ↓
node_llm
  ↓
END

每个节点负责一件具体的事情。

比如:

node_intent:识别用户意图
node_search:检索资料
node_llm:调用大模型生成回答

每条边负责描述节点之间的执行顺序。

比如:

node_intent -> node_search
node_search -> node_llm

所以,构建工作流的第一步,就是把用户配置里的节点和边,转换成程序内部真正可以执行的图结构。

这个事情通常就由 GraphBuilder 来做。


二、GraphBuilder 到底做了什么?

GraphBuilder 的职责可以概括成三句话:

1. 加节点
2. 加边
3. 设置入口和出口

听起来很简单,但这是整个工作流能跑起来的基础。

一个典型的构建过程大概是这样的:

public CompiledGraph<AgentState> buildGraph(
        WorkflowConfig config,
        Consumer<ExecutionEvent> eventCallback
) throws Exception {

    StateGraph<AgentState> graph = new StateGraph<>(AgentState::new);

    addNodes(graph, config.getNodes(), eventCallback);

    addEdges(graph, config.getEdges());

    setEntryAndExit(graph, config.getNodes(), config.getEdges());

    return graph.compile();
}

这段代码可以拆成几步来看。


三、第一步:创建 StateGraph

首先创建一张图:

StateGraph<AgentState> graph = new StateGraph<>(AgentState::new);

这里的 StateGraph<AgentState> 有两个关键点。

第一个是 StateGraph

它表示这不是一张普通的图,而是一张带状态流转能力的图。

普通的图只关心节点和边。

而工作流图不仅关心节点和边,还要关心执行过程中的状态,比如:

当前输入是什么?
当前执行到哪个节点?
每个节点的历史输出是什么?
有没有错误?
有没有事件需要回调?

第二个是 AgentState

它就是整个工作流执行时共享的上下文状态。

你可以把它理解成一张“任务流转表”。

工作流从开始到结束,很多关键数据都会存在这张表里。


四、第二步:把配置里的节点注册到图里

用户配置里的节点,一般只是一个描述。

比如:

{
  "id": "node_llm1",
  "type": "llm",
  "name": "分析节点",
  "config": {
    "prompt": "请分析用户输入:{{input}}"
  }
}

这个配置本身不能直接执行。

它只是告诉系统:

这里有一个节点
节点 ID 是 node_llm1
节点类型是 llm
节点配置是这些

但是图引擎真正需要的是一个可以执行的动作,也就是类似:

AsyncNodeAction<AgentState>

所以这里就需要一个适配过程。

通常会由 NodeAdapterWorkflowNode 转换成图引擎能识别的执行单元。

可以理解为:

WorkflowNode 配置节点
        ↓
NodeAdapter 适配
        ↓
AsyncNodeAction 可执行节点

伪代码大概是这样:

private void addNodes(
        StateGraph<AgentState> graph,
        List<WorkflowNode> nodes,
        Consumer<ExecutionEvent> eventCallback
) {
    for (WorkflowNode node : nodes) {
        AsyncNodeAction<AgentState> action =
                nodeAdapter.adapt(node, eventCallback);

        graph.addNode(node.getId(), action);
    }
}

这一步完成后,图里面就有了一批真正可以执行的节点。

但是此时它们还只是“孤零零地存在”。

还没有顺序关系。

接下来就要加边。


五、第三步:把配置里的边注册到图里

边描述的是节点之间的流转关系。

比如配置里有:

{
  "source": "node_intent",
  "target": "node_search"
}

意思就是:

node_intent 执行完之后,继续执行 node_search

注册到图里的过程通常类似这样:

private void addEdges(
        StateGraph<AgentState> graph,
        List<WorkflowEdge> edges
) {
    for (WorkflowEdge edge : edges) {
        graph.addEdge(edge.getSource(), edge.getTarget());
    }
}

这样,原来孤立的节点就被边串起来了。

假设有这些边:

node_a -> node_b
node_b -> node_c

图结构就变成:

node_a
  ↓
node_b
  ↓
node_c

但是这时候还有一个问题。

图引擎怎么知道从哪里开始执行?

又在哪里结束?

这就涉及入口和出口。


六、第四步:动态识别入口节点

一个不太好的做法是硬编码入口节点。

比如直接规定:

id = start 的节点就是入口

或者:

第一个配置节点就是入口

但这种方式不够灵活。

更通用的方式是根据边的拓扑关系动态推导。

什么是入口节点?

很简单:

没有任何入边的节点,就是入口节点。

比如:

node_a -> node_b -> node_c

这里 node_a 没有任何节点指向它。

所以 node_a 就是入口。

代码可以这样写:

private WorkflowNode findEntryNode(
        List<WorkflowNode> nodes,
        List<WorkflowEdge> edges
) {
    for (WorkflowNode node : nodes) {
        boolean hasIncomingEdge = edges.stream()
                .anyMatch(edge -> edge.getTarget().equals(node.getId()));

        if (!hasIncomingEdge) {
            return node;
        }
    }

    return null;
}

这段代码的逻辑非常直白。

遍历所有节点。

对每个节点检查一遍:

有没有边的 target 等于当前节点 ID?

如果没有,说明没有任何节点指向它。

那它就是入口节点。

找到入口节点之后,需要给图加一条特殊边:

StateGraph.START -> entryNode

也就是说,工作流不是直接从业务节点开始,而是从框架内置的 START 节点进入,再流转到真正的业务入口节点。

例如:

START
  ↓
node_a
  ↓
node_b
  ↓
node_c

这样图引擎就知道该从哪里开始跑了。


七、第五步:动态识别出口节点

出口节点的识别逻辑和入口节点刚好相反。

什么是出口节点?

没有任何出边的节点,就是出口节点。

还是这个例子:

node_a -> node_b -> node_c

node_c 后面没有任何节点。

所以 node_c 就是出口节点。

代码可以这样理解:

private WorkflowNode findExitNode(
        List<WorkflowNode> nodes,
        List<WorkflowEdge> edges
) {
    for (WorkflowNode node : nodes) {
        boolean hasOutgoingEdge = edges.stream()
                .anyMatch(edge -> edge.getSource().equals(node.getId()));

        if (!hasOutgoingEdge) {
            return node;
        }
    }

    return null;
}

找到出口节点之后,也需要补一条特殊边:

exitNode -> StateGraph.END

最终图就会变成这样:

START
  ↓
node_a
  ↓
node_b
  ↓
node_c
  ↓
END

这样,图引擎不仅知道从哪里开始,也知道在哪里结束。


八、完整图构建流程总结

到这里,GraphBuilder 的完整职责就非常清楚了。

它不是负责执行具体业务逻辑的。

它负责把一份工作流配置,转换成一张可以执行的图。

整个过程可以总结为:

WorkflowConfig
    ↓
读取 nodes
    ↓
NodeAdapter 把每个节点适配成 AsyncNodeAction
    ↓
graph.addNode 注册节点
    ↓
读取 edges
    ↓
graph.addEdge 注册边
    ↓
根据拓扑关系找到入口节点
    ↓
START -> entryNode
    ↓
根据拓扑关系找到出口节点
    ↓
exitNode -> END
    ↓
graph.compile()
    ↓
得到 CompiledGraph

换成一张简单的图就是:

配置文件
  ↓
GraphBuilder
  ↓
StateGraph<AgentState>
  ↓
CompiledGraph<AgentState>
  ↓
执行引擎运行

这就是图的构建过程。


九、节点之间的参数是怎么传递的?

图构建好了之后,下一个问题就来了:

A 节点执行完之后,B 节点怎么拿到 A 节点的输出?

这是工作流引擎里非常关键的一点。

一种常见的设计是,不让节点之间直接互相调用,也不让 A 节点直接把参数塞给 B 节点。

而是让所有节点都通过一个共享的 State 来传递数据。

也就是说:

A 节点不直接调用 B 节点
A 节点只负责把输出写入 State
B 节点执行时再从 State 里读取输入

这有点像接力赛。

A 跑完之后,把接力棒放到一个固定位置。

B 不需要知道 A 是谁,也不需要直接跟 A 通信。

B 只要去固定位置拿接力棒就行。

这个固定位置,就是 AgentState


十、核心字段:currentInput

在这个设计里,最重要的字段之一就是:

currentInput

它表示当前节点要处理的输入。

比如执行顺序是:

node_a -> node_b -> node_c

那么数据流大概是:

用户原始输入
   ↓
node_a 的 currentInput
   ↓
node_a 输出 outputA
   ↓
写回 currentInput
   ↓
node_b 的 currentInput = outputA
   ↓
node_b 输出 outputB
   ↓
写回 currentInput
   ↓
node_c 的 currentInput = outputB

也就是说:

上一个节点的输出,就是下一个节点的 currentInput。

这就是最基本的链式参数传递。


十一、NodeAdapter 里如何更新 State?

每个节点执行完成之后,通常会做三件事:

newStateData.put("nodeOutputs", nodeOutputs);
newStateData.put("currentInput", output);
newStateData.put("currentNodeId", node.getId());

这三行代码非常关键。

我们一行一行拆开看。


十二、第一行:保存所有节点的历史输出

newStateData.put("nodeOutputs", nodeOutputs);

nodeOutputs 用来存储每个节点的输出结果。

它可以理解成一个 Map:

Map<String, Object> nodeOutputs

结构大概是:

{
  "node_a": {
    "intent": "search",
    "query": "GraphBuilder 参数传递"
  },
  "node_b": {
    "documents": [
      "doc1",
      "doc2"
    ]
  },
  "node_c": {
    "answer": "最终总结结果"
  }
}

它的作用是存档。

每个节点执行完,不仅会把结果传给下一个节点,还会把自己的输出按节点 ID 保存起来。

这样后面的节点如果想引用更早之前某个节点的结果,也可以通过 nodeOutputs 拿到。

比如当前执行到了 node_c,它不只想拿 node_b 的输出,还想拿 node_a 的意图识别结果。

那就可以这样取:

nodeOutputs["node_a"]

或者进一步取字段:

nodeOutputs["node_a"]["intent"]

所以,nodeOutputs 解决的是:

跨节点、跨层级、跨步骤引用历史输出的问题。

十三、第二行:更新 currentInput

newStateData.put("currentInput", output);

这一行决定了链式传递。

当前节点执行完成后,它的输出 output 会被设置成新的 currentInput

这样下一个节点执行时,拿到的 currentInput 就是上一个节点的输出。

举个例子。

用户输入:

{
  "input": "帮我分析一下 GraphBuilder 的作用"
}

第一个节点 node_a 做意图识别,输出:

{
  "intent": "explain",
  "topic": "GraphBuilder"
}

执行完后,State 变成:

{
  "currentInput": {
    "intent": "explain",
    "topic": "GraphBuilder"
  },
  "nodeOutputs": {
    "node_a": {
      "intent": "explain",
      "topic": "GraphBuilder"
    }
  },
  "currentNodeId": "node_a"
}

接下来 node_b 执行时,它拿到的 currentInput 就是:

{
  "intent": "explain",
  "topic": "GraphBuilder"
}

所以 node_b 不需要知道 node_a 是怎么执行的。

它只需要关心当前输入是什么。


十四、第三行:记录当前节点 ID

newStateData.put("currentNodeId", node.getId());

这一行主要是为了记录执行进度。

它可以用于:

日志追踪
事件回调
异常排查
调试定位
前端展示当前执行节点

比如前端想展示:

当前执行到了“大模型总结节点”

后端就可以通过 currentNodeId 知道当前状态停留在哪个节点上。

如果某个节点执行失败,也可以快速知道是哪个节点出了问题。


十五、为什么不让节点直接传参?

有人可能会问:

为什么不直接让 A 节点调用 B 节点?

比如:

Object outputA = nodeA.run(input);
Object outputB = nodeB.run(outputA);

这样不是更简单吗?

在非常简单的线性流程里,这当然可以。

但是一旦工作流变复杂,就会有很多问题。

比如:

1. 节点之间耦合太强
2. 不方便动态编排
3. 不方便插入、删除、替换节点
4. 不方便做分支流程
5. 不方便做并行流程
6. 不方便追踪每个节点的输出
7. 不方便失败重试和恢复执行

工作流引擎追求的是“节点解耦”。

每个节点只需要做到:

从 State 里读输入
执行自己的逻辑
把输出写回 State

至于下一个节点是谁,不是当前节点关心的事情。

那是图引擎根据边来决定的。

这就把两个职责拆开了:

节点:负责做事
边:负责决定下一个做谁
State:负责传递数据
GraphBuilder:负责把节点和边组装成图

这个拆分非常重要。

它让整个工作流系统变得灵活、可扩展、可维护。


十六、第一个节点没有上游,currentInput 从哪来?

链式传递里还有一个特殊情况:

第一个节点没有上游节点,那它的 currentInput 是什么?

答案是:

StateManager 初始化时就会把用户原始输入放进去。

比如用户请求是:

帮我分析一下这段代码

初始化 State 时,可以设置:

{
  "currentInput": {
    "input": "帮我分析一下这段代码"
  }
}

所以第一个业务节点拿到的 currentInput 就是用户原始输入。

从这个角度看,整个数据流是这样的:

用户原始输入
   ↓
StateManager 初始化 currentInput
   ↓
入口节点读取 currentInput
   ↓
入口节点执行完成,输出写回 currentInput
   ↓
下一个节点继续读取 currentInput
   ↓
不断向后传递
   ↓
最终节点输出结果

这就形成了一条完整的数据链路。


十七、currentInput 和 nodeOutputs 的区别

这里非常容易混淆。

currentInputnodeOutputs 都跟节点输出有关,但它们的定位不一样。

可以这样理解:

currentInput:给下一个节点用的“当前接力棒”
nodeOutputs:保存所有节点结果的“历史档案”

举个例子:

node_a -> node_b -> node_c

执行完 node_a 后:

{
  "currentInput": {
    "aResult": "A 的输出"
  },
  "nodeOutputs": {
    "node_a": {
      "aResult": "A 的输出"
    }
  }
}

执行完 node_b 后:

{
  "currentInput": {
    "bResult": "B 的输出"
  },
  "nodeOutputs": {
    "node_a": {
      "aResult": "A 的输出"
    },
    "node_b": {
      "bResult": "B 的输出"
    }
  }
}

可以看到:

currentInput 会不断被覆盖。

它永远表示“当前要传给下一个节点的输入”。

nodeOutputs 不会简单覆盖之前节点的数据。

它会把每个节点的输出都保存下来。

所以:

如果只关心上一个节点的输出,用 currentInput。
如果要引用任意历史节点的输出,用 nodeOutputs。

这是参数传递设计里最核心的一点。


十八、Prompt 模板里的变量是怎么填充的?

除了节点之间的链式传递,工作流里还有一种常见需求:

在 Prompt 模板里引用变量。

比如一个 LLM 节点配置了这样的 Prompt:

请根据以下分析结果生成总结:

分析结果:{{analysis}}

用户问题:{{input}}

这里的 {{analysis}}{{input}} 就是模板变量。

真正调用大模型之前,需要把这些变量替换成具体值。

这个事情通常由 PromptTemplateService 来做。


十九、变量来源一般分两种

Prompt 模板里的变量,通常有两种来源:

1. input:静态输入值
2. reference:引用上游节点输出

先看第一种。


二十、input:静态值填充

如果某个参数类型是 input,说明它的值是直接配置好的。

比如:

{
  "name": "role",
  "type": "input",
  "value": "你是一个资深架构师"
}

模板里有:

{{role}}

替换之后就是:

你是一个资深架构师

这种方式适合配置一些固定内容,比如:

角色设定
输出语言
格式要求
固定提示词
默认参数

二十一、reference:引用上游节点输出

第二种更重要。

如果某个参数类型是 reference,说明它的值不是写死的,而是来自某个节点的输出。

比如配置:

{
  "name": "analysis",
  "type": "reference",
  "referenceNode": "node_llm1.analysis"
}

这表示:

我要把 node_llm1 节点输出里的 analysis 字段,填充到模板变量 {{analysis}} 里。

假设 node_llm1 的输出是:

{
  "analysis": "GraphBuilder 主要负责把配置转换成可执行图。"
}

模板是:

请基于以下分析继续总结:

{{analysis}}

替换之后就变成:

请基于以下分析继续总结:

GraphBuilder 主要负责把配置转换成可执行图。

这就是引用上游节点输出的过程。


二十二、referenceNode 为什么可以写成 node_llm1.analysis?

node_llm1.analysis 这种写法一般可以拆成两部分:

node_llm1:节点 ID
analysis:节点输出里的字段名

也就是说:

从 nodeOutputs 里找到 node_llm1 的输出
再从这个输出里取 analysis 字段

逻辑类似这样:

Object nodeOutput = nodeOutputs.get("node_llm1");
Object value = getFieldValue(nodeOutput, "analysis");

如果引用路径更深,也可以继续扩展。

比如:

node_search.result.documents

可以理解为:

从 node_search 节点输出里取 result
再从 result 里取 documents

对应的数据可能是:

{
  "result": {
    "documents": [
      "文档 1",
      "文档 2"
    ]
  }
}

这样模板就可以非常灵活地引用任意上游节点的输出。


二十三、把整个参数流转串起来看

假设我们有一个三节点流程:

node_input_cleaner -> node_analyzer -> node_writer

用户输入是:

{
  "input": "帮我写一篇关于 GraphBuilder 的博客"
}

初始化 State:

{
  "currentInput": {
    "input": "帮我写一篇关于 GraphBuilder 的博客"
  },
  "nodeOutputs": {}
}

1. 执行 node_input_cleaner

node_input_cleaner 拿到:

{
  "input": "帮我写一篇关于 GraphBuilder 的博客"
}

输出:

{
  "cleanInput": "写一篇介绍 GraphBuilder 构建流程的技术博客"
}

State 更新为:

{
  "currentInput": {
    "cleanInput": "写一篇介绍 GraphBuilder 构建流程的技术博客"
  },
  "nodeOutputs": {
    "node_input_cleaner": {
      "cleanInput": "写一篇介绍 GraphBuilder 构建流程的技术博客"
    }
  },
  "currentNodeId": "node_input_cleaner"
}

2. 执行 node_analyzer

node_analyzercurrentInput 是上一个节点输出:

{
  "cleanInput": "写一篇介绍 GraphBuilder 构建流程的技术博客"
}

它输出:

{
  "analysis": "文章应该重点解释图构建、入口出口识别、State 参数传递和模板变量替换。"
}

State 更新为:

{
  "currentInput": {
    "analysis": "文章应该重点解释图构建、入口出口识别、State 参数传递和模板变量替换。"
  },
  "nodeOutputs": {
    "node_input_cleaner": {
      "cleanInput": "写一篇介绍 GraphBuilder 构建流程的技术博客"
    },
    "node_analyzer": {
      "analysis": "文章应该重点解释图构建、入口出口识别、State 参数传递和模板变量替换。"
    }
  },
  "currentNodeId": "node_analyzer"
}

3. 执行 node_writer

node_writer 如果只需要上一个节点的结果,直接读:

currentInput.analysis

如果它还需要第一个节点的清洗结果,可以从历史输出里拿:

nodeOutputs.node_input_cleaner.cleanInput

如果 Prompt 模板是:

请根据用户需求和分析结果写一篇博客。

用户需求:{{cleanInput}}

分析结果:{{analysis}}

参数配置可以类似这样:

[
  {
    "name": "cleanInput",
    "type": "reference",
    "referenceNode": "node_input_cleaner.cleanInput"
  },
  {
    "name": "analysis",
    "type": "reference",
    "referenceNode": "node_analyzer.analysis"
  }
]

模板替换后就是:

请根据用户需求和分析结果写一篇博客。

用户需求:写一篇介绍 GraphBuilder 构建流程的技术博客

分析结果:文章应该重点解释图构建、入口出口识别、State 参数传递和模板变量替换。

然后 node_writer 再调用大模型生成最终内容。


二十四、整体架构可以这样理解

整个流程里,其实有四个核心角色。


1. GraphBuilder:负责搭图

它负责把配置变成可执行图。

读取节点
读取边
注册节点
注册边
识别入口
识别出口
编译图

它不关心每个节点具体怎么干活。

它只关心图怎么搭起来。


2. NodeAdapter:负责把配置节点变成可执行节点

配置里的节点只是描述。

真正执行时,需要把它转成 AsyncNodeAction

WorkflowNode
   ↓
NodeAdapter
   ↓
AsyncNodeAction

所以 NodeAdapter 是配置和执行之间的桥梁。


3. AgentState:负责保存执行上下文

所有节点之间不直接传参,而是通过 State 传递。

State 里至少会有:

currentInput:当前节点输入
nodeOutputs:所有节点历史输出
currentNodeId:当前节点 ID

它是整个工作流的数据中转站。


4. PromptTemplateService:负责模板变量替换

LLM 节点通常会有 Prompt 模板。

模板里的变量需要在执行前填充。

变量可能来自:

静态 input
上游 reference

所以 PromptTemplateService 负责把:

{{variable}}

替换成真正的值。


二十五、为什么这种设计比较通用?

这种设计有几个明显好处。


1. 节点之间解耦

A 节点不需要知道 B 节点是谁。

B 节点也不需要知道 A 节点内部怎么实现。

大家都只和 State 打交道。

这让节点可以自由组合。


2. 支持动态编排

因为入口、出口、边关系都是从配置里推导出来的,所以流程不是写死在代码里的。

只要改配置,就可以改变执行流程。

比如原来是:

A -> B -> C

后来想改成:

A -> D -> C

理论上只需要调整配置,不一定要改核心执行引擎。


3. 方便扩展节点类型

新增一个节点类型时,只需要让 NodeAdapter 支持新的类型。

比如:

LLM 节点
HTTP 节点
代码执行节点
条件判断节点
数据库查询节点
知识库检索节点

图构建逻辑不用大改。


4. 方便调试和观测

因为每个节点的输出都会被保存到 nodeOutputs,所以调试时可以清楚看到:

哪个节点输入了什么
哪个节点输出了什么
流程执行到哪里
哪里出现了异常

这对工作流系统非常重要。

没有这些信息,排查问题会非常痛苦。


5. 支持复杂引用

有了 nodeOutputs 之后,后面的节点不只能拿上一个节点的输出,还能拿任意历史节点的输出。

这就让复杂 Prompt 组装变得可控。

例如:

最终总结节点同时引用:
- 用户原始问题
- 意图识别结果
- 检索结果
- 中间分析结果

这些都可以从 State 里取出来。


二十六、最后总结

工作流引擎里的图构建和参数传递,可以用一句话概括:

GraphBuilder 负责把配置搭成图,State 负责让数据在节点之间流动。

再展开一点就是:

GraphBuilder 读取配置里的节点和边;
NodeAdapter 把每个配置节点适配成可执行动作;
GraphBuilder 把节点和边注册到 StateGraph;
系统根据拓扑关系动态识别入口节点和出口节点;
入口节点前面补 START;
出口节点后面补 END;
图编译后得到可执行的 CompiledGraph;
执行过程中,每个节点从 State 里读取 currentInput;
节点执行完成后,把输出写入 nodeOutputs;
同时把输出更新为新的 currentInput;
下一个节点再读取这个 currentInput;
如果需要引用更早的节点输出,就从 nodeOutputs 里按节点 ID 查;
Prompt 模板里的变量,则通过 input 和 reference 两种方式完成填充。

所以,一个工作流之所以能跑起来,靠的不是节点之间互相硬调用,而是三件事配合完成:

图决定执行顺序;
State 负责参数流转;
模板服务负责变量填充。

把这三层理解清楚,基本就抓住了工作流编排系统的核心。

简单说:

节点是一个个工位;
边是工位之间的路线;
State 是流转单;
currentInput 是当前接力棒;
nodeOutputs 是历史档案;
GraphBuilder 是施工队;
NodeAdapter 是转换器;
PromptTemplateService 是填空器。

这样一套设计,既能支持简单的线性流程,也能为后续的分支、并行、条件路由、失败重试、事件回调、可视化调试打下基础。

工作流引擎里的图是怎么构建的?节点之间的参数又是怎么传递的?
http://clxhxhhr.top/posts/591/
作者
clxstart
发布于
2026-09-13
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。
文章目录
目录