重构: 文档汇总+进度看板+孤儿任务清理脚本+gitignore 噪音排除
- docs/02 架构设计: 新增 aichat审查/异步审批构想/流式渲染调研/generating状态机/密钥迁移健壮性/工作流脚本执行边界/条件表达式引擎/F-07 trait下沉/Agent架构说明/任务推进构想/功能创意池;更新功能决策记录+归档/对抗论证/文档记录规范/经验记录
- docs/03 模块文档: 新增 AI对话引擎/DAG引擎详解;更新 df-knowledge/df-nodes/df-storage/df-workflow/df-ai
- docs/05 代码审查: 新增 全栈审查/全局review/架构审查/近期改动审查/工作区多角度走查/自研memo流式渲染审查
- docs/09 问题排查: 新增 aichat-apikey-401
- docs/INDEX+README 索引同步;docs/todo 待办看板(2026-06-15 汇总)
- PROGRESS.md Sprint 22-25;URGENT.md 加急清单快照(5 项 P0 已全修)
- scripts/cleanup_orphan_tasks.{py,sh} 孤儿任务清理工具
- .gitignore 补 *.broken.bak + tmp/ 噪音排除
This commit is contained in:
312
docs/03-模块文档/AI对话引擎-2026-06-14.md
Normal file
312
docs/03-模块文档/AI对话引擎-2026-06-14.md
Normal file
@@ -0,0 +1,312 @@
|
||||
# AI 对话引擎(Agentic Loop)
|
||||
|
||||
> 创建:2026-06-14 | 来源:基于 `src-tauri/src/commands/ai/` 实际代码核对编写
|
||||
> 关联模块文档:[df-ai-AI集成模块](./df-ai-AI集成模块-2026-06-12.md)(crate 层:Provider / ContextManager / 工具基础设施)
|
||||
> 关联架构文档:[Agent架构说明-2026-06-14.md](../02-架构设计/Agent架构说明-2026-06-14.md)(能力边界盘点)
|
||||
|
||||
---
|
||||
|
||||
## 一、概述
|
||||
|
||||
DevFlow 的 AI 对话不是简单的"发消息→收回复",而是一个 **ReAct 循环**(Reason + Act):LLM 流式回复 → 调用工具 → 执行工具 → 结果回传 LLM → 循环,直到 LLM 不再需要工具(返回纯文本)或达到最大轮次。
|
||||
|
||||
核心代码位于 `src-tauri/src/commands/ai/`,11 个子模块协作:
|
||||
|
||||
```
|
||||
commands/ai/
|
||||
├── mod.rs — 模块入口 + glob 重导出 + AiSession 定义
|
||||
├── commands.rs — 17 个 IPC 命令(send/approve/reject/clear/switch…)
|
||||
├── agentic.rs — ReAct 循环主体(run_agentic_loop / try_continue_agent_loop)
|
||||
├── stream_recv.rs — 流式接收(idle timeout / 断连检测 / 停止信号)
|
||||
├── conversation.rs — 持久化 + Token 累加器 + 截断函数
|
||||
├── audit.rs — 工具执行审计(process_tool_calls / audit_finalize / build_approval_reason)
|
||||
├── tool_registry.rs — 12 个 AI 工具注册 + 路径校验
|
||||
├── prompt.rs — system prompt 构建 + provider 获取
|
||||
├── skills.rs — 技能联想(SKILL.md frontmatter 解析)
|
||||
├── title.rs — 对话标题自动生成
|
||||
└── knowledge_inject.rs — 知识注入 + 知识提炼
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 二、ReAct 循环详解
|
||||
|
||||
### 核心流程
|
||||
|
||||
```
|
||||
用户发消息
|
||||
│
|
||||
▼
|
||||
┌─ for iteration in 0..MAX_AGENT_ITERATIONS(10) ──────────────┐
|
||||
│ │
|
||||
│ 1. 用户停止? → 收尾退出 │
|
||||
│ │
|
||||
│ 2. 构建请求消息 │
|
||||
│ ├─ system prompt(技能 + 知识注入) │
|
||||
│ ├─ 历史消息(超预算时裁剪,保护工具三元组 + 最近 6 条) │
|
||||
│ └─ 工具定义(12 个内置工具) │
|
||||
│ │
|
||||
│ 3. LLM 并发限流(全局 3 / 单对话 2 双层 Semaphore) │
|
||||
│ │
|
||||
│ 4. stream_llm(流式接收) │
|
||||
│ ├─ 逐 chunk 推送 AiTextDelta 到前端 │
|
||||
│ ├─ 累积 tool_calls(按 index 排序) │
|
||||
│ ├─ idle 120s timeout → 判定断连 │
|
||||
│ └─ 流尽未收 finished → 丢弃残缺 │
|
||||
│ │
|
||||
│ 5. 有 tool_calls? │
|
||||
│ ├─ 无 → 最终文本,break(正常结束) │
|
||||
│ └─ 有 → process_tool_calls │
|
||||
│ ├─ Low 风险 → 自动执行 │
|
||||
│ └─ Medium/High → 进 pending_approvals,暂停循环 │
|
||||
│ │
|
||||
│ 6. 全自动完成 → 继续下一轮 │
|
||||
│ 有 pending → return(generating 保持 true,等审批恢复) │
|
||||
│ │
|
||||
└─ 达 10 轮 → 正常结束 ────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
收尾(后台 spawn,不阻塞 Completed 事件)
|
||||
├─ save_conversation(消息 + token 累加落库)
|
||||
├─ maybe_spawn_extraction(知识提炼)
|
||||
└─ ensure_conversation_title(标题生成)
|
||||
```
|
||||
|
||||
### 退出条件
|
||||
|
||||
| 条件 | 处理 |
|
||||
|------|------|
|
||||
| LLM 只返回文本(无 tool_calls) | 正常结束,emit `AiCompleted` |
|
||||
| 有工具待审批 | 暂停循环,`generating` 保持 `true`,等 `ai_approve` → `try_continue_agent_loop` 恢复 |
|
||||
| 达 `MAX_AGENT_ITERATIONS`(10) | 正常结束 |
|
||||
| 用户请求停止 | 已生成文本入库后退出,emit `AiCompleted` |
|
||||
| 流式错误(idle timeout / 断连) | emit `AiError`,`generating = false`,退出 |
|
||||
|
||||
### 审批恢复
|
||||
|
||||
当用户审批通过最后一个 pending 工具后,`try_continue_agent_loop` 检测到 `generating && pending_approvals.is_empty()`,spawn 新的 `run_agentic_loop` 恢复循环。恢复前 emit `AiAgentRound` 通知前端新建 assistant 消息(审批结果不应追加到发起工具调用的旧消息)。
|
||||
|
||||
---
|
||||
|
||||
## 三、三重可靠性保险
|
||||
|
||||
| 保险层 | 机制 | 防什么 | 代码位置 |
|
||||
|--------|------|--------|---------|
|
||||
| **连接层** | `connect_timeout(30s)`,不设总 timeout | 连不上无限 hang;总 timeout 会误砍流式长任务(流式可持续数分钟) | `openai_compat.rs` / `anthropic_compat.rs` |
|
||||
| **流式层** | 每个 chunk 间 idle timeout 120s | 连上后中途静默无限 hang | `stream_recv.rs` |
|
||||
| **前端层** | 60s streaming watchdog | 后端任何路径漏发收尾事件 → 前端永久 `streaming=true` 卡死 | `stores/ai.ts`(`useAiEvents` composable) |
|
||||
|
||||
**审批等待时 watchdog 暂停**(不计超时),避免用户思考时间被误判。
|
||||
|
||||
### 断连丢弃残缺
|
||||
|
||||
维护 `finished_received` 标志;流尽未收到 finished 信号 → emit `AiError` 并**丢弃残缺响应,不当完整入库**。防脏历史污染对话记录。
|
||||
|
||||
### 停止生成保留文本
|
||||
|
||||
`AiSession.stop_flag: Arc<AtomicBool>` + 多检查点响应(循环顶 / stream 内 / 工具执行前)。停止后**保留已生成文本**——用户主动停止 ≠ 丢弃成果。
|
||||
|
||||
---
|
||||
|
||||
## 四、12 个 AI 工具
|
||||
|
||||
### 工具清单
|
||||
|
||||
工具定义在 `tool_registry.rs::build_ai_tool_registry`,编译期硬编码(无运行时动态注册)。
|
||||
|
||||
| 风险 | 工具 | 说明 |
|
||||
|------|------|------|
|
||||
| **Low**(自动执行) | `list_projects` | 列出项目(排软删,截断 50 条) |
|
||||
| **Low** | `list_tasks` | 列出任务(可按 project_id 筛选) |
|
||||
| **Low** | `list_ideas` | 列出灵感 |
|
||||
| **Low** | `read_file` | 读文件(offset/limit 分页,1MB 上限) |
|
||||
| **Low** | `list_directory` | 列目录(噪音剪枝 + 1000 条上限) |
|
||||
| **Medium**(需审批) | `create_project` | 创建项目(可选 path/stack 一步绑定) |
|
||||
| **Medium** | `update_project` | 改字段(复用 CRUD 白名单校验) |
|
||||
| **Medium** | `create_task` | 创建任务 |
|
||||
| **Medium** | `create_idea` | 创建灵感 |
|
||||
| **Medium** | `write_file` | 写文件(自动建父目录,1MB 上限) |
|
||||
| **Medium** | `bind_directory` | 项目绑定代码目录 + 探测技术栈 |
|
||||
| **High**(需审批) | `delete_project` | 删项目(软删进回收站) |
|
||||
| **High** | `run_workflow` | 运行工作流(当前返回提示,未实装) |
|
||||
|
||||
### 工具三要素同源
|
||||
|
||||
每个工具一次 `registry.register` 同时定义:`name + description + schema + RiskLevel + handler 闭包`。handler 即唯一执行路径,schema+risk+实现同源,消除双轨。
|
||||
|
||||
### 路径安全
|
||||
|
||||
- `validate_path`:禁 `..` 路径遍历、禁 `.ssh/.aws/.gnupg/AppData/ProgramData/Windows/System32` 等敏感目录
|
||||
- `resolve_workspace_path`:双层校验(词法 `starts_with` + `canonicalize` 解析 symlink),锚定 `workspace_root`
|
||||
|
||||
### 审计
|
||||
|
||||
每次工具调用写 `ai_tool_executions` 表(V9 建),`audit_finalize` 落盘 executed/rejected + 结果。
|
||||
|
||||
### 已知限制
|
||||
|
||||
- **截断 50 条无翻页**:三个 list 工具 `truncate(50)` 硬截断,无 offset/limit,AI 不知道有数据被漏掉(待改进)
|
||||
- **"能写不能跑"**:AI 有 `write_file` 但无 `run_command`,无法形成"写→跑→改"闭环(待改进)
|
||||
- **工具集封闭**:编译期硬编码,无 MCP 客户端,AI 运行时不能新增/修改工具
|
||||
|
||||
---
|
||||
|
||||
## 五、审批门控机制
|
||||
|
||||
### 流程
|
||||
|
||||
```
|
||||
AI 要执行 create_project(Medium 风险)
|
||||
│
|
||||
▼
|
||||
process_tool_calls → 不自动执行,写入 ai_tool_executions(status=pending)
|
||||
│
|
||||
▼
|
||||
emit AiApprovalRequired → 前端 ToolCard 显示审批按钮
|
||||
│
|
||||
├─ 用户点「同意」→ ai_approve → 执行工具 → try_continue_agent_loop 恢复
|
||||
└─ 用户点「拒绝」→ ai_reject → 跳过执行 → 恢复循环
|
||||
```
|
||||
|
||||
### 持久化
|
||||
|
||||
pending 审批写入 DB(`ai_tool_executions` 表 `status='pending'`),重启后 `restore_pending_approvals` 从 DB 恢复。`ai_conversation_switch` 用 `retain` 保其他对话的 pending(不清空全局 HashMap)。
|
||||
|
||||
### 审批卡片信息
|
||||
|
||||
`build_approval_reason`(`audit.rs`)为 9 种工具拼接 reason 含项目名(`resolve_project_label` 查项目名,查不到 fallback「(项目已不存在, id=xxx)」)。前端 `ToolCard.vue` 对 `id`/`project_id` 字段特化回显项目名。
|
||||
|
||||
### 与工作流审批的区别
|
||||
|
||||
| | AI 工具审批 | 工作流审批 |
|
||||
|---|---|---|
|
||||
| 触发 | AI 对话中调 Medium/High 工具 | DAG 执行到 HumanNode |
|
||||
| 事件 | `AiApprovalRequired` | `HumanApprovalRequest` / `HumanApprovalResponse` |
|
||||
| 恢复 | `ai_approve` → `try_continue_agent_loop` | `approve_human_approval` IPC → EventBus broadcast |
|
||||
| 通道 | 独立链路 | 独立链路(EventBus broadcast) |
|
||||
|
||||
两条审批链路完全独立,不共享事件类型或通道。
|
||||
|
||||
---
|
||||
|
||||
## 六、上下文窗口管理(ContextManager)
|
||||
|
||||
> 实现在 `crates/df-ai/src/context.rs`,解决长对话 token 暴涨导致 `context_length_exceeded` 死锁。
|
||||
|
||||
### 分组滑动窗口
|
||||
|
||||
```
|
||||
消息历史:[旧] U₁ A₁(tool) T(result) A₁' U₂ A₂(tool) T(result) A₂' U₃ A₃ [新]
|
||||
↑ ↑
|
||||
可淘汰区 保护区(最后6条)
|
||||
|
||||
淘汰单位 = 工具调用三元组(原子性同进同出):
|
||||
Assistant(tool_calls) + Tool(result)* + Assistant(紧随文本)
|
||||
```
|
||||
|
||||
- **保护区**:`PROTECT_COUNT = 6`(≈ 最近 2 个完整用户轮次),永不裁
|
||||
- **裁剪只影响发送视图**:`build_for_request` 返回裁剪版给 LLM;`all_messages_clone` 返回全量给持久化
|
||||
- **Token 估算零依赖**:`chars × 0.35`(保守 ±15%),不引入 tiktoken-rs(5MB BPE 数据文件对 Tauri 打包不友好)
|
||||
- **预算公式**:`(max_tokens 128k − output_reserve 8192) × safety_ratio 0.85 ≈ 101K tokens`
|
||||
|
||||
### 关键方法
|
||||
|
||||
| 方法 | 用途 |
|
||||
|------|------|
|
||||
| `push(message)` | 追加消息并计 token、更新缓存(push 不裁剪,裁剪统一在 build_for_request) |
|
||||
| `build_for_request(sys_tokens)` | 返回裁剪后的消息列表 + 是否发生裁剪 |
|
||||
| `all_messages_clone()` | 返回全量消息(持久化 / 标题生成) |
|
||||
| `restore_from_messages(msgs)` | 切换对话时重建缓存 |
|
||||
| `replace_tool_result_content(tool_call_id, new_content)` | 审批通过/拒绝时回填工具结果(反向 rposition 命中最近一条) |
|
||||
|
||||
---
|
||||
|
||||
## 七、Token 累加
|
||||
|
||||
### 两协议语义差异
|
||||
|
||||
| | OpenAI | Anthropic |
|
||||
|---|---|---|
|
||||
| usage 时机 | 末 chunk 一次性给全量 | message_start(input)+ message_delta(output) |
|
||||
| output_tokens | 最终值 | **累计值**(非增量),直接覆盖不累加 |
|
||||
|
||||
### 跨 loop 实例累加
|
||||
|
||||
审批暂停→恢复 spawn 全新 `run_agentic_loop` 实例,新 loop 局部累加器从 0 起。`save_conversation` 的 upsert 路径 token **读旧值叠加**(非覆盖),保证跨 loop 实例的对话总用量正确。
|
||||
|
||||
```
|
||||
loop 实例 1:prompt=500, completion=200 → save(旧值 None + 500/200)
|
||||
│ 审批暂停
|
||||
▼
|
||||
loop 实例 2:prompt=800, completion=300 → save(旧值 500/200 + 800/300 = 1300/500)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 八、LLM 并发控制
|
||||
|
||||
`LlmConcurrency`(`state.rs`):双层 Semaphore,运行时可调。
|
||||
|
||||
| Semaphore | 默认 permits | 限流对象 |
|
||||
|-----------|------------|---------|
|
||||
| global | 3 | 全部 LLM 调用(stream_llm / 标题 / 提炼) |
|
||||
| per_conv | 2 | 单对话并发 |
|
||||
|
||||
- permit 仅覆盖 `stream_llm` 调用本身;工具执行(`process_tool_calls`)是本地操作无 RPM 成本,permit 在 stream 后立即释放
|
||||
- Semaphore 重建用「软收敛」策略(替换内层 Arc,旧 permit 不受影响)
|
||||
|
||||
---
|
||||
|
||||
## 九、知识库集成
|
||||
|
||||
### 知识注入(对话开始时)
|
||||
|
||||
`build_knowledge_context(state, query, config) -> String`:
|
||||
- `auto_inject == false` → 返回空(零开销)
|
||||
- `hybrid_search()` top-3(LIKE 或 LIKE+向量混合)
|
||||
- 每条命中调 `increment_reuse_count`(fire-and-forget)
|
||||
- 格式化为 markdown,注入 system prompt 头部
|
||||
|
||||
### 知识提炼(对话结束后)
|
||||
|
||||
`extract_knowledge_from_conversation(db, conv_id, provider_cfg)`:
|
||||
- 后台 spawn,提炼失败仅 warn 不阻断
|
||||
- 取最后 6 条 user/assistant 消息 → LLM JSON 输出
|
||||
- parse 失败整批丢弃;成功逐条写 `candidate`(待人工审核)
|
||||
|
||||
`maybe_spawn_extraction(...)`:agentic loop 两处正常退出路径(max iterations / 无工具调用 break)统一调用。
|
||||
|
||||
---
|
||||
|
||||
## 十、切对话不中断路由
|
||||
|
||||
Sprint 8 实现:生成中可切换对话不打断。
|
||||
|
||||
- 后端给所有 event 加 `conversation_id` + spawn 前快照 conv_id
|
||||
- 前端按 id 路由:后台对话事件不污染当前视图
|
||||
- `switchConversation` 加 `_latestSwitchId` 丢弃过期响应
|
||||
|
||||
---
|
||||
|
||||
## 十一、Agent 能力边界(四个"无")
|
||||
|
||||
当前系统的硬边界,做设计时不能假设超出这些能力:
|
||||
|
||||
| 能力 | 现状 | 影响 |
|
||||
|------|------|------|
|
||||
| **配置/调用外部工具** | ❌ 无 MCP 客户端,工具全编译期硬编码 | 工具集封闭 |
|
||||
| **自造/迭代工具** | ❌ AI 运行时不能新增/修改工具 | 不能为特定任务临时造工具 |
|
||||
| **执行类工具** | ❌ 无 `run_command`/`run_script` | AI 能写代码但不能运行验证("能写不能跑") |
|
||||
| **agent ↔ workflow 打通** | ❌ `run_workflow` 工具是空壳 | AI 不能在对话中触发工作流 |
|
||||
|
||||
详见 [Agent架构说明-2026-06-14.md](../02-架构设计/Agent架构说明-2026-06-14.md)。
|
||||
|
||||
---
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [df-ai AI 集成模块](./df-ai-AI集成模块-2026-06-12.md) — crate 层(Provider / ContextManager / 工具基础设施)
|
||||
- [Agent架构说明](../02-架构设计/Agent架构说明-2026-06-14.md) — 能力边界盘点
|
||||
- [df-workflow 工作流引擎](./df-workflow-工作流引擎-2026-06-12.md) — DAG 引擎
|
||||
- [DAG 引擎详解](./DAG引擎详解-2026-06-14.md) — 基于代码的引擎详解
|
||||
- [经验记录](../02-架构设计/经验记录-2026-06-14.md) — 踩坑/约定/技巧
|
||||
372
docs/03-模块文档/DAG引擎详解-2026-06-14.md
Normal file
372
docs/03-模块文档/DAG引擎详解-2026-06-14.md
Normal file
@@ -0,0 +1,372 @@
|
||||
# DAG 引擎详解
|
||||
|
||||
> 来源:基于 `crates/df-workflow/src/` 实际代码核对编写,2026-06-14
|
||||
> 关联模块文档:[df-workflow-工作流引擎-2026-06-12.md](./df-workflow-工作流引擎-2026-06-12.md)(接口级参考)
|
||||
> 代码版本:Sprint 22 收尾(StateMachine Arc 共享 + 审批取消闭环已落地)
|
||||
|
||||
---
|
||||
|
||||
## 一、什么是 DAG
|
||||
|
||||
DAG = **Directed Acyclic Graph(有向无环图)**。DevFlow 用它来编排工作流——把一个复杂任务拆成多个节点,按依赖关系连接,引擎自动算出执行顺序。
|
||||
|
||||
```
|
||||
┌─────────┐
|
||||
│ 检查环境 │ ← 第 1 层(无依赖,先执行)
|
||||
└────┬────┘
|
||||
│
|
||||
┌────┴────┐
|
||||
│ 运行测试 │ ← 第 2 层(依赖「检查环境」完成)
|
||||
└────┬────┘
|
||||
│
|
||||
┌────┴────┐
|
||||
│ 构建产物 │ ← 第 3 层(依赖「运行测试」完成)
|
||||
└─────────┘
|
||||
```
|
||||
|
||||
也可以有分支和并行:
|
||||
|
||||
```
|
||||
┌──────────┐
|
||||
│ 代码扫描 │
|
||||
└──┬───┬───┘ ← 第 1 层
|
||||
│ │
|
||||
┌────┘ └────┐
|
||||
▼ ▼
|
||||
┌──────┐ ┌──────────┐
|
||||
│ 单元测试 │ │ AI 代码审查 │ ← 第 2 层(两个并行执行)
|
||||
└──┬───┘ └────┬─────┘
|
||||
│ │
|
||||
└──────┬───────┘
|
||||
▼
|
||||
┌────────────┐
|
||||
│ 汇总报告 │ ← 第 3 层(等上面两个都完成)
|
||||
└────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 二、核心组件(6 个文件,各司其职)
|
||||
|
||||
```
|
||||
用户定义工作流(JSON)
|
||||
│
|
||||
▼
|
||||
┌──────────┐ 序列化/反序列化 ┌──────────┐
|
||||
│ DagDef │ ←──────────────────→ │ 数据库持久化 │
|
||||
│ (dag_def) │ └──────────┘
|
||||
└─────┬─────┘
|
||||
│ NodeRegistry.build_dag()
|
||||
▼
|
||||
┌──────────┐ 拓扑排序分层 ┌──────────────┐
|
||||
│ Dag │ ──────────────────→ │ Vec<Vec<节点>> │
|
||||
│ (dag) │ │ 按层组织 │
|
||||
└──────────┘ └──────┬───────┘
|
||||
│
|
||||
┌──────┴───────┐
|
||||
▼ │
|
||||
┌────────────┐ │
|
||||
│ DagExecutor │ ←─── StateMachine
|
||||
│ (executor) │ (状态转换校验)
|
||||
└──────┬─────┘
|
||||
│
|
||||
┌──────┴─────┐
|
||||
│ EventBus │ → 事件广播到前端
|
||||
│ (eventbus) │
|
||||
└────────────┘
|
||||
```
|
||||
|
||||
### 2.1 DagDef(定义层)— 工作流的"蓝图"
|
||||
|
||||
> 源文件:`dag_def.rs`
|
||||
|
||||
```rust
|
||||
pub struct DagDef {
|
||||
pub nodes: HashMap<String, NodeDef>, // 节点定义
|
||||
pub edges: Vec<EdgeDef>, // 依赖关系
|
||||
}
|
||||
|
||||
pub struct NodeDef {
|
||||
pub id: String, // 如 "check-env"
|
||||
pub node_type: String, // 如 "script"、"ai"、"human"
|
||||
pub config: serde_json::Value, // 节点参数(命令、prompt 等)
|
||||
}
|
||||
|
||||
pub struct EdgeDef {
|
||||
pub source: String, // 如 "check-env"
|
||||
pub target: String, // 如 "run-tests"
|
||||
pub condition: Option<String>, // 可选条件(如 "true")
|
||||
}
|
||||
```
|
||||
|
||||
这是**可序列化**的,存到 SQLite 的 `workflow_defs.dag` 字段(JSON),也可以从模板加载。
|
||||
|
||||
### 2.2 Node(节点抽象)— 所有节点的统一接口
|
||||
|
||||
> 源文件:`node.rs`
|
||||
|
||||
```rust
|
||||
#[async_trait]
|
||||
pub trait Node: Send + Sync {
|
||||
async fn execute(&self, ctx: NodeContext) -> NodeResult;
|
||||
fn schema(&self) -> NodeSchema;
|
||||
fn is_blocking(&self) -> bool { false } // 阻塞节点(如人工审批)
|
||||
fn node_type(&self) -> &str;
|
||||
}
|
||||
```
|
||||
|
||||
每个节点执行时拿到一个 `NodeContext`:
|
||||
|
||||
```rust
|
||||
pub struct NodeContext {
|
||||
pub node_id: String,
|
||||
pub inputs: HashMap<String, NodeOutput>, // 上游节点的输出
|
||||
pub config: serde_json::Value, // 节点配置
|
||||
pub execution_id: String, // 本次执行 ID
|
||||
pub event_bus: EventBus, // 事件总线(发事件给前端)
|
||||
pub node_status: StateMachine, // 共享状态机(检查是否被取消)
|
||||
}
|
||||
```
|
||||
|
||||
**关键设计**:`node_status` 是 `StateMachine` 的 clone,但内部是 `Arc<Mutex<HashMap>>`,所以 clone 共享同一底层数据——外部 IPC 调 `set_cancelled` 写入后,运行中的节点能立即读到。
|
||||
|
||||
### 2.3 Dag(运行时图)+ 拓扑排序
|
||||
|
||||
> 源文件:`dag.rs`
|
||||
|
||||
`DagDef` 是数据蓝图,`Dag` 是装入真实节点实例的运行时图。核心方法是 `topological_layers()`:
|
||||
|
||||
```
|
||||
输入:节点 + 边的依赖关系
|
||||
输出:Vec<Vec<NodeId>> —— 分层的节点 ID
|
||||
|
||||
算法:Kahn 算法(BFS 分层)
|
||||
1. 算每个节点的入度(有多少上游)
|
||||
2. 入度=0 的节点入队(第一层)
|
||||
3. 取出一层节点,将它们的下游入度 -1
|
||||
4. 入度归零的下游入队(下一层)
|
||||
5. 重复直到所有节点处理完
|
||||
6. 如果处理数 ≠ 节点总数 → 有环,报错
|
||||
```
|
||||
|
||||
复杂度 O(V+E):一次性遍历边构建邻接索引(出边表)与入度表,BFS 分层只走索引查询(每条边仅被访问一次)。
|
||||
|
||||
这就是引擎的核心智能:**用户只需画依赖关系(谁依赖谁),引擎自动算出执行顺序和并行机会**。
|
||||
|
||||
### 2.4 DagExecutor(执行器)— 三阶段调度
|
||||
|
||||
> 源文件:`executor.rs`
|
||||
|
||||
```rust
|
||||
pub async fn run(&mut self, dag: &Dag, config: Value) -> Result<HashMap<NodeId, NodeOutput>>
|
||||
```
|
||||
|
||||
对每一层执行**三阶段模式**:
|
||||
|
||||
```
|
||||
┌─ 阶段一:准备 ──────────────────────────────────────┐
|
||||
│ 遍历层内每个节点: │
|
||||
│ ① emit NodeStarted 事件 │
|
||||
│ ② state_machine.set_running(Pending→Running) │
|
||||
│ ③ 收集上游输出作为 inputs │
|
||||
│ ④ 构建 NodeContext │
|
||||
│ ⑤ 创建 async future(不立即执行) │
|
||||
└──────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─ 阶段二:并发执行 ───────────────────────────────────┐
|
||||
│ futures::future::join_all(node_futures).await │
|
||||
│ │
|
||||
│ 同层所有节点真正并发跑(tokio 异步运行时) │
|
||||
│ 等待全部完成(或某个失败) │
|
||||
└──────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─ 阶段三:收尾 ───────────────────────────────────────┐
|
||||
│ 遍历结果: │
|
||||
│ ✅ 成功 → set_completed + emit NodeCompleted + 存输出 │
|
||||
│ ❌ 失败 → set_failed + emit NodeFailed + 记录错误 │
|
||||
│ 🚫 取消 → 跳过 set_failed(Cancelled 是终态) │
|
||||
│ │
|
||||
│ 任一失败 → return Err,后续层不执行 │
|
||||
│ 全部成功 → 进入下一层 │
|
||||
└──────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**为什么三阶段不合并?** 避免 Rust 的借用冲突——阶段一需要 `&self`(读状态机),阶段二的 future 捕获节点引用独立运行,阶段三回来再更新 `&mut self`。
|
||||
|
||||
**入边索引优化**:执行前预建 `adjacency_in: HashMap<NodeId, Vec<NodeId>>`(O(E) 一次构建),避免内层循环中调用 `dag.predecessors()`(每次 O(E) 全表扫描,整体退化 O(V·E))。
|
||||
|
||||
### 2.5 StateMachine(状态机)— 防止非法状态转换
|
||||
|
||||
> 源文件:`state.rs`
|
||||
|
||||
```
|
||||
合法转换图:
|
||||
|
||||
Pending ──→ Running ──→ Completed ✅ 正常完成
|
||||
│
|
||||
└──→ Failed ❌ 执行失败
|
||||
|
||||
任何节点 ←── set_cancelled 🚫 外部取消(不走转换校验)
|
||||
```
|
||||
|
||||
```rust
|
||||
fn is_legal(from: &NodeStatus, to: &NodeStatus) -> bool {
|
||||
matches!((from, to),
|
||||
(Pending, Running) // 启动
|
||||
| (Running, Completed) // 完成
|
||||
| (Running, Failed) // 失败
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
非法转换直接 `bail!`(如 Pending → Completed 跳过执行),防止状态被意外覆盖。
|
||||
|
||||
**共享语义**是关键:
|
||||
|
||||
```rust
|
||||
pub struct StateMachine {
|
||||
states: Arc<Mutex<HashMap<NodeId, NodeStatus>>>,
|
||||
}
|
||||
```
|
||||
|
||||
`clone()` 只是 Arc 引用计数 +1,所有副本共享同一 HashMap。这让取消机制可以跨边界工作:
|
||||
|
||||
```
|
||||
前端点「取消审批」
|
||||
│
|
||||
▼
|
||||
cancel_workflow_node IPC
|
||||
│
|
||||
▼
|
||||
AppState.workflow_state_registry[execution_id].set_cancelled(node_id)
|
||||
│ (写入共享 HashMap)
|
||||
▼
|
||||
HumanNode.execute 内部 ctx.node_status.is_cancelled() → true
|
||||
│ (读到写入,因为是同一份 HashMap)
|
||||
▼
|
||||
返回 Err("人工审批被取消")
|
||||
│
|
||||
▼
|
||||
Executor 检测到 is_cancelled → 跳过 set_failed(避免 Cancelled→Failed 非法转换)
|
||||
```
|
||||
|
||||
### 2.6 EventBus(事件总线)— 对外广播
|
||||
|
||||
> 源文件:`eventbus.rs`
|
||||
|
||||
```rust
|
||||
pub struct EventBus {
|
||||
sender: broadcast::Sender<WorkflowEvent>, // tokio broadcast
|
||||
}
|
||||
```
|
||||
|
||||
容量 256,所有事件通过 `broadcast` 广播。Tauri IPC 层订阅后转发给前端:
|
||||
|
||||
```
|
||||
Executor 发事件 → EventBus broadcast → IPC 层 listen → app.emit("workflow-event") → 前端实时展示
|
||||
```
|
||||
|
||||
事件类型:`NodeStarted` / `NodeCompleted` / `NodeFailed` / `WorkflowCompleted` / `HumanApprovalRequest` / `HumanApprovalResponse`
|
||||
|
||||
### 2.7 ConditionEngine(条件分支)— 当前最简实现
|
||||
|
||||
> 源文件:`conditions.rs`
|
||||
|
||||
```rust
|
||||
pub fn evaluate(expr: &str, _context: &Value) -> Result<bool> {
|
||||
if expr.trim() == "true" { return Ok(true); }
|
||||
if expr.trim() == "false" { return Ok(false); }
|
||||
// 其他一律 false(保守拒绝,不静默放行)
|
||||
Ok(false)
|
||||
}
|
||||
```
|
||||
|
||||
边上的 `condition` 字段目前只认 `"true"` / `"false"` 字面量。升级为真表达式求值是待办(T-260614-11)。
|
||||
|
||||
### 2.8 NodeRegistry(节点注册表)— 工厂模式创建节点
|
||||
|
||||
> 源文件:`registry.rs`
|
||||
|
||||
```rust
|
||||
pub struct NodeRegistry {
|
||||
factories: HashMap<String, NodeFactory>,
|
||||
}
|
||||
```
|
||||
|
||||
核心方法 `build_dag(def: &DagDef) -> Result<Dag>`:遍历 DagDef 的节点定义,按 `node_type` 查工厂函数创建真实节点实例,组装成运行时 Dag。
|
||||
|
||||
**不实现 Default trait**:原 Default 注册了一个会 panic 的 script 工厂(`unimplemented!`),违反项目铁律「无 panic」。所有调用方必须显式 `new()` + 手动 `register` 真实节点。
|
||||
|
||||
---
|
||||
|
||||
## 三、一次完整的执行流程
|
||||
|
||||
以 ProjectDetail.vue 中的 3 节点工作流为例:
|
||||
|
||||
```
|
||||
用户点「运行工作流」
|
||||
│
|
||||
▼
|
||||
IPC: run_workflow(dag_json)
|
||||
│
|
||||
▼
|
||||
① NodeRegistry.build_dag(dag_def)
|
||||
→ 从 JSON 创建 3 个真实节点实例 + 边
|
||||
→ 运行时 Dag
|
||||
│
|
||||
▼
|
||||
② Dag.topological_layers()
|
||||
→ 算出分层:[["check-env"], ["run-tests"], ["build"]]
|
||||
│
|
||||
▼
|
||||
③ DagExecutor.new(event_bus, execution_id).run(dag, config)
|
||||
│
|
||||
├─ 第 1 层 ["check-env"]
|
||||
│ ├─ set_running("check-env")
|
||||
│ ├─ ScriptNode.execute(ctx) → tokio::process::Command("cmd /C node -v")
|
||||
│ └─ set_completed("check-env") + emit NodeCompleted
|
||||
│
|
||||
├─ 第 2 层 ["run-tests"]
|
||||
│ ├─ inputs = { "check-env": 上一步输出 }
|
||||
│ ├─ set_running("run-tests")
|
||||
│ ├─ ScriptNode.execute(ctx) → cmd /C npm test
|
||||
│ └─ set_completed("run-tests")
|
||||
│
|
||||
└─ 第 3 层 ["build"]
|
||||
├─ set_running("build")
|
||||
├─ ScriptNode.execute(ctx) → cmd /C npm run build
|
||||
└─ set_completed("build") + emit WorkflowCompleted
|
||||
│
|
||||
▼
|
||||
④ 返回 HashMap<节点ID, 输出> + 前端实时看到日志
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、现有节点类型
|
||||
|
||||
| 节点 | 实现状态 | 能力 |
|
||||
|------|---------|------|
|
||||
| **ScriptNode** | ✅ 真实可用 | 执行 Shell 命令(Windows: `cmd /C`),非零退出码判断为失败 |
|
||||
| **AiNode** | ✅ 真实可用 | 调用 LLM(OpenAI/Anthropic),config 驱动 provider(base_url/api_key/model/prompt),支持上游输入优先 |
|
||||
| **HumanNode** | ✅ 审批闭环 | 阻塞等待人工审批:subscribe→send→select!(响应/超时/取消),execution_id+node_id 双键过滤 |
|
||||
| DockerNode | ❌ 未实现 | 设计意图:Docker 容器内执行命令(隔离构建/测试环境) |
|
||||
| GitNode | ❌ 未实现 | 设计意图:Git 操作(commit/push/merge/分支管理) |
|
||||
| HTTPNode | ❌ 未实现 | 设计意图:发起 HTTP 请求(调外部 API/Webhook) |
|
||||
| NotifyNode | ❌ 未实现 | 设计意图:多渠道通知(桌面/飞书/钉钉/Webhook) |
|
||||
| SubflowNode | ❌ 未实现 | 设计意图:嵌套子工作流(复杂流程拆分复用) |
|
||||
|
||||
---
|
||||
|
||||
## 五、当前局限
|
||||
|
||||
| 局限 | 说明 | 对应待办 |
|
||||
|------|------|---------|
|
||||
| 条件引擎只有 true/false | 无法做 `$.status == 'ok'` 这种真条件分支 | T-260614-11 |
|
||||
| 无断点续跑 | 执行中崩溃后无法从失败节点恢复 | Phase 4 |
|
||||
| 无暂停/恢复 | 只能取消,不能暂停后继续 | Phase 4 |
|
||||
| 无 DAG 可视化编辑器 | 用户只能写 JSON 定义 | Phase 5 |
|
||||
| 持久化仅存定义 | 执行状态不落盘(纯内存),重启丢失 | 待需求驱动 |
|
||||
| 缺 human 节点端到端测试 | 单测绿但前端无 human DAG 入口,审批闭环未端到端验证 | B-03b-R8(P0) |
|
||||
@@ -1,6 +1,6 @@
|
||||
# df-ai — AI 集成模块
|
||||
|
||||
> 创建: 2026-06-10 | 最后更新: 2026-06-13
|
||||
> 创建: 2026-06-10 | 最后更新: 2026-06-14
|
||||
|
||||
---
|
||||
|
||||
@@ -86,6 +86,7 @@ pub trait LlmProvider: Send + Sync {
|
||||
| finish 判定 | 逐 chunk 判 `finish_reason`:`stop`/`tool_calls`/`length` 三者同等视为正常 finished(`length` = max_tokens 截断,属正常终止而非断连)|
|
||||
| embed | POST `/v1/embeddings`,响应按 index 排序返回 `Vec<Vec<f32>>` |
|
||||
| connect timeout | `connect_timeout(30s)`,不设总 timeout(避免误砍流式长任务)|
|
||||
| complete 单请求超时 | ✅ FR-R4(2026-06-14 commit 36d68dd):`complete()` 同步路径用 `RequestBuilder::timeout(Duration::from_secs(60))` 设 60s 单请求超时;**仅作用于 complete,不影响 stream 流式路径**(stream 仍靠上层 idle timeout 兜底)|
|
||||
|
||||
> 注:idle timeout(120s)与断连丢弃(finished_received)属上层 `stream_llm`(src-tauri/commands/ai.rs)的职责,不在本 Provider 层。
|
||||
|
||||
@@ -121,6 +122,17 @@ SSE 解析抽成 `pub(crate) fn apply_anthropic_event(data: &str, usage_accum: &
|
||||
|
||||
支持 GLM 订阅端点(`https://open.bigmodel.cn/api/anthropic`)。
|
||||
|
||||
### tool_use_id 兜底(AC1/AC2,2026-06-14 commit 36d68dd)
|
||||
|
||||
GLM 端对 `tool_use_id` 为 `None`/空串的 `tool_result` 块会返 500 卡死会话。两路兜底:
|
||||
|
||||
| 路径 | 入站/出站 | 兜底 |
|
||||
|------|----------|------|
|
||||
| 出站(请求构造)| tool_result 块 | `tool_call_id` 为 None/空时**跳过该块** + `tracing::warn!`(不发出无效块)|
|
||||
| 入站(SSE 流式)| tool_use 块 | 缺 id 时填占位 id `tool_missing_{idx}`(idx 为 content_block index)+ `tracing::warn!`;同步路径(complete)缺 id 时跳过 |
|
||||
|
||||
> 两者均 warn 留痕,不静默吞掉,便于事后排查 provider 返回异常 tool_use 的场景。
|
||||
|
||||
---
|
||||
|
||||
## ContextManager 分组滑动窗口(context.rs,Sprint 11)
|
||||
@@ -149,24 +161,36 @@ SSE 解析抽成 `pub(crate) fn apply_anthropic_event(data: &str, usage_accum: &
|
||||
| `build_for_request(sys_tokens) -> (Vec<ChatMessage>, bool)` | 返回裁剪后的消息列表 + 是否发生裁剪(用于 LLM 调用)|
|
||||
| `all_messages_clone()` | 返回全量消息(用于 save_conversation / 标题生成)|
|
||||
| `restore_from_messages(msgs)` | 切换对话时重建 ContextManager 缓存 |
|
||||
| `replace_tool_result_content(tool_call_id, new_content) -> bool` | 原子更新工具结果内容(审批通过/拒绝时回填),返回是否找到并替换 |
|
||||
| `replace_tool_result_content(tool_call_id, new_content) -> bool` | 原子更新工具结果内容(审批通过/拒绝时回填),返回是否找到并替换。✅ FR-D4(2026-06-14 commit 4a95f6a):由正向 `position` 遍历改为反向 `rposition`(审批替换命中最近一条同 id 工具结果;该调用属审批低频路径,不引入索引)|
|
||||
|
||||
---
|
||||
|
||||
## AI 工具注册
|
||||
|
||||
> 归属:`ai_tools.rs` 仅提供基础设施(`RiskLevel` / `AiTool` / `AiToolRegistry`);12 个工具的具体定义与注册在 `src-tauri/src/commands/ai.rs::build_ai_tool_registry`,handler 即唯一执行路径(schema+risk+实现同源)。
|
||||
> 归属:`ai_tools.rs` 仅提供基础设施(`RiskLevel` / `AiTool` / `AiToolRegistry`);工具的具体定义与注册在 `src-tauri/src/commands/ai/tool_registry.rs::build_ai_tool_registry`,handler 即唯一执行路径(schema+risk+实现同源)。
|
||||
|
||||
12 个内置工具,按风险分级:
|
||||
13 个内置工具,按风险分级:
|
||||
|
||||
| 风险 | 工具 |
|
||||
|------|------|
|
||||
| Low(自动执行)| list_projects / list_tasks / list_ideas / read_file / list_directory |
|
||||
| Medium(需审批)| update_project / create_project / create_task / create_idea / write_file |
|
||||
| High(需审批)| delete_project / run_workflow |
|
||||
| High(需审批)| delete_project / run_workflow / run_command |
|
||||
|
||||
工具执行结果写 `ai_tool_executions` 表(审计日志)。
|
||||
|
||||
### run_command(Sprint 21,方案 A「直接暴露 Shell」)
|
||||
|
||||
让 AI 形成「写(write_file)→跑(run_command)→看 stdout→改」闭环。handler 复用 `df_execute::shell::execute`(跨平台:Windows `cmd /C` / Unix `sh -c`,已封装 `tokio::time::timeout` + `kill_on_drop` 防僵尸进程)。
|
||||
|
||||
| 维度 | 设计 |
|
||||
|------|------|
|
||||
| 风险 | **High**——强制人工审批,审批卡显示 `command`+`working_dir`(复用 ToolCard `toolArgsEntries`,零前端改动) |
|
||||
| 参数 | `command`(必填) / `working_dir`(可选,默认 workspace_root) / `timeout_secs`(可选,默认 60) |
|
||||
| working_dir 校验 | 走 `validate_path` 黑名单(`..` + `.ssh`/`.aws`/`windows`/`system32` 等),**不走** `resolve_workspace_path` 越界校验——High risk 靠人审兜底,放开目录才能在用户任意项目跑命令 |
|
||||
| 输出截断 | stdout/stderr 各 10KB,尾部保留(报错堆栈在末尾),超出设 `truncated: true`(`truncate_output` 辅助函数,char 边界安全截) |
|
||||
| 安全边界 | A 方案=最高风险,唯一防线=人审+黑名单。未做命令黑名单(`rm -rf`/`format`)、网络外传检测、资源限制——属 B(ScriptNode)/C(Docker)/D(MCP) 方案领域 |
|
||||
|
||||
---
|
||||
|
||||
## 知识库集成(Sprint 15,逻辑在 src-tauri/commands/ai.rs)
|
||||
@@ -223,12 +247,12 @@ SSE 解析抽成 `pub(crate) fn apply_anthropic_event(data: &str, usage_accum: &
|
||||
| Conditions(条件分支)| 缺失(df-ai 无实现,条件求值属 df-workflow crate)| JSON Path / 比较 / and-or-not |
|
||||
| Reflection(自纠)| 缺失 | 执行后自检 / 重试 |
|
||||
|
||||
详见 [Phase 2 计划 - 决策能力升级](../07-项目管理/Phase2计划.md)。
|
||||
详见 [Phase 2 计划 - 决策能力升级](../07-项目管理/Phase2计划-2026-06-12.md)。
|
||||
|
||||
---
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [df-storage 存储层](./df-storage-存储层.md)
|
||||
- [df-workflow 工作流引擎](./df-workflow-工作流引擎.md)
|
||||
- [df-nodes 节点集合](./df-nodes-节点集合.md)
|
||||
- [df-storage 存储层](./df-storage-存储层-2026-06-12.md)
|
||||
- [df-workflow 工作流引擎](./df-workflow-工作流引擎-2026-06-12.md)
|
||||
- [df-nodes 节点集合](./df-nodes-节点集合-2026-06-12.md)
|
||||
|
||||
@@ -130,6 +130,6 @@ Tier 1+ 目标:MCP Shell 封装(`mcp-server` 转发 IPC),外部工具使
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [df-storage 存储层](./df-storage-存储层.md) — knowledges 表结构 + 向量工具函数
|
||||
- [df-ai AI 集成模块](./df-ai-AI集成模块.md) — hybrid_search / generate_embedding / extract
|
||||
- [功能决策记录](../02-架构设计/功能决策记录.md) — 检索方案演进决策
|
||||
- [df-storage 存储层](./df-storage-存储层-2026-06-12.md) — knowledges 表结构 + 向量工具函数
|
||||
- [df-ai AI 集成模块](./df-ai-AI集成模块-2026-06-12.md) — hybrid_search / generate_embedding / extract
|
||||
- [功能决策记录](../02-架构设计/功能决策记录-2026-06-14.md) — 检索方案演进决策
|
||||
|
||||
@@ -71,4 +71,4 @@ crates/df-nodes/src/
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [df-workflow 工作流引擎](./df-workflow-工作流引擎.md)
|
||||
- [df-workflow 工作流引擎](./df-workflow-工作流引擎-2026-06-12.md)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# df-storage 存储层
|
||||
|
||||
> 创建: 2026-06-10 | 最后更新: 2026-06-13
|
||||
> 创建: 2026-06-10 | 最后更新: 2026-06-14
|
||||
|
||||
---
|
||||
|
||||
@@ -94,7 +94,7 @@ CREATE INDEX IF NOT EXISTS idx_knowledges_reuse_count ON knowledges(reuse_count
|
||||
| `increment_reuse_count(id)` | `UPDATE SET reuse_count = reuse_count + 1, updated_at = ?`(SQL 原子操作,连带刷 updated_at)|
|
||||
| `top_used(limit)` | published 按 reuse_count DESC,热门列表 |
|
||||
| `set_embedding(id, &[f32])` | UPDATE embedding BLOB(f32 little-endian)|
|
||||
| `search_vector(query_vec, limit)` | SELECT published + embedding IS NOT NULL → 纯 Rust 余弦批量比较,skip 维度不匹配 |
|
||||
| `search_vector(query_vec, limit)` | ✅ FR-D2(2026-06-14 commit 4a95f6a):由 `SELECT *` 改为**显式 14 列** `id, kind, title, content, tags, status, confidence, reuse_count, verified, source_project, source_ref, reasoning, created_at, updated_at`;`embedding` 单独另一条 SQL 取(BLOB 单独读,避免与大文本字段混取)→ 纯 Rust 余弦批量比较,skip 维度不匹配。消除 `SELECT *` 隐式依赖,字段精简(`reasoning` 已在列白名单中) |
|
||||
| `list_non_archived()` | `WHERE status != 'archived'` 全量,CASE confidence 语义排序(high>medium>low),次 created_at DESC → `Vec<KnowledgeRecord>` |
|
||||
|
||||
### 向量工具函数
|
||||
@@ -137,5 +137,5 @@ crates/df-storage/src/
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [SQLite CRUD 模式](../01-技术文档/SQLite-CRUD模式.md)
|
||||
- [前后端类型对齐](../02-架构设计/前后端类型对齐.md)
|
||||
- [SQLite CRUD 模式](../01-技术文档/SQLite-CRUD模式-2026-06-12.md)
|
||||
- [前后端类型对齐](../02-架构设计/前后端类型对齐-2026-06-12.md)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# df-workflow 工作流引擎
|
||||
|
||||
> 创建: 2026-06-10 | 状态: 初稿
|
||||
> 创建: 2026-06-10 | 状态: 初稿 | 最后更新: 2026-06-14
|
||||
|
||||
---
|
||||
|
||||
@@ -169,7 +169,7 @@ pub trait Node: Send + Sync {
|
||||
| `add_edge_with_condition` | `(&mut self, source, target, condition: String)` | 加带条件边 |
|
||||
| `predecessors` | `(&NodeId) -> Vec<NodeId>` | 上游节点 ID |
|
||||
| `successors` | `(&NodeId) -> Vec<NodeId>` | 下游节点 ID |
|
||||
| `topological_layers` | `() -> Result<Vec<Vec<NodeId>>>` | BFS 分层拓扑排序,同层可并行;**检测到环时报 `Workflow` 错误**("DAG 中存在环") |
|
||||
| `topological_layers` | `() -> Result<Vec<Vec<NodeId>>>` | BFS 分层拓扑排序,同层可并行;**检测到环时报 `Workflow` 错误**("DAG 中存在环")。✅ FR-D1(2026-06-14 commit 4b5f096):复杂度由 O(V·E) 降为 **O(V+E)**——一次性遍历边建 `adjacency_out`(出边表)+ 入度表,BFS 分层走索引而非每节点重扫 `self.edges`。`executor.rs::run` 预建 `adjacency_in`(入边索引)取前驱,消除逐节点 `predecessors()` 全表扫描 |
|
||||
|
||||
`Edge { source, target, condition: Option<String> }` — condition 为可选条件表达式,由 `conditions.rs` 求值。
|
||||
|
||||
@@ -219,9 +219,9 @@ AI Chat(B 路线)从单链 ReAct 升级为规划式协作时,本引擎需
|
||||
|
||||
2. **executor 分层并行接入 agentic loop** —— `topological_layers` + `join_all` 已实现(测试 `test_same_layer_runs_in_parallel`),但仅喂静态 DAG。需让 `run_agentic_loop` 内动态生成的 DAG 走这条并行通道,而非单轮串行 `process_tool_calls`。
|
||||
|
||||
详见 [Phase 2 计划 - 决策能力升级](../07-项目管理/Phase2计划.md)。
|
||||
详见 [Phase 2 计划 - 决策能力升级](../07-项目管理/Phase2计划-2026-06-12.md)。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [df-nodes 节点集合](./df-nodes-节点集合.md)
|
||||
- [Phase1 架构决策](../02-架构设计/Phase1架构决策.md)
|
||||
- [df-nodes 节点集合](./df-nodes-节点集合-2026-06-12.md)
|
||||
- [Phase1 架构决策](../02-架构设计/Phase1架构决策-2026-06-12.md)
|
||||
|
||||
Reference in New Issue
Block a user