大家好,我是Edison。
最近我一直在跟着圣杰的《.NET+AI智能体开发进阶》课程学习MAF开发多智能体工作流,我强烈推荐你也上车跟我一起出发!
上一篇,我们初步学习了Agent Skill的基本概念以及在MAF中实现了一个Agent Skill的案例。本篇,我们来了解下Agent Skill如何集成脚本执行能力。
1 Agent Skill中的执行脚本
根据上一篇我们知道,Agent Skill就是大模型随时翻阅的说明文档,它还可以包含一些资源文件,而脚本就是其中的一种重要资源。
skill/
├── SKILL.md # 必需:指令 + 元数据
├── scripts/ # 可选:可执行代码
├── references/ # 可选:文档资料
└── assets/ # 可选:模板、资源文件
scripts子目录
这些脚本会被放在其所属skill目录下的scripts子目录下,不管它是BASH脚本,PowerShell脚本,又或者是Python/C#代码,只要是Agent所在客户/服务端能够执行的都可以。
总结,Skills不仅让Agent知道处理规则(SKILL.md + references),还能让Agent直接上手处理(scripts) 。
工具 vs 技能
工具(Tool)是一种能力,而技能(Skill)是一种知识。比如,自己开发一个PowerShellTools,给模型提供一些run shell的能力,我们通过注册到 ChatOptions.Tools 完成加载。
又如,自己定义一个系统运维Skill,给模型提供一些系统运维的知识,我们通过注册到AIContextProviders完成加载。
知识指导行为,而非工具限定行为。
2 快速开始:MAF中集成Shell执行能力
假设我们有这样一个需求:某企业开发了一个IT助手Agent,它运行在每个员工的笔记本电脑上,可以回答用户关于系统方面的一些问题并加以分析给出建议。
例如,很多员工发现自己的笔记本电脑最近越来越慢了,又或者发现内存一直高负载 等等,在以前员工会直接联系IT部的某位员工,而现在IT部将自身的运维经验封装为了一个Skill并通过Agent直接回复员工。
以终为始,我们的Agent应用的解决方案长这样子:这是一个.NET控制台应用程序,其中有一个skills目录,存放了系统运维领域的知识,这是IT部呕心沥血整理的"宝典"。

