🤖 用 AutoGen 搭 PSE 三角色闭环:可重试、可追溯、可审计的多智能体框架

一、引言

autogen-pse 是一个基于 Microsoft AutoGen 构建的 Planner-Specialist-Evaluator(PSE) 三角色 Agent 协作框架。它通过顺序发言的 RoundRobinGroupChat 实现状态流转,配合 cycle 级别的循环控制与 token 优化策略,形成一个可重试、可追溯、可审计的自动化交付管线。

二、核心设计

2.1 设计决策 1:用 RoundRobinGroupChat 替代 Function Calling 实现 PSE 状态机

直觉会怎么做 :为 Planner、Specialist、Evaluator 分别定义工具函数(如 plan(), execute(), evaluate()),通过 LLM 的 Function Calling 来选择下一步动作,手动维护状态转移图。

实际怎么做的 :直接使用 AutoGen 的 RoundRobinGroupChat(顺序发言模式),配合自定义的 termination_conditionmax_turns 控制每轮对话长度。外层再套一个 while True 的 cycle 循环,在 run_task() 中根据 _detect_outcome() 的检测结果动态修改下一轮的 current_task 文本注入到对话中。

python 复制代码
# 🤖 用 AutoGen 搭 PSE 三角色闭环:可重试、可追溯、可审计的多智能体框架
def create_pse_team(
    model_client: Optional[OpenAIChatCompletionClient] = None,
    task: Optional[str] = None,
) -> RoundRobinGroupChat:
    if model_client is None:
        model_client = _create_model_client()

    planner = create_planner(model_client, task)
    specialist = create_specialist(model_client, task)
    evaluator = create_evaluator(model_client, task)

    text_term = TextMentionTermination("交付完成") | TextMentionTermination("BLOCKED")
    return RoundRobinGroupChat(
        participants=[planner, specialist, evaluator],
        termination_condition=text_term | ExternalTermination(),
        max_turns=TURNS_PER_CYCLE,
    )

为什么更好:避免了手写状态机的复杂性------LLM 天然理解"谁该说话"(RoundRobin 顺序),而 cycle 级别的控制逻辑集中在 orchestrator 中,通过改写 task 文本实现"修复重试" vs "重新规划"的分歧路径,代码更简洁且易于扩展。

2.2 设计决策 2:双轨重试与上下文控制------PARTIAL 传摘要、FAIL 清上下文

直觉会怎么做:无论 PARTIAL 还是 FAIL,都将完整的对话历史传递给下一轮循环,让 LLM 看到全部上下文。

实际怎么做的 :在 _run_one_cycle() 中,messages 列表被收集用于 trace 记录,但传给下一轮的是改写后的 current_task 文本 (而不是原始对话历史)。对于 PARTIAL,只传递 result.summary(判决摘要,截断至 2000 字);对于 FAIL,传递"基于原始任务重新制定计划"的指令,清空上轮讨论细节。

python 复制代码
# src/autogen_pse/orchestrator.py
async def _run_one_cycle(
    team: RoundRobinGroupChat,
    task: str,
    verbose: bool,
) -> CycleResult:
    tracker = TokenTracker()
    messages = []

    async for msg in team.run_stream(task=task):
        tracker.feed(msg)
        if isinstance(msg, TaskResult):
            continue
        messages.append(msg)
        if verbose:
            _print_message(msg)

    outcome, reason, summary = _detect_outcome(messages)
    chat_log = []
    for m in messages:
        if isinstance(m, TextMessage):
            chat_log.append({"source": m.source, "content": m.content})
    return CycleResult(outcome, summary, tracker.report, chat_log, reason=reason)

而在 run_task() 中,PARTIAL 和 FAIL 的处理逻辑如下:

python 复制代码
if result.outcome == "PARTIAL":
    partial_count += 1
    if partial_count > MAX_PARTIAL_RETRIES:
        current_task = (
            f"连续 PARTIAL {partial_count} 次,已达上限。"
            f"最近原因: {result.reason}。"
            f"请评估是否仍有可交付内容,或宣布 BLOCKED。\n\n"
            f"原始任务: {task}\n\n"
            f"最近判决: {result.summary}"
        )
        continue
    current_task = (
        f"上一轮 Evaluator 判决 PARTIAL (原因: {result.reason})。"
        f"请修复以下问题后重新提交:\n\n{result.summary}"
    )
    continue

if result.outcome == "FAIL":
    fail_count += 1
    fail_rem = MAX_FAIL_RETRIES - fail_count
    if fail_rem < 0:
        if verbose:
            print(f"\n❌ 连续 FAIL {MAX_FAIL_RETRIES}+ 次,强制 BLOCKED")
        break
    current_task = (
        f"上一轮被判 FAIL (原因: {result.reason})。请基于原始任务重新制定计划。"
        f"(剩余重试次数: {fail_rem})\n\n"
        f"原始任务: {task}\n\n"
        f"失败原因: {result.summary}"
    )
    continue

