{
  "metadata": {
    "id": "ch19",
    "title": "第19章：自动化工作流 Agent",
    "volume": "vol5",
    "volume_title": "专项篇",
    "word_count": 1426,
    "difficulty": "intermediate",
    "prerequisites": [
      "ch06",
      "ch09"
    ],
    "key_concepts": [
      "概述：从脚本到智能编排",
      "工作流 vs. 脚本 vs. Agent",
      "工作流 Agent 的核心能力",
      "工作流引擎设计",
      "核心数据结构",
      "DAG 工作流引擎",
      "条件分支与并行执行",
      "工作流 DSL（领域特定语言）",
      "并行执行与合并",
      "人工审批节点",
      "审批系统",
      "定时任务调度",
      "调度引擎",
      "事件触发机制",
      "事件总线"
    ],
    "learning_objectives": [],
    "estimated_tokens": 856,
    "source_file": "vol5/ch19_自动化工作流Agent.md"
  },
  "overview": "",
  "sections": [
    {
      "id": "19.1",
      "title": "19.1 概述：从脚本到智能编排",
      "level": 2,
      "content": "自动化工作流 Agent 是将多个 Agent 和工具按照特定逻辑串联起来的编排系统。如果说单个 Agent 是一个\"工人\"，工作流就是\"工厂\"——它协调多个工人、管理物料流转、处理异常中断、确保产出质量。",
      "subsections": [
        {
          "id": "19.1.1",
          "title": "19.1.1 工作流 vs. 脚本 vs. Agent",
          "content": "| 维度 | 脚本 | 工作流 | Agent |\n|------|------|--------|-------|\n| 执行路径 | 固定 | 条件分支 | 动态决策 |\n| 人工介入 | 无 | 审批节点 | 可协商 |\n| 错误处理 | try/catch | 重试/回滚 | 自我修复 |\n| 并行能力 | 多线程 | DAG 并行 | 多 Agent 协作 |\n| 状态管理 | 无/简单 | 持久化状态 | 记忆系统 |\n| 适应性 | 零 | 低 | 高 |"
        },
        {
          "id": "19.1.2",
          "title": "19.1.2 工作流 Agent 的核心能力",
          "content": "- **工作流引擎设计**：DAG 有向无环图、状态机、并行执行\n- **条件分支**：基于规则或 LLM 判断的动态路由\n- **并行执行**：无依赖任务的并发处理\n- **人工审批节点**：暂停流程等待人工确认\n- **定时任务调度**：Cron 表达式、时间窗口、超时处理\n- **事件触发机制**：Webhook、消息队列、状态变更监听"
        }
      ]
    },
    {
      "id": "19.2",
      "title": "19.2 工作流引擎设计",
      "level": 2,
      "content": "",
      "subsections": [
        {
          "id": "19.2.1",
          "title": "19.2.1 核心数据结构",
          "content": ""
        },
        {
          "id": "19.2.2",
          "title": "19.2.2 DAG 工作流引擎",
          "content": ""
        }
      ]
    },
    {
      "id": "19.3",
      "title": "19.3 条件分支与并行执行",
      "level": 2,
      "content": "",
      "subsections": [
        {
          "id": "19.3.1",
          "title": "19.3.1 工作流 DSL（领域特定语言）",
          "content": "为了让工作流定义更直观，我们设计一个简洁的 DSL："
        },
        {
          "id": "19.3.2",
          "title": "19.3.2 并行执行与合并",
          "content": ""
        }
      ]
    },
    {
      "id": "19.4",
      "title": "19.4 人工审批节点",
      "level": 2,
      "content": "",
      "subsections": [
        {
          "id": "19.4.1",
          "title": "19.4.1 审批系统",
          "content": ""
        }
      ]
    },
    {
      "id": "19.5",
      "title": "19.5 定时任务调度",
      "level": 2,
      "content": "",
      "subsections": [
        {
          "id": "19.5.1",
          "title": "19.5.1 调度引擎",
          "content": ""
        }
      ]
    },
    {
      "id": "19.6",
      "title": "19.6 事件触发机制",
      "level": 2,
      "content": "",
      "subsections": [
        {
          "id": "19.6.1",
          "title": "19.6.1 事件总线",
          "content": ""
        },
        {
          "id": "19.6.2",
          "title": "19.6.2 事件驱动的 Webhook 触发器",
          "content": ""
        }
      ]
    },
    {
      "id": "19.7",
      "title": "19.7 完整集成：自动化工作流 Agent",
      "level": 2,
      "content": "",
      "subsections": [
        {
          "id": "19.7.1",
          "title": "19.7.1 统一工作流编排器",
          "content": ""
        },
        {
          "id": "19.7.2",
          "title": "19.7.2 端到端示例",
          "content": ""
        }
      ]
    },
    {
      "id": "19.8",
      "title": "19.8 生产级考量",
      "level": 2,
      "content": "",
      "subsections": [
        {
          "id": "19.8.1",
          "title": "19.8.1 持久化与恢复",
          "content": "| 状态存储 | 方案 | 适用场景 |\n|----------|------|---------|\n| 内存 | Python dict | 开发/测试 |\n| 文件 | JSON/SQLite | 单机部署 |\n| Redis | Hash + TTL | 中等规模 |\n| 数据库 | PostgreSQL + JSONB | 生产环境 |"
        },
        {
          "id": "19.8.2",
          "title": "19.8.2 可观测性",
          "content": ""
        }
      ]
    },
    {
      "id": "本章小结",
      "title": "本章小结",
      "level": 2,
      "content": "本章从 DAG 工作流引擎出发，构建了完整的自动化工作流 Agent 体系：\n\n1. **工作流引擎设计**：基于 DAG 的节点编排、状态管理、上下文共享\n2. **条件分支与并行**：动态路由、Fan-out/Fan-in 并行模式、分支合并策略\n3. **人工审批节点**：审批请求、批准/拒绝、回调恢复、审批统计\n4. **定时任务调度**：Cron 表达式解析、固定间隔、超时处理、任务管理\n5. **事件触发机制**：发布/订阅事件总线、Webhook 管理器、事件驱动工作流\n6. **持久化与可观测性**：状态持久化、指标收集、性能追踪\n\n核心思想：**工作流 Agent 的本质是\"协调\"——协调人、机器、数据和时间，让复杂的业务流程变得可预测、可观测、可恢复。** 一个好的工作流系统，不仅要知道\"做什么\"，更要知道\"出错时怎么办\"。",
      "subsections": []
    }
  ],
  "code_blocks": [
    {
      "id": "code-1",
      "language": "text",
      "description": "自动化工作流 Agent 是将多个 Agent 和工具按照特定逻辑串联起来的编排系统。如果说单个 Agent 是一个\"工人\"，工作流就是\"工厂\"——它协调多个工人、管理物料流转、处理异常中断、确保产出",
      "code": "                    自主性\n                      ▲\n                      │\n              ┌───────┼───────┐\n              │       │       │\n           脚本    工作流    Agent\n           (确定性) (半自主)  (全自主)\n              │       │       │\n              │   ┌───┼───┐   │\n              │   │       │   │\n           固定  条件分支  人工  事件\n           顺序  并行     审批   触发",
      "section_ref": "19.1.1",
      "runnable": false,
      "dependencies": []
    },
    {
      "id": "code-2",
      "language": "python",
      "description": "",
      "code": "\"\"\"\n自动化工作流 Agent - 工作流引擎\n基于 DAG（有向无环图）的工作流编排系统\n\"\"\"\n\nfrom dataclasses import dataclass, field\nfrom enum import Enum\nfrom typing import Optional, Callable, Any\nimport json\nimport time\nimport uuid\nimport asyncio\nimport logging\nfrom collections import defaultdict, deque\n\nlogger = logging.getLogger(__name__)\n\n\nclass NodeType(Enum):\n    \"\"\"节点类型\"\"\"\n    TASK = \"task\"              # 普通任务节点\n    CONDITION = \"condition\"    # 条件分支节点\n    PARALLEL = \"parallel\"      # 并行网关\n    APPROVAL = \"approval\"      # 人工审批节点\n    TIMER = \"timer\"            # 定时器节点\n    EVENT = \"event\"            # 事件触发节点\n    SUB_WORKFLOW = \"sub_workflow\"  # 子工作流\n\n\nclass ExecutionStatus(Enum):\n    \"\"\"执行状态\"\"\"\n    PENDING = \"pending\"\n    RUNNING = \"running\"\n    SUCCESS = \"success\"\n    FAILED = \"failed\"\n    SKIPPED = \"skipped\"\n    WAITING = \"waiting\"         # 等待审批/事件\n    CANCELLED = \"cancelled\"\n    TIMEOUT = \"timeout\"\n\n\nclass BranchStrategy(Enum):\n    \"\"\"并行分支策略\"\"\"\n    ALL = \"all\"           # 全部执行（AND）\n    ANY = \"any\"           # 任一成功即可（OR）\n    MAJORITY = \"majority\" # 多数成功\n\n\n@dataclass\nclass WorkflowNode:\n    \"\"\"工作流节点\"\"\"\n    node_id: str\n    node_type: NodeType\n    name: str\n    handler: Optional[Callable] = None          # 执行函数\n    handler_name: Optional[str] = None         # handler 的可读名称\n    condition: Optional[Callable] = None        # 条件判断函数\n    timeout: int = 0                            # 超时秒数，0=无限\n    retry_count: int = 0                        # 重试次数\n    retry_delay: int = 1                        # 重试间隔（秒）\n    inputs: dict = field(default_factory=dict)  # 输入映射\n    outputs: dict = field(default_factory=dict) # 输出映射\n    metadata: dict = field(default_factory=dict)\n\n\n@dataclass\nclass WorkflowEdge:\n    \"\"\"工作流边（连接）\"\"\"\n    source_id: str\n    target_id: str\n    condition_name: Optional[str] = None   # 条件分支标识\n    condition_expr: Optional[str] = None    # 条件表达式\n\n\n@dataclass\nclass NodeResult:\n    \"\"\"节点执行结果\"\"\"\n    node_id: str\n    status: ExecutionStatus\n    output: Any = None\n    error: Optional[str] = None\n    start_time: float = 0.0\n    end_time: float = 0.0\n    duration_ms: int = 0\n    retry_count: int = 0\n\n\nclass WorkflowContext:\n    \"\"\"工作流执行上下文 - 节点间共享数据\"\"\"\n\n    def __init__(self, workflow_id: str):\n        self.workflow_id = workflow_id\n        self.variables: dict = {}\n        self.node_results: dict[str, NodeResult] = {}\n        self.start_time: float = time.time()\n\n    def set(self, key: str, value: Any):\n        \"\"\"设置变量\"\"\"\n        self.variables[key] = value\n\n    def get(self, key: str, default: Any = None) -> Any:\n        \"\"\"获取变量\"\"\"\n        return self.variables.get(key, default)\n\n    def set_node_result(self, node_id: str, result: NodeResult):\n        \"\"\"记录节点结果\"\"\"\n        self.node_results[node_id] = result\n        if result.status == ExecutionStatus.SUCCESS:\n            # 自动将输出存入上下文\n            if isinstance(result.output, dict):\n                for k, v in result.output.items():\n                    self.variables[f\"{node_id}.{k}\"] = v\n            else:\n                self.variables[node_id] = result.output\n\n    def get_node_result(self, node_id: str) -> Optional[NodeResult]:\n        \"\"\"获取节点结果\"\"\"\n        return self.node_results.get(node_id)\n\n    def to_dict(self) -> dict:\n        \"\"\"序列化为字典\"\"\"\n        return {\n            \"workflow_id\": self.workflow_id,\n            \"variables\": {\n                k: str(v)[:200] if not isinstance(v, (int, float, bool, type(None)))\n                else v\n                for k, v in self.variables.items()\n            },\n            \"node_status\": {\n                nid: result.status.value\n                for nid, result in self.node_results.items()\n            }\n        }",
      "section_ref": "19.2.1",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-3",
      "language": "python",
      "description": "",
      "code": "class WorkflowEngine:\n    \"\"\"\n    DAG 工作流引擎\n\n    特性:\n    - 基于有向无环图的任务编排\n    - 条件分支与并行执行\n    - 人工审批节点\n    - 超时与重试机制\n    - 执行上下文共享\n    \"\"\"\n\n    def __init__(self):\n        self.nodes: dict[str, WorkflowNode] = {}\n        self.edges: list[WorkflowEdge] = []\n        self._adjacency: dict[str, list[str]] = defaultdict(list)\n        self._reverse_adj: dict[str, list[str]] = defaultdict(list)\n        self._listeners: list[Callable] = []\n\n    def add_node(self, node: WorkflowNode) -> 'WorkflowEngine':\n        \"\"\"添加节点（支持链式调用）\"\"\"\n        self.nodes[node.node_id] = node\n        return self\n\n    def add_edge(self, source_id: str, target_id: str,\n                 condition_name: Optional[str] = None,\n                 condition_expr: Optional[str] = None) -> 'WorkflowEngine':\n        \"\"\"添加边（连接）\"\"\"\n        edge = WorkflowEdge(source_id, target_id, condition_name, condition_expr)\n        self.edges.append(edge)\n        self._adjacency[source_id].append(target_id)\n        self._reverse_adj[target_id].append(source_id)\n        return self\n\n    def on_event(self, listener: Callable):\n        \"\"\"注册事件监听器\"\"\"\n        self._listeners.append(listener)\n        return self\n\n    def _emit(self, event_type: str, data: dict = None):\n        \"\"\"触发事件\"\"\"\n        for listener in self._listeners:\n            try:\n                listener(event_type, data or {})\n            except Exception as e:\n                logger.error(f\"事件监听器异常: {e}\")\n\n    def _get_start_nodes(self) -> list[str]:\n        \"\"\"获取起始节点（没有入边的节点）\"\"\"\n        all_targets = set()\n        for edge in self.edges:\n            all_targets.add(edge.target_id)\n        return [nid for nid in self.nodes if nid not in all_targets]\n\n    def _get_next_nodes(self, node_id: str) -> list[tuple[str, Optional[str]]]:\n        \"\"\"获取后续节点及其条件标识\"\"\"\n        result = []\n        for edge in self.edges:\n            if edge.source_id == node_id:\n                result.append((edge.target_id, edge.condition_name))\n        return result\n\n    def _get_ready_nodes(self, context: WorkflowContext) -> list[str]:\n        \"\"\"获取可以执行的节点（所有前置节点已完成）\"\"\"\n        ready = []\n        completed_ids = {\n            nid for nid, r in context.node_results.items()\n            if r.status in (ExecutionStatus.SUCCESS, ExecutionStatus.SKIPPED)\n        }\n\n        for node_id in self.nodes:\n            if node_id in context.node_results:\n                continue  # 已执行过\n\n            # 检查所有前置节点是否完成\n            predecessors = self._reverse_adj.get(node_id, [])\n            if all(pid in completed_ids for pid in predecessors):\n                # 检查前置节点是否有失败\n                all_success = all(\n                    context.node_results[pid].status == ExecutionStatus.SUCCESS\n                    for pid in predecessors\n                )\n                if all_success or not predecessors:\n                    ready.append(node_id)\n\n        return ready\n\n    def _validate_dag(self) -> bool:\n        \"\"\"验证是否为有效的 DAG（无环）\"\"\"\n        visited = set()\n        path = set()\n\n        def dfs(node_id: str) -> bool:\n            if node_id in path:\n                return False  # 发现环\n            if node_id in visited:\n                return True\n            visited.add(node_id)\n            path.add(node_id)\n            for neighbor in self._adjacency.get(node_id, []):\n                if not dfs(neighbor):\n                    return False\n            path.remove(node_id)\n            return True\n\n        for node_id in self.nodes:\n            if node_id not in visited:\n                if not dfs(node_id):\n                    return False\n        return True\n\n    async def execute(self, workflow_id: Optional[str] = None,\n                      initial_data: Optional[dict] = None,\n                      max_parallel: int = 5) -> WorkflowContext:\n        \"\"\"\n        执行工作流\n\n        Args:\n            workflow_id: 工作流实例 ID\n            initial_data: 初始数据\n            max_parallel: 最大并行数\n\n        Returns:\n            WorkflowContext 包含完整执行结果\n        \"\"\"\n        wid = workflow_id or str(uuid.uuid4())[:8]\n        ctx = WorkflowContext(wid)\n\n        if initial_data:\n            for k, v in initial_data.items():\n                ctx.set(k, v)\n\n        if not self._validate_dag():\n            ctx.set(\"error\", \"工作流包含循环，无法执行\")\n            self._emit(\"workflow_failed\", {\"workflow_id\": wid, \"error\": \"循环检测失败\"})\n            return ctx\n\n        self._emit(\"workflow_started\", {\"workflow_id\": wid})\n\n        # 使用信号量控制并行度\n        semaphore = asyncio.Semaphore(max_parallel)\n\n        while True:\n            ready = self._get_ready_nodes(ctx)\n            if not ready:\n                break\n\n            async def run_node(node_id: str):\n                async with semaphore:\n                    return await self._execute_node(ctx, node_id)\n\n            # 并行执行就绪节点\n            results = await asyncio.gather(\n                *[run_node(nid) for nid in ready], return_exceptions=True\n            )\n\n            for result in results:\n                if isinstance(result, Exception):\n                    logger.error(f\"节点执行异常: {result}\")\n\n        # 检查是否所有节点都完成\n        total = len(self.nodes)\n        done = len(ctx.node_results)\n        success = sum(1 for r in ctx.node_results.values()\n                     if r.status == ExecutionStatus.SUCCESS)\n\n        self._emit(\"workflow_completed\", {\n            \"workflow_id\": wid,\n            \"total_nodes\": total,\n            \"completed\": done,\n            \"success\": success\n        })\n\n        return ctx\n\n    async def _execute_node(self, ctx: WorkflowContext, node_id: str) -> NodeResult:\n        \"\"\"执行单个节点\"\"\"\n        node = self.nodes.get(node_id)\n        if not node:\n            return NodeResult(node_id, ExecutionStatus.FAILED, error=\"节点不存在\")\n\n        self._emit(\"node_started\", {\"workflow_id\": ctx.workflow_id, \"node_id\": node_id})\n\n        start = time.time()\n        result = NodeResult(node_id, ExecutionStatus.RUNNING, start_time=start)\n\n        # 根据节点类型执行\n        try:\n            if node.node_type == NodeType.CONDITION:\n                output = await self._execute_condition(ctx, node)\n            elif node.node_type == NodeType.APPROVAL:\n                output = await self._execute_approval(ctx, node)\n            elif node.node_type == NodeType.TIMER:\n                output = await self._execute_timer(ctx, node)\n            else:\n                output = await self._execute_task(ctx, node)\n\n            result.status = ExecutionStatus.SUCCESS\n            result.output = output\n\n        except asyncio.TimeoutError:\n            result.status = ExecutionStatus.TIMEOUT\n            result.error = f\"节点执行超时 ({node.timeout}s)\"\n        except Exception as e:\n            result.status = ExecutionStatus.FAILED\n            result.error = str(e)\n\n        result.end_time = time.time()\n        result.duration_ms = int((result.end_time - result.start_time) * 1000)\n        ctx.set_node_result(node_id, result)\n\n        self._emit(\"node_completed\", {\n            \"workflow_id\": ctx.workflow_id,\n            \"node_id\": node_id,\n            \"status\": result.status.value,\n            \"duration_ms\": result.duration_ms\n        })\n\n        return result\n\n    async def _execute_task(self, ctx: WorkflowContext,\n                             node: WorkflowNode) -> Any:\n        \"\"\"执行普通任务节点（含重试）\"\"\"\n        if not node.handler:\n            return None\n\n        last_error = None\n        for attempt in range(node.retry_count + 1):\n            try:\n                if asyncio.iscoroutinefunction(node.handler):\n                    return await asyncio.wait_for(\n                        node.handler(ctx), timeout=node.timeout or None)\n                else:\n                    loop = asyncio.get_event_loop()\n                    return await asyncio.wait_for(\n                        loop.run_in_executor(None, node.handler, ctx),\n                        timeout=node.timeout or None)\n            except asyncio.TimeoutError:\n                raise\n            except Exception as e:\n                last_error = e\n                if attempt < node.retry_count:\n                    logger.warning(f\"节点 {node.node_id} 第 {attempt+1} 次重试: {e}\")\n                    await asyncio.sleep(node.retry_delay)\n\n        raise last_error\n\n    async def _execute_condition(self, ctx: WorkflowContext,\n                                  node: WorkflowNode) -> str:\n        \"\"\"执行条件分支 - 返回匹配的分支名称\"\"\"\n        if node.condition:\n            result = node.condition(ctx)\n            if asyncio.iscoroutine(result):\n                result = await result\n            return str(result)\n\n        # 根据后续边的条件表达式判断\n        for edge in self.edges:\n            if edge.source_id != node.node_id:\n                continue\n            if edge.condition_expr:\n                try:\n                    # 简单的条件表达式求值\n                    if eval(edge.condition_expr, {\"ctx\": ctx, **ctx.variables}):\n                        return edge.condition_name or edge.target_id\n                except Exception:\n                    continue\n        return \"default\"\n\n    async def _execute_approval(self, ctx: WorkflowContext,\n                                 node: WorkflowNode) -> Any:\n        \"\"\"执行人工审批节点 - 阻塞等待审批结果\"\"\"\n        # 实际实现中，这里会将工作流状态持久化并等待外部回调\n        approval_key = f\"approval.{node.node_id}\"\n        approved = ctx.get(approval_key)\n\n        if approved is None:\n            # 模拟等待审批\n            self._emit(\"approval_requested\", {\n                \"workflow_id\": ctx.workflow_id,\n                \"node_id\": node.node_id,\n                \"node_name\": node.name\n            })\n\n            # 在真实系统中，这里会暂停并等待外部 API 回调\n            # 演示中设置为等待状态\n            raise RuntimeError(\n                f\"审批节点 '{node.name}' 等待人工审批。\"\n                f\"请调用 ctx.set('{approval_key}', True/False) 后恢复。\"\n            )\n\n        if not approved:\n            raise RuntimeError(f\"审批被拒绝: {node.name}\")\n        return {\"approved\": True}\n\n    async def _execute_timer(self, ctx: WorkflowContext,\n                              node: WorkflowNode) -> Any:\n        \"\"\"执行定时器节点\"\"\"\n        delay = node.metadata.get(\"delay_seconds\", 0)\n        cron = node.metadata.get(\"cron_expression\")\n\n        if delay > 0:\n            await asyncio.sleep(delay)\n        elif cron:\n            # 简单的 Cron 解析（生产环境建议使用 APScheduler）\n            import re as _re\n            m = _re.match(r'(\\d+)\\s*(s|m|h)', cron)\n            if m:\n                value, unit = int(m.group(1)), m.group(2)\n                seconds = value * {\"s\": 1, \"m\": 60, \"h\": 3600}[unit]\n                await asyncio.sleep(seconds)\n\n        return {\"fired_at\": time.time()}",
      "section_ref": "19.2.2",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-4",
      "language": "python",
      "description": "为了让工作流定义更直观，我们设计一个简洁的 DSL：",
      "code": "\"\"\"\n工作流 DSL - 流畅的链式 API 定义工作流\n\"\"\"\n\nclass WorkflowBuilder:\n    \"\"\"工作流构建器 - 提供流畅的链式 API\"\"\"\n\n    def __init__(self, name: str = \"\"):\n        self.engine = WorkflowEngine()\n        self.name = name\n        self._current_node: Optional[str] = None\n\n    def task(self, node_id: str, name: str, handler: Callable,\n             timeout: int = 0, retry: int = 0, retry_delay: int = 1,\n             **metadata) -> 'WorkflowBuilder':\n        \"\"\"添加任务节点\"\"\"\n        node = WorkflowNode(\n            node_id=node_id, node_type=NodeType.TASK,\n            name=name, handler=handler,\n            timeout=timeout, retry_count=retry,\n            retry_delay=retry_delay, metadata=metadata\n        )\n        self.engine.add_node(node)\n        self._current_node = node_id\n        return self\n\n    def condition(self, node_id: str, name: str,\n                  condition_fn: Callable) -> 'WorkflowBuilder':\n        \"\"\"添加条件分支节点\"\"\"\n        node = WorkflowNode(\n            node_id=node_id, node_type=NodeType.CONDITION,\n            name=name, condition=condition_fn\n        )\n        self.engine.add_node(node)\n        self._current_node = node_id\n        return self\n\n    def parallel(self, node_id: str, name: str,\n                 strategy: BranchStrategy = BranchStrategy.ALL) -> 'WorkflowBuilder':\n        \"\"\"添加并行网关节点\"\"\"\n        node = WorkflowNode(\n            node_id=node_id, node_type=NodeType.PARALLEL,\n            name=name, metadata={\"strategy\": strategy.value}\n        )\n        self.engine.add_node(node)\n        self._current_node = node_id\n        return self\n\n    def approval(self, node_id: str, name: str,\n                 approver: Optional[str] = None) -> 'WorkflowBuilder':\n        \"\"\"添加人工审批节点\"\"\"\n        node = WorkflowNode(\n            node_id=node_id, node_type=NodeType.APPROVAL,\n            name=name, metadata={\"approver\": approver}\n        )\n        self.engine.add_node(node)\n        self._current_node = node_id\n        return self\n\n    def timer(self, node_id: str, name: str, delay: int = 0,\n              cron: Optional[str] = None) -> 'WorkflowBuilder':\n        \"\"\"添加定时器节点\"\"\"\n        node = WorkflowNode(\n            node_id=node_id, node_type=NodeType.TIMER,\n            name=name, metadata={\"delay_seconds\": delay, \"cron_expression\": cron}\n        )\n        self.engine.add_node(node)\n        self._current_node = node_id\n        return self\n\n    def edge(self, from_id: str, to_id: str,\n             condition: Optional[str] = None) -> 'WorkflowBuilder':\n        \"\"\"添加连接\"\"\"\n        self.engine.add_edge(from_id, to_id, condition_name=condition)\n        return self\n\n    def then(self, to_id: str, condition: Optional[str] = None) -> 'WorkflowBuilder':\n        \"\"\"从当前节点连接到目标节点\"\"\"\n        if self._current_node:\n            self.edge(self._current_node, to_id, condition)\n        self._current_node = to_id\n        return self\n\n    def build(self) -> WorkflowEngine:\n        \"\"\"构建并返回引擎\"\"\"\n        if not self.engine._validate_dag():\n            raise ValueError(\"工作流包含循环依赖\")\n        return self.engine\n\n\n# ==================== 示例：订单处理工作流 ====================\n\ndef build_order_workflow() -> WorkflowEngine:\n    \"\"\"构建一个完整的订单处理工作流\"\"\"\n\n    async def validate_order(ctx: WorkflowContext):\n        \"\"\"验证订单\"\"\"\n        order = ctx.get(\"order\", {})\n        if not order.get(\"items\"):\n            raise ValueError(\"订单为空\")\n        if not order.get(\"customer_id\"):\n            raise ValueError(\"缺少客户信息\")\n        ctx.set(\"order_valid\", True)\n        return {\"valid\": True, \"item_count\": len(order[\"items\"])}\n\n    async def check_inventory(ctx: WorkflowContext):\n        \"\"\"检查库存\"\"\"\n        order = ctx.get(\"order\", {})\n        in_stock = True\n        for item in order.get(\"items\", []):\n            # 模拟库存检查\n            if item.get(\"quantity\", 0) > 1000:\n                in_stock = False\n                break\n        ctx.set(\"in_stock\", in_stock)\n        return {\"in_stock\": in_stock}\n\n    async def process_payment(ctx: WorkflowContext):\n        \"\"\"处理支付\"\"\"\n        order = ctx.get(\"order\", {})\n        amount = sum(i.get(\"price\", 0) * i.get(\"quantity\", 1)\n                     for i in order.get(\"items\", []))\n        # 模拟支付\n        ctx.set(\"payment_id\", f\"pay_{uuid.uuid4().hex[:8]}\")\n        return {\"payment_id\": ctx.get(\"payment_id\"), \"amount\": amount}\n\n    async def reserve_inventory(ctx: WorkflowContext):\n        \"\"\"预留库存\"\"\"\n        await asyncio.sleep(0.1)  # 模拟处理时间\n        return {\"reserved\": True}\n\n    async def create_shipment(ctx: WorkflowContext):\n        \"\"\"创建发货单\"\"\"\n        await asyncio.sleep(0.1)\n        return {\"shipment_id\": f\"ship_{uuid.uuid4().hex[:8]}\"}\n\n    async def send_confirmation(ctx: WorkflowContext):\n        \"\"\"发送确认通知\"\"\"\n        return {\"notification_sent\": True}\n\n    def route_by_stock(ctx: WorkflowContext) -> str:\n        \"\"\"根据库存状况路由\"\"\"\n        return \"in_stock\" if ctx.get(\"in_stock\") else \"out_of_stock\"\n\n    async def handle_out_of_stock(ctx: WorkflowContext):\n        \"\"\"处理缺货\"\"\"\n        return {\"status\": \"backordered\", \"message\": \"商品缺货，已加入等待列表\"}\n\n    builder = WorkflowBuilder(name=\"订单处理流程\")\n    builder.task(\"start\", \"开始\", lambda ctx: {\"started\": True}) \\\n           .then(\"validate\", condition=None)\n    builder.task(\"validate\", \"验证订单\", validate_order, timeout=30, retry=2) \\\n           .then(\"check_stock\", condition=None)\n    builder.task(\"check_stock\", \"检查库存\", check_inventory) \\\n           .then(\"route\", condition=None)\n    builder.condition(\"route\", \"库存路由\", route_by_stock)\n    builder.task(\"process_payment\", \"处理支付\", process_payment, timeout=60, retry=3)\n    builder.task(\"reserve\", \"预留库存\", reserve_inventory)\n    builder.task(\"ship\", \"创建发货\", create_shipment)\n    builder.task(\"notify\", \"发送通知\", send_confirmation)\n    builder.task(\"out_of_stock\", \"缺货处理\", handle_out_of_stock)\n\n    # 构建边\n    builder.edge(\"route\", \"process_payment\", \"in_stock\")\n    builder.edge(\"route\", \"out_of_stock\", \"out_of_stock\")\n    builder.edge(\"process_payment\", \"reserve\")\n    builder.edge(\"process_payment\", \"ship\")       # 与 reserve 并行\n    builder.edge(\"reserve\", \"notify\")\n    builder.edge(\"ship\", \"notify\")\n    builder.edge(\"out_of_stock\", \"notify\")\n\n    return builder.build()",
      "section_ref": "19.3.1",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-5",
      "language": "python",
      "description": "",
      "code": "\"\"\"\n并行执行模式 - Fan-out / Fan-in\n\"\"\"\n\nclass ParallelExecutor:\n    \"\"\"并行执行器\"\"\"\n\n    @staticmethod\n    async def fan_out(tasks: list[Callable], context: WorkflowContext,\n                      strategy: BranchStrategy = BranchStrategy.ALL,\n                      max_parallel: int = 5) -> dict:\n        \"\"\"\n        Fan-out: 并行执行多个任务\n\n        Args:\n            tasks: 任务列表 [(task_id, callable), ...]\n            context: 共享上下文\n            strategy: 分支策略\n            max_parallel: 最大并行数\n        \"\"\"\n        semaphore = asyncio.Semaphore(max_parallel)\n        results = {}\n\n        async def run_task(task_id: str, task_fn: Callable):\n            async with semaphore:\n                try:\n                    if asyncio.iscoroutinefunction(task_fn):\n                        result = await task_fn(context)\n                    else:\n                        loop = asyncio.get_event_loop()\n                        result = await loop.run_in_executor(\n                            None, task_fn, context)\n                    results[task_id] = {\"status\": \"success\", \"output\": result}\n                except Exception as e:\n                    results[task_id] = {\"status\": \"failed\", \"error\": str(e)}\n\n        await asyncio.gather(*[\n            run_task(tid, fn) for tid, fn in tasks\n        ])\n\n        # 根据策略判断整体结果\n        success_count = sum(1 for r in results.values() if r[\"status\"] == \"success\")\n        total = len(results)\n\n        if strategy == BranchStrategy.ALL:\n            overall = success_count == total\n        elif strategy == BranchStrategy.ANY:\n            overall = success_count > 0\n        else:  # MAJORITY\n            overall = success_count > total // 2\n\n        return {\n            \"results\": results,\n            \"success_count\": success_count,\n            \"total\": total,\n            \"overall_success\": overall\n        }\n\n    @staticmethod\n    async def fan_in(context: WorkflowContext, task_ids: list[str],\n                     merge_fn: Optional[Callable] = None) -> Any:\n        \"\"\"\n        Fan-in: 收集并行任务结果并合并\n\n        Args:\n            context: 工作流上下文\n            task_ids: 要收集结果的节点 ID 列表\n            merge_fn: 自定义合并函数\n        \"\"\"\n        collected = {}\n        for tid in task_ids:\n            result = context.get_node_result(tid)\n            if result:\n                collected[tid] = result.output\n\n        if merge_fn:\n            return merge_fn(collected)\n\n        # 默认合并策略\n        if all(isinstance(v, dict) for v in collected.values()):\n            merged = {}\n            for v in collected.values():\n                merged.update(v)\n            return merged\n        return collected",
      "section_ref": "19.3.2",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-6",
      "language": "python",
      "description": "",
      "code": "\"\"\"\n人工审批系统 - 工作流中的暂停与恢复\n\"\"\"\n\nfrom datetime import datetime\n\n\n@dataclass\nclass ApprovalRequest:\n    \"\"\"审批请求\"\"\"\n    request_id: str\n    workflow_id: str\n    node_id: str\n    node_name: str\n    requester: str\n    approver: str\n    request_data: dict = field(default_factory=dict)\n    status: str = \"pending\"        # pending / approved / rejected\n    created_at: str = field(default_factory=lambda: datetime.now().isoformat())\n    resolved_at: Optional[str] = None\n    comment: str = \"\"\n\n\nclass ApprovalManager:\n    \"\"\"审批管理器\"\"\"\n\n    def __init__(self):\n        self.pending: dict[str, ApprovalRequest] = {}    # request_id -> request\n        self.resolved: list[ApprovalRequest] = []\n        self._callbacks: dict[str, Callable] = {}         # request_id -> callback\n\n    def request_approval(self, workflow_id: str, node_id: str,\n                          node_name: str, requester: str,\n                          approver: str, data: dict = None,\n                          callback: Optional[Callable] = None) -> str:\n        \"\"\"\n        发起审批请求\n\n        Args:\n            callback: 审批完成后的回调函数（用于恢复工作流）\n        \"\"\"\n        request_id = f\"apr_{uuid.uuid4().hex[:8]}\"\n        request = ApprovalRequest(\n            request_id=request_id,\n            workflow_id=workflow_id,\n            node_id=node_id,\n            node_name=node_name,\n            requester=requester,\n            approver=approver,\n            request_data=data or {}\n        )\n\n        self.pending[request_id] = request\n        if callback:\n            self._callbacks[request_id] = callback\n\n        logger.info(f\"审批请求已创建: {request_id} ({node_name}) → {approver}\")\n        return request_id\n\n    def approve(self, request_id: str, approver: str,\n                comment: str = \"\") -> bool:\n        \"\"\"批准审批\"\"\"\n        request = self.pending.get(request_id)\n        if not request:\n            return False\n\n        request.status = \"approved\"\n        request.approver = approver\n        request.comment = comment\n        request.resolved_at = datetime.now().isoformat()\n\n        self.resolved.append(request)\n        del self.pending[request_id]\n\n        # 触发回调恢复工作流\n        callback = self._callbacks.pop(request_id, None)\n        if callback:\n            try:\n                if asyncio.iscoroutinefunction(callback):\n                    asyncio.create_task(callback(True, comment))\n                else:\n                    callback(True, comment)\n            except Exception as e:\n                logger.error(f\"审批回调执行失败: {e}\")\n\n        return True\n\n    def reject(self, request_id: str, approver: str,\n               comment: str = \"\") -> bool:\n        \"\"\"拒绝审批\"\"\"\n        request = self.pending.get(request_id)\n        if not request:\n            return False\n\n        request.status = \"rejected\"\n        request.approver = approver\n        request.comment = comment\n        request.resolved_at = datetime.now().isoformat()\n\n        self.resolved.append(request)\n        del self.pending[request_id]\n\n        callback = self._callbacks.pop(request_id, None)\n        if callback:\n            try:\n                if asyncio.iscoroutinefunction(callback):\n                    asyncio.create_task(callback(False, comment))\n                else:\n                    callback(False, comment)\n            except Exception as e:\n                logger.error(f\"审批回调执行失败: {e}\")\n\n        return True\n\n    def get_pending_list(self, approver: Optional[str] = None) -> list[dict]:\n        \"\"\"获取待审批列表\"\"\"\n        requests = list(self.pending.values())\n        if approver:\n            requests = [r for r in requests if r.approver == approver]\n        return [\n            {\n                \"request_id\": r.request_id,\n                \"workflow_id\": r.workflow_id,\n                \"node_name\": r.node_name,\n                \"requester\": r.requester,\n                \"created_at\": r.created_at,\n                \"data_summary\": str(r.request_data)[:100]\n            }\n            for r in requests\n        ]\n\n    def get_stats(self) -> dict:\n        \"\"\"获取审批统计\"\"\"\n        total_approved = sum(1 for r in self.resolved if r.status == \"approved\")\n        total_rejected = sum(1 for r in self.resolved if r.status == \"rejected\")\n        return {\n            \"pending\": len(self.pending),\n            \"approved\": total_approved,\n            \"rejected\": total_rejected,\n            \"approval_rate\": (\n                total_approved / max(total_approved + total_rejected, 1) * 100\n            )\n        }",
      "section_ref": "19.4.1",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-7",
      "language": "python",
      "description": "",
      "code": "\"\"\"\n定时任务调度引擎\n支持 Cron 表达式、固定间隔和一次性延迟\n\"\"\"\n\nimport re\nfrom datetime import datetime, timedelta\n\n\n@dataclass\nclass ScheduledJob:\n    \"\"\"调度任务\"\"\"\n    job_id: str\n    name: str\n    handler: Callable\n    cron_expression: Optional[str] = None   # Cron 表达式\n    interval_seconds: int = 0               # 固定间隔（秒）\n    run_at: Optional[str] = None             # 一次性执行时间\n    max_retries: int = 3\n    timeout: int = 300\n    enabled: bool = True\n    last_run: Optional[str] = None\n    next_run: Optional[str] = None\n    run_count: int = 0\n    fail_count: int = 0\n\n\nclass CronParser:\n    \"\"\"Cron 表达式解析器（简化版）\n\n    支持格式: 分 时 日 月 星期\n    示例: \"0 9 * * 1-5\" (工作日每天9点)\n    \"\"\"\n\n    FIELD_NAMES = [\"minute\", \"hour\", \"day_of_month\", \"month\", \"day_of_week\"]\n\n    @classmethod\n    def parse(cls, expression: str) -> dict:\n        \"\"\"解析 Cron 表达式为字段配置\"\"\"\n        parts = expression.strip().split()\n        if len(parts) != 5:\n            raise ValueError(f\"Cron 表达式需要5个字段，得到 {len(parts)} 个\")\n\n        config = {}\n        for name, part in zip(cls.FIELD_NAMES, parts):\n            config[name] = cls._parse_field(part)\n        return config\n\n    @classmethod\n    def _parse_field(cls, field: str) -> list[int]:\n        \"\"\"解析单个 Cron 字段\"\"\"\n        values = set()\n        for part in field.split(\",\"):\n            if part == \"*\":\n                values.update(range(0, 60 if \"minute\" in field else 24))\n            elif \"/\" in part:\n                range_part, step = part.split(\"/\")\n                step = int(step)\n                if range_part == \"*\":\n                    values.update(range(0, 60, step))\n                elif \"-\" in range_part:\n                    start, end = map(int, range_part.split(\"-\"))\n                    values.update(range(start, end + 1, step))\n            elif \"-\" in part:\n                start, end = map(int, part.split(\"-\"))\n                values.update(range(start, end + 1))\n            else:\n                values.add(int(part))\n        return sorted(values)\n\n    @classmethod\n    def should_run(cls, expression: str) -> bool:\n        \"\"\"判断当前是否应该执行\"\"\"\n        try:\n            config = cls.parse(expression)\n        except ValueError:\n            return False\n\n        now = datetime.now()\n        checks = [\n            (now.minute, config[\"minute\"]),\n            (now.hour, config[\"hour\"]),\n            (now.day, config[\"day_of_month\"]),\n            (now.month, config[\"month\"]),\n            (now.weekday(), config[\"day_of_week\"]),\n        ]\n        return all(val in allowed for val, allowed in checks)\n\n    @classmethod\n    def next_run_time(cls, expression: str) -> str:\n        \"\"\"计算下次执行时间（简化实现）\"\"\"\n        now = datetime.now()\n        # 每分钟检查一次，找到最近的匹配时间\n        check = now.replace(second=0, microsecond=0) + timedelta(minutes=1)\n        for _ in range(525600):  # 最多查找一年\n            if cls._matches_time(check, expression):\n                return check.isoformat()\n            check += timedelta(minutes=1)\n        return now.isoformat()\n\n    @classmethod\n    def _matches_time(cls, dt: datetime, expression: str) -> bool:\n        \"\"\"检查时间是否匹配 Cron 表达式\"\"\"\n        try:\n            config = cls.parse(expression)\n        except ValueError:\n            return False\n        return (dt.minute in config[\"minute\"]\n                and dt.hour in config[\"hour\"]\n                and dt.day in config[\"day_of_month\"]\n                and dt.month in config[\"month\"]\n                and dt.weekday() in config[\"day_of_week\"])\n\n\nclass SchedulerEngine:\n    \"\"\"调度引擎\"\"\"\n\n    def __init__(self):\n        self.jobs: dict[str, ScheduledJob] = {}\n        self._running = False\n        self._task: Optional[asyncio.Task] = None\n\n    def add_job(self, job: ScheduledJob) -> 'SchedulerEngine':\n        \"\"\"添加调度任务\"\"\"\n        self.jobs[job.job_id] = job\n        # 计算下次执行时间\n        if job.cron_expression:\n            job.next_run = CronParser.next_run_time(job.cron_expression)\n        return self\n\n    def remove_job(self, job_id: str) -> bool:\n        \"\"\"移除调度任务\"\"\"\n        if job_id in self.jobs:\n            del self.jobs[job_id]\n            return True\n        return False\n\n    def enable_job(self, job_id: str, enabled: bool = True):\n        \"\"\"启用/禁用任务\"\"\"\n        if job_id in self.jobs:\n            self.jobs[job_id].enabled = enabled\n\n    async def start(self, check_interval: int = 60):\n        \"\"\"\n        启动调度器\n\n        Args:\n            check_interval: 检查间隔（秒）\n        \"\"\"\n        self._running = True\n        logger.info(f\"调度器已启动，检查间隔: {check_interval}s\")\n\n        while self._running:\n            now = datetime.now().isoformat()\n\n            for job in self.jobs.values():\n                if not job.enabled:\n                    continue\n                if job.next_run and now >= job.next_run:\n                    await self._execute_job(job)\n\n            await asyncio.sleep(check_interval)\n\n    async def stop(self):\n        \"\"\"停止调度器\"\"\"\n        self._running = False\n        if self._task:\n            self._task.cancel()\n\n    async def _execute_job(self, job: ScheduledJob):\n        \"\"\"执行调度任务\"\"\"\n        logger.info(f\"执行调度任务: {job.name} ({job.job_id})\")\n        job.last_run = datetime.now().isoformat()\n        job.run_count += 1\n\n        try:\n            if asyncio.iscoroutinefunction(job.handler):\n                await asyncio.wait_for(\n                    job.handler(), timeout=job.timeout or None)\n            else:\n                loop = asyncio.get_event_loop()\n                await loop.run_in_executor(None, job.handler)\n\n            # 更新下次执行时间\n            if job.cron_expression:\n                job.next_run = CronParser.next_run_time(job.cron_expression)\n\n        except asyncio.TimeoutError:\n            job.fail_count += 1\n            logger.error(f\"任务 {job.name} 超时\")\n        except Exception as e:\n            job.fail_count += 1\n            logger.error(f\"任务 {job.name} 执行失败: {e}\")\n\n    def get_status(self) -> dict:\n        \"\"\"获取调度器状态\"\"\"\n        return {\n            \"running\": self._running,\n            \"total_jobs\": len(self.jobs),\n            \"enabled_jobs\": sum(1 for j in self.jobs.values() if j.enabled),\n            \"jobs\": [\n                {\n                    \"id\": j.job_id, \"name\": j.name,\n                    \"enabled\": j.enabled,\n                    \"cron\": j.cron_expression,\n                    \"last_run\": j.last_run,\n                    \"next_run\": j.next_run,\n                    \"run_count\": j.run_count,\n                    \"fail_count\": j.fail_count\n                }\n                for j in self.jobs.values()\n            ]\n        }",
      "section_ref": "19.5.1",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-8",
      "language": "python",
      "description": "",
      "code": "\"\"\"\n事件总线 - 工作流的事件触发与响应机制\n\"\"\"\n\nfrom collections import defaultdict\nfrom dataclasses import dataclass, field\n\n\n@dataclass\nclass Event:\n    \"\"\"事件\"\"\"\n    event_id: str\n    event_type: str\n    source: str\n    payload: dict = field(default_factory=dict)\n    timestamp: str = field(default_factory=lambda: datetime.now().isoformat())\n    metadata: dict = field(default_factory=dict)\n\n\nclass EventBus:\n    \"\"\"事件总线 - 发布/订阅模式\"\"\"\n\n    def __init__(self):\n        self._subscribers: dict[str, list[Callable]] = defaultdict(list)\n        self._event_history: list[Event] = []\n        self._max_history: int = 1000\n\n    def subscribe(self, event_type: str, handler: Callable) -> 'EventBus':\n        \"\"\"订阅事件（支持通配符 *）\"\"\"\n        self._subscribers[event_type].append(handler)\n        return self\n\n    def unsubscribe(self, event_type: str, handler: Callable):\n        \"\"\"取消订阅\"\"\"\n        if event_type in self._subscribers:\n            self._subscribers[event_type] = [\n                h for h in self._subscribers[event_type] if h != handler\n            ]\n\n    async def publish(self, event_type: str, source: str,\n                      payload: dict = None, metadata: dict = None) -> list:\n        \"\"\"\n        发布事件\n\n        Returns:\n            所有处理器的返回值列表\n        \"\"\"\n        event = Event(\n            event_id=f\"evt_{uuid.uuid4().hex[:8]}\",\n            event_type=event_type,\n            source=source,\n            payload=payload or {},\n            metadata=metadata or {}\n        )\n\n        self._event_history.append(event)\n        if len(self._event_history) > self._max_history:\n            self._event_history = self._event_history[-self._max_history:]\n\n        results = []\n        handlers = (self._subscribers.get(event_type, []) +\n                    self._subscribers.get(\"*\", []))\n\n        for handler in handlers:\n            try:\n                if asyncio.iscoroutinefunction(handler):\n                    result = await handler(event)\n                else:\n                    result = handler(event)\n                results.append(result)\n            except Exception as e:\n                logger.error(f\"事件处理器异常 [{event_type}]: {e}\")\n                results.append({\"error\": str(e)})\n\n        return results\n\n    def get_history(self, event_type: Optional[str] = None,\n                    limit: int = 50) -> list[dict]:\n        \"\"\"获取事件历史\"\"\"\n        events = self._event_history\n        if event_type:\n            events = [e for e in events if e.event_type == event_type]\n        return [\n            {\n                \"event_id\": e.event_id,\n                \"event_type\": e.event_type,\n                \"source\": e.source,\n                \"timestamp\": e.timestamp,\n                \"payload_keys\": list(e.payload.keys())\n            }\n            for e in events[-limit:]\n        ]",
      "section_ref": "19.6.1",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-9",
      "language": "python",
      "description": "",
      "code": "\"\"\"\nWebhook 触发器 - 接收外部 HTTP 回调触发工作流\n\"\"\"\n\n@dataclass\nclass WebhookEndpoint:\n    \"\"\"Webhook 端点\"\"\"\n    endpoint_id: str\n    path: str\n    secret: Optional[str] = None\n    workflow_name: Optional[str] = None\n    event_type: str = \"webhook.triggered\"\n    enabled: bool = True\n    call_count: int = 0\n    last_called: Optional[str] = None\n\n\nclass WebhookManager:\n    \"\"\"Webhook 管理器\"\"\"\n\n    def __init__(self, event_bus: EventBus):\n        self.event_bus = event_bus\n        self.endpoints: dict[str, WebhookEndpoint] = {}\n\n    def register(self, path: str, secret: Optional[str] = None,\n                 workflow_name: Optional[str] = None) -> str:\n        \"\"\"注册 Webhook 端点\"\"\"\n        endpoint_id = f\"wh_{uuid.uuid4().hex[:8]}\"\n        self.endpoints[endpoint_id] = WebhookEndpoint(\n            endpoint_id=endpoint_id,\n            path=path,\n            secret=secret,\n            workflow_name=workflow_name\n        )\n        return endpoint_id\n\n    async def handle_request(self, path: str, payload: dict,\n                              signature: Optional[str] = None) -> dict:\n        \"\"\"\n        处理 Webhook 请求\n\n        在实际应用中，这通常由 HTTP 框架（FastAPI/Flask）的路由处理函数调用。\n        \"\"\"\n        # 查找匹配的端点\n        endpoint = None\n        for ep in self.endpoints.values():\n            if ep.path == path and ep.enabled:\n                endpoint = ep\n                break\n\n        if not endpoint:\n            return {\"success\": False, \"error\": \"未找到匹配的端点\"}\n\n        # 验证签名\n        if endpoint.secret and signature:\n            import hmac\n            import hashlib\n            expected = hmac.new(\n                endpoint.secret.encode(),\n                json.dumps(payload, sort_keys=True).encode(),\n                hashlib.sha256\n            ).hexdigest()\n            if not hmac.compare_digest(signature, expected):\n                return {\"success\": False, \"error\": \"签名验证失败\"}\n\n        # 更新统计\n        endpoint.call_count += 1\n        endpoint.last_called = datetime.now().isoformat()\n\n        # 发布事件\n        await self.event_bus.publish(\n            event_type=endpoint.event_type,\n            source=f\"webhook:{endpoint.endpoint_id}\",\n            payload=payload,\n            metadata={\"workflow_name\": endpoint.workflow_name, \"path\": path}\n        )\n\n        return {\"success\": True, \"endpoint_id\": endpoint.endpoint_id}\n\n    def list_endpoints(self) -> list[dict]:\n        \"\"\"列出所有端点\"\"\"\n        return [\n            {\n                \"id\": ep.endpoint_id,\n                \"path\": ep.path,\n                \"workflow\": ep.workflow_name,\n                \"enabled\": ep.enabled,\n                \"calls\": ep.call_count,\n                \"last_called\": ep.last_called\n            }\n            for ep in self.endpoints.values()\n        ]",
      "section_ref": "19.6.2",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-10",
      "language": "python",
      "description": "",
      "code": "\"\"\"\n自动化工作流 Agent - 完整集成\n\"\"\"\n\nclass WorkflowAgent:\n    \"\"\"\n    自动化工作流 Agent - 统一入口\n\n    使用示例:\n        agent = WorkflowAgent()\n        agent.define_workflow(\"daily_report\", builder)\n        await agent.start()\n    \"\"\"\n\n    def __init__(self):\n        self.engines: dict[str, WorkflowEngine] = {}\n        self.approval_mgr = ApprovalManager()\n        self.scheduler = SchedulerEngine()\n        self.event_bus = EventBus()\n        self.webhook_mgr = WebhookManager(self.event_bus)\n\n    def define_workflow(self, name: str, engine: WorkflowEngine):\n        \"\"\"注册工作流\"\"\"\n        self.engines[name] = engine\n\n    def define_workflow_from_builder(self, name: str,\n                                      builder: WorkflowBuilder):\n        \"\"\"通过 Builder 注册工作流\"\"\"\n        self.engines[name] = builder.build()\n\n    async def run_workflow(self, name: str,\n                            initial_data: Optional[dict] = None) -> WorkflowContext:\n        \"\"\"运行指定工作流\"\"\"\n        engine = self.engines.get(name)\n        if not engine:\n            raise ValueError(f\"工作流 '{name}' 不存在\")\n        return await engine.execute(initial_data=initial_data)\n\n    async def start(self):\n        \"\"\"启动调度器和事件监听\"\"\"\n        # 注册事件处理：Webhook 触发工作流\n        self.event_bus.subscribe(\"webhook.triggered\", self._on_webhook_trigger)\n\n        # 启动调度器\n        asyncio.create_task(self.scheduler.start(check_interval=30))\n\n    async def stop(self):\n        \"\"\"停止所有服务\"\"\"\n        await self.scheduler.stop()\n\n    async def _on_webhook_trigger(self, event: Event):\n        \"\"\"Webhook 事件处理\"\"\"\n        workflow_name = event.metadata.get(\"workflow_name\")\n        if workflow_name and workflow_name in self.engines:\n            await self.run_workflow(workflow_name, initial_data=event.payload)\n            logger.info(f\"Webhook 触发工作流: {workflow_name}\")\n\n    def get_dashboard(self) -> dict:\n        \"\"\"获取工作流仪表板数据\"\"\"\n        return {\n            \"workflows\": list(self.engines.keys()),\n            \"scheduler\": self.scheduler.get_status(),\n            \"approvals\": self.approval_mgr.get_stats(),\n            \"webhooks\": self.webhook_mgr.list_endpoints(),\n            \"recent_events\": self.event_bus.get_history(limit=10)\n        }",
      "section_ref": "19.7.1",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-11",
      "language": "python",
      "description": "",
      "code": "\"\"\"\n端到端使用示例 - 内容发布工作流\n\"\"\"\n\nimport asyncio\n\n\nasync def demo_workflow():\n    \"\"\"演示完整的工作流编排\"\"\"\n\n    # 定义任务函数\n    async def generate_content(ctx: WorkflowContext):\n        ctx.set(\"content\", \"AI生成的文章内容...\")\n        return {\"content_length\": 100}\n\n    async def review_content(ctx: WorkflowContext):\n        # 模拟 LLM 审核内容质量\n        content = ctx.get(\"content\", \"\")\n        score = min(100, 50 + len(content) // 2)\n        ctx.set(\"quality_score\", score)\n        return {\"score\": score}\n\n    async def optimize_seo(ctx: WorkflowContext):\n        return {\"keywords\": [\"AI\", \"自动化\"], \"optimized\": True}\n\n    async def check_compliance(ctx: WorkflowContext):\n        return {\"compliant\": True, \"issues\": []}\n\n    def needs_revision(ctx: WorkflowContext) -> str:\n        return \"revise\" if ctx.get(\"quality_score\", 0) < 70 else \"publish\"\n\n    async def revise_content(ctx: WorkflowContext):\n        ctx.set(\"content\", ctx.get(\"content\", \"\") + \"（已修订）\")\n        return {\"revised\": True}\n\n    async def publish_article(ctx: WorkflowContext):\n        return {\"published\": True, \"url\": \"/articles/123\"}\n\n    async def notify_author(ctx: WorkflowContext):\n        return {\"notification_sent\": True}\n\n    # 构建工作流\n    builder = WorkflowBuilder(name=\"内容发布流程\")\n    builder.task(\"generate\", \"生成内容\", generate_content) \\\n           .then(\"review\", condition=None)\n    builder.task(\"review\", \"质量审核\", review_content) \\\n           .then(\"quality_gate\", condition=None)\n    builder.condition(\"quality_gate\", \"质量门槛\", needs_revision)\n    builder.task(\"revise\", \"修订内容\", revise_content) \\\n           .then(\"review\")  # 修订后重新审核\n    builder.task(\"seo\", \"SEO优化\", optimize_seo)\n    builder.task(\"compliance\", \"合规检查\", check_compliance)\n    builder.task(\"publish\", \"发布文章\", publish_article)\n    builder.task(\"notify\", \"通知作者\", notify_author)\n\n    # 路由\n    builder.edge(\"quality_gate\", \"seo\", \"publish\")\n    builder.edge(\"quality_gate\", \"revise\", \"revise\")\n    builder.edge(\"seo\", \"compliance\")\n    builder.edge(\"compliance\", \"publish\")\n    builder.edge(\"publish\", \"notify\")\n\n    engine = builder.build()\n\n    # 创建 Agent\n    agent = WorkflowAgent()\n    agent.define_workflow(\"content_publish\", engine)\n\n    # 添加事件监听\n    agent.event_bus.subscribe(\"*\", lambda e: print(\n        f\"  [事件] {e.event_type} from {e.source}\"))\n\n    # 运行工作流\n    print(\"=== 运行内容发布工作流 ===\")\n    ctx = await agent.run_workflow(\"content_publish\", {\n        \"topic\": \"AI工作流\",\n        \"author\": \"张三\"\n    })\n\n    print(f\"\\n执行结果:\")\n    for nid, result in ctx.node_results.items():\n        status_icon = {\"SUCCESS\": \"✅\", \"FAILED\": \"❌\", \"SKIPPED\": \"⏭️\"}.get(\n            result.status.value, \"❓\")\n        print(f\"  {status_icon} {nid}: {result.status.value} \"\n              f\"({result.duration_ms}ms)\")\n\n    # 添加调度任务\n    print(\"\\n=== 添加调度任务 ===\")\n    async def daily_report():\n        print(\"  [调度] 执行每日报告生成...\")\n        return {\"report\": \"generated\"}\n\n    agent.scheduler.add_job(ScheduledJob(\n        job_id=\"daily_report\",\n        name=\"每日报告生成\",\n        handler=daily_report,\n        cron_expression=\"0 9 * * 1-5\",  # 工作日每天9点\n        timeout=300\n    ))\n\n    # 添加审批\n    print(\"\\n=== 审批系统 ===\")\n    approval_id = agent.approval_mgr.request_approval(\n        workflow_id=\"wf_001\", node_id=\"publish\",\n        node_name=\"发布文章\", requester=\"system\",\n        approver=\"editor_zhang\", data={\"article_id\": \"123\"}\n    )\n    print(f\"  待审批: {approval_id}\")\n    print(f\"  审批统计: {agent.approval_mgr.get_stats()}\")\n\n    # 注册 Webhook\n    print(\"\\n=== Webhook 管理 ===\")\n    wh_id = agent.webhook_mgr.register(\n        path=\"/api/webhook/cms\",\n        secret=\"my_secret_key\",\n        workflow_name=\"content_publish\"\n    )\n    print(f\"  注册 Webhook: {wh_id}\")\n    print(f\"  端点列表: {agent.webhook_mgr.list_endpoints()}\")\n\n    # 仪表板\n    print(\"\\n=== 工作流仪表板 ===\")\n    dashboard = agent.get_dashboard()\n    print(f\"  注册工作流: {dashboard['workflows']}\")\n    print(f\"  调度任务: {dashboard['scheduler']['total_jobs']}\")\n    print(f\"  待审批: {dashboard['approvals']['pending']}\")\n\n\nif __name__ == \"__main__\":\n    asyncio.run(demo_workflow())",
      "section_ref": "19.7.2",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-12",
      "language": "python",
      "description": "| 数据库 | PostgreSQL + JSONB | 生产环境 |",
      "code": "\"\"\"\n工作流状态持久化（SQLite 示例）\n\"\"\"\n\nimport sqlite3\n\n\nclass WorkflowPersistence:\n    \"\"\"工作流持久化层\"\"\"\n\n    def __init__(self, db_path: str = \"workflows.db\"):\n        self.db_path = db_path\n        self._init_db()\n\n    def _init_db(self):\n        with sqlite3.connect(self.db_path) as conn:\n            conn.execute(\"\"\"\n                CREATE TABLE IF NOT EXISTS workflow_instances (\n                    workflow_id TEXT PRIMARY KEY,\n                    name TEXT NOT NULL,\n                    status TEXT DEFAULT 'running',\n                    context_json TEXT,\n                    created_at TEXT,\n                    updated_at TEXT\n                )\n            \"\"\")\n            conn.execute(\"\"\"\n                CREATE TABLE IF NOT EXISTS node_executions (\n                    id INTEGER PRIMARY KEY AUTOINCREMENT,\n                    workflow_id TEXT,\n                    node_id TEXT,\n                    status TEXT,\n                    output_json TEXT,\n                    error TEXT,\n                    duration_ms INTEGER,\n                    started_at TEXT,\n                    FOREIGN KEY (workflow_id) REFERENCES workflow_instances(workflow_id)\n                )\n            \"\"\")\n\n    def save_workflow(self, workflow_id: str, name: str,\n                      context: WorkflowContext):\n        \"\"\"保存工作流状态\"\"\"\n        with sqlite3.connect(self.db_path) as conn:\n            conn.execute(\"\"\"\n                INSERT OR REPLACE INTO workflow_instances\n                (workflow_id, name, status, context_json, created_at, updated_at)\n                VALUES (?, ?, 'running', ?, ?, ?)\n            \"\"\", (\n                workflow_id, name,\n                json.dumps(context.to_dict(), ensure_ascii=False),\n                datetime.now().isoformat(),\n                datetime.now().isoformat()\n            ))\n            for nid, result in context.node_results.items():\n                conn.execute(\"\"\"\n                    INSERT INTO node_executions\n                    (workflow_id, node_id, status, output_json, error, duration_ms, started_at)\n                    VALUES (?, ?, ?, ?, ?, ?, ?)\n                \"\"\", (\n                    workflow_id, nid, result.status.value,\n                    json.dumps(str(result.output)[:500]) if result.output else None,\n                    result.error,\n                    result.duration_ms,\n                    datetime.fromtimestamp(result.start_time).isoformat()\n                ))\n\n    def load_workflow(self, workflow_id: str) -> Optional[dict]:\n        \"\"\"加载工作流状态\"\"\"\n        with sqlite3.connect(self.db_path) as conn:\n            row = conn.execute(\n                \"SELECT * FROM workflow_instances WHERE workflow_id = ?\",\n                (workflow_id,)\n            ).fetchone()\n            if not row:\n                return None\n            return {\"workflow_id\": row[0], \"name\": row[1],\n                    \"status\": row[2], \"context\": json.loads(row[3])}",
      "section_ref": "19.8.1",
      "runnable": true,
      "dependencies": [
        "sqlite3"
      ]
    },
    {
      "id": "code-13",
      "language": "python",
      "description": "",
      "code": "\"\"\"\n工作流可观测性 - 指标收集与追踪\n\"\"\"\n\nclass WorkflowMetrics:\n    \"\"\"工作流指标收集器\"\"\"\n\n    def __init__(self):\n        self._counters: dict[str, int] = defaultdict(int)\n        self._histograms: dict[str, list[float]] = defaultdict(list)\n        self._timers: dict[str, float] = {}\n\n    def increment(self, metric: str, value: int = 1):\n        self._counters[metric] += value\n\n    def record_duration(self, metric: str, duration_ms: float):\n        self._histograms[metric].append(duration_ms)\n\n    def start_timer(self, name: str):\n        self._timers[name] = time.time()\n\n    def stop_timer(self, name: str) -> float:\n        if name in self._timers:\n            duration = (time.time() - self._timers[name]) * 1000\n            self.record_duration(name, duration)\n            del self._timers[name]\n            return duration\n        return 0.0\n\n    def get_summary(self) -> dict:\n        \"\"\"获取指标摘要\"\"\"\n        summary = {\"counters\": dict(self._counters)}\n        for metric, values in self._histograms.items():\n            if values:\n                sorted_vals = sorted(values)\n                summary[metric] = {\n                    \"count\": len(values),\n                    \"avg\": sum(values) / len(values),\n                    \"p50\": sorted_vals[len(sorted_vals) // 2],\n                    \"p95\": sorted_vals[int(len(sorted_vals) * 0.95)],\n                    \"p99\": sorted_vals[int(len(sorted_vals) * 0.99)],\n                    \"max\": max(values),\n                    \"min\": min(values)\n                }\n        return summary",
      "section_ref": "19.8.2",
      "runnable": true,
      "dependencies": []
    }
  ],
  "tables": [
    {
      "headers": [
        "维度",
        "脚本",
        "工作流",
        "Agent"
      ],
      "data": [
        [
          "执行路径",
          "固定",
          "条件分支",
          "动态决策"
        ],
        [
          "人工介入",
          "无",
          "审批节点",
          "可协商"
        ],
        [
          "错误处理",
          "try/catch",
          "重试/回滚",
          "自我修复"
        ],
        [
          "并行能力",
          "多线程",
          "DAG 并行",
          "多 Agent 协作"
        ],
        [
          "状态管理",
          "无/简单",
          "持久化状态",
          "记忆系统"
        ],
        [
          "适应性",
          "零",
          "低",
          "高"
        ]
      ]
    },
    {
      "headers": [
        "状态存储",
        "方案",
        "适用场景"
      ],
      "data": [
        [
          "内存",
          "Python dict",
          "开发/测试"
        ],
        [
          "文件",
          "JSON/SQLite",
          "单机部署"
        ],
        [
          "Redis",
          "Hash + TTL",
          "中等规模"
        ],
        [
          "数据库",
          "PostgreSQL + JSONB",
          "生产环境"
        ]
      ]
    }
  ],
  "key_takeaways": [],
  "common_pitfalls": [],
  "related_chapters": [
    "ch06",
    "ch09"
  ]
}