新增: AiNode自审实施方案设计(NodeContext注入task+prompt模板+5项实施)
This commit is contained in:
345
docs/02-架构设计/AiNode自审实施方案-2026-06-16.md
Normal file
345
docs/02-架构设计/AiNode自审实施方案-2026-06-16.md
Normal file
@@ -0,0 +1,345 @@
|
|||||||
|
# AiNode 自审实施方案(②-⑥)
|
||||||
|
|
||||||
|
> 决策基线:**决策 a 已定** —— `TaskRecord` 加 `output_json` 字段(task 中心,产出跟 task 走)。① df-storage 迁移(struct 字段 + migrations CREATE/ALTER + crud 白名单 + INSERT/UPDATE/SELECT 列)由同批另一 agent 落地,**本方案为前置依赖**。
|
||||||
|
>
|
||||||
|
> 范围:**仅设计文档(read-only),不改任何 .rs/.ts/.vue 代码**。file:line 证据均独立 grep/Read 核验,未采信文档/会话描述声明。
|
||||||
|
>
|
||||||
|
> 日期:2026-06-16 | 作者:AiNode 自审调研 agent
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、现状盘点(file:line 证据)
|
||||||
|
|
||||||
|
### 1.1 已就绪(自审闭环地基)
|
||||||
|
|
||||||
|
| 能力 | 位置 | 状态 |
|
||||||
|
|------|------|------|
|
||||||
|
| `NodeContext` 结构 | `crates/df-workflow/src/node.rs:12-26` | 字段:`node_id / inputs / config / execution_id / event_bus / node_status`,**无 task 数据、无 db 句柄** |
|
||||||
|
| AiNode 通用执行 | `crates/df-nodes/src/ai_node.rs:116-164` | `AiNode.execute` 调 LLM → 返 `NodeOutput{text, model, usage}`(仅内存,不落 task) |
|
||||||
|
| AiNode 参数解析 | `crates/df-nodes/src/ai_node.rs:35-109` | `parse_params(config, inputs)`:prompt 优先上游 inputs["prompt"] > config.prompt;provider 配置从 config 取 |
|
||||||
|
| HumanNode 审批 | `crates/df-nodes/src/human_node.rs:39-176` | 阻塞节点,发 `HumanApprovalRequest` 等响应;reject 关键字(L19-22)→ Err → 工作流 failed |
|
||||||
|
| testing 模板(拓扑) | `crates/df-nodes/src/task_workflow_templates.rs:57-72` | `ai_self_review` → `human_review` 串行;节点级 config 仅含 human title/options,**ai_self_review config 为空 `{}`** |
|
||||||
|
| 模板选择 | `crates/df-nodes/src/task_workflow_templates.rs:28-35` | `template_for(target_status)` 按 in_progress/testing/done 选模板 |
|
||||||
|
| DagDef 选模板 | `src-tauri/src/commands/workflow.rs:84-92` | 空 dag + target_status → `template_for` 自动选 |
|
||||||
|
| **deep_merge(④-1)** | `crates/df-workflow/src/executor.rs:102-108` + `crates/df-workflow/src/dag.rs:169-188` | executor 构建 NodeContext 时 `deep_merge(initial_config, node_config)`,节点级覆盖全局级;**模板节点 config 为空 → 全局 config 原样穿透** |
|
||||||
|
| 工作流联动任务 | `src-tauri/src/commands/workflow.rs:235-308` | `task_id`+`target_status` 都 Some 时,完成推进/失败退回(`regression_target` L46-54) |
|
||||||
|
| **TaskAdvanceNode db 注入先例** | `crates/df-nodes/src/task_advance_node.rs:98-110` + `src-tauri/src/state.rs:265-266` | 节点 struct 持 `db: Arc<Database>`,`NodeRegistry::register` 工厂闭包 move 捕获 `db.clone()` 注入 —— **AiNode 可复用同一模式** |
|
||||||
|
| run_workflow 全局 config | `src-tauri/src/commands/workflow.rs:67-76` | 签名含 `config: serde_json::Value`,闭包内 `executor.run(&runtime_dag, config)` 传为 initial_config |
|
||||||
|
|
||||||
|
### 1.2 待 ①迁移落地(前置依赖)
|
||||||
|
|
||||||
|
| 缺口 | 位置 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| struct 字段 | `crates/df-storage/src/models.rs:69-76` | ✅ 已有 `output_json: Option<String>`(决策 a 已落地到模型层) |
|
||||||
|
| migrations CREATE | `crates/df-storage/src/migrations.rs:371` | ✅ CREATE TABLE tasks 含 `output_json TEXT`(新库) |
|
||||||
|
| migrations ALTER(老库) | `crates/df-storage/src/migrations.rs:394-395` 附近 | ⚠️ 未见 `ALTER TABLE tasks ADD COLUMN output_json`(**老库升级缺列**,需 ①迁移补) |
|
||||||
|
| crud SELECT | `crates/df-storage/src/crud.rs:779` | ⚠️ `list_active` SELECT 列未含 output_json |
|
||||||
|
| crud INSERT/UPDATE | `crates/df-storage/src/crud.rs:748-762` | ⚠️ INSERT/UPDATE_FULL 语句未含 output_json 列 |
|
||||||
|
| crud 行映射 | `crates/df-storage/src/crud.rs:544` | ⚠️ `output_json: row.get("output_json")` 存在,但 SELECT 缺列 → 运行时 row.get 会报错(待 ①补 SELECT) |
|
||||||
|
| crud 白名单 | `crates/df-storage/src/crud.rs:342-343` | ⚠️ `allowed_columns_for("tasks")` **未含 output_json**(AiNode 写产出走通用 `update_field` 必需) |
|
||||||
|
|
||||||
|
**结论**:①迁移尚未完成(struct 已加字段,crud 链路与白名单未跟上)。本方案所有 ②-⑥ 步骤均**强依赖 ①完成**,尤其白名单与 SELECT 列。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、五项实施方案
|
||||||
|
|
||||||
|
### 2.1 方案①:NodeContext 注入 task 机制
|
||||||
|
|
||||||
|
**问题**:`ai_execute`/`ai_self_review` 需拿到 `task_id` + task 数据(description / output_json)+ provider 配置;`ai_execute` 还需 db 句柄写产出。当前 `NodeContext`(node.rs:12-26)无此二者。
|
||||||
|
|
||||||
|
**三选项对比**:
|
||||||
|
|
||||||
|
| 选项 | 机制 | 优点 | 缺点 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| **A. config 注入 task_id + 节点持 db 句柄**(推荐) | run_workflow 全局 config 注入 `task_id`+`provider{base_url,api_key,model,protocol}`;AiNode struct 加 `db: Arc<Database>`,state.rs 工厂闭包 move 注入(**复用 TaskAdvanceNode 先例**) | 零改 NodeContext(不动 node.rs);db 注入已有先例(state.rs:265);config 经 deep_merge 穿透;AiNode 与 TaskAdvanceNode 注入方式一致 | AiNode 从无状态单例变有状态(state.rs:257-259 工厂闭包需改 move db.clone) |
|
||||||
|
| B. NodeContext 加 task 字段 | 改 node.rs 加 `task: Option<TaskRecord>` + db 句柄字段 | 节点直接读,省一次 DB 查 | 改 NodeContext 影响所有 Node(HumanNode/SleepNode/test 构造全改,human_node.rs:236 make_ctx 等测试全改);executor.rs:99-113 构造点要读 DB;df-workflow 反向依赖 df-storage/df-task(循环依赖风险) |
|
||||||
|
| C. IPC handler | AiNode 通过 event_bus 发事件,前端/IPC 侧写 task | 解耦 | AiNode 在 DAG 后台线程,无 AppHandle/State;引入跨线程往返;HumanNode 那套 broadcast 模式不适合写 DB;过度复杂 |
|
||||||
|
|
||||||
|
**推荐:选项 A**。理由:
|
||||||
|
1. **零侵入 NodeContext** —— 不动 node.rs,所有现有 Node 与测试(human_node.rs:236 make_ctx、executor.rs:99-113)零改动。
|
||||||
|
2. **先例已验证** —— TaskAdvanceNode(task_advance_node.rs:98-110)就是这套:struct 持 `db: Arc<Database>`,state.rs:265-266 工厂闭包 `move |_config| { Box::new(...::new(db.clone())) }` 注入。AiNode 照搬。
|
||||||
|
3. **config 穿透已就绪** —— run_workflow 全局 config(workflow.rs:72)→ executor deep_merge(executor.rs:102-108)→ 模板节点 config 空 `{}` → 全局 config 原样进 `NodeContext.config`。前端调 `workflowApi.run(name, {}, config, taskId, target)`(workflow.ts:12-26)即可把 task_id/provider 推进去。
|
||||||
|
4. **DB 句柄天然可得** —— state.rs:205 已有 `db: Arc<Database>`,build_registry 入参就是它(state.rs:252)。
|
||||||
|
|
||||||
|
**注入路径**:
|
||||||
|
```
|
||||||
|
前端 TaskDetail.handleWorkflowAdvance
|
||||||
|
→ workflowApi.run(name, {}, {task_id, provider:{base_url,api_key,model,protocol}}, taskId, "testing") (workflow.ts:12-26)
|
||||||
|
→ run_workflow(task_id, target_status, config) (workflow.rs:67)
|
||||||
|
→ executor.run(dag, config) // config 作 initial_config (workflow.rs:245)
|
||||||
|
→ NodeContext.config = deep_merge(initial_config, node_config={}) (executor.rs:102-108)
|
||||||
|
→ AiNode.execute(ctx): ctx.config["task_id"] / ctx.config["provider"] 可读
|
||||||
|
→ AiNode 持 self.db → TaskRepo::new(&self.db).get_by_id(task_id) 读 task
|
||||||
|
```
|
||||||
|
|
||||||
|
**provider 配置来源**:前端从 `ai_list_providers`(api/ai.ts:76)取默认 provider(types.ts:172-178 AiProviderRecord),拼成 `{base_url, api_key, model, protocol}` 注入 config。**api_key 安全**:复用 `src-tauri/src/commands/ai/secret.rs ensure_resolved_key`(ai_node.rs:50 注释已对齐此行为),前端不直接拿明文,由后端在注入 config 前解析(或前端传 provider_id,后端 AiNode 内部解析——更安全,见风险)。
|
||||||
|
|
||||||
|
### 2.2 方案②:ai_execute 写产出
|
||||||
|
|
||||||
|
**前置**:方案①A(AiNode 持 db + config 有 task_id)。
|
||||||
|
|
||||||
|
**写产出路径**(ai_node.rs:155-163 当前返 NodeOutput 后追加落库):
|
||||||
|
```
|
||||||
|
AiNode.execute(ctx):
|
||||||
|
1. parse_params(&ctx.config, &ctx.inputs) → p (现有,ai_node.rs:119)
|
||||||
|
2. provider.complete(request) → response (现有,ai_node.rs:146)
|
||||||
|
3. 【新增】若 ctx.config 有 task_id:
|
||||||
|
let task_id = ctx.config["task_id"].as_str()
|
||||||
|
let repo = TaskRepo::new(&self.db)
|
||||||
|
// 产出 schema: {text, model, usage} 直接序列化(与 NodeOutput.data 一致)
|
||||||
|
let output_json = serde_json::to_string(&json!({text, model, usage}))?
|
||||||
|
repo.update_field(task_id, "output_json", &output_json).await? // 需 ①白名单含 output_json
|
||||||
|
4. return NodeOutput::from_value(...) (现有,ai_node.rs:155)
|
||||||
|
```
|
||||||
|
|
||||||
|
**关键约束**:
|
||||||
|
- `update_field`(crud.rs:165)走白名单校验 —— **必须 ①把 output_json 加进 tasks 白名单**(crud.rs:342-343)。
|
||||||
|
- 产出 schema 与 `NodeOutput.data` 一致(ai_node.rs:155-163),下游 `ai_self_review` 既可从 `inputs["ai_execute"].data` 读(DAG 层),也可从 task.output_json 读(跨 DAG/重启)。**推荐读 inputs(DAG 内闭环),task.output_json 仅作持久化与 human_review 展示源**。
|
||||||
|
|
||||||
|
**in_progress 模板适用**:in_progress_template(task_workflow_templates.rs:40-50)单 ai_execute 节点,正是写产出处。testing 模板的 ai_self_review 读上游/读 task.output_json。
|
||||||
|
|
||||||
|
### 2.3 方案③:ai_self_review prompt 模板与自审结果
|
||||||
|
|
||||||
|
**输入**:`ai_self_review` 节点读上游 `inputs["ai_execute"]`(DAG 层 testing 模板无 ai_execute,故实际读 **task.output_json**)+ task.description(需求)。
|
||||||
|
|
||||||
|
**prompt 模板草案**:
|
||||||
|
```
|
||||||
|
【系统提示 system_prompt】
|
||||||
|
你是严格的代码/产出审查员。审查任务产出是否符合需求,按四维度给出结构化结论。
|
||||||
|
只输出 JSON,不要任何额外文字。
|
||||||
|
|
||||||
|
【用户提示 prompt】
|
||||||
|
## 任务需求
|
||||||
|
{task.description}
|
||||||
|
|
||||||
|
## 待审产出
|
||||||
|
{task.output_json 解析后的 text 字段}
|
||||||
|
|
||||||
|
## 审查维度
|
||||||
|
1. 需求符合度:产出是否覆盖需求描述的所有要点
|
||||||
|
2. 产出完整性:是否有遗漏、未完成的部分
|
||||||
|
3. 正确性:逻辑/事实/语法是否正确
|
||||||
|
4. 边界处理:异常输入、空值、错误路径是否考虑
|
||||||
|
|
||||||
|
## 输出格式(严格 JSON)
|
||||||
|
{
|
||||||
|
"verdict": "pass" | "fail",
|
||||||
|
"dimensions": {
|
||||||
|
"requirement_fit": {"score": 0-10, "issues": ["..."]},
|
||||||
|
"completeness": {"score": 0-10, "issues": ["..."]},
|
||||||
|
"correctness": {"score": 0-10, "issues": ["..."]},
|
||||||
|
"boundary": {"score": 0-10, "issues": ["..."]}
|
||||||
|
},
|
||||||
|
"summary": "一句话总结",
|
||||||
|
"suggestions": ["改进建议1", "改进建议2"]
|
||||||
|
}
|
||||||
|
verdict=fail 当且仅当任一维度 score < 6 或有阻断性 issue。
|
||||||
|
```
|
||||||
|
|
||||||
|
**自审结果写回 output_json**:**不覆盖**,加 `review` 子字段(保留 ai_execute 原始产出供 human_review 对照):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"text": "<ai_execute 原始产出>",
|
||||||
|
"model": "...",
|
||||||
|
"usage": {...},
|
||||||
|
"review": {
|
||||||
|
"verdict": "pass" | "fail",
|
||||||
|
"dimensions": {...},
|
||||||
|
"summary": "...",
|
||||||
|
"suggestions": [...],
|
||||||
|
"reviewed_at": "<now_millis>",
|
||||||
|
"reviewer_model": "<自审用的 model>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**verdict=fail 的处理**:当前 testing 模板(task_workflow_templates.rs:57-72)ai_self_review → human_review 串行,ai_self_review 返 Ok(不 Err)。**设计选择**:ai_self_review **不据 verdict 主动 Err**(自审是辅助,最终决策权在人)。verdict=fail 时:
|
||||||
|
- 写回 output_json(带 review.fail 信号)
|
||||||
|
- 节点返 Ok(让 human_review 节点继续,人看到 fail 结论再定)
|
||||||
|
- human_review 可在审批卡片高亮显示 verdict=fail(前端红标),人选拒绝 → Err → 工作流 failed → regression_target(workflow.rs:46-54 testing→in_review)退回
|
||||||
|
|
||||||
|
**替代方案(更激进,不推荐首版)**:ai_self_review verdict=fail 直接返 Err → 工作流 failed → 自动退回 in_review,跳过人工。**风险**:LLM 自审误判直接退回,人无干预机会。首版保守,verdict 仅作展示信号。
|
||||||
|
|
||||||
|
**实现要点**:
|
||||||
|
- prompt 模板放节点 config 还是硬编码?—— **放 config(run_workflow 全局 config 注入 `review_prompt_template`),模板可热替换**。但首版可硬编码在 AiNode 逻辑里(按 node_id=="ai_self_review" 分支),简化首版。
|
||||||
|
- LLM 输出解析:`serde_json::from_str(&response.text)` 解 JSON,失败兜底 `verdict=unknown, summary=原文`(防 LLM 不按要求输出)。
|
||||||
|
|
||||||
|
### 2.4 方案④:human_review 展示
|
||||||
|
|
||||||
|
**human_review 节点**(human_node.rs:39)当前从 `ctx.config` 读 title/description/options(human_node.rs:42-56),发 `HumanApprovalRequest`(human_node.rs:74-83)。**问题**:HumanApprovalRequest 事件载荷(df-core events)只有 title/description/options,**不含 task 产出/自审结果**。
|
||||||
|
|
||||||
|
**展示方案**:human_review 节点在发 HumanApprovalRequest 前,读 task.output_json,把"产出摘要 + 自审结论"拼进 `description` 字段(最小改动,复用现有事件结构):
|
||||||
|
```
|
||||||
|
human_review.execute(ctx):
|
||||||
|
1. 读 task.output_json(经方案①AiNode 持 db + ctx.config["task_id"])
|
||||||
|
→ 解析 review.verdict / review.summary / review.suggestions
|
||||||
|
→ 解析 text(ai_execute 产出)
|
||||||
|
2. 拼接 description:
|
||||||
|
description = format!("
|
||||||
|
## AI 自审结论:{verdict}
|
||||||
|
{summary}
|
||||||
|
建议:{suggestions}
|
||||||
|
|
||||||
|
## 产出
|
||||||
|
{text_前 N 字}
|
||||||
|
")
|
||||||
|
3. ctx.config["description"] = description (覆盖模板默认空串,human_node.rs:46-48)
|
||||||
|
4. 发 HumanApprovalRequest(现有流程,human_node.rs:74-83)
|
||||||
|
```
|
||||||
|
|
||||||
|
**约束**:HumanNode 当前**不持 db**(human_node.rs:36 `pub struct HumanNode;` 无字段)。需同样按方案①A 给 HumanNode 加 `db: Arc<Database>` + state.rs:254-256 工厂闭包改 move db。**或**:human_review 的 description 由 ai_self_review 节点算好,经 DAG outputs 透传(inputs["ai_self_review"].data)—— **更优,HumanNode 零改动**:
|
||||||
|
- ai_self_review 节点把"自审结论 + 产出摘要"塞进 NodeOutput.data
|
||||||
|
- human_review 读 `inputs["ai_self_review"].data["review_summary"]` 拼进 description
|
||||||
|
- HumanNode 不需 db,零改动
|
||||||
|
|
||||||
|
**推荐后者**(HumanNode 零改动,db 注入只给 AiNode)。
|
||||||
|
|
||||||
|
**审批卡片显示什么**(前端):
|
||||||
|
- title:模板已定「核对 AI 自审结果」(task_workflow_templates.rs:66)
|
||||||
|
- description:自审 verdict(pass/fail 红绿标)+ summary + suggestions + 产出摘要
|
||||||
|
- options:同意 / 拒绝(task_workflow_templates.rs:67,含拒绝触发退回)
|
||||||
|
|
||||||
|
### 2.5 方案⑤:前端展示 output_json
|
||||||
|
|
||||||
|
**当前 TaskDetail.vue**(src/views/TaskDetail.vue:1-140):展示 title/status/description/priority/branch 等,**无 output_json 展示**。已有工作流推进按钮(L71-91)+ workflow-event 监听(L81-90 轻量进度)+ df-data-changed 自动刷新(L82 注释)。
|
||||||
|
|
||||||
|
**UI 方案**(在 TaskDetail.vue 信息区加一个区块,L124 workflowDef 后):
|
||||||
|
```vue
|
||||||
|
<!-- F-AiNodeSelfReview: 任务产出 + 自审结果展示 -->
|
||||||
|
<div v-if="task.output_json" class="info-item info-block">
|
||||||
|
<span class="label">{{ $t('taskDetail.output') }}</span>
|
||||||
|
<div class="value output-block">
|
||||||
|
<!-- 自审结论卡(若有 review 子字段) -->
|
||||||
|
<div v-if="parsedOutput.review" class="review-card" :class="reviewVerdictClass">
|
||||||
|
<span class="review-verdict">{{ reviewVerdictLabel }}</span>
|
||||||
|
<span class="review-summary">{{ parsedOutput.review.summary }}</span>
|
||||||
|
<ul v-if="parsedOutput.review.suggestions?.length" class="review-suggestions">
|
||||||
|
<li v-for="(s, i) in parsedOutput.review.suggestions" :key="i">{{ s }}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<!-- AI 产出(Markdown 渲染,复用 useRendered) -->
|
||||||
|
<div v-if="parsedOutput.text" class="output-text ai-md" v-html="renderedOutput"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
**实现要点**:
|
||||||
|
- `parsedOutput = computed(() => JSON.parse(task.output_json))`(包 try/catch,解析失败降级显示原文)
|
||||||
|
- `reviewVerdictClass`:pass→绿,fail→红
|
||||||
|
- `renderedOutput`:复用 `useRendered`(TaskDetail.vue:150, 190-192 已用于 description)渲染 markdown
|
||||||
|
- i18n key:`taskDetail.output` / `taskDetail.review.pass` / `taskDetail.review.fail`
|
||||||
|
- workflow 完成后 task 经 df-data-changed 自动刷新(现有机制,TaskDetail.vue:82 注释),output_json 自动出现
|
||||||
|
|
||||||
|
**约束**:output_json 是 `Option<String>`(models.rs:76),前端 `task.output_json` 可能为 undefined(旧任务无产出)—— `v-if="task.output_json"` 守卫。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、②-⑥ 实施步骤拆解(标可并发)
|
||||||
|
|
||||||
|
> 依赖链:①迁移(另一 agent)→ ②-⑥。②-⑥ 内部:
|
||||||
|
|
||||||
|
```
|
||||||
|
② AiNode 持 db + 写产出 ──┐
|
||||||
|
├─→ ⑤ 前端展示(依赖 output_json schema 定型,即③review schema)
|
||||||
|
③ ai_self_review prompt ──┤
|
||||||
|
├─→ ④ human_review 展示(依赖③ review schema + ② 产出 schema)
|
||||||
|
┘
|
||||||
|
⑥ 端到端联调(依赖②③④⑤)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 步骤②:ai_execute 写产出(依赖①白名单)
|
||||||
|
- [ ] `crates/df-nodes/src/ai_node.rs`:`AiNode` struct 加 `db: Arc<Database>` 字段 + `new(db)` 构造
|
||||||
|
- [ ] `crates/df-nodes/src/ai_node.rs:execute`:LLM 完成后,若 `ctx.config["task_id"]` 存在 → `TaskRepo::new(&self.db).update_field(task_id, "output_json", &json)` 落库
|
||||||
|
- [ ] `src-tauri/src/state.rs:257-259`:ai 工厂闭包改 `move |_config| Box::new(AiNode::new(db.clone()))`(对齐 state.rs:265 TaskAdvanceNode 写法)
|
||||||
|
- [ ] 单测:mock db 验证 update_field 调用 + output_json 内容
|
||||||
|
|
||||||
|
### 步骤③:ai_self_review prompt 模板(依赖② schema)
|
||||||
|
- [ ] `crates/df-nodes/src/ai_node.rs`:按 node_id 分支(或新增 `AiReviewNode` 独立节点类型)—— **推荐独立节点** `ai_self_review` 类型,复用 AiNode 大部分逻辑但 prompt 模板固定、输出解析 JSON
|
||||||
|
- [ ] prompt 模板(见 2.3)硬编码或 config 注入
|
||||||
|
- [ ] LLM 输出 JSON 解析 + 兜底
|
||||||
|
- [ ] 自审结果写回 task.output_json(加 review 子字段,不覆盖 text)
|
||||||
|
- [ ] 注册 `ai_self_review` 节点类型到 state.rs build_registry
|
||||||
|
- [ ] testing 模板节点类型 "ai" → "ai_self_review"(task_workflow_templates.rs:59)
|
||||||
|
|
||||||
|
### 步骤④:human_review 展示(依赖③ review schema)
|
||||||
|
- [ ] `crates/df-nodes/src/ai_node.rs`(ai_self_review 节点):把 review 摘要塞进 NodeOutput.data,供下游 human_review 读
|
||||||
|
- [ ] **不改 HumanNode**(human_node.rs 零改动)—— human_review 节点 config 的 description 在模板层或 ai_self_review 输出层拼接
|
||||||
|
- [ ] 方案:testing 模板给 human_review 节点 config 加 description 占位,或新增轻量"描述拼装"逻辑(读 inputs["ai_self_review"])
|
||||||
|
- [ ] **若选 HumanNode 持 db 方案**:human_node.rs 加 db 字段 + state.rs:254 工厂闭包改 move(不推荐,多改一处)
|
||||||
|
|
||||||
|
### 步骤⑤:前端展示(依赖③ schema 定型)—— **可与②③④并发**
|
||||||
|
- [ ] `src/views/TaskDetail.vue`:加 output_json 展示区块(见 2.5)
|
||||||
|
- [ ] `src/api/types.ts`:TaskRecord 类型加 `output_json?: string`
|
||||||
|
- [ ] `src/locales/*.json`:加 taskDetail.output / review.pass / review.fail i18n key
|
||||||
|
- [ ] review.verdict 红绿标样式
|
||||||
|
|
||||||
|
### 步骤⑥:端到端联调(依赖②③④⑤全完成)
|
||||||
|
- [ ] 前端 TaskDetail 点工作流推进(testing)→ run_workflow → ai_self_review 调 LLM → 写 output_json → human_review 审批卡显示自审结论 → 人同意/拒绝 → 任务态推进/退回
|
||||||
|
- [ ] 验证 regression_target(workflow.rs:46-54):拒绝 → failed → testing 退回 in_review
|
||||||
|
|
||||||
|
**可并发标注**:
|
||||||
|
- **②③④ 串行**(共享 ai_node.rs / schema)
|
||||||
|
- **⑤ 可与②③④ 并发**(前端独立,仅依赖 schema 文档约定)
|
||||||
|
- **⑥ 必须最后**(全依赖)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、风险
|
||||||
|
|
||||||
|
| 风险 | 等级 | 说明 | 缓解 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| ①迁移未完成阻塞 ②-⑥ | **P0 阻断** | crud 白名单(crud.rs:342-343)/ SELECT 列(crud.rs:779)/ INSERT-UPDATE 语句(crud.rs:748-762)/ ALTER(migrations.rs)均未含 output_json | 本方案明确标注前置依赖;①agent 须先完成 crud 链路全量(白名单+SELECT+INSERT+UPDATE+ALTER) |
|
||||||
|
| api_key 明文经 config 注入 | **P0 安全** | 前端拼 provider config 含 api_key,经 IPC(明文)+ config(明文)传到 AiNode,前端可拿到明文 key(违背 FR-S1 mask 设计) | 改传 `provider_id`(非 api_key),AiNode 内部调 `secret::ensure_resolved_key`(src-tauri/src/commands/ai/secret.rs)解析;前端 config 只放 provider_id + model + protocol,base_url/api_key 后端解析注入 |
|
||||||
|
| LLM 自审输出不合规 JSON | P1 | LLM 可能不按要求输出纯 JSON | 兜底解析:JSON.parse 失败 → verdict=unknown + summary=原文;prompt 强约束 + temperature=0 |
|
||||||
|
| AiNode 从无状态变有状态 | P2 | state.rs:257-259 当前 `Box::new(AiNode)` 无参,改 `new(db.clone())` 后工厂闭包要 move db(对齐 state.rs:265 已有写法) | 低风险,TaskAdvanceNode 同模式已验证;注意所有 AiNode 测试构造点同步 |
|
||||||
|
| HumanNode 是否持 db 的方案分歧 | P2 | 方案④有两个选项(HumanNode 持 db vs ai_self_review 输出透传) | 推荐后者(HumanNode 零改动),review 摘要经 DAG inputs 透传;减少改动面 |
|
||||||
|
| ai_self_review verdict=fail 是否自动退回 | P2 | 激进方案(fail→Err→自动退回)vs 保守方案(fail→展示,人定) | 首版保守,verdict 仅作展示信号;后续可加 config 开关 `auto_reject_on_fail` |
|
||||||
|
| testing 模板 ai 节点类型改 ai_self_review | P2 | task_workflow_templates.rs:59 当前 `dag.add_node("ai_self_review", "ai", ...)` 类型是 "ai";若新增独立 ai_self_review 节点类型,模板要改 + registry 要注册 | 若复用 AiNode(按 node_id 分支 prompt)则模板不改;若独立节点则模板 + registry 都改(见步骤③) |
|
||||||
|
| output_json schema 演进 | P3 | 未来产出类型多样化(代码/文档/分析),单一 text+review schema 可能不够 | schema 设计预留扩展字段;output_json 本就是自由 JSON 字符串,可演进 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、依赖
|
||||||
|
|
||||||
|
- **① df-storage 迁移**(另一 agent,本方案前置):TaskRecord struct(✅ models.rs:76 已加)→ migrations(CREATE ✅ / ALTER ⚠️ 缺)→ crud(SELECT ⚠️ / INSERT ⚠️ / UPDATE ⚠️ / 白名单 ⚠️ 全缺)。**须全量完成 crud 链路 + 白名单含 output_json**。
|
||||||
|
- **provider 配置注入链**:前端 ai_list_providers(api/ai.ts:76)+ 默认 provider(types.ts:172-178)→ run_workflow config → deep_merge。已就绪,仅需前端拼装逻辑。
|
||||||
|
- **secret 解析**(若走 provider_id 方案):src-tauri/src/commands/ai/secret.rs ensure_resolved_key 已就绪(ai_node.rs:50 注释对齐)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、附录:file:line 证据索引
|
||||||
|
|
||||||
|
| 文件 | 行 | 内容 |
|
||||||
|
|------|----|------|
|
||||||
|
| crates/df-workflow/src/node.rs | 12-26 | NodeContext 结构(无 task/db) |
|
||||||
|
| crates/df-nodes/src/ai_node.rs | 35-109 | parse_params 参数解析 |
|
||||||
|
| crates/df-nodes/src/ai_node.rs | 112 | `pub struct AiNode;`(无状态) |
|
||||||
|
| crates/df-nodes/src/ai_node.rs | 116-164 | AiNode.execute 调 LLM 返 NodeOutput |
|
||||||
|
| crates/df-nodes/src/ai_node.rs | 155-163 | 产出 schema {text,model,usage} |
|
||||||
|
| crates/df-nodes/src/human_node.rs | 36 | `pub struct HumanNode;`(无状态) |
|
||||||
|
| crates/df-nodes/src/human_node.rs | 39-176 | HumanNode.execute 审批流程 |
|
||||||
|
| crates/df-nodes/src/human_node.rs | 19-22 | REJECT_KEYWORDS |
|
||||||
|
| crates/df-nodes/src/task_workflow_templates.rs | 28-35 | template_for 选模板 |
|
||||||
|
| crates/df-nodes/src/task_workflow_templates.rs | 57-72 | testing 模板(ai_self_review→human_review) |
|
||||||
|
| crates/df-nodes/src/task_advance_node.rs | 98-110 | TaskAdvanceNode db 注入先例 |
|
||||||
|
| crates/df-workflow/src/executor.rs | 99-113 | NodeContext 构造 |
|
||||||
|
| crates/df-workflow/src/executor.rs | 102-108 | deep_merge 节点级覆盖全局 |
|
||||||
|
| crates/df-workflow/src/dag.rs | 169-188 | deep_merge 实现 |
|
||||||
|
| src-tauri/src/commands/workflow.rs | 67-76 | run_workflow 签名(config/task_id/target_status) |
|
||||||
|
| src-tauri/src/commands/workflow.rs | 84-92 | 空 dag + target → template_for |
|
||||||
|
| src-tauri/src/commands/workflow.rs | 235-308 | 工作流联动任务推进/退回 |
|
||||||
|
| src-tauri/src/commands/workflow.rs | 46-54 | regression_target 失败退回映射 |
|
||||||
|
| src-tauri/src/state.rs | 252-266 | build_registry 节点注册(AiNode L257 / TaskAdvanceNode L265) |
|
||||||
|
| crates/df-storage/src/models.rs | 53-79 | TaskRecord(含 output_json L76) |
|
||||||
|
| crates/df-storage/src/migrations.rs | 371 | CREATE TABLE output_json TEXT |
|
||||||
|
| crates/df-storage/src/crud.rs | 165 | update_field 通用写 |
|
||||||
|
| crates/df-storage/src/crud.rs | 342-343 | tasks 白名单(缺 output_json) |
|
||||||
|
| crates/df-storage/src/crud.rs | 748-762 | TaskRepo INSERT/UPDATE(缺 output_json) |
|
||||||
|
| crates/df-storage/src/crud.rs | 779 | list_active SELECT(缺 output_json) |
|
||||||
|
| src/api/workflow.ts | 12-26 | workflowApi.run(含 config/taskId/targetStatus) |
|
||||||
|
| src/api/ai.ts | 76 | ai_list_providers |
|
||||||
|
| src/api/types.ts | 172-178 | AiProviderRecord(api_key/base_url) |
|
||||||
|
| src/views/TaskDetail.vue | 71-91 | 工作流推进按钮 + 轻量进度 |
|
||||||
|
| src/views/TaskDetail.vue | 124-127 | workflowDef 区块(output_json 展示插入点) |
|
||||||
|
| src-tauri/src/commands/ai/secret.rs | - | ensure_resolved_key(api_key 安全解析) |
|
||||||
Reference in New Issue
Block a user