引言:多智能体协作的困境
2024 年以来,以 AutoGen、LangGraph、CrewAI 为代表的多智能体框架层出不穷。它们让开发者能够编排多个 AI Agent 协同工作,但实际落地时往往会遇到三个核心问题:
- Token 爆炸:多轮对话中,上下文不断累积,每次重试都在历史消息上追加新的内容,导致单次请求轻松突破数万甚至数十万 Token,成本随之失控。
- 角色混沌:Agent 之间的职责边界模糊,Plan 和 Execute 混在一起,Evaluator 既做评审又给建议,最终谁也说不清问题出在哪。
- 不可审计:Agent 的决策过程像一个黑盒——它为什么 PASS?为什么 FAIL?用了多少 Token?花了多少钱?这些信息在大多数框架里都缺失。
这些问题促使我开发了 autogen-pse,一个基于微软 AutoGen 的 Planner-Specialist-Evaluator(PSE)三角色 Agent 框架。本文将从架构设计的角度,深入解析它的核心思想和实现细节。
一、PSE 三角色模型:为什么是这三个角色?
PSE 模式的灵感来自软件工程中的角色分工:
- Planner(规划者) → 相当于 Tech Lead:拆解需求、制定计划、分配任务、做交付决策。不做具体执行。
- Specialist(执行者) → 相当于 Developer:负责编写具体的交付物。只执行被分配的任务。
- Evaluator(评审者) → 相当于 Code Reviewer:独立验证交付物质量,输出 PASS/PARTIAL/FAIL 判决。不给建议,不替 Specialist 改代码。
角色约束
每个角色都有明确的「能做什么」和「不能做什么」:
| 角色 | 职责 | 限制 |
|---|---|---|
| Planner | 分析需求、拆解任务、分配执行、做交付决策 | 不写代码、不做计算 |
| Specialist | 执行具体任务、撰写交付物 | 只做被分配的事、完成后汇报 |
| Evaluator | 独立验证交付物、输出判决 | 不信任 Planner、不给建议、只输出 PASS/PARTIAL/FAIL |
这种严格的分工带来了几个好处:
- 职责清晰:任何失败都可以追溯到具体环节。是 Plan 错了?Execute 有 Bug?还是 Evaluation 标准有问题?
- 减少幻觉:Evaluator 不参与执行,它只做验证,视角更纯粹,更容易发现问题。
- 可审计:每一轮的 Plan→Execute→Evaluate 都被完整记录。
二、架构总览
用户入口
CLI / Makefile / Web
│
▼
┌──────────────────────┐
│ Orchestrator │
│ ┌────────────────┐ │
│ │ 循环控制引擎 │ │
│ │ step_buffer │ │
│ │ Trace 记录 │ │
│ │ Token 统计 │ │
│ └────────────────┘ │
└──────────────────────┘
│
▼
┌─────────────────────────┐
│ RoundRobinGroupChat │
│ │
│ Planner → Specialist │
│ → Evaluator │
│ → ToolAgent │
└─────────────────────────┘
│
┌──────────┼──────────┐
▼ ▼ ▼
Planner Specialist Evaluator
read_file read_file read_file
bash (只读) bash
pytest
ruff
│
ToolAgent
write_file
read_file
bash
pytest
ruff
为什么需要第四个 Agent——ToolAgent?
Planner、Specialist、Evaluator 都不是直接调用工具的。它们通过 DSML(Domain-Specific Markup Language)发送工具调用请求,由 ToolAgent 统一执行。
Planner: "我需要创建一个文件,内容如下:<tool_calls>..."
↓
ToolAgent 解析 XML,执行 write_file,返回结果
这种设计遵循了 最小权限原则:
- Planner 有
read_file+bash(可以读文件、执行命令,但不能写) - Specialist 只有
read_file(只能读,不能写也不能执行命令) - Evaluator 有
read_file+bash+pytest+ruff(可以验证,但不能写) - ToolAgent 有全部工具,但只被动执行,不主动做决策
三、循环控制与 step_buffer:解决 Token 爆炸的关键
这是 autogen-pse 最核心的设计。每个任务由多个「Plan → Execute → Evaluate」循环组成:
循环开始
↓
Planner 制定计划
↓
Specialist 执行并产出交付物
↓
Evaluator 验证交付物
↓
├── PASS → 交付完成,退出
├── PARTIAL → 修复后重试(最多 3 次)
├── FAIL → 重新制定计划(最多 2 次)
└── BLOCKED / TIMEOUT → 终止
step_buffer 策略
传统的多轮对话中,每次重试都会把完整的历史对话传给 Agent。这就导致:第 1 轮 10K Token,第 2 轮 20K,第 3 轮 30K……呈线性增长。
autogen-pse 的 step_buffer 策略做了两件事:
PARTIAL 重试时:只把 Evaluator 的判决摘要(约 2000 字)作为新的任务提示传入。Planner 只需要知道「上次的问题是什么」,不需要重新阅读整个对话历史。
原始任务: "请撰写一份项目进展周报"
↓ FAIL
新任务: "上一轮被判 FAIL(原因:缺少风险评估部分)。
请基于原始任务重新制定计划。"
FAIL 重试时:清空整个对话上下文,基于「原始任务 + 失败原因」重新生成计划。这迫使 Planner 产生一个全新的、不被之前错误污染的方案。
原始任务: "请撰写一份项目进展周报"
↓ FAIL × 2
新任务: "连续 PARTIAL 3 次,已达上限。
请评估是否仍有可交付内容,或宣布 BLOCKED。"
这个策略在实践中效果显著。以一份典型的定期报告生成任务为例,一次完整的 PSE 分析通常包含 3 轮对话,消耗约 200K Prompt Token + 28K Completion Token,费用约 ¥0.65。如果没有 step_buffer,同样的任务可能轻松突破 500K Token。
循环控制参数
所有参数都可以通过环境变量配置:
MAX_PARTIAL_RETRIES = 3 # PARTIAL 最大重试次数
MAX_FAIL_RETRIES = 2 # FAIL 最大重试次数
TURNS_PER_CYCLE = 20 # 每轮对话最大轮次(防止死循环)
四、工具系统:最小权限设计
autogen-pse 的工具系统体现了安全设计的最佳实践:
# Planner:可以读和执行
create_planner(client, task):
tools=[read_file, bash]
# Specialist:只读
create_specialist(client, task):
tools=[read_file]
# Evaluator:可以读、执行和测试
create_evaluator(client, task):
tools=[read_file, bash, run_pytest, run_ruff]
# ToolAgent:全部权限,但被动执行
create_tool_agent(client):
tools=[write_file, bash, read_file, run_pytest, run_ruff]
DSML 工具调用协议
Agent 之间不直接调用函数,而是通过 XML 标签嵌入在文本消息中:
<tool_calls>
<invoke name="bash">
<parameter name="command" string="true">python3 script.py</parameter>
</invoke>
</tool_calls>
ToolAgent 解析这些标签并执行,然后将结果返回给对话。这种间接调用有几个好处:
- 权限隔离:Agent 只能「请求」工具调用,不能「执行」工具调用
- 集中审计:所有工具调用都在 ToolAgent 这里过一道,方便日志记录
- 灵活扩展:要添加新工具,只需要在 ToolAgent 的 system prompt 中声明即可
Token 追踪与成本核算
每个 Agent 的 Token 消耗都被精确追踪:
class TokenTracker:
def feed(self, message):
if hasattr(message, "models_usage"):
stats = self.report.agents[source]
stats.prompt_tokens += usage.prompt_tokens
stats.completion_tokens += usage.completion_tokens
输出报告示例:
============================
📊 Token 消耗报告
============================
Planner | 轮次: 4 | 输入: 48672 | 输出: 2008 | 合计: 50680
Specialist | 轮次: 4 | 输入: 56152 | 输出: 13472 | 合计: 69624
Evaluator | 轮次: 4 | 输入: 102396 | 输出: 11942 | 合计: 114338
ToolAgent | 轮次: 3 | 输入: 3970 | 输出: 263 | 合计: 4233
---------------------------------------------------------------
总计 | 轮次: 15 | 输入: 211190 | 输出: 27685 | 合计: 238875
---------------------------------------------------------------
💰 预估费用: ¥0.6438
============================
五、实际应用:结构化报告自动生成
autogen-pse 的一个典型应用是「多源数据的结构化报告自动生成」任务。这个任务采用了「零 LLM 成本预处理 + LLM 深度分析」的两层架构:
第一层:规则引擎(零 LLM 成本)
make summarize
读取结构化的业务数据源(JSON/CSV),执行多类规则检查:
- 数据完整性检测:缺失或异常的字段
- 一致性校验:跨源数据之间的冲突
- 阈值越界检测:关键指标超出预警线
- 结构性异常:分布比例失衡
同时自动获取关联的外部参考数据。整个过程不使用任何 LLM,0 成本。
第二层:PSE 深度分析(LLM 驱动)
make review
将规则引擎的输出作为输入,交给 PSE 三人组进行深度分析。Planner 制定分析框架,Specialist 逐项撰写,Evaluator 独立验证。
如果配置了 RAG 知识库,还会自动检索相关领域知识文档,注入到分析上下文中。
技术细节:如何从对话中提取最终报告?
一个有趣的问题是:PSE 三人组的最终产出是一段对话历史,我怎么从中提取出完整的报告?
方案不是让 Agent 把报告写到文件里(这样有竞态风险),而是在执行完成后,解析 trace JSON,找到 Specialist 最后一次输出中标记为 ## 最终结论 的章节:
for msg in reversed(trace["cycles"][-1]["messages"]):
if msg["source"] == "Specialist" and "## 最终结论" in msg["content"]:
report = extract_under_heading(msg["content"], "## 最终结论")
这种「事后提取」的方式比「即时写入」更可靠,不会受到对话顺序和时序问题的影响。
六、Web Dashboard
除了 CLI 和 Makefile,autogen-pse 还提供了一个完整的 Web Dashboard:
技术栈
- FastAPI + Server-Sent Events:SSE 让用户能实时看到 LLM 的输出流
- Vite + React + Chart.js:前端显示资产趋势图和执行历史
- Docker 多阶段构建:Node 构建前端 + Python 运行后端
核心功能
- 一键执行:点按钮即可运行任务,实时日志输出
- 资产趋势图:Chart.js 展示总资产随时间的变化曲线
- 执行历史:最近 10 次 trace 的可视化列表,颜色标识判决结果
- Trace 详情:点击展开每一轮的完整对话,支持按 Agent 筛选 Token 消耗
七、从开发到部署的完整体验
本地开发
cp .env.example .env # 配置 API Key
uv sync # 安装 Python 依赖
make dev # 启动前端 Dev Server + API Server
make test # 运行 31 个单元测试
make lint # ruff 代码检查
Docker 部署
# 多阶段构建
FROM node:18 AS frontend # 构建 React 前端
FROM python:3.12 AS backend # 运行 FastAPI 后端
COPY --from=frontend /app/web/dist /app/web/dist
添加新任务
三步即可注册一个新任务:
mkdir -p tasks/my-task/prompts
# 1. 编写三个 Agent 的 system prompt
# tasks/my-task/prompts/planner.md
# tasks/my-task/prompts/specialist.md
# tasks/my-task/prompts/evaluator.md
# 2. 编写入口脚本 tasks/my-task/run.py
# 3. 注册到 tasks/_registry.json
八、技术栈一览
| 层级 | 技术 |
|---|---|
| Agent 框架 | AutoGen RoundRobinGroupChat |
| LLM 后端 | DeepSeek / OpenAI 兼容 |
| Web 后端 | FastAPI + SSE |
| Web 前端 | Vite + React + Chart.js |
| 配置管理 | pydantic-settings |
| RAG 知识库 | FAISS + Ollama Embeddings |
| 测试 | pytest(31 个用例) |
| 项目管理 | uv + hatchling |
| 容器化 | Docker 多阶段构建 |
九、设计原则总结
回顾 autogen-pse 的架构设计,有几个值得借鉴的原则:
1. 职责分离
Planner 不做执行,Evaluator 不给建议,Specialist 不写测试。每个角色只做一件事,且只做一件事。这让系统更可控、更可调试。
2. 最小权限
Agent 能调用的工具与其职责严格匹配。尤其是 write_file 只有 ToolAgent 能调用,这从根本上防止了 Agent 意外篡改文件。
3. 状态管理
step_buffer 策略解决了多轮 Agent 对话中的 Token 爆炸问题。关键在于:重试不等同于重新执行——PARTIAL 时保留上下文但压缩,FAIL 时彻底清空重来。
4. 可审计性
每一次执行都被完整记录到 trace JSON 文件中,包括每轮的判决、Token 消耗、耗时。7 天后自动清理旧 trace。
5. 渐进式成本控制
用规则引擎做零成本的预处理,只在必要时才调用 LLM 做深度分析。这种「先规则、后 AI」的思路比纯 LLM 方案更经济、更可靠。
十、未来方向
autogen-pse 是一个面向「可验证、可追溯」的多智能体协作框架,其设计本身与具体业务领域无关。计划中的改进方向包括:
- 并行执行:支持多个 Specialist 并行工作,提高效率
- 记忆系统:跨会话的知识持久化,让 Agent 记住历史决策的理由
- 规则引擎扩展:将规则检查从单一示例任务泛化为通用断言框架
- 更丰富的 Dashboard:可视化 Agent 的思考过程和决策链路
结语
构建可靠的多智能体系统,关键不在于用什么框架,而在于如何设计角色分工、状态管理和权限控制。autogen-pse 在实践中证明了一点:给每个 Agent 清晰的边界和明确的职责,比给它们更强大的能力重要得多。
如果你对 autogen-pse 感兴趣,欢迎访问 GitHub 项目主页:https://github.com/erishen/autogen-pse,也欢迎提交 Issue 或 PR。
本文首发于 erishen.cn,作者 Erishen Sun。
PSE 系列其他文章
- 基于 AutoGen 构建 PSE 三角色闭环:可重试、可追溯的 Agent 协作框架
- 当 LLM 开始骗自己:用正则守护文章可信度的多 Agent 框架
- LangGraph PSE:用状态机显式建模 PSE 三角色协作
- 让智能体从源头不说谎:用 LlamaIndex Workflow 构建 RAG 接地的 PSE 框架
为什么需要第四个 Agent(ToolAgent)?
把”调用工具”从 Specialist 的职责中拆出来,由 ToolAgent 统一承接所有工具调用。这样既强化了最小权限(工具权限集中管控),也让 Specialist 专注于”思考与产出”,职责更清晰。
step_buffer 解决了什么问题?
多智能体多轮对话会导致上下文(token)快速膨胀、成本失控甚至超出模型窗口。step_buffer 通过缓冲与截断策略控制每步注入的上下文规模,在成本与信息完整之间取得平衡。
工具系统的最小权限是怎么设计的?
每个工具显式声明自己能访问的资源,调用需遵循 DSML 工具调用协议,框架对每次调用做权限校验与 Token 追踪,确保工具不会越权且成本可核算。
PSE 如何做成本控制?
采用渐进式成本控制:先用规则引擎做零 LLM 成本的粗筛与格式化,只有确需深度判断的环节才调用 LLM(Evaluator/Specialist),避免为每一步都付出模型推理开销。
PSE 的设计原则有哪些?
职责分离、最小权限、状态管理、可审计性、渐进式成本控制。这五条贯穿三角色模型、工具系统与部署全过程,是框架”可验证、可追溯”的工程基石。
发表回复