Skip to content

第十一阶段:人类审批(human-in-the-loop) ​

前两个阶段,Agent 团队从头跑到尾,中间没人能插手。真实业务里,关键决策往往要人拍板。这一阶段把“你”也加进团队。

一、原理 ​

UserProxyAgent 是代表人类的 Agent。轮到它发言时,它会调用 input_func(这里就是 Python 的 input)等你在终端输入,你输入的内容就是它的发言。

光有它还不够,还要在 selector_prompt 里写好审批规则:

  • analyst 规划完成后,必须先交给 user 审批;
  • user 回复“同意”后,才交给 coder 写代码;
  • user 提出修改意见,就退回 analyst 重新规划。

这样一来,方案得你点头才会执行。

二、代码 11_autogen_human.py ​

python
import os, asyncio
from dotenv import load_dotenv
from autogen_agentchat.agents import AssistantAgent, CodeExecutorAgent, UserProxyAgent
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 的文件名,如 ![销量趋势](sales_trend.png)。"
    "只陈述数据能直接证明的事实,避免“决定了”“升级为”这类强断言,推测要写“可能”“需验证”。")

reviewer = AssistantAgent("reviewer", model_client=model, system_message=
    "你是审核员。只检查两类【阻塞问题】:1)结论里的数字与执行结果不符或自相矛盾;"
    "2)图片或 output/report.md 没有成功保存。"
    "措辞、语气、四舍五入写法之类属于【建议】,列出来即可,不影响通过。"
    "没有阻塞问题就必须在最后单独写一行 APPROVE。"
    "同一份报告最多打回 2 次,第 3 次审核只要没有数字错误就 APPROVE。")

# 轮到它时,终端会出现输入提示,你打的字就是它的发言
user = UserProxyAgent("user", input_func=input)

stop = TextMentionTermination("APPROVE") | MaxMessageTermination(50)

selector_prompt = """你在为数据分析团队选择下一位发言者。成员:
{roles}
规则:analyst 先规划,规划完成后必须选 user 审批;
user 回复“同意”后选 coder,user 提出修改意见则选 analyst 重新规划;
coder 写出代码后必须选 executor;执行报错就选 coder 修复;
数据齐全后选 writer 写报告,再选 coder 保存文件、executor 执行;
文件确认保存后选 reviewer;reviewer 指出措辞问题选 writer,指出代码问题选 coder。
对话记录:
{history}
从 {participants} 中只回复一个名字。"""

team = SelectorGroupChat(
    [analyst, user, coder, executor, writer, reviewer],
    model_client=model,
    selector_prompt=selector_prompt,
    termination_condition=stop,
    allow_repeated_speaker=True,
)

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())

三、踩坑一:看起来卡住了,没法输入 ​

运行后,终端停在那里不动,像卡死了一样。原因有两个:

  1. 重名:任务消息的发送者默认也叫 user,和 UserProxyAgent("user") 撞名。终端里分不清哪条是任务、哪条是在等你回复。
  2. 没有提示语:input_func=input 调用 input() 时不带提示文字,终端只剩一个光标,看不出它在等你输入。

建议:给这个 Agent 改个名(比如 boss),再给 input_func 加上提示语,例如:

python
boss = UserProxyAgent("boss", input_func=lambda _: input("请审批(输入“同意”或修改意见):"))

注意 selector_prompt 里的 user 也要一起改成 boss。

四、实测 ​

  1. analyst 给出方案后轮到人审批。输入:“不要做经营建议,改成预测下个月各产品销量”;
  2. analyst 根据意见调整了方案,再次交来审批。输入:“同意”;
  3. 这时 coder 才开始写代码,然后执行、写报告;
  4. 最后 reviewer 把阻塞问题和建议分开列出,判定没有阻塞问题,输出 APPROVE。

人的意见真的改变了团队的走向,这就是 human-in-the-loop。

五、现象:Agent 以为自己能调用工具 ​

输出里出现了类似 <|DSML|...calls> 的奇怪标记,还有 Agent 说“我跑了 bash 但没结果”。

原因:deepseek 模型训练时见过很多调用工具的场景,所以以为自己能调用工具,就自己“假装”调用了一次。实际上这些 AssistantAgent 并没有配置任何工具,真正能执行代码的只有 executor。这属于角色越界。

修法:在相关 Agent 的提示词里写明:“你没有任何工具,不能执行代码,只用文字输出。”

六、CodeExecutorAgent 的安全警告 ​

运行时 AutoGen 会给出安全警告:CodeExecutorAgent 会直接执行模型写的代码。除了生产环境换成 Docker 沙箱,还可以用 approval_func 在每次执行代码前加人工审批:先给你看要运行的代码,你同意了才执行。这和本节的思路一样,都是把危险动作的决定权交给人。

项目 3 小结 ​

  • 第九阶段:多 Agent 分工、互相纠错(RoundRobinGroupChat)。
  • 第十阶段:画图出报告、SelectorGroupChat 按需选人、防止审核死循环。
  • 第十一阶段:人类审批,关键步骤由人拍板。

下一阶段预告 ​

进入项目 4:分布式 RAG 服务。用 FastAPI、Docker、Redis、Celery、Qdrant,把项目 1 的知识库问答做成可以多人同时使用的服务。

👉 下一阶段:第十二阶段:Docker 安装与 FastAPI 问答接口