# CyberStrikeAI 图编排使用说明 [English](workflow-graph_en.md) 本文档说明 **图编排(Graph Orchestration)** 的完整使用方式:如何在画布上搭建流程、配置各类型节点、在节点之间传递数据,以及如何将流程绑定到角色并自动运行。 --- ## 一、在哪里使用图编排 1. 登录 CyberStrikeAI Web 端 2. 左侧导航进入 **图编排** 3. 在左侧列表选择已有流程,或新建流程 4. 在中央画布拖拽、连线、配置节点 5. 填写流程 **ID**、**名称**、**描述** 后点击 **保存** 保存后的流程可在 **角色管理** 中绑定到某个角色。绑定后,用户与该角色对话时会按流程图自动执行(`workflow_policy: auto`)。 --- ## 二、画布基本操作 | 操作 | 说明 | |------|------| | 添加节点 | 点击画布上方节点类型按钮(开始、工具、Agent、条件、审批、输出、结束) | | 连线 | 点击 **连线**,依次点击源节点和目标节点;再次点击 **连线** 退出连线模式 | | 选中元素 | 单击节点或连线,右侧显示 **节点属性** | | 删除选中 | 点击 **删除选中** 删除当前节点或连线 | | 自动布局 | 点击 **自动布局** 整理节点位置 | | 删除流程 | 点击 **删除** 删除整个流程定义 | **建议:** 每个流程至少包含 **1 个开始节点** 和 **1 个输出节点**;开始节点不应有入边,输出节点不应有出边。 --- ## 三、执行模型(先理解再配置) 图编排按 **有向图** 执行,引擎从 **开始** 节点出发,沿连线依次运行下游节点。 每次运行会维护一份内部状态,模板变量 `{{...}}` 从这里取值: | 内部状态 | 模板前缀 | 含义 | |----------|----------|------| | `inputs` | `{{inputs.xxx}}` | 流程启动时的输入(用户消息、会话 ID 等) | | `lastOutput` | `{{previous.xxx}}` | **上一个刚执行完** 的节点的输出 | | `outputs` | `{{outputs.xxx}}` | 全局 **命名变量池**(由节点的「输出变量名」写入) | | `nodeOutputs` | `{{节点ID.xxx}}` | 指定节点 ID 的完整输出对象 | ### 3.1 `previous` 是什么? `{{previous.output}}` 表示 **紧邻的上一个执行节点** 的 `output` 字段。 - 每执行完一个节点,引擎都会更新 `lastOutput` - **不是**「画布上画线的上游」,而是 **实际执行顺序上的上一步** 示例: ```text 开始 → Agent A → Agent B ``` Agent B 的 `{{previous.output}}` = Agent A 的输出。 但若中间有条件节点: ```text 开始 → Agent A → 条件 → Agent B ``` Agent B 的 `{{previous.output}}` = **条件节点** 的输出(`true` / `false`),**不是** Agent A 的结果。 ### 3.2 `outputs` 是什么? `outputs` 是引擎在运行过程中维护的 **命名变量注册表**。 当 Agent、工具、输出 等节点配置了 **输出变量名**(字段 `output_key`)后,节点执行成功会把结果写入: ```text outputs["你填的变量名"] = 节点输出内容 ``` 之后 **任意下游节点** 都可以通过 `{{outputs.变量名}}` 引用,不要求两个节点直接相连。 示例: - Agent A 的 **输出变量名** 填 `agent_result1` - Agent B 的 **输入来源** 填 `{{outputs.agent_result1}}` 即使 A 和 B 之间隔着条件节点,B 仍能拿到 A 的输出。 ### 3.3 什么时候用 `previous`,什么时候用 `outputs`? | 场景 | 推荐写法 | |------|----------| | 两个节点 **直连**,只取上一步结果 | `{{previous.output}}` | | 中间有其他节点(条件、工具、审批等) | `{{outputs.变量名}}` | | 需要引用 **更早** 的某个节点结果 | `{{outputs.变量名}}` 或 `{{节点ID.output}}` | | 条件判断要基于某 Agent 的输出 | `{{outputs.变量名}} != ""` | | 读取用户最初输入 | `{{inputs.message}}` | **记忆口诀:** - `previous` = 上一步(链式、紧邻) - `outputs` = 按名字取(跨节点、可回溯) --- ## 四、模板语法 ### 4.1 基本格式 ```text {{变量路径}} ``` 支持字母、数字、下划线、点、连字符,例如: ```text {{previous.output}} {{outputs.agent_result1}} {{inputs.message}} {{inputs.conversationId}} {{previous.matched}} {{node-abc123.output}} ``` ### 4.2 可用路径一览 | 路径 | 说明 | |------|------| | `{{inputs.message}}` | 用户消息(开始节点输入) | | `{{inputs.conversationId}}` | 会话 ID | | `{{inputs.projectId}}` | 项目 ID | | `{{previous.output}}` | 上一节点主输出 | | `{{previous.matched}}` | 上一条件节点的匹配结果(`true` / `false`) | | `{{outputs.变量名}}` | 某节点注册过的命名输出 | | `{{节点ID.output}}` | 指定节点 ID 的 `output` 字段 | ### 4.3 条件表达式 条件节点和连线条件支持简单比较: ```text {{outputs.agent_result1}} != "" {{previous.output}} == "ok" {{outputs.count}} == "100" ``` 规则: - 使用 `==` 或 `!=` 做字符串比较(两侧会自动去掉首尾空格和引号) - 无比较符时,非空且不为 `false` / `0` / `null` 视为真 --- ## 五、节点类型与配置 ### 5.1 开始(start) 流程入口,将用户输入注入 `inputs`。 | 字段 | 说明 | 默认值 | |------|------|--------| | 输入变量 | 逗号分隔的输入键名 | `message, conversationId, projectId` | 开始节点输出包含:`output`、`message`、`conversationId`、`projectId`。 ### 5.2 Agent(agent) 调用大模型 Agent 处理任务,支持多种运行模式。 | 字段 | 说明 | 默认值 | |------|------|--------| | Agent 模式 | `eino_single` / `deep` / `plan_execute` / `supervisor` | `eino_single` | | 输入来源 | 上游数据的模板表达式 | `{{previous.output}}` | | 节点指令 | 本节点要完成的任务描述 | 空 | | 输出变量名 | 写入 `outputs` 的键名 | `agent_result` | **消息拼装规则:** - 仅填 **节点指令**:直接把指令发给 Agent - 仅填 **输入来源**:生成「请基于上游节点输出继续处理:…」 - 两者都填:合并为「上游输入 + 节点指令」 Agent 节点执行后: - `previous.output` 更新为本节点响应文本 - 若配置了 **输出变量名**,同时写入 `outputs[输出变量名]` ### 5.3 工具(tool) 调用已启用的 MCP 工具。 | 字段 | 说明 | 默认值 | |------|------|--------| | MCP 工具 | 工具名称(必填) | — | | 参数模板 | JSON,支持 `{{...}}` 模板 | `{}` | | 超时秒数 | 可选 | 空 | 示例参数模板: ```json {"target": "{{inputs.message}}", "port": "443"} ``` 若配置了 **输出变量名**,工具返回结果会写入 `outputs`。 ### 5.4 条件(condition) 根据表达式计算分支,输出 `matched`(`true` / `false`)。 | 字段 | 说明 | 默认值 | |------|------|--------| | 条件表达式 | 支持 `{{...}}` 与 `==` / `!=` | `{{previous.output}} != ""` | **分支规则:** - 从条件节点连出的 **第一条线** 默认为 **「是」** 分支(`matched == true`) - **第二条线** 默认为 **「否」** 分支(`matched == false`) - 连线标签可写 `是` / `否`(或 `yes` / `no`、`true` / `false`)辅助识别 - 第三条及以后的出边需在 **连线条件** 中自定义表达式 连线条件示例(选中连线后在右侧配置): ```text {{previous.matched}} == "true" {{previous.matched}} == "false" ``` ### 5.5 审批(hitl) 人工确认检查点(当前为记录模式,自动标记 `approved: true` 并继续)。 | 字段 | 说明 | 默认值 | |------|------|--------| | 审批提示 | 支持模板 | `请审批该步骤是否继续执行` | | 审批方 | `human` / `audit_agent` | `human` | ### 5.6 输出(output) 将流程最终结果写入 `outputs`,供结束摘要和对话展示使用。 | 字段 | 说明 | 默认值 | |------|------|--------| | 输出变量名 | 必填,最终结果的键名 | `result` | | 变量来源 | 模板表达式,决定写入的值 | `{{previous.output}}` | **注意:** 输出节点是流程的「出口」,不应再有出边。 ### 5.7 结束(end) 可选节点,用于生成结束摘要模板(角色绑定流程中较少单独使用)。 | 字段 | 说明 | 默认值 | |------|------|--------| | 结束摘要模板 | 支持 `{{outputs.xxx}}` | `{{outputs.result}}` | --- ## 六、连线配置 选中 **连线** 后,右侧可配置 **连线条件**。 | 场景 | 示例 | |------|------| | 普通节点后的过滤 | `{{previous.output}} == "ok"` | | 条件节点「是」分支 | `{{previous.matched}} == "true"` | | 条件节点「否」分支 | `{{previous.matched}} == "false"` | 若不填连线条件: - 非条件节点:连线始终放行 - 条件节点:按出边顺序自动分配是/否分支 --- ## 七、完整示例:跨条件节点传递 Agent 输出 ### 7.1 流程结构 ```text 开始 → Agent(生成初始值)→ 条件 → Agent(加工)→ 输出 ↘ 否 → 输出 ``` ### 7.2 节点配置 **Agent 1(第一个 Agent)** | 字段 | 值 | |------|-----| | 节点指令 | 只输出 `123333333` | | 输出变量名 | `agent_result1` | **条件** | 字段 | 值 | |------|-----| | 条件表达式 | `{{outputs.agent_result1}} != ""` | **Agent 2(第二个 Agent)** | 字段 | 值 | |------|-----| | 输入来源 | `{{outputs.agent_result1}}` | | 节点指令 | 在输入基础上加 100,然后输出 | | 输出变量名 | `agent_result` | **输出** | 字段 | 值 | |------|-----| | 输出变量名 | `result` | | 变量来源 | `{{outputs.agent_result}}` | ### 7.3 常见错误 | 错误配置 | 原因 | |----------|------| | Agent 2 输入来源写 `{{previous.output}}` | `previous` 指向条件节点,得到的是 `true`/`false`,不是 Agent 1 的文本 | | 未给 Agent 1 填输出变量名 | `outputs.agent_result1` 不存在,下游取到空值 | | 条件表达式写 `{{previous.output}}` | 判断的是开始节点或上一节点的输出,而非 Agent 1 的命名变量 | --- ## 八、绑定角色并运行 ### 8.1 在角色管理中绑定 1. 进入 **角色管理**,编辑或新建角色 2. 选择 **工作流 / 图编排** 绑定的流程 ID 3. 策略设为 `auto`(默认:有 `workflow_id` 时自动执行) 4. 保存角色 也可在角色 YAML 中直接配置: ```yaml name: 工作流测试 workflow_id: "1233" workflow_version: latest workflow_policy: auto ``` ### 8.2 运行效果 用户选择该角色并发送消息后: 1. 引擎加载对应 `graph_json` 并按图执行 2. 对话页可看到 `workflow_start`、`workflow_node_start`、Agent 推理等进度事件 3. 流程结束后返回摘要,列出 `outputs` 中所有命名输出 若未配置输出节点或条件未命中,`outputs` 可能为空,摘要会提示检查输出节点与分支。 --- ## 九、保存前校验规则 保存时系统会自动检查: | 规则 | 说明 | |------|------| | 必须有开始节点 | 至少 1 个 `start` | | 必须有输出节点 | 至少 1 个 `output`,且填写输出变量名 | | 连线合法 | 源/目标节点存在,不能自环 | | 开始节点无入边 | 开始节点不能被指向 | | 输出节点无出边 | 输出节点后不应再连线 | | 工具节点 | 必须选择 MCP 工具 | | 条件节点 | 必须填写表达式;建议 1~2 条出边(是/否) | --- ## 十、排错指南 | 现象 | 可能原因 | 处理建议 | |------|----------|----------| | 下游拿到空值 | 上游未配置输出变量名 | 给上游 Agent/工具填 **输出变量名**,下游用 `{{outputs.xxx}}` | | 下游拿到 `true`/`false` | 误用 `{{previous.output}}`,上一步是条件节点 | 改用 `{{outputs.xxx}}` | | 条件总走「否」 | 表达式与真实输出格式不一致 | 检查 Agent 输出是否带引号、换行;用 `!= ""` 先验证 | | 流程无最终输出 | 未命中输出节点所在分支 | 检查条件分支连线;确保至少一条路径到达 **输出** 节点 | | 角色对话未跑流程 | 角色未绑定或未启用 | 确认 `workflow_id`、`workflow_policy: auto`、流程 `enabled: true` | | 工具节点失败 | 参数 JSON 不合法或工具未启用 | 检查参数模板;在 MCP 中启用对应工具 | --- ## 十一、最佳实践 1. **命名规范**:为每个需要被引用的节点设置有意义的输出变量名,如 `scan_result`、`parsed_targets`,避免都叫 `agent_result`。 2. **跨节点传参优先用 `outputs`**:只要中间可能插入条件、工具、审批节点,就应用命名变量。 3. **`previous` 仅用于直连**:A → B 且无中间节点时,`{{previous.output}}` 最简洁。 4. **条件判断引用源数据**:判断 Agent 输出时用 `{{outputs.xxx}}`,不要用 `{{previous.output}}`(除非条件紧跟在目标 Agent 之后)。 5. **每条路径都要有出口**:确保「是」「否」分支最终都能到达 **输出** 节点(或你期望的终点)。 6. **保存前跑一遍**:用简单指令(如固定字符串输出)验证数据传递,再替换为真实业务逻辑。 --- ## 十二、相关代码位置(开发者参考) | 模块 | 路径 | |------|------| | 执行引擎 | `internal/workflow/runner.go` | | 画布前端 | `web/static/js/workflows.js` | | 流程 API | `internal/handler/workflow.go` | | 角色绑定 | `internal/config/config.go`(`workflow_id` 字段) |