AutoGen(.NET) UserProxyAgent 详解:用 ALWAYS / NEVER / AUTO 三种模式构建人类输入代理 Agent
2026/9/7 18:17:52 网站建设 项目流程

AutoGen(.NET) UserProxyAgent 详解:用 ALWAYS / NEVER / AUTO 三种模式构建人类输入代理 Agent

【免费下载链接】autogenA programming framework for agentic AI项目地址: https://gitcode.com/GitHub_Trending/au/autogen

本文围绕 AutoGen(.NET) 框架中的UserProxyAgent展开。它是一类特殊的 Agent,负责把终端用户的真实输入"代理"给另一个 Agent 或一组 Agent(群聊)使用,是人机协作(Human-in-the-Loop)场景的核心组件。读完本文,你将掌握:如何以三种HumanInputMode模式创建和使用UserProxyAgent、它与AssistantAgent的等价关系,以及底层HumanInputMiddleware的完整源码实现——包括退出关键字、终止消息([GROUPCHAT_TERMINATE])和可定制输入管道等细节。

一、UserProxyAgent 是什么

UserProxyAgent是一个用于代理用户输入的特殊 Agent:在两个 Agent 对话时,由它代表"人类"出场,将用户敲入的文本作为回复消息发给接收方(另一个 Agent 或群聊管理器)。其定义位于 UserProxyAgent.cs,它直接继承自ConversableAgent,本身没有额外的实例逻辑,全部行为由基类构造参数驱动。

它支持三种人类输入模式(HumanInputMode枚举,定义于 ConversableAgent.cs):

