Appearance
第十阶段:报告生成、SelectorGroupChat 与审核死循环
上一阶段团队只会在终端里打印结论。这一阶段让它画图、写报告、保存文件。下面按实际踩坑的顺序讲。
一、新增 writer,coder 负责画图和存文件
- 新增 writer(报告撰写员):根据执行结果写 Markdown 报告,并用
引用图片。 - coder 用 matplotlib 画图:
- 开头写
matplotlib.use('Agg')。Agg 是不弹窗口、只输出文件的后端,适合由程序自动执行的代码。 - 中文字体设成
Microsoft YaHei,否则中文会显示成方框。 - 用
plt.savefig把 png 保存到output/,不要调用plt.show()。 - writer 写好报告后,coder 再写代码把它保存为
output/report.md。
- 开头写
二、第一次:RoundRobin、上限 30,修到一半就停了
审核员很尽责,抓出一个矛盾:报告说“2026-04 是全品类共同低点”,但鼠标的销量恰恰在 4 月达到峰值。可是还没修完,消息数就到了 30 条的上限,团队被强制停止。
原因:RoundRobin 是死板的轮流发言。比如只需要 writer 改一句话,也要等 coder、executor 等人依次说完一轮,轮次被白白浪费。
三、第二次:coder 写复杂代码,自己把自己搞崩
这次 coder 自己写了一个通用的表格格式化函数,对每一列都用 :,.0f 格式化,连字符串类型的“月份”列也不例外。结果报错:
ValueError: Unknown format code 'f' for object of type 'str'脚本报错了,审核员也就不承认 report.md 是有效产出。
修法:在提示词里要求 coder 写简单代码,不要自己写格式化函数,表格直接用 df.to_markdown()。这个方法依赖 tabulate 库:
bash
pip install tabulate四、换成 SelectorGroupChat
SelectorGroupChat 不再固定轮流,而是让模型根据 selector_prompt 和对话记录挑选下一个发言者。比如代码写好了就选 executor,执行报错了就选 coder,只是措辞有问题就直接选 writer。
allow_repeated_speaker=True 允许同一个人连续发言,比如 coder 可以连续修几次 bug。
五、第三次:审核死循环
流程顺畅了,又出现新问题:审核员每一轮都能挑出新的措辞毛病,比如“幅度接近”不够准确、某句话“过于肯定”、“99.99% 四舍五入”的写法不规范……writer 改一处,审核员又挑另一处,一直不给 APPROVE。
原因:提示词没有告诉审核员什么算“必须改”,它就把所有能挑的毛病都当成不通过的理由。
修法:
- 区分阻塞问题和建议。只有两类问题会阻塞通过:结论里的数字与执行结果不符或自相矛盾;图片或报告文件没有保存成功。措辞类问题只列为建议,不影响通过。
- 最多打回 2 次,第 3 次审核只要没有数字错误就 APPROVE。
- 从源头减少毛病:要求 writer 避免强断言,推测性的内容写成“可能”“需验证”。
六、最终代码与结果
最终版使用 SelectorGroupChat,消息上限是 50 条。10_autogen_report.py:
python
import os, asyncio
from dotenv import load_dotenv
from autogen_agentchat.agents import AssistantAgent, CodeExecutorAgent
from autogen_agentchat.teams import RoundRobinGroupChat
from autogen_agentchat.teams import SelectorGroupChat
from autogen_agentchat.conditions import TextMentionTermination, MaxMessageTermination
from autogen_agentchat.ui import Console
from autogen_ext.models.openai import OpenAIChatCompletionClient
from autogen_ext.code_executors.local import LocalCommandLineCodeExecutor
load_dotenv()
# 模型客户端:非 OpenAI 官方的模型要写 model_info,告诉 AutoGen 这个模型具备哪些能力
model = OpenAIChatCompletionClient(
model=os.getenv("LLM_MODEL"),
base_url=os.getenv("LLM_BASE_URL"),
api_key=os.getenv("LLM_API_KEY"),
model_info={"vision": False, "function_calling": True, "json_output": True,
"family": "unknown", "structured_output": False},
)
# 1. 分析师:拆解任务、提出分析思路
analyst = AssistantAgent("analyst", model_client=model, system_message=
"你是数据分析师。根据任务提出清晰的分析步骤,交给 coder 写代码。看到执行结果后给出业务结论。")
# 2. 程序员:只负责写代码
coder = AssistantAgent("coder", model_client=model, system_message=
"你是 Python 程序员。按分析师的要求写 pandas 代码,放在 ```python 代码块里。"
"数据文件是 data/sales.csv(utf-8-sig 编码)。用 print 输出关键数字。"
"需要画图时用 matplotlib,开头加 import matplotlib; matplotlib.use('Agg'),"
"中文字体用 plt.rcParams['font.sans-serif']=['Microsoft YaHei'],"
"图片用 plt.savefig 保存到 output/ 目录(先 os.makedirs('output', exist_ok=True)),不要 plt.show()。"
"当 writer 给出报告全文时,写代码把它原样保存为 output/report.md(utf-8)。"
"代码保持简单:不要自己写通用的表格格式化函数,表格用 df.to_markdown()。")
# 3. 执行器:真的去运行 coder 写的代码,结果交回群聊
executor = CodeExecutorAgent("executor",
code_executor=LocalCommandLineCodeExecutor(work_dir="."))
writer = AssistantAgent("writer", model_client=model, system_message=
"你是报告撰写员。根据执行结果写 Markdown 报告:标题、数据概况、结论(带数字)、"
"用  引用 output/ 里的图片、经营建议。"
"图片路径写相对 report.md 的文件名,如 。"
"只陈述数据能直接证明的事实,避免“决定了”“升级为”这类强断言,推测要写“可能”“需验证”。")
reviewer = AssistantAgent("reviewer", model_client=model, system_message=
"你是审核员。只检查两类【阻塞问题】:1)结论里的数字与执行结果不符或自相矛盾;"
"2)图片或 output/report.md 没有成功保存。"
"措辞、语气、四舍五入写法之类属于【建议】,列出来即可,不影响通过。"
"没有阻塞问题就必须在最后单独写一行 APPROVE。"
"同一份报告最多打回 2 次,第 3 次审核只要没有数字错误就 APPROVE。")
stop = TextMentionTermination("APPROVE") | MaxMessageTermination(50)
selector_prompt = """你在为数据分析团队选择下一位发言者。成员:
{roles}
规则:analyst 先规划;需要代码时选 coder;coder 写出代码后必须选 executor;
执行报错就选 coder 修复;数据齐全后选 writer 写报告,再选 coder 保存文件、executor 执行;
文件确认保存后选 reviewer;reviewer 指出措辞问题选 writer,指出代码问题选 coder。
对话记录:
{history}
从 {participants} 中只回复一个名字。"""
team = SelectorGroupChat(
[analyst, coder, executor, writer, reviewer],
model_client=model,
selector_prompt=selector_prompt,
termination_condition=stop,
allow_repeated_speaker=True, # 允许同一个人连着说,比如 coder 连续修 bug
)
task = """分析 data/sales.csv(utf-8-sig 编码)。
表结构:列为 月份(字符串, 如 2026-01)、产品、销量、单价、销售额;共 18 行,是“月份×产品”的汇总表。
要求:1)各产品总销售额,画柱状图 output/sales_by_product.png;
2)各产品月度销量趋势,画折线图 output/sales_trend.png;
3)给出经营建议;4)整理成报告保存为 output/report.md。"""
async def main():
await Console(team.run_stream(task = task))
asyncio.run(main())运行 python 10_autogen_report.py 后,审核员最终输出 APPROVE,output/ 里生成了 sales_by_product.png、sales_trend.png 和 report.md。
七、不要提交生成的文件
output/ 里都是每次运行重新生成的文件,已经加入 .gitignore,不会提交到仓库。
小结
- 让 Agent 写简单代码,少出错。
- 不是严格按顺序干活的流程,用 SelectorGroupChat 按需选人,比 RoundRobin 省轮次。
- 审核员要有明确的通过标准和打回次数上限,否则会陷入死循环。
下一阶段预告
第十一阶段:给 Agent 加上人类审批(UserProxyAgent / human-in-the-loop),关键步骤由你拍板,然后才继续往下执行。