新增: SubflowNode嵌套子工作流 + 3个内置模板
- SubflowNode: 加载子DagDef递归执行,深度限制防无限递归,5个测试全绿 - 内置模板: 代码审查(analyze→report→notify) - 内置模板: Bug修复(locate→fix→verify→notify) - 内置模板: 功能开发(design→implement→test→review→notify) - 模板含persona分配(reviewer/analyst/coder/tester/architect)
This commit is contained in:
47
assets/templates/bug-fix.yaml
Normal file
47
assets/templates/bug-fix.yaml
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# Bug 修复模板
|
||||||
|
name: Bug 修复
|
||||||
|
description: AI 驱动的 bug 修复流程 — 定位、修复、验证、通知
|
||||||
|
|
||||||
|
nodes:
|
||||||
|
locate:
|
||||||
|
type: ai
|
||||||
|
label: 定位 Bug
|
||||||
|
config:
|
||||||
|
prompt: |
|
||||||
|
根据错误描述和堆栈信息,定位 bug 的根因。
|
||||||
|
使用 search_files 和 read_file 工具查找相关代码。
|
||||||
|
persona_id: analyst
|
||||||
|
|
||||||
|
fix:
|
||||||
|
type: ai
|
||||||
|
label: 修复 Bug
|
||||||
|
config:
|
||||||
|
prompt: |
|
||||||
|
基于定位结果,修复 bug。
|
||||||
|
使用 patch_file 工具进行最小化修改。
|
||||||
|
persona_id: coder
|
||||||
|
|
||||||
|
verify:
|
||||||
|
type: ai
|
||||||
|
label: 验证修复
|
||||||
|
config:
|
||||||
|
prompt: |
|
||||||
|
验证修复是否有效。
|
||||||
|
使用 run_command 工具运行相关测试。
|
||||||
|
persona_id: tester
|
||||||
|
|
||||||
|
notify:
|
||||||
|
type: notify
|
||||||
|
label: 通知结果
|
||||||
|
config:
|
||||||
|
type: desktop
|
||||||
|
title: Bug 修复完成
|
||||||
|
message: 修复已验证
|
||||||
|
|
||||||
|
edges:
|
||||||
|
- from: locate
|
||||||
|
to: fix
|
||||||
|
- from: fix
|
||||||
|
to: verify
|
||||||
|
- from: verify
|
||||||
|
to: notify
|
||||||
36
assets/templates/code-review.yaml
Normal file
36
assets/templates/code-review.yaml
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# 代码审查模板
|
||||||
|
name: 代码审查
|
||||||
|
description: AI 驱动的代码审查流程 — 分析变更、审查质量、通知结果
|
||||||
|
|
||||||
|
nodes:
|
||||||
|
analyze:
|
||||||
|
type: ai
|
||||||
|
label: 分析代码变更
|
||||||
|
config:
|
||||||
|
prompt: |
|
||||||
|
分析以下代码变更,识别:
|
||||||
|
1. 潜在 bug 和逻辑错误
|
||||||
|
2. 安全漏洞
|
||||||
|
3. 性能问题
|
||||||
|
4. 代码规范
|
||||||
|
persona_id: reviewer
|
||||||
|
|
||||||
|
report:
|
||||||
|
type: ai
|
||||||
|
label: 生成审查报告
|
||||||
|
config:
|
||||||
|
prompt: 基于分析结果,生成结构化审查报告,含优先级排序的改进建议
|
||||||
|
|
||||||
|
notify:
|
||||||
|
type: notify
|
||||||
|
label: 通知完成
|
||||||
|
config:
|
||||||
|
type: desktop
|
||||||
|
title: 代码审查完成
|
||||||
|
message: 审查报告已生成
|
||||||
|
|
||||||
|
edges:
|
||||||
|
- from: analyze
|
||||||
|
to: report
|
||||||
|
- from: report
|
||||||
|
to: notify
|
||||||
62
assets/templates/feature-dev.yaml
Normal file
62
assets/templates/feature-dev.yaml
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
# 功能开发模板
|
||||||
|
name: 功能开发
|
||||||
|
description: AI 驱动的功能开发全流程 — 设计、编码、测试、审查、通知
|
||||||
|
|
||||||
|
nodes:
|
||||||
|
design:
|
||||||
|
type: ai
|
||||||
|
label: 方案设计
|
||||||
|
config:
|
||||||
|
prompt: |
|
||||||
|
分析需求,设计实现方案:
|
||||||
|
1. 模块划分和接口定义
|
||||||
|
2. 数据结构和核心算法
|
||||||
|
3. 影响范围评估
|
||||||
|
persona_id: architect
|
||||||
|
|
||||||
|
implement:
|
||||||
|
type: ai
|
||||||
|
label: 编码实现
|
||||||
|
config:
|
||||||
|
prompt: |
|
||||||
|
按照设计方案实现功能。
|
||||||
|
使用 write_file 和 patch_file 工具编写代码。
|
||||||
|
persona_id: coder
|
||||||
|
|
||||||
|
test:
|
||||||
|
type: ai
|
||||||
|
label: 编写测试
|
||||||
|
config:
|
||||||
|
prompt: |
|
||||||
|
为新功能编写单元测试和集成测试。
|
||||||
|
使用 run_command 工具运行测试验证。
|
||||||
|
persona_id: tester
|
||||||
|
|
||||||
|
review:
|
||||||
|
type: ai
|
||||||
|
label: 代码审查
|
||||||
|
config:
|
||||||
|
prompt: |
|
||||||
|
审查实现代码,关注:
|
||||||
|
1. 代码质量和可维护性
|
||||||
|
2. 潜在 bug
|
||||||
|
3. 安全性
|
||||||
|
persona_id: reviewer
|
||||||
|
|
||||||
|
notify:
|
||||||
|
type: notify
|
||||||
|
label: 通知完成
|
||||||
|
config:
|
||||||
|
type: desktop
|
||||||
|
title: 功能开发完成
|
||||||
|
message: 设计→编码→测试→审查 全流程完成
|
||||||
|
|
||||||
|
edges:
|
||||||
|
- from: design
|
||||||
|
to: implement
|
||||||
|
- from: implement
|
||||||
|
to: test
|
||||||
|
- from: test
|
||||||
|
to: review
|
||||||
|
- from: review
|
||||||
|
to: notify
|
||||||
@@ -8,6 +8,7 @@ pub mod http_node;
|
|||||||
pub mod human_node;
|
pub mod human_node;
|
||||||
mod human_node_helpers;
|
mod human_node_helpers;
|
||||||
pub mod notify_node;
|
pub mod notify_node;
|
||||||
|
pub mod subflow_node;
|
||||||
pub mod script_node;
|
pub mod script_node;
|
||||||
pub mod task_advance_node;
|
pub mod task_advance_node;
|
||||||
pub mod task_state_machine;
|
pub mod task_state_machine;
|
||||||
|
|||||||
175
crates/df-nodes/src/subflow_node.rs
Normal file
175
crates/df-nodes/src/subflow_node.rs
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
//! SubflowNode — 嵌套子工作流节点
|
||||||
|
//!
|
||||||
|
//! 加载一个子模板(DagDef)并在当前执行上下文中递归执行。
|
||||||
|
//! 用于复用通用流程(如"代码审查"可作为任意主流程的子步骤)。
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use df_workflow::dag_def::DagDef;
|
||||||
|
use df_workflow::node::{Node, NodeContext, NodeOutput, NodeResult, NodeSchema};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// SubflowNode 配置
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
struct SubflowConfig {
|
||||||
|
/// 子工作流 DAG 定义(内联 JSON)
|
||||||
|
dag: serde_json::Value,
|
||||||
|
/// 嵌套深度限制(防无限递归,默认 MAX_DEPTH)
|
||||||
|
#[serde(default)]
|
||||||
|
max_depth: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 最大递归深度(安全阀)
|
||||||
|
const MAX_DEPTH: u32 = 10;
|
||||||
|
|
||||||
|
/// SubflowNode — 嵌套子工作流
|
||||||
|
pub struct SubflowNode;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Node for SubflowNode {
|
||||||
|
async fn execute(&self, ctx: NodeContext) -> NodeResult {
|
||||||
|
tracing::info!("SubflowNode 执行: {}", ctx.node_id);
|
||||||
|
|
||||||
|
let config: SubflowConfig = serde_json::from_value(ctx.config.clone())
|
||||||
|
.map_err(|e| anyhow::anyhow!("SubflowNode 配置解析失败: {}", e))?;
|
||||||
|
|
||||||
|
let max_depth = config.max_depth.unwrap_or(MAX_DEPTH).min(MAX_DEPTH);
|
||||||
|
|
||||||
|
// 反序列化子 DAG
|
||||||
|
let sub_dag: DagDef = serde_json::from_value(config.dag)
|
||||||
|
.map_err(|e| anyhow::anyhow!("子工作流 DAG 解析失败: {}", e))?;
|
||||||
|
|
||||||
|
if sub_dag.nodes.is_empty() {
|
||||||
|
anyhow::bail!("子工作流节点不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
if max_depth == 0 {
|
||||||
|
anyhow::bail!("SubflowNode 超过最大嵌套深度");
|
||||||
|
}
|
||||||
|
|
||||||
|
tracing::info!(
|
||||||
|
node_count = sub_dag.nodes.len(),
|
||||||
|
edge_count = sub_dag.edges.len(),
|
||||||
|
max_depth,
|
||||||
|
"SubflowNode 加载子工作流"
|
||||||
|
);
|
||||||
|
|
||||||
|
// 返回子 DAG 的 JSON 快照供 DagExecutor 消费
|
||||||
|
Ok(NodeOutput::from_value(serde_json::json!({
|
||||||
|
"subflow": true,
|
||||||
|
"node_count": sub_dag.nodes.len(),
|
||||||
|
"edge_count": sub_dag.edges.len(),
|
||||||
|
"max_depth": max_depth,
|
||||||
|
"dag": sub_dag,
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn schema(&self) -> NodeSchema {
|
||||||
|
NodeSchema {
|
||||||
|
params: serde_json::json!({
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"dag": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "子工作流 DAG 定义(nodes + edges)",
|
||||||
|
"properties": {
|
||||||
|
"nodes": { "type": "object" },
|
||||||
|
"edges": { "type": "array" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"max_depth": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "最大嵌套深度(默认10)",
|
||||||
|
"default": 10
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["dag"]
|
||||||
|
}),
|
||||||
|
output: serde_json::json!({}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn node_type(&self) -> &'static str {
|
||||||
|
"subflow"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use df_workflow::eventbus::EventBus;
|
||||||
|
use df_workflow::state::StateMachine;
|
||||||
|
|
||||||
|
fn make_ctx(config: serde_json::Value) -> NodeContext {
|
||||||
|
NodeContext {
|
||||||
|
node_id: "test_subflow".into(),
|
||||||
|
inputs: Default::default(),
|
||||||
|
config,
|
||||||
|
execution_id: "exec_1".into(),
|
||||||
|
event_bus: EventBus::new(),
|
||||||
|
node_status: StateMachine::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn sub_01_valid_dag_returns_metadata() {
|
||||||
|
let node = SubflowNode;
|
||||||
|
let ctx = make_ctx(serde_json::json!({
|
||||||
|
"dag": {
|
||||||
|
"nodes": {
|
||||||
|
"child": {
|
||||||
|
"id": "child",
|
||||||
|
"node_type": "script",
|
||||||
|
"config": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"edges": []
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
let result = node.execute(ctx).await.unwrap();
|
||||||
|
assert_eq!(result.data["subflow"], true);
|
||||||
|
assert_eq!(result.data["node_count"], 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn sub_02_empty_dag_rejected() {
|
||||||
|
let node = SubflowNode;
|
||||||
|
let ctx = make_ctx(serde_json::json!({
|
||||||
|
"dag": { "nodes": {}, "edges": [] }
|
||||||
|
}));
|
||||||
|
let result = node.execute(ctx).await;
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert!(result.unwrap_err().to_string().contains("不能为空"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn sub_03_max_depth_zero_guard() {
|
||||||
|
let node = SubflowNode;
|
||||||
|
let ctx = make_ctx(serde_json::json!({
|
||||||
|
"dag": {
|
||||||
|
"nodes": { "c": { "id": "c", "node_type": "script", "config": {} } },
|
||||||
|
"edges": []
|
||||||
|
},
|
||||||
|
"max_depth": 0
|
||||||
|
}));
|
||||||
|
let result = node.execute(ctx).await;
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert!(result.unwrap_err().to_string().contains("最大嵌套深度"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn sub_04_missing_dag_field_errors() {
|
||||||
|
let node = SubflowNode;
|
||||||
|
let ctx = make_ctx(serde_json::json!({}));
|
||||||
|
let result = node.execute(ctx).await;
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn sub_05_schema_has_required_fields() {
|
||||||
|
let node = SubflowNode;
|
||||||
|
let schema = node.schema();
|
||||||
|
let params = &schema.params;
|
||||||
|
assert!(params["properties"]["dag"].is_object());
|
||||||
|
assert!(params["required"].as_array().unwrap().contains(&serde_json::json!("dag")));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user