Skip to content

Commit bce64d5

Browse files
committed
增加使用Microsoft Agent Framework框架创建一个带有审批功能的终端Agent文档
1 parent 029019a commit bce64d5

File tree

1 file changed

+82
-230
lines changed

1 file changed

+82
-230
lines changed
Lines changed: 82 additions & 230 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,66 @@
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+
114137
var response = await agent.RunAsync(InputText, thread);
115138
var 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
117148
while (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

Comments
 (0)