# 📋 Workflow 编写教程 本教程将指导你如何在 DataFlow-Agent 项目中创建和编写 Workflow(工作流)。 ## 目录 1. [准备工作](#1-准备工作) 2. [使用 CLI 快速创建](#2-使用-cli-快速创建) 3. [Workflow 基础结构](#3-workflow-基础结构) 4. [State 和 Request 定义](#4-state-和-request-定义) 5. [Agent 创建方式](#5-agent-创建方式) 6. [工具系统](#6-工具系统) 7. [节点编写](#7-节点编写) 8. [图构建](#8-图构建) 9. [完整示例](#9-完整示例) 10. [调试和测试](#10-调试和测试) 11. [最佳实践](#11-最佳实践) --- ## 1. 准备工作 ### 1.1 了解项目结构 ``` dataflow_agent/ ├── workflow/ # Workflow 定义目录 │ ├── wf_*.py # Workflow 文件(必须以 wf_ 开头) │ └── registry.py # Workflow 注册中心 ├── agentroles/ # Agent 角色定义 ├── state.py # State 和 Request 定义 ├── graphbuilder/ # 图构建器 ├── templates/ # 代码生成模板 └── cli.py # CLI 命令工具 ``` ### 1.2 核心概念 - **Workflow**: 由多个节点(Node)和边(Edge)组成的有向图 - **State**: 在 Workflow 中流转的状态对象,包含所有上下文信息 - **Request**: State 中的请求对象,包含用户输入和配置 - **Node**: Workflow 中的处理单元,通常调用 Agent 执行任务 - **Agent**: 具有特定角色的 LLM 调用封装,负责完成具体任务 - **Tool**: Agent 可以使用的工具函数(前置工具和后置工具) ### 1.3 命名规范 - **Workflow 文件**: 必须以 `wf_` 开头,如 `wf_my_workflow.py` - **注册名称**: 去掉 `wf_` 前缀,如 `wf_pipeline_write.py` 注册为 `"pipeline_write"` - **State 类**: 以 `State` 结尾,如 `MyWorkflowState` - **Request 类**: 以 `Request` 结尾,如 `MyWorkflowRequest` --- ## 2. 使用 CLI 快速创建 DataFlow-Agent 提供了 `dfa create` 命令来快速生成各种组件。 ### 2.1 创建 Workflow ```bash # 创建一个新的 workflow dfa create --wf_name my_workflow # 这将生成: # - dataflow_agent/workflow/wf_my_workflow.py (workflow 源码) # - tests/test_my_workflow.py (测试文件) ``` ### 2.2 创建 Agent ```bash # 创建一个新的 agent dfa create --agent_name my_agent # 这将生成: # - dataflow_agent/agentroles/common_agents/my_agent_agent.py # # 说明: # - Agent 文件会统一放在 common_agents/ 子目录下 # - 文件名会自动追加 `_agent` 后缀 # - 源码来自 dataflow_agent/templates/agent.py.jinja 渲染生成 ``` ### 2.3 创建 State ```bash # 创建自定义 State 和 Request dfa create --state_name my_workflow # 这将生成: # - dataflow_agent/states/my_workflow_state.py # # 若 dataflow_agent/states/ 目录不存在,会自动创建。 # 文件内容基于 dataflow_agent/templates/state_name.py.jinja 渲染生成。 ``` ### 2.4 创建 Prompt Template ```bash # 创建 prompt 模板 dfa create --prompt_name my_agent # 这将生成: # - dataflow_agent/promptstemplates/resources/pt_my_agent_repo.py # # 该文件由 dataflow_agent/templates/prompt_repo.py.jinja 渲染生成, # 用于集中管理与该 Agent 相关的一组 jinja prompt 模板。 ``` ### 2.5 其他创建选项 ```bash # 创建 Gradio 页面 dfa create --gradio_name my_page # 这将生成: # - gradio_app/pages/page_my_page.py # # 生成内容基于 dataflow_agent/templates/gradio_page.py.jinja, # 默认使用 `page_.py` 命名规范,便于自动发现。 # 创建 Agent-as-Tool dfa create --agent_as_tool_name my_tool_agent # 这将生成: # - dataflow_agent/agentroles/common_agents/my_tool_agent_agent.py # # 生成内容基于 dataflow_agent/templates/agent_as_tool_name.py.jinja。 ``` --- ## 3. Workflow 基础结构 ### 3.1 基本模板 ```python """ my_workflow workflow ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 功能描述:简要说明工作流功能 """ from __future__ import annotations from dataflow_agent.state import MainState # 或你的自定义 State from dataflow_agent.graphbuilder.graph_builder import GenericGraphBuilder from dataflow_agent.workflow.registry import register from dataflow_agent.logger import get_logger log = get_logger(__name__) @register("my_workflow") # 注册名称(不含 wf_ 前缀) def create_my_workflow_graph() -> GenericGraphBuilder: """ Workflow 工厂函数 通过 dfa run --wf my_workflow 调用 """ builder = GenericGraphBuilder( state_model=MainState, # State 类型 entry_point="step1" # 入口节点名称 ) # ========== 工具定义 ========== # 在这里定义前置工具和后置工具 # ========== 节点定义 ========== # 在这里定义节点函数 # ========== 图构建 ========== # 在这里添加节点和边 return builder ``` ### 3.2 注册机制 使用 `@register` 装饰器将 Workflow 注册到全局注册中心: ```python from dataflow_agent.workflow.registry import register @register("my_workflow") # 注册名称 def create_my_workflow_graph() -> GenericGraphBuilder: # ... pass ``` 注册后可以通过以下方式调用: ```python from dataflow_agent.workflow import run_workflow # 运行 workflow result = await run_workflow("my_workflow", state) ``` --- ## 4. State 和 Request 定义 ### 4.1 继承体系 所有 State 和 Request 都应该继承自基类: ```python from dataclasses import dataclass, field from dataflow_agent.state import MainState, MainRequest # ==================== Request ==================== @dataclass class MyWorkflowRequest(MainRequest): """自定义请求参数""" # 继承 MainRequest 的所有字段: # - language: str = "en" # - chat_api_url: str # - api_key: str # - model: str = "gpt-4o" # - target: str = "" # 添加自定义字段 input_data: str = "" config_param: str = "default" max_retries: int = 3 # ==================== State ==================== @dataclass class MyWorkflowState(MainState): """自定义状态""" # 继承 MainState 的所有字段: # - request: MainRequest # - messages: List[BaseMessage] # - agent_results: Dict[str, Any] # - temp_data: Dict[str, Any] # 重写 request 类型 request: MyWorkflowRequest = field(default_factory=MyWorkflowRequest) # 添加自定义字段 processing_result: dict = field(default_factory=dict) current_step: str = "start" intermediate_data: list = field(default_factory=list) ``` ### 4.2 使用 CLI 创建 ```bash # 快速创建 State 和 Request dfa create --state_name my_workflow # 生成的文件会包含基本结构,你只需添加自定义字段 ``` ### 4.3 State 字段说明 **MainRequest 核心字段**: - `language`: 用户偏好语言("en" | "zh") - `chat_api_url`: LLM API 端点 - `api_key`: API 密钥 - `model`: 模型名称(如 "gpt-4o") - `target`: 任务描述 **MainState 核心字段**: - `request`: 请求对象 - `messages`: 消息历史(LangChain 格式) - `agent_results`: Agent 执行结果字典 - `temp_data`: 临时数据存储 --- ## 5. Agent 创建方式 ### 5.1 新的策略模式 项目引入了策略模式,提供多种便捷的 Agent 创建函数: ```python from dataflow_agent.agentroles import ( create_simple_agent, # 简单模式 create_react_agent, # ReAct 模式(带验证) create_graph_agent, # 图模式(带工具调用) create_vlm_agent, # 视觉语言模型模式 ) ``` ### 5.2 简单模式 Agent 适用于单次 LLM 调用的场景: ```python async def my_node(state: MyWorkflowState) -> MyWorkflowState: """使用简单模式创建 Agent""" agent = create_simple_agent( name="my_agent", # Agent 名称(必须已注册) model_name="gpt-4o", # 模型名称 temperature=0.7, # 采样温度 (0.0-1.0) max_tokens=4096, # 最大 token 数 parser_type="json", # 解析器类型: "json" | "xml" | "text" ) # 执行 Agent state = await agent.execute(state) # 获取结果 result = state.agent_results.get("my_agent", {}).get("results", {}) return state ``` ### 5.3 ReAct 模式 Agent 适用于需要验证和重试的场景: ```python async def my_node(state: MyWorkflowState) -> MyWorkflowState: """使用 ReAct 模式创建 Agent""" agent = create_react_agent( name="my_agent", model_name="gpt-4o", temperature=0.1, max_retries=3, # 最大重试次数 parser_type="json", # validators=[...], # 可选:自定义验证器 ) state = await agent.execute(state) return state ``` ### 5.4 图模式 Agent 适用于需要调用工具的场景: ```python async def my_node(state: MyWorkflowState) -> MyWorkflowState: """使用图模式创建 Agent(支持工具调用)""" from dataflow_agent.toolkits.tool_manager import get_tool_manager agent = create_graph_agent( name="my_agent", model_name="gpt-4o", temperature=0.2, tool_mode="auto", # 工具调用模式: "auto" | "none" | "required" ) state = await agent.execute(state) return state ``` ### 5.5 VLM 模式 Agent 适用于处理图像的场景: ```python async def my_node(state: MyWorkflowState) -> MyWorkflowState: """使用 VLM 模式创建 Agent""" agent = create_vlm_agent( name="my_agent", model_name="gpt-4-vision-preview", temperature=0.1, vlm_mode="understanding", # 'understanding' | 'generation' | 'edit' image_detail="high", # 'low' | 'high' | 'auto' max_image_size=(2048, 2048), ) state = await agent.execute(state) return state ``` ### 5.6 并行模式 Agent 适用于需要批量处理多条数据的场景: ```python async def my_node(state: MyWorkflowState) -> MyWorkflowState: """使用并行模式创建 Agent""" agent = create_parallel_agent( name="my_agent", model_name="gpt-4o", temperature=0.3, concurrency_limit=3, # 并行度限制,默认5 parser_type="json", ) state = await agent.execute(state) return state ``` #### 并行模式的数据准备 并行模式会自动检测前置工具结果中的列表数据,支持三种方式: **方式 1: 前置工具直接返回列表** ```python @builder.pre_tool("items", "my_agent") def get_items(state: MyWorkflowState): """返回需要并行处理的数据列表""" return [ {"text": "第一条数据", "id": 1}, {"text": "第二条数据", "id": 2}, {"text": "第三条数据", "id": 3}, ] ``` **方式 2: 使用 parallel_items 字段** ```python @builder.pre_tool("data", "my_agent") def get_data(state: MyWorkflowState): """返回包含 parallel_items 的字典""" return { "parallel_items": [ {"text": "数据1"}, {"text": "数据2"}, ], "context": "共享上下文信息" # 会被所有并行任务共享 } ``` **方式 3: 任意列表字段(自动检测)** ```python @builder.pre_tool("batch_data", "my_agent") def get_batch_data(state: MyWorkflowState): """返回包含列表字段的字典""" return { "items": [ # 会被自动检测为并行数据 {"name": "item1"}, {"name": "item2"}, ], "config": {"mode": "fast"} # 会被所有并行任务共享 } ``` #### 并行结果处理 并行模式执行后,结果会包含在 `parallel_results` 字段中: ```python async def process_parallel_results(state: MyWorkflowState) -> MyWorkflowState: """处理并行执行结果""" result = state.agent_results.get("my_agent", {}).get("results", {}) # 获取所有并行结果 parallel_results = result.get("parallel_results", []) total_processed = result.get("total_processed", 0) log.info(f"共处理 {total_processed} 条数据") # 聚合结果 aggregated = { "success_count": sum(1 for r in parallel_results if not r.get("error")), "error_count": sum(1 for r in parallel_results if r.get("error")), "results": parallel_results } state.temp_data["aggregated_results"] = aggregated return state ``` ### 5.6 使用配置对象 也可以使用配置对象创建 Agent: ```python from dataflow_agent.agentroles import create_agent from dataflow_agent.agentroles.cores.configs import SimpleConfig, ReactConfig # 方式 1: 使用 SimpleConfig config = SimpleConfig( model_name="gpt-4o", temperature=0.7, max_tokens=4096, parser_type="json", ) agent = create_agent(name="my_agent", config=config) # 方式 2: 使用 ReactConfig config = ReactConfig( model_name="gpt-4o", temperature=0.1, max_retries=3, parser_type="json", ) agent = create_agent(name="my_agent", config=config) ``` --- ## 6. 工具系统 > ⚠️ **重要提示**:`@builder.pre_tool()` 和 `@builder.post_tool()` 装饰器的第二个参数(role)应该填写 **Agent 的 role_name**,而不是 workflow 中的节点名称!这是因为 Agent 在执行时会通过 `self.role_name` 从 ToolManager 中获取对应的工具。 ### 6.1 前置工具(Pre-Tool) 前置工具在 Agent 执行前运行,用于收集上下文信息并注入到 prompt 中。 #### 定义前置工具 ```python @builder.pre_tool("placeholder_name", "agent_role_name") # ← 第二个参数是 Agent 的 role_name def my_pre_tool(state: MyWorkflowState): """ 前置工具函数 Args: state: 当前状态对象 Returns: 任何可序列化的数据(字符串、列表、字典等) """ return state.request.input_data ``` #### 参数说明 - **第一个参数**: Prompt 模板中的占位符名称 - **第二个参数**: **Agent 的 role_name**(不是节点名称!) #### 正确示例 ✅ ```python # 假设你的 Agent 的 role_name 是 "processor" async def process_node(state: MyWorkflowState) -> MyWorkflowState: agent = create_simple_agent( name="processor", # ← 这是 Agent 的 role_name model_name="gpt-4o", ) state = await agent.execute(state) return state # 前置工具应该绑定到 Agent 的 role_name @builder.pre_tool("user_input", "processor") # ✅ 使用 Agent 的 role_name def get_user_input(state: MyWorkflowState): return state.request.target @builder.pre_tool("context", "processor") # ✅ 使用 Agent 的 role_name def get_context(state: MyWorkflowState): return { "history": state.messages, "previous_results": state.agent_results } ``` #### 错误示例 ❌ ```python # ❌ 错误:绑定到节点名称而不是 Agent 的 role_name @builder.pre_tool("user_input", "process_node") # ❌ 这是节点名称,不是 role_name def get_user_input(state: MyWorkflowState): return state.request.target # 这样会导致工具无法被 Agent 获取到! ``` #### 工作原理说明 ```python # 1. 在 workflow 中定义工具时,绑定到 Agent 的 role_name @builder.pre_tool("input", "my_agent") # "my_agent" 是 Agent 的 role_name # 2. GenericGraphBuilder 将工具注册到 ToolManager # ToolManager.register_pre_tool(name="input", role="my_agent", func=...) # 3. Agent 执行时,通过自己的 role_name 获取工具 class MyAgent(BaseAgent): @property def role_name(self): return "my_agent" # ← 必须匹配 async def execute_pre_tools(self, state): # 使用 self.role_name 获取工具 results = await self.tool_manager.execute_pre_tools(self.role_name) # 这里会获取到所有绑定到 "my_agent" 的前置工具 ``` 在 Prompt 模板中使用(通常存放在 `dataflow_agent/promptstemplates/...` 目录下,由 `pt_xxx_repo.py` 等 prompt repo 统一加载和渲染): ```jinja2 # task_prompt_for_my_agent.jinja 用户输入: {{ user_input }} 上下文信息: {{ context }} 请根据以上信息完成任务。 ``` ### 6.2 后置工具(Post-Tool) 后置工具是 Agent 在执行过程中可以调用的工具函数。 > ⚠️ **同样重要**:`@builder.post_tool()` 的参数也应该是 **Agent 的 role_name**,而不是节点名称! #### 定义后置工具 ```python from pydantic import BaseModel, Field from langchain.tools import tool class MyToolInput(BaseModel): """工具输入参数定义""" param1: str = Field(description="参数1的描述") param2: int = Field(default=10, description="参数2的描述") @builder.post_tool("agent_role_name") # ← 使用 Agent 的 role_name @tool(args_schema=MyToolInput) def my_post_tool(param1: str, param2: int = 10): """ 后置工具函数 这个工具的描述会被 LLM 看到,用于决定是否调用此工具。 Args: param1: 参数1 param2: 参数2 Returns: 工具执行结果 """ # 工具逻辑 result = f"处理 {param1},参数2={param2}" return result ``` #### 示例:文件读取工具 ```python from pydantic import BaseModel, Field from langchain.tools import tool from pathlib import Path class ReadFileInput(BaseModel): file_path: str = Field(description="要读取的文件路径") @builder.post_tool("file_processor") @tool(args_schema=ReadFileInput) def read_file_tool(file_path: str): """读取指定文件的内容""" try: content = Path(file_path).read_text(encoding="utf-8") return {"success": True, "content": content} except Exception as e: return {"success": False, "error": str(e)} ``` ### 6.3 工具使用流程 ``` 1. 定义前置工具 (@builder.pre_tool) ↓ 2. 前置工具执行,收集上下文 ↓ 3. 上下文注入到 Prompt ↓ 4. Agent 调用 LLM ↓ 5. LLM 决定是否调用后置工具 ↓ 6. 执行后置工具(如果需要) ↓ 7. 返回最终结果 ``` --- ## 7. 节点编写 ### 7.1 节点函数签名 节点函数必须是异步函数,接收 State 并返回 State: ```python async def my_node(state: MyWorkflowState) -> MyWorkflowState: """ 节点函数 Args: state: 当前状态对象 Returns: 更新后的状态对象 """ # 节点逻辑 return state ``` ### 7.2 调用 Agent 的节点 ```python async def process_node(state: MyWorkflowState) -> MyWorkflowState: """调用 Agent 处理数据""" from dataflow_agent.agentroles import create_simple_agent # 创建 Agent agent = create_simple_agent( name="processor", model_name="gpt-4o", temperature=0.5, ) # 执行 Agent state = await agent.execute(state) # 获取结果 result = state.agent_results.get("processor", {}).get("results", {}) log.info(f"处理结果: {result}") return state ``` ### 7.3 数据处理节点 ```python async def transform_node(state: MyWorkflowState) -> MyWorkflowState: """数据转换节点""" # 从前一个节点获取结果 previous_result = state.agent_results.get("processor", {}).get("results", {}) # 进行数据转换 transformed = { "data": previous_result.get("raw_data", []), "metadata": { "timestamp": datetime.now().isoformat(), "source": "processor" } } # 存储到 temp_data state.temp_data["transformed_data"] = transformed return state ``` ### 7.4 条件判断节点 ```python def decision_node(state: MyWorkflowState) -> str: """ 条件判断节点(返回下一个节点名称) 注意:条件节点通常是同步函数,返回字符串 """ result = state.agent_results.get("processor", {}).get("results", {}) if result.get("success"): return "success_node" else: return "error_node" ``` ### 7.5 终止节点 ```python async def end_node(state: MyWorkflowState) -> MyWorkflowState: """终止节点""" log.info("Workflow 执行完成") return state # 或者使用 lambda nodes = { "_end_": lambda state: state, } ``` --- ## 8. 图构建 ### 8.1 添加节点 ```python # 方式 1: 使用字典批量添加 nodes = { "step1": step1_node, "step2": step2_node, "step3": step3_node, "_end_": lambda state: state, } builder.add_nodes(nodes) # 方式 2: 单个添加 builder.add_node("step1", step1_node) builder.add_node("step2", step2_node) ``` ### 8.2 添加边(普通边) ```python # 方式 1: 使用列表批量添加 edges = [ ("step1", "step2"), ("step2", "step3"), ("step3", "_end_"), ] builder.add_edges(edges) # 方式 2: 单个添加 builder.add_edge("step1", "step2") builder.add_edge("step2", "step3") ``` ### 8.3 添加条件边 条件边根据函数返回值决定下一个节点: ```python def condition_func(state: MyWorkflowState) -> str: """ 条件函数 Returns: 下一个节点的名称 """ if state.temp_data.get("success"): return "success_node" elif state.temp_data.get("retry_count", 0) < 3: return "retry_node" else: return "_end_" # 添加条件边 builder.add_conditional_edges({ "decision_node": condition_func }) ``` ### 8.4 链式调用 GenericGraphBuilder 支持链式调用: ```python builder = (GenericGraphBuilder(state_model=MyWorkflowState, entry_point="start") .add_nodes(nodes) .add_edges(edges) .add_conditional_edges(conditional_edges) ) ``` ### 8.5 完整图构建示例 ```python @register("my_workflow") def create_my_workflow_graph() -> GenericGraphBuilder: builder = GenericGraphBuilder( state_model=MyWorkflowState, entry_point="start" ) # 定义节点 nodes = { "start": start_node, "process": process_node, "decision": decision_node, "success": success_node, "retry": retry_node, "_end_": lambda state: state, } # 定义普通边 edges = [ ("start", "process"), ("process", "decision"), ("success", "_end_"), ("retry", "process"), ] # 定义条件边 def decision_condition(state: MyWorkflowState) -> str: if state.temp_data.get("success"): return "success" elif state.temp_data.get("retry_count", 0) < 3: return "retry" else: return "_end_" conditional_edges = { "decision": decision_condition } # 构建图 return (builder .add_nodes(nodes) .add_edges(edges) .add_conditional_edges(conditional_edges) ) ``` --- ## 9. 完整示例 ### 9.1 场景:文本分析 Workflow 创建一个分析文本情感和关键词的 Workflow。 #### Step 1: 创建 State 和 Request ```python # dataflow_agent/states/text_analysis_state.py from dataclasses import dataclass, field from dataflow_agent.state import MainState, MainRequest @dataclass class TextAnalysisRequest(MainRequest): """文本分析请求""" text: str = "" analysis_type: str = "sentiment" # "sentiment" | "keywords" | "both" @dataclass class TextAnalysisState(MainState): """文本分析状态""" request: TextAnalysisRequest = field(default_factory=TextAnalysisRequest) # 分析结果 sentiment_result: dict = field(default_factory=dict) keywords_result: dict = field(default_factory=dict) final_report: str = "" ``` #### Step 2: 创建 Workflow ```python # dataflow_agent/workflow/wf_text_analysis.py """ text_analysis workflow ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 功能:分析文本的情感和关键词 """ from __future__ import annotations from dataflow_agent.states.text_analysis_state import TextAnalysisState from dataflow_agent.graphbuilder.graph_builder import GenericGraphBuilder from dataflow_agent.workflow.registry import register from dataflow_agent.agentroles import create_simple_agent from dataflow_agent.logger import get_logger log = get_logger(__name__) @register("text_analysis") def create_text_analysis_graph() -> GenericGraphBuilder: """创建文本分析工作流""" builder = GenericGraphBuilder( state_model=TextAnalysisState, entry_point="start" ) # ========== 前置工具 ========== @builder.pre_tool("text", "sentiment_analysis") def get_text_for_sentiment(state: TextAnalysisState): return state.request.text @builder.pre_tool("text", "keyword_extraction") def get_text_for_keywords(state: TextAnalysisState): return state.request.text @builder.pre_tool("sentiment", "report_generation") def get_sentiment(state: TextAnalysisState): return state.sentiment_result @builder.pre_tool("keywords", "report_generation") def get_keywords(state: TextAnalysisState): return state.keywords_result # ========== 节点定义 ========== async def start_node(state: TextAnalysisState) -> TextAnalysisState: """起始节点""" log.info(f"开始分析文本,类型: {state.request.analysis_type}") return state async def sentiment_analysis_node(state: TextAnalysisState) -> TextAnalysisState: """情感分析节点""" agent = create_simple_agent( name="sentiment_analyzer", model_name="gpt-4o", temperature=0.3, parser_type="json", ) state = await agent.execute(state) # 保存结果 result = state.agent_results.get("sentiment_analyzer", {}).get("results", {}) state.sentiment_result = result log.info(f"情感分析完成: {result}") return state async def keyword_extraction_node(state: TextAnalysisState) -> TextAnalysisState: """关键词提取节点""" agent = create_simple_agent( name="keyword_extractor", model_name="gpt-4o", temperature=0.3, parser_type="json", ) state = await agent.execute(state) # 保存结果 result = state.agent_results.get("keyword_extractor", {}).get("results", {}) state.keywords_result = result log.info(f"关键词提取完成: {result}") return state async def report_generation_node(state: TextAnalysisState) -> TextAnalysisState: """报告生成节点""" agent = create_simple_agent( name="report_generator", model_name="gpt-4o", temperature=0.7, parser_type="text", ) state = await agent.execute(state) # 保存报告 result = state.agent_results.get("report_generator", {}).get("results", {}) state.final_report = result.get("raw", "") log.info("报告生成完成") return state # ========== 条件判断 ========== def route_after_start(state: TextAnalysisState) -> str: """根据分析类型路由""" analysis_type = state.request.analysis_type if analysis_type == "sentiment": return "sentiment_analysis" elif analysis_type == "keywords": return "keyword_extraction" else: # "both" return "sentiment_analysis" def route_after_sentiment(state: TextAnalysisState) -> str: """情感分析后的路由""" if state.request.analysis_type == "both": return "keyword_extraction" else: return "report_generation" # ========== 图构建 ========== nodes = { "start": start_node, "sentiment_analysis": sentiment_analysis_node, "keyword_extraction": keyword_extraction_node, "report_generation": report_generation_node, "_end_": lambda state: state, } edges = [ ("keyword_extraction", "report_generation"), ("report_generation", "_end_"), ] conditional_edges = { "start": route_after_start, "sentiment_analysis": route_after_sentiment, } return (builder .add_nodes(nodes) .add_edges(edges) .add_conditional_edges(conditional_edges) ) ``` #### Step 3: 创建测试文件 ```python # tests/test_text_analysis.py """ 测试 text_analysis workflow """ import asyncio import pytest from dataflow_agent.states.text_analysis_state import TextAnalysisState, TextAnalysisRequest from dataflow_agent.workflow import run_workflow async def run_text_analysis_pipeline(): """执行文本分析工作流""" # 构造请求 request = TextAnalysisRequest( text="这是一个非常棒的产品,我很喜欢!", analysis_type="both", language="zh", model="gpt-4o", ) # 初始化状态 state = TextAnalysisState(request=request) # 运行 workflow final_state = await run_workflow("text_analysis", state) return final_state @pytest.mark.asyncio async def test_text_analysis_pipeline(): """测试文本分析工作流""" final_state = await run_text_analysis_pipeline() # 断言 assert final_state is not None assert final_state.sentiment_result assert final_state.keywords_result assert final_state.final_report # 打印结果 print("\n=== 情感分析结果 ===") print(final_state.sentiment_result) print("\n=== 关键词提取结果 ===") print(final_state.keywords_result) print("\n=== 最终报告 ===") print(final_state.final_report) if __name__ == "__main__": asyncio.run(run_text_analysis_pipeline()) ``` ### 9.2 场景:批量数据处理 Workflow(并行模式) 创建一个批量处理多条数据的 Workflow,展示并行模式的使用。 #### Step 1: 创建 State 和 Request ```python # dataflow_agent/states/batch_process_state.py from dataclasses import dataclass, field from typing import List from dataflow_agent.state import MainState, MainRequest @dataclass class BatchProcessRequest(MainRequest): """批量处理请求""" items: List[dict] = field(default_factory=list) # 待处理的数据列表 process_type: str = "summarize" # 处理类型 @dataclass class BatchProcessState(MainState): """批量处理状态""" request: BatchProcessRequest = field(default_factory=BatchProcessRequest) # 处理结果 processed_items: List[dict] = field(default_factory=list) success_count: int = 0 error_count: int = 0 summary: str = "" ``` #### Step 2: 创建 Workflow ```python # dataflow_agent/workflow/wf_batch_process.py """ batch_process workflow ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 功能:批量处理多条数据,使用并行模式提高效率 """ from __future__ import annotations from dataflow_agent.states.batch_process_state import BatchProcessState from dataflow_agent.graphbuilder.graph_builder import GenericGraphBuilder from dataflow_agent.workflow.registry import register from dataflow_agent.agentroles import create_parallel_agent, create_simple_agent from dataflow_agent.logger import get_logger log = get_logger(__name__) @register("batch_process") def create_batch_process_graph() -> GenericGraphBuilder: """创建批量处理工作流""" builder = GenericGraphBuilder( state_model=BatchProcessState, entry_point="prepare" ) # ========== 前置工具 ========== # 为并行处理 Agent 准备数据 @builder.pre_tool("items", "batch_processor") def get_items_for_parallel(state: BatchProcessState): """返回需要并行处理的数据列表""" # 将每个 item 包装成包含必要上下文的字典 return [ { "item": item, "process_type": state.request.process_type, "index": idx } for idx, item in enumerate(state.request.items) ] # 为汇总 Agent 准备数据 @builder.pre_tool("results", "summarizer") def get_results_for_summary(state: BatchProcessState): return state.processed_items @builder.pre_tool("stats", "summarizer") def get_stats_for_summary(state: BatchProcessState): return { "total": len(state.request.items), "success": state.success_count, "error": state.error_count } # ========== 节点定义 ========== async def prepare_node(state: BatchProcessState) -> BatchProcessState: """准备节点:验证输入数据""" items = state.request.items if not items: log.warning("没有待处理的数据") state.temp_data["skip_processing"] = True else: log.info(f"准备处理 {len(items)} 条数据") state.temp_data["skip_processing"] = False return state async def parallel_process_node(state: BatchProcessState) -> BatchProcessState: """并行处理节点:使用并行模式处理所有数据""" # 创建并行模式 Agent agent = create_parallel_agent( name="batch_processor", model_name="gpt-4o", temperature=0.3, concurrency_limit=5, # 同时处理5条数据 parser_type="json", ) # 执行并行处理 state = await agent.execute(state) # 提取并行结果 result = state.agent_results.get("batch_processor", {}).get("results", {}) parallel_results = result.get("parallel_results", []) # 统计结果 success_count = 0 error_count = 0 processed_items = [] for idx, item_result in enumerate(parallel_results): if item_result.get("error"): error_count += 1 processed_items.append({ "index": idx, "status": "error", "error": item_result.get("error") }) else: success_count += 1 processed_items.append({ "index": idx, "status": "success", "result": item_result }) # 更新状态 state.processed_items = processed_items state.success_count = success_count state.error_count = error_count log.info(f"并行处理完成: 成功 {success_count}, 失败 {error_count}") return state async def summarize_node(state: BatchProcessState) -> BatchProcessState: """汇总节点:生成处理报告""" agent = create_simple_agent( name="summarizer", model_name="gpt-4o", temperature=0.5, parser_type="text", ) state = await agent.execute(state) # 保存汇总结果 result = state.agent_results.get("summarizer", {}).get("results", {}) state.summary = result.get("raw", "") log.info("汇总报告生成完成") return state # ========== 条件判断 ========== def check_skip(state: BatchProcessState) -> str: """检查是否跳过处理""" if state.temp_data.get("skip_processing"): return "_end_" return "parallel_process" # ========== 图构建 ========== nodes = { "prepare": prepare_node, "parallel_process": parallel_process_node, "summarize": summarize_node, "_end_": lambda state: state, } edges = [ ("parallel_process", "summarize"), ("summarize", "_end_"), ] conditional_edges = { "prepare": check_skip, } return (builder .add_nodes(nodes) .add_edges(edges) .add_conditional_edges(conditional_edges) ) ``` #### Step 3: 创建测试文件 ```python # tests/test_batch_process.py """ 测试 batch_process workflow """ import asyncio import pytest from dataflow_agent.states.batch_process_state import BatchProcessState, BatchProcessRequest from dataflow_agent.workflow import run_workflow async def run_batch_process_pipeline(): """执行批量处理工作流""" # 构造请求 request = BatchProcessRequest( items=[ {"title": "文章1", "content": "这是第一篇文章的内容..."}, {"title": "文章2", "content": "这是第二篇文章的内容..."}, {"title": "文章3", "content": "这是第三篇文章的内容..."}, {"title": "文章4", "content": "这是第四篇文章的内容..."}, {"title": "文章5", "content": "这是第五篇文章的内容..."}, ], process_type="summarize", language="zh", model="gpt-4o", ) # 初始化状态 state = BatchProcessState(request=request) # 运行 workflow final_state = await run_workflow("batch_process", state) return final_state @pytest.mark.asyncio async def test_batch_process_pipeline(): """测试批量处理工作流""" final_state = await run_batch_process_pipeline() # 断言 assert final_state is not None assert len(final_state.processed_items) == 5 assert final_state.success_count + final_state.error_count == 5 # 打印结果 print(f"\n=== 处理统计 ===") print(f"成功: {final_state.success_count}") print(f"失败: {final_state.error_count}") print(f"\n=== 处理详情 ===") for item in final_state.processed_items: print(f" [{item['index']}] {item['status']}") print(f"\n=== 汇总报告 ===") print(final_state.summary) if __name__ == "__main__": asyncio.run(run_batch_process_pipeline()) ``` --- ## 10. 调试和测试 ### 10.1 添加日志 ```python from dataflow_agent.logger import get_logger log = get_logger(__name__) async def my_node(state: MyWorkflowState) -> MyWorkflowState: log.info("开始处理节点") log.debug(f"当前状态: {state}") try: # 处理逻辑 result = await process_data(state) log.info(f"处理成功: {result}") except Exception as e: log.error(f"处理失败: {e}") log.exception("详细错误信息") return state ``` ### 10.2 运行测试 ```bash # 运行单个测试文件 pytest tests/test_my_workflow.py -v -s # 运行所有测试 pytest tests/ -v # 直接运行测试文件 python tests/test_my_workflow.py ``` ### 10.3 调试技巧 #### 1. 打印中间状态 ```python async def debug_node(state: MyWorkflowState) -> MyWorkflowState: """调试节点""" log.critical("=== 调试信息 ===") log.critical(f"Request: {state.request}") log.critical(f"Agent Results: {state.agent_results}") log.critical(f"Temp Data: {state.temp_data}") return state ``` #### 2. 检查 Agent 结果 ```python async def my_node(state: MyWorkflowState) -> MyWorkflowState: state = await agent.execute(state) # 检查结果 result = state.agent_results.get("my_agent", {}) log.info(f"前置工具结果: {result.get('pre_tool_results')}") log.info(f"后置工具: {result.get('post_tools')}") log.info(f"执行结果: {result.get('results')}") return state ``` #### 3. 使用断点 ```python async def my_node(state: MyWorkflowState) -> MyWorkflowState: # 在需要的地方添加断点 import pdb; pdb.set_trace() # 或使用 breakpoint()(Python 3.7+) breakpoint() return state ``` ### 10.4 常见问题排查 #### 问题 1: Agent 未注册 ``` 错误: KeyError: 'my_agent' 解决: 确保 Agent 类使用了 @register 装饰器或正确继承了 BaseAgent ``` #### 问题 2: 前置工具未执行 ``` 错误: 前置工具结果为空 解决: 检查 @builder.pre_tool 的第二个参数是否与 Agent 的 role_name 匹配(不是节点名称!) ``` #### 问题 3: State 字段缺失 ``` 错误: AttributeError: 'MyWorkflowState' object has no attribute 'xxx' 解决: 在 State 类中添加缺失的字段定义 ``` #### 问题 4: 工具绑定到错误的角色 ``` 错误: Agent 执行时获取不到前置工具结果 原因: @builder.pre_tool("xxx", "node_name") 使用了节点名称而不是 Agent 的 role_name 解决: 将第二个参数改为 Agent 的 role_name 示例: @builder.pre_tool("xxx", "my_agent") # my_agent 是 Agent 的 role_name ``` --- ## 11. 最佳实践 ### 11.1 模块化设计 **原则**: 每个节点功能单一明确 ```python # ✅ 好的做法 async def fetch_data_node(state): """只负责获取数据""" state.raw_data = await fetch_from_api() return state async def process_data_node(state): """只负责处理数据""" state.processed_data = process(state.raw_data) return state # ❌ 不好的做法 async def fetch_and_process_node(state): """一个节点做太多事情""" state.raw_data = await fetch_from_api() state.processed_data = process(state.raw_data) state.validated_data = validate(state.processed_data) state.final_result = transform(state.validated_data) return state ``` ### 11.2 错误处理 **原则**: 在节点中添加异常捕获 ```python async def my_node(state: MyWorkflowState) -> MyWorkflowState: """带错误处理的节点""" try: # 主要逻辑 result = await agent.execute(state) state.success = True return result except Exception as e: log.exception(f"节点执行失败: {e}") # 记录错误 state.temp_data["error"] = str(e) state.success = False # 可以选择继续或终止 return state ``` ### 11.3 状态管理 **原则**: 合理设计 State 类的字段 ```python # ✅ 好的做法:清晰的字段分类 @dataclass class MyWorkflowState(MainState): # 输入数据 request: MyWorkflowRequest = field(default_factory=MyWorkflowRequest) # 中间结果 intermediate_data: dict = field(default_factory=dict) processing_status: str = "pending" # 最终输出 final_result: dict = field(default_factory=dict) # 元数据 start_time: str = "" end_time: str = "" duration: float = 0.0 # ❌ 不好的做法:字段混乱 @dataclass class MyWorkflowState(MainState): data1: dict = field(default_factory=dict) data2: dict = field(default_factory=dict) result: dict = field(default_factory=dict) temp: dict = field(default_factory=dict) ``` ### 11.4 工具复用 **原则**: 充分利用现有的工具函数 ```python # 复用项目中的工具 from dataflow_agent.toolkits.basetool.file_tools import local_tool_for_sample from dataflow_agent.toolkits.optool.op_tools import get_operator_content_str @builder.pre_tool("sample_data", "my_node") def get_sample(state: MyWorkflowState): """复用现有工具""" from types import SimpleNamespace req = SimpleNamespace(json_file=state.request.data_file) return local_tool_for_sample(req, sample_size=5) ``` ### 11.5 文档注释 **原则**: 为每个节点和工具添加详细注释 ```python async def my_node(state: MyWorkflowState) -> MyWorkflowState: """ 节点功能描述 此节点负责: 1. 从 API 获取数据 2. 验证数据格式 3. 存储到 state.raw_data Args: state: 当前工作流状态 Returns: 更新后的状态对象 Raises: ValueError: 当数据格式不正确时 ConnectionError: 当 API 连接失败时 """ # 实现 pass ``` ### 11.6 并行模式最佳实践 **原则**: 合理使用并行模式提高效率 #### 何时使用并行模式 ✅ **适合使用并行模式的场景**: - 需要处理多条独立的数据(如批量文本分析、图像处理) - 每条数据的处理逻辑相同 - 数据之间没有依赖关系 - 处理时间较长,并行可以显著提升效率 ❌ **不适合使用并行模式的场景**: - 数据之间有依赖关系(需要按顺序处理) - 数据量很小(并行开销大于收益) - 需要共享状态或累积结果 - 处理逻辑复杂且需要大量上下文 #### 并发度设置建议 ```python # 根据任务特点设置合适的并发度 agent = create_parallel_agent( name="processor", concurrency_limit=5, # 建议值:3-10 ) ``` **设置原则**: - **API 限流考虑**:如果 LLM API 有速率限制,设置较低的并发度(3-5) - **数据量考虑**:数据量大时可以适当提高并发度(5-10) - **资源限制**:考虑内存和网络带宽,避免过高并发导致系统负载过大 - **成本控制**:并发度越高,API 调用成本越高 #### 数据准备注意事项 ```python # ✅ 好的做法:为每个并行项提供完整上下文 @builder.pre_tool("items", "batch_processor") def prepare_parallel_data(state: MyWorkflowState): return [ { "item": item, "context": state.request.context, # 共享上下文 "config": state.request.config, # 共享配置 "index": idx # 用于追踪 } for idx, item in enumerate(state.request.items) ] # ❌ 不好的做法:只传递原始数据 @builder.pre_tool("items", "batch_processor") def prepare_parallel_data(state: MyWorkflowState): return state.request.items # 缺少必要的上下文信息 ``` #### 错误处理建议 ```python async def parallel_process_node(state: MyWorkflowState) -> MyWorkflowState: """并行处理节点(带完善的错误处理)""" agent = create_parallel_agent( name="batch_processor", concurrency_limit=5, ) state = await agent.execute(state) # 提取结果 result = state.agent_results.get("batch_processor", {}).get("results", {}) parallel_results = result.get("parallel_results", []) # 分类处理结果 success_items = [] failed_items = [] for idx, item_result in enumerate(parallel_results): if item_result.get("error"): # 记录失败项 failed_items.append({ "index": idx, "error": item_result.get("error"), "original_data": state.request.items[idx] }) log.warning(f"项 {idx} 处理失败: {item_result.get('error')}") else: success_items.append({ "index": idx, "result": item_result }) # 保存结果 state.temp_data["success_items"] = success_items state.temp_data["failed_items"] = failed_items # 决定是否需要重试失败项 if failed_items and len(failed_items) < len(parallel_results) * 0.3: # 如果失败率低于30%,可以考虑重试 log.info(f"将重试 {len(failed_items)} 个失败项") state.temp_data["need_retry"] = True else: state.temp_data["need_retry"] = False return state ``` #### 结果聚合模式 ```python async def aggregate_results_node(state: MyWorkflowState) -> MyWorkflowState: """聚合并行处理结果""" success_items = state.temp_data.get("success_items", []) failed_items = state.temp_data.get("failed_items", []) # 模式 1: 统计聚合 stats = { "total": len(success_items) + len(failed_items), "success": len(success_items), "failed": len(failed_items), "success_rate": len(success_items) / (len(success_items) + len(failed_items)) } # 模式 2: 数据聚合 aggregated_data = { "all_results": [item["result"] for item in success_items], "summary": { "key_metrics": calculate_metrics(success_items), "common_patterns": find_patterns(success_items) } } # 模式 3: 分组聚合 grouped_results = {} for item in success_items: category = item["result"].get("category", "unknown") if category not in grouped_results: grouped_results[category] = [] grouped_results[category].append(item) state.temp_data["aggregated"] = { "stats": stats, "data": aggregated_data, "grouped": grouped_results } return state ``` #### 性能优化建议 ```python # 1. 使用合适的数据结构 @builder.pre_tool("items", "batch_processor") def prepare_data(state: MyWorkflowState): # ✅ 好:预处理数据,减少每个并行任务的工作量 return [ { "text": item["text"].strip(), # 预处理 "metadata": extract_metadata(item), # 提前提取 "index": idx } for idx, item in enumerate(state.request.items) ] # 2. 批量大小控制 def split_into_batches(items: list, batch_size: int = 50): """将大量数据分批处理""" for i in range(0, len(items), batch_size): yield items[i:i + batch_size] async def process_large_dataset(state: MyWorkflowState): """处理大数据集""" all_results = [] for batch in split_into_batches(state.request.items, batch_size=50): # 每批使用并行模式处理 batch_state = create_batch_state(batch) result = await process_batch(batch_state) all_results.extend(result) return all_results # 3. 监控和日志 async def parallel_process_with_monitoring(state: MyWorkflowState): """带监控的并行处理""" import time start_time = time.time() agent = create_parallel_agent( name="batch_processor", concurrency_limit=5, ) state = await agent.execute(state) elapsed = time.time() - start_time # 记录性能指标 log.info(f"并行处理完成:") log.info(f" - 总数: {len(state.request.items)}") log.info(f" - 耗时: {elapsed:.2f}秒") log.info(f" - 平均: {elapsed/len(state.request.items):.2f}秒/项") log.info(f" - 吞吐: {len(state.request.items)/elapsed:.2f}项/秒") return state ``` #### 常见陷阱 ❌ **陷阱 1:忽略共享状态** ```python # 错误:并行任务之间共享可变状态 shared_counter = {"count": 0} @builder.pre_tool("items", "processor") def prepare_data(state): return [{"data": item, "counter": shared_counter} for item in items] # 问题:并发修改 shared_counter 会导致竞态条件 ``` ✅ **正确做法:每个任务独立** ```python @builder.pre_tool("items", "processor") def prepare_data(state): return [{"data": item, "index": idx} for idx, item in enumerate(items)] # 每个任务有独立的 index,不共享可变状态 ``` ❌ **陷阱 2:过度并行** ```python # 错误:对少量数据使用高并发 agent = create_parallel_agent( name="processor", concurrency_limit=20, # 只有5条数据却设置20并发 ) ``` ✅ **正确做法:根据数据量调整** ```python # 根据数据量动态调整并发度 data_count = len(state.request.items) concurrency = min(data_count, 5) # 最多5并发 agent = create_parallel_agent( name="processor", concurrency_limit=concurrency, ) ``` --- ## 总结 本教程涵盖了 DataFlow-Agent 项目中 Workflow 编写的完整流程: 1. ✅ 使用 CLI 快速创建组件 2. ✅ 定义 State 和 Request 3. ✅ 使用新的 Agent 创建方式 4. ✅ 配置前置工具和后置工具 5. ✅ 编写节点函数 6. ✅ 构建工作流图 7. ✅ 调试和测试 8. ✅ 遵循最佳实践 ### 快速开始检查清单 - [ ] 使用 `dfa create --wf_name xxx` 创建 Workflow - [ ] 定义或复用 State 和 Request - [ ] 创建必要的 Agent(如果不存在) - [ ] 定义前置工具(@builder.pre_tool) - [ ] 定义后置工具(@builder.post_tool,如果需要) - [ ] 实现节点函数 - [ ] 配置节点、边和条件边 - [ ] 编写测试文件 - [ ] 运行测试验证 - [ ] 添加文档注释 ### 参考资源 - [项目架构文档](../guides/architecture.md) - [Agent 开发指南](../guides/agent_development.md) - [工具系统文档](../guides/tool_system.md) - [示例 Workflow](../../dataflow_agent/workflow/) --- **祝你编写出高效、可维护的 Workflow!** 🚀