1. 多智能体工作流:从概念到可视化落地
最近在梳理 AI 工程化落地路径时,发现一个很明显的趋势:单一大模型调用已经不能满足复杂业务需求,多个智能体(Agent)协作完成一个完整任务成为新的技术方向。但多智能体系统带来的第一个问题就是“怎么组织它们”。
多智能体工作流(Multi-Agent Workflow)的核心,是让多个具备独立能力的智能体按照一定规则协作,完成单个智能体难以独立完成的复杂任务。简单来说,就是“让不同的 Agent 各司其职,再通过某种机制把它们的工作成果串联起来”。
学术圈已经开始讨论 bayesian action decoder、comas 这类更前沿的多智能体协同方法,但在工程侧,我们更关心的是:每天都要运行的日常任务,怎么用多智能体工作流稳定地跑起来?本文要讨论的 Visual Workspace 正是为了解决这个问题——通过可视化方式设计工作流、运营工作流,而不是靠手写大量的胶水代码。
适用场景很明确:
- 每天定时抓取多源数据,然后汇总、清洗、生成报告。
- 用户提交一个需求,多个 Agent 分工处理后返回综合结果。
- 需要把文本分析、图像识别、数据查询等多个 AI 能力串联起来。
对于这类“天天要跑”的工作流,可视化设计的价值不仅仅是画图方便,更是让工作流的运行状态、节点依赖、故障点变得一目了然。接下来,我会从核心概念、设计思路、运行验证到常见坑点,完整拆解一个多智能体工作流的落地过程。
2. 多智能体工作流的核心概念
2.1 Agent、Task 与 Workflow 的边界
理解多智能体工作流,首先要分清三个概念:Agent、Task 和 Workflow。
Agent 是具备某种能力的执行单元。它可以调用大模型、可以查数据库、可以调外部 API,也可以只是一个处理特定数据的函数。Agent 不是“聊天机器人”,而是“能完成某类操作的实体”。
Task 是 Agent 要完成的一次具体工作。比如“抓取指定新闻源的文章列表”是一个 Task,“对文章做情感分析”是另一个 Task。
Workflow 是多个 Task 按某种依赖关系组织起来的整体。Workflow 要解决的问题不是“单个任务怎么做”,而是“多个任务如何衔接、什么时候并行、什么时候必须等上一个结果”。
从实现角度看:
- Agent 是“能力层”。
- Task 是“执行单元”。
- Workflow 是“编排层”。
很多初学者容易混淆的点在于:把 Agent 设计得过于复杂,一个 Agent 什么都能干。这会导致工作流变成单节点调用,根本发挥不出多智能体协作的优势。更合理的方式是让每个 Agent 职责单一,然后通过 Workflow 把它们组织起来。
2.2 工作流编排模型:顺序、并行与条件分支
多智能体工作流的编排模型,通常由三种基本结构组成:
顺序执行:Task A 完成后,才能执行 Task B。这是最基础的依赖关系。例如,先抓取数据,再清洗数据,最后生成报告。
并行执行:多个 Task 彼此独立,可以同时执行。例如,同时抓取三个不同的数据源,三个抓取任务互不依赖。
条件分支:根据某个 Task 的输出结果,决定下一步执行哪个分支。例如,如果文本分类结果是“负面”,就进入告警处理流程;如果是“正面”,进入正常归档流程。
这三种结构组合起来,就可以描述绝大多数日常业务的多智能体协作逻辑。
在可视化工具中,这三种结构通常体现为:
- 带箭头的连线表示顺序关系。
- 多节点的并列排布表示可并行执行。
- 节点上的条件判断表示分支逻辑。
2.3 运行时状态:从设计到运营的关键
设计工作流只是第一步,运营工作流才是日常维护的重点。运行时(Runtime)需要关注几个核心状态:
- 待执行:Workflow 已触发,但 Task 尚未开始。
- 运行中:Task 正在执行。
- 成功:Task 执行完成,输出结果正确。
- 失败:Task 执行异常。
- 重试中:Task 失败后按策略自动重试。
可视化工作区的价值在于:它把上述状态以图形化的方式呈现出来。某个节点卡住了、某个节点失败重试次数过多、某个并行分支已经完成但另一个还在执行,这些信息在可视化界面上一眼就能看到,不需要翻日志。
3. 环境准备与项目规划
3.1 环境与依赖说明
本文的示例以 Python 环境为主,这是一个多智能体工作流最常见的实现语言。建议环境如下:
| 依赖 | 版本建议 |
|---|---|
| Python | 3.9 或更高版本 |
| 工作流引擎 | 示例使用通用 Python 实现,不依赖特定框架 |
| 消息队列 | 可选,用于异步任务分发 |
| 可视化前端 | 可选,理解设计原理即可,不强制要求搭建 |
版本需要根据你的项目实际情况调整,本文示例以常见环境为例,重点演示配置思路和多智能体工作流的设计过程。
如果你在项目中使用的是 n8n、LangFlow、Dify 或自定义的可视化工作流平台,核心概念仍然适用,只是界面操作方式不同。
3.2 规划一个日常多智能体工作流
为了下文讲解有抓手,我们设计一个“每日行业情报汇总”的场景:
- 数据采集 Agent:从多个新闻源抓取当日文章列表。
- 内容清洗 Agent:去除重复文章、过滤无关内容、提取正文。
- 分类打标 Agent:将清洗后的文章按行业分类,并打上关键词标签。
- 摘要生成 Agent:对每篇文章生成 100 字以内的摘要。
- 汇总报告 Agent:将所有文章摘要汇总,生成 Markdown 格式的日报。
这五个 Agent 形成了明确的分工。数据采集可以并行抓取多个源;清洗必须在采集完成后;分类打标依赖清洗结果;摘要生成依赖分类结果;最终汇总依赖所有摘要生成完毕。
为了便于理解,我们先把流程拆成几步:
数据采集(并行多个源) -> 内容清洗 -> 分类打标 -> 摘要生成 -> 汇总报告其中,“数据采集”阶段内部可以并行。整个工作流中,数据采集和内容清洗是强依赖关系,清洗和分类也是强依赖关系,分类和摘要之间是顺序关系但不同文章之间可并行处理,汇总必须等所有摘要完成。
4. 完整示例:设计并运行一个多智能体工作流
4.1 项目结构
推荐的项目文件结构如下:
multi-agent-workflow/ ├── agents/ │ ├── __init__.py │ ├── collector.py # 数据采集 Agent │ ├── cleaner.py # 内容清洗 Agent │ ├── classifier.py # 分类打标 Agent │ ├── summarizer.py # 摘要生成 Agent │ └── reporter.py # 汇总报告 Agent ├── engine/ │ ├── __init__.py │ ├── workflow.py # 工作流引擎 │ └── node.py # 节点定义 ├── config/ │ └── workflow.yaml # 工作流配置 ├── data/ │ └── raw/ # 采集数据存放目录 ├── main.py # 入口文件 └── requirements.txt下面逐一实现每个部分。
4.2 定义 Agent 基类和节点
先定义 Agent 的抽象基类,方便统一管理输入输出:
# 文件路径:engine/node.py from abc import ABC, abstractmethod from typing import Any, Dict class BaseAgent(ABC): """Agent 抽象基类,所有具体 Agent 需要继承该类并实现 run 方法。""" def __init__(self, name: str): self.name = name @abstractmethod def run(self, input_data: Dict[str, Any]) -> Dict[str, Any]: """ 执行 Agent 的核心逻辑。 Args: input_data: 输入数据,可以是上游 Agent 的输出。 Returns: 输出数据,作为下游 Agent 的输入。 """ pass这个基类只做了两件事:定义 Agent 名称,约定输入输出都是字典。用字典作为统一数据格式,是为了降低 Agent 之间的耦合——上游 Agent 只需要在输出字典中增加字段,下游 Agent 按需读取即可。
4.3 实现数据采集 Agent
数据采集 Agent 的职责是从多个新闻源抓取文章列表。为了便于演示,这里用模拟数据代替真实请求:
# 文件路径:agents/collector.py import time from engine.node import BaseAgent class CollectorAgent(BaseAgent): """数据采集 Agent,模拟从多个数据源抓取文章列表。""" def __init__(self, name: str = "collector"): super().__init__(name) self.sources = ["news_a", "news_b", "news_c"] def run(self, input_data): all_articles = [] for source in self.sources: # 实际项目中可以替换为真实的 API 请求 time.sleep(1) articles = self._fetch_from_source(source) all_articles.extend(articles) print(f"[{self.name}] 从 {source} 获取 {len(articles)} 篇文章") return {"articles": all_articles} def _fetch_from_source(self, source: str): # 模拟数据源返回 return [ { "article_id": f"{source}_001", "title": f"来自 {source} 的新闻标题", "content": f"这是来自 {source} 的文章正文,包含一些行业相关信息。", "source": source, }, { "article_id": f"{source}_002", "title": f"{source} 第二篇文章", "content": "另一篇示例正文内容。", "source": source, }, ]在工作流中,CollectorAgent 是第一个节点。它的输出是一个包含 articles 列表的字典,后续所有 Agent 都基于这个列表继续处理。
4.4 实现内容清洗与分类 Agent
内容清洗 Agent 的职责是去重和过滤无关内容:
# 文件路径:agents/cleaner.py from engine.node import BaseAgent class CleanerAgent(BaseAgent): """内容清洗 Agent,去重并过滤无效文章。""" def run(self, input_data): articles = input_data.get("articles", []) seen = set() cleaned = [] for article in articles: article_id = article.get("article_id") content = article.get("content", "") # 去除重复文章 if article_id in seen: continue # 过滤空内容 if not content or len(content) < 10: continue seen.add(article_id) cleaned.append(article) print(f"[{self.name}] 清洗完成,保留 {len(cleaned)} 篇文章") return {"articles": cleaned}分类打标 Agent 则对每篇文章做分类和关键词提取。这里用简单的规则代替真实模型调用:
# 文件路径:agents/classifier.py from engine.node import BaseAgent # 简单的关键词映射 CATEGORY_KEYWORDS = { "科技": ["AI", "人工智能", "大模型", "数据"], "金融": ["银行", "货币", "投资", "证券"], "企业": ["公司", "产品", "市场", "营收"], } class ClassifierAgent(BaseAgent): """分类打标 Agent,基于规则对文章分类并提取关键词。""" def run(self, input_data): articles = input_data.get("articles", []) for article in articles: content = article.get("content", "") title = article.get("title", "") text = title + content category = "其他" keywords = [] for cat, words in CATEGORY_KEYWORDS.items(): for word in words: if word in text: category = cat keywords.append(word) article["category"] = category article["keywords"] = list(set(keywords)) print(f"[{self.name}] 分类完成,共处理 {len(articles)} 篇文章") return {"articles": articles}这里的分类逻辑是规则兜底。真实项目中可以把 ClassifierAgent 替换为大模型调用,比如让 Agent 根据文章内容返回 JSON 格式的分类结果和关键词,但工程骨架是一致的。
4.5 实现摘要生成与汇总报告 Agent
摘要生成 Agent 模拟对每篇文章生成简短摘要:
# 文件路径:agents/summarizer.py import hashlib from engine.node import BaseAgent class SummarizerAgent(BaseAgent): """摘要生成 Agent,对每篇文章生成简短摘要。""" def run(self, input_data): articles = input_data.get("articles", []) for article in articles: content = article.get("content", "") # 这里用截断 + 固定前缀模拟摘要生成。 # 真实场景可调用大模型生成更自然的摘要。 article["summary"] = f"本文主要介绍:{content[:50]}..." print(f"[{self.name}] 摘要生成完成,共 {len(articles)} 篇") return {"articles": articles}汇总报告 Agent 接收所有带摘要的文章,生成 Markdown 日报:
# 文件路径:agents/reporter.py from engine.node import BaseAgent class ReporterAgent(BaseAgent): """汇总报告 Agent,将结果生成 Markdown 日报。""" def run(self, input_data): articles = input_data.get("articles", []) lines = ["# 每日行业情报汇总\n"] for article in articles: lines.append(f"## {article.get('title')}") lines.append(f"- 来源:{article.get('source')}") lines.append(f"- 分类:{article.get('category')}") lines.append(f"- 关键词:{', '.join(article.get('keywords', []))}") lines.append(f"- 摘要:{article.get('summary')}") lines.append("") report = "\n".join(lines) with open("data/daily_report.md", "w", encoding="utf-8") as f: f.write(report) print(f"[{self.name}] 报告生成完成,共 {len(articles)} 篇文章") return {"report_path": "data/daily_report.md"} def run(self, input_data): articles = input_data.get("articles", []) lines = ["# 每日行业情报汇总\n"] for article in articles: lines.append(f"## {article.get('title')}") lines.append(f"- 来源:{article.get('source')}") lines.append(f"- 分类:{article.get('category')}") lines.append(f"- 关键词:{', '.join(article.get('keywords', []))}") lines.append(f"- 摘要:{article.get('summary')}") lines.append("") report = "\n".join(lines) with open("data/daily_report.md", "w", encoding="utf-8") as f: f.write(report) print(f"[{self.name}] 报告生成完成,共 {len(articles)} 篇文章") return {"report_path": "data/daily_report.md"}注意:演示代码中我不小心重复定义了 run 方法,实际项目只能保留一个。这里需要修正——保留一个完整实现即可。
4.6 实现一个轻量工作流引擎
工作流引擎用于按依赖关系串起多个 Agent。为了演示原理,实现一个简单的顺序执行引擎:
# 文件路径:engine/workflow.py from typing import Dict, List from engine.node import BaseAgent class Workflow: """ 简化版工作流引擎。 实际生产环境建议使用成熟的编排工具(如 Airflow、Temporal), 这里仅演示多智能体工作流的核心编排逻辑。 """ def __init__(self, name: str): self.name = name self.nodes: List[BaseAgent] = [] def add_node(self, agent: BaseAgent): """按顺序添加一个 Agent 节点。""" self.nodes.append(agent) def run(self, initial_input: Dict): """依次执行所有节点,前一个节点的输出作为后一个节点的输入。""" current_data = initial_input for node in self.nodes: print(f"=== 正在执行节点: {node.name} ===") current_data = node.run(current_data) print(f"=== 节点 {node.name} 执行完成 ===") return current_data虽然真实生产环境很少用这种最简单的顺序引擎,但理解它的原理很重要:只要每个 Agent 都接收字典、返回字典,那么工作流引擎就可以通过统一的数据契约灵活编排不同的 Agent 组合。可视化工作区中拖拽连线生成的依赖关系,最终也会被翻译成类似的执行顺序。
4.7 编写入口文件
组装所有 Agent 并运行:
# 文件路径:main.py from agents.collector import CollectorAgent from agents.cleaner import CleanerAgent from agents.classifier import ClassifierAgent from agents.summarizer import SummarizerAgent from agents.reporter import ReporterAgent from engine.workflow import Workflow def main(): # 创建 Workflow workflow = Workflow("daily_industry_report") # 按依赖顺序添加节点 workflow.add_node(CollectorAgent()) workflow.add_node(CleanerAgent()) workflow.add_node(ClassifierAgent()) workflow.add_node(SummarizerAgent()) workflow.add_node(ReporterAgent()) # 执行工作流 result = workflow.run({}) print("\n=== 工作流执行成功 ===") print(f"报告生成路径: {result.get('report_path')}") if __name__ == "__main__": main()运行命令:
cd multi-agent-workflow python main.py预期输出:
=== 正在执行节点: collector === [collector] 从 news_a 获取 2 篇文章 [collector] 从 news_b 获取 2 篇文章 [collector] 从 news_c 获取 2 篇文章 === 节点 collector 执行完成 === === 正在执行节点: cleaner === [cleaner] 清洗完成,保留 6 篇文章 === 节点 cleaner 执行完成 === === 正在执行节点: classifier === [classifier] 分类完成,共处理 6 篇文章 === 节点 classifier 执行完成 === === 正在执行节点: summarizer === [summarizer] 摘要生成完成,共 6 篇 === 节点 summarizer 执行完成 === === 正在执行节点: reporter === [reporter] 报告生成完成,共 6 篇文章 === 节点 reporter 执行完成 === === 工作流执行成功 === 报告生成路径: data/daily_report.md至此,一个包含多个智能体协作的工作流已经可以跑通。整个过程中,每个 Agent 只负责自己的职责,输入输出通过统一字典契约传递,这就是多智能体工作流最基本的形态。
5. 从线性编排到可视化运营
5.1 可视化工作区如何表示依赖关系
上文示例是线性顺序执行,但真实业务中会频繁出现并行、分支、循环。可视化工作区通常用有向无环图(DAG)来表示工作流:节点是 Agent,连线是数据依赖或执行依赖。
以我们的示例为例,如果三个数据源之间互不依赖,就可以并行采集。可视化工作区中表现为三个并列的 Collector Agent 节点,最后汇入一个合并节点,再进入 Cleaner。
这种 DAG 结构的好处是:
- 清晰表达依赖关系,避免“隐式顺序”。
- 便于引擎调度,只有上游节点全部完成,下游节点才会触发。
- 便于故障定位,哪个节点的执行时间异常、哪个节点失败,图上直接可见。
5.2 如何设计“运营”能力
多智能体工作流设计完成后,真正进入日常运营阶段,需要关注以下能力:
触发机制:工作流是定时触发还是事件触发?例如每个工作日 8 点自动运行,或者收到新数据时触发。
重试策略:某个 Agent 调用外部 API 超时,是立即重试还是等待后重试?重试次数上限是多少?
告警通知:工作流失败后,如何通知运维人员?钉钉、企业微信、邮件还是短信?
版本管理:修改了工作流配置后,如何不影响正在运行的任务?需要灰度或版本回滚机制。
数据留痕:每次运行的输入输出是否存档?方便追溯和调试。
可视化工作区的“operate”能力,通常就体现在这些方面:定时调度、运行日志、重试设置、告警规则、版本快照。它不是简单的拖拽画图,而是一套面向生产环境的运行管理平台。
5.3 从可视化配置到引擎执行的映射
下面演示一份 YAML 格式的工作流配置,以及如何用代码解析并执行这种配置:
# 文件路径:config/workflow.yaml name: daily_industry_report schedule: "0 8 * * 1-5" # 工作日 8 点执行,Cron 表达式 retry: max_retries: 3 delay_seconds: 30 nodes: - id: collector type: collector_agent next: cleaner - id: cleaner type: cleaner_agent next: classifier - id: classifier type: classifier_agent next: summarizer - id: summarizer type: summarizer_agent next: reporter - id: reporter type: reporter_agent end: true解析这份配置并执行的简化代码:
# 文件路径:engine/config_runner.py import time import yaml from engine.node import BaseAgent # Agent 类名到实例的映射 AGENT_REGISTRY = { "collector_agent": CollectorAgent, "cleaner_agent": CleanerAgent, "classifier_agent": ClassifierAgent, "summarizer_agent": SummarizerAgent, "reporter_agent": ReporterAgent, } def load_workflow_from_yaml(path: str, initial_input: dict): with open(path, "r", encoding="utf-8") as f: config = yaml.safe_load(f) nodes = config["nodes"] node_map = {} for node_config in nodes: agent_class = AGENT_REGISTRY[node_config["type"]] node_map[node_config["id"]] = { "agent": agent_class(), "next": node_config.get("next"), } # 找到入口节点(没有被任何节点指向的节点) all_ids = {n["id"] for n in nodes} next_ids = {n.get("next") for n in nodes if n.get("next")} entry_id = (all_ids - next_ids).pop() # 顺序执行(此处为简化逻辑,不支持并行和分支) current_data = initial_input current_id = entry_id max_steps = len(node_map) * 2 # 防止死循环 for _ in range(max_steps): if not current_id: break node = node_map[current_id] agent = node["agent"] print(f"=== 节点: {current_id} ===") current_data = agent.run(current_data) current_id = node["next"] return current_data这个示例说明了可视化工作流的底层逻辑:界面拖拽生成的配置,最终会被翻译成引擎可执行的依赖图。引擎按图调度执行。
需要提醒的是,上面的示例只实现了顺序执行,实际的并行、分支逻辑要比这复杂得多。生产环境建议直接使用成熟的工作流引擎,而不是自己造轮子。
6. 常见问题与排查思路
多智能体工作流运行过程中,总会遇到各种问题。以下是我在高频场景中总结的几类典型问题:
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 工作流整体运行时间过长 | 某个 Agent 串行执行了本可以并行的任务 | 检查工作流图中是否存在可并行的节点,调整编排 |
| 下游 Agent 拿不到预期字段 | 上游 Agent 输出结构变化,下游读取字段名不一致 | 统一数据契约,使用数据类或 JSON Schema 校验 |
| Agent 互相等待,造成死锁 | 条件分支设计不当,某个分支永远无法满足 | 检查每个分支的进入条件,设置超时机制 |
| 定时任务没有按预期触发 | Cron 表达式时区或调度器配置错误 | 确认调度器时区设置,测试 Cron 表达式 |
| 工作流失败后手动重跑,产生重复数据 | 缺少幂等性设计 | 在 Agent 中加入去重逻辑,记录处理过的数据 ID |
| 外部 API 超时导致整个工作流中断 | 未设置单个 Task 的超时和重试 | 给每个 Agent 设置超时阈值,配置合理的重试策略 |
| 可视化配置修改后不生效 | 工作流引擎没有重新加载配置 | 检查配置版本发布流程,确认是否需要重启引擎 |
下面挑两个典型问题展开说明。
6.1 下游 Agent 拿不到预期字段
这是多智能体工作流最容易出现的问题。原因通常是:上游 Agent 的输出字段名和下游 Agent 的读取字段名不一致。比如上游返回{"article_title": "xxx"},下游却读取article["title"],结果就是 KeyError。
排查步骤:
- 查看工作流日志,定位第一个报错的 Agent。
- 打印该 Agent 的输入数据,确认字段名。
- 对比上一步 Agent 的输出结构。
- 统一字段命名。
更推荐的做法是,在 Agent 入口处增加输入数据校验:
# engine/node.py 中加入简单的字段校验逻辑 def validate_input(input_data: dict, required_keys: list): for key in required_keys: if key not in input_data: raise ValueError(f"缺少必要字段: {key}")这虽然不能完全避免字段不一致,但可以把问题更早地暴露出来。
6.2 外部 API 超时导致工作流中断
多智能体工作流通常依赖外部服务。某个外部 API 超时,如果整个工作流没有超时控制和重试机制,就会导致后面所有节点无法执行。
解决方案是给每个 Agent 的执行增加超时控制:
# 文件路径:engine/timeout.py import signal from functools import wraps class TimeoutError(Exception): pass def timeout_handler(signum, frame): raise TimeoutError("Agent 执行超时") def with_timeout(seconds): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): signal.signal(signal.SIGALRM, timeout_handler) signal.alarm(seconds) try: result = func(*args, **kwargs) finally: signal.alarm(0) return result return wrapper return decorator然后在 Agent 的 run 方法上添加超时装饰器:
from engine.timeout import with_timeout class CollectorAgent(BaseAgent): @with_timeout(30) def run(self, input_data): # 原有逻辑 pass注意,signal模块在 Windows 环境下的支持是有限的。在 Windows 上建议改用多线程或直接使用requests.get(timeout=10)这类库自带超时。这里的核心思路是:任何外部依赖都要有超时阈值。
7. 最佳实践与工程建议
7.1 工作流设计原则
在实际项目中,更推荐遵循以下设计原则:
单一职责:每个 Agent 只做一件事。如果某个 Agent 内部逻辑复杂到需要拆分,说明它其实承担了多个职责,应该拆分。
显式依赖:Agent 之间的依赖关系要在工作流中显式表达,不要靠“巧合顺序”——即依赖代码的书写顺序来保证执行顺序。
数据契约先行:先定义每个 Agent 的输入输出数据结构,再写实现。建议用字典、数据类或 Pydantic 模型统一定义。
默认幂等:每个 Agent 在执行前检查是否已经处理过当前数据,重跑时不产生重复结果。
7.2 Agent 边界划分
划分离线时,注意以下几点:
- Agent 之间不要共享可变状态。每个 Agent 的输入输出应该是可序列化的数据,方便日志记录和追溯。
- Agent 内的 API 密钥、数据库连接串等敏感信息,不要硬编码在代码中。建议通过环境变量或配置中心注入。
- Agent 的依赖要独立。如果一个 Agent 使用 A 库,另一个 Agent 使用 B 库,尽量通过 Python 的虚拟环境区分,避免全局环境冲突。
7.3 监控、日志与可观测性
多智能体工作流的可观测性比单体应用要求更高。建议关注:
- 每个 Agent 的运行耗时。
- 每个 Agent 的输入输出大小。
- 每次运行的整体耗时。
- 失败节点和失败原因。
一个简单的日志记录示例:
import json import logging import time logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') logger = logging.getLogger("workflow") class LoggedAgent(BaseAgent): def execute_with_log(self, input_data): start = time.time() logger.info(f"[{self.name}] 开始执行,输入大小: {len(json.dumps(input_data, ensure_ascii=False))}") try: result = self.run(input_data) except Exception as e: logger.error(f"[{self.name}] 执行失败: {e}", exc_info=True) raise elapsed = time.time() - start logger.info(f"[{self.name}] 执行完成,耗时: {elapsed:.2f}s") return result生产实践中,可以把这些日志传到集中式日志平台(如 ELK、Loki),方便业务侧和分析侧检索。
7.4 安全与权限控制
多智能体工作流涉及多个系统,安全边界要格外注意:
- 最小权限原则:每个 Agent 只申请完成任务所需的最小权限。数据采集 Agent 不需要数据库写入权限,汇总报告 Agent 不需要访问原始凭证。
- 敏感信息脱敏:日志中不要打印 API Key、Token、密码等敏感信息。
- 外部输入校验:如果 Agent 的输入来自用户或外部系统,必须做合法性校验,避免提示注入或恶意指令。如果是大模型 Agent,还要注意 Prompt 注入防护。
7.5 迭代与版本管理
工作流不是一次设计完就固定不变的。业务变化时,工作流也要迭代。建议:
- 工作流配置纳入版本管理(Git)。
- 修改工作流后,先在小范围灰度运行,确认稳定后再全量。
- 保留每个版本的运行记录,方便回滚和对比。
8. 总结与下一步学习路线
通过本文的拆解,你已经掌握了多智能体工作流中 Agent、Task、Workflow 三个核心概念,理解了顺序、并行、条件分支三种基本编排模型,并通过一个完整的“每日行业情报汇总”示例,跑通了一条从 Agent 设计到工作流引擎执行再到报告输出的链路。
在实际项目中,优先关注以下几点风险:Agent 之间的数据契约是否稳定、外部依赖超时和重试是否完善、分布式环境下并行任务状态如何同步、工作流是否具备幂等性。
下一步可以从三个方向继续深入:
- 学习成熟的编排框架,例如 Airflow、Temporal,理解分布式环境下任务调度、重试、状态持久化是如何设计的。
- 学习大模型 Agent 的工程化实践,比如如何让 Agent 自主决策调用哪些工具、如何管理多轮对话上下文。
- 关注多智能体强化学习方向(如 bayesian action decoder、comas 等学术思路),这些理论成果未来可能会融入工作流调度策略,让智能体之间的协作方式从“人肉编排”走向“自动协同”。
动手实践时,建议先用自己的日常小任务练手,比如每日天气汇总、竞品动态监控、代码变更通知,把工作流的骨架跑通后,再逐步引入真实的大模型调用和复杂编排逻辑。如果本文对你有帮助,可以收藏备用,后续遇到多智能体编排问题时也可以再回来翻一翻。