1- # 使用Microsoft Agent Framework框架创建一个带有审批功能的终端Agent
1+ # WPF/C#: 使用Microsoft Agent Framework框架创建一个带有审批功能的终端Agent
22
33## 前言
44
5- 在AI辅助开发领域,< span style = " color : dodgerblue ; " >Microsoft Agent Framework</ span >为我们提供了强大的工具来构建智能Agent。对于需要执行敏感操作 (如系统命令)的场景,人工审批机制显得尤为重要。本文以Rouyan为例,说明如何使用Microsoft Agent Framework创建一个能够执行终端命令并具备人工审批功能的WPF应用。
5+ 最近新出了一个Microsoft Agent Framework框架,我感觉还挺有意思的,就通过它的那个 ` Using function tools with human in the loop approvals ` 例子,做了一个终端助手Agent。我觉得使用这个作为学习人在环上这个例子蛮合适的,因为对于需要执行敏感操作 (如系统命令)的场景,人工审批机制显得尤为重要。本文以Rouyan为例,说明如何使用Microsoft Agent Framework创建一个能够执行终端命令并具备人工审批功能的WPF应用。
66
7- ## Microsoft Agent Framework简介
7+ 在详细介绍之前,先来看看它的效果。
88
9- < span style = " color : dodgerblue ; " >Microsoft Agent Framework</ span >是微软推出的AI Agent开发框架,它提供了:
9+ 1、比如获取当前时间
1010
11- * 1、AIAgent:核心Agent类,支持工具调用和流式响应 *
11+ 会先弹出一个人工审批窗口:
1212
13- * 2、AgentThread:管理对话上下文和状态 *
13+ ![ image-20251017213637660 ] ( https://mingupupup.oss-cn-wuhan-lr.aliyuncs.com/imgs/image-20251017213637660.png )
1414
15- * 3、Function调用:支持自定义函数和工具集成 *
15+ 然后你点击同意了才会执行:
1616
17- * 4、审批机制:内置的人工审批流程支持 *
17+ ![ image-20251017213716893 ] ( https://mingupupup.oss-cn-wuhan-lr.aliyuncs.com/imgs/image-20251017213716893.png )
1818
19- Microsoft Agent Framework项目地址: * https://github.com/microsoft/agents *
19+ 如果你拒绝了就是这样:
2020
21- ## 核心架构设计
21+ ![ image-20251017213755043 ] ( https://mingupupup.oss-cn-wuhan-lr.aliyuncs.com/imgs/image-20251017213755043.png )
2222
23- Rouyan中的终端Agent采用了以下架构:
23+ 实际上你可以利用终端做很多事情,我再举一个例子。
2424
25- ```
26- 用户输入 → AI分析 → 生成命令 → 人工审批 → 执行命令 → 返回结果
27- ```
25+ 2、新建一个文件,写入你好:
26+
27+ ![ image-20251017214320364] ( https://mingupupup.oss-cn-wuhan-lr.aliyuncs.com/imgs/image-20251017214320364.png )
28+
29+ 选择同意,结果如图所示:
30+
31+ ![ image-20251017214407194] ( https://mingupupup.oss-cn-wuhan-lr.aliyuncs.com/imgs/image-20251017214407194.png )
32+
33+ ![ image-20251017214543527] ( https://mingupupup.oss-cn-wuhan-lr.aliyuncs.com/imgs/image-20251017214543527.png )
34+
35+ 在介绍如何具体实现之前,先来介绍一下Microsoft Agent Framework。
36+
37+ ## Microsoft Agent Framework介绍
38+
39+ GitHub上的简介是:“一个用于构建、编排和部署AI代理及多代理工作流程的框架,支持Python和.NET。”
2840
29- 关键组件包括:
41+ GitHub地址: https://github.com/microsoft/agent-framework
3042
31- * 1、 ** TerminalAgentViewModel ** :主要的业务逻辑控制器 *
43+ ![ image-20251017215713327 ] ( https://mingupupup.oss-cn-wuhan-lr.aliyuncs.com/imgs/image-20251017215713327.png )
3244
33- * 2、 ** HumanApprovalDialogViewModel ** :审批对话框的数据模型 *
45+ Microsoft Agent Framework 是一个开源开发工具包,用于为 .NET 和 Python 构建 AI 代理和多代理工作流。它整合并扩展了 Semantic Kernel 和 AutoGen 项目的思想,融合了两者的优点,并新增了多项功能。该框架由同一团队开发,将成为未来构建 AI 代理的统一基础。
3446
35- * 3、 ** ExecuteCmd函数 ** :实际执行命令的工具函数 *
47+ Agent Framework 提供了两大主要功能类别:
3648
37- * 4、** WaitingViewModel** :等待状态的显示*
49+ AI 代理:单个代理利用大语言模型(LLM)处理用户输入,调用工具和 MCP 服务器执行操作,并生成响应。代理支持的模型提供商包括 Azure OpenAI、OpenAI 和 Azure AI。
50+
51+ 工作流:基于图形的工作流,用于连接多个代理和功能,以执行复杂的多步骤任务。工作流支持基于类型的路由、嵌套、检查点以及适用于人工干预场景的请求/响应模式。
52+
53+ 该框架还提供了基础构建模块,包括模型客户端(聊天补全和响应)、用于状态管理的代理线程、用于代理记忆的上下文提供程序、用于拦截代理操作的中间件,以及用于工具集成的MCP客户端。这些组件共同为您提供灵活性和强大功能,以构建交互性强、稳健且安全的AI应用程序。
54+
55+ ![ image-20251017215856749] ( https://mingupupup.oss-cn-wuhan-lr.aliyuncs.com/imgs/image-20251017215856749.png )
3856
3957## 具体实现
4058
41- ### 第一步:创建AI Agent工具函数
59+ 1、安装Nuget包:
4260
43- 首先定义一个可以执行Windows命令的函数,并使用Microsoft Agent Framework的特性进行标注:
61+ ![ image-20251017220544499] ( https://mingupupup.oss-cn-wuhan-lr.aliyuncs.com/imgs/image-20251017220544499.png )
62+
63+ 2、编写运行脚本的函数
4464
4565``` csharp
4666[Description (" Execute a Windows cmd.exe script and return its output." )]
@@ -81,43 +101,52 @@ static string ExecuteCmd([Description("The script content to run via 'cmd.exe /c
81101}
82102```
83103
84- 关键点:
104+ 3、配置AI Agent
85105
86- - ` [Description] ` 特性:为AI Agent提供函数说明
87- - 参数描述:帮助AI理解如何使用这个函数
88- - 错误处理:确保命令执行的安全性
106+ 文档中只写了Azure中怎么使用,兼容OpenAI格式的可以这样写:
89107
90- ### 第二步:配置AI Agent
108+ ``` csharp
109+ // 配置AI Agent
110+ DotEnv .Load ();
111+ var envVars = DotEnv .Read ();
91112
92- 在` TerminalAgentViewModel.cs:132 ` 中配置AI Agent:
113+ var apiKey = envVars [" OPENAI_API_KEY" ];
114+ var model = envVars [" OPENAI_CHAT_MODEL" ];
115+ var baseUrl = new Uri (envVars [" OPENAI_BASE_URL" ]);
93116
94- ``` csharp
95- AIAgent agent = new OpenAIClient (apiKeyCredential , openAIClientOptions )
96- .GetChatClient (model )
97- .CreateAIAgent (
98- instructions : " 你是一个乐于助人的助手,可以执行命令行脚本。请使用中文回答。" ,
99- tools : [new ApprovalRequiredAIFunction (AIFunctionFactory .Create (ExecuteCmd ))]
100- );
101- ```
117+ ApiKeyCredential apiKeyCredential = new ApiKeyCredential (apiKey );
102118
103- 核心要点:
119+ OpenAIClientOptions openAIClientOptions = new OpenAIClientOptions ();
120+ openAIClientOptions .Endpoint = baseUrl ;
121+
122+ AIAgent agent = new OpenAIClient (apiKeyCredential , openAIClientOptions )
123+ .GetChatClient (model )
124+ .CreateAIAgent (instructions : " 你是一个乐于助人的助手,可以执行命令行脚本。请使用中文回答。" , tools : [new ApprovalRequiredAIFunction (AIFunctionFactory .Create (ExecuteCmd ))]);
125+ ```
104126
105- - ** ApprovalRequiredAIFunction** :将普通函数包装为需要审批的函数
106- - ** AIFunctionFactory.Create** :将静态方法转换为AI可调用的函数
107- - ** instructions** :为Agent设置行为指令
127+ 这里有一个新东西就是` ApprovalRequiredAIFunction ` 。
108128
109- ### 第三步:实现人工审批流程
129+ 这说明如果调用这个函数需要经过人工审批。
110130
111- 处理AI Agent返回的用户输入请求:
131+ 4、审批流程
112132
113133``` csharp
134+ // Call the agent and check if there are any user input requests to handle.
135+ AgentThread thread = agent .GetNewThread ();
136+
114137var response = await agent .RunAsync (InputText , thread );
115138var userInputRequests = response .UserInputRequests .ToList ();
139+ ```
140+
141+ 我们先来看看这个是什么,运行起来打个断点看看:
142+
143+ ![ image-20251017221907635] ( https://mingupupup.oss-cn-wuhan-lr.aliyuncs.com/imgs/image-20251017221907635.png )
144+
145+ 这就是一个Agent想要执行的函数,那么现在来看看如何审批:
116146
147+ ``` csharp
117148while (userInputRequests .Count > 0 )
118149{
119- if (_cts ? .IsCancellationRequested == true ) break ;
120-
121150 var userInputResponses = new List <ChatMessage >();
122151
123152 foreach (var functionApprovalRequest in userInputRequests .OfType <FunctionApprovalRequestContent >())
@@ -137,208 +166,31 @@ while (userInputRequests.Count > 0)
137166 userInputResponses .Add (new ChatMessage (ChatRole .User , [functionApprovalRequest .CreateResponse (approved )]));
138167 }
139168
140- // 将用户审批结果传回Agent
169+ // Pass the user input responses back to the agent for further processing.
141170 response = await agent .RunAsync (userInputResponses , thread );
142171 userInputRequests = response .UserInputRequests .ToList ();
143172}
144173```
145174
146- 审批流程说明:
175+ 根据这个地方 ` userInputResponses.Add(new ChatMessage(ChatRole.User, [functionApprovalRequest.CreateResponse(approved)])); ` 中的approved传入的是true还是false表示用户是同意还是拒绝。
147176
148- 1 . ** 检测审批请求** :通过` UserInputRequests ` 获取需要审批的操作
149- 2 . ** 显示审批对话框** :向用户展示即将执行的命令详情
150- 3 . ** 收集审批结果** :用户同意或拒绝执行
151- 4 . ** 反馈给Agent** :通过` CreateResponse ` 将审批结果传回
177+ 然后发送请求获取新的回复,直到没有需要人工审批的函数为止。
152178
153- ### 第四步:创建审批对话框
179+ 5、流式响应
154180
155- ** HumanApprovalDialogViewModel ** 实现 :
181+ 最后再获取一个流式响应 :
156182
157183``` csharp
158- public class HumanApprovalDialogViewModel : Screen
159- {
160- private string _title = string .Empty ;
161- public string Title
162- {
163- get => _title ;
164- set => SetAndNotify (ref _title , value );
165- }
166-
167- private string _message = string .Empty ;
168- public string Message
169- {
170- get => _message ;
171- set => SetAndNotify (ref _message , value );
172- }
173-
174- // 同意操作
175- public void Approve ()
176- {
177- RequestClose (true );
178- }
179-
180- // 拒绝操作
181- public void Reject ()
182- {
183- RequestClose (false );
184- }
185- }
186- ```
187-
188- ** 审批对话框界面** (HumanApprovalDialogView.xaml):
189-
190- ``` xaml
191- <Window x : Class =" Rouyan.Pages.View.HumanApprovalDialogView"
192- Title =" {Binding Title}"
193- Height =" 200" Width =" 420"
194- WindowStartupLocation =" CenterOwner"
195- ResizeMode =" NoResize"
196- Topmost =" True" >
197- <Window .InputBindings>
198- <KeyBinding Command =" {s:Action Approve}" Key =" Y" />
199- <KeyBinding Command =" {s:Action Reject}" Key =" N" />
200- </Window .InputBindings>
201-
202- <Grid Margin =" 16" >
203- <Grid .RowDefinitions>
204- <RowDefinition Height =" *" />
205- <RowDefinition Height =" Auto" />
206- </Grid .RowDefinitions>
207-
208- <!-- 消息显示区域 -->
209- <ScrollViewer Grid.Row=" 0" VerticalScrollBarVisibility =" Auto" >
210- <TextBlock Text =" {Binding Message}"
211- TextWrapping =" Wrap"
212- FontSize =" 14" />
213- </ScrollViewer >
214-
215- <!-- 按钮区域 -->
216- <Grid Grid.Row=" 1" Margin =" 0,16,0,0" >
217- <Grid .ColumnDefinitions>
218- <ColumnDefinition Width =" *" />
219- <ColumnDefinition Width =" *" />
220- </Grid .ColumnDefinitions>
221-
222- <Button Grid.Column=" 0"
223- Content =" 同意(Y)"
224- Command =" {s:Action Approve}"
225- IsDefault =" True" />
226-
227- <Button Grid.Column=" 1"
228- Content =" 拒绝(N)"
229- Command =" {s:Action Reject}"
230- IsCancel =" True" />
231- </Grid >
232- </Grid >
233- </Window >
234- ```
235-
236- 界面特点:
237-
238- - ** 快捷键支持** :Y键同意,N键拒绝
239- - ** 模态对话框** :` Topmost="True" ` 确保始终在最前
240- - ** Stylet命令绑定** :使用` {s:Action} ` 绑定ViewModel方法
241-
242- ### 第五步:实现流式响应
243-
244- 获取最终结果并流式显示:
245-
246- ``` csharp
247- // 流式获取最终答案
248- await foreach (var update in agent .RunStreamingAsync (" 输出最终答案" , thread ).WithCancellation (_cts ! .Token ))
184+ await foreach (var update in agent .RunStreamingAsync (" 输出最终答案" , thread ))
249185{
250186 OutputText += update .Text ;
251187}
252188```
253189
254- 流式响应的优势:
255-
256- - ** 实时反馈** :用户可以看到实时的输出过程
257- - ** 可取消** :支持` CancellationToken ` 中断操作
258- - ** 更好的用户体验** :避免长时间等待的空白期
259-
260- ## 主界面设计
261-
262- ** TerminalAgentView.xaml** 提供了简洁的用户界面:
263-
264- ``` xaml
265- <Grid Margin =" 16" >
266- <Grid .RowDefinitions>
267- <RowDefinition Height =" Auto" /> <!-- 输入区域 -->
268- <RowDefinition Height =" Auto" /> <!-- 按钮区域 -->
269- <RowDefinition Height =" *" /> <!-- 输出区域 -->
270- </Grid .RowDefinitions>
271-
272- <!-- 输入文本框 -->
273- <TextBox Grid.Row=" 0"
274- Text =" {Binding InputText, UpdateSourceTrigger=PropertyChanged}"
275- AcceptsReturn =" True"
276- Height =" 60" />
277-
278- <!-- 控制按钮 -->
279- <StackPanel Grid.Row=" 1" Orientation =" Horizontal" >
280- <Button Content =" 运行" Command =" {s:Action Run}" />
281- <Button Content =" 取消" Command =" {s:Action Cancel}" />
282- </StackPanel >
283-
284- <!-- 输出显示 -->
285- <TextBox Grid.Row=" 2"
286- Text =" {Binding OutputText, UpdateSourceTrigger=PropertyChanged}"
287- IsReadOnly =" True" />
288- </Grid >
289- ```
290-
291- ## 安全性考虑
292-
293- 在实现过程中,Rouyan采用了多层安全措施:
294-
295- ** 1、人工审批**
296-
297- 所有命令执行都需要用户明确同意,防止恶意操作。
298-
299- ** 2、命令显示**
300-
301- 在审批对话框中完整显示即将执行的命令内容。
302-
303- ** 3、错误处理**
304-
305- 完善的异常捕获和错误信息反馈。
306-
307- ** 4、取消机制**
308-
309- 支持在任何阶段取消操作,避免意外执行。
310-
311- ## 实际使用效果
312-
313- 用户在输入框中输入请求,例如"获取当前时间":
314-
315- 1 . ** 显示等待窗体** :提示正在分析请求
316- 2 . ** AI分析** :Agent理解请求并生成相应的命令
317- 3 . ** 审批对话框** :显示具体要执行的命令(如` date /t ` )
318- 4 . ** 执行命令** :用户同意后执行并获取结果
319- 5 . ** 流式显示** :实时显示执行结果
320-
321- ## 扩展可能性
322-
323- 基于这个架构,可以进一步扩展:
324-
325- * 1、** 多种工具支持** :添加文件操作、网络请求等工具*
326-
327- * 2、** 审批级别** :根据命令危险程度设置不同审批级别*
328-
329- * 3、** 历史记录** :保存执行历史和审批记录*
330-
331- * 4、** 权限管理** :不同用户拥有不同的执行权限*
332-
333- ## 总结
334-
335- 通过Microsoft Agent Framework,我们成功创建了一个安全可靠的终端Agent。关键成功因素包括:
190+ ## 最后
336191
337- - ** 合理的架构设计** :清晰的组件分离和职责划分
338- - ** 完善的审批机制** :确保所有敏感操作都经过人工确认
339- - ** 良好的用户体验** :流式响应和实时反馈
340- - ** 充分的安全考虑** :多层防护措施
192+ 以上就是本期的全部内容,希望对你有所帮助。
341193
342- 这个实现为构建更复杂的AI Agent应用提供了良好的基础和参考 。
194+ 全部代码已上传至GitHub,地址: https://github.com/Ming-jiayou/Rouyan 。
343195
344- 项目地址: https://github.com/Ming-jiayou/Rouyan
196+ 终端助手的代码主要在src/Rouyan/Pages/ViewModel/TerminalAgentViewModel.cs中。
0 commit comments