系统运维Skill
首先,我们创建这个SKILL.md,内容如下:
---
name: system-ops
description: 系统运维诊断技能。适用于系统健康检查、磁盘空间分析、进程资源监控、故障排查等系统运维场景。包含可执行的诊断脚本。
---
# 系统运维(System Operations)
## 可用诊断脚本
以下脚本位于本技能的 `scripts/` 目录,可通过 `run_shell` 工具执行:
| 脚本 | 用途 | 执行命令 |
|------|------|--------|
| check-system-info.ps1 | 获取系统基本信息(OS、CPU、内存) |
| check-disk-usage.ps1 | 检查磁盘使用情况和剩余空间 |
| check-top-processes.ps1 | 查看 CPU/内存占用 Top 进程 |
## 运维检查流程
1. **基础检查**:先执行 `check-system-info.ps1` 获取系统概况
2. **针对性诊断**:根据用户问题,选择性执行磁盘或进程检查脚本
3. **分析报告**:综合脚本输出和故障排查指南,给出诊断结论和建议
4. **故障排查**:如需深入排查,参考 [references/troubleshooting-guide.md](references/troubleshooting-guide.md)
## 告警阈值
| 指标 | 正常 | 警告 | 严重 |
|------|------|------|------|
| CPU 使用率 | <70% | 70-90% | >90% |
| 内存使用率 | <80% | 80-95% | >95% |
| 磁盘使用率 | <70% | 70-90% | >90% |
| 单进程 CPU | <30% | 30-60% | >60% |
由于该Skill还定义了引用的资源,所以我们还需要添加:
(1)参考文件:故障自查指南
说明:这是一个markdown文件。
# 故障排查指南
## CPU 持续高负载
1. 通过 `check-top-processes.ps1` 识别高占用进程
2. 判断是否为预期行为(如编译、数据处理)
3. 如为异常进程,建议:
- 记录进程信息(PID、启动时间、命令行)
- 联系应用负责人确认
- 必要时可终止进程:`Stop-Process -Id <PID>`
## 内存不足
1. 检查物理内存使用率(通过 `check-system-info.ps1`)
2. 识别内存大户(通过 `check-top-processes.ps1`)
3. 常见原因:
- 内存泄漏:进程内存持续增长
- 缓存过多:数据库或应用缓存未限制
4. 缓解措施:
- 重启内存泄漏进程
- 调整应用缓存配置
- 考虑扩容
## 磁盘空间不足
1. 通过 `check-disk-usage.ps1` 确认各盘使用率
2. 清理建议(按优先级):
- 清理临时文件
- 清理日志文件(保留近 7 天)
- 清理 NuGet 包缓存:`dotnet nuget locals all --clear`
3. 长期方案:配置日志轮转、扩容磁盘
(2)脚本文件:PowerShell脚本
说明:这里主要在Windows系统中执行脚本,所以选择了PowerShell。
脚本1:获取系统基本信息
# check-system-info.ps1 — 获取系统基本信息
Write-Host "=== 系统基本信息 ==="
Write-Host "计算机名: $env:COMPUTERNAME"
Write-Host "操作系统: $([System.Runtime.InteropServices.RuntimeInformation]::OSDescription)"
Write-Host "处理器架构: $([System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture)"
Write-Host "逻辑处理器数: $([Environment]::ProcessorCount)"
$os = Get-CimInstance -ClassName Win32_OperatingSystem -ErrorAction SilentlyContinue
if ($os) {
$totalMemGB = [math]::Round($os.TotalVisibleMemorySize / 1MB, 2)
$freeMemGB = [math]::Round($os.FreePhysicalMemory / 1MB, 2)
$usedMemGB = [math]::Round($totalMemGB - $freeMemGB, 2)
$memUsagePercent = [math]::Round(($usedMemGB / $totalMemGB) * 100, 1)
Write-Host ""
Write-Host "=== 内存信息 ==="
Write-Host "总内存: ${totalMemGB} GB"
Write-Host "已使用: ${usedMemGB} GB ($memUsagePercent%)"
Write-Host "可用: ${freeMemGB} GB"
}
Write-Host ""
Write-Host "=== 系统运行时间 ==="
$uptime = (Get-Date) - (Get-CimInstance -ClassName Win32_OperatingSystem).LastBootUpTime
Write-Host "已运行: $($uptime.Days) 天 $($uptime.Hours) 小时 $($uptime.Minutes) 分钟"
脚本2:检查系统磁盘使用信息
# check-disk-usage.ps1 — 检查磁盘使用情况
Write-Host "=== 磁盘使用情况 ==="
Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DriveType=3" | ForEach-Object {
$totalGB = [math]::Round($_.Size / 1GB, 2)
$freeGB = [math]::Round($_.FreeSpace / 1GB, 2)
$usedGB = [math]::Round($totalGB - $freeGB, 2)
$usagePercent = if ($totalGB -gt 0) { [math]::Round(($usedGB / $totalGB) * 100, 1) } else { 0 }
$status = if ($usagePercent -gt 90) { "🔴 严重" }
elseif ($usagePercent -gt 70) { "🟡 警告" }
else { "🟢 正常" }
Write-Host ""
Write-Host "驱动器 $($_.DeviceID)"
Write-Host " 总容量: ${totalGB} GB"
Write-Host " 已使用: ${usedGB} GB ($usagePercent%)"
Write-Host " 可用: ${freeGB} GB"
Write-Host " 状态: $status"
}
脚本3:查看资源占用Top10信息
# check-top-processes.ps1 — 查看资源占用 Top 进程
param(
[int]$Top = 10
)
Write-Host "=== CPU 占用 Top $Top 进程 ==="
Get-Process | Sort-Object CPU -Descending | Select-Object -First $Top |
Format-Table -Property @{N='进程名';E={$_.ProcessName}},
@{N='PID';E={$_.Id}},
@{N='CPU(s)';E={[math]::Round($_.CPU, 2)}},
@{N='内存(MB)';E={[math]::Round($_.WorkingSet64/1MB, 1)}} -AutoSize |
Out-String | Write-Host
Write-Host ""
Write-Host "=== 内存占用 Top $Top 进程 ==="
Get-Process | Sort-Object WorkingSet64 -Descending | Select-Object -First $Top |
Format-Table -Property @{N='进程名';E={$_.ProcessName}},
@{N='PID';E={$_.Id}},
@{N='内存(MB)';E={[math]::Round($_.WorkingSet64/1MB, 1)}},
@{N='CPU(s)';E={[math]::Round($_.CPU, 2)}} -AutoSize |
Out-String | Write-Host
自定义ShellTools
最近MAF发布了1.0.0-rc2,支持了Agent Skill,但其尚未开放原生的脚本执行能力,虽然在GitHub的demo中我已经看到了脚本执行的demo。但貌似已经revert了,因此我们再静静等候一阵。
这里,我们通过自定义一个ShellTools来实现PowerShell的脚本执行能力:
public class ShellTools
{
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// run_shell --- 一个 Shell 工具做一切(含安全护栏)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 🛡️ 安全护栏 1:危险命令黑名单
private static string[] dangerousPatterns = [
"rm -rf /", "rm -rf /*", // 删除根目录
"sudo ", // 提权
"shutdown", "reboot", // 系统操作
"> /dev/", // 设备写入
":(){ :|:& };:", // Fork bomb
"mkfs.", // 格式化
"dd if=", // 磁盘覆写
"format ", // Windows 格式化
"del /f /s /q", // Windows 递归删除
];
[Description("执行 Shell 命令。通过操作系统原生 Shell 执行命令(Windows 用 cmd,Linux/Mac 用 bash)。包含安全护栏:危险命令阻止、输出截断(50KB)、超时控制(60秒)。")]
public static string RunShell(
[Description("要执行的 Shell 命令。例如:'pwsh -File /path/to/script.ps1' 或 'dir'")] string command,
[Description("命令执行的工作目录(可选)。如果不指定,使用当前目录。")] string? workingDirectory = null)
{
try
{
// 🛡️ 安全护栏 1:危险命令检查
if (dangerousPatterns.Any(d => command.Contains(d, StringComparison.OrdinalIgnoreCase)))
{
return "❌ 安全拦截:检测到危险命令,已阻止执行。";
}
// 🔑 跨平台 Shell 适配:
// Windows → cmd /c "command" (原生命令提示符)
// Linux/Mac → bash -c "command" (原生 Bash)
//
// 为什么不直接用 pwsh?
// SKILL.md 中的命令已经是 "pwsh -File ..."
// 如果 RunShell 也用 pwsh -Command 包裹 → pwsh 套 pwsh(冗余嵌套)
// 用原生 shell 分发 → pwsh -File 直接执行,零嵌套
var isWindows = OperatingSystem.IsWindows();
var processInfo = new ProcessStartInfo
{
FileName = isWindows ? "cmd" : "bash",
Arguments = isWindows ? $"/c {command}" : $"-c \"{command.Replace("\"", "\\\"")}\"",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
// 设置工作目录
if (!string.IsNullOrWhiteSpace(workingDirectory) && Directory.Exists(workingDirectory))
{
processInfo.WorkingDirectory = workingDirectory;
}
using var process = Process.Start(processInfo);
if (process == null)
{
return "❌ 无法启动 Shell 进程";
}
var stdout = process.StandardOutput.ReadToEnd();
var stderr = process.StandardError.ReadToEnd();
// 🛡️ 安全护栏 3:超时控制(60秒)
if (!process.WaitForExit(60_000))
{
process.Kill(entireProcessTree: true);
return "❌ 命令执行超时(60秒),已强制终止。";
}
var result = new StringBuilder();
if (!string.IsNullOrWhiteSpace(stdout))
{
result.AppendLine(stdout.Trim());
}
if (!string.IsNullOrWhiteSpace(stderr))
{
result.AppendLine($"⚠️ stderr: {stderr.Trim()}");
}
if (process.ExitCode != 0)
{
result.AppendLine($"⚠️ 退出码: {process.ExitCode}");
}
var output = result.Length > 0 ? result.ToString() : "(命令执行成功,无输出)";
// 🛡️ 安全护栏 2:输出截断(50KB)
const int maxOutputLength = 50_000;
if (output.Length > maxOutputLength)
{
output = output[..maxOutputLength] + "\n... (输出已截断,超过 50KB 上限)";
}
return output;
}
catch (Exception ex)
{
return $"❌ 执行失败: {ex.Message}";
}
}
}
这个ShellTools还考虑了必要的安全性,对于一些危险命令,会主动进行阻止,还对超时60s的操作进行了命令挂起。
智能体调用Agent Skill
这里,让我们一步一步来实现在MAF中让智能体调用集成脚本执行的Skill。
(1)创建SkillsProvider:从文件中发现和加载Skills
var skillsProvider = new FileAgentSkillsProvider(
skillPath: Path.Combine(Directory.GetCurrentDirectory(), "skills"),
options: new FileAgentSkillsProviderOptions
{
// 🔑 自定义提示词:引导模型加载技能后使用 run_shell 执行脚本
SkillsInstructionPrompt = """
你可以使用以下技能获取领域知识和操作指引。
每个技能提供专业指令、参考文档和可执行脚本。
<available_skills>
{0}
</available_skills>
工作流程:
1. 当用户任务匹配技能描述时,使用 `load_skill` 加载该技能的完整指令
2. 技能指令中会标明可用脚本及其执行命令
3. 使用 `run_shell` 工具执行技能中标注的命令
4. 需要时使用 `read_skill_resource` 读取参考资料
重要原则:先加载知识,再执行操作。
"""
}
);
Console.WriteLine("✅ FileAgentSkillsProvider 创建成功(知识层)");
Console.WriteLine("📂 自动注册工具: load_skill, read_skill_resource");
(2)创建Agent:注入SkillsProvider让其有Skill调用能力
AIAgent agent = chatClient.AsAIAgent(new ChatClientAgentOptions
{
Name = "SkillsBashAgent",
ChatOptions = new()
{
Instructions = "你是一个专业的系统运维助手。请用中文回答所有问题。",
// 🔑 能力层:仅注册一个 run_shell 工具
Tools = [AIFunctionFactory.Create(ShellTools.RunShell)],
},
// 🔑 知识层:通过 AIContextProviders 注入 Skills
AIContextProviders = [skillsProvider],
});
Console.WriteLine("✅ 技能 Agent 创建成功");
下面,我们就来测试一下这个Agent:
测试用例1
用户:帮我检查一下当前系统的整体健康状态,包括 CPU、内存和磁盘使用情况。
Agent 会:
1. 识别属于 system-ops 领域 → load_skill("system-ops")
2. 从 SKILL.md 获取脚本执行命令
3. 依次调用 run_shell 执行诊断脚本
4. 根据告警阈值分析结果
测试代码如下:
var session = await agent.CreateSessionAsync();
Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
Console.WriteLine(" 🔍 测试 1:系统健康检查");
Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
Console.WriteLine();
var question1 = "帮我检查一下当前系统的整体健康状态,包括 CPU、内存和磁盘使用情况。";
Console.WriteLine($"👤 用户: {question1}");
Console.WriteLine();
var response1 = await agent.RunAsync(question1, session);
Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
Console.WriteLine($"🤖 Agent: {response1.Text}");
Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
Console.WriteLine();
Console.WriteLine();
测试结果如下图所示:

测试用例2
危险脚本操作命令,验证Agent不会执行这些命令。
var dangerousTestCases = new[]
{
("rm -rf /", "删除根目录"),
("sudo apt-get install malware", "提权操作"),
("shutdown -s -t 0", "关机命令"),
};
foreach (var (cmd, desc) in dangerousTestCases)
{
var result = ShellTools.RunShell(cmd);
Console.WriteLine($"❌ 测试: {desc}");
Console.WriteLine($" 命令: {cmd}");
Console.WriteLine($" 结果: {result}");
Console.WriteLine();
}
// 测试正常命令
var normalResult = ShellTools.RunShell("Get-Date");
Console.WriteLine($"✅ 测试: 正常命令");
Console.WriteLine($" 命令: Get-Date");
Console.WriteLine($" 结果: {normalResult.Trim()}");
Console.WriteLine();
Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
Console.WriteLine("🛡️ 安全护栏验证完成:危险命令被正确拦截,正常命令正常执行。");
测试结果如下图所示:

可以看到,安全防护栏正常工作,防止危险命令被Agent执行。
3 小结
本文介绍了Agent Skill的脚本执行,脚本 和 工具的对比,最后通过一个MAF中Agent Skill 结合 PowerShell脚本执行 的系统运维案例介绍如何集成脚本执行,相信会对你有所帮助。
示例源码
GitHub: https://github.com/EdisonTalk/MAFD
参考资料
圣杰,《.NET + AI 智能体开发进阶》(推荐指数:★★★★★)
Microsoft Learn,《Agent Framework Tutorials》

作者:爱迪生
出处:https://edisontalk.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。