为什么更好:防止 token 爆炸------随着循环次数增加,完整对话历史的 token 量会线性增长,既浪费成本又可能超出模型上下文窗口。通过只传递结构化摘要/改写任务,每轮保持恒定 token 开销,同时确保 Evaluator 的反馈精准传递(PARTIAL 保留问题细节以便修复,FAIL 要求彻底重来)。

2.3 设计决策 3:可扩展的任务架构------注册、规则注入与后处理修正

直觉会怎么做:每个任务硬编码一套 prompt,或通过命令行参数切换不同任务的配置。

实际怎么做的:三层设计------

  1. 任务注册表 _registry.json 统一管理所有任务,CLI 通过 cli.py 的子命令调度
  2. prompt 加载器 prompts.py 支持按 task 参数动态加载 tasks/<name>/prompts/*.md,不存在时回退到 demo 通用 prompt,并通过 _inject_rules(){INVESTMENT_RULES} / {STOP_LOSS_RULES} 占位符替换为任务私密的规则文件内容
  3. 后处理管道portfolio-review/run.py 中,extract_review_from_trace() 从 trace JSON 中逆向提取 Specialist 的最终结论并保存为 Markdown 文件,sanitize_review() 用规则引擎强制修正 LLM 的违规输出(如越界的操作建议、与既定约束冲突的结论)

Prompt 加载与规则注入src/autogen_pse/prompts.py):

python 复制代码
def _load_private_rules(task_dir: Path, name: str) -> str:
    """加载任务私密规则文件,不存在时返回空字符串。"""
    rules_path = task_dir / f"{name}_rules.md"
    if rules_path.exists():
        return rules_path.read_text(encoding="utf-8").strip()
    return ""

def _inject_rules(template: str, name: str, task_dir: Path) -> str:
    """将 {INVESTMENT_RULES} / {STOP_LOSS_RULES} 占位符替换为私密规则文件内容。"""
    rules = _load_private_rules(task_dir, name)
    if not rules:
        return template

    # 替换可能的占位符
    for placeholder in ("INVESTMENT_RULES", "STOP_LOSS_RULES"):
        key = "{" + placeholder + "}"
        template = template.replace(key, rules)
    return template

def load_prompt(name: str, task: str | None = None) -> str:
    """加载指定角色的系统提示词。"""
    if task:
        task_dir = PROMPTS_DIR / task
        prompt_path = task_dir / "prompts" / f"{name}.md"
        if prompt_path.exists():
            template = prompt_path.read_text(encoding="utf-8")
            return _inject_rules(template, name, task_dir / "prompts")

    # 回退到通用提示词
    prompt_path = PROMPTS_DIR / "demo" / "prompts" / f"{name}.md"
    if not prompt_path.exists():
        raise FileNotFoundError(f"提示词文件不存在: {prompt_path}")
    return prompt_path.read_text(encoding="utf-8")

后处理管道tasks/portfolio-review/run.py):

extract_review_from_trace() 从 trace JSON 中逆向提取 Specialist 的最终结论并保存为 Markdown 文件;sanitize_review() 用规则引擎强制修正 LLM 的违规输出,例如:

  • 某些越界的操作建议 → 改为保守观察(除非触发既定阈值)
  • 低置信度的操作建议 → 移除(噪音过多,无实际意义)

后处理的核心思路是:不信任 LLM 的最终输出,用确定性规则兜底修正,确保交付物符合预设约束。

为什么更好:新增任务只需三步(建目录、写三个 prompt、写 run.py),无需改动核心引擎。私密规则文件(如领域专属约束)不进入 prompt 模板,保持模板通用性同时允许任务级定制。后处理修正弥补了 LLM 不可靠性的最后一环,形成"引擎通用化 + 任务私有化 + 输出规则化"的完整闭环。

三、Web Dashboard

除了 CLI 和 Makefile 入口,autogen-pse 还提供了一个基于 FastAPI + SSE 的 Web Dashboard,支持实时查看 PSE 执行过程和历史记录。

核心功能:

  • 一键执行:通过 Web 界面触发任务,SSE 流式输出让 Agent 的每一步思考过程实时可见
  • 执行历史:自动记录每次运行的 trace,支持按时间、判决结果筛选查看
  • Trace 详情:展开每一轮的完整对话,按 Agent 维度统计 Token 消耗
  • 自动 detect prepare :Web 层自动检测任务是否需要数据准备,无需手动运行 prepare

技术实现上,web_server.py 位于项目根目录,使用 FastAPI 提供 REST API + SSE 端点,前端通过 Vite + React 构建。启动方式:

bash 复制代码
make serve    # 启动 Web Dashboard (http://localhost:8080)

四、源码导航

模块 文件 说明
核心编排 orchestrator.py create_pse_team() + run_task() 全生命周期管理,cycle 控制、token 追踪、trace 写入
Agent 工厂 agents.py 三个角色的 AssistantAgent 创建,工具绑定差异(Specialist 只有 read_file,Evaluator 有 run_pytest/ruff)
Prompt 系统 prompts.py 任务级 prompt 加载 + 私密规则注入({STOP_LOSS_RULES} 等占位符替换)
工具函数 tools.py TokenTracker/TokenReport、bash/read_file/run_pytest/run_ruff、RAG 知识库检索
CLI 入口 cli.py 子命令调度,任务注册表驱动
Web 服务 web_server.py FastAPI + SSE 流式输出,自动 detect prepare 时机,trace 查询 API
Demo 任务 tasks/demo/run.py + tasks/demo/prompts/ 快速排序示例,最小可运行任务
示例任务 tasks/portfolio-review/run.py + prepare.py 完整生产级任务,含知识库注入、trace 提取、sanitize 后处理

五、快速开始

bash 复制代码
git clone https://github.com/erishen/autogen-pse.git
cd autogen-pse
cp .env.example .env          # 配置 API Key 和外部路径
uv sync                        # 安装依赖
make demo                      # 运行 quicksort demo

如需运行示例任务:

bash 复制代码
make review                    # 运行 PSE 示例任务
make serve                     # 启动 Web Dashboard (http://localhost:8080)

CLI 交互示例:

bash 复制代码
python cli.py list             # 列出所有可用任务
python cli.py run portfolio-review   # 运行示例任务
python cli.py trace -n 5       # 查看最近 5 次执行 trace

同系列文章

常见问题(FAQ)

Q:PSE 三角色模型由哪三个角色组成? Planner(规划者,负责拆解任务与情报收集)、Specialist(执行者,负责具体产出)、Evaluator(验证闸门,负责用程序化断言校验产出是否合格)。三者分工明确,Evaluator 作为合并闸门确保结果可追溯、可验证。

Q:为什么用 RoundRobinGroupChat 而不是 Function Calling 来实现状态机? Function Calling 把流程控制权交给 LLM,容易产生不可控的工具调用序列与状态漂移;RoundRobinGroupChat 以群聊轮转的方式显式驱动状态转移,每一步都可被程序观测与约束,更契合 PSE 对"可验证"的核心诉求。

Q:PARTIAL 和 FAIL 两种重试策略有什么区别? PARTIAL 表示局部失败,保留已有上下文、仅把错误摘要回灌给相关角色继续修正,成本较低;FAIL 表示严重失败,清空上下文从头重跑,正确性优先但成本更高。框架按错误严重度自动选择。

Q:如何在不改动核心的前提下扩展新的任务类型? 通过注册机制接入新任务,配合规则注入与后处理修正两个扩展点:规则注入决定任务约束,后处理修正对产出做最终格式化与校验,核心状态机保持不变。

Q:PSE 适合用在什么场景? 任何需要"可验证、可追溯、可控成本"的多智能体生成任务都适合,例如代码生成、技术文档撰写、数据分析报告等------只要产出错误代价高、不能靠 LLM 自我感觉良好来判断。

相关阅读

英文版:erishen.cn/autogen-pse...
📌 本文首发于 erishen.cn,持续更新与更多技术文章请访问站点。

相关推荐
肠畔码农1 小时前
深度解析 RocketMQ 死信队列(DLQ):消费重试的终点站、存储隔离与架构容灾本质
架构·rocketmq
小小龙学IT1 小时前
Qt 模型/视图框架(Model/View)深度解析:从 MVC 演化到数据展示架构的王者
qt·架构·mvc
迅利科技1 小时前
新能源汽车研发升级|CATIA 电气流体一体化如何应对整车 E/E 架构挑战
架构·汽车
HAHAXX81 小时前
Copilot 介入 RPA 架构设计:大模型时代 RPA 二次开发架构该如何演进
架构·copilot·rpa
CRMEB1 小时前
商城大促活动策划全流程:从定目标到复盘四阶段
java·开发语言·人工智能·ai·开源·php
vivo互联网技术1 小时前
协同文档下的 Agent 协作闭环:可回滚、可对比的透明化编辑实现
人工智能·笔记·agent
Patrick_Wilson2 小时前
当执行不再稀缺:AI Agent 时代的技术判断力
人工智能·架构·ai编程
Vuji2 小时前
Pi 插件解剖|ssh.ts:只用 221 行,让 Agent 直接在远程机器干活
前端·人工智能·agent
dong_junshuai2 小时前
每天一个开源项目#73 Munder Difflin:2.3K Star 的本地多Agent办公室
开源·github·agent