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):
| 模式 | 取值 | 行为 |
|---|---|---|
ALWAYS | 1 | 永远向用户询问输入,把输入作为回复 |
NEVER | 0 | 从不询问用户;使用默认回复(defaultReply,如果设置),或回退到底层 LLM 模型生成回复(如果提供了llmConfig) |
AUTO | 2 | 仅当对话被对方 Agent 以终止消息结束时才询问用户;其余情况使用默认回复或底层 LLM 生成回复 |
提示:创建
AssistantAgent时同样可以通过humanInputMode参数启停人类输入。UserProxyAgent等价于humanInputMode设为ALWAYS的AssistantAgent;反过来,AssistantAgent等价于humanInputMode设为NEVER的UserProxyAgent(详见第四节源码对照)。
构造函数完整参数
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) // 默认回复文本注意两个默认值的差异:UserProxyAgent的humanInputMode默认为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)暴露了五个可注入项,这是文档之外的一个实用扩展点:
| 参数 | 默认值 | 说明 |
|---|---|---|
prompt | Please give feedback: Press enter or type 'exit' to stop the conversation. | 询问用户的提示语 |
exitKeyword | exit | 触发终止消息的关键字 |
mode | HumanInputMode.AUTO | 输入模式 |
getInput | Console.ReadLine | 读取输入的委托,可替换为 Web 请求、数据库轮询等任意实现 |
writeLine | Console.WriteLine | 打印提示的委托,可重定向到日志或前端 |
getInput是Func<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 的组装逻辑:
- 系统消息补齐:若历史中没有
Role.System消息,会把构造时的systemMessage插到消息头; - 内层 Agent 选择:有
llmConfig时,根据配置类型(AzureOpenAIConfig/OpenAIConfig/LMStudioConfig)创建对应的OpenAIChatAgent;无配置时用DefaultReplyAgent(this.Name, defaultReply ?? "Default reply is not set...")兜底(L173-L176); - 中间件注册顺序:注释明确写出处理顺序为
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精确控制介入时机; - 需要完全无交互、以模型或固定文本回复:使用
AssistantAgent(NEVER)并设置defaultReply,或为UserProxyAgent传入llmConfig让模型接管。
所有相关实现与示例均可在当前仓库中验证:核心实现在 dotnet/src/AutoGen 目录(Agent/UserProxyAgent.cs、Agent/ConversableAgent.cs、Middleware/HumanInputMiddleware.cs),可复制运行的演示在 dotnet/samples/AgentChat/AutoGen.Basic.Sample 的Example06_UserProxyAgent.cs与CodeSnippet/UserProxyAgentCodeSnippet.cs。
【免费下载链接】autogenA programming framework for agentic AI项目地址: https://gitcode.com/GitHub_Trending/au/autogen
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考