说明:本文档基于当前代码实现梳理,覆盖后端执行器(
backend/src/main/java/com/agent/management/engine/executor)与前端 IO 推断逻辑(frontend/src/utils/ioInference.js)。 文中“转换器”一栏既描述当前运行时的实际行为,也给出推荐的统一转换规则,供后续在TemplateRenderer/ContextPromptHelper或前置条件校验中实现。
工作流上下文 WorkflowContext 内部维护一张扁平变量表 variables: ConcurrentHashMap<String, Object>:
request.inputs 全部放入。WorkflowLevelExecutor 调用 context.setNodeOutput(nodeId, output),把 output 中的每个 key-value 写入 variables。| 消费方式 | 触发位置 | 当前行为 |
|---|---|---|
{{变量名}} 模板渲染 |
TemplateRenderer |
从 variables 取值后调用 value.toString() 替换;变量不存在时保留占位符。 |
| 自动系统提示注入 | ContextPromptHelper |
把全部 variables 按 - key:value 格式拼接为系统提示,value 同样走 toString(),超长截断至 2000 字符。 |
| 前置条件校验 | WorkflowLevelExecutor.checkPreconditions |
仅校验存在性与类型兼容性,不做类型转换。 |
| Agent / Skill 用户消息 | AgentExecutor.buildUserMessage / HermesAgentExecutor.buildUserMessage |
按 Skill.inputs 定义取值后 toString() 拼接。 |
| 条件分支判断 | ConditionExecutor |
把全部 variables 的 toString() 拼成上下文摘要,交给 LLM 判断分支编号。 |
前后端统一的 IO 类型定义:
string | number | boolean | array | object | filePath | directoryPath
来源:
com.agent.management.model.dto.IOFieldfrontend/src/utils/ioInference.js 的 IO_TYPES| 节点 type | 中文名 | 后端执行器 | 前端组件 |
|---|---|---|---|
userInput |
用户输入 | UserInputExecutor |
InputNode.vue |
llm |
大模型处理 | LlmExecutor |
LLMNode.vue |
agent |
智能体 | AgentExecutor(hermes=false)/ HermesAgentExecutor(hermes=true) |
AgentNode.vue |
skill |
技能 | SkillExecutor(委托给 agent 执行器) |
SkillNode.vue |
smartAction |
智能操作 | SmartActionExecutor(hermes=false)/ HermesSmartActionExecutor(hermes=true) |
SmartActionNode.vue |
knowledgeRetrieval |
知识库检索 | KnowledgeRetrievalExecutor |
KnowledgeRetrievalNode.vue |
condition |
条件分支 | ConditionExecutor |
ConditionNode.vue |
output |
输出 | OutputExecutor |
OutputNode.vue |
自动输入:无。节点本身只负责把运行时已经注入到 variables 中的初始输入值注册为可用变量。
自动生成输出:
| 输出字段 | 类型 | 来源 | 说明 |
|---|---|---|---|
variables[i].name |
由前端声明的类型决定 | request.inputs 中同名 key |
节点 data.variables 数组里每个条目的 name 字段;若运行时未提供该 key,则不写入。 |
例如:data.variables =
[{name:"question", type:"string"}],则输出变量名为question。
自动输入:
| 输入字段 | 类型 | 来源 | 说明 |
|---|---|---|---|
systemPrompt 中的 {{var}} |
string | 上游 variables | 模板渲染后作为系统提示。 |
userPrompt 中的 {{var}} |
string | 上游 variables | 模板渲染后作为用户提示。 |
data.inputs[] 声明的字段 |
任意 | 上游 variables | 用于前端边映射校验与 checkPreconditions 前置条件校验。 |
| 全部上游 variables | object/map | 上下文 | 通过 ContextPromptHelper.merge(...) 自动追加到系统提示词末尾。 |
自动生成输出:
| 输出字段 | 类型 | 来源 | 说明 |
|---|---|---|---|
outputs[0].name 或 result |
string | LLM 响应文本 | 单输出场景。若 data.outputs 为空,默认变量名为 result。 |
outputs[i].name(i≥0) |
由 outputs[i].type 决定 | LLM 响应经 JSON 解析 | 多输出场景(outputs 长度 > 1):由 StructuredOutputHelper 注入 JSON 指令并解析。 |
多输出字段类型转换见第 4 节。
两者对外接口一致;SkillExecutor 把 skillId 复制为 agentId 后委托给 agent 执行器。
自动输入:
| 输入字段 | 类型 | 来源 | 说明 |
|---|---|---|---|
关联 Skill 的 inputs[] |
由 Skill 声明 | 上游 variables | buildUserMessage 按 Skill 输入定义从 context 取值。 |
| 全部上游 variables | object/map | 上下文 | 通过 ContextPromptHelper.merge(...) 追加到 SKILL.md 系统提示后。 |
注意:当前 agent/skill 节点的前端默认数据中没有
inputs/outputs数组,依赖 Skill 元数据中的inputs/outputs。
自动生成输出:
| 输出字段 | 类型 | 来源 | 说明 |
|---|---|---|---|
outputs[0].name 或 result |
string | Agent 执行返回的 finalText | 当前仅支持单输出,多 outputs 时也只取第一个。 |
自动输入:
| 输入字段 | 类型 | 来源 | 说明 |
|---|---|---|---|
actionPrompt 中的 {{var}} |
string | 上游 variables | 模板渲染后作为操作要求。 |
data.inputs[] 声明的字段 |
任意 | 上游 variables | 用于前端边映射校验与前置条件校验。 |
| 全部上游 variables | object/map | 上下文 | 通过 ContextPromptHelper 自动注入系统提示。 |
自动生成输出:
| 输出字段 | 类型 | 来源 | 说明 |
|---|---|---|---|
outputs[0].name 或 result |
string | LLM / Hermes 执行结果 | 单输出场景,默认变量名 result。 |
自动输入:
| 输入字段 | 类型 | 来源 | 说明 |
|---|---|---|---|
query 中的 {{var}} |
string | 上游 variables | 模板渲染后作为检索语句。 |
data.inputs[] 声明的字段 |
任意 | 上游 variables | 用于前端边映射校验与前置条件校验。 |
source / topK / 各类 sourceIds |
配置项 | 节点 data | 决定检索范围与来源类型。 |
自动生成输出:
| 输出字段 | 类型 | 来源 | 说明 |
|---|---|---|---|
evidences |
List<RagEvidence> |
检索结果 | 命中的证据列表,每个元素含 id / sourceType / evidenceType / title / content / score / sourceName / sourceRef / payload / metadata。 |
evidenceCount |
number | 检索结果 | evidences.size()。 |
sourceType |
string | 检索结果 | DOCUMENT / STRUCTURED_DATA / GRAPH / HYBRID。 |
diagnostics |
object | 检索结果 | 各来源的诊断信息,key 可能为 datasource:<id> / graph:<id> / error:...。 |
knowledgeBaseId |
number | 节点 data(仅 hybrid) | 混合检索时指定的知识库 ID。 |
enabledSources |
object/map | 后端结果(仅 hybrid) | 各来源是否启用。 |
默认输出变量:若用户未在
data.outputs中自定义,前端默认暴露evidences / evidenceCount / sourceType / diagnostics。
自动输入:
| 输入字段 | 类型 | 来源 | 说明 |
|---|---|---|---|
conditions[i].expression 中的 {{var}} |
string | 上游 variables | 条件表达式模板。 |
data.inputs[] 声明的字段 |
任意 | 上游 variables | 用于前端边映射校验。 |
| 全部上游 variables | object/map | 上下文 | ConditionExecutor 把 variables 摘要后交给 LLM 判断分支。 |
自动生成输出:
条件节点不向 variables 写入任何变量。它通过 NodeExecutionResult.selectedBranch 返回分支句柄(如 branch-0、branch-1),由 WorkflowLevelExecutor.activateDownstream 按 edge.sourceHandle 过滤下游激活节点。
自动输入:
| 输入字段 | 类型 | 来源 | 说明 |
|---|---|---|---|
data.fields[] 声明的字段 |
任意 | 上游 variables | 用于前端选择最终输出字段。 |
| 全部上游 variables | object/map | 上下文 | 节点执行时直接收集整个 variables 表。 |
自动生成输出:
| 输出字段 | 类型 | 来源 | 说明 | |||
|---|---|---|---|---|---|---|
| 全部上游 variables | 混合 | 上下文 | OutputExecutor 把 context.getVariables() 原样放入输出。 |
|||
_workingDirFiles |
array | 工作目录扫描 | 工作目录中经 .agentignore 过滤后的相对文件路径列表。 |
|||
_runId |
string | 上下文 | 本次运行 ID。 |
| 源实际值 | 目标类型 string | 目标类型 number | 目标类型 boolean | 目标类型 object | 目标类型 array | 目标类型 filePath / directoryPath |
|---|---|---|---|---|---|---|
String |
原值 | 尝试 Double.parseDouble;失败取 NaN |
"true"/"1"/"yes" 为 true,其余 false |
尝试 JSON 解析为对象;失败封装为 {value: 原值} |
尝试 JSON 解析为数组;失败封装为 [原值] |
若值对应工作目录中合法路径则通过;否则保留字符串 |
Number |
toString() |
原值 | != 0 为 true |
{value: 数字} |
[数字] |
不适用 |
Boolean |
toString() |
true→1,false→0 | 原值 | {value: true/false} |
[true/false] |
不适用 |
Map / POJO |
ObjectMapper.writeValueAsString |
若 map 仅含数值字段可尝试;否则 NaN | 非空 map 为 true | 原值 | [Map.Entry] 列表 |
不适用 |
Collection / Array |
拼接为字符串(推荐 JSON) | 长度值 | 非空为 true | 若长度为 1 取首元素;否则 {items: 数组} |
原值 | 不适用 |
RagEvidence(检索证据) |
content 字段内容 |
无意义 | 非空为 true | 将 POJO 转为 Map(含 title/content/score...) | [证据Map] |
不适用 |
前后端已有的类型兼容规则:
filePath / directoryPath / number / boolean → 可隐式转为 string。array → 可隐式赋值给 object。result(string)| 下游目标类型 | 转换器行为 |
|---|---|
string |
直接传递完整文本。 |
number |
尝试提取文本中的第一个数字;无数字返回 NaN。 |
boolean |
文本为 "true"/"1"/"yes" 返回 true; "false"/"0"/"no" 返回 false;其余按非空判断。 |
object |
① 尝试整段 JSON 解析为 Map;② 失败则取文本前 2000 字符封装为 {result: text}。 |
array |
① 尝试 JSON 解析为 List;② 失败则按行/段落拆分后封装为 ["line1", "line2", ...]。 |
由 StructuredOutputHelper.convertValue 已提供基础转换:
number:value.isNumber() ? value.numberValue() : Double.parseDouble(value.asText())boolean:value.isBoolean() ? value.booleanValue() : Boolean.parseBoolean(value.asText())array / object:MAPPER.treeToValue(value, Object.class)toString()evidences(List<RagEvidence>)这是用户问题中重点示例的场景。
| 下游目标类型 | 转换器行为 |
|---|---|
string |
取 Top-1 证据的 content 字段;若列表为空返回 ""。推荐可配置为 "top1" 或 "concat" 两种模式。 |
number |
返回 evidenceCount(即列表长度)。 |
boolean |
列表非空返回 true。 |
object |
取 Top-1 证据,将其字段(id/sourceType/evidenceType/title/content/score/sourceName/sourceRef/payload/metadata)转为 Map。 |
array |
取 Top-K(默认全部)证据列表,每条证据转为 Map。 |
filePath / directoryPath |
不适用,转换失败。 |
evidenceCount(number)| 下游目标类型 | 转换器行为 |
|---|---|
string |
String.valueOf(count) |
number |
原值 |
boolean |
count > 0 |
object |
{evidenceCount: count} |
array |
[count] |
sourceType(string)| 下游目标类型 | 转换器行为 |
|---|---|
string |
原值,如 "DOCUMENT" / "GRAPH" |
object |
{sourceType: value} |
array |
[value] |
diagnostics(Map)| 下游目标类型 | 转换器行为 |
|---|---|
string |
ObjectMapper.writeValueAsString(diagnostics) |
object |
原 Map |
array |
Map 的 entry 列表 [{key, value}, ...] |
由于前端已声明类型,转换器按声明类型直接透传;若下游目标类型与声明类型不兼容,再按 4.1 通用规则转换。
_workingDirFiles(array)
| 下游目标类型 | 转换器行为 |
|---|---|
string |
取第一条路径;空数组返回 ""。 |
array |
原数组 |
object |
{files: array} |
| 关注点 | 文件路径 |
|---|---|
| 节点执行器接口 | backend/src/main/java/com/agent/management/engine/NodeExecutor.java |
| 执行器注册与调度 | backend/src/main/java/com/agent/management/engine/WorkflowLevelExecutor.java |
| 上下文与变量存储 | backend/src/main/java/com/agent/management/engine/WorkflowContext.java |
{{变量}} 模板渲染 |
backend/src/main/java/com/agent/management/engine/TemplateRenderer.java |
| 上下文系统提示注入 | backend/src/main/java/com/agent/management/engine/ContextPromptHelper.java |
| 单/多输出变量名解析 | backend/src/main/java/com/agent/management/engine/NodeTypeUtils.java |
| LLM 结构化输出解析 | backend/src/main/java/com/agent/management/engine/StructuredOutputHelper.java |
| 前置条件类型校验 | backend/src/main/java/com/agent/management/engine/WorkflowLevelExecutor.java:294 |
| 前端 IO 推断与映射 | frontend/src/utils/ioInference.js |
| 前端节点默认数据 | frontend/src/utils/workflowNode.js |
| 证据数据结构 | backend/src/main/java/com/agent/management/rag/model/RagEvidence.java |
前端 WorkflowEditor.vue 中节点配置面板的"关联方式"下拉选项由 inputMappingOptions 计算得出,其范围:
getReachablePredecessors(targetId, allNodes, allEdges)),收集所有可达前序节点的输出字段。NodeWorkspace 的可达前驱定义一致(详见 docs/workflow-variable-scope-design.md 4.1 节),保证编辑期选项与运行期可见变量对齐。涉及的实现:
| 函数 | 位置 | 作用 |
|---|---|---|
getReachablePredecessors |
frontend/src/utils/ioInference.js |
反向 BFS 计算可达前驱节点列表 |
inputMappingOptions |
frontend/src/views/workflow/WorkflowEditor.vue |
列出可达前驱 × 输出字段作为关联选项 |
getNodeOutputs |
frontend/src/utils/ioInference.js |
提取单个节点的输出字段 |
TemplateRenderer 或新增 VariableConverter 中按“源字段 + 目标类型”注册转换函数,替代目前简单的 value.toString()。evidences 转 string 策略可配置:支持 top1、concat、json 等模式,满足不同下游节点需求。checkPreconditions 中引入转换器尝试,使 array→object、number→string 等兼容组合真正可用,而非仅做类型检查。ioInference.js 的 isTypeCompatible 基础上,增加转换器说明,让用户在连线和配置 mapping 时看到自动转换规则。