模式取值行为
ALWAYS1永远向用户询问输入,把输入作为回复
NEVER0从不询问用户;使用默认回复(defaultReply,如果设置),或回退到底层 LLM 模型生成回复(如果提供了llmConfig
AUTO2仅当对话被对方 Agent 以终止消息结束时才询问用户;其余情况使用默认回复或底层 LLM 生成回复

提示:创建AssistantAgent时同样可以通过humanInputMode参数启停人类输入。UserProxyAgent等价于humanInputMode设为ALWAYSAssistantAgent;反过来,AssistantAgent等价于humanInputMode设为NEVERUserProxyAgent(详见第四节源码对照)。

构造函数完整参数

UserProxyAgent.cs 的构造函数签名如下,所有参数透传给基类ConversableAgent

public UserProxyAgent( string name, // Agent 名称,用于消息的 From 字段 string systemMessage = "You are a helpful AI assistant", // 系统提示词 ConversableAgentConfig? llmConfig = null, // 底层 LLM 配置;AUTO/NEVER 模式下作为回退生成器 Func<IEnumerable<IMessage>, CancellationToken, Task<bool>>? isTermination = null, // AUTO 模式的终止判定函数 HumanInputMode humanInputMode = HumanInputMode.ALWAYS, // 人类输入模式,默认 ALWAYS IDictionary<string, Func<string, Task<string>>>? functionMap = null, // 函数调用映射表 string? defaultReply = null) // 默认回复文本

注意两个默认值的差异:UserProxyAgenthumanInputMode默认为ALWAYS(天然代表人类),而未显式提供llmConfig时,它在NEVER/AUTO回退路径上会落到DefaultReplyAgent,若连defaultReply也未设置,则回复固定为Default reply is not set. Please pass a default reply to assistant agent(见 ConversableAgent.cs)。

二、创建并使用 UserProxyAgent

1. 最小示例:ALWAYS 模式 + SendAsync

这是官方文档给出的最小可用片段,源码位于 UserProxyAgentCodeSnippet.cs(code_snippet_1区域):

// create a user proxy agent which always ask user for input var agent = new UserProxyAgent( name: "user", humanInputMode: HumanInputMode.ALWAYS); await agent.SendAsync("hello");

运行后,用户代理 Agent 会向控制台打印输入提示、读取用户输入,并把该输入作为对"hello"这条消息的回复返回。对应的控制台交互效果如开头的截图所示。

SendAsync(string)AutoGen.Core提供的扩展方法(AgentExtension.cs):它会把字符串包装成TextMessage(Role.User, message),再调用GenerateReplyAsync完成一次"收消息 → 出回复"的往返。

2. 实战示例:UserProxyAgent 与 OpenAIChatAgent 对话

更贴近真实场景的示例见 Example06_UserProxyAgent.cs:

var gpt4o = LLMConfiguration.GetOpenAIGPT4o_mini(); var assistantAgent = new OpenAIChatAgent( chatClient: gpt4o, name: "assistant", systemMessage: "You are an assistant that help user to do some tasks.") .RegisterMessageConnector() .RegisterPrintMessage(); // set human input mode to ALWAYS so that user always provide input var userProxyAgent = new UserProxyAgent( name: "user", humanInputMode: HumanInputMode.ALWAYS) .RegisterPrintMessage(); // start the conversation await userProxyAgent.InitiateChatAsync( receiver: assistantAgent, message: "Hey assistant, please help me to do some tasks.", maxRound: 10);

要点说明:

  • RegisterPrintMessage()是打印中间件,用于在控制台可视化每条消息,示例中给双方 Agent 都挂上了;
  • InitiateChatAsync(AgentExtension.cs)会创建一条以From = agent.Name的初始Role.User消息,然后把调用方和接收方组成一个RoundRobinGroupChat进行多轮往返,maxRound控制最大轮数(默认 10),返回完整聊天历史;
  • ALWAYS模式下,每一轮到user发言时都会阻塞等待用户输入;用户输入exit即结束整个群聊循环(原理见下节);
  • 运行该示例需要配置 OpenAI 凭据(示例中的LLMConfiguration从环境变量读取),并运行dotnet/samples/AgentChat/AutoGen.Basic.Sample项目。

三、源码级原理:HumanInputMiddleware 的三种模式分支

三种输入模式的实际执行逻辑集中在 HumanInputMiddleware.cs 的InvokeAsync(L41-L85),ConversableAgent.GenerateReplyAsync在每次生成回复前都会把它注册进中间件链:

// process order: function_call -> human_input -> inner_agent -> default_reply -> self_execute // first in, last out ... // process human input var humanInputMiddleware = new HumanInputMiddleware(mode: this.humanInputMode, isTermination: this.IsTermination); agent.Use(humanInputMiddleware);

NEVER:直接透传给底层 Agent

if (mode == HumanInputMode.NEVER) { return await agent.GenerateReplyAsync(context.Messages, context.Options, cancellationToken); }

不产生任何交互。此时回复由链条上更内层的 Agent 生成:如果构造时传入了llmConfig,则是底层 LLM Agent(OpenAIChatAgent等);否则是DefaultReplyAgent,返回defaultReply

ALWAYS:无条件询问用户

if (mode == HumanInputMode.ALWAYS) { this.writeLine(prompt); var input = getInput(); if (input == exitKeyword) { return new TextMessage(Role.Assistant, GroupChatExtension.TERMINATE, agent.Name); } input ??= string.Empty; return new TextMessage(Role.Assistant, input, agent.Name); }
  • 默认提示语为Please give feedback: Press enter or type 'exit' to stop the conversation.,退出关键字为exit
  • 输入exit时返回一条内容为GroupChatExtension.TERMINATE(即常量字符串"[GROUPCHAT_TERMINATE]",定义于 GroupChatExtension.cs)的助手消息;
  • 群聊循环在 GroupChatExtension.SendAsync 中检测到终止消息后会yield break,这正是用户输入exit能结束整段对话的原因;
  • 直接回车(input为 null)会被归一化为空字符串string.Empty作为回复。

AUTO:对话未终止时走底层回复,终止时才找人

if (mode == HumanInputMode.AUTO) { if (await isTermination(context.Messages, cancellationToken) is false) { return await agent.GenerateReplyAsync(context.Messages, context.Options, cancellationToken); } // ... 与 ALWAYS 相同的询问逻辑 }

终止判定默认实现(HumanInputMiddleware.cs)是检查聊天历史最后一条消息是否包含群聊终止标记:

private async Task<bool> DefaultIsTermination(IEnumerable<IMessage> messages, CancellationToken _) { return messages?.Last().IsGroupChatTerminateMessage() is true; }

这意味着 AUTO 模式的典型用法是:让 Agent 先自主跑完整个任务,直到对方发出[GROUPCHAT_TERMINATE],人类才介入做总结性反馈。也可以传入自定义isTermination函数改写这一判定。

可定制的输入/输出管道

HumanInputMiddleware构造函数(L25-L39)暴露了五个可注入项,这是文档之外的一个实用扩展点:

参数默认值说明
promptPlease give feedback: Press enter or type 'exit' to stop the conversation.询问用户的提示语
exitKeywordexit触发终止消息的关键字
modeHumanInputMode.AUTO输入模式
getInputConsole.ReadLine读取输入的委托,可替换为 Web 请求、数据库轮询等任意实现
writeLineConsole.WriteLine打印提示的委托,可重定向到日志或前端

getInputFunc<string?>类型,非阻塞场景下可替换为异步轮询外部输入源后再同步返回——从源码结构看,这是把UserProxyAgent从"控制台 Agent"改造为"服务化人机接口"的关键注入点。

四、UserProxyAgent 与 AssistantAgent:同一个基类的两个默认值

对照两个子类源码可以看到"等价关系"的实质:

  • AssistantAgent.cs:humanInputMode默认参数为HumanInputMode.NEVER,即默认永不询问用户;
  • UserProxyAgent.cs:humanInputMode默认参数为HumanInputMode.ALWAYS,即默认永远询问用户。

两者构造函数参数列表完全一致,都转发给ConversableAgent的 LLM 配置构造函数。因此官方文档的结论成立:显式指定humanInputMode后,两个类可以互相替代——new AssistantAgent(name, humanInputMode: HumanInputMode.ALWAYS)new UserProxyAgent(name)行为相同。命名差异只是语义提示:UserProxyAgent暗示"我代表人",AssistantAgent暗示"我代表模型"。

五、消息处理管线与回复生成顺序

理解UserProxyAgent何时"问人"、何时"用模型",需要看 ConversableAgent.GenerateReplyAsync 的组装逻辑:

  1. 系统消息补齐:若历史中没有Role.System消息,会把构造时的systemMessage插到消息头;
  2. 内层 Agent 选择:有llmConfig时,根据配置类型(AzureOpenAIConfig/OpenAIConfig/LMStudioConfig)创建对应的OpenAIChatAgent;无配置时用DefaultReplyAgent(this.Name, defaultReply ?? "Default reply is not set...")兜底(L173-L176);
  3. 中间件注册顺序:注释明确写出处理顺序为function_call -> human_input -> inner_agent -> default_reply -> self_execute,且遵循 "first in, last out"——FunctionCallMiddleware在外、HumanInputMiddleware居中、内层 Agent(LLM 或默认回复)在最内。

由此可以推断出一条清晰的优先级链:先尝试函数调用,再按模式决定是否交给人类,最后才轮到 LLM / 默认回复兜底。UserProxyAgent因为默认ALWAYS,在中间件阶段就把回复权交给了用户,内层默认回复实际上只在用户直接回车(返回空串)时才显得无关紧要。

六、小结与适用建议

  • 需要逐轮人工确认/纠偏的交互式任务:使用UserProxyAgent+HumanInputMode.ALWAYS,配合InitiateChatAsync(maxRound: ...)控制上限,输入exit随时终止;
  • 需要Agent 自主跑完、人工只参与收尾的批处理式任务:使用HumanInputMode.AUTO,可结合自定义isTermination精确控制介入时机;
  • 需要完全无交互、以模型或固定文本回复:使用AssistantAgentNEVER)并设置defaultReply,或为UserProxyAgent传入llmConfig让模型接管。

所有相关实现与示例均可在当前仓库中验证:核心实现在 dotnet/src/AutoGen 目录(Agent/UserProxyAgent.csAgent/ConversableAgent.csMiddleware/HumanInputMiddleware.cs),可复制运行的演示在 dotnet/samples/AgentChat/AutoGen.Basic.Sample 的Example06_UserProxyAgent.csCodeSnippet/UserProxyAgentCodeSnippet.cs

【免费下载链接】autogenA programming framework for agentic AI项目地址: https://gitcode.com/GitHub_Trending/au/autogen

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询